-
Notifications
You must be signed in to change notification settings - Fork 94
Expand file tree
/
Copy pathmiguelHx.py
More file actions
342 lines (290 loc) · 11.3 KB
/
miguelHx.py
File metadata and controls
342 lines (290 loc) · 11.3 KB
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
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
"""Python Version 3.9.2
4.5 - Validate BST:
Implement a function to check if a binary tree
is a binary search tree.
"""
import unittest
from abc import abstractmethod
from collections import deque
from dataclasses import dataclass
from typing import Generic, TypeVar, Dict
from typing import Optional, Protocol, Deque
from typing import Generator, List, Iterator
T = TypeVar('T', bound='Comparable')
class Comparable(Protocol):
@abstractmethod
def __lt__(self, other: T) -> bool:
pass
@abstractmethod
def __gt__(self, other: T) -> bool:
pass
@abstractmethod
def __eq__(self, other: object) -> bool:
pass
@dataclass
class BTNode(Generic[T]):
val: T
depth: int = 0
left_child: 'Optional[BTNode[T]]' = None
right_child: 'Optional[BTNode[T]]' = None
@property
def children(self) -> 'List[Optional[BTNode[T]]]':
return [self.left_child, self.right_child]
def children_as_str(self) -> str:
return ', '.join(str(child.val) if child else '' for child in self.children)
def __str__(self) -> str:
return f'Node ({self.val}), children: {self.children_as_str()}'
class BTIterator(Iterator[T]):
def __init__(self, root: Optional[BTNode[T]]):
self.gen = self.in_order_traversal_generator(root)
def in_order_traversal_generator(self, node: Optional[BTNode[T]]) -> Generator[T, Optional[BTNode[T]], None]:
if not node:
raise StopIteration
if node.left_child:
yield from self.in_order_traversal_generator(node.left_child)
yield node.val
if node.right_child:
yield from self.in_order_traversal_generator(node.right_child)
def __next__(self) -> T:
return next(self.gen)
@dataclass
class BinaryTree(Generic[T]):
root: 'Optional[BTNode[T]]' = None
def insert_left(self, value: T) -> None:
if not self.root:
self.root = BTNode(value)
else:
self._insert_left(value, self.root, 1)
def _insert_left(self, value: T, curr_node: BTNode[T], curr_depth: int) -> None:
if not curr_node.left_child:
curr_node.left_child = BTNode(value, curr_depth)
elif not curr_node.right_child:
curr_node.right_child = BTNode(value, curr_depth)
elif value == curr_node.val:
raise ValueError(f'Value {value} already exists in tree.')
else:
self._insert_left(value, curr_node.left_child, curr_depth + 1)
def insert_right(self, value: T) -> None:
if not self.root:
self.root = BTNode(value)
else:
self._insert_right(value, self.root, 1)
def _insert_right(self, value: T, curr_node: BTNode[T], curr_depth: int) -> None:
if not curr_node.right_child:
curr_node.right_child = BTNode(value, curr_depth)
elif not curr_node.left_child:
curr_node.left_child = BTNode(value, curr_depth)
elif value == curr_node.val:
raise ValueError(f'Value {value} already exists in tree.')
else:
self._insert_right(value, curr_node.right_child, curr_depth + 1)
def height(self) -> int:
return self._height(self.root)
def _height(self, node: Optional[BTNode[T]]) -> int:
if not node:
return 0
else:
return 1 + max(self._height(node.left_child), self._height(node.right_child))
def print_tree(self) -> None:
if self.root:
self._print_tree(self.root)
def _print_tree(self, curr_node: Optional[BTNode[T]]) -> None:
if curr_node:
self._print_tree(curr_node.left_child)
print(curr_node.val)
self._print_tree(curr_node.right_child)
def __iter__(self) -> BTIterator[T]:
return BTIterator(self.root)
@dataclass
class BinarySearchTree(BinaryTree[T]):
def insert(self, value: T) -> None:
if not self.root:
self.root = BTNode(value)
else:
self._insert(value, self.root, 1)
def _insert(self, value: T, curr_node: BTNode[T], curr_depth: int) -> None:
if value < curr_node.val:
if not curr_node.left_child:
# insert here
curr_node.left_child = BTNode(value, curr_depth)
else:
# otherwise, keep searching left subtree
self._insert(value, curr_node.left_child, curr_depth + 1)
elif value > curr_node.val:
if not curr_node.right_child:
# insert here
curr_node.right_child = BTNode(value, curr_depth)
else:
# otherwise, keep searching right subtree
self._insert(value, curr_node.right_child, curr_depth + 1)
else:
raise ValueError(f'Value {value} already exists in tree.')
def _calculate_max_value_of_subtree(node: Optional[BTNode[T]], max_value: T) -> T:
"""This helper function will calculate the max value between
the current node's value and its subtree.
Args:
node (Optional[BTNode[T]]): a binary tree node
max_value (T): max value used to pass down to each node
Returns:
T: max value of left/right subtree and current node
"""
if not node:
return max_value
else:
return max(
node.val,
_calculate_max_value_of_subtree(node.left_child, max_value),
_calculate_max_value_of_subtree(node.right_child, max_value)
)
def _calculate_min_value_of_subtree(node: Optional[BTNode[T]], min_value: T) -> T:
"""Same as _calculate_max_value_of_subtree but min instead of max.
Args:
node (Optional[BTNode[T]]): binary tree node
min_value (T): min value used to pass to each node
Returns:
T: min value of left/right subtree and current node
"""
if not node:
return min_value
else:
return min(
node.val,
_calculate_min_value_of_subtree(node.left_child, min_value),
_calculate_min_value_of_subtree(node.right_child, min_value)
)
def _validate_bst(node: Optional[BTNode[T]]) -> bool:
"""Helper function to validate_bst. Difference is,
this function takes in a node rather than a binary tree
instance. This will handle the logic for checking if we have
a binary search tree or not.
Args:
node (Optional[BTNode[T]]): current node to validate left and right subtrees
Returns:
bool: True if bst properties are intact, False otherwise
"""
if not node:
# empty node is considered a binary search tree
return True
max_val_left_subtree = _calculate_max_value_of_subtree(node.left_child, node.val)
min_val_right_subtree = _calculate_min_value_of_subtree(node.right_child, node.val)
if max_val_left_subtree > node.val:
return False
if min_val_right_subtree < node.val:
return False
return _validate_bst(node.left_child) and _validate_bst(node.right_child)
def validate_bst(bt: BinaryTree[T]) -> bool:
"""This function will check if the input
binary tree is a binary search tree.
In order for a binary tree to be a binary search
tree, the following conditions must be true for every
node in the tree:
The left subtree of a node contains only nodes with keys
lesser than the node's key.
The right subtree of a node contains only nodes with keys
greater than the node's key.
The left and right subtree each must also be a binary search
tree.
Args:
bt (BinaryTree[T]): Input binary tree, we will start at root
Returns:
bool: True if tree is a binary search tree, False otherwise
"""
return _validate_bst(bt.root)
class TestBinarySearchTree(unittest.TestCase):
def test_binary_search_tree_creation_height_3(self) -> None:
bst: BinarySearchTree = BinarySearchTree()
bst.insert(8)
bst.insert(4)
bst.insert(10)
bst.insert(2)
bst.insert(6)
bst.insert(20)
self.assertEqual(list(bst), [2, 4, 6, 8, 10, 20])
self.assertEqual(bst.height(), 3)
def test_binary_search_tree_creation_height_4(self) -> None:
bst: BinarySearchTree = BinarySearchTree()
bst.insert(8)
bst.insert(2)
bst.insert(10)
bst.insert(4)
bst.insert(6)
bst.insert(20)
self.assertEqual(list(bst), [2, 4, 6, 8, 10, 20])
self.assertEqual(bst.height(), 4)
class TestValidateBST(unittest.TestCase):
def test_validate_bst_true_case(self) -> None:
bst: BinarySearchTree = BinarySearchTree()
bst.insert(8)
bst.insert(4)
bst.insert(10)
bst.insert(2)
bst.insert(6)
bst.insert(20)
self.assertEqual(list(bst), [2, 4, 6, 8, 10, 20])
self.assertEqual(bst.height(), 3)
result = validate_bst(bst)
self.assertEqual(result, True)
def test_validate_bst_false_case_left_subtree(self) -> None:
bt: BinaryTree = BinaryTree()
bt.insert_left(8)
bt.insert_left(4)
bt.insert_left(10)
bt.insert_left(2)
bt.insert_left(1000)
bt.insert_right(20)
bt.insert_right(9)
self.assertEqual(list(bt), [2, 4, 1000, 8, 9, 10, 20])
self.assertEqual(bt.height(), 3)
result = validate_bst(bt)
self.assertEqual(result, False)
def test_validate_bst_false_case_right_subtree(self) -> None:
bt: BinaryTree = BinaryTree()
bt.insert_left(8)
bt.insert_left(4)
bt.insert_left(10)
bt.insert_left(2)
bt.insert_left(1000)
bt.insert_right(-100)
bt.insert_right(9)
self.assertEqual(list(bt), [2, 4, 1000, 8, 9, 10, -100])
self.assertEqual(bt.height(), 3)
result = validate_bst(bt)
self.assertEqual(result, False)
def test_calculate_max_value_of_subtree(self) -> None:
bt: BinaryTree = BinaryTree()
bt.insert_left(8)
bt.insert_left(4)
bt.insert_left(10)
bt.insert_left(2)
bt.insert_left(1000)
bt.insert_right(20)
bt.insert_right(9)
# expected result of left subtree is be 1000
result = _calculate_max_value_of_subtree(bt.root.left_child, bt.root.left_child.val)
self.assertEqual(result, 1000)
# expected result of overall max value is still 1000
result = _calculate_max_value_of_subtree(bt.root, bt.root.val)
self.assertEqual(result, 1000)
# expected result of right subtree is 20
result = _calculate_max_value_of_subtree(bt.root.right_child, bt.root.right_child.val)
self.assertEqual(result, 20)
def test_calculate_min_value_of_subtree(self) -> None:
bt: BinaryTree = BinaryTree()
bt.insert_left(8)
bt.insert_left(4)
bt.insert_left(10)
bt.insert_left(2)
bt.insert_left(1000)
bt.insert_right(-100)
bt.insert_right(9)
# left subtree min should be 2
result = _calculate_min_value_of_subtree(bt.root.left_child, bt.root.left_child.val)
self.assertEqual(result, 2)
# overall min value should be -100
result = _calculate_min_value_of_subtree(bt.root, bt.root.val)
self.assertEqual(result, -100)
# right subtree min should be -100
result = _calculate_min_value_of_subtree(bt.root.right_child, bt.root.right_child.val)
self.assertEqual(result, -100)
if __name__ == '__main__':
unittest.main()