Documentation
¶
Overview ¶
Package art implements an Adaptive Radix Tree: a variable-fanout trie that stores []byte keys in byte-wise sorted order.
A Tree[V] is a sorted map from []byte to V. Point operations (Put, Get, Delete) are O(k) in the key length, and in-order traversal is exposed via Go 1.23 range-over-func iterators.
Keys are ordered lexicographically by their raw bytes; shorter keys sort before longer keys that share the shorter key as a prefix. Keys passed to Put are copied, so callers may reuse their slices freely. A nil key and an empty-slice key are equivalent (both represent the empty key); the tree can hold at most one entry at the empty key.
Iteration:
- All yields every (key, value) pair in ascending key order.
- Range yields every (key, value) pair whose key lies in the half-open interval [start, end). A nil bound is unbounded on that side.
Sorted-map surface:
- Min and Max return the smallest and largest entry.
- Ceiling and Floor return the successor and predecessor of a target key (inclusive at equality).
- Clone returns an independent structural copy of the tree.
- Clear removes every entry in O(1).
Goroutine safety: a Tree is not safe for concurrent use by multiple goroutines when any goroutine is writing; concurrent reads are safe only while no goroutine is mutating the tree. Callers that need concurrent access should guard a Tree with their own sync.RWMutex or use the provided LockedTree wrapper. See the Tree type and the Concurrency section of the project README for the authoritative statement.
Minimal usage:
t := art.New[int]()
t.Put([]byte("apple"), 1)
t.Put([]byte("apricot"), 2)
t.Put([]byte("banana"), 3)
if v, ok := t.Get([]byte("apple")); ok {
_ = v // 1
}
for k, v := range t.All() {
_, _ = k, v // "apple"/1, "apricot"/2, "banana"/3
}
for k, v := range t.Range([]byte("ap"), []byte("b")) {
_, _ = k, v // "apple"/1, "apricot"/2
}
t.Delete([]byte("apple"))
_ = t.Len() // 2
See CHANGELOG.md at the repository root for version-to-version changes and release notes.
Index ¶
- type LockedTree
- type Tree
- func (t *Tree[V]) All() iter.Seq2[[]byte, V]
- func (t *Tree[V]) AllDescending() iter.Seq2[[]byte, V]
- func (t *Tree[V]) Ceiling(target []byte) (key []byte, value V, ok bool)
- func (t *Tree[V]) Clear()
- func (t *Tree[V]) Clone() *Tree[V]
- func (t *Tree[V]) Delete(key []byte) bool
- func (t *Tree[V]) Floor(target []byte) (key []byte, value V, ok bool)
- func (t *Tree[V]) Get(key []byte) (value V, ok bool)
- func (t *Tree[V]) Len() int
- func (t *Tree[V]) Max() (key []byte, value V, ok bool)
- func (t *Tree[V]) Min() (key []byte, value V, ok bool)
- func (t *Tree[V]) Put(key []byte, value V)
- func (t *Tree[V]) Range(start, end []byte) iter.Seq2[[]byte, V]
- func (t *Tree[V]) RangeDescending(start, end []byte) iter.Seq2[[]byte, V]
- func (t *Tree[V]) RangeFrom(start []byte) iter.Seq2[[]byte, V]
- func (t *Tree[V]) RangeTo(end []byte) iter.Seq2[[]byte, V]
Examples ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type LockedTree ¶ added in v0.5.0
type LockedTree[V any] struct { // contains filtered or unexported fields }
LockedTree is a thin sync.RWMutex-guarded wrapper around Tree for callers who want a drop-in concurrent sorted map with the obvious semantics: writes are serialized and readers run in parallel with each other.
The wrapper covers only the point operations (Put, Get, Delete, Len, Clear, Clone). Iteration is deliberately not wrapped because the caller controls when each yield returns, which makes a held RLock easy to hold for too long; callers that need an ordered scan should take a LockedTree.Clone and iterate the unlocked snapshot.
See the Concurrency section of the project README for the full discussion and trade-offs.
func NewLocked ¶ added in v0.5.0
func NewLocked[V any]() *LockedTree[V]
NewLocked returns an empty LockedTree.
func (*LockedTree[V]) Clear ¶ added in v0.5.0
func (t *LockedTree[V]) Clear()
Clear removes every entry.
func (*LockedTree[V]) Clone ¶ added in v0.5.0
func (t *LockedTree[V]) Clone() *Tree[V]
Clone returns an unlocked snapshot Tree. The snapshot does not share its mutex with the original, so callers can iterate the returned tree without holding any LockedTree lock.
func (*LockedTree[V]) Delete ¶ added in v0.5.0
func (t *LockedTree[V]) Delete(key []byte) bool
Delete removes key, returning whether it was present.
func (*LockedTree[V]) Get ¶ added in v0.5.0
func (t *LockedTree[V]) Get(key []byte) (V, bool)
Get returns the value stored under key, if any.
func (*LockedTree[V]) Len ¶ added in v0.5.0
func (t *LockedTree[V]) Len() int
Len returns the current number of entries.
func (*LockedTree[V]) Put ¶ added in v0.5.0
func (t *LockedTree[V]) Put(key []byte, value V)
Put associates value with key, replacing any previous value.
type Tree ¶
type Tree[V any] struct { // contains filtered or unexported fields }
Tree is a sorted map from []byte keys to V values, backed by an Adaptive Radix Tree.
A Tree is not safe for concurrent use by multiple goroutines when any goroutine is writing; concurrent reads are safe only while no goroutine is mutating the tree. Callers that need concurrent access should guard a Tree with their own sync.RWMutex or use the provided LockedTree wrapper. See the Concurrency section of the project README for the full discussion.
func (*Tree[V]) All ¶
All returns an iterator over every (key, value) pair in the tree in ascending byte-wise key order. Breaking out of the range stops the traversal immediately.
The yielded key slice aliases the tree's internal storage. It is safe to retain while the corresponding entry remains in the tree, and must be treated as read-only; mutating it corrupts the tree. If the entry may be deleted (including by the caller during iteration) while a retained reference is in use, copy the key.
See Tree.AllDescending for the reverse walk.
Example ¶
package main
import (
"fmt"
art "github.com/KentBeck/AdaptiveRadixTree2"
)
func main() {
t := art.New[int]()
t.Put([]byte("banana"), 3)
t.Put([]byte("apple"), 1)
t.Put([]byte("apricot"), 2)
for k, v := range t.All() {
fmt.Printf("%s=%d\n", k, v)
}
}
Output: apple=1 apricot=2 banana=3
func (*Tree[V]) AllDescending ¶ added in v0.5.0
AllDescending returns an iterator over every (key, value) pair in the tree in descending byte-wise key order. Breaking out of the range stops the traversal immediately.
The yielded key slice aliases the tree's internal storage under the same contract as Tree.All. See Tree.All for the forward walk.
func (*Tree[V]) Ceiling ¶ added in v0.2.0
Ceiling returns the smallest key that compares byte-wise >= target, along with its value. If no such key exists, ok is false. A nil target and an empty-slice target are equivalent (both represent the empty key), so Ceiling of either returns the tree's Min when the tree is non-empty.
Example ¶
package main
import (
"fmt"
art "github.com/KentBeck/AdaptiveRadixTree2"
)
func main() {
t := art.New[int]()
t.Put([]byte("apple"), 1)
t.Put([]byte("apricot"), 2)
t.Put([]byte("banana"), 3)
k, v, ok := t.Ceiling([]byte("apq"))
fmt.Printf("%s=%d ok=%v\n", k, v, ok)
_, _, ok = t.Ceiling([]byte("z"))
fmt.Println(ok)
}
Output: apricot=2 ok=true false
func (*Tree[V]) Clear ¶ added in v0.2.0
func (t *Tree[V]) Clear()
Clear removes every entry from the tree in O(1) by dropping the root reference. After Clear, Tree.Len returns 0 and subsequent Put calls behave as on a newly constructed tree.
Example ¶
package main
import (
"fmt"
art "github.com/KentBeck/AdaptiveRadixTree2"
)
func main() {
t := art.New[int]()
t.Put([]byte("apple"), 1)
t.Put([]byte("banana"), 2)
t.Clear()
fmt.Println(t.Len())
}
Output: 0
func (*Tree[V]) Clone ¶ added in v0.2.0
Clone returns a structural copy of t. Writes to t or to the returned tree do not affect each other. Key bytes may be shared between the two trees, matching the read-only-key contract of Tree.All and Tree.Range.
Example ¶
package main
import (
"fmt"
art "github.com/KentBeck/AdaptiveRadixTree2"
)
func main() {
t := art.New[int]()
t.Put([]byte("apple"), 1)
cp := t.Clone()
cp.Put([]byte("banana"), 2)
fmt.Println(t.Len(), cp.Len())
}
Output: 1 2
func (*Tree[V]) Delete ¶
Delete removes key from the tree, returning whether it was present. Traversal consumes each inner node's prefix and, if the key is exhausted at a node, targets that node's terminal value. After a successful remove the affected node is demoted to a smaller node type (or collapsed to its only child) whenever its child count crosses the next-smaller capacity. A nil key and an empty-slice key are equivalent (both represent the empty key).
Example ¶
package main
import (
"fmt"
art "github.com/KentBeck/AdaptiveRadixTree2"
)
func main() {
t := art.New[int]()
t.Put([]byte("apple"), 1)
fmt.Println(t.Delete([]byte("apple")))
fmt.Println(t.Delete([]byte("apple")))
}
Output: true false
func (*Tree[V]) Floor ¶ added in v0.2.0
Floor returns the largest key that compares byte-wise <= target, along with its value. If no such key exists, ok is false. A nil target and an empty-slice target are equivalent (both represent the empty key); Floor of either returns the empty key's entry when present, and otherwise ok is false.
Example ¶
package main
import (
"fmt"
art "github.com/KentBeck/AdaptiveRadixTree2"
)
func main() {
t := art.New[int]()
t.Put([]byte("apple"), 1)
t.Put([]byte("apricot"), 2)
t.Put([]byte("banana"), 3)
k, v, ok := t.Floor([]byte("apq"))
fmt.Printf("%s=%d ok=%v\n", k, v, ok)
_, _, ok = t.Floor([]byte(""))
fmt.Println(ok)
}
Output: apple=1 ok=true false
func (*Tree[V]) Get ¶
Get returns the value previously stored under key, if any. If the key is absent the returned value is the zero value of V. A nil key and an empty-slice key are equivalent (both represent the empty key).
Example ¶
package main
import (
"fmt"
art "github.com/KentBeck/AdaptiveRadixTree2"
)
func main() {
t := art.New[int]()
t.Put([]byte("apple"), 1)
hit, ok := t.Get([]byte("apple"))
fmt.Println(hit, ok)
miss, ok := t.Get([]byte("banana"))
fmt.Println(miss, ok)
}
Output: 1 true 0 false
func (*Tree[V]) Len ¶
Len returns the number of key-value pairs in the tree. It runs in O(1).
Example ¶
package main
import (
"fmt"
art "github.com/KentBeck/AdaptiveRadixTree2"
)
func main() {
t := art.New[int]()
fmt.Println(t.Len())
t.Put([]byte("apple"), 1)
t.Put([]byte("apricot"), 2)
fmt.Println(t.Len())
t.Delete([]byte("apple"))
fmt.Println(t.Len())
}
Output: 0 2 1
func (*Tree[V]) Max ¶ added in v0.2.0
Max returns the largest key in the tree, its value, and ok=true. If the tree is empty, ok is false and the returned key and value are their zero values.
Example ¶
package main
import (
"fmt"
art "github.com/KentBeck/AdaptiveRadixTree2"
)
func main() {
t := art.New[int]()
t.Put([]byte("banana"), 3)
t.Put([]byte("apple"), 1)
t.Put([]byte("apricot"), 2)
k, v, ok := t.Max()
fmt.Printf("%s=%d ok=%v\n", k, v, ok)
}
Output: banana=3 ok=true
func (*Tree[V]) Min ¶ added in v0.2.0
Min returns the smallest key in the tree, its value, and ok=true. If the tree is empty, ok is false and the returned key and value are their zero values.
Example ¶
package main
import (
"fmt"
art "github.com/KentBeck/AdaptiveRadixTree2"
)
func main() {
t := art.New[int]()
t.Put([]byte("banana"), 3)
t.Put([]byte("apple"), 1)
t.Put([]byte("apricot"), 2)
k, v, ok := t.Min()
fmt.Printf("%s=%d ok=%v\n", k, v, ok)
}
Output: apple=1 ok=true
func (*Tree[V]) Put ¶
Put associates value with key, replacing any previous value. An inner node's prefix is consumed from the key as traversal descends; keys that end at such a node's exact path are stored in its terminal slot. A nil key and an empty-slice key are equivalent (both represent the empty key); the tree can hold at most one entry at the empty key.
Example ¶
package main
import (
"fmt"
art "github.com/KentBeck/AdaptiveRadixTree2"
)
func main() {
t := art.New[int]()
t.Put([]byte("apple"), 1)
v, _ := t.Get([]byte("apple"))
fmt.Println(v)
}
Output: 1
func (*Tree[V]) Range ¶
Range returns an iterator over every (key, value) pair whose key lies in the half-open interval [start, end), in ascending byte-wise key order. A nil bound is treated as unbounded on that side, so Range(nil, nil) is equivalent to All. Breaking out of the range stops the traversal immediately.
The yielded key slice aliases the tree's internal storage under the same contract as Tree.All: safe to retain while the entry remains in the tree, must be treated as read-only, and must be copied if retained past a possible deletion of the entry.
See Tree.RangeFrom and Tree.RangeTo for open-ended variants and Tree.RangeDescending for the reverse walk of the same interval.
Example ¶
package main
import (
"fmt"
art "github.com/KentBeck/AdaptiveRadixTree2"
)
func main() {
t := art.New[int]()
t.Put([]byte("apple"), 1)
t.Put([]byte("apricot"), 2)
t.Put([]byte("banana"), 3)
for k, v := range t.Range([]byte("ap"), []byte("b")) {
fmt.Printf("%s=%d\n", k, v)
}
}
Output: apple=1 apricot=2
func (*Tree[V]) RangeDescending ¶ added in v0.5.0
RangeDescending returns an iterator over every (key, value) pair whose key lies in the half-open interval [start, end), in descending byte-wise key order. The bounds have the same semantics as Tree.Range — start is inclusive, end is exclusive, and a nil bound is unbounded on that side — so RangeDescending(nil, nil) is equivalent to Tree.AllDescending and start >= end yields nothing. Breaking out of the range stops the traversal immediately.
The yielded key slice aliases the tree's internal storage under the same contract as Tree.All. See Tree.Range for the ascending walk of the same interval.
func (*Tree[V]) RangeFrom ¶ added in v0.5.0
RangeFrom returns an iterator over every (key, value) pair whose key is byte-wise >= start, in ascending order. A nil start is equivalent to Tree.All. This is the shorthand for Range(start, nil) and matches google/btree's AscendGreaterOrEqual.
The yielded key slice aliases the tree's internal storage under the same contract as Tree.All.
func (*Tree[V]) RangeTo ¶ added in v0.5.0
RangeTo returns an iterator over every (key, value) pair whose key is byte-wise < end, in ascending order. A nil end is equivalent to Tree.All. This is the shorthand for Range(nil, end) and matches google/btree's AscendLessThan.
The yielded key slice aliases the tree's internal storage under the same contract as Tree.All.
Source Files
¶
Directories
¶
| Path | Synopsis |
|---|---|
|
Package artmap provides a typed façade over the []byte-keyed art.Tree: an Ordered sorted map keyed by any Go cmp.Ordered type, using byte-order-preserving encoders so the underlying ART preserves the natural ordering of K.
|
Package artmap provides a typed façade over the []byte-keyed art.Tree: an Ordered sorted map keyed by any Go cmp.Ordered type, using byte-order-preserving encoders so the underlying ART preserves the natural ordering of K. |