Lists in Practice
Part A is six problems on list operations, slicing and copying, walking into three classic traps deliberately. Part B takes one class roster from parsing to ranking to dirty data, and ends by showing where lists run out. Part C is git on your own machine.
Contents
What this session is for
- Get comfortable with list operations and slicing
- Reproduce and then fix three classic traps:
[[0]*n]*m, shallow copies, and deleting while iterating - Build the habit of settling an argument with
id()instead of guessing - Handle a realistically shaped dataset with nested lists, and find out where they stop working
- Start recording your own changes with git
Part A — required
5-1 Class statistics (warm-up)
Given a list of marks, print the count, the mean, the highest, the lowest and the pass rate. Round the mean to two decimal places.
input: [87, 92, 45, 78, 55, 95]
output:
count 6
mean 75.33
high 95
low 45
pass rate 66.67%
5-2 Write your own max
Without using max(), implement my_max(nums: list[int]) -> int | None returning the largest value. Return None for an empty list.
5-3 Deduplicate, order preserved
Implement dedup(nums: list[int]) -> list[int], which drops repeats while keeping the original order.
input: [3, 1, 3, 5, 1, 7]
output: [3, 1, 5, 7]
set() is not allowed here — it throws the ordering away.
5-4 Transpose a matrix
Implement transpose(matrix: list[list[int]]) -> list[list[int]].
input: [[1, 2, 3], [4, 5, 6]]
output: [[1, 4], [2, 5], [3, 6]]
5-5 Find the bug
The code below is supposed to double every even number. Instead it loops forever. Work out why, write a corrected version, and explain the cause in a sentence or two in your submission notes.
a = [1, 2, 3, 4, 5, 6]
for i in range(len(a)):
if a[i] % 2 == 0:
a.insert(i, a[i] * 2)
5-6 A copying experiment
Write code that uses id() to demonstrate all three of the following, and paste the output into your submission notes:
- After
b = a,aandbare the same object - After
b = a[:],aandbare different objects - But for a nested list, after
b = a[:],a[0]andb[0]are still the same object
Part B — going deeper: one class roster, four times over
The six problems in Part A have nothing to do with each other. This part has a single problem — a class roster — but you work it four times: read it in, rank it, survive the dirty version, and then find the scale at which the whole approach falls apart.
The data is thirty lines of text. Each line is student-id name quiz1 quiz2 quiz3:
RAW = [
"2026001 zhangsan 91 57 59",
"2026002 lisi 70 81 55",
"2026003 wangwu 43 65 66",
"2026004 zhaoliu - 78 90",
"2026005 qianqi 90 90 61",
"2026006 sunba 73 91 73",
"2026007 zhoujiu 60 53 75",
"2026008 wushi 105 60 70",
"2026008 wushi 60 70 80",
"2026009 zhengyi 60 61 69",
"2026010 chener 57 48 70",
"2026011 chusan 59 99 46",
"2026002 lisi 10 10 10",
"2026012 weisi 43 59 89",
"2026013 jiangwu 95 96 48",
"2026014 shenliu 53 73 62",
"2026015 hanqi 64 73 96",
"2026016 yangba 59 71 85",
"2026017 zhujiu 64 41",
"2026018 qinshi 59 81 93",
"2026011 chusan 88 88 88",
"2026019 youyi 97 73 62",
" 2026020 xuer 70 80 90 ",
"2026021 hesan 82 90 80",
"2026022 lvsi 61 55 60",
"2026023 shiwu 58 x 88",
"2026024 zhangliu 63 93 95",
"2026019 youyi 40 40 40",
"2026025 kongqi 41 100 67",
"2026026 caoba 99 50 48",
]
Several kinds of mess are hiding in those thirty lines; B-3 deals with them. B-1 and B-2 may assume every line is clean.
No file reading — that is L12. Copy the block above into your own file.
B-1 Read it in
Implement parse_roster(lines: list[str]) -> list[list], turning each line into one record:
["2026001", "zhangsan", [87, 92, 45]]
Keep the student ID as a string — you never do arithmetic on it. The three quiz marks go in a list of their own.
in: ["2026001 zhangsan 87 92 45", "2026002 lisi 78 65 90"]
out: [["2026001", "zhangsan", [87, 92, 45]],
["2026002", "lisi", [78, 65, 90]]]
B-2 Rank them
Implement rank(records: list[list]) -> list[list], sorting by total mark descending,
breaking ties by student ID ascending:
[[1, "2026003", "wangwu", 274],
[2, "2026002", "lisi", 233],
[3, "2026001", "zhangsan", 224]]
Positions are numbered 1, 2, 3, … with no special handling for ties.
B-3 The dirty version
Real data is not that tidy. The full RAW contains all of these:
| Case | Where it is in RAW | What to do |
|---|---|---|
| absent | 2026004 zhaoliu - 78 90 | - counts as 0; keep the record |
| wrong number of fields | 2026017 zhujiu 64 41 (one quiz short) | drop the whole record |
| invalid mark | 2026008 wushi 105 60 70, 2026023 shiwu 58 x 88 | not a whole number in 0..100: drop the record |
| duplicate ID | 2026002, 2026011 and 2026019 each appear twice | keep the first one |
| extra whitespace | 2026020 xuer 70 80 90 | keep it; it is fine |
Implement clean(lines: list[str]) -> list[list], returning cleaned records in the same
shape parse_roster produces. Apply the checks in the order of the table: field count,
then mark validity, and de-duplicate last, among the records that survived.
In your submission notes, say how many records you dropped and why.
B-4 Where this approach gives out
Now answer some queries, using nothing but lists:
- Write
find(records, sid), locating one record by student ID. - Add a counter to
findrecording how many comparisons it took. Look up the 1st, the 15th and the 30th student, and note the count for each. - Write
has_duplicate(records)— noset— and count its comparisons too.
Then answer:
- How does
find’s comparison count relate to the number of records? - How does
has_duplicate’s? What makes it worse thanfind? - This class has 75 students and the approach is entirely adequate. What happens with a university-wide table of thirty thousand, queried tens of thousands of times a day?
- What would you want a container to do, so that “find one record by ID” did not mean comparing against every record in turn?
Part C — fundamentals: git on your own machine
Your code now has some weight to it: break one of those Part B functions and you may not get back. That is the problem git solves — it remembers every version for you, so you can always go back to one.
This week is local only. Pushing to GitHub is next week.
Tell git who you are, once
git config --global user.name "Your Name"
git config --global user.email "you@example.com"
Every commit records who made it, so git has to be told. You do this once, ever.
Six commands
| Command | What it does |
|---|---|
git init | make the current directory a repository. Once, at the start of a project |
git status | what has changed, and what is staged. Type it whenever you are unsure |
git add file | put changes in the staging area: “these are the ones I mean to commit” |
git commit -m "message" | record everything staged as one version |
git log --oneline | the history, one line per commit |
git diff | changes not yet staged; git diff --staged for the staged ones |
C-1 Walk through it
Work in this week’s lab directory, and record each command and its output in your submission notes:
git init, thengit status— what does it tell you?- Create a
.gitignorewith at least__pycache__/in it git addyour lab code, rungit statusagain, and see what changed in the outputgit commit -m "lab05 part A"for your first version- Change a function, then
git diff. Work out what the leading-and+mean git addit and rungit diffagain — why is there nothing now? Trygit diff --staged- Commit the second version and look at
git log --oneline
C-2 Think it through
- If everything ends up committed anyway, why does git bother with a staging area? When would you want to commit only some of your changes?
- What belongs in a commit message? Compare these two and ask which helps you more three
months from now:
"fixed stuff"versus"B-2: sort on [-total, id] so ties are no longer order-dependent"
Submitting
The six Part A problems are marked independently on the platform, and so are Part B’s
parse_roster, rank and clean. The answers for B-4 and Part C go in your submission notes.
Before you submit
- Every function has type annotations
- 5-4’s wrong output is recorded in your notes
- 5-5’s infinite loop is explained
- 5-6’s three
id()outputs are pasted in - B-3 says how many records were dropped and why, with your own example of the orders disagreeing
- All four B-4 questions are answered
- Part C’s command output and both questions are written up