Add a resoruce indexing utility struct
The struct indexes resources which lowers set search complexity from linear to constant.
This commit is contained in:
@@ -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
|
||||
}
|
||||
@@ -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)
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user