## Introduction

We know that Python statements are executed sequentially. That is, the first statement in a program is executed first, followed by the second and so on. There may be a situation when you need to execute a block of code several times. A loop statement allows us to execute a code block multiple times without repeating the code in the program.

## The for Loop

The for loop in Python is used to iterate over iterable objects like list, tuple, string, sets and dictionaries. The syntax of the for loop is as follows:

SYNTAX The for loop.

1for item in sequence:
2    loop body


The variable item is called the loop variable. It changes for each iteration of the loop. The loop continues until we reach the last item in the sequence. Notice that the body of for loop is indented with a tab.

EXAMPLE The for loop.

1list1 = [2,4,6,8,10]
2for item in list1:
3    print(item)

2
4
6
8
10


In the following example, we compute the sum of the squares of all values in the list. Note that we need to initialize the sum to zero before we begin the loop.

EXAMPLE Compute a sum using the for loop.

1sum_squares = 0
2for item in list1:
3    sum_squares = sum_squares + item**2
4print(f"The sum of the squares is {sum_squares}")

The sum of the squares is 220


Mathematically, we are computing the sum: $$\sum_{x \in \mathcal{B}} x^2 = 2^2 + 4^2+6^2+8^2+10^2$$ where $\mathcal{B}$ is the set of all elements in list1.

Recall that only the indented statement is within the for loop. Therefore, the last print statement is not subjected to any iteration. The following shows the inner workings of the for loop for each iteration. The temp variable is created to store the sum before it is being updated (for the purpose of printing).

EXAMPLE Dissecting the for loop to compute a sum.

1sum_squares = 0
2for item in list1:
3    temp = sum_squares
4    sum_squares = sum_squares + item**2
5    print(f"{item}: the sum is now {temp} + {item**2} = {sum_squares}")
6print(f"The sum of the squares is {sum_squares}")

2: the sum is now 0 + 4 = 4
4: the sum is now 4 + 16 = 20
6: the sum is now 20 + 36 = 56
8: the sum is now 56 + 64 = 120
10: the sum is now 120 + 100 = 220
The sum of the squares is 220


It is possible to loop over the elements in a Python set (which we may recall is both unordered and unindexed). However, the result may not follow the order in which the items appear in the set.

EXAMPLE Looping through a set.

1set1 = {2,4,6,8}
2for item in set1:
3    print(item)

8
2
4
6


We can also loop over dictionaries. By default, Python will loop over the keys only.

EXAMPLE Looping through a dictionary.

1scores = {"Lily":62, "Jane":86, "Tom":75, "Matt":91}
2for item in scores:
3    print(item)

Lily
Jane
Tom
Matt


To loop over the values, we use the values() method.

EXAMPLE Looping through the values of a dictionary.

1for item in scores.values():
2    print(item)

62
86
75
91


To loop over the key:value pairs as tuples, we use the items() method.

EXAMPLE Looping through the key:value pairs of a dictionary.

1for item in scores.items():
2    print(item)

('Lily', 62)
('Jane', 86)
('Tom', 75)
('Matt', 91)


We may also separate the keys and the values by creating two loop variables in a tuple.

EXAMPLE Looping through a dictionary: separating keys and values.

1for (name,score) in scores.items():
2    print(f'{name} receives a score of {score}')

Lily receives a score of 62
Jane receives a score of 86
Tom receives a score of 75
Matt receives a score of 91


## Some Useful Functions

In this section, we introduce several useful functions related to looping.

### The range() Function

We can generate a sequence of integers using the range() function. For example, range(5) will generate numbers from 0 to 4 (5 numbers). The syntax is as follows:

SYNTAX The range() function.

1 range(start, stop, step)

Parameter Required? Default Value Description
start ❌ No 0 An integer number specifying the start index of the range object.
stop ✔️ Yes NA An integer number specifying the stop index of the range object.
step ❌ No 1 An integer number specifying the step size.

The stop argument is mandatory. Both the start and step arguments are optional. The start argument defaults to 0 and step argument defaults to 1 if not provided. Note that the range object produces integers up to but not including the endpoint. The range object evaluates “lazily” in the sense that you will not see the sequence generated unless you explicity convert it to a sequence using the list() or tuple() constructors for example.

