Loops

In the previous tutorial, we studied tests, which allow a computer to make decisions based on conditions specified in a program. We will now go even further in automating operations with the concept of loops. Loops will allow us to repeat an instruction several times without having to rewrite the same code each time.

To illustrate this idea, let’s imagine we want to display each element of a list. For now, we would do:

gamme = ['do', 're', 'mi', 'fa', 'sol', 'la', 'si']

print(gamme[0])
print(gamme[1])
print(gamme[2])
do
re
mi

And so on. We can immediately see that such an operation would be impractical for a list containing hundreds of elements. Loops will solve this problem in an elegant and efficient manner.

for loops

Definition

The first type of loop we will look at is the for loop. A for loop allows you to traverse the different elements contained in an object called an iterable, and perform operations with these elements. Iterable objects include all the sequential objects we have seen so far: strings, lists, tuples, etc.

Let’s illustrate how a for loop works by solving the problem presented earlier.

for note in gamme:
    print(note)
do
re
mi
fa
sol
la
si

Syntax

Let’s analyze the structure of a for loop:

  • The first line specifies a for statement, and like any statement in Python ends with :.

  • This is followed by a block of instructions, i.e., a series of operations (just one in our example) that will be executed at each iteration of the loop. This block is visible by its level of indentation, incremented by 1 compared to the statement. The block ends as soon as the indentation returns to its initial level.

As with conditional statements like if/else, indentation is crucial. If you forget it, Python returns an error.

for note in gamme:
    print(note)
do
re
mi
fa
sol
la
si

Operation

Now let’s look in more detail at what the for statement does. It defines an iteration variable (called note in our example), which will traverse the elements of the iterator specified after the in (the list gamme in our example). The syntax of a loop in Python lends itself well to a literal description; in our case: “for each note in the list gamme, print the note.”

Note that a loop defines a variable without needing the traditional variable = value assignment syntax. Furthermore, this variable is not deleted once the loop is finished; it then takes the value of the last element of the iterator.

note
'si'

The iterator does not necessarily have to be a list; it can be any iterable object. This includes all the sequential objects we have seen.

for char in "YMCA":
    print(char)

print()  # Newline

t = (1, 2, 3, 4, 5)
for i in t:
    print(i*9)
Y
M
C
A

9
18
27
36
45

However, the class of iterable objects is much larger than just sequential objects. For example, you can iterate over the keys of a dictionary, even though we saw in a previous tutorial that it is not a sequential object, since there is no notion of order in a dictionary.

inventaire = {'coffee': '500g', 'milk': '1.5L', 'cereal': '1kg'}
for key in inventaire:
    print(key)
    print(inventaire[key])
    print()  # Newline
coffee
500g

milk
1.5L

cereal
1kg

Iteration over integers with the range function

In programming, it is common to want to iterate over a series of integers. Instead of specifying this series in a list, which is not very practical if the series is long, we use the range(n) function. This creates an iterable object that contains all the integers between 0 and n-1, and can be used within a loop.

For example, let’s see how we can very simply display a multiplication table using this function.

table = 9

for i in range(11):
    print(i, i*9)
0 0
1 9
2 18
3 27
4 36
5 45
6 54
7 63
8 72
9 81
10 90

Iteration over indices

We have seen that a for loop works by iterating over the elements of an iterable. However, in the case of a sequential object like a list, we may sometimes want to iterate over the indices of the object to manipulate both the indices and the elements contained in the object. In this case, the range function can be used in combination with the len function to create an iterable object that contains exactly the indices of the initial list.

gamme = ['do', 're', 'mi', 'fa', 'sol', 'la', 'si']

for i in range(len(gamme)):
    print("The note number " + str(i) + " in the C major scale is " + gamme[i])
The note number 0 in the C major scale is do
The note number 1 in the C major scale is re
The note number 2 in the C major scale is mi
The note number 3 in the C major scale is fa
The note number 4 in the C major scale is sol
The note number 5 in the C major scale is la
The note number 6 in the C major scale is si

Since this need is frequent but the code above is not very readable, there is a built-in Python function called enumerate that allows iterating over both objects and indices. It is therefore preferable to use this syntax, which is clearer and helps avoid some errors.

The enumerate function applied to an iterable object returns a new iterable object that contains all the pairs (index, element) in the object, in the form of tuples. Since it is a special type of object – a generator, which we will see in a more advanced tutorial – you need to apply the list function to display its content.

list(enumerate(gamme))
[(0, 'do'), (1, 're'), (2, 'mi'), (3, 'fa'), (4, 'sol'), (5, 'la'), (6, 'si')]

Let’s see how to rewrite the previous loop with this new syntax.

for i, note in enumerate(gamme):
    print("The note number " + str(i) + " in the C major scale is " + note)
The note number 0 in the C major scale is do
The note number 1 in the C major scale is re
The note number 2 in the C major scale is mi
The note number 3 in the C major scale is fa
The note number 4 in the C major scale is sol
The note number 5 in the C major scale is la
The note number 6 in the C major scale is si

NB: To assign variables in the if statement, we used a handy technique we had already mentioned in an exercise in the tutorial on lists and tuples: tuple unpacking. Let’s illustrate it with an example:

t = (1, 2, 3)
a, b, c = t
print(a)
print(b)
print(c)
1
2
3

while loops

Definition

while loops provide an alternative way to specify repetitive procedures. The idea is no longer to iterate over a fixed number of objects, but to iterate as long as a condition (logical test) is met.

i = 1
while i <= 5:
    print(i)
    i = i + 1
1
2
3
4
5

Syntax

The main difference from the for loop is the statement: it is now a while statement, followed by a condition (test), and like any statement, ends with :.

The principle is the same for the rest: the while statement is followed by a block of instructions, indented by one level, which is executed sequentially at each iteration of the loop.

Stopping criterion

An essential difference between while loops and for loops lies in the stopping criterion. In a for loop, this criterion is clear: the loop iterates over the elements of an iterable object, necessarily of finite size. The loop stops when each element of the iterable has been traversed.

In a while loop, on the other hand, the stopping criterion is given by a logical condition, so the user must set the stopping criterion. In the example, for the loop to stop, the condition i <= 5 must become False, meaning i must become strictly greater than 5. We ensured this by initializing i to 1 before the loop starts, and then incrementing i by one unit at each iteration.

What happens if we forget to increment i? The stopping criterion is never reached, so the loop is infinite, and we must use the “Stop” button (black square) in Jupyter to stop the running program. Let’s verify this by incrementing the wrong variable.

i = 1
j = 1
while i <= 5:
    j = j + 1

Therefore, when we suspect that a while loop is taking too long to run, we must consider the possibility that we have fallen into an infinite loop, and ensure that the stopping criterion is reachable.

The break statement

An alternative way to specify a stopping criterion is to use the break statement. When this statement is reached and executed, the loop is immediately interrupted.

Let’s illustrate its operation with an example. The first line creates an infinite loop because, by definition, True is always evaluated as True. The program then asks the user to type a name, and does so indefinitely until the user types the expected name. Only then is the break statement reached, and the loop stops. The message “Welcome ” is finally displayed, as the second print is not included in the loop.

your_name = "Romain"

while True:
    print("Please enter your name.")
    name = input()
    if name == your_name:
        break
print("Welcome " + your_name)

It is important to note that a break statement only terminates the loop of the directly superior level. In the case of a multi-level loop, it is entirely possible for operations to continue even when a break statement has been reached.

Let’s illustrate this principle with an example.

i = 0
while i <= 5:
    for j in range(5):
        if j == 2:
           

 print("Break.")
            break
    i += 1
  File <tokenize>:7
    print("Break.")
    ^
IndentationError: unindent does not match any outer indentation level

At each iteration of the while loop, a for loop is launched, which reaches a break statement at the third iteration (when j is 2). This stops the for loop, but not the while loop, which executes its subsequent instructions (incrementing i by one unit) before proceeding to the next iteration.

