Final Exam Model Paper 10
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.
What does it mean for an algorithm to run in polynomial time?
Pick an answer to see why.
After learning memoization, Kiran says: “Remembering answers makes programs faster, so every function should remember every answer it has ever given.” Which judgment is right?
Pick an answer to see why.
What is a higher-order function?
Pick an answer to see why.
A team is asked to build software that controls the gates of a new irrigation dam, a kind of system nobody in the company has built before, where a mistake could flood fields and villages. Which development method suits it best?
Pick an answer to see why.
What is a key–value store?
Pick an answer to see why.
A land office’s servers answer two kinds of requests: quick lookups, and reports that take minutes. With round robin, requests often wait behind a report on a busy server while another server is free. Which way of balancing the load helps?
Pick an answer to see why.
On the evening a popular 1 GB video is released, 50,000 people in Nepal watch it. If every viewer got it from a server abroad, how much would cross the border, and how much if each internet company keeps one copy on an edge server inside its network (say 10 internet companies)?
Pick an answer to see why.
What are Certificate Transparency logs?
Pick an answer to see why.
Dawa and Nima meet and compare the key fingerprints their chat app shows for their conversation. The pictures are different. What does this suggest?
Pick an answer to see why.
A municipality plans face recognition cameras in its markets, to find people on a list of suspected pickpockets. Which first step is best?
Pick an answer to see why.
Section B
8 × 3 = 24Write short answers to the following questions.
Ramesh says he has beaten Turing’s proof: “My halts(program, data) runs the program for one hour. If it has stopped, I answer True; if not, I answer False.”
- i.Give a kind of program on which his
haltsis wrong. - ii.Turing’s
contraryaskshaltswhether a program stops when given itself, and does the opposite. Why can’t anyhalts, Ramesh’s or anyone else’s, be right aboutcontrary(contrary)? - iii.What can a real checker for endless loops honestly promise?
Answer
- i.Any program that stops after more than an hour: a long calculation, or one that waits for an hour and a minute. His
haltssays False, but it would have stopped. - ii.If
halts(contrary, contrary)says True, contrary loops for ever, so it doesn’t stop; if it says False, contrary stops at once. Either answer is made wrong by contrary itself, and contrary can be written from anyhaltsin a few lines. So no program decides, for every program and input, whether it stops: the halting problem is undecidable. - iii.Three answers instead of two: “it stops”, “it doesn’t stop”, or “I can’t tell”. Such checkers are useful, and often right; what none can do is never say “I can’t tell”.
Marking scheme
- i. A program that stops after more than an hour.
- ii. Whatever
haltsanswers aboutcontrary(contrary), contrary does the opposite, so it is wrong. - iii. It can answer “stops”, “doesn’t stop” or “can’t tell”, but not always know.
A bus company asked a chatbot for a fare function. The requirement: “Children under 5 travel free; children from 5 to 11 and passengers aged 60 or more pay half; everyone else pays full. Fares are whole rupees.” The chatbot wrote:
def fare(full, age): if age < 12: return full / 2 elif age < 5: return 0 elif age >= 60: return full / 2 return full- i.List tests, as ages with a full fare of Rs 800, with normal and boundary data and what each should give.
- ii.Work out what the function gives for your tests. Which fail, and why?
- iii.Write the corrected function.
Answer
- i.With Rs 800: age 3 → 0 (normal); 4 → 0 and 5 → 400 (the boundary at 5); 11 → 400 and 12 → 800 (the boundary at 12); 59 → 800 and 60 → 400 (the boundary at 60).
- ii.Ages 3 and 4 give 400.0, not 0:
age < 12is checked first and is true for every child, soage < 5is never reached. And every half fare is a float, 400.0, not a whole number of rupees (Rs 755 would give 377.5). The others give the right amount. - iii.Check the smallest group first, and use whole-number division:
def fare(full, age): """The fare in rupees: free under 5, half from 5 to 11, half from 60 on, full otherwise.""" if age < 5: return 0 elif age < 12: return full // 2 elif age >= 60: return full // 2 return full tests = [ ((800, 3), 0), # normal: a small child ((800, 4), 0), # boundary: the oldest free age ((800, 5), 400), # boundary: the youngest half fare ((800, 11), 400), # boundary: the oldest half fare for children ((800, 12), 800), # boundary: the youngest full fare ((800, 59), 800), # boundary: the oldest full fare ((800, 60), 400), # boundary: the youngest half fare for elders]for args, want in tests: got = fare(*args) print("ok " if got == want else "FAIL", args, "gave", got, "wanted", want)Output
ok (800, 3) gave 0 wanted 0ok (800, 4) gave 0 wanted 0ok (800, 5) gave 400 wanted 400ok (800, 11) gave 400 wanted 400ok (800, 12) gave 800 wanted 800ok (800, 59) gave 800 wanted 800ok (800, 60) gave 400 wanted 400(A company might round a half fare of an odd amount up instead; the requirement should say which.)
Marking scheme
- i. Tests at every boundary (4/5, 11/12, 59/60) with the right fares.
- ii. Under 5 gives 400.0, because
age < 12is checked first; half fares are floats. - iii. The corrected function: under 5 first, whole-number fares.
Your teammate reviews your code for a homestay booking app and writes three comments. How should you respond to each?
- i.“Line 14: a booking that ends on the day another starts is refused, but the requirement says that is allowed.”
- ii.“I would call this variable
nights, notn.” - iii.“Who taught you to write code like this?”
Answer
- i.A bug against the requirement: thank the reviewer, add a test for a booking ending on the day another starts, fix the comparison so the test passes, and reply that it is fixed.
- ii.A matter of taste, but a clear name helps every reader: rename it (it costs little), or explain briefly why
nis clear enough there. Either way, answer it. - iii.This comment is about the person, not the code, and doesn’t help. Don’t take it personally or answer in the same tone: ask calmly which part of the code they mean and what they would change, so the review can go back to the code.
Marking scheme
- i. Treat it as a bug: a test for it, the fix, and a reply.
- ii. Taste: accept the clearer name or explain, and answer it.
- iii. Don’t take it personally: ask what in the code they mean.
Read this short postmortem from a made-up wallet company:
“At 10:02 an engineer changed a setting on all our servers at once. The new setting made every server slow to answer the payment service. The payment service’s calls timed out, and it called again at once, and again, many times a second. The extra calls overloaded the servers that still worked, and all payments stopped for 47 minutes. We undid the setting at 10:49.”
- i.What made a small mistake into a complete outage?
- ii.Name three defenses that would have limited it.
- iii.The postmortem doesn’t name the engineer. Why is that right?
Answer
- i.The change went to every server at once, so nothing was left working as before; and the payment service retried at once, again and again, so the retries multiplied the load on the servers that were still working: a cascading failure.
- ii.Any three: a staged rollout (a few servers first, watched, then more); cells, so a bad change reaches only some users; exponential backoff with jitter for the retries; a circuit breaker that stops calling after too many failures; a quick way to undo every change.
- iii.It is blameless: it asks how the system let one mistake do so much harm, not whom to punish. If people are blamed, they hide mistakes, and the weaknesses stay; here the fix is in the system (staged rollouts, backoff), so the same mistake can’t do the same harm again.
Marking scheme
- i. One change everywhere at once, and retries at once that multiplied the load.
- ii. Three defenses (staged rollout, cells, backoff with jitter, circuit breaker, undo).
- iii. Blameless: fix the system, not punish the person, so mistakes are reported.
An agriculture ministry plans to decide where to send extension workers from the questions farmers type into a farming app: districts with many questions about a crop disease get more help.
- i.Which farmers are missing from this data?
- ii.How could decisions made from it fail them?
- iii.How could the ministry make its picture fairer?
Answer
- i.Farmers without a smartphone or mobile data, who can’t read well or type, who don’t know the app, or who farm where the signal is weak: often the poorest, the oldest, many women farmers, and remote districts.
- ii.Districts where farmers are online would look like they have the most disease and get the most help, while a disease spreading where few use the app would go unseen: help goes to those who already have the most.
- iii.Add data that doesn’t depend on the app: reports from the extension workers and agriculture offices, a random sample of farms visited in every district, and phone calls (not only typing) or local-language voice questions; and compare questions per farmer using the app, not raw counts.
Marking scheme
- i. Farmers without smartphones, data or reading and typing, and remote areas.
- ii. Help goes to districts that are online, and problems elsewhere go unseen.
- iii. Data that doesn’t depend on the app (a random sample of farms, offices’ reports), or counts adjusted for who uses it.
A report finds that districts with more mobile phone towers per person have higher SEE pass rates, and says: “More towers raise students’ results. The government should build towers to improve education.” Evaluate the claim.
Answer
The data shows the two go together; it doesn’t show that towers cause better results. Districts with more towers tend to be more urban, richer and easier to reach, with more schools, teachers, roads and electricity, and families who can pay for tuition: these confounders could raise pass rates and bring towers at the same time. Better results might even bring towers (richer districts attract companies). Phones and the internet might help students a little, but this data can’t say how much.
To find out whether better phone access raises results, the government would need a randomized comparison, such as giving internet access to schools chosen by chance and comparing their results with others, or at least comparing districts alike in wealth and roads. A recommendation to build towers “to improve education” goes beyond the evidence; towers may be worth building for other reasons.
Marking scheme
- Going together isn’t causing.
- Confounders named (wealth, towns, roads, schools), which could explain both.
- What would show cause (a randomized comparison, or like with like), and so the claim goes beyond the evidence.
A simple cipher numbers the letters A = 0, B = 1, …, Z = 25, and encrypts each letter by multiplying by 7, mod 26.
- i.Encrypt LAKE.
- ii.Find the number that undoes multiplying by 7, mod 26, by trying, and use it to decrypt XEGAZ.
- iii.Why could the cipher not multiply by 13 instead?
Answer
- i.L = 11: 77 mod 26 = 25, Z. A = 0: 0, A. K = 10: 70 mod 26 = 18, S. E = 4: 28 mod 26 = 2, C. LAKE becomes ZASC.
- ii.7 × 15 = 105 = 4 × 26 + 1, so 15 is the inverse of 7, mod 26. X = 23: 345 mod 26 = 7, H; E = 4: 60 mod 26 = 8, I; G = 6: 90 mod 26 = 12, M; A = 0: A; Z = 25: 375 mod 26 = 11, L. XEGAZ is HIMAL.
- iii.13 shares a factor (13) with 26, so it has no inverse: different letters encrypt to the same one (B = 1 and D = 3 both become 13, N, since 39 mod 26 = 13), and N could not be decrypted.
Marking scheme
- i. ZASC.
- ii. The inverse 15 (7 × 15 = 105 = 4 × 26 + 1), and XEGAZ decrypted to HIMAL.
- iii. 13 shares a factor with 26, so it has no inverse: two letters become the same one.
The tools you use will change after you leave school. Name the parts that official documentation usually has, and two ways to keep learning on your own.
Answer
- Official documentation, written by a tool’s makers, usually has a tutorial (to start), a reference (every function, exactly), how-to guides (recipes for common jobs), and release notes or a changelog (what changed in each version, including what breaks).
- Ways to keep learning, any two: build something slightly too hard for you; read other people’s code, such as open-source projects; explain what you learned (a note, a help answer); use AI as a tutor, not a substitute, checking it against the documentation; join others in a club, a meetup or an online community.
Marking scheme
- The parts of documentation: tutorial, reference, how-to guides, release notes.
- A first way to keep learning.
- A second way to keep learning.
Section C
2 × 8 = 16Write long answers to the following questions.
An electric tourist bus runs a 290 km route. Fully charged, it can go 120 km, and it charges fully at any charging station it stops at. The stations are at these distances from the start (made up):
| Station | Start | S1 | S2 | S3 | S4 | S5 | S6 | End |
|---|---|---|---|---|---|---|---|---|
| km from the start | 0 | 35 | 80 | 110 | 150 | 200 | 240 | 290 |
It leaves the start fully charged, and wants as few charging stops as possible.
- i.A greedy method: drive to the farthest station within reach, charge, and repeat. Which stations does it stop at? How many stops? (2)
- ii.Show that no plan can manage with fewer stops. (2)
- iii.Give a reason why the farthest-station method can never need more stops than any other plan, on any route. (2)
- iv.Now each station charges a different price, and the company wants the cheapest trip, not the fewest stops. Why might the greedy method fail now, and what kind of algorithm would you use? (2)
Answer
i. From the start (0), it can reach up to 120 km: the farthest station within reach is S3 (110). From S3 it can reach 230: the farthest is S5 (200). From S5 it can reach 320, past the end (290). So it stops at S3 and S5: 2 stops.
ii. With no stops, 290 km is more than 120. With one stop at a station s, the bus needs s ≤ 120 to get there and 290 − s ≤ 120, so s ≥ 170, to finish. No station is both at most 120 and at least 170 km from the start, so one stop is impossible: 2 is the fewest.
iii. Compare the greedy plan with any other plan, stop by stop. The greedy first stop is the farthest the bus can reach, so it is at least as far along as the other plan’s first stop. From a stop at least as far along, the bus can reach at least as far, so the greedy second stop is at least as far along as the other’s second, and so on. The greedy plan is never behind, so whenever the other plan can reach the end after some number of stops, the greedy one can too. Choosing the farthest station can’t spoil anything that comes after.
iv. Stopping at the farthest station might mean charging at an expensive one, when a cheaper one a little earlier, or an extra stop at a cheap one, would cost less. The reason in iii was about the number of stops, not the price, so it no longer holds. Use dynamic programming (the cheapest way to reach each station is the cheapest, over every station within 120 km before it, of reaching that station plus its price), or think of the stations as places in a weighted graph, with a road wherever one station can reach another, and use Dijkstra’s algorithm to find the cheapest way to the end.
Marking scheme
- i. Farthest within reach from the start: S3 (110); from S3: S5 (200).
- i. From S5 the end is in reach: 2 stops.
- ii. No stops: 290 > 120.
- ii. One stop would need a station within 120 km of both the start and the end (at least 170 km from the start): none exists.
- iii. The greedy stop is always at least as far along as the other plan’s stop with the same number.
- iii. So it reaches the end with no more stops than any other plan.
- iv. The cheapest station may not be the farthest; the reason about stops says nothing about price.
- iv. Dynamic programming over the stations, or Dijkstra’s algorithm on a graph of stations with prices.
A national park wants a model that ranks the households of its buffer zone, so that rangers search those most likely to hold illegal wildlife goods first. It is trained on five years of the park’s search records: which households were searched, and in which illegal goods were found. Most past searches were in Ward A, beside the road and the rangers’ post. (The records are made up.)
| Searches | Illegal goods found | |
|---|---|---|
| Ward A (beside the road and the post) | 400 | 20 |
| Ward B (a day’s walk away) | 50 | 4 |
The model ranks almost every household in Ward A above every household in Ward B, because 20 of the 24 finds were in Ward A.
- i.Work out the share of searches that found illegal goods in each ward. What has the model really learned, and what would happen if the park follows it for five more years? (3)
- ii.Who could be harmed, and how? (2)
- iii.Should the park use the model? Give your decision, and what must be done instead or first. (3)
Answer
i. Ward A: 20 ÷ 400 = 5% of searches found goods; Ward B: 4 ÷ 50 = 8%. Searches in Ward B found goods more often, not less. Ward A has more finds only because it had 8 times as many searches: it was near the post. The model learned where the rangers searched, not where wildlife crime happens: the past decisions are in the data. Following it sends even more searches to Ward A, which finds more there, which makes the next model rank Ward A higher still: a feedback loop that confirms itself, while Ward B, perhaps the bigger problem, goes unwatched.
ii. Ward A’s households: searched again and again, though 95% of searches there find nothing: 380 innocent households in the past five years, an intrusion into their homes and privacy and a loss of dignity, and a feeling of being treated as suspects for living near the road. The park and its wildlife: conservation depends on buffer-zone communities’ cooperation (they report poachers and fires); unfair searches lose their trust, and real poachers elsewhere are missed.
iii. One well-argued case: Not as it is.
- Get data that isn’t shaped by past searches: a small number of searches spread at random across all wards for a time, together with other evidence (informants’ reports, camera traps, seized goods traced back), so that the rate of crime in each ward can be compared fairly.
- Measure by ward: the share of searches that find something, in each ward, before and after any change.
- Keep people deciding: a model may suggest, but a ranger’s officer decides each search, records the reason, and the households have a way to complain.
- Involve the community: tell the buffer-zone committees what is used and why; an impact assessment and an entry in a public register before it is used, checks every year for the feedback loop, and a way to stop.
Marking scheme
- i. Ward A 5%, Ward B 8%.
- i. The model learned where searches happened (8 times as many in Ward A), not where crime is.
- i. Following it gives a feedback loop: more searches in A, more finds in A, a higher rank for A.
- ii. Ward A’s innocent households searched repeatedly (privacy, dignity).
- ii. Trust and cooperation lost, and crime elsewhere missed.
- iii. A clear decision (not as it is).
- iii. Fairer data: random searches across wards and other evidence, results measured by ward.
- iii. Safeguards: a person decides with a recorded reason, complaints, the community told, checks for the loop, 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.
Routes through the orchard. A cart carries crates through an apple orchard in Mustang from the gate, the top-left square of ORCHARD, to the store, the bottom-right square, moving only east or south, so every route it can take is a shortest one. Some squares have rocks (#). To share the wear on the paths, the owner wants to know how many different routes there are. Write count_routes(grid), which uses dynamic programming to count them. Write automatic tests for it, run by a small test runner: at least a grid of one square, an open 2 × 2 grid, a rock on the gate, a grid you worked out by hand, and a test that compares it with following every route, on many random small grids. Then print the number of routes through the orchard, and how many there would be without the rocks. (The orchard is made up.)
ORCHARD = [ "....#.", ".#....", "...#..", "#.....", "..#...",] # . an open square, # a rock def count_routes(grid): return 0 # Your tests hereAnswer
A route into a square comes either from the square to the north or from the one to the west, so the number of routes to a square is the sum of the numbers for those two (and 0 on a rock). Filling the table row by row works each square out once.
import random ORCHARD = [ "....#.", ".#....", "...#..", "#.....", "..#...",] # . an open square, # a rock def count_routes(grid): """How many routes go from the top-left square to the bottom-right one, moving only east or south.""" rows, cols = len(grid), len(grid[0]) ways = [[0] * cols for r in range(rows)] for r in range(rows): for c in range(cols): if grid[r][c] == "#": ways[r][c] = 0 elif r == 0 and c == 0: ways[r][c] = 1 else: from_north = ways[r - 1][c] if r > 0 else 0 from_west = ways[r][c - 1] if c > 0 else 0 ways[r][c] = from_north + from_west return ways[rows - 1][cols - 1] def try_every(grid, r=0, c=0): """Count the routes by following every one of them.""" if r == len(grid) or c == len(grid[0]) or grid[r][c] == "#": return 0 if r == len(grid) - 1 and c == len(grid[0]) - 1: return 1 return try_every(grid, r + 1, c) + try_every(grid, r, c + 1) def test_one_square(): assert count_routes(["."]) == 1 def test_open_two_by_two(): assert count_routes(["..", ".."]) == 2 def test_rock_at_the_start(): assert count_routes(["#.", ".."]) == 0 def test_worked_by_hand(): # by hand: the rock in the middle leaves only the two routes round the edge assert count_routes(["...", ".#.", "..."]) == 2 def test_same_as_following_every_route(): random.seed(1) for trial in range(200): rows, cols = random.randint(1, 6), random.randint(1, 6) grid = ["".join(random.choice("...#") for c in range(cols)) for r in range(rows)] assert count_routes(grid) == try_every(grid), grid 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") print("Routes through the orchard:", count_routes(ORCHARD))print("Without the rocks:", count_routes(["......"] * 5))Output
PASS test_one_squarePASS test_open_two_by_twoPASS test_rock_at_the_startPASS test_worked_by_handPASS test_same_as_following_every_route5 passed, 0 failedRoutes through the orchard: 17Without the rocks: 126Marking scheme
- A table with a count for every square, filled in an order that has the squares to the north and west done first.
- Each count the sum of the counts to the north and west; 0 on a rock; 1 at the gate.
- Squares on the top row and left column handled (nothing to the north or west).
- A function that follows every route (recursion) to compare with.
- Tests as
test_functions withassert: one square, an open 2 × 2 grid, a rock on the gate, a grid worked out by hand. - The comparison with following every route on many random grids; a runner reports passes and failures, and all pass.
- Prints 17 routes through the orchard, and 126 without the rocks.
A full packet of tea. A tea cooperative in Panchthar fills packets from scoops of different sizes. The requirement says: “packet_full is True when the scoops, in kilograms, make exactly 1 kg.” Run the tests, find why some fail, fix the code, and run them again.
def packet_full(scoops): """True when the scoops of tea, in kg, make exactly 1 kg.""" total = 0 for scoop in scoops: total = total + scoop return total == 1.0 def test_two_halves(): assert packet_full([0.5, 0.5]) is True def test_too_little(): assert packet_full([0.5, 0.25]) is False def test_ten_small_scoops(): assert packet_full([0.1] * 10) is True, f"gave {packet_full([0.1] * 10)}" def test_three_scoops(): assert packet_full([0.7, 0.2, 0.1]) is True, f"gave {packet_full([0.7, 0.2, 0.1])}" 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_two_halvesPASS test_too_littleFAIL test_ten_small_scoops gave FalseFAIL test_three_scoops gave False2 passed, 2 failedLine 6 compares two floats with ==. A computer stores numbers in binary, and 0.1, 0.2 and 0.7 can’t be written exactly in binary (as one third can’t be written exactly in decimals), so each is stored as the nearest binary fraction. Adding them, the tiny errors add up: ten scoops of 0.1 make 0.9999999999999999, which is not equal to 1.0. 0.5 and 0.25 are exact in binary, which is why the two tests that passed did. The tests match the requirement, so the code is wrong: compare with a small tolerance (or count in whole grams).
import math def packet_full(scoops): """True when the scoops of tea, in kg, make exactly 1 kg.""" total = 0 for scoop in scoops: total = total + scoop return math.isclose(total, 1.0) def test_two_halves(): assert packet_full([0.5, 0.5]) is True def test_too_little(): assert packet_full([0.5, 0.25]) is False def test_ten_small_scoops(): assert packet_full([0.1] * 10) is True, f"gave {packet_full([0.1] * 10)}" def test_three_scoops(): assert packet_full([0.7, 0.2, 0.1]) is True, f"gave {packet_full([0.7, 0.2, 0.1])}" 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_two_halvesPASS test_too_littlePASS test_ten_small_scoopsPASS test_three_scoops4 passed, 0 failedMarking scheme
- Runs the tests and names the failing ones: ten scoops of 0.1, and 0.7 + 0.2 + 0.1, give False.
- Finds the cause: comparing floats with
==on line 6, when 0.1, 0.2 and 0.7 can’t be stored exactly in binary. - Fixes the code (
math.isclose, a small tolerance, or whole grams), not the tests. - Runs all the tests again, and all pass.
Finding the complaints. A network of homestays in Tanahun tried a model that reads guests’ reviews and flags complaints, so that owners can answer them quickly. It was tested on 50 reviews, in English (en) or in Nepali written in Roman letters (rn). Each item gives the language, whether the review really was a complaint (1) or not (0), and whether the model flagged it (1) or not (0). (The reviews and results are made up.)
Write a program that counts the four outcomes and works out the model’s precision and recall, for all reviews and for each language. Then write one limit of the model, in a sentence for the homestay owners, with the numbers that show it.
reviews = [ "rn 0 0", "en 1 1", "en 0 1", "en 0 0", "rn 0 1", "en 0 0", "en 0 0", "rn 0 0", "en 0 0", "en 0 1", "en 1 0", "en 1 1", "rn 0 0", "en 0 0", "rn 1 0", "en 0 0", "en 1 1", "rn 1 1", "rn 1 0", "rn 0 1", "en 0 0", "rn 0 0", "en 0 0", "en 1 1", "rn 1 0", "en 1 1", "rn 0 0", "rn 0 0", "rn 0 0", "en 0 0", "en 0 0", "rn 0 0", "rn 1 0", "rn 0 0", "rn 1 0", "rn 1 1", "en 0 0", "en 1 1", "en 0 0", "en 0 0", "rn 1 1", "en 0 0", "rn 0 0", "en 1 1", "en 1 1", "en 0 0", "en 1 1", "en 0 0", "en 0 0", "en 0 0",] # Your program hereAnswer
reviews = [ "rn 0 0", "en 1 1", "en 0 1", "en 0 0", "rn 0 1", "en 0 0", "en 0 0", "rn 0 0", "en 0 0", "en 0 1", "en 1 0", "en 1 1", "rn 0 0", "en 0 0", "rn 1 0", "en 0 0", "en 1 1", "rn 1 1", "rn 1 0", "rn 0 1", "en 0 0", "rn 0 0", "en 0 0", "en 1 1", "rn 1 0", "en 1 1", "rn 0 0", "rn 0 0", "rn 0 0", "en 0 0", "en 0 0", "rn 0 0", "rn 1 0", "rn 0 0", "rn 1 0", "rn 1 1", "en 0 0", "en 1 1", "en 0 0", "en 0 0", "rn 1 1", "en 0 0", "rn 0 0", "en 1 1", "en 1 1", "en 0 0", "en 1 1", "en 0 0", "en 0 0", "en 0 0",] def measure(name, languages): """Count the outcomes for the reviews in these languages.""" tp = fp = fn = tn = 0 for review in reviews: language, complaint, flagged = review.split() if language not in languages: continue if complaint == "1" and flagged == "1": tp += 1 elif complaint == "1": fn += 1 elif flagged == "1": fp += 1 else: tn += 1 print(f"{name:13} TP {tp:2} FP {fp} FN {fn} TN {tn:2} precision {tp / (tp + fp):.0%} recall {tp / (tp + fn):.0%}") measure("All", ["en", "rn"])measure("English", ["en"])measure("Roman Nepali", ["rn"])Output
All TP 12 FP 4 FN 6 TN 28 precision 75% recall 67%English TP 9 FP 2 FN 1 TN 18 precision 82% recall 90%Roman Nepali TP 3 FP 2 FN 5 TN 10 precision 60% recall 38%One limit: “The model found only 3 of the 8 complaints written in Nepali in Roman letters, against 9 of 10 in English, so read the Roman Nepali reviews yourself: most complaints there are missed.” Guests who write in Roman Nepali are often the domestic tourists many homestays depend on.
Marking scheme
- A loop that splits each item and counts true positives, false positives, false negatives and true negatives.
- Precision and recall from the counts: 75% and 67% for all reviews.
- The languages measured apart (recall 90% in English, 38% in Roman Nepali).
- One limit stated in a sentence with its numbers (Roman Nepali’s recall).
Viva. The teacher asks each student about their own work, for example:
- i.Why is the number of routes to a square the sum of the numbers to the north and to the west? Why is the table faster than following every route? (2)
- ii.In the tea program, why did two tests pass, though the code was wrong? (1)
- iii.The model’s recall over all 50 reviews is 67%. Why is that not the number to tell the owners? What would you do? (2)
Answer
- i.Every route into a square arrives from exactly one of the two squares before it, north or west, so the routes split into two groups that don’t overlap: those through the north square and those through the west one. Adding their counts counts every route once. Following every route walks each one separately, and their number grows very fast with the size of the orchard (126 even for this small one, and about 3 × 10¹⁶ for an open grid of 30 × 30), while the table works out each square once: 30 squares here.
- ii.0.5 and 0.25 are exact in binary (a half and a quarter), so adding them gives exactly 1.0 or 0.75 with no error; only numbers like 0.1 that binary can’t store exactly show the bug.
- iii.It mixes the two languages: 90% of English complaints are found but only 38% of Roman Nepali ones. Owners would think they see two-thirds of all complaints, while most complaints in Roman Nepali are missed. I would collect and label many more Roman Nepali reviews, train and test the model on them, and report its results by language.
Marking scheme
- i. Every route arrives from the north or the west, never both, so the two counts add up to all of them.
- i. The table works out each square once; following routes takes as many steps as there are routes, which grows very fast.
- ii. 0.5 and 0.25 are stored exactly in binary, so those sums have no error.
- iii. The overall recall hides Roman Nepali’s (38%) against English (90%).
- iii. More Roman Nepali reviews to train and test on, results reported by language.
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.
