diff --git a/config.json b/config.json index 47ea5010625..e5ec4245e46 100644 --- a/config.json +++ b/config.json @@ -1064,6 +1064,20 @@ "uuid": "6b7f7b1f-c388-4f4c-b924-84535cc5e1a0", "slug": "hexadecimal", "deprecated": true + }, + { + "uuid": "6f196341-0ffc-9780-a7ca-1f817508247161cbcd9", + "slug": "binary-search-tree", + "core": false, + "unlocked_by": null, + "difficulty": 4, + "topics":[ + "data_structures", + "classes", + "trees", + "searching", + "object_oriented_programming" + ] } ], "foregone": [ diff --git a/exercises/binary-search-tree/README.md b/exercises/binary-search-tree/README.md new file mode 100644 index 00000000000..6681133f38f --- /dev/null +++ b/exercises/binary-search-tree/README.md @@ -0,0 +1,52 @@ +Insert and search for numbers in a binary tree. + +When we need to represent sorted data, an array does not make a good +data structure. + +Say we have the array `[1, 3, 4, 5]`, and we add 2 to it so it becomes +`[1, 3, 4, 5, 2]` now we must sort the entire array again! We can +improve on this by realizing that we only need to make space for the new +item `[1, nil, 3, 4, 5]`, and then adding the item in the space we +added. But this still requires us to shift many elements down by one. + +Binary Search Trees, however, can operate on sorted data much more +efficiently. + +A binary search tree consists of a series of connected nodes. Each node +contains a piece of data (e.g. the number 3), a variable named `left`, +and a variable named `right`. The `left` and `right` variables point at +`nil`, or other nodes. Since these other nodes in turn have other nodes +beneath them, we say that the left and right variables are pointing at +subtrees. All data in the left subtree is less than or equal to the +current node's data, and all data in the right subtree is greater than +the current node's data. + +For example, if we had a node containing the data 4, and we added the +data 2, our tree would look like this: + + 4 + / + 2 + +If we then added 6, it would look like this: + + 4 + / \ + 2 6 + +If we then added 3, it would look like this + + 4 + / \ + 2 6 + \ + 3 + +And if we then added 1, 5, and 7, it would look like this + + 4 + / \ + / \ + 2 6 + / \ / \ + 1 3 5 7 diff --git a/exercises/binary-search-tree/binary_search_tree.py b/exercises/binary-search-tree/binary_search_tree.py new file mode 100644 index 00000000000..60700f177a6 --- /dev/null +++ b/exercises/binary-search-tree/binary_search_tree.py @@ -0,0 +1,26 @@ +class Node: + def __init__(self): + pass + + +class BinarySearchTree: + def __init__(self): + pass + + def __base_function(self, num): + pass + + def __replace_node(self, num): + pass + + def contains(self, num): + pass + + def add(self, num): + pass + + def remove(self, num): + pass + + def size(self): + pass diff --git a/exercises/binary-search-tree/binary_search_tree_test.py b/exercises/binary-search-tree/binary_search_tree_test.py new file mode 100644 index 00000000000..6dc7698009e --- /dev/null +++ b/exercises/binary-search-tree/binary_search_tree_test.py @@ -0,0 +1,153 @@ +import unittest + +from binary_search_tree import BinarySearchTree + + +class BinarySearchTreeTests(unittest.TestCase): + def test_add_one_value_in_tree(self): + tree = BinarySearchTree() + self.assertEqual(tree.add(3), True) + self.assertEqual(tree.size(), 1) + self.assertEqual(tree.contains(3), True) + + def test_add_multiple_values_in_tree(self): + tree = BinarySearchTree() + self.assertEqual(tree.add(-1), True) + self.assertEqual(tree.add(50), True) + self.assertEqual(tree.add(-73), True) + self.assertEqual(tree.add(20), True) + self.assertEqual(tree.size(), 4) + self.assertEqual(tree.contains(-1), True) + self.assertEqual(tree.contains(50), True) + self.assertEqual(tree.contains(-73), True) + self.assertEqual(tree.contains(20), True) + + def test_add_repeated_values_in_tree(self): + tree = BinarySearchTree() + self.assertEqual(tree.add(2), True) + self.assertEqual(tree.add(3), True) + self.assertEqual(tree.add(4), True) + self.assertEqual(tree.add(2), False) + self.assertEqual(tree.add(1), True) + self.assertEqual(tree.add(1), False) + self.assertEqual(tree.add(1), False) + self.assertEqual(tree.add(2), False) + self.assertEqual(tree.add(3), False) + self.assertEqual(tree.add(4), False) + self.assertEqual(tree.add(5), True) + self.assertEqual(tree.size(), 5) + self.assertEqual(tree.contains(1), True) + self.assertEqual(tree.contains(2), True) + self.assertEqual(tree.contains(3), True) + self.assertEqual(tree.contains(4), True) + self.assertEqual(tree.contains(5), True) + + def test_remove_one_value_in_tree_with_no_elements(self): + tree = BinarySearchTree() + self.assertEqual(tree.remove(70), False) + self.assertEqual(tree.size(), 0) + self.assertEqual(tree.contains(70), False) + + def test_remove_multiple_values_in_tree_with_no_elements(self): + tree = BinarySearchTree() + self.assertEqual(tree.remove(70), False) + self.assertEqual(tree.remove(-25), False) + self.assertEqual(tree.size(), 0) + self.assertEqual(tree.contains(70), False) + self.assertEqual(tree.contains(-25), False) + + def test_remove_value_in_tree_with_one_element(self): + tree = BinarySearchTree() + tree.add(0) + self.assertEqual(tree.remove(0), True) + self.assertEqual(tree.size(), 0) + self.assertEqual(tree.contains(0), False) + + def test_remove_multiple_values_in_tree(self): + tree = BinarySearchTree() + tree.add(3) + tree.add(1) + tree.add(2) + tree.add(5) + tree.add(4) + self.assertEqual(tree.remove(3), True) + self.assertEqual(tree.remove(1), True) + self.assertEqual(tree.remove(5), True) + self.assertEqual(tree.remove(6), False) + self.assertEqual(tree.remove(7), False) + self.assertEqual(tree.size(), 2) + self.assertEqual(tree.contains(1), False) + self.assertEqual(tree.contains(2), True) + self.assertEqual(tree.contains(3), False) + self.assertEqual(tree.contains(4), True) + self.assertEqual(tree.contains(5), False) + self.assertEqual(tree.contains(6), False) + self.assertEqual(tree.contains(7), False) + + def test_contains_value_in_tree_with_no_elements(self): + tree = BinarySearchTree() + self.assertEqual(tree.contains(-123), False) + self.assertEqual(tree.contains(-0), False) + + def test_contains_value_in_tree_with_multiple_operations(self): + tree = BinarySearchTree() + tree.remove(1) + self.assertEqual(tree.contains(1), False) + tree.add(1) + self.assertEqual(tree.contains(1), True) + tree.remove(2) + self.assertEqual(tree.contains(2), False) + tree.add(3) + self.assertEqual(tree.contains(3), True) + tree.add(2) + self.assertEqual(tree.contains(2), True) + tree.add(4) + self.assertEqual(tree.contains(4), True) + tree.remove(1) + self.assertEqual(tree.contains(1), False) + tree.remove(1) + self.assertEqual(tree.contains(1), False) + tree.add(2) + self.assertEqual(tree.contains(2), True) + tree.add(5) + self.assertEqual(tree.contains(5), True) + tree.remove(2) + self.assertEqual(tree.contains(1), False) + self.assertEqual(tree.contains(2), False) + self.assertEqual(tree.contains(3), True) + self.assertEqual(tree.contains(4), True) + self.assertEqual(tree.contains(5), True) + + def test_size_in_tree_with_no_elements(self): + tree = BinarySearchTree() + self.assertEqual(tree.size(), 0) + + def test_size_in_tree_with_multiple_operations(self): + tree = BinarySearchTree() + self.assertEqual(tree.size(), 0) + tree.add(1) + self.assertEqual(tree.size(), 1) + tree.remove(1) + self.assertEqual(tree.size(), 0) + tree.remove(2) + self.assertEqual(tree.size(), 0) + tree.add(3) + self.assertEqual(tree.size(), 1) + tree.add(2) + self.assertEqual(tree.size(), 2) + tree.add(4) + self.assertEqual(tree.size(), 3) + tree.remove(1) + self.assertEqual(tree.size(), 3) + tree.remove(1) + self.assertEqual(tree.size(), 3) + tree.add(2) + self.assertEqual(tree.size(), 3) + tree.add(5) + self.assertEqual(tree.size(), 4) + tree.remove(2) + self.assertEqual(tree.size(), 3) + + +if __name__ == '__main__': + unittest.main() diff --git a/exercises/binary-search-tree/example.py b/exercises/binary-search-tree/example.py new file mode 100644 index 00000000000..70db692c484 --- /dev/null +++ b/exercises/binary-search-tree/example.py @@ -0,0 +1,68 @@ +class Node: + def __init__(self): + self.children = [None, None] + self.num = None + + +class BinarySearchTree: + def __init__(self): + self.__size = 0 + self.__root = Node() + + def __base_function(self, num): + node = self.__root + while (node.num is not None): + if node.num > num: + if node.children[0] is None: + node.children[0] = Node() + node = node.children[0] + elif node.num < num: + if node.children[1] is None: + node.children[1] = Node() + node = node.children[1] + else: + break + return node + + def __replace_node(self, node): + node.num = None + pos = -1 + + for i in range(len(node.children)): + if not (node.children[i] is None or node.children[i].num is None): + pos = i + + if not pos == -1: + child_node = node.children[pos] + n_pos = (pos + 1) % 2 + while not (child_node.children[n_pos] is None + or child_node.children[n_pos].num is None): + child_node = child_node.children[n_pos] + node.num = child_node.num + self.__replace_node(child_node) + + def contains(self, num): + node = self.__base_function(num) + if node.num is None: + return False + else: + return True + + def add(self, num): + node = self.__base_function(num) + if node.num is None: + node.num = num + self.__size += 1 + return True + return False + + def remove(self, num): + node = self.__base_function(num) + if node.num is not None: + self.__replace_node(node) + self.__size -= 1 + return True + return False + + def size(self): + return self.__size