diff --git a/btree/btree.go b/btree/btree.go index 727af40..fc0b4d9 100644 --- a/btree/btree.go +++ b/btree/btree.go @@ -1,5 +1,7 @@ package btree +import "slices" + type BTree[K, V any] struct { cmp func(K, K) int @@ -10,6 +12,7 @@ type BTree[K, V any] struct { // the root must have between t-1 and 2t-1 keys (inclusive). // Nodes with 2t-1 keys are considered "full". const tee = 10 +const teeFull = tee*2 - 1 type node[K, V any] struct { // Key ordering invariants (defining n=len(keys)): @@ -72,3 +75,46 @@ func (bt *BTree[K, V]) getFromNode(key K, n *node[K, V]) (v V, ok bool) { // recurse into children[i] return bt.getFromNode(key, n.children[i]) } + +// splitChild splits n.children[i] into two children nodes and moves the +// median key of n.children[i] into n. It assumes that n isn't full, +// but n.children[i] is full. +func (bt *BTree[K, V]) splitChild(n *node[K, V], i int) { + // Notation: y is the i-th child of n (the one being split), and z is the + // new node created to adopt y's t-1 largest keys. + y := n.children[i] + if len(n.keys) == teeFull || len(y.keys) != teeFull { + panic("expect n to be non-full and y to be full") + } + z := &node[K, V]{ + leaf: y.leaf, + } + + // Move keys to z. + // Before the move, y.keys: + // + // k[0] k[1] ... k[t-2] k[t-1] k[t] k[t+1] ... k[2t-2] + // + // k[t-1] is the median key -- it will move to n. + // k[0]..k[t-2] will stay with y + // k[t]..k[2t-2] will move to z + z.keys = make([]nodeKey[K, V], tee-1) + medianKey := y.keys[tee-1] + copy(z.keys, y.keys[tee:]) + y.keys = y.keys[:tee-1] + + // Move children to z. + // + // The first t children stay with y; the other t children move to z. + if !y.leaf { + z.children = make([]*node[K, V], tee) + copy(z.children, y.children[tee:]) + y.children = y.children[:tee] + } + + // Place the median key in n + n.keys = slices.Insert(n.keys, i, medianKey) + + // Place the pointer to z after the pointer to y in n + n.children = slices.Insert(n.children, i+1, z) +}