Understanding recursive functions calls - python

Consider the following code:
def print_mah(n):
if n <= 0:
return
else:
print('mah')
print_mah(n-1)
print_mah(3)
Here Python checks if n is less than or equal to 0, it finds that this is False so it prints 'mah' and calls the same function with n-1 until n is equal to 0, so 'mah' is printed 3 times.
But consider this manipulated code:
def print_mah(n):
if n <= 0:
return
else:
print_mah(n-1)
print('mah')
print_mah(3)
Python checks if n is less than or equal to 0, it finds that this is False so calls the same function again with n-1, and 'mah' is printed 3 times, too.
My question is that why 'mah' isn't printed just one time, in other words why print_mah isn't called with n=2, then Python finds that the condition is False, so it calls it with n=1, and finds the condition is False, so it calls it with n==0, and finds that the condition is True, so the function returns, and after that 'mah' is printed, only, once.

Python finds that the condition is False, so it calls it with n=1, and finds the condition is False, so it calls it with n==0, and finds that the condition is True, so the function returns
That actually is the exact execution path of the second version. Maybe you don't see, though, that when the function returns it picks back up where it left off after the recursive call returns, just like any other method call.
Therefore, when n==1 and it recurses with n==0, then it is returned and mah is printed the first time, then that returns and mah is printed when n==2, then that returns and mah is printed a third and final time.

return doesn't break out of your entire call stack, just the current function call. In this case:
def print_mah(n):
if n <= 0:
return
else:
print_mah(n-1)
print('mah')
print_mah(3)
print_mah is called three times before returning 0. You can think about it as if they were just nested logic, like this:
def print_mah(n):
if n <= 0:
return
else:
print_mah(n-1) # Replace this line with new call
print('mah')
Where we just call the function again inside the else statement at the comment.
def print_mah(n):
if n <= 0:
#return
else:
n = n-1
if n <= 0:
#return
else:
print_mah(n-1)
print('mah')
print('mah')
You can see that print('mah') shows up in sequence at the bottom, which, as it's written, will print sequentially as the functions backtrack out of the call stack.

Here is a "trace" of what the second program is doing:
print_mah(3) ->
print_mah(2) ->
print_mah(1) ->
print_mah(0) ->
# Does nothing, essentially.
<-
# print_mah(1) continues running.
print('mah') # The last thing that print_mah(1) does.
<-
# print_mah(2) continues running.
print('mah')
<-
# print_mah(3) continues running.
print('mah')
<-
What we see here is that print('mah') occurs three times (therefore, 'mah' gets printed three times).

My question is that why 'mah' isn't printed just one time, in other words why print_mah isn't called with n=2, then Python finds that the condition is False, so it calls it with n=1, and finds the condition is False, so it calls it with n==0, and finds that the condition is True, so the function returns, and after that 'mah' is printed, only, once.
The function only returns out of the most-inner function. The two function levels where the else condition is used will then continue execution after the call to print_mah and actually print. Here's a line-by-line walk through for print_mah(2) for brevity.
print_mah(2)
# Enter print_mah - Level 1
if n <= 0: # n = 2
# ...
# False, continue to else
else:
print_mah(n-1) # n = 2
# Enter print_mah - Level 2
if n <= 0: # n = 1
# ...
# False, continue to else
else:
print_mah(n-1) # n = 1
# Enter print_mah - Level 3
if n <= 0: # n = 0
return
# Return None to print_mah - Level 2
print('mah')
# Function is complete, return None to print_mah - Level 1
print('mah')
# Function is complete, return None to the execution scope

To understand the different maybe this can help.
Algorithm 1
def print_n(n):
if n <= 0:
return
else:
print_n(n-1)
print(n)
Algorithm 2
def print_n(n):
if n <= 0:
return
else:
print(n)
print_n(n-1)
These algorithms should provide different results, maybe this is a good starting point for further research.
Some help
If you call a function (f2) inside an other function (f1) the current function (f1) will wait until the called function (f2) finished.
Some keywords for research
function call
stack

Both functions will print mah three times, because you are not returning anything after the recursive call. When you call the function inside itself you should handle the stopping condition (which you already did in your first if-condition), then after that it will NOT exit the program because a recursive call builds a stack of operations. After the last operation gets stopped (returns something), then it will start to compile the rest of the function below the recursive call on its way back, until the end of the function is reached.

Related

Why is this Python generator functioning like this?

