diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..5ca17cf --- /dev/null +++ b/.travis.yml @@ -0,0 +1,5 @@ +language: python +python: + - "2.7" +install: "pip install -r requirements.txt" +script: py.test \ No newline at end of file diff --git a/README.md b/README.md index 011859c..8f20562 100644 --- a/README.md +++ b/README.md @@ -2,5 +2,14 @@ Code Fellows Dev Accelerator: This repository will be for implementations of classic data structures in python. -parens.py: -Includes a function balanceness() that takes string and determines if it is 'open', 'balanced', or 'broken', depending on the sequence of parenthesis contained. 'Open', 'balanced', and 'broken' are respectively determined as more leading '('s than ')'s, equal number of leading '(' and ')', and any sequence including ')' that do not have a preceding '('. 'Open' strings are represented by returning a 1, 'balanced' by 0, and 'broken' by -1, with an emptry string being considered 'balanced'. \ No newline at end of file +## parens.py: +Includes a function balanceness() that takes string and determines if it is 'open', 'balanced', or 'broken', depending on the sequence of parenthesis contained. 'Open', 'balanced', and 'broken' are respectively determined as more leading '('s than ')'s, equal number of leading '(' and ')', and any sequence including ')' that do not have a preceding '('. 'Open' strings are represented by returning a 1, 'balanced' by 0, and 'broken' by -1, with an emptry string being considered 'balanced'. + +## bst.py: +Binary search tree is a tree where each node has a left child and a right +child and every left child is smaller and every right child is larger. We implement it with a dictionary where each key is the value of the node and each value has three things in it, depth, left child and right child. This module includes a Bst class and the following functions: insert, balance, contains, size, depth, get\_dot. It also includes generators that traverse the tree in order: in\_order, pre\_order, post\_order, and breadth\_first. + + + +## Resources: +[Wikipedia Breadth First Search](en.wikipedia.org/wiki/Breadth-first_search) diff --git a/bst.py b/bst.py new file mode 100755 index 0000000..08b352b --- /dev/null +++ b/bst.py @@ -0,0 +1,301 @@ +#!/usr/bin/env python +import random +import subprocess +from collections import deque + + +class Bst(object): + """Binary Search Tree using a dictionary""" + def __init__(self, value=None): + self.tree = {} + self.top = None + self._size = 0 + self._depth = 0 + + if value is not None: + self.tree[value] = {} + self.top = value + self._size += 1 + self._depth += 1 + + def left(self, current): + return self.tree[current].get('left') + + def right(self, current): + return self.tree[current].get('right') + + def parent(self, current): + return self.tree[current].get('parent') + + def insert(self, value): + """Insert a node with value in order. + + In value is already present, it is ignored.""" + current = self.top + if current is None: + self.tree[value] = {} + self.top = value + self._size += 1 + self._depth += 1 + return + depth = 1 # starts trying to insert at depth 2 + while current is not None: + depth += 1 + if current == value: + return + if current < value: + traverse = self.right(current) + child = 'right' + else: + traverse = self.left(current) + child = 'left' + if traverse is None: + #actual insert + self.tree[value] = {} + + self.tree[current][child] = value + self.tree[value]['parent'] = current + self._size += 1 + if depth > self._depth: + self._depth = depth + return + current = traverse + + def delete(self, val): + if self.contains(val): + left_child = self.left(val) + right_child = self.right(val) + parent = self.parent(val) + if left_child is None and right_child is None: + # Delete a node with not children + if parent is not None: + # For a node with a parent... + if self.left(parent) == val: + del self.tree[parent]['left'] + else: + del self.tree[parent]['right'] + else: + # For deleting a node that is the root with no children + self.top = None + del self.tree[val] + elif left_child is not None: + biggest_child = self._rightmost(left_child) + self._swap_nodes(val, biggest_child) + self.delete(val) + else: + if parent is not None: + if self.left(parent) == val: + self.tree[parent]['left'] = right_child + else: + self.tree[parent]['right'] = right_child + self.tree[right_child]['parent'] = parent + else: + self.top = right_child + del self.tree[val] + + + def _swap_nodes(self, current, target): + """ + Swap two nodes. + + Additionally, update all information on related nodes, as in, children + and parent nodes. + """ + cur_parent = self.parent(current) + targ_parent = self.parent(target) + + children = [] + + children.append(self.left(current)) + children.append(self.right(current)) + children.append(self.left(target)) + children.append(self.right(target)) + + for child in children: + # If a node children, update it's childrens' information + # for 'swapping' + if child and self.parent(child) == current: + self.tree[child]['parent'] = target + elif child: + self.tree[child]['parent'] = current + + # Swapping information in the parent nodes relative to the nodes being 'swapped' + if current != self.top: + if current == self.tree.get(cur_parent).get('left'): + self.tree[cur_parent]['left'] = target + else: + self.tree[cur_parent]['right'] = target + + if target == self.tree.get(targ_parent).get('left'): + self.tree[targ_parent]['left'] = current + else: + self.tree[targ_parent]['right'] = current + + # Swap current and target's left, right, and parent information + self.tree[current], self.tree[target] = self.tree[target], self.tree[current] + + if current == self.top: + self.top = target + + + def _rightmost(self, start): + """Returns None if start is empty tree""" + while start is not None: + if self.right(start) is None: + return start + start = self.right(start) + + + + # left_of_parent = self.left(self.parent(current)) + # right_of_parent = self.right(self.parent(current)) + + # if current == val: + # if current == left_of_parent: + # del self.tree[self.parent(current)]['left'] + # else: + # del self.tree[self.parent(current)]['right'] + + def balance(self): + """Returns the balance of the tree: + + Returns a positive representing how much deeper the tree is on the + right site or negative if the left is longer. + Return 0 if the tree is balanced (same depth on both sides) + + """ + left_deep = 0 + right_deep = 0 + for node, v in self.tree.items(): + if self.top > node: + if left_deep < self.node_depth(node): + left_deep = self.node_depth(node) + elif self.top < node: + if right_deep < self.node_depth(node): + right_deep = self.node_depth(node) + return right_deep - left_deep + + def contains(self, value): + """Returns true if value is in the tree.""" + return value in self.tree + + def size(self): + """Returns the number of nodes in the tree.""" + return self._size + + def depth(self): + """Returns the depth of the tree.""" + return self._depth + + def node_depth(self, current): + """Returns the depth of a node in the tree.""" + depth = 0 + while current is not None: + current = self.parent(current) + depth +=1 + return depth + + def get_dot(self): + """return the tree with root 'self' as a dot graph for visualization""" + return "digraph G{\n%s}" % ("" if not self.tree else ( + "\n%s\n" % ( + # "\t%s;\n%s\n" % ( + # list(self.tree), + "\n".join(self._get_dot(self.top)) + ) + )) + + def _get_dot(self, current): + """recursively prepare a dot graph entry for this node.""" + left = self.tree[current].get('left') + right = self.tree[current].get('right') + parent = self.tree[current].get('parent') + if parent is not None: + yield "\t%s -> %s;" % (current, parent) + + if left is not None: + yield "\t%s -> %s;" % (current, left) + for i in self._get_dot(left): + yield i + elif right is not None: + r = random.randint(0, 1e9) + yield "\tnull%s [shape=point];" % r + yield "\t%s -> null%s;" % (current, r) + if right is not None: + yield "\t%s -> %s;" % (current, right) + for i in self._get_dot(right): + yield i + elif left is not None: + r = random.randint(0, 1e9) + yield "\tnull%s [shape=point];" % r + yield "\t%s -> null%s;" % (current, r) + + + def in_order(self, current='start'): + """ + Generator that traverses the binary tree in order. + """ + if current == 'start': + current = self.top + if current is not None: + for node in self.in_order(self.left(current)): + yield node + yield current + for node in self.in_order(self.right(current)): + yield node + + + def pre_order(self, current='dutch'): + """Generator that traverses the binary tree pre order.""" + if current == 'dutch': + current = self.top + if current is not None: + yield current + for node in self.pre_order(self.left(current)): + yield node + for node in self.pre_order(self.right(current)): + yield node + + def post_order(self, current='dutch'): + """Generator that traverses the binary tree post order.""" + if current == 'dutch': + current = self.top + if current is not None: + for node in self.post_order(self.left(current)): + yield node + for node in self.post_order(self.right(current)): + yield node + yield current + + def breadth_first(self): + """Generator that traverses the binary tree in breadth first order.""" + q1 = deque() + q1.appendleft(self.top) + current = self.top + while q1: + current = q1.pop() + if self.left(current) is not None: + q1.appendleft(self.left(current)) + if self.right(current) is not None: + q1.appendleft(self.right(current)) + yield current + + +def main(): + """Best case and worst case are the same.""" + tree = Bst() + inserts = [7, 4, 11, 2, 9, 6, 12, 5, 13, 0, 10, 8, 3, 1] + for i in inserts: + tree.insert(i) + + tree._swap_nodes(6, 4) + print tree.tree + # for num in enumerate(tree.pre_order()): + # print num + dot_graph = tree.get_dot() + t = subprocess.Popen(["dot", "-Tpng"], stdin=subprocess.PIPE) + t.communicate(dot_graph) + + +if __name__ == '__main__': + main() diff --git a/hash.py b/hash.py new file mode 100644 index 0000000..8923163 --- /dev/null +++ b/hash.py @@ -0,0 +1,34 @@ +class Hash(object): + def __init__(self, buckets): + self.h_list = [] + for i in range(0, buckets): + self.h_list.append([]) + + def set(self, key, val): + """Store the given val using the given key. + + Replaces the value if setting with a key already in. + """ + tup = (key, val) + bucket = self.h_list[self.hash(key)] + for item in bucket: + if item[0] == key: + bucket.remove(item) + break + bucket.append(tup) + + def hash(self, key): + if not isinstance(key, (str, unicode)): + type_ = type(key) + raise TypeError('Cannot hash something of type {}'.format(type_)) + total = 0 + for letter in key: + total += ord(letter) + return total % len(self.h_list) + + def get(self, key): + """return the value stored with the given key""" + bucket = self.h_list[self.hash(key)] + for element in bucket: + if element[0] == key: + return element[1] diff --git a/linked_list.py b/linked_list.py index 7458b48..a6bc65c 100644 --- a/linked_list.py +++ b/linked_list.py @@ -35,6 +35,8 @@ def insert(self, val): Set .data attribute to val and rearrange the list so the head is the new node with a reference to the old head. """ + # if isinstance(val, unicode): + # val = val.encode('utf-8') self.head = List_Node(val, self.head) self._size += 1 @@ -104,7 +106,7 @@ def display_prep(self): while temp: dummy = temp.data if isinstance(temp.data, str or unicode): - dummy = "'{}'".format(dummy.encode('utf-8')) + dummy = "'{}'".format(dummy) if temp is self.head: output = "{}{}".format(output, dummy) @@ -113,5 +115,4 @@ def display_prep(self): output = "{}, {}".format(output, dummy) temp = temp.next - return output + ")" diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..625ffd1 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,2 @@ +py==1.4.26 +pytest==2.6.4 diff --git a/test_bst.py b/test_bst.py new file mode 100644 index 0000000..ee0ba17 --- /dev/null +++ b/test_bst.py @@ -0,0 +1,223 @@ +import bst +import pytest + + +def test_Bst(): + """Constructing a bst tree""" + tree = bst.Bst() + assert tree.tree == {} + tree = bst.Bst(5) + assert tree.tree == {5: {}} + + +def test_insert(empty_tree): + """Test insert into a tree""" + tree = empty_tree + tree.insert(20) + assert tree.tree == {20: {}} + tree.insert(30) + expect = {20: {'right': 30}, + 30: {'parent': 20}} + assert tree.tree == expect + tree.insert(40) + expect = {20: {'right': 30}, + 30: {'right': 40, + 'parent': 20}, + 40: {'parent': 30}} + assert tree.tree == expect + tree.insert(10) + expect = {20: {'left': 10, + 'right': 30}, + 30: {'right': 40, + 'parent': 20}, + 40: {'parent': 30}, + 10: {'parent': 20}} + assert tree.tree == expect + + +def test_size_emptree(empty_tree): + t = empty_tree + assert t.size() == 0 + + +def test_size_tree(filled_tree): + t = filled_tree + assert t.size() == 14 + + +def test_depth_emptree(empty_tree): + t = empty_tree + assert t.depth() == 0 + + +def test_depth_tree(filled_tree): + t = filled_tree + assert t.depth() == 10 + + +def test_balance(empty_tree, filled_tree): + t = empty_tree + assert t.balance() == 0 + t.insert(3) + assert t.balance() == 0 + t = filled_tree + assert t.balance() == -5 + + +def test_contains(filled_tree): + t = filled_tree + for num in reversed(range(10)): + assert t.contains(num) is True + for num in range(10, 14): + assert t.contains(num) is True + for num in range(20, 25): + assert t.contains(num) is False + + +def test_in_order(filled_tree): + tree = filled_tree + gen = tree.in_order() + expected_order = range(0, 14) + for i in expected_order: + j = gen.next() + print str(i) + '=' + str(j) + assert i == j + with pytest.raises(StopIteration): + gen.next() + + +def test_pre_order(filled_tree): + tree = filled_tree + gen = tree.pre_order() + expected_order = [9, 8, 7, 6, 5, 4, 3, 2, 1, 0, 10, 11, 12, 13] + for i in expected_order: + j = gen.next() + print str(i) + '=' + str(j) + assert i == j + with pytest.raises(StopIteration): + gen.next() + +def test_post_order(filled_tree): + tree = filled_tree + gen = tree.post_order() + expected_order = [0, 1, 2, 3, 4, 5, 6, 7, 8, 13, 12, 11, 10, 9] + for i in expected_order: + j = gen.next() + print str(i) + '=' + str(j) + assert i == j + with pytest.raises(StopIteration): + gen.next() + + +def test_breadth_first_order(filled_tree): + tree = filled_tree + expected_order = [9, 8, 10, 7, 11, 6, 12, 5, 13, 4, 3, 2, 1, 0] + for place, item in enumerate(tree.breadth_first()): + assert expected_order[place] == item + + +def test_swap(filled_tree_2): + tree = filled_tree_2 + tree._swap_nodes(7, 5) + assert tree.top == 5 + + assert tree.parent(5) is None + assert tree.left(5) == 4 + assert tree.right(5) == 11 + + assert tree.parent(7) == 6 + assert tree.left(7) is None + assert tree.right(7) is None + +def test_swap_parentchild(filled_tree_2): + tree = filled_tree_2 + tree._swap_nodes(7, 4) + assert tree.top == 4 + + assert tree.parent(4) is None + assert tree.left(4) == 7 + assert tree.right(4) == 11 + + assert tree.parent(7) == 4 + assert tree.left(7) is 2 + assert tree.right(7) is 6 + +def test_deletion_easy_case(filled_tree_2): + tree = filled_tree_2 + assert tree.left(6) == 5 + tree.delete(5) + assert tree.left(6) is None + assert tree.contains(5) is False + + +def test_deletion_one_left_child_lesser(filled_tree_2): + tree = filled_tree_2 + assert tree.parent(6) == 4 + assert tree.left(6) == 5 + assert tree.right(6) is None + tree.delete(6) + assert tree.left(4) == 2 + assert tree.right(4) == 5 + + assert tree.parent(5) == 4 + assert tree.left(5) is None + assert tree.right(5) is None + + assert tree.contains(6) is False + + +def test_deletion_one_right_child_lesser(filled_tree_2): + tree = filled_tree_2 + assert tree.parent(0) == 2 + assert tree.left(0) is None + assert tree.right(0) == 1 + tree.delete(0) + + assert tree.left(2) == 1 + assert tree.right(2) == 3 + + assert tree.parent(1) == 2 + assert tree.left(1) is None + assert tree.right(1) is None + + assert tree.contains(0) is False + + +def test_node_depth(filled_tree_2): + assert filled_tree_2.node_depth(7) == 1 + assert filled_tree_2.node_depth(4) == 2 + assert filled_tree_2.node_depth(1) == 5 + assert filled_tree_2.node_depth(13) == 4 + +def test_rightmost(filled_tree_2): + assert filled_tree_2._rightmost(0) == 1 + assert filled_tree_2._rightmost(4) == 6 + assert filled_tree_2._rightmost(7) == 13 + assert filled_tree_2._rightmost(6) == 6 + + +@pytest.fixture(scope='function') +def filled_tree(): + """Upside down V Shaped Tree""" + tree = bst.Bst() + for num in reversed(range(10)): + tree.insert(num) + for num in range(10, 14): + tree.insert(num) + return tree + + +@pytest.fixture(scope='function') +def filled_tree_2(): + """Tree with lots of branches""" + inserts = [7, 4, 11, 2, 9, 6, 12, 5, 13, 0, 10, 8, 3, 1] + tree = bst.Bst() + for val in inserts: + tree.insert(val) + return tree + + +@pytest.fixture(scope='function') +def empty_tree(): + tree = bst.Bst() + return tree diff --git a/test_hash.py b/test_hash.py new file mode 100644 index 0000000..444bd5a --- /dev/null +++ b/test_hash.py @@ -0,0 +1,87 @@ +from hash import Hash +import pytest +from io import open + +def test_create(): + something = Hash(10) + assert isinstance(something.h_list, list) + assert len(something.h_list) == 10 + + +def test_bad_create(): + with pytest.raises(TypeError): + something = Hash() + + +def test_set_integer_return_error(): + hash_table = Hash(10) + word = 123 + with pytest.raises(TypeError): + hash_table.set(word, word) + + +def test_set_list_return_error(): + hash_table = Hash(10) + word = [1, 2, 3] + with pytest.raises(TypeError): + hash_table.set(word, word) + + +def test_duplicate_key_different_value(): + """a get on a hash table should return the last value put in""" + hash_table = Hash(10) + word = 'asd' + values = range(4) + for value in values: + hash_table.set(word, value) + # last value in was 3 + assert hash_table.get(word) == 3 + + +def test_duplicate_key_different_value_in_only_once(): + """a set on a hash table with duplicate keys should only put one in""" + hash_table = Hash(10) + word = 'asd' + values = range(4) + for value in values: + hash_table.set(word, value) + sum = 0 + for char in word: + sum += ord(char) + # should only be one value in bucket + assert len(hash_table.h_list[sum % 10]) == 1 + +def test_hash(): + something = Hash(10) + hashed_val = something.hash('hello') + assert hashed_val == 2 + + +def test_set(): + something = Hash(10) + something.set('asdf', 10) + + sum = 0 + for char in 'asdf': + sum += ord(char) + assert ('asdf', 10) in something.h_list[sum % 10] + + +def test_get(): + something = Hash(10) + something.set('asdf', 10) + assert something.get('asdf') == 10 + + +def test_on_word(): + """testing on word dictionary built into UNIX""" + infile = open('/usr/share/dict/words', 'r') + full_text = [] + for line in infile: + full_text.append(line.strip()) + allbins = Hash(10000) + for word in full_text: + allbins.set(word, word) + for word in full_text: + assert allbins.get(word) == word + diff --git a/test_linked_list.py b/test_linked_list.py index e959241..7728aac 100644 --- a/test_linked_list.py +++ b/test_linked_list.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- # test constructor from linked_list import List_Node from linked_list import Linked_List