← back to CS 115
Week 7257 min full read
7 concepts19 worked examples30 exercises4 exam-level7 figures
What are you here for?

07 Arrays and Multi-Dimensional Arrays

Start with this

One question before you read anything. Getting it wrong is the point: it shows you what this section is for.

§07.0 — a list whose items are lists, before any rule about it

Three questions before the new material, to find out which old part is worth rereading first. Not knowing them is not a problem; each one says where to look. Here is the first, and nothing in it is new: the list simply happens to hold lists.

pairs = [[1, 2], [3, 4], [5, 6]]
print(len(pairs))
print(pairs[1])
print(pairs[1][0])
Find(a) Write the three lines this prints.
Given
  • The list holds three items.

  • Each of those items is a list of two numbers.

IPython console
Hint 1/4

Answer the three prints separately, and for each one ask which level of the structure is being asked about: the outer list or one of the inner ones.

Hint 2/4

len counts the items of whatever it is handed. pairs[1] is the item at position 1 of the outer list, and a second bracket then indexes that item.

Hint 3/4

The items are [1, 2], [3, 4] and [5, 6], so the item at position 1 is the middle one.

Hint 4/4

The first line is a single digit, the second shows brackets, and the third does not.

Show solution

Answer each print by its bracket count rather than picturing the whole nested structure first, which takes longer and settles nothing.

Count at the outer level

$$\texttt{len(pairs)} = 3$$

Three items, each of them a list. The six numbers are one level further down and len never goes there.

$$\texttt{pairs[1]} = \texttt{[3, 4]}$$

Position 1 is the second item, because counting starts at 0. It prints with brackets because it is a list.

$$\texttt{pairs[1][0]} = 3$$

The second bracket indexes the list just obtained, so this is the first number of the middle pair.

Answer $$\boxed{3,\;\texttt{[3, 4]},\;3}$$
Check

Independent check: the brackets on the second printed line are what allow the third line to use two of them.

What this looks like in Spyder
The Variable Explorer tab showing a table with Name, Type, Size and Value columns for the names the program created.

The tab next to Help. It lists every name your program left behind with its type and its size, so it answers the one question a listing cannot: what is in this name right now. The stepper on this page shows the same four .

Spyder 5.5.1 on Linux, running the program in its own editor. A different version moves a few toolbar icons; the panes are named the same.

A script has to print the seating plan of an exam room, six seats across and six deep, so it builds the empty plan in one line: grid = [['-'] 6] 6. Then it writes one name into the front left seat with grid[0][0] = 'A' and prints the plan. The letter comes out in the front seat of every row, and no line of the script ever touched the other five rows.

By the end of this section you can build a of any size without the trap above, walk it by rows or by columns, say for any short program with two index brackets in it exactly what it prints, and write the two shapes of function the lab and the exam ask for: one that changes a table in place and one that hands back a new one.

In 60 seconds

A table is a list whose items are lists, so a row is table[r], a cell is table[r][c], and every question about a table comes down to which index you put on the outer loop and whether the rows you are looking at are separate objects or one object seen several times.

Two brackets, outer index first
$$\texttt{table[r][c]}\;=\;\text{item }c\ \text{of row }r$$

Every cell of a table. table[r] on its own is a whole row and is itself a list, which is why a second bracket may follow it.

The two lengths are different questions
$$\texttt{len(table)}\;=\;\text{rows},\quad\texttt{len(table[r])}\;=\;\text{cells in row }r$$

Any loop bound. The number of columns is len(table[0]) and that number only describes the whole table when every row has the same length.

Repeating a row repeats the reference
$$\texttt{[[0]*3]*2}\;\Longrightarrow\;\text{two names for ONE row}$$

Building an empty table. Build it with a loop that appends a freshly made row each time round, and the rows are separate objects.

A column walk moves the row index
$$\text{column }c:\;\texttt{for r in range(len(table)):}\;\texttt{table[r][c]}$$

Any per column answer. The index that stays still is the one in the answer, so for one number per column the column index goes on the outer loop.

Three most common mistakes
  1. Building a table with [[0] cols] rows. That makes one row and then a list holding the same row rows times, so writing one cell appears to write a whole column.

  2. Reading a table with table[r, c]. The comma builds a tuple, and a list refuses to be indexed by a tuple, so the program stops with a TypeError on that line.

  3. Starting the accumulator before the outer loop instead of inside it. The first column then comes out right and every column after it carries the previous columns, which is why this one is so often handed in.

Labs are 20 per cent of the course mark, the midterm 40 and the final 40. On the one past final paper read while writing this page, one of the four questions was a function over a table of strings returning one number per column, worth 20 marks. That is one paper rather than a rule, but a table question either works or does not, so the loop bounds are worth more than the idea.

How much time do you have?
10 minutes

The two brackets with the two lengths, and the one line that silently builds a table with one row in it. Those two cover the cheapest marks and the most expensive mistake.

The 60-second card · A table is a list whose items are lists · Building a table of a size you only learn while running, and the row that was secretly one row · Formula card
45 minutes

Everything a table answer in the lab needs: build it, walk it by rows, walk it by columns, and the per column skeleton that the exam question is built on.

The 60-second card · A table is a list whose items are lists · Two nested loops, and which of the two you are allowed to write through · Building a table of a size you only learn while running, and the row that was secretly one row · Walking a column, and what a column means when the rows are not the same length · Scaffolding comes off · B · computation
full read

Adds the three parts a whole lab question needs and the tracing question likes: building a new table from an old one instead of changing it, reading a table in from the keyboard or a file and printing it so the columns line up, and finding a cell and handing back where it was.

The 60-second card · Recall first · Conventions · A table is a list whose items are lists · Two nested loops, and which of the two you are allowed to write through · Building a table of a size you only learn while running, and the row that was secretly one row · Walking a column, and what a column means when the rows are not the same length · Changing a table in place against handing back a new one · Getting a table in from the keyboard or a file, and printing it so the columns line up · Finding a cell and reporting where it was · Method boxes · Look-alike pairs · Scaffolding comes off · Full exam-style question · A · concept · B · computation · C · exam level · D · interleaved · Mistake ledger · Formula card · Check yourself
By the end of this section
  1. Read a cell, a whole row and the two lengths out of a table, and say for any of the three which level of the structure the answer came from.

  2. Trace a program with two nested loops over a table and write down its exact output, including where the line endings fall.

  3. Build a table whose size is only known while the program is running, filling it from a rule on the row and column numbers, and avoid the repeated row.

  4. Compute one number per row or one number per column of a table, and decide which loop bound is safe when the rows are not all the same length.

  5. Distinguish a function that changes the table it is given from one that returns a new table, write either on demand, and copy a table so that its rows are independent.

  6. Load a table from the keyboard or from a file, converting each part as it arrives, and print the result so that the columns line up.

  7. Locate a cell in a table and hand back its position as a pair, together with an answer for the case where there is nothing to find.

Syllabus coverage

Arrays — covered

A list used as an array of values of one kind: building one of a fixed size with the star operator, reaching an item by index, and the fact that Python has no separate array type.

The one dimensional case was covered as lists last section, so this page spends its space on what the second bracket adds.

Multi-Dimensional Arrays — covered

A list whose items are lists

  • the two brackets
  • the two lengths
  • building a table at run time
  • the repeated row trap
  • the two nested loop shapes
  • per row and per column answers
  • ragged rows
  • in place against returning a new table
  • copying one safely
  • reading one in and printing it aligned
  • searching it

Tables of three or more dimensions — off syllabus

A list of tables, reached with three brackets, which the same rules describe with one more loop.

Named once in the notation block and nowhere else: the lecture and the lab both stop at two brackets, so the third is mentioned only so it is not mistaken for a new idea.

Recall first
A name holds a reference, not a copy

For a list, b = a makes a second name for one object. Changing the object through either name is visible through both, and a is b is True. A separate object takes a[:] or list(a).

Everything surprising in this section is this rule applied one level down, where the object being shared is a row rather than the table. The repeated row trap and the half copied table are both this sentence.

Indexing and assigning to an item of a list

For a list L, L[i] reads item i, L[i] = v replaces it, and len(L) is how many items there are, so the last index is len(L) - 1. Reading or writing past that is an IndexError.

A table uses this twice per cell, once on the table and once on a row, and the IndexError message is the one a produces.

append changes the list and hands back None

L.append(e) adds one item to the end of L and its value is None, so L = L.append(e) loses the list. L.append([1, 2]) adds one item that happens to be a list.

Tables are built by appending rows, so the item being appended is itself a list, and the None rule reappears for functions that change a table in place.

for over a range against for over a sequence

for x in L: hands over the items, and for i in range(len(L)): hands over the positions, so L[i] is the item. Only the second lets you assign to L[i].

The two loop shapes of this section are exactly these two, doubled. Which one a table walk needs is decided by whether a cell appears on the left of an equals sign.

A tuple carries several values as one

return (r, c) hands back one object holding two numbers, and the caller can print it or take it apart with pair[0] and pair[1]. A tuple cannot be changed after it is built.

A position in a table is two numbers, so a search returns a tuple. This is the section where the reason for tuples becomes concrete.

strip, split and the conversions

line.strip() hands back the line without the spaces and the newline at its ends, line.split(',') hands back a list of the pieces between the commas, and int(piece) turns one piece into a number. All three return something new and change nothing.

One line of a file becomes one row of a table with exactly these three, in this order.

print with end, and format with a

print(x, end='') writes x and does not end the line, a bare print() ends the line and writes nothing else, and format(n, '5d') gives a five character string holding the whole number n right aligned.

Every printed table on this page is these three together: the field width makes the columns, the empty ending keeps the row on one line, and the bare print ends it.

Try it yourself first (2 questions)
1§07.0 — a second name against a copy

The second one, and this is the trap of the three. Two names are made for a list, one by assignment and one by a full slice.

a = [1, 2]
b = a
b.append(3)
print(a, b)
c = a[:]
c.append(4)
print(a, c)
Find(a) Write the two lines this prints.
Given
  • b was made with an assignment and c with a full slice.

  • One item is appended through each of them.

IPython console
Hint 1/4

For each of the two appends, ask how many list objects exist at that moment. The printed lines follow from the count.

Hint 2/4

An assignment copies a reference and builds no object. A full slice builds a new list holding the same items.

Hint 3/4

So b and a are one list when the 3 is appended, and c is a separate list, made from [1, 2, 3], when the 4 is appended.

Hint 4/4

The first line shows the same three numbers twice, and on the second line only one of the two lists has four items.

Show solution

Count the list objects before following either append, or the two lines become a guess about which name the 3 landed in.

Count the objects before each append

$$\texttt{b = a}\;\Rightarrow\;\text{one list, two names}$$

Nothing is built, so the append has only one place to go and both names show it.

$$\texttt{c = a[:]}\;\Rightarrow\;\text{two lists}$$

The slice copies the three items into a new list, so the 4 lands in the new one only.

Answer $$\boxed{\text{alias: both grow};\quad\text{clone: only the clone grows}}$$
Check

Independent check without appending anything: a is b is True and a is c is False, which is the same fact stated before any change is made.

2§07.0 — the bounds of a range over positions

The third, on loop bounds, which is where a table walk goes wrong most often.

letters = ['p', 'q', 'r', 's']
for i in range(1, len(letters)):
    print(i, letters[i], end='  ')
print()
print(len(letters) - 1, letters[len(letters) - 1])
Find(a) Write the two lines this prints.
Given
  • The list has four items.

  • The loop starts at 1 rather than at 0.

IPython console
Hint 1/4

List the values i takes before printing anything. Two of the four positions are involved in a way worth noticing.

Hint 2/4

range(a, b) gives the whole numbers from a up to but not including b, so range(1, 4) is 1, 2 and 3.

Hint 3/4

Here len(letters) is 4, the items are p, q, r and s, and the loop never sees position 0.

Hint 4/4

The first line has three pairs on it and p appears nowhere.

Show solution

Settle the positions range visits before reading any item, because every wrong answer here comes from the bounds and not the list.

List the positions the loop visits

$$\texttt{range(1, 4)} \rightarrow 1, 2, 3$$

The stop value is not included, so position 3 is the last one and position 0 is never reached.

$$\texttt{letters[3]} = \texttt{s}$$

The last item, since the four positions are 0 to 3. This is why the second printed line repeats the end of the first.

Answer $$\boxed{1\ \texttt{q},\ 2\ \texttt{r},\ 3\ \texttt{s};\ \text{then}\ 3\ \texttt{s}}$$
Check

Independent check by counting: three pairs printed for a list of four items, which is exactly one fewer because the start was moved from 0 to 1.

Notation
symbolreads asmeanswatch out
$\texttt{[[1, 2, 3], [4, 5, 6]]}$

a table of two rows and three columns

A list whose two items are lists of three numbers each. Written on one line it is what Python prints; written on two lines with one row each it is the same object laid out for a reader.

The outer brackets are the table and the inner ones are the rows. A missing inner pair, as in [1, 2, 3, 4, 5, 6], is a flat list of six and has no rows at all.

$\texttt{table[r][c]}$

row r, column c

Two lookups in a row. The first hands back row r, which is a list, and the second hands back item c of that list.

table[r, c] is not a longer way of writing it. The comma builds a tuple and a list refuses to be indexed by one.

$\texttt{len(table)}\ \text{and}\ \texttt{len(table[r])}$

the number of rows, and the length of row r

The first counts the items of the outer list, the second counts the items of one row. Multiplying them gives the number of cells only when all the rows have the same length.

Neither of them counts cells. len always counts the items one level down from what it is handed.

$\texttt{[0] * n}$

a row of n zeros

A new list of n items, all of them the number 0. This is the ordinary way to make a row of a fixed size, and it is safe.

Repeating a list of lists is not safe: [[0] n] m gives m references to one row. The star copies the items, and an item that is a list is a reference.

$\texttt{format(cell, '5d')}$

this whole number, right aligned in five characters

A string of exactly five characters holding the number, padded on the left with spaces, so that a column of them lines up on the right.

The letter d is for whole numbers. A float needs something like '7.2f', and handing a float to '5d' stops the program.

$\texttt{table[r][c][k]}$

the k th item of the cell at row r, column c

What three brackets would mean if the cells were lists themselves. Nothing here or in the lab uses it.

Three brackets are still three lookups, and are worth avoiding where a clearer structure would do.

Conventions used here
Every block of output on this page came out of an interpreter.

No output here was predicted by eye. Each program was written to a file, run, and the characters it printed were copied back in. Where a line ends in a space because the program used end=' ', the space is invisible on screen and is not shown; the answer checker on this page ignores spaces at the end of a line for the same reason.

In a programming course the printed answer is the whole claim.

What the word array means on this page.

Python has no separate array type of its own. Where this week says array, read list: a one dimensional array is a list, and a two dimensional array is a list whose items are lists. A library that adds a genuine array type comes later in the course and nothing on this page uses it.

The syllabus line uses the word array, and there is no array type in Python to go looking for.

The words for the parts of a table, and which index comes first.

A table has rows and columns; one value in it is a cell. The outer index is always the row and the inner one always the column, so table[2][0] is the first cell of the third row. Both counts start at 0, so a table with four rows has row indexes 0, 1, 2 and 3. The names r and c are used for the two indexes throughout.

Half the mistakes in a table question are the two indexes the wrong way round.

and ragged ones.

A table is rectangular when every row has the same length. Only then does len(table[0]) describe the whole table, and only then does a column mean anything. When rows may differ, every inner loop bound is len(table[r]), and a claim about columns has to be dropped or reworded. Each statement on this page says which kind it needs.

Nothing in Python stops rows differing in length, so a column walk that assumes otherwise is a crash waiting for the marker's data.

A Sample Run shows what the user typed.

Where a program calls input, the Sample Run shows the prompt and then, on the same line, the characters the user typed, exactly as a lab sheet does. The terminal echoes what is typed, which is why those characters are part of the picture even though the program never printed them.

Lab work is marked against a Sample Run in this shape.

What this page is allowed to use.

Everything here is built from what the course has covered by this week: numbers, text, True and False, input, print with end, format, if, while, for, range, len, def and return, the string operations, files, tuples, lists and dictionaries with their methods. Shorter ways of writing some of these loops exist and are not used, because the lab sheet says only material covered in the course may be used.

A solution that reaches past the course teaches you something the lab will not accept.

Which lab this section is for.

This page is the first half of the sixth lab, the half about : a function that builds a square table by a rule on the row and column numbers, and a main that reads a size, calls it, and prints the table in matrix form. The second half of that lab is about classes and belongs to the next section, so nothing here uses one.

The ten lab sheets carry 20 per cent of the mark between them, and half a sheet is still half a sheet.

7.1A table is a list whose items are lists

Two brackets instead of one: the first picks a row, the second picks a cell inside that row.

Every list so far held values. Nothing stops one holding lists instead, and that single change is this whole week.

Solvable with what we have
  • Total a list holding one student's marks.

  • Find the largest number in a list.

  • Reach any item of a list by index.

Not solvable yet
  • Hold three marks for each of four students and reach one of them.

  • Print a seating plan as six rows of six seats.

  • Average each quiz across the group, reading down rather than along.

The obvious move with one bracket is to lay all twelve marks out flat, three per student, and work out the index by hand.

marks = [55, 70, 62,
         90, 85, 78,
         40, 51, 66,
         73, 68, 80]
print('third student, second quiz :', marks[3 * 3 + 2])
third student, second quiz : 80
Why it fails

The third student's second mark is 51; the 80 belongs to the fourth. Both counts start at 0, so the index is really (3 - 1) * 3 + (2 - 1), and that has to be remembered everywhere. The 3 in it is the row width, so a fourth quiz breaks every index.

DefinitionDefinition 7.1: a table, and its two lengths
Conditions
  • A table is a list whose items are themselves lists. Each inner list is a row, and one value inside a row is a cell.

  • table[r] is row r, and because it is a list, a second bracket may follow: table[r][c] is the cell in row r, column c. The outer index is always the row.

  • len(table) is the number of rows. It is not the number of cells.

  • len(table[r]) is the number of cells in row r. When every row is the same length, that common length is the number of columns and len(table[0]) reports it.

  • Indexes start at 0, so a table with len(table) rows has row indexes 0 to len(table) - 1, and the same holds inside each row.

  • A negative index counts back as it does in any list, so table[-1] is the last row and table[-1][-1] the last cell of it.

  • Nothing requires the rows to be the same length, and nothing requires the cells to be numbers. A table of strings is as ordinary as a table of ints.

$$\boxed{\texttt{table[r][c]}:\;\text{first bracket}\rightarrow\text{row},\ \text{second}\rightarrow\text{cell};\quad \texttt{len(table)}=\text{rows},\ \texttt{len(table[r])}=\text{cells in row }r}$$

