浏览代码

pkg/adt: add "visitLevel", make "IntervalTree" interface, more tests

Make "IntervalTree" an interface to abstract range tree interface

Signed-off-by: Gyuho Lee <leegyuho@amazon.com>
Gyuho Lee 6 年之前
父节点
当前提交
266214d19e
共有 3 个文件被更改,包括 376 次插入31 次删除
  1. 1 2
      pkg/adt/example_test.go
  2. 130 20
      pkg/adt/interval_tree.go
  3. 245 9
      pkg/adt/interval_tree_test.go

+ 1 - 2
pkg/adt/example_test.go

@@ -21,8 +21,7 @@ import (
 )
 
 func Example() {
-	ivt := &adt.IntervalTree{}
-
+	ivt := adt.NewIntervalTree()
 	ivt.Insert(adt.NewInt64Interval(1, 3), 123)
 	ivt.Insert(adt.NewInt64Interval(9, 13), 456)
 	ivt.Insert(adt.NewInt64Interval(7, 20), 789)

+ 130 - 20
pkg/adt/interval_tree.go

@@ -16,7 +16,9 @@ package adt
 
 import (
 	"bytes"
+	"fmt"
 	"math"
+	"strings"
 )
 
 // Comparable is an interface for trichotomic comparisons.
@@ -35,6 +37,17 @@ const (
 	red
 )
 
+func (c rbcolor) String() string {
+	switch c {
+	case black:
+		return "black"
+	case red:
+		return "black"
+	default:
+		panic(fmt.Errorf("unknown color %d", c))
+	}
+}
+
 // Interval implements a Comparable interval [begin, end)
 // TODO: support different sorts of intervals: (a,b), [a,b], (a, b]
 type Interval struct {
@@ -160,22 +173,55 @@ func (x *intervalNode) visit(iv *Interval, nv nodeVisitor) bool {
 	return true
 }
 
+// IntervalValue represents a range tree node that contains a range and a value.
 type IntervalValue struct {
 	Ivl Interval
 	Val interface{}
 }
 
 // IntervalTree represents a (mostly) textbook implementation of the
-// "Introduction to Algorithms" (Cormen et al, 2nd ed.) chapter 13 red-black tree
+// "Introduction to Algorithms" (Cormen et al, 3rd ed.) chapter 13 red-black tree
 // and chapter 14.3 interval tree with search supporting "stabbing queries".
-type IntervalTree struct {
+type IntervalTree interface {
+	// Insert adds a node with the given interval into the tree.
+	Insert(ivl Interval, val interface{})
+	// Delete removes the node with the given interval from the tree, returning
+	// true if a node is in fact removed.
+	Delete(ivl Interval) bool
+	// Len gives the number of elements in the tree.
+	Len() int
+	// Height is the number of levels in the tree; one node has height 1.
+	Height() int
+	// MaxHeight is the expected maximum tree height given the number of nodes.
+	MaxHeight() int
+	// Visit calls a visitor function on every tree node intersecting the given interval.
+	// It will visit each interval [x, y) in ascending order sorted on x.
+	Visit(ivl Interval, ivv IntervalVisitor)
+	// Find gets the IntervalValue for the node matching the given interval
+	Find(ivl Interval) *IntervalValue
+	// Intersects returns true if there is some tree node intersecting the given interval.
+	Intersects(iv Interval) bool
+	// Contains returns true if the interval tree's keys cover the entire given interval.
+	Contains(ivl Interval) bool
+	// Stab returns a slice with all elements in the tree intersecting the interval.
+	Stab(iv Interval) []*IntervalValue
+	// Union merges a given interval tree into the receiver.
+	Union(inIvt IntervalTree, ivl Interval)
+}
+
+// NewIntervalTree returns a new interval tree.
+func NewIntervalTree() IntervalTree {
+	return &intervalTree{}
+}
+
+type intervalTree struct {
 	root  *intervalNode
 	count int
 }
 
 // Delete removes the node with the given interval from the tree, returning
 // true if a node is in fact removed.
-func (ivt *IntervalTree) Delete(ivl Interval) bool {
+func (ivt *intervalTree) Delete(ivl Interval) bool {
 	z := ivt.find(ivl)
 	if z == nil {
 		return false
@@ -217,7 +263,7 @@ func (ivt *IntervalTree) Delete(ivl Interval) bool {
 	return true
 }
 
-func (ivt *IntervalTree) deleteFixup(x *intervalNode) {
+func (ivt *intervalTree) deleteFixup(x *intervalNode) {
 	for x != ivt.root && x.color() == black && x.parent != nil {
 		if x == x.parent.left {
 			w := x.parent.right
@@ -282,7 +328,7 @@ func (ivt *IntervalTree) deleteFixup(x *intervalNode) {
 }
 
 // Insert adds a node with the given interval into the tree.
-func (ivt *IntervalTree) Insert(ivl Interval, val interface{}) {
+func (ivt *intervalTree) Insert(ivl Interval, val interface{}) {
 	var y *intervalNode
 	z := &intervalNode{iv: IntervalValue{ivl, val}, max: ivl.End, c: red}
 	x := ivt.root
@@ -311,7 +357,7 @@ func (ivt *IntervalTree) Insert(ivl Interval, val interface{}) {
 	ivt.count++
 }
 
-func (ivt *IntervalTree) insertFixup(z *intervalNode) {
+func (ivt *intervalTree) insertFixup(z *intervalNode) {
 	for z.parent != nil && z.parent.parent != nil && z.parent.color() == red {
 		if z.parent == z.parent.parent.left {
 			y := z.parent.parent.right
@@ -352,7 +398,7 @@ func (ivt *IntervalTree) insertFixup(z *intervalNode) {
 }
 
 // rotateLeft moves x so it is left of its right child
-func (ivt *IntervalTree) rotateLeft(x *intervalNode) {
+func (ivt *intervalTree) rotateLeft(x *intervalNode) {
 	y := x.right
 	x.right = y.left
 	if y.left != nil {
@@ -364,8 +410,8 @@ func (ivt *IntervalTree) rotateLeft(x *intervalNode) {
 	y.updateMax()
 }
 
-// rotateLeft moves x so it is right of its left child
-func (ivt *IntervalTree) rotateRight(x *intervalNode) {
+// rotateRight moves x so it is right of its left child
+func (ivt *intervalTree) rotateRight(x *intervalNode) {
 	if x == nil {
 		return
 	}
@@ -381,7 +427,7 @@ func (ivt *IntervalTree) rotateRight(x *intervalNode) {
 }
 
 // replaceParent replaces x's parent with y
-func (ivt *IntervalTree) replaceParent(x *intervalNode, y *intervalNode) {
+func (ivt *intervalTree) replaceParent(x *intervalNode, y *intervalNode) {
 	y.parent = x.parent
 	if x.parent == nil {
 		ivt.root = y
@@ -397,13 +443,13 @@ func (ivt *IntervalTree) replaceParent(x *intervalNode, y *intervalNode) {
 }
 
 // Len gives the number of elements in the tree
-func (ivt *IntervalTree) Len() int { return ivt.count }
+func (ivt *intervalTree) Len() int { return ivt.count }
 
 // Height is the number of levels in the tree; one node has height 1.
-func (ivt *IntervalTree) Height() int { return ivt.root.height() }
+func (ivt *intervalTree) Height() int { return ivt.root.height() }
 
 // MaxHeight is the expected maximum tree height given the number of nodes
-func (ivt *IntervalTree) MaxHeight() int {
+func (ivt *intervalTree) MaxHeight() int {
 	return int((2 * math.Log2(float64(ivt.Len()+1))) + 0.5)
 }
 
@@ -412,12 +458,12 @@ type IntervalVisitor func(n *IntervalValue) bool
 
 // Visit calls a visitor function on every tree node intersecting the given interval.
 // It will visit each interval [x, y) in ascending order sorted on x.
-func (ivt *IntervalTree) Visit(ivl Interval, ivv IntervalVisitor) {
+func (ivt *intervalTree) Visit(ivl Interval, ivv IntervalVisitor) {
 	ivt.root.visit(&ivl, func(n *intervalNode) bool { return ivv(&n.iv) })
 }
 
 // find the exact node for a given interval
-func (ivt *IntervalTree) find(ivl Interval) (ret *intervalNode) {
+func (ivt *intervalTree) find(ivl Interval) (ret *intervalNode) {
 	f := func(n *intervalNode) bool {
 		if n.iv.Ivl != ivl {
 			return true
@@ -430,7 +476,7 @@ func (ivt *IntervalTree) find(ivl Interval) (ret *intervalNode) {
 }
 
 // Find gets the IntervalValue for the node matching the given interval
-func (ivt *IntervalTree) Find(ivl Interval) (ret *IntervalValue) {
+func (ivt *intervalTree) Find(ivl Interval) (ret *IntervalValue) {
 	n := ivt.find(ivl)
 	if n == nil {
 		return nil
@@ -439,7 +485,7 @@ func (ivt *IntervalTree) Find(ivl Interval) (ret *IntervalValue) {
 }
 
 // Intersects returns true if there is some tree node intersecting the given interval.
-func (ivt *IntervalTree) Intersects(iv Interval) bool {
+func (ivt *intervalTree) Intersects(iv Interval) bool {
 	x := ivt.root
 	for x != nil && iv.Compare(&x.iv.Ivl) != 0 {
 		if x.left != nil && x.left.max.Compare(iv.Begin) > 0 {
@@ -452,7 +498,7 @@ func (ivt *IntervalTree) Intersects(iv Interval) bool {
 }
 
 // Contains returns true if the interval tree's keys cover the entire given interval.
-func (ivt *IntervalTree) Contains(ivl Interval) bool {
+func (ivt *intervalTree) Contains(ivl Interval) bool {
 	var maxEnd, minBegin Comparable
 
 	isContiguous := true
@@ -476,7 +522,7 @@ func (ivt *IntervalTree) Contains(ivl Interval) bool {
 }
 
 // Stab returns a slice with all elements in the tree intersecting the interval.
-func (ivt *IntervalTree) Stab(iv Interval) (ivs []*IntervalValue) {
+func (ivt *intervalTree) Stab(iv Interval) (ivs []*IntervalValue) {
 	if ivt.count == 0 {
 		return nil
 	}
@@ -486,7 +532,7 @@ func (ivt *IntervalTree) Stab(iv Interval) (ivs []*IntervalValue) {
 }
 
 // Union merges a given interval tree into the receiver.
-func (ivt *IntervalTree) Union(inIvt IntervalTree, ivl Interval) {
+func (ivt *intervalTree) Union(inIvt IntervalTree, ivl Interval) {
 	f := func(n *IntervalValue) bool {
 		ivt.Insert(n.Ivl, n.Val)
 		return true
@@ -494,6 +540,64 @@ func (ivt *IntervalTree) Union(inIvt IntervalTree, ivl Interval) {
 	inIvt.Visit(ivl, f)
 }
 
+type visitedInterval struct {
+	root  Interval
+	left  Interval
+	right Interval
+	color rbcolor
+	depth int
+}
+
+func (vi visitedInterval) String() string {
+	bd := new(strings.Builder)
+	bd.WriteString(fmt.Sprintf("root [%v,%v,%v], left [%v,%v], right [%v,%v], depth %d",
+		vi.root.Begin, vi.root.End, vi.color,
+		vi.left.Begin, vi.left.End,
+		vi.right.Begin, vi.right.End,
+		vi.depth,
+	))
+	return bd.String()
+}
+
+// visitLevel traverses tree in level order.
+// used for testing
+func (ivt *intervalTree) visitLevel() []visitedInterval {
+	if ivt.root == nil {
+		return nil
+	}
+
+	rs := make([]visitedInterval, 0, ivt.Len())
+
+	type pair struct {
+		node  *intervalNode
+		depth int
+	}
+	queue := []pair{{ivt.root, 0}}
+	for len(queue) > 0 {
+		f := queue[0]
+		queue = queue[1:]
+
+		ivt := visitedInterval{
+			root:  f.node.iv.Ivl,
+			color: f.node.color(),
+			depth: f.depth,
+		}
+
+		if f.node.left != nil {
+			ivt.left = f.node.left.iv.Ivl
+			queue = append(queue, pair{f.node.left, f.depth + 1})
+		}
+		if f.node.right != nil {
+			ivt.right = f.node.right.iv.Ivl
+			queue = append(queue, pair{f.node.right, f.depth + 1})
+		}
+
+		rs = append(rs, ivt)
+	}
+
+	return rs
+}
+
 type StringComparable string
 
 func (s StringComparable) Compare(c Comparable) int {
@@ -543,6 +647,7 @@ func (s StringAffineComparable) Compare(c Comparable) int {
 func NewStringAffineInterval(begin, end string) Interval {
 	return Interval{StringAffineComparable(begin), StringAffineComparable(end)}
 }
+
 func NewStringAffinePoint(s string) Interval {
 	return NewStringAffineInterval(s, s+"\x00")
 }
@@ -551,6 +656,10 @@ func NewInt64Interval(a int64, b int64) Interval {
 	return Interval{Int64Comparable(a), Int64Comparable(b)}
 }
 
+func newInt64EmptyInterval() Interval {
+	return Interval{Begin: nil, End: nil}
+}
+
 func NewInt64Point(a int64) Interval {
 	return Interval{Int64Comparable(a), Int64Comparable(a + 1)}
 }
@@ -591,6 +700,7 @@ func (b BytesAffineComparable) Compare(c Comparable) int {
 func NewBytesAffineInterval(begin, end []byte) Interval {
 	return Interval{BytesAffineComparable(begin), BytesAffineComparable(end)}
 }
+
 func NewBytesAffinePoint(b []byte) Interval {
 	be := make([]byte, len(b)+1)
 	copy(be, b)

+ 245 - 9
pkg/adt/interval_tree_test.go

@@ -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{}{})
 		}