Skip to content

Commit

Permalink
Revert conversion to generics
Browse files Browse the repository at this point in the history
The generic version of the release is now in the v2 branch.

Use github.com/beevik/prefixtree/v2 to obtain the generic version
of prefixtree.
  • Loading branch information
beevik committed Jul 6, 2024
1 parent 72906da commit 3cbbfc7
Show file tree
Hide file tree
Showing 4 changed files with 66 additions and 69 deletions.
16 changes: 8 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ The following code adds strings and associated data (in this case an integer)
to a prefix tree.

```go
tree := prefixtree.New[int]()
tree := prefixtree.New()

tree.Add("apple", 10)
tree.Add("orange", 20)
Expand Down Expand Up @@ -61,20 +61,20 @@ Output:
```
prefix value error
------ ----- -----
a 0 prefixtree: prefix ambiguous
appl 0 prefixtree: prefix ambiguous
a <nil> prefixtree: prefix ambiguous
appl <nil> prefixtree: prefix ambiguous
apple 10 <nil>
apple p 30 <nil>
apple pie 30 <nil>
apple pies 0 prefixtree: prefix not found
apple pies <nil> prefixtree: prefix not found
o 20 <nil>
orang 20 <nil>
orange 20 <nil>
oranges 0 prefixtree: prefix not found
l 0 prefixtree: prefix ambiguous
lemo 0 prefixtree: prefix ambiguous
oranges <nil> prefixtree: prefix not found
l <nil> prefixtree: prefix ambiguous
lemo <nil> prefixtree: prefix ambiguous
lemon 40 <nil>
lemon m 50 <nil>
lemon meringue 50 <nil>
pear 0 prefixtree: prefix not found
pear <nil> prefixtree: prefix not found
```
14 changes: 7 additions & 7 deletions example_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import (
func ExampleTree_usage() {
// Create the tree. Add 5 strings, each with an associated
// integer.
tree := prefixtree.New[int]()
tree := prefixtree.New()
for i, s := range []string{
"apple",
"orange",
Expand Down Expand Up @@ -53,19 +53,19 @@ func ExampleTree_usage() {
// Output:
// prefix value error
// ------ ----- -----
// a 0 prefixtree: prefix ambiguous
// appl 0 prefixtree: prefix ambiguous
// a <nil> prefixtree: prefix ambiguous
// appl <nil> prefixtree: prefix ambiguous
// apple 0 <nil>
// apple p 2 <nil>
// apple pie 2 <nil>
// apple pies 0 prefixtree: prefix not found
// apple pies <nil> prefixtree: prefix not found
// o 1 <nil>
// orang 1 <nil>
// orange 1 <nil>
// oranges 0 prefixtree: prefix not found
// lemo 0 prefixtree: prefix ambiguous
// oranges <nil> prefixtree: prefix not found
// lemo <nil> prefixtree: prefix ambiguous
// lemon 4 <nil>
// lemon m 3 <nil>
// lemon meringue 3 <nil>
// lemon meringues 0 prefixtree: prefix not found
// lemon meringues <nil> prefixtree: prefix not found
}
83 changes: 40 additions & 43 deletions prefixtree.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,44 +25,43 @@ var (
ErrPrefixAmbiguous = errors.New("prefixtree: prefix ambiguous")
)

// A KeyValue type encapsulates a key string and its associated value of type
// V.
type KeyValue[V any] struct {
// A KeyValue type encapsulates a key string and its associated value.
type KeyValue struct {
Key string
Value V
Value any
}

// A Tree represents a prefix tree containing strings and their associated
// value data of type V. The tree is implemented as a trie and can be searched
// value data. The tree is implemented as a trie and can be searched
// efficiently for unique prefix matches.
type Tree[V any] struct {
type Tree struct {
key string
value V
links []link[V]
value any
links []link
descendants int
}

type link[V any] struct {
type link struct {
keyseg string
tree *Tree[V]
tree *Tree
}

// New returns an empty prefix tree with a value type of V.
func New[V any]() *Tree[V] {
return new(Tree[V])
// New returns an empty prefix tree.
func New() *Tree {
return new(Tree)
}

// isTerminal returns true if the tree is a terminal subtree in the
// prefix tree.
func (t *Tree[V]) isTerminal() bool {
func (t *Tree) isTerminal() bool {
return t.key != ""
}

// FindKey searches the prefix tree for a key string that uniquely matches the
// prefix. If found, the full matching key is returned. If not found,
// ErrPrefixNotFound is returned. If the prefix matches more than one key in
// the tree, ErrPrefixAmbiguous is returned.
func (t *Tree[V]) FindKey(prefix string) (key string, err error) {
func (t *Tree) FindKey(prefix string) (key string, err error) {
st, err := t.findSubtree(prefix)
if err != nil {
return "", err
Expand All @@ -75,17 +74,17 @@ func (t *Tree[V]) FindKey(prefix string) (key string, err error) {
// value is returned. If not found, ErrPrefixNotFound is returned. If the
// prefix matches more than one key in the tree, ErrPrefixAmbiguous is
// returned.
func (t *Tree[V]) FindKeyValue(prefix string) (kv KeyValue[V], err error) {
func (t *Tree) FindKeyValue(prefix string) (kv KeyValue, err error) {
st, err := t.findSubtree(prefix)
if err != nil {
return KeyValue[V]{}, err
return KeyValue{}, err
}
return KeyValue[V]{st.key, st.value}, nil
return KeyValue{st.key, st.value}, nil
}

// FindKeys searches the prefix tree for all key strings prefixed by the
// provided prefix and returns them.
func (t *Tree[V]) FindKeys(prefix string) (keys []string) {
func (t *Tree) FindKeys(prefix string) (keys []string) {
st, err := t.findSubtree(prefix)
if err == ErrPrefixNotFound {
return []string{}
Expand All @@ -100,44 +99,43 @@ func (t *Tree[V]) FindKeys(prefix string) (keys []string) {
// the prefix. If found, the value associated with the key is returned. If not
// found, ErrPrefixNotFound is returned. If the prefix matches more than one
// key in the tree, ErrPrefixAmbiguous is returned.
func (t *Tree[V]) FindValue(prefix string) (value V, err error) {
func (t *Tree) FindValue(prefix string) (value any, err error) {
st, err := t.findSubtree(prefix)
if err != nil {
var empty V
return empty, err
return nil, err
}
return st.value, nil
}

// FindKeyValues searches the prefix tree for all key strings prefixed by the
// provided prefix. All discovered keys and their values are returned.
func (t *Tree[V]) FindKeyValues(prefix string) (values []KeyValue[V]) {
func (t *Tree) FindKeyValues(prefix string) (values []KeyValue) {
st, err := t.findSubtree(prefix)
if err == ErrPrefixNotFound {
return []KeyValue[V]{}
return []KeyValue{}
}
if st.isTerminal() && err != ErrPrefixAmbiguous {
return []KeyValue[V]{{st.key, st.value}}
return []KeyValue{{st.key, st.value}}
}
return appendDescendantKeyValues(st, nil)
}

// FindValues searches the prefix tree for all key strings prefixed by the
// provided prefix. All associated values are returned.
func (t *Tree[V]) FindValues(prefix string) (values []V) {
func (t *Tree) FindValues(prefix string) (values []any) {
st, err := t.findSubtree(prefix)
if err == ErrPrefixNotFound {
return []V{}
return []any{}
}
if st.isTerminal() && err != ErrPrefixAmbiguous {
return []V{st.value}
return []any{st.value}
}
return appendDescendantValues(st, nil)
}

// findSubtree searches the prefix tree for the deepest subtree matching
// the prefix.
func (t *Tree[V]) findSubtree(prefix string) (*Tree[V], error) {
func (t *Tree) findSubtree(prefix string) (*Tree, error) {
outerLoop:
for {
// Ran out of prefix?
Expand Down Expand Up @@ -203,7 +201,7 @@ func matchingChars(s1, s2 string) int {

// appendDescendantKeys recursively appends a tree's descendant keys
// to an array of keys.
func appendDescendantKeys[V any](t *Tree[V], keys []string) []string {
func appendDescendantKeys(t *Tree, keys []string) []string {
if t.isTerminal() {
keys = append(keys, t.key)
}
Expand All @@ -215,9 +213,9 @@ func appendDescendantKeys[V any](t *Tree[V], keys []string) []string {

// appendDescendantKeyValues recursively appends a tree's descendant keys
// to an array of key/value pairs.
func appendDescendantKeyValues[V any](t *Tree[V], kv []KeyValue[V]) []KeyValue[V] {
func appendDescendantKeyValues(t *Tree, kv []KeyValue) []KeyValue {
if t.isTerminal() {
kv = append(kv, KeyValue[V]{t.key, t.value})
kv = append(kv, KeyValue{t.key, t.value})
}
for i := 0; i < len(t.links); i++ {
kv = appendDescendantKeyValues(t.links[i].tree, kv)
Expand All @@ -227,7 +225,7 @@ func appendDescendantKeyValues[V any](t *Tree[V], kv []KeyValue[V]) []KeyValue[V

// appendDescendantValues recursively appends a tree's descendant values
// to an array of values.
func appendDescendantValues[V any](t *Tree[V], values []V) []V {
func appendDescendantValues(t *Tree, values []any) []any {
if t.isTerminal() {
values = append(values, t.value)
}
Expand All @@ -238,7 +236,7 @@ func appendDescendantValues[V any](t *Tree[V], values []V) []V {
}

// Add a key string and its associated value data to the prefix tree.
func (t *Tree[V]) Add(key string, value V) {
func (t *Tree) Add(key string, value any) {
k := key
outerLoop:
for {
Expand All @@ -257,7 +255,7 @@ outerLoop:

// Check the links before and after the insertion point for a matching
// prefix to see if we need to split one of them.
var splitLink *link[V]
var splitLink *link
var splitIndex int
innerLoop:
for li, lm := max(ix-1, 0), min(ix, len(t.links)-1); li <= lm; li++ {
Expand All @@ -277,25 +275,24 @@ outerLoop:

// No split necessary, so insert a new link and subtree.
if splitLink == nil {
child := &Tree[V]{
child := &Tree{
key: key,
value: value,
links: nil,
descendants: 1,
}
t.links = append(t.links[:ix],
append([]link[V]{{k, child}}, t.links[ix:]...)...)
append([]link{{k, child}}, t.links[ix:]...)...)
break outerLoop
}

// A split is necessary, so split the current link's string and insert
// a child tree.
k1, k2 := splitLink.keyseg[:splitIndex], splitLink.keyseg[splitIndex:]
var empty V
child := &Tree[V]{
child := &Tree{
key: "",
value: empty,
links: []link[V]{{k2, splitLink.tree}},
value: nil,
links: []link{{k2, splitLink.tree}},
descendants: splitLink.tree.descendants,
}
splitLink.keyseg, splitLink.tree = k1, child
Expand All @@ -305,11 +302,11 @@ outerLoop:

// Output the structure of the tree to stdout. This function exists for
// debugging purposes.
func (t *Tree[V]) Output() {
func (t *Tree) Output() {
t.outputNode(0)
}

func (t *Tree[V]) outputNode(level int) {
func (t *Tree) outputNode(level int) {
fmt.Printf("%sNode: key=\"%s\" term=%v desc=%d value=%v\n",
strings.Repeat(" ", level), t.key, t.isTerminal(), t.descendants, t.value)
for i, l := range t.links {
Expand Down
22 changes: 11 additions & 11 deletions prefixtree_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,21 +13,21 @@ import (

type entry struct {
key string
value int
value any
}

type testcase struct {
key string
value int
value any
err error
}

func test(t *testing.T, entries []entry, cases []testcase) *Tree[int] {
func test(t *testing.T, entries []entry, cases []testcase) *Tree {
// Run 256 iterations of build/find using random tree entry
// insertion orders.
var tree *Tree[int]
var tree *Tree
for i := 0; i < 256; i++ {
tree = New[int]()
tree = New()
for _, i := range rand.Perm(len(entries)) {
tree.Add(entries[i].key, entries[i].value)
}
Expand Down Expand Up @@ -189,7 +189,7 @@ func TestFindKeys(t *testing.T) {
{"bog", 6},
}

tree := New[int]()
tree := New()
for _, entry := range entries {
tree.Add(entry.key, entry.value)
}
Expand Down Expand Up @@ -250,7 +250,7 @@ func TestFindValues(t *testing.T) {
{"bee", 5},
}

tree := New[int]()
tree := New()
for _, entry := range entries {
tree.Add(entry.key, entry.value)
}
Expand Down Expand Up @@ -332,10 +332,10 @@ func TestDictionary(t *testing.T) {
}

// Scan all words from the dictionary into the tree.
tree := New[bool]()
tree := New()
scanner := bufio.NewScanner(file)
for scanner.Scan() {
tree.Add(scanner.Text(), true)
tree.Add(scanner.Text(), nil)
}
file.Close()

Expand Down Expand Up @@ -382,10 +382,10 @@ func BenchmarkDictionary(b *testing.B) {
}

// Scan all words from the dictionary into the tree.
tree := New[bool]()
tree := New()
scanner := bufio.NewScanner(file)
for scanner.Scan() {
tree.Add(scanner.Text(), true)
tree.Add(scanner.Text(), nil)
}
file.Close()

Expand Down

0 comments on commit 3cbbfc7

Please sign in to comment.