Read it left to right as two separate lookups. Take the table, ask for row r, and you are holding a list; ask that list for item c, and you are holding a value.

Looks like this, but is not

Mathematics writes a matrix entry with one bracket pair and two numbers, so the same shape ought to work.

table = [[4, 8, 15],
         [16, 23, 42]]
print(table[1, 2])
Traceback (most recent call last):
  File "table.py", line 3, in <module>
    print(table[1, 2])
          ~~~~~^^^^^^
TypeError: list indices must be integers or slices, not tuple

The comma builds a tuple, as it always does in Python, so the line asks the list for the item at position (1, 2). A list can only be indexed by a whole number or a slice, which is what the message says. Two brackets are two separate lookups.

The same table written two ways, and why they are equal but not the same

A table can be written out in one go, or grown a row at a time with append. Both appear in the lecture and they are worth comparing once.

table = [[0, 3, 0],
         [0, 0, 1],
         [2, 0, 3]]

other = []
other.append([0, 3, 0])
other.append([0, 0, 1])
other.append([2, 0, 3])

print(table)
print(other)
print('same contents :', table == other)
print('same object   :', table is other)

Sample Run:

[[0, 3, 0], [0, 0, 1], [2, 0, 3]]
[[0, 3, 0], [0, 0, 1], [2, 0, 3]]
same contents : True
same object   : False
FindWhat the two printed lines show about the two tables, and what the layout of the first one buys.
Given
  • Both tables hold the same nine numbers in the same places.

  • The second one was built by three calls to append.

Solution

Split the two questions the last lines ask; the word same has two meanings here, so one answer cannot serve both.

Read the printed form

$$\texttt{[[0, 3, 0], [0, 0, 1], [2, 0, 3]]}$$

This is what a table looks like when printed: one flat line of three lists. The layout in the source with one row per line is for the reader; Python never sees it.

$$\texttt{append([0, 3, 0])}$$

Appending a list adds one item to the outer list, and that item happens to be a list. Nothing about append changes for a table.

Separate the two questions the last two lines ask

$$\texttt{table == other}\;\rightarrow\;\texttt{True}$$

Equality on lists compares item by item, and comparing two rows compares their cells, so this is a full check of all nine values.

$$\texttt{table is other}\;\rightarrow\;\texttt{False}$$

Two separate objects were built, one by the literal and one by three appends, so no name reaches both. This is the same is against equals equals from the previous section, one level up.

Answer $$\boxed{\texttt{==}\ \text{True},\ \texttt{is}\ \text{False}:\ \text{equal contents, two objects}}$$
Check

Independent check: both tables print on one line and len is 3 for each, so the three source lines produced three items.

One statement against four, and the longer one is what you need when the number of rows arrives at run time.

Use the literal when you type the values yourself and append when a loop or a file produces them.

Reading sizes and single cells out of a four by three table

One table, five questions about it, each of them one line of code.

table = [[4, 8, 15],
         [16, 23, 42],
         [7, 1, 9],
         [5, 5, 5]]
print('rows         :', len(table))
print('row 0 length :', len(table[0]))
print('row 2        :', table[2])
print('cell 2 1     :', table[2][1])
print('last cell    :', table[-1][-1])

Sample Run:

rows         : 4
row 0 length : 3
row 2        : [7, 1, 9]
cell 2 1     : 1
last cell    : 5
FindThe five printed values, and which of them would change if a row were added.
Given
  • The table has four rows and every row has three cells.

  • Row indexes run from 0 to 3 and cell indexes from 0 to 2.

Solution

Group the five lines by what each hands back rather than reading top to bottom, so the two len calls sit side by side.

The two lengths are two different counts

$$\texttt{len(table)} = 4$$

The outer list holds four items, and each item is a row. The twelve numbers are one level down, so no count of them appears here.

$$\texttt{len(table[0])} = 3$$

This asks the length of one row. It is the number of columns only because the rows here happen to match, and the page says so wherever it relies on that.

One bracket gives a list, two give a value

$$\texttt{table[2]} = \texttt{[7, 1, 9]}$$

The printed brackets are the proof that a row is itself a list. If it were not, the second bracket in the next line would be a TypeError.

$$\texttt{table[2][1]} = 1$$

Row 2 is [7, 1, 9] and its item 1 is the middle one. Counting from 0 inside the row matters here: the answer is not 7 and not 9.

Negative indexes work at both levels

$$\texttt{table[-1][-1]} = 5$$

The first bracket takes the last row, the second takes the last cell of that row. Two lookups again, which is why two negatives are allowed.

Answer $$\boxed{4,\;3,\;\texttt{[7, 1, 9]},\;1,\;5}$$
Check

Size check: 4 rows times 3 cells is 12, and the source shows twelve numbers. A len(table) of 12 would have meant a flat list.

Whenever a table question starts, write down its two lengths before anything else. Most wrong answers in this section are a loop bound that used the other one.

Checkpoint
§07.1 — the two lengths and one cell

Thirty seconds. A table of four rows, two cells each.

table = [[3, 1], [4, 1], [5, 9], [2, 6]]
print(len(table), len(table[0]))
print(table[2][0] + table[3][1])
Find(a) Write the two lines this prints.
Given
  • The table is [[3, 1], [4, 1], [5, 9], [2, 6]].

  • The second line adds one cell from row 2 to one cell from row 3.

IPython console
Hint 1/4

Three separate questions are being printed: how many rows, how long one row is, and the sum of two named cells. Answer them one at a time.

Hint 2/4

len(table) counts rows and len(table[0]) counts the cells of row 0. In table[r][c] the first number is the row.

Hint 3/4

The rows are [3, 1], [4, 1], [5, 9] and [2, 6], so table[2] is [5, 9] and table[3] is [2, 6].

Hint 4/4

The first line is two small numbers separated by a space, and the second is a single number under twenty.

Show solution

Read each len at the level it asks about before touching the arithmetic, because the counts are where this question is lost.

Count at the right level

$$\texttt{len(table)} = 4,\ \texttt{len(table[0])} = 2$$

The outer list has four items; the first of them has two. Neither call goes looking for the total number of cells.

$$5 + 6 = 11$$

Row 2 is [5, 9] so cell 0 of it is 5, and row 3 is [2, 6] so cell 1 of it is 6.

Answer $$\boxed{\texttt{4 2}\ \text{then}\ \texttt{11}}$$
Check

Independent check: the number of cells would be 4 times 2, which is 8, and 8 appears nowhere in the output. That is the point of the first line.

⚠ Treating len(table) as the number of cells

With one bracket, len really did count the values, and nothing in the call says which level it is asking about.

wrong$$\texttt{len([[3, 1], [4, 1], [5, 9]])} = 6$$
right$$\texttt{len([[3, 1], [4, 1], [5, 9]])} = 3$$
⚠ Indexing with one bracket and a comma

It is how a matrix entry is written in every mathematics course, and it reads perfectly well.

wrong$$\texttt{table[1, 2]}\;\Rightarrow\;\text{TypeError}$$
right$$\texttt{table[1][2]}$$
⚠ Putting the column first

Graphs are read as x then y, and that habit puts the across number before the down number.

wrong$$\texttt{table[c][r]}$$
right$$\texttt{table[r][c]}$$

7.2Two nested loops, and which of the two you are allowed to write through

Walking values needs no indexes; writing into cells needs them, because a loop name cannot be written through.

Reaching one cell took two brackets, so reaching all of them takes two loops, and the course gives you two ways of writing them that are not interchangeable.

MethodMethod 7.2: the two shapes of a full walk
Conditions
  • Reading every cell: for row in table: and inside it for cell in row:. The names row and cell hold a row and a value, and no index appears anywhere.

  • Writing into every cell: for r in range(len(table)): and inside it for c in range(len(table[r])):, then assign to table[r][c]. The indexes are what the assignment needs.

  • The inner bound is len(table[r]) and not len(table[0]) unless the table is known to be rectangular.

  • The order is one row at a time, left to right in each row. That order is what a printed table looks like, and it is why the row loop is the outer one.

  • A statement in the outer loop body but after the inner loop runs once per row. That is where the bare print() goes to end the line.

  • Assigning to the loop name, as in row = something or cell = something, changes nothing in the table. It points the name somewhere else and the next turn of the loop overwrites it.

  • Assigning to an item of the loop name, as in row[c] = something, does change the table, because row and table[r] are two names for one list.

$$\boxed{\text{read: }\texttt{for row in table: for cell in row:}\qquad \text{write: }\texttt{for r in range(len(table)): for c in range(len(table[r])):}}$$

The outer loop hands you one row at a time and the inner loop walks that row. If all you do is look, ask for the values; if you mean to change a cell, ask for the two numbers instead.

Looks like this, but is not

The loop name stands for the row, so setting it should clear the row.

table = [[4, 8], [15, 16]]
for row in table:
    row = [0, 0]
print('after rebinding :', table)

for row in table:
    for c in range(len(row)):
        row[c] = 0
print('after writing   :', table)
after rebinding : [[4, 8], [15, 16]]
after writing   : [[0, 0], [0, 0]]

The first loop points the name row at a brand new list and then the loop moves on, so the table never hears about it. The second loop keeps row pointing where the loop put it, which is at table[r] itself, and writes into that object. One assignment moves a name, the other changes an object, and the difference is whether a bracket comes before the equals sign.

Printing a table as rows, and the one missing line that ruins it

First the version that works.

table = [[4, 8, 15],
         [16, 23, 42],
         [7, 1, 9]]
for row in table:
    for cell in row:
        print(cell, end=' ')
    print()

Sample Run:

4 8 15
16 23 42
7 1 9

Now the same program with the last print() taken out, which is the commonest thing to leave out of a table exercise.

table = [[1, 2], [3, 4]]
for row in table:
    for cell in row:
        print(cell, end=' ')
print('and the rest of the line')

Sample Run:

1 2 3 4 and the rest of the line
FindWhy the second program produces one line, and where the missing statement has to go.
Given
  • end=' ' tells print to finish with a space instead of a new line.

  • A bare print() prints nothing and then ends the line.

Solution

Work out what end does to the line endings first, or the missing statement has three plausible indentations and no way to choose.

See what end does to the line breaks

$$\texttt{print(cell, end=' ')}$$

This is the only printing statement in the inner loop, and it never ends a line, so nothing in the inner loop can start a new one.

$$\texttt{print()}$$

A call with no arguments prints the line ending only. That is the whole job it is doing here.

Put it at the right level of indentation

$$\text{inside the row loop, after the cell loop}$$

One line ending per row is wanted, and the outer loop body runs once per row. Indented one step further it would fire after every cell, and outside the loop altogether it fires once at the end, which is the second program.

Answer $$\boxed{\texttt{print()}\ \text{in the row loop, level with the cell loop}}$$
Check

Independent count: three rows want three line endings, and only the bare print produces one, so it runs once per row.

Indentation is the answer to most table printing questions. Ask how many times a statement should run, then put it at the level that runs that many times.

Doubling every cell, which cannot be done with the reading loop

Every mark in the table is to be doubled in place, with no new table built.

table = [[4, 8, 15],
         [16, 23, 42]]
for r in range(len(table)):
    for c in range(len(table[r])):
        table[r][c] = table[r][c] * 2
print(table)

Sample Run:

[[8, 16, 30], [32, 46, 84]]
FindWhy this loop needs the indexes, and what the reading loop would have done instead.
Given
  • The table has two rows of three cells.

  • The new value of each cell depends on its old value.

Solution

Start from what the assignment needs on its left, since that is what forces the index loop and nothing else does.

Decide what the assignment needs on its left

$$\texttt{table[r][c] = }\ldots$$

An assignment has to name a place, and a cell is named by its two indexes. There is no way to write the cell that cell is currently standing on, because cell is a name holding a copy of the value.

$$\texttt{for cell in row: cell = cell * 2}$$

This is the version that does nothing: it moves the name cell, and the next turn of the loop replaces it. Worth writing once to see it fail.

Set the two bounds

$$\texttt{range(len(table))}$$

Row indexes 0 up to the number of rows minus one, which is what range gives when it is handed a single bound.

$$\texttt{range(len(table[r])) }$$

The bound is read fresh for each row, so this version also works on a table whose rows differ in length. Writing len(table[0]) here costs nothing today and crashes the day the rows differ.

Check the arithmetic in one cell

$$\texttt{table[1][2]}: 42 \rightarrow 84$$

The last cell of the last row is the one furthest from the top of the loop, so if the bounds are wrong it is the one that gets missed.

Answer $$\boxed{\texttt{[[8, 16, 30], [32, 46, 84]]}}$$
Check

Independent check without redoing the multiplications: every printed cell is even, and each one is larger than the cell it replaced.

Six assignments here, and n times m in general. There is no shorter honest way.

The test for which loop shape you need is one question: is there an equals sign with a cell on its left. If yes, you need the indexes.

Checkpoint
§07.2 — where the line endings go

Thirty seconds. The separator is a dash this time, and the bare print is missing on purpose.

table = [[1, 2], [3, 4]]
for row in table:
    for cell in row:
        print(cell, end='-')
print('end')
Find(a) Write exactly what this prints.
Given
  • end='-' makes print finish with a dash and no new line.

  • The final print is outside both loops.

IPython console
Hint 1/4

Count how many statements in this program can end a line. Then you know how many lines the output has.

Hint 2/4

print(x, end='-') writes x and then a dash. Only a print whose end is left alone ends the line.

Hint 3/4

The cells come out in the order 1, 2, 3, 4, each followed by a dash, and then the word end is printed.

Hint 4/4

There is one line, and it has four dashes in it.

Show solution

Count the inner print's firings first; the dashes are all its work and the missing line ending only joins them together.

Follow the two loops

$$1\texttt{-}2\texttt{-}3\texttt{-}4\texttt{-}$$

Two rows of two cells, and the inner print fires four times, each time adding the value and then a dash.

$$\texttt{print('end')}$$

This one has its ending left alone, so it both writes the word and finally ends the only line of output.

Answer $$\boxed{\texttt{1-2-3-4-end}}$$
Check

Independent check by counting characters: four values, four separators and three letters, which is eleven characters, and the printed line has eleven.

⚠ Leaving out the bare print at the end of the row

The inner loop looks complete once the cells are printed, and on a one row table the output looks right.

wrong$$\text{all cells on one line}$$
right$$\texttt{print()}\ \text{once per row}$$
⚠ Trying to write through the loop name

The name really does stand for the cell while you are reading, so it looks as though it stands for it while writing too.

wrong$$\texttt{for cell in row: cell = 0}$$
right$$\texttt{for c in range(len(row)): row[c] = 0}$$
⚠ Using len(table[0]) as the inner bound on a table whose rows differ

It is shorter, and on the rectangular tables of the lecture it gives the same number.

wrong$$\texttt{for c in range(len(table[0])):}$$
right$$\texttt{for c in range(len(table[r])):}$$

7.3Building a table of a size you only learn while running, and the row that was secretly one row

Append a freshly built row each time round the loop; repeating a row with the star operator repeats the arrow to it.

Writing the rows out by hand only works when you know them as you type. The lab asks for a table whose size the user gives you, so the rows have to be made while the program runs.

RuleRule 7.3: how to make an empty table, and the one line that does not
Conditions
  • The safe shape is a loop: start with table = [], and once per row build a fresh row and append it. table.append([0] cols) inside the loop is enough, because [0] cols builds a new list every time the line runs.

  • [0] * cols on its own is fine and is the normal way to make a row of a fixed size. The trap is one level up.

  • [[0] cols] rows builds one row and then a list holding that same row rows times. The outer star copies the reference, so all the rows are one object.

  • The same trap wears a second disguise: making one row before the loop and appending that name every time round. row = [0] * cols then table.append(row) inside the loop has exactly the effect of the star.

  • table[0] is table[1] tells the two cases apart at once: True means you have the trap, False means you have real rows.

  • Both versions print identically when nothing has been written yet, so the trap is invisible until a single cell is assigned.

  • Cells can be filled as the rows are built, which is what a rule on the row and column numbers asks for, or after the table exists with an index walk.

$$\boxed{\texttt{for r in range(rows): table.append([0] * cols)}\quad\text{builds }rows\text{ rows};\qquad \texttt{[[0] * cols] * rows}\quad\text{builds }1}$$

Make the empty table first, then make one row per turn of the loop and hand it over. Never make the row once and hand the same one over several times, whichever way you write that.

Looks like this, but is not

One row of three zeros, repeated twice, is a two by three table of zeros. The printed table agrees, right up to the first assignment.

bad = [[0] * 3] * 2

good = []
for r in range(2):
    good.append([0] * 3)

bad[0][0] = 9
good[0][0] = 9

print('bad  :', bad)
print('good :', good)
print('bad  rows are one object :', bad[0] is bad[1])
print('good rows are two        :', good[0] is good[1])
bad  : [[9, 0, 0], [9, 0, 0]]
good : [[9, 0, 0], [0, 0, 0]]
bad  rows are one object : True
good rows are two        : False

Both tables have two items and both print as two rows of three, so by shape they are the same table. The star ran [0] 3 once, got one list, and put that one list in both slots, so there is only one row to write into. The loop ran [0] 3 twice and got two lists. The two is lines are the cheap test, and they are worth running in the shell the first time this bites you.

The seating plan from the top of the page, and the one line that fixes it

This is the program the section opened with: six rows of six seats, one name written into the front left seat.

grid = [['-'] * 6] * 6
grid[0][0] = 'A'
for row in grid:
    print(row)

Sample Run:

['A', '-', '-', '-', '-', '-']
['A', '-', '-', '-', '-', '-']
['A', '-', '-', '-', '-', '-']
['A', '-', '-', '-', '-', '-']
['A', '-', '-', '-', '-', '-']
['A', '-', '-', '-', '-', '-']

And the same program with the first line replaced by a loop.

grid = []
for r in range(6):
    grid.append(['-'] * 6)
grid[0][0] = 'A'
for row in grid:
    print(row)

Sample Run:

['A', '-', '-', '-', '-', '-']
['-', '-', '-', '-', '-', '-']
['-', '-', '-', '-', '-', '-']
['-', '-', '-', '-', '-', '-']
['-', '-', '-', '-', '-', '-']
['-', '-', '-', '-', '-', '-']
FindWhy the first program shows the letter six times, and what it cost to fix.
Given
  • Both programs write to one cell only, grid[0][0].

  • Both print six lines of six items.

Solution

Count the list objects the first line builds before following the assignment, or six separate assignments look like the fault.

Count how many lists the first line builds

$$\texttt{['-'] * 6}$$

