Add a simple implementation of a path trie structure that can be used to look up assoicated data for any given path. The constructor will build the trie from a dict of paths to associated data. Later modification is currently not support. Add tests for it creation and lookup.
123 lines
2.3 KiB
Go
123 lines
2.3 KiB
Go
package disk
|
|
|
|
import (
|
|
"sort"
|
|
"strings"
|
|
)
|
|
|
|
// splits the path into its individual components. Retruns the
|
|
// empty list if the path is just the absolute root, i.e. "/".
|
|
func pathTrieSplitPath(path string) []string {
|
|
path = strings.Trim(path, "/")
|
|
if path == "" {
|
|
return []string{}
|
|
}
|
|
|
|
return strings.Split(path, "/")
|
|
}
|
|
|
|
type PathTrie struct {
|
|
Name []string
|
|
Paths []*PathTrie
|
|
Payload interface{}
|
|
}
|
|
|
|
// match checks if the given trie is a prefix of path
|
|
func (trie *PathTrie) match(path []string) bool {
|
|
if len(trie.Name) > len(path) {
|
|
return false
|
|
}
|
|
|
|
for i := range trie.Name {
|
|
if path[i] != trie.Name[i] {
|
|
return false
|
|
}
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
func (trie *PathTrie) get(path []string) (*PathTrie, []string) {
|
|
if len(path) < 1 {
|
|
panic("programming error: expected root node")
|
|
}
|
|
|
|
var node *PathTrie
|
|
for i := range trie.Paths {
|
|
if trie.Paths[i].match(path) {
|
|
node = trie.Paths[i]
|
|
break
|
|
}
|
|
}
|
|
|
|
// no subpath match, we are the best match
|
|
if node == nil {
|
|
return trie, path
|
|
}
|
|
|
|
// node, or one of its sub-nodes, is a match
|
|
prefix := len(node.Name)
|
|
|
|
// the node is a perfect match, return it
|
|
if len(path) == prefix {
|
|
return node, nil
|
|
}
|
|
|
|
// check if any sub-path's of node match
|
|
return node.get(path[prefix:])
|
|
}
|
|
|
|
func (trie *PathTrie) add(path []string) *PathTrie {
|
|
node := &PathTrie{Name: path}
|
|
|
|
if trie.Paths == nil {
|
|
trie.Paths = make([]*PathTrie, 0, 1)
|
|
}
|
|
|
|
trie.Paths = append(trie.Paths, node)
|
|
|
|
return node
|
|
}
|
|
|
|
// Construct a new trie from a map of paths to their payloads.
|
|
// Returns the root node of the trie.
|
|
func NewPathTrieFromMap(entries map[string]interface{}) *PathTrie {
|
|
root := &PathTrie{Name: []string{}}
|
|
|
|
keys := make([]string, 0, len(entries))
|
|
for k := range entries {
|
|
keys = append(keys, k)
|
|
}
|
|
|
|
sort.Strings(keys)
|
|
|
|
for _, k := range keys {
|
|
node, left := root.Lookup(k)
|
|
|
|
if len(left) > 0 {
|
|
node = node.add(left)
|
|
}
|
|
|
|
node.Payload = entries[k]
|
|
}
|
|
|
|
return root
|
|
}
|
|
|
|
// Lookup returns the node that is the prefix of path and
|
|
// the unmatched path segment. Must be called on the root
|
|
// trie node.
|
|
func (root *PathTrie) Lookup(path string) (*PathTrie, []string) {
|
|
|
|
if len(root.Name) != 0 {
|
|
panic("programming error: lookup on non-root trie node")
|
|
}
|
|
|
|
elements := pathTrieSplitPath(path)
|
|
|
|
if len(elements) == 0 {
|
|
return root, elements
|
|
}
|
|
|
|
return root.get(elements)
|
|
}
|