The continue statement

The continue statement allows you to skip to the next iteration of the loop.

Let’s enhance the previous example to illustrate its operation. As long as a different name than the expected one is entered, the continue statement is evaluated, and the program continues to ask for a name. When the correct name is entered, the program asks the user to enter a password. If the password is the expected one, the break statement is reached and executed, and the loop stops. If the password is incorrect, however, the loop restarts at the beginning of the execution block, so you need to enter a name again before the password.

your_name = ""

while True:
    print("Please enter your name.")
    name = input()
    if name != your_name:
        continue
    print("Please enter your password.")
    password = input()
    if password == "insee2021":
        break
print("Welcome " + your_name)

NB: The code above is only for example purposes. As we will see in a future tutorial on best coding practices, you should never write secrets (passwords, tokens, etc.) in plain text in your code.

Exercises

Comprehension questions

  • 1/ How does a for loop work?

  • 2/ Does the iteration variable defined in a for loop persist in memory once the loop is finished?

  • 3/ What does the range function do? Why is it particularly useful in for loops?

  • 4/ What does the enumerate function do? Why is it particularly useful in for loops?

  • 5/ How does a while loop work?

  • 6/ When does a while loop stop? How does this differ from for loops?

  • 7/ What does the break statement do?

  • 8/ What does the continue statement do?

Show solution

1/ A for loop defines an iteration variable that will traverse each element of an iterable object. At each iteration, a series of instructions is executed.

2/ Yes, and its final value is equal to the last value of the iterable object.

3/ The range(n) function creates an iterable object that contains all integers between 0 and n-1. It is widely used as an iterable in for loops because it allows iterating over a sequence of integers without having to manually put them in a list.

4/ The enumerate function applied to an iterable object returns a new iterable object that contains all pairs (index, element) associated with the initial object, in the form of tuples. In the context of a for loop, it allows iterating over both the elements of an iterable and their positions.

5/ A while loop executes a series of instructions repeatedly as long as the specified logical condition evaluates to True.

6/ A while loop stops as soon as the specified logical condition evaluates to False. If this never happens, a while loop can be infinite. In contrast, a for loop can be very long but never infinite, as it stops as soon as it finishes traversing the object.

7/ The break statement forces the loop of the directly superior level to terminate.

8/ The continue statement forces the loop of the directly superior level to skip to the next iteration.

Predicting results of while loops

Try to predict what the following while loops will produce, and check your results.

# 1.
i = 0
while i <= 10:
    print(i)

# 2.
a = 1
while (a < 10):
    a += 1
    if a == 5:
        break
    print("Stopping condition reached.")

# 3.
while False:
    print("hello world")

# 4.
while True:
    print("hello world")
    break

# 5.
while 5 >= 3:
    continue
    print("hello world")
# Test your answer in this cell
Show solution
    1. Infinite loop because i is never incremented, so the condition is always true. 0 will print infinitely.
    1. The loop will stop at the 4th iteration when a is 5. However, the print statement is misindented, so it will print 3 times instead of 1.
    1. False evaluates to False, so the loop does not execute at all. No output.
    1. True evaluates to True, so the loop is theoretically infinite, but there is a break. Thus, there will be only one iteration, resulting in one “hello world” print.
    1. 5 >= 3 evaluates to True, so the loop is infinite. The continue statement is executed at each iteration before the print can execute. The loop runs infinitely, but with no output.

Effect of an indentation error

Source: python.sdv.univ-paris-diderot.fr

To visualize the importance of indentation in instruction blocks, try to predict what the following two programs will return. Which one has the expected effect?

numbers = [4, 5, 6]
for num in numbers:
    if num == 5:
        print("The test is true")
        print(f"because the variable num is {num}")
The test is true
because the variable num is 5
numbers = [4, 5, 6]
for num in numbers:
    if num == 5:
        print("The test is true")
    print(f"because the variable num is {num}")
because the variable num is 4
The test is true
because the variable num is 5
because the variable num is 6
# Test your answer in this cell
Show solution

