9b. Prof. Rosenthal’s Additions#
Additions to Chapter 9: Lists: the nested loop, a loop inside another loop. The book uses nested loops, but it does not explain them. With lists, nested loops are more useful: for a nested list (a list that contains lists, Chapter 9), the outer loop takes one inner list at a time, and the inner loop takes the elements of that list. Read this page after the chapter. The practice questions at the end have answers: try each question first, then open its Answer.
Run the setup cell below first. In Colab, it downloads the file words.txt (the word list that
Chapter 7 uses), which one of the examples on this page reads.
from os.path import basename, exists
def download(url):
filename = basename(url)
if not exists(filename):
from urllib.request import urlretrieve
local, _ = urlretrieve(url, filename)
print("Downloaded " + str(local))
return filename
download('https://raw.githubusercontent.com/AllenDowney/ThinkPython/v3/words.txt');
What is a nested loop?#
A loop inside another loop is called a nested loop. The outer loop runs as usual, but each time it runs its body once, the inner loop runs all of its passes, from start to finish.
The indentation shows which loop a statement belongs to: statements indented once are in the outer loop; statements indented twice are in the inner loop.
for i in [1, 2]:
print('outer loop, i =', i)
for j in ['a', 'b']:
print(' inner loop, j =', j)
outer loop, i = 1
inner loop, j = a
inner loop, j = b
outer loop, i = 2
inner loop, j = a
inner loop, j = b
The outer loop runs twice, with i equal to 1 and then 2. Each time the outer loop runs
its body once, the inner loop runs twice, with j equal to 'a' and then 'b'. So the
inner print runs 4 times in total: 2 outer passes times 2 inner passes.
Example 1: a multiplication table#
Nested loops are the natural way to fill in a grid of values, with rows and columns. The outer loop chooses the row, and the inner loop chooses the column.
for i in range(1, 4):
for j in range(1, 4):
print(i * j, end=' ')
print()
1 2 3
2 4 6
3 6 9
For each row i, the inner loop prints i * 1, i * 2 and i * 3 on one line (end=' '
puts a space after each product instead of a newline). Then print() with no arguments ends
the row and moves to the next line. The table has 3 rows because the outer loop runs 3 times.
Example 2: the words in a file, and the letters in each word#
Chapter 12 will loop through the lines of a book and, inside that loop, through the words in each line. The same pattern works with the word list: the outer loop takes one line of the file at a time, and the inner loop takes one letter of the word at a time.
count = 0
for line in open('words.txt'):
word = line.strip()
print(word + ':')
for letter in word:
print(' ' + letter)
count += 1
if count == 2:
break
aa:
a
a
aah:
a
a
h
Example 3: every pair from two lists#
To combine every element of one list with every element of another list, loop through the first list and, inside that loop, loop through the second list.
colors = ['red', 'blue']
shapes = ['circle', 'square']
for color in colors:
for shape in shapes:
print(color, shape)
red circle
red square
blue circle
blue square
Example 4: counting with an inner loop#
The counter pattern from Chapter 7 works inside a nested loop, too. Here the outer loop takes
one word at a time, and the inner loop counts the vowels in that word. Notice that count
is set back to 0 for each word: the counting starts over inside the outer loop, before the
inner loop runs.
for word in ['apple', 'sky', 'hello']:
count = 0
for letter in word:
if letter in 'aeiou':
count += 1
print(word, 'has', count, 'vowels')
apple has 2 vowels
sky has 0 vowels
hello has 2 vowels
Example 5: a nested list#
Chapter 9 shows that a list can contain other lists: a nested list. A nested list can hold a table, with
one inner list for each row. Here, each row holds the test scores of one student. The outer loop takes one
row at a time, and the inner loop adds up the scores in that row. As in Example 4, total is set back to
0 for each row.
scores = [[90, 85, 77], [60, 75], [100, 95, 98, 91]]
for row in scores:
total = 0
for score in row:
total += score
print(row, 'total:', total)
[90, 85, 77] total: 252
[60, 75] total: 135
[100, 95, 98, 91] total: 384
Diving deeper: give the two loops different variable names
The outer and inner loops each have their own loop variable, and both variables exist at the same time. If you use the same name for both, the inner loop overwrites the outer loop’s variable, which is confusing:
for i in range(2):
for i in range(3):
print(i, end=' ')
print('-> after the inner loop, i is', i)
The inner loop sets i to 0, 1, 2, so when the inner loop finishes, i is 2, not the
outer loop’s value. (The outer loop still runs twice, because range(2) gives i a new value
at the start of each pass, but the code is needlessly hard to read.) Use different names for
the two loop variables, like i and j, or word and letter.
Practice questions#
Question 1 (easy): what is displayed?#
Predict what this code displays. Then run the cell to check (on the website, click Show code cell output).
for i in range(2):
for j in range(3):
print(i, j)
Answer
0 0
0 1
0 2
1 0
1 1
1 2
The outer loop runs twice. Each time it runs its body once, the inner loop runs 3 times, so
the inner print runs 2 times 3 = 6 times in total.
Question 2 (easy): how many times?#
How many times does the body of the inner loop run here?
for i in range(4):
for j in range(5):
print('hi')
Answer
20 times. The outer loop runs 4 times, and the inner loop runs 5 times for each outer pass, so 4 times 5 = 20. In general, multiply the number of outer passes by the number of inner passes.
Question 3 (medium): write a nested loop#
Write a nested loop that displays this rectangle of stars:
*****
*****
*****
Answer
for i in range(3):
for j in range(5):
print('*', end='')
print()
The outer loop runs 3 times, once per row. The inner loop prints 5 stars on one row
(end='' puts nothing after each star, so they stay together). Then print() with no
arguments ends the row and moves to the next line.
Question 4 (medium): what is displayed?#
Predict what this code displays. Then run the cell to check (on the website, click Show code cell output).
for word in ['be', 'or']:
print(word)
for letter in word:
print(' ' + letter)
Answer
be
b
e
or
o
r
The outer loop takes one word at a time. For each word, it prints the word, and then the inner loop prints each of its letters, indented by one space.
Question 5 (hard): write a function#
Write a function called total_letters that takes a list of words (strings) and returns the
total number of letters in all of the words. For example, total_letters(['be', 'or'])
returns 4.
Start from this header (replace ... with the body):
def total_letters(word_list):
...
Answer
def total_letters(word_list):
total = 0
for word in word_list:
for letter in word:
total += 1
return total
The outer loop takes one word at a time; the inner loop takes one letter of that word at a
time and adds 1 to the total. Unlike Example 4, the counter is set to 0 once, before both
loops, because it counts across all the words.
Question 6 (hard): find the bug#
This loop is supposed to display the pairs (0, 0), (0, 1), (1, 0), (1, 1), but the
output is confusing. What is wrong, and how do you fix it?
for i in range(2):
for i in range(2):
print(i, i)
Answer
Both loops use the same variable name, i, so the inner loop overwrites the outer loop’s
variable. The fix is to give the inner loop its own name:
for i in range(2):
for j in range(2):
print(i, j)
See “Diving deeper: give the two loops different variable names” above.
Question 7 (hard): write a function#
Write a function called count_above that takes a nested list of numbers, table, and a number limit,
and returns how many of the numbers in table are greater than limit. For example,
count_above([[90, 85, 77], [60, 75], [100, 95]], 80) returns 4, and count_above([[1, 2], [3]], 5)
returns 0.
Start from this header (replace ... with the body):
def count_above(table, limit):
...
Answer
def count_above(table, limit):
count = 0
for row in table:
for number in row:
if number > limit:
count += 1
return count
The outer loop takes one row (an inner list) at a time, and the inner loop takes one number of that row.
As in Question 5, the counter is set to 0 once, before the two loops, because it counts in all the rows.
Credits#
Additions by Prof. Y. Rosenthal, for Think Python: 3rd Edition by Allen B. Downey. Text license: Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International