Skip to content

Commit fdf2c27

Browse files
shivamMgtopheShivam Mamgain
authored
Add support for Trie.Walk() and Node.SetValue() (#4)
* udpate: add support for SelectOnValue, find item in the tree based on Value, using eval function * update: renaming, cleaning tests * update: renaming, SelectOnValue -> Walk, implement with childrenDLL to avoid non deterministic sort. * Add support for Trie.Walk() and Node.SetValue() * Fix err on Windows + Fix docs --------- Co-authored-by: tophe <cvigny@artprice.com> Co-authored-by: Shivam Mamgain <smamgain@microsoft.com>
1 parent 23eb4c7 commit fdf2c27

6 files changed

Lines changed: 142 additions & 3 deletions

File tree

.github/workflows/ci.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,6 @@ jobs:
3535
- name: Checkout code
3636
uses: actions/checkout@v2
3737
- name: Run tests
38-
run: go test ./...
38+
run: go test -v ./...
3939
- name: Run benchmarks
40-
run: go test -bench=.
40+
run: go test -v -bench=.

search_test.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -515,6 +515,7 @@ func getWordsTrie() *trie.Trie {
515515
panic(err)
516516
}
517517
word = strings.TrimRight(word, "\n")
518+
word = strings.TrimRight(word, "\r") // windows
518519
key := strings.Split(word, "")
519520
tri.Put(key, nil)
520521
}

trie.go

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,13 @@ func (n *Node) Value() interface{} {
4747
return n.value
4848
}
4949