The first program is correct. In the second, the second print is not correctly indented. As a result, it executes at each iteration and not just when num == 5.

Converting a for loop to a while loop

Rewrite the following for loop using a while loop.

gamme = ['do', 're', 'mi', 'fa', 'sol', 'la', 'si']

for i, note in enumerate(gamme):
    print("The note number " + str(i) + " in the C major scale is " + note)
The note number 0 in the C major scale is do
The note number 1 in the C major scale is re
The note number 2 in the C major scale is mi
The note number 3 in the C major scale is fa
The note number 4 in the C major scale is sol
The note number 5 in the C major scale is la
The note number 6 in the C major scale is si
# Test your answer in this cell
Show solution
gamme = ['do', 're', 'mi', 'fa', 'sol', 'la', 'si']

i = 0
while i <= (len(gamme) - 1):
    # Subtract 1 from the length of `gamme` because the max index is 6
    print("The note number " + str(i) + " in the C major scale is " + gamme[i])
    i += 1
The note number 0 in the C major scale is do
The note number 1 in the C major scale is re
The note number 2 in the C major scale is mi
The note number 3 in the C major scale is fa
The note number 4 in the C major scale is sol
The note number 5 in the C major scale is la
The note number 6 in the C major scale is si

Finding an element in a list

Given a target integer target_num and a list of integers l as defined in the following cell. Using a for loop and the enumerate function:

  • Check if the target integer is present in the list l.

  • If yes, display the message ‘The number target_num is at position i in the list’, and end the loop.

target_num = 78

l = [12, 98, 65, 39, 78, 55, 119, 27, 33]
# Test your answer in this cell
Show solution
target_num = 78

l = [12, 98, 65, 39, 78, 55, 119, 27, 33]

for i, n in enumerate(l):
    if n == target_num:
        print("The number " + str(n) + " is at position " + str(i) + " in the list.")
        break

# NB: more efficient version without loop
if target_num in l:
    pos = l.index(target_num)
    print("The number " + str(target_num) + " is at position " + str(pos) + " in the list.")
The number 78 is at position 4 in the list.
The number 78 is at position 4 in the list.

Fibonacci sequence

The Fibonacci sequence is defined as follows:

  • The first two numbers are 0 and 1

  • Each subsequent number in the sequence is obtained by adding the two preceding numbers

Write a program to calculate the first \(n\) terms of the sequence using a for loop.

# Test your answer in this cell
Show solution
n_terms = 20
num1 = 0
num2 = 1

for i in range(n_terms):
    print(num1)
    num3 = num1 + num2
    num1 = num2
    num2 =

 num3
  File <tokenize>:11
    num3
    ^
IndentationError: unindent does not match any outer indentation level

Multiplication table dictionary

Using two nested for loops, build a dictionary tables that allows generating multiplication tables up to the table of 12. Query your dictionary to check its accuracy.

Here are some examples of queries your dictionary should return:

  • tables[2][3] -> 6

  • tables[9][5] -> 45

  • tables[12][7] -> 84

# Test your answer in this cell
Show solution
tables = {}

for i in range(13):
    tables[i] = {}
    for j in range(13):
        tables[i][j] = i*j

print(tables[2][3])
print(tables[9][5])
print(tables[12][7])
6
45
84

Calculating the minimum and maximum of a series “by hand”

Calculate the minimum and maximum of the following series of values without using Python’s min and max functions.

x = [8, 18, 6, 0, 15, 17.5, 9, 1]

# Test your answer in this cell
Show solution
x = [8, 18, 6, 0, 15, 17.5, 9, 1]

current_min = x[0]
current_max = x[0]
for n in x[1:]:
    if n <= current_min:
        current_min = n
    if n >= current_max:
        current_max = n

print(current_min == min(x))
print(current_max == max(x))
True
True

Calculating the mean and variance “by hand”

Calculate the mean and variance of the following series of values without using pre-coded functions:

