Lesson 23 / 23
Using Practice Environments
Counting repeated practice's progress metric over the shared reference's five patterns: an environment that solves 144 problems sees all five patterns too, but its balance ratio is 0.2431, against 0.6667 in an environment that solves 30.
Contents
The previous two lessons concerned a single solution: which pattern is chosen, how it is tested. This lesson carries the question outside a single solution. Practice is repeated work, and repeated work has to have a progress metric, or the work continues without anyone knowing where it is going.
Environments are referred to by type, because what is measured is not the environment itself but the shape of the problem stream it produces. The result comes in two steps: neither the number of problems solved nor how many distinct patterns were seen is a progress metric.
Three Environment Types
In a pool-based practice environment, a large number of problems sit available and the person practicing chooses. Because the choice is free, it drifts toward what is familiar; the pattern distribution reflects the practitioner’s habit. In a timed contest environment, the environment determines the set and the practitioner has to see all of it; the set is small. In an in-house assessment, the number of problems is smallest, the distribution is again not chosen by the practitioner, and the inputs are not prepared in advance.
- AD21 — The pattern set is the shared reference’s five patterns: two pointers, sliding window, greedy, backtracking, memoization. These are the patterns measured throughout the course.
- AD22 — A round is one practice session spent in an environment. Problems per round are fixed by environment type: pool 24, contest 5, assessment 2. Six rounds are measured.
- AD23 — In the pool-based environment, choice weights are 50, 30, 10, 6, 4; in the other two environments the distribution is even. The weight models the practitioner’s tendency to return to a familiar pattern.
- AD24 — Choice is made with the shared reference’s deterministic generator;
randomis not used. - AD25 — The balance ratio is the count of the least-seen pattern divided by its share under an even distribution. If all five patterns were seen equally, the ratio would be 1.0000.
"""Pattern diversity across three environment rounds, over the shared reference's five patterns.""" SEED, SECOND = 20260218, 20260219 PATTERNS = ["two pointers", "sliding window", "greedy", "backtracking", "memoization"] ENVIRONMENTS = [("pool-based practice ", 24, [50, 30, 10, 6, 4]), ("timed contest ", 5, [20, 20, 20, 20, 20]), ("in-house assessment ", 2, [20, 20, 20, 20, 20])] def generator(seed): d = seed def next_value(n): nonlocal d d = (d * 1103515245 + 12345) % 2147483648 return d % n return next_value def choose(r, weights): x = r(sum(weights)) for i, w in enumerate(weights): if x < w: return i x -= w return len(weights) - 1 def metrics(counts): total = sum(counts) fair_share = total / len(counts) return {"problems": total, "covered": sum(1 for c in counts if c), "balance": round(min(counts) / fair_share, 4), "top_two": round(sum(sorted(counts)[-2:]) / total, 4)} def run(seed, rounds=6): print(f"seed {seed} , {rounds} rounds") print("environment problems covered balance top two distribution") for ad, per_round, weights in ENVIRONMENTS: r = generator(seed) counts = [0] * len(PATTERNS) for _ in range(rounds * per_round): counts[choose(r, weights)] += 1 o = metrics(counts) print(f"{ad} {o['problems']:7d} {o['covered']:8d} {o['balance']:6.4f}" f" {o['top_two']:7.4f} {counts}") run(SEED) print() run(SECOND) print() print("pool-based environment , cumulative round by round (seed 20260218)") print("round problems covered balance top two") r = generator(SEED) counts = [0] * len(PATTERNS) for t in range(1, 7): for _ in range(24): counts[choose(r, ENVIRONMENTS[0][2])] += 1 o = metrics(counts) print(f"{t:3d} {o['problems']:7d} {o['covered']:8d} {o['balance']:6.4f}" f" {o['top_two']:7.4f}")
seed 20260218 , 6 rounds environment problems covered balance top two distribution pool-based practice 144 5 0.2431 0.7986 [64, 51, 11, 11, 7] timed contest 30 5 0.6667 0.5000 [4, 5, 9, 6, 6] in-house assessment 12 5 0.4167 0.5833 [1, 4, 3, 2, 2] seed 20260219 , 6 rounds environment problems covered balance top two distribution pool-based practice 144 5 0.1389 0.8194 [76, 42, 16, 6, 4] timed contest 30 5 0.6667 0.5667 [8, 4, 9, 4, 5] in-house assessment 12 5 0.4167 0.6667 [1, 1, 5, 2, 3] pool-based environment , cumulative round by round (seed 20260218) round problems covered balance top two 1 24 4 0.0000 0.8333 2 48 5 0.2083 0.7917 3 72 5 0.2778 0.7639 4 96 5 0.2604 0.7500 5 120 5 0.2500 0.7750 6 144 5 0.2431 0.7986
The first table refutes two metrics at once. The pool-based environment solves 144 problems, the timed contest 30, the in-house assessment 12 — a 12-times difference. Yet the count of covered patterns is 5 in all three: “how many problems did I solve” swings 12 times over; “how many distinct patterns did I see” does not move at all.
The metric that distinguishes them is the distribution. In the pool the balance ratio is 0.2431: the least-seen pattern gets under a quarter of its even-distribution share, and the two most-seen patterns cover 0.7986 of the problems. The timed contest gives 0.6667 with 30 problems — 2.74 times more balanced on five times fewer. The in-house assessment’s 0.4167 comes not from the distribution but from the smallness of the sample.
The third table is the lesson’s most contrarian result. In the pool-based environment, coverage rises to 5 in the second round and never changes again: the 72 problems solved from round three to round six add nothing to it. The balance ratio peaks at 0.2778 in the third round and then falls — 0.2604 in the fourth, 0.2431 in the sixth. As problem count rises, the progress metric declines, because every added problem goes to the two dominant patterns.
The second corpus confirms the ranking: at seed 20260219 the pool gives 0.1389, the
contest 0.6667, the assessment 0.4167. The pool’s value drops from 0.2431 to 0.1389 —
the absolute value depends on the corpus — but the ordering of the three
environments and the size of the gap between pool and contest run the same way on
both.
Does the Environment Show the Error
Distribution alone is not enough either. Applying a pattern fifty times does not show what it does when its precondition breaks — the shared reference’s first reading already proved this: two pointers matches the oracle on 40 of 40 inputs when the precondition holds, so someone practicing on inputs that satisfy it never sees the pattern’s wrongness.
What is measured here is the environment’s mix: what percentage of the inputs satisfy the precondition, and how that ratio changes the number of wrong answers seen. Two selection policies are compared.
- AD26 — In the pool-based environment, 90 percent of inputs satisfy the precondition; in the timed contest, 75 percent; in the in-house assessment, 50 percent. In the pool, problems are prepared according to the pattern; in the assessment, the input arrives as it is.
- AD27 — Habitual selection applies the most-practiced pattern to every problem and never tests the precondition.
- AD28 — Precondition-checked selection first checks whether the input is sorted; if it is not, it does not apply the pattern and falls back to the oracle. The check is at most steps and stops at the first out-of-order pair.
- AD29 — 40 problems are solved in each environment, and the target is 11.
"""Two selection policies, against the oracle, across a mix of three environments.""" SEED, SECOND = 20260218, 20260219 def generator(seed): d = seed def next_value(n): nonlocal d d = (d * 1103515245 + 12345) % 2147483648 return d % n return next_value class Counter: def __init__(self): self.step = 0 def count(self, n=1): self.step += n def mix(sorted_ratio, seed=SEED, n=40, length=12): """sorted_ratio: what percentage of inputs satisfy the precondition.""" r = generator(seed) items = [] for _ in range(n): array = [r(30) - 9 for _ in range(length)] items.append(sorted(array) if r(100) < sorted_ratio else array) return items def oracle_pairs(array, target, s): for i in range(len(array)): for j in range(i + 1, len(array)): s.count() if array[i] + array[j] == target: return True return False def pattern_two_pointers(array, target, s): left, right = 0, len(array) - 1 while left < right: s.count() total = array[left] + array[right] if total == target: return True left, right = (left + 1, right) if total < target else (left, right - 1) return False def habitual_selection(array, target, s): """Applies the most-practiced pattern to every problem.""" return pattern_two_pointers(array, target, s) def precondition_checked_selection(array, target, s): """Tests the precondition first; falls back to the oracle if it does not hold.""" for i in range(len(array) - 1): s.count() if array[i] > array[i + 1]: return oracle_pairs(array, target, s) return pattern_two_pointers(array, target, s) def measure(policy, items, target=11): diverging, policy_steps, oracle_steps = 0, 0, 0 for array in items: s1, s2 = Counter(), Counter() a = policy(list(array), target, s1) b = oracle_pairs(list(array), target, s2) policy_steps, oracle_steps = policy_steps + s1.step, oracle_steps + s2.step if a != b: diverging += 1 return diverging, policy_steps, oracle_steps ENVIRONMENTS = [("pool-based practice ", 90), ("timed contest ", 75), ("in-house assessment ", 50)] for seed in (SEED, SECOND): print(f"seed {seed} , 40 problems per environment , target 11") print("environment sorted habitual precondition check oracle") print(" inputs diverging step diverging step step") for ad, ratio in ENVIRONMENTS: K = mix(ratio, seed) s = sum(1 for d in K if d == sorted(d)) a1, k1, h1 = measure(habitual_selection, K) a2, k2, _ = measure(precondition_checked_selection, K) print(f"{ad} {s:6d} {a1:7d} {k1:4d} {a2:7d} {k2:4d} {h1:4d}") print()
seed 20260218 , 40 problems per environment , target 11
environment sorted habitual precondition check oracle
inputs diverging step diverging step step
pool-based practice 34 5 165 0 600 834
timed contest 29 8 199 0 681 914
in-house assessment 17 15 255 0 754 856
seed 20260219 , 40 problems per environment , target 11
environment sorted habitual precondition check oracle
inputs diverging step diverging step step
pool-based practice 36 2 213 0 609 1055
timed contest 31 4 238 0 691 1079
in-house assessment 22 10 296 0 823 1161
Three numbers side by side. Oracle: 834 steps in the pool mix, correct on 40 of 40 inputs. Pattern: habitual selection 165 steps, precondition-checked selection 600 steps. Diverging inputs: habitual selection gives 5 in the pool, 8 in the contest, 15 in the assessment; the precondition check gives 0 in all three.
The first reading concerns the environment. Same policy, same pattern, same problem count — yet the wrong answers it sees rise from 5 to 15, three times over. The cause is the environment’s input mix: 34 of 40 pool inputs satisfy the precondition, against 17 in the assessment. Working with prepared inputs means seeing a pattern’s wrongness less, and seeing it less is not the same as fixing it.
The second reading concerns the policy. The precondition check gives 0 diverging inputs in all three environments; the cost is rising from 165 to 600 steps in the pool mix, that is, 3.64 times. It still stays below the oracle’s 834 steps: correctness is bought not at brute force’s price, but at 72 percent of it. The second corpus gives the same ranking (2, 4, 10) and the same 0 diverging inputs; the absolute numbers depend on the corpus, the ranking does not.
What follows is repeated practice’s progress metric, made of three numbers: how many patterns were seen, the distribution’s balance ratio, and how many were seen with their precondition broken. Problems solved gives none of these three.
Summary
- Environments separate by type: in pool-based practice the practitioner chooses the distribution; in the timed contest and in-house assessment the environment chooses and the set is small.
- The number of problems solved separates the environments by a factor of 12 (144, 30, 12), but the covered-pattern count is 5 in all three; neither metric shows progress.
- The distinguishing metric is the distribution: in the pool the balance ratio is 0.2431 and the two dominant patterns cover 0.7986 of the problems; the timed contest gives 0.6667 with five times fewer problems.
- Coverage rises to 5 in the second round and freezes; the balance ratio peaks at 0.2778 in the third round and then declines to 0.2431 — adding problems can make the metric worse.
- The environment’s input mix determines how much wrongness is seen: the same policy produces 5, 8, and 15 diverging inputs in the pool, contest, and assessment.
- Precondition-checked selection gives 0 diverging inputs in all three environments; its cost rises from 165 to 600 steps, 72 percent of the oracle’s 834.
Course Wrap-Up
Twenty-three lessons asked a single question: what does choosing a pattern mean accepting. Every lesson answered with three numbers. The oracle was always brute force and always correct — but not always expensive: the Longest Path Problem lesson measured the oracle at 562 steps against the pattern’s 3626 on eight vertices, and showed the threshold sits at ten. The pattern usually spent fewer steps. Diverging inputs counted how many inputs the pattern answered differently than the oracle. The course’s rule: a pattern’s number is not the steps it saves, but how many wrong answers it gives once its precondition breaks.
| Lesson | Oracle (steps) | Pattern (steps) | Diverging inputs / cost |
|---|---|---|---|
| Brute Force | exhaustive count 2640 | early exit 866 | early exit 0; sampling 14 at 406 |
| Divide and Conquer | 3120 | linear combine step 2680 | 0; incomplete combine step 40/40 at 920 |
| Greedy Algorithms | bottom-up | greedy | 827 of 969 systems (0.8535); largest excess 16 coins |
| Dynamic Programming | 163,840 | full-key memoization 2440 (67.15 times fewer) | 0; missing key 39/40; with no overlap, 19 memo entries wasted |
| Backtracking | without pruning 960,800 nodes, n=7 | with pruning 552 nodes | 1740.6 times, ratio grows with n (72.3 → 8156.2); over-pruning gives 0 solutions |
| Randomization | 780 | verified sampling 944–1056 | 0; unverified, at 200, 40/40 once the precondition breaks |
| Two Pointers | 972 | 154 (6.31 times fewer) | 25/40 unsorted, speedup drops to 2.33 times |
| Sliding Window | 2396 | 867 (2.76 times fewer) | 10/40 with negatives, in fewer steps |
| Fast and Slow Pointer | 480 | 299 | 14/40 on the second edge case; steps never change |
| Interval Merging | sorting by start: 40/40 correct | 2154 | 29/40 sorting by end, 40/40 by length and unsorted |
| Cyclic Sort | 1..n, no duplicates | comparison-free | with duplicates, plain 40/40; guarded 39/40; missing case 0/40 |
| Two Heaps | 480 median | with rebalancing step 1261 | unbalanced 295/480 wrong; rebalancing is more than half the pattern (1261/550) |
| K-th Element | four k values x 40 inputs | k-sized heap | 0/40; “k-th distinct value” is a separate question: 27/40 |
| Grid Traversal | same connectivity 40/40 | connected component | 38/40 once the definitions diverge |
| Knapsack | fractional variant 100,800 | greedy 174 | fractional 0/40; the same order in 0/1 gives 5/40, loss 7 |
| Travelling Salesman | 352,800 (20 samples) | nearest neighbor 560 | 17/20; average deviation 9.3, worst 33.33; two-opt 2/20 |
| Longest Path | acyclic 40/40 | relaxation procedure | with a cycle 38/40; largest overestimate 228 |
| N Queens | with pruning 2057 nodes, n=8 | with symmetry breaking 1029 | ratio 2.00, constant across five sizes; pruning’s grows, symmetry’s does not |
| Knight’s Tour and Maze | path model 90,111 | cell model 25 | ratio 3604, diverging 0 |
| Hamiltonian Paths | counting 29,270 | decision 1526 | ratio 19.18; in every case with no path, decision = counting |
| Problem Reading and Constraint | 866 | hash table 486, sorting pattern 1398, counting 396 | the wrong constraint reading diverges on 18 of 40 inputs (17 on the second corpus); at n=10<sup>8</sup> none of the three solutions fit the budget |
| Solution Verification | brute force, 1596 across 52 inputs | correct two pointers 329 | 40 random inputs catch 4 of 5 defects, 12 edge cases catch all 5; against an oracle sharing the same defect, diverging drops from 3 to 0 |
| Practice Environments | 834 | habitual selection 165, precondition check 600 | 5 diverging in the pool, 8 in the contest, 15 in the assessment; precondition check gives 0 diverging but 3.64 times the steps |
The table’s second reading matters more than its first: speedup brings a pattern closer to being wrong, and sometimes it does not even speed things up. Two pointers’ speedup drops to 2.33 times on an unsorted input while it is wrong on 25 inputs; greedy over-answers on 827 of 969 systems; memoization wastes 19 entries with no overlap. None of this was hidden: not knowing when a pattern goes wrong means not knowing when to choose it.
The third and lasting result concerns the oracle itself: brute force is not a baseline, it is an oracle. Every number in the course was written because of it. In the final lesson this became a habit: counting correctness against a second solution’s answer, not an opinion.
The next course, Theory of Computation, takes up the question left in the oracle’s shadow. The traveling salesman and Hamiltonian paths lessons explicitly deferred “why is this problem hard.” What will be asked there is whether not knowing anything better than brute force is, for some problems, a fact about our knowledge or about the problems themselves. The answer comes through decidability, reduction, and complexity classes.
To keep your progress and take notes, Log in
My notes
Log in to take notes.