When to use while loops vs recursion - python

I am a beginner and I am beginning to learn about 'while' statement loops to perform iteration. However, earlier on I learned about 'if/else' statements and how one can perform recursion using if/else by returning a variable as well. Here's a simple countdown function using while statement:
def countdown(n):
while n > 0:
print(n)
n = n-1
print('Blast off!')
And for comparison, here is a simple countdown function using if/else and recursion.
def countdown(n):
if n > 0:
print(n)
return countdown(n-1)
elif n < 0:
return None
else:
print('Blast off!')
return
As you can see, the two functions do almost exactly the same thing, with the only difference being that the if/else statement accounts for a case where n < 0 and returns a None value, while the 'while' statement simply omits the loop and prints 'Blast off!' even if n < 0 anyway. (If there is a way to factor this in the while statement, I would love to learn, do suggest!)
My question is, seeing how the same thing can be done in if/else statements and while statements and vice versa, I would like to know a case where they are clearly differentiated and one is clearly preferable to the other. Is there a subtle conceptual difference between the two types of statements that I am missing, or are they interchangeable in usage?

They are fundamentally different. The way you have described them, both are the same thing as for loops. (I don't mean to be rude) While is used when you want something to happen as long as or until something else happens. Recursion is used for functions that are based on themselves. (common examples being factorial or the Fibonacci sequence) Often, they behave similarly in the manner you described on small scale problems. When scaled up however, both have their advantages and drawbacks.
TLDR: the functions are inherently different in how they iterate. While they each iterate, they do so based on different conditions and with different use cases.

You are actually right, the logic behind is the same and you can also apply while loop for e.g. Fibonacci sequence. Just the notation of recursion is usually shorter and more elegant.
Your code can be even simplified a bit and then you see that your solutions are kind of similar:
While loop:
def countdown(n):
while n > 0: # Step 1 -> check
print(n)
n = n-1 # Step 2 -> recursive call in while loop
print('Blast off!') # Step 3 -> end ensured by Step 1
Recursion:
def countdown(n):
if n > 0: # Step 1 -> check
print(n)
return countdown(n-1) # Step 2 -> recursive call of the countdown()
else:
print('Blast off!') # Step 3 -> end ensured by Step 1
Significant is that action n-1. You recursively act on a variable or object (Step 2) as long as your condition related to that variable/object is valid (Step 1).

Related

Are both of these proper use of recursion? Is one better than the other?

I am trying to better understand recursion.
As an example, here are two definitions for calculating n! :
def factorial(num, p=1):
if num == 0:
return p
else:
p *= num
return factorial(num-1,p)
and
def factorial(num):
if num == 1:
return num
return num * factorial(num - 1)
Is one better than the other? I understand that the second one builds a call stack, while the first is calculating p at each step and just passing it to another iteration of the function. Are there separate names for these types of functions? Is the first one not recursion?
Just looking for some guidance on understanding the differences here. Thanks!
There is little difference between the two. I would say "simpler is better" and the 2nd one involves fewer variables so I would go for that one.
Or something even simpler:
def factorial(N): return max(1,N) if N<3 else N*factorial(N-1)
In real life though, you shouldn't implement it recursively because iterative solutions will be faster.

recursion Think Python 2 exercise 5.5

This question regarding exercise 5.5 from Think Python 2 has been asked and answered already, but I still don't understand how this function works. Here's the function in question:
def draw(t, length, n):
if n == 0:
return
angle = 50
t.fd(length*n)
t.lt(angle)
draw(t, length, n-1)
t.rt(2*angle)
draw(t, length, n-1)
t.lt(angle)
t.bk(length*n)
I see a recursive loop here:
def draw(t, length, n):
if n == 0:
return
angle = 50
t.fd(length*n)
t.lt(angle)
draw(t, length, n-1)
After the function calls itself a sufficient number of times to cause n == 0, how does it move on to the next step, namely t.rt(2*angle)? Once n == 0, shouldn't the function return None and simply end? If not, where is returning to? The same thing happens again the next time it calls itself, and then the function continues to perform work long after the state n == 0 has been satisfied (twice!). I'm obviously missing a key concept here and would appreciate any light anyone can shed on this topic. Thanks in advance~
The return statement returns execution of the program back to the line where the function that returned was called.
If you're not familiar with what a call stack is, think of it as a stack of functions - whenever something gets called, it's placed on top of the stack and that's what's running. When that returns, it's removed from the stack and so the function that called it (one below in the stack) is now the top, so that's where execution picks up again.
That particular function will return when n == 0 (so that's your base case), so once its recursive call draw(t, length, n-1) - and, by consequence, its subsequent recursive calls, since it won't finish execution until they do - reach that point, it'll move over to the next line.
Take a simpler function foo(sum, n) instead:
def foo(sum, n):
if (n == 0):
return sum
sum += foo(sum, n-1)
sum /= 2
return foo(sum, n-1)
Whenever you call that, it won't move from sum += foo(sum, n-1) until that call and all of the recursive calls made return. This function is structurally comparable to the one you showed and it might help you visualize a little better what's going on.
I've posted a picture explaining this in the following question , for a fast navigation here's my picture :

Python: base case of a recursive function

Currently I'm experimenting a little bit with recursive functions in Python. I've read some things on the internet about them and I also built some simple functioning recursive functions myself. Although, I'm still not sure how to use the base case.
I know that a well-designed recursive function satisfies the following rules:
There is a base case.
The recursive steps work towards the base case.
The solutions of the subproblems provide a solution for the original problem.
Now I want to come down to the question that I have: Is it allowed to make up a base case from multiple statements?
In other words is the base case of the following self written script, valid?
def checkstring(n, string):
if len(string) == 1:
if string == n:
return 1
else:
return 0
if string[-1:] == n:
return 1 + checkstring(n, string[0:len(string) - 1])
else:
return checkstring(n, string[0:len(string) - 1])
print(checkstring('l', 'hello'))
Yes, of course it is: the only requirement on the base case is that it does not call the function recursively. Apart from that it can do anything it wants.
That is absolutely fine and valid function. Just remember that for any scenario that you can call a recursion function from, there should be a base case reachable by recursion flow.
For example, take a look at the following (stupid) recursive function:
def f(n):
if n == 0:
return True
return f(n - 2)
This function will never reach its base case (n == 0) if it was called for odd number, like 5. You want to avoid scenarios like that and think about all possible base cases the function can get to (in the example above, that would be 0 and 1). So you would do something like
def f(n):
if n == 0:
return True
if n == 1:
return False
if n < 0:
return f(-n)
return f(n - 2)
Now, that is correct function (with several ifs that checks if number is even).
Also note that your function will be quite slow. The reason for it is that Python string slices are slow and work for O(n) where n is length of sliced string. Thus, it is recommended to try non-recursive solution so that you can not re-slice string each time.
Also note that sometimes the function do not have strictly base case. For example, consider following brute-force function that prints all existing combinations of 4 digits:
def brute_force(a, current_digit):
if current_digit == 4:
# This means that we already chosen all 4 digits and
# we can just print the result
print a
else:
# Try to put each digit on the current_digit place and launch
# recursively
for i in range(10):
a[current_digit] = i
brute_force(a, current_digit + 1)
a = [0] * 4
brute_force(a, 0)
Here, because function does not return anything but just considers different options, we do not have a base case.
In simple terms Yes, as long as it does not require the need to call the function recursively to arrive at the base case. Everything else is allowed.

Nested recursive function in python

I tried to implement a nested recursive function in python it is giving an error "RuntimeError: maximum recursion depth exceeded" you can see the function in the following code. your help regarding the question is appreciated.
n=10
def test(n):
if n<=0:
return 1
else:
return test(test(n-1)+1)
print test(n)
One very important part of recursion is that with every recursive call you have to get closer to your anchor case, which in this case is:
if n<=0:
return 1
However, with your code you are not getting close to this case. The problem is this line:
return test(test(n-1)+1)
Since test returns 1 when it reaches the end case and you add 1 to this result, let's see what happens when we call test(2):
The anchor case isn't executed and you go straight into the else. The you return
test(test(1)+1)
since 2-1=1. Your inner test call is also going to go to the else case and call:
test(test(0)+1)
Here your inner test call returns 1 (it has already reached the end case) which means that essentially in this line you are calling
test(2) //test(1+1)
again. Here you can see that your recursion is never ending, hence your maximum recursion depth exceeded error.
Just to clarify how you could make this code (which is obviously just an example) work:
def test(n):
if n <= 0:
return 1
else:
return test(test(n-1)-1) //notice the -1 instead of +1
Follow up question:
Why does changing the recursive call to test(test(n-2)) also result in infinite recursion?
Well this is basically because of the same reason that I pointed out right at the beginning. You need to get closer to your anchor case. While you can reach the case of n<=0 inside the nested recursive call test(n-2) you can certainly not reach it inside the outer function call.
Note that your function returns 1 when it reaches it's end case, so even if test(n-2) doesn't cause any more recursions it returns 1 (not 0), which means you end up with
test(1)
which is again going to cause an infinite loop (since you can not get n to be <= 0 for this outer function call).
You have multiple options to make the recursion work for this case: By changing your return value or by changing your anchor case. So changing the if statement to either of these will prevent an infinite recursion:
if n <= 0:
return 0
or
if n <= 1:
return 1 //you could also return any other value <= 1

How do I double my step variable for each for-loop iteration in Python 3?

I'm new to Python, and I'm playing around with recursive functions just for practice.
I made the following algorithm which takes a number as x and halves it until it is equal to 1. n is the number of times x has been halved.
def binary(x, n = 0):
print(n,":",x)
x = x // 2
n += 1
if x > 0:
binary(x, n)
return x
return x
I'm trying to make a loop that will call binary() with multiple values for x. I want my step to be doubled each iteration. I have it working with a while loop like the one below.
i = 1
while i < 1000000000:
print("when x is", i, ":")
binary(i)
i += i
For some reason though, I can't seem to achieve the same thing with a For loop. Here's what I have now.
for i in range(1,1000):
print("when x is", i, ":")
binary(i)
i += i
In the code above, i += i does not seem to effect the i in my header. I know that range() takes a third parameter called step, but I've tried this:
for i in range(1,1000, i += i):
# statements
This gives me a name error, and says "i is not defined".
Most of my experience with programming is in JavaScript and C#. In both of those languages I wouldn't of had any trouble doing this.
How would I get this to work in a For loop using Python 3?
The third parameter of range is indeed step. But you should use it with a computed value like:
for i in range(1,1000,2):
#statements
The reason why your i += i didn't worked is because, under the hood, the for-loop is executing something similar to i = next(...) at the end of an iteration, overiding your last increment.
Edit
You can achieve the desired effect using a generator, but it kinda kills the "trying to avoid while-loops" thing:
def myrange(start, stop):
i = start
while i < stop:
yield i
i += i
for i in myrange(1, 1000):
print(i)
Anyway, while-loops are perfectly valid constructs and I’d personnally go with one in this case. Do not forget that the for-loop has a rather different semantic in python than in both languages you’re used to. So trying to use a for-loop because you are able to do so with javascript seems like a bad idea if all what you need is a while-loop.
range can step by a fixed amount, but not a variable amount. Use a while-loop to increment i by i:
i += i
You could replace the while-loop with an iterator, such as:
import itertools as IT
for i in (2**i for i in IT.count()):
if i >= 1000000000: break
print("when x is", i, ":")
binary(i)
but I don't think this has any advantage over a simple while-loop.
If all you're doing is doubling i, then why not just raise it to the power?
for p in range(int(1000000000**0.5)):
print(binary(2**p)

Categories

Resources