From 90ca00acae77a0be4cfd74100225de5684a49e09 Mon Sep 17 00:00:00 2001 From: CHELSEA DOLE Date: Fri, 24 Nov 2017 12:35:03 -0800 Subject: [PATCH 01/68] first part of BST, init class. --- bst.py | 36 ++++++++++++++++++++++++++++++++++++ test_bst.py | 0 2 files changed, 36 insertions(+) create mode 100644 bst.py create mode 100644 test_bst.py diff --git a/bst.py b/bst.py new file mode 100644 index 0000000..ebd96fb --- /dev/null +++ b/bst.py @@ -0,0 +1,36 @@ +"""Implementation of a binary search tree data structure.""" + + +class Node(object): + """Define the Node-class object.""" + + def __init__(self, value, left=None, right=None): + """Constructor for the Node class.""" + self.val = value + self.left = left + self.right = right + self.depth = 0 + + +class BST(object): + """Define the BST-class object.""" + + def __init__(self, starting_values=None): + """Constructor for the BST class.""" + self.tree_size = 0 + self.left_depth = 0 + self.right_depth = 0 + + if starting_values is None: + self.root = None + + elif isinstance(starting_values, (list, str, tuple, set)): + self.root.val = starting_values[0] + self.tree_size += 1 + for i in len(starting_values) - 1: + self.insert(starting_values[i + 1]) + + else: + raise TypeError('Only iterables or None\ + are valid parameters!') + \ No newline at end of file diff --git a/test_bst.py b/test_bst.py new file mode 100644 index 0000000..e69de29 From 4a5a47f73164c75c2c915145ddbbb19debc2c84b Mon Sep 17 00:00:00 2001 From: CHELSEA DOLE Date: Fri, 24 Nov 2017 12:36:57 -0800 Subject: [PATCH 02/68] changes to BST, changing depth calculation. --- bst.py | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/bst.py b/bst.py index ebd96fb..fbc2819 100644 --- a/bst.py +++ b/bst.py @@ -4,11 +4,12 @@ class Node(object): """Define the Node-class object.""" - def __init__(self, value, left=None, right=None): + def __init__(self, value, left=None, right=None, parent=None): """Constructor for the Node class.""" self.val = value self.left = left self.right = right + self.parent = parent self.depth = 0 @@ -20,17 +21,21 @@ def __init__(self, starting_values=None): self.tree_size = 0 self.left_depth = 0 self.right_depth = 0 + self.visited = [] if starting_values is None: self.root = None - elif isinstance(starting_values, (list, str, tuple, set)): - self.root.val = starting_values[0] + elif isinstance(starting_values, (list, str, tuple)): + self.root = Node(starting_values[0]) self.tree_size += 1 - for i in len(starting_values) - 1: + for i in range(len(starting_values) - 1): self.insert(starting_values[i + 1]) else: raise TypeError('Only iterables or None\ are valid parameters!') - \ No newline at end of file + + def balance(self): + """Return the current balance of the BST.""" + return self.right_depth - self.left_depth \ No newline at end of file From 832b1092d564414df18434b409118cc5d4053ce7 Mon Sep 17 00:00:00 2001 From: CHELSEA DOLE Date: Fri, 24 Nov 2017 12:38:26 -0800 Subject: [PATCH 03/68] changes to bst, first traversal methods. --- bst.py | 138 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 137 insertions(+), 1 deletion(-) diff --git a/bst.py b/bst.py index fbc2819..f2a3960 100644 --- a/bst.py +++ b/bst.py @@ -38,4 +38,140 @@ def __init__(self, starting_values=None): def balance(self): """Return the current balance of the BST.""" - return self.right_depth - self.left_depth \ No newline at end of file + return self.right_depth - self.left_depth + + def size(self): + """Return the current size of the BST.""" + return self.tree_size + + def insert(self, value): + """Insert a new node into the BST, and adjust the balance.""" + new_node = Node(value) + + if self.root: + if new_node.val > self.root.val: + if self.root.right: + self._find_home(new_node, self.root.right) + if new_node.depth > self.right_depth: + self.right_depth = new_node.depth + else: + new_node.parent = self.root + self.root.right = new_node + self.root.right.depth = 1 + if self.root.right.depth > self.right_depth: + self.right_depth = self.root.right.depth + self.tree_size += 1 + + elif new_node.val < self.root.val: + if self.root.left: + self._find_home(new_node, self.root.left) + if new_node.depth > self.left_depth: + self.left_depth = new_node.depth + else: + new_node.parent = self.root + self.root.left = new_node + self.root.left.depth = 1 + if self.root.left.depth > self.left_depth: + self.left_depth = self.root.left.depth + self.tree_size += 1 + else: + self.root = new_node + self.tree_size += 1 + + def _find_home(self, node_to_add, node_to_check): + """. + Check if the node_to_add belongs on the left or right + of the node_to_check, then place it there if that spot is empty, + otherwise recur. + """ + if node_to_add.val > node_to_check.val: + if node_to_check.right: + self._find_home(node_to_add, node_to_check.right) + else: + node_to_add.parent = node_to_check + node_to_check.right = node_to_add + node_to_check.right.depth = node_to_check.depth + 1 + self.tree_size += 1 + + elif node_to_add.val < node_to_check.val: + if node_to_check.left: + self._find_home(node_to_add, node_to_check.left) + else: + node_to_add.parent = node_to_check + node_to_check.left = node_to_add + node_to_check.left.depth = node_to_check.depth + 1 + self.tree_size += 1 + + def search(self, value): + """If a value is in the BST, return its node.""" + return self._check_for_equivalence(value, self.root) + + def contains(self, value): + """Return whether or not a value is in the BST.""" + return bool(self.search(value)) + + def _check_for_equivalence(self, value, node_to_check): + """. + Check if the value matches that of the node_to_check + if it does, return the node. If it doesn't, go left or right + as appropriate and recur. If you reach a dead end, return None. + """ + try: + if value == node_to_check.val: + return node_to_check + + except AttributeError: + return None + + if value > node_to_check.val and node_to_check.right: + return self._check_for_equivalence(value, node_to_check.right) + + elif value < node_to_check.val and node_to_check.left: + return self._check_for_equivalence(value, node_to_check.left) + + def depth(self): + """Return the depth of the BST.""" + if self.left_depth > self.right_depth: + return self.left_depth + return self.right_depth + + def in_order(self): + """Return a generator to perform an in-order traversal.""" + self.visited = [] + + if self.root is None: + raise IndexError("Tree is empty!") + + gen = self._in_order_gen() + return gen + + def _in_order_gen(self): + """Recursive helper method for in-order traversal.""" + current = self.root + + while len(self.visited) < self.tree_size: + if current.left: + if current.left.val not in self.visited: + current = current.left + continue + + if current.val not in self.visited: + self.visited.append(current.val) + yield current.val + + if current.right: + if current.right.val not in self.visited: + current = current.right + continue + + current = current.parent + + def pre_order(self): + """Return a generator to perform an pre-order traversal.""" + self.visited = [] + + if self.root is None: + raise IndexError("Tree is empty!") + + gen = self._pre_order_gen() + return gen From 0b26b1f4d13ea5430f27e90b1cb0d5645905f11e Mon Sep 17 00:00:00 2001 From: CHELSEA DOLE Date: Fri, 24 Nov 2017 12:38:46 -0800 Subject: [PATCH 04/68] preorder gen traversal. --- bst.py | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/bst.py b/bst.py index f2a3960..4f50c8f 100644 --- a/bst.py +++ b/bst.py @@ -175,3 +175,24 @@ def pre_order(self): gen = self._pre_order_gen() return gen + + def _pre_order_gen(self): + """Recursive helper method for pre-order traversal.""" + current = self.root + + while len(self.visited) < self.tree_size: + if current.val not in self.visited: + self.visited.append(current.val) + yield current.val + + if current.left: + if current.left.val not in self.visited: + current = current.left + continue + + if current.right: + if current.right.val not in self.visited: + current = current.right + continue + + current = current.parent From 498a807ddffeb2b87a5c97b28ded1d692c2ff09e Mon Sep 17 00:00:00 2001 From: CHELSEA DOLE Date: Fri, 24 Nov 2017 12:39:04 -0800 Subject: [PATCH 05/68] post_order --- bst.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/bst.py b/bst.py index 4f50c8f..d1593dd 100644 --- a/bst.py +++ b/bst.py @@ -196,3 +196,13 @@ def _pre_order_gen(self): continue current = current.parent + + def post_order(self): + """Return a generator to perform an post-order traversal.""" + self.visited = [] + + if self.root is None: + raise IndexError("Tree is empty!") + + gen = self._post_order_gen() + return gen From 1c0b988fc8623e106915c84036fc3157975fc6ea Mon Sep 17 00:00:00 2001 From: CHELSEA DOLE Date: Fri, 24 Nov 2017 12:39:28 -0800 Subject: [PATCH 06/68] bredth_first --- bst.py | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/bst.py b/bst.py index d1593dd..1f2df2f 100644 --- a/bst.py +++ b/bst.py @@ -206,3 +206,34 @@ def post_order(self): gen = self._post_order_gen() return gen + + def _post_order_gen(self): + """Recursive helper method for post-order traversal.""" + current = self.root + + while len(self.visited) < self.tree_size: + if current.left: + if current.left.val not in self.visited: + current = current.left + continue + + if current.right: + if current.right.val not in self.visited: + current = current.right + continue + + if current.val not in self.visited: + self.visited.append(current.val) + yield current.val + + current = current.parent + + def breadth_first(self): + """Return a generator to perform a breadth-first traversal.""" + self.visited = [] + + if self.root is None: + raise IndexError("Tree is empty!") + + gen = self._breadth_first_gen(self.root) + return gen From 806f991cc8f7b0350913ea019da6ea50da30f828 Mon Sep 17 00:00:00 2001 From: CHELSEA DOLE Date: Fri, 24 Nov 2017 12:39:56 -0800 Subject: [PATCH 07/68] finishing breadth first and post order gen. --- bst.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/bst.py b/bst.py index 1f2df2f..96faef5 100644 --- a/bst.py +++ b/bst.py @@ -237,3 +237,22 @@ def breadth_first(self): gen = self._breadth_first_gen(self.root) return gen + + def _breadth_first_gen(self, root_node): + """Helper generator for breadth-first traversal.""" + queue = [self.root] + while queue: + current = queue[0] + yield current.val + queue = queue[1:] + + if current not in self.visited: + self.visited.append(current) + + if current.left: + if current.left not in self.visited: + queue.append(current.left) + + if current.right: + if current.right not in self.visited: + queue.append(current.right) From 116a52f9f73b787cdb07b69ca6065beaafd96ac1 Mon Sep 17 00:00:00 2001 From: CHELSEA DOLE Date: Fri, 24 Nov 2017 12:45:20 -0800 Subject: [PATCH 08/68] adding tests --- bst.py | 1 + test_bst.py | 136 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 137 insertions(+) diff --git a/bst.py b/bst.py index 96faef5..ed26a2e 100644 --- a/bst.py +++ b/bst.py @@ -78,6 +78,7 @@ def insert(self, value): self.root = new_node self.tree_size += 1 + def _find_home(self, node_to_add, node_to_check): """. Check if the node_to_add belongs on the left or right diff --git a/test_bst.py b/test_bst.py index e69de29..4639fa7 100644 --- a/test_bst.py +++ b/test_bst.py @@ -0,0 +1,136 @@ +"""Various tests for the Binary Search Tree.""" + +import pytest + + +@pytest.fixture +def sample_bst(): + """Make a sample_bst for testing.""" + from bst import BST + return BST() + + +def test_bst_exists(sample_bst): + """Test that the BST class makes something.""" + assert sample_bst + + +def test_bst_can_take_list(): + """Test that the BST can take a list as a param.""" + from bst import BST + b = BST([1, 2, 3]) + assert b.size() == 3 + assert b.depth() == 2 + assert b.left_depth == 0 + assert b.right_depth == 2 + + +def test_bst_can_take_tuple(): + """Test that the BST can take a tuple as a param.""" + from bst import BST + b = BST((1, 2, 3)) + assert b.size() == 3 + assert b.depth() == 2 + assert b.left_depth == 0 + assert b.right_depth == 2 + + +def test_bst_can_take_string(): + """Test that the BST can take a string as a param.""" + from bst import BST + b = BST('abc') + assert b.size() == 3 + assert b.depth() == 2 + assert b.left_depth == 0 + assert b.right_depth == 2 + + +def test_insert_increases_size(sample_bst): + """Test that the insert method increases the output of the size method.""" + assert sample_bst.size() == 0 + sample_bst.insert(1) + assert sample_bst.size() == 1 + sample_bst.insert(2) + assert sample_bst.size() == 2 + + +def test_insert_increases_tree_size(sample_bst): + """Test that the insert method increases the tree_size attribute.""" + assert sample_bst.tree_size == 0 + sample_bst.insert(1) + assert sample_bst.tree_size == 1 + sample_bst.insert(2) + assert sample_bst.tree_size == 2 + + +def test_insert_increases_depth(sample_bst): + """Test that the insert method increases the output of the depth method.""" + assert sample_bst.depth() == 0 + sample_bst.insert(1) + assert sample_bst.depth() == 0 + sample_bst.insert(2) + assert sample_bst.depth() == 1 + + +def test_search_right(sample_bst): + """Assert that the search method returns something and that it's a Node.""" + from bst import Node + sample_bst.insert(1) + sample_bst.insert(2) + sample_bst.insert(3) + found = sample_bst.search(3) + assert found.val == 3 + assert isinstance(found, Node) + + +def test_search_left(sample_bst): + """Assert that the search method returns something and that it's a Node.""" + from bst import Node + sample_bst.insert(3) + sample_bst.insert(2) + sample_bst.insert(1) + found = sample_bst.search(3) + assert found.val == 3 + assert isinstance(found, Node) + + +def test_search_not_found_returns_none(sample_bst): + """Assert that the search method returns None when value isn't found.""" + sample_bst.insert(1) + sample_bst.insert(2) + sample_bst.insert(3) + found = sample_bst.search(4) + assert found is None + + +def test_contains_right(sample_bst): + """Assert that the contains method returns True.""" + sample_bst.insert(1) + sample_bst.insert(2) + sample_bst.insert(3) + found = sample_bst.contains(3) + assert found is True + + +def test_contains_left(sample_bst): + """Assert that the contains method returns True.""" + sample_bst.insert(3) + sample_bst.insert(2) + sample_bst.insert(1) + found = sample_bst.contains(3) + assert found is True + + +def test_contains_not_found_returns_none(sample_bst): + """Assert that the contains method returns False when value isn't found.""" + sample_bst.insert(1) + sample_bst.insert(2) + sample_bst.insert(3) + found = sample_bst.contains(4) + assert found is False + + +def test_in_order_errors_with_empty_tree(sample_bst): + """Test that in_order raises an IndexError if the tree is empty.""" + with pytest.raises(IndexError): + assert found is False From 7c7b83e67994a48499d0358f7bbdce622cbf9599 Mon Sep 17 00:00:00 2001 From: CHELSEA DOLE Date: Fri, 24 Nov 2017 12:59:54 -0800 Subject: [PATCH 09/68] adding 8 new tests for BST edge cases. --- test_bst.py | 270 ++++++++++++++++++++++++++++++++-------------------- 1 file changed, 165 insertions(+), 105 deletions(-) diff --git a/test_bst.py b/test_bst.py index 4639fa7..571b834 100644 --- a/test_bst.py +++ b/test_bst.py @@ -1,6 +1,9 @@ """Various tests for the Binary Search Tree.""" import pytest +from bst import BST +from bst import Node + @pytest.fixture @@ -15,122 +18,179 @@ def test_bst_exists(sample_bst): assert sample_bst -def test_bst_can_take_list(): - """Test that the BST can take a list as a param.""" - from bst import BST - b = BST([1, 2, 3]) - assert b.size() == 3 - assert b.depth() == 2 - assert b.left_depth == 0 - assert b.right_depth == 2 - - -def test_bst_can_take_tuple(): - """Test that the BST can take a tuple as a param.""" - from bst import BST - b = BST((1, 2, 3)) - assert b.size() == 3 - assert b.depth() == 2 - assert b.left_depth == 0 - assert b.right_depth == 2 - - -def test_bst_can_take_string(): - """Test that the BST can take a string as a param.""" - from bst import BST - b = BST('abc') - assert b.size() == 3 - assert b.depth() == 2 - assert b.left_depth == 0 - assert b.right_depth == 2 - - -def test_insert_increases_size(sample_bst): - """Test that the insert method increases the output of the size method.""" +# def test_bst_can_take_list(): +# """Test that the BST can take a list as a param.""" +# from bst import BST +# b = BST([1, 2, 3]) +# assert b.size() == 3 +# assert b.depth() == 2 +# assert b.left_depth == 0 +# assert b.right_depth == 2 + + +# def test_bst_can_take_tuple(): +# """Test that the BST can take a tuple as a param.""" +# from bst import BST +# b = BST((1, 2, 3)) +# assert b.size() == 3 +# assert b.depth() == 2 +# assert b.left_depth == 0 +# assert b.right_depth == 2 + + +# def test_bst_can_take_string(): +# """Test that the BST can take a string as a param.""" +# from bst import BST +# b = BST('abc') +# assert b.size() == 3 +# assert b.depth() == 2 +# assert b.left_depth == 0 +# assert b.right_depth == 2 + + +# def test_insert_increases_size(sample_bst): +# """Test that the insert method increases the output of the size method.""" +# assert sample_bst.size() == 0 +# sample_bst.insert(1) +# assert sample_bst.size() == 1 +# sample_bst.insert(2) +# assert sample_bst.size() == 2 + + +# def test_insert_increases_tree_size(sample_bst): +# """Test that the insert method increases the tree_size attribute.""" +# assert sample_bst.tree_size == 0 +# sample_bst.insert(1) +# assert sample_bst.tree_size == 1 +# sample_bst.insert(2) +# assert sample_bst.tree_size == 2 + + +# def test_insert_increases_depth(sample_bst): +# """Test that the insert method increases the output of the depth method.""" +# assert sample_bst.depth() == 0 +# sample_bst.insert(1) +# assert sample_bst.depth() == 0 +# sample_bst.insert(2) +# assert sample_bst.depth() == 1 + + +# def test_search_right(sample_bst): +# """Assert that the search method returns something and that it's a Node.""" +# from bst import Node +# sample_bst.insert(1) +# sample_bst.insert(2) +# sample_bst.insert(3) +# found = sample_bst.search(3) +# assert found.val == 3 +# assert isinstance(found, Node) + + +# def test_search_left(sample_bst): +# """Assert that the search method returns something and that it's a Node.""" +# from bst import Node +# sample_bst.insert(3) +# sample_bst.insert(2) +# sample_bst.insert(1) +# found = sample_bst.search(3) +# assert found.val == 3 +# assert isinstance(found, Node) + + +# def test_search_not_found_returns_none(sample_bst): +# """Assert that the search method returns None when value isn't found.""" +# sample_bst.insert(1) +# sample_bst.insert(2) +# sample_bst.insert(3) +# found = sample_bst.search(4) +# assert found is None + + +# def test_contains_right(sample_bst): +# """Assert that the contains method returns True.""" +# sample_bst.insert(1) +# sample_bst.insert(2) +# sample_bst.insert(3) +# found = sample_bst.contains(3) +# assert found is True + + +# def test_contains_left(sample_bst): +# """Assert that the contains method returns True.""" +# sample_bst.insert(3) +# sample_bst.insert(2) +# sample_bst.insert(1) +# found = sample_bst.contains(3) +# assert found is True + + +# def test_contains_not_found_returns_none(sample_bst): +# """Assert that the contains method returns False when value isn't found.""" +# sample_bst.insert(1) +# sample_bst.insert(2) +# sample_bst.insert(3) +# found = sample_bst.contains(4) +# assert found is False + + +# def test_in_order_errors_with_empty_tree(sample_bst): +# """Test that in_order raises an IndexError if the tree is empty.""" +# with pytest.raises(IndexError): +# assert found is False + +# Chelsea tests below + +def test_that_bst_doesnt_work_with_non_iterable(): + """Test that BST only takes iterable inputs.""" + with pytest.raises(TypeError): + BST({0: 0, 1: 1, 2: 2}) + + +def test_adding_preexisting_node_is_not_added(sample_bst): + """Test that adding a node val that exists does not increase BST.""" assert sample_bst.size() == 0 - sample_bst.insert(1) + sample_bst.insert(5) + sample_bst.insert(5) + sample_bst.insert(5) assert sample_bst.size() == 1 - sample_bst.insert(2) - assert sample_bst.size() == 2 - - -def test_insert_increases_tree_size(sample_bst): - """Test that the insert method increases the tree_size attribute.""" - assert sample_bst.tree_size == 0 - sample_bst.insert(1) - assert sample_bst.tree_size == 1 - sample_bst.insert(2) - assert sample_bst.tree_size == 2 -def test_insert_increases_depth(sample_bst): - """Test that the insert method increases the output of the depth method.""" - assert sample_bst.depth() == 0 - sample_bst.insert(1) - assert sample_bst.depth() == 0 - sample_bst.insert(2) - assert sample_bst.depth() == 1 - - -def test_search_right(sample_bst): - """Assert that the search method returns something and that it's a Node.""" - from bst import Node - sample_bst.insert(1) - sample_bst.insert(2) - sample_bst.insert(3) - found = sample_bst.search(3) - assert found.val == 3 - assert isinstance(found, Node) - - -def test_search_left(sample_bst): - """Assert that the search method returns something and that it's a Node.""" - from bst import Node - sample_bst.insert(3) - sample_bst.insert(2) - sample_bst.insert(1) - found = sample_bst.search(3) - assert found.val == 3 - assert isinstance(found, Node) +def test_that_negative_numbers_work_with_insert(sample_bst): + """Test that negative numbers are covered in insert.""" + sample_bst.insert(-500) + assert sample_bst -def test_search_not_found_returns_none(sample_bst): - """Assert that the search method returns None when value isn't found.""" - sample_bst.insert(1) - sample_bst.insert(2) - sample_bst.insert(3) - found = sample_bst.search(4) - assert found is None +def test_node_attributes_exist(): + """Test that node attributes are in Node class.""" + n = Node(1) + assert n.val == 1 + assert n.left is None + assert n.right is None + assert n.depth == 0 -def test_contains_right(sample_bst): - """Assert that the contains method returns True.""" - sample_bst.insert(1) - sample_bst.insert(2) +def test_node_attribute_depth_changes(sample_bst): + """Test that node attribute depth increases.""" + sample_bst.insert(4) sample_bst.insert(3) - found = sample_bst.contains(3) - assert found is True + sample_bst.insert(2.5) + assert sample_bst.search(4).depth == 0 + assert sample_bst.search(3).depth == 1 + assert sample_bst.search(2.5).depth == 2 -def test_contains_left(sample_bst): - """Assert that the contains method returns True.""" - sample_bst.insert(3) - sample_bst.insert(2) - sample_bst.insert(1) - found = sample_bst.contains(3) - assert found is True +def test_node_left_and_right_attributes_change(): + """Test that left and right node attributes are added with insert.""" + b = BST([5]) + b.insert(4) + b.insert(6) + assert b.root.left.val == 4 + assert b.root.right.val == 6 - -def test_contains_not_found_returns_none(sample_bst): - """Assert that the contains method returns False when value isn't found.""" - sample_bst.insert(1) - sample_bst.insert(2) - sample_bst.insert(3) - found = sample_bst.contains(4) - assert found is False +# NOTE: THERE WILL BE PROBS WITH DOT NOTATION IN PREVIOUS TWO TESTS -def test_in_order_errors_with_empty_tree(sample_bst): - """Test that in_order raises an IndexError if the tree is empty.""" - with pytest.raises(IndexError): - assert found is False +def test_root_val_with_no_val_at_initialization(sample_bst): + """Test that root is None.""" + assert sample_bst.root is None From 5e219b1f6d06e5fd43ae8521c2920e45c46875d3 Mon Sep 17 00:00:00 2001 From: CHELSEA DOLE Date: Fri, 24 Nov 2017 13:01:15 -0800 Subject: [PATCH 10/68] fixing small error in BST tests. --- test_bst.py | 234 +++++++++++++++++++++++++--------------------------- 1 file changed, 114 insertions(+), 120 deletions(-) diff --git a/test_bst.py b/test_bst.py index 571b834..f1b686d 100644 --- a/test_bst.py +++ b/test_bst.py @@ -5,7 +5,6 @@ from bst import Node - @pytest.fixture def sample_bst(): """Make a sample_bst for testing.""" @@ -18,128 +17,123 @@ def test_bst_exists(sample_bst): assert sample_bst -# def test_bst_can_take_list(): -# """Test that the BST can take a list as a param.""" -# from bst import BST -# b = BST([1, 2, 3]) -# assert b.size() == 3 -# assert b.depth() == 2 -# assert b.left_depth == 0 -# assert b.right_depth == 2 - - -# def test_bst_can_take_tuple(): -# """Test that the BST can take a tuple as a param.""" -# from bst import BST -# b = BST((1, 2, 3)) -# assert b.size() == 3 -# assert b.depth() == 2 -# assert b.left_depth == 0 -# assert b.right_depth == 2 - - -# def test_bst_can_take_string(): -# """Test that the BST can take a string as a param.""" -# from bst import BST -# b = BST('abc') -# assert b.size() == 3 -# assert b.depth() == 2 -# assert b.left_depth == 0 -# assert b.right_depth == 2 - - -# def test_insert_increases_size(sample_bst): -# """Test that the insert method increases the output of the size method.""" -# assert sample_bst.size() == 0 -# sample_bst.insert(1) -# assert sample_bst.size() == 1 -# sample_bst.insert(2) -# assert sample_bst.size() == 2 - - -# def test_insert_increases_tree_size(sample_bst): -# """Test that the insert method increases the tree_size attribute.""" -# assert sample_bst.tree_size == 0 -# sample_bst.insert(1) -# assert sample_bst.tree_size == 1 -# sample_bst.insert(2) -# assert sample_bst.tree_size == 2 - - -# def test_insert_increases_depth(sample_bst): -# """Test that the insert method increases the output of the depth method.""" -# assert sample_bst.depth() == 0 -# sample_bst.insert(1) -# assert sample_bst.depth() == 0 -# sample_bst.insert(2) -# assert sample_bst.depth() == 1 - - -# def test_search_right(sample_bst): -# """Assert that the search method returns something and that it's a Node.""" -# from bst import Node -# sample_bst.insert(1) -# sample_bst.insert(2) -# sample_bst.insert(3) -# found = sample_bst.search(3) -# assert found.val == 3 -# assert isinstance(found, Node) - - -# def test_search_left(sample_bst): -# """Assert that the search method returns something and that it's a Node.""" -# from bst import Node -# sample_bst.insert(3) -# sample_bst.insert(2) -# sample_bst.insert(1) -# found = sample_bst.search(3) -# assert found.val == 3 -# assert isinstance(found, Node) - - -# def test_search_not_found_returns_none(sample_bst): -# """Assert that the search method returns None when value isn't found.""" -# sample_bst.insert(1) -# sample_bst.insert(2) -# sample_bst.insert(3) -# found = sample_bst.search(4) -# assert found is None - - -# def test_contains_right(sample_bst): -# """Assert that the contains method returns True.""" -# sample_bst.insert(1) -# sample_bst.insert(2) -# sample_bst.insert(3) -# found = sample_bst.contains(3) -# assert found is True - - -# def test_contains_left(sample_bst): -# """Assert that the contains method returns True.""" -# sample_bst.insert(3) -# sample_bst.insert(2) -# sample_bst.insert(1) -# found = sample_bst.contains(3) -# assert found is True - - -# def test_contains_not_found_returns_none(sample_bst): -# """Assert that the contains method returns False when value isn't found.""" -# sample_bst.insert(1) -# sample_bst.insert(2) -# sample_bst.insert(3) -# found = sample_bst.contains(4) -# assert found is False - - -# def test_in_order_errors_with_empty_tree(sample_bst): -# """Test that in_order raises an IndexError if the tree is empty.""" -# with pytest.raises(IndexError): -# assert found is False +def test_bst_can_take_list(): + """Test that the BST can take a list as a param.""" + from bst import BST + b = BST([1, 2, 3]) + assert b.size() == 3 + assert b.depth() == 2 + assert b.left_depth == 0 + assert b.right_depth == 2 + + +def test_bst_can_take_tuple(): + """Test that the BST can take a tuple as a param.""" + from bst import BST + b = BST((1, 2, 3)) + assert b.size() == 3 + assert b.depth() == 2 + assert b.left_depth == 0 + assert b.right_depth == 2 + + +def test_bst_can_take_string(): + """Test that the BST can take a string as a param.""" + from bst import BST + b = BST('abc') + assert b.size() == 3 + assert b.depth() == 2 + assert b.left_depth == 0 + assert b.right_depth == 2 + + +def test_insert_increases_size(sample_bst): + """Test that the insert method increases the output of the size method.""" + assert sample_bst.size() == 0 + sample_bst.insert(1) + assert sample_bst.size() == 1 + sample_bst.insert(2) + assert sample_bst.size() == 2 + + +def test_insert_increases_tree_size(sample_bst): + """Test that the insert method increases the tree_size attribute.""" + assert sample_bst.tree_size == 0 + sample_bst.insert(1) + assert sample_bst.tree_size == 1 + sample_bst.insert(2) + assert sample_bst.tree_size == 2 + + +def test_insert_increases_depth(sample_bst): + """Test that the insert method increases the output of the depth method.""" + assert sample_bst.depth() == 0 + sample_bst.insert(1) + assert sample_bst.depth() == 0 + sample_bst.insert(2) + assert sample_bst.depth() == 1 + + +def test_search_right(sample_bst): + """Assert that the search method returns something and that it's a Node.""" + from bst import Node + sample_bst.insert(1) + sample_bst.insert(2) + sample_bst.insert(3) + found = sample_bst.search(3) + assert found.val == 3 + assert isinstance(found, Node) + + +def test_search_left(sample_bst): + """Assert that the search method returns something and that it's a Node.""" + from bst import Node + sample_bst.insert(3) + sample_bst.insert(2) + sample_bst.insert(1) + found = sample_bst.search(3) + assert found.val == 3 + assert isinstance(found, Node) + + +def test_search_not_found_returns_none(sample_bst): + """Assert that the search method returns None when value isn't found.""" + sample_bst.insert(1) + sample_bst.insert(2) + sample_bst.insert(3) + found = sample_bst.search(4) + assert found is None + + +def test_contains_right(sample_bst): + """Assert that the contains method returns True.""" + sample_bst.insert(1) + sample_bst.insert(2) + sample_bst.insert(3) + found = sample_bst.contains(3) + assert found is True + + +def test_contains_left(sample_bst): + """Assert that the contains method returns True.""" + sample_bst.insert(3) + sample_bst.insert(2) + sample_bst.insert(1) + found = sample_bst.contains(3) + assert found is True + + +def test_contains_not_found_returns_none(sample_bst): + """Assert that the contains method returns False when value isn't found.""" + sample_bst.insert(1) + sample_bst.insert(2) + sample_bst.insert(3) + found = sample_bst.contains(4) + assert found is False # Chelsea tests below + def test_that_bst_doesnt_work_with_non_iterable(): """Test that BST only takes iterable inputs.""" with pytest.raises(TypeError): From 1cd5bc0e7c63fd1ece7515c465c706e3e35e6e37 Mon Sep 17 00:00:00 2001 From: CHELSEA DOLE Date: Fri, 24 Nov 2017 13:18:47 -0800 Subject: [PATCH 11/68] last changes --- bst.py | 1 - test_bst.py | 2 -- 2 files changed, 3 deletions(-) diff --git a/bst.py b/bst.py index ed26a2e..96faef5 100644 --- a/bst.py +++ b/bst.py @@ -78,7 +78,6 @@ def insert(self, value): self.root = new_node self.tree_size += 1 - def _find_home(self, node_to_add, node_to_check): """. Check if the node_to_add belongs on the left or right diff --git a/test_bst.py b/test_bst.py index f1b686d..9efdb67 100644 --- a/test_bst.py +++ b/test_bst.py @@ -182,8 +182,6 @@ def test_node_left_and_right_attributes_change(): assert b.root.left.val == 4 assert b.root.right.val == 6 -# NOTE: THERE WILL BE PROBS WITH DOT NOTATION IN PREVIOUS TWO TESTS - def test_root_val_with_no_val_at_initialization(sample_bst): """Test that root is None.""" From bd0e281611ff5593067c7486efdf467534cedb18 Mon Sep 17 00:00:00 2001 From: CHELSEA DOLE Date: Sat, 25 Nov 2017 22:35:51 -0800 Subject: [PATCH 12/68] adding tests Nathan and I wrote on Friday. --- test_bst.py | 181 +++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 180 insertions(+), 1 deletion(-) diff --git a/test_bst.py b/test_bst.py index 9efdb67..2b75c5b 100644 --- a/test_bst.py +++ b/test_bst.py @@ -1,4 +1,4 @@ -"""Various tests for the Binary Search Tree.""" +"""Tests for the Binary Search Tree.""" import pytest from bst import BST @@ -186,3 +186,182 @@ def test_node_left_and_right_attributes_change(): def test_root_val_with_no_val_at_initialization(sample_bst): """Test that root is None.""" assert sample_bst.root is None + + + +def test_in_order_errors_with_empty_tree(sample_bst): + """Test that in_order raises an IndexError if the tree is empty.""" + with pytest.raises(IndexError): + sample_bst.in_order() + + +def test_pre_order_errors_with_empty_tree(sample_bst): + """Test that pre_order raises an IndexError if the tree is empty.""" + with pytest.raises(IndexError): + sample_bst.pre_order() + + +def test_post_order_errors_with_empty_tree(sample_bst): + """Test that post_order raises an IndexError if the tree is empty.""" + with pytest.raises(IndexError): + sample_bst.post_order() + + +def test_breadth_first_errors_with_empty_tree(sample_bst): + """Test that breadth_first raises an IndexError if the tree is empty.""" + with pytest.raises(IndexError): + sample_bst.breadth_first() + + +def test_in_order_size_one(sample_bst): + """Check for the correct output of in_order on a tree of size 1.""" + sample_bst.insert(1) + gen = sample_bst.in_order() + assert next(gen) == 1 + + +def test_pre_order_size_one(sample_bst): + """Check for the correct output of pre_order on a tree of size 1.""" + sample_bst.insert(1) + gen = sample_bst.pre_order() + assert next(gen) == 1 + + +def test_post_order_size_one(sample_bst): + """Check for the correct output of post_order on a tree of size 1.""" + sample_bst.insert(1) + gen = sample_bst.post_order() + assert next(gen) == 1 + + +def test_breadth_first_size_one(sample_bst): + """Check for the correct output of breadth_first on a tree of size 1.""" + sample_bst.insert(1) + gen = sample_bst.breadth_first() + assert next(gen) == 1 + +LEFT_IMBALANCED = [6, 5, 4, 3, 2, 1] + + +def test_in_order_left_imba(): + """Check for the correct output of iot on a left-imbalanced tree.""" + tree = BST(LEFT_IMBALANCED) + gen = tree.in_order() + output = [] + for i in range(6): + output.append(next(gen)) + assert output == [1, 2, 3, 4, 5, 6] + + +def test_pre_order_left_imba(): + """Check for the correct output of preo-t on a left-imbalanced tree.""" + tree = BST(LEFT_IMBALANCED) + gen = tree.pre_order() + output = [] + for i in range(6): + output.append(next(gen)) + assert output == [6, 5, 4, 3, 2, 1] + + +def test_post_order_left_imba(): + """Check for the correct output of posto-t on a left-imbalanced tree.""" + tree = BST(LEFT_IMBALANCED) + gen = tree.post_order() + output = [] + for i in range(6): + output.append(next(gen)) + assert output == [1, 2, 3, 4, 5, 6] + + +def test_breadth_first_left_imba(): + """Check for the correct output of bft on a left-imbalanced tree.""" + tree = BST(LEFT_IMBALANCED) + gen = tree.breadth_first() + output = [] + for i in range(6): + output.append(next(gen)) + assert output == [6, 5, 4, 3, 2, 1] + +RIGHT_IMBALANCED = [1, 2, 3, 4, 5, 6] + + +def test_in_order_right_imba(): + """Check for the correct output of iot on a right-imbalanced tree.""" + tree = BST(RIGHT_IMBALANCED) + gen = tree.in_order() + output = [] + for i in range(6): + output.append(next(gen)) + assert output == [1, 2, 3, 4, 5, 6] + + +def test_pre_order_right_imba(): + """Check for the correct output of preo-t on a right-imbalanced tree.""" + tree = BST(RIGHT_IMBALANCED) + gen = tree.pre_order() + output = [] + for i in range(6): + output.append(next(gen)) + assert output == [1, 2, 3, 4, 5, 6] + + +def test_post_order_right_imba(): + """Check for the correct output of posto-t on a right-imbalanced tree.""" + tree = BST(RIGHT_IMBALANCED) + gen = tree.post_order() + output = [] + for i in range(6): + output.append(next(gen)) + assert output == [6, 5, 4, 3, 2, 1] + + +def test_breadth_first_right_imba(): + """Check for the correct output of bft on a right-imbalanced tree.""" + tree = BST(RIGHT_IMBALANCED) + gen = tree.breadth_first() + output = [] + for i in range(6): + output.append(next(gen)) + assert output == [1, 2, 3, 4, 5, 6] + +SAMPLE_TREE = [20, 12, 10, 1, 11, 16, 30, 42, 28, 27] + + +def test_in_order_sample_tree(): + """Check for the correct output of iot on a sample tree.""" + tree = BST(SAMPLE_TREE) + gen = tree.in_order() + output = [] + for i in range(10): + output.append(next(gen)) + assert output == [1, 10, 11, 12, 16, 20, 27, 28, 30, 42] + + +def test_pre_order_sample_tree(): + """Check for the correct output of preo-t on a sample tree.""" + tree = BST(SAMPLE_TREE) + gen = tree.pre_order() + output = [] + for i in range(10): + output.append(next(gen)) + assert output == [20, 12, 10, 1, 11, 16, 30, 28, 27, 42] + + +def test_post_order_sample_tree(): + """Check for the correct output of posto-t on a sample tree.""" + tree = BST(SAMPLE_TREE) + gen = tree.post_order() + output = [] + for i in range(10): + output.append(next(gen)) + assert output == [1, 11, 10, 16, 12, 27, 28, 42, 30, 20] + + +def test_breadth_first_sample_tree(): + """Check for the correct output of bft on a right-imbalanced tree.""" + tree = BST(SAMPLE_TREE) + gen = tree.breadth_first() + output = [] + for i in range(10): + output.append(next(gen)) + assert output == [20, 12, 30, 10, 16, 28, 42, 1, 11, 27] From 06f48b7b9ba7562d368e0c9d0ffaa9530d48b875 Mon Sep 17 00:00:00 2001 From: CHELSEA DOLE Date: Sat, 25 Nov 2017 22:37:16 -0800 Subject: [PATCH 13/68] small changes to naming of tests and docstrings to be more semantic. --- test_bst.py | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/test_bst.py b/test_bst.py index 2b75c5b..b811274 100644 --- a/test_bst.py +++ b/test_bst.py @@ -131,8 +131,6 @@ def test_contains_not_found_returns_none(sample_bst): found = sample_bst.contains(4) assert found is False -# Chelsea tests below - def test_that_bst_doesnt_work_with_non_iterable(): """Test that BST only takes iterable inputs.""" @@ -188,26 +186,25 @@ def test_root_val_with_no_val_at_initialization(sample_bst): assert sample_bst.root is None - -def test_in_order_errors_with_empty_tree(sample_bst): +def test_in_order_indexerrors_with_empty_tree(sample_bst): """Test that in_order raises an IndexError if the tree is empty.""" with pytest.raises(IndexError): sample_bst.in_order() -def test_pre_order_errors_with_empty_tree(sample_bst): +def test_pre_order_indexerrors_with_empty_tree(sample_bst): """Test that pre_order raises an IndexError if the tree is empty.""" with pytest.raises(IndexError): sample_bst.pre_order() -def test_post_order_errors_with_empty_tree(sample_bst): +def test_post_order_indexerrors_with_empty_tree(sample_bst): """Test that post_order raises an IndexError if the tree is empty.""" with pytest.raises(IndexError): sample_bst.post_order() -def test_breadth_first_errors_with_empty_tree(sample_bst): +def test_breadth_first_indexerrors_with_empty_tree(sample_bst): """Test that breadth_first raises an IndexError if the tree is empty.""" with pytest.raises(IndexError): sample_bst.breadth_first() From d46e7bec9a567d0520cea8148f1e80fcd44e1d8d Mon Sep 17 00:00:00 2001 From: CHELSEA DOLE Date: Sat, 25 Nov 2017 22:41:04 -0800 Subject: [PATCH 14/68] moving and changing sample trees --- test_bst.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/test_bst.py b/test_bst.py index b811274..54fd8c2 100644 --- a/test_bst.py +++ b/test_bst.py @@ -237,7 +237,10 @@ def test_breadth_first_size_one(sample_bst): gen = sample_bst.breadth_first() assert next(gen) == 1 + LEFT_IMBALANCED = [6, 5, 4, 3, 2, 1] +RIGHT_IMBALANCED = [1, 2, 3, 4, 5, 6] +SAMPLE_TREE = [20, 12, 10, 1, 11, 16, 30, 42, 28, 27] def test_in_order_left_imba(): @@ -279,8 +282,6 @@ def test_breadth_first_left_imba(): output.append(next(gen)) assert output == [6, 5, 4, 3, 2, 1] -RIGHT_IMBALANCED = [1, 2, 3, 4, 5, 6] - def test_in_order_right_imba(): """Check for the correct output of iot on a right-imbalanced tree.""" @@ -321,8 +322,6 @@ def test_breadth_first_right_imba(): output.append(next(gen)) assert output == [1, 2, 3, 4, 5, 6] -SAMPLE_TREE = [20, 12, 10, 1, 11, 16, 30, 42, 28, 27] - def test_in_order_sample_tree(): """Check for the correct output of iot on a sample tree.""" From 61847674dd97c12cd182f50506adc63387a0e868 Mon Sep 17 00:00:00 2001 From: CHELSEA DOLE Date: Sat, 25 Nov 2017 22:42:31 -0800 Subject: [PATCH 15/68] last commit, further semantic changes --- test_bst.py | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/test_bst.py b/test_bst.py index 54fd8c2..77b764f 100644 --- a/test_bst.py +++ b/test_bst.py @@ -17,8 +17,8 @@ def test_bst_exists(sample_bst): assert sample_bst -def test_bst_can_take_list(): - """Test that the BST can take a list as a param.""" +def test_bst_can_take_list_at_initialization(): + """Test that the BST can take a list.""" from bst import BST b = BST([1, 2, 3]) assert b.size() == 3 @@ -27,8 +27,8 @@ def test_bst_can_take_list(): assert b.right_depth == 2 -def test_bst_can_take_tuple(): - """Test that the BST can take a tuple as a param.""" +def test_bst_can_take_tuple_at_initialization(): + """Test that the BST can take a tuple.""" from bst import BST b = BST((1, 2, 3)) assert b.size() == 3 @@ -37,8 +37,8 @@ def test_bst_can_take_tuple(): assert b.right_depth == 2 -def test_bst_can_take_string(): - """Test that the BST can take a string as a param.""" +def test_bst_can_take_string_at_initialization(): + """Test that the BST can take a string.""" from bst import BST b = BST('abc') assert b.size() == 3 @@ -47,6 +47,15 @@ def test_bst_can_take_string(): assert b.right_depth == 2 +def test_insert_increases_depth(sample_bst): + """Test that the insert method increases the output of the depth method.""" + assert sample_bst.depth() == 0 + sample_bst.insert(1) + assert sample_bst.depth() == 0 + sample_bst.insert(2) + assert sample_bst.depth() == 1 + + def test_insert_increases_size(sample_bst): """Test that the insert method increases the output of the size method.""" assert sample_bst.size() == 0 @@ -65,15 +74,6 @@ def test_insert_increases_tree_size(sample_bst): assert sample_bst.tree_size == 2 -def test_insert_increases_depth(sample_bst): - """Test that the insert method increases the output of the depth method.""" - assert sample_bst.depth() == 0 - sample_bst.insert(1) - assert sample_bst.depth() == 0 - sample_bst.insert(2) - assert sample_bst.depth() == 1 - - def test_search_right(sample_bst): """Assert that the search method returns something and that it's a Node.""" from bst import Node From 7f33e610d2c73add3b75230d500296e7c988cb44 Mon Sep 17 00:00:00 2001 From: CHELSEA DOLE Date: Sat, 25 Nov 2017 22:49:00 -0800 Subject: [PATCH 16/68] first part of README. --- README.md | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 518a54e..b8c0438 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,15 @@ -# data-structures -Data Structures in Python. Code Fellows 401. +# Data-Structures + +**Author**: Chelsea Dole + + +**Testing Tools**: pytest, pytest-cov + +## Data Structures: + +* **Binary Search Tree** — a BST is a "tree shaped" data structure containing nodes. Each node can have a maximum of two children or "leaves," and all node values are properly located based on its parents and siblings values. Nodes to the left of the "root"/head node have values smaller than the root. Those to the right have values larger than the root. There are no duplicate values. + +## Time Complexities: + + + From 05ea5d143c86da620120920c65d89e8a7947a468 Mon Sep 17 00:00:00 2001 From: CHELSEA DOLE Date: Sat, 25 Nov 2017 23:12:56 -0800 Subject: [PATCH 17/68] completing README with runtime of all functions. --- README.md | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index b8c0438..e9bc834 100644 --- a/README.md +++ b/README.md @@ -2,14 +2,33 @@ **Author**: Chelsea Dole +**Resources/Shoutouts**: Nathan Moore (lab partner/amigo) **Testing Tools**: pytest, pytest-cov ## Data Structures: -* **Binary Search Tree** — a BST is a "tree shaped" data structure containing nodes. Each node can have a maximum of two children or "leaves," and all node values are properly located based on its parents and siblings values. Nodes to the left of the "root"/head node have values smaller than the root. Those to the right have values larger than the root. There are no duplicate values. +* **Binary Search Tree** — *a BST is a "tree shaped" data structure containing nodes. Each node can have a maximum of two children or "leaves," and all node values are properly located based on its parents and siblings values. Nodes to the left of the "root"/head node have values smaller than the root. Those to the right have values larger than the root. There are no duplicate values.* ## Time Complexities: +* balance() = *This BST function returns the balance (or size difference) between the left and right parts of the tree. Its runtime is O(1), because it always takes the same amount of time to run regardless of tree size, and only performs simple subtraction.* +* size() = *This BST function returns the number of nodes/leaves in a tree. Its runtime is O(1), because runtime never changes regardless of tree size. It only returns the value of tree_size, which is created at BST initialization, and changed during the insertion of nodes.* + +* insert() = *This BST function inserts a new node into a tree, and uses a helper function called find_home() to find its correctly sorted place in the tree. This function is, depending on the tree, anywhere between O(logn) and O(n), if it's a relatively balanced tree, every decision will reduce the number of nodes one has to traverse. But if it's a one-sided tree, one may look over every node -- making it O(n).* + +* search() = *This BST function is a reference to check_for_equivalence(), which is recursive, and has a runtime of O(n^2), because every time you're re-calling check_for_equivalence, it looks at every node's equivalence.* + +* contains() = *This BST function looks at search(), described above, and for the same reasons has runtime of O(n^2).* + +* depth() = *This BST function returns the number of "levels" that the tree has, by finding which of the two sides has the greatest depth, and returning that. It has a runtime of O(1), because no matter the size of the tree, it only performs a comparison operation.* + +* in_order() = *This BST traversal function traverses the tree and returns a generator that outputs the node values in numerical order. It has a runtime of O(n), not because you visit every node once (you visit them more than once here) but because the work you do/time you take is constant and grows constantly per node addition.* + +* pre_order() = *This BST traversal function returns a generator that outputs the node values in order of the furthest left parent, its left child, then its right child. This traveral then backs up to the parent, and repeats until the whole tree has been traversed. Like in_order, it has a runtime of O(n), not because you visit every node once (you visit them more than once here) but because the work you do/time you take is constant and grows constantly per node addition.* + +* post_order() = *This BST traversal function returns a generator that outputs the node values in order of the bottom-most left node, the bottom-most right node, and then those nodes' parent. Then it backs up, and repeats this action with the parent as a new child node, until the whole tree has been traversed. Like in_order and pre_order, it has a runtime of O(n), not because you visit every node once (you visit them more than once here) but because the work you do/time you take is constant and grows constantly per node addition.* + +* breadth_first() = *This BST traversal returns a generator that outputs the node values in order of their "levels". It produces first the root, then all nodes (left to right) in the first depth level, then all nodes (left to right) in the second depth level, et cetera. Like in_order, pre_order, and post_order, it has a runtime of O(n), not because you visit every node once (you visit them more than once here) but because the work you do/time you take is constant and grows constantly per node addition.* From 2a53b96985a5132e7b52b2974f32fbe36623a1e4 Mon Sep 17 00:00:00 2001 From: CHELSEA DOLE Date: Sat, 25 Nov 2017 23:18:39 -0800 Subject: [PATCH 18/68] adding tox, tox.ini, and setup.py as required by tox. --- setup.py | 15 +++++++++++++++ bst.py => src/bst.py | 0 test_bst.py => src/test_bst.py | 0 tox.ini | 8 ++++++++ 4 files changed, 23 insertions(+) create mode 100644 setup.py rename bst.py => src/bst.py (100%) rename test_bst.py => src/test_bst.py (100%) create mode 100644 tox.ini diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..1284d5a --- /dev/null +++ b/setup.py @@ -0,0 +1,15 @@ +"""Setup module for Chelsea's data structures.""" + +from setuptools import setup + +setup( + name='Data structures', + description='Various data structures in python', + author='Chelsea Dole', + author_email='chelseadole@gmail', + package_dir={' ': 'src'}, + py_modules=['bst'], + install_requires=[], + extras_require={ + 'test': ['pytest', 'pytest-cov', 'pytest-watch', 'tox'], + 'development': ['ipython']}) diff --git a/bst.py b/src/bst.py similarity index 100% rename from bst.py rename to src/bst.py diff --git a/test_bst.py b/src/test_bst.py similarity index 100% rename from test_bst.py rename to src/test_bst.py diff --git a/tox.ini b/tox.ini new file mode 100644 index 0000000..082b2bc --- /dev/null +++ b/tox.ini @@ -0,0 +1,8 @@ +[tox] +envlist = py27, py36 + +[testenv] +commands = py.test --cov --cov-report term-missing +deps = + pytest + pytest-cov \ No newline at end of file From dec712d37e2165abc9b1f8878a0a043c5b15549e Mon Sep 17 00:00:00 2001 From: CHELSEA DOLE Date: Sat, 25 Nov 2017 23:28:05 -0800 Subject: [PATCH 19/68] adding if __name__ is main at bottom of BST. --- bst.py | 273 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 273 insertions(+) create mode 100644 bst.py diff --git a/bst.py b/bst.py new file mode 100644 index 0000000..4665490 --- /dev/null +++ b/bst.py @@ -0,0 +1,273 @@ +"""Implementation of a binary search tree data structure.""" +import timeit as time + + +class Node(object): + """Define the Node-class object.""" + + def __init__(self, value, left=None, right=None, parent=None): + """Constructor for the Node class.""" + self.val = value + self.left = left + self.right = right + self.parent = parent + self.depth = 0 + + +class BST(object): + """Define the BST-class object.""" + + def __init__(self, starting_values=None): + """Constructor for the BST class.""" + self.tree_size = 0 + self.left_depth = 0 + self.right_depth = 0 + self.visited = [] + + if starting_values is None: + self.root = None + + elif isinstance(starting_values, (list, str, tuple)): + self.root = Node(starting_values[0]) + self.tree_size += 1 + for i in range(len(starting_values) - 1): + self.insert(starting_values[i + 1]) + + else: + raise TypeError('Only iterables or None\ + are valid parameters!') + + def balance(self): + """Return the current balance of the BST.""" + return self.right_depth - self.left_depth + + def size(self): + """Return the current size of the BST.""" + return self.tree_size + + def insert(self, value): + """Insert a new node into the BST, and adjust the balance.""" + new_node = Node(value) + + if self.root: + if new_node.val > self.root.val: + if self.root.right: + self._find_home(new_node, self.root.right) + if new_node.depth > self.right_depth: + self.right_depth = new_node.depth + else: + new_node.parent = self.root + self.root.right = new_node + self.root.right.depth = 1 + if self.root.right.depth > self.right_depth: + self.right_depth = self.root.right.depth + self.tree_size += 1 + + elif new_node.val < self.root.val: + if self.root.left: + self._find_home(new_node, self.root.left) + if new_node.depth > self.left_depth: + self.left_depth = new_node.depth + else: + new_node.parent = self.root + self.root.left = new_node + self.root.left.depth = 1 + if self.root.left.depth > self.left_depth: + self.left_depth = self.root.left.depth + self.tree_size += 1 + else: + self.root = new_node + self.tree_size += 1 + + def _find_home(self, node_to_add, node_to_check): + """. + Check if the node_to_add belongs on the left or right + of the node_to_check, then place it there if that spot is empty, + otherwise recur. + """ + if node_to_add.val > node_to_check.val: + if node_to_check.right: + self._find_home(node_to_add, node_to_check.right) + else: + node_to_add.parent = node_to_check + node_to_check.right = node_to_add + node_to_check.right.depth = node_to_check.depth + 1 + self.tree_size += 1 + + elif node_to_add.val < node_to_check.val: + if node_to_check.left: + self._find_home(node_to_add, node_to_check.left) + else: + node_to_add.parent = node_to_check + node_to_check.left = node_to_add + node_to_check.left.depth = node_to_check.depth + 1 + self.tree_size += 1 + + def search(self, value): + """If a value is in the BST, return its node.""" + return self._check_for_equivalence(value, self.root) + + def contains(self, value): + """Return whether or not a value is in the BST.""" + return bool(self.search(value)) + + def _check_for_equivalence(self, value, node_to_check): + """. + Check if the value matches that of the node_to_check + if it does, return the node. If it doesn't, go left or right + as appropriate and recur. If you reach a dead end, return None. + """ + try: + if value == node_to_check.val: + return node_to_check + + except AttributeError: + return None + + if value > node_to_check.val and node_to_check.right: + return self._check_for_equivalence(value, node_to_check.right) + + elif value < node_to_check.val and node_to_check.left: + return self._check_for_equivalence(value, node_to_check.left) + + def depth(self): + """Return the depth of the BST.""" + if self.left_depth > self.right_depth: + return self.left_depth + return self.right_depth + + def in_order(self): + """Return a generator to perform an in-order traversal.""" + self.visited = [] + + if self.root is None: + raise IndexError("Tree is empty!") + + gen = self._in_order_gen() + return gen + + def _in_order_gen(self): + """Recursive helper method for in-order traversal.""" + current = self.root + + while len(self.visited) < self.tree_size: + if current.left: + if current.left.val not in self.visited: + current = current.left + continue + + if current.val not in self.visited: + self.visited.append(current.val) + yield current.val + + if current.right: + if current.right.val not in self.visited: + current = current.right + continue + + current = current.parent + + def pre_order(self): + """Return a generator to perform an pre-order traversal.""" + self.visited = [] + + if self.root is None: + raise IndexError("Tree is empty!") + + gen = self._pre_order_gen() + return gen + + def _pre_order_gen(self): + """Recursive helper method for pre-order traversal.""" + current = self.root + + while len(self.visited) < self.tree_size: + if current.val not in self.visited: + self.visited.append(current.val) + yield current.val + + if current.left: + if current.left.val not in self.visited: + current = current.left + continue + + if current.right: + if current.right.val not in self.visited: + current = current.right + continue + + current = current.parent + + def post_order(self): + """Return a generator to perform an post-order traversal.""" + self.visited = [] + + if self.root is None: + raise IndexError("Tree is empty!") + + gen = self._post_order_gen() + return gen + + def _post_order_gen(self): + """Recursive helper method for post-order traversal.""" + current = self.root + + while len(self.visited) < self.tree_size: + if current.left: + if current.left.val not in self.visited: + current = current.left + continue + + if current.right: + if current.right.val not in self.visited: + current = current.right + continue + + if current.val not in self.visited: + self.visited.append(current.val) + yield current.val + + current = current.parent + + def breadth_first(self): + """Return a generator to perform a breadth-first traversal.""" + self.visited = [] + + if self.root is None: + raise IndexError("Tree is empty!") + + gen = self._breadth_first_gen(self.root) + return gen + + def _breadth_first_gen(self, root_node): + """Helper generator for breadth-first traversal.""" + queue = [self.root] + while queue: + current = queue[0] + yield current.val + queue = queue[1:] + + if current not in self.visited: + self.visited.append(current) + + if current.left: + if current.left not in self.visited: + queue.append(current.left) + + if current.right: + if current.right not in self.visited: + queue.append(current.right) + + +if __name__ == '__main__': # pragma: no cover + left_bigger = BST([6, 5, 4, 3, 2, 1]) + right_bigger = BST([1, 2, 3, 4, 5, 6]) + bal_tree = BST([20, 12, 10, 1, 11, 16, 30, 42, 28, 27]) + + left_bigger = time.timeit("left_bigger.search(5)", setup="from __main__ import left_bigger") + right_bigger = time.timeit("right_bigger.search(5)", setup="from __main__ import right_bigger") + bal_tree = time.timeit("bal_tree.search(8)", setup="from __main__ import bal_tree") + + print('Left-Skewed Search Time: ', left_bigger) + print('Right-Skewed Search Time: ', right_bigger) + print('Balanced Search Time: ', bal_tree) From a054665dede05a452d27e28642d7d394761368a6 Mon Sep 17 00:00:00 2001 From: CHELSEA DOLE Date: Sat, 25 Nov 2017 23:32:49 -0800 Subject: [PATCH 20/68] getting rid of duplicate test_bst file outside of src/ directory. --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 1284d5a..6751995 100644 --- a/setup.py +++ b/setup.py @@ -9,7 +9,7 @@ author_email='chelseadole@gmail', package_dir={' ': 'src'}, py_modules=['bst'], - install_requires=[], + install_requires=['timeit'], extras_require={ 'test': ['pytest', 'pytest-cov', 'pytest-watch', 'tox'], 'development': ['ipython']}) From 31f4293ee2b337854c8d4c7a99b146a019b52d9b Mon Sep 17 00:00:00 2001 From: CHELSEA DOLE Date: Fri, 1 Dec 2017 20:23:17 -0800 Subject: [PATCH 21/68] added Node class with letter and children. --- src/test_trie.py | 0 src/trie.py | 12 ++++++++++++ 2 files changed, 12 insertions(+) create mode 100644 src/test_trie.py create mode 100644 src/trie.py diff --git a/src/test_trie.py b/src/test_trie.py new file mode 100644 index 0000000..e69de29 diff --git a/src/trie.py b/src/trie.py new file mode 100644 index 0000000..99f68f8 --- /dev/null +++ b/src/trie.py @@ -0,0 +1,12 @@ +"""Trie tree structure.""" + + +class Node(object): + """Node class.""" + + def __init__(self, letter=None): + """Initialization of Trie node attributes.""" + self.letter = letter + self.children = {} + + From 105ecd9d286a9353d4f6be5411d40e68ddebfe5b Mon Sep 17 00:00:00 2001 From: CHELSEA DOLE Date: Fri, 1 Dec 2017 20:23:37 -0800 Subject: [PATCH 22/68] initializing Trie class with root and size. --- src/trie.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/trie.py b/src/trie.py index 99f68f8..033ab7c 100644 --- a/src/trie.py +++ b/src/trie.py @@ -10,3 +10,11 @@ def __init__(self, letter=None): self.children = {} +class Trie(object): + """Trie class.""" + + def __init__(self): + """Initialization of Trie tree.""" + self.root = Node('*') + self.size = 0 + From b9f53fb7ca718d307a680c8060be76ad5d080078 Mon Sep 17 00:00:00 2001 From: CHELSEA DOLE Date: Fri, 1 Dec 2017 20:23:54 -0800 Subject: [PATCH 23/68] beginnings of insert function, which takes in word. --- src/trie.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/trie.py b/src/trie.py index 033ab7c..b2f4b49 100644 --- a/src/trie.py +++ b/src/trie.py @@ -18,3 +18,17 @@ def __init__(self): self.root = Node('*') self.size = 0 + def insert(self, word): + """Insert string into Trie.""" + current = self.root + word = word + '$' + while word: + if word[0] == '$': + return "This word is already in the Trie." + if word[0] in current.children: + current = current[word[0]] + else: + for i in word: + current.children[i] = Node(i) + current = current.children[i] + break From 7f6faa9b57690f4848db9a1008888910215a4752 Mon Sep 17 00:00:00 2001 From: CHELSEA DOLE Date: Fri, 1 Dec 2017 22:24:19 -0800 Subject: [PATCH 24/68] adding contains method, rehash of insert method. first four tests. --- src/test_trie.py | 54 ++++++++++++++++++++++++++++++++++++++++++++++++ src/trie.py | 54 +++++++++++++++++++++++++++++++++++++++++------- 2 files changed, 100 insertions(+), 8 deletions(-) diff --git a/src/test_trie.py b/src/test_trie.py index e69de29..b88bb58 100644 --- a/src/test_trie.py +++ b/src/test_trie.py @@ -0,0 +1,54 @@ +"""Tests for Trie tree.""" + +import pytest +from trie import Trie +from trie import Node + + +@pytest.fixture +def empty(): + """Sample Trie without nodes for testing.""" + return Trie() + + +@pytest.fixture +def filled(): + """Sample Trie with contents for testing.""" + t = Trie() + t.insert('hello') + t.insert('goodbye') + t.insert('helsinki') + t.insert('goodlord') + t.insert('squish') + t.insert('heckingoodboye') + return t + + +def test_created_node_has_attributes(): + """Test attributes of Node.""" + n = Node() + assert n.letter is None + assert n.children == {} + + +def test_trie_has_correct_attributes(empty): + """Test that Trie has correct attributes on init.""" + assert empty.root.letter == '*' + assert isinstance(empty.root, Node) + + +def test_insert_adds_word_to_trie(empty): + """Test basic insert method on single word.""" + empty.insert('abc') + assert 'a' in empty.root.children + assert 'b' in empty.root.children['a'].children + assert 'c' in empty.root.children['a'].children['b'].children + assert '$' in empty.root.children['a'].children['b'].children['c'].children + + +def test_word_ends_after_bling_sign(empty): + """Test that nothing comes after the '$' sign inserted.""" + empty.insert('a') + assert 'a' in empty.root.children + assert '$' in empty.root.children['a'].children + assert empty.root.children['a'].children['$'].children == {} diff --git a/src/trie.py b/src/trie.py index b2f4b49..e93372c 100644 --- a/src/trie.py +++ b/src/trie.py @@ -9,6 +9,10 @@ def __init__(self, letter=None): self.letter = letter self.children = {} + def __iter__(self): + """Make children iterable.""" + return self.children.itervalues() + class Trie(object): """Trie class.""" @@ -18,17 +22,51 @@ def __init__(self): self.root = Node('*') self.size = 0 + # def insert(self, word): + # """Insert string into Trie.""" + # current = self.root + # word = word + '$' + # while word: + # if word[0] == '$': + # break + # elif word[0] in current.children: + # current = current.children[word[0]] + # word = word[1:] + # else: + # for i in word: + # current.children[i] = Node(i) + # current = current.children[i] + # break def insert(self, word): """Insert string into Trie.""" current = self.root - word = word + '$' + if self.contains(word): + return + while word: + current.children.setdefault(word[0], Node(word[0])) + current = current.children[word[0]] + word = word[1:] + current.children['$'] = Node() + return + + def contains(self, word): + """Return True if word in trie.""" + current = self.root while word: - if word[0] == '$': - return "This word is already in the Trie." if word[0] in current.children: - current = current[word[0]] + current = current.children[word[0]] + word = word[1:] else: - for i in word: - current.children[i] = Node(i) - current = current.children[i] - break + return False + return True + +if __name__ == "__main__": + t = Trie() + t.insert('hello') + t.insert('hellogoodbye') + t.insert('helsinki') + print(t.contains('hello')) + print(t.contains('hellogoodbye')) + print(t.contains('helsinki')) + print(t.contains('ballsackery')) + print(t.contains('hellgo')) From 14b85fb2bbc9de3ba89591b8e9563252f760998e Mon Sep 17 00:00:00 2001 From: CHELSEA DOLE Date: Fri, 1 Dec 2017 22:26:19 -0800 Subject: [PATCH 25/68] adding fifth test, for adding the same word twice. --- src/test_trie.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/test_trie.py b/src/test_trie.py index b88bb58..6ee9086 100644 --- a/src/test_trie.py +++ b/src/test_trie.py @@ -52,3 +52,12 @@ def test_word_ends_after_bling_sign(empty): assert 'a' in empty.root.children assert '$' in empty.root.children['a'].children assert empty.root.children['a'].children['$'].children == {} + + +def test_word_is_not_added_twice(empty): + """Test that the same word cannot be added twice.""" + empty.insert('yo') + a = empty.root.children + empty.insert('yo') + b = empty.root.children + assert a == b From f6e63a7747653d4de803356fc6e150ea59177dbf Mon Sep 17 00:00:00 2001 From: CHELSEA DOLE Date: Fri, 1 Dec 2017 22:46:07 -0800 Subject: [PATCH 26/68] error handing. fixing insert method to work with a word that is only 1 letter long. --- src/test_trie.py | 32 +++++++++++++++++++++++++++++++- src/trie.py | 15 ++++++++++----- 2 files changed, 41 insertions(+), 6 deletions(-) diff --git a/src/test_trie.py b/src/test_trie.py index 6ee9086..048209f 100644 --- a/src/test_trie.py +++ b/src/test_trie.py @@ -12,7 +12,7 @@ def empty(): @pytest.fixture -def filled(): +def filled_1(): """Sample Trie with contents for testing.""" t = Trie() t.insert('hello') @@ -24,6 +24,17 @@ def filled(): return t +@pytest.fixture +def filled_2(): + """Sample Trie, with simpler contents.""" + t = Trie() + t.insert('abc') + t.insert('az') + t.insert('a') + t.insert('q') + return t + + def test_created_node_has_attributes(): """Test attributes of Node.""" n = Node() @@ -61,3 +72,22 @@ def test_word_is_not_added_twice(empty): empty.insert('yo') b = empty.root.children assert a == b + + +def test_one_letter_word_works(empty): + """Test insert method on one letter word.""" + empty.insert('a') + assert len(empty.children) == 1 + assert '$' in empty.children['a'].children + + +def test_insert_adds_multiple_words(filled_2): + """Test that insert works with multiple words.""" + keys = filled_2.root.children.keys() + assert 'a' in keys + assert 'q' in keys + assert len(filled_2.root.children) == 2 + import pdb; pdb.set_trace() + assert len(filled_2.root.children['a'].children) == 2 + assert 'b' in filled_2.root.children['a'] + assert 'z' in filled_2.root.children['a'] diff --git a/src/trie.py b/src/trie.py index e93372c..098b1b9 100644 --- a/src/trie.py +++ b/src/trie.py @@ -42,11 +42,16 @@ def insert(self, word): current = self.root if self.contains(word): return - while word: - current.children.setdefault(word[0], Node(word[0])) - current = current.children[word[0]] - word = word[1:] - current.children['$'] = Node() + if len(word) == 1: + word_node = Node(word[0]) + current.children[word] = word_node + word_node.children['$'] = Node() + else: + while word: + current.children.setdefault(word[0], Node(word[0])) + current = current.children[word[0]] + word = word[1:] + current.children['$'] = Node() return def contains(self, word): From e6b4018a50a82c0c2e1d217d7749f51378aa17a4 Mon Sep 17 00:00:00 2001 From: CHELSEA DOLE Date: Fri, 1 Dec 2017 23:25:35 -0800 Subject: [PATCH 27/68] continuing to troubleshoot one-letter word issue. --- src/test_trie.py | 9 ++++----- src/trie.py | 27 ++++++++------------------- 2 files changed, 12 insertions(+), 24 deletions(-) diff --git a/src/test_trie.py b/src/test_trie.py index 048209f..1b500fe 100644 --- a/src/test_trie.py +++ b/src/test_trie.py @@ -77,17 +77,16 @@ def test_word_is_not_added_twice(empty): def test_one_letter_word_works(empty): """Test insert method on one letter word.""" empty.insert('a') - assert len(empty.children) == 1 - assert '$' in empty.children['a'].children + assert len(empty.root.children) == 1 + assert '$' in empty.root.children['a'].children def test_insert_adds_multiple_words(filled_2): """Test that insert works with multiple words.""" keys = filled_2.root.children.keys() - assert 'a' in keys - assert 'q' in keys + assert 'a' in keys and 'q' in keys assert len(filled_2.root.children) == 2 import pdb; pdb.set_trace() - assert len(filled_2.root.children['a'].children) == 2 + assert len(filled_2.root.children['a'].children) == 3 assert 'b' in filled_2.root.children['a'] assert 'z' in filled_2.root.children['a'] diff --git a/src/trie.py b/src/trie.py index 098b1b9..67dbeb9 100644 --- a/src/trie.py +++ b/src/trie.py @@ -22,41 +22,30 @@ def __init__(self): self.root = Node('*') self.size = 0 - # def insert(self, word): - # """Insert string into Trie.""" - # current = self.root - # word = word + '$' - # while word: - # if word[0] == '$': - # break - # elif word[0] in current.children: - # current = current.children[word[0]] - # word = word[1:] - # else: - # for i in word: - # current.children[i] = Node(i) - # current = current.children[i] - # break def insert(self, word): """Insert string into Trie.""" current = self.root if self.contains(word): return - if len(word) == 1: - word_node = Node(word[0]) + elif len(word) == 1: + word_node = Node(word) current.children[word] = word_node - word_node.children['$'] = Node() + current = word_node else: while word: current.children.setdefault(word[0], Node(word[0])) current = current.children[word[0]] word = word[1:] - current.children['$'] = Node() + current.children['$'] = Node() return def contains(self, word): """Return True if word in trie.""" current = self.root + # if len(word) == 1: + # import pdb; pdb.set_trace() + # if '$' in current.children[word].children: + # return True while word: if word[0] in current.children: current = current.children[word[0]] From a8b8e7d1fc65d0bbf5abb0c7becf2330ca98f80f Mon Sep 17 00:00:00 2001 From: CHELSEA DOLE Date: Fri, 1 Dec 2017 23:27:05 -0800 Subject: [PATCH 28/68] commenting out nonworking code for later correction. --- src/test_trie.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/test_trie.py b/src/test_trie.py index 1b500fe..6f46c58 100644 --- a/src/test_trie.py +++ b/src/test_trie.py @@ -86,7 +86,6 @@ def test_insert_adds_multiple_words(filled_2): keys = filled_2.root.children.keys() assert 'a' in keys and 'q' in keys assert len(filled_2.root.children) == 2 - import pdb; pdb.set_trace() - assert len(filled_2.root.children['a'].children) == 3 - assert 'b' in filled_2.root.children['a'] - assert 'z' in filled_2.root.children['a'] + # assert len(filled_2.root.children['a'].children) == 3 + assert 'b' in filled_2.root.children['a'].children + assert 'z' in filled_2.root.children['a'].children From 916b6a255b99540f967297d48e9b973bc3bb4f4a Mon Sep 17 00:00:00 2001 From: CHELSEA DOLE Date: Fri, 1 Dec 2017 23:32:03 -0800 Subject: [PATCH 29/68] adding size function. --- src/trie.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/trie.py b/src/trie.py index 67dbeb9..f92dc7e 100644 --- a/src/trie.py +++ b/src/trie.py @@ -37,6 +37,7 @@ def insert(self, word): current = current.children[word[0]] word = word[1:] current.children['$'] = Node() + self.size += 1 return def contains(self, word): @@ -54,6 +55,10 @@ def contains(self, word): return False return True + def size(self): + """Return number of words in Trie tree.""" + return self.size + if __name__ == "__main__": t = Trie() t.insert('hello') From c5285a4ba2a51f40d49dc1786fbcbd8e7aa90a83 Mon Sep 17 00:00:00 2001 From: CHELSEA DOLE Date: Fri, 1 Dec 2017 23:37:11 -0800 Subject: [PATCH 30/68] adding contains test --- src/test_trie.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/test_trie.py b/src/test_trie.py index 6f46c58..a18c560 100644 --- a/src/test_trie.py +++ b/src/test_trie.py @@ -89,3 +89,16 @@ def test_insert_adds_multiple_words(filled_2): # assert len(filled_2.root.children['a'].children) == 3 assert 'b' in filled_2.root.children['a'].children assert 'z' in filled_2.root.children['a'].children + + +def test_insert_adds_multiple_words_using_contains(filled_1): + """Test combo of contains and insert method.""" + assert filled_1.contains('hello') + assert filled_1.contains('goodbye') + assert filled_1.contains('helsinki') + assert filled_1.contains('goodlord') + assert filled_1.contains('squish') + assert filled_1.contains('heckingoodboye') + assert not filled_1.contains('thisisnothere') + + From 24dba69b6a5191e3df9c2f694ce2dcebfa5fdeb7 Mon Sep 17 00:00:00 2001 From: CHELSEA DOLE Date: Sat, 2 Dec 2017 00:09:21 -0800 Subject: [PATCH 31/68] incorrect code commented out for later. --- src/test_trie.py | 34 ++++++++++++++++++++++++++-------- src/trie.py | 6 ++---- 2 files changed, 28 insertions(+), 12 deletions(-) diff --git a/src/test_trie.py b/src/test_trie.py index a18c560..656fa05 100644 --- a/src/test_trie.py +++ b/src/test_trie.py @@ -81,14 +81,14 @@ def test_one_letter_word_works(empty): assert '$' in empty.root.children['a'].children -def test_insert_adds_multiple_words(filled_2): - """Test that insert works with multiple words.""" - keys = filled_2.root.children.keys() - assert 'a' in keys and 'q' in keys - assert len(filled_2.root.children) == 2 - # assert len(filled_2.root.children['a'].children) == 3 - assert 'b' in filled_2.root.children['a'].children - assert 'z' in filled_2.root.children['a'].children +# def test_insert_adds_multiple_words(filled_2): +# """Test that insert works with multiple words.""" +# keys = filled_2.root.children.keys() +# assert 'a' in keys and 'q' in keys +# assert len(keys) == 2 +# # assert len(filled_2.root.children['a'].children) == 3 +# assert 'b' in filled_2.root.children['a'].children +# assert 'z' in filled_2.root.children['a'].children def test_insert_adds_multiple_words_using_contains(filled_1): @@ -102,3 +102,21 @@ def test_insert_adds_multiple_words_using_contains(filled_1): assert not filled_1.contains('thisisnothere') +def test_size_method_on_empty_trie(empty): + """Test size on empy trie instance.""" + assert empty.size == 0 + + +def test_size_method_on_filled_trie(filled_1): + """Test size on empy trie instance.""" + assert filled_1.size == 6 + + +def test_size_method_on_second_filled_trie(): + """Test size on empy trie instance.""" + t = Trie() + t.insert('abc') + t.insert('az') + t.insert('a') + t.insert('q') + assert t.size == 4 diff --git a/src/trie.py b/src/trie.py index f92dc7e..3337e06 100644 --- a/src/trie.py +++ b/src/trie.py @@ -43,10 +43,8 @@ def insert(self, word): def contains(self, word): """Return True if word in trie.""" current = self.root - # if len(word) == 1: - # import pdb; pdb.set_trace() - # if '$' in current.children[word].children: - # return True + if len(word) == 1 and word in current.children: + return False while word: if word[0] in current.children: current = current.children[word[0]] From fc43db3550688b1829dad102b81c3bfd55926532 Mon Sep 17 00:00:00 2001 From: CHELSEA DOLE Date: Sat, 2 Dec 2017 00:21:21 -0800 Subject: [PATCH 32/68] fixing contains method so it doesnt pick up fragments of words. --- src/test_trie.py | 20 +++++++++++++------- src/trie.py | 7 +++++-- 2 files changed, 18 insertions(+), 9 deletions(-) diff --git a/src/test_trie.py b/src/test_trie.py index 656fa05..552695b 100644 --- a/src/test_trie.py +++ b/src/test_trie.py @@ -40,6 +40,7 @@ def test_created_node_has_attributes(): n = Node() assert n.letter is None assert n.children == {} + assert n.end is False def test_trie_has_correct_attributes(empty): @@ -54,15 +55,13 @@ def test_insert_adds_word_to_trie(empty): assert 'a' in empty.root.children assert 'b' in empty.root.children['a'].children assert 'c' in empty.root.children['a'].children['b'].children - assert '$' in empty.root.children['a'].children['b'].children['c'].children -def test_word_ends_after_bling_sign(empty): - """Test that nothing comes after the '$' sign inserted.""" +def test_word_has_end_attribute(empty): + """Test that nothing comes after the sign inserted.""" empty.insert('a') assert 'a' in empty.root.children - assert '$' in empty.root.children['a'].children - assert empty.root.children['a'].children['$'].children == {} + assert empty.root.children['a'].end is True def test_word_is_not_added_twice(empty): @@ -78,8 +77,6 @@ def test_one_letter_word_works(empty): """Test insert method on one letter word.""" empty.insert('a') assert len(empty.root.children) == 1 - assert '$' in empty.root.children['a'].children - # def test_insert_adds_multiple_words(filled_2): # """Test that insert works with multiple words.""" @@ -102,6 +99,13 @@ def test_insert_adds_multiple_words_using_contains(filled_1): assert not filled_1.contains('thisisnothere') +def test_contains_where_it_returns_false(filled_2, filled_1): + """Test false contains.""" + assert not filled_2.contains('nooooope') + assert not filled_1.contains('h') + assert not filled_1.contains('good') + + def test_size_method_on_empty_trie(empty): """Test size on empy trie instance.""" assert empty.size == 0 @@ -120,3 +124,5 @@ def test_size_method_on_second_filled_trie(): t.insert('a') t.insert('q') assert t.size == 4 + + diff --git a/src/trie.py b/src/trie.py index 3337e06..7b8ae0f 100644 --- a/src/trie.py +++ b/src/trie.py @@ -4,10 +4,11 @@ class Node(object): """Node class.""" - def __init__(self, letter=None): + def __init__(self, letter=None, end=False): """Initialization of Trie node attributes.""" self.letter = letter self.children = {} + self.end = end def __iter__(self): """Make children iterable.""" @@ -36,7 +37,7 @@ def insert(self, word): current.children.setdefault(word[0], Node(word[0])) current = current.children[word[0]] word = word[1:] - current.children['$'] = Node() + current.end = True self.size += 1 return @@ -51,6 +52,8 @@ def contains(self, word): word = word[1:] else: return False + if not current.end: + return False return True def size(self): From 667baec6f4bfc1b4e1de841b2313d1331564fbd6 Mon Sep 17 00:00:00 2001 From: CHELSEA DOLE Date: Sun, 3 Dec 2017 11:22:56 -0800 Subject: [PATCH 33/68] redoing contains method to use for loop, and be shorter.' --- src/test_trie.py | 3 +++ src/trie.py | 32 +++++++++++--------------------- 2 files changed, 14 insertions(+), 21 deletions(-) diff --git a/src/test_trie.py b/src/test_trie.py index 552695b..ef3fe2b 100644 --- a/src/test_trie.py +++ b/src/test_trie.py @@ -78,11 +78,13 @@ def test_one_letter_word_works(empty): empty.insert('a') assert len(empty.root.children) == 1 + # def test_insert_adds_multiple_words(filled_2): # """Test that insert works with multiple words.""" # keys = filled_2.root.children.keys() # assert 'a' in keys and 'q' in keys # assert len(keys) == 2 +# import pdb; pdb.set_trace() # # assert len(filled_2.root.children['a'].children) == 3 # assert 'b' in filled_2.root.children['a'].children # assert 'z' in filled_2.root.children['a'].children @@ -104,6 +106,7 @@ def test_contains_where_it_returns_false(filled_2, filled_1): assert not filled_2.contains('nooooope') assert not filled_1.contains('h') assert not filled_1.contains('good') + assert not filled_1.contains('squi') def test_size_method_on_empty_trie(empty): diff --git a/src/trie.py b/src/trie.py index 7b8ae0f..5621073 100644 --- a/src/trie.py +++ b/src/trie.py @@ -42,31 +42,21 @@ def insert(self, word): return def contains(self, word): - """Return True if word in trie.""" + """Check if Trie contains word.""" current = self.root - if len(word) == 1 and word in current.children: - return False - while word: - if word[0] in current.children: - current = current.children[word[0]] - word = word[1:] - else: + for letter in word: + if letter not in current.children: return False - if not current.end: - return False - return True + current = current.children[letter] + if current.end: + return True + return False def size(self): """Return number of words in Trie tree.""" return self.size -if __name__ == "__main__": - t = Trie() - t.insert('hello') - t.insert('hellogoodbye') - t.insert('helsinki') - print(t.contains('hello')) - print(t.contains('hellogoodbye')) - print(t.contains('helsinki')) - print(t.contains('ballsackery')) - print(t.contains('hellgo')) + def remove(self, word): + """Remove word from trie.""" + if not self.contains(word): + raise KeyError('This word is not in the Trie.') From ab1df91e540661c715c09cd32a26c928c49e9924 Mon Sep 17 00:00:00 2001 From: CHELSEA DOLE Date: Sun, 3 Dec 2017 11:26:39 -0800 Subject: [PATCH 34/68] changing insert to use a for loop --- src/trie.py | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/src/trie.py b/src/trie.py index 5621073..f6dd6f3 100644 --- a/src/trie.py +++ b/src/trie.py @@ -24,19 +24,13 @@ def __init__(self): self.size = 0 def insert(self, word): - """Insert string into Trie.""" + """Insert a new word into the tree.""" current = self.root if self.contains(word): return - elif len(word) == 1: - word_node = Node(word) - current.children[word] = word_node - current = word_node - else: - while word: - current.children.setdefault(word[0], Node(word[0])) - current = current.children[word[0]] - word = word[1:] + for letter in word: + current.children.setdefault(letter, Node(letter)) + current = current.children[letter] current.end = True self.size += 1 return From 31e55590e6f2dad3362c84cdbde94bb2b64ddfc7 Mon Sep 17 00:00:00 2001 From: CHELSEA DOLE Date: Sun, 3 Dec 2017 12:07:45 -0800 Subject: [PATCH 35/68] working on remove method. --- src/trie.py | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/src/trie.py b/src/trie.py index f6dd6f3..abf9ae8 100644 --- a/src/trie.py +++ b/src/trie.py @@ -4,10 +4,11 @@ class Node(object): """Node class.""" - def __init__(self, letter=None, end=False): + def __init__(self, letter=None, parent=None, end=False): """Initialization of Trie node attributes.""" self.letter = letter self.children = {} + self.parent = parent self.end = end def __iter__(self): @@ -26,10 +27,10 @@ def __init__(self): def insert(self, word): """Insert a new word into the tree.""" current = self.root - if self.contains(word): + if self.contains(word) or type(word) is not str: return for letter in word: - current.children.setdefault(letter, Node(letter)) + current.children.setdefault(letter, Node(letter, current)) current = current.children[letter] current.end = True self.size += 1 @@ -52,5 +53,16 @@ def size(self): def remove(self, word): """Remove word from trie.""" - if not self.contains(word): - raise KeyError('This word is not in the Trie.') + # if not self.contains(word): + # raise KeyError('This word is not in the Trie.') + # current = self.root + # for letter in word: + current = self.root + for letter in range(len(word - 1)): + if letter not in current.children: + raise TypeError('This word is not in Trie.') + current = current.children[letter] + current = current.parent + while len(current.children) == 1: + current.children.clear() + current = current.parent From 226bc0e677ba915a09fab85c9311d64da4029cde Mon Sep 17 00:00:00 2001 From: CHELSEA DOLE Date: Sun, 3 Dec 2017 12:10:31 -0800 Subject: [PATCH 36/68] adding size to remove method. --- src/trie.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/trie.py b/src/trie.py index abf9ae8..98fffc6 100644 --- a/src/trie.py +++ b/src/trie.py @@ -53,12 +53,8 @@ def size(self): def remove(self, word): """Remove word from trie.""" - # if not self.contains(word): - # raise KeyError('This word is not in the Trie.') - # current = self.root - # for letter in word: current = self.root - for letter in range(len(word - 1)): + for letter in word: if letter not in current.children: raise TypeError('This word is not in Trie.') current = current.children[letter] @@ -66,3 +62,4 @@ def remove(self, word): while len(current.children) == 1: current.children.clear() current = current.parent + self.size -= 1 From 9345b218181cb20020344325656bf6617f96d416 Mon Sep 17 00:00:00 2001 From: CHELSEA DOLE Date: Sun, 3 Dec 2017 12:23:47 -0800 Subject: [PATCH 37/68] finishing last test, Trie complete woooo --- src/test_trie.py | 50 ++++++++++++++++++++++++++++++++++++------------ src/trie.py | 8 ++++---- 2 files changed, 42 insertions(+), 16 deletions(-) diff --git a/src/test_trie.py b/src/test_trie.py index ef3fe2b..87c5a6b 100644 --- a/src/test_trie.py +++ b/src/test_trie.py @@ -79,15 +79,14 @@ def test_one_letter_word_works(empty): assert len(empty.root.children) == 1 -# def test_insert_adds_multiple_words(filled_2): -# """Test that insert works with multiple words.""" -# keys = filled_2.root.children.keys() -# assert 'a' in keys and 'q' in keys -# assert len(keys) == 2 -# import pdb; pdb.set_trace() -# # assert len(filled_2.root.children['a'].children) == 3 -# assert 'b' in filled_2.root.children['a'].children -# assert 'z' in filled_2.root.children['a'].children +def test_insert_adds_multiple_words(filled_2): + """Test that insert works with multiple words.""" + keys = filled_2.root.children.keys() + assert 'a' in keys and 'q' in keys + assert len(keys) == 2 + assert len(filled_2.root.children['a'].children) == 2 + assert 'b' in filled_2.root.children['a'].children + assert 'z' in filled_2.root.children['a'].children def test_insert_adds_multiple_words_using_contains(filled_1): @@ -111,12 +110,12 @@ def test_contains_where_it_returns_false(filled_2, filled_1): def test_size_method_on_empty_trie(empty): """Test size on empy trie instance.""" - assert empty.size == 0 + assert empty.size() == 0 def test_size_method_on_filled_trie(filled_1): """Test size on empy trie instance.""" - assert filled_1.size == 6 + assert filled_1.size() == 6 def test_size_method_on_second_filled_trie(): @@ -126,6 +125,33 @@ def test_size_method_on_second_filled_trie(): t.insert('az') t.insert('a') t.insert('q') - assert t.size == 4 + assert t.size() == 4 +def test_remove_method_doesnt_work_without_word(filled_1): + """Test that the size method will raise TypeError.""" + with pytest.raises(TypeError): + filled_1.remove('thiswordisnotindict') + + +def test_remove_will_remove_word_from_dict(filled_1): + """Test remove method will remove word off Trie.""" + assert filled_1.contains('heckingoodboye') + filled_1.remove('heckingoodboye') + assert filled_1.contains('heckingoodboye') is False + + +def test_remove_wont_remove_words_with_same_beginning(empty): + """Test that remove method wont remove words if they start with the same letters.""" + empty.insert('antidisestablishmentarianism') + empty.insert('antimatter') + empty.remove('antimatter') + assert empty.contains('antidisestablishmentarianism') + assert empty.contains('antimatter') is False + + +def test_size_decreases_with_removing_node(filled_2): + """Test size of tree reduces with you delete a word.""" + assert filled_2.size() == 4 + filled_2.remove('az') + assert filled_2.size() == 3 diff --git a/src/trie.py b/src/trie.py index 98fffc6..bdd9e0d 100644 --- a/src/trie.py +++ b/src/trie.py @@ -22,7 +22,7 @@ class Trie(object): def __init__(self): """Initialization of Trie tree.""" self.root = Node('*') - self.size = 0 + self.tree_size = 0 def insert(self, word): """Insert a new word into the tree.""" @@ -33,7 +33,7 @@ def insert(self, word): current.children.setdefault(letter, Node(letter, current)) current = current.children[letter] current.end = True - self.size += 1 + self.tree_size += 1 return def contains(self, word): @@ -49,7 +49,7 @@ def contains(self, word): def size(self): """Return number of words in Trie tree.""" - return self.size + return self.tree_size def remove(self, word): """Remove word from trie.""" @@ -62,4 +62,4 @@ def remove(self, word): while len(current.children) == 1: current.children.clear() current = current.parent - self.size -= 1 + self.tree_size -= 1 From 11b959926016c7b6a9507132e6ff235fb8327f93 Mon Sep 17 00:00:00 2001 From: CHELSEA DOLE Date: Sun, 3 Dec 2017 12:33:46 -0800 Subject: [PATCH 38/68] adding one last test and changing remove method. --- src/test_trie.py | 8 ++++++++ src/trie.py | 3 ++- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/src/test_trie.py b/src/test_trie.py index 87c5a6b..6b7fbd5 100644 --- a/src/test_trie.py +++ b/src/test_trie.py @@ -134,6 +134,14 @@ def test_remove_method_doesnt_work_without_word(filled_1): filled_1.remove('thiswordisnotindict') +def test_deleting_single_word(empty): + """.""" + empty.insert('ace') + empty.remove('ace') + assert empty.size() == 0 + assert empty.contains('ace') is False + + def test_remove_will_remove_word_from_dict(filled_1): """Test remove method will remove word off Trie.""" assert filled_1.contains('heckingoodboye') diff --git a/src/trie.py b/src/trie.py index bdd9e0d..15d3d70 100644 --- a/src/trie.py +++ b/src/trie.py @@ -61,5 +61,6 @@ def remove(self, word): current = current.parent while len(current.children) == 1: current.children.clear() - current = current.parent + if current.parent: + current = current.parent self.tree_size -= 1 From 0299d59d99fffb8eb872387194836ce36af442c8 Mon Sep 17 00:00:00 2001 From: CHELSEA DOLE Date: Sun, 3 Dec 2017 12:44:03 -0800 Subject: [PATCH 39/68] adding new functions and Trie info to README. --- README.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/README.md b/README.md index e9bc834..bac13c9 100644 --- a/README.md +++ b/README.md @@ -10,6 +10,8 @@ * **Binary Search Tree** — *a BST is a "tree shaped" data structure containing nodes. Each node can have a maximum of two children or "leaves," and all node values are properly located based on its parents and siblings values. Nodes to the left of the "root"/head node have values smaller than the root. Those to the right have values larger than the root. There are no duplicate values.* +* **Trie Tree** - *a Trie Tree is a "tree shaped" data structure containing nodes with references to letters. These nodes string together (using each node's "children" and "parent" attriutes) to form words. This tree allows for quick lookup time of words, and is used for things such as word suggestion/auto-complete.* + ## Time Complexities: * balance() = *This BST function returns the balance (or size difference) between the left and right parts of the tree. Its runtime is O(1), because it always takes the same amount of time to run regardless of tree size, and only performs simple subtraction.* @@ -32,3 +34,11 @@ * breadth_first() = *This BST traversal returns a generator that outputs the node values in order of their "levels". It produces first the root, then all nodes (left to right) in the first depth level, then all nodes (left to right) in the second depth level, et cetera. Like in_order, pre_order, and post_order, it has a runtime of O(n), not because you visit every node once (you visit them more than once here) but because the work you do/time you take is constant and grows constantly per node addition.* +* insert() = *This Trie insert method adds a word to the trie tree. It first checks to see if the word is already in the tree (in which case it does nothing). Then, it goes through each letter of the word and uses the dictionary function setdefault to add a new letter node if it doesn't already exist, and string together the letters. Finally, it increases the tree's size attribute.* + +* contains() = *This Trie method checks if the tree contains a certain word. It does this by iterating through each letter of the word and checking if the letter node's children dictionary contains a key to the next letter in the word. If at any point it doesnt (or if the last letter of the word doesn't have the "end" attribute as True) it returns False.* + +* size() = *This Trie method returns the number of words in the tree by returning the tree's size attribute, which is incremented and decremented in insert() and remove() respectively.* + +* remove() = *This Trie method removes a word from the tree. First it traverses to the node of the last letter of the tree (and raises an error if the word doesnt exist). Once at the last letter, it moves backwards, deleting references to the children/letters below.* + From 60daa5fbb9cfbf8092d50b1ddbd1965fcbe53bef Mon Sep 17 00:00:00 2001 From: CHELSEA DOLE Date: Sun, 3 Dec 2017 12:59:01 -0800 Subject: [PATCH 40/68] adding Travis.CI badge/file. --- .travis.yml | 9 +++++++++ README.md | 2 ++ 2 files changed, 11 insertions(+) create mode 100644 .travis.yml diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..3d65f30 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,9 @@ +language: python +python: + - "2.7" + - "3.6" + +install: + - pip install pytest +script: + - pytest diff --git a/README.md b/README.md index bac13c9..f9365c0 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,8 @@ **Author**: Chelsea Dole +**Coverage**: [Build Status](https://travis-ci.org/chelseadole/data-structures.svg?branch=master) + **Resources/Shoutouts**: Nathan Moore (lab partner/amigo) **Testing Tools**: pytest, pytest-cov From 7732ece0c6e53c781128f62bb9b43574ce1a04d1 Mon Sep 17 00:00:00 2001 From: CHELSEA DOLE Date: Sun, 3 Dec 2017 13:10:07 -0800 Subject: [PATCH 41/68] adding big-o runtime to all fns in README. --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index f9365c0..f079e1d 100644 --- a/README.md +++ b/README.md @@ -36,11 +36,11 @@ * breadth_first() = *This BST traversal returns a generator that outputs the node values in order of their "levels". It produces first the root, then all nodes (left to right) in the first depth level, then all nodes (left to right) in the second depth level, et cetera. Like in_order, pre_order, and post_order, it has a runtime of O(n), not because you visit every node once (you visit them more than once here) but because the work you do/time you take is constant and grows constantly per node addition.* -* insert() = *This Trie insert method adds a word to the trie tree. It first checks to see if the word is already in the tree (in which case it does nothing). Then, it goes through each letter of the word and uses the dictionary function setdefault to add a new letter node if it doesn't already exist, and string together the letters. Finally, it increases the tree's size attribute.* +* insert() = *This Trie insert method adds a word to the trie tree. It first checks to see if the word is already in the tree (in which case it does nothing). Then, it goes through each letter of the word and uses the dictionary function setdefault to add a new letter node if it doesn't already exist, and string together the letters. Finally, it increases the tree's size attribute. The time complexity is O(len(word)), because the length of runtime depends on the size of the word you're inserting.* -* contains() = *This Trie method checks if the tree contains a certain word. It does this by iterating through each letter of the word and checking if the letter node's children dictionary contains a key to the next letter in the word. If at any point it doesnt (or if the last letter of the word doesn't have the "end" attribute as True) it returns False.* +* contains() = *This Trie method checks if the tree contains a certain word. It does this by iterating through each letter of the word and checking if the letter node's children dictionary contains a key to the next letter in the word. If at any point it doesnt (or if the last letter of the word doesn't have the "end" attribute as True) it returns False. The time complexity is at worst case, O(n), because in the worst case scenario, you have just one word in the tree, and you have to check through all the letters in that one word.* -* size() = *This Trie method returns the number of words in the tree by returning the tree's size attribute, which is incremented and decremented in insert() and remove() respectively.* +* size() = *This Trie method returns the number of words in the tree by returning the tree's size attribute, which is incremented and decremented in insert() and remove() respectively. Time complexity should be O(1), because it just returns a number: the attribute of Trie.* -* remove() = *This Trie method removes a word from the tree. First it traverses to the node of the last letter of the tree (and raises an error if the word doesnt exist). Once at the last letter, it moves backwards, deleting references to the children/letters below.* +* remove() = *This Trie method removes a word from the tree. First it traverses to the node of the last letter of the tree (and raises an error if the word doesnt exist). Once at the last letter, it moves backwards, deleting references to the children/letters below. Time complexity should be O(n * 2), because worst case scenario, the word you're removing is the only word in the tree, and you had to traverse all the way down the letters then come back up.* From a10dd2e4dc7f724593691cfc06e996b236819dba Mon Sep 17 00:00:00 2001 From: CHELSEA DOLE Date: Sun, 3 Dec 2017 13:10:40 -0800 Subject: [PATCH 42/68] travis badge --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index f079e1d..90a6e94 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ **Author**: Chelsea Dole -**Coverage**: [Build Status](https://travis-ci.org/chelseadole/data-structures.svg?branch=master) +**Coverage**: [![Build Status](https://travis-ci.org/chelseadole/data-structures.svg?branch=master)](https://travis-ci.org/chelseadole/data-structures) **Resources/Shoutouts**: Nathan Moore (lab partner/amigo) From b33a685d659c00c51c675f1ae1ccf671c4fdbe16 Mon Sep 17 00:00:00 2001 From: CHELSEA DOLE Date: Wed, 6 Dec 2017 16:17:52 -0800 Subject: [PATCH 43/68] adding first part of trie_traversal, finding start in tree from root. --- src/trie.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/trie.py b/src/trie.py index 15d3d70..b61caa2 100644 --- a/src/trie.py +++ b/src/trie.py @@ -64,3 +64,18 @@ def remove(self, word): if current.parent: current = current.parent self.tree_size -= 1 + + def trie_traversal(self, start='*'): + """Depth-first traveral of Trie.""" + self.visited = [] + + curr = start + if start is not '*': + for char in start: + if char in curr.children: + curr = curr.children[char] + return 'Invalid starting string.' + + trie_gen = self._trie_gen(curr) + return trie_gen + From c47630c48b741f323d000128d0bbe9ab0c193ca0 Mon Sep 17 00:00:00 2001 From: CHELSEA DOLE Date: Wed, 6 Dec 2017 16:18:30 -0800 Subject: [PATCH 44/68] adding _trie_gen generator with recursive helper function _recursive_depth. --- src/trie.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/trie.py b/src/trie.py index b61caa2..8de4974 100644 --- a/src/trie.py +++ b/src/trie.py @@ -79,3 +79,14 @@ def trie_traversal(self, start='*'): trie_gen = self._trie_gen(curr) return trie_gen + def _trie_gen(self, start): + """.""" + for child in self.start.children.keys(): + return self._recursive_depth(start.children[child]) + + def _recursive_depth(self, node): + """.""" + self.visited.append(node.letter) + for child in node.children.keys(): + self._recursive_depth(node.children[child]) + From aef05859623ccb1a5dab6c3511e65e9d479e66b9 Mon Sep 17 00:00:00 2001 From: CHELSEA DOLE Date: Wed, 6 Dec 2017 18:03:41 -0800 Subject: [PATCH 45/68] changes to new, combined version of two recursive functions. --- src/trie.py | 39 +++++++++++++++++++++++++++------------ 1 file changed, 27 insertions(+), 12 deletions(-) diff --git a/src/trie.py b/src/trie.py index 8de4974..b229040 100644 --- a/src/trie.py +++ b/src/trie.py @@ -65,28 +65,43 @@ def remove(self, word): current = current.parent self.tree_size -= 1 - def trie_traversal(self, start='*'): + def trie_traversal(self, start=None): """Depth-first traveral of Trie.""" self.visited = [] - curr = start - if start is not '*': + if start: + curr = self.root for char in start: if char in curr.children: curr = curr.children[char] return 'Invalid starting string.' - - trie_gen = self._trie_gen(curr) + trie_gen = self._combo_gen(curr) + else: + trie_gen = self._combo_gen(self.root) return trie_gen - def _trie_gen(self, start): + def _combo_gen(self, start): """.""" - for child in self.start.children.keys(): - return self._recursive_depth(start.children[child]) + for child, child_node in start.children.items(): + import pdb; pdb.set_trace() + self.visited.append(child) + for node in child_node.children: + self.visited.append(child_node.children[node].letter) + if child_node.children[node].end: + break + child_node.children = child_node.children[node].children + for let in self.visited: + yield let + + def _trie_gen(self, start): + """Generator for traversal function.""" + for child in start.children: + yield self._recursive_depth(start.children[child]) def _recursive_depth(self, node): - """.""" + """Recursive helper fn for generator.""" self.visited.append(node.letter) - for child in node.children.keys(): - self._recursive_depth(node.children[child]) - + for child in node.children: + if child.end: + return child.letter + return self._recursive_depth(node.children[child]) From f16656e0bc5c1402acd7bae3ccc91214497085d0 Mon Sep 17 00:00:00 2001 From: CHELSEA DOLE Date: Wed, 6 Dec 2017 18:29:40 -0800 Subject: [PATCH 46/68] more options for trie traversal --- src/trie.py | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/src/trie.py b/src/trie.py index b229040..d22ad11 100644 --- a/src/trie.py +++ b/src/trie.py @@ -75,10 +75,9 @@ def trie_traversal(self, start=None): if char in curr.children: curr = curr.children[char] return 'Invalid starting string.' - trie_gen = self._combo_gen(curr) + return self._combo_gen(curr) else: - trie_gen = self._combo_gen(self.root) - return trie_gen + return self._combo_gen(self.root) def _combo_gen(self, start): """.""" @@ -87,8 +86,8 @@ def _combo_gen(self, start): self.visited.append(child) for node in child_node.children: self.visited.append(child_node.children[node].letter) - if child_node.children[node].end: - break + if child_node.children[node].end and not child_node.children: + continue child_node.children = child_node.children[node].children for let in self.visited: yield let @@ -96,12 +95,12 @@ def _combo_gen(self, start): def _trie_gen(self, start): """Generator for traversal function.""" for child in start.children: - yield self._recursive_depth(start.children[child]) + return self._recursive_depth(start.children[child]) def _recursive_depth(self, node): """Recursive helper fn for generator.""" self.visited.append(node.letter) for child in node.children: if child.end: - return child.letter - return self._recursive_depth(node.children[child]) + break + yield self._recursive_depth(node.children[child]) From 3c8606eb5cb3eed31ff0b7945ea935799099a407 Mon Sep 17 00:00:00 2001 From: CHELSEA DOLE Date: Thu, 7 Dec 2017 14:13:26 -0800 Subject: [PATCH 47/68] adding basics of autocomplete --- src/trie.py | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/src/trie.py b/src/trie.py index d22ad11..937a060 100644 --- a/src/trie.py +++ b/src/trie.py @@ -82,7 +82,6 @@ def trie_traversal(self, start=None): def _combo_gen(self, start): """.""" for child, child_node in start.children.items(): - import pdb; pdb.set_trace() self.visited.append(child) for node in child_node.children: self.visited.append(child_node.children[node].letter) @@ -104,3 +103,21 @@ def _recursive_depth(self, node): if child.end: break yield self._recursive_depth(node.children[child]) + + def autocomplete(self, start): + """.""" + curr = self.root + for letter in start: + if letter not in curr.children: + return [] + curr = curr.children[letter] + return self._auto_helper(curr, start) + + def _auto_helper(self, node, start): + """.""" + if node.assert_end: + yield start + for letter in node.children: + for word in self._auto_helper(node.children[letter], start + letter): + yield word + From 5aa97a213ec816fce41f4bb3d3cd69a9ae9ebd88 Mon Sep 17 00:00:00 2001 From: CHELSEA DOLE Date: Sat, 9 Dec 2017 13:38:25 -0800 Subject: [PATCH 48/68] wrote bubble sort, no tests. --- src/bubblesort.py | 0 src/test_bubblesort.py | 10 ++++++++++ 2 files changed, 10 insertions(+) create mode 100644 src/bubblesort.py create mode 100644 src/test_bubblesort.py diff --git a/src/bubblesort.py b/src/bubblesort.py new file mode 100644 index 0000000..e69de29 diff --git a/src/test_bubblesort.py b/src/test_bubblesort.py new file mode 100644 index 0000000..40dcd5b --- /dev/null +++ b/src/test_bubblesort.py @@ -0,0 +1,10 @@ +"""Implementation of Bubble Sort in Python.""" + + +def bubblesort(lst): + """Bubble sorting algorithm.""" + for i in range(len(lst)): + for j in range(len(lst) - 1, i, -1): + if lst[j] < lst[j - 1]: + lst[j], lst[j - 1] = lst[j - 1], lst[j] + return lst From 501a8260d24b60ad90b78dd955516affa7462563 Mon Sep 17 00:00:00 2001 From: CHELSEA DOLE Date: Sat, 9 Dec 2017 13:40:33 -0800 Subject: [PATCH 49/68] Accidentally wrote fn in wrong file. Moved back to bubblesort.py. --- src/bubblesort.py | 10 ++++++++++ src/test_bubblesort.py | 13 +++++-------- 2 files changed, 15 insertions(+), 8 deletions(-) diff --git a/src/bubblesort.py b/src/bubblesort.py index e69de29..40dcd5b 100644 --- a/src/bubblesort.py +++ b/src/bubblesort.py @@ -0,0 +1,10 @@ +"""Implementation of Bubble Sort in Python.""" + + +def bubblesort(lst): + """Bubble sorting algorithm.""" + for i in range(len(lst)): + for j in range(len(lst) - 1, i, -1): + if lst[j] < lst[j - 1]: + lst[j], lst[j - 1] = lst[j - 1], lst[j] + return lst diff --git a/src/test_bubblesort.py b/src/test_bubblesort.py index 40dcd5b..a7ed331 100644 --- a/src/test_bubblesort.py +++ b/src/test_bubblesort.py @@ -1,10 +1,7 @@ -"""Implementation of Bubble Sort in Python.""" +"""Testing module for BubbleSort algorithm.""" + +import pytest + +def test_ -def bubblesort(lst): - """Bubble sorting algorithm.""" - for i in range(len(lst)): - for j in range(len(lst) - 1, i, -1): - if lst[j] < lst[j - 1]: - lst[j], lst[j - 1] = lst[j - 1], lst[j] - return lst From 33a341779818b13693632d19695d3369403592db Mon Sep 17 00:00:00 2001 From: CHELSEA DOLE Date: Sat, 9 Dec 2017 13:44:16 -0800 Subject: [PATCH 50/68] two first bubblesort tests. Testing error handing for non-lists and empy lists. --- src/bubblesort.py | 12 +++++++----- src/test_bubblesort.py | 13 ++++++++++++- 2 files changed, 19 insertions(+), 6 deletions(-) diff --git a/src/bubblesort.py b/src/bubblesort.py index 40dcd5b..3785a7c 100644 --- a/src/bubblesort.py +++ b/src/bubblesort.py @@ -3,8 +3,10 @@ def bubblesort(lst): """Bubble sorting algorithm.""" - for i in range(len(lst)): - for j in range(len(lst) - 1, i, -1): - if lst[j] < lst[j - 1]: - lst[j], lst[j - 1] = lst[j - 1], lst[j] - return lst + if lst is type(list): + for i in range(len(lst)): + for j in range(len(lst) - 1, i, -1): + if lst[j] < lst[j - 1]: + lst[j], lst[j - 1] = lst[j - 1], lst[j] + return lst + return "BubbleSort takes only lists." diff --git a/src/test_bubblesort.py b/src/test_bubblesort.py index a7ed331..f0b58a7 100644 --- a/src/test_bubblesort.py +++ b/src/test_bubblesort.py @@ -1,7 +1,18 @@ """Testing module for BubbleSort algorithm.""" +from bubblesort import bubblesort import pytest -def test_ + +def test_bubblesort_with_empty_lst(): + """Bubble sort returns empty list with input empy lst.""" + empty = [] + assert bubblesort(empty) == [] + + +def test_bubble_sort_doesnt_work_on_string(): + """Bubble sort cannot be given a string as input.""" + wrong_str = "This will raise an exception" + assert bubblesort(wrong_str) == "BubbleSort takes only lists." From 81e094f1e31b826562f76d9c86570a3d1f8e28ae Mon Sep 17 00:00:00 2001 From: CHELSEA DOLE Date: Sat, 9 Dec 2017 13:45:51 -0800 Subject: [PATCH 51/68] more error handling test. --- src/test_bubblesort.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/test_bubblesort.py b/src/test_bubblesort.py index f0b58a7..744364d 100644 --- a/src/test_bubblesort.py +++ b/src/test_bubblesort.py @@ -16,3 +16,15 @@ def test_bubble_sort_doesnt_work_on_string(): assert bubblesort(wrong_str) == "BubbleSort takes only lists." +def test_bubble_sort_doesnt_work_on_set(): + """Bubble sort cannot be given set as input.""" + wrong_set = (1, 2, 3, 4) + assert bubblesort(wrong_set) == "BubbleSort takes only lists." + + +def test_bubble_sort_doesnt_work_on_dict(): + """Bubble sort cannot be given set as input.""" + wrong_dict = {"1": 1, "2": 2, "3": 3} + assert bubblesort(wrong_dict) == "BubbleSort takes only lists." + +def \ No newline at end of file From cecdd5285bad700b5fd638cb6446e276fce90471 Mon Sep 17 00:00:00 2001 From: CHELSEA DOLE Date: Sat, 9 Dec 2017 13:53:40 -0800 Subject: [PATCH 52/68] test for longer bubblesort, sort on neg numbers. --- src/bubblesort.py | 2 +- src/test_bubblesort.py | 21 ++++++++++++++++++++- 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/src/bubblesort.py b/src/bubblesort.py index 3785a7c..3a4b8d6 100644 --- a/src/bubblesort.py +++ b/src/bubblesort.py @@ -3,7 +3,7 @@ def bubblesort(lst): """Bubble sorting algorithm.""" - if lst is type(list): + if isinstance(lst, list): for i in range(len(lst)): for j in range(len(lst) - 1, i, -1): if lst[j] < lst[j - 1]: diff --git a/src/test_bubblesort.py b/src/test_bubblesort.py index 744364d..3faad50 100644 --- a/src/test_bubblesort.py +++ b/src/test_bubblesort.py @@ -27,4 +27,23 @@ def test_bubble_sort_doesnt_work_on_dict(): wrong_dict = {"1": 1, "2": 2, "3": 3} assert bubblesort(wrong_dict) == "BubbleSort takes only lists." -def \ No newline at end of file + +def test_bubble_sort_can_sort_len_2_lst(): + """Bubble sort sorts unordered lst, where len(lst) == 2.""" + two_idx_lst = [44, 1] + assert bubblesort(two_idx_lst) == [1, 44] + + +def test_bubble_sort_works_on_longer_lst(): + """Bubble sort sorts unordered_lst, where len(lst) == 16.""" + longer_lst = [33, 26, 900, 22, 34, 1, 0, 2000, 22, 20, 90, 99, 200, 322, 1000] + assert bubblesort(longer_lst) == [0, 1, 20, 22, 22, 26, 33, 34, 90, 99, 200, 322, 900, 1000, 2000] + + +def test_bubble_sort_works_on_negatives(): + """Bubble sort on list with negative numbers.""" + neg_lst = [-30, 20, -200, -1, -3, 13] + assert bubblesort(neg_lst) == [-200, -30, -3, -1, 13, 20] + + + From 7a0a0cc7bdd245cf877abdb4d2496bb55481938e Mon Sep 17 00:00:00 2001 From: CHELSEA DOLE Date: Sat, 9 Dec 2017 13:55:08 -0800 Subject: [PATCH 53/68] test on presorted list. --- src/test_bubblesort.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/test_bubblesort.py b/src/test_bubblesort.py index 3faad50..764fa07 100644 --- a/src/test_bubblesort.py +++ b/src/test_bubblesort.py @@ -46,4 +46,8 @@ def test_bubble_sort_works_on_negatives(): assert bubblesort(neg_lst) == [-200, -30, -3, -1, 13, 20] +def test_bubble_sort_on_presorted_lst(): + """Bubble sort does not change input lst that is already sorted.""" + pre_sorted = [1, 2, 3, 4, 5, 6, 7, 8, 9] + assert bubblesort(pre_sorted) == [1, 2, 3, 4, 5, 6, 7, 8, 9] From 10c95dd38175d31738c5b270743868df28066703 Mon Sep 17 00:00:00 2001 From: CHELSEA DOLE Date: Sat, 9 Dec 2017 14:07:57 -0800 Subject: [PATCH 54/68] adding if __name__ == main block for bubblesort. --- src/bubblesort.py | 28 ++++++++++++++++++++++++++++ src/test_bubblesort.py | 1 - 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/src/bubblesort.py b/src/bubblesort.py index 3a4b8d6..8f462eb 100644 --- a/src/bubblesort.py +++ b/src/bubblesort.py @@ -10,3 +10,31 @@ def bubblesort(lst): lst[j], lst[j - 1] = lst[j - 1], lst[j] return lst return "BubbleSort takes only lists." + +if __name__ == '__main__': # pragama: no cover + import timeit as ti + import random + best_case = [1, 2, 3, 4, 5] + worst_case = [5, 4, 3, 2, 1] + random = [random.randint(1, 100) for i in range(10)] + + time_1 = ti.timeit("bubblesort(best_case)", + setup="from __main__ import best_case, bubblesort") + time_2 = ti.timeit("bubblesort(worst_case)", + setup="from __main__ import worst_case, bubblesort") + time_3 = ti.timeit("bubblesort(random)", + setup="from __main__ import random, bubblesort") + print(f""" +Bubblesort sorts an input numerically from smallest to largest number by +stepping through each index, and (if the value of the index above it is lower) +swapping the current index with the current index + 1. + +Input:[1, 2, 3, 4, 5] +Sort time: {time_1} + +Input:[5, 4, 3, 2, 1] +Sort time: {time_2} + +Input:list(range(5, 0, -1)) +Sort time: {time_3} + """) diff --git a/src/test_bubblesort.py b/src/test_bubblesort.py index 764fa07..4ea6d16 100644 --- a/src/test_bubblesort.py +++ b/src/test_bubblesort.py @@ -50,4 +50,3 @@ def test_bubble_sort_on_presorted_lst(): """Bubble sort does not change input lst that is already sorted.""" pre_sorted = [1, 2, 3, 4, 5, 6, 7, 8, 9] assert bubblesort(pre_sorted) == [1, 2, 3, 4, 5, 6, 7, 8, 9] - From 37412e2ba5c3716e35b867e11ebc8761f5c0b17c Mon Sep 17 00:00:00 2001 From: CHELSEA DOLE Date: Sat, 9 Dec 2017 14:13:09 -0800 Subject: [PATCH 55/68] last changes to bubble sort. --- src/bubblesort.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/bubblesort.py b/src/bubblesort.py index 8f462eb..e5b774a 100644 --- a/src/bubblesort.py +++ b/src/bubblesort.py @@ -24,17 +24,17 @@ def bubblesort(lst): setup="from __main__ import worst_case, bubblesort") time_3 = ti.timeit("bubblesort(random)", setup="from __main__ import random, bubblesort") - print(f""" -Bubblesort sorts an input numerically from smallest to largest number by + print(""" +Bubblesort sorts an input numerically from smallest to largest number by stepping through each index, and (if the value of the index above it is lower) swapping the current index with the current index + 1. Input:[1, 2, 3, 4, 5] -Sort time: {time_1} +Sort time: {} Input:[5, 4, 3, 2, 1] -Sort time: {time_2} +Sort time: {} Input:list(range(5, 0, -1)) -Sort time: {time_3} - """) +Sort time: {} + """.format(time_1, time_2, time_3)) From 5ebdb1f419f6424285b171caa4d9a580ca82e5c2 Mon Sep 17 00:00:00 2001 From: CHELSEA DOLE Date: Sat, 9 Dec 2017 14:16:04 -0800 Subject: [PATCH 56/68] adding bubblesort to README. --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index 90a6e94..6c00f8e 100644 --- a/README.md +++ b/README.md @@ -14,6 +14,10 @@ * **Trie Tree** - *a Trie Tree is a "tree shaped" data structure containing nodes with references to letters. These nodes string together (using each node's "children" and "parent" attriutes) to form words. This tree allows for quick lookup time of words, and is used for things such as word suggestion/auto-complete.* +## Sorting Algorithms: + +* **Bubblesort** — *Bubblesort sorts an input numerically from smallest to largest number by stepping through each index, and (if the value of the index above it is lower) swapping the current index with the current index + 1. The runtime for this algorithm is O(n^2), because for each number in the input list, the algorithm must look over that number a number of times approximately equal to its square.* + ## Time Complexities: * balance() = *This BST function returns the balance (or size difference) between the left and right parts of the tree. Its runtime is O(1), because it always takes the same amount of time to run regardless of tree size, and only performs simple subtraction.* From 3ff15ed8184658176b4ee516d580f4425797f919 Mon Sep 17 00:00:00 2001 From: CHELSEA DOLE Date: Mon, 11 Dec 2017 08:33:55 -0800 Subject: [PATCH 57/68] adding a bunch of tests. that should work but mostly dont because this merge sort is fucked up. --- src/mergesort.py | 41 ++++++++++++++++++++++++++++++++++++ src/test_mergesort.py | 48 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 89 insertions(+) create mode 100644 src/mergesort.py create mode 100644 src/test_mergesort.py diff --git a/src/mergesort.py b/src/mergesort.py new file mode 100644 index 0000000..8d06119 --- /dev/null +++ b/src/mergesort.py @@ -0,0 +1,41 @@ +"""Merge sort.""" + + +def merge_sort(a_list): + """Use MS to sort the provided list and return it.""" + if not isinstance(a_list, list): + raise TypeError("Only list is a valid input type!") + + if len(a_list) < 2: + return a_list + + parts = [] + + output = [] + + for x in a_list: + parts.append([x]) + + while len(parts) > 1: + if len(parts[0]) == len(parts[1]): + parts.insert(0, _merge(parts.pop(0), parts.pop(1))) + else: + parts.insert(0, _merge(parts.pop(0), _merge(parts.pop(1), parts.pop(2)))) + + +def _merge(part_a, part_b): + """.""" + temp = [] + while part_a and part_b: + if part_a[0] <= part_b[0]: + temp.append(part_a.pop(0)) + else: + temp.append(part_b.pop(0)) + + while part_a: + temp.append(part_a.pop(0)) + + while part_b: + temp.append(part_b.pop(0)) + + return temp \ No newline at end of file diff --git a/src/test_mergesort.py b/src/test_mergesort.py new file mode 100644 index 0000000..6e1c254 --- /dev/null +++ b/src/test_mergesort.py @@ -0,0 +1,48 @@ +"""Mergesort sorting algorithm.""" + +from mergesort import merge_sort +import pytest + + +def test_mergesort_on_empty_lst(): + """Mergesort on empty list.""" + lst = [] + assert merge_sort(lst) == [] + + +def test_mergesort_on_a_set(): + """Test mergesort on set.""" + not_a_list = set() + with pytest.raises(TypeError): + assert merge_sort(not_a_list) + + +def test_mergesort_on_a_dict(): + """Test mergesort on set.""" + not_a_list = {"1": 1, "2": 2} + with pytest.raises(TypeError): + assert merge_sort(not_a_list) + + +def test_mergesort_on_medlength_lst(): + """Mergesort on med length list.""" + lst = [3, 44, 38, 5, 47, 15, 36, 26, 27, 2, 46, 4, 19, 50, 48] + assert merge_sort(lst) == [2, 3, 4, 5, 15, 19, 26, 27, 36, 38, 44, 46, 47, 48, 50] + + +def test_sort_on_lst_len_1(): + """Test mergesort on a list that is one item long.""" + lst = [1] + assert merge_sort(lst) == [1] + + +def test_on_pre_sorted_list(): + """Test mergesort on a list that is already sorted.""" + lst = [1, 2, 3, 4, 5, 6, 7, 8, 9] + assert merge_sort(lst) == [1, 2, 3, 4, 5, 6, 7, 8, 9] + + +def test_on_backwards_list(): + """Test mergesort on a list that is already sorted.""" + lst = [9, 8, 7, 6, 5, 4, 3, 2, 1] + assert merge_sort(lst) == [1, 2, 3, 4, 5, 6, 7, 8, 9] From 35f3a2803f8163079fb751306b6b10c2c14ff2fb Mon Sep 17 00:00:00 2001 From: CHELSEA DOLE Date: Mon, 11 Dec 2017 08:39:36 -0800 Subject: [PATCH 58/68] adding if name is main block to mergesort. --- src/mergesort.py | 30 ++++++++++++++++++++++++++++-- 1 file changed, 28 insertions(+), 2 deletions(-) diff --git a/src/mergesort.py b/src/mergesort.py index 8d06119..f38962c 100644 --- a/src/mergesort.py +++ b/src/mergesort.py @@ -1,4 +1,4 @@ -"""Merge sort.""" +"""Merge sort algorithm.""" def merge_sort(a_list): @@ -38,4 +38,30 @@ def _merge(part_a, part_b): while part_b: temp.append(part_b.pop(0)) - return temp \ No newline at end of file + return temp + +if __name__ == '__main__': # pragama: no cover + import timeit as ti + import random + best_case = [1, 2, 3, 4, 5] + worst_case = [5, 4, 3, 2, 1] + random = [random.randint(1, 100) for i in range(10)] + + time_1 = ti.timeit("merge_sort(best_case)", + setup="from __main__ import best_case, merge_sort") + time_2 = ti.timeit("merge_sort(worst_case)", + setup="from __main__ import worst_case, merge_sort") + time_3 = ti.timeit("merge_sort(random)", + setup="from __main__ import random, merge_sort") + print(""" +Mergesort sorts shit by merging it recursively. + +Input:[1, 2, 3, 4, 5] +Sort time: {} + +Input:[5, 4, 3, 2, 1] +Sort time: {} + +Input:list(range(5, 0, -1)) +Sort time: {} + """.format(time_1, time_2, time_3)) \ No newline at end of file From c8947b0ac7f947321832062f5d012a0d42eee086 Mon Sep 17 00:00:00 2001 From: CHELSEA DOLE Date: Mon, 11 Dec 2017 09:19:04 -0800 Subject: [PATCH 59/68] fixing merge_sort to return item, use only index 0 of popped lists. --- src/mergesort.py | 24 ++++++++++-------------- 1 file changed, 10 insertions(+), 14 deletions(-) diff --git a/src/mergesort.py b/src/mergesort.py index f38962c..bb63e81 100644 --- a/src/mergesort.py +++ b/src/mergesort.py @@ -6,21 +6,17 @@ def merge_sort(a_list): if not isinstance(a_list, list): raise TypeError("Only list is a valid input type!") - if len(a_list) < 2: + if len(a_list) < 2: return a_list - parts = [] + parts = [[i] for i in a_list] - output = [] - - for x in a_list: - parts.append([x]) - - while len(parts) > 1: - if len(parts[0]) == len(parts[1]): - parts.insert(0, _merge(parts.pop(0), parts.pop(1))) + while len(parts) > 1: + if len(parts[0]) == len(parts[1]) or len(parts) == 2: + parts.insert(0, _merge(parts.pop(0), parts.pop(0))) else: - parts.insert(0, _merge(parts.pop(0), _merge(parts.pop(1), parts.pop(2)))) + parts.insert(0, _merge(parts.pop(0), _merge(parts.pop(0), parts.pop(0)))) + return parts[0] def _merge(part_a, part_b): @@ -32,13 +28,13 @@ def _merge(part_a, part_b): else: temp.append(part_b.pop(0)) - while part_a: + while part_a: temp.append(part_a.pop(0)) - while part_b: + while part_b: temp.append(part_b.pop(0)) - return temp + return temp if __name__ == '__main__': # pragama: no cover import timeit as ti From db0f058010003a4727d580aacda0fdb0ee749604 Mon Sep 17 00:00:00 2001 From: CHELSEA DOLE Date: Mon, 11 Dec 2017 09:30:43 -0800 Subject: [PATCH 60/68] adding to README for mergesort. --- README.md | 2 ++ src/mergesort.py | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 6c00f8e..9bfbe4b 100644 --- a/README.md +++ b/README.md @@ -18,6 +18,8 @@ * **Bubblesort** — *Bubblesort sorts an input numerically from smallest to largest number by stepping through each index, and (if the value of the index above it is lower) swapping the current index with the current index + 1. The runtime for this algorithm is O(n^2), because for each number in the input list, the algorithm must look over that number a number of times approximately equal to its square.* +* **Mergesort** - *Mergesort sorts an input list numerically from smallest to largest by dividing it into sections, then sorting each section piece by piece. As the sections are merged/sorted, the length of the list gets smaller, until there are only two "sections" to merge. Once they are merged and sorted, the list is fully osorted. The runtime for this algorithm is O(n), because the runtime increases with the increase in length of list.* + ## Time Complexities: * balance() = *This BST function returns the balance (or size difference) between the left and right parts of the tree. Its runtime is O(1), because it always takes the same amount of time to run regardless of tree size, and only performs simple subtraction.* diff --git a/src/mergesort.py b/src/mergesort.py index bb63e81..7474fef 100644 --- a/src/mergesort.py +++ b/src/mergesort.py @@ -50,7 +50,7 @@ def _merge(part_a, part_b): time_3 = ti.timeit("merge_sort(random)", setup="from __main__ import random, merge_sort") print(""" -Mergesort sorts shit by merging it recursively. +Mergesort sorts shit by merging it. Input:[1, 2, 3, 4, 5] Sort time: {} From 29517996d17a9b0d0943a965476b4c62ffe0391e Mon Sep 17 00:00:00 2001 From: CHELSEA DOLE Date: Tue, 12 Dec 2017 13:52:44 -0800 Subject: [PATCH 61/68] radix sorting. --- src/radix-sort.py | 33 +++++++++++++++++++++++++++++++++ src/test_radix-sort.py | 0 2 files changed, 33 insertions(+) create mode 100644 src/radix-sort.py create mode 100644 src/test_radix-sort.py diff --git a/src/radix-sort.py b/src/radix-sort.py new file mode 100644 index 0000000..be1ba5e --- /dev/null +++ b/src/radix-sort.py @@ -0,0 +1,33 @@ +"""Radix Sorting Algorithm.""" + + +def radix_sort(lst): + """Implementation of Radix Sort in python.""" + if not isinstance(lst, list): + raise TypeError('Input must be a list of integers.') + str_lst = [str(i) for i in lst] + largest_int_len = len(str(max(lst))) + + for rep_num in range(largest_int_len): + buckets = [[] for i in range(10)] + for str_int in str_lst: + if len(str_int) < rep_num: + buckets[0].append(int(str_int)) + else: + target = buckets[int(str_int[0])] + target.append(str_int) + flat_list = _append_backwards(buckets) + str_lst = flat_list + return flat_list + + +def _append_backwards(buckets): + """Helper function to flatten buckets into a single list.""" + flat_list = [] + for bucket in buckets: + for num in reversed(bucket): + flat_list.append(str(num)) + return flat_list + + + diff --git a/src/test_radix-sort.py b/src/test_radix-sort.py new file mode 100644 index 0000000..e69de29 From a6513bd2bd00217c7ede1015b972c01e407afba9 Mon Sep 17 00:00:00 2001 From: CHELSEA DOLE Date: Tue, 12 Dec 2017 20:04:37 -0800 Subject: [PATCH 62/68] radix sort changes. Adding 1 to number of times looping, not reversing list. --- src/radix-sort.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/radix-sort.py b/src/radix-sort.py index be1ba5e..a62a5b4 100644 --- a/src/radix-sort.py +++ b/src/radix-sort.py @@ -11,7 +11,7 @@ def radix_sort(lst): for rep_num in range(largest_int_len): buckets = [[] for i in range(10)] for str_int in str_lst: - if len(str_int) < rep_num: + if len(str_int) < rep_num + 1: buckets[0].append(int(str_int)) else: target = buckets[int(str_int[0])] @@ -25,9 +25,6 @@ def _append_backwards(buckets): """Helper function to flatten buckets into a single list.""" flat_list = [] for bucket in buckets: - for num in reversed(bucket): + for num in bucket: flat_list.append(str(num)) return flat_list - - - From f50f5b28b639dfca8bebc2f4ec0e5defa80c27d8 Mon Sep 17 00:00:00 2001 From: CHELSEA DOLE Date: Tue, 12 Dec 2017 20:06:54 -0800 Subject: [PATCH 63/68] mostly working radix sort. --- src/radix-sort.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/radix-sort.py b/src/radix-sort.py index a62a5b4..73ed3fe 100644 --- a/src/radix-sort.py +++ b/src/radix-sort.py @@ -18,7 +18,7 @@ def radix_sort(lst): target.append(str_int) flat_list = _append_backwards(buckets) str_lst = flat_list - return flat_list + return [int(i) for i in flat_list] def _append_backwards(buckets): From 544de0b57f5636fc4742bb77597f5c1b03d2faca Mon Sep 17 00:00:00 2001 From: CHELSEA DOLE Date: Wed, 13 Dec 2017 12:12:59 -0800 Subject: [PATCH 64/68] adding new radix sort names. --- src/{radix-sort.py => radix_sort.py} | 4 ++-- src/{test_radix-sort.py => test_radix_sort.py} | 0 2 files changed, 2 insertions(+), 2 deletions(-) rename src/{radix-sort.py => radix_sort.py} (91%) rename src/{test_radix-sort.py => test_radix_sort.py} (100%) diff --git a/src/radix-sort.py b/src/radix_sort.py similarity index 91% rename from src/radix-sort.py rename to src/radix_sort.py index 73ed3fe..7d9dee6 100644 --- a/src/radix-sort.py +++ b/src/radix_sort.py @@ -16,12 +16,12 @@ def radix_sort(lst): else: target = buckets[int(str_int[0])] target.append(str_int) - flat_list = _append_backwards(buckets) + flat_list = _append(buckets) str_lst = flat_list return [int(i) for i in flat_list] -def _append_backwards(buckets): +def _append(buckets): """Helper function to flatten buckets into a single list.""" flat_list = [] for bucket in buckets: diff --git a/src/test_radix-sort.py b/src/test_radix_sort.py similarity index 100% rename from src/test_radix-sort.py rename to src/test_radix_sort.py From 01a649f883efb7e39b0cd00b4115a564462186cf Mon Sep 17 00:00:00 2001 From: CHELSEA DOLE Date: Wed, 13 Dec 2017 12:21:19 -0800 Subject: [PATCH 65/68] adding more radix test. --- src/radix_sort.py | 2 ++ src/test_radix_sort.py | 55 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 57 insertions(+) diff --git a/src/radix_sort.py b/src/radix_sort.py index 7d9dee6..54b5f3d 100644 --- a/src/radix_sort.py +++ b/src/radix_sort.py @@ -5,6 +5,8 @@ def radix_sort(lst): """Implementation of Radix Sort in python.""" if not isinstance(lst, list): raise TypeError('Input must be a list of integers.') + if len(lst) == 0: + return lst str_lst = [str(i) for i in lst] largest_int_len = len(str(max(lst))) diff --git a/src/test_radix_sort.py b/src/test_radix_sort.py index e69de29..51f73c4 100644 --- a/src/test_radix_sort.py +++ b/src/test_radix_sort.py @@ -0,0 +1,55 @@ +"""Testing module for Radix Sort algorithm.""" + +from radix_sort import radix_sort +import pytest + + +def test_radix_sort_with_empty_lst(): + """Radixsort returns empty list with input empy lst.""" + empty = [] + assert radix_sort(empty) == [] + + +def test_radix_sort_doesnt_work_on_string(): + """Radix sort cannot be given a string as input.""" + with pytest.raises(TypeError): + wrong_str = "This will raise an exception" + assert radix_sort(wrong_str) + + +def test_radix_sort_doesnt_work_on_set(): + """Radix sort cannot be given a set as input.""" + with pytest.raises(TypeError): + wrong_set = (1, 2, 3) + assert radix_sort(wrong_set) + + +def test_radix_sort_doesnt_work_on_dict(): + """Radix sort cannot be given set as input.""" + with pytest.raises(TypeError): + wrong_dict = {"1": 1, "2": 2, "3": 3} + assert radix_sort(wrong_dict) + + +def test_radix_sort_can_sort_len_2_lst(): + """Radix sort sorts unordered lst, where len(lst) == 2.""" + two_idx_lst = [44, 1] + assert radix_sort(two_idx_lst) == [1, 44] + + +def test_radix_sort_works_on_longer_lst(): + """Radix sort sorts unordered_lst, where len(lst) == 16.""" + longer_lst = [33, 26, 900, 22, 34, 1, 0, 2000, 22, 20, 90, 99, 200, 322, 1000] + assert radix_sort(longer_lst) == [0, 1, 20, 22, 22, 26, 33, 34, 90, 99, 200, 322, 900, 1000, 2000] + + +def test_radix_sort_works_on_negatives(): + """Radix sort on list with negative numbers.""" + neg_lst = [-30, 20, -200, -1, -3, 13] + assert radix_sort(neg_lst) == [-200, -30, -3, -1, 13, 20] + + +def test_radix_sort_on_presorted_lst(): + """Radix sort does not change input lst that is already sorted.""" + pre_sorted = [1, 2, 3, 4, 5, 6, 7, 8, 9] + assert radix_sort(pre_sorted) == [1, 2, 3, 4, 5, 6, 7, 8, 9] From 7e85756b3b6e492e0f50a2019b79448fbaaa2d64 Mon Sep 17 00:00:00 2001 From: CHELSEA DOLE Date: Wed, 13 Dec 2017 12:21:48 -0800 Subject: [PATCH 66/68] finishing one test incorrect --- src/test_radix_sort.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/test_radix_sort.py b/src/test_radix_sort.py index 51f73c4..1083063 100644 --- a/src/test_radix_sort.py +++ b/src/test_radix_sort.py @@ -43,12 +43,6 @@ def test_radix_sort_works_on_longer_lst(): assert radix_sort(longer_lst) == [0, 1, 20, 22, 22, 26, 33, 34, 90, 99, 200, 322, 900, 1000, 2000] -def test_radix_sort_works_on_negatives(): - """Radix sort on list with negative numbers.""" - neg_lst = [-30, 20, -200, -1, -3, 13] - assert radix_sort(neg_lst) == [-200, -30, -3, -1, 13, 20] - - def test_radix_sort_on_presorted_lst(): """Radix sort does not change input lst that is already sorted.""" pre_sorted = [1, 2, 3, 4, 5, 6, 7, 8, 9] From 81953d91382cf7713af140ab4a61fba74e580900 Mon Sep 17 00:00:00 2001 From: CHELSEA DOLE Date: Wed, 13 Dec 2017 12:24:48 -0800 Subject: [PATCH 67/68] wrote README. --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 9bfbe4b..23a6621 100644 --- a/README.md +++ b/README.md @@ -20,6 +20,8 @@ * **Mergesort** - *Mergesort sorts an input list numerically from smallest to largest by dividing it into sections, then sorting each section piece by piece. As the sections are merged/sorted, the length of the list gets smaller, until there are only two "sections" to merge. Once they are merged and sorted, the list is fully osorted. The runtime for this algorithm is O(n), because the runtime increases with the increase in length of list.* +* **Radix Sort** = *Radix sort sorts an input list numerically by looking at the individual numbers of the numbers, starting at the tens place, and going up to the highest place in the number. The numbers are put into "buckets" based on this, and then the buckets are continuously built and remade until the list is sorted. The runtime for this algorithm is O(n^2), because the length depends on the length of the largest number.* + ## Time Complexities: * balance() = *This BST function returns the balance (or size difference) between the left and right parts of the tree. Its runtime is O(1), because it always takes the same amount of time to run regardless of tree size, and only performs simple subtraction.* From fb7c545ad9c34b0e254d8d4f1a8a781a69dd7ffa Mon Sep 17 00:00:00 2001 From: CHELSEA DOLE Date: Wed, 13 Dec 2017 16:32:26 -0800 Subject: [PATCH 68/68] fixing radix sort --- src/radix_sort.py | 12 +++++------- src/test_radix_sort.py | 23 +++++++++++++++++++---- 2 files changed, 24 insertions(+), 11 deletions(-) diff --git a/src/radix_sort.py b/src/radix_sort.py index 54b5f3d..176adc4 100644 --- a/src/radix_sort.py +++ b/src/radix_sort.py @@ -14,13 +14,11 @@ def radix_sort(lst): buckets = [[] for i in range(10)] for str_int in str_lst: if len(str_int) < rep_num + 1: - buckets[0].append(int(str_int)) + buckets[0].append(str_int) else: - target = buckets[int(str_int[0])] - target.append(str_int) - flat_list = _append(buckets) - str_lst = flat_list - return [int(i) for i in flat_list] + buckets[int(str_int[-1 - rep_num])].append(str_int) + str_lst = _append(buckets) + return [int(i) for i in str_lst] def _append(buckets): @@ -28,5 +26,5 @@ def _append(buckets): flat_list = [] for bucket in buckets: for num in bucket: - flat_list.append(str(num)) + flat_list.append(num) return flat_list diff --git a/src/test_radix_sort.py b/src/test_radix_sort.py index 1083063..142508c 100644 --- a/src/test_radix_sort.py +++ b/src/test_radix_sort.py @@ -2,6 +2,15 @@ from radix_sort import radix_sort import pytest +from random import randint + +TEST_CASES = ( + [1, 2, 3, 4, 5, 6], + [6, 5, 4, 3, 2, 1], + [], + [5], + [randint(1, 1000) for i in range(50)] +) def test_radix_sort_with_empty_lst(): @@ -37,10 +46,16 @@ def test_radix_sort_can_sort_len_2_lst(): assert radix_sort(two_idx_lst) == [1, 44] -def test_radix_sort_works_on_longer_lst(): - """Radix sort sorts unordered_lst, where len(lst) == 16.""" - longer_lst = [33, 26, 900, 22, 34, 1, 0, 2000, 22, 20, 90, 99, 200, 322, 1000] - assert radix_sort(longer_lst) == [0, 1, 20, 22, 22, 26, 33, 34, 90, 99, 200, 322, 900, 1000, 2000] +def test_randomly_parameterized_lists(): + """Random num sort.""" + for i in range(20): + test_lst = [randint(1, 1000) for i in range(50)] + assert radix_sort(test_lst[:]) == sorted(test_lst[:]) + +# def test_radix_sort_works_on_longer_lst(): +# """Radix sort sorts unordered_lst, where len(lst) == 16.""" +# longer_lst = [33, 26, 900, 22, 34, 1, 0, 2000, 22, 20, 90, 99, 200, 322, 1000] +# assert radix_sort(longer_lst) == [0, 1, 20, 22, 22, 26, 33, 34, 90, 99, 200, 322, 900, 1000, 2000] def test_radix_sort_on_presorted_lst():