Exercises 1-4 run in a loose script (
structures_lab.py); 5-6 go inside your TicketFlow. Do not look at solutions.md before submitting.
Setup:
cd ~/dev/ticketflow && source .venv/bin/activateExercise 1 — The list vs set test
- Build a list of 100,000 IDs and measure:
import time, random
ids = list(range(100_000))
target = random.sample(ids, 1000)
t0 = time.perf_counter()
found = [x for x in target if x in ids] # O(?) per lookup
t1 = time.perf_counter()
print(f"list: {t1 - t0:.4f}s")- Repeat with
ids_set = set(ids). Write down both times and the speedup. - When does the set NOT pay off? (hint: building the set is O(n); how many lookups do you need to amortize it?)
Exercise 2 — Group and count (your availability)
Given this list of simulated seats:
seats = [
{"sector": "A", "row": "1", "number": n, "type": "VIP" if n < 5 else "GENERAL"}
for n in range(1, 51)
] + [
{"sector": "B", "row": "1", "number": n, "type": "GENERAL"}
for n in range(1, 31)
]- Group by sector with
defaultdict(list)and return{sector: count}. - Count by type with
Counter. - Compute each sector's lowest-numbered free seat... then sort the sectors by "smallest gap" (hint:
heapqormin+sorted; which is cleaner here and why?)
Exercise 3 — Top-K of expirations
- Generate 10,000 reservations with random
expires_at. Withheapq, extract the 10 that expire soonest in O(n log k). - Compare with
sorted(reservations, key=...)[:10]and time both. Does the heap win? Is the difference worth the reading complexity? - Now write the equivalent SQL/Django query (
Reservation.objects.filter(status=...).order_by("expires_at")[:10]) and compare approaches: where does each cost live (process memory vs DB + network)?
Exercise 4 — The contiguous block (two pointers)
A user wants 4 seats together in row "A" of section "B". The row's free seats are: [3, 4, 5, 6, 12, 13, 20, 21, 22, 23, 24].
- Write a function
best_block(free_numbers: list[int], k: int) -> list[int] | Nonethat returns the first block of k consecutive seats (or None). It must be O(n) — two pointers or a single pass; the double loop is forbidden. - Test it:
best_block([3,4,5,6,12,13,20,21,22,23,24], 4)→[20,21,22,23]andbest_block([3,4,20], 3)→None. - What changes if "best" means "the most centered in the row" rather than "the first"?
Exercise 5 — The hidden complexity of your API
Open your available_seats(event_id) (you wrote it in 00b):
- Count how many queries it runs with 3 events, 500 seats each. Is it 1, n or n²? (hint:
from django.db import connection; print(len(connection.queries))). - If it does N+1, fix it with
select_related/prefetch_relatedor with the subquery you saw in the 00b solutions. Measure before and after. - The response groups by sector (Exercise 2): where would grouping be more expensive, in Python or in SQL (
GROUP BY)? Justify with the expected response size and bandwidth.
Exercise 6 — Big-O analysis of your serializer (critical reading)
Take any DRF serializer from your project (or write one that validates 10 fields):
- How many times is each field validated? What is the cost of the global
validate()? - DRF runs
to_representationper object: if a listing serializes 200 events and each accessesevent.organizer.namewithoutselect_related, what is the query complexity of the whole response? - Write the rule you will use from today on: "every in-memory O(n) that accompanies a per-object access must be checked against ___".
Submit
Paste results, measurements and diffs in the chat. With the correction we close Lesson 04 and move to Lesson 05 — Linux and the terminal: the processes you just multiplied in Gunicorn, from the console that operates them.