pax_global_header00006660000000000000000000000064137472653240014527gustar00rootroot0000000000000052 comment=78e8a2adf6ccde430a97e4c2256f5f9a485ffe90 btree-0.3.0/000077500000000000000000000000001374726532400126305ustar00rootroot00000000000000btree-0.3.0/LICENSE000066400000000000000000000020361374726532400136360ustar00rootroot00000000000000Copyright (c) 2020 Josh Baker Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. btree-0.3.0/README.md000066400000000000000000000121061374726532400141070ustar00rootroot00000000000000# btree [![GoDoc](https://godoc.org/github.com/tidwall/btree?status.svg)](https://godoc.org/github.com/tidwall/btree) An [efficient](#performance) [B-tree](https://en.wikipedia.org/wiki/B-tree) implementation in Go. ## Installing To start using btree, install Go and run `go get`: ```sh $ go get -u github.com/tidwall/btree ``` ## Usage ```go package main import ( "fmt" "github.com/tidwall/btree" ) type Item struct { Key, Val string } // byKeys is a comparison function that compares item keys and returns true // when a is less than b. func byKeys(a, b interface{}) bool { i1, i2 := a.(*Item), b.(*Item) return i1.Key < i2.Key } // byVals is a comparison function that compares item values and returns true // when a is less than b. func byVals(a, b interface{}) bool { i1, i2 := a.(*Item), b.(*Item) if i1.Val < i2.Val { return true } if i1.Val > i2.Val { return false } // Both vals are equal so we should fall though // and let the key comparison take over. return byKeys(a, b) } func main() { // Create a tree for keys and a tree for values. // The "keys" tree will be sorted on the Keys field. // The "values" tree will be sorted on the Values field. keys := btree.New(byKeys) vals := btree.New(byVals) // Create some items. users := []*Item{ &Item{Key: "user:1", Val: "Jane"}, &Item{Key: "user:2", Val: "Andy"}, &Item{Key: "user:3", Val: "Steve"}, &Item{Key: "user:4", Val: "Andrea"}, &Item{Key: "user:5", Val: "Janet"}, &Item{Key: "user:6", Val: "Andy"}, } // Insert each user into both trees for _, user := range users { keys.Set(user) vals.Set(user) } // Iterate over each user in the key tree keys.Ascend(nil, func(item interface{}) bool { kvi := item.(*Item) fmt.Printf("%s %s\n", kvi.Key, kvi.Val) return true }) fmt.Printf("\n") // Iterate over each user in the val tree vals.Ascend(nil, func(item interface{}) bool { kvi := item.(*Item) fmt.Printf("%s %s\n", kvi.Key, kvi.Val) return true }) // Output: // user:1 Jane // user:2 Andy // user:3 Steve // user:4 Andrea // user:5 Janet // user:6 Andy // // user:4 Andrea // user:2 Andy // user:6 Andy // user:1 Jane // user:5 Janet // user:3 Steve } ``` ## Operations ### Basic ``` Len() # return the number of items in the btree Set(item) # insert or replace an existing item Get(item) # get an existing item Delete(item) # delete an item ``` ### Iteration ``` Ascend(pivot, iter) # scan items in ascending order starting at pivot. Descend(pivot, iter) # scan items in descending order starting at pivot. ``` ### Queues ``` Min() # return the first item in the btree Max() # return the last item in the btree PopMin() # remove and return the first item in the btree PopMax() # remove and return the last item in the btree ``` ### Bulk loading ``` Load(item) # load presorted items into tree ``` ### Path hints ``` SetHint(item, *hint) # insert or replace an existing item GetHint(item, *hint) # get an existing item DeleteHint(item, *hint) # delete an item ``` ## Performance This implementation was designed with performance in mind. The following benchmarks were run on my 2019 Macbook Pro (2.4 GHz 8-Core Intel Core i9) using Go 1.15.3. The items are simple 8-byte ints. - `tidwall`: The [tidwall/btree](https://github.com/tidwall/btree) package - `google`: The [google/btree](https://github.com/google/btree) package - `go-arr`: Just a simple Go array ``` ** sequential set ** tidwall: set-seq 1,000,000 ops in 143ms, 6,996,275/sec, 142 ns/op, 30.9 MB, 32 bytes/op tidwall: set-seq-hint 1,000,000 ops in 65ms, 15,441,082/sec, 64 ns/op, 30.9 MB, 32 bytes/op tidwall: load-seq 1,000,000 ops in 19ms, 53,242,398/sec, 18 ns/op, 30.9 MB, 32 bytes/op google: set-seq 1,000,000 ops in 175ms, 5,700,922/sec, 175 ns/op, 33.1 MB, 34 bytes/op go-arr: append 1,000,000 ops in 52ms, 19,153,714/sec, 52 ns/op, 41.3 MB, 43 bytes/op ** random set ** tidwall: set-rand 1,000,000 ops in 589ms, 1,697,471/sec, 589 ns/op, 22.5 MB, 23 bytes/op tidwall: set-rand-hint 1,000,000 ops in 592ms, 1,688,184/sec, 592 ns/op, 22.2 MB, 23 bytes/op tidwall: load-rand 1,000,000 ops in 578ms, 1,728,932/sec, 578 ns/op, 22.3 MB, 23 bytes/op google: set-rand 1,000,000 ops in 662ms, 1,509,924/sec, 662 ns/op, 32.1 MB, 33 bytes/op ** sequential get ** tidwall: get-seq 1,000,000 ops in 111ms, 8,995,090/sec, 111 ns/op tidwall: get-seq-hint 1,000,000 ops in 56ms, 18,017,397/sec, 55 ns/op google: get-seq 1,000,000 ops in 135ms, 7,414,046/sec, 134 ns/op ** random get ** tidwall: get-rand 1,000,000 ops in 139ms, 7,214,017/sec, 138 ns/op tidwall: get-rand-hint 1,000,000 ops in 191ms, 5,243,833/sec, 190 ns/op google: get-rand 1,000,000 ops in 161ms, 6,199,818/sec, 161 ns/op ``` *You can find the benchmark utility at [tidwall/btree-benchmark](https://github.com/tidwall/btree-benchmark)* ## Contact Josh Baker [@tidwall](http://twitter.com/tidwall) ## License Source code is available under the MIT [License](/LICENSE). btree-0.3.0/btree.go000066400000000000000000000316701374726532400142670ustar00rootroot00000000000000// Copyright 2020 Joshua J Baker. All rights reserved. // Use of this source code is governed by an MIT-style // license that can be found in the LICENSE file. package btree //tinygen:T interface{} const maxItems = 255 const minItems = maxItems * 40 / 100 type node struct { leaf bool numItems int16 items [maxItems]interface{} children *[maxItems + 1]*node } type justaLeaf struct { leaf bool numItems int16 items [maxItems]interface{} } // BTree is an ordered set items type BTree struct { root *node length int less func(a, b interface{}) bool lnode *node } func newNode(leaf bool) *node { n := &node{leaf: leaf} if !leaf { n.children = new([maxItems + 1]*node) } return n } // PathHint is a utility type used with the *Hint() functions. Hints provide // faster operations for clustered keys. type PathHint struct { path [8]uint8 } // New returns a new BTree func New(less func(a, b interface{}) bool) *BTree { if less == nil { panic("nil less") } tr := new(BTree) tr.less = less return tr } // Less is a convenience function that performs a comparison of two items // using the same "less" function provided to New. func (tr *BTree) Less(a, b interface{}) bool { return tr.less(a, b) } func (n *node) find(key interface{}, less func(a, b interface{}) bool, hint *PathHint, depth int, ) (index int16, found bool) { low := int16(0) high := n.numItems - 1 if hint != nil && depth < 8 { index = int16(hint.path[depth]) if index > n.numItems-1 { index = n.numItems - 1 } if less(key, n.items[index]) { high = index - 1 } else if less(n.items[index], key) { low = index + 1 } else { found = true goto done } } for low <= high { mid := low + ((high+1)-low)/2 if !less(key, n.items[mid]) { low = mid + 1 } else { high = mid - 1 } } if low > 0 && !less(n.items[low-1], key) && !less(key, n.items[low-1]) { index = low - 1 found = true } else { index = low found = false } done: if hint != nil && depth < 8 { if n.leaf && found { hint.path[depth] = byte(index + 1) } else { hint.path[depth] = byte(index) } } return index, found } // SetHint sets or replace a value for a key using a path hint func (tr *BTree) SetHint(item interface{}, hint *PathHint) (prev interface{}) { if item == nil { panic("nil item") } if tr.root == nil { tr.root = newNode(true) tr.root.items[0] = item tr.root.numItems = 1 tr.length = 1 return } prev = tr.root.set(item, tr.less, hint, 0) if prev != nil { return prev } tr.lnode = nil if tr.root.numItems == maxItems { n := tr.root right, median := n.split() tr.root = newNode(false) tr.root.children[0] = n tr.root.items[0] = median tr.root.children[1] = right tr.root.numItems = 1 } tr.length++ return prev } // Set or replace a value for a key func (tr *BTree) Set(item interface{}) (prev interface{}) { return tr.SetHint(item, nil) } func (n *node) split() (right *node, median interface{}) { right = newNode(n.leaf) median = n.items[maxItems/2] copy(right.items[:maxItems/2], n.items[maxItems/2+1:]) if !n.leaf { copy(right.children[:maxItems/2+1], n.children[maxItems/2+1:]) } right.numItems = maxItems / 2 if !n.leaf { for i := maxItems/2 + 1; i < maxItems+1; i++ { n.children[i] = nil } } for i := maxItems / 2; i < maxItems; i++ { n.items[i] = nil } n.numItems = maxItems / 2 return right, median } func (n *node) set(item interface{}, less func(a, b interface{}) bool, hint *PathHint, depth int, ) (prev interface{}) { i, found := n.find(item, less, hint, depth) if found { prev = n.items[i] n.items[i] = item return prev } if n.leaf { copy(n.items[i+1:n.numItems+1], n.items[i:n.numItems]) n.items[i] = item n.numItems++ return nil } prev = n.children[i].set(item, less, hint, depth+1) if prev != nil { return prev } if n.children[i].numItems == maxItems { right, median := n.children[i].split() copy(n.children[i+1:], n.children[i:]) copy(n.items[i+1:], n.items[i:]) n.items[i] = median n.children[i+1] = right n.numItems++ } return prev } func (n *node) scan(iter func(item interface{}) bool) bool { if n.leaf { for i := int16(0); i < n.numItems; i++ { if !iter(n.items[i]) { return false } } return true } for i := int16(0); i < n.numItems; i++ { if !n.children[i].scan(iter) { return false } if !iter(n.items[i]) { return false } } return n.children[n.numItems].scan(iter) } // Get a value for key func (tr *BTree) Get(key interface{}) interface{} { return tr.GetHint(key, nil) } // GetHint gets a value for key using a path hint func (tr *BTree) GetHint(key interface{}, hint *PathHint) interface{} { if tr.root == nil || key == nil { return nil } depth := 0 n := tr.root for { i, found := n.find(key, tr.less, hint, depth) if found { return n.items[i] } if n.leaf { return nil } n = n.children[i] depth++ } } // Len returns the number of items in the tree func (tr *BTree) Len() int { return tr.length } // Delete a value for a key func (tr *BTree) Delete(key interface{}) interface{} { return tr.DeleteHint(key, nil) } // DeleteHint deletes a value for a key using a path hint func (tr *BTree) DeleteHint(key interface{}, hint *PathHint) interface{} { if tr.root == nil || key == nil { return nil } prev := tr.root.delete(false, key, tr.less, hint, 0) if prev == nil { return nil } tr.lnode = nil if tr.root.numItems == 0 && !tr.root.leaf { tr.root = tr.root.children[0] } tr.length-- if tr.length == 0 { tr.root = nil } return prev } func (n *node) delete(max bool, key interface{}, less func(a, b interface{}) bool, hint *PathHint, depth int, ) interface{} { var i int16 var found bool if max { i, found = n.numItems-1, true } else { i, found = n.find(key, less, hint, depth) } if n.leaf { if found { prev := n.items[i] // found the items at the leaf, remove it and return. copy(n.items[i:], n.items[i+1:n.numItems]) n.items[n.numItems-1] = nil n.numItems-- return prev } return nil } var prev interface{} if found { if max { i++ prev = n.children[i].delete(true, "", less, nil, 0) } else { prev = n.items[i] maxItem := n.children[i].delete(true, "", less, nil, 0) n.items[i] = maxItem } } else { prev = n.children[i].delete(max, key, less, hint, depth+1) } if prev == nil { return nil } if n.children[i].numItems < minItems { if i == n.numItems { i-- } if n.children[i].numItems+n.children[i+1].numItems+1 < maxItems { // merge left + item + right n.children[i].items[n.children[i].numItems] = n.items[i] copy(n.children[i].items[n.children[i].numItems+1:], n.children[i+1].items[:n.children[i+1].numItems]) if !n.children[0].leaf { copy(n.children[i].children[n.children[i].numItems+1:], n.children[i+1].children[:n.children[i+1].numItems+1]) } n.children[i].numItems += n.children[i+1].numItems + 1 copy(n.items[i:], n.items[i+1:n.numItems]) copy(n.children[i+1:], n.children[i+2:n.numItems+1]) n.items[n.numItems] = nil n.children[n.numItems+1] = nil n.numItems-- } else if n.children[i].numItems > n.children[i+1].numItems { // move left -> right copy(n.children[i+1].items[1:], n.children[i+1].items[:n.children[i+1].numItems]) if !n.children[0].leaf { copy(n.children[i+1].children[1:], n.children[i+1].children[:n.children[i+1].numItems+1]) } n.children[i+1].items[0] = n.items[i] if !n.children[0].leaf { n.children[i+1].children[0] = n.children[i].children[n.children[i].numItems] } n.children[i+1].numItems++ n.items[i] = n.children[i].items[n.children[i].numItems-1] n.children[i].items[n.children[i].numItems-1] = nil if !n.children[0].leaf { n.children[i].children[n.children[i].numItems] = nil } n.children[i].numItems-- } else { // move right -> left n.children[i].items[n.children[i].numItems] = n.items[i] if !n.children[0].leaf { n.children[i].children[n.children[i].numItems+1] = n.children[i+1].children[0] } n.children[i].numItems++ n.items[i] = n.children[i+1].items[0] copy(n.children[i+1].items[:], n.children[i+1].items[1:n.children[i+1].numItems]) if !n.children[0].leaf { copy(n.children[i+1].children[:], n.children[i+1].children[1:n.children[i+1].numItems+1]) } n.children[i+1].numItems-- } } return prev } // Ascend the tree within the range [pivot, last] // Pass nil for pivot to scan all item in ascending order // Return false to stop iterating func (tr *BTree) Ascend(pivot interface{}, iter func(item interface{}) bool) { if tr.root == nil { return } if pivot == nil { tr.root.scan(iter) } else if tr.root != nil { tr.root.ascend(pivot, tr.less, nil, 0, iter) } } func (n *node) ascend(pivot interface{}, less func(a, b interface{}) bool, hint *PathHint, depth int, iter func(item interface{}) bool, ) bool { i, found := n.find(pivot, less, hint, depth) if !found { if !n.leaf { if !n.children[i].ascend(pivot, less, hint, depth+1, iter) { return false } } } for ; i < n.numItems; i++ { if !iter(n.items[i]) { return false } if !n.leaf { if !n.children[i+1].scan(iter) { return false } } } return true } func (n *node) reverse(iter func(item interface{}) bool) bool { if n.leaf { for i := n.numItems - 1; i >= 0; i-- { if !iter(n.items[i]) { return false } } return true } if !n.children[n.numItems].reverse(iter) { return false } for i := n.numItems - 1; i >= 0; i-- { if !iter(n.items[i]) { return false } if !n.children[i].reverse(iter) { return false } } return true } // Descend the tree within the range [pivot, first] // Pass nil for pivot to scan all item in descending order // Return false to stop iterating func (tr *BTree) Descend(pivot interface{}, iter func(item interface{}) bool) { if tr.root == nil { return } if pivot == nil { tr.root.reverse(iter) } else if tr.root != nil { tr.root.descend(pivot, tr.less, nil, 0, iter) } } func (n *node) descend(pivot interface{}, less func(a, b interface{}) bool, hint *PathHint, depth int, iter func(item interface{}) bool, ) bool { i, found := n.find(pivot, less, hint, depth) if !found { if !n.leaf { if !n.children[i].descend(pivot, less, hint, depth+1, iter) { return false } } i-- } for ; i >= 0; i-- { if !iter(n.items[i]) { return false } if !n.leaf { if !n.children[i].reverse(iter) { return false } } } return true } // Load is for bulk loading pre-sorted items func (tr *BTree) Load(item interface{}) interface{} { if item == nil { panic("nil item") } if tr.lnode != nil && tr.lnode.numItems < maxItems-2 { if tr.less(tr.lnode.items[tr.lnode.numItems-1], item) { tr.lnode.items[tr.lnode.numItems] = item tr.lnode.numItems++ tr.length++ return nil } } prev := tr.Set(item) if prev != nil { return prev } n := tr.root for { if n.leaf { tr.lnode = n break } n = n.children[n.numItems] } return nil } // Min returns the minimum item in tree. // Returns nil if the tree has no items. func (tr *BTree) Min() interface{} { if tr.root == nil { return nil } n := tr.root for { if n.leaf { return n.items[0] } n = n.children[0] } } // Max returns the maximum item in tree. // Returns nil if the tree has no items. func (tr *BTree) Max() interface{} { if tr.root == nil { return nil } n := tr.root for { if n.leaf { return n.items[n.numItems-1] } n = n.children[n.numItems] } } // PopMin removes the minimum item in tree and returns it. // Returns nil if the tree has no items. func (tr *BTree) PopMin() interface{} { if tr.root == nil { return nil } tr.lnode = nil n := tr.root for { if n.leaf { item := n.items[0] if n.numItems == minItems { return tr.Delete(item) } copy(n.items[:], n.items[1:]) n.items[n.numItems-1] = nil n.numItems-- tr.length-- return item } n = n.children[0] } } // PopMax removes the minimum item in tree and returns it. // Returns nil if the tree has no items. func (tr *BTree) PopMax() interface{} { if tr.root == nil { return nil } tr.lnode = nil n := tr.root for { if n.leaf { item := n.items[n.numItems-1] if n.numItems == minItems { return tr.Delete(item) } n.items[n.numItems-1] = nil n.numItems-- tr.length-- return item } n = n.children[n.numItems] } } // Height returns the height of the tree. // Returns zero if tree has no items. func (tr *BTree) Height() int { var height int if tr.root != nil { n := tr.root for { height++ if n.leaf { break } n = n.children[n.numItems] } } return height } // Walk iterates over all items in tree, in order. // The items param will contain one or more items. func (tr *BTree) Walk(iter func(item []interface{})) { if tr.root != nil { tr.root.walk(iter) } } func (n *node) walk(iter func(item []interface{})) { if n.leaf { iter(n.items[:n.numItems]) } else { for i := int16(0); i < n.numItems; i++ { n.children[i].walk(iter) iter(n.items[i : i+1]) } n.children[n.numItems].walk(iter) } } btree-0.3.0/btree_test.go000066400000000000000000000473051374726532400153300ustar00rootroot00000000000000// Copyright 2020 Joshua J Baker. All rights reserved. // Use of this source code is governed by an MIT-style // license that can be found in the LICENSE file. package btree // SEED=1603717878394178000 go test -run Random import ( "fmt" "math/rand" "os" "sort" "strconv" "strings" "testing" "time" ) var seed int64 func init() { var err error seed, err = strconv.ParseInt(os.Getenv("SEED"), 10, 64) if err != nil { seed = time.Now().UnixNano() } fmt.Printf("seed: %d\n", seed) rand.Seed(seed) } func randKeys(N int) (keys []string) { format := fmt.Sprintf("%%0%dd", len(fmt.Sprintf("%d", N-1))) for _, i := range rand.Perm(N) { keys = append(keys, fmt.Sprintf(format, i)) } return } const flatLeaf = true func (tr *BTree) print() { tr.root.print(0, tr.Height()) } func (n *node) print(level, height int) { if n == nil { println("NIL") return } if height == 0 && flatLeaf { fmt.Printf("%v", strings.Repeat(" ", level)) } for i := int16(0); i < n.numItems; i++ { if height > 0 { n.children[i].print(level+1, height-1) } if height > 0 || (height == 0 && !flatLeaf) { fmt.Printf("%v%v\n", strings.Repeat(" ", level), n.items[i]) } else { if i > 0 { fmt.Printf(",") } fmt.Printf("%v", n.items[i]) } } if height == 0 && flatLeaf { fmt.Printf("\n") } if height > 0 { n.children[n.numItems].print(level+1, height-1) } } func (tr *BTree) deepPrint() { fmt.Printf("%#v\n", tr) tr.root.deepPrint(0) } func (n *node) deepPrint(level int) { if n == nil { fmt.Printf("%v %#v\n", strings.Repeat(" ", level), n) return } fmt.Printf("%v count: %v\n", strings.Repeat(" ", level), n.numItems) fmt.Printf("%v items: %v\n", strings.Repeat(" ", level), n.items[:n.numItems]) if !n.leaf { fmt.Printf("%v child: %v\n", strings.Repeat(" ", level), n.children) } if !n.leaf { for i := int16(0); i < n.numItems; i++ { n.children[i].deepPrint(level + 1) } n.children[n.numItems].deepPrint(level + 1) } } func stringsEquals(a, b []string) bool { if len(a) != len(b) { return false } for i := 0; i < len(a); i++ { if a[i] != b[i] { return false } } return true } type pair struct { key string value interface{} } func pairLess(a, b interface{}) bool { return a.(pair).key < b.(pair).key } func TestDescend(t *testing.T) { tr := New(pairLess) var count int tr.Descend(pair{"1", nil}, func(item interface{}) bool { count++ return true }) if count > 0 { t.Fatalf("expected 0, got %v", count) } var keys []string for i := 0; i < 1000; i += 10 { keys = append(keys, fmt.Sprintf("%03d", i)) tr.Set(pair{keys[len(keys)-1], nil}) } var exp []string tr.Descend(nil, func(item interface{}) bool { exp = append(exp, item.(pair).key) return true }) for i := 999; i >= 0; i-- { var key string key = fmt.Sprintf("%03d", i) var all []string tr.Descend(pair{key, nil}, func(item interface{}) bool { all = append(all, item.(pair).key) return true }) for len(exp) > 0 && key < exp[0] { exp = exp[1:] } var count int tr.Descend(pair{key, nil}, func(item interface{}) bool { if count == (i+1)%maxItems { return false } count++ return true }) if count > len(exp) { t.Fatalf("expected 1, got %v", count) } if !stringsEquals(exp, all) { fmt.Printf("exp: %v\n", exp) fmt.Printf("all: %v\n", all) t.Fatal("mismatch") } } } func TestAscend(t *testing.T) { tr := New(pairLess) var count int tr.Ascend(pair{"1", nil}, func(item interface{}) bool { count++ return true }) if count > 0 { t.Fatalf("expected 0, got %v", count) } var keys []string for i := 0; i < 1000; i += 10 { keys = append(keys, fmt.Sprintf("%03d", i)) tr.Set(pair{keys[len(keys)-1], nil}) } exp := keys for i := -1; i < 1000; i++ { var key string if i == -1 { key = "" } else { key = fmt.Sprintf("%03d", i) } var all []string tr.Ascend(pair{key, nil}, func(item interface{}) bool { all = append(all, item.(pair).key) return true }) for len(exp) > 0 && key > exp[0] { exp = exp[1:] } var count int tr.Ascend(pair{key, nil}, func(item interface{}) bool { if count == (i+1)%maxItems { return false } count++ return true }) if count > len(exp) { t.Fatalf("expected 1, got %v", count) } if !stringsEquals(exp, all) { t.Fatal("mismatch") } } } func TestBTree(t *testing.T) { N := 10000 tr := New(pairLess) tr.sane() keys := randKeys(N) // insert all items for _, key := range keys { item := tr.Set(pair{key, key}) tr.sane() if item != nil { t.Fatal("expected nil") } } // check length if tr.Len() != len(keys) { t.Fatalf("expected %v, got %v", len(keys), tr.Len()) } // get each value for _, key := range keys { item := tr.Get(pair{key, nil}) if item == nil || item.(pair).value != key { t.Fatalf("expected '%v', got '%v'", key, item.(pair).value) } } // scan all items var last string all := make(map[string]interface{}) tr.Ascend(nil, func(item interface{}) bool { key := item.(pair).key value := item.(pair).value if key <= last { t.Fatal("out of order") } if value.(string) != key { t.Fatalf("mismatch") } last = key all[key] = value return true }) if len(all) != len(keys) { t.Fatalf("expected '%v', got '%v'", len(keys), len(all)) } // reverse all items var prev string all = make(map[string]interface{}) tr.Descend(nil, func(item interface{}) bool { key := item.(pair).key value := item.(pair).value if prev != "" && key >= prev { t.Fatal("out of order") } if value.(string) != key { t.Fatalf("mismatch") } prev = key all[key] = value return true }) if len(all) != len(keys) { t.Fatalf("expected '%v', got '%v'", len(keys), len(all)) } // try to get an invalid item item := tr.Get(pair{"invalid", nil}) if item != nil { t.Fatal("expected nil") } // scan and quit at various steps for i := 0; i < 100; i++ { var j int tr.Ascend(nil, func(item interface{}) bool { if j == i { return false } j++ return true }) } // reverse and quit at various steps for i := 0; i < 100; i++ { var j int tr.Descend(nil, func(item interface{}) bool { if j == i { return false } j++ return true }) } // delete half the items for _, key := range keys[:len(keys)/2] { item := tr.Delete(pair{key, nil}) if item == nil { t.Fatal("expected true") } value := item.(pair).value if value == nil || value.(string) != key { t.Fatalf("expected '%v', got '%v'", key, value) } } // check length if tr.Len() != len(keys)/2 { t.Fatalf("expected %v, got %v", len(keys)/2, tr.Len()) } // try delete half again for _, key := range keys[:len(keys)/2] { item := tr.Delete(pair{key, nil}) tr.sane() if item != nil { t.Fatal("expected false") } } // try delete half again for _, key := range keys[:len(keys)/2] { item := tr.Delete(pair{key, nil}) tr.sane() if item != nil { t.Fatal("expected false") } } // check length if tr.Len() != len(keys)/2 { t.Fatalf("expected %v, got %v", len(keys)/2, tr.Len()) } // scan items last = "" all = make(map[string]interface{}) tr.Ascend(nil, func(item interface{}) bool { key := item.(pair).key value := item.(pair).value if key <= last { t.Fatal("out of order") } if value.(string) != key { t.Fatalf("mismatch") } last = key all[key] = value return true }) if len(all) != len(keys)/2 { t.Fatalf("expected '%v', got '%v'", len(keys), len(all)) } // replace second half for _, key := range keys[len(keys)/2:] { item := tr.Set(pair{key, key}) tr.sane() if item == nil { t.Fatal("expected not nil") } value := item.(pair).value if value == nil || value.(string) != key { t.Fatalf("expected '%v', got '%v'", key, value) } } // delete next half the items for _, key := range keys[len(keys)/2:] { item := tr.Delete(pair{key, nil}) tr.sane() if item == nil { t.Fatal("expected not nil") } value := item.(pair).value if value == nil || value.(string) != key { t.Fatalf("expected '%v', got '%v'", key, value) } } // check length if tr.Len() != 0 { t.Fatalf("expected %v, got %v", 0, tr.Len()) } // do some stuff on an empty tree item = tr.Get(pair{keys[0], nil}) if item != nil { t.Fatal("expected nil") } tr.Ascend(nil, func(item interface{}) bool { t.Fatal("should not be reached") return true }) tr.Descend(nil, func(item interface{}) bool { t.Fatal("should not be reached") return true }) item = tr.Delete(pair{"invalid", nil}) tr.sane() if item != nil { t.Fatal("expected nil") } } func BenchmarkTidwallSequentialSet(b *testing.B) { tr := New(intLess) keys := rand.Perm(b.N) sort.Ints(keys) b.ResetTimer() for i := 0; i < b.N; i++ { tr.Set(keys[i]) } } func BenchmarkTidwallSequentialGet(b *testing.B) { tr := New(intLess) keys := rand.Perm(b.N) sort.Ints(keys) for i := 0; i < b.N; i++ { tr.Set(keys[i]) } b.ResetTimer() for i := 0; i < b.N; i++ { tr.Get(keys[i]) } } func BenchmarkTidwallRandomSet(b *testing.B) { tr := New(intLess) keys := rand.Perm(b.N) b.ResetTimer() for i := 0; i < b.N; i++ { tr.Set(keys[i]) } } func BenchmarkTidwallRandomGet(b *testing.B) { tr := New(intLess) keys := rand.Perm(b.N) for i := 0; i < b.N; i++ { tr.Set(keys[i]) } b.ResetTimer() for i := 0; i < b.N; i++ { tr.Get(keys[i]) } } func BenchmarkTidwallSequentialSetHint(b *testing.B) { tr := New(intLess) keys := rand.Perm(b.N) sort.Ints(keys) b.ResetTimer() var hint PathHint for i := 0; i < b.N; i++ { tr.SetHint(keys[i], &hint) } } func BenchmarkTidwallSequentialGetHint(b *testing.B) { // println("\n----------------------------------------------------------------") tr := New(intLess) keys := rand.Perm(b.N) sort.Ints(keys) for i := 0; i < b.N; i++ { tr.Set(keys[i]) } b.ResetTimer() var hint PathHint for i := 0; i < b.N; i++ { tr.GetHint(keys[i], &hint) // fmt.Printf("%064b\n", hint) } } func BenchmarkTidwallLoad(b *testing.B) { tr := New(intLess) keys := rand.Perm(b.N) sort.Ints(keys) b.ResetTimer() for i := 0; i < b.N; i++ { tr.Load(keys[i]) } } func TestBTreeOne(t *testing.T) { tr := New(pairLess) tr.Set(pair{"1", "1"}) tr.Delete(pair{"1", nil}) tr.Set(pair{"1", "1"}) tr.Delete(pair{"1", nil}) tr.Set(pair{"1", "1"}) tr.Delete(pair{"1", nil}) } func TestBTree256(t *testing.T) { tr := New(pairLess) var n int for j := 0; j < 2; j++ { for _, i := range rand.Perm(256) { tr.Set(pair{fmt.Sprintf("%d", i), i}) n++ if tr.Len() != n { t.Fatalf("expected 256, got %d", n) } } for _, i := range rand.Perm(256) { item := tr.Get(pair{fmt.Sprintf("%d", i), nil}) if item == nil { t.Fatal("expected true") } if item.(pair).value.(int) != i { t.Fatalf("expected %d, got %d", i, item.(pair).value.(int)) } } for _, i := range rand.Perm(256) { tr.Delete(pair{fmt.Sprintf("%d", i), nil}) n-- if tr.Len() != n { t.Fatalf("expected 256, got %d", n) } } for _, i := range rand.Perm(256) { item := tr.Get(pair{fmt.Sprintf("%d", i), nil}) if item != nil { t.Fatal("expected nil") } } } } func shuffle(r *rand.Rand, keys []int) { for i := range keys { j := r.Intn(i + 1) keys[i], keys[j] = keys[j], keys[i] } } func intLess(a, b interface{}) bool { return a.(int) < b.(int) } func TestRandom(t *testing.T) { r := rand.New(rand.NewSource(seed)) N := 200000 keys := rand.Perm(N) func() { defer func() { msg := fmt.Sprint(recover()) if msg != "nil less" { t.Fatal("expected 'nil less' panic") } }() New(nil) t.Fatalf("reached invalid code") }() tr := New(intLess) checkSane := func() { // tr.sane() } checkSane() if tr.Min() != nil { t.Fatalf("expected nil") } if tr.Max() != nil { t.Fatalf("expected nil") } if tr.PopMin() != nil { t.Fatalf("expected nil") } if tr.PopMax() != nil { t.Fatalf("expected nil") } if tr.Height() != 0 { t.Fatalf("expected 0, got %d", tr.Height()) } checkSane() func() { defer func() { msg := fmt.Sprint(recover()) if msg != "nil item" { t.Fatal("expected 'nil item' panic") } }() tr.Set(nil) t.Fatalf("reached invalid code") }() // keys = keys[:rand.Intn(len(keys))] shuffle(r, keys) for i := 0; i < len(keys); i++ { prev := tr.Set(keys[i]) checkSane() if prev != nil { t.Fatalf("expected nil") } if i%12345 == 0 { tr.sane() } } tr.sane() sort.Ints(keys) var n int tr.Ascend(nil, func(item interface{}) bool { n++ return false }) if n != 1 { t.Fatalf("expected 1, got %d", n) } n = 0 tr.Ascend(nil, func(item interface{}) bool { if item != keys[n] { t.Fatalf("expected %d, got %d", keys[n], item) } n++ return true }) if n != len(keys) { t.Fatalf("expected %d, got %d", len(keys), n) } if tr.Len() != len(keys) { t.Fatalf("expected %d, got %d", tr.Len(), len(keys)) } n = 0 tr.Descend(nil, func(item interface{}) bool { n++ return false }) if n != 1 { t.Fatalf("expected 1, got %d", n) } n = 0 tr.Descend(nil, func(item interface{}) bool { if item != keys[len(keys)-n-1] { t.Fatalf("expected %d, got %d", keys[len(keys)-n-1], item) } n++ return true }) if n != len(keys) { t.Fatalf("expected %d, got %d", len(keys), n) } if tr.Len() != len(keys) { t.Fatalf("expected %d, got %d", tr.Len(), len(keys)) } checkSane() // tr.deepPrint() n = 0 for i := 0; i < 1000; i++ { n := 0 tr.Ascend(nil, func(item interface{}) bool { if n == i { return false } n++ return true }) if n != i { t.Fatalf("expected %d, got %d", i, n) } } n = 0 for i := 0; i < 1000; i++ { n = 0 tr.Descend(nil, func(item interface{}) bool { if n == i { return false } n++ return true }) if n != i { t.Fatalf("expected %d, got %d", i, n) } } sort.Ints(keys) for i := 0; i < len(keys); i++ { var res interface{} var j int tr.Ascend(keys[i], func(item interface{}) bool { if j == 0 { res = item } j++ return j == i%500 }) if res != keys[i] { t.Fatal("not equal") } } for i := len(keys) - 1; i >= 0; i-- { var res interface{} var j int tr.Descend(keys[i], func(item interface{}) bool { if j == 0 { res = item } j++ return j == i%500 }) if res != keys[i] { t.Fatal("not equal") } } if tr.Height() == 0 { t.Fatalf("expected non-zero") } if tr.Min() != keys[0] { t.Fatalf("expected '%v', got '%v'", keys[0], tr.Min()) } if tr.Max() != keys[len(keys)-1] { t.Fatalf("expected '%v', got '%v'", keys[len(keys)-1], tr.Max()) } min := tr.PopMin() checkSane() if min != keys[0] { t.Fatalf("expected '%v', got '%v'", keys[0], min) } max := tr.PopMax() checkSane() if max != keys[len(keys)-1] { t.Fatalf("expected '%v', got '%v'", keys[len(keys)-1], max) } tr.Set(min) tr.Set(max) shuffle(r, keys) var hint PathHint for i := 0; i < len(keys); i++ { prev := tr.Get(keys[i]) if prev == nil || prev.(int) != keys[i] { t.Fatalf("expected '%v', got '%v'", keys[i], prev) } prev = tr.GetHint(keys[i], &hint) if prev == nil || prev.(int) != keys[i] { t.Fatalf("expected '%v', got '%v'", keys[i], prev) } } sort.Ints(keys) for i := 0; i < len(keys); i++ { item := tr.PopMin() if item != keys[i] { t.Fatalf("expected '%v', got '%v'", keys[i], item) } } for i := 0; i < len(keys); i++ { prev := tr.Set(keys[i]) if prev != nil { t.Fatalf("expected nil") } } for i := len(keys) - 1; i >= 0; i-- { item := tr.PopMax() if item != keys[i] { t.Fatalf("expected '%v', got '%v'", keys[i], item) } } for i := 0; i < len(keys); i++ { prev := tr.Set(keys[i]) if prev != nil { t.Fatalf("expected nil") } } if tr.Delete(nil) != nil { t.Fatal("expected nil") } checkSane() shuffle(r, keys) item := tr.Delete(keys[len(keys)/2]) checkSane() if item != keys[len(keys)/2] { t.Fatalf("expected '%v', got '%v'", keys[len(keys)/2], item) } item2 := tr.Delete(keys[len(keys)/2]) checkSane() if item2 != nil { t.Fatalf("expected '%v', got '%v'", nil, item2) } tr.Set(item) checkSane() for i := 0; i < len(keys); i++ { prev := tr.Delete(keys[i]) checkSane() if prev == nil || prev.(int) != keys[i] { t.Fatalf("expected '%v', got '%v'", keys[i], prev) } prev = tr.Get(keys[i]) if prev != nil { t.Fatalf("expected nil") } prev = tr.GetHint(keys[i], &hint) if prev != nil { t.Fatalf("expected nil") } if i%12345 == 0 { tr.sane() } } if tr.Height() != 0 { t.Fatalf("expected 0, got %d", tr.Height()) } shuffle(r, keys) for i := 0; i < len(keys); i++ { prev := tr.Load(keys[i]) checkSane() if prev != nil { t.Fatalf("expected nil") } } for i := 0; i < len(keys); i++ { prev := tr.Get(keys[i]) if prev == nil || prev.(int) != keys[i] { t.Fatalf("expected '%v', got '%v'", keys[i], prev) } } shuffle(r, keys) for i := 0; i < len(keys); i++ { prev := tr.Delete(keys[i]) checkSane() if prev == nil || prev.(int) != keys[i] { t.Fatalf("expected '%v', got '%v'", keys[i], prev) } prev = tr.Get(keys[i]) if prev != nil { t.Fatalf("expected nil") } } sort.Ints(keys) for i := 0; i < len(keys); i++ { prev := tr.Load(keys[i]) checkSane() if prev != nil { t.Fatalf("expected nil") } } shuffle(r, keys) item = tr.Load(keys[0]) checkSane() if item != keys[0] { t.Fatalf("expected '%v', got '%v'", keys[0], item) } func() { defer func() { msg := fmt.Sprint(recover()) if msg != "nil item" { t.Fatal("expected 'nil item' panic") } }() tr.Load(nil) checkSane() t.Fatalf("reached invalid code") }() } func (n *node) saneheight(height, maxheight int) bool { if n.leaf { if height != maxheight { return false } } else { i := int16(0) for ; i < n.numItems; i++ { if !n.children[i].saneheight(height+1, maxheight) { return false } } if !n.children[i].saneheight(height+1, maxheight) { return false } } return true } // btree_saneheight returns true if the height of all leaves match the height // of the btree. func (tr *BTree) saneheight() bool { height := tr.Height() if tr.root != nil { if height == 0 { return false } return tr.root.saneheight(1, height) } return height == 0 } func (n *node) deepcount() int { count := int(n.numItems) if !n.leaf { for i := int16(0); i <= n.numItems; i++ { count += n.children[i].deepcount() } } return count } // btree_deepcount returns the number of items in the btree. func (tr *BTree) deepcount() int { if tr.root != nil { return tr.root.deepcount() } return 0 } func (tr *BTree) nodesaneprops(n *node, height int) bool { if height == 1 { if n.numItems < 1 || n.numItems > maxItems { return false } } else { if n.numItems < minItems || n.numItems > maxItems { return false } } if !n.leaf { for i := int16(0); i < n.numItems; i++ { if !tr.nodesaneprops(n.children[i], height+1) { return false } } if !tr.nodesaneprops(n.children[n.numItems], height+1) { return false } } return true } func (tr *BTree) saneprops() bool { if tr.root != nil { return tr.nodesaneprops(tr.root, 1) } return true } func (tr *BTree) saneorder() bool { var last interface{} var count int var bad bool tr.Walk(func(items []interface{}) { for _, item := range items { if bad { return } if last != nil { if !tr.less(last, item) { bad = true return } } last = item count++ } }) return !bad && count == tr.length } // btree_sane returns true if the entire btree and every node are valid. // - height of all leaves are the equal to the btree height. // - deep count matches the btree count. // - all nodes have the correct number of items and counts. // - all items are in order. func (tr *BTree) sane() { if tr == nil { panic("nil tree") } if !tr.saneheight() { panic("!sane-height") } if tr.Len() != tr.length || tr.deepcount() != tr.length { panic("!sane-count") } if !tr.saneprops() { panic("!sane-props") } if !tr.saneorder() { panic("!sane-order") } }