One list of six dashes. This inner star is doing nothing wrong; it repeats a string inside one list.

$$\texttt{[\,\ldots\,] * 6}$$

The outer star repeats the single item of a one item list six times, and that item is the list just built. Six slots, one row.

Follow the single assignment

$$\texttt{grid[0][0] = 'A'}$$

The first bracket lands on the one row object, the second changes its first cell. There is nowhere else the letter could go, and nothing was overwritten by mistake.

$$\texttt{print(row)}\times 6$$

The printing loop visits six slots and prints the one row six times, which is why the output looks like six identical rows rather than a mistake.

Fix it where the rows are made

$$\texttt{for r in range(6): grid.append(['-'] * 6)}$$

The expression that builds a row is now inside the loop, so it runs six times and produces six lists. This is the only change; the rest of the program is untouched.

Answer $$\boxed{\text{one row in six slots}\;\longrightarrow\;\text{six rows, built inside the loop}}$$
Check

Independent check that needs no printout: grid[0] is grid[5] is True in the broken version and False in the fixed one.

One line became three and the star runs six times instead of twice: the price of six real rows.

Any time a table is built without a loop, look at it twice. Every table whose rows are made once is the same bug wearing a different suit.

A square table filled by a rule on its row and column numbers

The lab asks for a function that takes a size and returns a square table whose cells follow a rule on the two indexes. Here the rule is that the cell in row r and column c holds (r + 1) * (c + 1), and a second function prints the table with the columns lined up.

def times_table(n):
    """Assumes n is an int greater than 0.
    Returns an n by n table whose cell at row r and column c
    holds (r + 1) * (c + 1).
    """
    table = []
    for r in range(n):
        row = []
        for c in range(n):
            row.append((r + 1) * (c + 1))
        table.append(row)
    return table


def show(table):
    """Assumes table is a table of ints.
    Prints it with every cell in a field four characters wide.
    """
    for row in table:
        for cell in row:
            print(format(cell, '4d'), end='')
        print()


show(times_table(5))

Sample Run:

   1   2   3   4   5
   2   4   6   8  10
   3   6   9  12  15
   4   8  12  16  20
   5  10  15  20  25
FindThe shape of the building loop, and why the row is created where it is.
Given
  • The size is a parameter, so the table cannot be written out as a literal.

  • The rule needs both indexes, so the cells cannot be made with the star operator.

Solution

Decide where the fresh row is created before writing any loop, because that one placement is the whole difference from the star trap.

Put the fresh row at the top of the outer body

$$\texttt{row = []}$$

Inside the row loop, so a new empty list exists once per row. Moving this line above the loop is the trap from the counterexample, and here it would leave one row holding all n squared values.

$$\texttt{table.append(row)}$$

At the bottom of the same body, after the row has been filled. Appending it earlier also works, because the name and the slot reach the same object, but filling it first is easier to read.

Let the inner loop do the rule

$$\texttt{row.append((r + 1) * (c + 1))}$$

Appending rather than assigning, because the row has no cell c yet. Assigning to row[c] here would be an IndexError on an empty list.

$$(r + 1)(c + 1)$$

The plus ones are there because the indexes start at 0 and the table is meant to read as a multiplication table from 1. This is the only place in the function where that shift belongs.

Line the columns up when printing

$$\texttt{format(cell, '4d')}$$

A whole number right aligned in four characters, so 5 and 25 end at the same place. Printing with a space separator would leave the columns ragged as soon as the numbers differ in length.

Answer $$\boxed{\texttt{table = []}\;\rightarrow\;\texttt{row = []}\ \text{per row}\;\rightarrow\;\texttt{append}\ \text{per cell}\;\rightarrow\;\texttt{append}\ \text{the row}}$$
Check

Independent check on the printed table: it must be symmetric, since swapping r and c cannot change a product, and the diagonal holds squares.

Two loops and n squared appends. For n equal to 5 that is 25 appends and 5 more for the rows.

This is the whole pattern for any rule based table: empty table, fresh row per turn, one append per cell, hand the row over. Only the expression inside changes.

Checkpoint
§07.3 — one row in two slots

Thirty seconds, and the table is written with a literal row this time rather than with the star on the inside.

grid = [[1, 1]] * 2
grid[0][0] = 5
print(grid)
Find(a) Write the line this prints.
Given
  • The outer star repeats a one item list twice.

  • Only one cell is ever assigned to.

IPython console
Hint 1/4

Before answering, count how many list objects the first line creates. The answer follows from that number and nothing else.

Hint 2/4

Repeating a list with the star operator repeats its items. The item here is itself a list, and repeating a reference gives you the same object twice.

Hint 3/4

So grid[0] and grid[1] are the same [1, 1], and the assignment writes a 5 into the first cell of it.

Hint 4/4

Both printed rows look the same, and neither of them is [1, 1].

Show solution

Count the row objects rather than the brackets; a written out row hides the sharing that the inner star advertises.

Count the objects

$$\texttt{[[1, 1]] * 2}\;\rightarrow\;\text{1 row, 2 slots}$$

The star copies what is in the list, and what is in it is one reference to one row.

$$\texttt{grid[0][0] = 5}$$

There is only one place this can land, and both slots report it because both slots hold the same reference.

Answer $$\boxed{\texttt{[[5, 1], [5, 1]]}}$$
Check

Independent check: grid[0] is grid[1] is True here, and len(grid) is 2 while the number of distinct rows is 1. Those two facts together are the whole answer.

⚠ Building a table with the star on the outside

It is one short line, it prints correctly, and the trap shows up only after an assignment.

wrong$$\texttt{table = [[0] * cols] * rows}$$
right$$\texttt{for r in range(rows): table.append([0] * cols)}$$
⚠ Making the row once and appending it every turn

The row is created before the loop for tidiness, and the loop then appends the name it was given.

wrong$$\texttt{row = [0] * 3}\ \text{then}\ \texttt{table.append(row)}\ \text{in the loop}$$
right$$\texttt{table.append([0] * 3)}\ \text{in the loop}$$
⚠ Assigning to a cell of a row that has no cells yet

The finished table is indexed with brackets, so the half built one looks as though it should be too.

wrong$$\texttt{row = []}\ \text{then}\ \texttt{row[c] = v}\;\Rightarrow\;\text{IndexError}$$
right$$\texttt{row = []}\ \text{then}\ \texttt{row.append(v)}$$

7.4Walking a column, and what a column means when the rows are not the same length

The index that stays still while you add things up is the one that ends up in the answer.

Reading a row was easy because a row is an object you can hold. A column is not an object at all, and that is the only difficulty in this block.

RuleRule 7.4: one answer per row against one answer per column
Conditions
  • There is no table[c] for a column. A column exists only as the set of cells table[0][c], table[1][c] and so on, so reaching it always needs a loop over the row index.

  • For one answer per row, put the row index on the outer loop and start the accumulator inside that loop, just after it opens.

  • For one answer per column, put the column index on the outer loop and the row index on the inner one. The body is then table[r][c] with c held still.

  • The accumulator goes inside the outer loop and the append goes at the end of the outer body. Starting it before the outer loop is the single commonest error in this section, and it gives a first answer that is right and every later answer too large.

  • A per column walk needs the number of columns, which is len(table[0]), and that is a claim about the whole table only when the table is rectangular.

  • On a table whose rows differ, len(table[0]) is the length of row 0 and nothing more. Asking for table[r][c] beyond the end of row r stops the program with an IndexError.

  • Checking rectangularity is one loop: compare every len(table[r]) with len(table[0]) and report the first mismatch.

$$\boxed{\text{per row: }\texttt{for r ... : total = 0; for c ... }\qquad\text{per column: }\texttt{for c ... : total = 0; for r ... }}$$

Decide first how many numbers the answer has. If it is one per column, the column loop is the outer one, the total is reset as each column starts, and the row loop is what walks down.

Looks like this, but is not

A column walk that reads the number of columns from row 0, which is exactly what a rectangular table wants.

words = [['lion', 'ant'],
         ['cat', 'dog', 'ox'],
         ['bee']]
for c in range(len(words[0])):
    column = []
    for r in range(len(words)):
        column.append(words[r][c])
    print('column', c, ':', column)
column 0 : ['lion', 'cat', 'bee']
Traceback (most recent call last):
  File "columns.py", line 7, in <module>
    column.append(words[r][c])
                  ~~~~~~~~^^^
IndexError: list index out of range

Column 0 came out, because every row has a cell 0. Column 1 asked row 2 for its second cell and row 2 has one cell, so the program stopped. Notice also what column 0 quietly claimed: that ox, which is in row 1, belongs in no column at all, because the loop bound came from row 0 and row 0 is short. A ragged table has rows and has no columns, and the crash is the honest version of that.

Row totals and column totals of the same table of marks

Three students, three quizzes. One function reports how each student did, the other how hard each quiz was.

def row_totals(table):
    """Assumes table is a table of numbers.
    Returns a list with one total per row, in row order.
    """
    totals = []
    for row in table:
        total = 0
        for cell in row:
            total = total + cell
        totals.append(total)
    return totals


marks = [[55, 70, 62],
         [90, 85, 78],
         [40, 51, 66]]
print(row_totals(marks))

Sample Run:

[187, 253, 157]

The column version cannot use for row in table, because it needs one cell out of each row at a time.

def column_totals(table):
    """Assumes table is a rectangular table of numbers with at least one row.
    Returns a list with one total per column, in column order.
    """
    totals = []
    for c in range(len(table[0])):
        total = 0
        for r in range(len(table)):
            total = total + table[r][c]
        totals.append(total)
    return totals


marks = [[55, 70, 62],
         [90, 85, 78],
         [40, 51, 66]]
print(column_totals(marks))

Sample Run:

[185, 206, 206]
FindWhy the row version can avoid indexes entirely and the column version cannot.
Given
  • The marks are 55, 70, 62 then 90, 85, 78 then 40, 51, 66.

  • Both answers are lists of three numbers, but they are not the same three.

Solution

Let the shape of the answer pick the outer loop, and only then ask whether indexes are needed at all.

Write the row version with no indexes at all

$$\texttt{for row in table:}$$

A row is an object, so the loop can hand it over directly. Nothing in this function ever needs to know which row number it is on.

$$\texttt{total = 0}\ \text{inside the row loop}$$

One total per row is wanted, so it has to be reset as each row starts. Above the loop it would accumulate across rows and give running totals instead.

Turn the walk inside out for columns

$$\texttt{for c in range(len(table[0])):}$$

The answer has one number per column, so the column index is the one that must sit still while the inner loop runs. That decides which loop is outer, and it is the whole trick.

$$\texttt{for r in range(len(table)):}$$

This is the loop that walks down. Column 0 is table[0][0], table[1][0], table[2][0], and only the row index moves.

Read one column by hand to be sure

$$55 + 90 + 40 = 185$$

The first cell of each of the three rows. If the indexes had been swapped this would have been 55 plus 70 plus 62, which is 187 and is a row total.

Answer $$\boxed{\text{rows }[187, 253, 157],\qquad\text{columns }[185, 206, 206]}$$
Check

Independent check that catches a swapped index: both lists count all nine marks, and both add to 597.

Nine additions either way. The cost is identical; only the order of visiting differs.

Keep that equal grand total as a habit: per row and per column answers over one table must agree when summed.

Reporting the row lengths of a table that is not rectangular

When the rows may differ, the only safe questions are per row ones, and the first thing to print is the shape.

words = [['lion', 'ant'],
         ['cat', 'dog', 'ox'],
         ['bee']]
print('rows :', len(words))
for r in range(len(words)):
    print('row', r, 'has length', len(words[r]), ':', words[r])

Sample Run:

rows : 3
row 0 has length 2 : ['lion', 'ant']
row 1 has length 3 : ['cat', 'dog', 'ox']
row 2 has length 1 : ['bee']
FindWhich claims about this table are safe and which are not.
Given
  • The three rows hold two, three and one strings.

  • The loop uses the row index because the printed line names it.

Solution

Ask which counts exist before writing the loop, because a ragged table has no number of columns to report.

Separate the counts that exist from the one that does not

$$\texttt{len(words)} = 3$$

The number of rows is always available and always means what it says.

$$\texttt{len(words[r])}\in\{2, 3, 1\}$$

Each row reports its own length. There is no single number of columns to report, and asking for one is the mistake.

Use the index loop because the output names the row

$$\texttt{for r in range(len(words)):}$$

The printed line includes the row number, so the number is needed. With for row in words the number would have to be counted by hand in a separate name, which is the same thing written longer.

Answer $$\boxed{3\ \text{rows of lengths }2,\ 3,\ 1;\ \text{no number of columns exists}}$$
Check

Independent check: row 1 printed three items while row 0 printed two, which no rectangular reading of this table could produce.

Before any column walk, either prove the table is rectangular or write the loop bounds per row. A page of correct column code on a ragged table is still a crash.

Checkpoint
§07.4 — the lengths of a ragged table

Thirty seconds. Three rows, and none of them the same length.

table = [[1, 2, 3], [4, 5], [6]]
print(len(table[0]), len(table[1]), len(table[2]))
print(table[1][1], table[0][2])
Find(a) Write the two lines this prints.
Given
  • The rows are [1, 2, 3], [4, 5] and [6].

  • The second line reads one cell from row 1 and one from row 0.

IPython console
Hint 1/4

Each of the three lengths is a question about one row only. Do not look for a single width for the table, because it does not have one.

Hint 2/4

len(table[r]) counts the cells of row r. In table[r][c] the second index must be smaller than that row's own length.

Hint 3/4

Row 0 has three cells, row 1 has two and row 2 has one, so table[1][1] is the second cell of [4, 5] and table[0][2] is the third cell of [1, 2, 3].

Hint 4/4

The first line counts down from three, and the second line is two single digits.

Show solution

Ask each row for its own length instead of reading one width off row 0, which would be an error here.

Ask each row for its own length

$$\texttt{len(table[0])}=3,\ \texttt{len(table[1])}=2,\ \texttt{len(table[2])}=1$$

Three separate questions with three separate answers, which is what a ragged table gives you.

$$\texttt{table[1][1]}=5,\ \texttt{table[0][2]}=3$$

Each index stays inside its own row, so both reads succeed. Row 1 has indexes 0 and 1 only.

Answer $$\boxed{\texttt{3 2 1}\ \text{then}\ \texttt{5 3}}$$
Check

Independent check: the three lengths add to 6 and the table shows six numbers, so no row was miscounted.

⚠ Starting the accumulator before the outer loop

One name for the total looks tidier, and the first column or row still comes out right, which hides it.

wrong$$\texttt{total = 0}\ \text{above the outer loop}$$
right$$\texttt{total = 0}\ \text{as the first line of the outer body}$$
⚠ Putting the row index on the outer loop for a per column answer

Rows come first everywhere else on the page, so the row loop feels like the natural outer one.

wrong$$\texttt{for r ...: for c ...: }\text{append per row}$$
right$$\texttt{for c ...: for r ...: }\text{append per column}$$
⚠ Reading the number of columns from row 0 on a ragged table

Every table in the lecture is rectangular, so row 0 is a reliable witness there and the habit travels.

wrong$$\texttt{range(len(table[0]))}\;\text{as a claim about the table}$$
right$$\texttt{range(len(table[r]))}\;\text{per row}$$

7.5Changing a table in place against handing back a new one

Either write into the cells you were given, or build a fresh table and return it; mixing the two is where marks go.

Both walks so far either read a table or wrote into the one they were handed. A lab question usually says which of the two it wants, and the words it uses are worth learning.

RuleRule 7.5: the two families, one level up
Conditions
  • In place: the function takes a table, assigns to table[r][c], and returns nothing. The caller sees the change in its own table, and the call is a statement on a line of its own.

  • Returning a new one: the function starts from new = [], appends a freshly built row per turn, and returns new. The table it was given is untouched and the caller has to store the result.

  • A function that changes a table in place hands back None, so table = censor(table, 'cat') throws the table away and leaves the name holding None.

  • A cannot be done in place on a table that is not square, because the shape itself changes, so it belongs to the second family.

  • Copying the outer list with table[:] or list(table) gives a new outer list holding the same row objects. Writing a cell through the copy is visible through the original.

  • A copy whose rows are independent takes a loop: start from new = [] and append row[:] for each row. That is what deep means here and it is one line longer.

  • Which family a question wants can be read off its wording: returns a new table means the second, updates the table means the first, and a docstring should say which.

$$\boxed{\text{in place: assign }\texttt{table[r][c]},\ \text{return }\texttt{None}\qquad\text{new table: }\texttt{new = []},\ \texttt{new.append(row)},\ \text{return }\texttt{new}}$$

Decide at the start whether the caller wants its own table changed or a second table alongside it, write the docstring line that says so, and then the body has only one shape available to it.

Looks like this, but is not

A transpose swaps the cell at row r column c with the cell at row c column r, so a double loop that does exactly that should transpose the table where it stands.

square = [[1, 2], [3, 4]]
for r in range(len(square)):
    for c in range(len(square[r])):
        keep = square[r][c]
        square[r][c] = square[c][r]
        square[c][r] = keep
print(square)
[[1, 2], [3, 4]]

Every pair of cells is swapped twice, once when the loop reaches one of them and once when it reaches the other, so the table comes back exactly as it started. The fix is not another swap but a bound: only the cells above the should be visited. On a table that is not square the same loop stops with an IndexError instead, because square[c][r] reaches past the end of a row as soon as c is a legal column but not a legal row.

Transposing a two by three table into a three by two one

The new table has one row per old column, so the outer loop of the builder is the column loop.

def transpose(table):
    """Assumes table is a rectangular table with at least one row.
    Returns a NEW table whose row c is column c of the input.
    """
    new = []
    for c in range(len(table[0])):
        row = []
        for r in range(len(table)):
            row.append(table[r][c])
        new.append(row)
    return new


small = [[1, 2, 3],
         [4, 5, 6]]
flipped = transpose(small)
print('original :', small)
print('flipped  :', flipped)
print('shape    :', len(small), 'by', len(small[0]), 'became',
      len(flipped), 'by', len(flipped[0]))

Sample Run:

original : [[1, 2, 3], [4, 5, 6]]
flipped  : [[1, 4], [2, 5], [3, 6]]
shape    : 2 by 3 became 3 by 2
FindThe loop bounds of the builder, and the proof that the input survived.
Given
  • The input has two rows of three cells.

  • The function must not change the table it is given.

Solution

Fix the shape of the answer first: with the column loop outside, no index has to be swapped by hand afterwards.

Count the rows the answer needs

$$\texttt{len(new)} = \texttt{len(table[0])}$$

One row of the answer per column of the input, so the outer loop runs over the columns. Getting this the wrong way round builds a table of the right numbers in the wrong shape.

