implement inplace insertion

master
magical 2022-01-23 05:33:59 +00:00
parent 966e6a5864
commit 0ef57e0865
2 changed files with 154 additions and 0 deletions

105
inplace.go 100644
View File

@ -0,0 +1,105 @@
package pmap
type InplaceMap interface {
Map
SetInplace(Key, Value) InplaceMap
}
func (p pmap) SetInplace(k Key, v Value) InplaceMap {
root, added := inplace_insert(p.root, k, v, p.hash)
p.root = root
if added {
p.len++
}
return p
}
func inplace_insert(n interface{}, key Key, val Value, hashFn HashFunc) (newNode interface{}, added bool) {
if n == nil {
return leaf{key, val}, true
}
hash := hashFn(key)
var insert func(n interface{}, shift uint32) interface{}
insert = func(n interface{}, shift uint32) interface{} {
// insert returns a new node (if the node should be replaced)
// or nil (if the node is unchanged or modified in place)
//fmt.Printf("insert %v %x %#v\n", shift, hash, n)
switch n := n.(type) {
case leaf:
if n.k == key {
// replace existing entry
added = false
return leaf{key, val}
} else if h := hashFn(n.k); h == hash {
// collision
added = true
return &collision{hash, []leaf{{key, val}, n}}
} else {
if h>>shift == hash>>shift {
panic("pmap: infinite loop in insert")
}
// not a collision, so we must still have some hash bits left
// split the trie
x := newnode(n, h, shift)
if x0 := insert(x, shift); x0 != nil {
return x0
}
return x
}
case *node:
c := n.getNode(shift, hash, key)
if c == nil {
// new node
c = leaf{key, val}
added = true
m := bitmask(hash >> shift)
n.bitmap |= m
i := n.index(m)
n.child = append(n.child, nil) // allocate space
copy(n.child[i+1:], n.child[i:]) // shift array up
n.child[i] = c // mutate node
} else {
c = insert(c, shift+nodeShift)
if c == nil {
return nil
}
m := bitmask(hash >> shift)
i := n.index(m)
n.child[i] = c // mutate node
}
n.check()
return nil
case *collision:
if n.hash != hash {
// not a collision, so we must still have some hash bits left
// split the trie
x := newnode(n, n.hash, shift)
if x0 := insert(x, shift); x0 != nil {
return x0
}
return x
}
for i := range n.leaf {
if key == n.leaf[i].k {
// replace existing entry
n.leaf[i] = leaf{key, val} // mutate
added = false
return nil
}
}
// new collision
added = true
n.leaf = append(n.leaf, leaf{key, val}) // mutate
return nil
default:
panic("pmap: unhandled case in insert")
}
}
newNode = insert(n, 0)
if newNode == nil {
newNode = n
}
return
}

49
inplace_test.go 100644
View File

@ -0,0 +1,49 @@
package pmap
import (
"fmt"
"testing"
)
func TestInplace(t *testing.T) {
p := New(hash).(InplaceMap)
const numElems = 100
for i := range iter(numElems) {
p = p.SetInplace(i, i)
}
if p.Len() != numElems {
t.Fatalf("Len() = %v, want %v", p.Len(), numElems)
}
for i := range iter(numElems) {
v, ok := p.Get(i)
if v != i || !ok {
t.Errorf("Get(%d) = %v %v, want %v %v", i, v, ok, i, true)
}
}
fmt.Print(p.(pmap).stats())
}
func BenchmarkSetInplace(b *testing.B) {
b.Run("size=10", func(b *testing.B) { benchmarkSetInplace(b, 10) })
b.Run("size=100", func(b *testing.B) { benchmarkSetInplace(b, 100) })
b.Run("size=1000", func(b *testing.B) { benchmarkSetInplace(b, 1000) })
b.Run("size=10000", func(b *testing.B) { benchmarkSetInplace(b, 10000) })
if !testing.Short() {
b.Run("size=100000", func(b *testing.B) { benchmarkSetInplace(b, 100000) })
}
}
func benchmarkSetInplace(b *testing.B, numElems int) {
p := New(hash).(InplaceMap)
for i := range iter(numElems) {
p = p.SetInplace(i, i)
}
if p.Len() != numElems {
b.Fatalf("Len() = %v, want %v", p.Len(), numElems)
}
b.ResetTimer()
for i := range iter(b.N) {
k := numElems
p = p.SetInplace(k, i)
}
}