How This Works
3 minEight problems. For each:
- Read the spec.
- Decide which lesson's skill applies — the tag tells you.
- Write the solution. Don't peek at the hint until you've given it a real try.
- Time yourself if you want. Target: 6 minutes per problem.
Solve at least 5 to feel ready for the capstone. All 8 = you're flying.
Most real problems are 5-minute combinations of skills you already have. Olympic-style practice = small problems, fast iterations.
Problem 1 · Sum of Digits [PY-L2-02]
5 minGiven an integer, return the sum of its digits. Use a list comprehension.
assert digit_sum(1234) == 10 assert digit_sum(0) == 0 assert digit_sum(99999) == 45
Hint
def digit_sum(n): return sum(int(ch) for ch in str(abs(n)))
Problem 2 · Word Frequency [PY-L2-06]
7 minGiven a paragraph of text, return a dict mapping each lower-cased word to how many times it appears. Ignore punctuation.
word_freq("Hello, hello, world.") # → {"hello": 2, "world": 1}
Hint
import re def word_freq(text): words = re.findall(r"[a-z]+", text.lower()) counts = {} for w in words: counts[w] = counts.get(w, 0) + 1 return counts
The lower-case-then-findall pipeline strips punctuation for free.
Problem 3 · Group by First Letter [PY-L2-12]
5 minGiven a list of names, group them by first letter into a dict-of-lists.
group_by_first(["Anna", "Bob", "Alice", "Carl", "Andy"]) # → {"A": ["Anna", "Alice", "Andy"], "B": ["Bob"], "C": ["Carl"]}
Hint
def group_by_first(names): out = {} for n in names: out.setdefault(n[0], []).append(n) return out
setdefault(key, default) returns the existing value or installs the default and returns that — perfect for "append into a list per key".
Problem 4 · Anagram? [PY-L2-09]
5 minTwo words are anagrams if they have the same letters. Ignore case and spaces.
assert anagram("Listen", "Silent") assert anagram("Astronomer", "Moon starer") assert not anagram("hello", "world")
Hint
def anagram(a, b): def normalise(s): return sorted(c for c in s.lower() if c.isalpha()) return normalise(a) == normalise(b)
Sort the letters of each word. Equal sorted lists = anagrams. A set approach almost works but fails on repeats — "aab" vs "abb" would tie incorrectly with sets.
Problem 5 · Read & Sum a File [PY-L2-20]
5 minGiven a path to a file containing one number per line (with blanks and non-number lines mixed in), return the sum of the valid numbers. Handle missing file by returning 0.
Hint
def file_sum(path): total = 0 try: with open(path, encoding="utf-8") as f: for line in f: try: total += float(line.strip()) except ValueError: pass except FileNotFoundError: pass return total
Two layers of try/except. Outer catches missing file; inner catches bad lines. Returns 0 in either failure mode.
Problem 6 · Extract Phone Numbers [PY-L2-42]
6 minGiven a text, return a list of every Malaysian phone number. Use regex.
find_phones("Call 012-3456789 or +60 12-1234567 not 123 or 12-3456") # → ["012-3456789", "+60 12-1234567"]
Hint
import re def find_phones(text): return re.findall(r"\+?60[\s\-]?\d{2}-?\d{7,8}|\d{2,3}-\d{7,8}", text)
An alternation: international format (with optional +) or local format. Test against the examples and tweak.
Problem 7 · JSON Stats [PY-L2-45]
8 minGiven students.json with this shape:
[ {"name": "Aisyah", "scores": [82, 91, 76]}, {"name": "Wei Jie", "scores": [74, 68, 91]}, {"name": "Priya", "scores": [95, 97, 96]} ]
Write a function that loads this file and returns a list of dicts with name, average (1dp). Sort by average descending.
Hint
import json def student_averages(path): with open(path, encoding="utf-8") as f: students = json.load(f) out = [] for s in students: avg = round(sum(s["scores"]) / len(s["scores"]), 1) out.append({"name": s["name"], "average": avg}) out.sort(key=lambda x: x["average"], reverse=True) return out
Problem 8 · Safe Average [PY-L2-27]
5 minWrite safe_average(numbers) that returns the average of a list — and the string "no data" if the list is empty. Don't use if. Use try/except.
Hint
def safe_average(numbers): try: return sum(numbers) / len(numbers) except ZeroDivisionError: return "no data"
Empty-list average raises ZeroDivisionError because len returns 0. Catch it. EAFP in action.
Putting It All Together · A Test Runner
8 minBuild olympics.py with all eight functions, plus a hand-rolled test runner that confirms each one passes a few assertions. The pattern is the same as PY-L2-24's feedback tests and PY-L2-44's validator tests.
Show one possible solution (skeleton)
# olympics.py — eight functions + tests import re, json def digit_sum(n): return sum(int(ch) for ch in str(abs(n))) def word_freq(text): words = re.findall(r"[a-z]+", text.lower()) out = {} for w in words: out[w] = out.get(w, 0) + 1 return out def group_by_first(names): out = {} for n in names: out.setdefault(n[0], []).append(n) return out def anagram(a, b): norm = lambda s: sorted(c for c in s.lower() if c.isalpha()) return norm(a) == norm(b) def file_sum(path): total = 0 try: with open(path, encoding="utf-8") as f: for line in f: try: total += float(line.strip()) except ValueError: pass except FileNotFoundError: pass return total def find_phones(text): return re.findall(r"\+?60[\s\-]?\d{2}-?\d{7,8}|\d{2,3}-\d{7,8}", text) def safe_average(numbers): try: return sum(numbers) / len(numbers) except ZeroDivisionError: return "no data" # Tests passed = 0 total = 0 def check(name, condition): global passed, total total += 1 if condition: print(f" ✓ {name}") passed += 1 else: print(f" ✗ {name}") check("digit_sum(1234) == 10", digit_sum(1234) == 10) check("digit_sum(0) == 0", digit_sum(0) == 0) check("word_freq counts repeats", word_freq("Hi hi HI!")["hi"] == 3) check("group_by_first basic", group_by_first(["Anna", "Bob"]) == {"A": ["Anna"], "B": ["Bob"]}) check("anagram listen/silent", anagram("Listen", "Silent")) check("anagram hello/world false", not anagram("hello", "world")) check("safe_average empty", safe_average([]) == "no data") check("safe_average normal", safe_average([1, 2, 3]) == 2.0) print(f"\n{passed}/{total} passing.")
Non-negotiables: every function defined, every function tested with at least 2 assertions, a passing-count summary. This is your "done with Level 2 concepts" certificate to yourself.
Recap
3 minEight tiny problems, eight different L2 skills. Comprehensions, dicts, group-by, sets via sorted comparison, file safety, regex, JSON, try/except. Real software is sequences of small wins like these — five-minute functions composed into bigger features. Whichever problems felt slow are the ones to revisit; whichever felt fast are the ones to trust.
Tomorrow: the capstone. A polished CLI app that uses regex, JSON and files together. Bring everything.
Homework
4 minFinish any problems you skipped in class. Aim for all 8. Bring your olympics.py with full passing tests to the capstone lesson.
Stretch. Write two extra olympics problems of your own. They should each test a Level-2 skill. Add them to your test runner.