$$\texttt{len(new[c])} = \texttt{len(table)}$$

Each new row is as long as the input has rows, because it collects one cell from each of them.

Fill one new row

$$\texttt{row.append(table[r][c])}$$

With c held still by the outer loop, this walks down column c. The two indexes appear in the input order, so nothing is swapped by hand; the swap is in which loop is outer.

Show the input was not touched

$$\texttt{print(small)}\;\rightarrow\;\texttt{[[1, 2, 3], [4, 5, 6]]}$$

Nothing in the function assigns to any cell of table, so there is no way it could have changed. Printing it is the cheap proof that the function belongs to the second family.

Answer $$\boxed{\texttt{[[1, 4], [2, 5], [3, 6]]}}$$
Check

Independent check on the shape: rows times columns is 6 before and after, and reading the first column of the answer downwards gives 1, 2, 3.

Six appends for the cells and three for the rows, and one new list per row. Nothing is copied twice.

Whenever the answer is a table, write down its two lengths in terms of the input's two lengths before writing any loop. That one line fixes which loop is outer.

Blanking out a word wherever it appears, and why the function returns None

This one is in place: the caller's own table is changed, and nothing is handed back.

def censor(table, word):
    """Assumes table is a table of strings and word is a string.
    Replaces every cell equal to word with a single asterisk.
    Changes table in place and returns None.
    """
    for r in range(len(table)):
        for c in range(len(table[r])):
            if table[r][c] == word:
                table[r][c] = '*'


lines = [['the', 'cat', 'sat'],
         ['the', 'dog', 'ran'],
         ['a', 'cat', 'slept']]
print('what censor hands back :', censor(lines, 'cat'))
for row in lines:
    print(row)

Sample Run:

what censor hands back : None
['the', '*', 'sat']
['the', 'dog', 'ran']
['a', '*', 'slept']
FindWhy the call prints None, and what a caller must not write.
Given
  • The table holds nine strings and two of them are cat.

  • The function has no return statement.

Solution

Read what the body does to the table before asking what the call prints, because the return value is the easy half.

Read what the body does with the table

$$\texttt{table[r][c] = '*'}$$

An assignment to a cell of the caller's own table. The parameter holds a reference to it, so there is only one table in the program and the caller sees the change.

$$\texttt{return}\ \text{absent}$$

A function that falls off its end hands back None, which is why the first printed line says None rather than showing a table.

Say what the caller must not do

$$\texttt{lines = censor(lines, 'cat')}$$

This throws the table away: the censoring really happened, and then the name was pointed at None. The next loop over lines then stops the program.

$$\texttt{censor(lines, 'cat')}$$

The correct call is a statement on its own, exactly like L.sort() from the previous section. Same rule, one level deeper.

Check the ragged safe bound

$$\texttt{range(len(table[r]))}$$

The rows here are all three long, but nothing in the docstring promises that, so the bound is read per row and the function works on a ragged table too.

Answer $$\boxed{\texttt{None},\ \text{and two cells replaced by }\texttt{*}}$$
Check

Independent check: the cell count is unchanged, so each printed row still holds three items, and exactly two asterisks appear.

The docstring line that says whether a function changes its argument or returns something new is not decoration. Write it first, and the body has one shape left.

Two ways to copy a table, and the one that does not protect the rows

Cloning a list was a full slice in the previous section. One level up it only half works.

table = [[1, 2], [3, 4]]
shallow = table[:]
shallow[0][0] = 9
print('after writing into the slice copy :', table)

table = [[1, 2], [3, 4]]
deep = []
for row in table:
    deep.append(row[:])
deep[0][0] = 9
print('after writing into the row copy   :', table)
print('and the copy itself               :', deep)

Sample Run:

after writing into the slice copy : [[9, 2], [3, 4]]
after writing into the row copy   : [[1, 2], [3, 4]]
and the copy itself               : [[9, 2], [3, 4]]
FindWhich of the two the word copy means in a lab question, and how to write the safe one.
Given
  • Both copies are separate outer lists, so appending a row to either leaves the other alone.

  • Only the second copy has separate rows.

Solution

Ask what the slice copied rather than whether it copied, since the outer list really is new and a yes explains neither line.

See what the slice actually copied

$$\texttt{table[:]}$$

A new outer list with the same items. The items of a table are rows, so what was copied is two references, not two rows.

$$\texttt{shallow[0]}\ \text{is}\ \texttt{table[0]}$$

Same object, so writing a cell through either name shows through both. This is the aliasing rule from the previous section, unchanged; only the level is new.

Copy one level deeper

$$\texttt{deep.append(row[:])}$$

The slice is applied to the row rather than the table, so each appended item is a fresh list. Two lines of work, and the copy is now independent for cells as well as for rows.

Answer $$\boxed{\texttt{table[:]}\ \text{shares the rows};\ \texttt{append(row[:])}\ \text{does not}}$$
Check

Independent check that changes nothing: shallow[0] is table[0] is True, and for the row by row copy it is False.

A backup of a table is a loop, not a slice: the same sentence as last section with one more bracket in it.

Checkpoint
§07.5 — what a full slice of a table copies

Thirty seconds. A copy is taken, a row is added to it, and one cell is changed.

first = [[1, 2], [3, 4]]
second = first[:]
second.append([5, 6])
second[0][0] = 0
print(first)
print(second)
Find(a) Write the two lines this prints.
Given
  • second is a full slice of first.

  • One row is appended to second and one cell of it is assigned to.

IPython console
Hint 1/4

Two different changes are made to second, and they are not the same kind of change. Decide separately for each whether first can see it.

Hint 2/4

A full slice builds a new outer list holding the same rows. Appending changes the outer list only; assigning to a cell changes a row.

Hint 3/4

So second has three rows while first still has two, and second[0] and first[0] are the same [1, 2].

Hint 4/4

The first line has two rows in it and the 0 appears on both lines.

Show solution

Take the length change and the cell change as two questions, because the outer list is new and the rows are not.

Take the two changes separately

$$\texttt{second.append([5, 6])}$$

Changes the length of the new outer list only. first still has two items, so it cannot show this.

$$\texttt{second[0][0] = 0}$$

Reaches through the shared reference into the row [1, 2], which is also first[0], so both names show the 0.

Answer $$\boxed{\texttt{[[0, 2], [3, 4]]}\ \text{then}\ \texttt{[[0, 2], [3, 4], [5, 6]]}}$$
Check

Independent check on lengths: len(first) is 2 and len(second) is 3, which proves the outer lists are separate, while first[0] is second[0] is True, which proves the rows are not.

⚠ Storing the result of an in place function

Most functions hand something back, so assigning the call looks like the careful thing to do.

wrong$$\texttt{table = censor(table, w)}\;\Rightarrow\;\texttt{table}\ \text{is}\ \texttt{None}$$
right$$\texttt{censor(table, w)}$$
⚠ Backing up a table with a full slice

A full slice was the correct clone of a list one section ago, and it still makes a new outer list, so it looks right.

wrong$$\texttt{backup = table[:]}$$
right$$\texttt{for row in table: backup.append(row[:])}$$
⚠ Building the new table with the input's shape

The two lengths are both in scope, so it is easy to reach for the nearer one when the answer needs the other.

wrong$$\text{transpose with}\ \texttt{for r in range(len(table))}\ \text{outside}$$
right$$\text{transpose with}\ \texttt{for c in range(len(table[0]))}\ \text{outside}$$

7.6Getting a table in from the keyboard or a file, and printing it so the columns line up

A table arrives one row at a time, and every part of every row is text until int is called on it.

Every table up to here was written into the program. The lab reads the size from the user and the exam reads the values from a file, and both are the same loop with a different source.

MethodMethod 7.6: read a table in, print a table out
Conditions
  • From the keyboard: read the two sizes first and convert them with int, then run the two loops, appending a fresh row per turn and a converted value per cell.

  • From a file: one line of the file is one row. Strip the line, split it on its separator, walk the parts, convert each with int or float, append to the row, then append the row.

  • split hands back a list of strings. A row of text looks identical to a row of numbers when printed inside a table, so the conversion is easy to forget and shows up later as text being joined instead of numbers being added.

  • strip first, split second. A line read from a file ends in a newline character, and without the strip the last part of every row carries it.

  • Printing a table so it lines up needs a field width: print(format(cell, '5d'), end='') puts every whole number in five characters, right aligned, and the bare print() after the inner loop ends the row.

  • Printing with print(cell, end=' ') is fine when the cells are all the same width and ragged as soon as they are not.

  • The prompt of an input call belongs inside the call, and the value typed appears on the same line as the prompt because the terminal echoes it.

$$\boxed{\text{per line: }\texttt{parts = line.strip().split(',')}\;\rightarrow\;\texttt{row.append(int(part))}\;\rightarrow\;\texttt{table.append(row)}}$$

Cut the line into its parts, turn each part into the number it stands for, collect them into a row, and hand the finished row to the table. Then do it again for the next line.

Looks like this, but is not

The split gave three parts per line and the table printed correctly, so the table holds the numbers from the file.

out = open('marks.txt', 'w')
out.write('55,70,62\n')
out.close()

table = []
in_file = open('marks.txt', 'r')
for line in in_file:
    table.append(line.strip().split(','))
in_file.close()

print(table)
print(table[0][0] + table[0][1])
[['55', '70', '62']]
5570

The printed table gives it away only if you look for the quotation marks, and the addition gives it away completely: 55 plus 70 came out as 5570 because both cells are still strings and the plus sign joined them. The table is a perfectly good table; it is just a table of text. One int call per part is the whole fix.

Reading a table of any size from the user and printing it in matrix form

This is the second half of the lab exercise: the size comes from the user, and the table is printed so that the columns line up.

rows = int(input('How many rows? '))
cols = int(input('How many columns? '))

table = []
for r in range(rows):
    row = []
    for c in range(cols):
        cell = int(input('Value at row ' + str(r) + ' column ' + str(c) + ': '))
        row.append(cell)
    table.append(row)

print('The table:')
for row in table:
    for cell in row:
        print(format(cell, '5d'), end='')
    print()

Sample Run:

How many rows? 2
How many columns? 3
Value at row 0 column 0: 4
Value at row 0 column 1: 8
Value at row 0 column 2: 15
Value at row 1 column 0: 16
Value at row 1 column 1: 23
Value at row 1 column 2: 42
The table:
    4    8   15
   16   23   42
FindWhere each conversion goes, and how the printed columns are made to line up.
Given
  • The sizes are typed in before any value.

  • Every value typed is converted with int as it arrives.

Solution

Convert each value as it arrives rather than storing the text, so nothing below has to remember to convert it.

Convert as early as possible

$$\texttt{rows = int(input(...))}$$

The size is needed by range, which refuses a string, so this conversion cannot be postponed. Doing it on the same line keeps the two sizes as numbers everywhere below.

$$\texttt{cell = int(input(...))}$$

Converting here means the table holds numbers from the moment it is built. Storing the text and converting later means every later use has to remember to.

Build the prompt out of the two indexes

$$\texttt{'Value at row ' + str(r) + ' column ' + str(c) + ': '}$$

Joining text needs text on both sides, so the two numbers are converted the other way with str. Without those calls the line stops with a TypeError.

Give every cell the same width

$$\texttt{format(cell, '5d')}$$

Five characters, right aligned, so 4 and 15 and 42 all end at the same column. The separator is then not needed at all, which is why end is the empty string.

$$\texttt{print()}$$

One line ending per row, placed in the outer body as in the printing block earlier on this page.

Answer $$\boxed{\texttt{int}\ \text{on the way in},\ \texttt{format(cell, '5d')}\ \text{on the way out}}$$
Check

Independent check: every printed line must be columns times five characters long, so 15 here, and counting the first one gives 15.

One prompt per cell, so rows times columns of them, which is why the file version exists for anything larger.

This program is the skeleton of most table lab questions: two sizes, two loops, one conversion, one printing loop with a field width.

Reading a file of comma separated marks into a table of ints

Here the file is written first so the program is complete on its own; in the lab the file is given to you.

out = open('marks.txt', 'w')
out.write('55,70,62\n')
out.write('90,85,78\n')
out.write('40,51,66\n')
out.close()

table = []
in_file = open('marks.txt', 'r')
for line in in_file:
    parts = line.strip().split(',')
    row = []
    for part in parts:
        row.append(int(part))
    table.append(row)
in_file.close()

print(table)
print('second student, third quiz :', table[1][2])

Sample Run:

[[55, 70, 62], [90, 85, 78], [40, 51, 66]]
second student, third quiz : 78
FindThe order of the four steps per line, and what happens if the strip is left out.
Given
  • Each line of the file holds three whole numbers separated by commas.

  • The rows of the table come out in the order the lines were read.

Solution

Take one line at a time in the order strip, split, convert, append, because each step needs the previous one's result.

Take the line apart in the right order

$$\texttt{line.strip()}$$

Removes the newline character at the end. Without it the last part of each row would be a string such as 62 followed by a newline, and int would still accept it here, which makes this the kind of omission that survives until a strip is really needed.

$$\texttt{.split(',')}$$

Cuts the line at every comma and hands back a list of three strings. This list is almost the row already; what it is missing is the conversion.

Convert part by part

$$\texttt{row.append(int(part))}$$

One conversion per cell, which is the only place the text becomes a number. Appending parts directly would give the table of strings from the counterexample above.

Hand the row over once per line

$$\texttt{table.append(row)}$$

In the outer loop body, after the inner loop, so exactly one row is added per line of the file. Inside the inner loop it would append a half built row several times, and all those appends would be the same object.

Answer $$\boxed{\texttt{[[55, 70, 62], [90, 85, 78], [40, 51, 66]]}}$$
Check

Independent check that the cells are numbers: table[1][2] printed as 78 with no quotation marks, and the row count matches the three lines written.

A file whose lines are rows is the normal shape of table data, and this loop is the same one whatever the separator is. Only the argument to split changes.

Checkpoint
§07.6 — a field width against a separator

Thirty seconds. The same three numbers printed twice, once with a field width and once with a space.

row = [5, 40, 300]
for cell in row:
    print(format(cell, '5d'), end='')
print()
for cell in row:
    print(cell, end=' ')
print()
Find(a) Write the two lines this prints, counting the spaces.
Given
  • format(cell, '5d') puts a whole number in five characters, right aligned.

  • The second loop uses a single space as its separator.

IPython console
Hint 1/4

Work out how many characters the first line has before working out what is in it. That number is fixed by the field width and the number of cells.

Hint 2/4

Right aligned in five characters means the value is padded on the left until the whole thing is five long. A one digit number therefore carries four spaces in front of it.

Hint 3/4

The values are 5, 40 and 300, which are one, two and three digits long, so they need four, three and two spaces in front of them.

Hint 4/4

The first line is fifteen characters long and the second is shorter than that.

Show solution

Count each field out to its width rather than counting the gaps, since format pads per value and the gaps are leftovers.

Pad each value to five

$$5\rightarrow\text{4 spaces then }5$$

One digit in a field of five leaves four to fill, and right aligned means they go in front.

$$40\rightarrow\text{3 spaces},\quad 300\rightarrow\text{2 spaces}$$

Same rule, so the three fields are five characters each and the line is fifteen long.

Compare with the separator version

$$\texttt{5 40 300}$$

Here each value is printed at its natural width with one space after it, so the line is only nine characters and nothing is aligned.

Answer $$\boxed{\text{15 characters, then 9}}$$
Check

Independent count: three fields of five is 15 characters for the first line, and the second comes to 9 with its trailing space.

⚠ Appending the result of split straight into the table

The printed table looks right, and the quotation marks around the cells are easy to read past.

wrong$$\texttt{table.append(line.strip().split(','))}$$
right$$\texttt{row.append(int(part))}\ \text{per part}$$
⚠ Splitting before stripping

Splitting is the step you are thinking about, and the newline is invisible.

wrong$$\texttt{line.split(',')}\;\rightarrow\;\text{last part ends in a newline}$$
right$$\texttt{line.strip().split(',')}$$
⚠ Appending the row inside the inner loop

Both appends belong to the same reading job, so they drift to the same level of indentation.

wrong$$\texttt{table.append(row)}\ \text{in the cell loop}$$
right$$\texttt{table.append(row)}\ \text{in the line loop}$$

A position in a table is two numbers, so hand them back as a tuple and use one value to mean not found.

Totals and averages answer how much. A search answers where, and in a table where takes two numbers rather than one.

RuleRule 7.7: searching a table
Conditions
  • A full search is the index walk with a test inside it. The two indexes are the answer, so the reading walk cannot be used.

  • return (r, c) inside the inner loop leaves the function at once, from inside both loops. No break is needed and no flag is needed.

  • What to hand back when there is nothing: None is the honest choice for a position, because every pair of whole numbers could be a real answer.

  • return -1 is the course's other habit and works when the answer is a single index rather than a pair, since no real index is negative.

  • Without a return in the loop, the search needs a found flag and a break, and the break only leaves the inner loop. That is why returning is the shorter answer in a function.

  • Row order decides which match is found. The first match in row order is the one before which no earlier row and no earlier cell of the same row matched.

  • To find the largest cell rather than a given one, keep three names: the best value so far and its two indexes, and start them from table[0][0], never from zero.

$$\boxed{\texttt{for r ...: for c ...: if table[r][c] == target: return (r, c)}\quad\text{then}\quad\texttt{return None}}$$

Walk the cells in row order, and the moment one matches, leave the function carrying its two indexes. If the walk finishes, nothing matched, and the line after the loops says so.

Looks like this, but is not

A search that keeps looking after it has found something, because a break only leaves one loop.

def last_match(table, target):
    """Looks like a first match search and is not one.
    """
    place = None
    for r in range(len(table)):
        for c in range(len(table[r])):
            if table[r][c] == target:
                place = (r, c)
                break
    return place


t = [['a', 'b'], ['b', 'a']]
print(last_match(t, 'b'))
(1, 0)

The break leaves the cell loop only, so the row loop carries on and a later match overwrites the earlier one. The first b is at row 0 column 1 and the function reported row 1 column 0, which is the last match rather than the first. Either return the moment you find, or keep the flag and test it in the outer loop as well.

Where is this name sitting, and what to say when it is not

Three calls: one name that appears once, one that appears twice, and one that is not there.

def find_cell(table, target):
    """Assumes table is a table and target is a value.
    Returns a tuple (row, column) for the first cell equal to target,
    searching one row at a time. Returns None if there is no such cell.
    """
    for r in range(len(table)):
        for c in range(len(table[r])):
            if table[r][c] == target:
                return (r, c)
    return None


