mirror of
https://github.com/moby/moby.git
synced 2022-11-09 12:21:53 -05:00
refactor TruncIndex to use a trie & vendor deps
Docker-DCO-1.1-Signed-off-by: Cristian Staretu <cristian.staretu@gmail.com> (github: unclejack)
This commit is contained in:
parent
87c5ecdd69
commit
e0dad30579
14 changed files with 1795 additions and 58 deletions
|
@ -182,11 +182,7 @@ func (daemon *Daemon) register(container *Container, updateSuffixarray bool, con
|
|||
|
||||
// don't update the Suffixarray if we're starting up
|
||||
// we'll waste time if we update it for every container
|
||||
if updateSuffixarray {
|
||||
daemon.idIndex.Add(container.ID)
|
||||
} else {
|
||||
daemon.idIndex.AddWithoutSuffixarrayUpdate(container.ID)
|
||||
}
|
||||
daemon.idIndex.Add(container.ID)
|
||||
|
||||
// FIXME: if the container is supposed to be running but is not, auto restart it?
|
||||
// if so, then we need to restart monitor and init a new lock
|
||||
|
@ -377,8 +373,6 @@ func (daemon *Daemon) restore() error {
|
|||
}
|
||||
}
|
||||
|
||||
daemon.idIndex.UpdateSuffixarray()
|
||||
|
||||
for _, container := range containersToStart {
|
||||
utils.Debugf("Starting container %d", container.ID)
|
||||
if err := container.Start(); err != nil {
|
||||
|
|
|
@ -47,6 +47,8 @@ clone git github.com/gorilla/mux 136d54f81f
|
|||
|
||||
clone git github.com/syndtr/gocapability 3c85049eae
|
||||
|
||||
clone git github.com/tchap/go-patricia v1.0.0
|
||||
|
||||
clone hg code.google.com/p/go.net 84a4013f96e0
|
||||
|
||||
clone hg code.google.com/p/gosqlite 74691fb6f837
|
||||
|
|
|
@ -1,31 +1,34 @@
|
|||
package truncindex
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"index/suffixarray"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/tchap/go-patricia/patricia"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrNoID = errors.New("prefix can't be empty")
|
||||
)
|
||||
|
||||
// TruncIndex allows the retrieval of string identifiers by any of their unique prefixes.
|
||||
// This is used to retrieve image and container IDs by more convenient shorthand prefixes.
|
||||
type TruncIndex struct {
|
||||
sync.RWMutex
|
||||
index *suffixarray.Index
|
||||
ids map[string]bool
|
||||
bytes []byte
|
||||
trie *patricia.Trie
|
||||
ids map[string]struct{}
|
||||
}
|
||||
|
||||
func NewTruncIndex(ids []string) (idx *TruncIndex) {
|
||||
idx = &TruncIndex{
|
||||
ids: make(map[string]bool),
|
||||
bytes: []byte{' '},
|
||||
ids: make(map[string]struct{}),
|
||||
trie: patricia.NewTrie(),
|
||||
}
|
||||
for _, id := range ids {
|
||||
idx.ids[id] = true
|
||||
idx.bytes = append(idx.bytes, []byte(id+" ")...)
|
||||
idx.addId(id)
|
||||
}
|
||||
idx.index = suffixarray.New(idx.bytes)
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -33,11 +36,16 @@ func (idx *TruncIndex) addId(id string) error {
|
|||
if strings.Contains(id, " ") {
|
||||
return fmt.Errorf("Illegal character: ' '")
|
||||
}
|
||||
if _, exists := idx.ids[id]; exists {
|
||||
return fmt.Errorf("Id already exists: %s", id)
|
||||
if id == "" {
|
||||
return ErrNoID
|
||||
}
|
||||
if _, exists := idx.ids[id]; exists {
|
||||
return fmt.Errorf("Id already exists: '%s'", id)
|
||||
}
|
||||
idx.ids[id] = struct{}{}
|
||||
if inserted := idx.trie.Insert(patricia.Prefix(id), struct{}{}); !inserted {
|
||||
return fmt.Errorf("Failed to insert id: %s", id)
|
||||
}
|
||||
idx.ids[id] = true
|
||||
idx.bytes = append(idx.bytes, []byte(id+" ")...)
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -47,56 +55,46 @@ func (idx *TruncIndex) Add(id string) error {
|
|||
if err := idx.addId(id); err != nil {
|
||||
return err
|
||||
}
|
||||
idx.index = suffixarray.New(idx.bytes)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (idx *TruncIndex) AddWithoutSuffixarrayUpdate(id string) error {
|
||||
idx.Lock()
|
||||
defer idx.Unlock()
|
||||
return idx.addId(id)
|
||||
}
|
||||
|
||||
func (idx *TruncIndex) UpdateSuffixarray() {
|
||||
idx.Lock()
|
||||
defer idx.Unlock()
|
||||
idx.index = suffixarray.New(idx.bytes)
|
||||
}
|
||||
|
||||
func (idx *TruncIndex) Delete(id string) error {
|
||||
idx.Lock()
|
||||
defer idx.Unlock()
|
||||
if _, exists := idx.ids[id]; !exists {
|
||||
return fmt.Errorf("No such id: %s", id)
|
||||
}
|
||||
before, after, err := idx.lookup(id)
|
||||
if err != nil {
|
||||
return err
|
||||
if _, exists := idx.ids[id]; !exists || id == "" {
|
||||
return fmt.Errorf("No such id: '%s'", id)
|
||||
}
|
||||
delete(idx.ids, id)
|
||||
idx.bytes = append(idx.bytes[:before], idx.bytes[after:]...)
|
||||
idx.index = suffixarray.New(idx.bytes)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (idx *TruncIndex) lookup(s string) (int, int, error) {
|
||||
offsets := idx.index.Lookup([]byte(" "+s), -1)
|
||||
//log.Printf("lookup(%s): %v (index bytes: '%s')\n", s, offsets, idx.index.Bytes())
|
||||
if offsets == nil || len(offsets) == 0 || len(offsets) > 1 {
|
||||
return -1, -1, fmt.Errorf("No such id: %s", s)
|
||||
if deleted := idx.trie.Delete(patricia.Prefix(id)); !deleted {
|
||||
return fmt.Errorf("No such id: '%s'", id)
|
||||
}
|
||||
offsetBefore := offsets[0] + 1
|
||||
offsetAfter := offsetBefore + strings.Index(string(idx.bytes[offsetBefore:]), " ")
|
||||
return offsetBefore, offsetAfter, nil
|
||||
return nil
|
||||
}
|
||||
|
||||
func (idx *TruncIndex) Get(s string) (string, error) {
|
||||
idx.RLock()
|
||||
defer idx.RUnlock()
|
||||
before, after, err := idx.lookup(s)
|
||||
//log.Printf("Get(%s) bytes=|%s| before=|%d| after=|%d|\n", s, idx.bytes, before, after)
|
||||
if err != nil {
|
||||
return "", err
|
||||
var (
|
||||
id string
|
||||
)
|
||||
if s == "" {
|
||||
return "", ErrNoID
|
||||
}
|
||||
return string(idx.bytes[before:after]), err
|
||||
subTreeVisitFunc := func(prefix patricia.Prefix, item patricia.Item) error {
|
||||
if id != "" {
|
||||
// we haven't found the ID if there are two or more IDs
|
||||
id = ""
|
||||
return fmt.Errorf("we've found two entries")
|
||||
}
|
||||
id = string(prefix)
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := idx.trie.VisitSubtree(patricia.Prefix(s), subTreeVisitFunc); err != nil {
|
||||
return "", fmt.Errorf("No such id: %s", s)
|
||||
}
|
||||
if id != "" {
|
||||
return id, nil
|
||||
}
|
||||
return "", fmt.Errorf("No such id: %s", s)
|
||||
}
|
||||
|
|
|
@ -26,8 +26,16 @@ func TestTruncIndex(t *testing.T) {
|
|||
if err := index.Add(id); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Add an empty id (should fail)
|
||||
if err := index.Add(""); err == nil {
|
||||
t.Fatalf("Adding an empty id should return an error")
|
||||
}
|
||||
|
||||
// Get a non-existing id
|
||||
assertIndexGet(t, index, "abracadabra", "", true)
|
||||
// Get an empty id
|
||||
assertIndexGet(t, index, "", "", true)
|
||||
// Get the exact id
|
||||
assertIndexGet(t, index, id, id, false)
|
||||
// The first letter should match
|
||||
|
@ -60,6 +68,11 @@ func TestTruncIndex(t *testing.T) {
|
|||
t.Fatalf("Deleting a non-existing id should return an error")
|
||||
}
|
||||
|
||||
// Deleting an empty id should return an error
|
||||
if err := index.Delete(""); err == nil {
|
||||
t.Fatal("Deleting an empty id should return an error")
|
||||
}
|
||||
|
||||
// Deleting id2 should remove conflicts
|
||||
if err := index.Delete(id2); err != nil {
|
||||
t.Fatal(err)
|
||||
|
@ -84,7 +97,7 @@ func assertIndexGet(t *testing.T, index *TruncIndex, input, expectedResult strin
|
|||
if result, err := index.Get(input); err != nil && !expectError {
|
||||
t.Fatalf("Unexpected error getting '%s': %s", input, err)
|
||||
} else if err == nil && expectError {
|
||||
t.Fatalf("Getting '%s' should return an error", input)
|
||||
t.Fatalf("Getting '%s' should return an error, not '%s'", input, result)
|
||||
} else if result != expectedResult {
|
||||
t.Fatalf("Getting '%s' returned '%s' instead of '%s'", input, result, expectedResult)
|
||||
}
|
||||
|
|
25
vendor/src/github.com/tchap/go-patricia/.gitignore
vendored
Normal file
25
vendor/src/github.com/tchap/go-patricia/.gitignore
vendored
Normal file
|
@ -0,0 +1,25 @@
|
|||
# Swap files.
|
||||
*.swp
|
||||
|
||||
# Compiled Object files, Static and Dynamic libs (Shared Objects)
|
||||
*.o
|
||||
*.a
|
||||
*.so
|
||||
|
||||
# Folders
|
||||
_obj
|
||||
_test
|
||||
|
||||
# Architecture specific extensions/prefixes
|
||||
*.[568vq]
|
||||
[568vq].out
|
||||
|
||||
*.cgo1.go
|
||||
*.cgo2.c
|
||||
_cgo_defun.c
|
||||
_cgo_gotypes.go
|
||||
_cgo_export.*
|
||||
|
||||
_testmain.go
|
||||
|
||||
*.exe
|
9
vendor/src/github.com/tchap/go-patricia/.travis.yml
vendored
Normal file
9
vendor/src/github.com/tchap/go-patricia/.travis.yml
vendored
Normal file
|
@ -0,0 +1,9 @@
|
|||
language: go
|
||||
|
||||
go:
|
||||
- 1.2
|
||||
- tip
|
||||
|
||||
branches:
|
||||
exclude:
|
||||
- wip
|
3
vendor/src/github.com/tchap/go-patricia/AUTHORS
vendored
Normal file
3
vendor/src/github.com/tchap/go-patricia/AUTHORS
vendored
Normal file
|
@ -0,0 +1,3 @@
|
|||
This is the complete list of go-patricia copyright holders:
|
||||
|
||||
Ondřej Kupka <ondra.cap@gmail.com>
|
20
vendor/src/github.com/tchap/go-patricia/LICENSE
vendored
Normal file
20
vendor/src/github.com/tchap/go-patricia/LICENSE
vendored
Normal file
|
@ -0,0 +1,20 @@
|
|||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2014 The AUTHORS
|
||||
|
||||
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.
|
112
vendor/src/github.com/tchap/go-patricia/README.md
vendored
Normal file
112
vendor/src/github.com/tchap/go-patricia/README.md
vendored
Normal file
|
@ -0,0 +1,112 @@
|
|||
# go-patricia #
|
||||
|
||||
**Documentation**: [GoDoc](http://godoc.org/github.com/tchap/go-patricia/patricia)<br />
|
||||
**Build Status**: [![Build Status](https://travis-ci.org/tchap/go-patricia.png?branch=master)](https://travis-ci.org/tchap/go-patricia)<br >
|
||||
**Test Coverage**: Comming as soon as Drone.io people update their Go.
|
||||
|
||||
## About ##
|
||||
|
||||
A generic patricia trie (also called radix tree) implemented in Go (Golang).
|
||||
|
||||
The patricia trie as implemented in this library enables fast visiting of items
|
||||
in some particular ways:
|
||||
|
||||
1. visit all items saved in the tree,
|
||||
2. visit all items matching particular prefix (visit subtree), or
|
||||
3. given a string, visit all items matching some prefix of that string.
|
||||
|
||||
`[]byte` type is used for keys, `interface{}` for values.
|
||||
|
||||
`Trie` is not thread safe. Synchronize the access yourself.
|
||||
|
||||
### State of the Project ###
|
||||
|
||||
Apparently some people are using this, so the API should not change often.
|
||||
Any ideas on how to make the library better are still welcome.
|
||||
|
||||
More (unit) testing would be cool as well...
|
||||
|
||||
## Usage ##
|
||||
|
||||
Import the package from GitHub first.
|
||||
|
||||
```go
|
||||
import "github.com/tchap/go-patricia/patricia"
|
||||
```
|
||||
|
||||
Then you can start having fun.
|
||||
|
||||
```go
|
||||
printItem := func(prefix patricia.Prefix, item patricia.Item) error {
|
||||
fmt.Printf("%q: %v\n", prefix, item)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Create a new tree.
|
||||
trie := NewTrie()
|
||||
|
||||
// Insert some items.
|
||||
trie.Insert(Prefix("Pepa Novak"), 1)
|
||||
trie.Insert(Prefix("Pepa Sindelar"), 2)
|
||||
trie.Insert(Prefix("Karel Macha"), 3)
|
||||
trie.Insert(Prefix("Karel Hynek Macha"), 4)
|
||||
|
||||
// Just check if some things are present in the tree.
|
||||
key := Prefix("Pepa Novak")
|
||||
fmt.Printf("%q present? %v\n", key, trie.Match(key))
|
||||
// "Pepa Novak" present? true
|
||||
key = Prefix("Karel")
|
||||
fmt.Printf("Anybody called %q here? %v\n", key, trie.MatchSubtree(key))
|
||||
// Anybody called "Karel" here? true
|
||||
|
||||
// Walk the tree.
|
||||
trie.Visit(printItem)
|
||||
// "Pepa Novak": 1
|
||||
// "Pepa Sindelar": 2
|
||||
// "Karel Macha": 3
|
||||
// "Karel Hynek Macha": 4
|
||||
|
||||
// Walk a subtree.
|
||||
trie.VisitSubtree(Prefix("Pepa"), printItem)
|
||||
// "Pepa Novak": 1
|
||||
// "Pepa Sindelar": 2
|
||||
|
||||
// Modify an item, then fetch it from the tree.
|
||||
trie.Set(Prefix("Karel Hynek Macha"), 10)
|
||||
key = Prefix("Karel Hynek Macha")
|
||||
fmt.Printf("%q: %v\n", key, trie.Get(key))
|
||||
// "Karel Hynek Macha": 10
|
||||
|
||||
// Walk prefixes.
|
||||
prefix := Prefix("Karel Hynek Macha je kouzelnik")
|
||||
trie.VisitPrefixes(prefix, printItem)
|
||||
// "Karel Hynek Macha": 10
|
||||
|
||||
// Delete some items.
|
||||
trie.Delete(Prefix("Pepa Novak"))
|
||||
trie.Delete(Prefix("Karel Macha"))
|
||||
|
||||
// Walk again.
|
||||
trie.Visit(printItem)
|
||||
// "Pepa Sindelar": 2
|
||||
// "Karel Hynek Macha": 10
|
||||
|
||||
// Delete a subtree.
|
||||
trie.DeleteSubtree(Prefix("Pepa"))
|
||||
|
||||
// Print what is left.
|
||||
trie.Visit(printItem)
|
||||
// "Karel Hynek Macha": 10
|
||||
```
|
||||
|
||||
## License ##
|
||||
|
||||
MIT, check the `LICENSE` file.
|
||||
|
||||
[![Gittip
|
||||
Badge](http://img.shields.io/gittip/alanhamlett.png)](https://www.gittip.com/tchap/
|
||||
"Gittip Badge")
|
||||
|
||||
[![Bitdeli
|
||||
Badge](https://d2weczhvl823v0.cloudfront.net/tchap/go-patricia/trend.png)](https://bitdeli.com/free
|
||||
"Bitdeli Badge")
|
231
vendor/src/github.com/tchap/go-patricia/patricia/children.go
vendored
Normal file
231
vendor/src/github.com/tchap/go-patricia/patricia/children.go
vendored
Normal file
|
@ -0,0 +1,231 @@
|
|||
// Copyright (c) 2014 The go-patricia AUTHORS
|
||||
//
|
||||
// Use of this source code is governed by The MIT License
|
||||
// that can be found in the LICENSE file.
|
||||
|
||||
package patricia
|
||||
|
||||
const (
|
||||
// Max prefix length that is kept in a single trie node.
|
||||
MaxPrefixPerNode = 10
|
||||
// Max children to keep in a node in the sparse mode.
|
||||
MaxChildrenPerSparseNode = 8
|
||||
)
|
||||
|
||||
type childList interface {
|
||||
length() int
|
||||
head() *Trie
|
||||
add(child *Trie) childList
|
||||
replace(b byte, child *Trie)
|
||||
remove(child *Trie)
|
||||
next(b byte) *Trie
|
||||
walk(prefix *Prefix, visitor VisitorFunc) error
|
||||
}
|
||||
|
||||
type sparseChildList struct {
|
||||
children []*Trie
|
||||
}
|
||||
|
||||
func newSparseChildList() childList {
|
||||
return &sparseChildList{
|
||||
children: make([]*Trie, 0, MaxChildrenPerSparseNode),
|
||||
}
|
||||
}
|
||||
|
||||
func (list *sparseChildList) length() int {
|
||||
return len(list.children)
|
||||
}
|
||||
|
||||
func (list *sparseChildList) head() *Trie {
|
||||
return list.children[0]
|
||||
}
|
||||
|
||||
func (list *sparseChildList) add(child *Trie) childList {
|
||||
// Search for an empty spot and insert the child if possible.
|
||||
if len(list.children) != cap(list.children) {
|
||||
list.children = append(list.children, child)
|
||||
return list
|
||||
}
|
||||
|
||||
// Otherwise we have to transform to the dense list type.
|
||||
return newDenseChildList(list, child)
|
||||
}
|
||||
|
||||
func (list *sparseChildList) replace(b byte, child *Trie) {
|
||||
// Seek the child and replace it.
|
||||
for i, node := range list.children {
|
||||
if node.prefix[0] == b {
|
||||
list.children[i] = child
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (list *sparseChildList) remove(child *Trie) {
|
||||
for i, node := range list.children {
|
||||
if node.prefix[0] == child.prefix[0] {
|
||||
list.children = append(list.children[:i], list.children[i+1:]...)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// This is not supposed to be reached.
|
||||
panic("removing non-existent child")
|
||||
}
|
||||
|
||||
func (list *sparseChildList) next(b byte) *Trie {
|
||||
for _, child := range list.children {
|
||||
if child.prefix[0] == b {
|
||||
return child
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (list *sparseChildList) walk(prefix *Prefix, visitor VisitorFunc) error {
|
||||
for _, child := range list.children {
|
||||
*prefix = append(*prefix, child.prefix...)
|
||||
if child.item != nil {
|
||||
err := visitor(*prefix, child.item)
|
||||
if err != nil {
|
||||
if err == SkipSubtree {
|
||||
*prefix = (*prefix)[:len(*prefix)-len(child.prefix)]
|
||||
continue
|
||||
}
|
||||
*prefix = (*prefix)[:len(*prefix)-len(child.prefix)]
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
err := child.children.walk(prefix, visitor)
|
||||
*prefix = (*prefix)[:len(*prefix)-len(child.prefix)]
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type denseChildList struct {
|
||||
min int
|
||||
max int
|
||||
children []*Trie
|
||||
}
|
||||
|
||||
func newDenseChildList(list *sparseChildList, child *Trie) childList {
|
||||
var (
|
||||
min int = 255
|
||||
max int = 0
|
||||
)
|
||||
for _, child := range list.children {
|
||||
b := int(child.prefix[0])
|
||||
if b < min {
|
||||
min = b
|
||||
}
|
||||
if b > max {
|
||||
max = b
|
||||
}
|
||||
}
|
||||
|
||||
b := int(child.prefix[0])
|
||||
if b < min {
|
||||
min = b
|
||||
}
|
||||
if b > max {
|
||||
max = b
|
||||
}
|
||||
|
||||
children := make([]*Trie, max-min+1)
|
||||
for _, child := range list.children {
|
||||
children[int(child.prefix[0])-min] = child
|
||||
}
|
||||
children[int(child.prefix[0])-min] = child
|
||||
|
||||
return &denseChildList{min, max, children}
|
||||
}
|
||||
|
||||
func (list *denseChildList) length() int {
|
||||
return list.max - list.min + 1
|
||||
}
|
||||
|
||||
func (list *denseChildList) head() *Trie {
|
||||
return list.children[0]
|
||||
}
|
||||
|
||||
func (list *denseChildList) add(child *Trie) childList {
|
||||
b := int(child.prefix[0])
|
||||
|
||||
switch {
|
||||
case list.min <= b && b <= list.max:
|
||||
if list.children[b-list.min] != nil {
|
||||
panic("dense child list collision detected")
|
||||
}
|
||||
list.children[b-list.min] = child
|
||||
|
||||
case b < list.min:
|
||||
children := make([]*Trie, list.max-b+1)
|
||||
children[0] = child
|
||||
copy(children[list.min-b:], list.children)
|
||||
list.children = children
|
||||
list.min = b
|
||||
|
||||
default: // b > list.max
|
||||
children := make([]*Trie, b-list.min+1)
|
||||
children[b-list.min] = child
|
||||
copy(children, list.children)
|
||||
list.children = children
|
||||
list.max = b
|
||||
}
|
||||
|
||||
return list
|
||||
}
|
||||
|
||||
func (list *denseChildList) replace(b byte, child *Trie) {
|
||||
list.children[int(b)-list.min] = nil
|
||||
list.children[int(child.prefix[0])-list.min] = child
|
||||
}
|
||||
|
||||
func (list *denseChildList) remove(child *Trie) {
|
||||
i := int(child.prefix[0]) - list.min
|
||||
if list.children[i] == nil {
|
||||
// This is not supposed to be reached.
|
||||
panic("removing non-existent child")
|
||||
}
|
||||
list.children[i] = nil
|
||||
}
|
||||
|
||||
func (list *denseChildList) next(b byte) *Trie {
|
||||
i := int(b)
|
||||
if i < list.min || list.max < i {
|
||||
return nil
|
||||
}
|
||||
return list.children[i-list.min]
|
||||
}
|
||||
|
||||
func (list *denseChildList) walk(prefix *Prefix, visitor VisitorFunc) error {
|
||||
for _, child := range list.children {
|
||||
if child == nil {
|
||||
continue
|
||||
}
|
||||
*prefix = append(*prefix, child.prefix...)
|
||||
if child.item != nil {
|
||||
if err := visitor(*prefix, child.item); err != nil {
|
||||
if err == SkipSubtree {
|
||||
*prefix = (*prefix)[:len(*prefix)-len(child.prefix)]
|
||||
continue
|
||||
}
|
||||
*prefix = (*prefix)[:len(*prefix)-len(child.prefix)]
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
err := child.children.walk(prefix, visitor)
|
||||
*prefix = (*prefix)[:len(*prefix)-len(child.prefix)]
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
432
vendor/src/github.com/tchap/go-patricia/patricia/patricia.go
vendored
Normal file
432
vendor/src/github.com/tchap/go-patricia/patricia/patricia.go
vendored
Normal file
|
@ -0,0 +1,432 @@
|
|||
// Copyright (c) 2014 The go-patricia AUTHORS
|
||||
//
|
||||
// Use of this source code is governed by The MIT License
|
||||
// that can be found in the LICENSE file.
|
||||
|
||||
package patricia
|
||||
|
||||
import (
|
||||
"errors"
|
||||
)
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Trie
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
type (
|
||||
Prefix []byte
|
||||
Item interface{}
|
||||
VisitorFunc func(prefix Prefix, item Item) error
|
||||
)
|
||||
|
||||
// Trie is a generic patricia trie that allows fast retrieval of items by prefix.
|
||||
// and other funky stuff.
|
||||
//
|
||||
// Trie is not thread-safe.
|
||||
type Trie struct {
|
||||
prefix Prefix
|
||||
item Item
|
||||
|
||||
children childList
|
||||
}
|
||||
|
||||
// Public API ------------------------------------------------------------------
|
||||
|
||||
// Trie constructor.
|
||||
func NewTrie() *Trie {
|
||||
return &Trie{
|
||||
children: newSparseChildList(),
|
||||
}
|
||||
}
|
||||
|
||||
// Item returns the item stored in the root of this trie.
|
||||
func (trie *Trie) Item() Item {
|
||||
return trie.item
|
||||
}
|
||||
|
||||
// Insert inserts a new item into the trie using the given prefix. Insert does
|
||||
// not replace existing items. It returns false if an item was already in place.
|
||||
func (trie *Trie) Insert(key Prefix, item Item) (inserted bool) {
|
||||
return trie.put(key, item, false)
|
||||
}
|
||||
|
||||
// Set works much like Insert, but it always sets the item, possibly replacing
|
||||
// the item previously inserted.
|
||||
func (trie *Trie) Set(key Prefix, item Item) {
|
||||
trie.put(key, item, true)
|
||||
}
|
||||
|
||||
// Get returns the item located at key.
|
||||
//
|
||||
// This method is a bit dangerous, because Get can as well end up in an internal
|
||||
// node that is not really representing any user-defined value. So when nil is
|
||||
// a valid value being used, it is not possible to tell if the value was inserted
|
||||
// into the tree by the user or not. A possible workaround for this is not to use
|
||||
// nil interface as a valid value, even using zero value of any type is enough
|
||||
// to prevent this bad behaviour.
|
||||
func (trie *Trie) Get(key Prefix) (item Item) {
|
||||
_, node, found, leftover := trie.findSubtree(key)
|
||||
if !found || len(leftover) != 0 {
|
||||
return nil
|
||||
}
|
||||
return node.item
|
||||
}
|
||||
|
||||
// Match returns what Get(prefix) != nil would return. The same warning as for
|
||||
// Get applies here as well.
|
||||
func (trie *Trie) Match(prefix Prefix) (matchedExactly bool) {
|
||||
return trie.Get(prefix) != nil
|
||||
}
|
||||
|
||||
// MatchSubtree returns true when there is a subtree representing extensions
|
||||
// to key, that is if there are any keys in the tree which have key as prefix.
|
||||
func (trie *Trie) MatchSubtree(key Prefix) (matched bool) {
|
||||
_, _, matched, _ = trie.findSubtree(key)
|
||||
return
|
||||
}
|
||||
|
||||
// Visit calls visitor on every node containing a non-nil item.
|
||||
//
|
||||
// If an error is returned from visitor, the function stops visiting the tree
|
||||
// and returns that error, unless it is a special error - SkipSubtree. In that
|
||||
// case Visit skips the subtree represented by the current node and continues
|
||||
// elsewhere.
|
||||
func (trie *Trie) Visit(visitor VisitorFunc) error {
|
||||
return trie.walk(nil, visitor)
|
||||
}
|
||||
|
||||
// VisitSubtree works much like Visit, but it only visits nodes matching prefix.
|
||||
func (trie *Trie) VisitSubtree(prefix Prefix, visitor VisitorFunc) error {
|
||||
// Nil prefix not allowed.
|
||||
if prefix == nil {
|
||||
panic(ErrNilPrefix)
|
||||
}
|
||||
|
||||
// Empty trie must be handled explicitly.
|
||||
if trie.prefix == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Locate the relevant subtree.
|
||||
_, root, found, leftover := trie.findSubtree(prefix)
|
||||
if !found {
|
||||
return nil
|
||||
}
|
||||
prefix = append(prefix, leftover...)
|
||||
|
||||
// Visit it.
|
||||
return root.walk(prefix, visitor)
|
||||
}
|
||||
|
||||
// VisitPrefixes visits only nodes that represent prefixes of key.
|
||||
// To say the obvious, returning SkipSubtree from visitor makes no sense here.
|
||||
func (trie *Trie) VisitPrefixes(key Prefix, visitor VisitorFunc) error {
|
||||
// Nil key not allowed.
|
||||
if key == nil {
|
||||
panic(ErrNilPrefix)
|
||||
}
|
||||
|
||||
// Empty trie must be handled explicitly.
|
||||
if trie.prefix == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Walk the path matching key prefixes.
|
||||
node := trie
|
||||
prefix := key
|
||||
offset := 0
|
||||
for {
|
||||
// Compute what part of prefix matches.
|
||||
common := node.longestCommonPrefixLength(key)
|
||||
key = key[common:]
|
||||
offset += common
|
||||
|
||||
// Partial match means that there is no subtree matching prefix.
|
||||
if common < len(node.prefix) {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Call the visitor.
|
||||
if item := node.item; item != nil {
|
||||
if err := visitor(prefix[:offset], item); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if len(key) == 0 {
|
||||
// This node represents key, we are finished.
|
||||
return nil
|
||||
}
|
||||
|
||||
// There is some key suffix left, move to the children.
|
||||
child := node.children.next(key[0])
|
||||
if child == nil {
|
||||
// There is nowhere to continue, return.
|
||||
return nil
|
||||
}
|
||||
|
||||
node = child
|
||||
}
|
||||
}
|
||||
|
||||
// Delete deletes the item represented by the given prefix.
|
||||
//
|
||||
// True is returned if the matching node was found and deleted.
|
||||
func (trie *Trie) Delete(key Prefix) (deleted bool) {
|
||||
// Nil prefix not allowed.
|
||||
if key == nil {
|
||||
panic(ErrNilPrefix)
|
||||
}
|
||||
|
||||
// Empty trie must be handled explicitly.
|
||||
if trie.prefix == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
// Find the relevant node.
|
||||
parent, node, _, leftover := trie.findSubtree(key)
|
||||
if len(leftover) != 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
// If the item is already set to nil, there is nothing to do.
|
||||
if node.item == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
// Delete the item.
|
||||
node.item = nil
|
||||
|
||||
// Compact since that might be possible now.
|
||||
if compacted := node.compact(); compacted != node {
|
||||
if parent == nil {
|
||||
*node = *compacted
|
||||
} else {
|
||||
parent.children.replace(node.prefix[0], compacted)
|
||||
*parent = *parent.compact()
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// DeleteSubtree finds the subtree exactly matching prefix and deletes it.
|
||||
//
|
||||
// True is returned if the subtree was found and deleted.
|
||||
func (trie *Trie) DeleteSubtree(prefix Prefix) (deleted bool) {
|
||||
// Nil prefix not allowed.
|
||||
if prefix == nil {
|
||||
panic(ErrNilPrefix)
|
||||
}
|
||||
|
||||
// Empty trie must be handled explicitly.
|
||||
if trie.prefix == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
// Locate the relevant subtree.
|
||||
parent, root, found, _ := trie.findSubtree(prefix)
|
||||
if !found {
|
||||
return false
|
||||
}
|
||||
|
||||
// If we are in the root of the trie, reset the trie.
|
||||
if parent == nil {
|
||||
root.prefix = nil
|
||||
root.children = newSparseChildList()
|
||||
return true
|
||||
}
|
||||
|
||||
// Otherwise remove the root node from its parent.
|
||||
parent.children.remove(root)
|
||||
return true
|
||||
}
|
||||
|
||||
// Internal helper methods -----------------------------------------------------
|
||||
|
||||
func (trie *Trie) put(key Prefix, item Item, replace bool) (inserted bool) {
|
||||
// Nil prefix not allowed.
|
||||
if key == nil {
|
||||
panic(ErrNilPrefix)
|
||||
}
|
||||
|
||||
var (
|
||||
common int
|
||||
node *Trie = trie
|
||||
child *Trie
|
||||
)
|
||||
|
||||
if node.prefix == nil {
|
||||
if len(key) <= MaxPrefixPerNode {
|
||||
node.prefix = key
|
||||
goto InsertItem
|
||||
}
|
||||
node.prefix = key[:MaxPrefixPerNode]
|
||||
key = key[MaxPrefixPerNode:]
|
||||
goto AppendChild
|
||||
}
|
||||
|
||||
for {
|
||||
// Compute the longest common prefix length.
|
||||
common = node.longestCommonPrefixLength(key)
|
||||
key = key[common:]
|
||||
|
||||
// Only a part matches, split.
|
||||
if common < len(node.prefix) {
|
||||
goto SplitPrefix
|
||||
}
|
||||
|
||||
// common == len(node.prefix) since never (common > len(node.prefix))
|
||||
// common == len(former key) <-> 0 == len(key)
|
||||
// -> former key == node.prefix
|
||||
if len(key) == 0 {
|
||||
goto InsertItem
|
||||
}
|
||||
|
||||
// Check children for matching prefix.
|
||||
child = node.children.next(key[0])
|
||||
if child == nil {
|
||||
goto AppendChild
|
||||
}
|
||||
node = child
|
||||
}
|
||||
|
||||
SplitPrefix:
|
||||
// Split the prefix if necessary.
|
||||
child = new(Trie)
|
||||
*child = *node
|
||||
*node = *NewTrie()
|
||||
node.prefix = child.prefix[:common]
|
||||
child.prefix = child.prefix[common:]
|
||||
child = child.compact()
|
||||
node.children = node.children.add(child)
|
||||
|
||||
AppendChild:
|
||||
// Keep appending children until whole prefix is inserted.
|
||||
// This loop starts with empty node.prefix that needs to be filled.
|
||||
for len(key) != 0 {
|
||||
child := NewTrie()
|
||||
if len(key) <= MaxPrefixPerNode {
|
||||
child.prefix = key
|
||||
node.children = node.children.add(child)
|
||||
node = child
|
||||
goto InsertItem
|
||||
} else {
|
||||
child.prefix = key[:MaxPrefixPerNode]
|
||||
key = key[MaxPrefixPerNode:]
|
||||
node.children = node.children.add(child)
|
||||
node = child
|
||||
}
|
||||
}
|
||||
|
||||
InsertItem:
|
||||
// Try to insert the item if possible.
|
||||
if replace || node.item == nil {
|
||||
node.item = item
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (trie *Trie) compact() *Trie {
|
||||
// Only a node with a single child can be compacted.
|
||||
if trie.children.length() != 1 {
|
||||
return trie
|
||||
}
|
||||
|
||||
child := trie.children.head()
|
||||
|
||||
// If any item is set, we cannot compact since we want to retain
|
||||
// the ability to do searching by key. This makes compaction less usable,
|
||||
// but that simply cannot be avoided.
|
||||
if trie.item != nil || child.item != nil {
|
||||
return trie
|
||||
}
|
||||
|
||||
// Make sure the combined prefixes fit into a single node.
|
||||
if len(trie.prefix)+len(child.prefix) > MaxPrefixPerNode {
|
||||
return trie
|
||||
}
|
||||
|
||||
// Concatenate the prefixes, move the items.
|
||||
child.prefix = append(trie.prefix, child.prefix...)
|
||||
if trie.item != nil {
|
||||
child.item = trie.item
|
||||
}
|
||||
|
||||
return child
|
||||
}
|
||||
|
||||
func (trie *Trie) findSubtree(prefix Prefix) (parent *Trie, root *Trie, found bool, leftover Prefix) {
|
||||
// Find the subtree matching prefix.
|
||||
root = trie
|
||||
for {
|
||||
// Compute what part of prefix matches.
|
||||
common := root.longestCommonPrefixLength(prefix)
|
||||
prefix = prefix[common:]
|
||||
|
||||
// We used up the whole prefix, subtree found.
|
||||
if len(prefix) == 0 {
|
||||
found = true
|
||||
leftover = root.prefix[common:]
|
||||
return
|
||||
}
|
||||
|
||||
// Partial match means that there is no subtree matching prefix.
|
||||
if common < len(root.prefix) {
|
||||
leftover = root.prefix[common:]
|
||||
return
|
||||
}
|
||||
|
||||
// There is some prefix left, move to the children.
|
||||
child := root.children.next(prefix[0])
|
||||
if child == nil {
|
||||
// There is nowhere to continue, there is no subtree matching prefix.
|
||||
return
|
||||
}
|
||||
|
||||
parent = root
|
||||
root = child
|
||||
}
|
||||
}
|
||||
|
||||
func (trie *Trie) walk(actualRootPrefix Prefix, visitor VisitorFunc) error {
|
||||
var prefix Prefix
|
||||
// Allocate a bit more space for prefix at the beginning.
|
||||
if actualRootPrefix == nil {
|
||||
prefix = make(Prefix, 32+len(trie.prefix))
|
||||
copy(prefix, trie.prefix)
|
||||
prefix = prefix[:len(trie.prefix)]
|
||||
} else {
|
||||
prefix = make(Prefix, 32+len(actualRootPrefix))
|
||||
copy(prefix, actualRootPrefix)
|
||||
prefix = prefix[:len(actualRootPrefix)]
|
||||
}
|
||||
|
||||
// Visit the root first. Not that this works for empty trie as well since
|
||||
// in that case item == nil && len(children) == 0.
|
||||
if trie.item != nil {
|
||||
if err := visitor(prefix, trie.item); err != nil {
|
||||
if err == SkipSubtree {
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Then continue to the children.
|
||||
return trie.children.walk(&prefix, visitor)
|
||||
}
|
||||
|
||||
func (trie *Trie) longestCommonPrefixLength(prefix Prefix) (i int) {
|
||||
for ; i < len(prefix) && i < len(trie.prefix) && prefix[i] == trie.prefix[i]; i++ {
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Errors ----------------------------------------------------------------------
|
||||
|
||||
var (
|
||||
SkipSubtree = errors.New("Skip this subtree")
|
||||
ErrNilPrefix = errors.New("Nil prefix passed into a method call")
|
||||
)
|
161
vendor/src/github.com/tchap/go-patricia/patricia/patricia_dense_test.go
vendored
Normal file
161
vendor/src/github.com/tchap/go-patricia/patricia/patricia_dense_test.go
vendored
Normal file
|
@ -0,0 +1,161 @@
|
|||
// Copyright (c) 2014 The go-patricia AUTHORS
|
||||
//
|
||||
// Use of this source code is governed by The MIT License
|
||||
// that can be found in the LICENSE file.
|
||||
|
||||
package patricia
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
// Tests -----------------------------------------------------------------------
|
||||
|
||||
func TestTrie_InsertDense(t *testing.T) {
|
||||
trie := NewTrie()
|
||||
|
||||
data := []testData{
|
||||
{"aba", 0, success},
|
||||
{"abb", 1, success},
|
||||
{"abc", 2, success},
|
||||
{"abd", 3, success},
|
||||
{"abe", 4, success},
|
||||
{"abf", 5, success},
|
||||
{"abg", 6, success},
|
||||
{"abh", 7, success},
|
||||
{"abi", 8, success},
|
||||
{"abj", 9, success},
|
||||
{"abk", 0, success},
|
||||
{"abl", 1, success},
|
||||
{"abm", 2, success},
|
||||
{"abn", 3, success},
|
||||
{"abo", 4, success},
|
||||
{"abp", 5, success},
|
||||
{"abq", 6, success},
|
||||
{"abr", 7, success},
|
||||
{"abs", 8, success},
|
||||
{"abt", 9, success},
|
||||
{"abu", 0, success},
|
||||
{"abv", 1, success},
|
||||
{"abw", 2, success},
|
||||
{"abx", 3, success},
|
||||
{"aby", 4, success},
|
||||
{"abz", 5, success},
|
||||
}
|
||||
|
||||
for _, v := range data {
|
||||
t.Logf("INSERT prefix=%v, item=%v, success=%v", v.key, v.value, v.retVal)
|
||||
if ok := trie.Insert(Prefix(v.key), v.value); ok != v.retVal {
|
||||
t.Errorf("Unexpected return value, expected=%v, got=%v", v.retVal, ok)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestTrie_InsertDensePreceeding(t *testing.T) {
|
||||
trie := NewTrie()
|
||||
start := byte(70)
|
||||
// create a dense node
|
||||
for i := byte(0); i <= MaxChildrenPerSparseNode; i++ {
|
||||
if !trie.Insert(Prefix([]byte{start + i}), true) {
|
||||
t.Errorf("insert failed, prefix=%v", start+i)
|
||||
}
|
||||
}
|
||||
// insert some preceeding keys
|
||||
for i := byte(1); i < start; i *= i + 1 {
|
||||
if !trie.Insert(Prefix([]byte{start - i}), true) {
|
||||
t.Errorf("insert failed, prefix=%v", start-i)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestTrie_InsertDenseDuplicatePrefixes(t *testing.T) {
|
||||
trie := NewTrie()
|
||||
|
||||
data := []testData{
|
||||
{"aba", 0, success},
|
||||
{"abb", 1, success},
|
||||
{"abc", 2, success},
|
||||
{"abd", 3, success},
|
||||
{"abe", 4, success},
|
||||
{"abf", 5, success},
|
||||
{"abg", 6, success},
|
||||
{"abh", 7, success},
|
||||
{"abi", 8, success},
|
||||
{"abj", 9, success},
|
||||
{"abk", 0, success},
|
||||
{"abl", 1, success},
|
||||
{"abm", 2, success},
|
||||
{"abn", 3, success},
|
||||
{"abo", 4, success},
|
||||
{"abp", 5, success},
|
||||
{"abq", 6, success},
|
||||
{"abr", 7, success},
|
||||
{"abs", 8, success},
|
||||
{"abt", 9, success},
|
||||
{"abu", 0, success},
|
||||
{"abv", 1, success},
|
||||
{"abw", 2, success},
|
||||
{"abx", 3, success},
|
||||
{"aby", 4, success},
|
||||
{"abz", 5, success},
|
||||
{"aba", 0, failure},
|
||||
{"abb", 1, failure},
|
||||
{"abc", 2, failure},
|
||||
{"abd", 3, failure},
|
||||
{"abe", 4, failure},
|
||||
}
|
||||
|
||||
for _, v := range data {
|
||||
t.Logf("INSERT prefix=%v, item=%v, success=%v", v.key, v.value, v.retVal)
|
||||
if ok := trie.Insert(Prefix(v.key), v.value); ok != v.retVal {
|
||||
t.Errorf("Unexpected return value, expected=%v, got=%v", v.retVal, ok)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestTrie_DeleteDense(t *testing.T) {
|
||||
trie := NewTrie()
|
||||
|
||||
data := []testData{
|
||||
{"aba", 0, success},
|
||||
{"abb", 1, success},
|
||||
{"abc", 2, success},
|
||||
{"abd", 3, success},
|
||||
{"abe", 4, success},
|
||||
{"abf", 5, success},
|
||||
{"abg", 6, success},
|
||||
{"abh", 7, success},
|
||||
{"abi", 8, success},
|
||||
{"abj", 9, success},
|
||||
{"abk", 0, success},
|
||||
{"abl", 1, success},
|
||||
{"abm", 2, success},
|
||||
{"abn", 3, success},
|
||||
{"abo", 4, success},
|
||||
{"abp", 5, success},
|
||||
{"abq", 6, success},
|
||||
{"abr", 7, success},
|
||||
{"abs", 8, success},
|
||||
{"abt", 9, success},
|
||||
{"abu", 0, success},
|
||||
{"abv", 1, success},
|
||||
{"abw", 2, success},
|
||||
{"abx", 3, success},
|
||||
{"aby", 4, success},
|
||||
{"abz", 5, success},
|
||||
}
|
||||
|
||||
for _, v := range data {
|
||||
t.Logf("INSERT prefix=%v, item=%v, success=%v", v.key, v.value, v.retVal)
|
||||
if ok := trie.Insert(Prefix(v.key), v.value); ok != v.retVal {
|
||||
t.Errorf("Unexpected return value, expected=%v, got=%v", v.retVal, ok)
|
||||
}
|
||||
}
|
||||
|
||||
for _, v := range data {
|
||||
t.Logf("DELETE word=%v, success=%v", v.key, v.retVal)
|
||||
if ok := trie.Delete([]byte(v.key)); ok != v.retVal {
|
||||
t.Errorf("Unexpected return value, expected=%v, got=%v", v.retVal, ok)
|
||||
}
|
||||
}
|
||||
}
|
659
vendor/src/github.com/tchap/go-patricia/patricia/patricia_sparse_test.go
vendored
Normal file
659
vendor/src/github.com/tchap/go-patricia/patricia/patricia_sparse_test.go
vendored
Normal file
|
@ -0,0 +1,659 @@
|
|||
// Copyright (c) 2014 The go-patricia AUTHORS
|
||||
//
|
||||
// Use of this source code is governed by The MIT License
|
||||
// that can be found in the LICENSE file.
|
||||
|
||||
package patricia
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
const (
|
||||
success = true
|
||||
failure = false
|
||||
)
|
||||
|
||||
type testData struct {
|
||||
key string
|
||||
value interface{}
|
||||
retVal bool
|
||||
}
|
||||
|
||||
// Tests -----------------------------------------------------------------------
|
||||
|
||||
func TestTrie_InsertDifferentPrefixes(t *testing.T) {
|
||||
trie := NewTrie()
|
||||
|
||||
data := []testData{
|
||||
{"Pepaneeeeeeeeeeeeee", "Pepan Zdepan", success},
|
||||
{"Honzooooooooooooooo", "Honza Novak", success},
|
||||
{"Jenikuuuuuuuuuuuuuu", "Jenik Poustevnicek", success},
|
||||
}
|
||||
|
||||
for _, v := range data {
|
||||
t.Logf("INSERT prefix=%v, item=%v, success=%v", v.key, v.value, v.retVal)
|
||||
if ok := trie.Insert(Prefix(v.key), v.value); ok != v.retVal {
|
||||
t.Errorf("Unexpected return value, expected=%v, got=%v", v.retVal, ok)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestTrie_InsertDuplicatePrefixes(t *testing.T) {
|
||||
trie := NewTrie()
|
||||
|
||||
data := []testData{
|
||||
{"Pepan", "Pepan Zdepan", success},
|
||||
{"Pepan", "Pepan Zdepan", failure},
|
||||
}
|
||||
|
||||
for _, v := range data {
|
||||
t.Logf("INSERT prefix=%v, item=%v, success=%v", v.key, v.value, v.retVal)
|
||||
if ok := trie.Insert(Prefix(v.key), v.value); ok != v.retVal {
|
||||
t.Errorf("Unexpected return value, expected=%v, got=%v", v.retVal, ok)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestTrie_InsertVariousPrefixes(t *testing.T) {
|
||||
trie := NewTrie()
|
||||
|
||||
data := []testData{
|
||||
{"Pepan", "Pepan Zdepan", success},
|
||||
{"Pepin", "Pepin Omacka", success},
|
||||
{"Honza", "Honza Novak", success},
|
||||
{"Jenik", "Jenik Poustevnicek", success},
|
||||
{"Pepan", "Pepan Dupan", failure},
|
||||
{"Karel", "Karel Pekar", success},
|
||||
{"Jenik", "Jenik Poustevnicek", failure},
|
||||
{"Pepanek", "Pepanek Zemlicka", success},
|
||||
}
|
||||
|
||||
for _, v := range data {
|
||||
t.Logf("INSERT prefix=%v, item=%v, success=%v", v.key, v.value, v.retVal)
|
||||
if ok := trie.Insert(Prefix(v.key), v.value); ok != v.retVal {
|
||||
t.Errorf("Unexpected return value, expected=%v, got=%v", v.retVal, ok)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestTrie_InsertAndMatchPrefix(t *testing.T) {
|
||||
trie := NewTrie()
|
||||
t.Log("INSERT prefix=by week")
|
||||
trie.Insert(Prefix("by week"), 2)
|
||||
t.Log("INSERT prefix=by")
|
||||
trie.Insert(Prefix("by"), 1)
|
||||
|
||||
if !trie.Match(Prefix("by")) {
|
||||
t.Error("MATCH prefix=by, expected=true, got=false")
|
||||
}
|
||||
}
|
||||
|
||||
func TestTrie_SetGet(t *testing.T) {
|
||||
trie := NewTrie()
|
||||
|
||||
data := []testData{
|
||||
{"Pepan", "Pepan Zdepan", success},
|
||||
{"Pepin", "Pepin Omacka", success},
|
||||
{"Honza", "Honza Novak", success},
|
||||
{"Jenik", "Jenik Poustevnicek", success},
|
||||
{"Pepan", "Pepan Dupan", failure},
|
||||
{"Karel", "Karel Pekar", success},
|
||||
{"Jenik", "Jenik Poustevnicek", failure},
|
||||
{"Pepanek", "Pepanek Zemlicka", success},
|
||||
}
|
||||
|
||||
for _, v := range data {
|
||||
t.Logf("INSERT prefix=%v, item=%v, success=%v", v.key, v.value, v.retVal)
|
||||
if ok := trie.Insert(Prefix(v.key), v.value); ok != v.retVal {
|
||||
t.Errorf("Unexpected return value, expected=%v, got=%v", v.retVal, ok)
|
||||
}
|
||||
}
|
||||
|
||||
for _, v := range data {
|
||||
t.Logf("SET %q to 10", v.key)
|
||||
trie.Set(Prefix(v.key), 10)
|
||||
}
|
||||
|
||||
for _, v := range data {
|
||||
value := trie.Get(Prefix(v.key))
|
||||
t.Logf("GET %q => %v", v.key, value)
|
||||
if value.(int) != 10 {
|
||||
t.Errorf("Unexpected return value, != 10", value)
|
||||
}
|
||||
}
|
||||
|
||||
if value := trie.Get(Prefix("random crap")); value != nil {
|
||||
t.Errorf("Unexpected return value, %v != <nil>", value)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTrie_Match(t *testing.T) {
|
||||
trie := NewTrie()
|
||||
|
||||
data := []testData{
|
||||
{"Pepan", "Pepan Zdepan", success},
|
||||
{"Pepin", "Pepin Omacka", success},
|
||||
{"Honza", "Honza Novak", success},
|
||||
{"Jenik", "Jenik Poustevnicek", success},
|
||||
{"Pepan", "Pepan Dupan", failure},
|
||||
{"Karel", "Karel Pekar", success},
|
||||
{"Jenik", "Jenik Poustevnicek", failure},
|
||||
{"Pepanek", "Pepanek Zemlicka", success},
|
||||
}
|
||||
|
||||
for _, v := range data {
|
||||
t.Logf("INSERT prefix=%v, item=%v, success=%v", v.key, v.value, v.retVal)
|
||||
if ok := trie.Insert(Prefix(v.key), v.value); ok != v.retVal {
|
||||
t.Errorf("Unexpected return value, expected=%v, got=%v", v.retVal, ok)
|
||||
}
|
||||
}
|
||||
|
||||
for _, v := range data {
|
||||
matched := trie.Match(Prefix(v.key))
|
||||
t.Logf("MATCH %q => %v", v.key, matched)
|
||||
if !matched {
|
||||
t.Errorf("Inserted key %q was not matched", v.key)
|
||||
}
|
||||
}
|
||||
|
||||
if trie.Match(Prefix("random crap")) {
|
||||
t.Errorf("Key that was not inserted matched: %q", "random crap")
|
||||
}
|
||||
}
|
||||
|
||||
func TestTrie_MatchFalsePositive(t *testing.T) {
|
||||
trie := NewTrie()
|
||||
|
||||
if ok := trie.Insert(Prefix("A"), 1); !ok {
|
||||
t.Fatal("INSERT prefix=A, item=1 not ok")
|
||||
}
|
||||
|
||||
resultMatchSubtree := trie.MatchSubtree(Prefix("A extra"))
|
||||
resultMatch := trie.Match(Prefix("A extra"))
|
||||
|
||||
if resultMatchSubtree != false {
|
||||
t.Error("MatchSubtree returned false positive")
|
||||
}
|
||||
|
||||
if resultMatch != false {
|
||||
t.Error("Match returned false positive")
|
||||
}
|
||||
}
|
||||
|
||||
func TestTrie_MatchSubtree(t *testing.T) {
|
||||
trie := NewTrie()
|
||||
|
||||
data := []testData{
|
||||
{"Pepan", "Pepan Zdepan", success},
|
||||
{"Pepin", "Pepin Omacka", success},
|
||||
{"Honza", "Honza Novak", success},
|
||||
{"Jenik", "Jenik Poustevnicek", success},
|
||||
{"Pepan", "Pepan Dupan", failure},
|
||||
{"Karel", "Karel Pekar", success},
|
||||
{"Jenik", "Jenik Poustevnicek", failure},
|
||||
{"Pepanek", "Pepanek Zemlicka", success},
|
||||
}
|
||||
|
||||
for _, v := range data {
|
||||
t.Logf("INSERT prefix=%v, item=%v, success=%v", v.key, v.value, v.retVal)
|
||||
if ok := trie.Insert(Prefix(v.key), v.value); ok != v.retVal {
|
||||
t.Errorf("Unexpected return value, expected=%v, got=%v", v.retVal, ok)
|
||||
}
|
||||
}
|
||||
|
||||
for _, v := range data {
|
||||
key := Prefix(v.key[:3])
|
||||
matched := trie.MatchSubtree(key)
|
||||
t.Logf("MATCH_SUBTREE %q => %v", key, matched)
|
||||
if !matched {
|
||||
t.Errorf("Subtree %q was not matched", v.key)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestTrie_Visit(t *testing.T) {
|
||||
trie := NewTrie()
|
||||
|
||||
data := []testData{
|
||||
{"Pepa", 0, success},
|
||||
{"Pepa Zdepa", 1, success},
|
||||
{"Pepa Kuchar", 2, success},
|
||||
{"Honza", 3, success},
|
||||
{"Jenik", 4, success},
|
||||
}
|
||||
|
||||
for _, v := range data {
|
||||
t.Logf("INSERT prefix=%v, item=%v, success=%v", v.key, v.value, v.retVal)
|
||||
if ok := trie.Insert([]byte(v.key), v.value); ok != v.retVal {
|
||||
t.Fatalf("Unexpected return value, expected=%v, got=%v", v.retVal, ok)
|
||||
}
|
||||
}
|
||||
|
||||
if err := trie.Visit(func(prefix Prefix, item Item) error {
|
||||
name := data[item.(int)].key
|
||||
t.Logf("VISITING prefix=%q, item=%v", prefix, item)
|
||||
if !strings.HasPrefix(string(prefix), name) {
|
||||
t.Errorf("Unexpected prefix encountered, %q not a prefix of %q", prefix, name)
|
||||
}
|
||||
return nil
|
||||
}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTrie_VisitSkipSubtree(t *testing.T) {
|
||||
trie := NewTrie()
|
||||
|
||||
data := []testData{
|
||||
{"Pepa", 0, success},
|
||||
{"Pepa Zdepa", 1, success},
|
||||
{"Pepa Kuchar", 2, success},
|
||||
{"Honza", 3, success},
|
||||
{"Jenik", 4, success},
|
||||
}
|
||||
|
||||
for _, v := range data {
|
||||
t.Logf("INSERT prefix=%v, item=%v, success=%v", v.key, v.value, v.retVal)
|
||||
if ok := trie.Insert([]byte(v.key), v.value); ok != v.retVal {
|
||||
t.Fatalf("Unexpected return value, expected=%v, got=%v", v.retVal, ok)
|
||||
}
|
||||
}
|
||||
|
||||
if err := trie.Visit(func(prefix Prefix, item Item) error {
|
||||
t.Logf("VISITING prefix=%q, item=%v", prefix, item)
|
||||
if item.(int) == 0 {
|
||||
t.Logf("SKIP %q", prefix)
|
||||
return SkipSubtree
|
||||
}
|
||||
if strings.HasPrefix(string(prefix), "Pepa") {
|
||||
t.Errorf("Unexpected prefix encountered, %q", prefix)
|
||||
}
|
||||
return nil
|
||||
}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTrie_VisitReturnError(t *testing.T) {
|
||||
trie := NewTrie()
|
||||
|
||||
data := []testData{
|
||||
{"Pepa", 0, success},
|
||||
{"Pepa Zdepa", 1, success},
|
||||
{"Pepa Kuchar", 2, success},
|
||||
{"Honza", 3, success},
|
||||
{"Jenik", 4, success},
|
||||
}
|
||||
|
||||
for _, v := range data {
|
||||
t.Logf("INSERT prefix=%v, item=%v, success=%v", v.key, v.value, v.retVal)
|
||||
if ok := trie.Insert([]byte(v.key), v.value); ok != v.retVal {
|
||||
t.Fatalf("Unexpected return value, expected=%v, got=%v", v.retVal, ok)
|
||||
}
|
||||
}
|
||||
|
||||
someErr := errors.New("Something exploded")
|
||||
if err := trie.Visit(func(prefix Prefix, item Item) error {
|
||||
t.Logf("VISITING prefix=%q, item=%v", prefix, item)
|
||||
if item.(int) == 0 {
|
||||
return someErr
|
||||
}
|
||||
if item.(int) != 0 {
|
||||
t.Errorf("Unexpected prefix encountered, %q", prefix)
|
||||
}
|
||||
return nil
|
||||
}); err != nil && err != someErr {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTrie_VisitSubtree(t *testing.T) {
|
||||
trie := NewTrie()
|
||||
|
||||
data := []testData{
|
||||
{"Pepa", 0, success},
|
||||
{"Pepa Zdepa", 1, success},
|
||||
{"Pepa Kuchar", 2, success},
|
||||
{"Honza", 3, success},
|
||||
{"Jenik", 4, success},
|
||||
}
|
||||
|
||||
for _, v := range data {
|
||||
t.Logf("INSERT prefix=%v, item=%v, success=%v", v.key, v.value, v.retVal)
|
||||
if ok := trie.Insert([]byte(v.key), v.value); ok != v.retVal {
|
||||
t.Fatalf("Unexpected return value, expected=%v, got=%v", v.retVal, ok)
|
||||
}
|
||||
}
|
||||
|
||||
var counter int
|
||||
subtreePrefix := []byte("Pep")
|
||||
t.Log("VISIT Pep")
|
||||
if err := trie.VisitSubtree(subtreePrefix, func(prefix Prefix, item Item) error {
|
||||
t.Logf("VISITING prefix=%q, item=%v", prefix, item)
|
||||
if !bytes.HasPrefix(prefix, subtreePrefix) {
|
||||
t.Errorf("Unexpected prefix encountered, %q does not extend %q",
|
||||
prefix, subtreePrefix)
|
||||
}
|
||||
if len(prefix) > len(data[item.(int)].key) {
|
||||
t.Fatalf("Something is rather fishy here, prefix=%q", prefix)
|
||||
}
|
||||
counter++
|
||||
return nil
|
||||
}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if counter != 3 {
|
||||
t.Error("Unexpected number of nodes visited")
|
||||
}
|
||||
}
|
||||
|
||||
func TestTrie_VisitPrefixes(t *testing.T) {
|
||||
trie := NewTrie()
|
||||
|
||||
data := []testData{
|
||||
{"P", 0, success},
|
||||
{"Pe", 1, success},
|
||||
{"Pep", 2, success},
|
||||
{"Pepa", 3, success},
|
||||
{"Pepa Zdepa", 4, success},
|
||||
{"Pepa Kuchar", 5, success},
|
||||
{"Honza", 6, success},
|
||||
{"Jenik", 7, success},
|
||||
}
|
||||
|
||||
for _, v := range data {
|
||||
t.Logf("INSERT prefix=%v, item=%v, success=%v", v.key, v.value, v.retVal)
|
||||
if ok := trie.Insert([]byte(v.key), v.value); ok != v.retVal {
|
||||
t.Fatalf("Unexpected return value, expected=%v, got=%v", v.retVal, ok)
|
||||
}
|
||||
}
|
||||
|
||||
var counter int
|
||||
word := []byte("Pepa")
|
||||
if err := trie.VisitPrefixes(word, func(prefix Prefix, item Item) error {
|
||||
t.Logf("VISITING prefix=%q, item=%v", prefix, item)
|
||||
if !bytes.HasPrefix(word, prefix) {
|
||||
t.Errorf("Unexpected prefix encountered, %q is not a prefix of %q",
|
||||
prefix, word)
|
||||
}
|
||||
counter++
|
||||
return nil
|
||||
}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if counter != 4 {
|
||||
t.Error("Unexpected number of nodes visited")
|
||||
}
|
||||
}
|
||||
|
||||
func TestParticiaTrie_Delete(t *testing.T) {
|
||||
trie := NewTrie()
|
||||
|
||||
data := []testData{
|
||||
{"Pepan", "Pepan Zdepan", success},
|
||||
{"Honza", "Honza Novak", success},
|
||||
{"Jenik", "Jenik Poustevnicek", success},
|
||||
}
|
||||
|
||||
for _, v := range data {
|
||||
t.Logf("INSERT prefix=%v, item=%v, success=%v", v.key, v.value, v.retVal)
|
||||
if ok := trie.Insert([]byte(v.key), v.value); ok != v.retVal {
|
||||
t.Fatalf("Unexpected return value, expected=%v, got=%v", v.retVal, ok)
|
||||
}
|
||||
}
|
||||
|
||||
for _, v := range data {
|
||||
t.Logf("DELETE word=%v, success=%v", v.key, v.retVal)
|
||||
if ok := trie.Delete([]byte(v.key)); ok != v.retVal {
|
||||
t.Errorf("Unexpected return value, expected=%v, got=%v", v.retVal, ok)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestParticiaTrie_DeleteNonExistent(t *testing.T) {
|
||||
trie := NewTrie()
|
||||
|
||||
insertData := []testData{
|
||||
{"Pepan", "Pepan Zdepan", success},
|
||||
{"Honza", "Honza Novak", success},
|
||||
{"Jenik", "Jenik Poustevnicek", success},
|
||||
}
|
||||
deleteData := []testData{
|
||||
{"Pepan", "Pepan Zdepan", success},
|
||||
{"Honza", "Honza Novak", success},
|
||||
{"Pepan", "Pepan Zdepan", failure},
|
||||
{"Jenik", "Jenik Poustevnicek", success},
|
||||
{"Honza", "Honza Novak", failure},
|
||||
}
|
||||
|
||||
for _, v := range insertData {
|
||||
t.Logf("INSERT prefix=%v, item=%v, success=%v", v.key, v.value, v.retVal)
|
||||
if ok := trie.Insert([]byte(v.key), v.value); ok != v.retVal {
|
||||
t.Fatalf("Unexpected return value, expected=%v, got=%v", v.retVal, ok)
|
||||
}
|
||||
}
|
||||
|
||||
for _, v := range deleteData {
|
||||
t.Logf("DELETE word=%v, success=%v", v.key, v.retVal)
|
||||
if ok := trie.Delete([]byte(v.key)); ok != v.retVal {
|
||||
t.Errorf("Unexpected return value, expected=%v, got=%v", v.retVal, ok)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestParticiaTrie_DeleteSubtree(t *testing.T) {
|
||||
trie := NewTrie()
|
||||
|
||||
insertData := []testData{
|
||||
{"P", 0, success},
|
||||
{"Pe", 1, success},
|
||||
{"Pep", 2, success},
|
||||
{"Pepa", 3, success},
|
||||
{"Pepa Zdepa", 4, success},
|
||||
{"Pepa Kuchar", 5, success},
|
||||
{"Honza", 6, success},
|
||||
{"Jenik", 7, success},
|
||||
}
|
||||
deleteData := []testData{
|
||||
{"Pe", -1, success},
|
||||
{"Pe", -1, failure},
|
||||
{"Honzik", -1, failure},
|
||||
{"Honza", -1, success},
|
||||
{"Honza", -1, failure},
|
||||
{"Pep", -1, failure},
|
||||
{"P", -1, success},
|
||||
{"Nobody", -1, failure},
|
||||
{"", -1, success},
|
||||
}
|
||||
|
||||
for _, v := range insertData {
|
||||
t.Logf("INSERT prefix=%v, item=%v, success=%v", v.key, v.value, v.retVal)
|
||||
if ok := trie.Insert([]byte(v.key), v.value); ok != v.retVal {
|
||||
t.Fatalf("Unexpected return value, expected=%v, got=%v", v.retVal, ok)
|
||||
}
|
||||
}
|
||||
|
||||
for _, v := range deleteData {
|
||||
t.Logf("DELETE_SUBTREE prefix=%v, success=%v", v.key, v.retVal)
|
||||
if ok := trie.DeleteSubtree([]byte(v.key)); ok != v.retVal {
|
||||
t.Errorf("Unexpected return value, expected=%v, got=%v", v.retVal, ok)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
func TestTrie_Dump(t *testing.T) {
|
||||
trie := NewTrie()
|
||||
|
||||
data := []testData{
|
||||
{"Honda", nil, success},
|
||||
{"Honza", nil, success},
|
||||
{"Jenik", nil, success},
|
||||
{"Pepan", nil, success},
|
||||
{"Pepin", nil, success},
|
||||
}
|
||||
|
||||
for i, v := range data {
|
||||
if _, ok := trie.Insert([]byte(v.key), v.value); ok != v.retVal {
|
||||
t.Logf("INSERT %v %v", v.key, v.value)
|
||||
t.Fatalf("Unexpected return value, expected=%v, got=%v", i, ok)
|
||||
}
|
||||
}
|
||||
|
||||
dump := `
|
||||
+--+--+ Hon +--+--+ da
|
||||
| |
|
||||
| +--+ za
|
||||
|
|
||||
+--+ Jenik
|
||||
|
|
||||
+--+ Pep +--+--+ an
|
||||
|
|
||||
+--+ in
|
||||
`
|
||||
|
||||
var buf bytes.Buffer
|
||||
trie.Dump(buf)
|
||||
|
||||
if !bytes.Equal(buf.Bytes(), dump) {
|
||||
t.Logf("DUMP")
|
||||
t.Fatalf("Unexpected dump generated, expected\n\n%v\ngot\n\n%v", dump, buf.String())
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
func TestTrie_compact(t *testing.T) {
|
||||
trie := NewTrie()
|
||||
|
||||
trie.Insert(Prefix("a"), 0)
|
||||
trie.Insert(Prefix("ab"), 0)
|
||||
trie.Insert(Prefix("abc"), 0)
|
||||
trie.Insert(Prefix("abcd"), 0)
|
||||
trie.Insert(Prefix("abcde"), 0)
|
||||
trie.Insert(Prefix("abcdef"), 0)
|
||||
trie.Insert(Prefix("abcdefg"), 0)
|
||||
trie.Insert(Prefix("abcdefgi"), 0)
|
||||
trie.Insert(Prefix("abcdefgij"), 0)
|
||||
trie.Insert(Prefix("abcdefgijk"), 0)
|
||||
|
||||
trie.Delete(Prefix("abcdef"))
|
||||
trie.Delete(Prefix("abcde"))
|
||||
trie.Delete(Prefix("abcdefg"))
|
||||
|
||||
trie.Delete(Prefix("a"))
|
||||
trie.Delete(Prefix("abc"))
|
||||
trie.Delete(Prefix("ab"))
|
||||
|
||||
trie.Visit(func(prefix Prefix, item Item) error {
|
||||
// 97 ~~ 'a',
|
||||
for ch := byte(97); ch <= 107; ch++ {
|
||||
if c := bytes.Count(prefix, []byte{ch}); c > 1 {
|
||||
t.Errorf("%q appeared in %q %v times", ch, prefix, c)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func TestTrie_longestCommonPrefixLenght(t *testing.T) {
|
||||
trie := NewTrie()
|
||||
trie.prefix = []byte("1234567890")
|
||||
|
||||
switch {
|
||||
case trie.longestCommonPrefixLength([]byte("")) != 0:
|
||||
t.Fail()
|
||||
case trie.longestCommonPrefixLength([]byte("12345")) != 5:
|
||||
t.Fail()
|
||||
case trie.longestCommonPrefixLength([]byte("123789")) != 3:
|
||||
t.Fail()
|
||||
case trie.longestCommonPrefixLength([]byte("12345678901")) != 10:
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
// Examples --------------------------------------------------------------------
|
||||
|
||||
func ExampleTrie() {
|
||||
// Create a new tree.
|
||||
trie := NewTrie()
|
||||
|
||||
// Insert some items.
|
||||
trie.Insert(Prefix("Pepa Novak"), 1)
|
||||
trie.Insert(Prefix("Pepa Sindelar"), 2)
|
||||
trie.Insert(Prefix("Karel Macha"), 3)
|
||||
trie.Insert(Prefix("Karel Hynek Macha"), 4)
|
||||
|
||||
// Just check if some things are present in the tree.
|
||||
key := Prefix("Pepa Novak")
|
||||
fmt.Printf("%q present? %v\n", key, trie.Match(key))
|
||||
key = Prefix("Karel")
|
||||
fmt.Printf("Anybody called %q here? %v\n", key, trie.MatchSubtree(key))
|
||||
|
||||
// Walk the tree.
|
||||
trie.Visit(printItem)
|
||||
// "Pepa Novak": 1
|
||||
// "Pepa Sindelar": 2
|
||||
// "Karel Macha": 3
|
||||
// "Karel Hynek Macha": 4
|
||||
|
||||
// Walk a subtree.
|
||||
trie.VisitSubtree(Prefix("Pepa"), printItem)
|
||||
// "Pepa Novak": 1
|
||||
// "Pepa Sindelar": 2
|
||||
|
||||
// Modify an item, then fetch it from the tree.
|
||||
trie.Set(Prefix("Karel Hynek Macha"), 10)
|
||||
key = Prefix("Karel Hynek Macha")
|
||||
fmt.Printf("%q: %v\n", key, trie.Get(key))
|
||||
// "Karel Hynek Macha": 10
|
||||
|
||||
// Walk prefixes.
|
||||
prefix := Prefix("Karel Hynek Macha je kouzelnik")
|
||||
trie.VisitPrefixes(prefix, printItem)
|
||||
// "Karel Hynek Macha": 10
|
||||
|
||||
// Delete some items.
|
||||
trie.Delete(Prefix("Pepa Novak"))
|
||||
trie.Delete(Prefix("Karel Macha"))
|
||||
|
||||
// Walk again.
|
||||
trie.Visit(printItem)
|
||||
// "Pepa Sindelar": 2
|
||||
// "Karel Hynek Macha": 10
|
||||
|
||||
// Delete a subtree.
|
||||
trie.DeleteSubtree(Prefix("Pepa"))
|
||||
|
||||
// Print what is left.
|
||||
trie.Visit(printItem)
|
||||
// "Karel Hynek Macha": 10
|
||||
|
||||
// Output:
|
||||
// "Pepa Novak" present? true
|
||||
// Anybody called "Karel" here? true
|
||||
// "Pepa Novak": 1
|
||||
// "Pepa Sindelar": 2
|
||||
// "Karel Macha": 3
|
||||
// "Karel Hynek Macha": 4
|
||||
// "Pepa Novak": 1
|
||||
// "Pepa Sindelar": 2
|
||||
// "Karel Hynek Macha": 10
|
||||
// "Karel Hynek Macha": 10
|
||||
// "Pepa Sindelar": 2
|
||||
// "Karel Hynek Macha": 10
|
||||
// "Karel Hynek Macha": 10
|
||||
}
|
||||
|
||||
// Helpers ---------------------------------------------------------------------
|
||||
|
||||
func printItem(prefix Prefix, item Item) error {
|
||||
fmt.Printf("%q: %v\n", prefix, item)
|
||||
return nil
|
||||
}
|
78
vendor/src/github.com/tchap/go-patricia/patricia/patricia_test.go
vendored
Normal file
78
vendor/src/github.com/tchap/go-patricia/patricia/patricia_test.go
vendored
Normal file
|
@ -0,0 +1,78 @@
|
|||
// Copyright (c) 2014 The go-patricia AUTHORS
|
||||
//
|
||||
// Use of this source code is governed by The MIT License
|
||||
// that can be found in the LICENSE file.
|
||||
|
||||
package patricia
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// Tests -----------------------------------------------------------------------
|
||||
|
||||
func TestTrie_GetNonexistentPrefix(t *testing.T) {
|
||||
trie := NewTrie()
|
||||
|
||||
data := []testData{
|
||||
{"aba", 0, success},
|
||||
}
|
||||
|
||||
for _, v := range data {
|
||||
t.Logf("INSERT prefix=%v, item=%v, success=%v", v.key, v.value, v.retVal)
|
||||
if ok := trie.Insert(Prefix(v.key), v.value); ok != v.retVal {
|
||||
t.Errorf("Unexpected return value, expected=%v, got=%v", v.retVal, ok)
|
||||
}
|
||||
}
|
||||
|
||||
t.Logf("GET prefix=baa, expect item=nil")
|
||||
if item := trie.Get(Prefix("baa")); item != nil {
|
||||
t.Errorf("Unexpected return value, expected=<nil>, got=%v", item)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTrie_RandomKitchenSink(t *testing.T) {
|
||||
if testing.Short() {
|
||||
t.Skip()
|
||||
}
|
||||
const count, size = 750000, 16
|
||||
b := make([]byte, count+size+1)
|
||||
if _, err := rand.Read(b); err != nil {
|
||||
t.Fatal("error generating random bytes", err)
|
||||
}
|
||||
m := make(map[string]string)
|
||||
for i := 0; i < count; i++ {
|
||||
m[string(b[i:i+size])] = string(b[i+1 : i+size+1])
|
||||
}
|
||||
trie := NewTrie()
|
||||
getAndDelete := func(k, v string) {
|
||||
i := trie.Get(Prefix(k))
|
||||
if i == nil {
|
||||
t.Fatalf("item not found, prefix=%v", []byte(k))
|
||||
} else if s, ok := i.(string); !ok {
|
||||
t.Fatalf("unexpected item type, expecting=%v, got=%v", reflect.TypeOf(k), reflect.TypeOf(i))
|
||||
} else if s != v {
|
||||
t.Fatalf("unexpected item, expecting=%v, got=%v", []byte(k), []byte(s))
|
||||
} else if !trie.Delete(Prefix(k)) {
|
||||
t.Fatalf("delete failed, prefix=%v", []byte(k))
|
||||
} else if i = trie.Get(Prefix(k)); i != nil {
|
||||
t.Fatalf("unexpected item, expecting=<nil>, got=%v", i)
|
||||
} else if trie.Delete(Prefix(k)) {
|
||||
t.Fatalf("extra delete succeeded, prefix=%v", []byte(k))
|
||||
}
|
||||
}
|
||||
for k, v := range m {
|
||||
if !trie.Insert(Prefix(k), v) {
|
||||
t.Fatalf("insert failed, prefix=%v", []byte(k))
|
||||
}
|
||||
if byte(k[size/2]) < 128 {
|
||||
getAndDelete(k, v)
|
||||
delete(m, k)
|
||||
}
|
||||
}
|
||||
for k, v := range m {
|
||||
getAndDelete(k, v)
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue