lbcd/claimtrie/merkletrie/prefix_trie.go
Brannon King d7f97ab750 initial sketch and test of faster trie
use custom search

formatted
2021-07-19 11:15:45 -04:00

226 lines
5.9 KiB
Go

package merkletrie
import (
"github.com/lbryio/chain/chaincfg/chainhash"
)
type KeyType []byte
type PrefixTrieNode struct { // implements sort.Interface
children []*PrefixTrieNode
key KeyType
hash *chainhash.Hash
hasClaims bool
}
// insertAt inserts v into s at index i and returns the new slice.
// https://stackoverflow.com/questions/42746972/golang-insert-to-a-sorted-slice
func insertAt(data []*PrefixTrieNode, i int, v *PrefixTrieNode) []*PrefixTrieNode {
if i == len(data) {
// Insert at end is the easy case.
return append(data, v)
}
// Make space for the inserted element by shifting
// values at the insertion index up one index. The call
// to append does not allocate memory when cap(data) is
// greater than len(data).
data = append(data[:i+1], data[i:]...)
data[i] = v
return data
}
func (ptn *PrefixTrieNode) Insert(value *PrefixTrieNode) *PrefixTrieNode {
// keep it sorted (and sort.Sort is too slow)
index := sortSearch(ptn.children, value.key[0])
ptn.children = insertAt(ptn.children, index, value)
return value
}
// this sort.Search is stolen shamelessly from search.go,
// and modified for performance to not need a closure
func sortSearch(nodes []*PrefixTrieNode, b byte) int {
i, j := 0, len(nodes)
for i < j {
h := int(uint(i+j) >> 1) // avoid overflow when computing h
// i ≤ h < j
if nodes[h].key[0] < b {
i = h + 1 // preserves f(i-1) == false
} else {
j = h // preserves f(j) == true
}
}
// i == j, f(i-1) == false, and f(j) (= f(i)) == true => answer is i.
return i
}
func (ptn *PrefixTrieNode) FindNearest(start KeyType) (int, *PrefixTrieNode) {
// none of the children overlap on the first char or we would have a parent node with that char
index := sortSearch(ptn.children, start[0])
hits := ptn.children[index:]
if len(hits) > 0 {
return index, hits[0]
}
return -1, nil
}
type PrefixTrie interface {
InsertOrFind(value KeyType) (bool, *PrefixTrieNode)
Find(value KeyType) *PrefixTrieNode
FindPath(value KeyType) ([]int, []*PrefixTrieNode)
IterateFrom(start KeyType, handler func(value *PrefixTrieNode) bool)
Erase(value KeyType) bool
NodeCount() int
}
type prefixTrie struct {
root *PrefixTrieNode
Nodes int
}
func NewPrefixTrie() PrefixTrie {
// we never delete the root node
return &prefixTrie{root: &PrefixTrieNode{key: make(KeyType, 0)}, Nodes: 1}
}
func (pt *prefixTrie) NodeCount() int {
return pt.Nodes
}
func matchLength(a, b KeyType) int {
minLen := len(a)
if len(b) < minLen {
minLen = len(b)
}
for i := 0; i < minLen; i++ {
if a[i] != b[i] {
return i
}
}
return minLen
}
func (pt *prefixTrie) insert(value KeyType, node *PrefixTrieNode) (bool, *PrefixTrieNode) {
index, child := node.FindNearest(value)
match := 0
if index >= 0 { // if we found a child
match = matchLength(value, child.key)
if len(value) == match && len(child.key) == match {
return false, child
}
}
if match <= 0 {
pt.Nodes++
return true, node.Insert(&PrefixTrieNode{key: value})
}
if match < len(child.key) {
grandChild := PrefixTrieNode{key: child.key[match:], children: child.children,
hasClaims: child.hasClaims, hash: child.hash}
newChild := PrefixTrieNode{key: child.key[0:match], children: []*PrefixTrieNode{&grandChild}}
child = &newChild
node.children[index] = child
pt.Nodes++
if len(value) == match {
return true, child
}
}
return pt.insert(value[match:], child)
}
func (pt *prefixTrie) InsertOrFind(value KeyType) (bool, *PrefixTrieNode) {
if len(value) <= 0 {
return false, pt.root
}
return pt.insert(value, pt.root)
}
func find(value KeyType, node *PrefixTrieNode, pathIndexes *[]int, path *[]*PrefixTrieNode) *PrefixTrieNode {
index, child := node.FindNearest(value)
if index < 0 {
return nil
}
match := matchLength(value, child.key)
if len(value) == match && len(child.key) == match {
if pathIndexes != nil {
*pathIndexes = append(*pathIndexes, index)
}
if path != nil {
*path = append(*path, child)
}
return child
}
if match < len(child.key) || match == len(value) {
return nil
}
if pathIndexes != nil {
*pathIndexes = append(*pathIndexes, index)
}
if path != nil {
*path = append(*path, child)
}
return find(value[match:], child, pathIndexes, path)
}
func (pt *prefixTrie) Find(value KeyType) *PrefixTrieNode {
if len(value) <= 0 {
return pt.root
}
return find(value, pt.root, nil, nil)
}
func (pt *prefixTrie) FindPath(value KeyType) ([]int, []*PrefixTrieNode) {
pathIndexes := []int{-1}
path := []*PrefixTrieNode{pt.root}
result := find(value, pt.root, &pathIndexes, &path)
if result == nil {
return nil, nil
} // not sure I want this line
return pathIndexes, path
}
// IterateFrom can be used to find a value and run a function on that value.
// If the handler returns true it continues to iterate through the children of value.
func (pt *prefixTrie) IterateFrom(start KeyType, handler func(value *PrefixTrieNode) bool) {
node := find(start, pt.root, nil, nil)
if node == nil {
return
}
iterateFrom(node, handler)
}
func iterateFrom(node *PrefixTrieNode, handler func(value *PrefixTrieNode) bool) {
for handler(node) {
for _, child := range node.children {
iterateFrom(child, handler)
}
}
}
func (pt *prefixTrie) Erase(value KeyType) bool {
indexes, path := pt.FindPath(value)
if path == nil || len(path) <= 1 {
return false
}
nodes := pt.Nodes
for i := len(path) - 1; i > 0; i-- {
childCount := len(path[i].children)
noClaimData := !path[i].hasClaims
if childCount == 1 && noClaimData {
path[i].key = append(path[i].key, path[i].children[0].key...)
path[i].hash = nil
path[i].hasClaims = path[i].children[0].hasClaims
path[i].children = path[i].children[0].children
pt.Nodes--
continue
}
if childCount == 0 && noClaimData {
index := indexes[i]
path[i-1].children = append(path[i-1].children[:index], path[i-1].children[index+1:]...)
pt.Nodes--
continue
}
break
}
return nodes > pt.Nodes
}