seats = [['ali', 'bora', 'ceren'],
         ['deniz', 'ege', 'ali'],
         ['fatma', 'gizem', 'hakan']]
print(find_cell(seats, 'ege'))
print(find_cell(seats, 'ali'))
print(find_cell(seats, 'zeynep'))

Sample Run:

(1, 1)
(0, 0)
None
FindWhy the second call reports the earlier of the two, and where the final return has to sit.
Given
  • ali is in row 0 column 0 and also in row 1 column 2.

  • zeynep is in no cell.

Solution

Return from inside the loops instead of keeping a found flag, because break only leaves the inner one.

Follow the walk to the first match

$$(0,0),\ (0,1),\ (0,2),\ (1,0),\ (1,1)$$

Row order, five cells visited before ege is reached. The four cells after it are never compared, because return leaves the function rather than the loop.

$$\texttt{return (r, c)}$$

A tuple, because a position is two numbers that belong together. This is the same use of a tuple as returning two values from a function in the previous section.

See why ali gives the earlier place

$$\texttt{(0, 0)}\ \text{rather than}\ \texttt{(1, 2)}$$

The very first cell matches, so the function leaves immediately and the second ali is never reached. First in row order is a promise the docstring makes and the code keeps.

Put the not found answer after both loops

$$\texttt{return None}$$

Reached only when every cell has been compared, which is exactly what not found means. Inside the loops it would fire after the first cell that did not match.

Answer $$\boxed{\texttt{(1, 1)},\ \texttt{(0, 0)},\ \texttt{None}}$$
Check

Independent check: seats[1][1] is ege and seats[0][0] is ali, so both returned pairs name cells that really match.

Nine comparisons in the worst case, five in the first call: stopping early is cheaper on average and no worse.

Any question of the form where is it in a table wants a tuple back and a sentence in the docstring about the case where it is nowhere.

The largest reading and the day it was taken

Three names travel together here, and the starting value matters.

def largest(table):
    """Assumes table is a table of numbers with at least one cell.
    Returns a tuple (value, row, column) for the largest cell.
    If two cells tie, the one found first wins.
    """
    best = table[0][0]
    best_r = 0
    best_c = 0
    for r in range(len(table)):
        for c in range(len(table[r])):
            if table[r][c] > best:
                best = table[r][c]
                best_r = r
                best_c = c
    return (best, best_r, best_c)


rain = [[12, 0, 44],
        [7, 61, 5],
        [61, 3, 20]]
print(largest(rain))

Sample Run:

(61, 1, 1)
FindWhy the three names have to be updated together, and why the start is table[0][0].
Given
  • The largest value 61 appears twice, at row 1 column 1 and at row 2 column 0.

  • The function starts from the first cell of the table.

Solution

Start from a real cell rather than from a number, and move all three names inside one if.

Start from a cell, not from a number

$$\texttt{best = table[0][0]}$$

Starting from 0 would be wrong for a table of negative numbers, and there is no safe number to start from in general. The first cell is always a legal answer, which is what makes it safe.

$$\texttt{best\_r = 0},\ \texttt{best\_c = 0}$$

They have to agree with best from the first line onwards, or the function can return a value and a position that belong to different cells.

Update all three or none

$$\texttt{best},\ \texttt{best\_r},\ \texttt{best\_c}\ \text{together}$$

Three separate assignments inside one if. Forgetting one of them is the mistake here, and it produces an answer that looks almost right.

$$\texttt{>}\ \text{rather than}\ \texttt{>=}$$

With the strict test a tie leaves the earlier cell in place, which is what the docstring promises. With >= the last of the tied cells would win instead and the printed answer would be (61, 2, 0).

Answer $$\boxed{\texttt{(61, 1, 1)}}$$
Check

Independent check: rain[1][1] is 61 and no cell exceeds 61, and the other 61 at rain[2][0] comes later in row order.

Whenever an answer is a value together with where it came from, keep the names in step and say in the docstring what happens on a tie. Both are marks.

Checkpoint
§07.7 — the first match in row order

Thirty seconds. Two rows, two cells each, and three searches.

def where(table, target):
    """Assumes table is a table and target is a value.
    Returns a tuple (row, column) for the first matching cell,
    or None when there is no match.
    """
    for r in range(len(table)):
        for c in range(len(table[r])):
            if table[r][c] == target:
                return (r, c)
    return None


t = [[7, 3], [3, 7]]
print(where(t, 3))
print(where(t, 7))
print(where(t, 5))
Find(a) Write the three lines this prints.
Given
  • The table is [[7, 3], [3, 7]], so both 3 and 7 appear twice.

  • The search visits cells one row at a time.

IPython console
Hint 1/4

For each call, ask which cell the walk reaches first among the matching ones. That is the only question the function answers.

Hint 2/4

The order is row 0 left to right, then row 1 left to right, and a return ends the whole function rather than one loop.

Hint 3/4

So the visiting order is the cells holding 7, 3, 3, 7 in that sequence, at positions (0, 0), (0, 1), (1, 0) and (1, 1).

Hint 4/4

Two of the answers are pairs from row 0 and the third is a single word.

Show solution

Walk each call in row order; the question is which 3 the search reaches first, not where the 3s are.

Walk in row order for each call

$$\texttt{where(t, 3)}\rightarrow (0, 1)$$

Cell (0,0) holds 7 and does not match; cell (0,1) holds 3 and does, so the function leaves there and never sees the 3 in row 1.

$$\texttt{where(t, 7)}\rightarrow (0, 0)$$

The very first cell matches, so this is the shortest possible search.

$$\texttt{where(t, 5)}\rightarrow \texttt{None}$$

All four cells are compared and none matches, so the line after the loops runs.

Answer $$\boxed{(0, 1),\ (0, 0),\ \texttt{None}}$$
Check

Independent check: reading t[0][1] gives 3 and t[0][0] gives 7, so both pairs name cells that really hold what was asked for.

⚠ Using break for a search inside two loops

A break ends the loop you are in, and with one loop that was the whole search.

wrong$$\texttt{break}\ \text{leaves the cell loop only}$$
right$$\texttt{return (r, c)}\ \text{leaves the function}$$
⚠ Starting the running best at zero

Totals start at zero, so a best does too, and on tables of positive numbers it even works.

wrong$$\texttt{best = 0}$$
right$$\texttt{best = table[0][0]}$$
⚠ Updating the best value without its two indexes

The value is what the comparison is about, and the two bookkeeping lines are easy to leave for later.

wrong$$\texttt{best = table[r][c]}\ \text{alone}$$
right$$\texttt{best},\ \texttt{best\_r},\ \texttt{best\_c}\ \text{in the same if}$$
Building a table whose size arrives while the program is running

Whenever the number of rows or columns is a parameter, a typed value or the length of something else. That covers every table exercise in the lab sheet.

  1. Make the empty table before the loop

    table = [] on its own line. Nothing else goes above the loop, and no row does.

  2. Open a loop over the row numbers

    for r in range(rows):. The index is used even when the rule does not need it, since there is nothing yet to walk over.

  3. Make a fresh row as the first thing inside

    Either row = [] if the cells are to be appended one by one, or row = [0] * cols if they are all the same to start with. Either way the expression is inside the loop, so it runs once per row.

  4. Fill the row

    An inner loop over range(cols) with row.append(...), or nothing at all if the row was already made full of a starting value.

  5. Hand the row to the table

    table.append(row) as the last line of the outer body. One append per row, and never inside the inner loop.

Where it goes wrong
  • The row is built above the outer loop, so every slot holds the same row.

  • The whole table is built with [[0] cols] rows, which is the same failure in one line.

  • table.append(row) sits inside the inner loop, so a half built row is appended several times.

  • row[c] = value on a row made with row = [], which has no cell c yet.

Producing one answer per row, or one answer per column

Any question whose answer is a list shorter than the table: totals, averages, counts, largest values, longest strings, one number for each student or each quiz.

  1. Say out loud how many numbers the answer has

    One per row, or one per column. Getting it wrong gives a program that runs and answers another question.

  2. Put that index on the outer loop

    One per column means the column loop outside and the row loop inside. One per row is the other way round, and its outer loop can be for row in table:.

  3. Start the accumulator as the first line inside the outer loop

    total = 0, or count = 0, or best = table[0][c] for a largest. Inside, not above: above the loop it carries over from the previous row or column.

  4. Do the work in the inner loop

    One statement with the outer index held still, such as total = total + table[r][c].

  5. Append the finished accumulator at the end of the outer body

    answers.append(total), level with the inner loop rather than inside it.

Where it goes wrong
  • The accumulator starts above the outer loop, giving running totals with a right first answer.

  • The append is inside the inner loop, so the answer is as long as the table has cells.

  • A largest starts from 0 rather than a real cell, which breaks on negative data.

  • The inner bound is len(table[0]) on a table whose rows differ, which crashes on the first short row.

Turning lines of text into a table of numbers

Whenever the data is in a file or typed in, which in this course means most exam questions that mention a table at all.

  1. Open the file and start an empty table

    in_file = open(name, 'r') and table = []. Close the file when the loop is done.

  2. Take one line at a time

    for line in in_file: hands over the lines including the newline at the end of each.

  3. Strip, then split

    parts = line.strip().split(','), with the separator the file actually uses. Strip first, or the last part carries the newline.

  4. Convert part by part into a fresh row

    row = [] then a loop with row.append(int(part)). The table of strings you get without it looks right until two cells are added.

  5. Append the row and go round

    table.append(row) at the end of the line loop, so the table gets exactly one row per line.

Where it goes wrong
  • No conversion, so every cell is text and plus joins instead of adding.

  • Splitting before stripping, so the last cell of each row ends in a newline character.

  • A blank line at the end of the file producing an empty row.

  • Closing the file inside the loop, so the second line cannot be read.

doubled_copy, which leaves the table it was given alone

def doubled_copy(table):
    """Assumes table is a table of numbers.
    Returns a NEW table whose cells are twice those of table.
    The table passed in is not changed.
    """
    new = []
    for r in range(len(table)):
        row = []
        for c in range(len(table[r])):
            row.append(table[r][c] * 2)
        new.append(row)
    return new

Called on first = [[1, 2], [3, 4]] it hands back [[2, 4], [6, 8]] and first still prints as [[1, 2], [3, 4]].

FindWhat the caller has to do with the result.
Given
  • The body never assigns to a cell of table.

  • Every row of the answer is a list built inside the loop.

Solution

Read the two giveaway lines, new = [] and return new; the loop between them is the same in both members of the pair.

Read the two tell tale lines

$$\texttt{new = []}$$

A second table is being built, so there are two tables in the program from here on.

$$\texttt{return new}$$

The answer leaves through the return, so a caller that ignores the return value gets nothing at all from the call.

Answer $$\boxed{\texttt{doubled = doubled\_copy(first)}}$$
Check

Independent check: printing first after the call shows the original numbers, which is only possible if no cell of it was assigned to.

double_in_place, which changes the caller's table and returns None

def double_in_place(table):
    """Assumes table is a table of numbers.
    Doubles every cell of table and returns None.
    """
    for r in range(len(table)):
        for c in range(len(table[r])):
            table[r][c] = table[r][c] * 2

Called on first = [[1, 2], [3, 4]] the call itself prints as None, and first then prints as [[2, 4], [6, 8]].

FindWhat the caller must not write.
Given
  • The body assigns to table[r][c].

  • There is no return statement.

Solution

Read the assignment target and look for a return, because the arithmetic is identical in both members of the pair.

Read the two tell tale lines

$$\texttt{table[r][c] = }\ldots$$

An assignment into the caller's own table, which is the whole point of this family.

$$\texttt{first = double\_in\_place(first)}$$

The call that loses everything: the doubling happens and then the name is pointed at None. The correct call is the bare statement.

Answer $$\boxed{\texttt{double\_in\_place(first)}\ \text{as a statement}}$$
Check

Independent check: printing the call shows None, so anything assigned from it is None, whatever the table now holds.

Both functions double every cell of a two by two table and their bodies are nearly the same length, so the difference has to be read off two places: whether there is a new = [] with a return at the end, or an assignment to table[r][c] with no return at all.

How to tell them apart

Look for an equals sign with a cell on its left. If there is one, the function changes the caller's table and hands back None, so call it as a statement. If instead there is a new = [] and a return, the caller must store the result or the work is thrown away.

Scaffolding comes off
The common skeleton
  1. Decide how many numbers the answer has: one per row, or one per column. Write that down first.

  2. Put that index on the outer loop, and the other one on the inner loop.

  3. Start the accumulator as the first line inside the outer loop, never above it.

  4. Do the one line of work in the inner loop, with the outer index held still.

  5. Append the finished accumulator at the end of the outer body, level with the inner loop.

  6. Return the list of answers after both loops.

1 · fully worked

How many cells of each column are above a limit

Four students, three quizzes, and the question is how many students beat 45 on each quiz. The answer has one number per column, so the skeleton is the per column one.

def column_counts(table, limit):
    """Assumes table is a rectangular table of numbers with at least one row
    and limit is a number.
    Returns a list holding, for each column, how many of its cells are
    greater than limit.
    """
    counts = []
    for c in range(len(table[0])):
        count = 0
        for r in range(len(table)):
            if table[r][c] > limit:
                count = count + 1
        counts.append(count)
    return counts


marks = [[60, 70, 80],
         [90, 50, 40],
         [30, 60, 90],
         [40, 20, 10]]
print(column_counts(marks, 45))

Sample Run:

[2, 3, 2]
FindThe three counts, and where each line of the skeleton shows up in the code.
Given
  • The table has four rows and three columns.

  • The limit is 45 and the test is strictly greater.

Solution

The column loop goes outside because the answer has one number per column; the other way round needs three counters alive at once.

One answer per column, so the column loop is outer

$$\texttt{for c in range(len(table[0])):}$$

Three columns, three answers, so this loop runs three times and each turn produces one number. The table is stated to be rectangular in the docstring, which is what licenses len(table[0]) here.

$$\texttt{count = 0}$$

First line inside the outer loop, so each column starts from nothing. Above the loop it would report running totals: 2, then 5, then 7.

The inner loop walks down

$$\texttt{if table[r][c] > limit: count = count + 1}$$

Only r moves, so this reads one column from top to bottom. The strict comparison matters: a mark of exactly 45 does not count.

Check one column by hand

$$60, 90, 30, 40\;\rightarrow\;2$$

Column 0 top to bottom. Two of the four are above 45, which is the first number of the answer.

$$70, 50, 60, 20\;\rightarrow\;3$$

Column 1, where three of the four beat the limit. This is the column that would give 5 if the count were not reset.

Answer $$\boxed{\texttt{[2, 3, 2]}}$$
Check

Independent check: the three counts add to 7, and counting the marks above 45 one by one also gives 7.

Every remaining rung is this function with the body of the inner loop changed. The skeleton does not move.

2 · you write the reasoning

Easier this time, and the code is given. The question is the same one turned round: how many cells of each ROW are above the limit. Because the answer is one number per row, the outer loop can hand over the row object itself and no index is needed anywhere. Read the four steps and write the reason column yourself, then open the model reasons and compare.

def row_counts(table, limit):
    """Assumes table is a table of numbers and limit is a number.
    Returns a list holding, for each row, how many of its cells are
    greater than limit.
    """
    counts = []
    for row in table:
        count = 0
        for cell in row:
            if cell > limit:
                count = count + 1
        counts.append(count)
    return counts


marks = [[60, 70, 80],
         [90, 50, 40],
         [30, 60, 90],
         [40, 20, 10]]
print(row_counts(marks, 45))

Sample Run:

[3, 2, 2, 0]
  1. for row in table: hands over one whole row object per turn, with no index anywhere.

    reasoning

    One answer per row, so the row index is the one held still, and a row is an object the loop can hand over directly. This is why the per row version is the easier of the two and why it also works on a ragged table without any change.

  2. count = 0 sits as the first line inside the outer loop, not above it.

    reasoning

    First line inside the outer loop. Four rows means four fresh starts, and the four answers are independent of each other.

  3. for cell in row: walks the row the outer loop just handed over.

    reasoning

    No index is needed because nothing is assigned to. The name cell holds the value, and the test only reads it.

  4. counts.append(count) sits at the end of the outer body, level with the inner loop.

    reasoning

    At the end of the outer body, level with the inner loop. One append per row gives an answer of four numbers, which is the shape the docstring promises.

3 · find the buried error

Harder, and the program is somebody else's. It is supposed to return the average of each column to one decimal place. The table has four rows and three columns, so the correct answer is [55.0, 50.0, 55.0]. It prints something else, and there are exactly two mistakes in it.

def column_averages(table):
    """Assumes table is a rectangular table of numbers with at least one row.
    Returns a list with the average of each column, to one decimal place.
    """
    averages = []
    total = 0
    for c in range(len(table[0])):
        for r in range(len(table)):
            total = total + table[r][c]
        averages.append(round(total / len(table[0]), 1))
    return averages


marks = [[60, 70, 80],
         [90, 50, 40],
         [30, 60, 90],
         [40, 20, 10]]
print(column_averages(marks))

Sample Run:

[73.3, 140.0, 213.3]
  1. Collect the answers in a list and keep a running total for the column being worked on.

  2. One answer per column, so the column loop is the outer one.

  3. Walk down the column, adding each cell to the total.

  4. Divide the total by how many numbers went into it and round to one place.

  5. Hand back the finished list.

the two buried errors (2)
⚠ step 1

total = 0 is above the outer loop instead of inside it, so each column adds itself to whatever the previous columns left behind. Column 0 sums to 220 and comes out as 220 divided by something, while column 1 sums to 200 but is divided after being added to the 220, and column 2 carries both.

One name for the total looks tidier than resetting it every turn, and the first answer still looks plausible, so nothing draws attention to it. It is the single commonest error in per column code.

right

Move total = 0 to be the first line inside the column loop, so it runs once per column.

⚠ step 4

The divisor is len(table[0]), the number of columns, where the average of a column needs the number of rows, len(table). Here that is 3 instead of 4, so every average is a quarter too large even after the first mistake is fixed.

Both lengths are in scope and both are small numbers read off the same table, and on a square table the two give the same answer, so the mistake never shows up while you are testing on a three by three example.

right

Divide by len(table): a column has one cell per row, so the count of numbers in the average is the number of rows.

4 · the bare problem
§07.4 — one spread per column, no scaffolding

Bare rung. A quiz is called uneven when the marks on it are spread far apart, and the spread of a column is its largest cell minus its smallest. Write the function, using nothing beyond what this section uses.

marks = [[60, 70, 80],
         [90, 50, 40],
         [30, 60, 90],
         [40, 20, 10]]
print(column_ranges(marks))
Find(a) Write column_ranges(table) with a docstring, and say what the call above prints.
Given
  • The table has four rows and three columns and may be assumed rectangular.

  • The spread of a column is its largest cell minus its smallest cell.

  • The answer is a list with one number per column, in column order.

Hint 1/4

The answer has one number per column, so before writing anything decide which loop is the outer one and how many accumulators each turn of it needs.

Hint 2/4

Two accumulators per column, a largest and a smallest, and both must start from a real cell of that column rather than from a number. table[0][c] is the safe start for both.

Hint 3/4

Column 0 is 60, 90, 30, 40, column 1 is 70, 50, 60, 20 and column 2 is 80, 40, 90, 10, so both starting values for column 0 are 60.

Hint 4/4

The three spreads are 60, 50 and 80 in that order.

Show solution

Both accumulators start from table[0][c], and one pass updates both, so the column is never read twice.

Fix the shape from the answer's length

$$\texttt{for c in range(len(table[0])):}$$

Three answers for three columns, so the column index is held still while the inner loop runs. This is the same first step as every rung of this ladder.

$$\texttt{high = low = table[0][c]}\ \text{written as two lines}$$

Both start from a cell that is really in the column, so no comparison can be against a number that does not belong to the data. Two separate lines because the course has not needed chained assignment.

Walk the column once, updating both

$$\texttt{if table[r][c] > high: high = table[r][c]}$$

One pass is enough for both, so the column is not walked twice. The row index is the only thing that moves.

$$\texttt{if table[r][c] < low: low = table[r][c]}$$

A separate if rather than an elif, because a single cell can be neither, and on the first turn it is both the largest and the smallest seen so far.

Work the three answers out by hand

$$90 - 30 = 60,\quad 70 - 20 = 50,\quad 90 - 10 = 80$$

Column by column from the data, which is what the printed list should match.

Answer $$\boxed{\texttt{[60, 50, 80]}}$$
Check

Independent check: no spread can be negative or exceed 90 minus 10, that is 80, and the largest of the three answers is exactly 80.

Two accumulators are no harder than one, as long as both start from a real cell rather than from 0.

Full exam-style question

best_row: the row with the largest total, and its index, in one passexam format

Exam shape: one function, a stated return type, and a tie rule. Write best_row(table) which takes a table of numbers and returns a tuple holding the index of the row with the largest total and that total. If two rows tie, the earlier one wins.

def best_row(table):
    """Assumes table is a table of numbers with at least one row and
    no empty rows.
    Returns a tuple (index, total) for the row with the largest total.
    If two rows have the same total, the earlier one wins.
    """
    best_index = 0
    best_total = 0
    for cell in table[0]:
        best_total = best_total + cell

    for r in range(1, len(table)):
        total = 0
        for cell in table[r]:
            total = total + cell
        if total > best_total:
            best_total = total
            best_index = r
    return (best_index, best_total)


sales = [[12, 4, 9],
         [7, 20, 3],
         [8, 8, 14],
         [10, 10, 10]]
print(best_row(sales))
print(best_row([[5], [5], [4]]))

Sample Run:

(1, 30)
(0, 5)
FindWhy the answer is row 1 rather than row 2 or row 3, and why the first row is totalled before the loop.
Given
  • The four row totals are 25, 30, 30 and 30.

  • The second call has two rows tied on 5.

Solution

Total row 0 before the loop so the best so far is always a real row total rather than a chosen number.

Total row 0 first so that the best is a real row

$$\texttt{best\_total} = 12 + 4 + 9 = 25$$

Starting from a real row means no comparison is ever made against a number that is not a row total. Starting best_total at 0 would work here because the marks are positive, and would quietly fail on a table with negative numbers in it.

$$\texttt{best\_index = 0}$$

It has to agree with best_total from the first line, or the function can return an index and a total belonging to different rows.

Walk the remaining rows from 1

$$\texttt{for r in range(1, len(table)):}$$

Row 0 has already been counted, so starting at 0 would compare it with itself. Harmless here, and it is the kind of double counting that goes wrong the moment the body does anything besides compare.

$$\texttt{total = 0}\ \text{inside}$$

One total per row, reset as each row starts. Exactly the same line as in the per row skeleton earlier on the page.

Apply the tie rule with a strict comparison

$$\text{rows }1, 2, 3\ \text{all total } 30$$

7 plus 20 plus 3, then 8 plus 8 plus 14, then 10 plus 10 plus 10. All three are 30, so the tie rule decides the answer.

$$\texttt{if total > best\_total}$$

Strictly greater, so row 2 does not displace row 1 and row 3 does not displace row 2. Writing >= here would give (3, 30) and contradict the docstring.

Check the second call

$$\texttt{best\_row([[5], [5], [4]])} = (0, 5)$$

Rows of one cell are still rows. The tie between rows 0 and 1 goes to row 0, and row 2 never gets close.

Answer $$\boxed{(1, 30)\ \text{and}\ (0, 5)}$$
Check

Independent check without redoing the additions: the returned index must name a row whose total really is 30, and adding row 1 alone gives 30. The answer is also the smallest of the three indexes reaching 30, as the tie rule promised.

Each cell is added exactly once, so twelve additions for a twelve cell table and no row is walked twice.

Two things earn the marks here and neither is the loop: starting the best from a real row, and choosing between > and >= on purpose.

Practice

A · concept 4 questions
1§07.1 — what len counts on a table

A table of three rows, two cells each, and a claim about the printed number.

pairs = [[3, 1], [4, 1], [5, 9]]
print(len(pairs))

The claim: this prints 6, because the table holds six numbers.

Find(a) True or false, with the reason in one sentence.
Given
  • The table is [[3, 1], [4, 1], [5, 9]].

  • There are six numbers in it altogether.

Hint 1/4

Rewrite the table in your head as a list of three things, and name what each of those three things is. Then the count is obvious.

Hint 2/4

len counts the items of the object it is handed, one level down and no further. It never looks inside those items.

Hint 3/4

The items here are the three rows [3, 1], [4, 1] and [5, 9], each of which is itself a list.

Hint 4/4

False: the printed number is the number of rows, and the number of cells would take a loop to work out.

Show solution

Name the items before counting them, because the question is what len counts and not how many numbers are on the page.

Name the items

$$\text{items of }\texttt{pairs} = \texttt{[3, 1]},\ \texttt{[4, 1]},\ \texttt{[5, 9]}$$

Three of them, and each one is a list rather than a number.

$$\texttt{len(pairs)} = 3$$

The count of those items. Nothing in the call asks about their contents.

Answer $$\boxed{\text{False};\ \texttt{len(pairs)} = 3}$$
Check

Independent check: pairs[2] is a legal index and pairs[3] is an IndexError, which puts the number of items at 3 without using len at all.

2§07.3 — one assignment into a table built with the star operator

A three by three grid of zeros is built in one line, and then the middle cell is set to 5.

grid = [[0] * 3] * 3
grid[1][1] = 5
print(grid)
Find(a) Which line does this print?
Given
  • The grid is built with [[0] 3] 3.

  • Exactly one cell is assigned to.

Hint 1/4

Answer a smaller question first: how many list objects does the first line create in total, counting the outer one and the rows.

Hint 2/4

The star operator repeats the items of a list. When an item is itself a list, what gets repeated is the reference to it, not the list.

Hint 3/4

So [0] * 3 runs once and gives one row, and the outer star puts that one row into all three slots of the grid.

Hint 4/4

The three printed rows are identical to each other, and the 5 is in the middle of each of them.

Show solution

Count the objects the building line makes before following the assignment, which is itself unambiguous.

Count the objects the building line makes

$$\texttt{[0] * 3}\;\rightarrow\;\text{one row}$$

This is evaluated once, before the outer star sees it, so there is one row to repeat.

$$\texttt{[\,\text{row}\,] * 3}\;\rightarrow\;\text{three slots, one row}$$

The star copies the item, and the item is a reference. Three references to one object.

Follow the single assignment

$$\texttt{grid[1][1] = 5}$$

The first bracket reaches the and the second changes its middle cell, so all three slots now report a 5 in the middle.

Answer $$\boxed{\texttt{[[0, 5, 0], [0, 5, 0], [0, 5, 0]]}}$$
Check

Independent check: len(grid) is 3 but the number of distinct row objects is 1, and grid[0] is grid[2] is True. Neither of those depends on the printed line.

3§07.5 — what a full slice of a table protects

A backup of a table is taken with a full slice, and then one cell of the backup is changed.

table = [[1, 2], [3, 4]]
backup = table[:]
backup[0][0] = 99
print(table)

The claim: this prints [[1, 2], [3, 4]], because backup is a copy and writing to a copy cannot affect the original.

Find(a) True or false, with the reason in one sentence.
Given
  • backup was made with table[:].

  • One cell of backup is assigned to.

Hint 1/4

Separate two questions: is the outer list a new object, and are the rows new objects. The claim needs both and the slice gives one.

Hint 2/4

A full slice builds a new list holding the same items. The items of a table are rows, so what is duplicated is the references to them.

Hint 3/4

Here backup[0] and table[0] are the same list [1, 2], and the assignment writes into that list.

Hint 4/4

False: the 99 shows in both, and a backup with independent rows takes a loop that slices each row.

Show solution

Ask which level the slice copied instead of judging the claim as worded, since it is true of the outer list.

Ask what the slice copied

$$\texttt{table[:]}\;\rightarrow\;\text{new outer list, same rows}$$

A slice copies items, and the items here are two references to two rows.

$$\texttt{backup[0]}\ \text{is}\ \texttt{table[0]}$$

Same object, so the assignment has one place to land and both names show it.

Answer $$\boxed{\text{False};\ \texttt{[[99, 2], [3, 4]]}}$$
Check

Independent check with no assignment at all: backup is table is False while backup[0] is table[0] is True. Those two lines say exactly what the slice did and did not do.

4§07.4 — a safe inner bound on a ragged table

A function is to walk every cell of a table whose rows are not promised to be the same length, so the inner loop bound has to be chosen carefully.

table = [[1, 2, 3], [4], [5, 6]]
for r in range(len(table)):
    for c in range(???):
        print(table[r][c], end=' ')
    print()
Find(a) Which expression belongs where the question marks are?
Given
  • The three rows hold three, one and two cells.

  • The walk must reach every cell and must not stop the program.

Hint 1/4

The bound is being chosen once per row, inside the outer loop, so it is allowed to depend on which row the walk is on.

Hint 2/4

len(table[r]) is the length of row r, and that is the only expression that is correct for every row of a table whose rows differ.

Hint 3/4

Row 0 has three cells, row 1 has one and row 2 has two, so the three bounds needed here are 3, 1 and 2.

Hint 4/4

Only one of the four expressions gives a different number for each row.

Show solution

Pick the bound by what it has to depend on, not by trying candidates on row 0, where both look right.

Ask what the bound has to depend on

$$\texttt{len(table[r])}\in\{3, 1, 2\}$$

Evaluated once per row, so it changes with r, which is exactly what a ragged table needs.

$$\texttt{len(table[0])} = 3\ \text{always}$$

A single number used for every row, and row 1 has one cell, so the second turn of the walk reaches for a cell that does not exist.

Answer $$\boxed{\texttt{range(len(table[r]))}}$$
Check

Independent check by counting cells: the safe bound makes the walk print 3 plus 1 plus 2, which is six values, and the table holds six numbers.

B · computation 7 questions
1§07.2 — a diagonal printed with a separator

The walk prints the cell when the two indexes are equal and a dot when they are not.

table = [[1, 2, 3],
         [4, 5, 6],
         [7, 8, 9]]
for r in range(len(table)):
    for c in range(len(table[r])):
        if r == c:
            print(table[r][c], end=' ')
        else:
            print('.', end=' ')
    print()
Find(a) Write the three lines this prints.
Given
  • The table holds 1 to 9 in reading order.

  • Every printed item is followed by a space, and each row ends with a bare print.

IPython console
Hint 1/4

Work out for each row which single position satisfies the test, then fill the rest of the row with dots.

Hint 2/4

The walk is one row at a time, left to right, and the bare print at the end of the outer body ends each row.

Hint 3/4

In row 0 the equal position is column 0, holding 1; in row 1 it is column 1, holding 5; in row 2 it is column 2, holding 9.

Hint 4/4

Three lines, each with three items on it, and the numbers running down from the top left to the bottom right.

Show solution

Find the one matching position per row and take the dots as the rest, rather than deciding all nine cells.

Find the matching position in each row

$$r = 0\;\Rightarrow\;c = 0,\ \texttt{table[0][0]} = 1$$

One position per row satisfies r equal to c, and in row 0 it is the first.

$$r = 1\;\Rightarrow\;\texttt{table[1][1]} = 5,\quad r = 2\;\Rightarrow\;\texttt{table[2][2]} = 9$$

The same reasoning in the other two rows, and this is the main diagonal of the table.

Place the line endings

$$\texttt{print()}\;\times 3$$

One per turn of the outer loop, so three lines. Without it all nine items would be on one line.

Answer $$\boxed{\texttt{1 . .}\;/\;\texttt{. 5 .}\;/\;\texttt{. . 9}}$$
Check

Independent check by counting: nine items are printed, three of them numbers and six of them dots, and the three numbers are the three cells with equal indexes.

2§07.2 — writing into the cells above a condition

A three by four table of zeros is built with a loop, then some of its cells are given the sum of their two indexes.

grid = []
for r in range(3):
    grid.append([0] * 4)

for r in range(len(grid)):
    for c in range(len(grid[r])):
        if c > r:
            grid[r][c] = r + c
print(grid)
Find(a) Write the line this prints.
Given
  • The grid starts as three rows of four zeros, built inside a loop.

  • A cell is written only when its column index is greater than its row index.

IPython console
Hint 1/4

For each row, list which columns pass the test before working out any value. The number of cells that change falls by one each row.

Hint 2/4

The rows are separate objects here, because the row was built inside the loop, so each assignment touches one cell only.

Hint 3/4

In row 0 the columns 1, 2 and 3 pass; in row 1 the columns 2 and 3 pass; in row 2 only column 3 passes.

Hint 4/4

Every row still has four cells, and the zeros left in each row sit on the left.

Show solution

Settle which cells the condition admits before computing any value, because the refused ones keep their zeros.

Find the cells the condition lets through

$$r = 0:\;c \in \{1, 2, 3\}$$

Strictly greater, so column 0 is excluded in row 0 even though both indexes are equal there.

$$r = 1:\;c \in \{2, 3\},\quad r = 2:\;c \in \{3\}$$

One fewer column each row, which is why the untouched zeros form a triangle on the left.

Work out the values

$$r + c:\;0+1, 0+2, 0+3 = 1, 2, 3$$

Row 0 in order. The value is the sum of the indexes, not the column number.

$$1+2, 1+3 = 3, 4;\quad 2+3 = 5$$

Rows 1 and 2, giving the last two rows of the answer.

Confirm the rows are separate

$$\texttt{grid.append([0] * 4)}\ \text{inside the loop}$$

The expression runs three times, so there are three row objects and each assignment is visible in one row only.

Answer $$\boxed{\texttt{[[0, 1, 2, 3], [0, 0, 3, 4], [0, 0, 0, 5]]}}$$
Check

Independent check by counting the changed cells: 3 plus 2 plus 1 is 6, and the printed table has six non zero cells. The zeros number 6 as well, out of twelve.

3§07.3 — a board built with the star operator

A board of three rows and two cells is built in one line, one cell is set, and then the two lengths are printed.

board = [['.'] * 2] * 3
board[1][0] = 'x'
for row in board:
    print(row)
print(len(board), len(board[0]))
Find(a) Write the four lines this prints.
Given
  • The board is built with [['.'] 2] 3.

  • One cell is assigned to, in row 1.

IPython console
Hint 1/4

Two questions, and they have different answers: how many slots does the board have, and how many rows does it really have.

Hint 2/4

The outer star repeats the reference to the single row that the inner star built, so all the slots reach one object.

Hint 3/4

So writing an x into position 0 of the one row is visible from all three slots, and each printed row is a list of two items.

Hint 4/4

The first three lines are identical to each other, and the last line is two small numbers.

Show solution

Count the row objects first and read the two len calls last, since both lengths hold either way.

Count the row objects

$$\texttt{[['.'] * 2] * 3}\;\rightarrow\;\text{1 row, 3 slots}$$

The inner star runs once and the outer star repeats its result, which is a reference.

$$\texttt{board[1][0] = 'x'}$$

Lands in the single row, so the printing loop shows it three times.

Read the two lengths

$$\texttt{len(board)} = 3,\ \texttt{len(board[0])} = 2$$

Both are true statements about the structure. Neither of them reveals that the three slots share one row, which is why is is the test for that.

Answer $$\boxed{\text{three identical rows, then}\ \texttt{3 2}}$$
Check

Independent check: the number of x characters printed is 3 while only one assignment was made, and that mismatch is itself the proof that the rows are shared.

4§07.1 — a table indexed by its own contents

A while loop walks the rows, and each row says which cell of the table to write to.

t = [[1, 1], [0, 1], [1, 0]]
i = 0
while i < len(t):
    r = t[i][0]
    c = t[i][1]
    t[r][c] = i
    i = i + 1
print(t)
Find(a) Write the line this prints.
Given
  • The table is [[1, 1], [0, 1], [1, 0]] at the start.

  • Each turn reads the two cells of row i and uses them as a row and a column index.

IPython console
Hint 1/4

Write the table out after every turn of the loop. The values it holds are also the indexes it uses, so a change on one turn can change where the next turn writes.

Hint 2/4

r and c are read first, from row i, and only then is the assignment made. The assignment may land anywhere in the table, including in a row the loop has already passed.

Hint 3/4

At the start row 0 is [1, 1], row 1 is [0, 1] and row 2 is [1, 0], so the first turn reads r as 1 and c as 1.

Hint 4/4

Three turns happen and the table ends with a 2 in it, which is a value no starting cell had.

Show solution

Take the turns in order and re-read the table each time, because an earlier write changes what a later turn reads.

First turn, i is 0

$$r = \texttt{t[0][0]} = 1,\ c = \texttt{t[0][1]} = 1$$

Both indexes come out of row 0, which is untouched at this point.

$$\texttt{t[1][1]} = 0\;\Rightarrow\;\text{row 1 becomes}\ \texttt{[0, 0]}$$

The write lands in row 1, which the loop has not reached yet, so the next turn will read the changed version.

Second turn, i is 1

$$r = \texttt{t[1][0]} = 0,\ c = \texttt{t[1][1]} = 0$$

Row 1 is now [0, 0] rather than [0, 1], so c is 0 and not 1. This is the whole difficulty of the question.

$$\texttt{t[0][0]} = 1$$

Writes a 1 where a 1 already was, so this turn leaves no visible trace at all.

Third turn, i is 2

$$r = \texttt{t[2][0]} = 1,\ c = \texttt{t[2][1]} = 0$$

Row 2 was never written to, so its original values are used.

$$\texttt{t[1][0]} = 2$$

Row 1 is changed a second time, and 2 is the value of i on the last turn.

Answer $$\boxed{\texttt{[[1, 1], [2, 0], [1, 0]]}}$$
Check

Independent check: only three writes happened, at (1,1), (0,0) and (1,0), so no other cell can differ from the start and the printed table agrees.

5§07.3 — the same row appended three times

The row is built before the loop, and the loop appends it.

row = [0, 0, 0]
table = []
for r in range(3):
    table.append(row)
table[0][1] = 7
print(table)
row.append(9)
print(table)
Find(a) Write the two lines this prints.
Given
  • One list is built before the loop and appended three times.

  • After the first print, an item is appended to that list through its own name.

IPython console
Hint 1/4

Count the list objects in this program. The loop runs three times and creates none, which decides both printed lines.

Hint 2/4

Appending a name adds a reference to the object that name holds. Appending it three times adds three references to one object.

Hint 3/4

So table[0], table[1] and table[2] are all the list that row names, and the last statement lengthens that very list.

Hint 4/4

Both printed lines show three identical rows, and on the second line every row has four cells.

Show solution

Count the list objects before following either change, or a fourth cell appearing in all three rows has no explanation.

Count the objects

$$\texttt{row = [0, 0, 0]}\ \text{once, above the loop}$$

One list. The loop body has no expression that builds another, so nothing new appears in three turns.

$$\texttt{table.append(row)} \times 3$$

Three references to that one list, so table has three items and one distinct row.

Follow the two changes

$$\texttt{table[0][1] = 7}$$

Reaches the shared row through the table, so all three printed rows show the 7.

$$\texttt{row.append(9)}$$

Reaches the same row through its own name and makes it longer, so all three printed rows now have four cells.

Answer $$\boxed{\text{three rows of}\ \texttt{[0, 7, 0]},\ \text{then of}\ \texttt{[0, 7, 0, 9]}}$$
Check

Independent check: table[0] is table[2] and table[0] is row are both True, so one row object has four names for it.

6§07.6 — field widths against a single space

Two rows printed twice over, once with a field width and once with a space, so the spaces are the answer.

table = [[7, 105, 3],
         [42, 8, 1000]]
for row in table:
    for cell in row:
        print(format(cell, '6d'), end='')
    print()
Find(a) Write the two lines this prints, with the spaces in the right places.
Given
  • format(cell, '6d') gives a six character string, right aligned.

  • The values have one, three, one, two, one and four digits.

IPython console
Hint 1/4

Work out the length of each printed line before its contents. Three fields of six characters fixes it.

Hint 2/4

Right aligned in six means the digits are pushed to the right hand end of the field and the space left over goes in front.

Hint 3/4

So 7 carries five spaces, 105 carries three, 3 carries five, 42 carries four, 8 carries five and 1000 carries two.

Hint 4/4

Both lines are eighteen characters long and their last digits sit in the same column.

Show solution

Pad each value out to its own field width rather than counting gaps, since right aligned padding is decided per value.

Pad each value to six characters

$$7\rightarrow\text{5 spaces},\ 105\rightarrow\text{3 spaces},\ 3\rightarrow\text{5 spaces}$$

Six minus the number of digits, all of it in front, which is what right aligned means.

$$42\rightarrow\text{4},\ 8\rightarrow\text{5},\ 1000\rightarrow\text{2}$$

The second row, whose values have two, one and four digits.

Place the line endings

$$\texttt{print()}\ \text{once per row}$$

In the outer body, so two lines come out. The inner print never ends a line because its ending is the empty string.

Answer $$\boxed{\text{two lines of 18 characters}}$$
Check

Independent count: three fields of six is eighteen characters per line, and counting either printed line gives eighteen. The digits of the last column end in the same place on both lines.

7§07.7 — counting matches and keeping the last place

This search does not stop early, because it has to count as well as find.

def count_and_last(table, target):
    """Assumes table is a table and target is a value.
    Returns a tuple (how many cells equal target, the last such position).
    The position is None when the count is zero.
    """
    count = 0
    place = None
    for r in range(len(table)):
        for c in range(len(table[r])):
            if table[r][c] == target:
                count = count + 1
                place = (r, c)
    return (count, place)


t = [[1, 2, 1], [3, 1, 2]]
print(count_and_last(t, 1))
print(count_and_last(t, 9))
Find(a) Write the two lines this prints.
Given
  • The table is [[1, 2, 1], [3, 1, 2]].

  • The second call looks for a value that is in no cell.

IPython console
Hint 1/4

Two answers travel together here, a count and a position, and the position is overwritten every time the count goes up.

Hint 2/4

There is no return inside the loops, so the walk always visits every cell and the last match is the one left in place.

Hint 3/4

The value 1 is at (0, 0), at (0, 2) and at (1, 1), visited in that order, and 9 is nowhere.

Hint 4/4

The first line holds a three and a pair, and the second line holds a zero and a word.

Show solution

Let the walk finish and keep overwriting place, because the last match is wanted and an early return gives the first.

Walk every cell for the first call

$$(0,0)\ \text{matches},\ (0,2)\ \text{matches},\ (1,1)\ \text{matches}$$

Three cells hold 1, visited in row order, so count reaches 3.

$$\texttt{place} = (1, 1)$$

Each match overwrites place, so the value left at the end is the last match. Returning inside the loop would have given (0, 0) instead.

The call that finds nothing

$$\texttt{count} = 0,\ \texttt{place} = \texttt{None}$$

Neither is ever touched, so the starting values are the answer, and None is the one value no real position can be confused with.

Answer $$\boxed{(3, (1, 1))\ \text{and}\ (0, \texttt{None})}$$
Check

Independent check: the table has six cells of which three hold a 1, so a count of 3 is consistent, and t[1][1] really is 1, so the reported position names a matching cell.

C · exam level 4 questions
1§07.4 — a function that reports whether a table is rectangular

Exam shape, and the answer has to cope with the awkward cases. Write a function is_rectangular(table) in a file called shape.py, with a docstring, which returns True when every row of the table has the same length and False otherwise. A table with no rows counts as rectangular. Then show what the four calls below print.

print(is_rectangular([[1, 2], [3, 4], [5, 6]]))
print(is_rectangular([[1, 2], [3], [5, 6]]))
print(is_rectangular([]))
print(is_rectangular([[]]))
Find
  1. (a) Write is_rectangular(table) with its docstring.

  2. (b) Give the four lines the calls above print.

Given
  • The parameter is a table and nothing is promised about its rows.

  • A table with no rows is rectangular, and so is a table of one empty row.

  • Only material covered in the course may be used.

Hint 1/4

The question is whether all the row lengths agree, so pick one of them as the yardstick and compare the rest against it. Decide first what to do when there is no row to pick.

Hint 2/4

len(table) is the number of rows and len(table[r]) the length of row r. Reading table[0] is only safe once you know the table has at least one row.

Hint 3/4

For the four calls the row lengths are 2, 2, 2 then 2, 1, 2 then nothing at all then a single 0.

Hint 4/4

Three of the four calls print True and the second one prints False.

Show solution

Clear the empty table with the first test, since reading table[0] needs a row; inside the loop it would run once per row.

Deal with the case that has no yardstick

$$\texttt{if len(table) == 0: return True}$$

Reading table[0] needs a row to exist. Putting this test first is what makes the rest of the body safe, and the specification says which answer to give.

$$\texttt{width = len(table[0])}$$

One row is chosen as the standard. Any row would do; row 0 is the cheapest to name.

Compare every row against it

$$\texttt{if len(table[r]) != width: return False}$$

Returning at the first mismatch is both shorter and faster than a flag, and there is nothing left to learn once one row disagrees.

$$\texttt{return True}\ \text{after the loop}$$

Reached only when no row disagreed, which is what all rows agree means. Inside the loop it would report success after the first matching row.

Work through the four calls

$$[2, 2, 2] \rightarrow \texttt{True},\quad [2, 1, 2] \rightarrow \texttt{False}$$

The second call disagrees at row 1, so it returns without looking at row 2.

$$\texttt{[]} \rightarrow \texttt{True},\quad \texttt{[[]]} \rightarrow \texttt{True}$$

No rows at all takes the first test; one row of length 0 takes the loop, which finds one row whose length equals the yardstick 0.

Answer $$\boxed{\texttt{True},\ \texttt{False},\ \texttt{True},\ \texttt{True}}$$
Check

Independent check on the two awkward calls: len([]) is 0 and len([[]]) is 1, so they take different paths and reach True for different reasons.

Any function that indexes row 0 needs a line above it for the table with no row 0.

2§07.6 — a script that loads a table from a file and totals its columns

Exam shape, full script. A file rainfall.txt holds one line per measuring station and one whole number per month, separated by commas.

12,0,44
7,61,5
20,3,18

Write a script in rain.py with two functions, read_table(filename) which returns the file as a table of ints, and column_totals(table) which returns a list with one total per column. The script should print the table with every cell in a field five characters wide, then a line of fifteen dashes, then the totals in the same field width.

Find
  1. (a) Write the two functions and the script that uses them.

  2. (b) Give the Sample Run for the file above.

Given
  • The file has three lines of three comma separated whole numbers.

  • Both functions need a docstring, and only course material may be used.

  • The printed cells are five characters wide, so each line is fifteen characters long.

Hint 1/4

Three jobs and they do not mix: turn text into a table, turn a table into three totals, print both. Write the three separately and the script is four lines.

Hint 2/4

One line of the file becomes one row: strip, split on the comma, convert each part with int, append the row. One total per column means the column index goes on the outer loop with the total reset inside it.

Hint 3/4

The columns hold 12, 7, 20 then 0, 61, 3 then 44, 5, 18, so the totals to expect are 39, 64 and 67.

Hint 4/4

Five printed lines: three of data, one of dashes, and one of totals lining up under the data.

Show solution

Split the work so one function touches the file and the other only a table, so each half can be tested alone.

Split the work into three jobs

$$\texttt{read\_table}\;\rightarrow\;\text{text to table}$$

It touches the file and nothing else, so it can be tested by printing what it returns. Mixing the totalling into it would make both harder to check.

$$\texttt{column\_totals}\;\rightarrow\;\text{table to list}$$

It touches no file, so it can be tested on a table written by hand, which is how you would find a wrong loop bound.

Convert as each part arrives

$$\texttt{row.append(int(part))}$$

One conversion per cell, inside the part loop. This is the only place where text becomes a number, and skipping it is the classic way this question is lost.

$$\texttt{line.strip().split(',')}$$

Strip first so the last part of each line has no newline on it, then split at the commas.

Total the columns

$$12 + 7 + 20 = 39$$

Column 0 read downwards, which is what the inner loop over r does with c held at 0.

$$0 + 61 + 3 = 64,\quad 44 + 5 + 18 = 67$$

The other two columns, giving the three numbers on the last printed line.

Print in fields of five

$$\texttt{format(cell, '5d')},\ \texttt{end=''}$$

The field width makes the columns and the empty ending keeps the row on one line, with the bare print after the inner loop ending it.

Answer $$\boxed{\texttt{[39, 64, 67]},\ \text{printed in fields of five}}$$
Check

Independent check: the nine numbers add to 170 and so do the three totals, and each total is at least the biggest cell of its column.

Reading, computing and printing as three functions is what lets you find which of the three is wrong.

3§07.4 — a per column largest that fails on negative data

The function is supposed to return the largest cell of each column. On this table of temperature readings, all of them at or below zero in two of the three columns, it prints something impossible.

def column_maxima(table):
    """Assumes table is a rectangular table of numbers with at least one row.
    Returns a list with the largest cell of each column.
    """
    maxima = []
    for c in range(len(table[0])):
        best = 0
        for r in range(len(table)):
            if table[r][c] > best:
                best = table[r][c]
        maxima.append(best)
    return maxima


readings = [[-4, 12, -20],
            [-9, 3, -15],
            [-2, 8, -30]]
print(column_maxima(readings))

It prints [0, 12, 0], and the right answer is [-2, 12, -15].

Find(a) Which line is wrong, and why does column 1 still come out right?
Given
  • The table is rectangular, three rows by three columns.

  • Columns 0 and 2 hold negative numbers only.

  • The printed answer is [0, 12, 0] and the correct answer is [-2, 12, -15].

Hint 1/4

Ask what answer the function would give for a column in which no cell passes the very first comparison. That is the shape of the fault.

Hint 2/4

A running largest has to start from a value that is really in the data, because any number you choose instead becomes an answer the data never offered.

Hint 3/4

Column 0 holds -4, -9 and -2, and none of them is greater than 0, so the starting value is never replaced. Column 1 holds 12, 3 and 8, and 12 beats 0.

Hint 4/4

The fix is one line: start from table[0][c] rather than from 0.

Show solution

Start from the column whose reported value is in no cell, which points straight at a starting value that survived.

Find the column where the start survived

$$\text{column 0}: -4, -9, -2\ \text{all}\ <0$$

No cell passes the comparison against 0, so best is still 0 when the append happens. The reported value is not in the data at all, which is the signature of this fault.

$$\text{column 1}: 12 > 0$$

The first cell replaces the starting value, after which the walk behaves correctly, which is why one third of the answer looked right.

Rule the other three lines out

$$\texttt{len(table[0])} = 3 = \texttt{len(table[r])}\ \text{for all }r$$

The rows all have three cells, so the bound is right and the walk reaches every cell.

$$\text{three appends, three answers}$$

The printed list has three numbers for three columns, so the append is in the right place. And with >= the wrong columns would still report 0, so the comparison is not the fault either.

Fix it and recompute

$$\texttt{best = table[0][c]}$$

Inside the column loop, before the row loop, so each column starts from its own first cell.

$$[-2, 12, -15]$$

Largest of -4, -9, -2 is -2; largest of 12, 3, 8 is 12; largest of -20, -15, -30 is -15.

Answer $$\boxed{\texttt{best = 0}\;\longrightarrow\;\texttt{best = table[0][c]}}$$
Check

Independent check: every reported value must appear in its own column, which is true of -2, 12 and -15 and false of 0.

A running largest that starts from a number instead of a cell is only right for data that happens to straddle that number.

4§07.5 — the same job written for both families

Exam shape, and the question asks for both halves on purpose. Write swap_rows(table, i, j) which trades rows i and j inside the table it is given and returns nothing, and with_rows_swapped(table, i, j) which leaves the table alone and returns a new table with the two rows traded. Both need docstrings. Then say what the script below prints.

first = [[1, 1], [2, 2], [3, 3]]
second = with_rows_swapped(first, 0, 2)
print('new table   :', second)
print('input table :', first)
print('what swap_rows hands back :', swap_rows(first, 0, 2))
print('input table :', first)
Find
  1. (a) Write both functions with their docstrings.

  2. (b) Give the four lines the script prints.

Given
  • first is [[1, 1], [2, 2], [3, 3]] before anything is called.

  • The indexes 0 and 2 are given and may be assumed valid.

  • One function changes its argument and the other must not.

Hint 1/4

Neither function needs to touch a single cell. Both of them move whole rows, which are objects, so the work happens at the outer level only.

Hint 2/4

A swap inside a list needs a third name to hold one of the two values while the other is moved. A new table is built by appending, one row per turn, choosing which row to append.

Hint 3/4

For first the rows are [1, 1], [2, 2] and [3, 3], and the indexes to trade are 0 and 2, so row 1 stays where it is in both versions.

Hint 4/4

The third printed line is None, and the two lines that print the input table are different from each other.

Show solution

Swap in place with a third name, and build the copy by choosing per row rather than copying and then swapping.

Swap in place with a third name

$$\texttt{keep = table[i]}$$

The row at i is about to be overwritten, so a name has to hold it. Without this line the first assignment loses it and the table ends with row j in both places.

$$\texttt{table[i] = table[j]};\ \texttt{table[j] = keep}$$

Two assignments at the outer level. No cell is touched, because whole rows are being moved between slots.

Build the new table by choosing per row

$$\texttt{if r == i: new.append(table[j])}$$

At position i the answer wants row j, so the choice is made as the new table is built and nothing has to be swapped afterwards.

$$\texttt{else: new.append(table[r])}$$

Every other row keeps its place. Appending the same row object is what the docstring warns about, and it is what makes this version cheap.

Read the four printed lines

$$\texttt{second} = \texttt{[[3, 3], [2, 2], [1, 1]]}$$

The new table, with rows 0 and 2 traded and row 1 where it was.

$$\texttt{first}\ \text{unchanged, then}\ \texttt{None},\ \text{then changed}$$

The second function never assigned to a slot of the input, and the third function call has no return statement, so it hands back None and its effect shows on the line after.

Answer $$\boxed{\texttt{None}\ \text{from}\ \texttt{swap\_rows};\ \texttt{first}\ \text{changed only after it}}$$
Check

Independent check: swapping twice must give the table back, and second is first is False while second[1] is first[1] is True.

When a question asks for both families, write the docstring lines first. They differ by one sentence, and that sentence decides the whole body.

D · interleaved 4 questions
1§07.2 — digits of three numbers, collected and totalled

A table is built from three numbers, one row per number, and then each row is totalled.

numbers = [407, 92, 1358]
table = []
for n in numbers:
    digits = []
    for ch in str(n):
        digits.append(int(ch))
    table.append(digits)

print(table)
for row in table:
    total = 0
    for d in row:
        total = total + d
    print(row, 'digit total', total)
Find(a) Write the four lines this prints.
Given
  • The numbers are 407, 92 and 1358.

  • str(n) gives the digits as text and int(ch) turns one character back into a number.

IPython console
Hint 1/4

Two separate jobs are happening: a table is being built with rows of different lengths, and then one number per row is worked out. Do the first job completely before starting the second.

Hint 2/4

Walking a string hands over its characters one at a time, so the row for a number has as many cells as the number has digits. The rows here are therefore not all the same length.

Hint 3/4

407 has three digits, 92 has two and 1358 has four, so the three rows have lengths 3, 2 and 4.

Hint 4/4

The first line shows a ragged table, and two of the three totals are the same number.

Show solution

Build the whole table first and total it afterwards, because the row lengths differ and the two jobs have different bounds.

Build one row per number

$$\texttt{str(407)}\rightarrow\texttt{'407'}\rightarrow[4, 0, 7]$$

The characters are walked in order and each is converted, so the row holds the digits as numbers rather than as text. Without the int call the totals would be a joined string.

