Level 4 — ApplicationIntroduction to Programming (Python)

Introduction to Programming (Python)

60 minutes50 marksprintable — key stays hidden on paper

Level 4 — Application (novel problems, no hints) Time limit: 60 minutes Total marks: 50

Answer all questions. Show working / trace tables where asked. Assume Python 3.x. Predict output exactly as Python would print it.


Question 1 — Trace & Predict (10 marks)

For each snippet, state the exact printed output. If it raises an error, name the exception type and briefly say why.

(a) (3 marks)

s = "abcdefgh"
print(s[1:6:2] + s[::-1][0])

(b) (3 marks)

x = [1, 2, 3]
y = x
y += [4]
print(x, y is x)

(c) (4 marks)

def f(a, b=[]):
    b.append(a)
    return b
 
print(f(1))
print(f(2))
print(f(3, []))

Question 2 — Build a function (12 marks)

Write a function word_frequency(text) that:

  • takes a string text,
  • splits it into words on whitespace,
  • normalises each word to lowercase and strips surrounding punctuation . , ! ?,
  • returns a dictionary mapping each distinct word → its count.

Then, using a dictionary/list comprehension or built-ins, add a second function top_word(freq) that returns the word with the highest count (any one on a tie).

(a) Write word_frequency. (7 marks) (b) Write top_word. (3 marks) (c) State the returned dict for word_frequency("Hi, hi! Bye.") and the result of top_word on it. (2 marks)


Question 3 — Loops, logic & bitwise (10 marks)

(a) Using a for loop and range(), write code that prints all integers from 1 to 50 that are divisible by 3 but not by 5, one per line. (4 marks)

(b) Given integer n, write a single boolean expression (no loops) that is True exactly when n is a power of two and positive (e.g. 1, 2, 4, 8...). Use bitwise operators. Explain in one sentence why it works. (4 marks)

(c) What does sum(1 for c in "Mississippi" if c in "sp") evaluate to? (2 marks)


Question 4 — Recursion (10 marks)

A "digital root" repeatedly sums the digits of a positive integer until a single digit remains (e.g. 9875 → 29 → 11 → 2).

(a) Write a recursive function digital_root(n) with a clear base case and recursive case. (6 marks)

(b) Trace the sequence of digital_root calls (arguments only) for digital_root(9875). (2 marks)

(c) For very large n given as a huge integer, could deep recursion here hit Python's recursion depth limit? Justify. (2 marks)


Question 5 — Data structures & comprehensions (8 marks)

You are given:

students = [
    {"name": "Ana", "scores": [80, 90]},
    {"name": "Ben", "scores": [50, 60, 70]},
    {"name": "Cy",  "scores": [100]},
]

(a) Using a list comprehension, produce a list of (name, average) tuples. (4 marks)

(b) Using a dict comprehension, build {name: passed} where passed is True if average ≥ 65. (2 marks)

(c) Write one expression using a built-in that returns the name of the student with the highest average. (2 marks)

Answer keyMark scheme & solutions

Question 1 (10 marks)

(a) s[1:6:2] → indices 1,3,5 → "bdf". s[::-1] = "hgfedcba", [0] = "h". Result: bdfh.

  • Slice correct (2), concatenation (1). (3 marks)

(b) y = x aliases the same list. y += [4] is in-place extend (mutates the shared list). So x becomes [1, 2, 3, 4] and y is x is True. Output: [1, 2, 3, 4] True

  • List value (2), is result with reasoning (1). (3 marks)

(c) Mutable default argument b=[] is created once and shared.

  • f(1)[1]
  • f(2)[1, 2] (same list persists)
  • f(3, [])[3] (fresh list passed explicitly) Output:
[1]
[1, 2]
[3]
  • Each line 1 mark + reasoning 1. (4 marks)

Question 2 (12 marks)

(a) (7 marks)

def word_frequency(text):
    freq = {}
    for word in text.split():
        w = word.lower().strip(".,!?")
        if w:
            freq[w] = freq.get(w, 0) + 1
    return freq
  • split() on whitespace (1), lowercase (1), strip punctuation via strip(".,!?") (2), counting with get/default (2), returns dict (1).

