Capstone: Quarry's Index Layer
In short: Every lesson in this section answered the same question a different way: what keeps a search tree short? Rotations are the one move that does it (lesson 1). AVL trees apply that move after every insert using a balance factor (lesson 2); B-trees sidestep the question by widening each node so a disk index stays shallow regardless (lesson 3); treaps and skip lists reach for randomness instead of bookkeeping (lesson 4); k-d trees split on alternating coordinates to prune a plane instead of a line (lesson 5). Quarry's index layer puts the AVL machinery to work on something closer to a real workload: ids that arrive in no particular order, and queries that follow.
What you're building
index_layer_report(n, seed) -> {
"height": h, "rotations": r, "queries": n,
"total_search_steps": t, "avg_search_steps": a,
"balanced_bound": b,
}index_layer_report(8, 42)
# {"height": 4, "rotations": 2, "queries": 8, "total_search_steps": 21,
# "avg_search_steps": 2.62, "balanced_bound": 4}height,rotations: the AVL tree's final height after inserting a shuffled batch ofnids, and how many rotations that took.total_search_steps,avg_search_steps: run a search for every id, in a second shuffled order, and add up how many nodes each search visited.balanced_bound:ceil(log2(n + 1)), the height a perfectly packed tree ofnnodes would have — the number every other field is measured against.
Lesson 1 inserted ids in sorted order on purpose, to show the worst case. Real index layers rarely get keys that considerate. This capstone shuffles the insert order — and the query order — with a seeded random generator, so the numbers are realistic and exactly reproducible.
Build, then measure, don't assume
8 shuffled ids build a tree of height 4, exactly the balanced bound — and it
only took 2 rotations to get there, versus 8 if the AVL guarantee didn't
hold and lesson 1's chain happened instead. At 100 ids, height 8 sits within
AVL's own promise of ~1.44 * log2(n), and the average search (5.84
steps) comes in well under the worst case (8) — most keys aren't sitting at
the deepest leaf.
Why a static index couldn't do this
Before B-trees, disk indexes were often built ISAM-style: sort every key once, lay out a static, perfectly balanced index on disk, and leave it alone. Reads were fast — the index never moved. But writes didn't fit: a new key had nowhere balanced to go, so ISAM systems bolted an unsorted "overflow area" onto each page and let it grow, or rebuilt the entire index offline on a schedule.
def isam_overflow_depth(inserts_since_rebuild):
# ISAM doesn't rebalance; every insert since the last rebuild
# just appends to an unsorted overflow chain on its page.
return inserts_since_rebuild
print(isam_overflow_depth(0), isam_overflow_depth(50))That's the gap this whole section closes. Every structure in lessons 1–5 rebalances in place, during normal operation, with no separate offline step and no degrading overflow chain: B-trees split a full node and push the median up; AVL and red-black trees rotate or recolour; treaps and skip lists use randomness that never needs "freshening." An index layer that rebalances as it goes never needs a maintenance window — which is exactly why ISAM lost to B-trees, and why Quarry's index layer measures itself under real inserts instead of assuming a clean, pre-sorted load.
Quick practice
import math
print(math.ceil(math.log2(1000001)))🎯 Your turn
Build index_layer_report(n, seed): with rng = random.Random(seed),
shuffle list(range(n)) and insert every key into an AVL tree (rebalancing
with rotations after every insert), counting total rotations. Then shuffle
the SAME keys again on the SAME rng and search for every one, counting one
step per node visited. Return {"height": ..., "rotations": ..., "queries": n, "total_search_steps": ..., "avg_search_steps": round(total_search_steps / n, 2) if n else 0.0, "balanced_bound": ceil(log2(n+1)) if n else 0}.
index_layer_report(8, 42)→{"height": 4, "rotations": 2, "queries": 8, "total_search_steps": 21, "avg_search_steps": 2.62, "balanced_bound": 4}
Hint: reuse lesson 2's AVL insert/rotation functions unchanged, just
threaded with a counter. Build the query order with a second call to
rng.shuffle on the same generator — not a new random.Random(seed).
Press ▶ Run, try the Live App chips, then ✓ Check. Green = every piece of Quarry's index layer is built. ✅
