Skip to content

Commit

Permalink
impl. InsertMutable and DeleteMutable
Browse files Browse the repository at this point in the history
  • Loading branch information
gaissmai committed Jan 10, 2023
1 parent e3b0771 commit 4207cc8
Show file tree
Hide file tree
Showing 5 changed files with 170 additions and 44 deletions.
80 changes: 51 additions & 29 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,29 +68,33 @@ type Interface[T any] interface {

## API
```go
import "github.com/gaissmai/interval"
import "github.com/gaissmai/interval"

type Tree[T Interface[T]] struct{ ... }
type Tree[T Interface[T]] struct{ ... }

func NewTree[T Interface[T]](items ...T) *Tree[T]
func (t *Tree[T]) Insert(items ...T) *Tree[T]
func (t *Tree[T]) Delete(item T) (*Tree[T], bool)
func NewTree[T Interface[T]](items ...T) (t Tree[T])

func (t *Tree[T]) Shortest(item T) (result T, ok bool)
func (t *Tree[T]) Largest(item T) (result T, ok bool)
func (t Tree[T]) Insert(items ...T) Tree[T]
func (t Tree[T]) Delete(item T) (Tree[T], bool)

func (t *Tree[T]) Subsets(item T) []T
func (t *Tree[T]) Supersets(item T) []T
func (t *Tree[T]) InsertMutable(items ...T)
func (t *Tree[T]) DeleteMutable(item T) bool

func (t *Tree[T]) Clone() *Tree[T]
func (t *Tree[T]) Union(b *Tree[T], overwrite bool) *Tree[T]
func (t Tree[T]) Shortest(item T) (result T, ok bool)
func (t Tree[T]) Largest(item T) (result T, ok bool)

func (t *Tree[T]) Visit(start, stop T, visitFn func(t T) bool)
func (t *Tree[T]) Fprint(w io.Writer) error
func (t *Tree[T]) Size() int
func (t *Tree[T]) Min() (min T)
func (t *Tree[T]) Max() (max T)
func (t Tree[T]) Subsets(item T) []T
func (t Tree[T]) Supersets(item T) []T

func (t Tree[T]) Clone() Tree[T]
func (t Tree[T]) Union(other Tree[T], overwrite bool, immutable bool) Tree[T]

func (t Tree[T]) Visit(start, stop T, visitFn func(item T) bool)
func (t Tree[T]) Fprint(w io.Writer) error
func (t Tree[T]) String() string
func (t Tree[T]) Size() int
func (t Tree[T]) Min() (min T)
func (t Tree[T]) Max() (max T)
```

## Benchmarks
Expand All @@ -107,19 +111,30 @@ of the trees is **O(log(n))** and the **allocs/op** represent this well.
The data structure is a randomized BST, the expected depth is determined with very
high probability (for large n) but not deterministic.

If the original tree is allowed to mutate during insert and delete because the old state is no longer needed,
then the values are correspondingly better.

```
$ go test -benchmem -bench='Insert'
goos: linux
goarch: amd64
pkg: github.com/gaissmai/interval
cpu: Intel(R) Core(TM) i5-8250U CPU @ 1.60GHz
BenchmarkInsert/Into1-8 8728209 142 ns/op 128 B/op 2 allocs/op
BenchmarkInsert/Into10-8 2740953 391 ns/op 320 B/op 5 allocs/op
BenchmarkInsert/Into100-8 889261 1610 ns/op 896 B/op 14 allocs/op
BenchmarkInsert/Into1_000-8 601810 2081 ns/op 1088 B/op 17 allocs/op
BenchmarkInsert/Into10_000-8 754924 1334 ns/op 960 B/op 15 allocs/op
BenchmarkInsert/Into100_000-8 388801 2921 ns/op 1728 B/op 27 allocs/op
BenchmarkInsert/Into1_000_000-8 363061 4081 ns/op 2304 B/op 36 allocs/op
BenchmarkInsert/Into1-8 7343677 164 ns/op 128 B/op 2 allocs/op
BenchmarkInsert/Into10-8 2436724 603 ns/op 384 B/op 6 allocs/op
BenchmarkInsert/Into100-8 1397262 879 ns/op 704 B/op 11 allocs/op
BenchmarkInsert/Into1_000-8 964476 1224 ns/op 896 B/op 14 allocs/op
BenchmarkInsert/Into10_000-8 650233 1783 ns/op 1344 B/op 21 allocs/op
BenchmarkInsert/Into100_000-8 487922 2310 ns/op 1408 B/op 22 allocs/op
BenchmarkInsert/Into1_000_000-8 408178 3608 ns/op 2048 B/op 32 allocs/op

BenchmarkInsertMutable/Into1-8 10579569 118 ns/op 64 B/op 1 allocs/op
BenchmarkInsertMutable/Into10-8 7502996 151 ns/op 64 B/op 1 allocs/op
BenchmarkInsertMutable/Into100-8 5379123 225 ns/op 64 B/op 1 allocs/op
BenchmarkInsertMutable/Into1_000-8 4460654 260 ns/op 64 B/op 1 allocs/op
BenchmarkInsertMutable/Into10_000-8 2734352 424 ns/op 64 B/op 1 allocs/op
BenchmarkInsertMutable/Into100_000-8 1757281 606 ns/op 64 B/op 1 allocs/op
BenchmarkInsertMutable/Into1_000_000-8 1610089 746 ns/op 64 B/op 1 allocs/op
```

### Delete
Expand All @@ -132,12 +147,19 @@ goos: linux
goarch: amd64
pkg: github.com/gaissmai/interval
cpu: Intel(R) Core(TM) i5-8250U CPU @ 1.60GHz
BenchmarkDelete/DeleteFrom10-8 8288145 149 ns/op 128 B/op 2 allocs/op
BenchmarkDelete/DeleteFrom100-8 1034215 1097 ns/op 960 B/op 15 allocs/op
BenchmarkDelete/DeleteFrom1_000-8 343502 3019 ns/op 2176 B/op 34 allocs/op
BenchmarkDelete/DeleteFrom10_000-8 543692 2128 ns/op 1728 B/op 27 allocs/op
BenchmarkDelete/DeleteFrom100_000-8 375445 3058 ns/op 2048 B/op 32 allocs/op
BenchmarkDelete/DeleteFrom1_000_000-8 266654 5381 ns/op 3200 B/op 50 allocs/op
BenchmarkDelete/DeleteFrom10-8 8018518 150 ns/op 128 B/op 2 allocs/op
BenchmarkDelete/DeleteFrom100-8 2349710 708 ns/op 448 B/op 7 allocs/op
BenchmarkDelete/DeleteFrom1_000-8 616173 2051 ns/op 1536 B/op 24 allocs/op
BenchmarkDelete/DeleteFrom10_000-8 446180 2362 ns/op 1856 B/op 29 allocs/op
BenchmarkDelete/DeleteFrom100_000-8 272798 4224 ns/op 2816 B/op 44 allocs/op
BenchmarkDelete/DeleteFrom1_000_000-8 231808 5897 ns/op 3520 B/op 55 allocs/op

BenchmarkDeleteMutable/DeleteFrom10-8 7682869 156 ns/op 0 B/op 0 allocs/op
BenchmarkDeleteMutable/DeleteFrom100-8 13009023 92 ns/op 0 B/op 0 allocs/op
BenchmarkDeleteMutable/DeleteFrom1_000-8 1912417 627 ns/op 0 B/op 0 allocs/op
BenchmarkDeleteMutable/DeleteFrom10_000-8 1362752 889 ns/op 0 B/op 0 allocs/op
BenchmarkDeleteMutable/DeleteFrom100_000-8 893157 1334 ns/op 0 B/op 0 allocs/op
BenchmarkDeleteMutable/DeleteFrom1_000_000-8 647199 1828 ns/op 0 B/op 0 allocs/op
```

### Lookup
Expand Down
39 changes: 37 additions & 2 deletions bench_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package interval_test

import (
"math/rand"
"testing"

"github.com/gaissmai/interval"
Expand Down Expand Up @@ -31,10 +32,27 @@ func BenchmarkInsert(b *testing.B) {
}
}

func BenchmarkDelete(b *testing.B) {
for n := 10; n <= 1_000_000; n *= 10 {
func BenchmarkInsertMutable(b *testing.B) {
for n := 1; n <= 1_000_000; n *= 10 {
tree := interval.NewTree(generateIvals(n)...)
probe := generateIvals(1)[0]
name := "Into" + intMap[n]

b.Run(name, func(b *testing.B) {
b.ResetTimer()
for n := 0; n < b.N; n++ {
(&tree).InsertMutable(probe)
}
})
}
}

func BenchmarkDelete(b *testing.B) {
for n := 10; n <= 1_000_000; n *= 10 {
ivals := generateIvals(n)
probe := ivals[rand.Intn(len(ivals))]

tree := interval.NewTree(ivals...)
name := "DeleteFrom" + intMap[n]

b.Run(name, func(b *testing.B) {
Expand All @@ -46,6 +64,23 @@ func BenchmarkDelete(b *testing.B) {
}
}

func BenchmarkDeleteMutable(b *testing.B) {
for n := 10; n <= 1_000_000; n *= 10 {
ivals := generateIvals(n)
probe := ivals[rand.Intn(len(ivals))]

tree := interval.NewTree(generateIvals(n)...)
name := "DeleteFrom" + intMap[n]

b.Run(name, func(b *testing.B) {
b.ResetTimer()
for n := 0; n < b.N; n++ {
_ = (&tree).DeleteMutable(probe)
}
})
}
}

func BenchmarkUnionImmutable(b *testing.B) {
this100_000 := interval.NewTree(generateIvals(100_000)...)
for n := 10; n <= 100_000; n *= 10 {
Expand Down
8 changes: 8 additions & 0 deletions helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"fmt"
"io"
"math"
"strings"
)

type traverseOrder uint8
Expand Down Expand Up @@ -88,6 +89,13 @@ func (t *node[T]) traverse(order traverseOrder, depth int, visitFn func(n *node[
}
}

// String returns a hierarchical tree diagram of the ordered intervals as string, just a wrapper for [Fprint].
func (t Tree[T]) String() string {
w := new(strings.Builder)
_ = t.Fprint(w)
return w.String()
}

// Fprint writes a hierarchical tree diagram of the ordered intervals to w.
//
// example: IP CIDRs as intervals
Expand Down
17 changes: 17 additions & 0 deletions treap.go
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,14 @@ func (t Tree[T]) Insert(items ...T) Tree[T] {
return t
}

// InsertMutable inserts items into the tree, changing the original tree.
// If the original tree does not need to be preserved then this is much faster than the immutable [Insert].
func (t *Tree[T]) InsertMutable(items ...T) {
for i := range items {
t.root = t.root.insert(makeNode(items[i]), false)
}
}

// insert into tree, changing nodes are copied, new treap is returned, old treap is modified if immutable is false.
func (n *node[T]) insert(b *node[T], immutable bool) *node[T] {
if n == nil {
Expand Down Expand Up @@ -173,6 +181,15 @@ func (t Tree[T]) Delete(item T) (Tree[T], bool) {
return t, true
}

// DeleteMutable removes an item from tree, returns true if it exists, false otherwise.
// If the original tree does not need to be preserved then this is much faster than the immutable [Delete].
func (t *Tree[T]) DeleteMutable(item T) bool {
l, m, r := t.root.split(item, false)
t.root = join(l, r, false)

return m != nil
}

// Union combines any two trees. In case of duplicate items, the "overwrite" flag
// controls whether the union keeps the original or whether it is replaced by the item in the other treap.
//
Expand Down
70 changes: 57 additions & 13 deletions treap_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,13 +47,17 @@ func TestNewTree(t *testing.T) {
var zeroItem Ival
var zeroTree interval.Tree[Ival]

if zeroTree.String() != "" {
t.Errorf("String() = %v, want \"\"", "")
}

w := new(strings.Builder)
if err := zeroTree.Fprint(w); err != nil {
t.Fatal(err)
}

if w.String() != "" {
t.Errorf("Write(w) = %v, want \"\"", w.String())
t.Errorf("Fprint(w) = %v, want \"\"", w.String())
}

w.Reset()
Expand All @@ -62,7 +66,7 @@ func TestNewTree(t *testing.T) {
}

if w.String() != "" {
t.Errorf("Write(w) = %v, want \"\"", w.String())
t.Errorf("FprintBST(w) = %v, want \"\"", w.String())
}

if _, ok := zeroTree.Delete(zeroItem); ok {
Expand Down Expand Up @@ -117,10 +121,27 @@ func TestTreeWithDups(t *testing.T) {
is := []Ival{
{0, 100},
{41, 102},
{41, 102},
{41, 102},
{41, 102},
{41, 102},
{41, 102},
{41, 102},
{42, 67},
{42, 67},
{42, 67},
{42, 67},
{42, 67},
{42, 67},
{42, 67},
{42, 67},
{48, 50},
{3, 13},
{3, 13},
{3, 13},
{3, 13},
{3, 13},
{3, 13},
}

tree := interval.NewTree(is...)
Expand All @@ -135,11 +156,8 @@ func TestTreeWithDups(t *testing.T) {
└─ 42...67
└─ 48...50
`
w := new(strings.Builder)
tree.Fprint(w)

if w.String() != asStr {
t.Errorf("Fprint()\nwant:\n%sgot:\n%s", asStr, w.String())
if tree.String() != asStr {
t.Errorf("Fprint()\nwant:\n%sgot:\n%s", asStr, tree.String())
}
}

Expand Down Expand Up @@ -186,6 +204,35 @@ func TestImmutable(t *testing.T) {
}
}

