Skip to content

Commit

Permalink
impl. Find()
Browse files Browse the repository at this point in the history
  • Loading branch information
gaissmai committed Jan 11, 2023
1 parent d856a68 commit 89acec8
Show file tree
Hide file tree
Showing 4 changed files with 93 additions and 11 deletions.
26 changes: 21 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ type Interface[T any] interface {
func (t *Tree[T]) InsertMutable(items ...T)
func (t *Tree[T]) DeleteMutable(item T) bool

func (t Tree[T]) Find(item T) (result T, ok bool)
func (t Tree[T]) Shortest(item T) (result T, ok bool)
func (t Tree[T]) Largest(item T) (result T, ok bool)

Expand Down Expand Up @@ -188,9 +189,24 @@ goos: linux
goarch: amd64
pkg: github.com/gaissmai/interval
cpu: Intel(R) Core(TM) i5-8250U CPU @ 1.60GHz
BenchmarkLargest/In100-8 14285817 77.25 ns/op 0 B/op 0 allocs/op
BenchmarkLargest/In1_000-8 12253501 95.82 ns/op 0 B/op 0 allocs/op
BenchmarkLargest/In10_000-8 8254034 145.5 ns/op 0 B/op 0 allocs/op
BenchmarkLargest/In100_000-8 6909498 174.2 ns/op 0 B/op 0 allocs/op
BenchmarkLargest/In1_000_000-8 6963052 160.7 ns/op 0 B/op 0 allocs/op
BenchmarkLargest/In100-8 14285817 77.2 ns/op 0 B/op 0 allocs/op
BenchmarkLargest/In1_000-8 12253501 95.8 ns/op 0 B/op 0 allocs/op
BenchmarkLargest/In10_000-8 8254034 145.5 ns/op 0 B/op 0 allocs/op
BenchmarkLargest/In100_000-8 6909498 174.2 ns/op 0 B/op 0 allocs/op
BenchmarkLargest/In1_000_000-8 6963052 160.7 ns/op 0 B/op 0 allocs/op
```

... and the simple `Find()` for the exact match:

```
$ go test -benchmem -bench='Find'
goos: linux
goarch: amd64
pkg: github.com/gaissmai/interval
cpu: Intel(R) Core(TM) i5-8250U CPU @ 1.60GHz
BenchmarkFind/In100-8 11051197 102.8 ns/op 0 B/op 0 allocs/op
BenchmarkFind/In1_000-8 12076317 99.6 ns/op 0 B/op 0 allocs/op
BenchmarkFind/In10_000-8 8264806 145.3 ns/op 0 B/op 0 allocs/op
BenchmarkFind/In100_000-8 2878986 412.3 ns/op 0 B/op 0 allocs/op
BenchmarkFind/In1_000_000-8 4449298 263.9 ns/op 0 B/op 0 allocs/op
```
16 changes: 16 additions & 0 deletions bench_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,22 @@ func BenchmarkUnionNonImmutable(b *testing.B) {
}
}

func BenchmarkFind(b *testing.B) {
for n := 100; n <= 1_000_000; n *= 10 {
ivals := generateIvals(n)
tree := interval.NewTree(ivals...)
probe := ivals[rand.Intn(len(ivals))]
name := "In" + intMap[n]

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

func BenchmarkShortest(b *testing.B) {
for n := 100; n <= 1_000_000; n *= 10 {
tree := interval.NewTree(generateIvals(n)...)
Expand Down
45 changes: 39 additions & 6 deletions treap.go
Original file line number Diff line number Diff line change
Expand Up @@ -175,10 +175,9 @@ func (t Tree[T]) Delete(item T) (Tree[T], bool) {
n = join(l, r, immutable)

t.root = n
if m == nil {
return t, false
}
return t, true
ok := m != nil

return t, ok
}

// DeleteMutable removes an item from tree, returns true if it exists, false otherwise.
Expand All @@ -195,14 +194,15 @@ func (t *Tree[T]) DeleteMutable(item T) bool {
//
// The "immutable" flag controls whether the two trees are allowed to be modified.
//
// To create very large trees, it may be time-saving to split the input data into chunks,
// To create very large trees, it may be time-saving to slice the input data into chunks,
// fan out for creation and combine the generated subtrees with non-immutable unions.
func (t Tree[T]) Union(other Tree[T], overwrite bool, immutable bool) Tree[T] {
n := t.root.union(other.root, overwrite, immutable)
return Tree[T]{root: n}
}

func (n *node[T]) union(b *node[T], overwrite bool, immutable bool) *node[T] {
// recursion stop condition
if n == nil {
return b
}
Expand Down Expand Up @@ -240,6 +240,7 @@ func (n *node[T]) union(b *node[T], overwrite bool, immutable bool) *node[T] {
// split the treap into all nodes that compare less-than, equal
// and greater-than the provided item (BST key). The resulting nodes are
// properly formed treaps or nil.
// If the split must be immutable, first copy concerned nodes.
func (t *node[T]) split(key T, immutable bool) (left, mid, right *node[T]) {
// recursion stop condition
if t == nil {
Expand Down Expand Up @@ -288,6 +289,34 @@ func (t *node[T]) split(key T, immutable bool) (left, mid, right *node[T]) {
}
}

// Find, searches for the interval in the tree and returns it as well as true,
// otherwise the zero value for item and false.
func (t Tree[T]) Find(item T) (result T, ok bool) {
n := t.root
return n.find(item)
}

func (n *node[T]) find(item T) (result T, ok bool) {
// recursion stop condition(s)
if n == nil {
return
}

cmp := compare(item, n.item)
if cmp == 0 {
return n.item, true
}

// rec-descent
switch {
case cmp < 0:
return n.left.find(item)
case cmp > 0:
return n.right.find(item)
}
panic("unreachable")
}

// Shortest returns the most specific interval that covers item. ok is true on
// success.
//
Expand Down Expand Up @@ -326,6 +355,7 @@ func (t Tree[T]) Shortest(item T) (result T, ok bool) {
return n.shortest(item)
}

// shortest can't use tree.split(key) because of allocations or mutations.
func (n *node[T]) shortest(item T) (result T, ok bool) {
if n == nil {
return
Expand Down Expand Up @@ -426,6 +456,7 @@ func (t Tree[T]) Largest(item T) (result T, ok bool) {
return n.largest(item)
}

// largest can't use tree.split(key) because of allocations or mutations.
func (t *node[T]) largest(item T) (result T, ok bool) {
if t == nil {
return
Expand Down Expand Up @@ -458,6 +489,7 @@ func (t Tree[T]) Supersets(item T) []T {
}
var result []T

// supersets algo with tree.split(), allocations allowed
l, m, _ := n.split(item, true)
result = l.supersets(item)

Expand Down Expand Up @@ -506,6 +538,7 @@ func (t Tree[T]) Subsets(item T) []T {
}
var result []T

// subsets algo with tree.split(), allocations allowed
_, m, r := n.split(item, true)

// if key is in treap, start with key in result
Expand Down Expand Up @@ -546,7 +579,7 @@ func (t *node[T]) subsets(item T) (result []T) {
}

// join combines two disjunct treaps. All nodes in treap a have keys <= that of treap b
// for this algorithm to work correctly. The join is immutable, first copy concerned nodes.
// for this algorithm to work correctly. If the join must be immutable, first copy concerned nodes.
func join[T Interface[T]](a, b *node[T], immutable bool) *node[T] {
// recursion stop condition
if a == nil {
Expand Down
17 changes: 17 additions & 0 deletions treap_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,23 @@ func TestMutable(t *testing.T) {
}
}

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

ivals := generateIvals(100_00)
tree := interval.NewTree(ivals...)

for _, ival := range ivals {
item, ok := tree.Find(ival)
if ok != true {
t.Errorf("Find(%v) = %v, want %v", item, ok, true)
}
if item.CompareLower(ival) != 0 || item.CompareUpper(ival) != 0 {
t.Errorf("Find(%v) = %v, want %v", ival, item, ival)
}
}
}

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

Expand Down

0 comments on commit 89acec8

Please sign in to comment.