MATH40006 Practice Paper 3 Solutions
English review edition prepared on 4 October 2026 from a preserved source copy. It is a later presentation, not the historical study interface. Source checks and difficulty judgements describe the original material author's own process; they do not indicate Imperial College London endorsement.
Source SHA-256: be5291580ec4d01f2c8198e6ebe497a78224cef91433679ec96cdfa48bd0f65fSource date: 2026-08-06
Notes — cell 1
# MATH40006 Revised Historical-Coverage Mock 3 - Course-Method Audited Solutions **Time allowed:** 60 minutes **Total marks:** 50 **Role:** 2023-24 historical ability: prime algorithms, recursion and lexicon data The main solutions use methods evidenced in the supplied official papers and mark schemes. No lecture notes or problem sheets were available in this window, so “course-method audited” here means audited against the official answer methods that are actually accessible.
Code — cell 2
import numpy as np
import sympy as sp
import matplotlib.pyplot as plt
import pandas as pd
from time import perf_counter
from copy import copy
import pickle
import ast
import math
sp.init_printing()
def primes_numpy(n):
pos=np.ones(n+1,dtype=bool); pos[:2]=False
for p in range(2,int(np.sqrt(n))+1):
if pos[p]: pos[p*p::p]=False
return np.arange(n+1)[pos]
mini=[{'word':'Alpha','description':'first'}, {'word':'Beta','description':'second'}, {'word':'Delta','description':'change'}, {'word':'Gamma','description':'third'}]
with open('mini_lexicon.dat','w',encoding='utf-8') as f: f.write(repr(mini))
Notes — cell 3
## Question 1 (20 marks) **Recognition signal.** A vectorised sieve is supplied and a second algorithm must grow a Python list in place. **First key step.** In `conditional_append`, stop once `q*q>p`; append only after no divisor has been found. **Course-method chain.** test supplied code -> in-place helper -> odd-candidate driver -> cross-check -> fair timing -> cautious interpretation. **Marking points.** 3+5+3+4+2+2+1. **Common errors.** Returning a new list rather than appending; testing every previous prime; accidentally omitting p when all tested primes are small; drawing complexity conclusions from one timing. **Final self-check.** The two prime lists must be exactly equal for all validation cases. ### Historical-basis and difficulty audit - **Historical formal-question basis:** 2023-24 Assessment 3 Q1(a-f) - **Accessible course-material basis:** 2023-24 official marking scheme - **Historical ability gap filled:** fill Eratosthenes, conditional appending, trial-division construction and timing comparisons - **Equivalent 2026 difficulty unit:** 2026 Q2(20 mark algorithmic-synthesis difficulty unit ) - **Why equivalent:** two algorithms, testing and timing, in a total of 5 stages; reduce discussion at the million-element scale; code and an estimated 22 minutes are comparable .
Code — cell 4
def conditional_append(primes,p):
for q in primes:
if q*q>p: primes.append(p); return
if p%q==0: return
primes.append(p)
def primes_incremental(n):
if n<2:return []
primes=[2]
for p in range(3,n+1,2): conditional_append(primes,p)
return primes
print(primes_numpy(29),primes_numpy(30),primes_numpy(49))
for p in [21,23]:
lis=[2,3,5,7,11,13,17,19]; conditional_append(lis,p); print(p,lis)
for n in [2,3,29,49,200]: print(n,np.array_equal(np.array(primes_incremental(n)),primes_numpy(n)))
for fn in [primes_numpy,primes_incremental]:
t=perf_counter(); ans=fn(100000); print(fn.__name__,perf_counter()-t,len(ans))
[ 2 3 5 7 11 13 17 19 23 29] [ 2 3 5 7 11 13 17 19 23 29] [ 2 3 5 7 11 13 17 19 23 29 31 37 41 43 47] 21 [2, 3, 5, 7, 11, 13, 17, 19] 23 [2, 3, 5, 7, 11, 13, 17, 19, 23] 2 True 3 True 29 True 49 True 200 True primes_numpy 0.0009999969998943925 9592 primes_incremental 0.04289674900007867 9592
Notes — cell 5
## Question 2 (20 marks) **Recognition signal.** The recursive call divides the integer by 3, and the question then asks for a representation-based verification. **First key step.** Write the base case first and ensure the recursive expression uses integer division and remainder. **Course-method chain.** recursive definition -> tests -> plot -> digit conversion/list run transform -> exhaustive verification -> logarithmic depth argument. **Marking points.** 4+2+2+5+4+3. **Common errors.** Using `/` rather than `//`; returning NumPy scalar/float; forgetting the final run in list_crush; claiming Theta(n). **Final self-check.** For every tested n, `s3(n)==sum(ternary_digits(n))`; each recursive call removes one base-3 digit. ### Historical-basis and difficulty audit - **Historical formal-question basis:** 2023-24 Assessment 3 Q2(a-i) - **Accessible course-material basis:** 2023-24 official marking scheme - **Historical ability gap filled:** add recursive branches, base representations, run compression and method transfer - **Equivalent 2026 difficulty unit:** 2026 Q1(20 mark multi-step synthesis difficulty unit ) - **Why equivalent:** abstraction is higher but plotting is lighter; base cases, recursion, inverse tests, run compression and complexity take about 20 minutes .
Code — cell 6
def s3(n): return n if n<3 else s3(n//3)+n%3
def ternary_digits(n):
out=[]
while n: out.append(n%3); n//=3
return out[::-1] or [0]
def list_crush(lis):
if not lis:return []
out=[lis[0]]
for x in lis[1:]:
if x!=out[-1]: out.append(x)
return out
print([(n,s3(n)) for n in [1,8,2026,9999]])
vals=np.arange(1,501); plt.figure(); plt.plot(vals,[s3(int(n)) for n in vals],'.',markersize=2); plt.show()
print(all(s3(n)==sum(ternary_digits(n)) for n in range(1,501)))
print(list_crush([1,1,0,0,1,1,1,2,2,0]))
[(1, 1), (8, 4), (2026, 6), (9999, 7)]
<Figure size 640x480 with 1 Axes>
True [1, 0, 1, 2, 0]
Notes — cell 7
## Question 3 (10 marks) **Recognition signal.** The file contains a Python literal list of dictionaries, followed by three alternative lookup representations. **First key step.** Read text using a `with` block, then parse with `ast.literal_eval` rather than `eval`. **Course-method chain.** file read -> safe parse -> tuple projection -> sequential search -> dict/Series conversion -> hit/miss verification. **Marking points.** 2+1+3+2+2. **Common errors.** Opening in binary mode; unsafe eval; raising an exception instead of returning failed in the sequential function; confusing Series index and values. **Final self-check.** The value for Delta must be `change` in all three representations. ### Historical-basis and difficulty audit - **Historical formal-question basis:** 2023-24 Assessment 3 Q3(i-a to ii-e) - **Accessible course-material basis:** 2023-24 official marking scheme - **Historical ability gap filled:** add file reading, sequential search, dictionaries and Series; take the complete 35 mark formal question compressed into a short bridge exercise - **Equivalent 2026 difficulty unit:** 2026 Q3(10 mark short-data difficulty unit ) - **Why equivalent:** select only the essential input, two containers, successful/ failed-lookup tests, estimated 9-10 minutes .
Code — cell 8
with open('mini_lexicon.dat','r',encoding='utf-8') as f: data=ast.literal_eval(f.read())
tuples=[(d['word'],d['description']) for d in data]
def get_value(lis,key):
for k,v in lis:
if k==key:return v
return 'failed'
dic=dict(tuples); ser=pd.Series(dic)
print(tuples); print(get_value(tuples,'Delta'),dic['Delta'],ser['Delta'])
print(get_value(tuples,'Missing'),dic.get('Missing','failed'),ser.get('Missing','failed'))
[('Alpha', 'first'), ('Beta', 'second'), ('Delta', 'change'), ('Gamma', 'third')]
change change change
failed failed failed