Basics of Recursion in Python

Basics of recursion in Python

Whenever you face a problem like this, try to express the result of the function with the same function.

In your case, you can get the result by adding the first number with the result of calling the same function with rest of the elements in the list.

For example,

listSum([1, 3, 4, 5, 6]) = 1 + listSum([3, 4, 5, 6])
= 1 + (3 + listSum([4, 5, 6]))
= 1 + (3 + (4 + listSum([5, 6])))
= 1 + (3 + (4 + (5 + listSum([6]))))
= 1 + (3 + (4 + (5 + (6 + listSum([])))))

Now, what should be the result of listSum([])? It should be 0. That is called base condition of your recursion. When the base condition is met, the recursion will come to an end. Now, lets try to implement it.

The main thing here is, splitting the list. You can use slicing to do that.

Simple version

>>> def listSum(ls):
... # Base condition
... if not ls:
... return 0
...
... # First element + result of calling `listsum` with rest of the elements
... return ls[0] + listSum(ls[1:])
>>>
>>> listSum([1, 3, 4, 5, 6])
19

Tail Call Recursion

Once you understand how the above recursion works, you can try to make it a little bit better. Now, to find the actual result, we are depending on the value of the previous function also. The return statement cannot immediately return the value till the recursive call returns a result. We can avoid this by, passing the current to the function parameter, like this

>>> def listSum(ls, result):
... if not ls:
... return result
... return listSum(ls[1:], result + ls[0])
...
>>> listSum([1, 3, 4, 5, 6], 0)
19

Here, we pass what the initial value of the sum to be in the parameters, which is zero in listSum([1, 3, 4, 5, 6], 0). Then, when the base condition is met, we are actually accumulating the sum in the result parameter, so we return it. Now, the last return statement has listSum(ls[1:], result + ls[0]), where we add the first element to the current result and pass it again to the recursive call.

This might be a good time to understand Tail Call. It would not be relevant to Python, as it doesn't do Tail call optimization.


Passing around index version

Now, you might think that we are creating so many intermediate lists. Can I avoid that?

Of course, you can. You just need the index of the item to be processed next. But now, the base condition will be different. Since we are going to be passing index, how will we determine how the entire list has been processed? Well, if the index equals to the length of the list, then we have processed all the elements in it.

>>> def listSum(ls, index, result):
... # Base condition
... if index == len(ls):
... return result
...
... # Call with next index and add the current element to result
... return listSum(ls, index + 1, result + ls[index])
...
>>> listSum([1, 3, 4, 5, 6], 0, 0)
19

Inner function version

If you look at the function definition now, you are passing three parameters to it. Lets say you are going to release this function as an API. Will it be convenient for the users to pass three values, when they actually find the sum of a list?

Nope. What can we do about it? We can create another function, which is local to the actual listSum function and we can pass all the implementation related parameters to it, like this

>>> def listSum(ls):
...
... def recursion(index, result):
... if index == len(ls):
... return result
... return recursion(index + 1, result + ls[index])
...
... return recursion(0, 0)
...
>>> listSum([1, 3, 4, 5, 6])
19

Now, when the listSum is called, it just returns the return value of recursion inner function, which accepts the index and the result parameters. Now we are only passing those values, not the users of listSum. They just have to pass the list to be processed.

In this case, if you observe the parameters, we are not passing ls to recursion but we are using it inside it. ls is accessible inside recursion because of the closure property.


Default parameters version

Now, if you want to keep it simple, without creating an inner function, you can make use of the default parameters, like this

>>> def listSum(ls, index=0, result=0):
... # Base condition
... if index == len(ls):
... return result
...
... # Call with next index and add the current element to result
... return listSum(ls, index + 1, result + ls[index])
...
>>> listSum([1, 3, 4, 5, 6])
19

Now, if the caller doesn't explicitly pass any value, then 0 will be assigned to both index and result.


Recursive Power problem

Now, lets apply the ideas to a different problem. For example, lets try to implement the power(base, exponent) function. It would return the value of base raised to the power exponent.

power(2, 5) = 32
power(5, 2) = 25
power(3, 4) = 81

Now, how can we do this recursively? Let us try to understand how those results are achieved.

power(2, 5) = 2 * 2 * 2 * 2 * 2 = 32
power(5, 2) = 5 * 5 = 25
power(3, 4) = 3 * 3 * 3 * 3 = 81

Hmmm, so we get the idea. The base multiplied to itself, exponent times gives the result. Okay, how do we approach it. Lets try to define the solution with the same function.

power(2, 5) = 2 * power(2, 4)
= 2 * (2 * power(2, 3))
= 2 * (2 * (2 * power(2, 2)))
= 2 * (2 * (2 * (2 * power(2, 1))))

What should be the result if anything raised to power 1? Result will be the same number, right? We got our base condition for our recursion :-)

            = 2 * (2 * (2 * (2 * 2)))
= 2 * (2 * (2 * 4))
= 2 * (2 * 8)
= 2 * 16
= 32

Alright, lets implement it.

>>> def power(base, exponent):
... # Base condition, if `exponent` is lesser than or equal to 1, return `base`
... if exponent <= 1:
... return base
...
... return base * power(base, exponent - 1)
...
>>> power(2, 5)
32
>>> power(5, 2)
25
>>> power(3, 4)
81

Okay, how will be define the Tail call optimized version of it? Lets pass the current result as the parameter to the function itself and return the result when the base condition it met. Let's keep it simple and use the default parameter approach directly.

>>> def power(base, exponent, result=1):
... # Since we start with `1`, base condition would be exponent reaching 0
... if exponent <= 0:
... return result
...
... return power(base, exponent - 1, result * base)
...
>>> power(2, 5)
32
>>> power(5, 2)
25
>>> power(3, 4)
81

Now, we reduce the exponent value in every recursive call and multiple result with base and pass it to the recursive power call. We start with the value 1, because we are approaching the problem in reverse. The recursion will happen like this

power(2, 5, 1) = power(2, 4, 1 * 2)
= power(2, 4, 2)
= power(2, 3, 2 * 2)
= power(2, 3, 4)
= power(2, 2, 4 * 2)
= power(2, 2, 8)
= power(2, 1, 8 * 2)
= power(2, 1, 16)
= power(2, 0, 16 * 2)
= power(2, 0, 32)

Since exponent becomes zero, the base condition is met and the result will be returned, so we get 32 :-)

Recursion function in Python

In the expression fibonacci(number-1) + fibonacci(number-2) the first function call will have to complete before the second function call is invoked.

So, the whole recursion stack for the first call has to be complete before the second call is started.

Understand where total value is stored in recursive function - Python

The intermediate running totals are never "stored" in a variable - they are passed down the call stack each time one of the recursive calls returns:

  • The first recursive call to return is mysum([]) which returns the number 0.
  • After this, the recursive call mysum([5]) returns 5 + 0 = 5.
  • After this the recursive call mysum([4,5]) returns 4 + 5 = 9.
  • Then the recursive call mysum([3,4,5]) returns 3 + 9 = 12.
  • Then mysum([2,3,4,5]) returns 2 + 12 = 14.
  • Then finally, the original non-recursive call mysum([1,2,3,4,5]) returns 1 + 14 = 15.

I have an interactive demo which shows how some recursive functions are computed step-by-step using a call stack. It may help you to understand how recursive functions are executed.

Calculating Div and Mod using recursion

The following works as long as p>=0 and q>0.

def mod(p,q):
if p<q:
return p
return mod(p-q,q)

def div(p,q):
if p<q:
return 0
return 1+div(p-q,q)


Related Topics



Leave a reply



Submit