Iteration
0. Learning objectives
- Learn how to iterate through sequences, such as strings or the result of the
range()function - Repeat blocks of code using
forandwhileloops - Understand the consequences of using
breakandcontinue
1. Loops
We discussed how we can write and call functions to perform calculations that are abstracted in terms of some input variables (parameters). We have also looked at how you can use conditionals to make choices within your code in order to follow different computational paths. Nonetheless, even with functions and conditionals, our code is still missing the ability to do something that is fundamental to automation: repeating code! We can explicitly repeat code using loops.
There are two types of loops we will see: for loops and while loops. Note that you can always convert a for loop into a while loop. At a very low abstraction level (machine code) they are both implemented in using identical constructs, however, it may be easier for us humans to use one over the other depending on the specific application.
On top of for and while, we will also
see a few new keywords in Python: the break and continue keywords.
1.0. for loops
If you wanted to count how many vowels are in a string, what would you do? You might come up with an algorithm that involves looking at each character, determining whether or not it is a vowel, and then adding up the number of vowels that you see. Using a loop, we can do exactly that by going through one chracter after the other using their indices!
What we need to do this, is an easy way to iterate through these indices, what we call an iterable.
Strings, as sequences (or collection) of characters are, in fact, iterable. This is also one of the
reasons why we are able to use the in operator on a string to find out
if a character (or substring) is present inside the string. You can almost see the interpreter going
through one character at a time in the string and check.
We can actually leverage this behavior of the in operator to loop
through every item in an iterable (a sequence). For example, in the case of a string, we can use
it to loop through every character. But, what is a loop?
A loop is a programming language construct that allow us to go through an iterable. Feels like we are going in circle, doesn't it? Let's look at a specific construct.
|
The for loop, see the template on the right, introduces a new keyword: the |
|
At each iteration, every item in the sequence will get in turn assigned to the variable.
Therefore, we can use the value stored in variable (which, again, changes on every
iteration) if we need to.
So far, we iterated through the characters in a string. But what if we want to iterate through a sequence of numbers? Like, maybe iterate through the numbers 0 to 10?
We can use the range(start, end, step) function. This function creates an iterable
sequence starting at start, ending at end in steps of
step. The way range() works is just like what we saw with string slicing! You
can think it as a function that generates the indexes that your slicing operator, with the same
parameters, would generate. You can even use negative steps or omit some of the parameters, as we will
see. For example, range(10) will create an iterable sequence with the numbers 0 to 9
(remember it goes up to but not including 10).
Example 0: Playing with for loops
Here are a couple of simple examples.
Example 1: Counting vowels with a loop
Let's now write our vowel counter in two different ways. In the first way, we will iterate through each
character. In the second way, we will iterate through the indices 0 to the number of characters - 1 in
text. In the index example, notice how we are accessing text[i] since
i holds the current integer value generated using the range() function.
Example 1: Arithmetic series
In 1780s, Carl Friedrich Gauss's 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$.
Here is a possible iterative solution to the problem.
Challenge 0: Adding even numbers
Try writing your own function that adds all of the even integers between $1$ and $n$. Challenge yourself to write the function in two ways: one using a conditional and one using the step parameter of the range function. The more practice you get, the better!
1.1. while loops
for loops are probably one of the most common techniques for iterating
through a sequence. But it requires that you have a sequence to iterate through or, at least, to "know"
how many iterations you need to accomplish. What if you don't know (beforehand) the number of iterations
that you have to perform? For example, coming back to the cereal example we looked at when discussing
algorithms: you should continue eating cereal while there is cereal left in your bowl (or until
you're not hungry anymore, whichever comes first). I don't think anyone eats cereal knowing they will
take 23 spoonfuls. Instead, you want to keep iterating as long as (while) some condition is true.
|
This is where the |
|
Example 0: Playing with while loops
Here are a couple of example of loops coded using the while construct. Some of them are
examples that we have done above using the for construct. Can you identify them? They both
solve the same problem. In what do they differ?
Example 1: Doubling an investment
Let's see a good use of a while loop. We will use an application from finance and try to find the number
of years until an investment doubles. We will assume that we start off with some amount of money
(principal) $P$ and we are investing in a fund that will give us an interest $r$ every year. That
interest is then going to be added to our principal and the new interest, at the end of the following
year, will be calculated on this sum, and so on, year after year. This is known as compounding.
In other words, after year 1 we will have $P(1+r)$ dollars, after year 2 we will have
$P(1+r)(1+r)=P(1+r)^2$ dollars, meaning that after $n$ years we will have $P(1+r)^n$. Yes, we could
carry out this calculation analytically, but try to do it with a while
loop.
Have a look at first solution and notice that the condition for the while in
our problem is current_value < 2 * P. Remember that the comparison operator <
will return either True or False depending on the result of the comparison.
This is equivalent to write (current_value < 2*P) == True. In the second solution we use a
technique that is often used for more complex problems. We define a flag variable which is a
Boolean, check the value of that variable in our while condition, and then update the
variable if certain conditions are met. In this case if the value of out investment has doubled. This is
often used if there are multiple condition that can stop the loop and are spread around our code block.
Challenge 0: For loops to while loops (and vice-versa)
Try re-writing some of the functions that we have seen using for loops with while loops
(vowels, add_up_to) and some of the functions that we have seen using while
loops with for loops (factorial, find).
Challenge 1: When while loops are more appropriate
I did not ask you to write the function investment_double using a for loop. Why might this
function be more difficult to write using a for loop?
The exact number of iterations is not known until we have completed the loop, because it is
dependent on current_value. In fact, the purpose of our loop is to compute
years, which is the number of times we run the body of the loop!
1.2. break and continue
Sometimes you might want to skip an iteration, or something happened inside your loop that
makes you want to abandon either the current or any further iteration. For example, what if you want
to perform a certain computation on all the number up to n (let's say you want to
double them) but you want to skip that computation for all the multiples of 5? Luckily we have a
special keyword to skip iterations: the continue keyword. If
Python hits the continue keyword, it will skip the
remaining lines of code in the block for the current iteration and go right to the next iteration in
your loop. Here is an simple example:
Another useful keyword is the break keyword. If Python
reaches a break, then it will abandon not only the current
iteration, but the entire loop, all future iterations, and jump to the end of the loop (back to the
original indentation). This keyword might be useful if you don't know when to exit the iteration.
Maybe you are interacting with a human and you don't know when they are going to be done entering
data or typing on a keyboard. Consider the following example where we wrote code simulating a 'dumb
terminal' (they were e real thing, look them up) where you can continue to type lines and get the "I
don't understand!" reply until you type a line with only the word 'exit' in it at which point the
terminal with just quit. In this case, break would work very nice since it can exit
your loop.
break and continue should be used only when absolutely necessary since they
make the code harder to read and understand. When people see a while or
for, they will expect the loops to run until completion and consider the code as a
block that does something so many times under the condition specified at the beginning of your loop.
If you use break or continue, you throw a wrench in this usual
interpretation. So, if you have to use it, make sure you clearly comment your code specifying its
behavior.