Lab 3: branching and iteration
In this lab, we will start working with two cornerstone programming constructs, namely branching and iteration. These belong to the so-called control flow instructions, as they let the programmer to alter the otherwise sequential flow of instructions (i.e., one instruction after the other). Branching and iteration are here to stay. Thus, it is important that this week you grasp their semantics while starting to develop your problem solving skills with them.
In order to help you out to develop and/or check your understanding of programs with branches and loops, we highly encourage you to use the online Python tutor along this lab (and beyond this lab). We consider the online Python tutor to be an splendid tool to satisfy this goal. Click here for more details.
As we see more and more Python programming constructs, it becomes easier to forget them. To mitigate this, we highly encourage you to keep in mind the Python cheat sheet available here, and ideally that you print it on paper and come to the labs with it all the way through.
Like lab 2, we expect that there will likely be more material in this lab than you can finish during the lab time. If you do not have time to finish all exercises, you can continue working on them later. You can do this at home on your own computer, or in the CSIT lab computers during the drop-in sessions. You may also use the InfoCommons computers (available in the university libraries and other teaching spaces).
Warm-up exercise: Truth values#
A truth value, also known as boolean or logical value, is a value that has only two possible states:
True
or False
. You get boolean values as a result of, e.g, using
comparison operators, such as ==
, or <
. Both True
and False
are reserved words in Python, and they are different to strings such as
"True"
and "False"
.
Start up a shell, and try evaluating following expressions. Make sure you understand the results you obtain:
In [1]: 5 > 4
Out [1]: ...
In [2]: type(5 > 4)
Out [2]: ...
In [3]: "aab" > "ab"
Out [3]: ...
In [4]: type("aab" > "ab")
Out [4]: ...
In [5]: type("False")
Out [5]: ...
In [6]: "False"==False
Out [6]: ...
Note that comparison operators among strings, such as, e.g.,
"aab" > "ab"
, are evaluated by comparing, from left-to-right,
character by character, the ASCII
codes (integer numbers) of each character.
(For completeness,, you can see
here the
correspondence among characters and ASCII codes.) In any case, if you are
comparing strings with only letters, it is easy, as comparison operators
follow alphabetic order. Upper-case letters are assumed to be before than the
lower-case ones, such that, e.g., "Z"<"a"
, evaluates to True
, and "z"<"A"
to False
.
On the other hand, you can combine boolean values with boolean operators and
, or
and not
.
In [7]: 5 > 4 and 4 > 9
Out [7]: ...
In [8]: not 4 > 9
Out [8]: ...
In [9]: True or False
Out [9]: ...
In [10]: True or True
Out [10]: ...
In [11]: type(True or False)
Out [11]: ...
Note: Python allows you to write some compound boolean expressions without a boolean operator:
10 < x <= 20
means the same as
10 < x and x <= 20
The comparisons can be both strict, both non-strict, or a mixture of strict and non-strict.
From here on, you should switch to writing your code in a file rather than typing it into the shell.
Exercise 1: programs with conditional branching #
Exercise 1(a): fixing a wrong function#
One can use boolean values to make decisions in if
statements - this is
called conditional branching. The lecture slides contain the following function for determining
the grade associated with a final mark:
def print_grade(mark):
if mark >= 80:
print("High Distinction")
if mark >= 70:
print("Distinction")
if mark >= 60:
print("Credit")
if mark >= 50:
print("Pass")
else:
print("Fail")
Copy the function to a file so that you can run it, and test it with a
range of different values (keeping in mind that the mark should be a
number between 0 and 100) so that you see all the possible messages
printed on screen. If you struggle to understand why the
messages that are printed are actually printed, this is a good opportunity to use
the online Python tutor.
Click here
for instructions.
As we saw in the lecture, this function does not perform as intended,
and we saw one way to fix it, namely by replacing each test with compound
test expressions (for example mark < 80 and mark >= 70
). Let us try
to solve the problem following a different approach.
Rewrite the function so that it works correctly using only simple
comparison expressions, if
and else
statements. By simple
comparison expression we mean a logical expression which does not
have boolean operators such as and
or or
.
Note that during the lecture last week we also saw a solution to this
exercise using elif
, and only simple comparison expressions
(no boolean operators).
Exercise 1(b): computing the median of three numbers#
The median of three numbers, a, b and c, is the one that ends up in the middle when the numbers are sorted in increasing order. For example, the median of -2, 7 and 9 is 7, and the median of 7, 9 and -2 is also 7.
Write a function median
with three parameters a
b
and c
, which
returns the median of its three arguments. (Of course, the function only
works if all three arguments are numbers… or does it? What happens if
you call it with three strings? What happens if you call it with a mix
of numbers and strings?)
Note that there are many different ways to solve this problem.
Testing your function. You can test the function with sets of
small numbers (like 1,2,3); if it works for small numbers, it should
work just as well for large numbers. Note that the median of three
numbers is the same no matter in which order they are given to the
function; that is, median(1,2,3)
, median(1,3,2)
, median(2,1,3)
,
median(2,3,1)
, median(3,1,2)
and median(3,2,1)
should all
return the same result. What happens if you call your function with
two or three copies of the same value? (such as median(2,1,2)
or
median(1,1,1)
).
Exercise 2: using iteration to print a formatted table with formula evaluations#
The following exercise requires you to use a while
loop.
When writing this kind of loops, chances are high that you end up in an infinite
loop until you are able to find a working solution. In the event of
an infinite loop, you should know how to interrupt the Python
interpreter. One typically interrupts the Python shell hitting the Ctrl+C keyboard combination
(which stands for press the C key while holding down the Ctrl key).
If you are running on a VSCode interactive window,
you can click on the Interrupt option at the top of the window.
For Spyder, this can be achieved by clicking on the little red square button
at top right of console pane (or Ctrl+C)
The following polynomial of degree 2 (also known as quadratic polynomial) in one variable \(t\):
\[y(t)=100t-\frac{1}{2}9.8t^2\]describes the vertical motion of a ball thrown up from the ground level at an initial velocity of 100 \(\frac{m}{s}\) (meters per second). This polynomial is used as follows: if we want to know the vertical position of the ball, say, after 0.2 seconds, then we just evaluate \(y(t)\) at \(t=0.2\) seconds, that is, substitute \(t\) by 0.2 in the polynomial defined above.
The goal of this exercise is to write a function, print_ball_motion_table
, with no parameters,
that prints on screen a nicely formatted table of several
evaluations of \(y(t)\) at different values of \(t\) in an interval \([0,T]\),
where \(T\) is given.
In order to generate the different values of \(t\) at which we want to evaluate \(y(t)\), we split the interval \([0,T]\) into \(N\) equal-sized parts. The size of each part is thus \(\Delta t = \frac{T}{N}\). This will give us \(N+1\) different values of \(t\). An example of this partition is shown below with \(T=20\) and \(N=4\) (time interval \([0,20]\) split into \(4\) parts).
You may want to start this exercise
from the following incomplete code, and then fill-in the gaps (i.e.,
replace the three underscores ___
by correct code):
def print_ball_motion_table():
"""
Prints a table of the position, at different times,
of a ball thrown vertically from the ground level at
a velocity of 100 m/s. The interval [0,T] is split
into N parts, resulting into N+1 time points at which to
calculate and show the position of the ball, that is, N+1
rows in the table.
"""
T=20
N=4
t=___ # Initialize current time t to 0.0 (s)
i=___ # Initialize loop counter to zero
dt=___ # Compute Delta_t (s)
while ___: # Loop condition (compare integers!)
y=___ # Compute y(t)
print(f"{t:5.2f} {y:7.2f}")
___ # Increment loop counter i (by how much?)
___ # Increment current time t (by how much?)
Some comments in regards to this code:
- The
print(f"{t:5.2f} {y:7.2f}")
print statement, witht
andy
being variables, prints two columns with 5 and 7 characters wide each, formatted in decimal notation using 2 decimal digits, and separated by a single space. - It is best, to avoid trouble with rounding errors, that you write your loop condition such that it compares integer numbers instead of floating point numbers.
- Visualize the execution of your function with the online Python tutor.
For reference, the output of the function should be:
0.00 0.00
5.00 377.50
10.00 510.00
15.00 397.50
20.00 40.00
Modify your function definition statement such that T
and N
are function parameters
instead of local variables with hard-coded (fixed) values. Test the function with different values
of T
and N
. Did you write a function to evaluate \(y(t)\)? (this would allow to re-use the
formula in another context)
Exercise 3: robot programming problems#
For some of the problems in this section, you will need to use
a new feature of the robot, namely the robot’s box sensor.
The command robot.sense_color()
returns the colour of the box
in front of the sensor, as a string (a value of type str
). It
will return an empty string (''
) if there is no box in front
of the sensor.
As usual, if you did not already, download a copy of the robot.py module and save it in your working directory for this lab. Make sure that you can import the module: the Python interpreter will look for it in the current working directory.
Remember that you must start by initialising the simulation:
robot.init()
(If you need more of a reminder about how to run and program the robot, revisit Lab 1.)
Exercise 3(a): stacking an arbitrary number of boxes#
In an early lecture, we introduced the problem of stacking boxes lined up on the shelf into a tower. Even though we could define functions that encapsulate the main steps of picking up, stacking and so on, we still needed to write different programs, with a different number of repetitions, for different numbers of boxes. (For example, here are programs for stacking 3 boxes and stacking 5 boxes.)
Write a single function, make_tower
, that stacks any
number of boxes that stand in a line on the shelf into a single
tower.
To create problems with different sizes, you pass extra arguments
to the init
function, for example:
robot.init(width = 7, boxes = "flat")
The “width” is the number of spaces on the table; because the initial setup leaves one empty space between each pair of boxes (otherwise the robot would not be able to pick them up), you need a width of 2*n - 1 to make a problem with n boxes (i.e., width 5 gives you 3 boxes; width 7 gives you 4 boxes; and so on).
There are two important questions to consider in the designing an algorithm for this problem:
What is the sequence of commands that is repeated for every box added to the stack?
How do you determine when to stop? The robot’s sensor can only detect the presence of boxes; how can you tell if it has reached the end of the table?
Exercise 3(b): finding boxes of a particular colour in a box stack#
In last week’s lecture, we developed a function
find_box
that returned True
or False
depending
on whether there is a box of a given colour in the box
stack or not.
Following the same idea, write a function that finds the position of a box with a given colour in a stack. That is, your function should look like this:
def find_box_position(colour):
...
and should return the position (height above the shelf) where a
box of that colour is. The bottom-most box is in position 1
, the
one immediately on top of it in position 2
, and so on towards the
top of the stack. If there is no box of the specified colour,
the function should return a distinct value, such as -1
. (We
choose -1
because it can never be the position in a stack, so we
can distinguish it from the case where a box has been found.)
You can use either a recursive or an iterative method (or why not try both). Note that there are two cases: either the box is found, or the robot has searched all the way through the stack.
To test your function, you need to set up simulations with a
tall stack of different colours. You can do this by passing
additional arguments to the init
function, for example:
init(height = 5, boxes = [["black", "blue", "white", "red"]])
The height
argument specifies the maximum height that the lift can go.
Exercise 4: The square root algorithm #
The Babylonian algorithm is a method of computing square roots of numbers. The method was recorded as far back as the 1st century, though the name suggests it is older than that. The algorithm can be derived as a special case of the Newton-Raphson method for solving an equation of the form \(f(x) = 0\).
The Babylonian algorithm works as follows: We want to compute the square root of some number \(a\). First, we take a guess: pick some number \(x\). Then check how good is our guess, by computing the (absolute) difference \(abs(x^2-a)\). If it’s close enough (for example, less than \(10^{-6}\)) we’re done. Otherwise, calculate an improved guess \(\frac{1}{2}(x + \frac{a}{x})\); this replaces the previous value of \(x\) and we repeat from the test.
Implement a function that computes square roots using the Babylonian
algorithm. From the algorithm’s description, you may already have guessed
that using a while
loop is a good way to go.
Testing your function. The math
module provides a square root
function, math.sqrt
, so you can test your function by comparing its
result with what math.sqrt
returns. Try changing the threshold for
when the answer is close enough and see what effect this has on the
values returned by your function.
To get a better understanding of what is happening, you can also try
adding a print
call inside the loop in your function, so that you can
see how the guesses are updated in each iteration. They should be
converging towards the correct answer. Recall that you can also
use the Online Python tutor.
(You can find this example, and it’s solution, in Chapter 7 of Downey’s book.)
Exercise 5: Digit Sums #
The digit sum of a (positive) integer number is the sum of the digits of the number when it is written out (in base 10). For example the digit sum of 301 is 3 + 0 + 1 = 4.
Write three functions, sum_odd_digits(number)
,
sum_even_digits(number)
and sum_all_digits(number)
, which compute
the sum of the odd digits, the even digits, and all digits, respectively,
in a given number. For example,
sum_odd_digits(1282736)
should return 11 (because 1 + 7 + 3 = 11)sum_even_digits(1282736)
should return 18 (because 2 + 8 + 2 + 6 = 18)sum_all_digits(1282736)
should return 29 (because 1 + 2 + 8 + 2 + 7 + 3 + 6 = 29)
Hint #1:
The modulo (%
) and floor division (//
) operators are useful for
solving this problem. For example, number % 10
will give you the
least significant (“rightmost”) digit in number
.
Hint #2: Think about whether you could write one or more additional functions that incorporate the common functionality from all three functions.