Documentation ¶
Overview ¶
Package trie contains radix tree implementation in golang (without any dependencies).
Copyright 2021, Mikhail Vitsen (@porfirion) https://github.com/porfirion/trie
Trie maps `[]byte` prefixes to `interface{}` values. It can be used for efficient search of substrings or bulk prefix checking.
Trie is created by
t := &Trie{} t.Put("foo", "bar") t.Put("buzz", 123)
or by convenience constructors:
t := BuildFromMap(map[string]interface{}{ "foo": "bar", "buzz": 123, })
or
t := BuildFromList([]struct{Key string; Value interface{}}{ {Key: "foo", Value: "bar"}, {Key: "buzz", Value: 123}, })
Two common operations are:
value, ok := t.Get(key) // returns associated value value, prefixLen, ok := t.SearchPrefixIn(input) // searches the longest prefix of input, that stored in trie
but also handy method
prefix, ok := t.TakePrefix(input) // returns longest prefix without associated value
Example (Prefixes) ¶
package main import ( "fmt" "strings" ) // Also can be created with // // prefixes := &trie.Trie{} // prefixes.PutString("one", 1) // prefixes.PutString("two", 2) // prefixes.PutString("three", 3) // prefixes.PutString("", 0) var prefixes = BuildFromMap(map[string]int{ "one": 1, "two": 2, "three": 3, "": 0, }) func main() { var inputs = []string{ "twoApple", "oneBanana", "Carrot", } for _, inp := range inputs { if val, prefixLen, ok := prefixes.SearchPrefixInString(inp); ok { fmt.Println(strings.Repeat(inp[prefixLen:], *val)) } } }
Output: AppleApple Banana
Example (Routing) ¶
package main import ( "fmt" ) func spSliceToStr(a []*string) []string { b := make([]string, len(a)) for i := range a { b[i] = *a[i] } return b } var routes = BuildFromMap(map[string]string{ "": "root", // as universal prefix "/api/user": "user", "/api/user/list": "usersList", "/api/group": "group", "/api/group/list": "groupsList", "/api/articles/": "articles", }) func main() { var inputs = []string{ "/api/user/list", "/api/user/li", "/api/group", "/api/unknown", } for _, inp := range inputs { exact, ok := routes.GetByString(inp) route := routes.GetAllByString(inp) if ok { fmt.Printf("%-17s:\thandler %-10s\t(route %v)\n", inp, *exact, spSliceToStr(route)) } else { fmt.Printf("%-17s:\thandler not found\t(route %v)\n", inp, spSliceToStr(route)) } } }
Output: /api/user/list : handler usersList (route [root user usersList]) /api/user/li : handler not found (route [root user]) /api/group : handler group (route [root group]) /api/unknown : handler not found (route [root])
Index ¶
- type Trie
- func (t *Trie[T]) Count() int
- func (t *Trie[T]) Get(key []byte) (*T, bool)
- func (t *Trie[T]) GetAll(mask []byte) []*T
- func (t *Trie[T]) GetAllByString(str string) []*T
- func (t *Trie[T]) GetAllKeyValues(mask []byte) []struct{ ... }
- func (t *Trie[T]) GetAllKeys(mask []byte) []string
- func (t *Trie[T]) GetByString(key string) (*T, bool)
- func (t *Trie[T]) Iterate(callback func(prefix []byte, value *T))
- func (t *Trie[T]) Put(newPrefix []byte, val T) (oldValue *T)
- func (t *Trie[T]) PutString(prefix string, value T)
- func (t *Trie[T]) SearchPrefixIn(input []byte) (value *T, prefixLen int, ok bool)
- func (t *Trie[T]) SearchPrefixInString(str string) (value *T, prefixLen int, ok bool)
- func (t Trie[T]) String() string
- func (t *Trie[T]) SubTrie(mask []byte, keepPrefix bool) (subTrie *Trie[T], ok bool)
- func (t *Trie[T]) TakePrefix(str string) (prefix string, ok bool)
- type TrieKey
Examples ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type Trie ¶
Trie implements sparse radix (Patricia) trie. Makes zero allocation on Get and SearchPrefixIn operations and two allocations per Put
Create it just as &Trie{} and add required data. Also there are some convenience constructors (for example for initialization from map[prefix]value)
When generics come ^^ it would be a
type Trie[type ValueType] struct {...}
func BuildFromList ¶
BuildFromList can be used to create Trie with arbitrary bytes slice as key (not valid strings, etc)
func BuildFromMap ¶
BuildFromMap may be useful for var declaration
func (*Trie[T]) GetAll ¶
GetAll returns all Values whose prefixes are subsets of mask
tr := {"": v0, "/user/": v1, "/user/list": v2, "/group/": v3} tr.GetAll("/user/list", false) -> [v0, v1, v2]
func (*Trie[T]) GetAllByString ¶
GetAllByString is a convenience method for GetAll
func (*Trie[T]) GetAllKeyValues ¶
GetAllKeyValues returns all key-value pairs whose prefixes are subsets of mask
tr := {"": v0, "/user/": v1, "/user/list": v2, "/group/": v3} tr.GetAllKeyValues("/user/list", false) -> [{"", v0}, {"/user/", v1}, {"/user/list", v2}]
func (*Trie[T]) GetAllKeys ¶
GetAllKeys returns all keys whose prefixes are subsets of mask
tr := {"": v0, "/user/": v1, "/user/list": v2, "/group/": v3} tr.GetAll("/user/list", false) -> ["", "/user/", "/user/list"]
func (*Trie[T]) GetByString ¶
GetByString is a convenience method for Get
func (*Trie[T]) Iterate ¶
Iterate calls callback for each value stored in trie
Not thread safe.
Also prefix's underlying array would change on every call - so you can't rely on it after callback finishes (e.g. you should not pass it to another goroutine without copying)
It seems like the only possible iteration order is by key (prefix):
0x1, 0x1 0x1, 0x1 0x2, 0x1 0x3, 0x2, 0x2 0x1, 0x2 0x2, etc...
But it's not guarantied. You shouldn't rely on it!
Can be used in combination with SubTrie:
tr.SubTrie(mask).Iterate(func...)
Example ¶
example := &T{Prefix: []byte{0xF0, 0x9F, 0x91}, Value: sp("short"), Children: &[256]*T{ 0x10: {Prefix: []byte{0x10}, Value: sp("modified")}, 0xA8: {Prefix: []byte{0xA8}, Value: sp("nokey"), Children: &[256]*T{ 0xE2: {Prefix: []byte{0xE2, 0x80, 0x8D}, Value: sp("withsep"), Children: &[256]*T{ 0xF0: {Prefix: []byte{0xF0, 0x9F, 0x94, 0xA7}, Value: sp("withkey")}, }}, }}, }} example.Iterate(func(prefix []byte, value *string) { fmt.Printf("[%v] %+v\n", prefix, *value) })
Output: [[240 159 145]] short [[240 159 145 16]] modified [[240 159 145 168]] nokey [[240 159 145 168 226 128 141]] withsep [[240 159 145 168 226 128 141 240 159 148 167]] withkey
func (*Trie[T]) Put ¶
Put adds new entry into trie or replaces existing with specified prefix. Prefix can have zero length - associated value would be added into root Trie
WARNING! nil shouldn't be stored as value: you wouldn't be able to find it nor by SearchPrefixIn, nor by Get, nor by Iterate If you don't need any value (you need only prefixes) - you can use struct{}{}. See TakePrefix
There were some variants of control of replace. Handling it in place results in dependency on ValueType (interfaces and pointers are handled differently). Also there are some incomparable types, that cause panic when compared :(. Store separate function like OnReplaceCallback - requires passing it to children. And if you update it in parent - it wouldn't be updated in children automatically. Another problem - node doesn't know it's whole prefix, since it doesn't know it's parent! With current realization caller of Put knows whole prefix and we shouldn't collect it inside. IMHO, much easier.
func (*Trie[T]) SearchPrefixIn ¶
SearchPrefixIn searches the longest matching prefix in input bytes. If input has prefix that matches any stored key returns associated value, prefix length, true OR nil, 0, false otherwise
func (*Trie[T]) SearchPrefixInString ¶
SearchPrefixInString is a convenience method for SearchPrefixIn
Example ¶
tr := BuildFromMap(map[string]func(s string) string{ "lower": func(s string) string { return strings.ToLower(s) }, "upper": func(s string) string { return strings.ToUpper(s) }, "snake": func(s string) string { var res = make([]rune, 0, len(s)) for i, r := range s { if unicode.IsUpper(r) && i > 0 { res = append(res, '_', unicode.ToLower(r)) } else { res = append(res, unicode.ToLower(r)) } } return string(res) }, "camel": func(s string) string { var res = make([]rune, 0, len(s)) toUpper := false for _, r := range s { if r == '_' { toUpper = true continue } else if toUpper { res = append(res, unicode.ToUpper(r)) toUpper = false } else { res = append(res, unicode.ToLower(r)) } } return string(res) }, }) inputs := []string{ "upperapple", "lowerBANANA", "cameltest_me", "snakeAnotherExample", "noprefixString", } for _, inp := range inputs { if raw, prefixLen, ok := tr.SearchPrefixInString(inp); ok { format := *raw fmt.Println(format(inp[prefixLen:])) } else { fmt.Printf("no prefix found in \"%s\"\n", inp) } }
Output: APPLE banana testMe another_example no prefix found in "noprefixString"
func (Trie[T]) String ¶
Example ¶
example := &T{Prefix: []byte{0xF0, 0x9F, 0x91}, Value: sp("short"), Children: &[256]*T{ 0x10: {Prefix: []byte{0x10}, Value: sp("modified")}, 0xA8: {Prefix: []byte{0xA8}, Value: sp("nokey"), Children: &[256]*T{ 0xE2: {Prefix: []byte{0xE2, 0x80, 0x8D}, Value: sp("withsep"), Children: &[256]*T{ 0xF0: {Prefix: []byte{0xF0, 0x9F, 0x94, 0xA7}, Value: sp("withkey")}, }}, }}, }} fmt.Println(example)
Output: [F0 9F 91] short ├─10─ [10] modified ├─A8─ [A8] nokey │ ├─E2─ [E2 80 8D] withsep │ │ ├─F0─ [F0 9F 94 A7] withkey
func (*Trie[T]) SubTrie ¶
SubTrie returns new Trie with all values whose prefixes include mask
keepPrefix indicates whether the new trie should keep original prefixes, or should contain only those parts, that are out of mask:
tr := {"": v0, "/user/": v1, "/user/list": v2, "/group/": v3}. tr.SubTrie("/user", false) -> {"/": v1, "/list": v2} tr.SubTrie("/user", true) -> {"/user/": v1, "/user/list": v2}
type TrieKey ¶
type TrieKey struct { Trie[dummy] }
func BuildPrefixesOnly ¶
BuildPrefixesOnly used to create just searching prefixes without any data
Example ¶
prefixes := BuildPrefixesOnly("tiny_", "small_", "normal_", "large_") var myString = "large_banana" if prefix, ok := prefixes.TakePrefix(myString); ok { fmt.Printf("prefix \"%s\" found\n", prefix) } else { fmt.Println("no prefix found") }
Output: prefix "large_" found