package avl
import (
"sort"
"strings"
"testing"
)
func TestTraverseByOffset(t *testing.T) {
const testStrings = `Alfa
Alfred
Alpha
Alphabet
Beta
Beth
Book
Browser`
tt := []struct {
name string
desc bool
}{
{"ascending", false},
{"descending", true},
}
for _, tt := range tt {
t.Run(tt.name, func(t *testing.T) {
sl := strings.Split(testStrings, "\n")
// sort a first time in the order opposite to how we'll be traversing
// the tree, to ensure that we are not just iterating through with
// insertion order.
sort.Strings(sl)
if !tt.desc {
reverseSlice(sl)
}
r := NewNode(sl[0], nil)
for _, v := range sl[1:] {
r, _ = r.Set(v, nil)
}
// then sort sl in the order we'll be traversing it, so that we can
// compare the result with sl.
reverseSlice(sl)
var result []string
for i := 0; i < len(sl); i++ {
r.TraverseByOffset(i, 1, tt.desc, true, func(n *Node) bool {
result = append(result, n.Key())
return false
})
}
if !slicesEqual(sl, result) {
t.Errorf("want %v got %v", sl, result)
}
for l := 2; l <= len(sl); l++ {
// "slices"
for i := 0; i <= len(sl); i++ {
max := i + l
if max > len(sl) {
max = len(sl)
}
exp := sl[i:max]
actual := []string{}
r.TraverseByOffset(i, l, tt.desc, true, func(tr *Node) bool {
actual = append(actual, tr.Key())
return false
})
if !slicesEqual(exp, actual) {
t.Errorf("want %v got %v", exp, actual)
}
}
}
})
}
}
func TestHas(t *testing.T) {
tests := []struct {
name string
input []string
hasKey string
expected bool
}{
{
"has key in non-empty tree",
[]string{"C", "A", "B", "E", "D"},
"B",
true,
},
{
"does not have key in non-empty tree",
[]string{"C", "A", "B", "E", "D"},
"F",
false,
},
{
"has key in single-node tree",
[]string{"A"},
"A",
true,
},
{
"does not have key in single-node tree",
[]string{"A"},
"B",
false,
},
{
"does not have key in empty tree",
[]string{},
"A",
false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
var tree *Node
for _, key := range tt.input {
tree, _ = tree.Set(key, nil)
}
result := tree.Has(tt.hasKey)
if result != tt.expected {
t.Errorf("Expected %v, got %v", tt.expected, result)
}
})
}
}
func TestGet(t *testing.T) {
tests := []struct {
name string
input []string
getKey string
expectIdx int
expectVal interface{}
expectExists bool
}{
{
"get existing key",
[]string{"C", "A", "B", "E", "D"},
"B",
1,
nil,
true,
},
{
"get non-existent key (smaller)",
[]string{"C", "A", "B", "E", "D"},
"@",
0,
nil,
false,
},
{
"get non-existent key (larger)",
[]string{"C", "A", "B", "E", "D"},
"F",
5,
nil,
false,
},
{
"get from empty tree",
[]string{},
"A",
0,
nil,
false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
var tree *Node
for _, key := range tt.input {
tree, _ = tree.Set(key, nil)
}
idx, val, exists := tree.Get(tt.getKey)
if idx != tt.expectIdx {
t.Errorf("Expected index %d, got %d", tt.expectIdx, idx)
}
if val != tt.expectVal {
t.Errorf("Expected value %v, got %v", tt.expectVal, val)
}
if exists != tt.expectExists {
t.Errorf("Expected exists %t, got %t", tt.expectExists, exists)
}
})
}
}
func TestGetByIndex(t *testing.T) {
tests := []struct {
name string
input []string
idx int
expectKey string
expectVal interface{}
expectPanic bool
}{
{
"get by valid index",
[]string{"C", "A", "B", "E", "D"},
2,
"C",
nil,
false,
},
{
"get by valid index (smallest)",
[]string{"C", "A", "B", "E", "D"},
0,
"A",
nil,
false,
},
{
"get by valid index (largest)",
[]string{"C", "A", "B", "E", "D"},
4,
"E",
nil,
false,
},
{
"get by invalid index (negative)",
[]string{"C", "A", "B", "E", "D"},
-1,
"",
nil,
true,
},
{
"get by invalid index (out of range)",
[]string{"C", "A", "B", "E", "D"},
5,
"",
nil,
true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
var tree *Node
for _, key := range tt.input {
tree, _ = tree.Set(key, nil)
}
if tt.expectPanic {
defer func() {
if r := recover(); r == nil {
t.Errorf("Expected a panic but didn't get one")
}
}()
}
key, val := tree.GetByIndex(tt.idx)
if !tt.expectPanic {
if key != tt.expectKey {
t.Errorf("Expected key %s, got %s", tt.expectKey, key)
}
if val != tt.expectVal {
t.Errorf("Expected value %v, got %v", tt.expectVal, val)
}
}
})
}
}
func TestRemove(t *testing.T) {
tests := []struct {
name string
input []string
removeKey string
expected []string
}{
{
"remove leaf node",
[]string{"C", "A", "B", "D"},
"B",
[]string{"A", "C", "D"},
},
{
"remove node with one child",
[]string{"C", "A", "B", "D"},
"A",
[]string{"B", "C", "D"},
},
{
"remove node with two children",
[]string{"C", "A", "B", "E", "D"},
"C",
[]string{"A", "B", "D", "E"},
},
{
"remove root node",
[]string{"C", "A", "B", "E", "D"},
"C",
[]string{"A", "B", "D", "E"},
},
{
"remove non-existent key",
[]string{"C", "A", "B", "E", "D"},
"F",
[]string{"A", "B", "C", "D", "E"},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
var tree *Node
for _, key := range tt.input {
tree, _ = tree.Set(key, nil)
}
tree, _, _, _ = tree.Remove(tt.removeKey)
result := make([]string, 0)
tree.Iterate("", "", func(n *Node) bool {
result = append(result, n.Key())
return false
})
if !slicesEqual(tt.expected, result) {
t.Errorf("want %v got %v", tt.expected, result)
}
})
}
}
func TestTraverse(t *testing.T) {
tests := []struct {
name string
input []string
expected []string
}{
{
"empty tree",
[]string{},
[]string{},
},
{
"single node tree",
[]string{"A"},
[]string{"A"},
},
{
"small tree",
[]string{"C", "A", "B", "E", "D"},
[]string{"A", "B", "C", "D", "E"},
},
{
"large tree",
[]string{"H", "D", "L", "B", "F", "J", "N", "A", "C", "E", "G", "I", "K", "M", "O"},
[]string{"A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O"},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
var tree *Node
for _, key := range tt.input {
tree, _ = tree.Set(key, nil)
}
t.Run("iterate", func(t *testing.T) {
var result []string
tree.Iterate("", "", func(n *Node) bool {
result = append(result, n.Key())
return false
})
if !slicesEqual(tt.expected, result) {
t.Errorf("want %v got %v", tt.expected, result)
}
})
t.Run("ReverseIterate", func(t *testing.T) {
var result []string
tree.ReverseIterate("", "", func(n *Node) bool {
result = append(result, n.Key())
return false
})
expected := make([]string, len(tt.expected))
copy(expected, tt.expected)
for i, j := 0, len(expected)-1; i < j; i, j = i+1, j-1 {
expected[i], expected[j] = expected[j], expected[i]
}
if !slicesEqual(expected, result) {
t.Errorf("want %v got %v", expected, result)
}
})
t.Run("TraverseInRange", func(t *testing.T) {
var result []string
start, end := "C", "M"
tree.TraverseInRange(start, end, true, true, func(n *Node) bool {
result = append(result, n.Key())
return false
})
expected := make([]string, 0)
for _, key := range tt.expected {
if key >= start && key < end {
expected = append(expected, key)
}
}
if !slicesEqual(expected, result) {
t.Errorf("want %v got %v", expected, result)
}
})
})
}
}
func TestRotateWhenHeightDiffers(t *testing.T) {
tests := []struct {
name string
input []string
expected []string
}{
{
"right rotation when left subtree is higher",
[]string{"E", "C", "A", "B", "D"},
[]string{"A", "B", "C", "E", "D"},
},
{
"left rotation when right subtree is higher",
[]string{"A", "C", "E", "D", "F"},
[]string{"A", "C", "D", "E", "F"},
},
{
"left-right rotation",
[]string{"E", "A", "C", "B", "D"},
[]string{"A", "B", "C", "E", "D"},
},
{
"right-left rotation",
[]string{"A", "E", "C", "B", "D"},
[]string{"A", "B", "C", "E", "D"},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
var tree *Node
for _, key := range tt.input {
tree, _ = tree.Set(key, nil)
}
// perform rotation or balance
tree = tree.balance()
// check tree structure
var result []string
tree.Iterate("", "", func(n *Node) bool {
result = append(result, n.Key())
return false
})
if !slicesEqual(tt.expected, result) {
t.Errorf("want %v got %v", tt.expected, result)
}
})
}
}
func TestRotateAndBalance(t *testing.T) {
tests := []struct {
name string
input []string
expected []string
}{
{
"right rotation",
[]string{"A", "B", "C", "D", "E"},
[]string{"A", "B", "C", "D", "E"},
},
{
"left rotation",
[]string{"E", "D", "C", "B", "A"},
[]string{"A", "B", "C", "D", "E"},
},
{
"left-right rotation",
[]string{"C", "A", "E", "B", "D"},
[]string{"A", "B", "C", "D", "E"},
},
{
"right-left rotation",
[]string{"C", "E", "A", "D", "B"},
[]string{"A", "B", "C", "D", "E"},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
var tree *Node
for _, key := range tt.input {
tree, _ = tree.Set(key, nil)
}
tree = tree.balance()
var result []string
tree.Iterate("", "", func(n *Node) bool {
result = append(result, n.Key())
return false
})
if !slicesEqual(tt.expected, result) {
t.Errorf("want %v got %v", tt.expected, result)
}
})
}
}
func slicesEqual(w1, w2 []string) bool {
if len(w1) != len(w2) {
return false
}
for i := 0; i < len(w1); i++ {
if w1[0] != w2[0] {
return false
}
}
return true
}
func maxint8(a, b int8) int8 {
if a > b {
return a
}
return b
}
func reverseSlice(ss []string) {
for i := 0; i < len(ss)/2; i++ {
j := len(ss) - 1 - i
ss[i], ss[j] = ss[j], ss[i]
}
}