Discussion 3: Recursion, Tree Recursion

Getting Started

If you could change one historic event, what would it be? And what would you make happen instead?

Recursion

VERY IMPORTANT: In this discussion, don't check your answers by running your code. Figure things out and check your work by thinking about what your code will do and drawing environment diagrams. Your goal should be to have all checks pass the first time you run them!

A recursive function is a function that calls itself in its body, either directly or indirectly.

Let's look at the canonical example, factorial.

Factorial, denoted with the ! operator, is defined as:

n! = n * (n-1) * ... * 1

For example, 5! = 5 * 4 * 3 * 2 * 1 = 120

The recursive implementation for factorial is as follows:

def factorial(n):
    if n == 0:
        return 1
    return n * factorial(n - 1)

We know from its definition that 0! is 1. Since n == 0 is the smallest number we can compute the factorial of, we use it as our base case. The recursive step also follows from the definition of factorial, i.e., n! = n * (n-1)!.

Recursive functions have three important components:

  1. Base case. You can think of the base case as the case of the simplest function input, or as the stopping condition for the recursion.

    In our example, factorial(0) is our base case for the factorial function.

  2. Recursive call on a smaller problem. You can think of this step as calling the function on a smaller problem that our current problem depends on. We assume that a recursive call on this smaller problem will give us the expected result; we call this idea the "recursive leap of faith".

    In our example, factorial(n) depends on the smaller problem of factorial(n-1).

  3. Solve the larger problem. In step 2, we found the result of a smaller problem. We want to now use that result to figure out what the result of our current problem should be, which is what we want to return from our current function call.

    In our example, we can compute factorial(n) by multiplying the result of our smaller problem factorial(n-1) (which represents (n-1)!) by n (the reasoning being that n! = n * (n-1)!).

The next few questions in lab will have you writing recursive functions. Here are some general tips:

  • Paradoxically, to write a recursive function, you must assume that the function is fully functional before you finish writing it; this is called the recursive leap of faith.
  • Consider how you can solve the current problem using the solution to a simpler version of the problem. The amount of work done in a recursive function can be deceptively little: remember to take the leap of faith and trust the recursion to solve the slightly smaller problem without worrying about how.
  • Think about what the answer would be in the simplest possible case(s). These will be your base cases - the stopping points for your recursive calls. Make sure to consider the possibility that you're missing base cases (this is a common way recursive solutions fail).
  • It may help to write an iterative version first.

Q1: Warm Up: Recursive Multiplication

These exercises are meant to help refresh your memory of the topics covered in lecture.

Write a function that takes two numbers m and n and returns their product. Assume m and n are positive integers. Use recursion, not mul or *.

Hint: 5 * 3 = 5 + (5 * 2) = 5 + 5 + (5 * 1).

For the base case, what is the simplest possible input for multiply?

For the recursive case, what are we doing in every step? How do we change m or n to make this a smaller version of the same problem?

Run in 61A Code

Q2: Swipe

Implement swipe, which prints the digits of argument n, one per line, first backward then forward. The left-most digit is printed only once. Do not use while or for or str. (Use recursion, of course!)

Run in 61A Code

Q3: Skip Factorial

Define the base case for the skip_factorial function, which returns the product of every other positive integer, starting with n.

Run in 61A Code

Q4: Recursive Hailstone

Recall the hailstone function from Homework 1. First, 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. Complete this recursive version of hailstone that prints out the values of the sequence and returns the number of steps.

Run in 61A Code

Tree Recursion

A tree recursive function is a recursive function that makes more than one call to itself, resulting in a tree-like series of calls.

For example, this is the Virahanka-Fibonacci sequence: 0, 1, 1, 2, 3, 5, 8, 13, ....

Each term is the sum of the previous two terms. This tree-recursive function calculates the nth Virahanka-Fibonacci number.

def virfib(n):
    if n == 0 or n == 1:
        return n
    return virfib(n - 1) + virfib(n - 2)

Calling virfib(6) results in a call structure that resembles an upside-down tree (where f is virfib):

Virahanka-Fibonacci tree.

Each recursive call f(i) makes a call to f(i-1) and a call to f(i-2). Whenever we reach an f(0) or f(1) call, we can directly return 0 or 1 without making more recursive calls. These calls are our base cases.

A base case returns an answer without depending on the results of other calls. Once we reach a base case, we can go back and answer the recursive calls that led to the base case.

As we will see, tree recursion is often effective for problems with branching choices. In these problems, you make a recursive call for each branching choice.

Q5: Count Stair Ways

Imagine that you want to go up a flight of stairs that has n steps, where n is a positive integer. You can take either one or two steps each time you move. In how many ways can you go up the entire flight of stairs?

You'll write a function count_stair_ways to answer this question. Before you write any code, consider:

  • How many ways are there to go up a flight of stairs with n = 1 step? What about n = 2 steps? Try writing or drawing out some other examples and see if you notice any patterns.
  • What is the base case for this question? What is the smallest input?

Now, fill in the code for count_stair_ways:

Run in 61A Code
You're done! Excellent work this week. Please be sure to fill out your TA's attendance form to get credit for this discussion!

Extra Challenge

Try these out if you like challenges. You might have fun.

Q6: Subsequences

A subsequence of a sequence S is a subset of elements from S, in the same order they appear in S. Consider the list [1, 2, 3]. Here are a few of its subsequences [], [1, 3], [2], and [1, 2, 3].

Write a function that takes in a list and returns all possible subsequences of that list. The subsequences should be returned as a list of lists, where each nested list is a subsequence of the original input.

In order to accomplish this, you might first want to write a function insert_into_all that takes an item and a list of lists, adds the item to the beginning of each nested list, and returns the resulting list.

Run in 61A Code