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 this page on Colab

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

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)

Hide code cell output

0 0
0 1
0 2
1 0
1 1
1 2

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')

Question 3 (medium): write a nested loop#

Write a nested loop that displays this rectangle of stars:

*****
*****
*****

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)

Hide code cell output

be
 b
 e
or
 o
 r

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):
    ...

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)

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):
    ...

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