diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..ccd3ca9 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,18 @@ +language: python +python: + - "2.7" + - "3.5" + + +# command to install dependencies +install: + - pip install -e .[test] + # - pip install coveralls + +# command to run tests +script: + - py.test --cov=. --cover-report term-missing + +# notifications +notifications: + email: false \ No newline at end of file diff --git a/README.MD b/README.MD index eaae067..92a6866 100644 --- a/README.MD +++ b/README.MD @@ -1,32 +1,38 @@ +[![Build Status](https://travis-ci.org/chamberi/data-structures.svg?branch=master)](https://travis-ci.org/chamberi/data-structures) #Summary -The assignment was to implement a [Weighted Graph](https://codefellows.github.io/sea-python-401d5/assignments/graph_3_weighted.html) -in Python containing the following methods: +radix_sort: sorts numbers by their 0-9 digits first, putting them in a list from lowest to highest. Next does the same for 10s (10-99), and so forth. +_random_list() returns a list of random numbers from 0 to 300 of random size less than 300. - * g.nodes(): return a list of all nodes in the graph - * g.edges(): return a list of all edges in the graph - * g.add_node(n): adds a new node 'n' to the graph - * g.add_edge(n1, n2): adds a new edge to the graph connecting 'n1' and 'n2', if either n1 or n2 are not already present in the graph, they should be added. - * g.del_node(n): deletes the node 'n' from the graph, raises an error if no such node exists - * g.del_edge(n1, n2): deletes the edge connecting 'n1' and 'n2' from the graph, raises an error if no such edge exists - * g.has_node(n): True if node 'n' is contained in the graph, False if not. - * g.neighbors(n): returns the list of all nodes connected to 'n' by edges, raises an error if n is not in g - * g.adjacent(n1, n2): returns True if there is an edge connecting n1 and n2, False if not, raises an error if either of the supplied nodes are not in g - * g.depth_first_traversal(start): Returns the path list for the entire graph with a depth first traversal. - * g.breadth_first_travers(start): Returns the path list for the entire graph with a breadth first traversal. +## Timeit Information: -# Coverage: + number of runs: 3 + random radix_sort_timed: [0.48798475600779057, 0.4725390509702265, 0.47569359897170216] + average: 0.4787391353165731 ----------- coverage: platform darwin, python 3.5.2-final-0 ----------- + number of runs: 3 + arranged case radix_sort_timed: [0.48790664895204827, 0.48251010198146105, 0.48216112999944016] + average: 0.4841926269776498 + number of runs: 3 + reversed case radix_sort_timed: [0.48425215197494254, 0.4971498280065134, 0.4777760480064899] + average: 0.48639267599598196 -| Name | Stmts | Miss | Cover | -| ----------------------- | ----- | ---- | ----- | -| weighted_graph.py | 78 | 3 | 96% | -| test_weighted_graph.py | 178 | 0 | 100% | -| ----------------------- | --- | -- | ---- | -| TOTAL | 256 | 3 | 98% | +## Tests and Coverage + test_radix.py .......... + ---------- coverage: platform darwin, python 2.7.12-final-0 ---------- + Name Stmts Miss Cover Missing + ------------------------------------------------------- + radix.py 43 16 63% 53-74 + + + test_radix.py .......... + + ---------- coverage: platform darwin, python 3.5.2-final-0 ----------- + Name Stmts Miss Cover Missing + ------------------------------------------------------- + radix.py 43 16 63% 53-74 diff --git a/src/balanced_bst.py b/src/balanced_bst.py new file mode 100644 index 0000000..a3fe063 --- /dev/null +++ b/src/balanced_bst.py @@ -0,0 +1,608 @@ +"""Module for Binary Search Tree.""" + +from queue_ds import Queue +import timeit +import random + + +class Node(object): + """Node class.""" + + def __init__(self, value=None, left=None, right=None): + """Init of the Node class.""" + self.value = value + self.left = left + self.right = right + self.parent = None + + +class BinarySearchTree(object): + """Binary Search Tree.""" + + """insert(self, val): will insert the value val into the BST. If val is already present, it will be ignored.""" + """search(self, val): will return the node containing that value, else None""" + """size(self): will return the integer size of the BST (equal to the total number of values stored in the tree). It will return 0 if the tree is empty.""" + """depth(self): will return an integer representing the total number of levels in the tree. If there is one value, the depth should be 1, if two values it will be 2, if three values it may be 2 or three, depending, etc.""" + """contains(self, val): will return True if val is in the BST, False if not.""" + """balance(self): will return an integer, positive or negative that represents how well balanced the tree is. Trees which are higher on the left than the right should return a positive value, trees which are higher on the right than the left should return a negative value. An ideally-balanced tree should return 0.""" + """in_order(self): will return a generator that will return the values in the tree using in-order traversal, one at a time.""" + """pre_order(self): will return a generator that will return the values in the tree using pre-order traversal, one at a time.""" + """post_order(self): will return a generator that will return the values in the tree using post_order traversal, one at a time.""" + """breadth_first(self): will return a generator that will return the values in the tree using breadth-first traversal, one at a time.""" + + def __init__(self, if_iter=None): + """Init of the Binary Search Tree class.""" + self.root = None + self.counter = 0 + if if_iter: + try: + for value in if_iter: + self.insert(value) + except TypeError: + self.insert(if_iter) + self._in_order = self._in_order_trav() + self._pre_order = self._pre_order_trav() + self._post_order = self._post_order_trav() + self._breadth_first = self._breadth_first_trav() + + def size(self): + """Return size of Binary Search Tree.""" + return self.counter + + def contains(self, val): + """Return True if val is in the BST, False if not.""" + if self.search(val): + return True + return False + + def search(self, val): + """Return the node containing that value, else None.""" + vertex = self.root + while vertex: + if val > vertex.value: + if not vertex.right: + return None + vertex = vertex.right + elif val < vertex.value: + if not vertex.left: + return None + vertex = vertex.left + else: + return vertex + return None + + def depth(self): + """ + Return an integer representing the total number of levels in the tree. + + If there is one value, the depth should be 1, if two values it will be 2, + if three values it may be 2 or three, depending, etc. + """ + return self._calc_depth(self.root) + + def _calc_depth(self, tree): + """Calculate the depth of the binary search tree recursively.""" + if tree is None: + return 0 + else: + return max(self._calc_depth(tree.right), self._calc_depth(tree.left)) + 1 + + def balance(self): + """ + Return an integer, positive or negative that represents how well balanced the tree is. + + Trees which are higher on the left than the right should return a positive value, + trees which are higher on the right than the left should return a negative value. + An ideally-balanced tree should return 0. + """ + if self.root is None: + return 0 + return self._calc_balance(self.root) + + def _calc_balance(self, node): + """Calculate the balance of a subtree at node.""" + return self._calc_depth(node.right) - self._calc_depth(node.left) + + def in_order(self): + """Return the next value from the generator _in_order.""" + return next(self._in_order) + + def _in_order_trav(self): + """Traverse in_order, yielding via generator.""" + vertex = self.root + visited = [] + while (visited or vertex is not None): + + if vertex is not None: + visited.append(vertex) + vertex = vertex.left + else: + vertex = visited.pop() + yield vertex.value + vertex = vertex.right + + def pre_order(self): + """Return the next value from the generator _pre_order.""" + return next(self._pre_order) + + def _pre_order_trav(self): + """Traverse pre_order, yielding via generator.""" + vertex = self.root + visited = [] + while (visited or vertex is not None): + if vertex is not None: + yield vertex.value + visited.append(vertex) + vertex = vertex.left + else: + vertex = visited.pop() + vertex = vertex.right + + def post_order(self): + """Return the next value from the generator _post_order.""" + return next(self._post_order) + + def _post_order_trav(self): + """Traverse pre_order, yielding via generator.""" + vertex = self.root + peek_vertex = None + last_vertex = None + visited = [] + while (visited or vertex is not None): + if vertex is not None: + visited.append(vertex) + vertex = vertex.left + else: + peek_vertex = visited[-1] + if peek_vertex.right and peek_vertex.right is not last_vertex: + vertex = peek_vertex.right + else: + yield peek_vertex.value + last_vertex = visited.pop() + + def breadth_first(self): + """Return the next value from the generator _breadth_first.""" + return next(self._breadth_first) + + def _breadth_first_trav(self): + """Traverse breadth first order, yielding a generator.""" + q = Queue() + q.enqueue(self.root) + while len(q) > 0: + vertex = q.dequeue() + yield vertex.value + if (vertex.left): + q.enqueue(vertex.left) + if (vertex.right): + q.enqueue(vertex.right) + + def insert(self, val): + """Take a value, inserts into Binary Search Tree at correct placement.""" + if self.root is None: + self.root = Node(val) + self.counter += 1 + + else: + vertex = self.root + while True: + if val > vertex.value: + if vertex.right: + vertex = vertex.right + else: + new_node = Node(val) + vertex.right = new_node + new_node.parent = vertex + self.counter += 1 + break + + elif val < vertex.value: + if vertex.left: + vertex = vertex.left + else: + new_node = Node(val) + vertex.left = new_node + new_node.parent = vertex + self.counter += 1 + break + else: + break + self._balance_tree() + + def delete(self, val): + """Remove val from the tree if present, if not present this method is a no-op. Return None in all cases.""" + vertex = self.root + parent_of_del = None + del_node = None + if self.root is None: + return + if self.root.value == val: + left = self.root.left + right = self.root.right + if not right: + self.root = self.root.left + self.root.parent = None + self.counter -= 1 + self._balance_tree() + return + if not right.left: + self.root = right + self.root.left = left + self.root.parent = None + self.counter -= 1 + self._balance_tree() + return + vertex = vertex.right + while True: + if not vertex.left.left: + min_parent = vertex + break + else: + vertex = vertex.left + min_node = min_parent.left + min_parent.left = min_node.right + if min_parent.left: + min_parent.left.parent = min_parent + self.root = min_node + self.root.left = left + self.root.right = right + self.root.parent = None + self.counter -= 1 + self._balance_tree() + else: + while True: + if vertex.right and val == vertex.right.value: + parent_of_del = vertex + del_node = parent_of_del.right + min_parent = self._find_min_parent(parent_of_del, "right") + break + elif vertex.left and val == vertex.left.value: + parent_of_del = vertex + del_node = parent_of_del.left + min_parent = self._find_min_parent(parent_of_del, "left") + break + elif val > vertex.value and vertex.right: + vertex = vertex.right + elif val < vertex.value and vertex.left: + vertex = vertex.left + else: + self._balance_tree() + return + + if parent_of_del.right and val == parent_of_del.right.value: + if not min_parent: + parent_of_del.right = None + self.counter -= 1 + self._balance_tree() + return + if min_parent is del_node: + right = del_node.right + del_node_left = del_node.left + parent_of_del.right = right + if right: + right.parent = parent_of_del + parent_of_del.right.left = del_node_left + if del_node_left: + del_node_left.parent = parent_of_del.right + self.counter -= 1 + self._balance_tree() + return + left = del_node.left + right = del_node.right + min_node = min_parent.left + min_parent.left = min_node.right + if min_parent.left: + min_parent.left.parent = min_parent + del_node = min_node + del_node.right = right + if right: + del_node.right.parent = del_node + del_node.left = left + if left: + del_node.left.parent = del_node + parent_of_del.right = del_node + if del_node: + parent_of_del.right.parent = parent_of_del + self.counter -= 1 + self._balance_tree() + + elif parent_of_del.left and val == parent_of_del.left.value: + if not min_parent: + parent_of_del.left = None + self.counter -= 1 + self._balance_tree() + return + if min_parent is del_node: + left = del_node.right + del_node_left = del_node.left + parent_of_del.left = left + if left: + left.parent = parent_of_del + parent_of_del.left.left = del_node_left + if del_node_left: + del_node_left.parent = parent_of_del.right + self.counter -= 1 + self._balance_tree() + return + left = del_node.left + right = del_node.right + min_node = min_parent.left + min_parent.left = min_node.right + if min_parent.left: + min_parent.left.parent = min_parent + del_node = min_node + del_node.right = right + if right: + del_node.right.parent = del_node + del_node.left = left + if left: + del_node.left.parent = del_node + parent_of_del.left = del_node + if del_node: + parent_of_del.left.parent = parent_of_del + self.counter -= 1 + self._balance_tree() + + def _find_min_parent(self, vertex, side): + """Find the parent of the replacement node, given the parent of the delete node.""" + if side == "right": + if not vertex.right.right and not vertex.right.left: + return + if vertex.right.right and not vertex.right.right.left: + return vertex.right + elif vertex.right.right and vertex.right.right.left: + vertex = vertex.right.right + while True: + if not vertex.left.left: + return vertex + else: + vertex = vertex.left + else: + if not vertex.left.right and not vertex.left.left: + return + if vertex.left.right and not vertex.left.right.left: + return vertex.left + elif vertex.left.right and vertex.left.right.left: + vertex = vertex.left.right + while True: + if not vertex.left.left: + return vertex + else: + vertex = vertex.left + return + + def _balance_tree(self): + for node in self._post_order_node(): + if self._calc_balance(node) > 1: + if self._calc_balance(node.right) < 0: + self._right_left_rotation(node) + else: + self._left_rotation(node) + elif self._calc_balance(node) < -1: + if self._calc_balance(node.left) > 0: + self._left_right_rotation(node) + else: + self._right_rotation(node) + + def _left_rotation(self, node): + a = node + a_parent = a.parent + b = a.right + d = b.left + if a is self.root: + self.root = b + self.root.parent = None + self.root.left = a + a.parent = self.root + a.right = d + if d: + d.parent = a + return + a_parent.right = b + b.parent = a_parent + b.left = a + a.parent = b + a.right = d + if d: + d.parent = a + + def _right_rotation(self, node): + a = node + a_parent = a.parent + b = a.left + d = b.right + if a is self.root: + self.root = b + self.root.parent = None + self.root.right = a + a.parent = self.root + a.left = d + if d: + d.parent = a + return + a_parent.left = b + b.parent = a_parent + b.right = a + a.parent = b + a.left = d + if d: + d.parent = a + + def _left_right_rotation(self, node): + """Try left right rotation on node.""" + # if self._calc_balance(node.left) > 0: + if node.left.right.left: + vertex = node + left_head = node.left + right_sub = node.left.right + switcher = node.left.right.left + # if node.left.right.left: + # switcher = node.left.right.left + if vertex.parent: + right_sub.parent = vertex.parent + if vertex.parent.value > vertex.value: + vertex.parent.left = right_sub + else: + vertex.parent.right = right_sub + else: + self.root = right_sub + right_sub.parent = None + switcher.parent = left_head + # if node.left.right.left: + # switcher.parent = left_head + left_head.parent = right_sub + left_head.right = switcher + # if node.left.right.left: + # left_head.right = switcher + right_sub.right = vertex + right_sub.left = left_head + vertex.parent = right_sub + vertex.left = None + else: + vertex = node + left_head = node.left + right_sub = node.left.right + switcher = node.left.right.right + # if node.left.right.right: + # switcher = node.left.right.right + if vertex.parent: + right_sub.parent = vertex.parent + if vertex.parent.value > vertex.value: + vertex.parent.left = right_sub + else: + vertex.parent.right = right_sub + else: + self.root = right_sub + right_sub.parent = None + switcher.parent = vertex + vertex.left = switcher + # if node.left.right.right: + # switcher.parent = vertex + # if node.left.right.right: + # vertex.left = switcher + vertex.parent = right_sub + right_sub.right = vertex + right_sub.left = left_head + left_head.parent = right_sub + left_head.right = None + + def _right_left_rotation(self, node): + """Try right left rotation on node.""" + if node.right.left.left: + # if self._calc_balance(node.right) < 0: + vertex = node + right_head = node.right + left_sub = node.right.left + switcher = node.right.left.left + # if node.right.left.left: + # switcher = node.right.left.left + if vertex.parent: + left_sub.parent = vertex.parent + if vertex.parent.value > vertex.value: + vertex.parent.left = left_sub + else: + vertex.parent.right = left_sub + else: + self.root = left_sub + left_sub.parent = None + switcher.parent = right_head + # if node.right.left.left: + # switcher.parent = right_head + right_head.parent = vertex + right_head.left = None + left_sub.right = right_head + left_sub.left = vertex + vertex.parent = left_sub + vertex.right = switcher + # if node.right.left.left: + # vertex.right = switcher + # else: + # vertex.right = None + else: + # import pdb; pdb.set_trace() + vertex = node + right_head = node.right + left_sub = node.right.left + switcher = node.right.left.right + # if node.right.left.right: + # switcher = node.right.left.right + if vertex.parent: + left_sub.parent = vertex.parent + if vertex.parent.value > vertex.value: + vertex.parent.left = left_sub + else: + vertex.parent.right = left_sub + else: + self.root = left_sub + left_sub.parent = None + switcher.parent = right_head + # if node.right.left.right: + # switcher.parent = right_head + vertex.right = None + vertex.parent = left_sub + left_sub.left = vertex + left_sub.right = right_head + right_head.left = switcher + # if node.right.left.right: + # right_head.left = switcher + right_head.parent = left_sub + + def _post_order_node(self): + vertex = self.root + peek_vertex = None + last_vertex = None + visited = [] + while (visited or vertex is not None): + if vertex is not None: + visited.append(vertex) + vertex = vertex.left + else: + peek_vertex = visited[-1] + if peek_vertex.right and peek_vertex.right is not last_vertex: + vertex = peek_vertex.right + else: + yield peek_vertex + last_vertex = visited.pop() + + # def balance(self): + # """ + # Return an integer, positive or negative that represents how well balanced the tree is. + + # Trees which are higher on the left than the right should return a positive value, + # trees which are higher on the right than the left should return a negative value. + # An ideally-balanced tree should return 0. + # """ + # if self.root is None: + # return 0 + # return self._calc_depth(self.root.right) - self._calc_depth(self.root.left) + + + # def print_bst(self): + # """Return.""" + # thislevel = [self.root] + # while thislevel: + # nextlevel = [] + # print_level = [] + # counter = 0 + # for n in thislevel: + # print(print_level[counter]) + # if n.left: + # nextlevel.append(n.left) + # print_level.append(n.left.value) + # else: + # print_level.append('_') + # if n.right: + # nextlevel.append(n.right) + # print_level.append(n.right.value) + # else: + # print_level.append('_') + # print() + # thislevel = nextlevel + + + +# if __name__ == "__main__": + +# res1 = timeit.repeat(stmt="depth(g)", setup="from graph import g, depth", number=10, repeat=3) +# res2 = timeit.repeat(stmt="breadth(g)", setup="from graph import g, breadth", number=10, repeat=3) +# print("Depth First: ", res1) +# print("Breadth First: ", res2) diff --git a/src/bst.py b/src/bst.py new file mode 100644 index 0000000..e8bba13 --- /dev/null +++ b/src/bst.py @@ -0,0 +1,330 @@ +"""Module for Binary Search Tree.""" + +from queue_ds import Queue +import timeit +import random + + +class Node(object): + """Node class.""" + + def __init__(self, value=None, left=None, right=None): + """Init of the Node class.""" + self.value = value + self.left = left + self.right = right + + +class BinarySearchTree(object): + """Binary Search Tree.""" + + """insert(self, val): will insert the value val into the BST. If val is already present, it will be ignored.""" + """search(self, val): will return the node containing that value, else None""" + """size(self): will return the integer size of the BST (equal to the total number of values stored in the tree). It will return 0 if the tree is empty.""" + """depth(self): will return an integer representing the total number of levels in the tree. If there is one value, the depth should be 1, if two values it will be 2, if three values it may be 2 or three, depending, etc.""" + """contains(self, val): will return True if val is in the BST, False if not.""" + """balance(self): will return an integer, positive or negative that represents how well balanced the tree is. Trees which are higher on the left than the right should return a positive value, trees which are higher on the right than the left should return a negative value. An ideally-balanced tree should return 0.""" + """in_order(self): will return a generator that will return the values in the tree using in-order traversal, one at a time.""" + """pre_order(self): will return a generator that will return the values in the tree using pre-order traversal, one at a time.""" + """post_order(self): will return a generator that will return the values in the tree using post_order traversal, one at a time.""" + """breadth_first(self): will return a generator that will return the values in the tree using breadth-first traversal, one at a time.""" + + def __init__(self, if_iter=None): + """Init of the Binary Search Tree class.""" + self.root = None + self.counter = 0 + if if_iter: + try: + for value in if_iter: + self.insert(value) + except TypeError: + self.insert(if_iter) + self._in_order = self._in_order_trav() + self._pre_order = self._pre_order_trav() + self._post_order = self._post_order_trav() + self._breadth_first = self._breadth_first_trav() + + def insert(self, val): + """Take a value, inserts into Binary Search Tree at correct placement.""" + if self.root is None: + self.root = Node(val) + self.counter += 1 + + else: + vertex = self.root + while True: + if val > vertex.value: + if vertex.right: + vertex = vertex.right + else: + vertex.right = Node(val) + self.counter += 1 + break + + elif val < vertex.value: + if vertex.left: + vertex = vertex.left + else: + vertex.left = Node(val) + self.counter += 1 + break + else: + break + + def size(self): + """Return size of Binary Search Tree.""" + return self.counter + + def contains(self, val): + """Return True if val is in the BST, False if not.""" + if self.search(val): + return True + return False + + def search(self, val): + """Return the node containing that value, else None.""" + vertex = self.root + while vertex: + if val > vertex.value: + if not vertex.right: + return None + vertex = vertex.right + elif val < vertex.value: + if not vertex.left: + return None + vertex = vertex.left + else: + return vertex + return None + + def depth(self): + """ + Return an integer representing the total number of levels in the tree. + + If there is one value, the depth should be 1, if two values it will be 2, + if three values it may be 2 or three, depending, etc. + """ + return self._calc_depth(self.root) + + def _calc_depth(self, tree): + """Calculate the depth of the binary search tree recursively.""" + if tree is None: + return 0 + else: + return max(self._calc_depth(tree.right), self._calc_depth(tree.left)) + 1 + + def balance(self): + """ + Return an integer, positive or negative that represents how well balanced the tree is. + + Trees which are higher on the left than the right should return a positive value, + trees which are higher on the right than the left should return a negative value. + An ideally-balanced tree should return 0. + """ + if self.root is None: + return 0 + return self._calc_depth(self.root.right) - self._calc_depth(self.root.left) + + def in_order(self): + """Return the next value from the generator _in_order.""" + return next(self._in_order) + + def _in_order_trav(self): + """Traverse in_order, yielding via generator.""" + vertex = self.root + visited = [] + while (visited or vertex is not None): + + if vertex is not None: + visited.append(vertex) + vertex = vertex.left + else: + vertex = visited.pop() + yield vertex.value + vertex = vertex.right + + def pre_order(self): + """Return the next value from the generator _pre_order.""" + return next(self._pre_order) + + def _pre_order_trav(self): + """Traverse pre_order, yielding via generator.""" + vertex = self.root + visited = [] + while (visited or vertex is not None): + if vertex is not None: + yield vertex.value + visited.append(vertex) + vertex = vertex.left + else: + vertex = visited.pop() + vertex = vertex.right + + def post_order(self): + """Return the next value from the generator _post_order.""" + return next(self._post_order) + + def _post_order_trav(self): + """Traverse pre_order, yielding via generator.""" + vertex = self.root + peek_vertex = None + last_vertex = None + visited = [] + while (visited or vertex is not None): + if vertex is not None: + visited.append(vertex) + vertex = vertex.left + else: + peek_vertex = visited[-1] + if peek_vertex.right and peek_vertex.right is not last_vertex: + vertex = peek_vertex.right + else: + yield peek_vertex.value + last_vertex = visited.pop() + + def breadth_first(self): + """Return the next value from the generator _breadth_first.""" + return next(self._breadth_first) + + def _breadth_first_trav(self): + """Traverse breadth first order, yielding a generator.""" + q = Queue() + q.enqueue(self.root) + while len(q) > 0: + vertex = q.dequeue() + yield vertex.value + if (vertex.left): + q.enqueue(vertex.left) + if (vertex.right): + q.enqueue(vertex.right) + + def delete(self, val): + """Remove val from the tree if present, if not present this method is a no-op. Return None in all cases.""" + vertex = self.root + parent_of_del = None + del_node = None + if self.root is None: + return + if self.root.value == val: + left = self.root.left + right = self.root.right + if not right: + self.root = self.root.left + self.counter -= 1 + return + if not right.left: + self.root = right + self.root.left = left + self.counter -= 1 + return + vertex = vertex.right + while True: + if not vertex.left.left: + min_parent = vertex + break + else: + vertex = vertex.left + min_node = min_parent.left + min_parent.left = min_node.right + self.root = min_node + self.root.left = left + self.root.right = right + self.counter -= 1 + else: + while True: + if vertex.right and val == vertex.right.value: + parent_of_del = vertex + del_node = parent_of_del.right + min_parent = self._find_min_parent(parent_of_del, "right") + break + elif vertex.left and val == vertex.left.value: + parent_of_del = vertex + del_node = parent_of_del.left + min_parent = self._find_min_parent(parent_of_del, "left") + break + elif val > vertex.value and vertex.right: + vertex = vertex.right + elif val < vertex.value and vertex.left: + vertex = vertex.left + else: + return + + if parent_of_del.right and val == parent_of_del.right.value: + if not min_parent: + parent_of_del.right = None + self.counter -= 1 + return + if min_parent is del_node: + right = del_node.right + del_node_left = del_node.left + parent_of_del.right = right + parent_of_del.right.left = del_node_left + self.counter -= 1 + return + left = del_node.left + right = del_node.right + min_node = min_parent.left + min_parent.left = min_node.right + del_node = min_node + del_node.right = right + del_node.left = left + parent_of_del.right = del_node + self.counter -= 1 + + elif parent_of_del.left and val == parent_of_del.left.value: + if not min_parent: + parent_of_del.left = None + self.counter -= 1 + return + if min_parent is del_node: + left = del_node.right + del_node_left = del_node.left + parent_of_del.left = left + parent_of_del.left.left = del_node_left + self.counter -= 1 + return + left = del_node.left + right = del_node.right + min_node = min_parent.left + min_parent.left = min_node.right + del_node = min_node + del_node.right = right + del_node.left = left + parent_of_del.left = del_node + self.counter -= 1 + + def _find_min_parent(self, vertex, side): + """Find the parent of the replacement node, given the parent of the delete node.""" + if side == "right": + if not vertex.right.right and not vertex.right.left: + return + if vertex.right.right and not vertex.right.right.left: + return vertex.right + elif vertex.right.right and vertex.right.right.left: + vertex = vertex.right.right + while True: + if not vertex.left.left: + return vertex + else: + vertex = vertex.left + else: + if not vertex.left.right and not vertex.left.left: + return + if vertex.left.right and not vertex.left.right.left: + return vertex.left + elif vertex.left.right and vertex.left.right.left: + vertex = vertex.left.right + while True: + if not vertex.left.left: + return vertex + else: + vertex = vertex.left + return + + +# if __name__ == "__main__": + +# res1 = timeit.repeat(stmt="depth(g)", setup="from graph import g, depth", number=10, repeat=3) +# res2 = timeit.repeat(stmt="breadth(g)", setup="from graph import g, breadth", number=10, repeat=3) +# print("Depth First: ", res1) +# print("Breadth First: ", res2) diff --git a/src/decision_tree.py b/src/decision_tree.py new file mode 100644 index 0000000..46392a2 --- /dev/null +++ b/src/decision_tree.py @@ -0,0 +1,288 @@ +"""Module for decision tree.""" + +# DECISION TREE (DT) +# +# CodeFellows 401d5 +# Submission Date: +# +# Authors: Colin Lamont +# Ben Shields +# +# URL: + + +class TreeNode(object): + """An individual node for a decision tree.""" + + def __init__(self, column=None, split=None, left=None, right=None, data=None): + """Init function for TreeNode class.""" + self.column = column + self.data = data + self.split = split + self.left = left + self.right = right + + +class DTC(object): + """Decision Tree Class. + + clf.fit(self, data): construct a decision tree based on some incoming data set; returns nothing + clf.predict(self, data): returns labels for your test data. + max_depth: limits the maximum number of steps your tree can take down any decision chain. + min_leaf_size: Limits the minimum number of data points that may exist within a region before ending a decision chain. + """ + + def __init__(self, max_depth=None, min_leaf_size=1): + """Initialize the DTC object.""" + self.max_depth = max_depth + self.min_leaf_size = min_leaf_size + self.root = None + self.fitted = False + + def fit(self, data): + """Generate conditions for classification of flowers based on training set.""" + self.fitted = True + depth = 0 + node_args = self._split(data) + self.root = TreeNode(column=node_args[0], split=node_args[1], data=data) + node = self.root + nodes = [] + nodes.append(node) + depth += 1 + while(depth <= self.max_depth): + node = nodes.pop() + node_data = node.data + node_args = self._split(node_data) + data_right, data_left, right_type, left_type = self._make_lr_data(data, node_args) + if self._is_pure(data_right) or depth >= self.max_depth: + node.right = right_type + else: + right_node_args = self._split(data_right) + node.right = TreeNode(column=right_node_args[0], split=right_node_args[1], data=data_right) + nodes.append(node.right) + depth += 1 + if depth >= self.max_depth: + self._assign_leaves() + break + if self._is_pure(data_left) or depth >= self.max_depth: + node.left = left_type + else: + left_node_args = self._split(data_left) + node.left = TreeNode(column=left_node_args[0], split=left_node_args[1], data=data_left) + nodes.append(node.left) + depth += 1 + if depth >= self.max_depth: + self._assign_leaves() + break + if self._tree_complete(): + break + + def _tree_complete(self): + """Check to see if the decision tree is completely filled out, with no more nodes to assign.""" + nodes = [] + nodes.append(self.root) + while(True): + if len(nodes) == 0: + return True + node = nodes.pop() + if type(node.right) == TreeNode: + nodes.append(node.right) + if type(node.left) == TreeNode: + nodes.append(node.left) + if not node.right: + return False + if not node.left: + return False + + def _assign_leaves(self): + """Assign end leaves to nodes who have children that are None.""" + nodes = [] + nodes.append(self.root) + while(True): + if len(nodes) == 0: + return + node = nodes.pop() + if type(node.right) == TreeNode: + nodes.append(node.right) + if type(node.left) == TreeNode: + nodes.append(node.left) + if not node.right: + node_args = self._split(node.data) + node.right = self._make_lr_data(node.data, node_args)[2] + if not node.left: + node_args = self._split(node.data) + node.left = self._make_lr_data(node.data, node_args)[3] + + def _make_lr_data(self, data, node_args): + """Fill data_right and data_left lists depending on column and split.""" + data_right, data_left = [], [] + right_type = {'setosa': 0, 'versicolor': 0} + left_type = {'setosa': 0, 'versicolor': 0} + if node_args[0] == 'x': + for each in data: + if each[0] > node_args[1]: + data_right.append(each) + right_type[each[2]] += 1 + else: + data_left.append(each) + left_type[each[2]] += 1 + elif node_args[0] == 'y': + for each in data: + if each[1] > node_args[1]: + data_right.append(each) + right_type[each[2]] += 1 + else: + data_left.append(each) + left_type[each[2]] += 1 + if right_type['setosa'] > right_type['versicolor']: + right_type = 'setosa' + else: + right_type = 'versicolor' + if left_type['setosa'] > left_type['versicolor']: + left_type = 'setosa' + else: + left_type = 'versicolor' + return data_right, data_left, right_type, left_type + + def _is_pure(self, data): + """Check to see if the data is pure.""" + setosa = [] + versicolor = [] + for each in data: + if each[2] == "setosa": + setosa.append(each) + else: + versicolor.append(each) + if len(setosa) == 0: + return True + elif len(versicolor) == 0: + return True + return False + + # column_name = self.some_best_column_algorithm() + # split_pt = self.some_best_split_point_algorithm() + # if result of splitting produces nodes with at least one value: + # node.left = TreeNode(data_idx=node.data_idx where less than split_pt) + # node.right = TreeNode(data_idx=node.data_idx where greater than split_pt) + # elif left has one value or left is purely one label: + # end left + # elif right has one value or right is purely one label: + # end right + + + + # self.root = TreeNode(data_idx=data.index) + + # until max depth or min leaf min_leaf_size + # split nodes starting at root + + def _split(self, data): + """Given some input node containing data, find best column to split on, and assign split point, and child nodes.""" + pl_list = [] + pw_list = [] + for each in data: + pl_list.append(each[0]) + for each in data: + pw_list.append(each[1]) + t = None + axis = None + min_g = None + for each in pl_list: + data_left = [] + data_right = [] + for j in range(len(data)): + if pl_list[j] < each: + data_left.append(data[j]) + else: + data_right.append(data[j]) + if len(data_left) == 0 or len(data_right) == 0: + continue + g = self._min_func(data, data_left, data_right) + if min_g is None: + t = each + min_g = g + axis = 'x' + if g < min_g: + min_g = g + t = each + axis = 'x' + for each in pw_list: + data_left = [] + data_right = [] + for j in range(len(data)): + if pl_list[j] < each: + data_left.append(data[j]) + else: + data_right.append(data[j]) + if len(data_left) == 0 or len(data_right) == 0: + continue + g = self._min_func(data, data_left, data_right) + if min_g is None: + t = each + min_g = g + axis = 'y' + if g < min_g: + min_g = g + t = each + axis = 'y' + return axis, t + + def _min_func(self, total_data, data_left, data_right): + """Docstring.""" + return (len(data_left) / len(total_data)) * self._analyze_purities(data_left) + (len(data_right) / len(total_data)) * self._analyze_purities(data_right) + + def _analyze_purities(self, data): + """Docstring.""" + setosa = [] + versicolor = [] + for each in data: + if each[2] == "setosa": + setosa.append(each) + else: + versicolor.append(each) + return (len(setosa) / len(data)) * (1 - (len(setosa) / len(data))) + (len(versicolor) / len(data)) * (1 - (len(versicolor) / len(data))) + + def predict(self, data): + """Return the likely classification for a flower(s) given petal length and petal width.""" + if not self.fitted: + return "Decision Tree not trained... yet." + node = self.root + while(True): + if node.column == 'x': + if data[0] < node.split: + if type(node.left) == str: + return node.left + else: + node = node.left + else: + if type(node.right) == str: + return node.right + else: + node = node.right + elif node.column == 'y': + if data[1] < node.split: + if type(node.left) == str: + return node.left + else: + node = node.left + else: + if type(node.right) == str: + return node.right + else: + node = node.right + + + + # return_list = [] + # for each in data: + # if each[0] < 2.5: + # return_list.append("setosa") + # else: + # return_list.append("versicolor") + # return return_list + +########################################################################### + + # def log_scalar(num_array): + # top = np.log10(num_array) - min(np.log10(num_array)) + # bottom = max(np.log10(num_array)) - min(np.log10(num_array)) diff --git a/src/flowers_data.csv b/src/flowers_data.csv new file mode 100644 index 0000000..63fed67 --- /dev/null +++ b/src/flowers_data.csv @@ -0,0 +1,101 @@ +petal length (cm),petal width (cm),sepal length (cm),sepal width (cm),target,class_names +1.4,0.2,5.1,3.5,0,setosa +1.4,0.2,4.9,3.0,0,setosa +1.3,0.2,4.7,3.2,0,setosa +1.5,0.2,4.6,3.1,0,setosa +1.4,0.2,5.0,3.6,0,setosa +1.7,0.4,5.4,3.9,0,setosa +1.4,0.3,4.6,3.4,0,setosa +1.5,0.2,5.0,3.4,0,setosa +1.4,0.2,4.4,2.9,0,setosa +1.5,0.1,4.9,3.1,0,setosa +1.5,0.2,5.4,3.7,0,setosa +1.6,0.2,4.8,3.4,0,setosa +1.4,0.1,4.8,3.0,0,setosa +1.1,0.1,4.3,3.0,0,setosa +1.2,0.2,5.8,4.0,0,setosa +1.5,0.4,5.7,4.4,0,setosa +1.3,0.4,5.4,3.9,0,setosa +1.4,0.3,5.1,3.5,0,setosa +1.7,0.3,5.7,3.8,0,setosa +1.5,0.3,5.1,3.8,0,setosa +1.7,0.2,5.4,3.4,0,setosa +1.5,0.4,5.1,3.7,0,setosa +1.0,0.2,4.6,3.6,0,setosa +1.7,0.5,5.1,3.3,0,setosa +1.9,0.2,4.8,3.4,0,setosa +1.6,0.2,5.0,3.0,0,setosa +1.6,0.4,5.0,3.4,0,setosa +1.5,0.2,5.2,3.5,0,setosa +1.4,0.2,5.2,3.4,0,setosa +1.6,0.2,4.7,3.2,0,setosa +1.6,0.2,4.8,3.1,0,setosa +1.5,0.4,5.4,3.4,0,setosa +1.5,0.1,5.2,4.1,0,setosa +1.4,0.2,5.5,4.2,0,setosa +1.5,0.1,4.9,3.1,0,setosa +1.2,0.2,5.0,3.2,0,setosa +1.3,0.2,5.5,3.5,0,setosa +1.5,0.1,4.9,3.1,0,setosa +1.3,0.2,4.4,3.0,0,setosa +1.5,0.2,5.1,3.4,0,setosa +1.3,0.3,5.0,3.5,0,setosa +1.3,0.3,4.5,2.3,0,setosa +1.3,0.2,4.4,3.2,0,setosa +1.6,0.6,5.0,3.5,0,setosa +1.9,0.4,5.1,3.8,0,setosa +1.4,0.3,4.8,3.0,0,setosa +1.6,0.2,5.1,3.8,0,setosa +1.4,0.2,4.6,3.2,0,setosa +1.5,0.2,5.3,3.7,0,setosa +1.4,0.2,5.0,3.3,0,setosa +4.7,1.4,7.0,3.2,1,versicolor +4.5,1.5,6.4,3.2,1,versicolor +4.9,1.5,6.9,3.1,1,versicolor +4.0,1.3,5.5,2.3,1,versicolor +4.6,1.5,6.5,2.8,1,versicolor +4.5,1.3,5.7,2.8,1,versicolor +4.7,1.6,6.3,3.3,1,versicolor +3.3,1.0,4.9,2.4,1,versicolor +4.6,1.3,6.6,2.9,1,versicolor +3.9,1.4,5.2,2.7,1,versicolor +3.5,1.0,5.0,2.0,1,versicolor +4.2,1.5,5.9,3.0,1,versicolor +4.0,1.0,6.0,2.2,1,versicolor +4.7,1.4,6.1,2.9,1,versicolor +3.6,1.3,5.6,2.9,1,versicolor +4.4,1.4,6.7,3.1,1,versicolor +4.5,1.5,5.6,3.0,1,versicolor +4.1,1.0,5.8,2.7,1,versicolor +4.5,1.5,6.2,2.2,1,versicolor +3.9,1.1,5.6,2.5,1,versicolor +4.8,1.8,5.9,3.2,1,versicolor +4.0,1.3,6.1,2.8,1,versicolor +4.9,1.5,6.3,2.5,1,versicolor +4.7,1.2,6.1,2.8,1,versicolor +4.3,1.3,6.4,2.9,1,versicolor +4.4,1.4,6.6,3.0,1,versicolor +4.8,1.4,6.8,2.8,1,versicolor +5.0,1.7,6.7,3.0,1,versicolor +4.5,1.5,6.0,2.9,1,versicolor +3.5,1.0,5.7,2.6,1,versicolor +3.8,1.1,5.5,2.4,1,versicolor +3.7,1.0,5.5,2.4,1,versicolor +3.9,1.2,5.8,2.7,1,versicolor +5.1,1.6,6.0,2.7,1,versicolor +4.5,1.5,5.4,3.0,1,versicolor +4.5,1.6,6.0,3.4,1,versicolor +4.7,1.5,6.7,3.1,1,versicolor +4.4,1.3,6.3,2.3,1,versicolor +4.1,1.3,5.6,3.0,1,versicolor +4.0,1.3,5.5,2.5,1,versicolor +4.4,1.2,5.5,2.6,1,versicolor +4.6,1.4,6.1,3.0,1,versicolor +4.0,1.2,5.8,2.6,1,versicolor +3.3,1.0,5.0,2.3,1,versicolor +4.2,1.3,5.6,2.7,1,versicolor +4.2,1.2,5.7,3.0,1,versicolor +4.2,1.3,5.7,2.9,1,versicolor +4.3,1.3,6.2,2.9,1,versicolor +3.0,1.1,5.1,2.5,1,versicolor +4.1,1.3,5.7,2.8,1,versicolor diff --git a/src/hash_table.py b/src/hash_table.py new file mode 100644 index 0000000..d0ac755 --- /dev/null +++ b/src/hash_table.py @@ -0,0 +1,132 @@ +"""Hash table module.""" + +# HASH TABLE (HT) +# +# CodeFellows 401d5 +# Submission Date: +# +# Authors: Colin Lamont +# Ben Shields +# +# URL: + + +""" +The HashTable is a data structure that implements an +assosiative array. The structure maps keys to values +in such a way that values may be accessed in O(1) time. +This is done through use of a hashing function. +The hashing function maps the content of the key to +a specific bucket. As the content of the key always +maps to a specific hash value, the key's value may +be accessed quickly. + + - get(key) - should return the value stored with the given key + - set(key, val) - should store the given val using the given key + - _hash(key) - should hash the key provided (note that this is an internal api) +""" + + +class HashTable(object): + """Hash Table.""" + + """self._num_buckets - the number of buckets in the Hash Table.""" + """get(key) - should return the value stored with the given key.""" + """set(key, val) - store the given val using the given key.""" + """_hash(key) - hash the key provided (note that this is an internal api).""" + + def __init__(self, hash_type='additive'): + """Init function for the Hash Table class.""" + self._num_buckets = 50000 + self._container = [[] for i in range(0, self._num_buckets)] + self._type = hash_type + + def get(self, key): + """Return the value corresponding to this key in the hash table.""" + hashed_value = self._hash(key) + for each in self._container[hashed_value]: + if each[0] == key: + return each[1] + return 'Key not in hash table.' + + def set(self, key, val): + """Place the key value pair in the hash table.""" + if type(key) is not str: + return 'Keys must be strings.' + hashed_value = self._hash(key) + for each in self._container[hashed_value]: + if each[0] == key: + (self._container[hashed_value]).remove(each) + (self._container[hashed_value]).append((key, val)) + + def _hash(self, key): + """Additive hash function.""" + if self._type == 'additive': + return self._additive_hash(key) + if self._type == 'colin-ben': + return self._colin_ben_hash(key) + + def _additive_hash(self, key): + return sum([ord(each) for each in key]) % self._num_buckets + + def _colin_ben_hash(self, key): + ords = [] + sieve = gen_primes() + for each in key: + num = ord(each) + if ord(each) % 2 == 0: + num = int(str(ord(each))[::-1]) + ords.append([num, next(sieve)]) + a_sum = sum([each[0] * each[1] for each in ords]) + return a_sum % self._num_buckets + + # def _colin_ben_hash(self, key): + # ords = [] + # sieve = gen_primes() + # for each in key: + # num = ord(each) + # if ord(each) % 2 == 0: + # num = int(str(ord(each))[::-1]) + # ords.append([num, next(sieve)]) + # a_sum = sum([(each[0] << next(sieve)) * (each[1] << next(sieve)) for each in ords]) + # return a_sum % self._num_buckets + + +def gen_primes(): + """ Generate an infinite sequence of prime numbers. + """ + # Maps composites to primes witnessing their compositeness. + # This is memory efficient, as the sieve is not "run forward" + # indefinitely, but only as long as required by the current + # number being tested. + # + D = {} + + # The running integer that's checked for primeness + q = 2 + + while True: + if q not in D: + # q is a new prime. + # Yield it and mark its first multiple that isn't + # already marked in previous iterations + # + yield q + D[q * q] = [q] + else: + # q is composite. D[q] is the list of primes that + # divide it. Since we've reached q, we no longer + # need it in the map, but we'll mark the next + # multiples of its witnesses to prepare for larger + # numbers + # + for p in D[q]: + D.setdefault(p + q, []).append(p) + del D[q] + + q += 1 + +# Gen Primes function: +# Sieve of Eratosthenes +# Code by David Eppstein, UC Irvine, 28 Feb 2002 +# http://code.activestate.com/recipes/117119/ diff --git a/src/insertion_sort.py b/src/insertion_sort.py new file mode 100644 index 0000000..b1733b0 --- /dev/null +++ b/src/insertion_sort.py @@ -0,0 +1,61 @@ +"""Insertion Sort Module.""" + +# INSERTION SORT (IS) +# +# CodeFellows 401d5 +# Submission Date: +# +# Authors: Colin Lamont +# Ben Shields +# +# URL: + + +def insertion_sort(isl): + """Insertion sort method.""" + for item in range(len(isl)): + x = isl[item] + neighbor = item - 1 + while neighbor >= 0 and isl[neighbor] > x: + isl[neighbor + 1] = isl[neighbor] + neighbor -= 1 + isl[neighbor + 1] = x + return isl + + +def _random_list(): + """Return a list of random numbers from 0 to 300 of random size less than 300.""" + import random + b = random + return b.sample(range(0, 300), 150) + + +a = _random_list() +r = a[:] +b = sorted(a) +w = b[::-1] + + +if __name__ == "__main__": + import timeit + + random_insertion_sort_timed = timeit.repeat(stmt="insertion_sort(r)", setup="from insertion_sort import insertion_sort, r", number=1000, repeat=3) + random_average_insertion_sort_timed = float(sum(random_insertion_sort_timed) / len(random_insertion_sort_timed)) + + print("number of runs: " + str(3)) + print("random insertion_sort_timed: " + str(random_insertion_sort_timed)) + print("average: ", str(random_average_insertion_sort_timed)) + + best_insertion_sort_timed = timeit.repeat(stmt="insertion_sort(b)", setup="from insertion_sort import insertion_sort, b", number=1000, repeat=3) + best_average_insertion_sort_timed = float(sum(best_insertion_sort_timed) / len(best_insertion_sort_timed)) + + print("number of runs: " + str(3)) + print("best case insertion_sort_timed: " + str(best_insertion_sort_timed)) + print("average: ", str(best_average_insertion_sort_timed)) + + worst_insertion_sort_timed = timeit.repeat(stmt="insertion_sort(w)", setup="from insertion_sort import insertion_sort, w", number=1000, repeat=3) + worst_average_insertion_sort_timed = float(sum(worst_insertion_sort_timed) / len(worst_insertion_sort_timed)) + + print("number of runs: " + str(3)) + print("worst case insertion_sort_timed: " + str(worst_insertion_sort_timed)) + print("average: ", str(worst_average_insertion_sort_timed)) diff --git a/src/merge_sort.py b/src/merge_sort.py new file mode 100644 index 0000000..da033fa --- /dev/null +++ b/src/merge_sort.py @@ -0,0 +1,78 @@ +"""Merge Sort Module.""" + +# MERGE SORT (MS) +# +# CodeFellows 401d5 +# Submission Date: +# +# Authors: Colin Lamont +# Ben Shields +# +# URL: + + +def merge_sort(msl): + """Merge sort method.""" + if len(msl) == 1 or not msl: + return msl + msl1 = msl[:int(len(msl) / 2)] + msl2 = msl[int(len(msl) / 2):] + + msl1 = merge_sort(msl1) + msl2 = merge_sort(msl2) + + def _merge(msla, mslb): + """Merge compares the two lists and returns a sorted list from lowest to highest value.""" + sorted_list = [] + while len(msla) and len(mslb): + if msla[0] < mslb[0]: + low = msla.pop(0) + else: + low = mslb.pop(0) + sorted_list.append(low) + if len(msla): + sorted_list.extend(msla) + return sorted_list + else: + sorted_list.extend(mslb) + return sorted_list + + return _merge(msl1, msl2) + + +def _random_list(): + """Return a list of random numbers from 0 to 300 of size 150.""" + import random + b = random + return b.sample(range(0, 300), 150) + + +a = _random_list() +r = a[:] +b = sorted(a) +w = b[::-1] + + +if __name__ == "__main__": + import timeit + + random_merge_sort_timed = timeit.repeat(stmt="merge_sort(r)", setup="from merge_sort import merge_sort, r", number=1000, repeat=3) + random_average_merge_sort_timed = float(sum(random_merge_sort_timed) / len(random_merge_sort_timed)) + + print("number of runs: " + str(3)) + print("random merge_sort_timed: " + str(random_merge_sort_timed)) + print("average: ", str(random_average_merge_sort_timed)) + + best_merge_sort_timed = timeit.repeat(stmt="merge_sort(b)", setup="from merge_sort import merge_sort, b", number=1000, repeat=3) + best_average_merge_sort_timed = float(sum(best_merge_sort_timed) / len(best_merge_sort_timed)) + + print("number of runs: " + str(3)) + print("best case merge_sort_timed: " + str(best_merge_sort_timed)) + print("average: ", str(best_average_merge_sort_timed)) + + worst_merge_sort_timed = timeit.repeat(stmt="merge_sort(w)", setup="from merge_sort import merge_sort, w", number=1000, repeat=3) + worst_average_merge_sort_timed = float(sum(worst_merge_sort_timed) / len(worst_merge_sort_timed)) + + print("number of runs: " + str(3)) + print("worst case merge_sort_timed: " + str(worst_merge_sort_timed)) + print("average: ", str(worst_average_merge_sort_timed)) diff --git a/src/quick_sort.py b/src/quick_sort.py new file mode 100644 index 0000000..f26a9c3 --- /dev/null +++ b/src/quick_sort.py @@ -0,0 +1,71 @@ +"""Quick Sort Module.""" + +# QUICK SORT (MS) +# +# CodeFellows 401d5 +# Submission Date: +# +# Authors: Colin Lamont +# Ben Shields +# +# URL: + +""" +quick_sort: picks a pivot, compares the rest of list next to the pivot, creates a lesser and greater list, and recursively calls quick_sort, combining the sorted lists and initial pivot at the end. +""" + + +def quick_sort(sort_list): + """Quick sort method.""" + if len(sort_list) == 1 or not sort_list: + return sort_list + pivot = sort_list[0] + sort_list1 = [] + sort_list2 = [] + for item in sort_list[1:]: + if item < pivot: + sort_list1.append(item) + else: + sort_list2.append(item) + sort_list1 = quick_sort(sort_list1) + sort_list1.append(pivot) + sort_list2 = quick_sort(sort_list2) + return sort_list1 + sort_list2 + + +def _random_list(): + """Return a list of random numbers from 0 to 300 of size 150.""" + import random + b = random + return b.sample(range(0, 300), 150) + + +a = _random_list() +r = a[:] +b = sorted(a) +w = b[::-1] + + +if __name__ == "__main__": + import timeit + + random_quick_sort_timed = timeit.repeat(stmt="quick_sort(r)", setup="from quick_sort import quick_sort, r", number=1000, repeat=3) + random_average_quick_sort_timed = float(sum(random_quick_sort_timed) / len(random_quick_sort_timed)) + + print("number of runs: " + str(3)) + print("random quick_sort_timed: " + str(random_quick_sort_timed)) + print("average: ", str(random_average_quick_sort_timed)) + + arranged_quick_sort_timed = timeit.repeat(stmt="quick_sort(b)", setup="from quick_sort import quick_sort, b", number=1000, repeat=3) + arranged_average_quick_sort_timed = float(sum(arranged_quick_sort_timed) / len(arranged_quick_sort_timed)) + + print("number of runs: " + str(3)) + print("arranged case quick_sort_timed: " + str(arranged_quick_sort_timed)) + print("average: ", str(arranged_average_quick_sort_timed)) + + reversed_quick_sort_timed = timeit.repeat(stmt="quick_sort(w)", setup="from quick_sort import quick_sort, w", number=1000, repeat=3) + reversed_average_quick_sort_timed = float(sum(reversed_quick_sort_timed) / len(reversed_quick_sort_timed)) + + print("number of runs: " + str(3)) + print("reversed case quick_sort_timed: " + str(reversed_quick_sort_timed)) + print("average: ", str(reversed_average_quick_sort_timed)) diff --git a/src/radix.py b/src/radix.py new file mode 100644 index 0000000..00fe5f6 --- /dev/null +++ b/src/radix.py @@ -0,0 +1,73 @@ +"""Radix Sort Module.""" + +# RADIX SORT (MS) +# +# CodeFellows 401d5 +# Submission Date: +# +# Authors: Colin Lamont +# Ben Shields +# +# URL: + +""" +radix_sort: sorts numbers by their 0-9 digits first, putting them in a list from lowest to highest. Next does the same for 10s (10-99), and so forth. +""" + + +def radix_sort(sort_list): + """Radix sort method.""" + if len(sort_list) == 1 or not sort_list: + return sort_list + iters = len(str(max(sort_list))) - 1 + for i in range(iters + 1): + temp_list = [] + num_pots = [[] for x in range(10)] + for item in sort_list: + try: + num_pots[int(str(item)[-(i + 1)])].append(item) + except: + num_pots[0].append(item) + for nums in num_pots: + temp_list.extend(nums) + sort_list = temp_list + iters -= 1 + return temp_list + + +def _random_list(): + """Return a list of random numbers from 0 to 300 of size 150.""" + import random + b = random + return b.sample(range(0, 300), 150) + + +a = _random_list() +r = a[:] +b = sorted(a) +w = b[::-1] + + +if __name__ == "__main__": + import timeit + + random_radix_sort_timed = timeit.repeat(stmt="radix_sort(r)", setup="from radix import radix_sort, r", number=1000, repeat=3) + random_average_radix_sort_timed = float(sum(random_radix_sort_timed) / len(random_radix_sort_timed)) + + print("number of runs: " + str(3)) + print("random radix_sort_timed: " + str(random_radix_sort_timed)) + print("average: ", str(random_average_radix_sort_timed)) + + arranged_radix_sort_timed = timeit.repeat(stmt="radix_sort(b)", setup="from radix import radix_sort, b", number=1000, repeat=3) + arranged_average_radix_sort_timed = float(sum(arranged_radix_sort_timed) / len(arranged_radix_sort_timed)) + + print("number of runs: " + str(3)) + print("arranged case radix_sort_timed: " + str(arranged_radix_sort_timed)) + print("average: ", str(arranged_average_radix_sort_timed)) + + reversed_radix_sort_timed = timeit.repeat(stmt="radix_sort(w)", setup="from radix import radix_sort, w", number=1000, repeat=3) + reversed_average_radix_sort_timed = float(sum(reversed_radix_sort_timed) / len(reversed_radix_sort_timed)) + + print("number of runs: " + str(3)) + print("reversed case radix_sort_timed: " + str(reversed_radix_sort_timed)) + print("average: ", str(reversed_average_radix_sort_timed)) diff --git a/src/test_balanced_bst.py b/src/test_balanced_bst.py new file mode 100644 index 0000000..0dfbfe1 --- /dev/null +++ b/src/test_balanced_bst.py @@ -0,0 +1,738 @@ +"""Test Module for Balanced Binary Search Tree.""" +from balanced_bst import BinarySearchTree +import pytest + +BST_ROT_2_R_1 = [3, 2] +BST_ROT_2_L_3 = [1, 2] +BST_ROT_LR_GC_7 = [10, 12, 5, 8, 2] +BST_ROT_RL = [10, 5, 15, 13, 20] + + +@pytest.fixture +def filled_bst_rot_2_r_1(): + """Fixture for a 2 node tree for a right rotation with insertion of 1.""" + new_tree = BinarySearchTree(BST_ROT_2_R_1) + return new_tree + + +@pytest.fixture +def filled_bst_rot_2_l_3(): + """Fixture for a 2 node tree for a left rotation with insertion of 3.""" + new_tree = BinarySearchTree(BST_ROT_2_L_3) + return new_tree + + +@pytest.fixture +def filled_bst_rot_lr_gc_7(): + """Fixture for a binary search tree for a left-right rotation with insertion of 7 to tree 10, 12, 5, 8, 2.""" + new_tree = BinarySearchTree(BST_ROT_LR_GC_7) + return new_tree + + +@pytest.fixture +def filled_bst_rot_rl(): + """Fixture for a binary search tree for a right-left rotation.""" + new_tree = BinarySearchTree(BST_ROT_RL) + return new_tree + + +def test_simple_3_node_right_rotation(filled_bst_rot_2_r_1): + """Balance bst via right rotation, when adding 1 to tree of 3, 2.""" + a = filled_bst_rot_2_r_1 + a.insert(1) + assert a.root.value == 2 + assert a.root.right.value == 3 + assert a.root.left.value == 1 + + +def test_simple_3_node_left_rotation(filled_bst_rot_2_l_3): + """Balance bst via left rotation, when adding 3 to tree of 1, 2.""" + a = filled_bst_rot_2_l_3 + a.insert(3) + assert a.root.value == 2 + assert a.root.right.value == 3 + assert a.root.left.value == 1 + + +def test_left_right_rotation(filled_bst_rot_lr_gc_7): + """Balance bst via left-right rotation, when adding 7 to tree of 10, 12, 5, 8, 2.""" + a = filled_bst_rot_lr_gc_7 + a.insert(7) + assert a.root.value == 8 + assert a.root.right.value == 10 + assert a.root.left.value == 5 + assert a.root.left.right.value == 7 + assert a.root.left.left.value == 2 + assert a.root.right.right.value == 12 + + +def test_bst_empty_tree(): + """Test balancing empty tree.""" + a = BinarySearchTree() + assert a.root is None + + +def test_bst_1_value(): + """Test balancing tree with one value.""" + a = BinarySearchTree() + a.insert(1) + assert a.root.value == 1 + assert a.root.right is None + assert a.root.left is None + + +def test_bst_2_values(): + """Test balancing tree with two values.""" + a = BinarySearchTree() + a.insert(1) + a.insert(2) + assert a.root.value == 1 + assert a.root.right.value == 2 + assert a.root.left is None + + +def test_bst_3_values(): + """Test balancing tree with three values.""" + a = BinarySearchTree() + a.insert(1) + a.insert(2) + a.insert(3) + assert a.root.value == 2 + assert a.root.right.value == 3 + assert a.root.left.value == 1 + + +def test_bst_4_values(): + """Test balancing tree with four values.""" + a = BinarySearchTree() + a.insert(1) + a.insert(2) + a.insert(3) + a.insert(4) + assert a.root.value == 2 + assert a.root.right.value == 3 + assert a.root.left.value == 1 + assert a.root.right.right.value == 4 + + +def test_bst_5_values(): + """Test balancing tree with five values.""" + a = BinarySearchTree() + a.insert(1) + a.insert(2) + a.insert(3) + a.insert(4) + a.insert(5) + assert a.root.value == 2 + assert a.root.right.value == 4 + assert a.root.left.value == 1 + assert a.root.right.right.value == 5 + assert a.root.right.left.value == 3 + + +def test_bst_6_values(): + """Test balancing tree with six values.""" + a = BinarySearchTree() + a.insert(1) + a.insert(2) + a.insert(3) + a.insert(4) + a.insert(5) + a.insert(6) + assert a.root.value == 4 + assert a.root.right.value == 5 + assert a.root.left.value == 2 + assert a.root.right.right.value == 6 + assert a.root.left.left.value == 1 + assert a.root.left.right.value == 3 + + +def test_bst_7_values(): + """Test balancing tree with seven values.""" + a = BinarySearchTree() + a.insert(1) + a.insert(2) + a.insert(3) + a.insert(4) + a.insert(5) + a.insert(6) + a.insert(7) + assert a.root.value == 4 + assert a.root.right.value == 6 + assert a.root.left.value == 2 + assert a.root.right.right.value == 7 + assert a.root.left.left.value == 1 + assert a.root.left.right.value == 3 + assert a.root.right.left.value == 5 + + +def test_bst_8_values(): + """Test balancing tree with eight values.""" + a = BinarySearchTree() + a.insert(1) + a.insert(2) + a.insert(3) + a.insert(4) + a.insert(5) + a.insert(6) + a.insert(7) + a.insert(8) + assert a.root.value == 4 + assert a.root.right.value == 6 + assert a.root.left.value == 2 + assert a.root.right.right.value == 7 + assert a.root.left.left.value == 1 + assert a.root.left.right.value == 3 + assert a.root.right.left.value == 5 + assert a.root.right.right.right.value == 8 + + +def test_bst_9_values(): + """Test balancing tree with nine values.""" + a = BinarySearchTree() + a.insert(1) + a.insert(2) + a.insert(3) + a.insert(4) + a.insert(5) + a.insert(6) + a.insert(7) + a.insert(8) + a.insert(9) + assert a.root.value == 4 + assert a.root.right.value == 6 + assert a.root.left.value == 2 + assert a.root.right.right.value == 8 + assert a.root.left.left.value == 1 + assert a.root.left.right.value == 3 + assert a.root.right.left.value == 5 + assert a.root.right.right.right.value == 9 + assert a.root.right.right.left.value == 7 + + +def test_bst_10_values(): + """Test balancing tree with ten values.""" + a = BinarySearchTree() + a.insert(1) + a.insert(2) + a.insert(3) + a.insert(4) + a.insert(5) + a.insert(6) + a.insert(7) + a.insert(8) + a.insert(9) + a.insert(10) + assert a.root.value == 4 + assert a.root.right.value == 8 + assert a.root.left.value == 2 + assert a.root.right.right.value == 9 + assert a.root.left.left.value == 1 + assert a.root.left.right.value == 3 + assert a.root.right.right.right.value == 10 + assert a.root.right.left.value == 6 + assert a.root.right.left.right.value == 7 + assert a.root.right.left.left.value == 5 + + +def test_bst_11_values(): + """Test balancing tree with eleven values.""" + a = BinarySearchTree() + a.insert(1) + a.insert(2) + a.insert(3) + a.insert(4) + a.insert(5) + a.insert(6) + a.insert(7) + a.insert(8) + a.insert(9) + a.insert(10) + a.insert(11) + assert a.root.value == 4 + assert a.root.right.value == 8 + assert a.root.left.value == 2 + assert a.root.right.right.value == 10 + assert a.root.left.left.value == 1 + assert a.root.left.right.value == 3 + assert a.root.right.right.right.value == 11 + assert a.root.right.right.left.value == 9 + assert a.root.right.left.value == 6 + assert a.root.right.left.right.value == 7 + assert a.root.right.left.left.value == 5 + + +def test_bst_12_values(): + """Test balancing tree with twelve values.""" + a = BinarySearchTree() + a.insert(1) + a.insert(2) + a.insert(3) + a.insert(4) + a.insert(5) + a.insert(6) + a.insert(7) + a.insert(8) + a.insert(9) + a.insert(10) + a.insert(11) + a.insert(12) + assert a.root.value == 8 + assert a.root.right.value == 10 + assert a.root.right.right.value == 11 + assert a.root.right.left.value == 9 + assert a.root.right.right.right.value == 12 + assert a.root.left.value == 4 + assert a.root.left.left.value == 2 + assert a.root.left.left.left.value == 1 + assert a.root.left.left.right.value == 3 + assert a.root.left.right.value == 6 + assert a.root.left.right.left.value == 5 + assert a.root.left.right.right.value == 7 + + +def test_bst_13_values(): + """Test balancing tree with thirteen values.""" + a = BinarySearchTree() + a.insert(1) + a.insert(2) + a.insert(3) + a.insert(4) + a.insert(5) + a.insert(6) + a.insert(7) + a.insert(8) + a.insert(9) + a.insert(10) + a.insert(11) + a.insert(12) + a.insert(13) + assert a.root.value == 8 + assert a.root.right.value == 10 + assert a.root.right.right.value == 12 + assert a.root.right.left.value == 9 + assert a.root.right.right.right.value == 13 + assert a.root.right.right.left.value == 11 + assert a.root.left.value == 4 + assert a.root.left.left.value == 2 + assert a.root.left.left.left.value == 1 + assert a.root.left.left.right.value == 3 + assert a.root.left.right.value == 6 + assert a.root.left.right.left.value == 5 + assert a.root.left.right.right.value == 7 + + +def test_bst_14_values(): + """Test balancing tree with fourteen values.""" + a = BinarySearchTree() + a.insert(1) + a.insert(2) + a.insert(3) + a.insert(4) + a.insert(5) + a.insert(6) + a.insert(7) + a.insert(8) + a.insert(9) + a.insert(10) + a.insert(11) + a.insert(12) + a.insert(13) + a.insert(14) + assert a.root.value == 8 + assert a.root.right.value == 12 + assert a.root.right.right.value == 13 + assert a.root.right.left.value == 10 + assert a.root.right.right.right.value == 14 + assert a.root.right.left.left.value == 9 + assert a.root.right.left.right.value == 11 + assert a.root.left.value == 4 + assert a.root.left.left.value == 2 + assert a.root.left.left.left.value == 1 + assert a.root.left.left.right.value == 3 + assert a.root.left.right.value == 6 + assert a.root.left.right.left.value == 5 + assert a.root.left.right.right.value == 7 + + +def test_bst_1_value_starting_at_14_down(): + """Test balancing tree with one value.""" + a = BinarySearchTree() + a.insert(14) + assert a.root.value == 14 + assert a.root.right is None + assert a.root.left is None + + +def test_bst_2_values_starting_at_14_down(): + """Test balancing tree with two values.""" + a = BinarySearchTree() + a.insert(14) + a.insert(13) + assert a.root.value == 14 + assert a.root.right is None + assert a.root.left.value == 13 + + +def test_bst_3_values_starting_at_14_down(): + """Test balancing tree with three values.""" + a = BinarySearchTree() + a.insert(14) + a.insert(13) + a.insert(12) + assert a.root.value == 13 + assert a.root.right.value == 14 + assert a.root.left.value == 12 + + +def test_bst_4_values_starting_at_14_down(): + """Test balancing tree with four values.""" + a = BinarySearchTree() + a.insert(14) + a.insert(13) + a.insert(12) + a.insert(11) + assert a.root.value == 13 + assert a.root.right.value == 14 + assert a.root.left.value == 12 + assert a.root.left.left.value == 11 + + +def test_bst_5_values_starting_at_14_down(): + """Test balancing tree with five values.""" + a = BinarySearchTree() + a.insert(14) + a.insert(13) + a.insert(12) + a.insert(11) + a.insert(10) + assert a.root.value == 13 + assert a.root.right.value == 14 + assert a.root.left.value == 11 + assert a.root.left.right.value == 12 + assert a.root.left.left.value == 10 + + +def test_bst_6_values_starting_at_14_down(): + """Test balancing tree with six values.""" + a = BinarySearchTree() + a.insert(14) + a.insert(13) + a.insert(12) + a.insert(11) + a.insert(10) + a.insert(9) + assert a.root.value == 11 + assert a.root.right.value == 13 + assert a.root.left.value == 10 + assert a.root.right.right.value == 14 + assert a.root.right.left.value == 12 + assert a.root.left.left.value == 9 + + +def test_bst_7_values_starting_at_14_down(): + """Test balancing tree with seven values.""" + a = BinarySearchTree() + a.insert(14) + a.insert(13) + a.insert(12) + a.insert(11) + a.insert(10) + a.insert(9) + a.insert(8) + assert a.root.value == 11 + assert a.root.right.value == 13 + assert a.root.left.value == 9 + assert a.root.right.right.value == 14 + assert a.root.left.left.value == 8 + assert a.root.left.right.value == 10 + assert a.root.right.left.value == 12 + + +def test_bst_8_values_starting_at_14_down(): + """Test balancing tree with eight values.""" + a = BinarySearchTree() + a.insert(14) + a.insert(13) + a.insert(12) + a.insert(11) + a.insert(10) + a.insert(9) + a.insert(8) + a.insert(7) + assert a.root.value == 11 + assert a.root.right.value == 13 + assert a.root.left.value == 9 + assert a.root.right.right.value == 14 + assert a.root.left.left.value == 8 + assert a.root.left.right.value == 10 + assert a.root.right.left.value == 12 + assert a.root.left.left.left.value == 7 + + +def test_bst_9_values_starting_at_14_down(): + """Test balancing tree with nine values.""" + a = BinarySearchTree() + a.insert(14) + a.insert(13) + a.insert(12) + a.insert(11) + a.insert(10) + a.insert(9) + a.insert(8) + a.insert(7) + a.insert(6) + assert a.root.value == 11 + assert a.root.right.value == 13 + assert a.root.left.value == 9 + assert a.root.right.right.value == 14 + assert a.root.left.left.value == 7 + assert a.root.left.right.value == 10 + assert a.root.right.left.value == 12 + assert a.root.left.left.left.value == 6 + assert a.root.left.left.right.value == 8 + + +def test_bst_10_values_starting_at_14_down(): + """Test balancing tree with ten values.""" + a = BinarySearchTree() + a.insert(14) + a.insert(13) + a.insert(12) + a.insert(11) + a.insert(10) + a.insert(9) + a.insert(8) + a.insert(7) + a.insert(6) + a.insert(5) + assert a.root.value == 11 + assert a.root.right.value == 13 + assert a.root.right.right.value == 14 + assert a.root.right.left.value == 12 + assert a.root.left.value == 7 + assert a.root.left.left.value == 6 + assert a.root.left.right.value == 9 + assert a.root.left.left.left.value == 5 + assert a.root.left.right.right.value == 10 + assert a.root.left.right.left.value == 8 + + +def test_bst_11_values_starting_at_14_down(): + """Test balancing tree with eleven values.""" + a = BinarySearchTree() + a.insert(14) + a.insert(13) + a.insert(12) + a.insert(11) + a.insert(10) + a.insert(9) + a.insert(8) + a.insert(7) + a.insert(6) + a.insert(5) + a.insert(4) + assert a.root.value == 11 + assert a.root.right.value == 13 + assert a.root.right.right.value == 14 + assert a.root.right.left.value == 12 + assert a.root.left.value == 7 + assert a.root.left.left.value == 5 + assert a.root.left.right.value == 9 + assert a.root.left.left.left.value == 4 + assert a.root.left.right.right.value == 10 + assert a.root.left.right.left.value == 8 + assert a.root.left.left.right.value == 6 + + +def test_bst_12_values_starting_at_14_down(): + """Test balancing tree with twelve values.""" + a = BinarySearchTree() + a.insert(14) + a.insert(13) + a.insert(12) + a.insert(11) + a.insert(10) + a.insert(9) + a.insert(8) + a.insert(7) + a.insert(6) + a.insert(5) + a.insert(4) + a.insert(3) + assert a.root.value == 7 + assert a.root.right.value == 11 + assert a.root.right.right.value == 13 + assert a.root.right.left.value == 9 + assert a.root.right.left.left.value == 8 + assert a.root.right.left.right.value == 10 + assert a.root.right.right.left.value == 12 + assert a.root.right.right.right.value == 14 + assert a.root.left.value == 5 + assert a.root.left.left.value == 4 + assert a.root.left.right.value == 6 + assert a.root.left.left.left.value == 3 + + +def test_bst_13_values_starting_at_14_down(): + """Test balancing tree with thirteen values.""" + a = BinarySearchTree() + a.insert(14) + a.insert(13) + a.insert(12) + a.insert(11) + a.insert(10) + a.insert(9) + a.insert(8) + a.insert(7) + a.insert(6) + a.insert(5) + a.insert(4) + a.insert(3) + a.insert(2) + assert a.root.value == 7 + assert a.root.right.value == 11 + assert a.root.right.right.value == 13 + assert a.root.right.left.value == 9 + assert a.root.right.left.left.value == 8 + assert a.root.right.left.right.value == 10 + assert a.root.right.right.left.value == 12 + assert a.root.right.right.right.value == 14 + assert a.root.left.value == 5 + assert a.root.left.left.value == 3 + assert a.root.left.right.value == 6 + assert a.root.left.left.left.value == 2 + assert a.root.left.left.right.value == 4 + + +def test_bst_14_values_starting_at_14_down(): + """Test balancing tree with fourteen values.""" + a = BinarySearchTree() + a.insert(14) + a.insert(13) + a.insert(12) + a.insert(11) + a.insert(10) + a.insert(9) + a.insert(8) + a.insert(7) + a.insert(6) + a.insert(5) + a.insert(4) + a.insert(3) + a.insert(2) + a.insert(1) + assert a.root.value == 7 + assert a.root.right.value == 11 + assert a.root.right.right.value == 13 + assert a.root.right.left.value == 9 + assert a.root.right.left.left.value == 8 + assert a.root.right.left.right.value == 10 + assert a.root.right.right.left.value == 12 + assert a.root.right.right.right.value == 14 + assert a.root.left.value == 3 + assert a.root.left.left.value == 2 + assert a.root.left.right.value == 5 + assert a.root.left.left.left.value == 1 + assert a.root.left.right.right.value == 6 + assert a.root.left.right.left.value == 4 + + +def test_bst_left_right_basic(filled_bst_rot_2_r_1): + """Test left right rotation.""" + a = filled_bst_rot_2_r_1 + a.insert(1) + assert a.root.value == 2 + assert a.root.parent is None + assert a.root.left.value == 1 + assert a.root.right.value == 3 + + +def test_bst_right_left_basic(filled_bst_rot_2_l_3): + """Test right left rotation.""" + a = filled_bst_rot_2_l_3 + a.insert(3) + assert a.root.value == 2 + assert a.root.parent is None + assert a.root.left.value == 1 + assert a.root.right.value == 3 + + +def test_bst_left_right_root_left_right_add_left(filled_bst_rot_lr_gc_7): + """Test left right rotation.""" + a = filled_bst_rot_lr_gc_7 + a.insert(7) + assert a.root.value == 8 + assert a.root.parent is None + assert a.root.left.value == 5 + assert a.root.left.left.value == 2 + assert a.root.left.right.value == 7 + assert a.root.right.value == 10 + assert a.root.right.left is None + assert a.root.right.right.value == 12 + + +def test_bst_left_right_root_left_right_add_right(filled_bst_rot_lr_gc_7): + """Test left right rotation.""" + a = filled_bst_rot_lr_gc_7 + a.insert(9) + assert a.root.value == 8 + assert a.root.parent is None + assert a.root.left.value == 5 + assert a.root.left.left.value == 2 + assert a.root.left.right is None + assert a.root.right.value == 10 + assert a.root.right.left.value == 9 + assert a.root.right.right.value == 12 + + +def test_right_left_root_right_left_add_left(filled_bst_rot_rl): + """Test right left rotation.""" + a = filled_bst_rot_rl + a.insert(12) + assert a.root.value == 13 + assert a.root.parent is None + assert a.root.left.value == 10 + assert a.root.left.left.value == 5 + assert a.root.left.right.value == 12 + assert a.root.right.value == 15 + assert a.root.right.left is None + assert a.root.right.right.value == 20 + + +def test_right_left_root_right_left_add_right(filled_bst_rot_rl): + """Test right left rotation.""" + a = filled_bst_rot_rl + a.insert(14) + assert a.root.value == 13 + assert a.root.parent is None + assert a.root.left.value == 10 + assert a.root.left.left.value == 5 + assert a.root.left.right is None + assert a.root.right.value == 15 + assert a.root.right.left.value == 14 + assert a.root.right.right.value == 20 + + +def test_bst_double_rotation_one(): + """Test double rotation one.""" + a = BinarySearchTree([10, 5, 15, 13, 20]) + a.insert(12) + assert a.root.value == 13 + assert a.root.right.value == 15 + assert a.root.left.value == 10 + assert a.root.right.right.value == 20 + assert a.root.left.left.value == 5 + assert a.root.left.right.value == 12 + + +def test_bst_double_rotation_two(): + """Test double rotation one.""" + a = BinarySearchTree([15, 10, 20, 5, 13]) + a.insert(14) + assert a.root.value == 13 + assert a.root.right.value == 15 + assert a.root.left.value == 10 + assert a.root.right.right.value == 20 + assert a.root.right.left.value == 14 + assert a.root.left.left.value == 5 diff --git a/src/test_bst.py b/src/test_bst.py new file mode 100644 index 0000000..6a39301 --- /dev/null +++ b/src/test_bst.py @@ -0,0 +1,504 @@ +"""Test Module for Binary Search Tree.""" +from bst import BinarySearchTree +import pytest + +BST_SIMPLE = [8, 10, 3, 14, 13, 1, 6, 7, 4] +BST_STRAIGHT_LINE = [1, 2, 3, 4, 5, 6, 7] +BST_BIG = [ + 30, 50, 45, 60, 15, 25, 10, 47, 42, + 40, 27, 55, 44, 46, 49, 7, 12, 65, 70, 57, + 63, 52, 23, 4, 9, 11, 13, 21, 24, 26, 28 +] + + +@pytest.fixture +def big_bst(): + """Fixture to fill big bst.""" + from bst import BinarySearchTree + new_tree = BinarySearchTree(BST_BIG) + return new_tree + + +@pytest.fixture +def straight_bst(): + """Fixture to fill bst tree with a straight line of nodes down right side.""" + from bst import BinarySearchTree + new_tree = BinarySearchTree(BST_STRAIGHT_LINE) + return new_tree + + +@pytest.fixture +def filled_bst(): + """Fixture to fill the bst tree with nodes.""" + from bst import BinarySearchTree + new_tree = BinarySearchTree(BST_SIMPLE) + return new_tree + + +def test_insert_5_is_root(): + """Test the insert function.""" + a = BinarySearchTree() + a.insert(5) + assert a.root + + +def test_insert_5_where_root_equals_5(): + """Test the insert funciton.""" + a = BinarySearchTree() + a.insert(5) + assert a.root.value == 5 + + +def test_insert_5_and_10_and_confirm_right(): + """Test the insert function.""" + a = BinarySearchTree() + a.insert(5) + a.insert(10) + assert a.root.right.value == 10 + + +def test_insert_many_numbers(): + """Test the insert function.""" + a = BinarySearchTree() + a.insert(8) + a.insert(10) + a.insert(3) + a.insert(14) + a.insert(13) + a.insert(1) + a.insert(6) + a.insert(7) + a.insert(4) + assert a.root.right.right.left.value == 13 + assert a.root.left.value == 3 + assert a.root.right.right.value == 14 + assert a.root.value == 8 + assert a.root.left.right.left.value == 4 + + +def test_size_returns_size_of_binary_search_tree(): + """Test that the size method returns size of the bst.""" + a = BinarySearchTree() + a.insert(8) + a.insert(10) + a.insert(3) + a.insert(14) + a.insert(13) + a.insert(1) + a.insert(6) + a.insert(7) + a.insert(4) + assert a.size() == 9 + + +def test_binary_search_tree_contains_value(): + """Test that the contains method returns True if value in binary search tree.""" + a = BinarySearchTree() + a.insert(8) + a.insert(10) + a.insert(3) + a.insert(14) + a.insert(13) + a.insert(1) + a.insert(6) + a.insert(7) + a.insert(4) + assert a.contains(4) + + +def test_binary_search_tree_does_not_contain_value(): + """Test that the contains method returns True if value in binary search tree.""" + a = BinarySearchTree() + a.insert(8) + a.insert(10) + a.insert(3) + a.insert(14) + a.insert(13) + a.insert(1) + a.insert(6) + a.insert(7) + a.insert(4) + assert a.contains(100) is False + + +def test_search_5(): + """Test the search function.""" + a = BinarySearchTree() + a.insert(5) + assert a.search(5) == a.root + + +def test_search_10(): + """Test the search function.""" + a = BinarySearchTree() + a.insert(5) + a.insert(10) + assert a.search(10) == a.root.right + + +def test_search_empty(): + """Test the search function.""" + a = BinarySearchTree() + assert a.search(5) is None + + +def test_search_none(): + """Test the search function.""" + a = BinarySearchTree() + a.insert(8) + a.insert(10) + a.insert(3) + a.insert(14) + a.insert(13) + a.insert(1) + a.insert(6) + a.insert(7) + a.insert(4) + assert a.search(100) is None + + +def test_depth_zero(): + """Test the depth function.""" + a = BinarySearchTree() + assert a.depth() == 0 + + +def test_depth_one(): + """Test the depth function.""" + a = BinarySearchTree() + a.insert(8) + assert a.depth() == 1 + + +def test_depth_many(): + """Test the depth function.""" + a = BinarySearchTree() + a.insert(8) + a.insert(10) + a.insert(3) + a.insert(14) + a.insert(13) + a.insert(1) + a.insert(6) + a.insert(7) + a.insert(4) + assert a.depth() == 4 + + +def test_balance(): + """Test the balance function.""" + a = BinarySearchTree() + a.insert(8) + a.insert(10) + a.insert(3) + a.insert(14) + a.insert(13) + a.insert(1) + a.insert(6) + a.insert(7) + assert a.balance() == 0 + + +def test_balance_unbalanced_right(filled_bst): + """Test the balance function.""" + filled_bst.insert(11) + filled_bst.insert(12) + assert filled_bst.balance() == 2 + + +def test_balance_unbalanced_left(filled_bst): + """Test the balance function.""" + filled_bst.insert(5) + filled_bst.insert(4.5) + assert filled_bst.balance() == -2 + + +def test_in_order_traversal_first_node_traversed_is_1(filled_bst): + """In-order traversal will start with 1.""" + in_order_list = [] + for x in filled_bst._in_order_trav(): + in_order_list.append(x) + assert in_order_list[0] == 1 + + +def test_in_order_traversal_first_node_traversed_is_1_reg(filled_bst): + """In-order traversal's first value from generator will get a 1.""" + assert filled_bst.in_order() == 1 + assert filled_bst.in_order() == 3 + assert filled_bst.in_order() == 4 + assert filled_bst.in_order() == 6 + assert filled_bst.in_order() == 7 + assert filled_bst.in_order() == 8 + assert filled_bst.in_order() == 10 + assert filled_bst.in_order() == 13 + assert filled_bst.in_order() == 14 + + +def test_pre_order_traversal_first_node_traversed_is_8(filled_bst): + """Pre-order traversal will get 8.""" + assert filled_bst.pre_order() == 8 + assert filled_bst.pre_order() == 3 + assert filled_bst.pre_order() == 1 + assert filled_bst.pre_order() == 6 + assert filled_bst.pre_order() == 4 + assert filled_bst.pre_order() == 7 + assert filled_bst.pre_order() == 10 + assert filled_bst.pre_order() == 14 + assert filled_bst.pre_order() == 13 + + +def test_post_order_traversal(filled_bst): + """Post-order traversal.""" + assert filled_bst.post_order() == 1 + assert filled_bst.post_order() == 4 + assert filled_bst.post_order() == 7 + assert filled_bst.post_order() == 6 + assert filled_bst.post_order() == 3 + assert filled_bst.post_order() == 13 + assert filled_bst.post_order() == 14 + assert filled_bst.post_order() == 10 + assert filled_bst.post_order() == 8 + + +def test_delete_empty(): + """Test delete function with empty binary search tree.""" + a = BinarySearchTree() + assert a.delete(5) is None + + +def test_delete_filled_root(filled_bst): + """Test delete of root.""" + a = filled_bst + assert a.size() == 9 + a.delete(8) + assert a.size() == 8 + assert a.in_order() == 1 + assert a.in_order() == 3 + assert a.in_order() == 4 + assert a.in_order() == 6 + assert a.in_order() == 7 + assert a.in_order() == 10 + assert a.in_order() == 13 + assert a.in_order() == 14 + + +def test_delete_end(filled_bst): + """Test delete of root.""" + a = filled_bst + assert a.size() == 9 + a.delete(1) + assert a.size() == 8 + assert a.in_order() == 3 + assert a.in_order() == 4 + assert a.in_order() == 6 + assert a.in_order() == 7 + assert a.in_order() == 8 + assert a.in_order() == 10 + assert a.in_order() == 13 + assert a.in_order() == 14 + + +def test_delete_vertex_of_left_sub_head_in_bst(filled_bst): + """Test delete of a BST's left sub tree's head.""" + a = filled_bst + a.delete(3) + assert a.root.value == 8 + assert a.root.left.value == 4 + assert a.root.left.right.value == 6 + assert a.root.left.right.right.value == 7 + + +def test_delete_vertex_of_lower_vertex_with_2_children(filled_bst): + """Test lower vertex removal at bottom of tree with 2 children.""" + a = filled_bst + a.delete(6) + assert a.root.value == 8 + assert a.root.left.right.value == 7 + assert a.root.left.right.left.value == 4 + assert a.root.left.value == 3 + assert a.root.left.left.value == 1 + assert a.root.right.value == 10 + + +def test_delete(straight_bst): + """Test removal of node from straight line bst.""" + a = straight_bst + a.delete(4) + assert a.root.value == 1 + assert a.root.right.value == 2 + assert a.root.right.right.value == 3 + assert a.root.right.right.right.value == 5 + assert a.root.right.right.right.right.value == 6 + + +def test_node_deletion_from_big_tree_with_grand_children(big_bst): + """Test deletion of node in big bst tree with children and grand children.""" + a = big_bst + a.delete(45) + assert a.root.right.left.value == 46 + assert a.root.right.left.right.value == 47 + assert a.root.right.left.left.value == 42 + assert a.root.right.left.right.right.value == 49 + assert a.root.right.left.right.left is None + + +def test_deletion_from_big_tree_with_great_grand_children(big_bst): + """Test deletion of node in big bst tree with children and grand children.""" + a = big_bst + a.delete(50) + assert a.root.right.value == 52 + assert a.root.right.left.value == 45 + assert a.root.right.right.value == 60 + assert a.root.right.right.left.value == 55 + assert a.root.right.right.right.value == 65 + assert a.root.right.right.left.right.value == 57 + assert a.root.right.right.left.left is None + assert a.root.right.right.right.right.value == 70 + assert a.root.right.right.right.left.value == 63 + + +def test_node_deletion_from_big_tree_root(big_bst): + """Test root deletion on big_bst.""" + a = big_bst + a.delete(30) + assert a.root.value == 40 + assert a.root.right.value == 50 + assert a.root.right.left.value == 45 + assert a.root.right.left.left.value == 42 + assert a.root.right.left.left.left is None + assert a.root.right.left.left.right.value == 44 + assert a.root.right.right.value == 60 + assert a.root.right.right.left.value == 55 + assert a.root.right.right.right.value == 65 + assert a.root.right.right.left.right.value == 57 + assert a.root.right.right.left.left.value == 52 + assert a.root.right.right.right.right.value == 70 + assert a.root.right.right.right.left.value == 63 + + +def test_node_deletion_from_big_tree_furthest_left(big_bst): + """Test furthest left node deletion on big_bst.""" + a = big_bst + a.delete(4) + assert a.root.value == 30 + assert a.root.left.value == 15 + assert a.root.left.left.value == 10 + assert a.root.left.left.left.value == 7 + assert a.root.left.left.left.left is None + assert a.root.right.value == 50 + assert a.root.right.left.value == 45 + assert a.root.right.left.left.value == 42 + assert a.root.right.left.left.left.value == 40 + assert a.root.right.left.left.right.value == 44 + assert a.root.right.right.value == 60 + assert a.root.right.right.left.value == 55 + assert a.root.right.right.right.value == 65 + assert a.root.right.right.left.right.value == 57 + assert a.root.right.right.left.left.value == 52 + assert a.root.right.right.right.right.value == 70 + assert a.root.right.right.right.left.value == 63 + + +def test_big_bst(big_bst): + """Test nodes in proper places in big_bst.""" + a = big_bst + assert a.root.value == 30 + assert a.root.left.value == 15 + assert a.root.left.right.value == 25 + assert a.root.left.right.left.value == 23 + assert a.root.left.left.value == 10 + assert a.root.left.left.right.value == 12 + assert a.root.left.left.right.right.value == 13 + assert a.root.left.left.right.left.value == 11 + assert a.root.left.left.left.value == 7 + assert a.root.left.left.left.right.value == 9 + assert a.root.left.left.left.left.value == 4 + assert a.root.right.value == 50 + assert a.root.right.left.value == 45 + assert a.root.right.left.left.value == 42 + assert a.root.right.left.left.left.value == 40 + assert a.root.right.left.left.right.value == 44 + assert a.root.right.right.value == 60 + assert a.root.right.right.left.value == 55 + assert a.root.right.right.right.value == 65 + assert a.root.right.right.left.right.value == 57 + assert a.root.right.right.left.left.value == 52 + assert a.root.right.right.right.right.value == 70 + assert a.root.right.right.right.left.value == 63 + + +def test_big_bst_insert_delete_min_node_with_right_child(big_bst): + """Test deletion of node with min node with right child.""" + a = big_bst + a.insert(22) + a.delete(15) + assert a.root.value == 30 + assert a.root.left.value == 21 + assert a.root.left.right.value == 25 + assert a.root.left.right.left.value == 23 + assert a.root.left.right.left.left.value == 22 + assert a.root.left.left.value == 10 + assert a.root.left.left.right.value == 12 + assert a.root.left.left.right.right.value == 13 + assert a.root.left.left.right.left.value == 11 + assert a.root.left.left.left.value == 7 + assert a.root.left.left.left.right.value == 9 + assert a.root.left.left.left.left.value == 4 + assert a.root.right.value == 50 + assert a.root.right.left.value == 45 + assert a.root.right.left.left.value == 42 + assert a.root.right.left.left.left.value == 40 + assert a.root.right.left.left.right.value == 44 + assert a.root.right.right.value == 60 + assert a.root.right.right.left.value == 55 + assert a.root.right.right.right.value == 65 + assert a.root.right.right.left.right.value == 57 + assert a.root.right.right.left.left.value == 52 + assert a.root.right.right.right.right.value == 70 + assert a.root.right.right.right.left.value == 63 + + +def test_big_bst_insert_delete_root_min_node_with_right_child(big_bst): + """Test deletion of root node with min node with right child.""" + a = big_bst + a.insert(41) + a.delete(30) + a = big_bst + assert a.root.value == 40 + assert a.root.left.value == 15 + assert a.root.left.right.value == 25 + assert a.root.left.right.left.value == 23 + assert a.root.left.left.value == 10 + assert a.root.left.left.right.value == 12 + assert a.root.left.left.right.right.value == 13 + assert a.root.left.left.right.left.value == 11 + assert a.root.left.left.left.value == 7 + assert a.root.left.left.left.right.value == 9 + assert a.root.left.left.left.left.value == 4 + assert a.root.right.value == 50 + assert a.root.right.left.value == 45 + assert a.root.right.left.left.value == 42 + assert a.root.right.left.left.left.value == 41 + assert a.root.right.left.left.right.value == 44 + assert a.root.right.right.value == 60 + assert a.root.right.right.left.value == 55 + assert a.root.right.right.right.value == 65 + assert a.root.right.right.left.right.value == 57 + assert a.root.right.right.left.left.value == 52 + assert a.root.right.right.right.right.value == 70 + assert a.root.right.right.right.left.value == 63 + + +def test_multiple_delete_to_empty(filled_bst): + """Test that all nodes can be deleted and size output reflects this.""" + a = filled_bst + a.delete(8) + a.delete(10) + a.delete(3) + a.delete(14) + a.delete(13) + a.delete(1) + a.delete(6) + a.delete(7) + a.delete(4) + assert a.size() == 0 diff --git a/src/test_decision_tree.py b/src/test_decision_tree.py new file mode 100644 index 0000000..ccd1e2e --- /dev/null +++ b/src/test_decision_tree.py @@ -0,0 +1,8 @@ +"""Module for testing decsion tree.""" + +from decision_tree import DTC +import pytest + +TRAIN_DTC_DATA = [[1.4, 0.2, "setosa"], [1.4, 0.2, "setosa"], [1.3, 0.2, "setosa"], [1.5, 0.2, "setosa"], [1.4, 0.2, "setosa"], [1.7, 0.4, "setosa"], [1.4, 0.3, "setosa"], [1.5, 0.2, "setosa"], [1.4, 0.2, "setosa"], [1.5, 0.1, "setosa"], [1.5, 0.2, "setosa"], [1.6, 0.2, "setosa"], [1.4, 0.1, "setosa"], [1.1, 0.1, "setosa"], [1.2, 0.2, "setosa"], [1.5, 0.4, "setosa"], [1.3, 0.4, "setosa"], [1.4, 0.3, "setosa"], [1.7, 0.3, "setosa"], [1.5, 0.3, "setosa"], [1.7, 0.2, "setosa"], [1.5, 0.4, "setosa"], [1, 0.2, "setosa"], [1.7, 0.5, "setosa"], [1.9, 0.2, "setosa"], [1.6, 0.2, "setosa"], [1.6, 0.4, "setosa"], [1.5, 0.2, "setosa"], [1.4, 0.2, "setosa"], [1.6, 0.2, "setosa"], [1.6, 0.2, "setosa"], [1.5, 0.4, "setosa"], [1.5, 0.1, "setosa"], [1.4, 0.2, "setosa"], [1.5, 0.1, "setosa"], [1.2, 0.2, "setosa"], [1.3, 0.2, "setosa"], [1.5, 0.1, "setosa"], [1.3, 0.2, "setosa"], [1.5, 0.2, "setosa"], [3.5, 1, "versicolor"], [4.2, 1.5, "versicolor"], [4, 1, "versicolor"], [4.7, 1.4, "versicolor"], [3.6, 1.3, "versicolor"], [4.4, 1.4, "versicolor"], [4.5, 1.5, "versicolor"], [4.1, 1, "versicolor"], [4.5, 1.5, "versicolor"], [3.9, 1.1, "versicolor"], [4.8, 1.8, "versicolor"], [4, 1.3, "versicolor"], [4.9, 1.5, "versicolor"], [4.7, 1.2, "versicolor"], [4.3, 1.3, "versicolor"], [4.4, 1.4, "versicolor"], [4.8, 1.4, "versicolor"], [5, 1.7, "versicolor"], [4.5, 1.5, "versicolor"], [3.5, 1, "versicolor"], [3.8, 1.1, "versicolor"], [3.7, 1, "versicolor"], [3.9, 1.2, "versicolor"], [5.1, 1.6, "versicolor"], [4.5, 1.5, "versicolor"], [4.5, 1.6, "versicolor"], [4.7, 1.5, "versicolor"], [4.4, 1.3, "versicolor"], [4.1, 1.3, "versicolor"], [4, 1.3, "versicolor"], [4.4, 1.2, "versicolor"], [4.6, 1.4, "versicolor"], [4, 1.2, "versicolor"], [3.3, 1, "versicolor"], [4.2, 1.3, "versicolor"], [4.2, 1.2, "versicolor"], [4.2, 1.3, "versicolor"], [4.3, 1.3, "versicolor"], [3, 1.1, "versicolor"], [4.1, 1.3, "versicolor"]] + +TEST_DTC_DATA = [[[1.3, 0.3], ["setosa"]], [[1.3, 0.3], ["setosa"]], [[1.3, 0.2], ["setosa"]], [[1.6, 0.6], ["setosa"]], [[1.9, 0.4], ["setosa"]], [[1.4, 0.3], ["setosa"]], [[1.6, 0.2], ["setosa"]], [[1.4, 0.2], ["setosa"]], [[1.5, 0.2], ["setosa"]], [[1.4, 0.2], ["setosa"]], [[4.7, 1.4], ["versicolor"]], [[4.5, 1.5], ["versicolor"]], [[4.9, 1.5], ["versicolor"]], [[4, 1.3], ["versicolor"]], [[4.6, 1.5], ["versicolor"]], [[4.5, 1.3], ["versicolor"]], [[4.7, 1.6], ["versicolor"]], [[3.3, 1], ["versicolor"]], [[4.6, 1.3], ["versicolor"]], [[3.9, 1.4], ["versicolor"]]] diff --git a/src/test_hash_table.py b/src/test_hash_table.py new file mode 100644 index 0000000..d46e8ec --- /dev/null +++ b/src/test_hash_table.py @@ -0,0 +1,56 @@ +"""Test module for Hash Table.""" + +from hash_table import HashTable +import pytest + + +@pytest.fixture +def colin_ben_filled_hash_table(): + """Fixture for a filled hash table using the words from a dictionary text file hashed with the Colin-Ben hashing algorithm.""" + test_table = HashTable('colin-ben') + for line in open('/usr/share/dict/words'): + test_table.set(line, line) + return test_table + + +@pytest.fixture +def additive_filled_hash_table(): + """Fixture for a filled hash table using the words from a dictionary text file hashed with the additive technique.""" + test_table = HashTable('additive') + for line in open('/usr/share/dict/words'): + test_table.set(line, line) + return test_table + + +@pytest.fixture +def colin_ben_filled_hash_table_tiny(): + """Fixture for a filled hash table using the words from a dictionary text file hashed with the Colin-Ben hashing algorithm.""" + test_table = HashTable('colin-ben') + test_table.set('tornado', 25) + test_table.set('kangaroo', 50) + return test_table + + +def test_table_correct(colin_ben_filled_hash_table): + """Testing that get works correctly.""" + count = 0 + for line in open('/usr/share/dict/words'): + count += 1 + if colin_ben_filled_hash_table.get(line) != line: + assert False + assert True + + +def test_table_correct2(additive_filled_hash_table): + """Testing that get works correctly.""" + # import pdb; pdb.set_trace() + for line in open('/usr/share/dict/words'): + if additive_filled_hash_table.get(line) != line: + assert False + assert True + + +def test_table_correct_tiny(colin_ben_filled_hash_table_tiny): + """Testing that get works correctly.""" + assert colin_ben_filled_hash_table_tiny.get('kangaroo') == 50 + assert colin_ben_filled_hash_table_tiny.get('koala') == 'Key not in hash table.' diff --git a/src/test_insertion_sort.py b/src/test_insertion_sort.py new file mode 100644 index 0000000..376ba23 --- /dev/null +++ b/src/test_insertion_sort.py @@ -0,0 +1,108 @@ +"""Test insertion sort.""" + +from insertion_sort import insertion_sort +import pytest +import random + + +@pytest.fixture +def rand_list1(): + """A random sized list of random integers.""" + a = random + b = random + c = a.randint(0, 300) + d = b.sample(range(0, 300), c) + return d + + +@pytest.fixture +def rand_list2(): + """A random sized list of random integers.""" + a = random + b = random + c = a.randint(0, 300) + d = b.sample(range(0, 300), c) + return d + + +PARAMS_LIST_NO_REPEATS_NO_DECIMALS = [ + ([1, 2, 3, 4, 5, 6, 7, 8, 9], [1, 2, 3, 4, 5, 6, 7, 8, 9]), + ([1, 2, 3, 4, 5, 6, 7, 8, 9, 0], [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]), + ([1], [1]), + ([2, 1], [1, 2]), + ([-1, 8], [-1, 8]), +] + +PARAMS_LIST_REPEATS_NO_DECIMALS = [ + ([1, 1, 1, 1, 1, 1, 1, 0], [0, 1, 1, 1, 1, 1, 1, 1]), + ([0, -0], [0, 0]), + ([55, 44, 44, 55], [44, 44, 55, 55]), + ([55, 44, 55, 44], [44, 44, 55, 55]), + ([44, 55, 77, 66, 66], [44, 55, 66, 66, 77]), + ([55, 55, -44, 66, 77], [-44, 55, 55, 66, 77]), +] + +PARAMS_LIST_DECIMALS_NO_REPEATS = [ + ([1.0, 1, 3.5], [1, 1.0, 3.5]), + ([3.14, 2.67], [2.67, 3.14]), + ([2.67, 3.14], [2.67, 3.14]), + ([345543.234534522646654356345643563, 34.53453452456266], [34.53453452456266, 345543.234534522646654356345643563]), + ([0.2452345234, .11111111111], [.11111111111, 0.2452345234]), +] + +PARAMS_LIST_DECIMALS_AND_REPEATS = [ + ([1.5, 1, 1, 1.5, 1, 1.5, 1, 0], [0, 1, 1, 1, 1, 1.5, 1.5, 1.5]), + ([-20, 0, -22.2], [-22.2, -20, 0]), + ([-3, -3.555556, -3.555555], [-3.555556, -3.555555, -3]), + ([.0980248972834], [.0980248972834]), + ([978120346.19238471934, -9782947.98797052], [-9782947.98797052, 978120346.19238471934]), + ([1.1111111111111, .555555555555, -.43434343434343, -.343434343434343434343434], [-.43434343434343, -.343434343434343434343434, .555555555555, 1.1111111111111]), +] + +WORD_PARAMS_LIST = [ + (["the", "brown", "cow", "doth", "protest", "too", "much"], ["brown", "cow", "doth", "much", "protest", "the", "too"]), + (["sometimes", "Brown", "altoids", "look", "so", "very", "strange", "in", "town", "now"], ["Brown", "altoids", "in", "look", "now", "so", "sometimes", "strange", "town", "very"]), + (["Big", "SMALL", "BOY", "almost", "ZOO", "SiNg"], ["BOY", "Big", "SMALL", "SiNg", "ZOO", "almost"]) +] + + +def test_rand_list1_sorted(rand_list1): + """Test if the random list is sorted.""" + new_list = rand_list1[:] + assert insertion_sort(rand_list1) == sorted(new_list) + + +def test_rand_list2_sorted(rand_list2): + """Test if the random list is sorted.""" + new_list = rand_list2[:] + assert insertion_sort(rand_list2) == sorted(new_list) + + +@pytest.mark.parametrize('n, result', PARAMS_LIST_NO_REPEATS_NO_DECIMALS) +def test_list_no_repeats_no_decimals(n, result): + """Test input lists with no repeats and no decimals.""" + assert insertion_sort(n) == result + + +@pytest.mark.parametrize('n, result', PARAMS_LIST_REPEATS_NO_DECIMALS) +def test_list_repeats_no_decimals(n, result): + """Test input lists with repeats and no decimals.""" + assert insertion_sort(n) == result + + +@pytest.mark.parametrize('n, result', PARAMS_LIST_DECIMALS_NO_REPEATS) +def test_list_decimals_no_repeats(n, result): + """Test input lists with decimals and no repeats.""" + assert insertion_sort(n) == result + + +@pytest.mark.parametrize('n, result', PARAMS_LIST_DECIMALS_AND_REPEATS) +def test_list_decimals_and_repeats(n, result): + """Test input lists with decimals and no repeats.""" + assert insertion_sort(n) == result + + +@pytest.mark.parametrize('n, result', WORD_PARAMS_LIST) +def test_word_list_with_sorted(n, result): + """Test list of words with the insertion sort.""" + assert insertion_sort(n) == result diff --git a/src/test_merge_sort.py b/src/test_merge_sort.py new file mode 100644 index 0000000..32423e1 --- /dev/null +++ b/src/test_merge_sort.py @@ -0,0 +1,126 @@ +"""Test merge sort.""" + +from merge_sort import merge_sort +import pytest +import random +import string + + +@pytest.fixture +def rand_list1(): + """A random sized list of random integers.""" + a = random + b = random + c = a.randint(0, 300) + d = b.sample(range(0, 300), c) + return d + + +@pytest.fixture +def rand_list2(): + """A random sized list of random integers.""" + a = random + b = random + c = a.randint(0, 300) + d = b.sample(range(0, 300), c) + return d + + +@pytest.fixture +def rand_letter1(): + """Choose a sequence of random letters.""" + return [random.choice(string.ascii_letters) for x in range(10)] + + +PARAMS_LIST_NO_REPEATS_NO_DECIMALS = [ + ([1, 2, 3, 4, 5, 6, 7, 8, 9], [1, 2, 3, 4, 5, 6, 7, 8, 9]), + ([1, 2, 3, 4, 5, 6, 7, 8, 9, 0], [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]), + ([1], [1]), + ([2, 1], [1, 2]), + ([-1, 8], [-1, 8]), +] + +PARAMS_LIST_REPEATS_NO_DECIMALS = [ + ([1, 1, 1, 1, 1, 1, 1, 0], [0, 1, 1, 1, 1, 1, 1, 1]), + ([0, -0], [0, 0]), + ([55, 44, 44, 55], [44, 44, 55, 55]), + ([55, 44, 55, 44], [44, 44, 55, 55]), + ([44, 55, 77, 66, 66], [44, 55, 66, 66, 77]), + ([55, 55, -44, 66, 77], [-44, 55, 55, 66, 77]), +] + +PARAMS_LIST_DECIMALS_NO_REPEATS = [ + ([1.0, 1, 3.5], [1, 1.0, 3.5]), + ([3.14, 2.67], [2.67, 3.14]), + ([2.67, 3.14], [2.67, 3.14]), + ([345543.234534522646654356345643563, 34.53453452456266], [34.53453452456266, 345543.234534522646654356345643563]), + ([0.2452345234, .11111111111], [.11111111111, 0.2452345234]), +] + +PARAMS_LIST_DECIMALS_AND_REPEATS = [ + ([1.5, 1, 1, 1.5, 1, 1.5, 1, 0], [0, 1, 1, 1, 1, 1.5, 1.5, 1.5]), + ([-20, 0, -22.2], [-22.2, -20, 0]), + ([-3, -3.555556, -3.555555], [-3.555556, -3.555555, -3]), + ([.0980248972834], [.0980248972834]), + ([978120346.19238471934, -9782947.98797052], [-9782947.98797052, 978120346.19238471934]), + ([1.1111111111111, .555555555555, -.43434343434343, -.343434343434343434343434], [-.43434343434343, -.343434343434343434343434, .555555555555, 1.1111111111111]), +] + +WORD_PARAMS_LIST = [ + (["the", "brown", "cow", "doth", "protest", "too", "much"], ["brown", "cow", "doth", "much", "protest", "the", "too"]), + (["sometimes", "Brown", "altoids", "look", "so", "very", "strange", "in", "town", "now"], ["Brown", "altoids", "in", "look", "now", "so", "sometimes", "strange", "town", "very"]), + (["Big", "SMALL", "BOY", "almost", "ZOO", "SiNg"], ["BOY", "Big", "SMALL", "SiNg", "ZOO", "almost"]) +] + +LETTER_LIST = [ + (["c", "v", "n", "t", "f", "b", "l", "q", "e", "p"], ["b", "c", "e", "f", "l", "n", "p", "q", "t", "v"]), + (["Z", "x", "R", "A", "m", "G", "j", "W", "r", "H"], ["A", "G", "H", "R", "W", "Z", "j", "m", "r", "x"]), +] + + +def test_rand_list1_sorted(rand_list1): + """Test if the random list is sorted.""" + new_list = rand_list1[:] + assert merge_sort(rand_list1) == sorted(new_list) + + +def test_rand_list2_sorted(rand_list2): + """Test if the random list is sorted.""" + new_list = rand_list2[:] + assert merge_sort(rand_list2) == sorted(new_list) + + +@pytest.mark.parametrize('n, result', PARAMS_LIST_NO_REPEATS_NO_DECIMALS) +def test_list_no_repeats_no_decimals(n, result): + """Test input lists with no repeats and no decimals.""" + assert merge_sort(n) == result + + +@pytest.mark.parametrize('n, result', PARAMS_LIST_REPEATS_NO_DECIMALS) +def test_list_repeats_no_decimals(n, result): + """Test input lists with repeats and no decimals.""" + assert merge_sort(n) == result + + +@pytest.mark.parametrize('n, result', PARAMS_LIST_DECIMALS_NO_REPEATS) +def test_list_decimals_no_repeats(n, result): + """Test input lists with decimals and no repeats.""" + assert merge_sort(n) == result + + +@pytest.mark.parametrize('n, result', PARAMS_LIST_DECIMALS_AND_REPEATS) +def test_list_decimals_and_repeats(n, result): + """Test input lists with decimals and no repeats.""" + assert merge_sort(n) == result + + +@pytest.mark.parametrize('n, result', WORD_PARAMS_LIST) +def test_word_list_with_sorted(n, result): + """Test list of words with the insertion sort.""" + assert merge_sort(n) == result + + +def test_rand_letter_sorted(rand_letter1): + """Test if the random letter list is sorted.""" + new_list = rand_letter1 + assert merge_sort(new_list) == sorted(new_list) diff --git a/src/test_quick_sort.py b/src/test_quick_sort.py new file mode 100644 index 0000000..a03eb00 --- /dev/null +++ b/src/test_quick_sort.py @@ -0,0 +1,126 @@ +"""Test quick sort.""" + +from quick_sort import quick_sort +import pytest +import random +import string + + +@pytest.fixture +def rand_list1(): + """A random sized list of random integers.""" + a = random + b = random + c = a.randint(0, 300) + d = b.sample(range(0, 300), c) + return d + + +@pytest.fixture +def rand_list2(): + """A random sized list of random integers.""" + a = random + b = random + c = a.randint(0, 300) + d = b.sample(range(0, 300), c) + return d + + +@pytest.fixture +def rand_letter1(): + """Choose a sequence of random letters.""" + return [random.choice(string.ascii_letters) for x in range(10)] + + +PARAMS_LIST_NO_REPEATS_NO_DECIMALS = [ + ([1, 2, 3, 4, 5, 6, 7, 8, 9], [1, 2, 3, 4, 5, 6, 7, 8, 9]), + ([1, 2, 3, 4, 5, 6, 7, 8, 9, 0], [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]), + ([1], [1]), + ([2, 1], [1, 2]), + ([-1, 8], [-1, 8]), +] + +PARAMS_LIST_REPEATS_NO_DECIMALS = [ + ([1, 1, 1, 1, 1, 1, 1, 0], [0, 1, 1, 1, 1, 1, 1, 1]), + ([0, -0], [0, 0]), + ([55, 44, 44, 55], [44, 44, 55, 55]), + ([55, 44, 55, 44], [44, 44, 55, 55]), + ([44, 55, 77, 66, 66], [44, 55, 66, 66, 77]), + ([55, 55, -44, 66, 77], [-44, 55, 55, 66, 77]), +] + +PARAMS_LIST_DECIMALS_NO_REPEATS = [ + ([1.0, 1, 3.5], [1, 1.0, 3.5]), + ([3.14, 2.67], [2.67, 3.14]), + ([2.67, 3.14], [2.67, 3.14]), + ([345543.234534522646654356345643563, 34.53453452456266], [34.53453452456266, 345543.234534522646654356345643563]), + ([0.2452345234, .11111111111], [.11111111111, 0.2452345234]), +] + +PARAMS_LIST_DECIMALS_AND_REPEATS = [ + ([1.5, 1, 1, 1.5, 1, 1.5, 1, 0], [0, 1, 1, 1, 1, 1.5, 1.5, 1.5]), + ([-20, 0, -22.2], [-22.2, -20, 0]), + ([-3, -3.555556, -3.555555], [-3.555556, -3.555555, -3]), + ([.0980248972834], [.0980248972834]), + ([978120346.19238471934, -9782947.98797052], [-9782947.98797052, 978120346.19238471934]), + ([1.1111111111111, .555555555555, -.43434343434343, -.343434343434343434343434], [-.43434343434343, -.343434343434343434343434, .555555555555, 1.1111111111111]), +] + +WORD_PARAMS_LIST = [ + (["the", "brown", "cow", "doth", "protest", "too", "much"], ["brown", "cow", "doth", "much", "protest", "the", "too"]), + (["sometimes", "Brown", "altoids", "look", "so", "very", "strange", "in", "town", "now"], ["Brown", "altoids", "in", "look", "now", "so", "sometimes", "strange", "town", "very"]), + (["Big", "SMALL", "BOY", "almost", "ZOO", "SiNg"], ["BOY", "Big", "SMALL", "SiNg", "ZOO", "almost"]) +] + +LETTER_LIST = [ + (["c", "v", "n", "t", "f", "b", "l", "q", "e", "p"], ["b", "c", "e", "f", "l", "n", "p", "q", "t", "v"]), + (["Z", "x", "R", "A", "m", "G", "j", "W", "r", "H"], ["A", "G", "H", "R", "W", "Z", "j", "m", "r", "x"]), +] + + +def test_rand_list1_sorted(rand_list1): + """Test if the random list is sorted.""" + new_list = rand_list1[:] + assert quick_sort(rand_list1) == sorted(new_list) + + +def test_rand_list2_sorted(rand_list2): + """Test if the random list is sorted.""" + new_list = rand_list2[:] + assert quick_sort(rand_list2) == sorted(new_list) + + +@pytest.mark.parametrize('n, result', PARAMS_LIST_NO_REPEATS_NO_DECIMALS) +def test_list_no_repeats_no_decimals(n, result): + """Test input lists with no repeats and no decimals.""" + assert quick_sort(n) == result + + +@pytest.mark.parametrize('n, result', PARAMS_LIST_REPEATS_NO_DECIMALS) +def test_list_repeats_no_decimals(n, result): + """Test input lists with repeats and no decimals.""" + assert quick_sort(n) == result + + +@pytest.mark.parametrize('n, result', PARAMS_LIST_DECIMALS_NO_REPEATS) +def test_list_decimals_no_repeats(n, result): + """Test input lists with decimals and no repeats.""" + assert quick_sort(n) == result + + +@pytest.mark.parametrize('n, result', PARAMS_LIST_DECIMALS_AND_REPEATS) +def test_list_decimals_and_repeats(n, result): + """Test input lists with decimals and no repeats.""" + assert quick_sort(n) == result + + +@pytest.mark.parametrize('n, result', WORD_PARAMS_LIST) +def test_word_list_with_sorted(n, result): + """Test list of words with the insertion sort.""" + assert quick_sort(n) == result + + +def test_rand_letter_sorted(rand_letter1): + """Test if the random letter list is sorted.""" + new_list = rand_letter1 + assert quick_sort(new_list) == sorted(new_list) diff --git a/src/test_radix.py b/src/test_radix.py new file mode 100644 index 0000000..2d172b5 --- /dev/null +++ b/src/test_radix.py @@ -0,0 +1,64 @@ +"""Test radix sort.""" + +from radix import radix_sort +import pytest +import random + + +@pytest.fixture +def rand_list1(): + """A random sized list of random integers.""" + a = random + b = random + c = a.randint(0, 300) + d = b.sample(range(0, 300), c) + return d + + +@pytest.fixture +def rand_list2(): + """A random sized list of random integers.""" + a = random + b = random + c = a.randint(0, 300) + d = b.sample(range(0, 300), c) + return d + + +PARAMS_LIST_NO_REPEATS_NO_DECIMALS = [ + ([1, 2, 3, 4, 5, 6, 7, 8, 9], [1, 2, 3, 4, 5, 6, 7, 8, 9]), + ([1, 2, 3, 4, 5, 6, 7, 8, 9, 0], [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]), + ([1], [1]), + ([2, 1], [1, 2]), +] + +PARAMS_LIST_REPEATS_NO_DECIMALS = [ + ([1, 1, 1, 1, 1, 1, 1, 0], [0, 1, 1, 1, 1, 1, 1, 1]), + ([55, 44, 44, 55], [44, 44, 55, 55]), + ([55, 44, 55, 44], [44, 44, 55, 55]), + ([44, 55, 77, 66, 66], [44, 55, 66, 66, 77]), +] + + +def test_rand_list1_sorted(rand_list1): + """Test if the random list is sorted.""" + new_list = rand_list1[:] + assert radix_sort(rand_list1) == sorted(new_list) + + +def test_rand_list2_sorted(rand_list2): + """Test if the random list is sorted.""" + new_list = rand_list2[:] + assert radix_sort(rand_list2) == sorted(new_list) + + +@pytest.mark.parametrize('n, result', PARAMS_LIST_NO_REPEATS_NO_DECIMALS) +def test_list_no_repeats_no_decimals(n, result): + """Test input lists with no repeats and no decimals.""" + assert radix_sort(n) == result + + +@pytest.mark.parametrize('n, result', PARAMS_LIST_REPEATS_NO_DECIMALS) +def test_list_repeats_no_decimals(n, result): + """Test input lists with repeats and no decimals.""" + assert radix_sort(n) == result diff --git a/src/test_trie.py b/src/test_trie.py new file mode 100644 index 0000000..59a9534 --- /dev/null +++ b/src/test_trie.py @@ -0,0 +1,91 @@ +"""Test module for Trie Tree.""" + +from trie import Trie +import pytest + + +SIMPLE_INPUT = ['abort', 'tony', 'borg', 'russia'] +MODERATE_INPUT = ['fast', 'faster', 'fastest', 'fastener', 'breakfasttime'] +COMPLEX_INPUT = ['aaaaaa', 'aardvark', 'aaraal', 'aapppp', 'aapear', 'tornado', 'ado', 'tor', 'to', 'o', 'oo', 'oooo', 'elephant', 'elevate', 'elephants'] + +PARAMS_CONTAINS_SIMPLE = [ + ('abort', True), + ('tony', True), + ('ab', False), + ('z', False), + ('a', False), + ('t', False), +] + + +@pytest.fixture +def simple_trie(): + """Trie with simple input.""" + a = Trie() + for each in SIMPLE_INPUT: + a.insert(each) + return a + + +@pytest.fixture +def moderate_trie(): + """Trie with moderate input.""" + a = Trie() + for each in MODERATE_INPUT: + a.insert(each) + return a + + +@pytest.fixture +def complex_trie(): + """Trie with complex input.""" + a = Trie() + for each in COMPLEX_INPUT: + a.insert(each) + return a + + +@pytest.mark.parametrize('n, result', PARAMS_CONTAINS_SIMPLE) +def test_contains_simple(simple_trie, n, result): + """Test contains function.""" + assert simple_trie.contains(n) == result + + +def test_size_of_an_empty_trie(): + """Test for the size of an empty trie.""" + a = Trie() + assert a.size() == 0 + + +def test_size_of_a_filled_trie(): + """Test for the size of a filled trie.""" + a = Trie() + for each in MODERATE_INPUT: + a.insert(each) + assert a.size() == 5 + + +def test_removal_from_a_filled_trie(): + """Test the removal of a node from of a filled trie.""" + a = Trie() + for each in MODERATE_INPUT: + a.insert(each) + a.remove("fast") + assert a.contains("fast") is False + + +def test_removeal_of_an_empty_trie(): + """Test the removal of a node from an empty trie.""" + a = Trie() + with pytest.raises(IndexError): + a.remove("fast") + + +def test_removal_of_substring_word_of_another_word_in_trie(): + """Test the removal of 'o', where 'oo' and 'oooo' exist too in the trie.""" + a = Trie() + for each in COMPLEX_INPUT: + a.insert(each) + a.remove('o') + assert a.size() == 14 + assert a.contains('o') is False diff --git a/src/test_trie_trav.py b/src/test_trie_trav.py new file mode 100644 index 0000000..d23f686 --- /dev/null +++ b/src/test_trie_trav.py @@ -0,0 +1,76 @@ +from trie_trav import Trie +import pytest + +SIMPLE_INPUT = ['abort', 'tony', 'borg', 'russia'] +MODERATE_INPUT = ['fast', 'faster', 'fastest', 'fastener', 'breakfasttime'] +COMPLEX_INPUT = ['aaaaaa', 'aardvark', 'aaraal', 'aapppp', 'aapear', 'tornado', 'ado', 'tor', 'to', 'o', 'oo', 'oooo', 'elephant', 'elevate', 'elephants'] + +PARAMS_SIMPLE = [ + ('rus', ['s', 'i', 'a']), + ('o', ['r', 't', 'n', 'y', 'r', 'g']), + ('a', ['b', 'o', 'r', 't']), + ('r', ['t', 'g']) +] + +PARAMS_MODERATE = [ + ('fast', ['e', 'r', 's', 't', 'n', 'e', 'r', 't', 'i', 'm', 'e']), + ('f', ['a', 's', 't', 'e', 'r', 's', 't', 'n', 'e', 'r', 'a', 's', 't', 't', 'i', 'm', 'e']), + ('a', ['s', 't', 'e', 'r', 's', 't', 'n', 'e', 'r', 'k', 'f', 'a', 's', 't', 'i', 'm', 'e']), + ('faste', ['r', 's', 't', 'n', 'e', 'r']), + ('t', ['r', 'r', 's', 't', 'n', 'e', 'r', 't', 'i', 'm', 'e']), + ('faster', []), +] + +PARAMS_COMPLEX = [ + ('a', ['d', 'o', 'a', 'a', 'a', 'a', 'a', 'r', 'd', 'v', 'a', 'r', 'k', 'a', 'a', 'l', 'p', 'p', 'p', 'p', 'e', 'a', 'r', 'd', 'o', 'n', 't', 's', 't', 'e']), + ('', []), + ('', []), +] + + +@pytest.fixture +def simple_trie(): + """Trie with simple input.""" + a = Trie() + for each in SIMPLE_INPUT: + a.insert(each) + return a + + +@pytest.fixture +def moderate_trie(): + """Trie with moderate input.""" + a = Trie() + for each in MODERATE_INPUT: + a.insert(each) + return a + + +@pytest.fixture +def complex_trie(): + """Trie with complex input.""" + a = Trie() + for each in COMPLEX_INPUT: + a.insert(each) + return a + + +@pytest.mark.parametrize('n, result', PARAMS_SIMPLE) +def test_traversal_simple(simple_trie, n, result): + """Test traversal with input 'o'.""" + g = simple_trie.traversal(n) + assert list(g) == result + + +@pytest.mark.parametrize('n, result', PARAMS_MODERATE) +def test_traversal_moderate(simple_trie, n, result): + """Test traversal with input 'o'.""" + g = simple_trie.traversal(n) + assert list(g) == result + + +@pytest.mark.parametrize('n, result', PARAMS_COMPLEX) +def test_traversal_complex(simple_trie, n, result): + """Test traversal with input 'o'.""" + g = simple_trie.traversal(n) + assert list(g) == result diff --git a/src/trie.py b/src/trie.py new file mode 100644 index 0000000..904aba1 --- /dev/null +++ b/src/trie.py @@ -0,0 +1,105 @@ +"""Module for Trie tree.""" + +# TRIE TREE (TT) +# +# CodeFellows 401d5 +# Submission Date: +# +# Authors: Colin Lamont +# Ben Shields +# +# URL: + + +class Node(object): + """Node Class contains a value and a dictionary of nodes.""" + + def __init__(self, val=None): + """Initialize the Node class with val and empty nodes dictionary.""" + self.val = val + self.nodes = {} + + +class Trie(object): + """Trie class, which is the Trie tree.""" + + """ + insert(self, string): will insert the input string into the trie. If character in the input string is already present, it will be ignored. + contains(self, string): will return True if the string is in the trie, False if not. + size(self): will return the total number of words contained within the trie. 0 if empty. + remove(self, string): will remove the given string from the trie. If the word does not exist, will raise an appropriate exception + """ + + def __init__(self): + """Initialize the Trie class with root Node with ('*') and size of 0.""" + self.root = Node('*') + self._size = 0 + + def insert(self, word): + """Insert method, which takes a word and inserts each letter of the word into the Trie, with pointer to next Node or $ if end.""" + node = self.root + new_node = None + new_word = False + counter = 0 + for each in word: + if each in node.nodes: + if counter == (len(word) - 1) and '$' not in node.nodes: + new_word = True + new_node = Node(each) + node.nodes[each] = new_node + node = new_node + break + counter += 1 + node = node.nodes[each] + continue + counter += 1 + new_word = True + new_node = Node(each) + node.nodes[each] = new_node + node = new_node + if new_word: + self._size += 1 + node.nodes['$'] = None + + def contains(self, word): + """The contains method returns True if the word is found in the Trie tree, or False if not.""" + node = self.root + for each in word: + if each in node.nodes: + node = node.nodes[each] + else: + return False + if '$' in node.nodes: + return True + return False + + def size(self): + """The size method returns the number of words in the Trie.""" + return self._size + + def remove(self, word): + """The remove method removes the word from the Trie.""" + node_list = [] + node = self.root + for each in word: + if each in node.nodes: + node_list.append(node.nodes[each]) + node = node.nodes[each] + else: + raise(IndexError) + last = node_list.pop() + if '$' not in last.nodes: + return + del last.nodes['$'] + for i in range(len(node_list)): + last_val = last.val + last = node_list.pop() + if '$' in last.nodes: + self._size -= 1 + break + if len(last.nodes) > 1: + del last.nodes[last_val] + self._size -= 1 + break + del last.nodes[last_val] + self._size -= 1 diff --git a/src/trie_trav.py b/src/trie_trav.py new file mode 100644 index 0000000..c1c5eaf --- /dev/null +++ b/src/trie_trav.py @@ -0,0 +1,128 @@ +"""Module for Trie tree with traversal.""" + +# TRIE TREE TRAVERSAL (TTT) +# +# CodeFellows 401d5 +# Submission Date: +# +# Authors: Colin Lamont +# Ben Shields +# +# URL: + +from collections import OrderedDict + + +class Node(object): + """Node Class contains a value and a dictionary of nodes.""" + + def __init__(self, val=None): + """Initialize the Node class with val and empty nodes dictionary.""" + self.val = val + self.nodes = OrderedDict() + + +class Trie(object): + """Trie class, which is the Trie tree.""" + + """ + insert(self, string): will insert the input string into the trie. If character in the input string is already present, it will be ignored. + contains(self, string): will return True if the string is in the trie, False if not. + size(self): will return the total number of words contained within the trie. 0 if empty. + remove(self, string): will remove the given string from the trie. If the word doesn’t exist, will raise an appropriate exception. + """ + + def __init__(self): + """Initialize the Trie class with root Node with ('*') and size of 0.""" + self.root = Node('*') + self._size = 0 + + def insert(self, word): + """Insert method, which takes a word and inserts each letter of the word into the Trie, with pointer to next Node or $ if end.""" + node = self.root + new_node = None + new_word = False + for each in word: + if each in node.nodes: + node = node.nodes[each] + continue + new_word = True + new_node = Node(each) + node.nodes[each] = new_node + node = new_node + if new_word: + self._size += 1 + node.nodes['$'] = None + + def contains(self, word): + """The contains method returns True if the word is found in the Trie tree, or False if not.""" + node = self.root + for each in word: + if each in node.nodes: + node = node.nodes[each] + else: + return False + if '$' in node.nodes: + return True + return False + + def size(self): + """The size method returns the number of words in the Trie.""" + return self._size + + def remove(self, word): + """The remove method removes the word from the Trie.""" + node_list = [] + node = self.root + for each in word: + if each in node.nodes: + node_list.append(node.nodes[each]) + node = node.nodes[each] + last = node_list.pop() + if '$' not in last.nodes: + return + del last.nodes['$'] + for i in range(len(node_list)): + last_val = last.val + last = node_list.pop() + if '$' in last.nodes: + self._size -= 1 + break + if len(last.nodes) > 1: + del last.nodes[last_val] + self._size -= 1 + break + del last.nodes[last_val] + + def traversal(self, start=None): + """A generator yielding values in the tree in depth first order.""" + if not start: + stack = [] + stack.append(self.root) + while len(stack) > 0: + node = stack.pop() + if node.val != '*': + yield node.val + items = node.nodes.items() + items = reversed(items) + nodes = OrderedDict(items) + for each in nodes: + if len(node.nodes) == 1 and '$' in node.nodes: + break + stack.append(nodes[each]) + + def _traversal(self, node): + + stack = [] + stack.append(node) + while len(stack) > 0: + node = stack.pop() + if node.val != '*': + yield node.val + items = node.nodes.items() + items = reversed(items) + nodes = OrderedDict(items) + for each in nodes: + if len(node.nodes) == 1 and '$' in node.nodes: + break + stack.append(nodes[each])