Skip to content

Commit a71bd2a

Browse files
committed
chore(bst): 100% test coverage.
1 parent f49a1d9 commit a71bd2a

6 files changed

Lines changed: 179 additions & 93 deletions

File tree

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,5 +77,5 @@ for i := 0; i < 2; i++ {
7777
The complete test coverage requires running not only the unit tests, but also
7878
the benchmarks, like:
7979
```
80-
go test -race -run=. -bench=. -coverprofile=cover.out -covermode=atomic ./...
80+
go test -race -run=. -bench=. -benchtime=10ms -coverprofile=cover.out -covermode=atomic ./...
8181
```

binarysearchtree/bst_opaque_test.go

Lines changed: 0 additions & 63 deletions
This file was deleted.
Lines changed: 38 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,9 @@ package binarysearchtree
22

33
import (
44
"golang.org/x/exp/constraints"
5-
)
65

7-
type walkCB[E constraints.Ordered] func(*E)
6+
"github.com/fgm/container"
7+
)
88

99
type node[E constraints.Ordered] struct {
1010
data *E
@@ -68,7 +68,7 @@ func (n *node[E]) upsert(m *node[E]) *E {
6868
}
6969
}
7070

71-
func (n *node[E]) walkInOrder(cb walkCB[E]) {
71+
func (n *node[E]) walkInOrder(cb container.WalkCB[E]) {
7272
if n == nil {
7373
return
7474
}
@@ -81,7 +81,7 @@ func (n *node[E]) walkInOrder(cb walkCB[E]) {
8181
}
8282
}
8383

84-
func (n *node[E]) walkPostOrder(cb walkCB[E]) {
84+
func (n *node[E]) walkPostOrder(cb container.WalkCB[E]) {
8585
if n == nil {
8686
return
8787
}
@@ -94,7 +94,7 @@ func (n *node[E]) walkPostOrder(cb walkCB[E]) {
9494
cb(n.data)
9595
}
9696

97-
func (n *node[E]) walkPreOrder(cb walkCB[E]) {
97+
func (n *node[E]) walkPreOrder(cb container.WalkCB[E]) {
9898
if n == nil {
9999
return
100100
}
@@ -107,43 +107,58 @@ func (n *node[E]) walkPreOrder(cb walkCB[E]) {
107107
}
108108
}
109109

110-
type Tree[E constraints.Ordered] struct {
110+
// Intrinsic holds nodes which are their own ordering key.
111+
type Intrinsic[E constraints.Ordered] struct {
111112
root *node[E]
112113
}
113114

115+
// Len returns the number of nodes in the tree, for the container.Countable interface.
116+
// Complexity is O(n).
117+
func (t *Intrinsic[E]) Len() int {
118+
l := 0
119+
t.WalkPostOrder(func(_ *E) { l++ })
120+
return l
121+
}
122+
123+
func (t *Intrinsic[E]) Elements() []E {
124+
var sl []E
125+
t.WalkPreOrder(func(e *E) { sl = append(sl, *e) })
126+
return sl
127+
}
128+
114129
// Upsert adds a value to the tree, replacing and returning the previous one if any.
115130
// If none existed, it returns nil.
116-
func (t *Tree[E]) Upsert(e ...*E) []*E {
117-
res := make([]*E, 0, len(e))
131+
func (t *Intrinsic[E]) Upsert(e ...*E) []*E {
132+
results := make([]*E, 0, len(e))
133+
var result *E
118134
for _, oneE := range e {
119135
n := &node[E]{data: oneE}
120136

121137
switch {
122138
case t == nil, e == nil:
123-
res = append(res, nil)
139+
result = nil
124140
case t.root == nil:
125141
t.root = n
126-
res = append(res, nil)
142+
result = nil
127143
default:
128-
res = append(res, t.root.upsert(n))
144+
result = t.root.upsert(n)
129145
}
146+
results = append(results, result)
130147
}
131-
return res
148+
return results
132149
}
133150

134-
func (t *Tree[E]) Delete(e *E) {
151+
func (t *Intrinsic[E]) Delete(e *E) {
135152
if t == nil || e == nil {
136153
return
137154
}
138155
t.root.delete(e)
139-
// two children: promote the leftmost child of the right tree as the root.
140-
// If it had a right child (can't have a left child since it is rightmost), promote it.
141156
}
142157

143158
// IndexOf returns the position of the value among those in the tree.
144159
// If the value cannot be found, it will return 0, false, otherwise the position
145160
// starting at 0, and true.
146-
func (t *Tree[E]) IndexOf(e *E) (int, bool) {
161+
func (t *Intrinsic[E]) IndexOf(e *E) (int, bool) {
147162
index, found := 0, false
148163
t.WalkInOrder(func(x *E) {
149164
if *x == *e {
@@ -159,32 +174,32 @@ func (t *Tree[E]) IndexOf(e *E) (int, bool) {
159174
return index, found
160175
}
161176

162-
// WalkInOrder in useful for search and listing nodes in order.
163-
func (t *Tree[E]) WalkInOrder(cb walkCB[E]) {
177+
// WalkInOrder is useful for search and listing nodes in order.
178+
func (t *Intrinsic[E]) WalkInOrder(cb container.WalkCB[E]) {
164179
if t == nil {
165180
return
166181
}
167182
t.root.walkInOrder(cb)
168183
}
169184

170185
// WalkPostOrder in useful for deleting subtrees.
171-
func (t *Tree[E]) WalkPostOrder(fn func(e *E)) {
186+
func (t *Intrinsic[E]) WalkPostOrder(cb container.WalkCB[E]) {
172187
if t == nil {
173188
return
174189
}
175-
t.root.walkPostOrder(fn)
190+
t.root.walkPostOrder(cb)
176191
}
177192

178193
// WalkPreOrder is useful to clone the tree.
179-
func (t *Tree[E]) WalkPreOrder(cb walkCB[E]) {
194+
func (t *Intrinsic[E]) WalkPreOrder(cb container.WalkCB[E]) {
180195
if t == nil {
181196
return
182197
}
183198
t.root.walkPreOrder(cb)
184199
}
185200

186-
func (t *Tree[E]) Clone() *Tree[E] {
187-
clone := &Tree[E]{}
201+
func (t *Intrinsic[E]) Clone() container.BinarySearchTree[E] {
202+
clone := &Intrinsic[E]{}
188203
t.WalkPreOrder(func(e *E) {
189204
clone.Upsert(e)
190205
})
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
package binarysearchtree_test
2+
3+
import (
4+
"strconv"
5+
"testing"
6+
7+
"github.com/fgm/container"
8+
bst "github.com/fgm/container/binarysearchtree"
9+
)
10+
11+
func TestIntrinsic_nil(t *testing.T) {
12+
var tree *bst.Intrinsic[int]
13+
tree.WalkInOrder(bst.P)
14+
tree.WalkPostOrder(bst.P)
15+
tree.WalkPreOrder(bst.P)
16+
tree.Upsert(nil)
17+
tree.Delete(nil)
18+
19+
tree = &bst.Intrinsic[int]{}
20+
tree.WalkInOrder(bst.P)
21+
tree.WalkPostOrder(bst.P)
22+
tree.WalkPreOrder(bst.P)
23+
// Output:
24+
}
25+
26+
func TestIntrinsic_Upsert(t *testing.T) {
27+
tree := bst.Simple()
28+
actual := tree.Upsert(&bst.One)
29+
if len(actual) != 1 {
30+
t.Fatalf("expected overwriting upsert to return one value, got %v", actual)
31+
}
32+
if *actual[0] != bst.One {
33+
t.Fatalf("expected overwriting upsert to return %d, got %d", bst.One, *actual[0])
34+
}
35+
36+
actual = tree.Upsert(&bst.Six)
37+
if len(actual) != 1 {
38+
t.Fatalf("expected non-overwriting upsert to return one value, got %v", actual)
39+
}
40+
if actual[0] != nil {
41+
t.Fatalf("expected non-overwriting upsert to return one nil, got %v", actual[0])
42+
}
43+
}
44+
45+
func TestIntrinsic_IndexOf(t *testing.T) {
46+
tree := bst.Simple()
47+
checks := [...]struct {
48+
input int
49+
expectedOK bool
50+
expectedIndex int
51+
}{
52+
{bst.One, true, 0},
53+
{bst.Two, true, 1},
54+
{bst.Three, true, 2},
55+
{bst.Four, true, 3},
56+
{bst.Five, true, 4},
57+
{bst.Six, false, 0},
58+
}
59+
for _, check := range checks {
60+
t.Run(strconv.Itoa(check.input), func(t *testing.T) {
61+
actualIndex, actualOK := tree.IndexOf(&check.input)
62+
if actualOK != check.expectedOK {
63+
t.Fatalf("%d found: %t but expected %t", check.input, actualOK, check.expectedOK)
64+
}
65+
if actualIndex != check.expectedIndex {
66+
t.Fatalf("%d at index %d but expected %d", check.input, actualIndex, check.expectedIndex)
67+
}
68+
})
69+
}
70+
}
71+
72+
func TestIntrinsic_Len(t *testing.T) {
73+
si := bst.Simple().(container.Enumerable[int]).Elements()
74+
hf := bst.HalfFull().(container.Enumerable[int]).Elements()
75+
76+
checks := [...]struct {
77+
name string
78+
input []int
79+
deletions []int
80+
expected int
81+
}{
82+
{"empty", nil, nil, 0},
83+
{"simple", si, nil, 5},
84+
{"half-full", hf, nil, 6},
85+
{"overwrite element", append(si, bst.Three), nil, 5},
86+
{"delete nonexistent", si, []int{bst.Six}, 5},
87+
{"delete existing childless", si, []int{bst.One}, 4},
88+
{"delete existing with 1 left child", si, []int{bst.Two}, 4},
89+
{"delete existing with 1 right child", si, []int{bst.Four}, 4},
90+
{"delete existing with 2 children", hf, []int{bst.Three}, 5},
91+
}
92+
for _, check := range checks {
93+
t.Run(check.name, func(t *testing.T) {
94+
tree := bst.Intrinsic[int]{}
95+
// In these loops, e is always the same variable: without cloning,
96+
// each iteration reuses the same pointer, overwriting the tree.
97+
for _, e := range check.input {
98+
clone := e
99+
tree.Upsert(&clone)
100+
}
101+
for _, e := range check.deletions {
102+
clone := e
103+
tree.Delete(&clone)
104+
}
105+
if tree.Len() != check.expected {
106+
t.Fatalf("Found len %d, but expected %d", tree.Len(), check.expected)
107+
}
108+
})
109+
}
110+
}

binarysearchtree/bst_transparent_test.go renamed to binarysearchtree/intrinsic_transparent_test.go

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ package binarysearchtree
33
import (
44
"fmt"
55
"testing"
6+
7+
"github.com/fgm/container"
68
)
79

810
var (
@@ -15,8 +17,8 @@ var (
1517
// 2 4
1618
// / \
1719
// 1 5
18-
func Simple() *Tree[int] {
19-
simple := Tree[int]{}
20+
func Simple() container.BinarySearchTree[int] {
21+
simple := Intrinsic[int]{}
2022
simple.Upsert(&Three, &Two, &Four, &One, &Five)
2123
return &simple
2224
}
@@ -27,8 +29,8 @@ func Simple() *Tree[int] {
2729
// 2 5
2830
// / / \
2931
// 1 4 6
30-
func HalfFull() *Tree[int] {
31-
hf := Tree[int]{}
32+
func HalfFull() container.BinarySearchTree[int] {
33+
hf := Intrinsic[int]{}
3234
hf.Upsert(&Three, &Two, &Five, &One, &Four, &Six)
3335
return &hf
3436
}
@@ -71,8 +73,8 @@ func ExampleBST_WalkPreOrder() {
7173
}
7274

7375
func TestBST_Clone(t *testing.T) {
74-
bst := Simple()
75-
clone := bst.Clone()
76+
bst := Simple().(*Intrinsic[int])
77+
clone := bst.Clone().(*Intrinsic[int])
7678
input := bst.root
7779
output := clone.root
7880
checks := [...]struct {

0 commit comments

Comments
 (0)