Tables of numbers
Problem
Three weeks, three lists
A coffee shop records how many coffees it sold each workday. For one week, the list from the last lesson is exactly what you need. For three weeks you make three lists:
week1 = [42, 38, 51, 47, 60]week2 = [45, 40, 49, 52, 63]week3 = [39, 44, 50, 48, 58]
tuesday = week1[1] + week2[1] + week3[1]print(f"Tuesday: {tuesday}")Tuesday: 122Now the manager asks how much was sold on all the Tuesdays of the past year. That is 52 weeks, so 52 lists and a sum with 52 terms. And a loop cannot go through week1, week2, week3: those are different names, not indexes.
The data is a table: the weeks are rows, the days are columns. You need a list that is a table.
The idea
A list of lists: row and column
A table in Python is a list of lists: each element of the outer list is a whole row, and a row is a regular list of numbers. In Java and C# this is called a two-dimensional array, and often a matrix.
sales = [ [42, 38, 51, 47, 60], [45, 40, 49, 52, 63], [39, 44, 50, 48, 58],]print(sales[1][0])print(f"{len(sales)} weeks, {len(sales[0])} days")453 weeks, 5 days- Each inner
[ … ]is one row. We write them one under the other, so the table is easy to see. sales[1][0]is the row with index 1 (the second week) and the column with index 0 (Monday). The row comes first, then the column. Counting starts at 0 here too.len(sales)is the count of rows.len(sales[0])is the length of the first row, that is, the count of columns.
sales[1] is a whole row, a regular list with 5 numbers. That is why you write two separate brackets [1][0]: first you get the row, then the number in it.
sales = [ [42, 38, 51, 47, 60], [45, 40, 49, 52, 63], [39, 44, 50, 48, 58],]print(sales[1])print(sales)[45, 40, 49, 52, 63][[42, 38, 51, 47, 60], [45, 40, 49, 52, 63], [39, 44, 50, 48, 58]]You make an empty 52 × 5 table full of zeros row by row: 52 times you add a new row of 5 zeros.
table = []for week in range(52): table.append([0] * 5)table[0][1] = 7print(len(table), len(table[0]), table[0][1], table[1][1])52 5 7 0More precisely, if you are curious
- Since the rows are separate lists, they can have different lengths. Such a table is called jagged:
shifts = [ [8, 8], [6, 6, 6], [12],]print(len(shifts[1]), len(shifts[2]))3 1In it, len(sales[0]) is not the length of every row. That is why in a nested loop it is safer to write len(sales[row]): the length of the row you are going through right now.
- Don't make the empty table with
[[0] * 5] * 3. That way all three places point to one and the same row, like the two names of one list from the last lesson. A change in one row shows in all of them:
table = [[0] * 5] * 3table[0][1] = 7print(table)[[0, 7, 0, 0, 0], [0, 7, 0, 0, 0], [0, 7, 0, 0, 0]]See it
The whole table on the screen
To go through every cell, you need two loops, one inside the other. You already know nested loops from the stars: the outer one picks the row, the inner one goes through the columns of that row.
for week in range(len(sales)): for day in range(len(sales[0])): print(f"{sales[week][day]:5}", end="") print() 42 38 51 47 60 45 40 49 52 63 39 44 50 48 58The outer loop runs 3 times, once for each week. On each of its rounds the inner one runs 5 times. 15 cells in total, in the order you read a table: left to right, top to bottom.
:5 in the f-string prints the number in a field 5 characters wide, aligned to the right. That way the columns line up one under the other. end="" keeps the numbers on one line, and the empty print() after each row moves to a new one.
Try it
Which cell?
In the grade book each row is a student, and each column is a subject.
What will the program print?
grades = [ [5, 3, 4, 6], [3, 4, 6, 5], [6, 2, 5, 4],]print(grades[1][2])The first index is the row: 1 is the second student, [3, 4, 6, 5]. The second index is the column: 2 is the third subject. The grade is 6. You get 2 if you mix up the row and the column, and 3 if you count from 1.
The idea
Sums by rows and by columns
How many coffees were sold each week? That is the sum of each row. The sum starts at 0 for each new row, so total is inside the outer loop:
for week in range(len(sales)): total = 0 for day in range(len(sales[0])): total += sales[week][day] print(f"Week {week + 1}: {total}")Week 1: 238Week 2: 249Week 3: 239sales = [ [42, 38, 51, 47, 60], [45, 40, 49, 52, 63], [39, 44, 50, 48, 58],]for week in range(len(sales)): total = 0 for day in range(len(sales[0])): total += sales[week][day] print(f"Week {week + 1}: {total}")Step by step, in words
- The table: 3 weeks (rows) of 5 days (columns).
- week = 0: total starts over, from 0.
- The inner loop goes through the days: 42 + 38 + 51 + 47 + 60 = 238.
- It prints "Week 1: 238".
- week = 1: total starts over, from 0.
- The inner loop goes through the days: 45 + 40 + 49 + 52 + 63 = 249.
- It prints "Week 2: 249".
- week = 2: total starts over, from 0.
- The inner loop goes through the days: 39 + 44 + 50 + 48 + 58 = 239.
- It prints "Week 3: 239".
How much was sold on Monday, on Tuesday, and so on? That is the sum of each column. The table is the same, only the order of the loops changes: the day is on the outside, the weeks are on the inside. You will finish it on the next card.
Which loop is on the outside decides what you add up. The indexes in sales[week][day] don't move: the row always comes first.
Try it
Sum by days
The outer loop goes through the days, the inner one through the weeks. What goes in the blank?
days = ["Mon", "Tue", "Wed", "Thu", "Fri"]
for day in range(len(sales[0])):
total = 0
for week in range(len(sales)):
total += sales[][day]
print(f"{days[day]}: {total}")week. The row is the week, so week comes first. The day stays in place while the inner loop goes through the three weeks. For Tuesday you get 38 + 40 + 44 = 122, the same as on the first card, but without 52 names.
Try it
Find the bug
The hall has 3 rows of 5 seats: 1 is taken, 0 is free. There are 7 free seats, but the program says 5. Which line has the bug?
Pick the line with the bug.
The inner loop is for the seats in the row, and their count is len(hall[0]) (5). len(hall) is the count of rows (3), so the last two seats in each row are never checked. Python cannot see a bug like this: both are numbers, and the program runs.
The idea
Booking: people count from 1
A moviegoer wants row 3, seat 2. For them the first row is row 1. For the list it is row 0. That is why you subtract 1 from the numbers a person types before you use them as indexes:
row = int(input())seat = int(input())if hall[row - 1][seat - 1] == 1: print("The seat is taken.")else: hall[row - 1][seat - 1] = 1 print(f"Booked: row {row}, seat {seat}")32Booked: row 3, seat 212The seat is taken.hall is the hall from the previous card. You change a cell like a regular variable: hall[row - 1][seat - 1] = 1.
More precisely, if you are curious
- If someone asks for row 7, index 6 is outside the table and the program stops with
IndexError, just like with a regular list. It is trickier if they ask for row 0: the index is -1, and in Python that is the last row. There is no error, and the wrong seat gets booked:
hall = [ [1, 1, 0, 0, 1], [0, 1, 1, 1, 0], [0, 0, 0, 1, 1],]try: print(hall[7 - 1][2 - 1])except IndexError as error: print(f"IndexError: {error}")print(hall[0 - 1])IndexError: list index out of range[0, 0, 0, 1, 1]So check the numbers before you use them: 1 <= row <= len(hall), and the same for the seat.
- Here each number is on its own line. How to read a whole line of numbers at once (
0 1 1 0), you will see in the next lesson, on text.
Exercise
Working with a table
Solve the exercises in your editor. Write the tables straight into the code.
1. Diagonals and rows. For the 2 × 2 table below, find the sum along the main diagonal (from top left down to bottom right), along the other diagonal, and the sums of the first and the second row. On the main diagonal the row index and the column index are equal. On the other diagonal the column is n - 1 - row.
1 23 4Main diagonal: 5Other diagonal: 5First row: 3Second row: 72. Flip the table. The sales are recorded by weeks (rows) and days (columns). Accounting wants them by days and weeks. Make a new table in which the rows become columns. This is called transposing. The new table has the size columns × rows, and cell [r][c] goes to [c][r].
42 38 51 45 40 49 42 45 38 40 51 49Exercise
Mazes and treasure
3. How many paths are there? The maze is a table: 0 is a passage, 1 is a wall. You start from the top left corner and get to the bottom right one, moving only right or down. How many different paths are there?
0 0 0 00 1 0 00 0 0 11 0 0 0Paths: 3Hint
Make a second table ways with the same size. In it, write how many ways there are to reach each cell. There is 1 way to reach the start. There are 0 ways to reach a wall. Every other cell is reached either from above or from the left, so the count is the sum of the cell above it and the cell to its left (if they exist). Fill in the table row by row, from top to bottom. The answer is in the last cell.
4. The shortest path. A hard one. Now you can move in all four directions: up, down, left and right. What is the smallest number of steps to get from the top left to the bottom right corner? If there is no path, print "No path".
0 0 0 0 01 1 1 1 00 0 0 0 00 1 1 1 10 0 0 0 0Shortest path: 16 stepsHint
Send out a "wave" from the start. Make a table dist full of -1 (we haven't reached that cell yet), and put 0 at the start. Then repeat: find all the cells whose dist equals the current step, and for their free neighbors that we haven't reached yet, write step + 1. The wave grows by one step on every round. Stop when it reaches the goal or when nothing has changed (then there is no path). The four directions are handy as two lists: dr = [-1, 1, 0, 0] and dc = [0, 0, -1, 1]. The neighbor in direction k is at row r + dr[k] and column c + dc[k].
More exercises
- Find the treasure. A game on a map that is a table: 0 is a path, 1 is an obstacle, 2 is the treasure. The player starts in the top left corner. On each move, print the map (
Pfor the player,#for an obstacle,.for a path) and read a command as a number: 8 up, 2 down, 4 left, 6 right (as on the number pad). If the move leads off the map or into an obstacle, the player stays in place. When they step on the treasure, print "You found the treasure in … moves!". - The row with the most sales. For the table
sales, find which week was the strongest and which day of the week is the strongest on average. - Free seats in a row. For the hall
hall, find a row that has at least 3 free seats next to each other, and print the number of the row and of the first seat.
Test