I was looking into this article by RealPython about Python generators. It uses a palindrome generator as a exemplary tool to explain the .send, .throw and .close methods. Building a program following their instructions gives the following code:
def infinite_palindromes():
num = 0
while True:
if is_palindrome(num):
i = (yield num)
if i is not None:
num = i
num += 1
def is_palindrome(num):
# Skip single-digit inputs
if num // 10 == 0:
return False
temp = num
reversed_num = 0
while temp != 0:
reversed_num = (reversed_num * 10) + (temp % 10)
temp = temp // 10
if num == reversed_num:
return True
else:
return False
pal_gen = infinite_palindromes()
for e in pal_gen:
digits = len(str(e))
pal_gen.send(10**(digits))
I watched the flow of the program in debugging and what happens is basically, when the function finds 11, the first palindrome, it enters into the for e loop block, the digits variable is defined, and 10 to the power of that variable value is sent back to the generator. 100 is sent back, and since it is different from None, num is equated to i and becomes 100, then it's summed with 1 and becomes 101, the next palindrome. Here is what I don't get: when that number is throw and correctly identified as a palindrome, the code goes back to the for e in pal_gen: line, but it doesn't enter the block. Only the next palindrome, 111 is sent back down and enters the block, where the digits variable is defined again, and so on and so forth, and this patterns repeats, with only every other palindrome entering the block. Why is that?
Can someone, please, explain to me why that happens?
To understand the behaviour, we need to understand how for loops are implemented in Python. This code:
for e in pal_gen:
digits = len(str(e))
pal_gen.send(10**(digits))
is basically equivalent to (except that no name _iter is actually generated):
_iter = iter(pal_gen)
while True:
try:
e = next(_iter)
digits = len(str(e))
pal_gen.send(10**(digits))
except StopIteration:
break
However, because pal_gen is a generator object (created by calling the generator function infinite_palindromes), it is already an iterator; calling iter on it returns the same object unchanged. Thus, we effectively have:
while True:
try:
e = next(pal_gen)
digits = len(str(e))
pal_gen.send(10**(digits))
except StopIteration:
break
The other thing to note here is that calling .send on a generator advances the generator, and so does passing it to next (i.e., using it like any other iterator). It's just that the code was written to ignore the value retrieved using .send.
In fact, next on a generator is equivalent to calling .send and passing None (by convention; Python defines the behaviour this way so that the assignment in the generator code, i = (yield num), has something to assign).
So, each time through the loop, two values are sent to the generator:
while True:
try:
e = pal_gen.send(None)
digits = len(str(e))
pal_gen.send(10**(digits))
except StopIteration:
break
The control flow is like so:
None is sent to the generator. Because it hasn't yielded yet, this value is discarded (Python requires it to be None, since it can't be used by the generator).
The generator iterates its own loop until 11 is found and yielded. The execution of the generator pauses here: i is not assigned yet.
The main loop receives 11 as a value for e, computes 100 as the next value to work with, and sends that to the generator.
The generator receives 100 and assigns it to i, then proceeds with its logic. The next step is if i is not None:, and that is indeed the case - thus, 100 is assigned to num. num is incremented at the end of the loop.
The loop inside the generator runs into the next iteration, and immediately finds that 101 is a palindrome. Thus, the .send call returns a value of 101, but this isn't assigned anywhere.
Similarly, in the second iteration of the main loop, 111 will be found and yielded (as the next palindrome after 101) and assigned to e in the first call; then 1000 is sent explicitly, causing 1001 to be yielded and ignored. In the third iteration, 1111 is assigned to e, then 10001 is ignored. In the fourth iteration, 10101 (not 11111! This is the next palindrome after 10001) is assigned, and 100001 is ignored. Etc.
If the code is tested at the REPL, the values returned from the explicit .send will be displayed. This is just a quirk of the REPL.

Recursion program flow

I have some textbook code that calls itself recursively. I don't understand the program flow. Here is the code:
def Recur_Factorial_Data(DataArray):
numbers = list(DataArray)
num_counter = 0
list_of_results = []
for num_float in numbers:
n = int(num_float)
1. result = Recur_Factorial(n)
list_of_results.append(result)
def Recur_Factorial(num):
if num == 1:
2. return num
else:
result = num*Recur_Factorial(num-1)
3. return result
if num < 0:
return -1
elif num == 0:
return 0
else:
return 0
In Recur_Factorial_Data, I loop through the data elements and call Recur_Factorial, which returns its value back to the calling function (Recur_Factorial_Data). I would expect that the lines marked 2 ("return num") and 3 ("return result") would always return a value back to the calling function, but that's not the case. For example, where the initial value (from the array DataArray) is 11, the function repeatedly calls itself until num is 1; at that point, the program falls to the line marked 2, but it does not loop back to the line marked 1. Instead, it falls through to the next line. The same happened on the line marked 3 -- I would expect it to return the result back to the calling function, but it does that in some cases and not in others.
I hope this is enough description to understand my question -- I just don't know why each return does not loop back to return the result to the calling function.
EDIT: The question at Understanding how recursive functions work is very helpful, and I recommend it to anyone interested in recursion. My question here is a bit different -- I asked it in the context of the program flow of the Python code where the recursive function is called.
If it call itself recursively 10 times, it will be at the 10th level of recursion and should go back 10 times at the point where there was a recursive call with an intermediate result and only then exit from the recursive function to the place where it was called. Learn more about recursion
Also try to rearrange instructions in Recur_Factorial function in this way:
def Recur_Factorial(num):
if num < 0:
return -1
elif num == 0:
return 0
elif num == 1:
return num
else:
return num * Recur_Factorial(num-1)

