Recursion with Python
What is Recursion?
If you don’t know recursion, read this sentence again.
- How to Learn Recursion
- See Step 1
Some real world examples of recursion:
- An onion. Peel one layer of an onion, what do you have? An onion.
- Fractals, and those found in nature.
Recursive programming involves a function calling itself. Note that Python has a
maximum recursion stack of 1,000
, but if you ever need to increase it for
learning purposes, you can do so like this:
import sys
sys.setrecursionlimit(10000)
Also, note that anything that can be done recursively can also be done iteratively, and vice versa. Some algorithms should naturally be solved using recursion, like Towers of Hanoi, while others are better suited for iteration.
You should always try to write your code iteratively in Python since recursion isn’t optimized and is often slower than iteration. The examples that I’m going to show you are strictly for educational purposes.
Fibonacci
A lot of times, the fibonacci sequence is shown to beginners as an example to recursion.
def fibonacci(n):
if n == 0 or n == 1:
return n
return fibonacci(n-1) + fibonacci(n-2)
In mathematics, recursion is the natural way to solve the fibonacci problem. The
fibonacci mapping of an index, n
is always the sum of the two previous
fibonacci numbers.
I believe this is a bad example for a couple of reasons:
- It’s not practical. Try
fibonacci(20)
. It’ll blow up because it has to recompute the same sequences multiple times, coming out to a slowO(2^n)
exponential runtime. - There’s already a constant time formula to solve the fibonacci problem. Why would we use recursion or even an iterative approach?
Here are some better examples used to demonstrate recursion.
Length of a List
Let’s say you want to find the length of a list, some_list
. In recursion,
there is often a base case, and a recursive case.
- Our base case here is that if
some_list
is empty, then the count of that list is0
. - Otherwise, we add
1
and make a recursive call to the function, passing the parameter as all but the first element ofsome_list
.
def findLengthOfList(some_list):
return 0 if some_list == [] else 1 + findLengthOfList(some_list[1:])
print(findLengthOfList([1, 2, 3, 4, 5])) # 5
I mean obviously, in a real program you should use len(some_list)
, but
remember, this is for educational purposes!
Max of a List
- Our base case here is to return
max_given
if the list is empty. - The recursive step is to pass the body (all but the first) of the list to
our function again, along with the
max_so_far
which we computed.
def findMaxOfList(some_list, max_given):
if some_list == []:
return max_given
max_so_far = max_given if max_given > some_list[0] else some_list[0]
return findMaxOfList(some_list[1:], max_so_far)
print(findMaxOfList([1, 3, 6, 2, 3, -10], -float("Inf"))) # 6
Likewise, you can always use max(some_list)
instead of rolling your own
function.
Reversing a List
Once again, we can break the algorithm into a base case and a recursive case.
- If we receive an empty list, or a list of a single element, then it is already “reversed”. Simply return that list.
- If we receive a list of 2 or more elements, we can return a new list,
consisting of the last element of
some_list
followed by a recursive call with the parameter as all but the last element ofsome_list
.
def reverse(some_list):
if len(some_list) < 2:
return some_list
return [some_list[-1]] + reverse(some_list[:-1])
print(reverse(list(range(10)))) # [9, 8, 7, 6, 5, 4, 3, 2, 1, 0]
Don’t roll your own reverse
function in production! Use the built in
reversed()
function instead.
Sorting a List using Quick sort
This algorithm is super elegant when solved by recursion, even though it is slower than the iterative version of quick sort.
It works like this:
- Base case: if we receive an empty list, then it is already sorted.
- Take the first element of a list,
first
. - Partition the rest of the list by those that are greater than
first
, and those that are less thanfirst
, which are computed recursively. - Finally, we return a newly created list which
less_than_first
comes first, followed byfirst
, and lastly, we appendgreater_than_first
.
def quicksort(some_list):
if some_list == []:
return []
first = some_list[0]
less_than_first = quicksort([elem for elem in some_list[1:] if elem < first])
greater_than_first = quicksort([elem for elem in some_list[1:] if elem >= first])
return less_than_first + [first] + greater_than_first
print(quicksort([4, 3, 1, 2, 5])) # [1, 2, 3, 4, 5]
Don’t forget about sorted()
or .sort()
if you want to apply this to real
code!
Thanks for reading! Please ask questions or share your thoughts!
For programming languages that optimize recursion, check out Functional Programming languages like Haskell, or a Lisp.
If you’d like to learn more about recursion and functional programming, check out this book: The Little Schemer - 4th Edition