(b) (3 marks)

def top_word(freq):
    return max(freq, key=freq.get)
  • max over keys (1), key=freq.get comparing counts (2). Accept a loop-based equivalent.

(c) (2 marks) word_frequency("Hi, hi! Bye."){'hi': 2, 'bye': 1} (1). top_word'hi' (1).


Question 3 (10 marks)

(a) (4 marks)

for n in range(1, 51):
    if n % 3 == 0 and n % 5 != 0:
        print(n)
  • Correct range 1–50 (1), divisible-by-3 test (1), not-divisible-by-5 (1), print per line (1). Values printed: 3,6,9,12,18,21,24,27,33,36,39,42,48 (multiples of 3 up to 50 excluding 15,30,45).

(b) (4 marks)

n > 0 and (n & (n - 1)) == 0
  • Expression correct (3). Explanation (1): a power of two has exactly one set bit; subtracting 1 flips that bit and sets all lower bits, so ANDing gives 0; the n > 0 guard excludes 0 and negatives.

(c) (2 marks) "Mississippi" contains s×4 and p×2 → characters in "sp" count = 6.


Question 4 (10 marks)

(a) (6 marks)

def digital_root(n):
    if n < 10:              # base case: single digit
        return n
    return digital_root(sum(int(d) for d in str(n)))  # recursive case
  • Base case n < 10 (2), digit-sum step (2), recursive call on the sum (2). Accept arithmetic digit-sum via %///.

(b) (2 marks) digital_root(9875) → digital_root(29) → digital_root(11) → digital_root(2) → 2 (9+8+7+5=29, 2+9=11, 1+1=2). Correct chain (2).

(c) (2 marks) No (practically). Each recursion roughly reduces the number to its digit sum, which shrinks it dramatically (a d-digit number's digit sum ≤ 9d). The recursion depth grows only about logarithmically in the number of digits, so it stays tiny (a handful of calls) even for astronomically large inputs — nowhere near the ~1000 default limit. (Reasoning 2.)


Question 5 (8 marks)

(a) (4 marks)

[(s["name"], sum(s["scores"]) / len(s["scores"])) for s in students]

[('Ana', 85.0), ('Ben', 60.0), ('Cy', 100.0)]

  • Comprehension structure (2), average computed correctly (2).

(b) (2 marks)

{s["name"]: (sum(s["scores"]) / len(s["scores"])) >= 65 for s in students}

{'Ana': True, 'Ben': False, 'Cy': True}

(c) (2 marks)

max(students, key=lambda s: sum(s["scores"]) / len(s["scores"]))["name"]

'Cy'


[
  {"claim": "Q1a slice+reverse gives 'bdfh'", "code": "s='abcdefgh'; result = (s[1:6:2] + s[::-1][0]) == 'bdfh'"},
  {"claim": "Q3c count of s/p chars in Mississippi is 6", "code": "result = sum(1 for c in 'Mississippi' if c in 'sp') == 6"},
  {"claim": "Q4 digital_root(9875)==2", "code": "\ndef dr(n):\n    return n if n<10 else dr(sum(int(d) for d in str(n)))\nresult = dr(9875) == 2"},
  {"claim": "Q5a averages correct", "code": "\nstudents=[{'name':'Ana','scores':[80,90]},{'name':'Ben','scores':[50,60,70]},{'name':'Cy','scores':[100]}]\nout=[(s['name'],sum(s['scores'])/len(s['scores'])) for s in students]\nresult = out == [('Ana',85.0),('Ben',60.0),('Cy',100.0)]"},
  {"claim": "Q3b power-of-two test correct for 1,2,4,8 True and 0,3,6 False", "code": "\nf=lambda n: n>0 and (n & (n-1))==0\nresult = all(f(x) for x in [1,2,4,8,1024]) and not any(f(x) for x in [0,3,6,-2])"}
]