From 25a38e09daacacd864ef8242fb4b2b59355a967a Mon Sep 17 00:00:00 2001 From: Julien Wilson Date: Wed, 18 Jan 2017 13:56:19 -0800 Subject: [PATCH 01/34] Update .travis.yml --- .travis.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 3abf556..9556c32 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,7 +3,8 @@ language: python python: - "2.7" - "3.5" + install: - pip install -e .[test] -script: py.test +script: py.test src/test_bst.py From 97e30fc8508ee0091b775e3898841c3283e1664e Mon Sep 17 00:00:00 2001 From: Julien Wilson Date: Wed, 18 Jan 2017 13:57:56 -0800 Subject: [PATCH 02/34] Update README.md --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index d4d8ec5..81a891e 100755 --- a/README.md +++ b/README.md @@ -1,3 +1,5 @@ +[![Build Status](https://travis-ci.org/julienawilson/data-structures.svg?branch=bst)](https://travis-ci.org/julienawilson/data-structures) + # data-structures Patrick Saunders and Julien Wilson
From c8b6b90bd942c5d344e0696dff2210d4c817da20 Mon Sep 17 00:00:00 2001 From: Rick Valenzuela Date: Wed, 18 Jan 2017 14:05:26 -0800 Subject: [PATCH 03/34] added new methods to README and module doctstrings --- README.md | 5 +++++ src/bst.py | 5 +++++ 2 files changed, 10 insertions(+) diff --git a/README.md b/README.md index d4d8ec5..e5337ab 100755 --- a/README.md +++ b/README.md @@ -30,3 +30,8 @@ Methods include: Trees that are higher on the left than the right should return a positive value; trees that are higher on the right than the left should return a negative value; an ideally-balanced tree should return 0. +* in_order(self): Return a generator that returns each node value from in-order traversal. +* pre_order(self): Return a generator that returns each node value from pre-order traversal. +* post_order(self): Return a generator that returns each node value from post_order traversal. +* breadth_first(self): Return a generator returns each node value from breadth-first traversal. + diff --git a/src/bst.py b/src/bst.py index 1e2f3cc..b08ebb8 100644 --- a/src/bst.py +++ b/src/bst.py @@ -10,6 +10,11 @@ Trees that are higher on the left than the right should return a positive value; trees that are higher on the right than the left should return a negative value; an ideally-balanced tree should return 0. +in_order(self): Return a generator that returns each node value from in-order traversal. +pre_order(self): Return a generator that returns each node value from pre-order traversal. +post_order(self): Return a generator that returns each node value from post_order traversal. +breadth_first(self): Return a generator returns each node value from breadth-first traversal. + """ from queue import Queue From cb54d06aac08daeb6f0b88eb251ece7352930747 Mon Sep 17 00:00:00 2001 From: Rick Valenzuela Date: Wed, 18 Jan 2017 14:28:22 -0800 Subject: [PATCH 04/34] adding delete method --- src/bst.py | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/src/bst.py b/src/bst.py index b08ebb8..2dd6974 100644 --- a/src/bst.py +++ b/src/bst.py @@ -23,11 +23,12 @@ class Node(): """Node object for the binary search tree.""" - def __init__(self, value, left=None, right=None): + def __init__(self, value, left=None, right=None, parent=None): """Instantiate a node object.""" self.value = value self.left = left self.right = right + self.parent = parent class BinarySearchTree(): @@ -54,6 +55,7 @@ def insert(self, value): current_node = current_node.left else: current_node.left = Node(value) + current_node.left.parent = current_node self._size += 1 break elif value > current_node.value: @@ -61,6 +63,7 @@ def insert(self, value): current_node = current_node.right else: current_node.right = Node(value) + current_node.right.parent = current_node self._size += 1 break else: @@ -186,6 +189,7 @@ def post_order(self): yield peek_node last_node_vis = stack.pop() + def breadth_first(self): """Return a generator that yields tree values according to breadth first traversal.""" trav_list = Queue([self.root]) @@ -196,3 +200,14 @@ def breadth_first(self): if current_node.right: trav_list.enqueue(current_node.right) yield current_node + + def delete(self, value): + """Get rid of a node. Or at least its connection.""" + target_node = self.search(value) + if not (target_node.left or target_node.right): + if target_node.value > target_node.parent.value: + target_node.parent.right = None + target_node.parent = None + else: + target_node.parent.left = None + target_node.parent = None From 18d787744787f407352aa87759568838721d2f03 Mon Sep 17 00:00:00 2001 From: Rick Valenzuela Date: Wed, 18 Jan 2017 14:53:33 -0800 Subject: [PATCH 05/34] added 1-child node instances for delete --- src/bst.py | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/src/bst.py b/src/bst.py index 2dd6974..18641b1 100644 --- a/src/bst.py +++ b/src/bst.py @@ -204,10 +204,35 @@ def breadth_first(self): def delete(self, value): """Get rid of a node. Or at least its connection.""" target_node = self.search(value) + if not target_node: + return None if not (target_node.left or target_node.right): if target_node.value > target_node.parent.value: target_node.parent.right = None target_node.parent = None + self._size -= 1 else: target_node.parent.left = None target_node.parent = None + self._size -= 1 + elif not (target_node.left and target_node.right): + if target_node.left: + if target_node < target_node.parent: + target_node.left.parent = target_node.parent + target_node.parent.left = target_node.left + target_node.parent = None + target_node.left = None + self._size -= 1 + if target_node.right: + if target_node < target_node.parent: + target_node.right.parent = target_node.parent + target_node.parent.right = target_node.right + target_node.parent = None + target_node.right = None + self._size -= 1 + + + + + target_node.left.parent = target_node.parent.left + From 4ba611ab8092ed28d35301d08b3e884686e5aafc Mon Sep 17 00:00:00 2001 From: Julien Date: Wed, 18 Jan 2017 15:56:16 -0800 Subject: [PATCH 06/34] delete for node with two childs --- src/bst.py | 43 ++++++++++++++++++++++++++++++------------- 1 file changed, 30 insertions(+), 13 deletions(-) diff --git a/src/bst.py b/src/bst.py index 18641b1..79f5e76 100644 --- a/src/bst.py +++ b/src/bst.py @@ -189,7 +189,6 @@ def post_order(self): yield peek_node last_node_vis = stack.pop() - def breadth_first(self): """Return a generator that yields tree values according to breadth first traversal.""" trav_list = Queue([self.root]) @@ -220,19 +219,37 @@ def delete(self, value): if target_node < target_node.parent: target_node.left.parent = target_node.parent target_node.parent.left = target_node.left - target_node.parent = None - target_node.left = None - self._size -= 1 + else: + target_node.left.parent = target_node.parent + target_node.parent.right = target_node.left + self._size -= 1 + target_node.parent = None + target_node.left = None if target_node.right: if target_node < target_node.parent: + target_node.right.parent = target_node.parent + target_node.parent.left = target_node.right + else: target_node.right.parent = target_node.parent target_node.parent.right = target_node.right - target_node.parent = None - target_node.right = None - self._size -= 1 - - - - - target_node.left.parent = target_node.parent.left - + self._size -= 1 + target_node.parent = None + target_node.right = None + else: + del_node = self.search(value) + current_node = del_node.right + while current_node.left: + current_node = current_node.left + replace_node = current_node + self.delete(current_node) + if del_node.parent: + replace_node.parent = del_node.parent + if replace_node.value < del_node.value: + del_node.parent.left = replace_node + else: + del_node.parent.right = replace_node + replace_node.left = del_node.left + replace_node.right = del_node.right + del_node.parent = None + del_node.left = None + del_node.right = None From cf9a522d55fa125e77d99609c568e7596c7ef5fc Mon Sep 17 00:00:00 2001 From: Rick Valenzuela Date: Wed, 18 Jan 2017 15:56:36 -0800 Subject: [PATCH 07/34] added the easy tests for delete --- src/test_bst.py | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/src/test_bst.py b/src/test_bst.py index 9572c27..447be16 100644 --- a/src/test_bst.py +++ b/src/test_bst.py @@ -352,3 +352,24 @@ def test_bfs_weird_tree(weird_tree): for node in weird_tree.breadth_first(): bfs_list.append(node.value) assert bfs_list == [50, 44, 79, 2, 48, 80, 49, 83, 90, 100, 103, 102] + + +def test_delete_node_with_no_children(small_tree): + """Test calling delete on node with no children.""" + small_tree.delete(35) + assert small_tree.search(35) == None + + +def test_delete_node_with_no_children_annuls_parent_connection(small_tree): + """Test calling delete on node with no children kills parent's connection.""" + small_tree.delete(35) + assert small_tree.search(40).left is None + + +def test_delete_node_with_one_child_reassigns_connection(small_tree): + """Test deleting a node reassigns its one child to expected new parent.""" + small_tree.delete(40) + assert small_tree.search(35).parent.value == 50 + assert small_tree.search(50).left.value == 35 + +# def test_delete_node_w_ \ No newline at end of file From 1505ae8d057e3ea75b45753ad251dfe76ed952cc Mon Sep 17 00:00:00 2001 From: Julien Date: Wed, 18 Jan 2017 15:58:51 -0800 Subject: [PATCH 08/34] fix in the delete method --- src/bst.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bst.py b/src/bst.py index 79f5e76..a91f04f 100644 --- a/src/bst.py +++ b/src/bst.py @@ -226,7 +226,7 @@ def delete(self, value): target_node.parent = None target_node.left = None if target_node.right: - if target_node < target_node.parent: + if target_node.value < target_node.parent.value: target_node.right.parent = target_node.parent target_node.parent.left = target_node.right else: From 136cd502d1ba10d8d71b86b4f738c720202540c5 Mon Sep 17 00:00:00 2001 From: Julien Date: Wed, 18 Jan 2017 16:09:33 -0800 Subject: [PATCH 09/34] delete fix --- src/bst.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bst.py b/src/bst.py index a91f04f..b561815 100644 --- a/src/bst.py +++ b/src/bst.py @@ -216,7 +216,7 @@ def delete(self, value): self._size -= 1 elif not (target_node.left and target_node.right): if target_node.left: - if target_node < target_node.parent: + if target_node.value < target_node.parent.value: target_node.left.parent = target_node.parent target_node.parent.left = target_node.left else: From 2214ff2f93128b7cdd49949fd475db6c4986d706 Mon Sep 17 00:00:00 2001 From: Julien Date: Wed, 18 Jan 2017 16:22:38 -0800 Subject: [PATCH 10/34] fix in delete, more tests --- src/bst.py | 2 +- src/test_bst.py | 12 +++++++++++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/src/bst.py b/src/bst.py index b561815..0e4852e 100644 --- a/src/bst.py +++ b/src/bst.py @@ -241,7 +241,7 @@ def delete(self, value): while current_node.left: current_node = current_node.left replace_node = current_node - self.delete(current_node) + self.delete(current_node.value) if del_node.parent: replace_node.parent = del_node.parent if replace_node.value < del_node.value: diff --git a/src/test_bst.py b/src/test_bst.py index 447be16..456fb51 100644 --- a/src/test_bst.py +++ b/src/test_bst.py @@ -360,6 +360,12 @@ def test_delete_node_with_no_children(small_tree): assert small_tree.search(35) == None +def test_delete_node_with_no_children_update_size(small_tree): + """Test calling delete on node with no children.""" + small_tree.delete(35) + assert small_tree.size() == 5 + + def test_delete_node_with_no_children_annuls_parent_connection(small_tree): """Test calling delete on node with no children kills parent's connection.""" small_tree.delete(35) @@ -372,4 +378,8 @@ def test_delete_node_with_one_child_reassigns_connection(small_tree): assert small_tree.search(35).parent.value == 50 assert small_tree.search(50).left.value == 35 -# def test_delete_node_w_ \ No newline at end of file + +def test_delete_node_with_two_childs_updates_size(small_tree): + """Test that delete node with two childs updates size.""" + small_tree.delete(80) + assert small_tree.size() == 5 From dba1e8561376e082c652fa3590926a3ea51f6265 Mon Sep 17 00:00:00 2001 From: Julien Date: Thu, 19 Jan 2017 13:25:59 -0800 Subject: [PATCH 11/34] balance fix --- src/bst.py | 59 +++++++++++++++++++----------------------------------- 1 file changed, 21 insertions(+), 38 deletions(-) diff --git a/src/bst.py b/src/bst.py index 0e4852e..9151a1f 100644 --- a/src/bst.py +++ b/src/bst.py @@ -134,11 +134,11 @@ def contains(self, value): def balance(self): """Return numerical representation of how balanced the tree is.""" if self.root.left: - depth_left = self.depth(self.root.left) + depth_left = self.depth(self.root.left) + 1 else: depth_left = 0 if self.root.right: - depth_right = self.depth(self.root.right) + depth_right = self.depth(self.root.right) + 1 else: depth_right = 0 balance = depth_right - depth_left @@ -204,52 +204,35 @@ def delete(self, value): """Get rid of a node. Or at least its connection.""" target_node = self.search(value) if not target_node: - return None + return if not (target_node.left or target_node.right): if target_node.value > target_node.parent.value: target_node.parent.right = None - target_node.parent = None - self._size -= 1 else: target_node.parent.left = None - target_node.parent = None - self._size -= 1 elif not (target_node.left and target_node.right): if target_node.left: - if target_node.value < target_node.parent.value: - target_node.left.parent = target_node.parent - target_node.parent.left = target_node.left - else: - target_node.left.parent = target_node.parent - target_node.parent.right = target_node.left - self._size -= 1 - target_node.parent = None - target_node.left = None - if target_node.right: - if target_node.value < target_node.parent.value: - target_node.right.parent = target_node.parent - target_node.parent.left = target_node.right - else: - target_node.right.parent = target_node.parent - target_node.parent.right = target_node.right - self._size -= 1 - target_node.parent = None - target_node.right = None + target_node.left.parent = target_node.parent + target_node.parent.left = target_node.left + else: + target_node.right.parent = target_node.parent + target_node.parent.right = target_node.right else: - del_node = self.search(value) - current_node = del_node.right + current_node = target_node.right while current_node.left: current_node = current_node.left replace_node = current_node self.delete(current_node.value) - if del_node.parent: - replace_node.parent = del_node.parent - if replace_node.value < del_node.value: - del_node.parent.left = replace_node + self._size += 1 # undoes size change within delete + if target_node.parent: + replace_node.parent = target_node.parent + if replace_node.value < target_node.value: + target_node.parent.left = replace_node else: - del_node.parent.right = replace_node - replace_node.left = del_node.left - replace_node.right = del_node.right - del_node.parent = None - del_node.left = None - del_node.right = None + target_node.parent.right = replace_node + replace_node.left = target_node.left + replace_node.right = target_node.right + target_node.parent = None + target_node.left = None + target_node.right = None + self._size -= 1 From 621f7ff93cf664847ed41cc5a0f377c15e5a7de4 Mon Sep 17 00:00:00 2001 From: Rick Valenzuela Date: Thu, 19 Jan 2017 13:26:43 -0800 Subject: [PATCH 12/34] added tests for delete to check two-way connections of deleted; added test for contains --- src/test_bst.py | 38 ++++++++++++++++++++++++++++++++++++-- 1 file changed, 36 insertions(+), 2 deletions(-) diff --git a/src/test_bst.py b/src/test_bst.py index 447be16..f511f92 100644 --- a/src/test_bst.py +++ b/src/test_bst.py @@ -194,6 +194,11 @@ def test_contains_true_weird_tree_root(weird_tree): assert weird_tree.contains(50) is True +def test_contains_with_nonexistent_val_gt_root(small_tree): + """Test contains returns False when value is greater than root but node nonexistent.""" + assert small_tree.contains(99) is False + + def test_depth_on_small_tree(small_tree): """Test the size on a small Tree.""" assert small_tree.depth() == 2 @@ -213,6 +218,13 @@ def test_balance_on_weird_tree(weird_tree): """Test balance of smal tree fixture.""" assert weird_tree.balance() == 4 +def test_balance_w_no_left_nodes(): + b_tree = BinarySearchTree() + b_tree.insert(17) + b_tree.insert(43) + import pdb; pdb.set_trace() + assert b_tree.balance() == 1 + def test_inorder_no_nodes(): """Test in-order traversal on empty tree returns empty path.""" @@ -364,12 +376,34 @@ def test_delete_node_with_no_children_annuls_parent_connection(small_tree): """Test calling delete on node with no children kills parent's connection.""" small_tree.delete(35) assert small_tree.search(40).left is None + with pytest.raises(AttributeError): + assert small_tree.search(35).parent -def test_delete_node_with_one_child_reassigns_connection(small_tree): +def test_delete_node_with_no_children_annuls_own_connection(small_tree): + """Test calling delete on node with no children kills parent's connection.""" + small_tree.delete(35) + with pytest.raises(AttributeError): + assert small_tree.search(35).parent + + +def test_delete_node_with_one_child_reassigns_connections(small_tree): """Test deleting a node reassigns its one child to expected new parent.""" small_tree.delete(40) assert small_tree.search(35).parent.value == 50 assert small_tree.search(50).left.value == 35 -# def test_delete_node_w_ \ No newline at end of file + +def test_delete_node_annuls_own_connections(small_tree): + """Test calling delete on node kills parent and child connections.""" + small_tree.delete(40) + with pytest.raises(AttributeError): + assert small_tree.search(40).parent is None + with pytest.raises(AttributeError): + assert small_tree.search(40).left is None + + +def test_delete_updates_size(small_tree): + """Test that deleting a node updates tree's size.""" + small_tree.delete(40) + assert small_tree.size() == 5 From 95ec63663852ef19ec0cc7c36bb74c3f28f16b13 Mon Sep 17 00:00:00 2001 From: Rick Valenzuela Date: Thu, 19 Jan 2017 13:32:27 -0800 Subject: [PATCH 13/34] deleted corpse code --- src/test_bst.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/test_bst.py b/src/test_bst.py index b8aaf8f..e9cff62 100644 --- a/src/test_bst.py +++ b/src/test_bst.py @@ -222,7 +222,6 @@ def test_balance_w_no_left_nodes(): b_tree = BinarySearchTree() b_tree.insert(17) b_tree.insert(43) - import pdb; pdb.set_trace() assert b_tree.balance() == 1 From cc7ad6cc8411ffb327c61f41dad44f68e35b6b67 Mon Sep 17 00:00:00 2001 From: Rick Valenzuela Date: Thu, 19 Jan 2017 14:11:13 -0800 Subject: [PATCH 14/34] changed balance method so it can take non-root nodes as argument --- src/bst.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/bst.py b/src/bst.py index 9151a1f..0d5be77 100644 --- a/src/bst.py +++ b/src/bst.py @@ -131,14 +131,14 @@ def contains(self, value): else: return False - def balance(self): - """Return numerical representation of how balanced the tree is.""" - if self.root.left: - depth_left = self.depth(self.root.left) + 1 + def balance(self, node=self.root): + """Return numerical representation of how balanced the tree (or branches) is.""" + if node.left: + depth_left = self.depth(node.left) + 1 else: depth_left = 0 - if self.root.right: - depth_right = self.depth(self.root.right) + 1 + if node.right: + depth_right = self.depth(node.right) + 1 else: depth_right = 0 balance = depth_right - depth_left From 5e0c421ca589f89f3190e083055b05e1c12b6fde Mon Sep 17 00:00:00 2001 From: Rick Valenzuela Date: Thu, 19 Jan 2017 14:45:10 -0800 Subject: [PATCH 15/34] added autobalanc helper functions to rotate left/right --- src/bst.py | 40 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 39 insertions(+), 1 deletion(-) diff --git a/src/bst.py b/src/bst.py index 0d5be77..5db564d 100644 --- a/src/bst.py +++ b/src/bst.py @@ -131,8 +131,10 @@ def contains(self, value): else: return False - def balance(self, node=self.root): + def balance(self): """Return numerical representation of how balanced the tree (or branches) is.""" + if node is None: + node = self.root if node.left: depth_left = self.depth(node.left) + 1 else: @@ -144,6 +146,42 @@ def balance(self, node=self.root): balance = depth_right - depth_left return balance + + def autobalance(self, node=self.root): + """Make sure tree rebalances itself.""" + nodes = post_order() + this_node = next(nodes) + if abs(balance(this_node)) > 1: + + + def rebalance(self, node): + if balance(node) < -1 and balance(node.left) < 0: + + + + def rotate_right(self, node, holder_node=None): + """Helper function to shift nodes clockwise.""" + if node.left.right: + holder_node = node.left.right + node.left.right = node + node.parent = node.left + node.left.parent = None + node.left = holder_node + node.left.parent = node + + + def rotate_left(self, node, holder_node=None): + """Helper function to shift nodes counterclockwise.""" + if node.left.right: + holder_node = node.right.left + node.right.left = node + node.parent = node.right + node.right.parent = None + node.right = holder_node + node.right.parent = node + + + def in_order(self): """Return generator that returns tree values one at a time using in-order traversal.""" stack = [] From c63dc6d27888c8b5aa98d647ff2e64733f5d0256 Mon Sep 17 00:00:00 2001 From: Julien Date: Thu, 19 Jan 2017 15:16:06 -0800 Subject: [PATCH 16/34] tests for rotation --- src/bst.py | 35 +++++++++++++---------- src/test_bst.py | 74 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 94 insertions(+), 15 deletions(-) diff --git a/src/bst.py b/src/bst.py index 5db564d..53a7830 100644 --- a/src/bst.py +++ b/src/bst.py @@ -131,7 +131,7 @@ def contains(self, value): else: return False - def balance(self): + def balance(self, node=None): """Return numerical representation of how balanced the tree (or branches) is.""" if node is None: node = self.root @@ -146,20 +146,22 @@ def balance(self): balance = depth_right - depth_left return balance - - def autobalance(self, node=self.root): + def autobalance(self, node=None): """Make sure tree rebalances itself.""" - nodes = post_order() + if node is None: + node = self.root + nodes = self.post_order() this_node = next(nodes) - if abs(balance(this_node)) > 1: - + if abs(self.balance(this_node)) > 1: + pass def rebalance(self, node): - if balance(node) < -1 and balance(node.left) < 0: - + """Balance the given node.""" + if self.balance(node) < -1 and self.balance(node.left) < 0: + pass - - def rotate_right(self, node, holder_node=None): + # deleting 35 but no rotating anything + def rotate_right(self, node, holder_node=None): """Helper function to shift nodes clockwise.""" if node.left.right: holder_node = node.left.right @@ -167,8 +169,10 @@ def rotate_right(self, node, holder_node=None): node.parent = node.left node.left.parent = None node.left = holder_node - node.left.parent = node - + if holder_node: + node.left.parent = node + if node == self.root: + self.root = node.parent def rotate_left(self, node, holder_node=None): """Helper function to shift nodes counterclockwise.""" @@ -178,9 +182,10 @@ def rotate_left(self, node, holder_node=None): node.parent = node.right node.right.parent = None node.right = holder_node - node.right.parent = node - - + if holder_node: + node.right.parent = node + if node == self.root: + self.root = node.parent def in_order(self): """Return generator that returns tree values one at a time using in-order traversal.""" diff --git a/src/test_bst.py b/src/test_bst.py index e9cff62..ce02b82 100644 --- a/src/test_bst.py +++ b/src/test_bst.py @@ -419,3 +419,77 @@ def test_delete_node_with_two_childs_updates_size(small_tree): """Test that delete node with two childs updates size.""" small_tree.delete(80) assert small_tree.size() == 5 + + +def test_rotate_left_small_tree_assign_child(small_tree): + """Test that left rotation on small tree reassigns children.""" + small_tree.rotate_left(small_tree.root) + assert small_tree.search(80).left.value == 50 + + +def test_rotate_left_small_tree_assign_parent(small_tree): + """Test that left rotation on small tree reassigns parent.""" + small_tree.rotate_left(small_tree.root) + assert small_tree.search(50).parent.value == 80 + + +def test_rotate_left_reassigns_root(small_tree): + """Test the left rotation reassigns root.""" + small_tree.rotate_left(small_tree.root) + assert small_tree.root.value == 80 + + +def test_rotate_left_doesnt_reassign_root(small_tree): + """Test the left rotation does not reassign root.""" + small_tree.rotate_left(small_tree.search(80)) + assert small_tree.root.value == 50 + + +def test_rotate_left_small_tree_assign_parent_not_root(small_tree): + """Test that left rotation on small tree reassigns children.""" + small_tree.rotate_left(small_tree.search(80)) + assert small_tree.search(80).parent.value == 90 + + +def test_rotate_left_small_tree_assign_child_not_root(small_tree): + """Test that left rotation on small tree reassigns children.""" + small_tree.rotate_left(small_tree.search(80)) + assert small_tree.search(80).right is None + + +def test_rotate_right_small_tree_assign_child(small_tree): + """Test that right rotation on small tree reassigns children.""" + small_tree.rotate_right(small_tree.root) + assert small_tree.search(40).right.value == 50 + + +def test_rotate_right_small_tree_assign_parent(small_tree): + """Test that right rotation on small tree reassigns parent.""" + small_tree.rotate_right(small_tree.root) + assert small_tree.search(50).parent.value == 40 + + +def test_rotate_right_small_tree_assign_parent_not_root(small_tree): + """Test that right rotation on small tree reassigns children.""" + small_tree.rotate_right(small_tree.search(40)) + assert small_tree.search(40).parent.value == 35 + + +def test_rotate_right_small_tree_assign_child_not_root(small_tree): + """Test that right rotation on small tree reassigns children.""" + small_tree.rotate_right(small_tree.search(40)) + assert small_tree.search(40).left is None + + +def test_rotate_right_small_tree_assign_parent_child_not_root(small_tree): + """Test that right rotation on small tree reassigns children.""" + small_tree.rotate_right(small_tree.search(40)) + import pdb; pdb.set_trace() # deleting 35 but no rotating anything + assert small_tree.search(50).left.value == 35 + + +def test_rotate_right_small_tree_assign_right_child_not_root(small_tree): + """Test that right rotation on small tree reassigns children.""" + small_tree.rotate_right(small_tree.search(40)) + import pdb; pdb.set_trace() # deleting 35 but no rotating anything + assert small_tree.search(35).right is None From 98ec9d159beef8df5b17541ab24f0185d8f6bc68 Mon Sep 17 00:00:00 2001 From: Julien Date: Mon, 23 Jan 2017 16:47:17 -0800 Subject: [PATCH 17/34] bug in left right rotation --- src/bst.py | 69 +++++++++++++++++++++++++++++++++++++------------ src/test_bst.py | 14 +++++++--- 2 files changed, 63 insertions(+), 20 deletions(-) diff --git a/src/bst.py b/src/bst.py index 53a7830..4de75a9 100644 --- a/src/bst.py +++ b/src/bst.py @@ -68,6 +68,7 @@ def insert(self, value): break else: break + self.autobalance() def search(self, value): """Search the Binary Search Tree for a value, return node or none.""" @@ -131,9 +132,11 @@ def contains(self, value): else: return False - def balance(self, node=None): + def balance(self, node='root'): """Return numerical representation of how balanced the tree (or branches) is.""" if node is None: + return 0 + if node == 'root': node = self.root if node.left: depth_left = self.depth(node.left) + 1 @@ -148,26 +151,51 @@ def balance(self, node=None): def autobalance(self, node=None): """Make sure tree rebalances itself.""" + # import pdb; pdb.set_trace() if node is None: node = self.root nodes = self.post_order() - this_node = next(nodes) - if abs(self.balance(this_node)) > 1: - pass + while True: + try: + this_node = next(nodes) + except StopIteration: + break + if abs(self.balance(this_node)) > 1: + self.rebalance(this_node) + # pass def rebalance(self, node): """Balance the given node.""" - if self.balance(node) < -1 and self.balance(node.left) < 0: - pass + if self.balance(node) > 1: + if self.balance(node.right) >= 1: + self.rotate_left(node) + else: + self.rotate_right(node.right) + self.rotate_left(node) + elif self.balance(node) < 1: + if self.balance(node.left) >= 1: + self.rotate_right(node) + else: + self.rotate_left(node.left) + self.rotate_right(node) - # deleting 35 but no rotating anything - def rotate_right(self, node, holder_node=None): + + # deleting 35 but no rotating anything + def rotate_right(self, node, holder_node=None): """Helper function to shift nodes clockwise.""" - if node.left.right: - holder_node = node.left.right - node.left.right = node + if node is None: + return + try: + if node.left.right: + holder_node = node.left.right + except AttributeError: + pass + if node.left: + node.left.parent = node.parent + node.left.right = node + if node.parent: + node.parent.left = node.left node.parent = node.left - node.left.parent = None node.left = holder_node if holder_node: node.left.parent = node @@ -176,11 +204,20 @@ def rotate_right(self, node, holder_node=None): def rotate_left(self, node, holder_node=None): """Helper function to shift nodes counterclockwise.""" - if node.left.right: - holder_node = node.right.left - node.right.left = node + if node is None: + return + try: + if node.right.left: + holder_node = node.right.left + except AttributeError: + pass + + if node.right: + node.right.parent = node.parent + node.right.left = node + if node.parent: + node.parent.right = node.right node.parent = node.right - node.right.parent = None node.right = holder_node if holder_node: node.right.parent = node diff --git a/src/test_bst.py b/src/test_bst.py index ce02b82..25a4705 100644 --- a/src/test_bst.py +++ b/src/test_bst.py @@ -21,6 +21,8 @@ def small_tree(): @pytest.fixture() def weird_tree(): """Grow a small tree with five nodes.""" + import pdb; pdb.set_trace() + tree = BinarySearchTree() tree.insert(50) tree.insert(79) @@ -218,6 +220,7 @@ def test_balance_on_weird_tree(weird_tree): """Test balance of smal tree fixture.""" assert weird_tree.balance() == 4 + def test_balance_w_no_left_nodes(): b_tree = BinarySearchTree() b_tree.insert(17) @@ -367,6 +370,7 @@ def test_bfs_weird_tree(weird_tree): def test_delete_node_with_no_children(small_tree): """Test calling delete on node with no children.""" + import pdb; pdb.set_trace() small_tree.delete(35) assert small_tree.search(35) == None @@ -412,11 +416,12 @@ def test_delete_node_annuls_own_connections(small_tree): def test_delete_updates_size(small_tree): """Test that deleting a node updates tree's size.""" small_tree.delete(40) - assert small_tree.size() == 5 + assert small_tree.size() == 5 def test_delete_node_with_two_childs_updates_size(small_tree): """Test that delete node with two childs updates size.""" + import pdb; pdb.set_trace() small_tree.delete(80) assert small_tree.size() == 5 @@ -483,13 +488,14 @@ def test_rotate_right_small_tree_assign_child_not_root(small_tree): def test_rotate_right_small_tree_assign_parent_child_not_root(small_tree): """Test that right rotation on small tree reassigns children.""" + import pdb; pdb.set_trace() small_tree.rotate_right(small_tree.search(40)) - import pdb; pdb.set_trace() # deleting 35 but no rotating anything + # import pdb; pdb.set_trace() # deleting 35 but no rotating anything assert small_tree.search(50).left.value == 35 def test_rotate_right_small_tree_assign_right_child_not_root(small_tree): """Test that right rotation on small tree reassigns children.""" small_tree.rotate_right(small_tree.search(40)) - import pdb; pdb.set_trace() # deleting 35 but no rotating anything - assert small_tree.search(35).right is None + # import pdb; pdb.set_trace() # deleting 35 but no rotating anything + assert small_tree.search(35).left is None From b26a465cbfaef954600986be93bfd81be5e09885 Mon Sep 17 00:00:00 2001 From: Julien Date: Mon, 23 Jan 2017 17:26:22 -0800 Subject: [PATCH 18/34] true/false switch for autobalancing tree --- src/bst.py | 5 +++-- src/test_bst.py | 41 +++++++++++++++++++++-------------------- 2 files changed, 24 insertions(+), 22 deletions(-) diff --git a/src/bst.py b/src/bst.py index 4de75a9..96d8503 100644 --- a/src/bst.py +++ b/src/bst.py @@ -42,7 +42,7 @@ def __init__(self): self._size = 0 self.root = None - def insert(self, value): + def insert(self, value, autobalance=True): """Insert a value in to the binary search tree.""" if self.root is None: self.root = Node(value) @@ -68,7 +68,8 @@ def insert(self, value): break else: break - self.autobalance() + if autobalance: + self.autobalance() def search(self, value): """Search the Binary Search Tree for a value, return node or none.""" diff --git a/src/test_bst.py b/src/test_bst.py index 25a4705..7040cbd 100644 --- a/src/test_bst.py +++ b/src/test_bst.py @@ -21,21 +21,21 @@ def small_tree(): @pytest.fixture() def weird_tree(): """Grow a small tree with five nodes.""" - import pdb; pdb.set_trace() + # import pdb; pdb.set_trace() tree = BinarySearchTree() - tree.insert(50) - tree.insert(79) - tree.insert(80) - tree.insert(83) - tree.insert(90) - tree.insert(100) - tree.insert(44) - tree.insert(48) - tree.insert(49) - tree.insert(103) - tree.insert(2) - tree.insert(102) + tree.insert(50, autobalance=False) + tree.insert(79, autobalance=False) + tree.insert(80, autobalance=False) + tree.insert(83, autobalance=False) + tree.insert(90, autobalance=False) + tree.insert(100, autobalance=False) + tree.insert(44, autobalance=False) + tree.insert(48, autobalance=False) + tree.insert(49, autobalance=False) + tree.insert(103, autobalance=False) + tree.insert(2, autobalance=False) + tree.insert(102, autobalance=False) return tree @@ -110,25 +110,26 @@ def test_inserting_higher_val_pushes_right(): def test_inserting_less_but_more_into_populated_tree(small_tree): """Test inserting lower value that would push left then right.""" - small_tree.insert(43) + small_tree.insert(43, autobalance=False) assert small_tree.root.left.right.value == 43 def test_inserting_lower_item_into_populated_tree(small_tree): """Test inserting value that pushes all the way left.""" - small_tree.insert(33) + small_tree.insert(33, autobalance=False) + import pdb; pdb.set_trace() assert small_tree.root.left.left.left.value == 33 def test_insert_to_small_tree_updates_size(small_tree): """Test that insert on small tree increments size.""" - small_tree.insert(43) + small_tree.insert(43, autobalance=False) assert small_tree._size == 7 def test_insert_to_small_tree_existing_num(small_tree): """Test that inserting existing number doesn't change size.""" - small_tree.insert(40) + small_tree.insert(40, autobalance=False) assert small_tree.size() == 6 @@ -370,7 +371,7 @@ def test_bfs_weird_tree(weird_tree): def test_delete_node_with_no_children(small_tree): """Test calling delete on node with no children.""" - import pdb; pdb.set_trace() + # import pdb; pdb.set_trace() small_tree.delete(35) assert small_tree.search(35) == None @@ -421,7 +422,7 @@ def test_delete_updates_size(small_tree): def test_delete_node_with_two_childs_updates_size(small_tree): """Test that delete node with two childs updates size.""" - import pdb; pdb.set_trace() + # import pdb; pdb.set_trace() small_tree.delete(80) assert small_tree.size() == 5 @@ -488,7 +489,7 @@ def test_rotate_right_small_tree_assign_child_not_root(small_tree): def test_rotate_right_small_tree_assign_parent_child_not_root(small_tree): """Test that right rotation on small tree reassigns children.""" - import pdb; pdb.set_trace() + # import pdb; pdb.set_trace() small_tree.rotate_right(small_tree.search(40)) # import pdb; pdb.set_trace() # deleting 35 but no rotating anything assert small_tree.search(50).left.value == 35 From 99a354c4755e03ed47c304f4cdc599dbcfb7b262 Mon Sep 17 00:00:00 2001 From: Julien Date: Tue, 24 Jan 2017 13:46:04 -0800 Subject: [PATCH 19/34] fixed the rotations bug. small operator error. --- src/bst.py | 7 ++----- src/test_bst.py | 25 ++++++++++++++++--------- 2 files changed, 18 insertions(+), 14 deletions(-) diff --git a/src/bst.py b/src/bst.py index 96d8503..a88799a 100644 --- a/src/bst.py +++ b/src/bst.py @@ -152,7 +152,6 @@ def balance(self, node='root'): def autobalance(self, node=None): """Make sure tree rebalances itself.""" - # import pdb; pdb.set_trace() if node is None: node = self.root nodes = self.post_order() @@ -173,15 +172,13 @@ def rebalance(self, node): else: self.rotate_right(node.right) self.rotate_left(node) - elif self.balance(node) < 1: - if self.balance(node.left) >= 1: + elif self.balance(node) < -1: + if self.balance(node.left) <= -1: self.rotate_right(node) else: self.rotate_left(node.left) self.rotate_right(node) - - # deleting 35 but no rotating anything def rotate_right(self, node, holder_node=None): """Helper function to shift nodes clockwise.""" if node is None: diff --git a/src/test_bst.py b/src/test_bst.py index 7040cbd..dc139ad 100644 --- a/src/test_bst.py +++ b/src/test_bst.py @@ -21,8 +21,6 @@ def small_tree(): @pytest.fixture() def weird_tree(): """Grow a small tree with five nodes.""" - # import pdb; pdb.set_trace() - tree = BinarySearchTree() tree.insert(50, autobalance=False) tree.insert(79, autobalance=False) @@ -117,7 +115,6 @@ def test_inserting_less_but_more_into_populated_tree(small_tree): def test_inserting_lower_item_into_populated_tree(small_tree): """Test inserting value that pushes all the way left.""" small_tree.insert(33, autobalance=False) - import pdb; pdb.set_trace() assert small_tree.root.left.left.left.value == 33 @@ -223,6 +220,7 @@ def test_balance_on_weird_tree(weird_tree): def test_balance_w_no_left_nodes(): + """Test the balance of a tree with only a root and its right child.""" b_tree = BinarySearchTree() b_tree.insert(17) b_tree.insert(43) @@ -371,7 +369,6 @@ def test_bfs_weird_tree(weird_tree): def test_delete_node_with_no_children(small_tree): """Test calling delete on node with no children.""" - # import pdb; pdb.set_trace() small_tree.delete(35) assert small_tree.search(35) == None @@ -404,7 +401,6 @@ def test_delete_node_with_one_child_reassigns_connections(small_tree): assert small_tree.search(50).left.value == 35 - def test_delete_node_annuls_own_connections(small_tree): """Test calling delete on node kills parent and child connections.""" small_tree.delete(40) @@ -422,7 +418,6 @@ def test_delete_updates_size(small_tree): def test_delete_node_with_two_childs_updates_size(small_tree): """Test that delete node with two childs updates size.""" - # import pdb; pdb.set_trace() small_tree.delete(80) assert small_tree.size() == 5 @@ -489,14 +484,26 @@ def test_rotate_right_small_tree_assign_child_not_root(small_tree): def test_rotate_right_small_tree_assign_parent_child_not_root(small_tree): """Test that right rotation on small tree reassigns children.""" - # import pdb; pdb.set_trace() small_tree.rotate_right(small_tree.search(40)) - # import pdb; pdb.set_trace() # deleting 35 but no rotating anything assert small_tree.search(50).left.value == 35 def test_rotate_right_small_tree_assign_right_child_not_root(small_tree): """Test that right rotation on small tree reassigns children.""" small_tree.rotate_right(small_tree.search(40)) - # import pdb; pdb.set_trace() # deleting 35 but no rotating anything assert small_tree.search(35).left is None + + +def test_tree_autobalances(): + """Test that a right skewed tree is balanced after many insertions.""" + tree = BinarySearchTree() + tree.insert(50) + tree.insert(60) + tree.insert(70) + tree.insert(80) + tree.insert(90) + tree.insert(100) + tree.insert(66) + tree.insert(59) + tree.insert(89) + assert abs(tree.balance()) <= 1 From 61c5e7d2a85d724748c4e24e585275dab9333861 Mon Sep 17 00:00:00 2001 From: Rick Valenzuela Date: Tue, 24 Jan 2017 13:48:19 -0800 Subject: [PATCH 20/34] adding to docstrings --- src/bst.py | 27 ++++++++++++++------------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/src/bst.py b/src/bst.py index 4de75a9..a5af578 100644 --- a/src/bst.py +++ b/src/bst.py @@ -1,19 +1,20 @@ """Classes for binary search tree. Methods include: -insert(self, val): Insert value into tree; if value already exists, ignore it. -search(self, val): Return node containing that value, else None. -size(self): Return number of nodes/vertices in tree, 0 if empty. -depth(self): Return number of levels in tree. Tree with one value has depth of 0. -contains(self, val): Return True if value is in tree, False if not. -balance(self): Return a positive or negative integer representing tree's balance. +insert(val): Insert value into tree; if value already exists, ignore it. Method autobalances after insertion. +search(val): Return node containing that value, else None. +size(): Return number of nodes/vertices in tree, 0 if empty. +depth(): Return number of levels in tree. Tree with one value has depth of 0. +contains(val): Return True if value is in tree, False if not. +balance(): Return a positive or negative integer representing tree's balance. Trees that are higher on the left than the right should return a positive value; trees that are higher on the right than the left should return a negative value; an ideally-balanced tree should return 0. -in_order(self): Return a generator that returns each node value from in-order traversal. -pre_order(self): Return a generator that returns each node value from pre-order traversal. -post_order(self): Return a generator that returns each node value from post_order traversal. -breadth_first(self): Return a generator returns each node value from breadth-first traversal. +in_order(): Return a generator that returns each node value from in-order traversal. +pre_order(): Return a generator that returns each node value from pre-order traversal. +post_order(): Return a generator that returns each node value from post_order traversal. +breadth_first(): Return a generator returns each node value from breadth-first traversal. +delete(value): Delete a node's connections (edges), effectively deleting node. """ @@ -151,17 +152,17 @@ def balance(self, node='root'): def autobalance(self, node=None): """Make sure tree rebalances itself.""" - # import pdb; pdb.set_trace() if node is None: node = self.root nodes = self.post_order() while True: try: this_node = next(nodes) + if abs(self.balance(this_node)) > 1: + self.rebalance(this_node) except StopIteration: break - if abs(self.balance(this_node)) > 1: - self.rebalance(this_node) + # pass def rebalance(self, node): From 2f8af436a8271e1a77876030b065993c23274041 Mon Sep 17 00:00:00 2001 From: Rick Valenzuela Date: Tue, 24 Jan 2017 14:01:58 -0800 Subject: [PATCH 21/34] added docstrings, made autobalance, rebalance and rotations into hidden methods --- README.md | 5 ++++- src/bst.py | 33 +++++++++++++++++++-------------- src/test_bst.py | 40 ++++++++++++++++++++-------------------- 3 files changed, 43 insertions(+), 35 deletions(-) diff --git a/README.md b/README.md index 26db65c..578c691 100755 --- a/README.md +++ b/README.md @@ -23,7 +23,7 @@ A linked list that points in both directions A tree of nodes sorted by values less than and greater than root branching to the left and right, respectively. Methods include: -* insert(self, val): Insert value into tree; if value already exists, ignore it. +* insert(self, val): Insert value into tree; if value already exists, ignore it. Method autobalances after insertion, and tree size increments by one. * search(self, val): Return node containing that value, else None. * size(self): Return number of nodes/vertices in tree, 0 if empty. * depth(self): Return number of levels in tree. Tree with one value has depth of 0. @@ -36,4 +36,7 @@ Methods include: * pre_order(self): Return a generator that returns each node value from pre-order traversal. * post_order(self): Return a generator that returns each node value from post_order traversal. * breadth_first(self): Return a generator returns each node value from breadth-first traversal. +* delete(value): Delete a node's connections (edges), effectively deleting node. Method autobalances after deletion, and tree size decrements by one. + + diff --git a/src/bst.py b/src/bst.py index 7a5ea1e..c87e934 100644 --- a/src/bst.py +++ b/src/bst.py @@ -1,7 +1,8 @@ """Classes for binary search tree. Methods include: -insert(val): Insert value into tree; if value already exists, ignore it. Method autobalances after insertion. +insert(val): Insert value into tree; if value already exists, ignore it. + Method autobalances after insertion, and tree size increments by one. search(val): Return node containing that value, else None. size(): Return number of nodes/vertices in tree, 0 if empty. depth(): Return number of levels in tree. Tree with one value has depth of 0. @@ -15,6 +16,7 @@ post_order(): Return a generator that returns each node value from post_order traversal. breadth_first(): Return a generator returns each node value from breadth-first traversal. delete(value): Delete a node's connections (edges), effectively deleting node. + Method autobalances after deletion, and tree size decrements by one. """ @@ -70,7 +72,7 @@ def insert(self, value, autobalance=True): else: break if autobalance: - self.autobalance() + self._autobalance() def search(self, value): """Search the Binary Search Tree for a value, return node or none.""" @@ -151,7 +153,7 @@ def balance(self, node='root'): balance = depth_right - depth_left return balance - def autobalance(self, node=None): + def _autobalance(self, node=None): """Make sure tree rebalances itself.""" if node is None: node = self.root @@ -160,28 +162,28 @@ def autobalance(self, node=None): try: this_node = next(nodes) if abs(self.balance(this_node)) > 1: - self.rebalance(this_node) + self._rebalance(this_node) except StopIteration: break # pass - def rebalance(self, node): + def _rebalance(self, node): """Balance the given node.""" if self.balance(node) > 1: if self.balance(node.right) >= 1: - self.rotate_left(node) + self._rotate_left(node) else: - self.rotate_right(node.right) - self.rotate_left(node) + self._rotate_right(node.right) + self._rotate_left(node) elif self.balance(node) < -1: if self.balance(node.left) <= -1: - self.rotate_right(node) + self._rotate_right(node) else: - self.rotate_left(node.left) - self.rotate_right(node) + self._rotate_left(node.left) + self._rotate_right(node) - def rotate_right(self, node, holder_node=None): + def _rotate_right(self, node, holder_node=None): """Helper function to shift nodes clockwise.""" if node is None: return @@ -202,7 +204,7 @@ def rotate_right(self, node, holder_node=None): if node == self.root: self.root = node.parent - def rotate_left(self, node, holder_node=None): + def _rotate_left(self, node, holder_node=None): """Helper function to shift nodes counterclockwise.""" if node is None: return @@ -280,7 +282,7 @@ def breadth_first(self): trav_list.enqueue(current_node.right) yield current_node - def delete(self, value): + def delete(self, value, autobalance=True): """Get rid of a node. Or at least its connection.""" target_node = self.search(value) if not target_node: @@ -316,3 +318,6 @@ def delete(self, value): target_node.left = None target_node.right = None self._size -= 1 + if autobalance: + self._autobalance() + diff --git a/src/test_bst.py b/src/test_bst.py index dc139ad..1aa7243 100644 --- a/src/test_bst.py +++ b/src/test_bst.py @@ -369,19 +369,19 @@ def test_bfs_weird_tree(weird_tree): def test_delete_node_with_no_children(small_tree): """Test calling delete on node with no children.""" - small_tree.delete(35) + small_tree.delete(35, autobalance=False) assert small_tree.search(35) == None def test_delete_node_with_no_children_update_size(small_tree): """Test calling delete on node with no children.""" - small_tree.delete(35) + small_tree.delete(35, autobalance=False) assert small_tree.size() == 5 def test_delete_node_with_no_children_annuls_parent_connection(small_tree): """Test calling delete on node with no children kills parent's connection.""" - small_tree.delete(35) + small_tree.delete(35, autobalance=False) assert small_tree.search(40).left is None with pytest.raises(AttributeError): assert small_tree.search(35).parent @@ -389,21 +389,21 @@ def test_delete_node_with_no_children_annuls_parent_connection(small_tree): def test_delete_node_with_no_children_annuls_own_connection(small_tree): """Test calling delete on node with no children kills parent's connection.""" - small_tree.delete(35) + small_tree.delete(35, autobalance=False) with pytest.raises(AttributeError): assert small_tree.search(35).parent def test_delete_node_with_one_child_reassigns_connections(small_tree): """Test deleting a node reassigns its one child to expected new parent.""" - small_tree.delete(40) + small_tree.delete(40, autobalance=False) assert small_tree.search(35).parent.value == 50 assert small_tree.search(50).left.value == 35 def test_delete_node_annuls_own_connections(small_tree): """Test calling delete on node kills parent and child connections.""" - small_tree.delete(40) + small_tree.delete(40, autobalance=False) with pytest.raises(AttributeError): assert small_tree.search(40).parent is None with pytest.raises(AttributeError): @@ -412,85 +412,85 @@ def test_delete_node_annuls_own_connections(small_tree): def test_delete_updates_size(small_tree): """Test that deleting a node updates tree's size.""" - small_tree.delete(40) + small_tree.delete(40, autobalance=False) assert small_tree.size() == 5 def test_delete_node_with_two_childs_updates_size(small_tree): """Test that delete node with two childs updates size.""" - small_tree.delete(80) + small_tree.delete(80, autobalance=False) assert small_tree.size() == 5 def test_rotate_left_small_tree_assign_child(small_tree): """Test that left rotation on small tree reassigns children.""" - small_tree.rotate_left(small_tree.root) + small_tree._rotate_left(small_tree.root) assert small_tree.search(80).left.value == 50 def test_rotate_left_small_tree_assign_parent(small_tree): """Test that left rotation on small tree reassigns parent.""" - small_tree.rotate_left(small_tree.root) + small_tree._rotate_left(small_tree.root) assert small_tree.search(50).parent.value == 80 def test_rotate_left_reassigns_root(small_tree): """Test the left rotation reassigns root.""" - small_tree.rotate_left(small_tree.root) + small_tree._rotate_left(small_tree.root) assert small_tree.root.value == 80 def test_rotate_left_doesnt_reassign_root(small_tree): """Test the left rotation does not reassign root.""" - small_tree.rotate_left(small_tree.search(80)) + small_tree._rotate_left(small_tree.search(80)) assert small_tree.root.value == 50 def test_rotate_left_small_tree_assign_parent_not_root(small_tree): """Test that left rotation on small tree reassigns children.""" - small_tree.rotate_left(small_tree.search(80)) + small_tree._rotate_left(small_tree.search(80)) assert small_tree.search(80).parent.value == 90 def test_rotate_left_small_tree_assign_child_not_root(small_tree): """Test that left rotation on small tree reassigns children.""" - small_tree.rotate_left(small_tree.search(80)) + small_tree._rotate_left(small_tree.search(80)) assert small_tree.search(80).right is None def test_rotate_right_small_tree_assign_child(small_tree): """Test that right rotation on small tree reassigns children.""" - small_tree.rotate_right(small_tree.root) + small_tree._rotate_right(small_tree.root) assert small_tree.search(40).right.value == 50 def test_rotate_right_small_tree_assign_parent(small_tree): """Test that right rotation on small tree reassigns parent.""" - small_tree.rotate_right(small_tree.root) + small_tree._rotate_right(small_tree.root) assert small_tree.search(50).parent.value == 40 def test_rotate_right_small_tree_assign_parent_not_root(small_tree): """Test that right rotation on small tree reassigns children.""" - small_tree.rotate_right(small_tree.search(40)) + small_tree._rotate_right(small_tree.search(40)) assert small_tree.search(40).parent.value == 35 def test_rotate_right_small_tree_assign_child_not_root(small_tree): """Test that right rotation on small tree reassigns children.""" - small_tree.rotate_right(small_tree.search(40)) + small_tree._rotate_right(small_tree.search(40)) assert small_tree.search(40).left is None def test_rotate_right_small_tree_assign_parent_child_not_root(small_tree): """Test that right rotation on small tree reassigns children.""" - small_tree.rotate_right(small_tree.search(40)) + small_tree._rotate_right(small_tree.search(40)) assert small_tree.search(50).left.value == 35 def test_rotate_right_small_tree_assign_right_child_not_root(small_tree): """Test that right rotation on small tree reassigns children.""" - small_tree.rotate_right(small_tree.search(40)) + small_tree._rotate_right(small_tree.search(40)) assert small_tree.search(35).left is None From ae697bd0e90cff5309a3a365584241bc2a2c167a Mon Sep 17 00:00:00 2001 From: Julien Date: Wed, 25 Jan 2017 08:35:27 -0800 Subject: [PATCH 22/34] hash table --- src/hash_table.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 src/hash_table.py diff --git a/src/hash_table.py b/src/hash_table.py new file mode 100644 index 0000000..e69de29 From 14197b95503facbaf44b99040c71170b7e82abff Mon Sep 17 00:00:00 2001 From: Rick Valenzuela Date: Wed, 25 Jan 2017 12:34:21 -0800 Subject: [PATCH 23/34] added pseudocode-ish addititve hash --- README.md | 1 - src/hash_table.py | 16 ++++++++++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 9ee31d0..0da3d09 100755 --- a/README.md +++ b/README.md @@ -1,4 +1,3 @@ -<<<<<<< HEAD [![Build Status](https://travis-ci.org/julienawilson/data-structures.svg?branch=master)](https://travis-ci.org/julienawilson/data-structures) # Data Structures diff --git a/src/hash_table.py b/src/hash_table.py index e69de29..c575a4f 100644 --- a/src/hash_table.py +++ b/src/hash_table.py @@ -0,0 +1,16 @@ +"""Class for hash tables.""" + +class HashTable(buckets, hash=additive): + """Something something.""" + def __init__(self): + self.bins = [dict for bucket in range(buckets)] + + def _hash(self, additive, word): + self.additive = additive + self.word = word + + additive = sum([ord(char) for char in list(word)]) % len(buckets) + + + + From 366131f44b2115522d142cfc91892fbff1070908 Mon Sep 17 00:00:00 2001 From: Julien Date: Wed, 25 Jan 2017 12:50:25 -0800 Subject: [PATCH 24/34] add hash func written --- src/hash_table.py | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/src/hash_table.py b/src/hash_table.py index c575a4f..ac69093 100644 --- a/src/hash_table.py +++ b/src/hash_table.py @@ -1,16 +1,23 @@ """Class for hash tables.""" -class HashTable(buckets, hash=additive): - """Something something.""" - def __init__(self): - self.bins = [dict for bucket in range(buckets)] - def _hash(self, additive, word): - self.additive = additive - self.word = word +class HashTable(object): + """Something something.""" - additive = sum([ord(char) for char in list(word)]) % len(buckets) + def __init__(self, size, hash_alg='additive'): + """Initialize a hash table.""" + self._size = size + self.buckets = [[] for bucket in range(self._size)] + self._hash_alg = hash_alg + # def _hash(self, hash_alg, word): + # if + # self.word = word + # additive = sum([ord(char) for char in list(word)]) % len(buckets) + def _add_hash(self, word): + """Return Additive hash value.""" + return sum([ord(char) for char in list(word)]) % self._size + # def set(self, key, value) \ No newline at end of file From 577725b1fedf72724e99f3d881e26e8f02aebcdf Mon Sep 17 00:00:00 2001 From: Rick Valenzuela Date: Wed, 25 Jan 2017 12:51:39 -0800 Subject: [PATCH 25/34] added blank test file for hash table module --- src/test_hash_table.py | 1 + 1 file changed, 1 insertion(+) create mode 100644 src/test_hash_table.py diff --git a/src/test_hash_table.py b/src/test_hash_table.py new file mode 100644 index 0000000..28e8635 --- /dev/null +++ b/src/test_hash_table.py @@ -0,0 +1 @@ +"""Test for our implemtation of hash tables.""" From 5ca754b9c11f9310aeb8dc52c43c5e04a3e3bda6 Mon Sep 17 00:00:00 2001 From: Julien Date: Wed, 25 Jan 2017 13:31:45 -0800 Subject: [PATCH 26/34] set and hash functions --- src/hash_table.py | 25 +++++++++++++++++-------- 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/src/hash_table.py b/src/hash_table.py index ac69093..708d61a 100644 --- a/src/hash_table.py +++ b/src/hash_table.py @@ -8,16 +8,25 @@ def __init__(self, size, hash_alg='additive'): """Initialize a hash table.""" self._size = size self.buckets = [[] for bucket in range(self._size)] - self._hash_alg = hash_alg + self._hash_alg = self._hash(hash_alg) - # def _hash(self, hash_alg, word): - # if - # self.word = word - # additive = sum([ord(char) for char in list(word)]) % len(buckets) + def _hash(self, hash_alg): + if hash_alg == 'additive': + return self._additive_hash + else: + raise ValueError("Please enter a valid hash algorithm. The options are 'additive'.") - def _add_hash(self, word): + def _additive_hash(self, word): """Return Additive hash value.""" return sum([ord(char) for char in list(word)]) % self._size - - # def set(self, key, value) \ No newline at end of file + def set(self, key, value): + """Set a new key value pair in the has table.""" + if type(key) is not str: + raise TypeError("Key for hash table must be a string.") + hash_val = self._hash_alg(key) + for pair in self.buckets[hash_val]: + if pair[0] == key: + pair[1] = value + return + self.buckets[hash_val].append([key, value]) From 1d25cbdcafa32cc324e2f15777eb0473e48c2da8 Mon Sep 17 00:00:00 2001 From: Julien Date: Wed, 25 Jan 2017 13:45:30 -0800 Subject: [PATCH 27/34] get for the hash function --- src/hash_table.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/hash_table.py b/src/hash_table.py index 708d61a..32423bf 100644 --- a/src/hash_table.py +++ b/src/hash_table.py @@ -30,3 +30,13 @@ def set(self, key, value): pair[1] = value return self.buckets[hash_val].append([key, value]) + + def get(self, key): + """Get the value from the hash table.""" + if type(key) is not str: + raise TypeError("Key for hash table must be a string.") + hash_val = self._hash_alg(key) + for pair in self.buckets[hash_val]: + if pair[0] == key: + return pair[1] + return From 0ec88b57bf9d516e808fa3f8f44b4c052362ba3e Mon Sep 17 00:00:00 2001 From: Rick Valenzuela Date: Wed, 25 Jan 2017 13:51:54 -0800 Subject: [PATCH 28/34] added test for seting a number; fix typon in docstrings --- src/hash_table.py | 2 +- src/test_hash_table.py | 15 +++++++++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/src/hash_table.py b/src/hash_table.py index 708d61a..0143662 100644 --- a/src/hash_table.py +++ b/src/hash_table.py @@ -21,7 +21,7 @@ def _additive_hash(self, word): return sum([ord(char) for char in list(word)]) % self._size def set(self, key, value): - """Set a new key value pair in the has table.""" + """Set a new key-value pair in the hash table.""" if type(key) is not str: raise TypeError("Key for hash table must be a string.") hash_val = self._hash_alg(key) diff --git a/src/test_hash_table.py b/src/test_hash_table.py index 28e8635..571db9c 100644 --- a/src/test_hash_table.py +++ b/src/test_hash_table.py @@ -1 +1,16 @@ """Test for our implemtation of hash tables.""" + +import pytest +from hash_table import HashTable + +def test_simple_additive_hash(): + """Test that additive hash on small word.""" + h_table = HashTable(10) + assert h_table._additive_hash('a') == 7 + +def test_set_add_a_nonstring(): + """Test that set() won't take in a number type.""" + h_table = HashTable(10) + with pytest.raises(TypeError): + h_table.set(3, 'AI') + From bf2a18bdad9b152ce0d9a7a38e5b39010dd5e5ad Mon Sep 17 00:00:00 2001 From: Rick Valenzuela Date: Wed, 25 Jan 2017 14:42:01 -0800 Subject: [PATCH 29/34] added tests using unix dictionary list --- src/test_hash_table.py | 54 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) diff --git a/src/test_hash_table.py b/src/test_hash_table.py index 571db9c..59e892f 100644 --- a/src/test_hash_table.py +++ b/src/test_hash_table.py @@ -1,6 +1,7 @@ """Test for our implemtation of hash tables.""" import pytest +# import os from hash_table import HashTable def test_simple_additive_hash(): @@ -8,9 +9,62 @@ def test_simple_additive_hash(): h_table = HashTable(10) assert h_table._additive_hash('a') == 7 + def test_set_add_a_nonstring(): """Test that set() won't take in a number type.""" h_table = HashTable(10) with pytest.raises(TypeError): h_table.set(3, 'AI') + +def test_set_add_word(): + """Test that set() adds key-value pair.""" + h_table = HashTable(10) + h_table.set('thinking', 'tiring') + assert h_table.get('thinking') == 'tiring' + + +def test_get(): + """Test that get() retrieves value.""" + h_table = HashTable(10) + h_table.set('thinking', 'tiring') + assert h_table.get('thinking') == 'tiring' + +def test_dictionary_attacks_me_test(): + """Dictionary test.""" + h_table = HashTable(3000) + f = open("/usr/share/dict/words", 'r') + while True: + word = f.readline() + if not word: + break + h_table.set(word, word) + f_again = open("/usr/share/dict/words", 'r') + while True: + word = f_again.readline() + if not word: + break + if word != h_table.get(word): + assert False + assert True + +def test_dictionary_attacks_me_with_fail(): + """Add a whole dictionary, change a key's value, test the changed happened.""" + not_matching = 0 + h_table = HashTable(3000) + f = open("/usr/share/dict/words", 'r') + while True: + word = f.readline() + if not word: + break + h_table.set(word, word) + h_table.set("Adirondack\n", "pickle") + f_again = open("/usr/share/dict/words", 'r') + while True: + word = f_again.readline() + if not word: + break + if word != h_table.get(word): + not_matching += 1 + assert not_matching == 1 + From 7743b627c9b371c235f48b6ce1443daaf619c453 Mon Sep 17 00:00:00 2001 From: Julien Date: Wed, 25 Jan 2017 15:03:53 -0800 Subject: [PATCH 30/34] xor hash --- src/hash_table.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/hash_table.py b/src/hash_table.py index ece271b..6e55f45 100644 --- a/src/hash_table.py +++ b/src/hash_table.py @@ -13,13 +13,22 @@ def __init__(self, size, hash_alg='additive'): def _hash(self, hash_alg): if hash_alg == 'additive': return self._additive_hash + if hash_alg == 'xor': + return self._xor_hash else: - raise ValueError("Please enter a valid hash algorithm. The options are 'additive'.") + raise ValueError("Please enter a valid hash algorithm. The options are 'additive' and 'xor'.") def _additive_hash(self, word): """Return Additive hash value.""" return sum([ord(char) for char in list(word)]) % self._size + def _xor_hash(self, word): + """Return a xor hash.""" + hash_val = 0 + for i in range(len(word)): + hash_val ^= ord(word[i]) + return hash_val + def set(self, key, value): """Set a new key-value pair in the hash table.""" if type(key) is not str: From 2c15414e3e3413cf57931cada48858410f8ea2d5 Mon Sep 17 00:00:00 2001 From: Rick Valenzuela Date: Wed, 25 Jan 2017 15:30:14 -0800 Subject: [PATCH 31/34] added tests for xor with unix dict file --- src/test_hash_table.py | 60 ++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 58 insertions(+), 2 deletions(-) diff --git a/src/test_hash_table.py b/src/test_hash_table.py index 59e892f..3329122 100644 --- a/src/test_hash_table.py +++ b/src/test_hash_table.py @@ -4,19 +4,25 @@ # import os from hash_table import HashTable + def test_simple_additive_hash(): """Test that additive hash on small word.""" h_table = HashTable(10) assert h_table._additive_hash('a') == 7 +def test_simple_xor_hash(): + """Test that additive hash on small word.""" + h_table = HashTable(10, 'xor') + assert h_table._xor_hash('at') == 21 + + def test_set_add_a_nonstring(): """Test that set() won't take in a number type.""" h_table = HashTable(10) with pytest.raises(TypeError): h_table.set(3, 'AI') - def test_set_add_word(): """Test that set() adds key-value pair.""" h_table = HashTable(10) @@ -30,6 +36,7 @@ def test_get(): h_table.set('thinking', 'tiring') assert h_table.get('thinking') == 'tiring' + def test_dictionary_attacks_me_test(): """Dictionary test.""" h_table = HashTable(3000) @@ -39,6 +46,7 @@ def test_dictionary_attacks_me_test(): if not word: break h_table.set(word, word) + f.close() f_again = open("/usr/share/dict/words", 'r') while True: word = f_again.readline() @@ -46,9 +54,11 @@ def test_dictionary_attacks_me_test(): break if word != h_table.get(word): assert False + f_again.close() assert True -def test_dictionary_attacks_me_with_fail(): + +def test_dictionary_attacks_me_with_change(): """Add a whole dictionary, change a key's value, test the changed happened.""" not_matching = 0 h_table = HashTable(3000) @@ -58,6 +68,51 @@ def test_dictionary_attacks_me_with_fail(): if not word: break h_table.set(word, word) + f.close() + h_table.set("Adirondack\n", "pickle") + f_again = open("/usr/share/dict/words", 'r') + while True: + word = f_again.readline() + if not word: + break + if word != h_table.get(word): + not_matching += 1 + f_again.close() + assert not_matching == 1 + + +def test_dictionary_test_with_xor(): + """Dictionary test.""" + h_table = HashTable(3000, 'xor') + f = open("/usr/share/dict/words", 'r') + while True: + word = f.readline() + if not word: + break + h_table.set(word, word) + f.close() + f_again = open("/usr/share/dict/words", 'r') + while True: + word = f_again.readline() + if not word: + break + if word != h_table.get(word): + assert False + f_again.close() + assert True + + +def test_dictionary_and_change_with_xor(): + """Add a whole dictionary, change a key's value, test the changed happened.""" + not_matching = 0 + h_table = HashTable(3000, 'xor') + f = open("/usr/share/dict/words", 'r') + while True: + word = f.readline() + if not word: + break + h_table.set(word, word) + f.close() h_table.set("Adirondack\n", "pickle") f_again = open("/usr/share/dict/words", 'r') while True: @@ -66,5 +121,6 @@ def test_dictionary_attacks_me_with_fail(): break if word != h_table.get(word): not_matching += 1 + f_again.close() assert not_matching == 1 From 5eddf669c6a53964976fe6d9433c8b02b1705dc4 Mon Sep 17 00:00:00 2001 From: Rick Valenzuela Date: Wed, 25 Jan 2017 15:40:06 -0800 Subject: [PATCH 32/34] Added class docstring to module; added module info to README --- README.md | 7 +++++++ src/hash_table.py | 13 ++++++++++++- 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 0da3d09..1e3dfb6 100755 --- a/README.md +++ b/README.md @@ -46,5 +46,12 @@ Methods include: * breadth_first(self): Return a generator returns each node value from breadth-first traversal. * delete(value): Delete a node's connections (edges), effectively deleting node. Method autobalances after deletion, and tree size decrements by one. +##Hash Table +Stores key-value pairs using a given hashing algorithm. Choices for hashing algorithms are additive hash and xor hash. +Additive hash sums the Unicode code point for each letter in the word or string, then calls modulo with the number of buckets in the table. +XOR hash runs exclusive or with the letters of the word or string. +Methods include: +set(key, value): Add a key-value pair to the hash table. +get(key): Retrieve a value for the given key. diff --git a/src/hash_table.py b/src/hash_table.py index 6e55f45..29bbdd5 100644 --- a/src/hash_table.py +++ b/src/hash_table.py @@ -1,4 +1,15 @@ -"""Class for hash tables.""" +"""Class for hash tables. + +Choices for hashing algorithms are additive hash and xor hash. +Additive hash sums the Unicode code point for each letter in the word or string, +then calls modulo with the number of buckets in the table. +XOR hash runs exclusive or with the letters of the word or string. +Methods include: +set(key, value): Add a key-value pair to the hash table. +get(key): Retrieve a value for the given key. + +""" + class HashTable(object): From e2f78880ca067b04b3d351329a5e236a3b1da177 Mon Sep 17 00:00:00 2001 From: Julien Wilson Date: Thu, 26 Jan 2017 10:56:38 -0800 Subject: [PATCH 33/34] Update README.md --- README.md | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 1e3dfb6..7e54ac1 100755 --- a/README.md +++ b/README.md @@ -52,6 +52,9 @@ Additive hash sums the Unicode code point for each letter in the word or string, XOR hash runs exclusive or with the letters of the word or string. Methods include: -set(key, value): Add a key-value pair to the hash table. -get(key): Retrieve a value for the given key. - +* set(key, value): Add a key-value pair to the hash table. calls a hash function, but is otherwise O(k) where k is the number of items in the bucket. +* get(key): Retrieve a value for the given key. Add a key-value pair to the hash table. calls a hash function, but is otherwise O(k) where k is the number of items in the bucket. +* _hash(hash_alg): Decide which hash algorithm to call. O(1) +* _additive_hash(word): return a hash using an additive method; O(n) where n=length of word +* _xor_hash(word): return a hash using an additive method; O(n) where n=length of word +* From 6636b1641e42dfe1674d9fea25952b4115923605 Mon Sep 17 00:00:00 2001 From: Julien Date: Thu, 26 Jan 2017 11:13:08 -0800 Subject: [PATCH 34/34] hash table tests tidy --- src/test_hash_table.py | 2 +- tox.ini | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/test_hash_table.py b/src/test_hash_table.py index 3329122..41e1585 100644 --- a/src/test_hash_table.py +++ b/src/test_hash_table.py @@ -23,6 +23,7 @@ def test_set_add_a_nonstring(): with pytest.raises(TypeError): h_table.set(3, 'AI') + def test_set_add_word(): """Test that set() adds key-value pair.""" h_table = HashTable(10) @@ -123,4 +124,3 @@ def test_dictionary_and_change_with_xor(): not_matching += 1 f_again.close() assert not_matching == 1 - diff --git a/tox.ini b/tox.ini index 8721979..3b2ab95 100755 --- a/tox.ini +++ b/tox.ini @@ -2,6 +2,6 @@ envlist = py27, py35 [testenv] -commands = py.test src/test_bst.py +commands = py.test src/test_hash_table.py deps = pytest \ No newline at end of file