Recursion

This function prints the given text the given numberOfTimes.

def repeatPrint(text, numberOfTimes):
    # Write solution here...

For example:

repeatPrint('Hello!', 3)

Should output:

Hello!
Hello!
Hello!
In [31]:
# Implementation of repeatPrint using a for loop:
def repeatPrint(text, numberOfTimes):
    for i in range(numberOfTimes):
        print(text)

# Let's try it:
repeatPrint('Hello!', 3)
Hello!
Hello!
Hello!
In [32]:
# Implementation of repeatPrint using a while loop:
def repeatPrint(text, numberOfTimes):
    i = 0
    while i < numberOfTimes:
        print(text)
        i += 1

# Let's try it:
repeatPrint('Hello!', 3)
Hello!
Hello!
Hello!

Let us now define repeatPrint using a technique called Recursion. A recursive function is a function that calls itself.

In [33]:
# Implementation of repeatPrint using recursion:
def repeatPrint(text, numberOfTimes):
    if numberOfTimes == 0:
        # If the number of times to print is 0, there is no work to do.
        return
    # Print the text once.
    print(text)
    # Now we have one fewer number of times to print the text.
    numberOfTimesRemaining = numberOfTimes - 1
    repeatPrint(text, numberOfTimesRemaining)

# Let's try it:
repeatPrint('Hello!', 3)
Hello!
Hello!
Hello!

Every recursive function has two important parts: the base case and the recursive case.

The base case occurs when we have the simplest possible input to the function, where the answer is obvious. For repeatPrint the base case occurs when numberOfTimes == 0.

The recursive case occurs all other times from the base case where we are required to call the function itself with a different input (that gets one step closer to the base case). For repeatPrint the recursive case occurs when numberOfTimes > 0.

In [ ]:
# What happens if we forget the base case?
def repeatPrint(text, numberOfTimes):
    # Print the text once.
    print(text)
    # Now we have one fewer number of times to print the text.
    numberOfTimesRemaining = numberOfTimes - 1
    repeatPrint(text, numberOfTimesRemaining)

# Let's try it:
repeatPrint('Hello!', 3)

# Answer: repeatPrint will recursively call itself repeatedly, without stopping!
# So this bad code. Similar to an infinite loop.

Let us try out recursion with another example.

The following sequence is called the Fibonacci sequence:

0, 1, 1, 2, 3, 5, 8, 13, 21, 34, ...

Can you see the pattern in this sequence?

The Fibonacci sequnce is 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 elements of this sequence:

F(0) = 0, by definition
F(1) = 1, by definition
F(2) = F(1) + F(0) = 1 + 0 = 1
F(3) = F(2) + F(1) = 1 + 1 = 2
F(4) = F(3) + F(2) = 2 + 1 = 3
F(5) = F(4) + F(3) = 3 + 2 = 5
...

This sequence appears in algorithms, math, and in nature in various places. We will not discuss the sequence itself in detail, but you can read more about it here: https://en.wikipedia.org/wiki/Fibonacci_number.

Let us define a function fib(n) that computes the $n^{th}$ Fibonacci number.

def fib(n):
    # Write solution here...
In [34]:
# Implementation of fib using for loop:
def fib(n):
    if n == 0 or n == 1:
        return 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

# Let's try it:
fib(4)
Out[34]:
3
In [35]:
# Implementation of fib using recursion:
def fib(n):
    # Base case:
    if n == 0 or n == 1:
        return n
    # Recursive case:
    return fib(n - 1) + fib(n - 2)

# Let's try it:
fib(4)
Out[35]:
3

Let us practice how to understand recursive functions with a couple more examples.

In [36]:
# What is the behavior of this function?
def mystery(a):
    if a == 0:
        return 0
    return a + mystery(a - 1)

mystery(4)

# Answer:
#     mystery(a) = 0 + 1 + 2 + ... + a
Out[36]:
10
In [37]:
# What is the behavior of this functioin?
def mystery2(a, b):
    if b == 0:
        return 0
    return a + mystery2(a, b - 1)

mystery2(5, 4)

# Answer:
#     mystery2(a, b) = a * b
Out[37]:
20