#!/usr/bin/env python # coding: utf-8 # # AddisCoder: Week 2, Day 4 # ## Getting the Hang of Recursion # Some problems split into multiple smaller problems, rather than just 1 smaller problem... # ## Review with Example # To find the value to the following expression ${2^n}$ for non-negative integers ${n}$, we can use an old friend `for` loop. # In[ ]: def exponentiate(base, exponent): total = 1 for i in range(exponent): total *= base return total print(exponentiate(2, 5)) # 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. # In[ ]: 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 # ### BASE CASE # 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 # ``` # ### RECURSION STEP # 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 # ``` # ### Tracing steps # `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` # In[ ]: 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) # ## Branching Recursion # # 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. # # ```python # def fib(n): # ``` # In[ ]: # 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) # ### 7 lines of code! # Try, on your own, writing what a recursive method for finding the n-th Fibonacci number might look like # In[ ]: # implementation of fib using recursion: def fib(n): # base case: # ? # recursion step: # ? fib(4) # ### 3 lines of code!! # While recursion solutions can be more difficult to think about at first, they can be simpler and more elegant solutions to a problem. # In[ ]: # Let's try running it with a few different values of n: print(fib(0)) # In[ ]: print(fib(5)) # In[12]: # Hmm, that's interesting. It takes a while to run. print(fib(20)) # In[13]: # We'll talk about why it takes a while to run in the afternoon. # ### Extra Practice Problems # In[ ]: # 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 ***" # In[ ]: # 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) # In[ ]: # 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) # In[ ]: # 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) # In[ ]: # 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 ***"