0. Exam Environment
| Item | Detail |
|---|---|
| Date | Sat 14 Feb, 10:30am – 12:30pm |
| Venue | LT19 (COM2 Level 1) |
| Format | 4 questions, 5 marks each, 2 hours |
| Environment | SEB (no internet, no syntax help, can run code) |
| Test cases | Public visible, private hidden |
| Hardcopy | Allowed (unlimited) |
| Seat | 71 |
Allowed Modules
1import datetime
2import functools # reduce, partial, lru_cache
3import itertools # combinations, permutations, chain, product
4import math # ceil, floor, sqrt, log, gcd
5import numpy as np # full numpy
6import random # randint, choice, shuffle
7import re # findall, match, search, sub
from collections import ...is NOT ALLOWED! No defaultdict, Counter, deque, namedtuple.
defaultdict Replacements
1# ❌ BANNED: defaultdict(int)
2counts[key] += 1 # KeyError if key doesn't exist
3
4# ✅ Regular dict counting
5counts[key] = counts.get(key, 0) + 1
6
7# ❌ BANNED: defaultdict(list)
8groups[key].append(val)
9
10# ✅ setdefault
11groups.setdefault(key, []).append(val)
12
13# ❌ BANNED: defaultdict(set)
14groups[key].add(val)
15
16# ✅ setdefault with set
17groups.setdefault(key, set()).add(val)
1. Built-in Functions
| Function | What It Does | Example |
|---|---|---|
len(x) | Length | len([1,2,3]) → 3 |
sum(x) | Sum | sum([1,2,3]) → 6 |
min(x) / max(x) | Min/max | max([3,1,2]) → 3 |
abs(x) | Absolute value | abs(-5) → 5 |
round(x, n) | Round to n decimals | round(3.14, 1) → 3.1 |
range(a,b,s) | Integer sequence | list(range(0,10,2)) → [0,2,4,6,8] |
enumerate(x, start) | Index + value | list(enumerate('ab',1)) → [(1,‘a’),(2,‘b’)] |
zip(a, b) | Pair up elements | list(zip([1,2],['a','b'])) → [(1,‘a’),(2,‘b’)] |
sorted(x, key=, reverse=) | New sorted list | sorted([3,1,2]) → [1,2,3] |
any(x) / all(x) | Any/all truthy | any([0,1,0]) → True |
isinstance(x, type) | Type check | isinstance(5, int) → True |
map(fn, iter) | Apply fn to each | list(map(str, [1,2])) → [‘1’,‘2’] |
filter(fn, iter) | Keep if fn true | list(filter(lambda x:x>0, [-1,2])) → [2] |
TRAP:
round()uses banker’s rounding (nearest even).round(2.5)→ 2! Usemath.ceil()for “always round up”.
TRAP:
map(fn, iter)has NOkey=parameter. That’ssorted(). Don’t mix them up.
2. String Methods
| Method | What | Example |
|---|---|---|
s.lower() / s.upper() | Case conversion | "Hi".lower() → "hi" |
s.strip() | Remove whitespace | " hi ".strip() → "hi" |
s.split(sep) | Split to list | "a,b".split(",") → ['a','b'] |
sep.join(lst) | Join list to string | ",".join(['a','b']) → "a,b" |
s.replace(old, new) | Replace substring | "hello".replace("l","r") → "herro" |
s.find(sub) | Index or -1 | "hello".find("ll") → 2 |
s.startswith(p) / s.endswith(p) | Prefix/suffix check | "hello".startswith("he") → True |
s.isdigit() / s.isalpha() | All digits/letters? | "123".isdigit() → True |
s.count(sub) | Count occurrences | "hello".count("l") → 2 |
ord(c) / chr(n) | Char ↔ ASCII | ord('A') → 65, chr(65) → ‘A’ |
f"{x:.2f}" | Format float | f"{3.14159:.2f}" → "3.14" |
String is IMMUTABLE — methods return new strings, never modify in place.
Empty string edge case: "".split(" ") → [""] (NOT []!)
3. List Methods
| Method | What | In-Place? | Returns |
|---|---|---|---|
lst.append(x) | Add to end | Yes | None |
lst.extend(iter) | Add all items | Yes | None |
lst.insert(i, x) | Insert at index | Yes | None |
lst.pop(i) | Remove & return at i | Yes | The item |
lst.remove(x) | Remove first x | Yes | None |
lst.sort(key=, reverse=) | Sort in place | Yes | None |
lst.reverse() | Reverse in place | Yes | None |
lst.index(x) | Find index of x | No | int |
lst.count(x) | Count occurrences | No | int |
lst.copy() or lst[:] | Shallow copy | No | New list |
sorted()vs.sort(): “sortED = nEw Data” —sorted()returns new list,.sort()modifies in place and returns None.
4. Dictionary Methods
| Method | What | Example |
|---|---|---|
d[key] | Get value (KeyError if missing) | d['name'] |
d.get(key, default) | Get or default (no error) | d.get('age', 0) |
d.setdefault(key, default) | Get or set+return default | d.setdefault('age', 0) |
d.keys() / d.values() | All keys / values | list(d.keys()) |
d.items() | (key, value) pairs | for k, v in d.items() |
d.update(other) | Merge other dict | d.update({'a': 1}) |
d.pop(key) | Remove & return value | d.pop('name') |
key in d | Key exists? | 'name' in d |
Dict Counting Pattern (No collections!)
1counts = {}
2for item in items:
3 counts[item] = counts.get(item, 0) + 1
Dict Grouping Pattern (No defaultdict!)
1groups = {}
2for item in items:
3 key = item[0] # or whatever grouping logic
4 groups.setdefault(key, []).append(item)
5. Set Operations
| Operation | Operator | Method | Result |
|---|---|---|---|
| Intersection | a & b | a.intersection(b) | In BOTH |
| Union | a | b | a.union(b) | In EITHER |
| Difference | a - b | a.difference(b) | In a, NOT in b |
| Symmetric Diff | a ^ b | a.symmetric_difference(b) | In ONE, not both |
| Subset | a <= b | a.issubset(b) | All of a in b? |
| Superset | a >= b | a.issuperset(b) | All of b in a? |
1a = {1, 2, 3, 4}
2b = {3, 4, 5, 6}
3a & b # {3, 4} — common
4a | b # {1, 2, 3, 4, 5, 6} — all
5a - b # {1, 2} — only in a
6a ^ b # {1, 2, 5, 6} — not shared
Set from list: set([1,2,2,3]) → {1, 2, 3} (deduplicates)
frozenset: Immutable set, can be dict key. frozenset({1, 2})
6. Lambda + Sorting Patterns
1# Lambda = anonymous function
2lambda x: x[1] # get index 1
3lambda x: -x[1] # sort descending
4lambda x: (x[0], -x[1]) # multi-key: first asc, second desc
5
6# ⚠️ sorted uses key=, map does NOT
7sorted(data, key=lambda x: -x[1]) # ✅
8map(lambda x: x**2, data) # ✅ NO key=
Dense Ranking (1-2-2-3)
1sorted_rows = sorted(rows, key=lambda x: (-x[1], -x[2], x[0]))
2rank, prev = 1, None
3result = []
4for row in sorted_rows:
5 curr = (row[1], row[2])
6 if prev is not None and prev != curr:
7 rank += 1
8 result.append((rank, *row))
9 prev = curr
7. Iteration Patterns
enumerate — index + value
1for i, val in enumerate(lst): # i=0,1,2...
2for i, val in enumerate(lst, start=1): # i=1,2,3...
zip — pair up lists
1for a, b in zip(list1, list2): # stops at SHORTER list
2 print(a, b)
3
4# Unzip:
5pairs = [(1,'a'), (2,'b')]
6nums, letters = zip(*pairs) # (1,2), ('a','b')
7
8# Element-wise sum:
9[x + y for x, y in zip(a, b)]
Comprehensions
1# List
2[x**2 for x in range(5)] # [0, 1, 4, 9, 16]
3[x for x in data if x > 0] # filter
4[x**2 for x in data if x % 2 == 0] # filter + transform
5
6# Dict
7{k: v for k, v in pairs}
8{word: len(word) for word in words}
9
10# Set
11{x for x in data if x > 0}
12
13# Nested (2D matrix)
14[[i*3+j for j in range(3)] for i in range(3)]
Functional (map/filter — no loops)
1filter(lambda x: abs(x) > threshold, changes) # keep matching
2map(lambda x: x**2, values) # transform each
3sum(map(lambda x: x**2, filter(lambda x: x > 0, data))) # chain
4
5# ⚠️ map() and filter() return iterators, wrap in list() to see contents
6list(map(lambda x: x*2, [1,2,3])) # [2, 4, 6]
8. functools Module
1from functools import reduce
reduce — fold list to single value
1from functools import reduce
2
3# Sum (same as sum())
4reduce(lambda acc, x: acc + x, [1, 2, 3, 4]) # 10
5
6# Product
7reduce(lambda acc, x: acc * x, [1, 2, 3, 4]) # 24
8
9# Max (same as max())
10reduce(lambda acc, x: acc if acc > x else x, [3, 1, 4, 1, 5]) # 5
11
12# With initial value
13reduce(lambda acc, x: acc + x, [1, 2, 3], 100) # 106
How reduce works:
reduce(fn, [a, b, c, d])=fn(fn(fn(a, b), c), d)— left fold.
Other functools
1from functools import partial, lru_cache
2
3# partial — pre-fill some arguments
4add10 = partial(lambda x, y: x + y, 10)
5add10(5) # 15
6
7# lru_cache — memoization (cache results)
8@lru_cache(maxsize=None)
9def fib(n):
10 if n < 2: return n
11 return fib(n-1) + fib(n-2)
9. itertools Module
1import itertools
Most Useful Functions
1from itertools import combinations, permutations, chain, product, accumulate
2
3# combinations — choose k from n (order doesn't matter)
4list(combinations([1,2,3], 2))
5# [(1,2), (1,3), (2,3)]
6
7# permutations — arrange k from n (order matters)
8list(permutations([1,2,3], 2))
9# [(1,2), (1,3), (2,1), (2,3), (3,1), (3,2)]
10
11# chain — flatten multiple iterables into one
12list(chain([1,2], [3,4], [5]))
13# [1, 2, 3, 4, 5]
14
15# product — cartesian product (all combinations)
16list(product([1,2], ['a','b']))
17# [(1,'a'), (1,'b'), (2,'a'), (2,'b')]
18
19# accumulate — running totals (like prefix sum)
20list(accumulate([1, 2, 3, 4]))
21# [1, 3, 6, 10]
22
23# accumulate with custom function
24list(accumulate([3, 1, 4, 1, 5], max))
25# [3, 3, 4, 4, 5] — running max!
Quick Reference Table
| Function | What | Ordered? | Example |
|---|---|---|---|
combinations(iter, k) | Choose k items | No (sorted) | C(n,k) combos |
permutations(iter, k) | Arrange k items | Yes | P(n,k) arrangements |
product(A, B) | All pairs | Yes | Cartesian product |
chain(A, B) | Concat iterables | N/A | Flatten |
accumulate(iter, fn) | Running fold | N/A | Prefix sum/max |
10. Common Coding Patterns
Two-Pointer Merge
1def merge(l1, l2, comp=lambda x, y: x - y):
2 i, j, out = 0, 0, []
3 while i < len(l1) and j < len(l2):
4 if comp(l1[i], l2[j]) <= 0:
5 out.append(l1[i]); i += 1
6 else:
7 out.append(l2[j]); j += 1
8 out.extend(l1[i:])
9 out.extend(l2[j:])
10 return out
Sliding Window
1L, R = 0, window - 1
2while R < len(data):
3 window_slice = data[L:R+1]
4 # process window
5 L += 1; R += 1
Track-Seen (Find Duplicates)
1seen = set()
2added = set()
3duplicates = []
4for x in lst:
5 if x in seen:
6 if x not in added:
7 duplicates.append(x)
8 added.add(x)
9 else:
10 seen.add(x)
Dict Counting (Word Frequency)
1counts = {}
2for word in text.lower().split():
3 counts[word] = counts.get(word, 0) + 1
Group by Key (No defaultdict!)
1groups = {}
2for i, (x, y) in enumerate(points):
3 groups.setdefault(('row', y), []).append((i, x))
11. NumPy Quick Reference
1import numpy as np
Array Creation
1np.array([1, 2, 3]) # from list
2np.zeros((3, 4)) # 3x4 of zeros
3np.ones((2, 3)) # 2x3 of ones
4np.arange(0, 10, 2) # [0, 2, 4, 6, 8]
5np.linspace(0, 1, 5) # [0, 0.25, 0.5, 0.75, 1.0]
6data.reshape(-1, 4) # reshape, -1 = auto
Indexing & Slicing
1a[1:-1] # exclude first and last
2a[:, np.newaxis] # add dimension (for broadcasting)
3a[np.newaxis, :] # add dimension at front
4a[a > 5] # boolean indexing (filter)
Broadcasting Pattern (Pairwise Operations)
1# Make (n,1,d) and (1,n,d) for pairwise comparison
2R = a[:, np.newaxis, :] # shape (n, 1, d)
3C = a[np.newaxis, :, :] # shape (1, n, d)
4diff = R - C # shape (n, n, d) pairwise differences
Common Operations
1np.abs(x) # element-wise absolute value
2np.square(x) # element-wise square
3np.sqrt(x) # element-wise square root
4np.sum(x, axis=0) # sum along axis (0=rows, 1=cols, 2=depth)
5np.mean(x, axis=0) # mean along axis
6np.dot(a, b.T) # matrix multiplication
7np.linalg.norm(a, axis=1) # vector norms (per row)
8np.outer(a, b) # outer product
9np.where(condition) # indices where True
10np.argsort(a) # indices that would sort the array
11np.argmax(a) # index of max value
“For Loop → NumPy” Conversion Patterns
1# ❌ For loop (slow)
2result = []
3for x in data:
4 if x > threshold:
5 result.append(x ** 2)
6
7# ✅ NumPy (fast, vectorized)
8a = np.array(data)
9mask = a > threshold
10result = a[mask] ** 2
11
12# ❌ For loop (pairwise distances)
13for i in range(n):
14 for j in range(n):
15 dist[i][j] = sum(abs(a[i] - a[j]))
16
17# ✅ NumPy (broadcasting)
18diff = a[:, np.newaxis, :] - a[np.newaxis, :, :]
19dist = np.sum(np.abs(diff), axis=2)
Distance Matrices
1# Setup: a is (n, d) array
2R = a[:, np.newaxis, :]
3C = a[np.newaxis, :, :]
4D = R - C
5
6# Manhattan: sum of |differences|
7manhattan = np.sum(np.abs(D), axis=2)
8
9# Euclidean: sqrt(sum of squared differences)
10euclidean = np.sqrt(np.sum(np.square(D), axis=2))
11
12# Cosine: 1 - (dot / product of norms)
13dot_products = np.dot(a, a.T)
14norms = np.linalg.norm(a, axis=1)
15cosine_dist = 1 - dot_products / np.outer(norms, norms)
Local Peaks (Vectorized)
1x = np.array(values)
2lefts = x[:-2] # all except last 2
3currs = x[1:-1] # middle elements
4rights = x[2:] # all except first 2
5scale = 1 + threshold
6peaks = np.where((currs > lefts * scale) & (currs > rights * scale))[0] + 1
12. Regex Quick Reference
1import re
Core Functions
1re.findall(pattern, text) # all matches as list of strings
2re.match(pattern, text) # match at START only → match object or None
3re.search(pattern, text) # first match ANYWHERE → match object or None
4re.sub(pattern, repl, text) # replace all matches
5
6match.group(0) # full match
7match.group(1) # first capture group (first parentheses)
Pattern Syntax
| Pattern | Meaning | Example |
|---|---|---|
. | Any character (except newline) | a.c matches “abc” |
[A-Z] | One uppercase letter | [A-Z]{3} = 3 uppercase |
[^A-Z] | NOT uppercase | Negated set |
[0-9] or \d | One digit | \d{4} = 4 digits |
\w | Word char (letter/digit/_) | \w+ = one or more word chars |
\s | Whitespace | \s+ = one or more spaces |
{2,3} | 2 to 3 repetitions | [A-Z]{2,3} = 2 or 3 uppercase |
+ | 1 or more | \d+ = one or more digits |
* | 0 or more | \d* = zero or more digits |
? | 0 or 1 (optional) | colou?r matches “color” and “colour” |
^ / $ | Start / end of string | ^Hello = starts with Hello |
(...) | Capture group | (\d+) captures digits |
(?:...) | Non-capturing group | Group without capturing |
(?=...) | Lookahead | Assert what follows |
(?!...) | Negative lookahead | Assert what does NOT follow |
(?<=...) | Lookbehind | Assert what precedes |
(?<!...) | Negative lookbehind | Assert what does NOT precede |
Common Patterns
1# Extract all numbers
2re.findall(r'\d+', "abc 123 def 456") # ['123', '456']
3
4# Validate email-like pattern
5re.match(r'^[\w.]+@[\w.]+\.\w+$', "user@mail.com")
6
7# Split on multiple delimiters
8re.split(r'[,;\s]+', "a, b; c d") # ['a', 'b', 'c', 'd']
9
10# Replace with function
11re.sub(r'\d+', lambda m: str(int(m.group()) * 2), "a1b2c3") # "a2b4c6"
12
13# Overlapping matches (lookahead trick)
14re.findall(r'(?=([A-Z]{2}\d+))', text)
13. Python Traps (Common Exam Tricks)
Mutable Default Arguments
1# ❌ TRAP: same list reused across calls
2def add(item, lst=[]):
3 lst.append(item)
4 return lst
5add(1) # [1]
6add(2) # [1, 2] ← NOT [2]!
7
8# ✅ FIX:
9def add(item, lst=None):
10 if lst is None:
11 lst = []
12 lst.append(item)
13 return lst
List Aliasing
1a = [1, 2, 3]
2b = a # SAME object! b IS a
3b.append(4)
4print(a) # [1, 2, 3, 4] ← a changed too!
5
6# FIX: b = a[:] or b = a.copy() or b = list(a)
Negative Indexing
1lst = [10, 20, 30]
2lst[-1] # 30 (last element) — NOT an error!
3# Always check index BEFORE accessing:
4if pointer == -1:
5 return None
6val = lst[pointer]
Scope
1x = 10
2def foo():
3 x = 5 # local x, global unchanged
4foo()
5print(x) # 10
6
7# To modify global: use `global x` inside function
is vs ==
1a = [1, 2]
2b = [1, 2]
3a == b # True (same content)
4a is b # False (different objects)
List Multiplication with Mutables
1# ❌ TRAP:
2matrix = [[]] * 3 # 3 refs to SAME list
3matrix[0].append(1) # ALL change: [[1],[1],[1]]
4
5# ✅ FIX:
6matrix = [[] for _ in range(3)] # 3 SEPARATE lists
Loop Variable Scope Leaking
1for i in range(5):
2 pass
3print(i) # 4 ← i survives after the loop! (NOT block-scoped)
Mutation During Iteration
1# ❌ TRAP: removing while iterating
2lst = [1, 2, 3, 4, 5]
3for x in lst:
4 if x % 2 == 0:
5 lst.remove(x) # Skips elements!
6
7# ✅ FIX: iterate over copy, or use comprehension
8lst = [x for x in lst if x % 2 != 0]
14. math Module
1import math
| Function | What | Example |
|---|---|---|
math.ceil(x) | Round UP | math.ceil(2.1) → 3 |
math.floor(x) | Round DOWN | math.floor(2.9) → 2 |
math.sqrt(x) | Square root | math.sqrt(16) → 4.0 |
math.log(x) | Natural log | math.log(math.e) → 1.0 |
math.log10(x) | Log base 10 | math.log10(100) → 2.0 |
math.gcd(a, b) | Greatest common divisor | math.gcd(12, 8) → 4 |
math.factorial(n) | n! | math.factorial(5) → 120 |
math.pi | 3.14159… | |
math.e | 2.71828… | |
math.inf | Infinity | Good for initial min/max |
No import alternative:
x ** 0.5=math.sqrt(x),float('inf')=math.inf
15. Exam Strategy
4 questions, 30 min each:
- Read ALL questions first (5 min) — rank by difficulty, easiest first
- Per question (~25 min):
- Read spec carefully (3 min) — underline edge cases, constraints
- Plan approach (2 min) — what data structure, what pattern
- Implement (15 min) — happy path first
- Test & edge cases (5 min) — run public tests, then self-test edges
- Reserve last 10 min — re-read specs, check hidden edge cases
Edge Cases Checklist (Self-Test!)
Since private test cases are hidden, always self-test:
- Empty input (
[],"",{}) - Single element
- All same / all unique
- Negative numbers / zero
- Already sorted / reverse sorted
- Boundary (first, last element)
- Very large input (will your O(n²) TLE?)
If Stuck
- Move to next question, come back with fresh eyes
- Write pseudocode comments even if you can’t code it (logic credit)
print()debug to understand what’s happening
Source
Session: 2026-02-12 session notes
Practice files: 000_mods/TCX1002/midterm_prep/p01-p12
Tutorial reviews: T2 REVIEW.md
Date learned: 2026-02-12
Context: Midterm prep — coverage matrix analysis, gap drilling, exam environment discovery
Last updated: 2026-02-12