// 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 {: [, ...]} string. This uses the default // %s string conversion for . 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 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) }