Lecture 3: Conditions & Recursion
Last time, we talked about how to write and call functions to perform calculations that are abstracted in terms of some input variables.
However, we were still pretty much using |
Whenever a squirrel reaches a fork in a tree, it must decide which branch to visit next in order to reach it's nest. You'll be able to draw something like this next week - stay tuned! |
Remember comparators from the last lecture?
We only briefly talked about them, but they're really useful for making decisions in your programs.
As an example, think about how your alarm works.
How does it know when to wake you up?
Well, suppose it keeps track of a time
variable.
Let's say there's a mini program inside your clock that just runs forever.
At any point in time, you can check if your wakeUp
time is reached, at which point your alarm should start beeping.
Your alarm will make the decision to start beeping if some condition is true.
Here, the condition is time == wakeUp
, which means the current time
is greater than the wakeUp
time.
Whoever designed your alarm probably used a conditional like this (but they probably added other features like checking if you hit the snooze button to determine if your alarm should go off).
When writing a conditional statement, we need a few ingredients.
As you read through these, follow along with the syntax for writing a conditional in Python
on the right.
|
When you branch your code into a block of statements, all other blocks are ignored.
In fact, the program will jump to the end of the entire if
statement after evaluating the block of code.
In the example above, this means that if condition1
is False
but condition2
is True
then statement2
will be executed, and then the program will jump to Line 17 (the print
statement).
Within an if
statement, you can have other if
statements.
As you can imagine, this means you can branch the execution of a program into many different paths!
Remember, the squirrel from the beginning of the lecture?
Each decision the squirrel made caused it to branch into a new part of the tree.
The particular path the squirrel took represented a single flow of execution through the program.
Let's do some examples to practice writing conditions and functions in Python
.
Write a function absolute
that returns the absolute value of a number $x$.
Recall that the absolute value, denoted by $\lvert x\rvert$, is equal to $x$ if $x \ge 0$ and is equal to $-x$ if $x$ is less than zero.
Test your function with the inputs $1$, and then $-5$ (already set up for you), and ensure that you obtain $1$ and $5$, respectively.
If you get stuck, you can view the solution by clicking on the solution.py
tab.
Write a function getLetterGrade
that returns a string representing the letter grade a student should receive based on their final grade percentage.
Assume that if the grade percentage (represented by the variable score
) is greater than 90
, then we assign a letter grade of A
.
If the score
is greater than 80
, then assign a letter grade of B
.
If the score
is greater than 70
, then assign a letter grade of C
.
If the score
is greater than 60
, then assign a letter grade of D
.
Otherwise, assign a letter grade of F
.
Don't worry about grade modifiers (-
or +
), but feel free to try this out if you want.
Test your function by calling it with the grades: 93
, 89.99999
, 81
, 74
and then 52
.
If you get stuck, you can view the solution by clicking on the solution.py
tab.
As mentioned above, you can have an if
statement nested within other if
statements.
Write a function getWeatherForecast
that returns a message as to what the weather will be for the day, given a temperature T
(degrees Fahrenheit) and a precipitation P
(in percent).
Here is the message you should return for the different cases:
- If the temperature is below 36 degrees Fahrenheit and the precipitation is above 70%:
it will be cold and snowy
. - If the temperature is below 36 degrees Fahrenheit and the precipitation is above 30%, but below 70%:
it will be cold and cloudy
. - If the temperature is below 36 degrees Fahrenheit and the precipitation is below 30%:
it will be cold and sunny
. - If the temperature is above 36 degrees Fahrenheit and the precipitation is above 70%:
it will be warm and rainy
. - If the temperature is above 36 degrees Fahrenheit and the precipitation is above 30%, but below 70%:
it will be warm and cloudy
. - If the temperature is above 36 degrees Fahrenheit and the precipitation is below 30%:
it will be warm and sunny
.
Use your weather forceaster to determine which weather icon should be used for today's weather in Middlebury. You can visit the DarkSky website to find the temperature and precipitation values for a particular day: https://darksky.net/forecast/40.7127,-74.0059/us12/en. Click on "Today" and then "more details" to see the temperature and precipitation charts.
As an extra challenge, try to incorporate some "wind" variable (W
) into your function!
Maybe report that it will be windy if W
is greater than 20 mph.
introduction to recursion
With functions and conditionals, we can start repeating computations. We will use a technique known as recursion, and in a few weeks we will cover another method for repeating computations. Recursion can be tricky so we will start with the basics this week, and then do a lot more practice next week.
Imagine you are at the dining hall, and there is a huge line to get curly fries. There are too many people in line to count all of them, so you tap the person in front of you on the shoulder and ask them how many people are ahead of them. Whatever they say, you can just add 1 to get the total number of people in front of you. Maybe they follow the same idea: they ask the person in front of them how many people are ahead of them and add 1 to the result. Suppose this continues until the person at the start of the line is reached. How many people are in front of this person? None! So they inform the person behind them that there are no people in front of them. The second person in line then adds 1 and tells the third person in line that there is 1 person ahead of them. The third person in line then adds 1 and tells the fourth person in line that there are 2 people ahead of them. This continues until you are finally reached, at which point you know the total number of people ahead of you in the line. Click on in the demo on the right to see how this works. This is main idea of recursion: keep calling the same function until we reach some base case. The base case consists of a problem we know how to solve, so we just do some computation and then return. Otherwise, we keep calling the function recursively: we need to make sure that the inputs to our recursive function calls are getting closer and closer to our base case. Otherwise, the program would run forever! |
|
The main ingredients we need for writing a recursive function are a recursive step and a base case. At the beginning of our recursion, the problem is too difficult to solve, but we can break it up into a problem(s) we know how to solve and use that result to solve our current problem. The outline of a recursive function is shown on the right, and the ingredients are described below.
This probably sounds super vague right now, but don't worry, it'll make sense once we start doing some examples. In the curly fries example, the "big problem" was figuring out how many people are in front of us. Since we couldn't solve this directly, we broke it up into a "smaller problem", in which we asked the person in front of us how many people are in front of them and then added 1 to the result. |
|
The example above contains two versions of a template for a recursive function.
We are given some input
that we will use to do some computation.
However, we can't directly solve our problem with this input
, unless we have reached the base case, which we check with the conditional baseCase
(treat this as some conditional, so it returns either True
or False
).
If we haven't reached the base case, then we will call our function "recursively" by breaking up the input into something smaller, with the hopes that we eventually reach the base case (in which we know what computation to perform).
This recursive function call might return something that we need to use to perform additional computation before returning the result.
Check your understanding by determining if both versions (switch the version by clicking on the tabs) do the same thing.
else
portion, which is never reached if the base case is reached.
In Version 2, we automatically return
from the function after doing some computation for the base case.
So the stuff on Line 14 are not reached if the base case is reached (but they are reached for functions that call the base case).
Let's write a recursive function to compute the value of some input number $x$ raised to te power of an integer $p$, denoted by $x^p$.
Yes, I know that Python
can do this with x ** p
but let's write a recursive function to do this.
For an integer $p$, we can write out $x^p$ as
power
function below.
Check that your function works correctly by calling power(2,4)
and making sure you get 16
.
Let's do another math-y example, this time with the factorial, represented with an exclamation point.
Recall that $5! = 5 \times 4 \times 3 \times 2 \times 1$.
Observe that $5! = 5 \times 4!$ and $4! = 4 \times 3!$.
Hmm, this sounds pretty recursivey.
So in order to compute $5!$, we can just multiply $5$ times whatever we get from $4!$, and then keep doing that until we get to our base case.
What is our base case?
Well when we get to $1$, then $1! = 1$ (in fact, $0!$ is also equal to $1$).
See if you can complete the factorial
function below.
Okay, that's enough math.
Let's try and get a feel for the path Python
takes through a program.
Consider the following recursive function that hopefully clarifies the path a recursive function call takes.
The goBack
function takes in some integer n
.
If we reached the base case (we chose n == 0
for this example, but it could have been something else), then we print "stop"
.
Otherwise, we are in the recursive case, in which we want to print "go"
(along with the current value of n
).
We then call goBack
recursively with an input of n-1
(we make the current input smaller for the recursive call).
Within the goBack(n-1)
function call, we may still not reach the base case, so it will again print "go"
with the current value of n
(which is equal to n-1
relative the "caller" of this function).
After we finally reach the base case, then we come back up the recursive calls and print "back"
followed by the local value of n
.
The goBack
function in the last exercise revealed the "path" a recursive function takes after it is called, which can be tricky to visualize.
One useful method for visualizing the path your program takes is through what's called a stack diagram.
A stack diagram is composed of frames, in which each frame represents a particular function call.
You can draw out this frame as a box, kind of like the "ships" we saw last week.
Inside each box, you should put a title for the box which includes the function name and values for the input variables.
Now your job is to "be the computer", in other words, do the calculations Python
would do for you, calculating intermediate variables and then writing out call the "calls" to other functions.
Whenever you need to call another function, create a new frame!
You should also draw an arrow between the frames as (1) a function is called from another function (the "caller") and (2) a function returns a value to it's caller.
At the end of the day, you should have two arrows between all the frames: one when you go down the functions calls, and one when go back up.
Have a look at the following demo on for the goBack
function.
This demo describes the steps you would take to draw a stack diagram for the initial input of goBack(5)
.
Click on the to take a single "step" through the execution of the program.
Notice which line is currently being executed in the editor on the left.
|
|
You should also investigate the use of the debugger in Thonny
in order to play around with stack diagrams.
Thonny
's debugger is a really nice tool that "pops out" a new window every time a new frame is created.
You can also see the values of the variables in each frame.
The "step in" button in Thonny
is kind of like the above - it "steps" one line at a time.
Technically speaking, there is also a frame for the "main" portion of the script.
Our goal is to focus on a particular function call so we will usually omit this "main" frame.
I will specify which function call to start your stack diagram at, such as goBack(5)
in the above example.
© Philip Claude Caplan, 2021