|
|
@@ -16,12 +16,247 @@ package adt
|
|
|
|
|
|
import (
|
|
|
"math/rand"
|
|
|
+ "reflect"
|
|
|
"testing"
|
|
|
"time"
|
|
|
)
|
|
|
|
|
|
+// TestIntervalTreeInsert tests interval tree insertion.
|
|
|
+func TestIntervalTreeInsert(t *testing.T) {
|
|
|
+ // "Introduction to Algorithms" (Cormen et al, 3rd ed.) chapter 14, Figure 14.4
|
|
|
+ ivt := NewIntervalTree()
|
|
|
+ ivt.Insert(NewInt64Interval(16, 21), 30)
|
|
|
+ ivt.Insert(NewInt64Interval(8, 9), 23)
|
|
|
+ ivt.Insert(NewInt64Interval(0, 3), 3)
|
|
|
+ ivt.Insert(NewInt64Interval(5, 8), 10)
|
|
|
+ ivt.Insert(NewInt64Interval(6, 10), 10)
|
|
|
+ ivt.Insert(NewInt64Interval(15, 23), 23)
|
|
|
+ ivt.Insert(NewInt64Interval(17, 19), 20)
|
|
|
+ ivt.Insert(NewInt64Interval(25, 30), 30)
|
|
|
+ ivt.Insert(NewInt64Interval(26, 26), 26)
|
|
|
+ ivt.Insert(NewInt64Interval(19, 20), 20)
|
|
|
+
|
|
|
+ expected := []visitedInterval{
|
|
|
+ {root: NewInt64Interval(16, 21), color: black, left: NewInt64Interval(8, 9), right: NewInt64Interval(25, 30), depth: 0},
|
|
|
+
|
|
|
+ {root: NewInt64Interval(8, 9), color: red, left: NewInt64Interval(5, 8), right: NewInt64Interval(15, 23), depth: 1},
|
|
|
+ {root: NewInt64Interval(25, 30), color: red, left: NewInt64Interval(17, 19), right: NewInt64Interval(26, 26), depth: 1},
|
|
|
+
|
|
|
+ {root: NewInt64Interval(5, 8), color: black, left: NewInt64Interval(0, 3), right: NewInt64Interval(6, 10), depth: 2},
|
|
|
+ {root: NewInt64Interval(15, 23), color: black, left: newInt64EmptyInterval(), right: newInt64EmptyInterval(), depth: 2},
|
|
|
+ {root: NewInt64Interval(17, 19), color: black, left: newInt64EmptyInterval(), right: NewInt64Interval(19, 20), depth: 2},
|
|
|
+ {root: NewInt64Interval(26, 26), color: black, left: newInt64EmptyInterval(), right: newInt64EmptyInterval(), depth: 2},
|
|
|
+
|
|
|
+ {root: NewInt64Interval(0, 3), color: red, left: newInt64EmptyInterval(), right: newInt64EmptyInterval(), depth: 3},
|
|
|
+ {root: NewInt64Interval(6, 10), color: red, left: newInt64EmptyInterval(), right: newInt64EmptyInterval(), depth: 3},
|
|
|
+ {root: NewInt64Interval(19, 20), color: red, left: newInt64EmptyInterval(), right: newInt64EmptyInterval(), depth: 3},
|
|
|
+ }
|
|
|
+
|
|
|
+ tr := ivt.(*intervalTree)
|
|
|
+ visits := tr.visitLevel()
|
|
|
+ if !reflect.DeepEqual(expected, visits) {
|
|
|
+ t.Fatalf("level order expected %v, got %v", expected, visits)
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// TestIntervalTreeSelfBalanced ensures range tree is self-balanced after inserting ranges to the tree.
|
|
|
+// Use https://www.cs.usfca.edu/~galles/visualization/RedBlack.html for test case creation.
|
|
|
+//
|
|
|
+// Regular Binary Search Tree
|
|
|
+// [0,1]
|
|
|
+// \
|
|
|
+// [1,2]
|
|
|
+// \
|
|
|
+// [3,4]
|
|
|
+// \
|
|
|
+// [5,6]
|
|
|
+// \
|
|
|
+// [7,8]
|
|
|
+// \
|
|
|
+// [8,9]
|
|
|
+//
|
|
|
+// Self-Balancing Binary Search Tree
|
|
|
+// [1,2]
|
|
|
+// / \
|
|
|
+// [0,1] [5,6]
|
|
|
+// / \
|
|
|
+// [3,4] [7,8]
|
|
|
+// \
|
|
|
+// [8,9]
|
|
|
+//
|
|
|
+func TestIntervalTreeSelfBalanced(t *testing.T) {
|
|
|
+ ivt := NewIntervalTree()
|
|
|
+ ivt.Insert(NewInt64Interval(0, 1), 0)
|
|
|
+ ivt.Insert(NewInt64Interval(1, 2), 0)
|
|
|
+ ivt.Insert(NewInt64Interval(3, 4), 0)
|
|
|
+ ivt.Insert(NewInt64Interval(5, 6), 0)
|
|
|
+ ivt.Insert(NewInt64Interval(7, 8), 0)
|
|
|
+ ivt.Insert(NewInt64Interval(8, 9), 0)
|
|
|
+
|
|
|
+ expected := []visitedInterval{
|
|
|
+ {root: NewInt64Interval(1, 2), color: black, left: NewInt64Interval(0, 1), right: NewInt64Interval(5, 6), depth: 0},
|
|
|
+
|
|
|
+ {root: NewInt64Interval(0, 1), color: black, left: newInt64EmptyInterval(), right: newInt64EmptyInterval(), depth: 1},
|
|
|
+ {root: NewInt64Interval(5, 6), color: red, left: NewInt64Interval(3, 4), right: NewInt64Interval(7, 8), depth: 1},
|
|
|
+
|
|
|
+ {root: NewInt64Interval(3, 4), color: black, left: newInt64EmptyInterval(), right: newInt64EmptyInterval(), depth: 2},
|
|
|
+ {root: NewInt64Interval(7, 8), color: black, left: newInt64EmptyInterval(), right: NewInt64Interval(8, 9), depth: 2},
|
|
|
+
|
|
|
+ {root: NewInt64Interval(8, 9), color: red, left: newInt64EmptyInterval(), right: newInt64EmptyInterval(), depth: 3},
|
|
|
+ }
|
|
|
+
|
|
|
+ tr := ivt.(*intervalTree)
|
|
|
+ visits := tr.visitLevel()
|
|
|
+ if !reflect.DeepEqual(expected, visits) {
|
|
|
+ t.Fatalf("level order expected %v, got %v", expected, visits)
|
|
|
+ }
|
|
|
+
|
|
|
+ if visits[len(visits)-1].depth != 3 {
|
|
|
+ t.Fatalf("expected self-balanced tree with last level 3, but last level got %d", visits[len(visits)-1].depth)
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// TestIntervalTreeDelete ensures delete operation maintains red-black tree properties.
|
|
|
+// Use https://www.cs.usfca.edu/~galles/visualization/RedBlack.html for test case creation.
|
|
|
+// See https://github.com/etcd-io/etcd/issues/10877 for more detail.
|
|
|
+//
|
|
|
+//
|
|
|
+// After insertion:
|
|
|
+// [510,511]
|
|
|
+// / \
|
|
|
+// ---------- -----------------------
|
|
|
+// / \
|
|
|
+// [82,83] [830,831]
|
|
|
+// / \ / \
|
|
|
+// / \ / \
|
|
|
+// [11,12] [383,384](red) [647,648] [899,900](red)
|
|
|
+// / \ / \ / \
|
|
|
+// / \ / \ / \
|
|
|
+// [261,262] [410,411] [514,515](red) [815,816](red) [888,889] [972,973]
|
|
|
+// / \ /
|
|
|
+// / \ /
|
|
|
+// [238,239](red) [292,293](red) [953,954](red)
|
|
|
+//
|
|
|
+//
|
|
|
+// After deleting 514 (no rebalance):
|
|
|
+// [510,511]
|
|
|
+// / \
|
|
|
+// ---------- -----------------------
|
|
|
+// / \
|
|
|
+// [82,83] [830,831]
|
|
|
+// / \ / \
|
|
|
+// / \ / \
|
|
|
+// [11,12] [383,384](red) [647,648] [899,900](red)
|
|
|
+// / \ \ / \
|
|
|
+// / \ \ / \
|
|
|
+// [261,262] [410,411] [815,816](red) [888,889] [972,973]
|
|
|
+// / \ /
|
|
|
+// / \ /
|
|
|
+// [238,239](red) [292,293](red) [953,954](red)
|
|
|
+//
|
|
|
+//
|
|
|
+// After deleting 11 (requires rebalancing):
|
|
|
+// [510,511]
|
|
|
+// / \
|
|
|
+// ---------- --------------------------
|
|
|
+// / \
|
|
|
+// [383,384] [830,831]
|
|
|
+// / \ / \
|
|
|
+// / \ / \
|
|
|
+// [261,262](red) [410,411] [647,648] [899,900](red)
|
|
|
+// / \ \ / \
|
|
|
+// / \ \ / \
|
|
|
+// [82,83] [292,293] [815,816](red) [888,889] [972,973]
|
|
|
+// \ /
|
|
|
+// \ /
|
|
|
+// [238,239](red) [953,954](red)
|
|
|
+//
|
|
|
+//
|
|
|
+func TestIntervalTreeDelete(t *testing.T) {
|
|
|
+ ivt := NewIntervalTree()
|
|
|
+ ivt.Insert(NewInt64Interval(510, 511), 0)
|
|
|
+ ivt.Insert(NewInt64Interval(82, 83), 0)
|
|
|
+ ivt.Insert(NewInt64Interval(830, 831), 0)
|
|
|
+ ivt.Insert(NewInt64Interval(11, 12), 0)
|
|
|
+ ivt.Insert(NewInt64Interval(383, 384), 0)
|
|
|
+ ivt.Insert(NewInt64Interval(647, 648), 0)
|
|
|
+ ivt.Insert(NewInt64Interval(899, 900), 0)
|
|
|
+ ivt.Insert(NewInt64Interval(261, 262), 0)
|
|
|
+ ivt.Insert(NewInt64Interval(410, 411), 0)
|
|
|
+ ivt.Insert(NewInt64Interval(514, 515), 0)
|
|
|
+ ivt.Insert(NewInt64Interval(815, 816), 0)
|
|
|
+ ivt.Insert(NewInt64Interval(888, 889), 0)
|
|
|
+ ivt.Insert(NewInt64Interval(972, 973), 0)
|
|
|
+ ivt.Insert(NewInt64Interval(238, 239), 0)
|
|
|
+ ivt.Insert(NewInt64Interval(292, 293), 0)
|
|
|
+ ivt.Insert(NewInt64Interval(953, 954), 0)
|
|
|
+
|
|
|
+ tr := ivt.(*intervalTree)
|
|
|
+
|
|
|
+ expectedBeforeDelete := []visitedInterval{
|
|
|
+ {root: NewInt64Interval(510, 511), color: black, left: NewInt64Interval(82, 83), right: NewInt64Interval(830, 831), depth: 0},
|
|
|
+
|
|
|
+ {root: NewInt64Interval(82, 83), color: black, left: NewInt64Interval(11, 12), right: NewInt64Interval(383, 384), depth: 1},
|
|
|
+ {root: NewInt64Interval(830, 831), color: black, left: NewInt64Interval(647, 648), right: NewInt64Interval(899, 900), depth: 1},
|
|
|
+
|
|
|
+ {root: NewInt64Interval(11, 12), color: black, left: newInt64EmptyInterval(), right: newInt64EmptyInterval(), depth: 2},
|
|
|
+ {root: NewInt64Interval(383, 384), color: red, left: NewInt64Interval(261, 262), right: NewInt64Interval(410, 411), depth: 2},
|
|
|
+ {root: NewInt64Interval(647, 648), color: black, left: NewInt64Interval(514, 515), right: NewInt64Interval(815, 816), depth: 2},
|
|
|
+ {root: NewInt64Interval(899, 900), color: red, left: NewInt64Interval(888, 889), right: NewInt64Interval(972, 973), depth: 2},
|
|
|
+
|
|
|
+ {root: NewInt64Interval(261, 262), color: black, left: NewInt64Interval(238, 239), right: NewInt64Interval(292, 293), depth: 3},
|
|
|
+ {root: NewInt64Interval(410, 411), color: black, left: newInt64EmptyInterval(), right: newInt64EmptyInterval(), depth: 3},
|
|
|
+ {root: NewInt64Interval(514, 515), color: red, left: newInt64EmptyInterval(), right: newInt64EmptyInterval(), depth: 3},
|
|
|
+ {root: NewInt64Interval(815, 816), color: red, left: newInt64EmptyInterval(), right: newInt64EmptyInterval(), depth: 3},
|
|
|
+ {root: NewInt64Interval(888, 889), color: black, left: newInt64EmptyInterval(), right: newInt64EmptyInterval(), depth: 3},
|
|
|
+ {root: NewInt64Interval(972, 973), color: black, left: NewInt64Interval(953, 954), right: newInt64EmptyInterval(), depth: 3},
|
|
|
+
|
|
|
+ {root: NewInt64Interval(238, 239), color: red, left: newInt64EmptyInterval(), right: newInt64EmptyInterval(), depth: 4},
|
|
|
+ {root: NewInt64Interval(292, 293), color: red, left: newInt64EmptyInterval(), right: newInt64EmptyInterval(), depth: 4},
|
|
|
+ {root: NewInt64Interval(953, 954), color: red, left: newInt64EmptyInterval(), right: newInt64EmptyInterval(), depth: 4},
|
|
|
+ }
|
|
|
+ visitsBeforeDelete := tr.visitLevel()
|
|
|
+ if !reflect.DeepEqual(expectedBeforeDelete, visitsBeforeDelete) {
|
|
|
+ t.Fatalf("level order after insertion expected %v, got %v", expectedBeforeDelete, visitsBeforeDelete)
|
|
|
+ }
|
|
|
+
|
|
|
+ // delete the node "514"
|
|
|
+ range514 := NewInt64Interval(514, 515)
|
|
|
+ if deleted := tr.Delete(NewInt64Interval(514, 515)); !deleted {
|
|
|
+ t.Fatalf("range %v not deleted", range514)
|
|
|
+ }
|
|
|
+
|
|
|
+ expectedAfterDelete514 := []visitedInterval{
|
|
|
+ {root: NewInt64Interval(510, 511), color: black, left: NewInt64Interval(82, 83), right: NewInt64Interval(830, 831), depth: 0},
|
|
|
+
|
|
|
+ {root: NewInt64Interval(82, 83), color: black, left: NewInt64Interval(11, 12), right: NewInt64Interval(383, 384), depth: 1},
|
|
|
+ {root: NewInt64Interval(830, 831), color: black, left: NewInt64Interval(647, 648), right: NewInt64Interval(899, 900), depth: 1},
|
|
|
+
|
|
|
+ {root: NewInt64Interval(11, 12), color: black, left: newInt64EmptyInterval(), right: newInt64EmptyInterval(), depth: 2},
|
|
|
+ {root: NewInt64Interval(383, 384), color: red, left: NewInt64Interval(261, 262), right: NewInt64Interval(410, 411), depth: 2},
|
|
|
+ {root: NewInt64Interval(647, 648), color: black, left: newInt64EmptyInterval(), right: NewInt64Interval(815, 816), depth: 2},
|
|
|
+ {root: NewInt64Interval(899, 900), color: red, left: NewInt64Interval(888, 889), right: NewInt64Interval(972, 973), depth: 2},
|
|
|
+
|
|
|
+ {root: NewInt64Interval(261, 262), color: black, left: NewInt64Interval(238, 239), right: NewInt64Interval(292, 293), depth: 3},
|
|
|
+ {root: NewInt64Interval(410, 411), color: black, left: newInt64EmptyInterval(), right: newInt64EmptyInterval(), depth: 3},
|
|
|
+ {root: NewInt64Interval(815, 816), color: red, left: newInt64EmptyInterval(), right: newInt64EmptyInterval(), depth: 3},
|
|
|
+ {root: NewInt64Interval(888, 889), color: black, left: newInt64EmptyInterval(), right: newInt64EmptyInterval(), depth: 3},
|
|
|
+ {root: NewInt64Interval(972, 973), color: black, left: NewInt64Interval(953, 954), right: newInt64EmptyInterval(), depth: 3},
|
|
|
+
|
|
|
+ {root: NewInt64Interval(238, 239), color: red, left: newInt64EmptyInterval(), right: newInt64EmptyInterval(), depth: 4},
|
|
|
+ {root: NewInt64Interval(292, 293), color: red, left: newInt64EmptyInterval(), right: newInt64EmptyInterval(), depth: 4},
|
|
|
+ {root: NewInt64Interval(953, 954), color: red, left: newInt64EmptyInterval(), right: newInt64EmptyInterval(), depth: 4},
|
|
|
+ }
|
|
|
+ visitsAfterDelete514 := tr.visitLevel()
|
|
|
+ if !reflect.DeepEqual(expectedAfterDelete514, visitsAfterDelete514) {
|
|
|
+ t.Fatalf("level order after deleting '514' expected %v, got %v", expectedAfterDelete514, visitsAfterDelete514)
|
|
|
+ }
|
|
|
+
|
|
|
+ // TODO: validate deletion 11
|
|
|
+}
|
|
|
+
|
|
|
func TestIntervalTreeIntersects(t *testing.T) {
|
|
|
- ivt := &IntervalTree{}
|
|
|
+ ivt := NewIntervalTree()
|
|
|
ivt.Insert(NewStringInterval("1", "3"), 123)
|
|
|
|
|
|
if ivt.Intersects(NewStringPoint("0")) {
|
|
|
@@ -42,7 +277,7 @@ func TestIntervalTreeIntersects(t *testing.T) {
|
|
|
}
|
|
|
|
|
|
func TestIntervalTreeStringAffine(t *testing.T) {
|
|
|
- ivt := &IntervalTree{}
|
|
|
+ ivt := NewIntervalTree()
|
|
|
ivt.Insert(NewStringAffineInterval("8", ""), 123)
|
|
|
if !ivt.Intersects(NewStringAffinePoint("9")) {
|
|
|
t.Errorf("missing 9")
|
|
|
@@ -53,15 +288,16 @@ func TestIntervalTreeStringAffine(t *testing.T) {
|
|
|
}
|
|
|
|
|
|
func TestIntervalTreeStab(t *testing.T) {
|
|
|
- ivt := &IntervalTree{}
|
|
|
+ ivt := NewIntervalTree()
|
|
|
ivt.Insert(NewStringInterval("0", "1"), 123)
|
|
|
ivt.Insert(NewStringInterval("0", "2"), 456)
|
|
|
ivt.Insert(NewStringInterval("5", "6"), 789)
|
|
|
ivt.Insert(NewStringInterval("6", "8"), 999)
|
|
|
ivt.Insert(NewStringInterval("0", "3"), 0)
|
|
|
|
|
|
- if ivt.root.max.Compare(StringComparable("8")) != 0 {
|
|
|
- t.Fatalf("wrong root max got %v, expected 8", ivt.root.max)
|
|
|
+ tr := ivt.(*intervalTree)
|
|
|
+ if tr.root.max.Compare(StringComparable("8")) != 0 {
|
|
|
+ t.Fatalf("wrong root max got %v, expected 8", tr.root.max)
|
|
|
}
|
|
|
if x := len(ivt.Stab(NewStringPoint("0"))); x != 3 {
|
|
|
t.Errorf("got %d, expected 3", x)
|
|
|
@@ -94,7 +330,7 @@ type xy struct {
|
|
|
func TestIntervalTreeRandom(t *testing.T) {
|
|
|
// generate unique intervals
|
|
|
ivs := make(map[xy]struct{})
|
|
|
- ivt := &IntervalTree{}
|
|
|
+ ivt := NewIntervalTree()
|
|
|
maxv := 128
|
|
|
rand.Seed(time.Now().UnixNano())
|
|
|
|
|
|
@@ -168,7 +404,7 @@ func TestIntervalTreeSortedVisit(t *testing.T) {
|
|
|
},
|
|
|
}
|
|
|
for i, tt := range tests {
|
|
|
- ivt := &IntervalTree{}
|
|
|
+ ivt := NewIntervalTree()
|
|
|
for _, ivl := range tt.ivls {
|
|
|
ivt.Insert(ivl, struct{}{})
|
|
|
}
|
|
|
@@ -217,7 +453,7 @@ func TestIntervalTreeVisitExit(t *testing.T) {
|
|
|
}
|
|
|
|
|
|
for i, tt := range tests {
|
|
|
- ivt := &IntervalTree{}
|
|
|
+ ivt := NewIntervalTree()
|
|
|
for _, ivl := range ivls {
|
|
|
ivt.Insert(ivl, struct{}{})
|
|
|
}
|
|
|
@@ -284,7 +520,7 @@ func TestIntervalTreeContains(t *testing.T) {
|
|
|
},
|
|
|
}
|
|
|
for i, tt := range tests {
|
|
|
- ivt := &IntervalTree{}
|
|
|
+ ivt := NewIntervalTree()
|
|
|
for _, ivl := range tt.ivls {
|
|
|
ivt.Insert(ivl, struct{}{})
|
|
|
}
|