Commit cdca637e authored by Kamil Trzciński's avatar Kamil Trzciński 💬

Add `github.com/markelog/trie`

parent 4bf5c2fd
Pipeline #50523848 passed with stages
in 65 minutes and 14 seconds
......@@ -446,6 +446,17 @@
revision = "282ce0e5322c82529687d609ee670fac7c7d917c"
version = "v1.1.1"
[[projects]]
branch = "master"
digest = "1:26fa3ab93b25b864677e6e6ce8d94ea52d3d667344edbbe62f2236573741a0a1"
name = "github.com/markelog/trie"
packages = [
".",
"node",
]
pruneopts = "N"
revision = "098fa99650c08b308663e2b2f057eddf4fb83fa2"
[[projects]]
branch = "master"
digest = "1:6f2e6a8a38f1e3b7d579404f0420bdf7a11389f4dc2de93134a142da2bbdca05"
......@@ -1046,6 +1057,7 @@
"github.com/jpillora/backoff",
"github.com/kardianos/osext",
"github.com/kr/pty",
"github.com/markelog/trie",
"github.com/mattn/go-zglob",
"github.com/minio/minio-go",
"github.com/minio/minio-go/pkg/credentials",
......
......@@ -154,6 +154,10 @@ ignored = ["test", "appengine"]
name = "gitlab.com/gitlab-org/gitlab-terminal"
revision = "5af59b871b1bcc3f4b733f6db0ff3b6e8b247b92"
[[constraint]]
name = "github.com/markelog/trie"
branch = "master"
##
## Refrain innovations ;)
##
......
The MIT License (MIT)
Copyright (c) Oleg Gaidarenko <markelog@gmail.com>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
// Package node implements data value intendent to be used in the trie package
package node
// Node stores essential node data
type Node struct {
// Key of the node
Key string
// Value of the node
Value interface{}
// Parent leaf of the node
Parent *Node
// ImmediateParent might be a branch or a leaf
ImmediateParent *Node
// Children of the node
Children []*Node
// Keys of children
Keys map[string]*Node
// Leaf, is it?
Leaf bool
}
// New returns new Node (so we wouldn't have to define so many params)
func New(key string, value interface{}) *Node {
return &Node{
Key: key,
Value: value,
Parent: nil,
Children: []*Node{},
Keys: map[string]*Node{},
Leaf: false,
}
}
package node_test
import (
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
"testing"
)
func TestRequest(t *testing.T) {
RegisterFailHandler(Fail)
RunSpecs(t, "Node Suite")
}
package node_test
import (
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
"github.com/sanity-io/litter"
. "github.com/markelog/trie/node"
)
var _ = Describe("node", func() {
Describe("New", func() {
It("should properly create new node", func() {
new := New("test", 1)
expected := `&node.Node{
Key: "test",
Value: 1,
Parent: nil,
ImmediateParent: nil,
Children: []*node.Node{}
,
Keys: map[string]*node.Node{
},
Leaf: false,
}`
Expect(litter.Sdump(new)).To(Equal(expected))
})
})
})
// Package trie implements the trie data structure
// See https://en.wikipedia.org/wiki/Trie
package trie
import "github.com/markelog/trie/node"
// Trie essential structure
type Trie struct {
// Root node
Root *node.Node
// Size of the trie (counting only the leafs)
Size int
}
// Walker for the traverse function
// If such function returns false it will stop traverse
type Walker func(item *node.Node) bool
// New returns new Trie
func New() *Trie {
return &Trie{
Root: node.New("", ""),
}
}
// Add stuff to the trie
func (trie *Trie) Add(key string, value interface{}) (result *node.Node) {
var (
current = trie.Root
parent = trie.Root
length = len(key)
)
for i := 1; i < length+1; i++ {
sKey := key[0:i]
// Create that new node which we should add
newNode := node.New(sKey, nil)
// Specify params of the new node as if its branch node
newNode.Parent = current
newNode.ImmediateParent = current
newNode.Leaf = false
// Check to see if character node exists in keys children
if current.Keys[sKey] == nil {
current.Keys[sKey] = newNode
// Means we already have node here which might not be leaf
// So we have to replace it
} else if sKey == key {
current.Keys[sKey].Leaf = true
result = newNode
newNode.Parent = parent
break
}
// Next one
current = current.Keys[sKey]
// Only redefine the parent for the potential leaf when parent is also
// a leaf - so it would be more enjoyable to traverse
if current.Leaf {
parent = current
}
// Now if its an actual word - specificy as a leaf,
// not like vile branch (see above for initial params)
if sKey == key {
newNode.Leaf = true
newNode.Value = value
newNode.Parent = parent
parent.Children = append(parent.Children, newNode)
result = newNode
}
}
// Increment the size of the trie
trie.Size++
return
}
// Remove removes subtree of the exact key match
func (trie *Trie) Remove(key string) bool {
target := trie.Find(key)
// If there is no key like that in the trie
if target == nil {
return false
}
parent := target.Parent
immediate := target.ImmediateParent
// Remove target references from children
parent.Children = removeKey(parent.Children, target)
// Remove target reference from the keys
delete(immediate.Keys, key)
for immediate != nil {
// If we have leafs in the branch we still need that node
if hasLeafs(immediate.Keys) {
break
}
// Nullify removed elements
immediate.Parent = nil
immediate.Keys = map[string]*node.Node{}
immediate = immediate.ImmediateParent
}
// Set parent to nil, since we are no longer part of the trie
target.Parent = nil
target.ImmediateParent = nil
// And decrease the size
trie.Size--
return true
}
// Yank removes only one leaf not the subtree.
// Difference with Remove() is that Yank does not removes the leaf subtree,
// still removes the branches though
func (trie *Trie) Yank(key string) bool {
target := trie.Find(key)
// If there is no key like that in the trie
if target == nil {
return false
}
parent := target.Parent
// Check is we have leafs forward down the tree
childExist := false
trie.Visit(target, func(item *node.Node) bool {
childExist = true
return false
})
// If we does not have anything valuable afterwards, then just remove it
if childExist == false {
return trie.Remove(key)
}
// Otherwise replace leaf with the branch
target.Leaf = false
for _, element := range target.Children {
element.Parent = parent
}
target.Children = []*node.Node{}
trie.Size--
return true
}
func hasLeafs(keys map[string]*node.Node) bool {
for _, element := range keys {
if element.Leaf {
return true
}
}
return false
}
// Index returns the index of the node,
// if not found returns the -1
func index(list []*node.Node, node *node.Node) int {
for index, element := range list {
if node == element {
return index
}
}
return -1
}
// removes they key from the list if node is in the list
func removeKey(list []*node.Node, node *node.Node) []*node.Node {
index := index(list, node)
// It seems we have nothing to do here?
if index == -1 {
return list
}
// Put target element to the end,
// with replacing current index with lastest element
list[len(list)-1], list[index] = list[index], list[len(list)-1]
// Now miss the latest removed element
return list[:len(list)-1]
}
// Contains check presence of the key in the trie
func (trie Trie) Contains(key string) bool {
var (
current = trie.Root
length = len(key)
)
// for every character in the word
for i := 1; i < length+1; i++ {
sKey := key[0:i]
// Check if we have such key, since if its not then we can abort
if current.Keys[sKey] == nil {
return false
}
// Key exist - proceed to the next depth of the trie
current = current.Keys[sKey]
}
// We finished going through all the words, but is it a whole word?
return current.Leaf
}
// Search returns every word with the given prefix
func (trie Trie) Search(prefix string) (result []*node.Node) {
var (
current = trie.Root
length = len(prefix)
)
for i := 1; i < length+1; i++ {
partPrefix := prefix[0:i]
// If we don't have anything to search for anymore
if current.Keys[partPrefix] == nil {
return
}
// Proceed forward then
current = current.Keys[partPrefix]
}
result = findAll(current, result)
return
}
// findAll recursively find the words
func findAll(current *node.Node, result []*node.Node) []*node.Node {
if current.Leaf {
result = append(result, current)
}
for key := range current.Keys {
result = findAll(current.Keys[key], result)
}
return result
}
// Find specific one full matched key
func (trie Trie) Find(key string) *node.Node {
var (
current = trie.Root
length = len(key)
)
for i := 1; i < length+1; i++ {
partKey := key[0:i]
// If we don't have anything anymore - return what we got
if current.Keys[partKey] == nil {
return nil
}
// Proceed forward then
current = current.Keys[partKey]
if partKey == key && current.Leaf {
return current
}
}
return nil
}
// Traverse the leaves (not the branches)
func (trie Trie) Traverse(fn Walker) {
trie.Visit(trie.Root, fn)
}
// Visit specific part of the tree
func (trie Trie) Visit(current *node.Node, fn Walker) {
var (
children = current.Children
length = len(children)
)
for i := 0; i < length; i++ {
result := fn(children[i])
if result == false {
return
}
trie.Visit(children[i], fn)
}
return
}
// VisitAll allows visit every node
func (trie Trie) VisitAll(current *node.Node, fn Walker) {
keys := current.Keys
for _, element := range keys {
result := fn(element)
if result == false {
return
}
trie.VisitAll(element, fn)
}
return
}
package trie_test
import (
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
"testing"
)
func TestRequest(t *testing.T) {
RegisterFailHandler(Fail)
RunSpecs(t, "Trie Suite")
}
This diff is collapsed.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment