Add a resoruce indexing utility struct

The struct indexes resources which lowers set search complexity
from linear to constant.
This commit is contained in:
Tomaž Jerman
2022-06-09 15:04:11 +02:00
parent f9e9433bcd
commit a68ddf1f2a
2 changed files with 158 additions and 0 deletions
+80
View File
@@ -0,0 +1,80 @@
package resource
type (
IndexNode struct {
children map[string]*IndexNode
values []interface{}
}
)
func NewIndex() *IndexNode {
return &IndexNode{
children: make(map[string]*IndexNode),
values: make([]interface{}, 0),
}
}
func (in *IndexNode) Add(value interface{}, pp ...[]string) {
if in == nil {
*in = *NewIndex()
}
if len(pp) == 1 {
for _, identifier := range pp[0] {
if identifier == "*" {
in.values = append(in.values, value)
} else {
nn := in.children[identifier]
if nn == nil {
nn = NewIndex()
in.children[identifier] = nn
}
nn.values = append(nn.values, value)
}
}
return
}
p := pp[0]
for _, identifier := range p {
if identifier == "*" {
in.values = append(in.values, value)
continue
}
if _, ok := in.children[identifier]; !ok {
in.children[identifier] = NewIndex()
}
in.children[identifier].Add(value, pp[1:]...)
}
}
func (di *IndexNode) Collect(pp ...[]string) (out []interface{}) {
if len(pp) == 0 {
if len(di.values) > 0 {
out = append(out, di.values...)
}
return
}
p := pp[0]
for _, identifier := range p {
skip := false
if _, ok := di.children[identifier]; ok {
aux := di.children[identifier].Collect(pp[1:]...)
out = append(out, aux...)
skip = len(aux) > 0
}
out = append(out, di.values...)
if skip {
break
}
}
return
}
+78
View File
@@ -0,0 +1,78 @@
package resource
import (
"testing"
"github.com/stretchr/testify/require"
)
func pathHelper(pp ...string) (out [][]string) {
for _, p := range pp {
out = append(out, []string{p})
}
return
}
func TestSimplePath(t *testing.T) {
n := NewIndex()
n.Add(1, pathHelper("a")...)
out := n.Collect(pathHelper("a")...)
require.Len(t, out, 1)
require.Equal(t, 1, out[0])
}
func TestSimpleLongPath(t *testing.T) {
n := NewIndex()
n.Add(1, pathHelper("a", "b", "c")...)
out := n.Collect(pathHelper("a", "b", "c")...)
require.Len(t, out, 1)
require.Equal(t, 1, out[0])
}
func TestWildSimplePath(t *testing.T) {
n := NewIndex()
n.Add(1, pathHelper("a", "*")...)
out := n.Collect(pathHelper("a", "b")...)
require.Len(t, out, 1)
require.Equal(t, 1, out[0])
}
func TestComplex(t *testing.T) {
n := NewIndex()
n.Add(1, pathHelper("resource", "lvl1.id1", "lvl2.id2", "res.id")...)
n.Add(2, pathHelper("resource", "lvl1.id1", "lvl2.id2", "*")...)
n.Add(3, pathHelper("resource", "lvl1.id1", "lvl2.id2", "*")...)
n.Add(4, pathHelper("resource", "*", "*", "*")...)
var out []interface{}
t.Run("full path", func(t *testing.T) {
out = n.Collect(pathHelper("resource", "lvl1.id1", "lvl2.id2", "res.id")...)
require.Len(t, out, 4)
require.Equal(t, []interface{}{1, 2, 3, 4}, out)
})
t.Run("full path; id not matching", func(t *testing.T) {
out = n.Collect(pathHelper("resource", "lvl1.id1", "lvl2.id2", "invalid")...)
require.Len(t, out, 3)
require.Equal(t, []interface{}{2, 3, 4}, out)
})
t.Run("partial matching path 1", func(t *testing.T) {
out = n.Collect(pathHelper("resource", "invald", "invalid", "invalid")...)
require.Len(t, out, 1)
require.Equal(t, []interface{}{4}, out)
})
t.Run("not matching path", func(t *testing.T) {
out = n.Collect(pathHelper("invalid", "invald", "invalid", "invalid")...)
require.Len(t, out, 0)
})
}