aboutsummaryrefslogtreecommitdiff
path: root/tests/core/container/test_core_rbtree.odin
blob: 78d710b7fb608009405b5dbac942153308ff51aa (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
package test_core_container

import rb "core:container/rbtree"
import "core:math/rand"
import "core:testing"
import "base:intrinsics"
import "core:slice"
import "core:log"

test_rbtree_integer :: proc(t: ^testing.T, $Key: typeid, $Value: typeid) {
	log.infof("Testing Red-Black Tree($Key=%v,$Value=%v) using random seed %v.", type_info_of(Key), type_info_of(Value), t.seed)
	tree: rb.Tree(Key, Value)
	rb.init(&tree)

	testing.expect(t, rb.len(tree)   == 0,   "empty: len should be 0")
	testing.expect(t, rb.first(&tree) == nil, "empty: first should be nil")
	testing.expect(t, rb.last(&tree)  == nil, "empty: last should be nil")
	iter := rb.iterator(&tree, .Forward)
	testing.expect(t, rb.iterator_get(&iter) == nil, "empty/iterator: first node should be nil")

	// Test insertion.
	NR_INSERTS :: 32 + 1 // Ensure at least 1 collision.
	inserted_map := make(map[Key]^rb.Node(Key, Value))

	min_key := max(Key)
	max_key := min(Key)

	for i := 0; i < NR_INSERTS; i += 1 {
		k := Key(rand.uint32()) & 0x1f
		min_key = min(min_key, k); max_key = max(max_key, k)
		v := Value(rand.uint32())

		existing_node, in_map := inserted_map[k]
		n, inserted, _ := rb.find_or_insert(&tree, k, v)
		testing.expect(t, in_map != inserted, "insert: inserted should match inverse of map lookup")
		if inserted {
			inserted_map[k] = n
		} else {
			testing.expect(t, existing_node == n, "insert: expecting existing node")
		}
	}

	entry_count := len(inserted_map)
	testing.expect(t, rb.len(tree) == entry_count, "insert: len after")
	validate_rbtree(t, &tree)

	first := rb.first(&tree)
	last  := rb.last(&tree)
	testing.expectf(t, first != nil && first.key == min_key, "insert: first should be present with key %v", min_key)
	testing.expectf(t, last  != nil && last.key  == max_key, "insert: last should be present with key %v", max_key)

	// Ensure that all entries can be found.
	for k, v in inserted_map {
		testing.expect(t, v == rb.find(tree, k), "Find(): Node")
		testing.expect(t, k == v.key,            "Find(): Node key")
	}

	// Test the forward/backward iterators.
	inserted_keys: [dynamic]Key
	for k in inserted_map {
		append(&inserted_keys, k)
	}
	slice.sort(inserted_keys[:])

	iter = rb.iterator(&tree, rb.Direction.Forward)
	visited: int
	for node in rb.iterator_next(&iter) {
		k, idx := node.key, visited
		testing.expect(t, inserted_keys[idx] == k,        "iterator/forward: key")
		testing.expect(t, node == rb.iterator_get(&iter), "iterator/forward: get")
		visited += 1
	}
	testing.expect(t, visited == entry_count, "iterator/forward: visited")

	slice.reverse(inserted_keys[:])
	iter = rb.iterator(&tree, rb.Direction.Backward)
	visited = 0
	for node in rb.iterator_next(&iter) {
		k, idx := node.key, visited
		testing.expect(t, inserted_keys[idx] == k, "iterator/backward: key")
		visited += 1
	}
	testing.expect(t, visited == entry_count, "iterator/backward: visited")

	// Test removal (and on_remove callback)
	rand.shuffle(inserted_keys[:])
	callback_count := entry_count
	tree.user_data = &callback_count
	tree.on_remove = proc(key: Key, value: Value, user_data: rawptr) {
		(^int)(user_data)^ -= 1
	}
	for k, i in inserted_keys {
		node := rb.find(tree, k)
		testing.expect(t, node != nil, "remove: find (pre)")

		ok := rb.remove(&tree, k)
		testing.expect(t, ok, "remove: succeeds")
		testing.expect(t, entry_count - (i + 1) == rb.len(tree), "remove: len (post)")
		validate_rbtree(t, &tree)

		testing.expect(t, nil == rb.find(tree, k), "remove: find (post")
	}
	testing.expect(t, rb.len(tree)    == 0,   "remove: len should be 0")
	testing.expectf(t, callback_count == 0,   "remove: on_remove should've been called %v times, it was %v", entry_count, callback_count)
	testing.expect(t, rb.first(&tree) == nil, "remove: first should be nil")
	testing.expect(t, rb.last(&tree)  == nil, "remove: last should be nil")

	// Refill the tree.
	for k in inserted_keys {
		rb.find_or_insert(&tree, k, 42)
	}

	// Test that removing the node doesn't break the iterator.
	callback_count = entry_count
	iter = rb.iterator(&tree, rb.Direction.Forward)
	if node := rb.iterator_get(&iter); node != nil {
		k := node.key

		ok := rb.iterator_remove(&iter)
		testing.expect(t, ok, "iterator/remove: success")

		ok = rb.iterator_remove(&iter)
		testing.expect(t, !ok, "iterator/remove: redundant removes should fail")

		testing.expect(t, rb.find(tree, k)       == nil, "iterator/remove: node should be gone")
		testing.expect(t, rb.iterator_get(&iter) == nil, "iterator/remove: get should return nil")

		// Ensure that iterator_next still works.
		node, ok = rb.iterator_next(&iter)
		testing.expect(t, ok   == (rb.len(tree) > 0),  "iterator/remove: next should return false")
		testing.expect(t, node == rb.first(&tree),     "iterator/remove: next should return first")

		validate_rbtree(t, &tree)
	}
	testing.expect(t, rb.len(tree) == entry_count - 1, "iterator/remove: len should drop by 1")

	rb.destroy(&tree)
	testing.expect(t, rb.len(tree)    == 0, "destroy: len should be 0")
	testing.expectf(t, callback_count == 0, "remove: on_remove should've been called %v times, it was %v", entry_count, callback_count)

	// print_tree_node(tree._root)
	delete(inserted_map)
	delete(inserted_keys)
}

@(test)
test_rbtree :: proc(t: ^testing.T) {
	test_rbtree_integer(t, u16, u16)
}

print_tree_node :: proc(n: ^$N/rb.Node($Key, $Value), indent := 0) {
	if n == nil {
		fmt.println("<empty tree>")
		return
	}
	if n.right != nil {
		print_tree_node(n.right, indent + 1)
	}
	for _ in 0..<indent {
		fmt.printf("\t")
	}
	if n.color == .Black {
		fmt.printfln("%v", n.key)
	} else {
		fmt.printfln("<%v>", n.key)
	}
	if n.left != nil {
		print_tree_node(n.left, indent + 1)
	}
}

validate_rbtree :: proc(t: ^testing.T, tree: ^$T/rb.Tree($Key, $Value)) {
	verify_rbtree_propery_1(t, tree._root)
	verify_rbtree_propery_2(t, tree._root)
	/* Property 3 is implicit */
	verify_rbtree_propery_4(t, tree._root)
	verify_rbtree_propery_5(t, tree._root)
}

verify_rbtree_propery_1 :: proc(t: ^testing.T, n: ^$N/rb.Node($Key, $Value)) {
	testing.expect(t, rb.node_color(n) == .Black || rb.node_color(n) == .Red, "Property #1: Each node is either red or black.")
	if n == nil {
		return
	}
	verify_rbtree_propery_1(t, n._left)
	verify_rbtree_propery_1(t, n._right)
}

verify_rbtree_propery_2 :: proc(t: ^testing.T, root: ^$N/rb.Node($Key, $Value)) {
	testing.expect(t, rb.node_color(root) == .Black, "Property #2: Root node should be black.")
}

verify_rbtree_propery_4 :: proc(t: ^testing.T, n: ^$N/rb.Node($Key, $Value)) {
	if rb.node_color(n) == .Red {
		//  A red node's left, right and parent should be black
		all_black := rb.node_color(n._left) == .Black && rb.node_color(n._right) == .Black && rb.node_color(n._parent) == .Black
		testing.expect(t, all_black, "Property #3: Red node's children + parent must be black.")
	}
	if n == nil {
		return
	}
	verify_rbtree_propery_4(t, n._left)
	verify_rbtree_propery_4(t, n._right)
}

verify_rbtree_propery_5 :: proc(t: ^testing.T, root: ^$N/rb.Node($Key, $Value)) {
	black_count_path := -1
	verify_rbtree_propery_5_helper(t, root, 0, &black_count_path)
}
verify_rbtree_propery_5_helper :: proc(t: ^testing.T, n: ^$N/rb.Node($Key, $Value), black_count: int, path_black_count: ^int) {
	black_count := black_count

	if rb.node_color(n) == .Black {
		black_count += 1
	}
	if n == nil {
		if path_black_count^ == -1 {
			path_black_count^ = black_count
		} else {
			testing.expect(t, black_count == path_black_count^, "Property #5: Paths from a node to its leaves contain same black count.")
		}
		return
	}
	verify_rbtree_propery_5_helper(t, n._left,  black_count, path_black_count)
	verify_rbtree_propery_5_helper(t, n._right, black_count, path_black_count)
}
// Properties 4 and 5 together guarantee that no path in the tree is more than about twice as long as any other path,
// which guarantees that it has O(log n) height.