EXAMPLE The range() function.

1range(5) # sequence is hidden

range(0, 5)


EXAMPLE Converting a range() object to a list.

1list(range(5)) # convert to list object.

[0, 1, 2, 3, 4]


EXAMPLE Converting a range() object to a tuple.

1tuple(range(5)) # converts to tuple object.

(0, 1, 2, 3, 4)


EXAMPLE The range() function with start and stop arguments.

1list(range(2,8)) # range object with start and stop arguments.

[2, 3, 4, 5, 6, 7]


EXAMPLE The range() function with all 3 arguments.

1list(range(3,20,2)) # range object with start, stop and step_size arguments.

[3, 5, 7, 9, 11, 13, 15, 17, 19]


We may use the reversed() function on a range() object to create the sequence in reversed order.

EXAMPLE The reversed() function applied to a range() object.

1list(reversed(range(1,10, 2)))

[9, 7, 5, 3, 1]


Let’s see how the range object is being used in a for loop. Let’s compute the sum of the even numbers from $2$ to $100$, i.e. $2+4+6+…+100$.

In mathematical notation, let $\mathcal{S} = \{ x | x \in \mathbb{Z}, 1\leq x \leq 50 \}$ where $\mathbb{Z}$ is the set of all integers. We then compute the sum:

$$\sum_{i \in \mathcal{S}} 2i = 2 + 4+ 6 + \cdots + 96+98 + 100.$$ EXAMPLE Looping through a range() object.

1total = 0
2for i in range(101):
3    if i % 2 == 0: # modulo operator to ensure that only even numbers get summed
4        total += i
5print(f'Sum of all even numbers from 1 to 100 is {total}')

Sum of all even numbers from 1 to 100 is 2550


In the above code, total += i is a shorter way of writing total = total + i. Also, the % symbol is the modulo character that has been discussed here . For example, i % 2 computes the remainder when i is divided by 2. Consequently, a remainder of 0 means the number i is divisible by 2 and is even.

Tip: There are more efficient methods to compute the sum. One way is to create a range object that contains only the numbers in the series and then apply the sum() function.
1numbers = list(range(2,101,2))
2print(f'Sum of all even numbers from 1 to 100 is {sum(numbers)}')

Sum of all even numbers from 1 to 100 is 2550


### The enumerate() Function

If you need a counter variable while looping over a sequence, you may use the enumerate() function. It returns a sequence of (index, element) tuples. By default, the index starts at zero with a step size of one. Alternatively, you can specify an optional start index. The syntax is as follows:

SYNTAX The enumerate() function.

1 enumerate(sequence, start)

Parameter Required? Default Value Description
sequence ✔️ Yes NA An iterable object that supports iteration.
start ❌ No 0 The index value from which the counter is to be started.

The sequence argument is mandatory while the start argument is optional. The start argument defaults to 0 if not specified.

EXAMPLE Using enumerate() function in a for loop.

1fruits= ["apple","orange","banana","pear","pineapple","durian"]
2for item in enumerate(fruits):
3    print(item)

(0, 'apple')
(1, 'orange')
(2, 'banana')
(3, 'pear')
(4, 'pineapple')
(5, 'durian')


If you want to “decouple” the (index, element) tuple, you can create two loop variables in a tuple.

EXAMPLE Decoupling the (index, element) tuple in enumerate().

1for (index, fruit) in enumerate(fruits):
2    print(f'{index}: {fruit}')

0: apple
1: orange
2: banana
3: pear
4: pineapple
5: durian


We can specify an optional start index in the enumerate function.

EXAMPLE Specifying the start index in enumerate().

1for (index, fruit) in enumerate(fruits,2):
2    print(f'{index}: {fruit}')

2: apple
3: orange
4: banana
5: pear
6: pineapple
7: durian


### The random.sample() Function

Since we have discussed the range() function, we will introduce a closely related function called the random.sample() function which is part of Python’s random module used to work with random data generation. First, we need to import the module using a import random statement.

SYNTAX Importing the random module.

1import random


We use random.sample(range(), n) to generate a list of n random integers within the specified range(). Take note that no repetition is allowed. Therefore, the number n must not be more than the number of integers within the specified range. Essentially, the sample() function takes the items from the specified range object and mixes them randomly.

