6. Loops
Loops are used to recycle logic over and over over any number of data or inputs. Without loops, for as many times as you need to apply the same logic to your data, you will have to code (or copy and paste) your logic that many times. Loops are frequently used to iterate
over collections such as lists, sets, tuples and maps. There are two basic loops, the while
and for-each
loops. Let’s investigate them further below.
6.1. while
The while loop has the following syntax.
1while <some-condition-is-true>:
2 # perform some logic
Below, we loop until the variable n
is equal to or greater than 10. At the end of the block of code under the while loop, we increment n
by one. If we did not increment n
then the termination condition will never be False
and we would loop forever.
1n = 0
2
3while n < 10:
4 print(n)
5 n += 1
You can also loop endless and break
out of a while
loop manually. In the example below, we loop forever since we set while True:
. However, inside the loop, we check if n
is a multiple of 10, and if so, we issue a break
(a manual termination) of the while
loop.
1n = 0
2while True:
3 print(n)
4 n += 1
5 if n % 10 == 0:
6 break
6.1.1. Exercise
Create a program to keep asking for the user to input something (anything) 5 times. Use a while
loop to help you in this exercise. Inside the loop, simply print back out what the user entered.
Solution.
1n = 0
2while n < 5:
3 user_input = input('enter in anything: ')
4 print(f'{user_input}')
6.1.2. Exercise
Create a program using a while
loop to keep asking for the user to input something endlessly. Inside the loop, if the user enters in n
, then break
. If the user did not enter n
as the input, then echo the user’s input back to the terminal.
Solution.
1while True:
2 user_input = input('enter in anything: ')
3
4 if user_input == 'n':
5 break
6
7 print(f'{user_input}')
6.2. for-each
The for-each
loop is best understood when looping over the elements of a collections. To appreciate the power of the for-each
loop, let’s see what it would take to print a list of 10 numbers without a for-each
loop.
1numbers = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
2
3print(numbers[0])
4print(numbers[1])
5print(numbers[2])
6print(numbers[3])
7print(numbers[4])
8print(numbers[5])
9print(numbers[6])
10print(numbers[7])
11print(numbers[8])
12print(numbers[9])
This approach to printing each number from a list is not scalable if there are a million numbers to print. We need to use the for-each
loop. The syntax of the for-each
loop is as follows.
1for <element> in <collection>:
2 # perform some logic over the element
6.2.1. Basic for-each
Here are some examples of using a for-each
loop against different types of collections.
1# looping over an array/list
2number_list = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
3
4for number in number_list:
5 print(number)
6
7# looping over a set
8number_set = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
9
10for number in number_set:
11 print(number)
12
13# looping over a tuple
14number_tuple = (0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
15
16for number in number_tuple:
17 print(number)
18
19# looping over a map
20number_map = {
21 'a': 0,
22 'b': 1,
23 'c': 2
24}
25
26for key, val in number_map.items():
27 print(key, val)
6.2.2. Exercise
Loop through each number in the list below and print the number and what the number times 2 would be.
1numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
Solution.
1numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
2
3for number in numbers:
4 n = number * 2
5 print(f'{number} x 2 = {n}')
6.2.3. Exercise
Loop through each number in the list below and print the number and what the number times 2 would be only if the number is even.
1numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
Solution.
1numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
2
3for number in numbers:
4 if number % 2 == 0:
5 n = number * 2
6 print(f'{number} x 2 = {n}')
6.2.4. Exercise
Loop through each number in the list below and print the number and what the number times 3 would be only if the number times 3 is odd.
1numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
Solution.
1numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
2
3for number in numbers:
4 n = number * 3
5 if n % 2 != 0:
6 print(f'{number} x 3 = {n}')
6.2.5. Exercise
Loop through each key-value pair in the following dictionary. Since the values are tuples, loop through each element in the tuple and print them.
1data = {
2 'fred': (28, 150.5, 5.5),
3 'john': (32, 180.2, 6.2)
4}
Solution.
1data = {
2 'fred': (28, 150.5, 5.5),
3 'john': (32, 180.2, 6.2)
4}
5
6for key, tup in data.items():
7 for item in tup:
8 print(item)
6.2.6. for-each with break
Breaking is also possible inside a for-each loop.
1numbers = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
2
3for number in numbers:
4 if number == 3:
5 print('found 3')
6 break
6.2.7. Exercise
Loop through the following list and break after the third odd number is encountered.
1numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
Solution.
1numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
2
3total_odds = 0
4
5for number in numbers:
6 if number % 2 != 0:
7 total_odds += 1
8 if total_odds == 3:
9 break
6.2.8. for-each with continue
When used inside a for-each
or while
loop, the continue
command forces the logic back to the start of the code block inside the loop; all code below the continue
are skipped. The code below iterates through each integer a list and skips printing the integer if it is odd.
1numbers = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
2
3for number in numbers:
4 if number % 2 != 0:
5 continue
6 print(number)
7
6.2.9. Exercise
Loop through the following list of names and skip printing names that do not start with a j
. Use the continue
command to achieve this request.
1names = ['jane', 'mary', 'josephine', 'nancy']
Solution.
1names = ['jane', 'mary', 'josephine', 'nancy']
2
3for name in names:
4 if not name.startswith('j'):
5 continue
6 print(name)
6.2.10. for-each with enumeration
Sometimes, we want to access the corresponding index of an element as we are iterating through a collection. To get the index and element, we can pass the collection to the enumerate()
function.
1numbers = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
2
3for i, number in enumerate(numbers):
4 print(i, number)
Note that enumerate()
will return a tuple, and so we can loop over the elements as follows.
1numbers = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
2
3for tup in enumerate(numbers):
4 print(tup[0], tup[1])
Or, we can unpack the tuple inside the for-each
loop.
1numbers = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
2
3for tup in enumerate(numbers):
4 i, number = tup
5 print(i, number)
Typically, we prefer to unpack the tuple with the for-each
loop as for i, numbers in enumerate(numbers):
.
If we did not want to start the index at 0, then we can specify a starting index as follows.
1numbers = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
2
3for i, number in enumerate(numbers, 10):
4 print(i, number)
5
6# the code above will print
7# 10 0
8# 11 1
9# 12 2
10# and so on ...
6.2.11. Exercise
Loop through the following list of names and print the name only if the name starts with a j
and the associated index is odd.
1names = ['jane', 'mary', 'josephine', 'jack', 'nancy']
Solution.
1names = ['jane', 'mary', 'josephine', 'jack', 'nancy']
2
3for i, name in enumerate(names):
4 if name.startswith('j') and i % 2 != 0:
5 print(name)
6.2.12. Looping over two lists
What if we want to iterate over 2 collections at the same time? We can use the zip()
function. zip()
aligns the elements from 2 or more lists and creates a tuple for each aligned elements. We can create a list from zipping two lists as follows.
1names = ['Jack', 'John', 'Joe']
2ages = [18, 19, 20]
3
4persons = list(zip(names, ages))
5print(persons)
6
7# should print [('Jack', 18), ('John', 19), ('Joe', 20)]
The example below show the use of using a for-each
loop with zip()
. Note how we unpack the tuple elements generated by zip()
.
1names = ['Jack', 'John', 'Joe']
2ages = [18, 19, 20]
3
4for name, age in zip(names, ages):
5 print(name, age)
6.2.13. Exercise
Loop over the two lists below simultaneously using zip()
and print the name and age only if the age is even.
1names = ['Jack', 'John', 'Joe']
2ages = [18, 19, 20]
Solution.
1names = ['Jack', 'John', 'Joe']
2ages = [18, 19, 20]
3
4for name, age in zip(names, ages):
5 if age % 2 == 0:
6 print(f'{name}, {age}')
6.2.14. Looping with enumerate and zip
We can combine enumerate()
with zip()
as follows.
1names = ['Jack', 'John', 'Joe']
2ages = [18, 19, 20]
3persons = list(enumerate(zip(names, ages)))
4print(persons)
5
6# the above should print [(0, ('Jack', 18)), (1, ('John', 19)), (2, ('Joe', 20))]
7# note the result is a list of tuples, where each tuple has the index in the first
8# position and a tuple in the second position
Here is how we can use enumerate()
with zip()
with a for-each
loop.
1names = ['Jack', 'John', 'Joe']
2ages = [18, 19, 20]
3
4for i, (name, age) in enumerate(zip(names, ages)):
5 print(i, name, age)
6.2.15. Exercise
Loop through both lists below. Only print the name and age if the name starts with a J
, the index is odd and the age is odd.
1names = ['Mary', 'Jack', 'John', 'Nancy', 'Sam', 'Jeremy', 'Mark']
2ages = [18, 19, 21, 24, 26, 27, 32]
1names = ['Mary', 'Jack', 'John', 'Nancy', 'Sam', 'Jeremy', 'Mark']
2ages = [18, 19, 21, 24, 26, 27, 32]
3
4for i, (name, age) in enumerate(zip(names, ages)):
5 if i % 2 != 0 and age % 2 != 0 and name.startswith('J'):
6 print(f'{i}, {name}, {age}')
6.3. Comprehension
Comprehensions are a way to transform an existing collection into a new one using the for-each
loop. There are comprehensions to generate lists, sets and dictionaries.
6.3.1. List comprehension
The list comprehension
has the following syntax: [<expression> for <element> in <collection> <filter:optional>]
. The <expression>
is required and the <filter:optional>
is optional. The <expression>
is typically a transformation of the <element>
. Let’s say we have a list numbers = [1, 2, 3, 4]
and we want to transform this list into a new one where each element is multiplied by two. We can use a regular for-each
loop or a list comprehension.
1# do not create a list this way
2
3numbers = []
4for num in [1, 2, 3, 4]:
5 n = num * 2
6 numbers.append(n)
7
8# use a list comprehension
9numbers = [num * 2 for num in [1, 2, 3, 4]]
An example of using a filter is as follows. In this example, we transform each integer to a new number by multiplying it by 3 and we filter for for only odd-indexed numbers.
1numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
2nums = [number * 3 for i, number in numbers if i % 2 != 0]
6.3.2. Exercise
Transform the list below into a new one with all boolean values, where each element in the new list indicates if the corresponding element in the existing list is even True
or odd False
.
1numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
1numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
2nums = [True if number % 2 == 0 else False for number in numbers]
6.3.3. Nested list comprehension
List comprehensions can also be nested as in the example below. Note that we have a list of lists, which represents a two-dimensional matrix. So we have to iterate over the rows and then the columns in that row. The result is that we end up with a flattened
list (the new list is not like the first list, which is a list of lists).
1matrix = [[1, 2], [3, 4], [5, 6]]
2
3flattened = [col for row in matrix for col in row]
4
5# flattened = [1, 2, 3, 4, 5, 6]
6.3.4. Exercise
Flatten the matrix below using a nested list comprehension, but, only include those elements whose row and column indices are equal.
1matrix = [[1, 2, 3], [3, 4, 5], [5, 6, 7]]
Solution.
1matrix = [[1, 2, 3], [3, 4, 5], [5, 6, 7]]
2diagonal = [col for i, row in enumerate(matrix) for j, col in enumerate(row) if i == j]
6.3.5. Set comprehension
Set comprehensions
are just like list comprensions, except, instead of using []
to setup the comprehension, we use {}
. The set comprehension
has the following syntax: {<expression> for <element> in <collection> <filter:optional>}
.
1# do not create a set in this way
2
3s = set()
4for name in ['Jack', 'John', 'Joe', 'Mary']:
5 n = len(name)
6 s.add(n)
7
8# create a set in this way, using a set comprehension
9s = {len(name) for name in ['Jack', 'John', 'Joe', 'Mary']}
6.3.6. Exercise
Find all the unique odd numbers in the following list of numbers.
1numbers = [1, 2, 3, 4, 5, 2, 3, 5, 1, 1]
Solution.
1numbers = [1, 2, 3, 4, 5, 2, 3, 5, 1, 1]
2nums = {number for number in numbers if number % 2 != 0}
6.3.7. Dictionary comprehension
A dictionary comprehension
is used to generate a new dictionary. The dictionary comprehension
has the following syntax: {<key>: <expression> for <element> in <collection> <filter:optional>}
.
1# do not create a map in this way
2names = ['Jack', 'John', 'Joe', 'Mary']
3
4m = {}
5for name in names:
6 m[name] = len(name)
7
8
9# create a map in this way, using a map comprehension
10names = ['Jack', 'John', 'Joe', 'Mary']
11
12m = {name: len(name) for name in names}
6.3.8. Exercise
Below is a list of names. Note that the elements are tuples, where the first element in the tuple is the first name and the second element is the last name. Transform the list into a dictionary where they key is the initials of each name and the corresponding value is the full name.
1names = [('Jack', 'Washington'), ('John', 'Doe'), ('Joe', 'Black'), ('Mary', 'Swanson')]
Solution.
1names = [('Jack', 'Washington'), ('John', 'Doe'), ('Joe', 'Black'), ('Mary', 'Swanson')]
2persons = {f'{fname[0]}.{lname[0]}.': f'{fname} {lname}' for fname, lname in names}
3print(persons)
4
5# should print {'J.W.': 'Jack Washington', 'J.D.': 'John Doe', 'J.B.': 'Joe Black', 'M.S.': 'Mary Swanson'}
6.3.9. Generator comprehension
With list, set and dictionary comprehensions, the resulting data is evaluated and created immediately. The generator comprehension
, however, only evaluates the expression as needed. The former comprehensions are eagerly
evaluated and the latter comprehension is lazily
evaluated. If we need to generate a new collection that does not need to be stored, use a generator comprehension as intermediary results will not be stored. The syntax for a generator comprehension is nearly identical to the list comprehension, except that we use ()
instead of []
: (<expression> for <element> in <collection> <filter:optional>)
1# do not do this, it's not memory efficient
2x = (10, ) * 100000
3y = [num * 2 for num in x]
4
5# do this instead, use a generator comprehension
6x = (10, ) * 100000
7y = (num * 2 for num in x)
6.3.10. Exercise
Write a program to ask the user to input a number over and over (until the user requests to quit by entering q
to quit). Each time a user enters a number, whatever that number is, compute the list of numbers that results when multiplying it by 1, 2, 3, 4, 5, 6, 7, 8, 9, 10.
Solution.
1# we use a tuple for the collection
2# why? because we do not want this collection to ever be modified
3# we define this collection of numbers outside the while loop
4# why? because we only need to define it once, not over and over inside the loop
5multipliers = (1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
6
7while True:
8 user_input = input('enter a number or "q" to quit: ')
9
10 if user_input == 'q':
11 break
12
13 num = int(user_input)
14 numbers = [n * num for n in multipliers]
15 print(numbers)