Changing & Building Lists
The last page of the chapter, and the one with the trap on it. A list can be changed while you are looking at it — including while a loop is walking it — and that is a door strings never had. Five programs that use it properly, and one that shows what happens when you do not.
1Program 1 — the evens and the odds, in two lists
Split one list into two: the even numbers and the odd ones.
# split one list into two: the evens and the odds
numbers = [12, 7, 30, 45, 8, 21]
evens = []
odds = []
for n in numbers:
if n % 2 == 0:
evens.append(n)
else:
odds.append(n)
print('Original:', numbers)
print('Evens: ', evens)
print('Odds: ', odds)Original: [12, 7, 30, 45, 8, 21] Evens: [12, 30, 8] Odds: [7, 45, 21]
The original is printed last and is completely untouched, which is the other half of the argument: the old list is still there if anything else needs it.
2Program 2 — a list with the repeats left out
Build a new list keeping only the first appearance of each value.
# a new list with the repeats left out
numbers = [4, 7, 4, 2, 7, 9, 4]
without_repeats = []
for n in numbers:
if n not in without_repeats:
without_repeats.append(n)
print('Original: ', numbers)
print('Without repeats:', without_repeats)Original: [4, 7, 4, 2, 7, 9, 4] Without repeats: [4, 7, 2, 9]
The collector is doing two jobs at once, exactly as it did when removing repeated characters from a string: without_repeats is both the answer being built and the record of what has already been seen. The test asks the half-built answer whether it has had this value before.
set() cannot promise you. set(numbers) removes the repeats in a single call, because a set has nowhere to put a value it already holds, but it keeps no order at all. That version is worked through on The Built-in Way, the last page of this submenu; this loop is the one to write when the order has to survive.3Program 3 — the one that goes wrong
Here is the obvious way to remove every 2 from a list. It is wrong, and it does not say so:
# removing items while the loop is still walking the list
numbers = [1, 2, 2, 3, 2]
for n in numbers:
if n == 2:
numbers.remove(n)
print(numbers)[1, 3, 2]
| round | looks at position | finds | list afterwards |
|---|---|---|---|
| 1 | 0 | 1 | [1, 2, 2, 3, 2] |
| 2 | 1 | 2 — removed | [1, 2, 3, 2] |
| 3 | 2 | 3 (the second 2 slid to position 1) | [1, 2, 3, 2] |
| 4 | 3 | 2 — removed | [1, 3, 2] |
| 5 | — | position 4 is past the end, so the loop stops | [1, 3, 2] |
The fix is not a cleverer loop — it is to stop removing. Build a new list of the items you want to keep, and the walking and the changing never touch each other:
# the safe way: build a list of what you want to keep
numbers = [1, 2, 2, 3, 2]
kept = []
for n in numbers:
if n != 2:
kept.append(n)
print(kept)[1, 3]
range(len(numbers)) and calling numbers.pop(i) raises IndexError: list index out of range — because range() worked out its numbers from the length before the list started shrinking. An error you can see beats a wrong answer you cannot.4Program 4 — a running total
Turn a list of daily sales into a list of totals so far.
# a running total: each item is the sum of everything up to it
sales = [100, 250, 175, 300]
running = []
total = 0
for s in sales:
total = total + s
running.append(total)
print('Daily sales: ', sales)
print('Running total:', running)Daily sales: [100, 250, 175, 300] Running total: [100, 350, 525, 825]
total = 0A plain number collector, alongside the list collector. Two collectors in one loop is normal — they are answering two halves of the same question.
total = total + sThe running sum, updated before it is stored. The order of these two lines is the program: append first and every item would be one day behind.
running.append(total)A snapshot of the total as it stands. The last item of running is always the same as the plain total of the whole list — 825 here.
5Program 5 — join two lists, one item from each
Given two lists of the same length, build one list taking an item from each in turn.
# join two lists, taking one item from each in turn
first = ['a', 'b', 'c']
second = [1, 2, 3]
mixed = []
for i in range(len(first)):
mixed.append(first[i])
mixed.append(second[i])
print(mixed)['a', 1, 'b', 2, 'c', 3]
The index form, because two lists are being read at the same position — the parallel-list shape again. Two append() calls per round, so the new list ends up twice the length of either original.
6Program 6 — move every zero to the end
Shift all the zeros to the end of the list, keeping the other items in their original order.
# move every zero to the end, keeping the other items in order
numbers = [4, 0, 7, 0, 2, 9]
kept = []
zeros = 0
for n in numbers:
if n == 0:
zeros = zeros + 1
else:
kept.append(n)
for i in range(zeros):
kept.append(0)
print('Before:', numbers)
print('After: ', kept)Before: [4, 0, 7, 0, 2, 9] After: [4, 7, 2, 9, 0, 0]
zeros and appending a 0 each time — a for loop over a range(), with the loop variable never used. That is a perfectly ordinary thing to write, and it is what “do this n times” looks like.7Recap
Writing through positions can only replace. Anything that adds or removes items needs a new list and append().
The items after the removed one shift left, and the loop skips one. No error — just a wrong answer that looks nearly right.
Build a list of what you want to keep. The walking and the changing then touch different lists and cannot interfere.
A running total keeps a number and a list side by side, and the order of the two lines inside the loop is the program.
- 1
Split a list of marks into passes (33+) and fails, into two new lists.
Hint · Program 1 with a different test. Check that the two lengths add up to the original.
- 2
Remove every negative reading from a list — safely.
Hint · Keep, do not remove:
if r >= 0: kept.append(r). - 3
Build a list of the running maximum: the biggest value seen so far, at each position.
Hint · The running-total shape with a champion instead of a total.
- 4
Given two lists, build one holding every item of both, with no repeats.
Hint · Two loops one after the other, both appending only when
not inthe answer so far. - 5
Move every zero to the front instead of the end.
Hint · Append the zeros first, then the kept items — or build the kept list first and use
insert(0, 0).
for n in numbers: if n == 2: numbers.remove(n) — on [1, 2, 2, 3, 2], what is printed?
Why can't a list be filtered by writing through positions, as marks[i] = ... does?
In the running-total program, what if running.append(total) came before total = total + s?