LambdaLabTM
Computer Science · Class 11 · Lists Revisited
ProgramsBuilding⏱️ 17 min read

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

📋 The problem

Split one list into two: the even numbers and the odd ones.

split_evens_odds.py
# 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)
Output
Original: [12, 7, 30, 45, 8, 21]
Evens:    [12, 30, 8]
Odds:     [7, 45, 21]
Key Takeaway
This is why building beats changing in place. The two new lists are different lengths from the original — three items each out of six — and writing through positions can only ever replace an item, never remove one. Anything that changes how many items there are has to build a new list.

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

📋 The problem

Build a new list keeping only the first appearance of each value.

no_repeats.py
# 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)
Output
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.

Tip
The order is kept, and that is not free. Items come out in the order they first appeared — which is the one thing 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:

remove_broken.py
# 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)
Output
[1, 3, 2]
Watch Out
A 2 survives, and no error is raised. The loop walks by position under the bonnet — round 1 looks at position 0, round 2 at position 1, and so on. Removing an item shifts everything after it one place left, so the next item slides into the position the loop has just finished with and is never looked at. Remove the 2 at position 1 and the second 2 moves into position 1, which the loop has already passed.
What the loop actually sees
roundlooks at positionfindslist afterwards
101[1, 2, 2, 3, 2]
212 — removed[1, 2, 3, 2]
323 (the second 2 slid to position 1)[1, 2, 3, 2]
432 — removed[1, 3, 2]
5position 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:

remove_fixed.py
# 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)
Output
[1, 3]
Watch Out
The index version fails louder, which is a mercy. Looping with 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

📋 The problem

Turn a list of daily sales into a list of totals so far.

running_total.py
# 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)
Output
Daily sales:  [100, 250, 175, 300]
Running total: [100, 350, 525, 825]
total = 0

A 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 + s

The 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

📋 The problem

Given two lists of the same length, build one list taking an item from each in turn.

interleave.py
# 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)
Output
['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.

Tip
A list can hold anything, and this one holds both. Strings and numbers together in one list is perfectly legal — Python never asks that a list be all of one type. It is worth doing once so the rule stops sounding like a technicality.

6Program 6 — move every zero to the end

📋 The problem

Shift all the zeros to the end of the list, keeping the other items in their original order.

move_zeros.py
# 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)
Output
Before: [4, 0, 7, 0, 2, 9]
After:  [4, 7, 2, 9, 0, 0]
Key Takeaway
Two loops, and the second one is not walking a list at all. It is counting to 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.
move_zeros.py

7Recap

Changing the length means building

Writing through positions can only replace. Anything that adds or removes items needs a new list and append().

Never remove from a list you are looping over

The items after the removed one shift left, and the loop skips one. No error — just a wrong answer that looks nearly right.

Keep, do not remove

Build a list of what you want to keep. The walking and the changing then touch different lists and cannot interfere.

Two collectors in one loop is normal

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.

✍️ Now write these yourself
  1. 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. 2

    Remove every negative reading from a list — safely.

    Hint · Keep, do not remove: if r >= 0: kept.append(r).

  3. 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. 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 in the answer so far.

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

Quick Check

for n in numbers: if n == 2: numbers.remove(n) — on [1, 2, 2, 3, 2], what is printed?

Quick Check

Why can't a list be filtered by writing through positions, as marks[i] = ... does?

Quick Check

In the running-total program, what if running.append(total) came before total = total + s?