50+
// SetValue sets the value for the key ending at this Node. If Node is not a terminal, value is not set.
51+
func (n *Node) SetValue(value interface{}) {
52+
if n.isTerminal {
53+
n.value = value
54+
}
55+
}
56+
5057
// ChildNodes returns the child-nodes of this Node.
5158
func (n *Node) ChildNodes() []*Node {
5259
return n.childNodes()
@@ -101,7 +108,7 @@ func (t *Trie) Root() *Node {
101108
return t.root
102109
}
103110

104-
// Put upserts value the given key in the Trie. It returns a boolean depending on
111+
// Put upserts value for the given key in the Trie. It returns a boolean depending on
105112
// whether the key already existed or not.
106113
func (t *Trie) Put(key []string, value interface{}) (existed bool) {
107114
node := t.root

trie_test.go

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,24 @@ import (
88
"github.com/stretchr/testify/assert"
99
)
1010

11+
func TestNode_SetValue(t *testing.T) {
12+
tri := trie.New()
13+
tri.Put([]string{"a", "b"}, 1)
14+
tri.Put([]string{"a", "b", "c"}, 2)
15+
16+
node := tri.Root()
17+
node.SetValue(10)
18+
assert.Equal(t, nil, node.Value())
19+
20+
node = tri.Root().ChildNodes()[0]
21+
node.SetValue(10)
22+
assert.Equal(t, nil, node.Value())
23+
24+
node = tri.Root().ChildNodes()[0].ChildNodes()[0]
25+
node.SetValue(10)
26+
assert.Equal(t, 10, node.Value())
27+
}
28+
1129
func TestTrie_Put(t *testing.T) {
1230
tri := trie.New()
1331
existed := tri.Put([]string{"an", "umbrella"}, 2)

walk.go

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
package trie
2+
3+
type WalkFunc func(key []string, node *Node) error
4+
5+
// Walk traverses the Trie and calls walker function. If walker function returns an error, Walk early-returns with that error.
6+
// Traversal follows insertion order.
7+
func (t *Trie) Walk(key []string, walker WalkFunc) error {
8+
node := t.root
9+
for _, keyPart := range key {
10+
child, ok := node.children[keyPart]
11+
if !ok {
12+
return nil
13+
}
14+
node = child
15+
}
16+
return t.walk(node, &key, walker)
17+
}
18+
19+
func (t *Trie) walk(node *Node, prefixKey *[]string, walker WalkFunc) error {
20+
if node.isTerminal {
21+
key := make([]string, len(*prefixKey))
22+
copy(key, *prefixKey)
23+
if err := walker(key, node); err != nil {
24+
return err
25+
}
26+
}
27+
28+
for dllNode := node.childrenDLL.head; dllNode != nil; dllNode = dllNode.next {
29+
child := dllNode.trieNode
30+
*prefixKey = append(*prefixKey, child.keyPart)
31+
err := t.walk(child, prefixKey, walker)
32+
*prefixKey = (*prefixKey)[:len(*prefixKey)-1]
33+
if err != nil {
34+
return err
35+
}
36+
}
37+
return nil
38+
}

walk_test.go

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
package trie_test
2+
3+
import (
4+
"errors"
5+
"testing"
6+
7+
"github.com/shivamMg/trie"
8+
"github.com/stretchr/testify/assert"
9+
)
10+
11+
func TestTrie_WalkErr(t *testing.T) {
12+
tri := trie.New()
13+
tri.Put([]string{"d", "a", "l", "i"}, 1)
14+
tri.Put([]string{"d", "a", "l", "i", "b"}, 2)
15+
tri.Put([]string{"d", "a", "l", "i", "b", "e"}, 3)
16+
tri.Put([]string{"d", "a", "l", "i", "b", "e", "r", "t"}, 4)
17+
18+
var selected []string
19+
walker := func(key []string, node *trie.Node) error {
20+
what := node.Value().(int)
21+
if what == 3 {
22+
selected = key
23+
return errors.New("found")
24+
}
25+
return nil
26+
}
27+
28+
err := tri.Walk(nil, walker)
29+
assert.EqualError(t, err, "found")
30+
assert.EqualValues(t, []string{"d", "a", "l", "i", "b", "e"}, selected)
31+
}
32+
33+
func TestTrie_Walk(t *testing.T) {
34+
tri := trie.New()
35+
tri.Put([]string{"d", "a", "l", "i"}, []int{0, 1, 2, 4, 5})
36+
tri.Put([]string{"d", "a", "l", "i", "b"}, []int{1, 2, 4, 5})
37+
tri.Put([]string{"d", "a", "l", "i", "b", "e"}, []int{1, 0, 2, 4, 5, 0})
38+
tri.Put([]string{"d", "a", "l", "i", "b", "e", "r", "t"}, []int{1, 2, 4, 5})
39+
type KVPair struct {
40+
key []string
41+
value []int
42+
}
43+
var selected []KVPair
44+
walker := func(key []string, node *trie.Node) error {
45+
what := node.Value().([]int)
46+
for _, i := range what {
47+
if i == 0 {
48+
selected = append(selected, KVPair{key, what})
49+
break
50+
}
51+
}
52+
return nil
53+
}
54+
55+
err := tri.Walk(nil, walker)
56+
assert.NoError(t, err)
57+
expected := []KVPair{
58+
{[]string{"d", "a", "l", "i"}, []int{0, 1, 2, 4, 5}},
59+
{[]string{"d", "a", "l", "i", "b", "e"}, []int{1, 0, 2, 4, 5, 0}},
60+
}
61+
assert.EqualValues(t, expected, selected)
62+
63+
selected = nil
64+
err = tri.Walk([]string{"d", "a", "l", "i", "b"}, walker)
65+
assert.NoError(t, err)
66+
expected = []KVPair{
67+
{[]string{"d", "a", "l", "i", "b", "e"}, []int{1, 0, 2, 4, 5, 0}},
68+
}
69+
assert.EqualValues(t, expected, selected)
70+
71+
selected = nil
72+
err = tri.Walk([]string{"a", "b", "c"}, walker)
73+
assert.NoError(t, err)
74+
assert.Nil(t, selected)
75+
}

0 commit comments

Comments
 (0)