From f1a3c28208bc796d642dce167fc2e6e762dcd285 Mon Sep 17 00:00:00 2001 From: Michal-Leszczynski <2000michal@wp.pl> Date: Tue, 29 Mar 2022 22:25:21 +0200 Subject: [PATCH 1/2] Generic approach to B-tree implementation Generic approach: goos: linux goarch: amd64 pkg: github.com/google/btree cpu: Intel(R) Core(TM) i5-6500 CPU @ 3.20GHz BenchmarkInsert-4 5942443 203.5 ns/op 19 B/op 0 allocs/op BenchmarkSeek-4 8253608 140.2 ns/op 0 B/op 0 allocs/op BenchmarkDeleteInsert-4 2772153 432.4 ns/op 0 B/op 0 allocs/op BenchmarkDeleteInsertCloneOnce-4 2724934 429.2 ns/op 0 B/op 0 allocs/op BenchmarkDeleteInsertCloneEachTime-4 881670 1337 ns/op 1886 B/op 11 allocs/op BenchmarkDelete-4 5176340 229.0 ns/op 0 B/op 0 allocs/op BenchmarkGet-4 6767662 180.4 ns/op 0 B/op 0 allocs/op BenchmarkGetCloneEachTime-4 4545211 268.4 ns/op 48 B/op 3 allocs/op BenchmarkAscend-4 22772 52821 ns/op 0 B/op 0 allocs/op BenchmarkDescend-4 23056 52434 ns/op 0 B/op 0 allocs/op BenchmarkAscendRange-4 13488 87513 ns/op 0 B/op 0 allocs/op BenchmarkDescendRange-4 10000 118079 ns/op 0 B/op 0 allocs/op BenchmarkAscendGreaterOrEqual-4 19116 62130 ns/op 0 B/op 0 allocs/op BenchmarkDescendLessOrEqual-4 12943 92503 ns/op 0 B/op 0 allocs/op BenchmarkDeleteAndRestore/CopyBigFreeList-4 205 5846645 ns/op 141780 B/op 13 allocs/op BenchmarkDeleteAndRestore/Copy-4 187 6127481 ns/op 453648 B/op 1169 allocs/op BenchmarkDeleteAndRestore/ClearBigFreelist-4 349 3452555 ns/op 783 B/op 2 allocs/op BenchmarkDeleteAndRestore/Clear-4 339 3548745 ns/op 288473 B/op 1061 allocs/op PASS ok github.com/google/btree 34.314s Interface approach: goos: linux goarch: amd64 pkg: github.com/google/btree cpu: Intel(R) Core(TM) i5-6500 CPU @ 3.20GHz BenchmarkInsert-4 4931660 236.1 ns/op 36 B/op 0 allocs/op BenchmarkSeek-4 6406713 176.9 ns/op 7 B/op 0 allocs/op BenchmarkDeleteInsert-4 2522662 489.5 ns/op 0 B/op 0 allocs/op BenchmarkDeleteInsertCloneOnce-4 2496774 478.5 ns/op 0 B/op 0 allocs/op BenchmarkDeleteInsertCloneEachTime-4 546816 2186 ns/op 2992 B/op 11 allocs/op BenchmarkDelete-4 4529532 258.3 ns/op 0 B/op 0 allocs/op BenchmarkGet-4 5962708 204.7 ns/op 0 B/op 0 allocs/op BenchmarkGetCloneEachTime-4 4169655 292.0 ns/op 48 B/op 3 allocs/op BenchmarkAscend-4 16887 68180 ns/op 0 B/op 0 allocs/op BenchmarkDescend-4 17565 69563 ns/op 0 B/op 0 allocs/op BenchmarkAscendRange-4 10000 111384 ns/op 0 B/op 0 allocs/op BenchmarkDescendRange-4 7618 145634 ns/op 0 B/op 0 allocs/op BenchmarkAscendGreaterOrEqual-4 15567 76653 ns/op 0 B/op 0 allocs/op BenchmarkDescendLessOrEqual-4 9375 119378 ns/op 0 B/op 0 allocs/op BenchmarkDeleteAndRestore/CopyBigFreeList-4 182 6649303 ns/op 274293 B/op 14 allocs/op BenchmarkDeleteAndRestore/Copy-4 174 6796221 ns/op 860600 B/op 1152 allocs/op BenchmarkDeleteAndRestore/ClearBigFreelist-4 307 3885217 ns/op 855 B/op 2 allocs/op BenchmarkDeleteAndRestore/Clear-4 285 4168013 ns/op 538771 B/op 1042 allocs/op PASS ok github.com/google/btree 35.118s --- btree.go | 281 ++++++++++++++++++++++++++------------------------ btree_mem.go | 3 +- btree_test.go | 265 +++++++++++++++++++++++++---------------------- go.mod | 4 +- 4 files changed, 292 insertions(+), 261 deletions(-) diff --git a/btree.go b/btree.go index b83acdb..2951e39 100644 --- a/btree.go +++ b/btree.go @@ -56,45 +56,40 @@ import ( ) // Item represents a single object in the tree. -type Item interface { +type Item[T any] interface { // Less tests whether the current item is less than the given argument. // // This must provide a strict weak ordering. // If !a.Less(b) && !b.Less(a), we treat this to mean a == b (i.e. we can only // hold one of either a or b in the tree). - Less(than Item) bool + Less(than T) bool } const ( DefaultFreeListSize = 32 ) -var ( - nilItems = make(items, 16) - nilChildren = make(children, 16) -) - // FreeList represents a free list of btree nodes. By default each // BTree has its own FreeList, but multiple BTrees can share the same // FreeList. // Two Btrees using the same freelist are safe for concurrent write access. -type FreeList struct { +type FreeList[T Item[T]] struct { mu sync.Mutex - freelist []*node + freelist []*node[T] } // NewFreeList creates a new free list. // size is the maximum size of the returned free list. -func NewFreeList(size int) *FreeList { - return &FreeList{freelist: make([]*node, 0, size)} +func NewFreeList[T Item[T]](size int) *FreeList[T] { + return &FreeList[T]{freelist: make([]*node[T], 0, size)} } -func (f *FreeList) newNode() (n *node) { +func (f *FreeList[T]) newNode() (n *node[T]) { f.mu.Lock() index := len(f.freelist) - 1 if index < 0 { f.mu.Unlock() - return new(node) + return new(node[T]) } n = f.freelist[index] f.freelist[index] = nil @@ -105,7 +100,7 @@ func (f *FreeList) newNode() (n *node) { // freeNode adds the given node to the list, returning true if it was added // and false if it was discarded. -func (f *FreeList) freeNode(n *node) (out bool) { +func (f *FreeList[T]) freeNode(n *node[T]) (out bool) { f.mu.Lock() if len(f.freelist) < cap(f.freelist) { f.freelist = append(f.freelist, n) @@ -118,34 +113,35 @@ func (f *FreeList) freeNode(n *node) (out bool) { // ItemIterator allows callers of Ascend* to iterate in-order over portions of // the tree. When this function returns false, iteration will stop and the // associated Ascend* function will immediately return. -type ItemIterator func(i Item) bool +type ItemIterator[T Item[T]] func(i T) bool // New creates a new B-Tree with the given degree. // // New(2), for example, will create a 2-3-4 tree (each node contains 1-3 items // and 2-4 children). -func New(degree int) *BTree { - return NewWithFreeList(degree, NewFreeList(DefaultFreeListSize)) +func New[T Item[T]](degree int) *BTree[T] { + return NewWithFreeList[T](degree, NewFreeList[T](DefaultFreeListSize)) } // NewWithFreeList creates a new B-Tree that uses the given node free list. -func NewWithFreeList(degree int, f *FreeList) *BTree { +func NewWithFreeList[T Item[T]](degree int, f *FreeList[T]) *BTree[T] { if degree <= 1 { panic("bad degree") } - return &BTree{ + return &BTree[T]{ degree: degree, - cow: ©OnWriteContext{freelist: f}, + cow: ©OnWriteContext[T]{freelist: f}, } } // items stores items in a node. -type items []Item +type items[T Item[T]] []T // insertAt inserts a value into the given index, pushing all subsequent values // forward. -func (s *items) insertAt(index int, item Item) { - *s = append(*s, nil) +func (s *items[T]) insertAt(index int, item T) { + var x T + *s = append(*s, x) if index < len(*s) { copy((*s)[index+1:], (*s)[index:]) } @@ -154,37 +150,44 @@ func (s *items) insertAt(index int, item Item) { // removeAt removes a value at a given index, pulling all subsequent values // back. -func (s *items) removeAt(index int) Item { +func (s *items[T]) removeAt(index int) T { + var x T item := (*s)[index] copy((*s)[index:], (*s)[index+1:]) - (*s)[len(*s)-1] = nil + (*s)[len(*s)-1] = x *s = (*s)[:len(*s)-1] return item } // pop removes and returns the last element in the list. -func (s *items) pop() (out Item) { +func (s *items[T]) pop() (out T) { + var x T index := len(*s) - 1 out = (*s)[index] - (*s)[index] = nil + (*s)[index] = x *s = (*s)[:index] return } // truncate truncates this instance at index so that it contains only the // first index items. index must be less than or equal to length. -func (s *items) truncate(index int) { - var toClear items +func (s *items[T]) truncate(index int) { + var i int + var x T + var toClear items[T] *s, toClear = (*s)[:index], (*s)[index:] for len(toClear) > 0 { - toClear = toClear[copy(toClear, nilItems):] + for i = 0; i < 16 && i < len(toClear); i++ { + toClear[i] = x + } + toClear = toClear[i:] } } // find returns the index where the given item should be inserted into this // list. 'found' is true if the item already exists in the list at the given // index. -func (s items) find(item Item) (index int, found bool) { +func (s items[T]) find(item T) (index int, found bool) { i := sort.Search(len(s), func(i int) bool { return item.Less(s[i]) }) @@ -195,11 +198,11 @@ func (s items) find(item Item) (index int, found bool) { } // children stores child nodes in a node. -type children []*node +type children[T Item[T]] []*node[T] // insertAt inserts a value into the given index, pushing all subsequent values // forward. -func (s *children) insertAt(index int, n *node) { +func (s *children[T]) insertAt(index int, n *node[T]) { *s = append(*s, nil) if index < len(*s) { copy((*s)[index+1:], (*s)[index:]) @@ -209,7 +212,7 @@ func (s *children) insertAt(index int, n *node) { // removeAt removes a value at a given index, pulling all subsequent values // back. -func (s *children) removeAt(index int) *node { +func (s *children[T]) removeAt(index int) *node[T] { n := (*s)[index] copy((*s)[index:], (*s)[index+1:]) (*s)[len(*s)-1] = nil @@ -218,7 +221,7 @@ func (s *children) removeAt(index int) *node { } // pop removes and returns the last element in the list. -func (s *children) pop() (out *node) { +func (s *children[T]) pop() (out *node[T]) { index := len(*s) - 1 out = (*s)[index] (*s)[index] = nil @@ -228,11 +231,15 @@ func (s *children) pop() (out *node) { // truncate truncates this instance at index so that it contains only the // first index children. index must be less than or equal to length. -func (s *children) truncate(index int) { - var toClear children +func (s *children[T]) truncate(index int) { + var i int + var toClear children[T] *s, toClear = (*s)[:index], (*s)[index:] for len(toClear) > 0 { - toClear = toClear[copy(toClear, nilChildren):] + for i = 0; i < 16 && i < len(toClear); i++ { + toClear[i] = nil + } + toClear = toClear[i:] } } @@ -241,13 +248,13 @@ func (s *children) truncate(index int) { // It must at all times maintain the invariant that either // * len(children) == 0, len(items) unconstrained // * len(children) == len(items) + 1 -type node struct { - items items - children children - cow *copyOnWriteContext +type node[T Item[T]] struct { + items items[T] + children children[T] + cow *copyOnWriteContext[T] } -func (n *node) mutableFor(cow *copyOnWriteContext) *node { +func (n *node[T]) mutableFor(cow *copyOnWriteContext[T]) *node[T] { if n.cow == cow { return n } @@ -255,20 +262,20 @@ func (n *node) mutableFor(cow *copyOnWriteContext) *node { if cap(out.items) >= len(n.items) { out.items = out.items[:len(n.items)] } else { - out.items = make(items, len(n.items), cap(n.items)) + out.items = make(items[T], len(n.items), cap(n.items)) } copy(out.items, n.items) // Copy children if cap(out.children) >= len(n.children) { out.children = out.children[:len(n.children)] } else { - out.children = make(children, len(n.children), cap(n.children)) + out.children = make(children[T], len(n.children), cap(n.children)) } copy(out.children, n.children) return out } -func (n *node) mutableChild(i int) *node { +func (n *node[T]) mutableChild(i int) *node[T] { c := n.children[i].mutableFor(n.cow) n.children[i] = c return c @@ -277,7 +284,7 @@ func (n *node) mutableChild(i int) *node { // split splits the given node at the given index. The current node shrinks, // and this function returns the item that existed at that index and a new node // containing all items/children after it. -func (n *node) split(i int) (Item, *node) { +func (n *node[T]) split(i int) (T, *node[T]) { item := n.items[i] next := n.cow.newNode() next.items = append(next.items, n.items[i+1:]...) @@ -291,7 +298,7 @@ func (n *node) split(i int) (Item, *node) { // maybeSplitChild checks if a child should be split, and if so splits it. // Returns whether or not a split occurred. -func (n *node) maybeSplitChild(i, maxItems int) bool { +func (n *node[T]) maybeSplitChild(i, maxItems int) bool { if len(n.children[i].items) < maxItems { return false } @@ -305,16 +312,17 @@ func (n *node) maybeSplitChild(i, maxItems int) bool { // insert inserts an item into the subtree rooted at this node, making sure // no nodes in the subtree exceed maxItems items. Should an equivalent item be // be found/replaced by insert, it will be returned. -func (n *node) insert(item Item, maxItems int) Item { +func (n *node[T]) insert(item T, maxItems int) (T, bool) { + var x T i, found := n.items.find(item) if found { out := n.items[i] n.items[i] = item - return out + return out, true } if len(n.children) == 0 { n.items.insertAt(i, item) - return nil + return x, false } if n.maybeSplitChild(i, maxItems) { inTree := n.items[i] @@ -326,49 +334,52 @@ func (n *node) insert(item Item, maxItems int) Item { default: out := n.items[i] n.items[i] = item - return out + return out, true } } return n.mutableChild(i).insert(item, maxItems) } // get finds the given key in the subtree and returns it. -func (n *node) get(key Item) Item { +func (n *node[T]) get(key T) (T, bool) { + var x T i, found := n.items.find(key) if found { - return n.items[i] + return n.items[i], true } else if len(n.children) > 0 { return n.children[i].get(key) } - return nil + return x, false } // min returns the first item in the subtree. -func min(n *node) Item { +func min[T Item[T]](n *node[T]) (T, bool) { + var x T if n == nil { - return nil + return x, false } for len(n.children) > 0 { n = n.children[0] } if len(n.items) == 0 { - return nil + return x, false } - return n.items[0] + return n.items[0], true } // max returns the last item in the subtree. -func max(n *node) Item { +func max[T Item[T]](n *node[T]) (T, bool) { + var x T if n == nil { - return nil + return x, false } for len(n.children) > 0 { n = n.children[len(n.children)-1] } if len(n.items) == 0 { - return nil + return x, false } - return n.items[len(n.items)-1] + return n.items[len(n.items)-1], true } // toRemove details what item to remove in a node.remove call. @@ -381,27 +392,28 @@ const ( ) // remove removes an item from the subtree rooted at this node. -func (n *node) remove(item Item, minItems int, typ toRemove) Item { +func (n *node[T]) remove(item T, minItems int, typ toRemove) (T, bool) { + var x T var i int var found bool switch typ { case removeMax: if len(n.children) == 0 { - return n.items.pop() + return n.items.pop(), true } i = len(n.items) case removeMin: if len(n.children) == 0 { - return n.items.removeAt(0) + return n.items.removeAt(0), true } i = 0 case removeItem: i, found = n.items.find(item) if len(n.children) == 0 { if found { - return n.items.removeAt(i) + return n.items.removeAt(i), true } - return nil + return x, false } default: panic("invalid type") @@ -421,8 +433,8 @@ func (n *node) remove(item Item, minItems int, typ toRemove) Item { // We use our special-case 'remove' call with typ=maxItem to pull the // predecessor of item i (the rightmost leaf of our immediate left child) // and set it into where we pulled the item from. - n.items[i] = child.remove(nil, minItems, removeMax) - return out + n.items[i], _ = child.remove(x, minItems, removeMax) + return out, true } // Final recursive call. Once we're here, we know that the item isn't in this // node and that the child is big enough to remove from. @@ -448,7 +460,7 @@ func (n *node) remove(item Item, minItems int, typ toRemove) Item { // We then simply redo our remove call, and the second time (regardless of // whether we're in case 1 or 2), we'll have enough items and can guarantee // that we hit case A. -func (n *node) growChildAndRemove(i int, item Item, minItems int, typ toRemove) Item { +func (n *node[T]) growChildAndRemove(i int, item T, minItems int, typ toRemove) (T, bool) { if i > 0 && len(n.children[i-1].items) > minItems { // Steal from left child child := n.mutableChild(i) @@ -499,13 +511,13 @@ const ( // will force the iterator to include the first item when it equals 'start', // thus creating a "greaterOrEqual" or "lessThanEqual" rather than just a // "greaterThan" or "lessThan" queries. -func (n *node) iterate(dir direction, start, stop Item, includeStart bool, hit bool, iter ItemIterator) (bool, bool) { +func (n *node[T]) iterate(dir direction, start, stop *T, includeStart bool, hit bool, iter ItemIterator[T]) (bool, bool) { var ok, found bool var index int switch dir { case ascend: if start != nil { - index, _ = n.items.find(start) + index, _ = n.items.find(*start) } for i := index; i < len(n.items); i++ { if len(n.children) > 0 { @@ -513,12 +525,12 @@ func (n *node) iterate(dir direction, start, stop Item, includeStart bool, hit b return hit, false } } - if !includeStart && !hit && start != nil && !start.Less(n.items[i]) { + if !includeStart && !hit && start != nil && !(*start).Less(n.items[i]) { hit = true continue } hit = true - if stop != nil && !n.items[i].Less(stop) { + if stop != nil && !n.items[i].Less(*stop) { return hit, false } if !iter(n.items[i]) { @@ -532,7 +544,7 @@ func (n *node) iterate(dir direction, start, stop Item, includeStart bool, hit b } case descend: if start != nil { - index, found = n.items.find(start) + index, found = n.items.find(*start) if !found { index = index - 1 } @@ -540,8 +552,8 @@ func (n *node) iterate(dir direction, start, stop Item, includeStart bool, hit b index = len(n.items) - 1 } for i := index; i >= 0; i-- { - if start != nil && !n.items[i].Less(start) { - if !includeStart || hit || start.Less(n.items[i]) { + if start != nil && !n.items[i].Less(*start) { + if !includeStart || hit || (*start).Less(n.items[i]) { continue } } @@ -550,7 +562,7 @@ func (n *node) iterate(dir direction, start, stop Item, includeStart bool, hit b return hit, false } } - if stop != nil && !stop.Less(n.items[i]) { + if stop != nil && !(*stop).Less(n.items[i]) { return hit, false // continue } hit = true @@ -568,7 +580,7 @@ func (n *node) iterate(dir direction, start, stop Item, includeStart bool, hit b } // Used for testing/debugging purposes. -func (n *node) print(w io.Writer, level int) { +func (n *node[T]) print(w io.Writer, level int) { fmt.Fprintf(w, "%sNODE:%v\n", strings.Repeat(" ", level), n.items) for _, c := range n.children { c.print(w, level+1) @@ -582,11 +594,11 @@ func (n *node) print(w io.Writer, level int) { // // Write operations are not safe for concurrent mutation by multiple // goroutines, but Read operations are. -type BTree struct { +type BTree[T Item[T]] struct { degree int length int - root *node - cow *copyOnWriteContext + root *node[T] + cow *copyOnWriteContext[T] } // copyOnWriteContext pointers determine node ownership... a tree with a write @@ -603,8 +615,8 @@ type BTree struct { // tree's context, that node is modifiable in place. Children of that node may // not share context, but before we descend into them, we'll make a mutable // copy. -type copyOnWriteContext struct { - freelist *FreeList +type copyOnWriteContext[T Item[T]] struct { + freelist *FreeList[T] } // Clone clones the btree, lazily. Clone should not be called concurrently, @@ -618,7 +630,7 @@ type copyOnWriteContext struct { // will initially experience minor slow-downs caused by additional allocs and // copies due to the aforementioned copy-on-write logic, but should converge to // the original performance characteristics of the original tree. -func (t *BTree) Clone() (t2 *BTree) { +func (t *BTree[T]) Clone() (t2 *BTree[T]) { // Create two entirely new copy-on-write contexts. // This operation effectively creates three trees: // the original, shared nodes (old b.cow) @@ -632,17 +644,17 @@ func (t *BTree) Clone() (t2 *BTree) { } // maxItems returns the max number of items to allow per node. -func (t *BTree) maxItems() int { +func (t *BTree[T]) maxItems() int { return t.degree*2 - 1 } // minItems returns the min number of items to allow per node (ignored for the // root node). -func (t *BTree) minItems() int { +func (t *BTree[T]) minItems() int { return t.degree - 1 } -func (c *copyOnWriteContext) newNode() (n *node) { +func (c *copyOnWriteContext[T]) newNode() (n *node[T]) { n = c.freelist.newNode() n.cow = c return @@ -659,7 +671,7 @@ const ( // freeNode frees a node within a given COW context, if it's owned by that // context. It returns what happened to the node (see freeType const // documentation). -func (c *copyOnWriteContext) freeNode(n *node) freeType { +func (c *copyOnWriteContext[T]) freeNode(n *node[T]) freeType { if n.cow == c { // clear to allow GC n.items.truncate(0) @@ -680,15 +692,13 @@ func (c *copyOnWriteContext) freeNode(n *node) freeType { // Otherwise, nil is returned. // // nil cannot be added to the tree (will panic). -func (t *BTree) ReplaceOrInsert(item Item) Item { - if item == nil { - panic("nil item being added to BTree") - } +func (t *BTree[T]) ReplaceOrInsert(item T) (T, bool) { + var x T if t.root == nil { t.root = t.cow.newNode() t.root.items = append(t.root.items, item) t.length++ - return nil + return x, false } else { t.root = t.root.mutableFor(t.cow) if len(t.root.items) >= t.maxItems() { @@ -699,78 +709,81 @@ func (t *BTree) ReplaceOrInsert(item Item) Item { t.root.children = append(t.root.children, oldroot, second) } } - out := t.root.insert(item, t.maxItems()) - if out == nil { + out, found := t.root.insert(item, t.maxItems()) + if !found { t.length++ } - return out + return out, found } // Delete removes an item equal to the passed in item from the tree, returning // it. If no such item exists, returns nil. -func (t *BTree) Delete(item Item) Item { +func (t *BTree[T]) Delete(item T) (T, bool) { return t.deleteItem(item, removeItem) } // DeleteMin removes the smallest item in the tree and returns it. // If no such item exists, returns nil. -func (t *BTree) DeleteMin() Item { - return t.deleteItem(nil, removeMin) +func (t *BTree[T]) DeleteMin() (T, bool) { + var x T + return t.deleteItem(x, removeMin) } // DeleteMax removes the largest item in the tree and returns it. // If no such item exists, returns nil. -func (t *BTree) DeleteMax() Item { - return t.deleteItem(nil, removeMax) +func (t *BTree[T]) DeleteMax() (T, bool) { + var x T + return t.deleteItem(x, removeMax) } -func (t *BTree) deleteItem(item Item, typ toRemove) Item { +func (t *BTree[T]) deleteItem(item T, typ toRemove) (T, bool) { + var x T if t.root == nil || len(t.root.items) == 0 { - return nil + return x, false } t.root = t.root.mutableFor(t.cow) - out := t.root.remove(item, t.minItems(), typ) + out, found := t.root.remove(item, t.minItems(), typ) if len(t.root.items) == 0 && len(t.root.children) > 0 { oldroot := t.root t.root = t.root.children[0] t.cow.freeNode(oldroot) } - if out != nil { + if found { t.length-- } - return out + return out, found } // AscendRange calls the iterator for every value in the tree within the range // [greaterOrEqual, lessThan), until iterator returns false. -func (t *BTree) AscendRange(greaterOrEqual, lessThan Item, iterator ItemIterator) { +func (t *BTree[T]) AscendRange(greaterOrEqual, lessThan T, iterator ItemIterator[T]) { if t.root == nil { return } - t.root.iterate(ascend, greaterOrEqual, lessThan, true, false, iterator) + t.root.iterate(ascend, &greaterOrEqual, &lessThan, true, false, iterator) } // AscendLessThan calls the iterator for every value in the tree within the range // [first, pivot), until iterator returns false. -func (t *BTree) AscendLessThan(pivot Item, iterator ItemIterator) { +func (t *BTree[T]) AscendLessThan(pivot T, iterator ItemIterator[T]) { if t.root == nil { return } - t.root.iterate(ascend, nil, pivot, false, false, iterator) + t.root.iterate(ascend, nil, &pivot, false, false, iterator) } // AscendGreaterOrEqual calls the iterator for every value in the tree within // the range [pivot, last], until iterator returns false. -func (t *BTree) AscendGreaterOrEqual(pivot Item, iterator ItemIterator) { +func (t *BTree[T]) AscendGreaterOrEqual(pivot T, iterator ItemIterator[T]) { if t.root == nil { return } - t.root.iterate(ascend, pivot, nil, true, false, iterator) + t.root.iterate(ascend, &pivot, nil, true, false, iterator) } // Ascend calls the iterator for every value in the tree within the range // [first, last], until iterator returns false. -func (t *BTree) Ascend(iterator ItemIterator) { +func (t *BTree[T]) Ascend(iterator ItemIterator[T]) { if t.root == nil { return } @@ -779,34 +792,34 @@ func (t *BTree) Ascend(iterator ItemIterator) { // DescendRange calls the iterator for every value in the tree within the range // [lessOrEqual, greaterThan), until iterator returns false. -func (t *BTree) DescendRange(lessOrEqual, greaterThan Item, iterator ItemIterator) { +func (t *BTree[T]) DescendRange(lessOrEqual, greaterThan T, iterator ItemIterator[T]) { if t.root == nil { return } - t.root.iterate(descend, lessOrEqual, greaterThan, true, false, iterator) + t.root.iterate(descend, &lessOrEqual, &greaterThan, true, false, iterator) } // DescendLessOrEqual calls the iterator for every value in the tree within the range // [pivot, first], until iterator returns false. -func (t *BTree) DescendLessOrEqual(pivot Item, iterator ItemIterator) { +func (t *BTree[T]) DescendLessOrEqual(pivot T, iterator ItemIterator[T]) { if t.root == nil { return } - t.root.iterate(descend, pivot, nil, true, false, iterator) + t.root.iterate(descend, &pivot, nil, true, false, iterator) } // DescendGreaterThan calls the iterator for every value in the tree within // the range [last, pivot), until iterator returns false. -func (t *BTree) DescendGreaterThan(pivot Item, iterator ItemIterator) { +func (t *BTree[T]) DescendGreaterThan(pivot T, iterator ItemIterator[T]) { if t.root == nil { return } - t.root.iterate(descend, nil, pivot, false, false, iterator) + t.root.iterate(descend, nil, &pivot, false, false, iterator) } // Descend calls the iterator for every value in the tree within the range // [last, first], until iterator returns false. -func (t *BTree) Descend(iterator ItemIterator) { +func (t *BTree[T]) Descend(iterator ItemIterator[T]) { if t.root == nil { return } @@ -815,30 +828,32 @@ func (t *BTree) Descend(iterator ItemIterator) { // Get looks for the key item in the tree, returning it. It returns nil if // unable to find that item. -func (t *BTree) Get(key Item) Item { +func (t *BTree[T]) Get(key T) (T, bool) { + var x T if t.root == nil { - return nil + return x, false } return t.root.get(key) } // Min returns the smallest item in the tree, or nil if the tree is empty. -func (t *BTree) Min() Item { +func (t *BTree[T]) Min() (T, bool) { return min(t.root) } // Max returns the largest item in the tree, or nil if the tree is empty. -func (t *BTree) Max() Item { +func (t *BTree[T]) Max() (T, bool) { return max(t.root) } // Has returns true if the given key is in the tree. -func (t *BTree) Has(key Item) bool { - return t.Get(key) != nil +func (t *BTree[T]) Has(key T) bool { + _, found := t.Get(key) + return found } // Len returns the number of items currently in the tree. -func (t *BTree) Len() int { +func (t *BTree[T]) Len() int { return t.length } @@ -862,7 +877,7 @@ func (t *BTree) Len() int { // O(tree size): when all nodes are owned by another tree, all nodes are // iterated over looking for nodes to add to the freelist, and due to // ownership, none are. -func (t *BTree) Clear(addNodesToFreelist bool) { +func (t *BTree[T]) Clear(addNodesToFreelist bool) { if t.root != nil && addNodesToFreelist { t.root.reset(t.cow) } @@ -872,7 +887,7 @@ func (t *BTree) Clear(addNodesToFreelist bool) { // reset returns a subtree to the freelist. It breaks out immediately if the // freelist is full, since the only benefit of iterating is to fill that // freelist up. Returns true if parent reset call should continue. -func (n *node) reset(c *copyOnWriteContext) bool { +func (n *node[T]) reset(c *copyOnWriteContext[T]) bool { for _, child := range n.children { if !child.reset(c) { return false @@ -885,6 +900,6 @@ func (n *node) reset(c *copyOnWriteContext) bool { type Int int // Less returns true if int(a) < int(b). -func (a Int) Less(b Item) bool { - return a < b.(Int) +func (a Int) Less(b Int) bool { + return a < b } diff --git a/btree_mem.go b/btree_mem.go index cb95b7f..3e7db2e 100644 --- a/btree_mem.go +++ b/btree_mem.go @@ -12,6 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. +//go:build ignore // +build ignore // This binary compares memory usage between btree and gollrb. @@ -54,7 +55,7 @@ func main() { } t = tr // keep it around } else { - tr := btree.New(*degree) + tr := btree.New[btree.Int](*degree) for _, v := range vals { tr.ReplaceOrInsert(btree.Int(v)) } diff --git a/btree_test.go b/btree_test.go index feed087..6692c21 100644 --- a/btree_test.go +++ b/btree_test.go @@ -32,7 +32,7 @@ func init() { } // perm returns a random permutation of n Int items in the range [0, n). -func perm(n int) (out []Item) { +func perm(n int) (out []Int) { for _, v := range rand.Perm(n) { out = append(out, Int(v)) } @@ -40,7 +40,7 @@ func perm(n int) (out []Item) { } // rang returns an ordered list of Int items in the range [0, n). -func rang(n int) (out []Item) { +func rang(n int) (out []Int) { for i := 0; i < n; i++ { out = append(out, Int(i)) } @@ -48,8 +48,8 @@ func rang(n int) (out []Item) { } // all extracts all items from a tree in order as a slice. -func all(t *BTree) (out []Item) { - t.Ascend(func(a Item) bool { +func all[T Item[T]](t *BTree[T]) (out []T) { + t.Ascend(func(a T) bool { out = append(out, a) return true }) @@ -57,7 +57,7 @@ func all(t *BTree) (out []Item) { } // rangerev returns a reversed ordered list of Int items in the range [0, n). -func rangrev(n int) (out []Item) { +func rangrev(n int) (out []Int) { for i := n - 1; i >= 0; i-- { out = append(out, Int(i)) } @@ -65,8 +65,8 @@ func rangrev(n int) (out []Item) { } // allrev extracts all items from a tree in reverse order as a slice. -func allrev(t *BTree) (out []Item) { - t.Descend(func(a Item) bool { +func allrev[T Item[T]](t *BTree[T]) (out []T) { + t.Descend(func(a T) bool { out = append(out, a) return true }) @@ -76,30 +76,30 @@ func allrev(t *BTree) (out []Item) { var btreeDegree = flag.Int("degree", 32, "B-Tree degree") func TestBTree(t *testing.T) { - tr := New(*btreeDegree) + tr := New[Int](*btreeDegree) const treeSize = 10000 for i := 0; i < 10; i++ { - if min := tr.Min(); min != nil { + if min, found := tr.Min(); found { t.Fatalf("empty min, got %+v", min) } - if max := tr.Max(); max != nil { + if max, found := tr.Max(); found { t.Fatalf("empty max, got %+v", max) } for _, item := range perm(treeSize) { - if x := tr.ReplaceOrInsert(item); x != nil { + if _, found := tr.ReplaceOrInsert(item); found { t.Fatal("insert found item", item) } } for _, item := range perm(treeSize) { - if x := tr.ReplaceOrInsert(item); x == nil { + if _, found := tr.ReplaceOrInsert(item); !found { t.Fatal("insert didn't find item", item) } } - if min, want := tr.Min(), Item(Int(0)); min != want { - t.Fatalf("min: want %+v, got %+v", want, min) + if min, found := tr.Min(); !found || min != Int(0) { + t.Fatalf("min: want %+v, got %+v", Int(0), min) } - if max, want := tr.Max(), Item(Int(treeSize-1)); max != want { - t.Fatalf("max: want %+v, got %+v", want, max) + if max, found := tr.Max(); !found || max != Int(treeSize-1) { + t.Fatalf("max: want %+v, got %+v", Int(treeSize-1), max) } got := all(tr) want := rang(treeSize) @@ -114,7 +114,7 @@ func TestBTree(t *testing.T) { } for _, item := range perm(treeSize) { - if x := tr.Delete(item); x == nil { + if _, found := tr.Delete(item); !found { t.Fatalf("didn't find %v", item) } } @@ -125,44 +125,55 @@ func TestBTree(t *testing.T) { } func ExampleBTree() { - tr := New(*btreeDegree) + tr := New[Int](*btreeDegree) for i := Int(0); i < 10; i++ { tr.ReplaceOrInsert(i) } fmt.Println("len: ", tr.Len()) - fmt.Println("get3: ", tr.Get(Int(3))) - fmt.Println("get100: ", tr.Get(Int(100))) - fmt.Println("del4: ", tr.Delete(Int(4))) - fmt.Println("del100: ", tr.Delete(Int(100))) - fmt.Println("replace5: ", tr.ReplaceOrInsert(Int(5))) - fmt.Println("replace100:", tr.ReplaceOrInsert(Int(100))) - fmt.Println("min: ", tr.Min()) - fmt.Println("delmin: ", tr.DeleteMin()) - fmt.Println("max: ", tr.Max()) - fmt.Println("delmax: ", tr.DeleteMax()) - fmt.Println("len: ", tr.Len()) + fmt.Printf("get3: ") + fmt.Println(tr.Get(Int(3))) + fmt.Printf("get100: ") + fmt.Println(tr.Get(Int(100))) + fmt.Printf("del4: ") + fmt.Println(tr.Delete(Int(4))) + fmt.Printf("del100: ") + fmt.Println(tr.Delete(Int(100))) + fmt.Printf("replace5: ") + fmt.Println(tr.ReplaceOrInsert(Int(5))) + fmt.Printf("replace100: ") + fmt.Println(tr.ReplaceOrInsert(Int(100))) + fmt.Printf("min: ") + fmt.Println(tr.Min()) + fmt.Printf("delmin: ") + fmt.Println(tr.DeleteMin()) + fmt.Printf("max: ") + fmt.Println(tr.Max()) + fmt.Printf("delmax: ") + fmt.Println(tr.DeleteMax()) + fmt.Printf("len: ") + fmt.Println(tr.Len()) // Output: // len: 10 - // get3: 3 - // get100: - // del4: 4 - // del100: - // replace5: 5 - // replace100: - // min: 0 - // delmin: 0 - // max: 100 - // delmax: 100 + // get3: 3 true + // get100: 0 false + // del4: 4 true + // del100: 0 false + // replace5: 5 true + // replace100: 0 false + // min: 0 true + // delmin: 0 true + // max: 100 true + // delmax: 100 true // len: 8 } func TestDeleteMin(t *testing.T) { - tr := New(3) + tr := New[Int](3) for _, v := range perm(100) { tr.ReplaceOrInsert(v) } - var got []Item - for v := tr.DeleteMin(); v != nil; v = tr.DeleteMin() { + var got []Int + for v, found := tr.DeleteMin(); found; v, found = tr.DeleteMin() { got = append(got, v) } if want := rang(100); !reflect.DeepEqual(got, want) { @@ -171,12 +182,12 @@ func TestDeleteMin(t *testing.T) { } func TestDeleteMax(t *testing.T) { - tr := New(3) + tr := New[Int](3) for _, v := range perm(100) { tr.ReplaceOrInsert(v) } - var got []Item - for v := tr.DeleteMax(); v != nil; v = tr.DeleteMax() { + var got []Int + for v, found := tr.DeleteMax(); found; v, found = tr.DeleteMax() { got = append(got, v) } // Reverse our list. @@ -189,12 +200,12 @@ func TestDeleteMax(t *testing.T) { } func TestAscendRange(t *testing.T) { - tr := New(2) + tr := New[Int](2) for _, v := range perm(100) { tr.ReplaceOrInsert(v) } - var got []Item - tr.AscendRange(Int(40), Int(60), func(a Item) bool { + var got []Int + tr.AscendRange(Int(40), Int(60), func(a Int) bool { got = append(got, a) return true }) @@ -202,8 +213,8 @@ func TestAscendRange(t *testing.T) { t.Fatalf("ascendrange:\n got: %v\nwant: %v", got, want) } got = got[:0] - tr.AscendRange(Int(40), Int(60), func(a Item) bool { - if a.(Int) > 50 { + tr.AscendRange(Int(40), Int(60), func(a Int) bool { + if a > 50 { return false } got = append(got, a) @@ -215,12 +226,12 @@ func TestAscendRange(t *testing.T) { } func TestDescendRange(t *testing.T) { - tr := New(2) + tr := New[Int](2) for _, v := range perm(100) { tr.ReplaceOrInsert(v) } - var got []Item - tr.DescendRange(Int(60), Int(40), func(a Item) bool { + var got []Int + tr.DescendRange(Int(60), Int(40), func(a Int) bool { got = append(got, a) return true }) @@ -228,8 +239,8 @@ func TestDescendRange(t *testing.T) { t.Fatalf("descendrange:\n got: %v\nwant: %v", got, want) } got = got[:0] - tr.DescendRange(Int(60), Int(40), func(a Item) bool { - if a.(Int) < 50 { + tr.DescendRange(Int(60), Int(40), func(a Int) bool { + if a < 50 { return false } got = append(got, a) @@ -239,13 +250,14 @@ func TestDescendRange(t *testing.T) { t.Fatalf("descendrange:\n got: %v\nwant: %v", got, want) } } + func TestAscendLessThan(t *testing.T) { - tr := New(*btreeDegree) + tr := New[Int](*btreeDegree) for _, v := range perm(100) { tr.ReplaceOrInsert(v) } - var got []Item - tr.AscendLessThan(Int(60), func(a Item) bool { + var got []Int + tr.AscendLessThan(Int(60), func(a Int) bool { got = append(got, a) return true }) @@ -253,8 +265,8 @@ func TestAscendLessThan(t *testing.T) { t.Fatalf("ascendrange:\n got: %v\nwant: %v", got, want) } got = got[:0] - tr.AscendLessThan(Int(60), func(a Item) bool { - if a.(Int) > 50 { + tr.AscendLessThan(Int(60), func(a Int) bool { + if a > 50 { return false } got = append(got, a) @@ -266,12 +278,12 @@ func TestAscendLessThan(t *testing.T) { } func TestDescendLessOrEqual(t *testing.T) { - tr := New(*btreeDegree) + tr := New[Int](*btreeDegree) for _, v := range perm(100) { tr.ReplaceOrInsert(v) } - var got []Item - tr.DescendLessOrEqual(Int(40), func(a Item) bool { + var got []Int + tr.DescendLessOrEqual(Int(40), func(a Int) bool { got = append(got, a) return true }) @@ -279,8 +291,8 @@ func TestDescendLessOrEqual(t *testing.T) { t.Fatalf("descendlessorequal:\n got: %v\nwant: %v", got, want) } got = got[:0] - tr.DescendLessOrEqual(Int(60), func(a Item) bool { - if a.(Int) < 50 { + tr.DescendLessOrEqual(Int(60), func(a Int) bool { + if a < 50 { return false } got = append(got, a) @@ -290,13 +302,14 @@ func TestDescendLessOrEqual(t *testing.T) { t.Fatalf("descendlessorequal:\n got: %v\nwant: %v", got, want) } } + func TestAscendGreaterOrEqual(t *testing.T) { - tr := New(*btreeDegree) + tr := New[Int](*btreeDegree) for _, v := range perm(100) { tr.ReplaceOrInsert(v) } - var got []Item - tr.AscendGreaterOrEqual(Int(40), func(a Item) bool { + var got []Int + tr.AscendGreaterOrEqual(Int(40), func(a Int) bool { got = append(got, a) return true }) @@ -304,8 +317,8 @@ func TestAscendGreaterOrEqual(t *testing.T) { t.Fatalf("ascendrange:\n got: %v\nwant: %v", got, want) } got = got[:0] - tr.AscendGreaterOrEqual(Int(40), func(a Item) bool { - if a.(Int) > 50 { + tr.AscendGreaterOrEqual(Int(40), func(a Int) bool { + if a > 50 { return false } got = append(got, a) @@ -317,12 +330,12 @@ func TestAscendGreaterOrEqual(t *testing.T) { } func TestDescendGreaterThan(t *testing.T) { - tr := New(*btreeDegree) + tr := New[Int](*btreeDegree) for _, v := range perm(100) { tr.ReplaceOrInsert(v) } - var got []Item - tr.DescendGreaterThan(Int(40), func(a Item) bool { + var got []Int + tr.DescendGreaterThan(Int(40), func(a Int) bool { got = append(got, a) return true }) @@ -330,8 +343,8 @@ func TestDescendGreaterThan(t *testing.T) { t.Fatalf("descendgreaterthan:\n got: %v\nwant: %v", got, want) } got = got[:0] - tr.DescendGreaterThan(Int(40), func(a Item) bool { - if a.(Int) < 50 { + tr.DescendGreaterThan(Int(40), func(a Int) bool { + if a < 50 { return false } got = append(got, a) @@ -350,7 +363,7 @@ func BenchmarkInsert(b *testing.B) { b.StartTimer() i := 0 for i < b.N { - tr := New(*btreeDegree) + tr := New[Int](*btreeDegree) for _, item := range insertP { tr.ReplaceOrInsert(item) i++ @@ -365,21 +378,21 @@ func BenchmarkSeek(b *testing.B) { b.StopTimer() size := 100000 insertP := perm(size) - tr := New(*btreeDegree) + tr := New[Int](*btreeDegree) for _, item := range insertP { tr.ReplaceOrInsert(item) } b.StartTimer() for i := 0; i < b.N; i++ { - tr.AscendGreaterOrEqual(Int(i%size), func(i Item) bool { return false }) + tr.AscendGreaterOrEqual(Int(i%size), func(i Int) bool { return false }) } } func BenchmarkDeleteInsert(b *testing.B) { b.StopTimer() insertP := perm(benchmarkTreeSize) - tr := New(*btreeDegree) + tr := New[Int](*btreeDegree) for _, item := range insertP { tr.ReplaceOrInsert(item) } @@ -393,7 +406,7 @@ func BenchmarkDeleteInsert(b *testing.B) { func BenchmarkDeleteInsertCloneOnce(b *testing.B) { b.StopTimer() insertP := perm(benchmarkTreeSize) - tr := New(*btreeDegree) + tr := New[Int](*btreeDegree) for _, item := range insertP { tr.ReplaceOrInsert(item) } @@ -408,7 +421,7 @@ func BenchmarkDeleteInsertCloneOnce(b *testing.B) { func BenchmarkDeleteInsertCloneEachTime(b *testing.B) { b.StopTimer() insertP := perm(benchmarkTreeSize) - tr := New(*btreeDegree) + tr := New[Int](*btreeDegree) for _, item := range insertP { tr.ReplaceOrInsert(item) } @@ -428,7 +441,7 @@ func BenchmarkDelete(b *testing.B) { i := 0 for i < b.N { b.StopTimer() - tr := New(*btreeDegree) + tr := New[Int](*btreeDegree) for _, v := range insertP { tr.ReplaceOrInsert(v) } @@ -454,7 +467,7 @@ func BenchmarkGet(b *testing.B) { i := 0 for i < b.N { b.StopTimer() - tr := New(*btreeDegree) + tr := New[Int](*btreeDegree) for _, v := range insertP { tr.ReplaceOrInsert(v) } @@ -477,7 +490,7 @@ func BenchmarkGetCloneEachTime(b *testing.B) { i := 0 for i < b.N { b.StopTimer() - tr := New(*btreeDegree) + tr := New[Int](*btreeDegree) for _, v := range insertP { tr.ReplaceOrInsert(v) } @@ -493,14 +506,14 @@ func BenchmarkGetCloneEachTime(b *testing.B) { } } -type byInts []Item +type byInts []Int func (a byInts) Len() int { return len(a) } func (a byInts) Less(i, j int) bool { - return a[i].(Int) < a[j].(Int) + return a[i] < a[j] } func (a byInts) Swap(i, j int) { @@ -509,7 +522,7 @@ func (a byInts) Swap(i, j int) { func BenchmarkAscend(b *testing.B) { arr := perm(benchmarkTreeSize) - tr := New(*btreeDegree) + tr := New[Int](*btreeDegree) for _, v := range arr { tr.ReplaceOrInsert(v) } @@ -517,9 +530,9 @@ func BenchmarkAscend(b *testing.B) { b.ResetTimer() for i := 0; i < b.N; i++ { j := 0 - tr.Ascend(func(item Item) bool { - if item.(Int) != arr[j].(Int) { - b.Fatalf("mismatch: expected: %v, got %v", arr[j].(Int), item.(Int)) + tr.Ascend(func(item Int) bool { + if item != arr[j] { + b.Fatalf("mismatch: expected: %v, got %v", arr[j], item) } j++ return true @@ -529,7 +542,7 @@ func BenchmarkAscend(b *testing.B) { func BenchmarkDescend(b *testing.B) { arr := perm(benchmarkTreeSize) - tr := New(*btreeDegree) + tr := New[Int](*btreeDegree) for _, v := range arr { tr.ReplaceOrInsert(v) } @@ -537,9 +550,9 @@ func BenchmarkDescend(b *testing.B) { b.ResetTimer() for i := 0; i < b.N; i++ { j := len(arr) - 1 - tr.Descend(func(item Item) bool { - if item.(Int) != arr[j].(Int) { - b.Fatalf("mismatch: expected: %v, got %v", arr[j].(Int), item.(Int)) + tr.Descend(func(item Int) bool { + if item != arr[j] { + b.Fatalf("mismatch: expected: %v, got %v", arr[j], item) } j-- return true @@ -548,7 +561,7 @@ func BenchmarkDescend(b *testing.B) { } func BenchmarkAscendRange(b *testing.B) { arr := perm(benchmarkTreeSize) - tr := New(*btreeDegree) + tr := New[Int](*btreeDegree) for _, v := range arr { tr.ReplaceOrInsert(v) } @@ -556,9 +569,9 @@ func BenchmarkAscendRange(b *testing.B) { b.ResetTimer() for i := 0; i < b.N; i++ { j := 100 - tr.AscendRange(Int(100), arr[len(arr)-100], func(item Item) bool { - if item.(Int) != arr[j].(Int) { - b.Fatalf("mismatch: expected: %v, got %v", arr[j].(Int), item.(Int)) + tr.AscendRange(Int(100), arr[len(arr)-100], func(item Int) bool { + if item != arr[j] { + b.Fatalf("mismatch: expected: %v, got %v", arr[j], item) } j++ return true @@ -571,7 +584,7 @@ func BenchmarkAscendRange(b *testing.B) { func BenchmarkDescendRange(b *testing.B) { arr := perm(benchmarkTreeSize) - tr := New(*btreeDegree) + tr := New[Int](*btreeDegree) for _, v := range arr { tr.ReplaceOrInsert(v) } @@ -579,9 +592,9 @@ func BenchmarkDescendRange(b *testing.B) { b.ResetTimer() for i := 0; i < b.N; i++ { j := len(arr) - 100 - tr.DescendRange(arr[len(arr)-100], Int(100), func(item Item) bool { - if item.(Int) != arr[j].(Int) { - b.Fatalf("mismatch: expected: %v, got %v", arr[j].(Int), item.(Int)) + tr.DescendRange(arr[len(arr)-100], Int(100), func(item Int) bool { + if item != arr[j] { + b.Fatalf("mismatch: expected: %v, got %v", arr[j], item) } j-- return true @@ -593,7 +606,7 @@ func BenchmarkDescendRange(b *testing.B) { } func BenchmarkAscendGreaterOrEqual(b *testing.B) { arr := perm(benchmarkTreeSize) - tr := New(*btreeDegree) + tr := New[Int](*btreeDegree) for _, v := range arr { tr.ReplaceOrInsert(v) } @@ -602,9 +615,9 @@ func BenchmarkAscendGreaterOrEqual(b *testing.B) { for i := 0; i < b.N; i++ { j := 100 k := 0 - tr.AscendGreaterOrEqual(Int(100), func(item Item) bool { - if item.(Int) != arr[j].(Int) { - b.Fatalf("mismatch: expected: %v, got %v", arr[j].(Int), item.(Int)) + tr.AscendGreaterOrEqual(Int(100), func(item Int) bool { + if item != arr[j] { + b.Fatalf("mismatch: expected: %v, got %v", arr[j], item) } j++ k++ @@ -620,7 +633,7 @@ func BenchmarkAscendGreaterOrEqual(b *testing.B) { } func BenchmarkDescendLessOrEqual(b *testing.B) { arr := perm(benchmarkTreeSize) - tr := New(*btreeDegree) + tr := New[Int](*btreeDegree) for _, v := range arr { tr.ReplaceOrInsert(v) } @@ -629,9 +642,9 @@ func BenchmarkDescendLessOrEqual(b *testing.B) { for i := 0; i < b.N; i++ { j := len(arr) - 100 k := len(arr) - tr.DescendLessOrEqual(arr[len(arr)-100], func(item Item) bool { - if item.(Int) != arr[j].(Int) { - b.Fatalf("mismatch: expected: %v, got %v", arr[j].(Int), item.(Int)) + tr.DescendLessOrEqual(arr[len(arr)-100], func(item Int) bool { + if item != arr[j] { + b.Fatalf("mismatch: expected: %v, got %v", arr[j], item) } j-- k-- @@ -648,7 +661,7 @@ func BenchmarkDescendLessOrEqual(b *testing.B) { const cloneTestSize = 10000 -func cloneTest(t *testing.T, b *BTree, start int, p []Item, wg *sync.WaitGroup, trees *[]*BTree, lock *sync.Mutex) { +func cloneTest[T Item[T]](t *testing.T, b *BTree[T], start int, p []T, wg *sync.WaitGroup, trees *[]*BTree[T], lock *sync.Mutex) { t.Logf("Starting new clone at %v", start) lock.Lock() *trees = append(*trees, b) @@ -664,8 +677,8 @@ func cloneTest(t *testing.T, b *BTree, start int, p []Item, wg *sync.WaitGroup, } func TestCloneConcurrentOperations(t *testing.T) { - b := New(*btreeDegree) - trees := []*BTree{} + b := New[Int](*btreeDegree) + trees := []*BTree[Int]{} p := perm(cloneTestSize) var wg sync.WaitGroup wg.Add(1) @@ -693,7 +706,7 @@ func TestCloneConcurrentOperations(t *testing.T) { wg.Wait() t.Log("Checking all values again") for i, tree := range trees { - var wantpart []Item + var wantpart []Int if i < len(trees)/2 { wantpart = want[:cloneTestSize/2] } else { @@ -709,16 +722,16 @@ func BenchmarkDeleteAndRestore(b *testing.B) { items := perm(16392) b.ResetTimer() b.Run(`CopyBigFreeList`, func(b *testing.B) { - fl := NewFreeList(16392) - tr := NewWithFreeList(*btreeDegree, fl) + fl := NewFreeList[Int](16392) + tr := NewWithFreeList[Int](*btreeDegree, fl) for _, v := range items { tr.ReplaceOrInsert(v) } b.ReportAllocs() b.ResetTimer() for i := 0; i < b.N; i++ { - dels := make([]Item, 0, tr.Len()) - tr.Ascend(ItemIterator(func(b Item) bool { + dels := make([]Int, 0, tr.Len()) + tr.Ascend(ItemIterator[Int](func(b Int) bool { dels = append(dels, b) return true })) @@ -726,22 +739,22 @@ func BenchmarkDeleteAndRestore(b *testing.B) { tr.Delete(del) } // tr is now empty, we make a new empty copy of it. - tr = NewWithFreeList(*btreeDegree, fl) + tr = NewWithFreeList[Int](*btreeDegree, fl) for _, v := range items { tr.ReplaceOrInsert(v) } } }) b.Run(`Copy`, func(b *testing.B) { - tr := New(*btreeDegree) + tr := New[Int](*btreeDegree) for _, v := range items { tr.ReplaceOrInsert(v) } b.ReportAllocs() b.ResetTimer() for i := 0; i < b.N; i++ { - dels := make([]Item, 0, tr.Len()) - tr.Ascend(ItemIterator(func(b Item) bool { + dels := make([]Int, 0, tr.Len()) + tr.Ascend(ItemIterator[Int](func(b Int) bool { dels = append(dels, b) return true })) @@ -749,15 +762,15 @@ func BenchmarkDeleteAndRestore(b *testing.B) { tr.Delete(del) } // tr is now empty, we make a new empty copy of it. - tr = New(*btreeDegree) + tr = New[Int](*btreeDegree) for _, v := range items { tr.ReplaceOrInsert(v) } } }) b.Run(`ClearBigFreelist`, func(b *testing.B) { - fl := NewFreeList(16392) - tr := NewWithFreeList(*btreeDegree, fl) + fl := NewFreeList[Int](16392) + tr := NewWithFreeList[Int](*btreeDegree, fl) for _, v := range items { tr.ReplaceOrInsert(v) } @@ -771,7 +784,7 @@ func BenchmarkDeleteAndRestore(b *testing.B) { } }) b.Run(`Clear`, func(b *testing.B) { - tr := New(*btreeDegree) + tr := New[Int](*btreeDegree) for _, v := range items { tr.ReplaceOrInsert(v) } diff --git a/go.mod b/go.mod index fe4d5ca..58b7d0d 100644 --- a/go.mod +++ b/go.mod @@ -14,4 +14,6 @@ module github.com/google/btree -go 1.12 +go 1.18 + +require github.com/petar/GoLLRB v0.0.0-20210522233825-ae3b015fd3e9 // indirect From 93be90b154a94b6da0085b7e1c93458a1a9b786c Mon Sep 17 00:00:00 2001 From: Michal-Leszczynski <2000michal@wp.pl> Date: Thu, 5 May 2022 13:12:21 +0200 Subject: [PATCH 2/2] v2 release with generic approach --- btree.go | 281 +++++------ btree_mem.go | 2 +- btree_test.go | 265 +++++----- go.mod | 2 +- v2/.github/workflows/test.yml | 14 + v2/LICENSE | 202 ++++++++ v2/README.md | 10 + v2/btree.go | 905 ++++++++++++++++++++++++++++++++++ v2/btree_mem.go | 77 +++ v2/btree_test.go | 800 ++++++++++++++++++++++++++++++ v2/go.mod | 19 + 11 files changed, 2288 insertions(+), 289 deletions(-) create mode 100644 v2/.github/workflows/test.yml create mode 100644 v2/LICENSE create mode 100644 v2/README.md create mode 100644 v2/btree.go create mode 100644 v2/btree_mem.go create mode 100644 v2/btree_test.go create mode 100644 v2/go.mod diff --git a/btree.go b/btree.go index 2951e39..b83acdb 100644 --- a/btree.go +++ b/btree.go @@ -56,40 +56,45 @@ import ( ) // Item represents a single object in the tree. -type Item[T any] interface { +type Item interface { // Less tests whether the current item is less than the given argument. // // This must provide a strict weak ordering. // If !a.Less(b) && !b.Less(a), we treat this to mean a == b (i.e. we can only // hold one of either a or b in the tree). - Less(than T) bool + Less(than Item) bool } const ( DefaultFreeListSize = 32 ) +var ( + nilItems = make(items, 16) + nilChildren = make(children, 16) +) + // FreeList represents a free list of btree nodes. By default each // BTree has its own FreeList, but multiple BTrees can share the same // FreeList. // Two Btrees using the same freelist are safe for concurrent write access. -type FreeList[T Item[T]] struct { +type FreeList struct { mu sync.Mutex - freelist []*node[T] + freelist []*node } // NewFreeList creates a new free list. // size is the maximum size of the returned free list. -func NewFreeList[T Item[T]](size int) *FreeList[T] { - return &FreeList[T]{freelist: make([]*node[T], 0, size)} +func NewFreeList(size int) *FreeList { + return &FreeList{freelist: make([]*node, 0, size)} } -func (f *FreeList[T]) newNode() (n *node[T]) { +func (f *FreeList) newNode() (n *node) { f.mu.Lock() index := len(f.freelist) - 1 if index < 0 { f.mu.Unlock() - return new(node[T]) + return new(node) } n = f.freelist[index] f.freelist[index] = nil @@ -100,7 +105,7 @@ func (f *FreeList[T]) newNode() (n *node[T]) { // freeNode adds the given node to the list, returning true if it was added // and false if it was discarded. -func (f *FreeList[T]) freeNode(n *node[T]) (out bool) { +func (f *FreeList) freeNode(n *node) (out bool) { f.mu.Lock() if len(f.freelist) < cap(f.freelist) { f.freelist = append(f.freelist, n) @@ -113,35 +118,34 @@ func (f *FreeList[T]) freeNode(n *node[T]) (out bool) { // ItemIterator allows callers of Ascend* to iterate in-order over portions of // the tree. When this function returns false, iteration will stop and the // associated Ascend* function will immediately return. -type ItemIterator[T Item[T]] func(i T) bool +type ItemIterator func(i Item) bool // New creates a new B-Tree with the given degree. // // New(2), for example, will create a 2-3-4 tree (each node contains 1-3 items // and 2-4 children). -func New[T Item[T]](degree int) *BTree[T] { - return NewWithFreeList[T](degree, NewFreeList[T](DefaultFreeListSize)) +func New(degree int) *BTree { + return NewWithFreeList(degree, NewFreeList(DefaultFreeListSize)) } // NewWithFreeList creates a new B-Tree that uses the given node free list. -func NewWithFreeList[T Item[T]](degree int, f *FreeList[T]) *BTree[T] { +func NewWithFreeList(degree int, f *FreeList) *BTree { if degree <= 1 { panic("bad degree") } - return &BTree[T]{ + return &BTree{ degree: degree, - cow: ©OnWriteContext[T]{freelist: f}, + cow: ©OnWriteContext{freelist: f}, } } // items stores items in a node. -type items[T Item[T]] []T +type items []Item // insertAt inserts a value into the given index, pushing all subsequent values // forward. -func (s *items[T]) insertAt(index int, item T) { - var x T - *s = append(*s, x) +func (s *items) insertAt(index int, item Item) { + *s = append(*s, nil) if index < len(*s) { copy((*s)[index+1:], (*s)[index:]) } @@ -150,44 +154,37 @@ func (s *items[T]) insertAt(index int, item T) { // removeAt removes a value at a given index, pulling all subsequent values // back. -func (s *items[T]) removeAt(index int) T { - var x T +func (s *items) removeAt(index int) Item { item := (*s)[index] copy((*s)[index:], (*s)[index+1:]) - (*s)[len(*s)-1] = x + (*s)[len(*s)-1] = nil *s = (*s)[:len(*s)-1] return item } // pop removes and returns the last element in the list. -func (s *items[T]) pop() (out T) { - var x T +func (s *items) pop() (out Item) { index := len(*s) - 1 out = (*s)[index] - (*s)[index] = x + (*s)[index] = nil *s = (*s)[:index] return } // truncate truncates this instance at index so that it contains only the // first index items. index must be less than or equal to length. -func (s *items[T]) truncate(index int) { - var i int - var x T - var toClear items[T] +func (s *items) truncate(index int) { + var toClear items *s, toClear = (*s)[:index], (*s)[index:] for len(toClear) > 0 { - for i = 0; i < 16 && i < len(toClear); i++ { - toClear[i] = x - } - toClear = toClear[i:] + toClear = toClear[copy(toClear, nilItems):] } } // find returns the index where the given item should be inserted into this // list. 'found' is true if the item already exists in the list at the given // index. -func (s items[T]) find(item T) (index int, found bool) { +func (s items) find(item Item) (index int, found bool) { i := sort.Search(len(s), func(i int) bool { return item.Less(s[i]) }) @@ -198,11 +195,11 @@ func (s items[T]) find(item T) (index int, found bool) { } // children stores child nodes in a node. -type children[T Item[T]] []*node[T] +type children []*node // insertAt inserts a value into the given index, pushing all subsequent values // forward. -func (s *children[T]) insertAt(index int, n *node[T]) { +func (s *children) insertAt(index int, n *node) { *s = append(*s, nil) if index < len(*s) { copy((*s)[index+1:], (*s)[index:]) @@ -212,7 +209,7 @@ func (s *children[T]) insertAt(index int, n *node[T]) { // removeAt removes a value at a given index, pulling all subsequent values // back. -func (s *children[T]) removeAt(index int) *node[T] { +func (s *children) removeAt(index int) *node { n := (*s)[index] copy((*s)[index:], (*s)[index+1:]) (*s)[len(*s)-1] = nil @@ -221,7 +218,7 @@ func (s *children[T]) removeAt(index int) *node[T] { } // pop removes and returns the last element in the list. -func (s *children[T]) pop() (out *node[T]) { +func (s *children) pop() (out *node) { index := len(*s) - 1 out = (*s)[index] (*s)[index] = nil @@ -231,15 +228,11 @@ func (s *children[T]) pop() (out *node[T]) { // truncate truncates this instance at index so that it contains only the // first index children. index must be less than or equal to length. -func (s *children[T]) truncate(index int) { - var i int - var toClear children[T] +func (s *children) truncate(index int) { + var toClear children *s, toClear = (*s)[:index], (*s)[index:] for len(toClear) > 0 { - for i = 0; i < 16 && i < len(toClear); i++ { - toClear[i] = nil - } - toClear = toClear[i:] + toClear = toClear[copy(toClear, nilChildren):] } } @@ -248,13 +241,13 @@ func (s *children[T]) truncate(index int) { // It must at all times maintain the invariant that either // * len(children) == 0, len(items) unconstrained // * len(children) == len(items) + 1 -type node[T Item[T]] struct { - items items[T] - children children[T] - cow *copyOnWriteContext[T] +type node struct { + items items + children children + cow *copyOnWriteContext } -func (n *node[T]) mutableFor(cow *copyOnWriteContext[T]) *node[T] { +func (n *node) mutableFor(cow *copyOnWriteContext) *node { if n.cow == cow { return n } @@ -262,20 +255,20 @@ func (n *node[T]) mutableFor(cow *copyOnWriteContext[T]) *node[T] { if cap(out.items) >= len(n.items) { out.items = out.items[:len(n.items)] } else { - out.items = make(items[T], len(n.items), cap(n.items)) + out.items = make(items, len(n.items), cap(n.items)) } copy(out.items, n.items) // Copy children if cap(out.children) >= len(n.children) { out.children = out.children[:len(n.children)] } else { - out.children = make(children[T], len(n.children), cap(n.children)) + out.children = make(children, len(n.children), cap(n.children)) } copy(out.children, n.children) return out } -func (n *node[T]) mutableChild(i int) *node[T] { +func (n *node) mutableChild(i int) *node { c := n.children[i].mutableFor(n.cow) n.children[i] = c return c @@ -284,7 +277,7 @@ func (n *node[T]) mutableChild(i int) *node[T] { // split splits the given node at the given index. The current node shrinks, // and this function returns the item that existed at that index and a new node // containing all items/children after it. -func (n *node[T]) split(i int) (T, *node[T]) { +func (n *node) split(i int) (Item, *node) { item := n.items[i] next := n.cow.newNode() next.items = append(next.items, n.items[i+1:]...) @@ -298,7 +291,7 @@ func (n *node[T]) split(i int) (T, *node[T]) { // maybeSplitChild checks if a child should be split, and if so splits it. // Returns whether or not a split occurred. -func (n *node[T]) maybeSplitChild(i, maxItems int) bool { +func (n *node) maybeSplitChild(i, maxItems int) bool { if len(n.children[i].items) < maxItems { return false } @@ -312,17 +305,16 @@ func (n *node[T]) maybeSplitChild(i, maxItems int) bool { // insert inserts an item into the subtree rooted at this node, making sure // no nodes in the subtree exceed maxItems items. Should an equivalent item be // be found/replaced by insert, it will be returned. -func (n *node[T]) insert(item T, maxItems int) (T, bool) { - var x T +func (n *node) insert(item Item, maxItems int) Item { i, found := n.items.find(item) if found { out := n.items[i] n.items[i] = item - return out, true + return out } if len(n.children) == 0 { n.items.insertAt(i, item) - return x, false + return nil } if n.maybeSplitChild(i, maxItems) { inTree := n.items[i] @@ -334,52 +326,49 @@ func (n *node[T]) insert(item T, maxItems int) (T, bool) { default: out := n.items[i] n.items[i] = item - return out, true + return out } } return n.mutableChild(i).insert(item, maxItems) } // get finds the given key in the subtree and returns it. -func (n *node[T]) get(key T) (T, bool) { - var x T +func (n *node) get(key Item) Item { i, found := n.items.find(key) if found { - return n.items[i], true + return n.items[i] } else if len(n.children) > 0 { return n.children[i].get(key) } - return x, false + return nil } // min returns the first item in the subtree. -func min[T Item[T]](n *node[T]) (T, bool) { - var x T +func min(n *node) Item { if n == nil { - return x, false + return nil } for len(n.children) > 0 { n = n.children[0] } if len(n.items) == 0 { - return x, false + return nil } - return n.items[0], true + return n.items[0] } // max returns the last item in the subtree. -func max[T Item[T]](n *node[T]) (T, bool) { - var x T +func max(n *node) Item { if n == nil { - return x, false + return nil } for len(n.children) > 0 { n = n.children[len(n.children)-1] } if len(n.items) == 0 { - return x, false + return nil } - return n.items[len(n.items)-1], true + return n.items[len(n.items)-1] } // toRemove details what item to remove in a node.remove call. @@ -392,28 +381,27 @@ const ( ) // remove removes an item from the subtree rooted at this node. -func (n *node[T]) remove(item T, minItems int, typ toRemove) (T, bool) { - var x T +func (n *node) remove(item Item, minItems int, typ toRemove) Item { var i int var found bool switch typ { case removeMax: if len(n.children) == 0 { - return n.items.pop(), true + return n.items.pop() } i = len(n.items) case removeMin: if len(n.children) == 0 { - return n.items.removeAt(0), true + return n.items.removeAt(0) } i = 0 case removeItem: i, found = n.items.find(item) if len(n.children) == 0 { if found { - return n.items.removeAt(i), true + return n.items.removeAt(i) } - return x, false + return nil } default: panic("invalid type") @@ -433,8 +421,8 @@ func (n *node[T]) remove(item T, minItems int, typ toRemove) (T, bool) { // We use our special-case 'remove' call with typ=maxItem to pull the // predecessor of item i (the rightmost leaf of our immediate left child) // and set it into where we pulled the item from. - n.items[i], _ = child.remove(x, minItems, removeMax) - return out, true + n.items[i] = child.remove(nil, minItems, removeMax) + return out } // Final recursive call. Once we're here, we know that the item isn't in this // node and that the child is big enough to remove from. @@ -460,7 +448,7 @@ func (n *node[T]) remove(item T, minItems int, typ toRemove) (T, bool) { // We then simply redo our remove call, and the second time (regardless of // whether we're in case 1 or 2), we'll have enough items and can guarantee // that we hit case A. -func (n *node[T]) growChildAndRemove(i int, item T, minItems int, typ toRemove) (T, bool) { +func (n *node) growChildAndRemove(i int, item Item, minItems int, typ toRemove) Item { if i > 0 && len(n.children[i-1].items) > minItems { // Steal from left child child := n.mutableChild(i) @@ -511,13 +499,13 @@ const ( // will force the iterator to include the first item when it equals 'start', // thus creating a "greaterOrEqual" or "lessThanEqual" rather than just a // "greaterThan" or "lessThan" queries. -func (n *node[T]) iterate(dir direction, start, stop *T, includeStart bool, hit bool, iter ItemIterator[T]) (bool, bool) { +func (n *node) iterate(dir direction, start, stop Item, includeStart bool, hit bool, iter ItemIterator) (bool, bool) { var ok, found bool var index int switch dir { case ascend: if start != nil { - index, _ = n.items.find(*start) + index, _ = n.items.find(start) } for i := index; i < len(n.items); i++ { if len(n.children) > 0 { @@ -525,12 +513,12 @@ func (n *node[T]) iterate(dir direction, start, stop *T, includeStart bool, hit return hit, false } } - if !includeStart && !hit && start != nil && !(*start).Less(n.items[i]) { + if !includeStart && !hit && start != nil && !start.Less(n.items[i]) { hit = true continue } hit = true - if stop != nil && !n.items[i].Less(*stop) { + if stop != nil && !n.items[i].Less(stop) { return hit, false } if !iter(n.items[i]) { @@ -544,7 +532,7 @@ func (n *node[T]) iterate(dir direction, start, stop *T, includeStart bool, hit } case descend: if start != nil { - index, found = n.items.find(*start) + index, found = n.items.find(start) if !found { index = index - 1 } @@ -552,8 +540,8 @@ func (n *node[T]) iterate(dir direction, start, stop *T, includeStart bool, hit index = len(n.items) - 1 } for i := index; i >= 0; i-- { - if start != nil && !n.items[i].Less(*start) { - if !includeStart || hit || (*start).Less(n.items[i]) { + if start != nil && !n.items[i].Less(start) { + if !includeStart || hit || start.Less(n.items[i]) { continue } } @@ -562,7 +550,7 @@ func (n *node[T]) iterate(dir direction, start, stop *T, includeStart bool, hit return hit, false } } - if stop != nil && !(*stop).Less(n.items[i]) { + if stop != nil && !stop.Less(n.items[i]) { return hit, false // continue } hit = true @@ -580,7 +568,7 @@ func (n *node[T]) iterate(dir direction, start, stop *T, includeStart bool, hit } // Used for testing/debugging purposes. -func (n *node[T]) print(w io.Writer, level int) { +func (n *node) print(w io.Writer, level int) { fmt.Fprintf(w, "%sNODE:%v\n", strings.Repeat(" ", level), n.items) for _, c := range n.children { c.print(w, level+1) @@ -594,11 +582,11 @@ func (n *node[T]) print(w io.Writer, level int) { // // Write operations are not safe for concurrent mutation by multiple // goroutines, but Read operations are. -type BTree[T Item[T]] struct { +type BTree struct { degree int length int - root *node[T] - cow *copyOnWriteContext[T] + root *node + cow *copyOnWriteContext } // copyOnWriteContext pointers determine node ownership... a tree with a write @@ -615,8 +603,8 @@ type BTree[T Item[T]] struct { // tree's context, that node is modifiable in place. Children of that node may // not share context, but before we descend into them, we'll make a mutable // copy. -type copyOnWriteContext[T Item[T]] struct { - freelist *FreeList[T] +type copyOnWriteContext struct { + freelist *FreeList } // Clone clones the btree, lazily. Clone should not be called concurrently, @@ -630,7 +618,7 @@ type copyOnWriteContext[T Item[T]] struct { // will initially experience minor slow-downs caused by additional allocs and // copies due to the aforementioned copy-on-write logic, but should converge to // the original performance characteristics of the original tree. -func (t *BTree[T]) Clone() (t2 *BTree[T]) { +func (t *BTree) Clone() (t2 *BTree) { // Create two entirely new copy-on-write contexts. // This operation effectively creates three trees: // the original, shared nodes (old b.cow) @@ -644,17 +632,17 @@ func (t *BTree[T]) Clone() (t2 *BTree[T]) { } // maxItems returns the max number of items to allow per node. -func (t *BTree[T]) maxItems() int { +func (t *BTree) maxItems() int { return t.degree*2 - 1 } // minItems returns the min number of items to allow per node (ignored for the // root node). -func (t *BTree[T]) minItems() int { +func (t *BTree) minItems() int { return t.degree - 1 } -func (c *copyOnWriteContext[T]) newNode() (n *node[T]) { +func (c *copyOnWriteContext) newNode() (n *node) { n = c.freelist.newNode() n.cow = c return @@ -671,7 +659,7 @@ const ( // freeNode frees a node within a given COW context, if it's owned by that // context. It returns what happened to the node (see freeType const // documentation). -func (c *copyOnWriteContext[T]) freeNode(n *node[T]) freeType { +func (c *copyOnWriteContext) freeNode(n *node) freeType { if n.cow == c { // clear to allow GC n.items.truncate(0) @@ -692,13 +680,15 @@ func (c *copyOnWriteContext[T]) freeNode(n *node[T]) freeType { // Otherwise, nil is returned. // // nil cannot be added to the tree (will panic). -func (t *BTree[T]) ReplaceOrInsert(item T) (T, bool) { - var x T +func (t *BTree) ReplaceOrInsert(item Item) Item { + if item == nil { + panic("nil item being added to BTree") + } if t.root == nil { t.root = t.cow.newNode() t.root.items = append(t.root.items, item) t.length++ - return x, false + return nil } else { t.root = t.root.mutableFor(t.cow) if len(t.root.items) >= t.maxItems() { @@ -709,81 +699,78 @@ func (t *BTree[T]) ReplaceOrInsert(item T) (T, bool) { t.root.children = append(t.root.children, oldroot, second) } } - out, found := t.root.insert(item, t.maxItems()) - if !found { + out := t.root.insert(item, t.maxItems()) + if out == nil { t.length++ } - return out, found + return out } // Delete removes an item equal to the passed in item from the tree, returning // it. If no such item exists, returns nil. -func (t *BTree[T]) Delete(item T) (T, bool) { +func (t *BTree) Delete(item Item) Item { return t.deleteItem(item, removeItem) } // DeleteMin removes the smallest item in the tree and returns it. // If no such item exists, returns nil. -func (t *BTree[T]) DeleteMin() (T, bool) { - var x T - return t.deleteItem(x, removeMin) +func (t *BTree) DeleteMin() Item { + return t.deleteItem(nil, removeMin) } // DeleteMax removes the largest item in the tree and returns it. // If no such item exists, returns nil. -func (t *BTree[T]) DeleteMax() (T, bool) { - var x T - return t.deleteItem(x, removeMax) +func (t *BTree) DeleteMax() Item { + return t.deleteItem(nil, removeMax) } -func (t *BTree[T]) deleteItem(item T, typ toRemove) (T, bool) { - var x T +func (t *BTree) deleteItem(item Item, typ toRemove) Item { if t.root == nil || len(t.root.items) == 0 { - return x, false + return nil } t.root = t.root.mutableFor(t.cow) - out, found := t.root.remove(item, t.minItems(), typ) + out := t.root.remove(item, t.minItems(), typ) if len(t.root.items) == 0 && len(t.root.children) > 0 { oldroot := t.root t.root = t.root.children[0] t.cow.freeNode(oldroot) } - if found { + if out != nil { t.length-- } - return out, found + return out } // AscendRange calls the iterator for every value in the tree within the range // [greaterOrEqual, lessThan), until iterator returns false. -func (t *BTree[T]) AscendRange(greaterOrEqual, lessThan T, iterator ItemIterator[T]) { +func (t *BTree) AscendRange(greaterOrEqual, lessThan Item, iterator ItemIterator) { if t.root == nil { return } - t.root.iterate(ascend, &greaterOrEqual, &lessThan, true, false, iterator) + t.root.iterate(ascend, greaterOrEqual, lessThan, true, false, iterator) } // AscendLessThan calls the iterator for every value in the tree within the range // [first, pivot), until iterator returns false. -func (t *BTree[T]) AscendLessThan(pivot T, iterator ItemIterator[T]) { +func (t *BTree) AscendLessThan(pivot Item, iterator ItemIterator) { if t.root == nil { return } - t.root.iterate(ascend, nil, &pivot, false, false, iterator) + t.root.iterate(ascend, nil, pivot, false, false, iterator) } // AscendGreaterOrEqual calls the iterator for every value in the tree within // the range [pivot, last], until iterator returns false. -func (t *BTree[T]) AscendGreaterOrEqual(pivot T, iterator ItemIterator[T]) { +func (t *BTree) AscendGreaterOrEqual(pivot Item, iterator ItemIterator) { if t.root == nil { return } - t.root.iterate(ascend, &pivot, nil, true, false, iterator) + t.root.iterate(ascend, pivot, nil, true, false, iterator) } // Ascend calls the iterator for every value in the tree within the range // [first, last], until iterator returns false. -func (t *BTree[T]) Ascend(iterator ItemIterator[T]) { +func (t *BTree) Ascend(iterator ItemIterator) { if t.root == nil { return } @@ -792,34 +779,34 @@ func (t *BTree[T]) Ascend(iterator ItemIterator[T]) { // DescendRange calls the iterator for every value in the tree within the range // [lessOrEqual, greaterThan), until iterator returns false. -func (t *BTree[T]) DescendRange(lessOrEqual, greaterThan T, iterator ItemIterator[T]) { +func (t *BTree) DescendRange(lessOrEqual, greaterThan Item, iterator ItemIterator) { if t.root == nil { return } - t.root.iterate(descend, &lessOrEqual, &greaterThan, true, false, iterator) + t.root.iterate(descend, lessOrEqual, greaterThan, true, false, iterator) } // DescendLessOrEqual calls the iterator for every value in the tree within the range // [pivot, first], until iterator returns false. -func (t *BTree[T]) DescendLessOrEqual(pivot T, iterator ItemIterator[T]) { +func (t *BTree) DescendLessOrEqual(pivot Item, iterator ItemIterator) { if t.root == nil { return } - t.root.iterate(descend, &pivot, nil, true, false, iterator) + t.root.iterate(descend, pivot, nil, true, false, iterator) } // DescendGreaterThan calls the iterator for every value in the tree within // the range [last, pivot), until iterator returns false. -func (t *BTree[T]) DescendGreaterThan(pivot T, iterator ItemIterator[T]) { +func (t *BTree) DescendGreaterThan(pivot Item, iterator ItemIterator) { if t.root == nil { return } - t.root.iterate(descend, nil, &pivot, false, false, iterator) + t.root.iterate(descend, nil, pivot, false, false, iterator) } // Descend calls the iterator for every value in the tree within the range // [last, first], until iterator returns false. -func (t *BTree[T]) Descend(iterator ItemIterator[T]) { +func (t *BTree) Descend(iterator ItemIterator) { if t.root == nil { return } @@ -828,32 +815,30 @@ func (t *BTree[T]) Descend(iterator ItemIterator[T]) { // Get looks for the key item in the tree, returning it. It returns nil if // unable to find that item. -func (t *BTree[T]) Get(key T) (T, bool) { - var x T +func (t *BTree) Get(key Item) Item { if t.root == nil { - return x, false + return nil } return t.root.get(key) } // Min returns the smallest item in the tree, or nil if the tree is empty. -func (t *BTree[T]) Min() (T, bool) { +func (t *BTree) Min() Item { return min(t.root) } // Max returns the largest item in the tree, or nil if the tree is empty. -func (t *BTree[T]) Max() (T, bool) { +func (t *BTree) Max() Item { return max(t.root) } // Has returns true if the given key is in the tree. -func (t *BTree[T]) Has(key T) bool { - _, found := t.Get(key) - return found +func (t *BTree) Has(key Item) bool { + return t.Get(key) != nil } // Len returns the number of items currently in the tree. -func (t *BTree[T]) Len() int { +func (t *BTree) Len() int { return t.length } @@ -877,7 +862,7 @@ func (t *BTree[T]) Len() int { // O(tree size): when all nodes are owned by another tree, all nodes are // iterated over looking for nodes to add to the freelist, and due to // ownership, none are. -func (t *BTree[T]) Clear(addNodesToFreelist bool) { +func (t *BTree) Clear(addNodesToFreelist bool) { if t.root != nil && addNodesToFreelist { t.root.reset(t.cow) } @@ -887,7 +872,7 @@ func (t *BTree[T]) Clear(addNodesToFreelist bool) { // reset returns a subtree to the freelist. It breaks out immediately if the // freelist is full, since the only benefit of iterating is to fill that // freelist up. Returns true if parent reset call should continue. -func (n *node[T]) reset(c *copyOnWriteContext[T]) bool { +func (n *node) reset(c *copyOnWriteContext) bool { for _, child := range n.children { if !child.reset(c) { return false @@ -900,6 +885,6 @@ func (n *node[T]) reset(c *copyOnWriteContext[T]) bool { type Int int // Less returns true if int(a) < int(b). -func (a Int) Less(b Int) bool { - return a < b +func (a Int) Less(b Item) bool { + return a < b.(Int) } diff --git a/btree_mem.go b/btree_mem.go index 3e7db2e..21ee62b 100644 --- a/btree_mem.go +++ b/btree_mem.go @@ -55,7 +55,7 @@ func main() { } t = tr // keep it around } else { - tr := btree.New[btree.Int](*degree) + tr := btree.New(*degree) for _, v := range vals { tr.ReplaceOrInsert(btree.Int(v)) } diff --git a/btree_test.go b/btree_test.go index 6692c21..feed087 100644 --- a/btree_test.go +++ b/btree_test.go @@ -32,7 +32,7 @@ func init() { } // perm returns a random permutation of n Int items in the range [0, n). -func perm(n int) (out []Int) { +func perm(n int) (out []Item) { for _, v := range rand.Perm(n) { out = append(out, Int(v)) } @@ -40,7 +40,7 @@ func perm(n int) (out []Int) { } // rang returns an ordered list of Int items in the range [0, n). -func rang(n int) (out []Int) { +func rang(n int) (out []Item) { for i := 0; i < n; i++ { out = append(out, Int(i)) } @@ -48,8 +48,8 @@ func rang(n int) (out []Int) { } // all extracts all items from a tree in order as a slice. -func all[T Item[T]](t *BTree[T]) (out []T) { - t.Ascend(func(a T) bool { +func all(t *BTree) (out []Item) { + t.Ascend(func(a Item) bool { out = append(out, a) return true }) @@ -57,7 +57,7 @@ func all[T Item[T]](t *BTree[T]) (out []T) { } // rangerev returns a reversed ordered list of Int items in the range [0, n). -func rangrev(n int) (out []Int) { +func rangrev(n int) (out []Item) { for i := n - 1; i >= 0; i-- { out = append(out, Int(i)) } @@ -65,8 +65,8 @@ func rangrev(n int) (out []Int) { } // allrev extracts all items from a tree in reverse order as a slice. -func allrev[T Item[T]](t *BTree[T]) (out []T) { - t.Descend(func(a T) bool { +func allrev(t *BTree) (out []Item) { + t.Descend(func(a Item) bool { out = append(out, a) return true }) @@ -76,30 +76,30 @@ func allrev[T Item[T]](t *BTree[T]) (out []T) { var btreeDegree = flag.Int("degree", 32, "B-Tree degree") func TestBTree(t *testing.T) { - tr := New[Int](*btreeDegree) + tr := New(*btreeDegree) const treeSize = 10000 for i := 0; i < 10; i++ { - if min, found := tr.Min(); found { + if min := tr.Min(); min != nil { t.Fatalf("empty min, got %+v", min) } - if max, found := tr.Max(); found { + if max := tr.Max(); max != nil { t.Fatalf("empty max, got %+v", max) } for _, item := range perm(treeSize) { - if _, found := tr.ReplaceOrInsert(item); found { + if x := tr.ReplaceOrInsert(item); x != nil { t.Fatal("insert found item", item) } } for _, item := range perm(treeSize) { - if _, found := tr.ReplaceOrInsert(item); !found { + if x := tr.ReplaceOrInsert(item); x == nil { t.Fatal("insert didn't find item", item) } } - if min, found := tr.Min(); !found || min != Int(0) { - t.Fatalf("min: want %+v, got %+v", Int(0), min) + if min, want := tr.Min(), Item(Int(0)); min != want { + t.Fatalf("min: want %+v, got %+v", want, min) } - if max, found := tr.Max(); !found || max != Int(treeSize-1) { - t.Fatalf("max: want %+v, got %+v", Int(treeSize-1), max) + if max, want := tr.Max(), Item(Int(treeSize-1)); max != want { + t.Fatalf("max: want %+v, got %+v", want, max) } got := all(tr) want := rang(treeSize) @@ -114,7 +114,7 @@ func TestBTree(t *testing.T) { } for _, item := range perm(treeSize) { - if _, found := tr.Delete(item); !found { + if x := tr.Delete(item); x == nil { t.Fatalf("didn't find %v", item) } } @@ -125,55 +125,44 @@ func TestBTree(t *testing.T) { } func ExampleBTree() { - tr := New[Int](*btreeDegree) + tr := New(*btreeDegree) for i := Int(0); i < 10; i++ { tr.ReplaceOrInsert(i) } fmt.Println("len: ", tr.Len()) - fmt.Printf("get3: ") - fmt.Println(tr.Get(Int(3))) - fmt.Printf("get100: ") - fmt.Println(tr.Get(Int(100))) - fmt.Printf("del4: ") - fmt.Println(tr.Delete(Int(4))) - fmt.Printf("del100: ") - fmt.Println(tr.Delete(Int(100))) - fmt.Printf("replace5: ") - fmt.Println(tr.ReplaceOrInsert(Int(5))) - fmt.Printf("replace100: ") - fmt.Println(tr.ReplaceOrInsert(Int(100))) - fmt.Printf("min: ") - fmt.Println(tr.Min()) - fmt.Printf("delmin: ") - fmt.Println(tr.DeleteMin()) - fmt.Printf("max: ") - fmt.Println(tr.Max()) - fmt.Printf("delmax: ") - fmt.Println(tr.DeleteMax()) - fmt.Printf("len: ") - fmt.Println(tr.Len()) + fmt.Println("get3: ", tr.Get(Int(3))) + fmt.Println("get100: ", tr.Get(Int(100))) + fmt.Println("del4: ", tr.Delete(Int(4))) + fmt.Println("del100: ", tr.Delete(Int(100))) + fmt.Println("replace5: ", tr.ReplaceOrInsert(Int(5))) + fmt.Println("replace100:", tr.ReplaceOrInsert(Int(100))) + fmt.Println("min: ", tr.Min()) + fmt.Println("delmin: ", tr.DeleteMin()) + fmt.Println("max: ", tr.Max()) + fmt.Println("delmax: ", tr.DeleteMax()) + fmt.Println("len: ", tr.Len()) // Output: // len: 10 - // get3: 3 true - // get100: 0 false - // del4: 4 true - // del100: 0 false - // replace5: 5 true - // replace100: 0 false - // min: 0 true - // delmin: 0 true - // max: 100 true - // delmax: 100 true + // get3: 3 + // get100: + // del4: 4 + // del100: + // replace5: 5 + // replace100: + // min: 0 + // delmin: 0 + // max: 100 + // delmax: 100 // len: 8 } func TestDeleteMin(t *testing.T) { - tr := New[Int](3) + tr := New(3) for _, v := range perm(100) { tr.ReplaceOrInsert(v) } - var got []Int - for v, found := tr.DeleteMin(); found; v, found = tr.DeleteMin() { + var got []Item + for v := tr.DeleteMin(); v != nil; v = tr.DeleteMin() { got = append(got, v) } if want := rang(100); !reflect.DeepEqual(got, want) { @@ -182,12 +171,12 @@ func TestDeleteMin(t *testing.T) { } func TestDeleteMax(t *testing.T) { - tr := New[Int](3) + tr := New(3) for _, v := range perm(100) { tr.ReplaceOrInsert(v) } - var got []Int - for v, found := tr.DeleteMax(); found; v, found = tr.DeleteMax() { + var got []Item + for v := tr.DeleteMax(); v != nil; v = tr.DeleteMax() { got = append(got, v) } // Reverse our list. @@ -200,12 +189,12 @@ func TestDeleteMax(t *testing.T) { } func TestAscendRange(t *testing.T) { - tr := New[Int](2) + tr := New(2) for _, v := range perm(100) { tr.ReplaceOrInsert(v) } - var got []Int - tr.AscendRange(Int(40), Int(60), func(a Int) bool { + var got []Item + tr.AscendRange(Int(40), Int(60), func(a Item) bool { got = append(got, a) return true }) @@ -213,8 +202,8 @@ func TestAscendRange(t *testing.T) { t.Fatalf("ascendrange:\n got: %v\nwant: %v", got, want) } got = got[:0] - tr.AscendRange(Int(40), Int(60), func(a Int) bool { - if a > 50 { + tr.AscendRange(Int(40), Int(60), func(a Item) bool { + if a.(Int) > 50 { return false } got = append(got, a) @@ -226,12 +215,12 @@ func TestAscendRange(t *testing.T) { } func TestDescendRange(t *testing.T) { - tr := New[Int](2) + tr := New(2) for _, v := range perm(100) { tr.ReplaceOrInsert(v) } - var got []Int - tr.DescendRange(Int(60), Int(40), func(a Int) bool { + var got []Item + tr.DescendRange(Int(60), Int(40), func(a Item) bool { got = append(got, a) return true }) @@ -239,8 +228,8 @@ func TestDescendRange(t *testing.T) { t.Fatalf("descendrange:\n got: %v\nwant: %v", got, want) } got = got[:0] - tr.DescendRange(Int(60), Int(40), func(a Int) bool { - if a < 50 { + tr.DescendRange(Int(60), Int(40), func(a Item) bool { + if a.(Int) < 50 { return false } got = append(got, a) @@ -250,14 +239,13 @@ func TestDescendRange(t *testing.T) { t.Fatalf("descendrange:\n got: %v\nwant: %v", got, want) } } - func TestAscendLessThan(t *testing.T) { - tr := New[Int](*btreeDegree) + tr := New(*btreeDegree) for _, v := range perm(100) { tr.ReplaceOrInsert(v) } - var got []Int - tr.AscendLessThan(Int(60), func(a Int) bool { + var got []Item + tr.AscendLessThan(Int(60), func(a Item) bool { got = append(got, a) return true }) @@ -265,8 +253,8 @@ func TestAscendLessThan(t *testing.T) { t.Fatalf("ascendrange:\n got: %v\nwant: %v", got, want) } got = got[:0] - tr.AscendLessThan(Int(60), func(a Int) bool { - if a > 50 { + tr.AscendLessThan(Int(60), func(a Item) bool { + if a.(Int) > 50 { return false } got = append(got, a) @@ -278,12 +266,12 @@ func TestAscendLessThan(t *testing.T) { } func TestDescendLessOrEqual(t *testing.T) { - tr := New[Int](*btreeDegree) + tr := New(*btreeDegree) for _, v := range perm(100) { tr.ReplaceOrInsert(v) } - var got []Int - tr.DescendLessOrEqual(Int(40), func(a Int) bool { + var got []Item + tr.DescendLessOrEqual(Int(40), func(a Item) bool { got = append(got, a) return true }) @@ -291,8 +279,8 @@ func TestDescendLessOrEqual(t *testing.T) { t.Fatalf("descendlessorequal:\n got: %v\nwant: %v", got, want) } got = got[:0] - tr.DescendLessOrEqual(Int(60), func(a Int) bool { - if a < 50 { + tr.DescendLessOrEqual(Int(60), func(a Item) bool { + if a.(Int) < 50 { return false } got = append(got, a) @@ -302,14 +290,13 @@ func TestDescendLessOrEqual(t *testing.T) { t.Fatalf("descendlessorequal:\n got: %v\nwant: %v", got, want) } } - func TestAscendGreaterOrEqual(t *testing.T) { - tr := New[Int](*btreeDegree) + tr := New(*btreeDegree) for _, v := range perm(100) { tr.ReplaceOrInsert(v) } - var got []Int - tr.AscendGreaterOrEqual(Int(40), func(a Int) bool { + var got []Item + tr.AscendGreaterOrEqual(Int(40), func(a Item) bool { got = append(got, a) return true }) @@ -317,8 +304,8 @@ func TestAscendGreaterOrEqual(t *testing.T) { t.Fatalf("ascendrange:\n got: %v\nwant: %v", got, want) } got = got[:0] - tr.AscendGreaterOrEqual(Int(40), func(a Int) bool { - if a > 50 { + tr.AscendGreaterOrEqual(Int(40), func(a Item) bool { + if a.(Int) > 50 { return false } got = append(got, a) @@ -330,12 +317,12 @@ func TestAscendGreaterOrEqual(t *testing.T) { } func TestDescendGreaterThan(t *testing.T) { - tr := New[Int](*btreeDegree) + tr := New(*btreeDegree) for _, v := range perm(100) { tr.ReplaceOrInsert(v) } - var got []Int - tr.DescendGreaterThan(Int(40), func(a Int) bool { + var got []Item + tr.DescendGreaterThan(Int(40), func(a Item) bool { got = append(got, a) return true }) @@ -343,8 +330,8 @@ func TestDescendGreaterThan(t *testing.T) { t.Fatalf("descendgreaterthan:\n got: %v\nwant: %v", got, want) } got = got[:0] - tr.DescendGreaterThan(Int(40), func(a Int) bool { - if a < 50 { + tr.DescendGreaterThan(Int(40), func(a Item) bool { + if a.(Int) < 50 { return false } got = append(got, a) @@ -363,7 +350,7 @@ func BenchmarkInsert(b *testing.B) { b.StartTimer() i := 0 for i < b.N { - tr := New[Int](*btreeDegree) + tr := New(*btreeDegree) for _, item := range insertP { tr.ReplaceOrInsert(item) i++ @@ -378,21 +365,21 @@ func BenchmarkSeek(b *testing.B) { b.StopTimer() size := 100000 insertP := perm(size) - tr := New[Int](*btreeDegree) + tr := New(*btreeDegree) for _, item := range insertP { tr.ReplaceOrInsert(item) } b.StartTimer() for i := 0; i < b.N; i++ { - tr.AscendGreaterOrEqual(Int(i%size), func(i Int) bool { return false }) + tr.AscendGreaterOrEqual(Int(i%size), func(i Item) bool { return false }) } } func BenchmarkDeleteInsert(b *testing.B) { b.StopTimer() insertP := perm(benchmarkTreeSize) - tr := New[Int](*btreeDegree) + tr := New(*btreeDegree) for _, item := range insertP { tr.ReplaceOrInsert(item) } @@ -406,7 +393,7 @@ func BenchmarkDeleteInsert(b *testing.B) { func BenchmarkDeleteInsertCloneOnce(b *testing.B) { b.StopTimer() insertP := perm(benchmarkTreeSize) - tr := New[Int](*btreeDegree) + tr := New(*btreeDegree) for _, item := range insertP { tr.ReplaceOrInsert(item) } @@ -421,7 +408,7 @@ func BenchmarkDeleteInsertCloneOnce(b *testing.B) { func BenchmarkDeleteInsertCloneEachTime(b *testing.B) { b.StopTimer() insertP := perm(benchmarkTreeSize) - tr := New[Int](*btreeDegree) + tr := New(*btreeDegree) for _, item := range insertP { tr.ReplaceOrInsert(item) } @@ -441,7 +428,7 @@ func BenchmarkDelete(b *testing.B) { i := 0 for i < b.N { b.StopTimer() - tr := New[Int](*btreeDegree) + tr := New(*btreeDegree) for _, v := range insertP { tr.ReplaceOrInsert(v) } @@ -467,7 +454,7 @@ func BenchmarkGet(b *testing.B) { i := 0 for i < b.N { b.StopTimer() - tr := New[Int](*btreeDegree) + tr := New(*btreeDegree) for _, v := range insertP { tr.ReplaceOrInsert(v) } @@ -490,7 +477,7 @@ func BenchmarkGetCloneEachTime(b *testing.B) { i := 0 for i < b.N { b.StopTimer() - tr := New[Int](*btreeDegree) + tr := New(*btreeDegree) for _, v := range insertP { tr.ReplaceOrInsert(v) } @@ -506,14 +493,14 @@ func BenchmarkGetCloneEachTime(b *testing.B) { } } -type byInts []Int +type byInts []Item func (a byInts) Len() int { return len(a) } func (a byInts) Less(i, j int) bool { - return a[i] < a[j] + return a[i].(Int) < a[j].(Int) } func (a byInts) Swap(i, j int) { @@ -522,7 +509,7 @@ func (a byInts) Swap(i, j int) { func BenchmarkAscend(b *testing.B) { arr := perm(benchmarkTreeSize) - tr := New[Int](*btreeDegree) + tr := New(*btreeDegree) for _, v := range arr { tr.ReplaceOrInsert(v) } @@ -530,9 +517,9 @@ func BenchmarkAscend(b *testing.B) { b.ResetTimer() for i := 0; i < b.N; i++ { j := 0 - tr.Ascend(func(item Int) bool { - if item != arr[j] { - b.Fatalf("mismatch: expected: %v, got %v", arr[j], item) + tr.Ascend(func(item Item) bool { + if item.(Int) != arr[j].(Int) { + b.Fatalf("mismatch: expected: %v, got %v", arr[j].(Int), item.(Int)) } j++ return true @@ -542,7 +529,7 @@ func BenchmarkAscend(b *testing.B) { func BenchmarkDescend(b *testing.B) { arr := perm(benchmarkTreeSize) - tr := New[Int](*btreeDegree) + tr := New(*btreeDegree) for _, v := range arr { tr.ReplaceOrInsert(v) } @@ -550,9 +537,9 @@ func BenchmarkDescend(b *testing.B) { b.ResetTimer() for i := 0; i < b.N; i++ { j := len(arr) - 1 - tr.Descend(func(item Int) bool { - if item != arr[j] { - b.Fatalf("mismatch: expected: %v, got %v", arr[j], item) + tr.Descend(func(item Item) bool { + if item.(Int) != arr[j].(Int) { + b.Fatalf("mismatch: expected: %v, got %v", arr[j].(Int), item.(Int)) } j-- return true @@ -561,7 +548,7 @@ func BenchmarkDescend(b *testing.B) { } func BenchmarkAscendRange(b *testing.B) { arr := perm(benchmarkTreeSize) - tr := New[Int](*btreeDegree) + tr := New(*btreeDegree) for _, v := range arr { tr.ReplaceOrInsert(v) } @@ -569,9 +556,9 @@ func BenchmarkAscendRange(b *testing.B) { b.ResetTimer() for i := 0; i < b.N; i++ { j := 100 - tr.AscendRange(Int(100), arr[len(arr)-100], func(item Int) bool { - if item != arr[j] { - b.Fatalf("mismatch: expected: %v, got %v", arr[j], item) + tr.AscendRange(Int(100), arr[len(arr)-100], func(item Item) bool { + if item.(Int) != arr[j].(Int) { + b.Fatalf("mismatch: expected: %v, got %v", arr[j].(Int), item.(Int)) } j++ return true @@ -584,7 +571,7 @@ func BenchmarkAscendRange(b *testing.B) { func BenchmarkDescendRange(b *testing.B) { arr := perm(benchmarkTreeSize) - tr := New[Int](*btreeDegree) + tr := New(*btreeDegree) for _, v := range arr { tr.ReplaceOrInsert(v) } @@ -592,9 +579,9 @@ func BenchmarkDescendRange(b *testing.B) { b.ResetTimer() for i := 0; i < b.N; i++ { j := len(arr) - 100 - tr.DescendRange(arr[len(arr)-100], Int(100), func(item Int) bool { - if item != arr[j] { - b.Fatalf("mismatch: expected: %v, got %v", arr[j], item) + tr.DescendRange(arr[len(arr)-100], Int(100), func(item Item) bool { + if item.(Int) != arr[j].(Int) { + b.Fatalf("mismatch: expected: %v, got %v", arr[j].(Int), item.(Int)) } j-- return true @@ -606,7 +593,7 @@ func BenchmarkDescendRange(b *testing.B) { } func BenchmarkAscendGreaterOrEqual(b *testing.B) { arr := perm(benchmarkTreeSize) - tr := New[Int](*btreeDegree) + tr := New(*btreeDegree) for _, v := range arr { tr.ReplaceOrInsert(v) } @@ -615,9 +602,9 @@ func BenchmarkAscendGreaterOrEqual(b *testing.B) { for i := 0; i < b.N; i++ { j := 100 k := 0 - tr.AscendGreaterOrEqual(Int(100), func(item Int) bool { - if item != arr[j] { - b.Fatalf("mismatch: expected: %v, got %v", arr[j], item) + tr.AscendGreaterOrEqual(Int(100), func(item Item) bool { + if item.(Int) != arr[j].(Int) { + b.Fatalf("mismatch: expected: %v, got %v", arr[j].(Int), item.(Int)) } j++ k++ @@ -633,7 +620,7 @@ func BenchmarkAscendGreaterOrEqual(b *testing.B) { } func BenchmarkDescendLessOrEqual(b *testing.B) { arr := perm(benchmarkTreeSize) - tr := New[Int](*btreeDegree) + tr := New(*btreeDegree) for _, v := range arr { tr.ReplaceOrInsert(v) } @@ -642,9 +629,9 @@ func BenchmarkDescendLessOrEqual(b *testing.B) { for i := 0; i < b.N; i++ { j := len(arr) - 100 k := len(arr) - tr.DescendLessOrEqual(arr[len(arr)-100], func(item Int) bool { - if item != arr[j] { - b.Fatalf("mismatch: expected: %v, got %v", arr[j], item) + tr.DescendLessOrEqual(arr[len(arr)-100], func(item Item) bool { + if item.(Int) != arr[j].(Int) { + b.Fatalf("mismatch: expected: %v, got %v", arr[j].(Int), item.(Int)) } j-- k-- @@ -661,7 +648,7 @@ func BenchmarkDescendLessOrEqual(b *testing.B) { const cloneTestSize = 10000 -func cloneTest[T Item[T]](t *testing.T, b *BTree[T], start int, p []T, wg *sync.WaitGroup, trees *[]*BTree[T], lock *sync.Mutex) { +func cloneTest(t *testing.T, b *BTree, start int, p []Item, wg *sync.WaitGroup, trees *[]*BTree, lock *sync.Mutex) { t.Logf("Starting new clone at %v", start) lock.Lock() *trees = append(*trees, b) @@ -677,8 +664,8 @@ func cloneTest[T Item[T]](t *testing.T, b *BTree[T], start int, p []T, wg *sync. } func TestCloneConcurrentOperations(t *testing.T) { - b := New[Int](*btreeDegree) - trees := []*BTree[Int]{} + b := New(*btreeDegree) + trees := []*BTree{} p := perm(cloneTestSize) var wg sync.WaitGroup wg.Add(1) @@ -706,7 +693,7 @@ func TestCloneConcurrentOperations(t *testing.T) { wg.Wait() t.Log("Checking all values again") for i, tree := range trees { - var wantpart []Int + var wantpart []Item if i < len(trees)/2 { wantpart = want[:cloneTestSize/2] } else { @@ -722,16 +709,16 @@ func BenchmarkDeleteAndRestore(b *testing.B) { items := perm(16392) b.ResetTimer() b.Run(`CopyBigFreeList`, func(b *testing.B) { - fl := NewFreeList[Int](16392) - tr := NewWithFreeList[Int](*btreeDegree, fl) + fl := NewFreeList(16392) + tr := NewWithFreeList(*btreeDegree, fl) for _, v := range items { tr.ReplaceOrInsert(v) } b.ReportAllocs() b.ResetTimer() for i := 0; i < b.N; i++ { - dels := make([]Int, 0, tr.Len()) - tr.Ascend(ItemIterator[Int](func(b Int) bool { + dels := make([]Item, 0, tr.Len()) + tr.Ascend(ItemIterator(func(b Item) bool { dels = append(dels, b) return true })) @@ -739,22 +726,22 @@ func BenchmarkDeleteAndRestore(b *testing.B) { tr.Delete(del) } // tr is now empty, we make a new empty copy of it. - tr = NewWithFreeList[Int](*btreeDegree, fl) + tr = NewWithFreeList(*btreeDegree, fl) for _, v := range items { tr.ReplaceOrInsert(v) } } }) b.Run(`Copy`, func(b *testing.B) { - tr := New[Int](*btreeDegree) + tr := New(*btreeDegree) for _, v := range items { tr.ReplaceOrInsert(v) } b.ReportAllocs() b.ResetTimer() for i := 0; i < b.N; i++ { - dels := make([]Int, 0, tr.Len()) - tr.Ascend(ItemIterator[Int](func(b Int) bool { + dels := make([]Item, 0, tr.Len()) + tr.Ascend(ItemIterator(func(b Item) bool { dels = append(dels, b) return true })) @@ -762,15 +749,15 @@ func BenchmarkDeleteAndRestore(b *testing.B) { tr.Delete(del) } // tr is now empty, we make a new empty copy of it. - tr = New[Int](*btreeDegree) + tr = New(*btreeDegree) for _, v := range items { tr.ReplaceOrInsert(v) } } }) b.Run(`ClearBigFreelist`, func(b *testing.B) { - fl := NewFreeList[Int](16392) - tr := NewWithFreeList[Int](*btreeDegree, fl) + fl := NewFreeList(16392) + tr := NewWithFreeList(*btreeDegree, fl) for _, v := range items { tr.ReplaceOrInsert(v) } @@ -784,7 +771,7 @@ func BenchmarkDeleteAndRestore(b *testing.B) { } }) b.Run(`Clear`, func(b *testing.B) { - tr := New[Int](*btreeDegree) + tr := New(*btreeDegree) for _, v := range items { tr.ReplaceOrInsert(v) } diff --git a/go.mod b/go.mod index 58b7d0d..ba383a2 100644 --- a/go.mod +++ b/go.mod @@ -14,6 +14,6 @@ module github.com/google/btree -go 1.18 +go 1.12 require github.com/petar/GoLLRB v0.0.0-20210522233825-ae3b015fd3e9 // indirect diff --git a/v2/.github/workflows/test.yml b/v2/.github/workflows/test.yml new file mode 100644 index 0000000..5f0d8ba --- /dev/null +++ b/v2/.github/workflows/test.yml @@ -0,0 +1,14 @@ +on: [push, pull_request] +name: Test +jobs: + test: + runs-on: ubuntu-latest + steps: + - name: Install Go + uses: actions/setup-go@v2 + with: + go-version: 1.11.x + - name: Checkout code + uses: actions/checkout@v2 + - name: Test + run: go test -v ./... \ No newline at end of file diff --git a/v2/LICENSE b/v2/LICENSE new file mode 100644 index 0000000..d645695 --- /dev/null +++ b/v2/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/v2/README.md b/v2/README.md new file mode 100644 index 0000000..eab5dbf --- /dev/null +++ b/v2/README.md @@ -0,0 +1,10 @@ +# BTree implementation for Go + +This package provides an in-memory B-Tree implementation for Go, useful as +an ordered, mutable data structure. + +The API is based off of the wonderful +http://godoc.org/github.com/petar/GoLLRB/llrb, and is meant to allow btree to +act as a drop-in replacement for gollrb trees. + +See http://godoc.org/github.com/google/btree for documentation. diff --git a/v2/btree.go b/v2/btree.go new file mode 100644 index 0000000..5064f6f --- /dev/null +++ b/v2/btree.go @@ -0,0 +1,905 @@ +// Copyright 2014 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package btree implements in-memory B-Trees of arbitrary degree. +// +// btree implements an in-memory B-Tree for use as an ordered data structure. +// It is not meant for persistent storage solutions. +// +// It has a flatter structure than an equivalent red-black or other binary tree, +// which in some cases yields better memory usage and/or performance. +// See some discussion on the matter here: +// http://google-opensource.blogspot.com/2013/01/c-containers-that-save-memory-and-time.html +// Note, though, that this project is in no way related to the C++ B-Tree +// implementation written about there. +// +// Within this tree, each node contains a slice of items and a (possibly nil) +// slice of children. For basic numeric values or raw structs, this can cause +// efficiency differences when compared to equivalent C++ template code that +// stores values in arrays within the node: +// * Due to the overhead of storing values as interfaces (each +// value needs to be stored as the value itself, then 2 words for the +// interface pointing to that value and its type), resulting in higher +// memory use. +// * Since interfaces can point to values anywhere in memory, values are +// most likely not stored in contiguous blocks, resulting in a higher +// number of cache misses. +// These issues don't tend to matter, though, when working with strings or other +// heap-allocated structures, since C++-equivalent structures also must store +// pointers and also distribute their values across the heap. +// +// This implementation is designed to be a drop-in replacement to gollrb.LLRB +// trees, (http://github.com/petar/gollrb), an excellent and probably the most +// widely used ordered tree implementation in the Go ecosystem currently. +// Its functions, therefore, exactly mirror those of +// llrb.LLRB where possible. Unlike gollrb, though, we currently don't +// support storing multiple equivalent values. +package v2 + +import ( + "fmt" + "io" + "sort" + "strings" + "sync" +) + +// Item represents a single object in the tree. +type Item[T any] interface { + // Less tests whether the current item is less than the given argument. + // + // This must provide a strict weak ordering. + // If !a.Less(b) && !b.Less(a), we treat this to mean a == b (i.e. we can only + // hold one of either a or b in the tree). + Less(than T) bool +} + +const ( + DefaultFreeListSize = 32 +) + +// FreeList represents a free list of btree nodes. By default each +// BTree has its own FreeList, but multiple BTrees can share the same +// FreeList. +// Two Btrees using the same freelist are safe for concurrent write access. +type FreeList[T Item[T]] struct { + mu sync.Mutex + freelist []*node[T] +} + +// NewFreeList creates a new free list. +// size is the maximum size of the returned free list. +func NewFreeList[T Item[T]](size int) *FreeList[T] { + return &FreeList[T]{freelist: make([]*node[T], 0, size)} +} + +func (f *FreeList[T]) newNode() (n *node[T]) { + f.mu.Lock() + index := len(f.freelist) - 1 + if index < 0 { + f.mu.Unlock() + return new(node[T]) + } + n = f.freelist[index] + f.freelist[index] = nil + f.freelist = f.freelist[:index] + f.mu.Unlock() + return +} + +// freeNode adds the given node to the list, returning true if it was added +// and false if it was discarded. +func (f *FreeList[T]) freeNode(n *node[T]) (out bool) { + f.mu.Lock() + if len(f.freelist) < cap(f.freelist) { + f.freelist = append(f.freelist, n) + out = true + } + f.mu.Unlock() + return +} + +// ItemIterator allows callers of Ascend* to iterate in-order over portions of +// the tree. When this function returns false, iteration will stop and the +// associated Ascend* function will immediately return. +type ItemIterator[T Item[T]] func(i T) bool + +// New creates a new B-Tree with the given degree. +// +// New(2), for example, will create a 2-3-4 tree (each node contains 1-3 items +// and 2-4 children). +func New[T Item[T]](degree int) *BTree[T] { + return NewWithFreeList[T](degree, NewFreeList[T](DefaultFreeListSize)) +} + +// NewWithFreeList creates a new B-Tree that uses the given node free list. +func NewWithFreeList[T Item[T]](degree int, f *FreeList[T]) *BTree[T] { + if degree <= 1 { + panic("bad degree") + } + return &BTree[T]{ + degree: degree, + cow: ©OnWriteContext[T]{freelist: f}, + } +} + +// items stores items in a node. +type items[T Item[T]] []T + +// insertAt inserts a value into the given index, pushing all subsequent values +// forward. +func (s *items[T]) insertAt(index int, item T) { + var x T + *s = append(*s, x) + if index < len(*s) { + copy((*s)[index+1:], (*s)[index:]) + } + (*s)[index] = item +} + +// removeAt removes a value at a given index, pulling all subsequent values +// back. +func (s *items[T]) removeAt(index int) T { + var x T + item := (*s)[index] + copy((*s)[index:], (*s)[index+1:]) + (*s)[len(*s)-1] = x + *s = (*s)[:len(*s)-1] + return item +} + +// pop removes and returns the last element in the list. +func (s *items[T]) pop() (out T) { + var x T + index := len(*s) - 1 + out = (*s)[index] + (*s)[index] = x + *s = (*s)[:index] + return +} + +// truncate truncates this instance at index so that it contains only the +// first index items. index must be less than or equal to length. +func (s *items[T]) truncate(index int) { + var i int + var x T + var toClear items[T] + *s, toClear = (*s)[:index], (*s)[index:] + for len(toClear) > 0 { + for i = 0; i < 16 && i < len(toClear); i++ { + toClear[i] = x + } + toClear = toClear[i:] + } +} + +// find returns the index where the given item should be inserted into this +// list. 'found' is true if the item already exists in the list at the given +// index. +func (s items[T]) find(item T) (index int, found bool) { + i := sort.Search(len(s), func(i int) bool { + return item.Less(s[i]) + }) + if i > 0 && !s[i-1].Less(item) { + return i - 1, true + } + return i, false +} + +// children stores child nodes in a node. +type children[T Item[T]] []*node[T] + +// insertAt inserts a value into the given index, pushing all subsequent values +// forward. +func (s *children[T]) insertAt(index int, n *node[T]) { + *s = append(*s, nil) + if index < len(*s) { + copy((*s)[index+1:], (*s)[index:]) + } + (*s)[index] = n +} + +// removeAt removes a value at a given index, pulling all subsequent values +// back. +func (s *children[T]) removeAt(index int) *node[T] { + n := (*s)[index] + copy((*s)[index:], (*s)[index+1:]) + (*s)[len(*s)-1] = nil + *s = (*s)[:len(*s)-1] + return n +} + +// pop removes and returns the last element in the list. +func (s *children[T]) pop() (out *node[T]) { + index := len(*s) - 1 + out = (*s)[index] + (*s)[index] = nil + *s = (*s)[:index] + return +} + +// truncate truncates this instance at index so that it contains only the +// first index children. index must be less than or equal to length. +func (s *children[T]) truncate(index int) { + var i int + var toClear children[T] + *s, toClear = (*s)[:index], (*s)[index:] + for len(toClear) > 0 { + for i = 0; i < 16 && i < len(toClear); i++ { + toClear[i] = nil + } + toClear = toClear[i:] + } +} + +// node is an internal node in a tree. +// +// It must at all times maintain the invariant that either +// * len(children) == 0, len(items) unconstrained +// * len(children) == len(items) + 1 +type node[T Item[T]] struct { + items items[T] + children children[T] + cow *copyOnWriteContext[T] +} + +func (n *node[T]) mutableFor(cow *copyOnWriteContext[T]) *node[T] { + if n.cow == cow { + return n + } + out := cow.newNode() + if cap(out.items) >= len(n.items) { + out.items = out.items[:len(n.items)] + } else { + out.items = make(items[T], len(n.items), cap(n.items)) + } + copy(out.items, n.items) + // Copy children + if cap(out.children) >= len(n.children) { + out.children = out.children[:len(n.children)] + } else { + out.children = make(children[T], len(n.children), cap(n.children)) + } + copy(out.children, n.children) + return out +} + +func (n *node[T]) mutableChild(i int) *node[T] { + c := n.children[i].mutableFor(n.cow) + n.children[i] = c + return c +} + +// split splits the given node at the given index. The current node shrinks, +// and this function returns the item that existed at that index and a new node +// containing all items/children after it. +func (n *node[T]) split(i int) (T, *node[T]) { + item := n.items[i] + next := n.cow.newNode() + next.items = append(next.items, n.items[i+1:]...) + n.items.truncate(i) + if len(n.children) > 0 { + next.children = append(next.children, n.children[i+1:]...) + n.children.truncate(i + 1) + } + return item, next +} + +// maybeSplitChild checks if a child should be split, and if so splits it. +// Returns whether or not a split occurred. +func (n *node[T]) maybeSplitChild(i, maxItems int) bool { + if len(n.children[i].items) < maxItems { + return false + } + first := n.mutableChild(i) + item, second := first.split(maxItems / 2) + n.items.insertAt(i, item) + n.children.insertAt(i+1, second) + return true +} + +// insert inserts an item into the subtree rooted at this node, making sure +// no nodes in the subtree exceed maxItems items. Should an equivalent item be +// be found/replaced by insert, it will be returned. +func (n *node[T]) insert(item T, maxItems int) (T, bool) { + var x T + i, found := n.items.find(item) + if found { + out := n.items[i] + n.items[i] = item + return out, true + } + if len(n.children) == 0 { + n.items.insertAt(i, item) + return x, false + } + if n.maybeSplitChild(i, maxItems) { + inTree := n.items[i] + switch { + case item.Less(inTree): + // no change, we want first split node + case inTree.Less(item): + i++ // we want second split node + default: + out := n.items[i] + n.items[i] = item + return out, true + } + } + return n.mutableChild(i).insert(item, maxItems) +} + +// get finds the given key in the subtree and returns it. +func (n *node[T]) get(key T) (T, bool) { + var x T + i, found := n.items.find(key) + if found { + return n.items[i], true + } else if len(n.children) > 0 { + return n.children[i].get(key) + } + return x, false +} + +// min returns the first item in the subtree. +func min[T Item[T]](n *node[T]) (T, bool) { + var x T + if n == nil { + return x, false + } + for len(n.children) > 0 { + n = n.children[0] + } + if len(n.items) == 0 { + return x, false + } + return n.items[0], true +} + +// max returns the last item in the subtree. +func max[T Item[T]](n *node[T]) (T, bool) { + var x T + if n == nil { + return x, false + } + for len(n.children) > 0 { + n = n.children[len(n.children)-1] + } + if len(n.items) == 0 { + return x, false + } + return n.items[len(n.items)-1], true +} + +// toRemove details what item to remove in a node.remove call. +type toRemove int + +const ( + removeItem toRemove = iota // removes the given item + removeMin // removes smallest item in the subtree + removeMax // removes largest item in the subtree +) + +// remove removes an item from the subtree rooted at this node. +func (n *node[T]) remove(item T, minItems int, typ toRemove) (T, bool) { + var x T + var i int + var found bool + switch typ { + case removeMax: + if len(n.children) == 0 { + return n.items.pop(), true + } + i = len(n.items) + case removeMin: + if len(n.children) == 0 { + return n.items.removeAt(0), true + } + i = 0 + case removeItem: + i, found = n.items.find(item) + if len(n.children) == 0 { + if found { + return n.items.removeAt(i), true + } + return x, false + } + default: + panic("invalid type") + } + // If we get to here, we have children. + if len(n.children[i].items) <= minItems { + return n.growChildAndRemove(i, item, minItems, typ) + } + child := n.mutableChild(i) + // Either we had enough items to begin with, or we've done some + // merging/stealing, because we've got enough now and we're ready to return + // stuff. + if found { + // The item exists at index 'i', and the child we've selected can give us a + // predecessor, since if we've gotten here it's got > minItems items in it. + out := n.items[i] + // We use our special-case 'remove' call with typ=maxItem to pull the + // predecessor of item i (the rightmost leaf of our immediate left child) + // and set it into where we pulled the item from. + n.items[i], _ = child.remove(x, minItems, removeMax) + return out, true + } + // Final recursive call. Once we're here, we know that the item isn't in this + // node and that the child is big enough to remove from. + return child.remove(item, minItems, typ) +} + +// growChildAndRemove grows child 'i' to make sure it's possible to remove an +// item from it while keeping it at minItems, then calls remove to actually +// remove it. +// +// Most documentation says we have to do two sets of special casing: +// 1) item is in this node +// 2) item is in child +// In both cases, we need to handle the two subcases: +// A) node has enough values that it can spare one +// B) node doesn't have enough values +// For the latter, we have to check: +// a) left sibling has node to spare +// b) right sibling has node to spare +// c) we must merge +// To simplify our code here, we handle cases #1 and #2 the same: +// If a node doesn't have enough items, we make sure it does (using a,b,c). +// We then simply redo our remove call, and the second time (regardless of +// whether we're in case 1 or 2), we'll have enough items and can guarantee +// that we hit case A. +func (n *node[T]) growChildAndRemove(i int, item T, minItems int, typ toRemove) (T, bool) { + if i > 0 && len(n.children[i-1].items) > minItems { + // Steal from left child + child := n.mutableChild(i) + stealFrom := n.mutableChild(i - 1) + stolenItem := stealFrom.items.pop() + child.items.insertAt(0, n.items[i-1]) + n.items[i-1] = stolenItem + if len(stealFrom.children) > 0 { + child.children.insertAt(0, stealFrom.children.pop()) + } + } else if i < len(n.items) && len(n.children[i+1].items) > minItems { + // steal from right child + child := n.mutableChild(i) + stealFrom := n.mutableChild(i + 1) + stolenItem := stealFrom.items.removeAt(0) + child.items = append(child.items, n.items[i]) + n.items[i] = stolenItem + if len(stealFrom.children) > 0 { + child.children = append(child.children, stealFrom.children.removeAt(0)) + } + } else { + if i >= len(n.items) { + i-- + } + child := n.mutableChild(i) + // merge with right child + mergeItem := n.items.removeAt(i) + mergeChild := n.children.removeAt(i + 1) + child.items = append(child.items, mergeItem) + child.items = append(child.items, mergeChild.items...) + child.children = append(child.children, mergeChild.children...) + n.cow.freeNode(mergeChild) + } + return n.remove(item, minItems, typ) +} + +type direction int + +const ( + descend = direction(-1) + ascend = direction(+1) +) + +// iterate provides a simple method for iterating over elements in the tree. +// +// When ascending, the 'start' should be less than 'stop' and when descending, +// the 'start' should be greater than 'stop'. Setting 'includeStart' to true +// will force the iterator to include the first item when it equals 'start', +// thus creating a "greaterOrEqual" or "lessThanEqual" rather than just a +// "greaterThan" or "lessThan" queries. +func (n *node[T]) iterate(dir direction, start, stop *T, includeStart bool, hit bool, iter ItemIterator[T]) (bool, bool) { + var ok, found bool + var index int + switch dir { + case ascend: + if start != nil { + index, _ = n.items.find(*start) + } + for i := index; i < len(n.items); i++ { + if len(n.children) > 0 { + if hit, ok = n.children[i].iterate(dir, start, stop, includeStart, hit, iter); !ok { + return hit, false + } + } + if !includeStart && !hit && start != nil && !(*start).Less(n.items[i]) { + hit = true + continue + } + hit = true + if stop != nil && !n.items[i].Less(*stop) { + return hit, false + } + if !iter(n.items[i]) { + return hit, false + } + } + if len(n.children) > 0 { + if hit, ok = n.children[len(n.children)-1].iterate(dir, start, stop, includeStart, hit, iter); !ok { + return hit, false + } + } + case descend: + if start != nil { + index, found = n.items.find(*start) + if !found { + index = index - 1 + } + } else { + index = len(n.items) - 1 + } + for i := index; i >= 0; i-- { + if start != nil && !n.items[i].Less(*start) { + if !includeStart || hit || (*start).Less(n.items[i]) { + continue + } + } + if len(n.children) > 0 { + if hit, ok = n.children[i+1].iterate(dir, start, stop, includeStart, hit, iter); !ok { + return hit, false + } + } + if stop != nil && !(*stop).Less(n.items[i]) { + return hit, false // continue + } + hit = true + if !iter(n.items[i]) { + return hit, false + } + } + if len(n.children) > 0 { + if hit, ok = n.children[0].iterate(dir, start, stop, includeStart, hit, iter); !ok { + return hit, false + } + } + } + return hit, true +} + +// Used for testing/debugging purposes. +func (n *node[T]) print(w io.Writer, level int) { + fmt.Fprintf(w, "%sNODE:%v\n", strings.Repeat(" ", level), n.items) + for _, c := range n.children { + c.print(w, level+1) + } +} + +// BTree is an implementation of a B-Tree. +// +// BTree stores Item instances in an ordered structure, allowing easy insertion, +// removal, and iteration. +// +// Write operations are not safe for concurrent mutation by multiple +// goroutines, but Read operations are. +type BTree[T Item[T]] struct { + degree int + length int + root *node[T] + cow *copyOnWriteContext[T] +} + +// copyOnWriteContext pointers determine node ownership... a tree with a write +// context equivalent to a node's write context is allowed to modify that node. +// A tree whose write context does not match a node's is not allowed to modify +// it, and must create a new, writable copy (IE: it's a Clone). +// +// When doing any write operation, we maintain the invariant that the current +// node's context is equal to the context of the tree that requested the write. +// We do this by, before we descend into any node, creating a copy with the +// correct context if the contexts don't match. +// +// Since the node we're currently visiting on any write has the requesting +// tree's context, that node is modifiable in place. Children of that node may +// not share context, but before we descend into them, we'll make a mutable +// copy. +type copyOnWriteContext[T Item[T]] struct { + freelist *FreeList[T] +} + +// Clone clones the btree, lazily. Clone should not be called concurrently, +// but the original tree (t) and the new tree (t2) can be used concurrently +// once the Clone call completes. +// +// The internal tree structure of b is marked read-only and shared between t and +// t2. Writes to both t and t2 use copy-on-write logic, creating new nodes +// whenever one of b's original nodes would have been modified. Read operations +// should have no performance degredation. Write operations for both t and t2 +// will initially experience minor slow-downs caused by additional allocs and +// copies due to the aforementioned copy-on-write logic, but should converge to +// the original performance characteristics of the original tree. +func (t *BTree[T]) Clone() (t2 *BTree[T]) { + // Create two entirely new copy-on-write contexts. + // This operation effectively creates three trees: + // the original, shared nodes (old b.cow) + // the new b.cow nodes + // the new out.cow nodes + cow1, cow2 := *t.cow, *t.cow + out := *t + t.cow = &cow1 + out.cow = &cow2 + return &out +} + +// maxItems returns the max number of items to allow per node. +func (t *BTree[T]) maxItems() int { + return t.degree*2 - 1 +} + +// minItems returns the min number of items to allow per node (ignored for the +// root node). +func (t *BTree[T]) minItems() int { + return t.degree - 1 +} + +func (c *copyOnWriteContext[T]) newNode() (n *node[T]) { + n = c.freelist.newNode() + n.cow = c + return +} + +type freeType int + +const ( + ftFreelistFull freeType = iota // node was freed (available for GC, not stored in freelist) + ftStored // node was stored in the freelist for later use + ftNotOwned // node was ignored by COW, since it's owned by another one +) + +// freeNode frees a node within a given COW context, if it's owned by that +// context. It returns what happened to the node (see freeType const +// documentation). +func (c *copyOnWriteContext[T]) freeNode(n *node[T]) freeType { + if n.cow == c { + // clear to allow GC + n.items.truncate(0) + n.children.truncate(0) + n.cow = nil + if c.freelist.freeNode(n) { + return ftStored + } else { + return ftFreelistFull + } + } else { + return ftNotOwned + } +} + +// ReplaceOrInsert adds the given item to the tree. If an item in the tree +// already equals the given one, it is removed from the tree and returned. +// Otherwise, nil is returned. +// +// nil cannot be added to the tree (will panic). +func (t *BTree[T]) ReplaceOrInsert(item T) (T, bool) { + var x T + if t.root == nil { + t.root = t.cow.newNode() + t.root.items = append(t.root.items, item) + t.length++ + return x, false + } else { + t.root = t.root.mutableFor(t.cow) + if len(t.root.items) >= t.maxItems() { + item2, second := t.root.split(t.maxItems() / 2) + oldroot := t.root + t.root = t.cow.newNode() + t.root.items = append(t.root.items, item2) + t.root.children = append(t.root.children, oldroot, second) + } + } + out, found := t.root.insert(item, t.maxItems()) + if !found { + t.length++ + } + return out, found +} + +// Delete removes an item equal to the passed in item from the tree, returning +// it. If no such item exists, returns nil. +func (t *BTree[T]) Delete(item T) (T, bool) { + return t.deleteItem(item, removeItem) +} + +// DeleteMin removes the smallest item in the tree and returns it. +// If no such item exists, returns nil. +func (t *BTree[T]) DeleteMin() (T, bool) { + var x T + return t.deleteItem(x, removeMin) +} + +// DeleteMax removes the largest item in the tree and returns it. +// If no such item exists, returns nil. +func (t *BTree[T]) DeleteMax() (T, bool) { + var x T + return t.deleteItem(x, removeMax) +} + +func (t *BTree[T]) deleteItem(item T, typ toRemove) (T, bool) { + var x T + if t.root == nil || len(t.root.items) == 0 { + return x, false + } + t.root = t.root.mutableFor(t.cow) + out, found := t.root.remove(item, t.minItems(), typ) + if len(t.root.items) == 0 && len(t.root.children) > 0 { + oldroot := t.root + t.root = t.root.children[0] + t.cow.freeNode(oldroot) + } + if found { + t.length-- + } + return out, found +} + +// AscendRange calls the iterator for every value in the tree within the range +// [greaterOrEqual, lessThan), until iterator returns false. +func (t *BTree[T]) AscendRange(greaterOrEqual, lessThan T, iterator ItemIterator[T]) { + if t.root == nil { + return + } + t.root.iterate(ascend, &greaterOrEqual, &lessThan, true, false, iterator) +} + +// AscendLessThan calls the iterator for every value in the tree within the range +// [first, pivot), until iterator returns false. +func (t *BTree[T]) AscendLessThan(pivot T, iterator ItemIterator[T]) { + if t.root == nil { + return + } + t.root.iterate(ascend, nil, &pivot, false, false, iterator) +} + +// AscendGreaterOrEqual calls the iterator for every value in the tree within +// the range [pivot, last], until iterator returns false. +func (t *BTree[T]) AscendGreaterOrEqual(pivot T, iterator ItemIterator[T]) { + if t.root == nil { + return + } + t.root.iterate(ascend, &pivot, nil, true, false, iterator) +} + +// Ascend calls the iterator for every value in the tree within the range +// [first, last], until iterator returns false. +func (t *BTree[T]) Ascend(iterator ItemIterator[T]) { + if t.root == nil { + return + } + t.root.iterate(ascend, nil, nil, false, false, iterator) +} + +// DescendRange calls the iterator for every value in the tree within the range +// [lessOrEqual, greaterThan), until iterator returns false. +func (t *BTree[T]) DescendRange(lessOrEqual, greaterThan T, iterator ItemIterator[T]) { + if t.root == nil { + return + } + t.root.iterate(descend, &lessOrEqual, &greaterThan, true, false, iterator) +} + +// DescendLessOrEqual calls the iterator for every value in the tree within the range +// [pivot, first], until iterator returns false. +func (t *BTree[T]) DescendLessOrEqual(pivot T, iterator ItemIterator[T]) { + if t.root == nil { + return + } + t.root.iterate(descend, &pivot, nil, true, false, iterator) +} + +// DescendGreaterThan calls the iterator for every value in the tree within +// the range [last, pivot), until iterator returns false. +func (t *BTree[T]) DescendGreaterThan(pivot T, iterator ItemIterator[T]) { + if t.root == nil { + return + } + t.root.iterate(descend, nil, &pivot, false, false, iterator) +} + +// Descend calls the iterator for every value in the tree within the range +// [last, first], until iterator returns false. +func (t *BTree[T]) Descend(iterator ItemIterator[T]) { + if t.root == nil { + return + } + t.root.iterate(descend, nil, nil, false, false, iterator) +} + +// Get looks for the key item in the tree, returning it. It returns nil if +// unable to find that item. +func (t *BTree[T]) Get(key T) (T, bool) { + var x T + if t.root == nil { + return x, false + } + return t.root.get(key) +} + +// Min returns the smallest item in the tree, or nil if the tree is empty. +func (t *BTree[T]) Min() (T, bool) { + return min(t.root) +} + +// Max returns the largest item in the tree, or nil if the tree is empty. +func (t *BTree[T]) Max() (T, bool) { + return max(t.root) +} + +// Has returns true if the given key is in the tree. +func (t *BTree[T]) Has(key T) bool { + _, found := t.Get(key) + return found +} + +// Len returns the number of items currently in the tree. +func (t *BTree[T]) Len() int { + return t.length +} + +// Clear removes all items from the btree. If addNodesToFreelist is true, +// t's nodes are added to its freelist as part of this call, until the freelist +// is full. Otherwise, the root node is simply dereferenced and the subtree +// left to Go's normal GC processes. +// +// This can be much faster +// than calling Delete on all elements, because that requires finding/removing +// each element in the tree and updating the tree accordingly. It also is +// somewhat faster than creating a new tree to replace the old one, because +// nodes from the old tree are reclaimed into the freelist for use by the new +// one, instead of being lost to the garbage collector. +// +// This call takes: +// O(1): when addNodesToFreelist is false, this is a single operation. +// O(1): when the freelist is already full, it breaks out immediately +// O(freelist size): when the freelist is empty and the nodes are all owned +// by this tree, nodes are added to the freelist until full. +// O(tree size): when all nodes are owned by another tree, all nodes are +// iterated over looking for nodes to add to the freelist, and due to +// ownership, none are. +func (t *BTree[T]) Clear(addNodesToFreelist bool) { + if t.root != nil && addNodesToFreelist { + t.root.reset(t.cow) + } + t.root, t.length = nil, 0 +} + +// reset returns a subtree to the freelist. It breaks out immediately if the +// freelist is full, since the only benefit of iterating is to fill that +// freelist up. Returns true if parent reset call should continue. +func (n *node[T]) reset(c *copyOnWriteContext[T]) bool { + for _, child := range n.children { + if !child.reset(c) { + return false + } + } + return c.freeNode(n) != ftFreelistFull +} + +// Int implements the Item interface for integers. +type Int int + +// Less returns true if int(a) < int(b). +func (a Int) Less(b Int) bool { + return a < b +} diff --git a/v2/btree_mem.go b/v2/btree_mem.go new file mode 100644 index 0000000..7f88809 --- /dev/null +++ b/v2/btree_mem.go @@ -0,0 +1,77 @@ +// Copyright 2014 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//go:build ignore +// +build ignore + +// This binary compares memory usage between btree and gollrb. +package main + +import ( + "flag" + "fmt" + "math/rand" + "runtime" + "time" + + btree "github.com/google/btree/v2" + "github.com/petar/GoLLRB/llrb" +) + +var ( + size = flag.Int("size", 1000000, "size of the tree to build") + degree = flag.Int("degree", 8, "degree of btree") + gollrb = flag.Bool("llrb", false, "use llrb instead of btree") +) + +func main() { + flag.Parse() + vals := rand.Perm(*size) + var t, v interface{} + v = vals + var stats runtime.MemStats + for i := 0; i < 10; i++ { + runtime.GC() + } + fmt.Println("-------- BEFORE ----------") + runtime.ReadMemStats(&stats) + fmt.Printf("%+v\n", stats) + start := time.Now() + if *gollrb { + tr := llrb.New() + for _, v := range vals { + tr.ReplaceOrInsert(llrb.Int(v)) + } + t = tr // keep it around + } else { + tr := btree.New[btree.Int](*degree) + for _, v := range vals { + tr.ReplaceOrInsert(btree.Int(v)) + } + t = tr // keep it around + } + fmt.Printf("%v inserts in %v\n", *size, time.Since(start)) + fmt.Println("-------- AFTER ----------") + runtime.ReadMemStats(&stats) + fmt.Printf("%+v\n", stats) + for i := 0; i < 10; i++ { + runtime.GC() + } + fmt.Println("-------- AFTER GC ----------") + runtime.ReadMemStats(&stats) + fmt.Printf("%+v\n", stats) + if t == v { + fmt.Println("to make sure vals and tree aren't GC'd") + } +} diff --git a/v2/btree_test.go b/v2/btree_test.go new file mode 100644 index 0000000..4648348 --- /dev/null +++ b/v2/btree_test.go @@ -0,0 +1,800 @@ +// Copyright 2014 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package v2 + +import ( + "flag" + "fmt" + "math/rand" + "reflect" + "sort" + "sync" + "testing" + "time" +) + +func init() { + seed := time.Now().Unix() + fmt.Println(seed) + rand.Seed(seed) +} + +// perm returns a random permutation of n Int items in the range [0, n). +func perm(n int) (out []Int) { + for _, v := range rand.Perm(n) { + out = append(out, Int(v)) + } + return +} + +// rang returns an ordered list of Int items in the range [0, n). +func rang(n int) (out []Int) { + for i := 0; i < n; i++ { + out = append(out, Int(i)) + } + return +} + +// all extracts all items from a tree in order as a slice. +func all[T Item[T]](t *BTree[T]) (out []T) { + t.Ascend(func(a T) bool { + out = append(out, a) + return true + }) + return +} + +// rangerev returns a reversed ordered list of Int items in the range [0, n). +func rangrev(n int) (out []Int) { + for i := n - 1; i >= 0; i-- { + out = append(out, Int(i)) + } + return +} + +// allrev extracts all items from a tree in reverse order as a slice. +func allrev[T Item[T]](t *BTree[T]) (out []T) { + t.Descend(func(a T) bool { + out = append(out, a) + return true + }) + return +} + +var btreeDegree = flag.Int("degree", 32, "B-Tree degree") + +func TestBTree(t *testing.T) { + tr := New[Int](*btreeDegree) + const treeSize = 10000 + for i := 0; i < 10; i++ { + if min, found := tr.Min(); found { + t.Fatalf("empty min, got %+v", min) + } + if max, found := tr.Max(); found { + t.Fatalf("empty max, got %+v", max) + } + for _, item := range perm(treeSize) { + if _, found := tr.ReplaceOrInsert(item); found { + t.Fatal("insert found item", item) + } + } + for _, item := range perm(treeSize) { + if _, found := tr.ReplaceOrInsert(item); !found { + t.Fatal("insert didn't find item", item) + } + } + if min, found := tr.Min(); !found || min != Int(0) { + t.Fatalf("min: want %+v, got %+v", Int(0), min) + } + if max, found := tr.Max(); !found || max != Int(treeSize-1) { + t.Fatalf("max: want %+v, got %+v", Int(treeSize-1), max) + } + got := all(tr) + want := rang(treeSize) + if !reflect.DeepEqual(got, want) { + t.Fatalf("mismatch:\n got: %v\nwant: %v", got, want) + } + + gotrev := allrev(tr) + wantrev := rangrev(treeSize) + if !reflect.DeepEqual(gotrev, wantrev) { + t.Fatalf("mismatch:\n got: %v\nwant: %v", got, want) + } + + for _, item := range perm(treeSize) { + if _, found := tr.Delete(item); !found { + t.Fatalf("didn't find %v", item) + } + } + if got = all(tr); len(got) > 0 { + t.Fatalf("some left!: %v", got) + } + } +} + +func ExampleBTree() { + tr := New[Int](*btreeDegree) + for i := Int(0); i < 10; i++ { + tr.ReplaceOrInsert(i) + } + fmt.Println("len: ", tr.Len()) + fmt.Printf("get3: ") + fmt.Println(tr.Get(Int(3))) + fmt.Printf("get100: ") + fmt.Println(tr.Get(Int(100))) + fmt.Printf("del4: ") + fmt.Println(tr.Delete(Int(4))) + fmt.Printf("del100: ") + fmt.Println(tr.Delete(Int(100))) + fmt.Printf("replace5: ") + fmt.Println(tr.ReplaceOrInsert(Int(5))) + fmt.Printf("replace100: ") + fmt.Println(tr.ReplaceOrInsert(Int(100))) + fmt.Printf("min: ") + fmt.Println(tr.Min()) + fmt.Printf("delmin: ") + fmt.Println(tr.DeleteMin()) + fmt.Printf("max: ") + fmt.Println(tr.Max()) + fmt.Printf("delmax: ") + fmt.Println(tr.DeleteMax()) + fmt.Printf("len: ") + fmt.Println(tr.Len()) + // Output: + // len: 10 + // get3: 3 true + // get100: 0 false + // del4: 4 true + // del100: 0 false + // replace5: 5 true + // replace100: 0 false + // min: 0 true + // delmin: 0 true + // max: 100 true + // delmax: 100 true + // len: 8 +} + +func TestDeleteMin(t *testing.T) { + tr := New[Int](3) + for _, v := range perm(100) { + tr.ReplaceOrInsert(v) + } + var got []Int + for v, found := tr.DeleteMin(); found; v, found = tr.DeleteMin() { + got = append(got, v) + } + if want := rang(100); !reflect.DeepEqual(got, want) { + t.Fatalf("ascendrange:\n got: %v\nwant: %v", got, want) + } +} + +func TestDeleteMax(t *testing.T) { + tr := New[Int](3) + for _, v := range perm(100) { + tr.ReplaceOrInsert(v) + } + var got []Int + for v, found := tr.DeleteMax(); found; v, found = tr.DeleteMax() { + got = append(got, v) + } + // Reverse our list. + for i := 0; i < len(got)/2; i++ { + got[i], got[len(got)-i-1] = got[len(got)-i-1], got[i] + } + if want := rang(100); !reflect.DeepEqual(got, want) { + t.Fatalf("ascendrange:\n got: %v\nwant: %v", got, want) + } +} + +func TestAscendRange(t *testing.T) { + tr := New[Int](2) + for _, v := range perm(100) { + tr.ReplaceOrInsert(v) + } + var got []Int + tr.AscendRange(Int(40), Int(60), func(a Int) bool { + got = append(got, a) + return true + }) + if want := rang(100)[40:60]; !reflect.DeepEqual(got, want) { + t.Fatalf("ascendrange:\n got: %v\nwant: %v", got, want) + } + got = got[:0] + tr.AscendRange(Int(40), Int(60), func(a Int) bool { + if a > 50 { + return false + } + got = append(got, a) + return true + }) + if want := rang(100)[40:51]; !reflect.DeepEqual(got, want) { + t.Fatalf("ascendrange:\n got: %v\nwant: %v", got, want) + } +} + +func TestDescendRange(t *testing.T) { + tr := New[Int](2) + for _, v := range perm(100) { + tr.ReplaceOrInsert(v) + } + var got []Int + tr.DescendRange(Int(60), Int(40), func(a Int) bool { + got = append(got, a) + return true + }) + if want := rangrev(100)[39:59]; !reflect.DeepEqual(got, want) { + t.Fatalf("descendrange:\n got: %v\nwant: %v", got, want) + } + got = got[:0] + tr.DescendRange(Int(60), Int(40), func(a Int) bool { + if a < 50 { + return false + } + got = append(got, a) + return true + }) + if want := rangrev(100)[39:50]; !reflect.DeepEqual(got, want) { + t.Fatalf("descendrange:\n got: %v\nwant: %v", got, want) + } +} + +func TestAscendLessThan(t *testing.T) { + tr := New[Int](*btreeDegree) + for _, v := range perm(100) { + tr.ReplaceOrInsert(v) + } + var got []Int + tr.AscendLessThan(Int(60), func(a Int) bool { + got = append(got, a) + return true + }) + if want := rang(100)[:60]; !reflect.DeepEqual(got, want) { + t.Fatalf("ascendrange:\n got: %v\nwant: %v", got, want) + } + got = got[:0] + tr.AscendLessThan(Int(60), func(a Int) bool { + if a > 50 { + return false + } + got = append(got, a) + return true + }) + if want := rang(100)[:51]; !reflect.DeepEqual(got, want) { + t.Fatalf("ascendrange:\n got: %v\nwant: %v", got, want) + } +} + +func TestDescendLessOrEqual(t *testing.T) { + tr := New[Int](*btreeDegree) + for _, v := range perm(100) { + tr.ReplaceOrInsert(v) + } + var got []Int + tr.DescendLessOrEqual(Int(40), func(a Int) bool { + got = append(got, a) + return true + }) + if want := rangrev(100)[59:]; !reflect.DeepEqual(got, want) { + t.Fatalf("descendlessorequal:\n got: %v\nwant: %v", got, want) + } + got = got[:0] + tr.DescendLessOrEqual(Int(60), func(a Int) bool { + if a < 50 { + return false + } + got = append(got, a) + return true + }) + if want := rangrev(100)[39:50]; !reflect.DeepEqual(got, want) { + t.Fatalf("descendlessorequal:\n got: %v\nwant: %v", got, want) + } +} + +func TestAscendGreaterOrEqual(t *testing.T) { + tr := New[Int](*btreeDegree) + for _, v := range perm(100) { + tr.ReplaceOrInsert(v) + } + var got []Int + tr.AscendGreaterOrEqual(Int(40), func(a Int) bool { + got = append(got, a) + return true + }) + if want := rang(100)[40:]; !reflect.DeepEqual(got, want) { + t.Fatalf("ascendrange:\n got: %v\nwant: %v", got, want) + } + got = got[:0] + tr.AscendGreaterOrEqual(Int(40), func(a Int) bool { + if a > 50 { + return false + } + got = append(got, a) + return true + }) + if want := rang(100)[40:51]; !reflect.DeepEqual(got, want) { + t.Fatalf("ascendrange:\n got: %v\nwant: %v", got, want) + } +} + +func TestDescendGreaterThan(t *testing.T) { + tr := New[Int](*btreeDegree) + for _, v := range perm(100) { + tr.ReplaceOrInsert(v) + } + var got []Int + tr.DescendGreaterThan(Int(40), func(a Int) bool { + got = append(got, a) + return true + }) + if want := rangrev(100)[:59]; !reflect.DeepEqual(got, want) { + t.Fatalf("descendgreaterthan:\n got: %v\nwant: %v", got, want) + } + got = got[:0] + tr.DescendGreaterThan(Int(40), func(a Int) bool { + if a < 50 { + return false + } + got = append(got, a) + return true + }) + if want := rangrev(100)[:50]; !reflect.DeepEqual(got, want) { + t.Fatalf("descendgreaterthan:\n got: %v\nwant: %v", got, want) + } +} + +const benchmarkTreeSize = 10000 + +func BenchmarkInsert(b *testing.B) { + b.StopTimer() + insertP := perm(benchmarkTreeSize) + b.StartTimer() + i := 0 + for i < b.N { + tr := New[Int](*btreeDegree) + for _, item := range insertP { + tr.ReplaceOrInsert(item) + i++ + if i >= b.N { + return + } + } + } +} + +func BenchmarkSeek(b *testing.B) { + b.StopTimer() + size := 100000 + insertP := perm(size) + tr := New[Int](*btreeDegree) + for _, item := range insertP { + tr.ReplaceOrInsert(item) + } + b.StartTimer() + + for i := 0; i < b.N; i++ { + tr.AscendGreaterOrEqual(Int(i%size), func(i Int) bool { return false }) + } +} + +func BenchmarkDeleteInsert(b *testing.B) { + b.StopTimer() + insertP := perm(benchmarkTreeSize) + tr := New[Int](*btreeDegree) + for _, item := range insertP { + tr.ReplaceOrInsert(item) + } + b.StartTimer() + for i := 0; i < b.N; i++ { + tr.Delete(insertP[i%benchmarkTreeSize]) + tr.ReplaceOrInsert(insertP[i%benchmarkTreeSize]) + } +} + +func BenchmarkDeleteInsertCloneOnce(b *testing.B) { + b.StopTimer() + insertP := perm(benchmarkTreeSize) + tr := New[Int](*btreeDegree) + for _, item := range insertP { + tr.ReplaceOrInsert(item) + } + tr = tr.Clone() + b.StartTimer() + for i := 0; i < b.N; i++ { + tr.Delete(insertP[i%benchmarkTreeSize]) + tr.ReplaceOrInsert(insertP[i%benchmarkTreeSize]) + } +} + +func BenchmarkDeleteInsertCloneEachTime(b *testing.B) { + b.StopTimer() + insertP := perm(benchmarkTreeSize) + tr := New[Int](*btreeDegree) + for _, item := range insertP { + tr.ReplaceOrInsert(item) + } + b.StartTimer() + for i := 0; i < b.N; i++ { + tr = tr.Clone() + tr.Delete(insertP[i%benchmarkTreeSize]) + tr.ReplaceOrInsert(insertP[i%benchmarkTreeSize]) + } +} + +func BenchmarkDelete(b *testing.B) { + b.StopTimer() + insertP := perm(benchmarkTreeSize) + removeP := perm(benchmarkTreeSize) + b.StartTimer() + i := 0 + for i < b.N { + b.StopTimer() + tr := New[Int](*btreeDegree) + for _, v := range insertP { + tr.ReplaceOrInsert(v) + } + b.StartTimer() + for _, item := range removeP { + tr.Delete(item) + i++ + if i >= b.N { + return + } + } + if tr.Len() > 0 { + panic(tr.Len()) + } + } +} + +func BenchmarkGet(b *testing.B) { + b.StopTimer() + insertP := perm(benchmarkTreeSize) + removeP := perm(benchmarkTreeSize) + b.StartTimer() + i := 0 + for i < b.N { + b.StopTimer() + tr := New[Int](*btreeDegree) + for _, v := range insertP { + tr.ReplaceOrInsert(v) + } + b.StartTimer() + for _, item := range removeP { + tr.Get(item) + i++ + if i >= b.N { + return + } + } + } +} + +func BenchmarkGetCloneEachTime(b *testing.B) { + b.StopTimer() + insertP := perm(benchmarkTreeSize) + removeP := perm(benchmarkTreeSize) + b.StartTimer() + i := 0 + for i < b.N { + b.StopTimer() + tr := New[Int](*btreeDegree) + for _, v := range insertP { + tr.ReplaceOrInsert(v) + } + b.StartTimer() + for _, item := range removeP { + tr = tr.Clone() + tr.Get(item) + i++ + if i >= b.N { + return + } + } + } +} + +type byInts []Int + +func (a byInts) Len() int { + return len(a) +} + +func (a byInts) Less(i, j int) bool { + return a[i] < a[j] +} + +func (a byInts) Swap(i, j int) { + a[i], a[j] = a[j], a[i] +} + +func BenchmarkAscend(b *testing.B) { + arr := perm(benchmarkTreeSize) + tr := New[Int](*btreeDegree) + for _, v := range arr { + tr.ReplaceOrInsert(v) + } + sort.Sort(byInts(arr)) + b.ResetTimer() + for i := 0; i < b.N; i++ { + j := 0 + tr.Ascend(func(item Int) bool { + if item != arr[j] { + b.Fatalf("mismatch: expected: %v, got %v", arr[j], item) + } + j++ + return true + }) + } +} + +func BenchmarkDescend(b *testing.B) { + arr := perm(benchmarkTreeSize) + tr := New[Int](*btreeDegree) + for _, v := range arr { + tr.ReplaceOrInsert(v) + } + sort.Sort(byInts(arr)) + b.ResetTimer() + for i := 0; i < b.N; i++ { + j := len(arr) - 1 + tr.Descend(func(item Int) bool { + if item != arr[j] { + b.Fatalf("mismatch: expected: %v, got %v", arr[j], item) + } + j-- + return true + }) + } +} +func BenchmarkAscendRange(b *testing.B) { + arr := perm(benchmarkTreeSize) + tr := New[Int](*btreeDegree) + for _, v := range arr { + tr.ReplaceOrInsert(v) + } + sort.Sort(byInts(arr)) + b.ResetTimer() + for i := 0; i < b.N; i++ { + j := 100 + tr.AscendRange(Int(100), arr[len(arr)-100], func(item Int) bool { + if item != arr[j] { + b.Fatalf("mismatch: expected: %v, got %v", arr[j], item) + } + j++ + return true + }) + if j != len(arr)-100 { + b.Fatalf("expected: %v, got %v", len(arr)-100, j) + } + } +} + +func BenchmarkDescendRange(b *testing.B) { + arr := perm(benchmarkTreeSize) + tr := New[Int](*btreeDegree) + for _, v := range arr { + tr.ReplaceOrInsert(v) + } + sort.Sort(byInts(arr)) + b.ResetTimer() + for i := 0; i < b.N; i++ { + j := len(arr) - 100 + tr.DescendRange(arr[len(arr)-100], Int(100), func(item Int) bool { + if item != arr[j] { + b.Fatalf("mismatch: expected: %v, got %v", arr[j], item) + } + j-- + return true + }) + if j != 100 { + b.Fatalf("expected: %v, got %v", len(arr)-100, j) + } + } +} +func BenchmarkAscendGreaterOrEqual(b *testing.B) { + arr := perm(benchmarkTreeSize) + tr := New[Int](*btreeDegree) + for _, v := range arr { + tr.ReplaceOrInsert(v) + } + sort.Sort(byInts(arr)) + b.ResetTimer() + for i := 0; i < b.N; i++ { + j := 100 + k := 0 + tr.AscendGreaterOrEqual(Int(100), func(item Int) bool { + if item != arr[j] { + b.Fatalf("mismatch: expected: %v, got %v", arr[j], item) + } + j++ + k++ + return true + }) + if j != len(arr) { + b.Fatalf("expected: %v, got %v", len(arr), j) + } + if k != len(arr)-100 { + b.Fatalf("expected: %v, got %v", len(arr)-100, k) + } + } +} +func BenchmarkDescendLessOrEqual(b *testing.B) { + arr := perm(benchmarkTreeSize) + tr := New[Int](*btreeDegree) + for _, v := range arr { + tr.ReplaceOrInsert(v) + } + sort.Sort(byInts(arr)) + b.ResetTimer() + for i := 0; i < b.N; i++ { + j := len(arr) - 100 + k := len(arr) + tr.DescendLessOrEqual(arr[len(arr)-100], func(item Int) bool { + if item != arr[j] { + b.Fatalf("mismatch: expected: %v, got %v", arr[j], item) + } + j-- + k-- + return true + }) + if j != -1 { + b.Fatalf("expected: %v, got %v", -1, j) + } + if k != 99 { + b.Fatalf("expected: %v, got %v", 99, k) + } + } +} + +const cloneTestSize = 10000 + +func cloneTest[T Item[T]](t *testing.T, b *BTree[T], start int, p []T, wg *sync.WaitGroup, trees *[]*BTree[T], lock *sync.Mutex) { + t.Logf("Starting new clone at %v", start) + lock.Lock() + *trees = append(*trees, b) + lock.Unlock() + for i := start; i < cloneTestSize; i++ { + b.ReplaceOrInsert(p[i]) + if i%(cloneTestSize/5) == 0 { + wg.Add(1) + go cloneTest(t, b.Clone(), i+1, p, wg, trees, lock) + } + } + wg.Done() +} + +func TestCloneConcurrentOperations(t *testing.T) { + b := New[Int](*btreeDegree) + trees := []*BTree[Int]{} + p := perm(cloneTestSize) + var wg sync.WaitGroup + wg.Add(1) + go cloneTest(t, b, 0, p, &wg, &trees, &sync.Mutex{}) + wg.Wait() + want := rang(cloneTestSize) + t.Logf("Starting equality checks on %d trees", len(trees)) + for i, tree := range trees { + if !reflect.DeepEqual(want, all(tree)) { + t.Errorf("tree %v mismatch", i) + } + } + t.Log("Removing half from first half") + toRemove := rang(cloneTestSize)[cloneTestSize/2:] + for i := 0; i < len(trees)/2; i++ { + tree := trees[i] + wg.Add(1) + go func() { + for _, item := range toRemove { + tree.Delete(item) + } + wg.Done() + }() + } + wg.Wait() + t.Log("Checking all values again") + for i, tree := range trees { + var wantpart []Int + if i < len(trees)/2 { + wantpart = want[:cloneTestSize/2] + } else { + wantpart = want + } + if got := all(tree); !reflect.DeepEqual(wantpart, got) { + t.Errorf("tree %v mismatch, want %v got %v", i, len(want), len(got)) + } + } +} + +func BenchmarkDeleteAndRestore(b *testing.B) { + items := perm(16392) + b.ResetTimer() + b.Run(`CopyBigFreeList`, func(b *testing.B) { + fl := NewFreeList[Int](16392) + tr := NewWithFreeList[Int](*btreeDegree, fl) + for _, v := range items { + tr.ReplaceOrInsert(v) + } + b.ReportAllocs() + b.ResetTimer() + for i := 0; i < b.N; i++ { + dels := make([]Int, 0, tr.Len()) + tr.Ascend(ItemIterator[Int](func(b Int) bool { + dels = append(dels, b) + return true + })) + for _, del := range dels { + tr.Delete(del) + } + // tr is now empty, we make a new empty copy of it. + tr = NewWithFreeList[Int](*btreeDegree, fl) + for _, v := range items { + tr.ReplaceOrInsert(v) + } + } + }) + b.Run(`Copy`, func(b *testing.B) { + tr := New[Int](*btreeDegree) + for _, v := range items { + tr.ReplaceOrInsert(v) + } + b.ReportAllocs() + b.ResetTimer() + for i := 0; i < b.N; i++ { + dels := make([]Int, 0, tr.Len()) + tr.Ascend(ItemIterator[Int](func(b Int) bool { + dels = append(dels, b) + return true + })) + for _, del := range dels { + tr.Delete(del) + } + // tr is now empty, we make a new empty copy of it. + tr = New[Int](*btreeDegree) + for _, v := range items { + tr.ReplaceOrInsert(v) + } + } + }) + b.Run(`ClearBigFreelist`, func(b *testing.B) { + fl := NewFreeList[Int](16392) + tr := NewWithFreeList[Int](*btreeDegree, fl) + for _, v := range items { + tr.ReplaceOrInsert(v) + } + b.ReportAllocs() + b.ResetTimer() + for i := 0; i < b.N; i++ { + tr.Clear(true) + for _, v := range items { + tr.ReplaceOrInsert(v) + } + } + }) + b.Run(`Clear`, func(b *testing.B) { + tr := New[Int](*btreeDegree) + for _, v := range items { + tr.ReplaceOrInsert(v) + } + b.ReportAllocs() + b.ResetTimer() + for i := 0; i < b.N; i++ { + tr.Clear(true) + for _, v := range items { + tr.ReplaceOrInsert(v) + } + } + }) +} diff --git a/v2/go.mod b/v2/go.mod new file mode 100644 index 0000000..3b19a4e --- /dev/null +++ b/v2/go.mod @@ -0,0 +1,19 @@ +// Copyright 2014 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +module github.com/google/btree/v2 + +go 1.18 + +require github.com/petar/GoLLRB v0.0.0-20210522233825-ae3b015fd3e9 // indirect