From 2fd601d2a8fb390331ff67052f6d80ae2aabf882 Mon Sep 17 00:00:00 2001 From: Karl Gaissmaier Date: Sat, 28 Jan 2023 19:20:41 +0100 Subject: [PATCH] more splits before search --- README.md | 22 ++++---------- bench_test.go | 80 +++++++++++++++++++++++++++++++++++++++++++++++++-- treap.go | 18 ++++++++++-- treap_test.go | 78 +++++++++++++++++++++++++++++++++++++++++++++++-- 4 files changed, 174 insertions(+), 24 deletions(-) diff --git a/README.md b/README.md index 69425f3..91bd09a 100644 --- a/README.md +++ b/README.md @@ -192,24 +192,9 @@ BenchmarkCoverLCP/In100_000-8 1533745 782 ns/op 0 B/op 0 BenchmarkCoverLCP/In1_000_000-8 1732171 693 ns/op 0 B/op 0 allocs/op ``` -... also for `CoverSCP()`: - - -``` -$ go test -benchmem -bench='CoverSCP' -goos: linux -goarch: amd64 -pkg: github.com/gaissmai/interval -cpu: Intel(R) Core(TM) i5-8250U CPU @ 1.60GHz -BenchmarkCoverSCP/In100-8 11576743 95 ns/op 0 B/op 0 allocs/op -BenchmarkCoverSCP/In1_000-8 10032699 114 ns/op 0 B/op 0 allocs/op -BenchmarkCoverSCP/In10_000-8 6087580 196 ns/op 0 B/op 0 allocs/op -BenchmarkCoverSCP/In100_000-8 5949422 202 ns/op 0 B/op 0 allocs/op -BenchmarkCoverSCP/In1_000_000-8 4775067 250 ns/op 0 B/op 0 allocs/op -``` - ... and for `Intersects()`: + ``` $ go test -benchmem -bench='Intersects' goos: linux @@ -225,7 +210,7 @@ BenchmarkIntersects/In100_000-4 7483621 191 ns/op 0 B/op 0 BenchmarkIntersects/In1_000_000-4 8134428 149 ns/op 0 B/op 0 allocs/op ``` -... and for Find(), the exact match: +... or Find(), the exact match: ``` $ go test -benchmem -bench='Find' @@ -239,3 +224,6 @@ BenchmarkFind/In10_000-8 12858908 90 ns/op 0 B/op 0 al BenchmarkFind/In100_000-8 4696676 256 ns/op 0 B/op 0 allocs/op BenchmarkFind/In1_000_000-8 7131028 163 ns/op 0 B/op 0 allocs/op ``` + +With all other methods, the treap is split before the search, which allocates memory +but minimises the search space. diff --git a/bench_test.go b/bench_test.go index 39b64fa..572542a 100644 --- a/bench_test.go +++ b/bench_test.go @@ -142,6 +142,22 @@ func BenchmarkIntersects(b *testing.B) { } } +func BenchmarkIntersects2(b *testing.B) { + for n := 1; n <= 1_000_000; n *= 10 { + ivals := genUintIvals(n) + tree := interval.NewTree(cmpUintInterval, ivals...) + probe := gen2UintIvals(1)[0] + name := "In" + intMap[n] + + b.Run(name, func(b *testing.B) { + b.ResetTimer() + for n := 0; n < b.N; n++ { + _ = tree.Intersects(probe) + } + }) + } +} + func BenchmarkFind(b *testing.B) { for n := 1; n <= 1_000_000; n *= 10 { ivals := genUintIvals(n) @@ -173,6 +189,21 @@ func BenchmarkCoverLCP(b *testing.B) { } } +func BenchmarkCoverLCP2(b *testing.B) { + for n := 100; n <= 1_000_000; n *= 10 { + tree := interval.NewTree(cmpUintInterval, gen2UintIvals(n)...) + probe := genUintIvals(1)[0] + name := "In" + intMap[n] + + b.Run(name, func(b *testing.B) { + b.ResetTimer() + for n := 0; n < b.N; n++ { + _, _ = tree.CoverLCP(probe) + } + }) + } +} + func BenchmarkCoverSCP(b *testing.B) { for n := 100; n <= 1_000_000; n *= 10 { tree := interval.NewTree(cmpUintInterval, genUintIvals(n)...) @@ -188,6 +219,21 @@ func BenchmarkCoverSCP(b *testing.B) { } } +func BenchmarkCoverSCP2(b *testing.B) { + for n := 100; n <= 1_000_000; n *= 10 { + tree := interval.NewTree(cmpUintInterval, gen2UintIvals(n)...) + probe := genUintIvals(1)[0] + name := "In" + intMap[n] + + b.Run(name, func(b *testing.B) { + b.ResetTimer() + for n := 0; n < b.N; n++ { + _, _ = tree.CoverSCP(probe) + } + }) + } +} + func BenchmarkCoveredBy(b *testing.B) { for n := 100; n <= 100_000; n *= 10 { tree := interval.NewTree(cmpUintInterval, genUintIvals(n)...) @@ -204,7 +250,7 @@ func BenchmarkCoveredBy(b *testing.B) { } func BenchmarkCovers(b *testing.B) { - for n := 100; n <= 100_000; n *= 10 { + for n := 100; n <= 1_000_000; n *= 10 { tree := interval.NewTree(cmpUintInterval, genUintIvals(n)...) probe := genUintIvals(1)[0] name := "In" + intMap[n] @@ -218,6 +264,21 @@ func BenchmarkCovers(b *testing.B) { } } +func BenchmarkCovers2(b *testing.B) { + for n := 100; n <= 1_000_000; n *= 10 { + tree := interval.NewTree(cmpUintInterval, gen2UintIvals(n)...) + probe := genUintIvals(1)[0] + name := "In" + intMap[n] + + b.Run(name, func(b *testing.B) { + b.ResetTimer() + for n := 0; n < b.N; n++ { + _ = tree.Covers(probe) + } + }) + } +} + func BenchmarkPrecededBy(b *testing.B) { for m := 100; m <= 10_000; m *= 10 { tree := interval.NewTree(cmpUintInterval, genUintIvals(m)...) @@ -249,7 +310,7 @@ func BenchmarkPrecedes(b *testing.B) { } func BenchmarkIntersections(b *testing.B) { - for n := 100; n <= 10_000; n *= 10 { + for n := 100; n <= 100_000; n *= 10 { tree := interval.NewTree(cmpUintInterval, genUintIvals(n)...) probe := genUintIvals(1)[0] name := "In" + intMap[n] @@ -263,6 +324,21 @@ func BenchmarkIntersections(b *testing.B) { } } +func BenchmarkIntersections2(b *testing.B) { + for n := 100; n <= 100_000; n *= 10 { + tree := interval.NewTree(cmpUintInterval, gen2UintIvals(n)...) + probe := genUintIvals(1)[0] + name := "In" + intMap[n] + + b.Run(name, func(b *testing.B) { + b.ResetTimer() + for n := 0; n < b.N; n++ { + _ = tree.Intersections(probe) + } + }) + } +} + func BenchmarkMin(b *testing.B) { for n := 100; n <= 1_000_000; n *= 10 { ivals := genUintIvals(n) diff --git a/treap.go b/treap.go index aa98fc9..b1a5557 100644 --- a/treap.go +++ b/treap.go @@ -431,7 +431,14 @@ func (t *Tree[T]) lcp(n *node[T], item T) (result T, ok bool) { // tree.CoverSCP(ival{6,9}) returns ival{}, false // func (t Tree[T]) CoverSCP(item T) (result T, ok bool) { - return t.scp(t.root, item) + l, m, _ := t.split(t.root, item, true) + result, ok = t.scp(l, item) + + if !ok && m != nil { + return m.item, ok + } + + return result, ok } // scp rec-descent @@ -462,7 +469,14 @@ func (t *Tree[T]) scp(n *node[T], item T) (result T, ok bool) { // Covers returns all intervals that cover the item. // The returned intervals are in sorted order. func (t Tree[T]) Covers(item T) []T { - return t.covers(t.root, item) + l, m, _ := t.split(t.root, item, true) + result := t.covers(l, item) + + if m != nil { + return append(result, m.item) + } + + return result } // covers rec-descent diff --git a/treap_test.go b/treap_test.go index 41daa2a..c98ce2b 100644 --- a/treap_test.go +++ b/treap_test.go @@ -44,6 +44,17 @@ func genUintIvals(n int) []uintInterval { return is } +// random test data +func gen2UintIvals(n int) []uintInterval { + is := make([]uintInterval, n) + for i := 0; i < n; i++ { + a := rand.Int() + b := a + 100 + is[i] = makeUintIval(uint(a), uint(b)) + } + return is +} + func equals(a, b uintInterval) bool { return a[0] == b[0] && a[1] == b[1] } @@ -266,7 +277,7 @@ func TestFind(t *testing.T) { } } -func TestLookup(t *testing.T) { +func TestCoverLCP(t *testing.T) { t.Parallel() for i := 0; i < 100; i++ { @@ -318,9 +329,49 @@ func TestLookup(t *testing.T) { if got, ok := tree1.CoverLCP(item); ok { t.Errorf("CoverLCP(%v) = %v, want %v", item, got, !ok) } + } +} - item = uintInterval{7, 7} - want = uintInterval{1, 8} +func TestCoverSCP(t *testing.T) { + t.Parallel() + + for i := 0; i < 100; i++ { + // bring some variance into the Treap due to the prio randomness + tree1 := interval.NewTree(cmpUintInterval, ps...) + + // ▼ + // ├─ 0...6 + // │ └─ 0...5 + // ├─ 1...8 + // │ ├─ 1...7 + // │ │ └─ 1...5 + // │ │ └─ 1...4 + // │ └─ 2...8 + // │ ├─ 2...7 + // │ └─ 4...8 + // │ └─ 6...7 + // └─ 7...9 + + item := uintInterval{7, 7} + want := uintInterval{1, 8} + if got, _ := tree1.CoverSCP(item); got != want { + t.Errorf("CoverSCP(%v) = %v, want %v", item, got, want) + } + + item = uintInterval{7, 9} + want = uintInterval{7, 9} + if got, _ := tree1.CoverSCP(item); got != want { + t.Errorf("CoverSCP(%v) = %v, want %v", item, got, want) + } + + item = uintInterval{8, 9} + want = uintInterval{7, 9} + if got, _ := tree1.CoverSCP(item); got != want { + t.Errorf("CoverSCP(%v) = %v, want %v", item, got, want) + } + + item = uintInterval{0, 6} + want = uintInterval{0, 6} if got, _ := tree1.CoverSCP(item); got != want { t.Errorf("CoverSCP(%v) = %v, want %v", item, got, want) } @@ -342,6 +393,11 @@ func TestLookup(t *testing.T) { t.Errorf("CoverSCP(%v) = %v, want %v", item, ok, false) } + item = uintInterval{6, 10} + if _, ok := tree1.CoverSCP(item); ok { + t.Errorf("CoverSCP(%v) = %v, want %v", item, ok, false) + } + } } @@ -471,6 +527,14 @@ func TestIntersects(t *testing.T) { t.Fatalf("Intersects(%v), got: %v, want: %v", item, got, want) } + item = uintInterval{0, 1} + want = true + got = tree1.Intersects(item) + + if got != want { + t.Fatalf("Intersects(%v), got: %v, want: %v", item, got, want) + } + item = uintInterval{1, 1} want = true got = tree1.Intersects(item) @@ -486,6 +550,14 @@ func TestIntersects(t *testing.T) { if got != want { t.Fatalf("Intersects(%v), got: %v, want: %v", item, got, want) } + + item = uintInterval{10, 10} + want = false + got = tree1.Intersects(item) + + if got != want { + t.Fatalf("Intersects(%v), got: %v, want: %v", item, got, want) + } } func TestIntersections(t *testing.T) {