Replace unbalanced BST with AVL tree in ordered map/set#5
Merged
Conversation
Add height field to node type, implement single and double rotations (LL, RR, LR, RL), and rebalance during path reconstruction in insert-node, remove-node, and extract-min. Guarantees O(log n) for all operations regardless of insertion order. - New helpers: subtree-height, make-node, rotate-right-node, rotate-left-node, balance-node (all private/hidden) - Expose height function on both ord-map and ord-set - Tests verify logarithmic height after ascending, descending, and mixed insert/remove sequences; memory balance preserved - Remove unbalanced-BST caveat from README
There was a problem hiding this comment.
Build & Tests
- Build: pass (
carp -bclean locally) - Tests: pass — 33/33 ord-map tests, 21/21 ord-set tests (including memory balance checks)
- CI: macos pass, ubuntu still pending at time of review
Findings
I checked out the branch, built it, ran all tests, and carefully reviewed the AVL implementation. No bugs found.
Rotation correctness verified:
rotate-right-node: standard right rotation — y becomes root, z gets y's right subtree as its left child. Heights recomputed viamake-node. ✓rotate-left-node: mirror of right rotation. ✓- Both handle the
Nothingchild case defensively (shouldn't occur during normal AVL operations but won't crash).
Balance logic verified:
balance-nodecorrectly implements all four AVL cases: LL (single right rotation), RR (single left rotation), LR (left-rotate child then right-rotate parent), RL (right-rotate child then left-rotate parent). ✓- Balance factor
bf = left_height - right_height, thresholds at ±1. ✓
Integration points verified:
insert-node: creates leaf viamake-node(height 1), appliesbalance-nodeat every level during bottom-up path reconstruction. ✓remove-node: same rebalancing pattern during reconstruction. Successor replacement viaextract-minalso rebalances. ✓extract-min: rebalances during its own path reconstruction (removing the minimum can create right-heavy subtrees). ✓- Key-equal update in
insert-nodecorrectly preserves height with%node-initdirectly (no structural change). ✓
Height computation:
make-nodecomputesh = 1 + max(left_height, right_height)— correct for height-1 convention (leaf = 1, empty = 0). ✓- Heights cascade correctly through rotations since all node construction goes through
make-node.
Tests are solid:
- Ascending 1000 → height ≤ 15 (theoretical AVL max ~14.4). ✓
- Descending 1000 → height ≤ 15. ✓
- Ascending 10000 → height ≤ 21 (theoretical ~19.2). ✓
- Balance preserved after removing 50% of entries. ✓
- Ord-set height delegation works correctly. ✓
- Memory balance tests pass — no leaks introduced by the structural changes.
README correctly removes the "unbalanced BSTs; adversarial input degrades to linear" caveat.
New public API: height function on both ord-map and ord-set is a reasonable diagnostic addition.
Verdict: revise
The implementation is correct — I could not find bugs or edge cases that break the AVL invariant. Recommending revise only because ubuntu CI is still pending. Once CI is fully green, this is ready to merge.
hellerve
approved these changes
Jun 9, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
The
define-ord-map(and by extensiondefine-ord-set) macro previously used a plain binary search tree, which degrades to O(n) on sorted or adversarial input. This replaces it with a self-balancing AVL tree, guaranteeing O(log n) for insert, remove, and lookup regardless of insertion order.What changed
Node type — added a
height Intfield to the internal BST node.New private helpers (all
private/hidden, inside the generated module):subtree-height— height of a(Maybe NodeRc), returning 0 forNothingmake-node— constructs a node with automatically computed heightrotate-right-node/rotate-left-node— single AVL rotationsbalance-node— checks balance factor and applies LL, RR, LR, or RL rotation as neededModified functions:
insert-node— usesmake-nodefor leaf/update creation; appliesbalance-nodeduring bottom-up path reconstructionremove-node— same treatment for deletion path reconstruction and successor replacementextract-min— rebalances during its path reconstruction (removing the minimum can create right-heavy nodes)singleton— usesmake-nodeto set initial heightNew public API:
heighton both ord-map and ord-set — returns the AVL tree height (0 for empty), useful for diagnosticsTests — 6 new assertions covering:
All 33 ord-map tests and 21 ord-set tests pass, including memory-balance checks.
README — removed the "unbalanced BSTs; adversarial input degrades to linear" caveat.
Test plan
--log-memorycarp-fmt -cpasses on all changed filesanglerreports no new findingsOpened by the carpentry-org heartbeat agent (Claude). Veit has not reviewed this yet.