Some problems split into multiple smaller problems, rather than just 1 smaller problem...
To find the value to the following expression ${2^n}$ for non-negative integers ${n}$, we can use an old friend for
loop.
def exponentiate(base, exponent):
total = 1
for i in range(exponent):
total *= base
return total
print(exponentiate(2, 5))
32
But we can also use recursion to help find the value for the same expression. When solving a problem in programming, often times, multiple solutions to the same problem exist.
def exponentiate_recur(base, exponent):
if exponent == 0: #base case
return 1
return exponentiate_recur(base, exponent-1) * base
# try on your own
print(exponentiate_recur(2, 5) == 32) # this should print out True
True
The base case returns the answer to the smallest version of the problem.
For our example, the smallest version of the problem would be finding the answer to ${2^0}$.
if exponent == 0:
return 1
The recursion step solves smaller version of the problem and combines answers at each step of the smaller problem to return the final answer.
For our example, knowing the answer to ${2^4}$ would help find the answer to ${2^5}$, knowing the answer to ${2^3}$ would help find the answer to ${2^4}$, ...
small_answer = exponentiate_recur(base, exponent - 1)
return 2 * small_answer
exponentiate_recur(2, 5)
=> recursion step =>
2 * exponentiate_recur(2,4)
=> recursion step =>
2 * 2 * exponentiate_recur(2,3)
=> recursion step =>
2 * 2 * 2 * exponentiate_recur(2,2)
=> recursion step =>
2 * 2 * 2 * 2 * exponentiate_recur(2,1)
=> recursion step =>
2 * 2 * 2 * 2 * 2 * exponentiate_recur(2,0)
=> base case =>
2 * 2 * 2 * 2 * 2 * 1
=> recursion step (to combine answers) =>
32
def exponentiate_with_print(base, exponent):
if exponent == 0:
print("Current exponent is: "+ str(exponent)+ ", returning 1")
return 1
print("Current exponent is: "+ str(exponent)+ ", returning " + str(base) + " * " + "exponentiate_with_print("+ str(base) + ", " + str(exponent - 1) + ")")
return base * exponentiate_with_print(base, exponent - 1)
exponentiate_with_print(2, 5)
Sometimes, recursion can branch.
The following sequence of numbers is called the Fibonacci numbers:
0, 1, 1, 2, 3, 5, 8, 13, 21, 34, ...
Can you see the pattern in this sequence?
The Fibonacci numbers are defined as follows:
F(0) = 0
F(1) = 1
F(n) = F(n - 1) + F(n - 2), for n >= 2
We can expand this out a bit to see the first few Fibonacci numbers in the sequence:
F(0) = 0, by definition, (0-th Fibonacci number)
F(1) = 1, by definition, (1-st Fibonacci number)
F(2) = F(1) + F(0) = 1 + 0 = 1, (2-nd Fibonacci number)
F(3) = F(2) + F(1) = 1 + 1 = 2, (3-rd Fibonacci number)
F(4) = F(3) + F(2) = 2 + 1 = 3, (4-th Fibonacci number)
...
This sequence appears in algorithms, math, and in nature in various places. Today, we can use the definition of Fibonacci numbers to program a function that returns the n-th Fibonacci number!
Let us define a function fib(n)
that takes a non-negative integer ${n}$ as an input and outputs the $n^{th}$ Fibonacci number.
def fib(n):
# implementation of fib using for loop:
def fib(n):
fib_i_prev_prev = 0 # initialized to F(0)
fib_i_prev = 1 # initialized to F(1)
for i in range(n - 1):
# compute the next number in the sequence
fib_i = fib_i_prev_prev + fib_i_prev
# save state to use for the next computation
fib_i_prev_prev = fib_i_prev
fib_i_prev = fib_i
return fib_i
fib(4)
3
Try, on your own, writing what a recursive method for finding the n-th Fibonacci number might look like
# implementation of fib using recursion:
def fib(n):
# base case:
# ?
# recursion step:
# ?
fib(4)
While recursion solutions can be more difficult to think about at first, they can be simpler and more elegant solutions to a problem.
# Let's try running it with a few different values of n:
print(fib(0))
print(fib(5))
# Hmm, that's interesting. It takes a while to run.
print(fib(20))
987
# We'll talk about why it takes a while to run in the afternoon.
# Question 1: Skip Add
# Write a function skip_add that takes a single argument n and computes the sum of every other integers between 0 and n starting from n. Assume n is non-negative.
def skip_add(n):
""" Takes a number x and returns x + x-2 + x-4 + x-6 + ... + 0.
>>> skip_add(5) # 5 + 3 + 1 + 0
9
>>> skip_add(10) # 10 + 8 + 6 + 4 + 2 + 0
30
"""
"*** YOUR CODE HERE ***"
# Question 3: Common Misconception
# Fix the error with this recursive function.
def skip_mul(n):
"""Return the product of n * (n - 2) * (n - 4) * ...
>>> skip_mul(5) # 5 * 3 * 1
15
>>> skip_mul(8) # 8 * 6 * 4 * 2 * 0
0
"""
if n == 0:
return 0
else:
return n * skip_mul(n - 2)
# Question 4: Common Misconception
# Fix the error with the following recursive function.
def factorial(n):
"""Return n * (n - 1) * (n - 2) * ... * 1.
>>> factorial(5)
120
"""
if n == 0:
return 1
else:
n * factorial(n-1)
# Question 5: Common Misconception
# Fix the error with the following recursive function:
def print_up_to(n):
"""Print every natural number up to n, inclusive.
>>> print_up_to(5)
1
2
3
4
5
"""
i = 1
if i > n:
return
else:
print(i)
i += 1
print_up_to(n)
# Question 7: Hailstone
# For the hailstone function from homework 1, you pick a positive integer n as the start. If n is even, divide it by 2. If n
# is odd, multiply it by 3 and add 1. Repeat this process until n is 1. Write a recursive version of hailstone that prints out
# the values of the sequence and returns the number of steps.
def hailstone(n):
"""Print out the hailstone sequence starting at n, and return the
number of elements in the sequence.
>>> a = hailstone(10)
10
5
16
8
4
2
1
>>> a
7
"""
"*** YOUR CODE HERE ***"