Skip to content

Commit

Permalink
New DeletePrefix function (#23)
Browse files Browse the repository at this point in the history
* New DeletePrefix function

The DeletePrefix removes all values whose key is prefixed by the given prefix. This prunes an entire subtree and is therefore more efficient than deleting individual items.

* Change `interface{}` to `any`
* Remove depricated `Bytes` type
* test with go1.20
  • Loading branch information
gammazero authored Feb 24, 2023
1 parent a61ace0 commit dd01f7f
Show file tree
Hide file tree
Showing 9 changed files with 146 additions and 52 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/go.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ jobs:
- name: Set up Go
uses: actions/setup-go@v2
with:
go-version: 1.16
go-version: '1.20'

- name: Vet
run: go vet ./...
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ func main() {
// Output: Found TOM

// Find all items whose keys start with "tom"
rt.Walk("tom", func(key string, value interface{}) bool {
rt.Walk("tom", func(key string, value any) bool {
fmt.Println(value)
return false
})
Expand All @@ -60,7 +60,7 @@ func main() {
// TOMMY

// Find all items whose keys are a prefix of "tomato"
rt.WalkPath("tomato", func(key string, value interface{}) bool {
rt.WalkPath("tomato", func(key string, value any) bool {
fmt.Println(value)
return false
})
Expand Down
6 changes: 3 additions & 3 deletions bench_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ func benchmarkPut(b *testing.B, filePath string) {
b.ResetTimer()
b.ReportAllocs()
for n := 0; n < b.N; n++ {
tree := new(Bytes)
tree := new(Tree)
for _, w := range words {
tree.Put(w, w)
}
Expand All @@ -113,7 +113,7 @@ func benchmarkWalk(b *testing.B, filePath string) {
var count int
for n := 0; n < b.N; n++ {
count = 0
tree.Walk("", func(k string, value interface{}) bool {
tree.Walk("", func(k string, value any) bool {
count++
return false
})
Expand All @@ -137,7 +137,7 @@ func benchmarkWalkPath(b *testing.B, filePath string) {
for n := 0; n < b.N; n++ {
found := false
for _, w := range words {
tree.WalkPath(w, func(key string, value interface{}) bool {
tree.WalkPath(w, func(key string, value any) bool {
found = true
return false
})
Expand Down
4 changes: 2 additions & 2 deletions doc_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ func ExampleTree_Walk() {
rt.Put("tornado", "TORNADO")

// Find all items whose keys start with "tom"
rt.Walk("tom", func(key string, value interface{}) bool {
rt.Walk("tom", func(key string, value any) bool {
fmt.Println(value)
return false
})
Expand All @@ -28,7 +28,7 @@ func ExampleTree_WalkPath() {
rt.Put("tornado", "TORNADO")

// Find all items that are a prefix of "tomato"
rt.WalkPath("tomato", func(key string, value interface{}) bool {
rt.WalkPath("tomato", func(key string, value any) bool {
fmt.Println(value)
return false
})
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
module github.com/gammazero/radixtree

go 1.17
go 1.18
2 changes: 1 addition & 1 deletion iterator.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ func (it *Iterator) Copy() *Iterator {

// Next returns the next key and value stored in the Tree, and true when
// iteration is complete.
func (it *Iterator) Next() (key string, value interface{}, done bool) {
func (it *Iterator) Next() (key string, value any, done bool) {
for {
if len(it.nodes) == 0 {
break
Expand Down
2 changes: 1 addition & 1 deletion stepper.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ func (s *Stepper) Next(radix byte) bool {

// Value returns the value at the current Stepper position, and true or false
// to indicate if a value is present at the position.
func (s *Stepper) Value() (interface{}, bool) {
func (s *Stepper) Value() (any, bool) {
// Only return value if all of this node's prefix was matched. Otherwise,
// have not fully traversed into this node (edge not completely traversed).
if s.p != len(s.node.prefix) {
Expand Down
91 changes: 73 additions & 18 deletions tree.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import (
"strings"
)

// Tree is a radix tree of bytes keys and interface{} values.
// Tree is a radix tree of bytes keys and any values.
type Tree struct {
root radixNode
size int
Expand All @@ -29,19 +29,19 @@ type radixNode struct {
//
// If the function returns true Walk stops immediately and returns. This
// applies to WalkPath as well.
type WalkFunc func(key string, value interface{}) bool
type WalkFunc func(key string, value any) bool

// InspectFunc is the type of the function called for each node visited by
// Inspect. The key argument contains the key at which the node is located, the
// depth is the distance from the root of the tree, and children is the number
// of children the node has.
//
// If the function returns true Inspect stops immediately and returns.
type InspectFunc func(link, prefix, key string, depth, children int, hasValue bool, value interface{}) bool
type InspectFunc func(link, prefix, key string, depth, children int, hasValue bool, value any) bool

type leaf struct {
key string
value interface{}
value any
}

type edge struct {
Expand All @@ -56,7 +56,7 @@ func (t *Tree) Len() int {

// Get returns the value stored at the given key. Returns false if there is no
// value present for the key.
func (t *Tree) Get(key string) (interface{}, bool) {
func (t *Tree) Get(key string) (any, bool) {
node := &t.root
// Consume key data while mathcing edge and prefix; return if remaining key
// data matches nothing.
Expand All @@ -83,7 +83,7 @@ func (t *Tree) Get(key string) (interface{}, bool) {
// Put inserts the value into the tree at the given key, replacing any existing
// items. It returns true if it adds a new value, false if it replaces an
// existing value.
func (t *Tree) Put(key string, value interface{}) bool {
func (t *Tree) Put(key string, value any) bool {
var (
p int
isNewValue bool
Expand Down Expand Up @@ -176,13 +176,68 @@ func (t *Tree) Delete(key string) bool {
key = key[len(node.prefix):]
}

var deleted bool
if node.leaf != nil {
// delete the node value, indicate that value was deleted.
node.leaf = nil
deleted = true
if node.leaf == nil {
return false
}

// delete the node value, indicate that value was deleted.
node.leaf = nil
t.size--

// If node is leaf, remove from parent. If parent becomes leaf, repeat.
node = node.prune(parents, links)

// If node has become compressible, compress it.
if node != &t.root {
node.compress()
}

return true
}

// DeletePrefix removes all values whose key is prefixed by the given prefix.
// Returns true if any values were removed.
func (t *Tree) DeletePrefix(prefix string) bool {
node := &t.root
var (
parents []*radixNode
links []byte
)
for len(prefix) != 0 {
parents = append(parents, node)

// Find edge for radix.
node = node.getEdge(prefix[0])
if node == nil {
// Node does not exist.
return false
}
links = append(links, prefix[0])

// Consume prefix.
prefix = prefix[1:]
if !strings.HasPrefix(prefix, node.prefix) {
if strings.HasPrefix(node.prefix, prefix) {
// Prefix consumed, so it prefixes every key from node down.
break
}
return false
}
prefix = prefix[len(node.prefix):]
}

if node.edges != nil {
var count int
node.walk(func(k string, _ any) bool {
count++
return false
})
t.size -= count
node.edges = nil
} else {
t.size--
}
node.leaf = nil

// If node is leaf, remove from parent. If parent becomes leaf, repeat.
node = node.prune(parents, links)
Expand All @@ -192,7 +247,7 @@ func (t *Tree) Delete(key string) bool {
node.compress()
}

return deleted
return true
}

// Walk visits all nodes whose keys match or are prefixed by the specified key,
Expand Down Expand Up @@ -263,9 +318,12 @@ func (t *Tree) Inspect(inspectFn InspectFunc) {
}

// split splits a node such that a node:
// ("prefix", leaf, edges[])
//
// ("prefix", leaf, edges[])
//
// is split into parent branching node, and a child leaf node:
// ("pre", nil, edges[f])--->("ix", leaf, edges[])
//
// ("pre", nil, edges[f])--->("ix", leaf, edges[])
func (node *radixNode) split(p int) {
split := &radixNode{
edges: node.edges,
Expand Down Expand Up @@ -334,7 +392,7 @@ func (node *radixNode) walk(walkFn WalkFunc) bool {

func (node *radixNode) inspect(link, key string, depth int, inspectFn InspectFunc) bool {
key += link + node.prefix
var val interface{}
var val any
var hasVal bool
if node.leaf != nil {
val = node.leaf.value
Expand Down Expand Up @@ -394,6 +452,3 @@ func (node *radixNode) delEdge(radix byte) {
node.edges = node.edges[:len(node.edges)-1]
}
}

// Deprecated: Bytes is deprecated, use Tree.
type Bytes = Tree
Loading

0 comments on commit dd01f7f

Please sign in to comment.