CS51A - Spring 2025 - Class 13
Example code in this lecture
mystery_recursion.py
recursion.py
turtle_recursion.py
Lecture notes
administrative
- midterm Thursday
- Assignment 6
writing recursive functions
1. define what the function header is
- what is the name of the function?
- 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, e.g
- for smaller numbers (like in factorial)
- for lists that are smaller/shorter
- for strings that are shorter
- 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? This is often the base case
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 some time
3. show it will work for the next time (i.e. time after "some time")
4. therefore, it must work for all the times
What does mystery_recursion function in
mystery_recursion.py code
do?
- Recursive function
- Work through a small example:
- rec_mystery([2, 4, 3, 1])
rec_mystery([2, 4, 3, 1]) # when recursive call finishes: compares m = 4 and l[0] = 2 and returns 4
|
|
---> rec_myster([4, 3, 1]) # when recursive call finishes: compares m = 3 and l[0] = 4 and returns 4
|
|
-->rec_mystery([3, 1]) # when recursive call finishes: compares m = 1 and l[0] = 3 and returns 3
|
|
--> rec_mystery([1]) # returns 1
- what does this function do?
- calculates the max!
- how?
1. rec_max(list)
2. rec_max(list) = ??? rec_max(list[1:])
- assume/trust that the recursive call works
- if it does, then it will return the largest value in list[1:]
- the largest value of the whole list is then either the first element (l[0]) or the largest value in the rest of the list (rec_max(list[1:])
3. The list will get smaller and smaller. max([]) doesn't really make sense, so our base case will be when there's a single element
- recursive case:
- make a recursive call on the rest of the list
- store that value in m
- compare m to the first element and return whichever it larger
Write a recursive function called count_odd that takes as input a list and returns the number of odd elements in the list
- For example:
>>> count_odd([1, 2, 3])
2
>>> count_odd([1, 2, 3, 4])
2
>>> count_odd([1,2, 3, 4, 5])
3
1. define the function header
- count_odd(some_list)
- returns an int
2. define the recursive case
- Assume the function works. What would we get if we called: count = count_odd(some_list[1:])?
- the number of odd numbers in the rest of the list
- How do we get the total number of odd numbers in the list?
- check if the first number is odd, if it is, return count + 1
- if it's not, just return count
3. define the base case
- [] => 0
4. put it together
- look at the count_odd function in
recursion.py code
look at the spiral function in
turtle_recursion.py code
- for example, what would the picture look like if I called:
>>> spiral(80, 50)
- what does this function do?
- recursive function
- draws a spiral on the screen
- forward 80
- left 30
- spiral( 76, 49 )
- forward 76
- left 30
- spiral(72.2, 48)
- forward 72.2
- left 30
- ...
- when does it stop?
- when levels == 0
- I put a dot here just do make it explicit
- we could have also just done nothing if we wanted
- repeat 50 times:
- forward length
- left 30
- reduce length by 5%
- what if we wanted to end up back at the starting point, but we couldn't pick the pen up?
- one way would be to trace our steps backward
- Assume that the recursive call returns back to its starting point. What would we need to do to make sure that our call returned back to the starting point?
- Add the following after the recursive call:
right(30)
backward(length)
- if we run it now, we draw the spiral all the way down, and then we retrace backwards
- why does this work?
- each call to spiral retraces its own part after the recursive call
- the stack keeps track of each of the recursive calls
run broccoli_demo function in
turtle_recursion.py code
1. define what the function header is
- broccoli(x, y, length, angle)
2. define the recursive case
- broccoli is a line with three other broccolis at the end
- one directly straight out
- one 20 degrees to the left
- one 20 degrees to the right
- the three other broccolis should be smaller/shorter than the current
3. define the base case
- eventually the length of the broccoli to be drawn gets too short
- at that point instead of recursing, we draw a yellow dot at the end and stop recursing
4. put it all together
- look at broccoli function in
turtle_recursion.py code
- draw a line in the direction specified
- check the base case
- if the line length is less than 10, just put a yellow dot at the end
- otherwise, it's the recursive case
- draw three smaller broccolis at different angles
- what are new_x and new_y?
- the ending coordinates of the line being drawn
- why do we need to save them?
- after the first recursive call to broccoli the turtle won't be in the same place
- if we turn tracing back on, what will we see, that is, what order will it draw in?
- going to go right (angle -20) over and over again until it gets too short
- it will end the recursion then draw a short center
- this also will be a base case and draw the left
- then it will start to work it's way back up
- eventually, it will make it back up to the top-level and start drawing the center stalk
- after drawing the center stalk, it will draw the left stalk
Why does recursion work?
- Specifically, why can we trust that the recursive call is going to work?
- The base case works (assuming we coded it up correctly)
- If the base case works, the the call before the base case works
- If that works, then the call before that works.
- Etc, etc.
write a function called power that takes a base and an exponent and returns base^exponent (i.e. the same thing as '**' without using '**')
- you can assume that exponent >= 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)