Skip to content

Commit 43f4adc

Browse files
Jia Heihexxa
authored andcommitted
feat: enable GetLongerMatches API with utest
1 parent 3c7dd9a commit 43f4adc

File tree

2 files changed

+140
-4
lines changed

2 files changed

+140
-4
lines changed

radix.go

Lines changed: 95 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,10 @@ func (n *node) NextNode() (Node, bool) {
4343

4444
// Value returns node's value, it returns nil if there is no
4545
func (n *node) Value() (interface{}, bool) {
46-
return n.Leaf.Val, n.Leaf != nil
46+
if n.Leaf != nil {
47+
return n.Leaf.Val, true
48+
}
49+
return nil, false
4750
}
4851

4952
// Extra returns node's Extra Info
@@ -73,7 +76,7 @@ type RTree struct {
7376
}
7477

7578
// return common prefix's offset of s1 and s2, in byte
76-
// s1[:offset] == s2[:offset]
79+
// s1[:offset+1] == s2[:offset+1]
7780
func commonPrefixOffset(s1, s2 string) int {
7881
i := 0
7982
runes1, runes2 := []rune(s1), []rune(s2)
@@ -454,7 +457,96 @@ func (T *RTree) GetAllPrefixMatches(key string) map[string]interface{} {
454457
return resultMap
455458
}
456459

457-
// GetBestMatch returns the longest match in the tree according to the key
460+
type traverseLog struct {
461+
n *node
462+
base string
463+
}
464+
465+
// GetLongerMatches returns at most `limmit` matches which are longer than the key
466+
// if no match is found, it returns an empty map
467+
func (T *RTree) GetLongerMatches(key string, limit int) map[string]interface{} {
468+
T.m.RLock()
469+
defer T.m.RUnlock()
470+
471+
resultMap := map[string]interface{}{}
472+
if T.root == nil {
473+
return resultMap
474+
} else if len(key) == 0 {
475+
return resultMap
476+
}
477+
478+
var ok bool
479+
var rune1 rune
480+
var matchedNode *node
481+
node1 := T.root
482+
pathSuffix := key
483+
baseOffset := 0 // key[:baseOffset] is matched
484+
for {
485+
if node1 == nil {
486+
return resultMap
487+
}
488+
489+
rune1 = getRune1(pathSuffix)
490+
matchedNode, ok = node1.Idx[rune1]
491+
if !ok {
492+
return resultMap
493+
}
494+
495+
offset := commonPrefixOffset(matchedNode.Prefix, pathSuffix)
496+
if offset == -1 {
497+
// this is impossible
498+
panic(errImpossible(matchedNode.Prefix, key))
499+
} else if offset == len(matchedNode.Prefix)-1 && offset < len(pathSuffix)-1 {
500+
pathSuffix = pathSuffix[offset+1:]
501+
node1 = matchedNode.Children
502+
baseOffset += offset + 1
503+
continue
504+
} else if offset == len(pathSuffix)-1 {
505+
break
506+
}
507+
return resultMap
508+
}
509+
510+
if matchedNode.Leaf != nil {
511+
resultMap[key[:baseOffset]+matchedNode.Prefix] = matchedNode.Leaf.Val
512+
}
513+
// start from next level becasue matchedNode's siblings are not results
514+
// traverse from the matchedNode and return values
515+
queue := []*traverseLog{&traverseLog{
516+
n: matchedNode.Children,
517+
base: key[:baseOffset] + matchedNode.Prefix,
518+
}}
519+
for len(queue) > 0 {
520+
tlog := queue[0]
521+
queue = queue[1:]
522+
if tlog.n == nil {
523+
continue
524+
}
525+
526+
if tlog.n.Leaf != nil {
527+
resultMap[tlog.base+tlog.n.Prefix] = tlog.n.Leaf.Val
528+
if len(resultMap) > limit {
529+
break
530+
}
531+
}
532+
if tlog.n.Next != nil {
533+
queue = append(queue, &traverseLog{
534+
n: tlog.n.Next,
535+
base: tlog.base,
536+
})
537+
}
538+
if tlog.n.Children != nil {
539+
queue = append(queue, &traverseLog{
540+
n: tlog.n.Children,
541+
base: tlog.base + tlog.n.Prefix,
542+
})
543+
}
544+
}
545+
546+
return resultMap
547+
}
548+
549+
// GetBestMatch returns the longest match from all existings values which key is short than the input key
458550
// if there is no match, it returns empty string, nil and false
459551
func (T *RTree) GetBestMatch(key string) (string, interface{}, bool) {
460552
T.m.RLock()

radix_random_test.go

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"flag"
55
"fmt"
66
"math/rand"
7+
"strings"
78
"testing"
89
"time"
910
)
@@ -14,7 +15,7 @@ const (
1415
)
1516

1617
var (
17-
// use -args to input these options: "go test -args -d=true"
18+
// use -args to input these options: "go test -args -h=10"
1819
actionCount = flag.Int("c", 30, "how many actions will be applied on RTree and map")
1920
insertRatio = flag.Int("i", 60, "control the the ratio between insert action and remove action")
2021
maxLen = flag.Int("l", 5, "how long will random string be generated")
@@ -29,6 +30,7 @@ func TestWithRandomKeys(t *testing.T) {
2930
seedRand()
3031
for i := 0; i < *testRound; i++ {
3132
randomTest(t)
33+
longerKeyTest(t)
3234
}
3335
}
3436

@@ -118,6 +120,31 @@ func randomTest(t *testing.T) {
118120
}
119121
}
120122

123+
func longerKeyTest(t *testing.T) {
124+
var actions []string
125+
tree := NewRTree()
126+
dict := make(map[string]string)
127+
randomStrings := GetTestStrings()
128+
129+
for i := 0; i < *actionCount; i++ {
130+
key := randomStrings[rand.Intn(len(randomStrings))]
131+
doRandomAction(&actions, key, tree, dict)
132+
}
133+
134+
randomKey := randomStrings[rand.Intn(len(randomStrings))]
135+
end := rand.Intn(len(randomKey)) + 1
136+
start := rand.Intn(end)
137+
randomKey = randomKey[start:end]
138+
longerMatches := tree.GetLongerMatches(randomKey, 5)
139+
if !checkLongerMatches(randomKey, longerMatches) {
140+
fmt.Printf("longerMatches of (%s): %+v\n", randomKey, longerMatches)
141+
printActions(actions)
142+
printRTree(tree)
143+
printMap(dict)
144+
t.Fatalf("incorrect longer matches")
145+
}
146+
}
147+
121148
func checkPrefixMatches(
122149
key string,
123150
prefixes map[string]interface{},
@@ -138,6 +165,23 @@ func checkPrefixMatches(
138165
return true
139166
}
140167

168+
// checkLongerMatches only checks if key is the prefix of each longerMatches
169+
// Becasue currently, besides the first one,
170+
// it doesn't return results by the order of how much it is closed to the key
171+
func checkLongerMatches(
172+
key string,
173+
longerMatches map[string]interface{},
174+
) bool {
175+
for longerMatch := range longerMatches {
176+
if !strings.HasPrefix(longerMatch, key) {
177+
fmt.Printf("%s is not a prefix of %s \n", key, longerMatch)
178+
return false
179+
}
180+
}
181+
182+
return true
183+
}
184+
141185
func GetTestStrings() []string {
142186
var str []rune
143187
var randomStrings []string

0 commit comments

Comments
 (0)