node_test.gno
15.98 Kb · 763 lines
1package avl
2
3import (
4 "sort"
5 "strings"
6 "testing"
7
8 "gno.land/p/nt/ufmt/v0"
9)
10
11func TestTraverseByOffset(t *testing.T) {
12 const testStrings = `Alfa
13Alfred
14Alpha
15Alphabet
16Beta
17Beth
18Book
19Browser`
20 tt := []struct {
21 name string
22 asc bool
23 }{
24 {"ascending", true},
25 {"descending", false},
26 }
27
28 for _, tt := range tt {
29 t.Run(tt.name, func(t *testing.T) {
30 // use sl to insert the values, and reversed to match the values
31 // we do this to ensure that the order of TraverseByOffset is independent
32 // from the insertion order
33 sl := strings.Split(testStrings, "\n")
34 sort.Strings(sl)
35 reversed := append([]string{}, sl...)
36 reverseSlice(reversed)
37
38 if !tt.asc {
39 sl, reversed = reversed, sl
40 }
41
42 r := NewNode(reversed[0], nil)
43 for _, v := range reversed[1:] {
44 r, _ = r.Set(v, nil)
45 }
46
47 var result []string
48 for i := 0; i < len(sl); i++ {
49 r.TraverseByOffset(i, 1, tt.asc, true, func(n *Node) bool {
50 result = append(result, n.Key())
51 return false
52 })
53 }
54
55 if !slicesEqual(sl, result) {
56 t.Errorf("want %v got %v", sl, result)
57 }
58
59 for l := 2; l <= len(sl); l++ {
60 // "slices"
61 for i := 0; i <= len(sl); i++ {
62 max := i + l
63 if max > len(sl) {
64 max = len(sl)
65 }
66 exp := sl[i:max]
67 actual := []string{}
68
69 r.TraverseByOffset(i, l, tt.asc, true, func(tr *Node) bool {
70 actual = append(actual, tr.Key())
71 return false
72 })
73 if !slicesEqual(exp, actual) {
74 t.Errorf("want %v got %v", exp, actual)
75 }
76 }
77 }
78 })
79 }
80}
81
82func TestTraverseByOffsetNegativeOffset(t *testing.T) {
83 // a negative offset must be treated as 0, not silently drop the right
84 // subtree. building {"a","b","c","d"} yields a tree whose root has two
85 // inner children, exercising the traverseByOffset delta computation.
86 keys := []string{"a", "b", "c", "d"}
87 r := NewNode(keys[0], nil)
88 for _, k := range keys[1:] {
89 r, _ = r.Set(k, nil)
90 }
91
92 for _, offset := range []int{-1, -2, -100} {
93 var got []string
94 r.TraverseByOffset(offset, len(keys), true, true, func(n *Node) bool {
95 got = append(got, n.Key())
96 return false
97 })
98 if !slicesEqual(keys, got) {
99 t.Errorf("offset %d: want %v got %v", offset, keys, got)
100 }
101 }
102}
103
104func TestHas(t *testing.T) {
105 tests := []struct {
106 name string
107 input []string
108 hasKey string
109 expected bool
110 }{
111 {
112 "has key in non-empty tree",
113 []string{"C", "A", "B", "E", "D"},
114 "B",
115 true,
116 },
117 {
118 "does not have key in non-empty tree",
119 []string{"C", "A", "B", "E", "D"},
120 "F",
121 false,
122 },
123 {
124 "has key in single-node tree",
125 []string{"A"},
126 "A",
127 true,
128 },
129 {
130 "does not have key in single-node tree",
131 []string{"A"},
132 "B",
133 false,
134 },
135 {
136 "does not have key in empty tree",
137 []string{},
138 "A",
139 false,
140 },
141 }
142
143 for _, tt := range tests {
144 t.Run(tt.name, func(t *testing.T) {
145 var tree *Node
146 for _, key := range tt.input {
147 tree, _ = tree.Set(key, nil)
148 }
149
150 result := tree.Has(tt.hasKey)
151
152 if result != tt.expected {
153 t.Errorf("Expected %v, got %v", tt.expected, result)
154 }
155 })
156 }
157}
158
159func TestGet(t *testing.T) {
160 tests := []struct {
161 name string
162 input []string
163 getKey string
164 expectIdx int
165 expectVal any
166 expectExists bool
167 }{
168 {
169 "get existing key",
170 []string{"C", "A", "B", "E", "D"},
171 "B",
172 1,
173 nil,
174 true,
175 },
176 {
177 "get non-existent key (smaller)",
178 []string{"C", "A", "B", "E", "D"},
179 "@",
180 0,
181 nil,
182 false,
183 },
184 {
185 "get non-existent key (larger)",
186 []string{"C", "A", "B", "E", "D"},
187 "F",
188 5,
189 nil,
190 false,
191 },
192 {
193 "get from empty tree",
194 []string{},
195 "A",
196 0,
197 nil,
198 false,
199 },
200 }
201
202 for _, tt := range tests {
203 t.Run(tt.name, func(t *testing.T) {
204 var tree *Node
205 for _, key := range tt.input {
206 tree, _ = tree.Set(key, nil)
207 }
208
209 idx, val, exists := tree.Get(tt.getKey)
210
211 if idx != tt.expectIdx {
212 t.Errorf("Expected index %d, got %d", tt.expectIdx, idx)
213 }
214
215 if val != tt.expectVal {
216 t.Errorf("Expected value %v, got %v", tt.expectVal, val)
217 }
218
219 if exists != tt.expectExists {
220 t.Errorf("Expected exists %t, got %t", tt.expectExists, exists)
221 }
222 })
223 }
224}
225
226func TestGetByIndex(t *testing.T) {
227 tests := []struct {
228 name string
229 input []string
230 idx int
231 expectKey string
232 expectVal any
233 expectPanic bool
234 }{
235 {
236 "get by valid index",
237 []string{"C", "A", "B", "E", "D"},
238 2,
239 "C",
240 nil,
241 false,
242 },
243 {
244 "get by valid index (smallest)",
245 []string{"C", "A", "B", "E", "D"},
246 0,
247 "A",
248 nil,
249 false,
250 },
251 {
252 "get by valid index (largest)",
253 []string{"C", "A", "B", "E", "D"},
254 4,
255 "E",
256 nil,
257 false,
258 },
259 {
260 "get by invalid index (negative)",
261 []string{"C", "A", "B", "E", "D"},
262 -1,
263 "",
264 nil,
265 true,
266 },
267 {
268 "get by invalid index (out of range)",
269 []string{"C", "A", "B", "E", "D"},
270 5,
271 "",
272 nil,
273 true,
274 },
275 }
276
277 for _, tt := range tests {
278 t.Run(tt.name, func(t *testing.T) {
279 var tree *Node
280 for _, key := range tt.input {
281 tree, _ = tree.Set(key, nil)
282 }
283
284 if tt.expectPanic {
285 defer func() {
286 if r := recover(); r == nil {
287 t.Errorf("Expected a panic but didn't get one")
288 }
289 }()
290 }
291
292 key, val := tree.GetByIndex(tt.idx)
293
294 if !tt.expectPanic {
295 if key != tt.expectKey {
296 t.Errorf("Expected key %s, got %s", tt.expectKey, key)
297 }
298
299 if val != tt.expectVal {
300 t.Errorf("Expected value %v, got %v", tt.expectVal, val)
301 }
302 }
303 })
304 }
305}
306
307func TestRemove(t *testing.T) {
308 tests := []struct {
309 name string
310 input []string
311 removeKey string
312 expected []string
313 }{
314 {
315 "remove leaf node",
316 []string{"C", "A", "B", "D"},
317 "B",
318 []string{"A", "C", "D"},
319 },
320 {
321 "remove node with one child",
322 []string{"C", "A", "B", "D"},
323 "A",
324 []string{"B", "C", "D"},
325 },
326 {
327 "remove node with two children",
328 []string{"C", "A", "B", "E", "D"},
329 "C",
330 []string{"A", "B", "D", "E"},
331 },
332 {
333 "remove root node",
334 []string{"C", "A", "B", "E", "D"},
335 "C",
336 []string{"A", "B", "D", "E"},
337 },
338 {
339 "remove non-existent key",
340 []string{"C", "A", "B", "E", "D"},
341 "F",
342 []string{"A", "B", "C", "D", "E"},
343 },
344 }
345
346 for _, tt := range tests {
347 t.Run(tt.name, func(t *testing.T) {
348 var tree *Node
349 for _, key := range tt.input {
350 tree, _ = tree.Set(key, nil)
351 }
352
353 tree, _, _, _ = tree.Remove(tt.removeKey)
354
355 result := make([]string, 0)
356 tree.Iterate("", "", func(n *Node) bool {
357 result = append(result, n.Key())
358 return false
359 })
360
361 if !slicesEqual(tt.expected, result) {
362 t.Errorf("want %v got %v", tt.expected, result)
363 }
364 })
365 }
366}
367
368func TestTraverse(t *testing.T) {
369 tests := []struct {
370 name string
371 input []string
372 expected []string
373 }{
374 {
375 "empty tree",
376 []string{},
377 []string{},
378 },
379 {
380 "single node tree",
381 []string{"A"},
382 []string{"A"},
383 },
384 {
385 "small tree",
386 []string{"C", "A", "B", "E", "D"},
387 []string{"A", "B", "C", "D", "E"},
388 },
389 {
390 "large tree",
391 []string{"H", "D", "L", "B", "F", "J", "N", "A", "C", "E", "G", "I", "K", "M", "O"},
392 []string{"A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O"},
393 },
394 }
395
396 for _, tt := range tests {
397 t.Run(tt.name, func(t *testing.T) {
398 var tree *Node
399 for _, key := range tt.input {
400 tree, _ = tree.Set(key, nil)
401 }
402
403 t.Run("iterate", func(t *testing.T) {
404 var result []string
405 tree.Iterate("", "", func(n *Node) bool {
406 result = append(result, n.Key())
407 return false
408 })
409 if !slicesEqual(tt.expected, result) {
410 t.Errorf("want %v got %v", tt.expected, result)
411 }
412 })
413
414 t.Run("ReverseIterate", func(t *testing.T) {
415 var result []string
416 tree.ReverseIterate("", "", func(n *Node) bool {
417 result = append(result, n.Key())
418 return false
419 })
420 expected := make([]string, len(tt.expected))
421 copy(expected, tt.expected)
422 for i, j := 0, len(expected)-1; i < j; i, j = i+1, j-1 {
423 expected[i], expected[j] = expected[j], expected[i]
424 }
425 if !slicesEqual(expected, result) {
426 t.Errorf("want %v got %v", expected, result)
427 }
428 })
429
430 t.Run("TraverseInRange", func(t *testing.T) {
431 var result []string
432 start, end := "C", "M"
433 tree.TraverseInRange(start, end, true, true, func(n *Node) bool {
434 result = append(result, n.Key())
435 return false
436 })
437 expected := make([]string, 0)
438 for _, key := range tt.expected {
439 if key >= start && key < end {
440 expected = append(expected, key)
441 }
442 }
443 if !slicesEqual(expected, result) {
444 t.Errorf("want %v got %v", expected, result)
445 }
446 })
447
448 t.Run("early termination", func(t *testing.T) {
449 if len(tt.input) == 0 {
450 return // Skip for empty tree
451 }
452
453 var result []string
454 var count int
455 tree.Iterate("", "", func(n *Node) bool {
456 count++
457 result = append(result, n.Key())
458 return true // Stop after first item
459 })
460
461 if count != 1 {
462 t.Errorf("Expected callback to be called exactly once, got %d calls", count)
463 }
464 if len(result) != 1 {
465 t.Errorf("Expected exactly one result, got %d items", len(result))
466 }
467 if len(result) > 0 && result[0] != tt.expected[0] {
468 t.Errorf("Expected first item to be %v, got %v", tt.expected[0], result[0])
469 }
470 })
471 })
472 }
473}
474
475func TestRotateWhenHeightDiffers(t *testing.T) {
476 tests := []struct {
477 name string
478 input []string
479 expected []string
480 }{
481 {
482 "right rotation when left subtree is higher",
483 []string{"E", "C", "A", "B", "D"},
484 []string{"A", "B", "C", "D", "E"},
485 },
486 {
487 "left rotation when right subtree is higher",
488 []string{"A", "C", "E", "D", "F"},
489 []string{"A", "C", "D", "E", "F"},
490 },
491 {
492 "left-right rotation",
493 []string{"E", "A", "C", "B", "D"},
494 []string{"A", "B", "C", "D", "E"},
495 },
496 {
497 "right-left rotation",
498 []string{"A", "E", "C", "B", "D"},
499 []string{"A", "B", "C", "D", "E"},
500 },
501 }
502
503 for _, tt := range tests {
504 t.Run(tt.name, func(t *testing.T) {
505 var tree *Node
506 for _, key := range tt.input {
507 tree, _ = tree.Set(key, nil)
508 }
509
510 // perform rotation or balance
511 tree = tree.balance()
512
513 // check tree structure
514 var result []string
515 tree.Iterate("", "", func(n *Node) bool {
516 result = append(result, n.Key())
517 return false
518 })
519
520 if !slicesEqual(tt.expected, result) {
521 t.Errorf("want %v got %v", tt.expected, result)
522 }
523 })
524 }
525}
526
527func TestRotateAndBalance(t *testing.T) {
528 tests := []struct {
529 name string
530 input []string
531 expected []string
532 }{
533 {
534 "right rotation",
535 []string{"A", "B", "C", "D", "E"},
536 []string{"A", "B", "C", "D", "E"},
537 },
538 {
539 "left rotation",
540 []string{"E", "D", "C", "B", "A"},
541 []string{"A", "B", "C", "D", "E"},
542 },
543 {
544 "left-right rotation",
545 []string{"C", "A", "E", "B", "D"},
546 []string{"A", "B", "C", "D", "E"},
547 },
548 {
549 "right-left rotation",
550 []string{"C", "E", "A", "D", "B"},
551 []string{"A", "B", "C", "D", "E"},
552 },
553 }
554
555 for _, tt := range tests {
556 t.Run(tt.name, func(t *testing.T) {
557 var tree *Node
558 for _, key := range tt.input {
559 tree, _ = tree.Set(key, nil)
560 }
561
562 tree = tree.balance()
563
564 var result []string
565 tree.Iterate("", "", func(n *Node) bool {
566 result = append(result, n.Key())
567 return false
568 })
569
570 if !slicesEqual(tt.expected, result) {
571 t.Errorf("want %v got %v", tt.expected, result)
572 }
573 })
574 }
575}
576
577func TestRemoveFromEmptyTree(t *testing.T) {
578 var tree *Node
579 newTree, _, val, removed := tree.Remove("NonExistent")
580 if newTree != nil {
581 t.Errorf("Removing from an empty tree should still be nil tree.")
582 }
583 if val != nil || removed {
584 t.Errorf("Expected no value and removed=false when removing from empty tree.")
585 }
586}
587
588func TestBalanceAfterRemoval(t *testing.T) {
589 tests := []struct {
590 name string
591 insertKeys []string
592 removeKey string
593 expectedBalance int
594 }{
595 {
596 name: "balance after removing right node",
597 insertKeys: []string{"B", "A", "D", "C", "E"},
598 removeKey: "E",
599 expectedBalance: 0,
600 },
601 {
602 name: "balance after removing left node",
603 insertKeys: []string{"D", "B", "E", "A", "C"},
604 removeKey: "A",
605 expectedBalance: 0,
606 },
607 {
608 name: "ensure no lean after removal",
609 insertKeys: []string{"C", "B", "E", "A", "D", "F"},
610 removeKey: "F",
611 expectedBalance: -1,
612 },
613 {
614 name: "descending order insert, remove middle node",
615 insertKeys: []string{"E", "D", "C", "B", "A"},
616 removeKey: "C",
617 expectedBalance: 0,
618 },
619 {
620 name: "ascending order insert, remove middle node",
621 insertKeys: []string{"A", "B", "C", "D", "E"},
622 removeKey: "C",
623 expectedBalance: 0,
624 },
625 {
626 name: "duplicate key insert, remove the duplicated key",
627 insertKeys: []string{"C", "B", "C", "A", "D"},
628 removeKey: "C",
629 expectedBalance: 1,
630 },
631 {
632 name: "complex rotation case",
633 insertKeys: []string{"H", "B", "A", "C", "E", "D", "F", "G"},
634 removeKey: "B",
635 expectedBalance: 0,
636 },
637 }
638
639 for _, tt := range tests {
640 t.Run(tt.name, func(t *testing.T) {
641 var tree *Node
642 for _, key := range tt.insertKeys {
643 tree, _ = tree.Set(key, nil)
644 }
645
646 tree, _, _, _ = tree.Remove(tt.removeKey)
647
648 balance := tree.calcBalance()
649 if balance != tt.expectedBalance {
650 t.Errorf("Expected balance factor %d, got %d", tt.expectedBalance, balance)
651 }
652
653 if balance < -1 || balance > 1 {
654 t.Errorf("Tree is unbalanced with factor %d", balance)
655 }
656
657 if errMsg := checkSubtreeBalance(t, tree); errMsg != "" {
658 t.Errorf("AVL property violation after removal: %s", errMsg)
659 }
660 })
661 }
662}
663
664func TestBSTProperty(t *testing.T) {
665 var tree *Node
666 keys := []string{"D", "B", "F", "A", "C", "E", "G"}
667 for _, key := range keys {
668 tree, _ = tree.Set(key, nil)
669 }
670
671 var result []string
672 inorderTraversal(t, tree, &result)
673
674 for i := 1; i < len(result); i++ {
675 if result[i] < result[i-1] {
676 t.Errorf("BST property violated: %s < %s (index %d)",
677 result[i], result[i-1], i)
678 }
679 }
680}
681
682// inorderTraversal performs an inorder traversal of the tree and returns the keys in a list.
683func inorderTraversal(t *testing.T, node *Node, result *[]string) {
684 t.Helper()
685
686 if node == nil {
687 return
688 }
689 // leaf
690 if node.height == 0 {
691 *result = append(*result, node.key)
692 return
693 }
694 inorderTraversal(t, node.leftNode, result)
695 inorderTraversal(t, node.rightNode, result)
696}
697
698// checkSubtreeBalance checks if all nodes under the given node satisfy the AVL tree conditions.
699// The balance factor of all nodes must be ∈ [-1, +1]
700func checkSubtreeBalance(t *testing.T, node *Node) string {
701 t.Helper()
702
703 if node == nil {
704 return ""
705 }
706
707 if node.IsLeaf() {
708 // leaf node must be height=0, size=1
709 if node.height != 0 {
710 return ufmt.Sprintf("Leaf node %s has height %d, expected 0", node.Key(), node.height)
711 }
712 if node.size != 1 {
713 return ufmt.Sprintf("Leaf node %s has size %d, expected 1", node.Key(), node.size)
714 }
715 return ""
716 }
717
718 // check balance factor for current node
719 balanceFactor := node.calcBalance()
720 if balanceFactor < -1 || balanceFactor > 1 {
721 return ufmt.Sprintf("Node %s is unbalanced: balanceFactor=%d", node.Key(), balanceFactor)
722 }
723
724 // check height / size relationship for children
725 left, right := node.getLeftNode(), node.getRightNode()
726 expectedHeight := maxInt8(left.height, right.height) + 1
727 if node.height != expectedHeight {
728 return ufmt.Sprintf("Node %s has incorrect height %d, expected %d", node.Key(), node.height, expectedHeight)
729 }
730 expectedSize := left.Size() + right.Size()
731 if node.size != expectedSize {
732 return ufmt.Sprintf("Node %s has incorrect size %d, expected %d", node.Key(), node.size, expectedSize)
733 }
734
735 // recursively check the left/right subtree
736 if errMsg := checkSubtreeBalance(t, left); errMsg != "" {
737 return errMsg
738 }
739 if errMsg := checkSubtreeBalance(t, right); errMsg != "" {
740 return errMsg
741 }
742
743 return ""
744}
745
746func slicesEqual(w1, w2 []string) bool {
747 if len(w1) != len(w2) {
748 return false
749 }
750 for i := 0; i < len(w1); i++ {
751 if w1[i] != w2[i] {
752 return false
753 }
754 }
755 return true
756}
757
758func reverseSlice(ss []string) {
759 for i := 0; i < len(ss)/2; i++ {
760 j := len(ss) - 1 - i
761 ss[i], ss[j] = ss[j], ss[i]
762 }
763}