Skip to content

Commit 65df6b6

Browse files
fgmfgmarand
authored andcommitted
chore(bst): 100% coverage with cancelable walks, IndexOf.
1 parent ed480a9 commit 65df6b6

4 files changed

Lines changed: 128 additions & 56 deletions

File tree

binarysearchtree/intrinsic.go

Lines changed: 59 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package binarysearchtree
22

33
import (
44
"cmp"
5+
"errors"
56

67
"github.com/fgm/container"
78
)
@@ -68,43 +69,64 @@ func (n *node[E]) upsert(m *node[E]) *E {
6869
}
6970
}
7071

71-
func (n *node[E]) walkInOrder(cb container.WalkCB[E]) {
72+
func (n *node[E]) walkInOrder(cb container.WalkCB[E]) error {
73+
var err error
7274
if n == nil {
73-
return
75+
return nil
7476
}
7577
if n.left != nil {
76-
n.left.walkInOrder(cb)
78+
if err = n.left.walkInOrder(cb); err != nil {
79+
return err
80+
}
81+
}
82+
if err := cb(n.data); err != nil {
83+
return err
7784
}
78-
cb(n.data)
7985
if n.right != nil {
80-
n.right.walkInOrder(cb)
86+
if err = n.right.walkInOrder(cb); err != nil {
87+
return err
88+
}
8189
}
90+
return nil
8291
}
8392

84-
func (n *node[E]) walkPostOrder(cb container.WalkCB[E]) {
93+
func (n *node[E]) walkPostOrder(cb container.WalkCB[E]) error {
94+
var err error
8595
if n == nil {
86-
return
96+
return nil
8797
}
8898
if n.left != nil {
89-
n.left.walkPostOrder(cb)
99+
if err = n.left.walkPostOrder(cb); err != nil {
100+
return err
101+
}
90102
}
91103
if n.right != nil {
92-
n.right.walkPostOrder(cb)
104+
if err = n.right.walkPostOrder(cb); err != nil {
105+
return err
106+
}
93107
}
94-
cb(n.data)
108+
return cb(n.data)
95109
}
96110

97-
func (n *node[E]) walkPreOrder(cb container.WalkCB[E]) {
111+
func (n *node[E]) walkPreOrder(cb container.WalkCB[E]) error {
112+
var err error
98113
if n == nil {
99-
return
114+
return nil
115+
}
116+
if err := cb(n.data); err != nil {
117+
return err
100118
}
101-
cb(n.data)
102119
if n.left != nil {
103-
n.left.walkPreOrder(cb)
120+
if err = n.left.walkPreOrder(cb); err != nil {
121+
return err
122+
}
104123
}
105124
if n.right != nil {
106-
n.right.walkPreOrder(cb)
125+
if err = n.right.walkPreOrder(cb); err != nil {
126+
return err
127+
}
107128
}
129+
return nil
108130
}
109131

110132
// Intrinsic holds nodes which are their own ordering key.
@@ -116,13 +138,13 @@ type Intrinsic[E cmp.Ordered] struct {
116138
// Complexity is O(n).
117139
func (t *Intrinsic[E]) Len() int {
118140
l := 0
119-
t.WalkPostOrder(func(_ *E) { l++ })
141+
t.WalkPostOrder(func(_ *E) error { l++; return nil })
120142
return l
121143
}
122144

123145
func (t *Intrinsic[E]) Elements() []E {
124146
var sl []E
125-
t.WalkPreOrder(func(e *E) { sl = append(sl, *e) })
147+
t.WalkPreOrder(func(e *E) error { sl = append(sl, *e); return nil })
126148
return sl
127149
}
128150

@@ -159,49 +181,50 @@ func (t *Intrinsic[E]) Delete(e *E) {
159181
// If the value cannot be found, it will return 0, false, otherwise the position
160182
// starting at 0, and true.
161183
func (t *Intrinsic[E]) IndexOf(e *E) (int, bool) {
162-
index, found := 0, false
163-
t.WalkInOrder(func(x *E) {
184+
errFound := errors.New("found")
185+
index := 0
186+
err := t.WalkInOrder(func(x *E) error {
164187
if *x == *e {
165-
found = true
166-
}
167-
if !found {
168-
index++
188+
return errFound
169189
}
190+
index++
191+
return nil
170192
})
171-
if !found {
172-
index = 0
193+
if err != errFound {
194+
return 0, false
173195
}
174-
return index, found
196+
return index, true
175197
}
176198

177199
// WalkInOrder is useful for search and listing nodes in order.
178-
func (t *Intrinsic[E]) WalkInOrder(cb container.WalkCB[E]) {
200+
func (t *Intrinsic[E]) WalkInOrder(cb container.WalkCB[E]) error {
179201
if t == nil {
180-
return
202+
return nil
181203
}
182-
t.root.walkInOrder(cb)
204+
return t.root.walkInOrder(cb)
183205
}
184206

185207
// WalkPostOrder in useful for deleting subtrees.
186-
func (t *Intrinsic[E]) WalkPostOrder(cb container.WalkCB[E]) {
208+
func (t *Intrinsic[E]) WalkPostOrder(cb container.WalkCB[E]) error {
187209
if t == nil {
188-
return
210+
return nil
189211
}
190-
t.root.walkPostOrder(cb)
212+
return t.root.walkPostOrder(cb)
191213
}
192214

193215
// WalkPreOrder is useful to clone the tree.
194-
func (t *Intrinsic[E]) WalkPreOrder(cb container.WalkCB[E]) {
216+
func (t *Intrinsic[E]) WalkPreOrder(cb container.WalkCB[E]) error {
195217
if t == nil {
196-
return
218+
return nil
197219
}
198-
t.root.walkPreOrder(cb)
220+
return t.root.walkPreOrder(cb)
199221
}
200222

201223
func (t *Intrinsic[E]) Clone() container.BinarySearchTree[E] {
202224
clone := &Intrinsic[E]{}
203-
t.WalkPreOrder(func(e *E) {
225+
t.WalkPreOrder(func(e *E) error {
204226
clone.Upsert(e)
227+
return nil
205228
})
206229
return clone
207230
}

binarysearchtree/intrinsic_opaque_test.go

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
package binarysearchtree_test
22

33
import (
4+
"fmt"
5+
"log"
46
"strconv"
57
"testing"
68

@@ -108,3 +110,47 @@ func TestIntrinsic_Len(t *testing.T) {
108110
})
109111
}
110112
}
113+
114+
func TestIntrinsic_Walk_canceling(t *testing.T) {
115+
tree := bst.Simple()
116+
checks := [...]struct {
117+
name string
118+
walker func(cb container.WalkCB[int]) error
119+
calls1, calls3, calls5 int
120+
}{
121+
{"in order", tree.WalkInOrder, 1, 3, 5},
122+
{"post order", tree.WalkPostOrder, 1, 5, 3},
123+
{"pre order", tree.WalkPreOrder, 3, 1, 5},
124+
}
125+
tree.WalkInOrder(func(e *int) error { log.Println(*e); return nil })
126+
stopper := func(at int) container.WalkCB[int] {
127+
called := 0
128+
return container.WalkCB[int](func(e *int) error {
129+
called++
130+
if *e == at {
131+
return fmt.Errorf("%d", called)
132+
}
133+
return nil
134+
})
135+
}
136+
for _, check := range checks {
137+
t.Run(check.name, func(t *testing.T) {
138+
for _, val := range []struct {
139+
input, expected int
140+
}{
141+
{1, check.calls1},
142+
{3, check.calls3},
143+
{5, check.calls5},
144+
} {
145+
err := check.walker(stopper(val.input))
146+
actual, err := strconv.Atoi(err.Error())
147+
if err != nil {
148+
t.Fatalf("unexpected non-int error: %v", err)
149+
}
150+
if actual != val.expected {
151+
t.Fatalf("got %d but expected %d", actual, val.expected)
152+
}
153+
}
154+
})
155+
}
156+
}

binarysearchtree/intrinsic_transparent_test.go

Lines changed: 19 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -12,34 +12,37 @@ var (
1212
)
1313

1414
// Simple builds this tree:
15-
// 3
16-
// / \
17-
// 2 4
18-
// / \
19-
// 1 5
15+
//
16+
// 3
17+
// / \
18+
// 2 4
19+
// / \
20+
// 1 5
2021
func Simple() container.BinarySearchTree[int] {
2122
simple := Intrinsic[int]{}
2223
simple.Upsert(&Three, &Two, &Four, &One, &Five)
2324
return &simple
2425
}
2526

2627
// HalfFull builds this tree, which contains all deletion cases
27-
// 3
28-
// / \
29-
// 2 5
30-
// / / \
31-
// 1 4 6
28+
//
29+
// 3
30+
// / \
31+
// 2 5
32+
// / / \
33+
// 1 4 6
3234
func HalfFull() container.BinarySearchTree[int] {
3335
hf := Intrinsic[int]{}
3436
hf.Upsert(&Three, &Two, &Five, &One, &Four, &Six)
3537
return &hf
3638
}
3739

38-
func P(e *int) {
39-
_, _ = fmt.Println(*e)
40+
func P(e *int) error {
41+
_, err := fmt.Println(*e)
42+
return err
4043
}
4144

42-
func ExampleBST_WalkInOrder() {
45+
func ExampleIntrinsic_WalkInOrder() {
4346
bst := Simple()
4447
bst.WalkInOrder(P)
4548
// Output:
@@ -50,7 +53,7 @@ func ExampleBST_WalkInOrder() {
5053
// 5
5154
}
5255

53-
func ExampleBST_WalkPostOrder() {
56+
func ExampleIntrinsic_WalkPostOrder() {
5457
bst := Simple()
5558
bst.WalkPostOrder(P)
5659
// Output:
@@ -61,7 +64,7 @@ func ExampleBST_WalkPostOrder() {
6164
// 3
6265
}
6366

64-
func ExampleBST_WalkPreOrder() {
67+
func ExampleIntrinsic_WalkPreOrder() {
6568
bst := Simple()
6669
bst.WalkPreOrder(P)
6770
// Output:
@@ -72,7 +75,7 @@ func ExampleBST_WalkPreOrder() {
7275
// 5
7376
}
7477

75-
func TestBST_Clone(t *testing.T) {
78+
func TestIntrinsic_Clone(t *testing.T) {
7679
bst := Simple().(*Intrinsic[int])
7780
clone := bst.Clone().(*Intrinsic[int])
7881
input := bst.root

types.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -63,9 +63,9 @@ type BinarySearchTree[E cmp.Ordered] interface {
6363
Delete(*E)
6464
IndexOf(*E) (int, bool)
6565
Upsert(...*E) []*E
66-
WalkInOrder(cb WalkCB[E])
67-
WalkPostOrder(cb WalkCB[E])
68-
WalkPreOrder(cb WalkCB[E])
66+
WalkInOrder(cb WalkCB[E]) error
67+
WalkPostOrder(cb WalkCB[E]) error
68+
WalkPreOrder(cb WalkCB[E]) error
6969
}
7070

71-
type WalkCB[E any] func(*E)
71+
type WalkCB[E any] func(*E) error

0 commit comments

Comments
 (0)