x = [8, 18, 6, 0, 15, 17.5, 9, 1]

To recall, the formulas are:

  • mean: \[\bar{x} = {\frac {1}{n}}\sum_{i=1}^{n}x_{i}\]

  • variance: \[\sigma^2 = {\frac {1}{n}}\sum_{i=1}^{n} (x_{i}-\bar{x})^2\]

NB:

  • n to the power of k is written in Python as n**k

  • in practice, you should never try to recode such functions yourself, but use functions from suitable packages like numpy.

# Test your answer in this cell
Show solution
x = [8, 18, 6, 0, 15, 17.5, 9, 1]
n = len(x)

sum_mean = 0
for x_i in x:
    sum_mean += x_i
mean = sum_mean / n

sum_var = 0
for x_i in x:
    sum_var += (x_i - mean)**2
variance = sum_var / n

print(mean)
print(variance)

# Verification with numpy package functions
import numpy as np
print(np.mean(x))
print(np.var(x))
9.3125
42.93359375
9.3125
42.93359375

Advanced use of the range function

We saw earlier the basic use of the range function: range(n) creates an iterable object that contains all integers from 0 to n-1. The possible uses of this function are, however, more comprehensive and sometimes useful for specific problems.

The complete syntax of the function is range(start, stop, step) where:

  • start is the integer at which the sequence of integers starts

  • stop is the integer before which the sequence of integers ends

  • step is the step, i.e., the value of the increment between each integer in the sequence.

Only the stop parameter is mandatory, which is used when calling range(n).

Using the range function, display:

  • All integers from 0 to 10 (10 excluded)

  • All integers from 10 to 20 (20 included)

  • All even numbers between 30 and 40 (40 included)

  • All multiples of 10 between 1 and 100 (100 excluded)

  • All integers from 10 to 20 (20 included), in reverse order (from 20 to 10)

# Test your answer in this cell
Show solution
print(list(range(10)))

print(list(range(10, 21)))

print(list(range(30, 41, 2)))

print(list(range(10, 100, 10)))

print(list(range(20, 9, -1)))
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
[10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20]
[30, 32, 34, 36, 38, 40]
[10, 20, 30, 40, 50, 60, 70, 80, 90]
[20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10]

The price is right, improved version

In the previous tutorial, we coded a game of “The Price is Right”. But it was somewhat limited because you had to rerun the code at each stage of the game. Using loops, rewrite the game to be fully automatic.

Game rules reminder:

Using input and the if, elif, and else statements, code the following program:

  • Ask the user for a value, which will be stored in a variable p

  • If p is strictly less than $15, print (using the print function) the message “too low!”.

  • If p is strictly greater than $15, print the message “too high!”.

  • If p equals $15, print the message “spot on!”

Note, input returns a string by default. Therefore, you need to convert the value of p to an integer (using the int function) for the game to work.

# Test your answer in this cell
Show solution
right_price = 15

while True:
    print("Propose a number between 1 and 50.")
    p = input()
    p = int(p)
    if p < right_price:
        print("too low!")
    elif p > right_price:
        print("too high!")
    else:
        break

print("spot on!")
Propose a number between 1 and 50.
---------------------------------------------------------------------------
StdinNotImplementedError                  Traceback (most recent call last)
Cell In[64], line 5
      3 while True:
      4     print("Propose a number between 1 and 50.")
----> 5     p = input()
      6     p = int(p)
      7     if p < right_price:

File /opt/hostedtoolcache/Python/3.10.15/x64/lib/python3.10/site-packages/ipykernel/kernelbase.py:1281, in Kernel.raw_input(self, prompt)
   1279 if not self._allow_stdin:
   1280     msg = "raw_input was called, but this frontend does not support input requests."
-> 1281     raise StdinNotImplementedError(msg)
   1282 return self._input_request(
   1283     str(prompt),
   1284     self._parent_ident["shell"],
   1285     self.get_parent("shell"),
   1286     password=False,
   1287 )

StdinNotImplementedError: raw_input was called, but this frontend does not support input requests.