Final Exam Model Paper 7
Theory paper: 50 marksTime: 2 hours
Practical exam: 20 marksTime: 1 hour 30 minutes
All questions are compulsory.
A full practice run of the Grade 12 final exam: the theory paper (50 marks, 2 hours) and, below it, the practical exam (20 marks, 1 hour 30 minutes, at a computer, marked by the school). In the theory paper, work out on paper what each program does, as in the exam hall: its programs have no Run button. In the practical exam, the programs run on this page.
A practice paper by Science Playground, made to its own course’s grid; not an official exam paper.
Write your answers on paper first. Then open each answer and its marking scheme, and give yourself one mark for each point you got. In multiple choice, your first choice counts.
Section A
10 × 1 = 101. Choose the correct option for each of the following questions.
When is a problem called NP-complete?
Pick an answer to see why.
Pratik times two ways of sorting on a list of 10 marks: the O(n²) one takes 2 microseconds, the O(n log n) one 5 microseconds. He says: “The O(n²) way is faster, so we should use it to sort the SEE results of 500,000 students.” Which judgment is right?
Pick an answer to see why.
When is a merge in git a fast-forward?
Pick an answer to see why.
Reshma reads the code of a fare function and chooses one test for each branch of its if, elif and else, so that every return runs. What kind of testing is this?
Pick an answer to see why.
What is a cascading failure?
Pick an answer to see why.
A load test of a bus-ticket website finds that it slows down at 2,000 visitors a minute. The database is 100% busy; the 4 web servers are each 40% busy. What should the company do first?
Pick an answer to see why.
On SEE results day a results website expects 3,000 requests a second at its peak. Each server can answer 150 requests a second when it is 100% busy. How many servers does it need so that none is more than 60% busy?
Pick an answer to see why.
What is hybrid encryption?
Pick an answer to see why.
A notice from a ward chair, signed with her private key, checks correctly against her public key. What does this prove?
Pick an answer to see why.
A shop in Siraha uses an AI camera that matches customers’ faces against a list of people it suspects of shoplifting. A customer says: “This breaks the National Artificial Intelligence Policy, 2082, so the shop can be fined under it.” Which judgment is right?
Pick an answer to see why.
Section B
8 × 3 = 24Write short answers to the following questions.
A shop’s calculator app turns what is typed into Reverse Polish notation with the shunting-yard algorithm, then works it out with a stack.
- i.Turn
(9 − 3) ÷ 2 + 4 × 5into RPN. - ii.When the algorithm reads the 5, which operators are on its stack, and why is × on top of +?
- iii.For
20 − 6 − 4, why must the first − go to the output when the second one arrives? What would the answer be if it stayed on the stack?
Answer
- i.9 3 − 2 ÷ 4 5 × + (it works out as 6 ÷ 2 + 20 = 23).
- ii.+ with × on top. When × arrived, the operator on the stack was +, which binds less tightly, so it stayed and × went on top to wait for its right-hand number. At the end × leaves first, so 4 × 5 is worked out before the +.
- iii.Two operators of the same level count as binding “at least as tightly”, so the first − leaves for the output: RPN
20 6 − 4 −, which is (20 − 6) − 4 = 10, working from the left. If the first stayed on the stack, the RPN would be20 6 4 − −, which is 20 − (6 − 4) = 18, a wrong answer.
Marking scheme
- i. 9 3 − 2 ÷ 4 5 × +.
- ii. + and × on the stack, × on top because + binds less tightly, so × waits above it.
- iii. Operators of the same level leave first to work from the left: 10; staying would give 18.
Sabina keeps the code of her college’s bus timetable app in git. Write the git commands she needs to:
- i.make a new branch called
fare-tableand move onto it; - ii.after changing
fares.py, save the change as a commit with a good message; - iii.go back to
main, join the branch’s work into it, and send it to the team’s remote.
Answer
- i.
git switch -c fare-table(orgit branch fare-tablethengit switch fare-table). - ii.
git add fares.py, thengit commit -m "Add fares for the new Lalitpur–Godawari route"(a short message saying what changed and why). - iii.
git switch main,git merge fare-table,git push.
Marking scheme
- i.
git switch -c fare-table(orgit branchthengit switch;git checkout -balso accepted). - ii.
git add fares.pyandgit commit -mwith a clear message. - iii.
git switch main,git merge fare-table, thengit push.
Four students in Tanahun are building an app for their district’s farmers’ market. Their teacher asks them to work by extreme programming (XP). Explain how each of these practices would help them: i. tests written before the code; ii. pair programming; iii. merging everyone’s work many times a day with every test run.
Answer
- i.Tests first: writing the test makes them decide exactly what a function must do (what a price list with no vegetables should give, for example) before writing it, and the test then checks it after every change, so a change that breaks it is caught at once.
- ii.Pair programming: two people at one computer, one typing and one reviewing as they go, swapping often. Mistakes are caught as they are made, and at least two people understand every part, so the project doesn’t stop if one is ill.
- iii.Continuous integration: when each change is small and joined often, conflicts are small and easy to settle, and running every test each time shows a regression within minutes, while the change is still fresh in its author’s mind, instead of a pile of conflicts and bugs at the end.
Marking scheme
- i. Tests first: they decide what the code must do, and catch a change that breaks it.
- ii. Pair programming: mistakes caught as they are made, and shared knowledge of the code.
- iii. Continuous integration: small, frequent merges with every test run catch conflicts and regressions early.
A bus company keeps its ticket bookings on a leader in Kathmandu, with a follower in Butwal kept by asynchronous replication. One morning the leader’s disk fails; the follower is made the new leader within a minute. Three customers who were told “Your seat is booked” in the last second before the failure find that the system has no record of their tickets.
- i.Why were those three bookings lost?
- ii.What could happen to those customers on the day of travel?
- iii.What could the company change so that this can’t happen, and what would it cost?
Answer
- i.With asynchronous replication the leader says “done” at once and sends the change to the follower just after. The three bookings were on the leader but hadn’t yet reached Butwal when the disk failed, so they were lost with it.
- ii.Their seats look free on the new leader and can be sold again: two passengers may turn up for the same seat, or the customers may have no ticket at all, though they paid.
- iii.Use synchronous replication for bookings: “booked” is shown only after the follower has the change too. The cost is speed: every booking waits for an answer from Butwal, so it is slower, and if the follower can’t be reached, bookings must wait.
Marking scheme
- i. Asynchronous: “done” was said before the follower had the change, so the last changes were lost with the leader.
- ii. The seats can be sold again: two passengers for one seat, or paid customers with no ticket.
- iii. Synchronous replication, at the cost of slower bookings that wait for the follower.
A seed company says: “Farmers in Makwanpur who chose our new maize seed last year harvested 30% more than those who didn’t.”
- i.Why doesn’t this show that the seed caused the bigger harvest?
- ii.Describe how a randomized controlled experiment could test the seed fairly.
- iii.Why should the people who weigh the harvests not know which fields got the new seed?
Answer
- i.The farmers chose the seed. Those who buy a new, dearer seed may also have better land, irrigation, more fertilizer or more time; these hidden factors (confounders) could give the bigger harvest with any seed.
- ii.Take a number of farmers willing to try it, and in each farm divide the land into plots, or take whole farms, and let chance (a coin, or a random draw) decide which get the new seed (the treatment group) and which the usual seed (the control group). Grow both the same way and compare the average harvests. Chance spreads land, water and care evenly between the groups on average, so a difference bigger than chance could explain shows the seed’s effect.
- iii.So that their hopes or beliefs can’t tilt the result (weighing a little more carefully, or rounding up, on the plots they expect to do well). This is a blind design.
Marking scheme
- i. The farmers chose: other factors (land, water, fertilizer) could explain the difference.
- ii. Chance decides which plots or farms get the seed and which don’t; then compare the groups.
- iii. Blind: knowing could tilt how they measure.
A bus-ticket app tests a new payment screen, B, against its old one, A, with users chosen at random. The team planned to run the test for 28 days, but they check the p-value every evening. On day 4 it falls to 0.04, so they stop the test and announce: “B is better.” Judge what they did, and say how they should run such a test.
Answer
Stopping as soon as the p-value dips under 5% is unfair to chance. A p-value of 4% means chance alone gives a difference this big about once in 25 tries; checking every evening gives chance a new try each day, and early on, with few users, the difference jumps about a lot. So a screen that is no better can easily look “significant” on some day, and stopping on that day picks the lucky moment: the claim is not supported. Four days also leave out part of the week, when people may buy tickets differently (weekdays and Saturday).
They should decide in advance the measure (the share who finish buying), the size of difference worth having and how many users that needs for enough power, run the test for the whole planned time, then do the shuffle test once, and report the size of the difference with its interval.
Marking scheme
- Checking every day and stopping when p < 5% gives chance many tries, so a screen that is no better can look significant.
- So “B is better” isn’t supported by this test (and 4 days may not be like the rest of the weeks).
- The right way: decide the measure, size and length in advance, run the full test, test once, and report the size of the difference.
For n = 35 = 5 × 7:
- i.Work out φ(n).
- ii.Use the pattern that every number raised to the power 1 + φ(n) comes back to itself, mod n, to find 3⁴⁹ mod 35 and 2²⁶ mod 35, without working out the big powers.
- iii.Explain how this pattern makes RSA decryption give back the message.
Answer
- i.φ(35) = (5 − 1)(7 − 1) = 4 × 6 = 24.
- ii.49 = 1 + 2 × 24, so 3⁴⁹ comes back to 3: 3⁴⁹ = 3²⁵ × 3²⁴, and 3²⁵ ≡ 3 (power 1 + 24), so it is 3 × 3²⁴ = 3²⁵ ≡ 3. 26 = 25 + 1, so 2²⁶ = 2²⁵ × 2 ≡ 2 × 2 = 4. (Check: 2²⁶ = 67,108,864, which leaves 4 when divided by 35.)
- iii.In RSA, d is chosen so that e × d = 1 + a multiple of φ(n). Decrypting works out cᵈ = (mᵉ)ᵈ = mᵉˣᵈ, a power that is 1 more than a multiple of φ(n), so by the pattern it comes back to m, the message.
Marking scheme
- i. φ(35) = 24.
- ii. 3⁴⁹ mod 35 = 3 and 2²⁶ mod 35 = 4, found from the pattern.
- iii. e × d = 1 + a multiple of φ(n), so (mᵉ)ᵈ = mᵉˣᵈ comes back to m.
When an AI system causes harm, three ideas help decide which people should answer for it. Name and explain each.
Answer
- Control: responsibility follows the power to make a difference. Those who decide how a system works, how it is tested and when it is released carry more than someone told only to watch it.
- Knowledge: someone who knew, or should have known, of a risk carries more than someone who couldn’t have known.
- A person for every decision: every AI system that affects people should have a named person or office responsible for it, who can be asked to explain and to put things right. A system nobody answers for leaves a responsibility gap.
Marking scheme
- Control: responsibility follows the power to decide how the system works, is tested and released.
- Knowledge: who knew, or should have known, of the risk.
- A named person or office for every system; without one, a responsibility gap.
Section C
2 × 8 = 16Write long answers to the following questions.
A national park has seven ranger posts, A to G. Posts close enough to interfere must not use the same radio channel. These pairs interfere (made up):
A–D, A–G, B–C, B–G, C–D, C–F, C–G, E–F, E–G, F–G
The channels are numbered 1, 2, 3, … The park wants to use as few channels as possible.
- i.A greedy method gives the posts channels in the order A, B, C, D, E, F, G: each post gets the lowest-numbered channel not already used by a post it interferes with. Work out each post’s channel. How many channels does it use? (2)
- ii.Another greedy method takes the posts in order of how many posts each interferes with, most first (ties in alphabetical order), and gives channels the same way. Work out the order and each post’s channel. How many channels does it use? (2)
- iii.Show that the park can’t manage with fewer channels than in ii, and check that ii’s channels are allowed. (2)
- iv.A larger park has 200 posts. Why can’t a program try every way of giving them 3 channels? What would you do instead? (2)
Answer
i.
| Post | Interferes with (already given) | Channel |
|---|---|---|
| A | — | 1 |
| B | — | 1 |
| C | B (1) | 2 |
| D | A (1), C (2) | 3 |
| E | — | 1 |
| F | C (2), E (1) | 3 |
| G | A (1), B (1), C (2), E (1), F (3) | 4 |
It uses 4 channels.
ii. How many each interferes with: G 5, C 4, F 3, and A, B, D, E 2 each. So the order is G, C, F, A, B, D, E.
| Post | Interferes with (already given) | Channel |
|---|---|---|
| G | — | 1 |
| C | G (1) | 2 |
| F | C (2), G (1) | 3 |
| A | G (1) | 2 |
| B | C (2), G (1) | 3 |
| D | A (2), C (2) | 1 |
| E | F (3), G (1) | 2 |
It uses 3 channels.
iii. C, F and G all interfere with each other (C–F, C–G, F–G), so the three of them need three different channels: no way of giving channels can use fewer than 3. Checking ii’s channels pair by pair: A–D 2/1, A–G 2/1, B–C 3/2, B–G 3/1, C–D 2/1, C–F 2/3, C–G 2/1, E–F 2/3, E–G 2/1, F–G 3/1: every pair differs, so 3 is the fewest, and ii found it. The order in i, which happened to give G its channel last, cost a fourth channel.
iv. Each post can have any of 3 channels, so there are 3²⁰⁰ ways, about 2.7 × 10⁹⁵: at a billion a second, far longer than the age of the universe, and each post more multiplies the work by 3. Like the trek through the villages, it is a problem where every method known to be sure of the best can take work that grows explosively. Instead, use a heuristic, such as the most-interference-first greedy method, perhaps tried with several orders and improved by moving single posts; measure it against the best answer (found by trying every way) on small parks, and accept a good answer that may not be the best.
Marking scheme
- i. The channels worked out post by post in alphabetical order (A 1, B 1, C 2, D 3, E 1, F 3).
- i. G gets 4: 4 channels in all.
- ii. The counts (G 5, C 4, F 3, the rest 2) and the order G, C, F, A, B, D, E.
- ii. The channels (G 1, C 2, F 3, A 2, B 3, D 1, E 2): 3 channels.
- iii. C, F and G interfere with each other, so at least 3 channels are needed.
- iii. Every interfering pair checked to differ in ii.
- iv. 3²⁰⁰ ≈ 2.7 × 10⁹⁵ ways: far too many, tripling with each post more.
- iv. A heuristic such as the most-interference-first greedy method, checked against the best on small cases.
The disaster office of Saptari is offered a model that forecasts, six hours ahead, whether a river will flood a village beside it; when the model’s score passes a threshold, a siren tells families to leave for the shelter. The company tested it on 600 six-hour periods from past monsoons, in 40 of which the village flooded. (The model and its results are made up.)
| Threshold | Floods warned of | Floods missed | False alarms | Quiet periods, rightly quiet |
|---|---|---|---|---|
| 0.3 | 38 | 2 | 76 | 484 |
| 0.5 | 34 | 6 | 26 | 534 |
| 0.7 | 27 | 13 | 6 | 554 |
- i.Work out the precision and recall at each threshold. (3)
- ii.Which threshold should the siren use? Argue from the cost of each kind of mistake, including what happens to people who hear many false alarms. (3)
- iii.What should be done before and after the model is used, to keep it safe? (2)
Answer
i.
- 0.3: precision 38 ÷ (38 + 76) = 38 ÷ 114 ≈ 33%; recall 38 ÷ 40 = 95%.
- 0.5: precision 34 ÷ (34 + 26) = 34 ÷ 60 ≈ 57%; recall 34 ÷ 40 = 85%.
- 0.7: precision 27 ÷ (27 + 6) = 27 ÷ 33 ≈ 82%; recall 27 ÷ 40 ≈ 68%.
ii. One well-argued case (other conclusions earn marks with good reasons):
- A missed flood can cost lives, houses, animals and stored grain: it is the worst mistake, so recall matters most, which argues against 0.7, which misses a third of the floods.
- But a false alarm isn’t free: a family leaves at night with children, old people and animals, and loses a night’s sleep and work. At 0.3, two of every three sirens are false (76 in these 600 periods). People who are called out again and again for nothing stop believing the siren (alarm fatigue), and then even a true warning saves fewer people. So the lowest threshold can save fewer lives than it seems.
- A good answer: two levels. At 0.3, a “get ready” message (pack, move animals and grain higher, the old and sick go first), which costs little when it is wrong; at 0.5 or 0.7, the siren to leave. Or, with one level, 0.5: it misses 6 floods rather than 2, and over half its alarms are true, so people keep trusting it; together with the warnings that already exist, from the river gauges upstream and the people who watch them.
iii. Before: test it on monsoons it wasn’t trained on, and on this village’s river, and ask for a model card; decide who presses the siren (a person in the disaster office, who sees the score with the upstream gauges), and keep the old warnings running beside it. After: record every flood and every alarm, and check its recall and false alarms each monsoon; a river changes its course, and new embankments and roads change how it floods, so the model can drift; have a plan to stop using it, and tell the villages what it can and can’t do.
Marking scheme
- i. Precision and recall at 0.3: about 33% and 95%.
- i. At 0.5: about 57% and 85%.
- i. At 0.7: about 82% and 68%.
- ii. The cost of a miss argued (lives, homes): recall matters most.
- ii. The cost of false alarms argued, including alarm fatigue: people stop trusting the siren.
- ii. A clear choice that follows from the costs (such as two levels, or 0.5 with the existing warnings).
- iii. Before: tested on new monsoons and this river, a person deciding with the upstream gauges, old warnings kept.
- iii. After: every alarm and flood recorded, checked each monsoon for drift, a way to stop.
Practical exam
20 marksMarked by the school. Work alone at a computer, with no internet and no AI, in front of the teacher, for 1 hour 30 minutes. Write and run each program, show it working, and answer the teacher’s questions. On this page, press Edit on a program to type in it, and Run to run it.
Fewest buses. LINES lists the stops of each bus line in Lalitpur (made up: these are not the real routes). A bus goes both ways along its line, and you can change from one line to another at any stop they share. Write fewest_buses(start, goal), which returns the fewest buses needed and the lines in order, or None if no buses connect the two stops. Write automatic tests for it, run by a small test runner: at least the start being the goal, a trip on one bus, a stop that can’t be reached, a trip you worked out by hand, and a test that every trip needs as many buses as the trip back. Then print the trip from Lubhu to Thecho.
LINES = { "Red": ["Lagankhel", "Satdobato", "Gwarko", "Koteshwor"], "Blue": ["Lagankhel", "Jawalakhel", "Kupondole", "Thapathali"], "Green": ["Satdobato", "Bhaisepati", "Sainbu", "Bungamati"], "Yellow": ["Godawari", "Badikhel", "Chapagaun", "Bungamati"], "White": ["Gwarko", "Imadol", "Lubhu", "Lamatar"], "Orange": ["Thecho", "Chapagaun", "Dhapakhel", "Satdobato"], "Purple": ["Tikabhairab", "Lele"],} def fewest_buses(start, goal): return None # Your tests hereAnswer
A breadth-first search over the lines: first every line through the start (one bus), then every line that shares a stop with one of those (two buses), and so on. A queue takes them in that order, so the first line found that reaches the goal needs the fewest buses.
from collections import deque LINES = { "Red": ["Lagankhel", "Satdobato", "Gwarko", "Koteshwor"], "Blue": ["Lagankhel", "Jawalakhel", "Kupondole", "Thapathali"], "Green": ["Satdobato", "Bhaisepati", "Sainbu", "Bungamati"], "Yellow": ["Godawari", "Badikhel", "Chapagaun", "Bungamati"], "White": ["Gwarko", "Imadol", "Lubhu", "Lamatar"], "Orange": ["Thecho", "Chapagaun", "Dhapakhel", "Satdobato"], "Purple": ["Tikabhairab", "Lele"],} def fewest_buses(start, goal): """(number of buses, the lines in order) with the fewest buses, or None.""" if start == goal: return 0, [] waiting = deque() came_from = {} for line, stops in LINES.items(): if start in stops: came_from[line] = None waiting.append(line) while waiting: line = waiting.popleft() if goal in LINES[line]: route = [] while line is not None: route.append(line) line = came_from[line] return len(route), route[::-1] for other, stops in LINES.items(): if other not in came_from and set(stops) & set(LINES[line]): came_from[other] = line waiting.append(other) return None def test_already_there(): assert fewest_buses("Gwarko", "Gwarko") == (0, []) def test_one_bus(): assert fewest_buses("Lagankhel", "Koteshwor") == (1, ["Red"]) def test_no_bus_goes_there(): assert fewest_buses("Lagankhel", "Lele") is None def test_worked_by_hand(): # by hand: Thapathali on Blue to Lagankhel, Red to Satdobato, Orange to Chapagaun, Yellow to Godawari buses, lines = fewest_buses("Thapathali", "Godawari") assert buses == 4, f"{buses} {lines}" def test_same_both_ways(): stops = {s for line in LINES.values() for s in line} for a in stops: for b in stops: there, back = fewest_buses(a, b), fewest_buses(b, a) assert (there is None) == (back is None), f"{a} and {b}" if there: assert there[0] == back[0], f"{a} to {b}: {there[0]}, back {back[0]}" passed = failed = 0for name, test in list(globals().items()): if name.startswith("test_"): try: test() print("PASS", name) passed += 1 except AssertionError as problem: print("FAIL", name, problem) failed += 1print(passed, "passed,", failed, "failed") buses, lines = fewest_buses("Lubhu", "Thecho")print(f"Lubhu to Thecho: {buses} buses: {' → '.join(lines)}")Output
PASS test_already_therePASS test_one_busPASS test_no_bus_goes_therePASS test_worked_by_handPASS test_same_both_ways5 passed, 0 failedLubhu to Thecho: 3 buses: White → Red → OrangeMarking scheme
- Treats the lines as the places to search, joined where two lines share a stop.
- A queue (breadth-first), starting from every line through the start, each line taken once.
- Reads the lines back from the came-from notes, and returns
Nonewhen the queue empties. - Handles the start being the goal (0 buses).
- Tests as
test_functions withassert: one bus, a stop no bus reaches, a trip worked out by hand. - The both-ways test over every pair of stops; a runner reports passes and failures, and all pass.
- Prints the trip from Lubhu to Thecho: 3 buses, White, Red, Orange.
The medicine shelf. A health post’s program keeps a list of its medicines with the days left before each expires. The requirement says: “still_good gives the medicines whose expiry is still ahead (more than 0 days left), in the same order.” Run the tests, find why some fail, fix the code, and run them again. (The stock is made up.)
def still_good(stock): """The medicines whose expiry is still ahead (days left more than 0), in the same order.""" for medicine in stock: if medicine[1] <= 0: stock.remove(medicine) return stock def test_all_good(): assert still_good([("ORS", 120), ("Paracetamol", 30)]) == [("ORS", 120), ("Paracetamol", 30)] def test_one_expired(): assert still_good([("ORS", 120), ("Zinc", -5), ("Paracetamol", 30)]) == [("ORS", 120), ("Paracetamol", 30)] def test_two_expired_together(): stock = [("Amoxicillin", 60), ("Zinc", -5), ("Iron", 0), ("ORS", 120)] result = still_good(stock) assert result == [("Amoxicillin", 60), ("ORS", 120)], f"gave {result}" def test_all_expired(): result = still_good([("Zinc", -5), ("Iron", 0), ("Vitamin A", -40)]) assert result == [], f"gave {result}" passed = failed = 0for name, test in list(globals().items()): if name.startswith("test_"): try: test() print("PASS", name) passed += 1 except AssertionError as problem: print("FAIL", name, problem) failed += 1print(passed, "passed,", failed, "failed")Answer
The first run:
Output
PASS test_all_goodPASS test_one_expiredFAIL test_two_expired_together gave [('Amoxicillin', 60), ('Iron', 0), ('ORS', 120)]FAIL test_all_expired gave [('Iron', 0)]2 passed, 2 failedLine 5 removes items from the list while the loop on line 3 is going through it. When Zinc (item 1) is removed, Iron moves up into its place, and the loop goes on to item 2, which is now ORS: Iron is never looked at. So an expired medicine straight after another stays on the list. The tests that passed had no two expired medicines together. The function also changes the list it was given, which the requirement doesn’t ask for. The tests match the requirement, so the code is wrong: build a new list instead.
def still_good(stock): """The medicines whose expiry is still ahead (days left more than 0), in the same order.""" good = [] for medicine in stock: if medicine[1] > 0: good.append(medicine) return good def test_all_good(): assert still_good([("ORS", 120), ("Paracetamol", 30)]) == [("ORS", 120), ("Paracetamol", 30)] def test_one_expired(): assert still_good([("ORS", 120), ("Zinc", -5), ("Paracetamol", 30)]) == [("ORS", 120), ("Paracetamol", 30)] def test_two_expired_together(): stock = [("Amoxicillin", 60), ("Zinc", -5), ("Iron", 0), ("ORS", 120)] result = still_good(stock) assert result == [("Amoxicillin", 60), ("ORS", 120)], f"gave {result}" def test_all_expired(): result = still_good([("Zinc", -5), ("Iron", 0), ("Vitamin A", -40)]) assert result == [], f"gave {result}" passed = failed = 0for name, test in list(globals().items()): if name.startswith("test_"): try: test() print("PASS", name) passed += 1 except AssertionError as problem: print("FAIL", name, problem) failed += 1print(passed, "passed,", failed, "failed")Output
PASS test_all_goodPASS test_one_expiredPASS test_two_expired_togetherPASS test_all_expired4 passed, 0 failedMarking scheme
- Runs the tests and names the failing ones: Iron stays on the list in both.
- Finds the cause: removing from the list while looping over it skips the item after each one removed.
- Fixes the code (a new list, or looping over a copy), not the tests.
- Runs all the tests again, and all pass.
A cough that tells? A health post in Rautahat tried an app that listens to a person’s cough and flags those who should give a sample for a TB test. For 48 people, the list gives each one’s age, whether the lab test found TB (1) or not (0), and whether the app flagged them (1) or not (0). (The people and results are made up.)
Write a program that counts the four outcomes and works out the app’s precision and recall, for everyone, for children under 15, and for people 15 and over. Then write one limit of the app, in a sentence for the health post, with the numbers that show it.
people = [ (25, 0, 0), (18, 0, 0), (4, 0, 0), (26, 0, 1), (19, 0, 0), (52, 0, 0), (16, 0, 0), (12, 0, 0), (78, 0, 0), (10, 1, 1), (9, 1, 0), (45, 0, 0), (39, 0, 0), (72, 1, 1), (44, 0, 0), (75, 1, 1), (35, 0, 0), (72, 0, 1), (11, 0, 0), (7, 1, 0), (74, 0, 0), (27, 1, 1), (13, 1, 1), (14, 0, 0), (11, 1, 0), (7, 0, 0), (6, 0, 0), (4, 0, 1), (53, 1, 0), (72, 1, 1), (33, 0, 1), (23, 0, 0), (20, 0, 1), (7, 0, 0), (40, 0, 0), (4, 0, 0), (3, 0, 0), (15, 0, 0), (39, 1, 1), (8, 0, 0), (65, 0, 1), (72, 1, 1), (74, 1, 1), (38, 1, 1), (56, 0, 0), (22, 0, 0), (38, 1, 1), (71, 0, 0),] # Your program hereAnswer
people = [ (25, 0, 0), (18, 0, 0), (4, 0, 0), (26, 0, 1), (19, 0, 0), (52, 0, 0), (16, 0, 0), (12, 0, 0), (78, 0, 0), (10, 1, 1), (9, 1, 0), (45, 0, 0), (39, 0, 0), (72, 1, 1), (44, 0, 0), (75, 1, 1), (35, 0, 0), (72, 0, 1), (11, 0, 0), (7, 1, 0), (74, 0, 0), (27, 1, 1), (13, 1, 1), (14, 0, 0), (11, 1, 0), (7, 0, 0), (6, 0, 0), (4, 0, 1), (53, 1, 0), (72, 1, 1), (33, 0, 1), (23, 0, 0), (20, 0, 1), (7, 0, 0), (40, 0, 0), (4, 0, 0), (3, 0, 0), (15, 0, 0), (39, 1, 1), (8, 0, 0), (65, 0, 1), (72, 1, 1), (74, 1, 1), (38, 1, 1), (56, 0, 0), (22, 0, 0), (38, 1, 1), (71, 0, 0),] def measure(name, low, high): """Count the outcomes for people aged low to high.""" tp = fp = fn = tn = 0 for age, tb, flagged in people: if not low <= age <= high: continue if tb and flagged: tp += 1 elif tb: fn += 1 elif flagged: fp += 1 else: tn += 1 print(f"{name:9} TP {tp:2} FP {fp} FN {fn} TN {tn:2} precision {tp / (tp + fp):.0%} recall {tp / (tp + fn):.0%}") measure("Everyone", 0, 120)measure("Under 15", 0, 14)measure("15 and up", 15, 120)Output
Everyone TP 11 FP 6 FN 4 TN 27 precision 65% recall 73%Under 15 TP 2 FP 1 FN 3 TN 10 precision 67% recall 40%15 and up TP 9 FP 5 FN 1 TN 17 precision 64% recall 90%One limit: “The app flagged only 2 of the 5 children under 15 who had TB, so a child it doesn’t flag may still have TB; and with only 5 children with TB in the test, we can’t yet say how well it works for children.” Children’s coughs are different and they often can’t cough on purpose, so a model that hears adults well may not hear them.
Marking scheme
- A loop over the people that counts true positives, false positives, false negatives and true negatives.
- Precision and recall from the counts: 65% and 73% for everyone.
- The age groups measured apart (recall 40% under 15, 90% from 15).
- One limit stated in a sentence with its numbers (children’s recall, or only 5 children with TB).
Viva. The teacher asks each student about their own work, for example:
- i.Why does a queue (breadth-first search) find the trip with the fewest buses? What did your both-ways test check, and why is it evidence the function is right? (2)
- ii.In the medicine program, why did two tests pass, though the code was wrong? (1)
- iii.The app’s recall for everyone is 73%. Why would it be wrong to tell the health post that it finds about three in four people with TB, whatever their age? What would you do? (2)
Answer
- i.The queue takes every line that needs one bus before any that needs two, every two-bus line before any three-bus line, and so on, so the first line found that reaches the goal is on a trip with the fewest buses. Buses go both ways along their lines, so the fewest buses from A to B must equal the fewest from B to A; the test checked this for every pair of stops. Many mistakes in the search (a line skipped, or lines taken in the wrong order) would make some pair differ, so passing for every pair is evidence, though not proof: a mistake that is the same both ways would still pass.
- ii.Removing an item skips only the item straight after it. In the two tests that passed, no expired medicine came straight after another, so nothing that mattered was skipped.
- iii.The 73% mixes two groups: for people 15 and over it found 9 of 10, but for children only 2 of 5. Telling the health post “three in four” would hide that it misses most children with TB. I would test it on many more children with TB, report its recall by age, and until it does better, not use it to decide which children are tested.
Marking scheme
- i. The queue takes trips in order of buses needed, so the first that reaches the goal has the fewest.
- i. The both-ways test checked every pair of stops gives the same count both ways, as it must when buses run both ways.
- ii. No two expired medicines came together in those tests, so the skipped item didn’t matter.
- iii. The overall recall hides the children’s: 2 of 5.
- iii. More children with TB to test on, recall reported by age, and not relied on for children meanwhile.
Your score
0 / 50 theory
0 / 20 practical
24 questions not marked yet
| Section A | – | Full marks 10 |
| Section B | – | Full marks 24 |
| Section C | – | Full marks 16 |
| Practical exam | – | Full marks 20 |
Marks lost, by unit
Mark a question to see your score here.
