Skip to content

Commit

Permalink
Implemented verify
Browse files Browse the repository at this point in the history
  • Loading branch information
eliben committed Sep 3, 2024
1 parent d760a2a commit 03250c0
Show file tree
Hide file tree
Showing 2 changed files with 64 additions and 15 deletions.
19 changes: 9 additions & 10 deletions btree/btree.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,16 @@ type BTree[K, V any] struct {

const defaultTee = 10

// node is a node in the BTree.
//
// Key ordering invariants (defining n=len(keys)):
//
// keys[i].key <= keys[i+1].key for each i in 0..n-2
//
// There are n+1 elements in children (0..n)
// if kj is any key in children[j], then: keys[j-1].key <= kj <= keys[j].key
// Boundaries: k0 <= keys[0].key and keys[n-1].key <= kn
type node[K, V any] struct {
// Key ordering invariants (defining n=len(keys)):
//
// keys[i].key <= keys[i+1].key for each i in 0..n-2
//
// There are n+1 elements in children (0..n)
// if kj is any key in children[j], then: keys[j-1].key <= kj <= keys[j].key
// Boundaries: k0 <= keys[0].key and keys[n-1].key <= kn
keys []nodeKey[K, V]
children []*node[K, V]
leaf bool
Expand Down Expand Up @@ -187,9 +189,6 @@ func (bt *BTree[K, V]) nodeKeyCmp(a, b nodeKey[K, V]) int {
return bt.cmp(a.key, b.key)
}

// TODO: add "verify" method that verifies all invariants: min/max number
// of keys per node, num keys vs. num children, height, and ordering invariants
// of each node. use it in tests
// TODO: add deletion

// nodesPreOrder returns an iterator over the nodes of bt in pre-order.
Expand Down
60 changes: 55 additions & 5 deletions btree/verify.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,35 +3,85 @@ package btree
import (
"errors"
"fmt"
"slices"
)

// verify checks B-tree invariants on bt and returns an error combining all
// the problems encountered. Returns nil if bt is ok.
func (bt *BTree[K, V]) verify() error {
var errs []error

// Verify invariants on each node separately
for n := range bt.nodesPreOrder() {
if err := bt.verifyNode(n); err != nil {
errs = append(errs, err)
}
}

if len(errs) > 0 {
return errors.Join(errs...)
}

// Recursive visit to calculate height of all leaf nodes in the tree.
var heights []int
var visit func(n *node[K, V], h int)
visit = func(n *node[K, V], h int) {
if n.leaf {
heights = append(heights, h)
return
}

for _, c := range n.children {
visit(c, h+1)
}
}
visit(bt.root, 0)

cp := slices.Compact(heights)
if len(cp) != 1 {
return fmt.Errorf("different leaf heights: %v", cp)
}

return nil
}

func (bt *BTree[K, V]) verifyNode(n *node[K, V]) error {
if len(n.keys) > 2*bt.tee-1 {
return fmt.Errorf("node %p has too many keys: %d", n, len(n.keys))
return fmt.Errorf("node %p: has too many keys: %d", n, len(n.keys))
}
if n != bt.root && len(n.keys) < bt.tee-1 {
return fmt.Errorf("node %p has too few keys: %d", n, len(n.keys))
return fmt.Errorf("node %p: has too few keys: %d", n, len(n.keys))
}

if !n.leaf && len(n.children) != len(n.keys)+1 {
return fmt.Errorf("internal node %p has %d keys but %d children", n, len(n.keys), len(n.children))
if n == bt.root && len(n.keys) < 1 {
return fmt.Errorf("node %p: root has 0 keys", n)
}

if !slices.IsSortedFunc(n.keys, bt.nodeKeyCmp) {
return fmt.Errorf("node %p: keys not sorted", n)
}

if !n.leaf {
if len(n.children) != len(n.keys)+1 {
return fmt.Errorf("internal node %p: %d keys but %d children", n, len(n.keys), len(n.children))
}

for j, k := range n.keys {
// Verify ordering invariant (see type node's comment).

// 1: ensure that kj <= keys[j].key
for ci, kj := range n.children[j].keys {
if bt.nodeKeyCmp(kj, k) > 0 {
return fmt.Errorf("node %p: key %v of child [%d] >= key %v of node[%d]", n, kj.key, j, k.key, ci)
}
}

// 2: ensure that keys[j-1].key <= kj
for ci, kj := range n.children[j+1].keys {
if bt.nodeKeyCmp(k, kj) > 0 {
return fmt.Errorf("node %p: key %v of child [%d] <= key %v of node[%d]", n, kj.key, j, k.key, ci)
}
}
}
}

return nil
Expand Down

0 comments on commit 03250c0

Please sign in to comment.