Lecture 4: Recursion & Turtles
Writing recursive functions can be tricky, so we'll start off this week by practicing writing recursive functions.
Next, we will talk about the |
|
recursion examples
Remember the two main ingredients we need to write a recursive function: we need to treat a base case and a recursive step. Ultimately, you need to make sure that your recursive function calls will eventually reach the base case - otherwise your program could run forever! Note that you might also have multiple base cases.
When Carl Friedrich Gauss was 7 years old (in the 1780s), his teacher (wanting to quiet down the class) gave the class a seemingly impossible task: add up all the numbers from 1 to 100.
This should have kept them busy for hours.
However, Gauss finished the problem almost immediately!
In fact, Gauss proved that the sum of the first $n$ natural numbers (i.e. the sum of the numbers starting at $1$ and going until $n$) is equal to $n(n+1)/2$.
Write a recursive program called sumn
to compute the sum of the first $n$ integers, starting at 1.
Ensure that your program computes the correct result for $n = 100$, i.e. it should be equal to $100 \times 101 / 2 = 5,050$.
It's spring time, so bunnies are starting to take over Middlebury. Fibonacci actually studied an idealized model for bunny population growth. Assume that we start by placing two baby rabbits somewhere in Middlebury. Rabbits mature into adult rabbits after 1 month at which point they breed a new pair of baby rabbits (this is an assumption in our idealized model). So at the beginning, we have no rabbits, but then after 1 month we have a pair of baby rabbits. The next month, these baby rabbits have matured into a pair of adult rabbits. The month after that, these adults rabbits had a new pair of baby rabbits, which then mature into a new pair of adult rabbits for the following month. Thus, every month, any existing pair of adult rabbits had a new pair of baby rabbits and any baby rabbits at the start of the month are now fully grown adult rabbits. We can express the total number of rabbit pairs $F(n)$ in terms of the month $n$. Our model is therefore
$$ F(n) = F(n-1) + F(n-2) \quad \mbox{for } n \ge 2,\quad \mbox{ and } \quad F(0) = 0,\ F(1) = 1. $$
This is the Fibonacci sequence!
The $n$-th Fibonacci number is called $F(n)$, which can be expressed as the sum of the $(n-1)$-th Fibonacci number plus the $(n-2)$-th Fibonacci number.
Write a recursive program fib
which computes the $n$-th Fibonacci number.
How many base cases should you have?
Ensure that your function correctly computes $F(10) = 55$.
Fibonacci sequences appear in various patterns in nature, including branching in trees, fruitlets of a pineapple and the flowering of the leaves of artichokes and pinecones to name a few.
My first job was a cashier job at a bakery (a bagel shop, actually) - let me know if you want some Montreal bagel secrets :)
Every time I would give a customer back their change, I had to do the math in my head as to how many toonies, loonies, quarters, dime, nickels and pennies (pennies have since been obsoleted in Canada) I should give back to the customer.
We can actually write a recursive algorithm to do this math for us.
Suppose a customer gives you $n$ dollars to pay for something that costs $m$ dollars (and cents).
You would then need to return $c = n - m$ in coins.
How many toonies (2 $\$$), loonies (1 $\$$), quarters (25 cents), dimes (10 cents), nickels (5 cents) and pennies (1 cent) should you give back to the customer?
Write a function that computes the total number of coins to return to the customer for a certain change of amount
(in cents).
Try to minimize the total number of coins you give them and assume you have an infinite number of each coin in your register.
Verify that you return 3 coins for amount
$ = 3.25 \$$ (325 cents) and return 8 coins for amount
$ = 3.24\$ $ (324 cents).
Let's revisit the Fibonacci example above.
Try to run fib(20)
, and then run fib(30)
.
You can even be ambitious and run fib(40)
.
What do you notice?
It's really slow eh - in fact, I don't recommend trying fib(40)
or you'll be waiting a loooong time!
Well, even though writing a recursive function is elegant because it looks almost exactly like the math, it can be inefficient.
So be careful when you write recursive functions.
The reason that our fib
function is slow is because it recomputes many values that have already been computed.
For example, let's expand fib(5)
:
Notice how many times we are recomputing $F(n-2)$ since $F(n-1)$ depends on it.
There are few ways around this: (1) store these previous values or (2) come up with a more efficient solution that does the solution backwards.
We will see how to do the former in a few weeks, but the latter can be solved by keeping track of how many Fibonacci numbers we still need to compute in order to compute the $n$-th Fibonacci number, starting from $F(0)$.
For example, if we want to compute $F(5)$, then we know that $F(0) = 0$ and $F(1) = 1$, so we need to compute $F(2)$, $F(3)$ and $F(4)$ to compute $F(5)$.
This means that we have 3 Fibonacci numbers remaining
to be computed.
Whenever we want to compute those remaining Fibonacci numbers, they will require one less Fibonacci number to be computed (i.e. remaining-1
).
Check out the solution to this problem below.
This is a pretty confusing solution to the Fibonacci number calculation, but it now allows you to compute fib(40)
which we weren't able to do before.
I suggest printing out the values (uncomment Line 4) to see the order in which fib_helper
is called, and all the values for the previous
, current
and remaining
Fibonacci numbers.
Although this solution is much more efficient, it's much harder to read and understand.
It's time to write your first graphics program!
You will now be introduced to the turtle
module.
Just like the math
module we used before, you need to import the turtle
whenever you want to use it: import
turtle
.
Since the turtle
functions are all contained within this module, you will also need to use "dot notation" to call the turtle
drawing functions.
Remember Lightbot from homework 1?
The
To get a feel for the |
|
Let's do some examples, so you can see how the turtle
module works.
We'll start off by drawing a square which has a side length of length
(on each of the four sides).
See if you can draw two cascading squares using this function.
Note: after running the trinket
, you will need to click the "Stop" button before re-running it.
square
function twice, and making sure that we lift the pen (and then put it back down) in between calls to the square
function.
Try typing this into the script portion of the trinket
above.
Developing graphics programs can be useful for practicing with recursion.
Try to write a recursive function called |
|
Note: if you don't want to see the turtle "tracing" animation, you can set turtle.tracer(False)
before doing any drawing commands, which will do the drawing without showing you the animation.
This will be useful when you are performing a lot of drawing commands and don't want to wait for the animation to run.
Another note: turning the tracer off in repl.it
doesn't seem to work: a workaround is to set the turtle
"speed": setting turtle.speed(0)
is the fastest.
Let's do another recursive example with the turtle.
This time, we'll draw a spiral.
Our spiral
function will take in length
that we would like to draw for the current level
(same concept as the previous example).
The base case will be if the current "level" is equal to zero, in which case we return (back through all the recursive function calls).
Otherwise, in the recursive step, we will go forward by some length, then turn left by an angle of 30 degrees, and then keep recursing by calling spiral
with a smaller length (95% of the current length) and decrement the level by 1.
After drawing a part of the spiral and performing the recursive call, we would also like the turtle to return to it's starting position, so we need to re-orient the turtle by turning right by 30 degrees, and then moving backwards (undoing the forward call) by the same length
.
© Philip Claude Caplan, 2021