$$\text{rows of length }3, 2, 4$$

One cell per digit, so the row lengths are the digit counts and the table is ragged.

Total each row

$$4 + 0 + 7 = 11,\quad 9 + 2 = 11,\quad 1 + 3 + 5 + 8 = 17$$

The per row skeleton with the inside the outer loop, which is why the three totals are independent.

Answer $$\boxed{11,\ 11,\ 17}$$
Check

Independent check: all nine digits add to 39, and the three printed totals also add to 39.

2§07.6 — a file whose lines hold a name and then numbers

One list collects the names and a table collects the numbers, from the same lines.

out = open('week.txt', 'w')
out.write('Mon 3 4\n')
out.write('Tue 0 7\n')
out.write('Wed 5 1\n')
out.close()

days = []
table = []
in_file = open('week.txt', 'r')
for line in in_file:
    parts = line.strip().split()
    days.append(parts[0])
    row = []
    for i in range(1, len(parts)):
        row.append(int(parts[i]))
    table.append(row)
in_file.close()

print(days)
print(table)
for r in range(len(table)):
    print(days[r], table[r][0] + table[r][1])
Find(a) Write the five lines this prints.
Given
  • Each line of the file holds a day name and then two whole numbers, separated by spaces.

  • split() with no argument splits on the spaces.

IPython console
Hint 1/4

Each line is being cut into three parts and the three are going to two different places. Decide which part goes where before predicting anything.

Hint 2/4

The inner loop starts at 1, so part 0 is deliberately skipped by it, and that part is the one already appended to the names list.

Hint 3/4

So the parts of the first line are Mon, 3 and 4, giving the name Mon and the row [3, 4].

Hint 4/4

The names print with quotation marks and the numbers do not, and two of the three sums are equal.

Show solution

Cut each line into parts once and then decide what each part is for, rather than reading the file twice.

Cut each line into its three parts

$$\texttt{'Mon 3 4'}\rightarrow[\texttt{'Mon'}, \texttt{'3'}, \texttt{'4'}]$$

Split with no argument cuts at the spaces, so the day and the two numbers come out as three strings.

$$\texttt{days.append(parts[0])}$$

Part 0 goes to the names list as text, which is why it prints with quotation marks.

Convert the rest into a row

$$\texttt{for i in range(1, len(parts))}$$

Starting at 1 skips the day, and the bound is the number of parts, so a line with three numbers would give a row of three without any change to the code.

$$\texttt{row.append(int(parts[i]))}$$

One conversion per number, so the table holds ints and the additions on the last lines are additions.

Read the three sums

$$3 + 4 = 7,\quad 0 + 7 = 7,\quad 5 + 1 = 6$$

Row by row, with the day name taken from the same position of the other list.

Answer $$\boxed{\texttt{Mon 7},\ \texttt{Tue 7},\ \texttt{Wed 6}}$$
Check

Independent check: the six numbers in the file add to 20, and so do the three printed sums.

3§07.5 — two keys of a dictionary holding one row

A dictionary is built whose values are lists, and the second key is given the first key's value.

marks = {}
marks['ali'] = [55, 70]
marks['bora'] = marks['ali']
marks['bora'].append(90)
print(marks)
print(len(marks), len(marks['ali']))
Find(a) Write the two lines this prints.
Given
  • The dictionary starts empty and two keys are added.

  • The value for the second key is taken from the first.

IPython console
Hint 1/4

Count the list objects in this program before reading the append. The dictionary has two keys and that is a separate count.

Hint 2/4

Storing a value under a key stores a reference to it, so two keys can name one list, and appending through either key changes that one list.

Hint 3/4

So marks['ali'] and marks['bora'] are the same [55, 70], and the 90 is appended to it once.

Hint 4/4

Both printed values have three numbers in them, and the two numbers on the second line are different from each other.

Show solution

Count the objects before reading either line, since the append and both counts follow from that one question.

Count the objects

$$\texttt{marks['bora'] = marks['ali']}$$

The right hand side is a reference to the existing list, so nothing is built and two keys now reach one list.

$$\texttt{marks['bora'].append(90)}$$

One append into the one list, which is why both printed values show it.

Read the two counts

$$\texttt{len(marks)} = 2,\ \texttt{len(marks['ali'])} = 3$$

The first counts keys and the second counts the items of the shared list. Both are true and they answer different questions.

Answer $$\boxed{\text{two keys, one list of three}}$$
Check

Independent check without printing the dictionary: marks['ali'] is marks['bora'] is True, and that single line says everything the two printed lines say.

4§07.3 — a table as the default value of a parameter

The parameter has a default, and the body changes whatever it is handed.

def add_row(table=[]):
    table.append([0, 0])
    return table


print(add_row())
print(add_row())
print(add_row([[1, 1]]))
print(add_row())
Find(a) Write the four lines this prints.
Given
  • The default value of the parameter is an empty list.

  • The function appends one row and returns the table.

  • Two of the four calls pass nothing and one passes a table of its own.

IPython console
Hint 1/4

Ask how many times the expression that builds the default list is evaluated over the whole run of the program. It is not once per call.

Hint 2/4

A default value is worked out once, when the definition is executed, and the same object is used by every call that does not supply its own. When that object is a list, the calls share it.

Hint 3/4

So the first, second and fourth calls all append into one list, and the third call appends into the table it was handed instead.

Hint 4/4

The last printed line shows three rows, and the third printed line shows two.

Show solution

Work out when the default list is built before following any call, or the second line looks like a printing fault.

Work out when the default is built

$$\texttt{def add\_row(table=[]):}$$

The empty list is created once, as the definition is executed, and then belongs to the function rather than to any call.

$$\text{calls 1, 2 and 4 share it}$$

None of them passes an argument, so all three append into that one list. This is why the lengths run 1, 2 and then 3.

Follow the call that brings its own

$$\texttt{add\_row([[1, 1]])}\rightarrow\texttt{[[1, 1], [0, 0]]}$$

The parameter is bound to the table just written, so the append lands there and the shared default is not involved at all.

$$\text{call 4}\rightarrow\texttt{[[0, 0], [0, 0], [0, 0]]}$$

Back to the shared list, which already held two rows, so it now holds three. The third call left no mark on it.

Answer $$\boxed{1, 2, 2, 3\ \text{rows on the four lines}}$$
Check

Independent check on the counts: three of the four calls append into the shared list, so its final length must be 3, and the call with its own table must show 2.

Mistake ledger (21 entries)
⚠ Treating len(table) as the number of cells

With one bracket, len really did count the values, and nothing in the call says which level it is asking about.

wrong$$\texttt{len([[3, 1], [4, 1], [5, 9]])} = 6$$
right$$\texttt{len([[3, 1], [4, 1], [5, 9]])} = 3$$
⚠ Indexing with one bracket and a comma

It is how a matrix entry is written in every mathematics course, and it reads perfectly well.

wrong$$\texttt{table[1, 2]}\;\Rightarrow\;\text{TypeError}$$
right$$\texttt{table[1][2]}$$
⚠ Putting the column first

Graphs are read as x then y, and that habit puts the across number before the down number.

wrong$$\texttt{table[c][r]}$$
right$$\texttt{table[r][c]}$$
⚠ Leaving out the bare print at the end of the row

The inner loop looks complete once the cells are printed, and on a one row table the output looks right.

wrong$$\text{all cells on one line}$$
right$$\texttt{print()}\ \text{once per row}$$
⚠ Trying to write through the loop name

The name really does stand for the cell while you are reading, so it looks as though it stands for it while writing too.

wrong$$\texttt{for cell in row: cell = 0}$$
right$$\texttt{for c in range(len(row)): row[c] = 0}$$
⚠ Using len(table[0]) as the inner bound on a table whose rows differ

It is shorter, and on the rectangular tables of the lecture it gives the same number.

wrong$$\texttt{for c in range(len(table[0])):}$$
right$$\texttt{for c in range(len(table[r])):}$$
⚠ Building a table with the star on the outside

It is one short line, it prints correctly, and the trap shows up only after an assignment.

wrong$$\texttt{table = [[0] * cols] * rows}$$
right$$\texttt{for r in range(rows): table.append([0] * cols)}$$
⚠ Making the row once and appending it every turn

The row is created before the loop for tidiness, and the loop then appends the name it was given.

wrong$$\texttt{row = [0] * 3}\ \text{then}\ \texttt{table.append(row)}\ \text{in the loop}$$
right$$\texttt{table.append([0] * 3)}\ \text{in the loop}$$
⚠ Assigning to a cell of a row that has no cells yet

The finished table is indexed with brackets, so the half built one looks as though it should be too.

wrong$$\texttt{row = []}\ \text{then}\ \texttt{row[c] = v}\;\Rightarrow\;\text{IndexError}$$
right$$\texttt{row = []}\ \text{then}\ \texttt{row.append(v)}$$
⚠ Starting the accumulator before the outer loop

One name for the total looks tidier, and the first column or row still comes out right, which hides it.

wrong$$\texttt{total = 0}\ \text{above the outer loop}$$
right$$\texttt{total = 0}\ \text{as the first line of the outer body}$$
⚠ Putting the row index on the outer loop for a per column answer

Rows come first everywhere else on the page, so the row loop feels like the natural outer one.

wrong$$\texttt{for r ...: for c ...: }\text{append per row}$$
right$$\texttt{for c ...: for r ...: }\text{append per column}$$
⚠ Reading the number of columns from row 0 on a ragged table

Every table in the lecture is rectangular, so row 0 is a reliable witness there and the habit travels.

wrong$$\texttt{range(len(table[0]))}\;\text{as a claim about the table}$$
right$$\texttt{range(len(table[r]))}\;\text{per row}$$
⚠ Storing the result of an in place function

Most functions hand something back, so assigning the call looks like the careful thing to do.

wrong$$\texttt{table = censor(table, w)}\;\Rightarrow\;\texttt{table}\ \text{is}\ \texttt{None}$$
right$$\texttt{censor(table, w)}$$
⚠ Backing up a table with a full slice

A full slice was the correct clone of a list one section ago, and it still makes a new outer list, so it looks right.

wrong$$\texttt{backup = table[:]}$$
right$$\texttt{for row in table: backup.append(row[:])}$$
⚠ Building the new table with the input's shape

The two lengths are both in scope, so it is easy to reach for the nearer one when the answer needs the other.

wrong$$\text{transpose with}\ \texttt{for r in range(len(table))}\ \text{outside}$$
right$$\text{transpose with}\ \texttt{for c in range(len(table[0]))}\ \text{outside}$$
⚠ Appending the result of split straight into the table

The printed table looks right, and the quotation marks around the cells are easy to read past.

wrong$$\texttt{table.append(line.strip().split(','))}$$
right$$\texttt{row.append(int(part))}\ \text{per part}$$
⚠ Splitting before stripping

Splitting is the step you are thinking about, and the newline is invisible.

wrong$$\texttt{line.split(',')}\;\rightarrow\;\text{last part ends in a newline}$$
right$$\texttt{line.strip().split(',')}$$
⚠ Appending the row inside the inner loop

Both appends belong to the same reading job, so they drift to the same level of indentation.

wrong$$\texttt{table.append(row)}\ \text{in the cell loop}$$
right$$\texttt{table.append(row)}\ \text{in the line loop}$$
⚠ Using break for a search inside two loops

A break ends the loop you are in, and with one loop that was the whole search.

wrong$$\texttt{break}\ \text{leaves the cell loop only}$$
right$$\texttt{return (r, c)}\ \text{leaves the function}$$
⚠ Starting the running best at zero

Totals start at zero, so a best does too, and on tables of positive numbers it even works.

wrong$$\texttt{best = 0}$$
right$$\texttt{best = table[0][0]}$$
⚠ Updating the best value without its two indexes

The value is what the comparison is about, and the two bookkeeping lines are easy to leave for later.

wrong$$\texttt{best = table[r][c]}\ \text{alone}$$
right$$\texttt{best},\ \texttt{best\_r},\ \texttt{best\_c}\ \text{in the same if}$$
Formula card
A cell, and the two lengths
$$\boxed{\texttt{table[r][c]}:\;\text{first bracket}\rightarrow\text{row},\ \text{second}\rightarrow\text{cell};\quad \texttt{len(table)}=\text{rows},\ \texttt{len(table[r])}=\text{cells in row }r}$$

The outer index is the row and the inner one the position inside it. len(table[0]) describes the whole table only when the rows have the same length.

The two shapes of a full walk
$$\boxed{\text{read: }\texttt{for row in table: for cell in row:}\qquad \text{write: }\texttt{for r in range(len(table)): for c in range(len(table[r])):}}$$

The reading shape cannot assign to a cell, because the loop name holds the value. The writing shape works for both and is longer.

Building a table of a given size
$$\boxed{\texttt{for r in range(rows): table.append([0] * cols)}\quad\text{builds }rows\text{ rows};\qquad \texttt{[[0] * cols] * rows}\quad\text{builds }1}$$

The expression that builds a row must be inside the loop. Starting from table = [] and appending is the only safe shape.

One answer per row, one answer per column
$$\boxed{\text{per row: }\texttt{for r ... : total = 0; for c ... }\qquad\text{per column: }\texttt{for c ... : total = 0; for r ... }}$$

The accumulator starts inside the outer loop and the append is at the end of the outer body. The per column version needs the table to be rectangular.

In place against returning a new table
$$\boxed{\text{in place: assign }\texttt{table[r][c]},\ \text{return }\texttt{None}\qquad\text{new table: }\texttt{new = []},\ \texttt{new.append(row)},\ \text{return }\texttt{new}}$$

An in place function hands back None, so it is called as a statement. A copy whose rows are independent needs row[:] per row.

One line of text into one row of numbers
$$\boxed{\text{per line: }\texttt{parts = line.strip().split(',')}\;\rightarrow\;\texttt{row.append(int(part))}\;\rightarrow\;\texttt{table.append(row)}}$$

Strip before splitting, and convert every part as it arrives. Without the conversion the cells are text and plus joins them.

Finding a cell and reporting where
$$\boxed{\texttt{for r ...: for c ...: if table[r][c] == target: return (r, c)}\quad\text{then}\quad\texttt{return None}}$$

A return inside the loops leaves the function, so no break and no flag is needed. The answer for nothing found must be a value no real position could be.

Check yourself

Close the page and write, from memory: the two lengths of a table and what each counts; the two shapes of a nested walk and which can assign to a cell; the line that builds a table of a given size and the two ways of getting it wrong; where the accumulator goes for a per column answer; the safe inner bound on a ragged table; the two families of table function and what each hands back; and what a search returns when it finds nothing.

  • Say what len(table) and len(table[0]) each count for [[3, 1], [4, 1], [5, 9]], and why table[1, 2] is not a way of reaching a cell?

    c-table

  • Write the walk that doubles every cell, say why the reading walk cannot do it, and place the bare print() that ends each printed row?

    c-walk

  • Build a table of rows by cols zeros in three lines, and say what [[0] cols] rows builds instead and how is proves it?

    c-build

  • Write the per column skeleton from memory, say which line has to be inside the outer loop, and give the inner bound that is safe when the rows differ?

    c-columns

  • Write both a function that doubles a table in place and one that returns a doubled copy, and say what each call looks like on the caller's side?

    c-newtable

  • Turn the line 12,0,44 into the row [12, 0, 44] in the right order of operations, and print a row of three numbers in fields five characters wide?

    c-io

  • Write a search that returns the position of the first matching cell as a pair, say why a break would not do, and give the answer for nothing found?

    c-search

Glossary (15 terms)
tabletablo

A list whose items are lists, holding values arranged in rows and columns. Python has no separate type for one.

two dimensional listiki boyutlu liste

The same thing as a table, named after the two index brackets needed to reach one value in it.

rowsatır

One item of a table, which is itself a list. table[r] is a row and can be handed around, printed and changed on its own.

columnkolon

The set of cells at the same position in every row. It is not an object, so reaching one takes a loop over the row index.

cellhücre

One value inside a table, named by two indexes as table[r][c].

The order two nested loops reach the cells in when the row loop is outer: row 0 left to right, then row 1, and so on.

rectangular table

A table in which every row has the same length. Only for such a table does a number of columns exist, and only then does len(table[0]) describe more than row 0.

ragged table

A table whose rows differ in length. A column walk over one stops with an IndexError at the first short row.

transposedevrik

The table whose row c is column c of the original, so a table of n rows by m columns becomes one of m rows by n columns. It has to be returned as a new table when the shape is not square.

A copy of a table whose rows are new objects as well, built by appending row[:] for each row. Writing a cell of it cannot be seen through the original.

field widthalan genişliği

The number of characters a printed value is padded out to, as in format(cell, '5d'). Equal field widths are what line the columns up.

accumulator reset

Starting a total or a count as the first statement inside the outer loop, so each row or column begins from nothing.

shared row

A row object reached by more than one slot of a table, which [[0] m] n builds. Writing one of its cells appears to write a column.

A position in a table, handed back as a tuple (r, c) because it takes two numbers to say where a cell is.

main diagonal

The cells whose two indexes are equal, so table[0][0], table[1][1] and so on.

What comes next
§08 · Classes and Object-Oriented Programming (Chapter 8)

Every table here held numbers or strings, and the row index had to be remembered separately from what it meant. The next section gives the cells names and behaviour: a row becomes one object that knows its own parts, so a table of stock items becomes a list of things, each of which can be asked its price. The loops that walk a list of objects are the ones already written here.

Sources
  • kitapJohn Guttag, Introduction to Computation and Programming Using Python, with Application to Understanding Data, second edition The syllabus line for this week names no chapter number, so none is claimed here. The textbook treats tables as an application of the chapter on lists and mutability rather than as a chapter of their own.
  • ders malzemesiThe course's own lecture slides for structured types, whose last four slides are on tables Used for the boundary of what counts as covered: tables as lists of lists, the two ways of creating one, two brackets, the two lengths, the two nested loop shapes, and the lecture's two exercises, a table read from the user with its row and column sums and a table of words with one blanked out.
  • ders malzemesiThe lab sheet for the two dimensional list and class lab Used for the shape of an exercise: a named script or function, a stated return value, and a Sample Run given character for character. Its first question builds a square table from a rule on the two indexes and prints it in matrix form.
  • ders malzemesiOne past midterm paper and one past final paper, both with solutions Used only for the shape and weight of questions, never their wording. The final's third question returns one number per column of a table of strings, worth 20 marks; the midterm's heaviest is four programs traced by hand, worth 30.
  • sabitThe Python interpreter itself Every block of output here came from running the program above it. The two tracebacks are real, with the temporary path replaced by a plain file name.

Spotted something missing or wrong? tell us · share your own notes or an old exam.

Last updated .