blob: 511fde51565f1338fa0cad6a06817f1302d9930e [file] [log] [blame]
// Copyright 2021 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// trie implements persistent Patricia trie maps.
//
// Each Map is effectively a map from uint64 to interface{}. Patricia tries are
// a form of radix tree that are particularly appropriate when many maps will be
// created, merged together and large amounts of sharing are expected (e.g.
// environment abstract domains in program analysis).
//
// This implementation closely follows the paper:
//
// C. Okasaki and A. Gill, “Fast mergeable integer maps,” in ACM SIGPLAN
// Workshop on ML, September 1998, pp. 77–86.
//
// Each Map is immutable and can be read from concurrently. The map does not
// guarantee that the value pointed to by the interface{} value is not updated
// concurrently.
//
// These Maps are optimized for situations where there will be many maps created at
// with a high degree of sharing and combining of maps together. If you do not expect,
// significant amount of sharing, the builtin map[T]U is much better choice!
//
// Each Map is created by a Builder. Each Builder has a unique Scope and each node is
// created within this scope. Maps x and y are == if they contains the same
// (key,value) mappings and have equal scopes.
//
// Internally these are big endian Patricia trie nodes, and the keys are sorted.
package trie
import (
"fmt"
"strings"
)
// Map is effectively a finite mapping from uint64 keys to interface{} values.
// Maps are immutable and can be read from concurrently.
//
// Notes on concurrency:
// - A Map value itself is an interface and assignments to a Map value can race.
// - Map does not guarantee that the value pointed to by the interface{} value
// is not updated concurrently.
type Map struct {
s Scope
n node
}
func (m Map) Scope() Scope {
return m.s
}
func (m Map) Size() int {
if m.n == nil {
return 0
}
return m.n.size()
}
func (m Map) Lookup(k uint64) (interface{}, bool) {
if m.n != nil {
if leaf := m.n.find(key(k)); leaf != nil {
return leaf.v, true
}
}
return nil, false
}
// Converts the map into a {<key>: <value>[, ...]} string. This uses the default
// %s string conversion for <value>.
func (m Map) String() string {
var kvs []string
m.Range(func(u uint64, i interface{}) bool {
kvs = append(kvs, fmt.Sprintf("%d: %s", u, i))
return true
})
return fmt.Sprintf("{%s}", strings.Join(kvs, ", "))
}
// Range over the leaf (key, value) pairs in the map in order and
// applies cb(key, value) to each. Stops early if cb returns false.
// Returns true if all elements were visited without stopping early.
func (m Map) Range(cb func(uint64, interface{}) bool) bool {
if m.n != nil {
return m.n.visit(cb)
}
return true
}
// DeepEqual returns true if m and other contain the same (k, v) mappings
// [regardless of Scope].
//
// Equivalently m.DeepEqual(other) <=> reflect.DeepEqual(Elems(m), Elems(other))
func (m Map) DeepEqual(other Map) bool {
if m.Scope() == other.Scope() {
return m.n == other.n
}
if (m.n == nil) || (other.n == nil) {
return m.Size() == 0 && other.Size() == 0
}
return m.n.deepEqual(other.n)
}
// Elems are the (k,v) elements in the Map as a map[uint64]interface{}
func Elems(m Map) map[uint64]interface{} {
dest := make(map[uint64]interface{}, m.Size())
m.Range(func(k uint64, v interface{}) bool {
dest[k] = v
return true
})
return dest
}
// node is an internal node within a trie map.
// A node is either empty, a leaf or a branch.
type node interface {
size() int
// visit the leaves (key, value) pairs in the map in order and
// applies cb(key, value) to each. Stops early if cb returns false.
// Returns true if all elements were visited without stopping early.
visit(cb func(uint64, interface{}) bool) bool
// Two nodes contain the same elements regardless of scope.
deepEqual(node) bool
// find the leaf for the given key value or nil if it is not present.
find(k key) *leaf
// implementations must implement this.
nodeImpl()
}
// empty represents the empty map within a scope.
//
// The current builder ensure
type empty struct {
s Scope
}
// leaf represents a single <key, value> pair.
type leaf struct {
k key
v interface{}
}
// branch represents a tree node within the Patricia trie.
//
// All keys within the branch match a `prefix` of the key
// up to a `branching` bit, and the left and right nodes
// contain keys that disagree on the bit at the `branching` bit.
type branch struct {
sz int // size. cached for O(1) lookup
prefix prefix // == mask(p0, branching) for some p0
branching bitpos
// Invariants:
// - neither is nil.
// - neither is *empty.
// - all keys in left are <= p.
// - all keys in right are > p.
left, right node
}
// all of these types are Maps.
var _ node = &empty{}
var _ node = &leaf{}
var _ node = &branch{}
func (*empty) nodeImpl() {}
func (*leaf) nodeImpl() {}
func (*branch) nodeImpl() {}
func (*empty) find(k key) *leaf { return nil }
func (l *leaf) find(k key) *leaf {
if k == l.k {
return l
}
return nil
}
func (br *branch) find(k key) *leaf {
kp := prefix(k)
if !matchPrefix(kp, br.prefix, br.branching) {
return nil
}
if zeroBit(kp, br.branching) {
return br.left.find(k)
}
return br.right.find(k)
}
func (*empty) size() int { return 0 }
func (*leaf) size() int { return 1 }
func (br *branch) size() int { return br.sz }
func (*empty) deepEqual(m node) bool {
_, ok := m.(*empty)
return ok
}
func (l *leaf) deepEqual(m node) bool {
if m, ok := m.(*leaf); ok {
return m == l || (l.k == m.k && l.v == m.v)
}
return false
}
func (br *branch) deepEqual(m node) bool {
if m, ok := m.(*branch); ok {
if br == m {
return true
}
return br.sz == m.sz && br.branching == m.branching && br.prefix == m.prefix &&
br.left.deepEqual(m.left) && br.right.deepEqual(m.right)
}
// if m is not a branch, m contains 0 or 1 elem.
// br contains at least 2 keys that disagree on a prefix.
return false
}
func (*empty) visit(cb func(uint64, interface{}) bool) bool {
return true
}
func (l *leaf) visit(cb func(uint64, interface{}) bool) bool {
return cb(uint64(l.k), l.v)
}
func (br *branch) visit(cb func(uint64, interface{}) bool) bool {
if !br.left.visit(cb) {
return false
}
return br.right.visit(cb)
}