EXAMPLE The random.sample(range(), n) function.

1random.sample(range(1,11),10) # 10 random integers with range(1,11).

[8, 10, 9, 2, 3, 1, 7, 6, 5, 4]


In the above code, we are generating 10 random integers within the range(1,11).

Caution: If we had used any value of $n>10$, we will get an error since there are only 10 integers within the specified range and the random integers generated cannot be repeated.

We can get the list sorted using the sorted() function.

EXAMPLE The sorted() function to sort a list.

1sorted(random.sample(range(1,11),10))

[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]


EXAMPLE The sorted() function to sort a list (in reverse order).

1sorted(random.sample(range(1,11),10), reverse=True)

[10, 9, 8, 7, 6, 5, 4, 3, 2, 1]


What if you want repetitions of the numbers from the range object? That brings us to the random.choices() function.

### The random.choices() Function

We use the random.choices(range(), k=n) function to select k=n multiple random items from a range() object (or any iterable sequence) with repetitions allowed.

EXAMPLE The random.choices(range(), k=n) function.

1random.choices(range(1,11),k=10)

[5, 8, 10, 1, 1, 1, 8, 2, 7, 10]


Since repetitions are allowed, the is no limit on the value of k.

1random.choices(range(1,11),k=15)

[1, 4, 8, 5, 4, 7, 1, 5, 2, 6, 9, 7, 2, 8, 9]


Let’s see how we can use the above random functions with for loops.

EXAMPLE Use a random number generator to simulate the tossing of an unbiased die 10 times and print out the results.

1tosses = random.choices(range(1,7),k=10) # generate a list of random integers between 1 and 6, 10 times.
2for (toss,item) in enumerate(tosses,1):
3    print(f'Toss {toss} shows {item}')

Toss 1 shows 1
Toss 2 shows 1
Toss 3 shows 4
Toss 4 shows 4
Toss 5 shows 5
Toss 6 shows 2
Toss 7 shows 4
Toss 8 shows 2
Toss 9 shows 1
Toss 10 shows 6

Question: Simulate the tossing of an unbiased die 100 times and count the numbers of occurrences for each face.
1tosses = random.choices(range(1,7),k=100) # generate a list of random integers between 1 and 6, 100 times.
2dict1 = {} # empty dictionary {face: frequency}
3for x in range(1,7):
4    dict1[x] = tosses.count(x)
5    print(f'Face {x} shows {dict1[x]} times')

Face 1 shows 20 times
Face 2 shows 22 times
Face 3 shows 15 times
Face 4 shows 14 times
Face 5 shows 17 times
Face 6 shows 12 times


If you do not wish to store the results in a dictionary but merely want to print out the results, you can use the following shorter code.

1tosses = random.choices(range(1,7),k=100)
2for x in range(1,7):
3    print(f'Face {x} shows {tosses.count(x)} times')

Face 1 shows 20 times
Face 2 shows 22 times
Face 3 shows 15 times
Face 4 shows 14 times
Face 5 shows 17 times
Face 6 shows 12 times


### The zip() Function

To loop over two or more sequences (iterables) at the same time, the entries can be paired with the zip() function to create a list of tuples.

EXAMPLE Using the zip() function to combine 2 iterables into a list of tuples.

1names = ["Lily", "Jane", "Tom", "Matt"]
2scores = [62, 86, 75, 91]
3for (name, score) in zip(names, scores):
4    print(f'{name} receives a score of {score}')

Lily receives a score of 62
Jane receives a score of 86
Tom receives a score of 75
Matt receives a score of 91


We can further add the enumerate() function to create an index.

EXAMPLE Using both zip() and enumerate() functions in a for loop.

1for (i, (name, score)) in enumerate(zip(names, scores)):
2    print(f'{i}: {name} receives a score of {score}')

0: Lily receives a score of 62
1: Jane receives a score of 86
2: Tom receives a score of 75
3: Matt receives a score of 91


zip() can take an arbitrary number of sequences, but the number of elements it produces is determined by the shortest sequence.

EXAMPLE zip() function with 3 iterables.

1grades = ['C', 'A', 'B'] # assume grade for the last student isn't available.

Lily receives a score of 62 and grade C.