Exercise 1: The Corrected Formula Against an Asymmetric Shape — Possible Solution ==================================================================== THE TEST ------------------------------ for n_small, m_large in [(2, 2000), (2, 20000), (2, 100000)]: tiny = make_rows(n_small, n_small) huge = [(i, random.randrange(n_small)) for i in range(m_large)] predicted = choose_join_strategy_corrected(n_small, m_large) # ... real, repeated, averaged timing for nested_loop_join and hash_join ... actual = 'nested_loop' if nl_time <= hj_time else 'hash_join' print(predicted, actual, nl_time, hj_time) RESULT ------------------------------ n=2 m=2000: predicted=hash_join, actual=nested_loop -- MISMATCH n=2 m=20000: predicted=hash_join, actual=nested_loop -- MISMATCH n=2 m=100000: predicted=hash_join, actual=nested_loop -- MISMATCH Every single asymmetric size tested is a MISMATCH. The corrected formula predicts hash_join every time (since n*m grows huge as m grows, while n+m+8 grows only linearly) -- but nested-loop actually wins in real, measured terms at every scale tested, right up to 100,000 rows on the large side. WHY THIS IS GENUINELY SURPRISING, AND WHY IT HAPPENS ------------------------------ Naive intuition says: once one side is huge, hash join should obviously win, since n*m grows quadratically with the huge side while n+m only grows linearly. The measured reality is more subtle. With n=2 (an extremely tiny build side), nested-loop's own total work is n*m = 2*m -- literally just TWO passes over the m-row table, each pass being a simple tuple-index comparison. Hash join, meanwhile, has to do a real dict hash+lookup for EVERY ONE of the m probe rows -- and a dict lookup, even a fast one, costs measurably more per operation in real Python than a plain tuple comparison. So while hash join does fewer TOTAL steps in the formula's own accounting (m+2+8 vs 2m), each of hash join's steps is intrinsically more expensive than each of nested-loop's steps -- and with n this small, nested-loop's "2 passes" penalty barely costs anything extra per row of m, so the per-operation cost difference dominates instead. WHY THE CHAPTER'S OWN CORRECTED FORMULA MISSES THIS ------------------------------ HASH_JOIN_OVERHEAD=8 was calibrated ONLY against equal-sized inputs (n=m, tested from 2 through 600). That calibration captures ONE real effect (dict construction has a fixed startup cost) but silently assumes every "step" in either formula costs the same real amount of time -- an assumption that happens to be close enough to true when n and m are comparable in size, but breaks down badly when they're wildly different, because then the PER-STEP cost difference between a comparison and a dict lookup, not the fixed startup overhead, is what actually dominates the real running time. WHY THIS WORKS AS AN ANSWER ------------------------------ Testing a shape the chapter's own calibration never covered -- genuinely asymmetric, not just "small" or "large" -- and getting a clean, reproducible, 3-for-3 mismatch demonstrates that a cost model calibrated against one family of inputs (equal-sized) doesn't automatically generalize to a different family (asymmetric), even when both use the exact same underlying formula shape. A genuinely robust planner would need either separate calibration for asymmetric cases, or a cost model with distinct per-operation-type costs (one constant for a comparison, a different one for a dict operation) rather than a single overhead constant applied uniformly.