A statement that has the same indentation with its previous "if" statement will always be executed?

I'm attending an online course on Python.
if <TestExpression>:
<block>
i=21
Regarding the above code ("code1"), the lecturer said that i=21 will execute, whether or not the test expression is true, because i=21 has the same indentation with the if statement, meaning i=21 is not part of the if statement.
However, the lecturer also said the following code ("code2"):
def bigger(a,b):
if a>b:
return a
return b
is the same as the following code ("code3"):
def bigger(a,b):
if a>b:
return a
else:
return b
Doesn't this contradict what he previously said? i=21 in code1 and return b in code2 both have the same indentation with their previous if statements, but why i=21 will always execute no matter the test expression is true or false, while return b will execute only when the test expression (a>b) is false?
How do you decide whether the statement immediately following the if construct which has the same indentation with the if line should execute? In another word, how do you decide whether that statement is part of the if construct?
This is a great question you have asked, and it's an issue a lot of new people have with functions specifically.
The issue lies with the return keyword. In a function, when it returns a value, the function immediately breaks. Thus, no subsequent code in the function is run. Because of this, we can short-hand our conditional statements to:
if a > b:
return a
# Function breaks here - no more code is executed.
# This bit is never reached if a is returned
return b
Observe:
def test():
if 10 > 5:
print("Hello!")
return True
print("Hi!")
return False
When we call test(), only Hello! is printed.
One reason this is helpful is to stop erroneous whitespace when having multiple conditional statements in Python. Of course, in most other programming languages, since whitespace isn't a feature, it's not as necessary.
Imagine you were writing a function to check if a number was prime, where lots of conditional statements were required. (Of course, for the sake of this example, this isn't efficient):
def is_prime(n):
if n == 1:
return False
else:
if n == 2:
return True
else:
for i in range(2, n):
if n % i == 0:
return False # A factor exists
else:
continue
return True
Knowing that a return breaks the function, we can re-write this as:
def is_prime(n):
if n == 1:
return False
if n == 2:
return True:
for i in range(2, n):
if n % i == 0:
return False # A factor exists
return True
See how the indentation is so much cleaner?
That's because you can't return twice from a function. When if is satisfied, it will return and won't execute any more statement. So, the next return statement is unreachable.
The next return will only execute when if is failed. else is also executed when if is failed.
That's why your teacher says, both of them are same.

How does return terminate all instances of a recursion in Python

How does return terminates all the instances in a recursion? In the following it seems to me that the only time the return statement is called is in the base case and yet it seems to close all of the other instances where n is > 1.
def reco(n):
print('create instance nbr ', n)
if n == 1:
print('base case reached, instances will be popped LIFO')
return
else:
n -= 1
reco(n)
print('pop instance nbr ', n)
n = 5
reco(n)
You're right, the return statement only executes once, but the reco() call is the second-to-last statement in all the other instances. The execution just falls out the end of the other instances.
It might help if you imagined that every function body has an implicit return None statement at the end.

Flow of recursion in python roman to arabic

So I just solved the last quiz on recursion on codeacademy. But while I was trying to understand the flow of execution of this code on pythontutor.org, I just couldnt follow the flow of this code.
def rval(letr):
if(letr=='I'):
return 1
elif(letr=='V'):
return 5
elif(letr=='X'):
return 10
elif(letr=='L'):
return 50
elif(letr=='C'):
return 100
elif(letr=='D'):
return 500
elif(letr=='M'):
return 1000
else:
return "error"
def arabic(n):
if len(n)==0:
return 0
elif len(n)==1:
return rval(n)
elif len(n)==2:
if rval(n[0])>rval(n[1]):
return rval(n[0])+rval(n[1])
else:
return rval(n[1])-rval(n[0])
else:
return arabic(n[len(n)-2:])+arabic(n[:len(n)-2])
arabic('DXCVI')
======PROBLEM FACED======
My question is this --
Suppose I run arabic('DXCVI'), then how does this line arabic(n[len(n)-2:])+arabic(n[:len(n)-2]) get executed
Do both arabic(n[len(n)-2:]) & arabic(n[:len(n)-2]) start getting executed simultaneously or does the second term wait until the first is done/returns a value ?
The left part arabic(n[len(n)-2:]) is always called first. An easy way to find this out is to put a print statement in your arabic(n) function.
Try something like this:
def arabic(n):
print n
if len(n)==0:
return 0
elif len(n)==1:
return rval(n)
elif len(n)==2:
# more code
Which would output this:
DXCVI
VI
DXC
XC
D
Proving that the left side, arabic(n[len(n)-2:]), gets called before +arabic(n[:len(n)-2]).
First arabic(n[len(n)-2:]) (the left part) is executed, then arabic(n[:len(n)-2]) (the right part) is executed.
Remember that the first part may also have recursive calls, so the second one must wait until it returns a value to get executed.

Categories