Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

added function node.insertWithIndex that returns index/placement #50

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
88 changes: 88 additions & 0 deletions btree_generic.go
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,14 @@ type node[T any] struct {
items items[T]
children items[*node[T]]
cow *copyOnWriteContext[T]
size int
}

func (n *node[T]) resize() {
n.size = len(n.items)
for _, c := range n.children {
n.size += c.size
}
}

func (n *node[T]) mutableFor(cow *copyOnWriteContext[T]) *node[T] {
Expand Down Expand Up @@ -271,6 +279,8 @@ func (n *node[T]) split(i int) (T, *node[T]) {
next.children = append(next.children, n.children[i+1:]...)
n.children.truncate(i + 1)
}
next.resize()
n.resize()
return item, next
}

Expand All @@ -287,6 +297,39 @@ func (n *node[T]) maybeSplitChild(i, maxItems int) bool {
return true
}

// insert inserts an item into the subtree rooted at this node, making sure
kroksys marked this conversation as resolved.
Show resolved Hide resolved
// no nodes in the subtree exceed maxItems items. Should an equivalent item be
// be found/replaced by insert, it will be returned. IndexSet holds children
// index path to inserted element; last indexSet element is inserted item index.
func (n *node[T]) insertWithIndex(item T, maxItems int, indexSet []int) (_ T, _ bool, _ []int) {
i, found := n.items.find(item, n.cow.less)
if found {
out := n.items[i]
n.items[i] = item
return out, true, indexSet
}
indexSet = append(indexSet, i)
if len(n.children) == 0 {
n.items.insertAt(i, item)
return n.items[i], false, indexSet
}
if n.maybeSplitChild(i, maxItems) {
inTree := n.items[i]
switch {
case n.cow.less(item, inTree):
// no change, we want first split node
case n.cow.less(inTree, item):
i++ // we want second split node
indexSet[len(indexSet)-1]++
default:
out := n.items[i]
n.items[i] = item
return out, true, indexSet
}
}
return n.mutableChild(i).insertWithIndex(item, maxItems, indexSet)
}

// 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.
Expand Down Expand Up @@ -678,6 +721,51 @@ func (c *copyOnWriteContext[T]) freeNode(n *node[T]) freeType {
}
}

// ReplaceOrInsertWithIndex 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,
// and the second return value is true. Otherwise, (zeroValue, false, index)
// Index represents sequential ordered position/placement.
//
// nil cannot be added to the tree (will panic).
func (t *BTreeG[T]) ReplaceOrInsertWithIndex(item T) (_ T, _ bool, _ int) {
if t.root == nil {
t.root = t.cow.newNode()
t.root.items = append(t.root.items, item)
t.length++
return
} 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)
t.root.resize()
}
}
out, outb, indexSet := t.root.insertWithIndex(item, t.maxItems(), nil)
if !outb {
t.length++
}
index := 0
if len(indexSet) != 0 {
index = indexSet[len(indexSet)-1]
indexSet = indexSet[:len(indexSet)-1]
if len(indexSet) != 0 {
n := t.root
for _, stopIndex := range indexSet {
index += stopIndex
for j := 0; j < stopIndex; j++ {
index += n.children[j].size
}
n = n.children[stopIndex]
}
}
}
return out, outb, index
}

// 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,
// and the second return value is true. Otherwise, (zeroValue, false)
Expand Down
30 changes: 30 additions & 0 deletions btree_generic_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -334,6 +334,19 @@ func TestDescendGreaterThanG(t *testing.T) {
}
}

func TestInsertGWithIndex(t *testing.T) {
insertP := rand.Perm(benchmarkTreeSize)
ordered := insertP
sort.Ints(ordered)
tr := NewOrderedG[int](*btreeDegree)
for want, item := range insertP {
_, _, got := tr.ReplaceOrInsertWithIndex(item)
if !reflect.DeepEqual(got, want) {
t.Fatalf("replaceorinsertwithindex:\n got: %v\nwant: %v", got, want)
}
}
}

func BenchmarkInsertG(b *testing.B) {
b.StopTimer()
insertP := rand.Perm(benchmarkTreeSize)
Expand All @@ -351,6 +364,23 @@ func BenchmarkInsertG(b *testing.B) {
}
}

func BenchmarkInsertGWithIndex(b *testing.B) {
b.StopTimer()
insertP := rand.Perm(benchmarkTreeSize)
b.StartTimer()
i := 0
for i < b.N {
tr := NewOrderedG[int](*btreeDegree)
for _, item := range insertP {
tr.ReplaceOrInsertWithIndex(item)
i++
if i >= b.N {
return
}
}
}
}

func BenchmarkSeekG(b *testing.B) {
b.StopTimer()
size := 100000
Expand Down