func TestMutable(t *testing.T) {
tree1 := interval.NewTree(ps...)
tree2 := tree1.Clone()

min := tree1.Min()

var ok bool
if ok = (&tree1).DeleteMutable(min); !ok {
t.Fatal("DeleteMutable, could not delete min item")
}
if reflect.DeepEqual(tree1, tree2) {
t.Fatal("DeleteMutable didn't change receiver")
}

// reset tree1, tree2
tree1 = interval.NewTree(ps...)
tree2 = tree1.Clone()

item := Ival{-111, 666}
(&tree1).InsertMutable(item)

if reflect.DeepEqual(tree1, tree2) {
t.Fatal("InsertMutable didn't change receiver")
}
if _, ok := tree1.Delete(item); !ok {
t.Fatal("InsertMutable didn't change receiver")
}
}

func TestLookup(t *testing.T) {
t.Parallel()

Expand Down Expand Up @@ -380,11 +427,8 @@ func TestUnion(t *testing.T) {
└─ 7...9
`

w := new(strings.Builder)
tree.Fprint(w)

if w.String() != asStr {
t.Errorf("Fprint()\nwant:\n%sgot:\n%s", asStr, w.String())
if tree.String() != asStr {
t.Errorf("Fprint()\nwant:\n%sgot:\n%s", asStr, tree.String())
}

// now with dupe overwrite
Expand All @@ -393,7 +437,7 @@ func TestUnion(t *testing.T) {
tree = tree.Union(b, true, true)
}

w.Reset()
w := new(strings.Builder)
tree.Fprint(w)
if w.String() != asStr {
t.Errorf("Fprint()\nwant:\n%sgot:\n%s", asStr, w.String())
Expand Down

0 comments on commit 4207cc8

Please sign in to comment.