CS150 - Fall 2011 - Class 18

  • exercises

  • admin
       - CS talk, Wednesday at 12:30 in MBH 104
          - pizza at 12:20
          - extra credit for attending

  • running python programs interactively from the command-line
       - last time we saw you can run python from Terminal
       - if you just type "python" you get the shell
       - if you type "python some_program.py" it runs the program
       - sometimes, you want to run the program, but then still be able to check values in the shell after the program is finished
          - to do this, you need to at the "-i" flag when calling python
       
             python -i some_program.py

          - python will execute the program, but when it's done, it will not exit and you can interact with the python terminal

  • write a function called factorial that takes a single parameter and returns the factorial of that number
       >>> factorial(1)
       1
       >>> factorial(2)
       2
       >>> factorial(3)
       6
       >>> factorial(8)
       40320

       - look at factorial_iterative function in recursive.py
          - I'm guessing most people wrote it this way
          - does a loop from 1 up to n and multiplies the numbers
       - could we have done it any other way?

  • recursion
       - a recursive function is defined with respect to itself
          - what does this mean?
             - somewhere inside the function, the function calls itself
             - just like any other function call
             - the recursive call should be on a "smaller" version of the problem
       - can we write factorial recursively?
          - key idea: try and break down the problem into some computation, plus a smaller subproblem that looks similar

             5! = 5 * 4 * 3 * 2 * 1
             5! = 5 * 4!

          - a first try:

             def factorial(n):
                return n * factorial(n-1)

          - what happens if we run this with say 5?
             5 * factorial(4)
                |
                V
                4 * factorial(3)
                   |
                   V
                   3 * factorial(2)
                      |
                      V
                      2 * factorial(1)
                         |
                         V
                         1 * factorial(0)
                            |
                            V
                            0 * factorial(-1)
                               ...

             - at some point we need to stop. this is called the "base case" for recursion
             - when is that?
                - when n = 1 (or if you want to account for 0!, at 0)
          - how can we change our function to do this?
       - look at factorial function in recursion.py code
          - first thing, check to see if we're at the base case (if n == 0)
             - if so, just return 1 (this lets 0! be 1)
          - otherwise, we fall into our recursive case:
             - n * factorial(n-1)   

  • writing recursive functions
       1. define what the function header is
          - what parameters does the function take?
       2. define the recursive case
          - pretend you had a working version of your function, but it only works on smaller versions of your current problem, how could you write your function?
             - the recursive problem should be getting "smaller", by some definition of smaller
          - other ideas:
             - sometimes define it in English first and then translate that into code
             - often nice to think about it mathematically, using equals
       3. define the base case
          - recursive calls should be making the problem "smaller"
          - what is the smallest (or simplest) problem
       4. put it all together
          - first, check the base case
             - return something (or do something) for the base case
          - if the base case isn't true
             - calculate the problem using the recursive definition
             - return the answer

       - recursion has a similar feel to "induction" in mathematics
          - proof by induction in mathematics:
             1. show something works the first time (base case)
             2. assume that it works for this time
             3. show it will work for the next time
             4. therefore, it must work for all the times

  • write a recursive function called reverse that takes a string as a parameter and reverses the string
       1. define what the function header is
          def reverse(some_string)
       
       2. define the recursive case
          - pretend like we have a function called reverse that we can use but only on smaller strings
          - to reverse a string
             - remove the first character
             - reverse the remaining characters
             - put that first character at the end

          reverse(some_string) = reverse(some_string[1:]) + some_string[0]

       3. define the base case
          - in each case the string is going to get shorter
          - eventually, it will be an empty string. what is the reverse of the empty string?
             - ""

       4. look at reverse function in recursion.py code
          - check the base case first: if the length of the string is 0
          - otherwise
             - call reverse again on the shorter version of the string
             - append on what is returned by some_string

       - if we added a print statement to reverse to print out each call to reverse what would we see?
          - e.g. print "Reverse: " + some_string
       
             >>> reverse("abcd")
             Reverse: abcd
             Reverse: bcd
             Reverse: cd
             Reverse: d
             Reverse:

          - we can also change the function to see what is being returned each time:

             >>> reverse("abcd")
             Reverse: abcd
             Reverse: bcd
             Reverse: cd
             Reverse: d
             Reverse:
             Returning:
             Returning: d
             Returning: dc
             Returning: dcb
             Returning: dcba

       - to reverse the string "abcd", reverse is called four times recursively

  • write a function called power that takes a base and an exponent and returns base^exponent (i.e. the same thing as ** without using **)
       - can assume that the exponent is >= 0

       1. define what the function header is
          def power(base, exponent)

       2. define the recursive case
          b^e = b * b^(e-1)

          - we can define the power function as the power function of the exponent - 1 times the base

       3. define the base case
          - each time the exponent is getting smaller
          - eventually, the exponent will be 0
             - b^0 = 1

       4. look at the power function in recursion.py code
          - check the base case when the exponent == 0
             - in this case just return 1
          - otherwise, do the recursive case
             - base * power(base, exponent-1)

       - how many times is power called?
          - exponent+1 times
             - power(base, exponent)
             - power(base, exponent-1)
             - power(base, exponent-2)
             - ...
             - power(base, 1)
             - power(base, 0)

       - can we do any better efficiency-wise (that is, less calls to exponent)?
       - look at fast_power function in recursion.py code
          - two different recursive cases
             - if the exponent is even
                - b^e = b^(e/2) * b^(e/2)
             - if the exponent is odd
                - our normal recursive case
                - b^e = b * b^(e-1)
          - why is this faster?
             - when it's even, instead of just decreasing by 1, we decrease by a half!

  • We can write lots of other functions this way as well. Write the following functions that take as input a list and calculate their values recursively:
       - sum
       - len (length)
       - max
       - min