diff --git a/.gitignore b/.gitignore index ba74660..bbf5af0 100644 --- a/.gitignore +++ b/.gitignore @@ -55,3 +55,6 @@ docs/_build/ # PyBuilder target/ + +# Graphviz Output +graphviz/ diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..c5473c0 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,28 @@ +# Set up notification options +notifications: + email: + # change is when the repo status goes from pass to fail or vice versa + on_success: change + on_failure: always + +# specify language +language: python +python: + - "2.7" + +## command to install dependencies +install: + - 'pip install -r requirements.txt' + +## Script to run +script: py.test + +branches: + ## whitelist + only: + - master + - staging + +# blacklist + except: + - /^.*test.*$/ diff --git a/README.md b/README.md new file mode 100644 index 0000000..9faa9ec --- /dev/null +++ b/README.md @@ -0,0 +1,187 @@ +[![Build Status](https://travis-ci.org/jonathanstallings/data-structures.svg?branch=master)](https://travis-ci.org/jonathanstallings/data-structures) + + +#Data Structures +Implementation of LinkedList and Stack data structures in Python. + +##LinkedList +The LinkedList class is composed of a Node base class. + +Available methods inlude: +* insert(val) +* pop() +* size() +* search(val) +* remove(node) +* display() + +##Stack +The Stack data class is a first-in-first-out data structure built via composition from LinkedList. +Available methods include: +* push(value) +* pop() + +##Queue +The Queue data class is a first-in-last-out data structure built via encapsulation of a LinkedList. + +Available methods inlude: +* enqueque(value) +* dequeque() +* len() + +##Binary Heap +The Binary Heap data class is a binary tree data structure built implemented on a built-in Python +list. The binary heap default to a minheap sort, meaning that the smallest values will be sorted to +the top of the heap. Alternatively, the binary heap can be instantiated as a maxheap so that the +greatest values will be sorted to the top. + +Available methods include: +* pop() +* push() +See the doc strings for implementation details. + +##PriorityQ +The PriorityQ data class is a binary tree that implements sorting primarily by priority value and +secondarily by insertion order. The PriorityQ defaults to minheap sorting for both. A QNode is implemented +as a base class to containerize the value and priority and to provide convenient APIs for comparison. + +Available methods include: +* insert(item) +* pop() +* peek() +See the doc strings for implementation details. + +Instantiation of a PriorityQ takes an iterable which may contain (value, priority) iterables, +non-iterable values, or QNode objects. + +##Graph +The graph data class is a network consisting nodes with an arbitrary number of references (edges) to other +nodes in the graph. Methods allows abilities such as adding, removing, and checking the existance of nodes +and edges in the graph. Additionaly, the graph class contains to traversal methods. Given a start node, the +methods will traverse the entire network reachable from that node and return the path travelled as a list of +nodes travelled. Both [depth-first](https://en.wikipedia.org/wiki/Graph_traversal#Depth-first_search) and [breadth first](https://en.wikipedia.org/wiki/Graph_traversal#Breadth-first_search) traversal methods are available. + +Available methods include: + +* nodes() +* edges() +* add_node(n) +* add_edge(n1, n2) +* del_node(n) +* del_edge(n1, n2) +* has_node(n) +* neighbors(n) +* adjacent(n1, n2) +* depth_first_traversal(start) +* breadth_first_traversal(start) +* uniform_cost_search(n1, n2) +* bellmanford(n1, n2) + +The uniform_cost_search method returns a path that corresponds to the +shortest path between n1 and n2. This algorithm tracks historical paths +and is able to search for the shortest path in a relatively uncostly way. +Time complexity is O(edges + node log nodes) with relatively low memory +overhead. + +The bellmanfor search method also returns the same path, but has the added +ability to handle edges with negative values and detect negative feedback +loops. It is relatively robust, but at added time complexity cost of +O(nodes * edges). + +See the doc strings for additional implementation details. + +##Binary Search Tree +Contains a Node class which implements an AVL binary search tree. + +Each node can be considered a binary search tree and has the usual +methods to insert, delete, and check membership of nodes. By default, +the insert and delete methods will perform self-balancing consistent +with an AVL tree. This behavior can be suppressed by passing the optional +'balanced=False' keyword argument to the insert or delete methods. + +The class also supports four traversal methods which return generators: + +- in_order +- pre_order +- post_order +- breadth_first. + +Additionally, methods are included to help visualize the tree structure. +get_dot returns DOT source code, suitable for use with programs such as +Graphviz (http://graphviz.readthedocs.org/en/stable/index.html), and +save_render saves a rendering of the tree structure to the file system. +Passing the optional 'render=True' keyword argument to the insert and +delete methods will automatically save a render to disk upon execution. + +Finally, the helper methods 'create_best_case' and 'create_worst_case' +facilitates creation of tree composeds of _n_ integers. + +This module was completed with reference to the following: + +[Binary Search Tree libary in Python](http://www.laurentluce.com/posts/binary-search-tree-library-in-python/) +by Laurent Luce. + +[How to Balance your Binary Search Trees - AVL Trees](https://triangleinequality.wordpress.com/2014/07/15/how-to-balance-your-binary-search-trees-avl-trees/) + +[The AVL Tree Rotations Tutorial](http://pages.cs.wisc.edu/~paton/readings/liblitVersion/AVL-Tree-Rotations.pdf) +by John Hargrove +Available methods include: + +* insert(val, balanced=True, render=False) +* delete(val, balanced=True, render=False) +* contains(val) +* lookup(val) +* size() +* depth() +* balance() +* in_order() +* pre_order() +* post_order() +* breadth_first() +* get_dot() +* save_render() +* create_best_case() +* create_worst_case() + + +See the doc strings for additional implementation details. + +##Hash Table +Contains a HashTable class. The [hash table](https://en.wikipedia.org/wiki/Hash_table) +is implemented on a list of lists, with a default table size of 8192. This table +size can be overridden on initialization by passing a size keyword argument. Insertion +and lookup time complexity ranges from O(1) at best to O(n) at worst. + +Available methods include: + +* set(key, value) +* get(key) + +##Insertion Sort +This module contains the in_sort method, which performs an +in-place insertion sort on a passed in list. Insertion sort has a best case time +complexity of O(n) when list is nearly sorted, and a worst case of O(n2) +when the incoming list is already reverse sorted. While this not always +the most efficient algorithm, it is still popular when the data is nearly +sorted (because it is adaptive) or when the problem size is small. +See the excellent 'sortingalgorithms.com' for more information. + +## Merge Sort +This module contains the merge_srt method, which performs an +in-place merge sort on a passed in list. Merge sort has a best case time +complexity of O(n log n) when list is nearly sorted, and also a worst case of +O(n log n). Merge sort is a very predictable and stable sort, but it is not +adaptive. See the excellent 'sortingalgorithms.com' for more information. + +##Quick Sort +"""This module contains the quick sort method (quick_srt), which performs an +in-place sort on a passed in list. Quick sort has a best case time +complexity of O(n log n) when all elements are equal, and a worst case of +O(n2). Quick sort is not stable or adaptive, but it is robust and has low +overhead. + +This module was completed with reference to: +[Quicksort: A Python Implementation](http://pfsensesetup.com/pythonscript.net/quicksort-a-python-implementation/) +by maximumdx + +See the excellent 'sortingalgorithms.com' for more information. diff --git a/__init__.py b/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/binary_heap.py b/binary_heap.py new file mode 100644 index 0000000..6220261 --- /dev/null +++ b/binary_heap.py @@ -0,0 +1,143 @@ +from __future__ import unicode_literals + + +class BinaryHeap(object): + """A class for a binary heap.""" + def __init__(self, iterable=(), minheap=True): + """Initializes a binary heap, optionally with items from an iterable. + + By default, the binary will sort as a minheap, with smallest values + at the head. If minheap is set to false, the binary heap will sort + as a maxheap, with largest values at the head. + """ + self.tree = [] + self.minheap = minheap + for val in iterable: + self.push(val) + + def __repr__(self): + return repr(self.tree) + + def __len__(self): + return len(self.tree) + + def __iter__(self): + return iter(self.tree) + + def __getitem__(self, index): + return self.tree[index] + + def __setitem__(self, index, value): + self.tree[index] = value + + def pop(self): + """Pop the head from the heap and return.""" + if len(self) <= 1: + to_return = self.tree.pop() + else: + endpos = len(self) - 1 + self._swap(0, endpos) + to_return = self.tree.pop() + self._bubbledown(0) + return to_return + + def push(self, value): + """Push a value onto a stack. + + args: + value: the value to add + """ + self.tree.append(value) # Add protection for different types case + if len(self) > 1: + endpos = len(self) - 1 + self._bubbleup(endpos) + + def _bubbleup(self, pos): + """Perform one step of heap sort up the tree. + + args: + pos: the index position to inspect + """ + parent = self._find_parent(pos) + if pos == 0: # find_parent will return -1 at end of list + return + elif self._is_unsorted(self[pos], self[parent]): + self._swap(pos, parent) + self._bubbleup(parent) + + def _bubbledown(self, pos): + """Perform one step of heap sort down the tree. + + args: + pos: the index position to inspect + """ + lchild, rchild = self._find_children(pos) + try: # Evaluating whether lchild exists; may refactor + lval = self[lchild] + try: + rval = self[rchild] + except IndexError: # Case of left_child only + if self._is_unsorted(lval, self[pos]): + self._swap(lchild, pos) + else: # Case of left_child and right_child + if self._is_unsorted(lval, rval): + target = lchild + else: + target = rchild + if self._is_unsorted(self[target], self[pos]): + self._swap(target, pos) + self._bubbledown(target) + + except IndexError: # Case of no lchild + return + + def _find_parent(self, pos): + """Returns the parent index of given position. + + args: + pos: the index position to inspect + + Returns: index of the parent + """ + parent = (pos - 1) // 2 + return parent + + def _find_children(self, pos): + """Returns the indices of children from given position. + + args: + pos: the index position to inspect + + Returns: index of left child and right child + """ + lchild = (pos * 2) + 1 + rchild = lchild + 1 + return lchild, rchild + + def _is_unsorted(self, item1, item2): + """Compare two items according to heaptype. + + For a minheap, checks if first item is less than second item. + For a maxheap, checks if first item is greater than second item. + + args: + item1: first item + item2: second item + + Returns: True if heaptype comparison matches, else False + """ + if self.minheap is True: + return item1 < item2 + elif self.minheap is False: + return item1 > item2 + else: + raise AttributeError('heaptype not assigned') + + def _swap(self, pos1, pos2): + """Swap the values at given index positions. + + args: + pos1: the index of the first item + pos2: the index of the second item + """ + self[pos1], self[pos2] = self[pos2], self[pos1] diff --git a/bst.py b/bst.py new file mode 100644 index 0000000..d9837de --- /dev/null +++ b/bst.py @@ -0,0 +1,513 @@ +"""Contains a Node class which implements an AVL binary search tree. + +Each node can be considered a binary search tree and has the usual +methods to insert, delete, and check membership of nodes. By default, +the insert and delete methods will perform self-balancing consistent +with an AVL tree. This behavior can be suppressed by passing the optional +'balanced=False' keyword argument to the insert or delete methods. + +The class also supports four traversal methods which return generators: + +- in_order +- pre_order +- post_order +- breadth_first. + +Additionally, methods are included to help visualize the tree structure. +get_dot returns DOT source code, suitable for use with programs such as +Graphviz (http://graphviz.readthedocs.org/en/stable/index.html), and +save_render saves a rendering of the tree structure to the file system. +Passing the optional 'render=True' keyword argument to the insert and +delete methods will automatically save a render to disk upon execution. + +Finally, the helper methods 'create_best_case' and 'create_worst_case' +facilitate creation of tree composeds of _n_ integers. + +This module was completed with reference to the following: + +'Binary Search Tree libary in Python' +(http://www.laurentluce.com/posts/binary-search-tree-library-in-python/) +by Laurent Luce. + +'How to Balance your Binary Search Trees - AVL Trees' +(https://triangleinequality.wordpress.com/2014/07/15/how-to-balance-your-binary-search-trees-avl-trees/) + +'The AVL Tree Rotations Tutorial' +(http://pages.cs.wisc.edu/~paton/readings/liblitVersion/AVL-Tree-Rotations.pdf) +by John Hargrove + +""" +from __future__ import print_function +from __future__ import unicode_literals +import random + +from queue import Queue + + +class Node(object): + """A class for a binary search tree node.""" + def __init__(self, val=None, parent=None): + self.val = val + self.parent = parent + self.left = None + self.right = None + + def __repr__(self): + return ''.format(self.val) + + def __str__(self): + return '{}'.format(self.val) + + def __len__(self): + return self.size() + + def __iter__(self): + return self.in_order() + + def __add__(self, other): + for item in other: + self.insert(item) + + def __sub__(self, other): + for item in other: + self.delete(item) + + def insert(self, val, balanced=True, render=False): + """Insert a node with a value into the tree. + + If val is already present, it will be ignored. + + args: + val: the value to insert + balanced: performs AVL self-balancing if set to True + render: automatically saves a render to disk if set to True + """ + if self.val is not None: + if val == self.val: + return None + if val < self.val: + if self.left is None: + self.left = Node(val, self) + if balanced: + self.left._self_balance() + else: + self.left.insert(val, balanced, render) + elif val > self.val: + if self.right is None: + self.right = Node(val, self) + if balanced: + self.right._self_balance() + else: + self.right.insert(val, balanced, render) + else: + self.val = val + if render and self.parent is None: + self.save_render() + + def delete(self, val, balanced=True, render=False): + """Delete a node matching value and reorganize tree as needed. + + If the matched node is the only node in the tree, only its value + will be deleted. + + args: + val: the value of the node to delete + balanced: performs AVL self-balancing if set to True + render: automatically saves a render to disk if set to True + """ + node = self.lookup(val) + parent = node.parent + if node is not None: + children_count = node._children_count() + if children_count == 0: + if parent: + if parent.left is node: + parent.left = None + else: + parent.right = None + if balanced: + parent._self_balance() + else: + self.val = None + elif children_count == 1: + if node.left: + child = node.left + else: + child = node.right + if parent: + if parent.left is node: + parent.left = child + else: + parent.right = child + child.parent = parent + if balanced: + child._self_balance() + else: + self.left = child.left + self.right = child.right + try: + self.right.parent = self + self.left.parent = self + except AttributeError: + pass + self.val = child.val + if balanced: + self._self_balance() + else: + parent = node + successor = node.right + while successor.left: + parent = successor + successor = successor.left + node.val = successor.val + if parent.left == successor: + parent.left = successor.right + try: + parent.left.parent = parent + except AttributeError: + pass + parent._self_balance() + else: + parent.right = successor.right + try: + parent.right.parent = parent + except AttributeError: + pass + if balanced: + parent._self_balance() + if render and self.parent is None: + self.save_render() + + def contains(self, val): + """Check tree for node with given value. + + args: + val: the value to check for + + returns: True if val is in the tree, False if not. + """ + if val == self.val: + return True + elif val < self.val: + if self.left is None: + return False + return self.left.contains(val) + elif val > self.val: + if self.right is None: + return False + return self.right.contains(val) + + def lookup(self, val): + """Find a node by value and return that node. + + args: + val: the value to search by + + returns: a node + """ + if val < self.val: + if self.left is None: + return None, None + return self.left.lookup(val) + elif val > self.val: + if self.right is None: + return None, None + return self.right.lookup(val) + else: + return self + + def size(self): + """Return the total number of nodes in the tree. + + returns: integer of total node; 0 if empty + """ + if self.val is None: + return 0 + left_size = self.left.size() if self.left is not None else 0 + right_size = self.right.size() if self.right is not None else 0 + return left_size + right_size + 1 + + def depth(self): + """Return an the total number of levels in the tree. + + If there is one value, the depth should be 1, if two values it'll be 2, + if three values it may be 2 or three, depending, etc. + + returns: integer of level number + """ + left_depth = self.left.depth() if self.left is not None else 0 + right_depth = self.right.depth() if self.right is not None else 0 + return max(left_depth, right_depth) + 1 + + def balance(self): + """Return a positive or negative number representing tree balance. + + Trees higher on the left than the right should return a positive value, + trees higher on the right than the left should return a negative value. + An ideally-balanced tree should return 0. + + returns: integer + """ + left_depth = self.left.depth() if self.left is not None else 0 + right_depth = self.right.depth() if self.right is not None else 0 + return left_depth - right_depth + + def _is_left(self): + """Check nodes relationship to parent. + + returns: + - True if node is left child of parent + - False if node is right childe of parent + - None if node has no parent + """ + if self.parent is None: + return None + else: + return self is self.parent.left + + def _rotate_right(self): + """Perform a single right tree rotation.""" + pivot = self.left + if pivot is None: + return + self.val, pivot.val = pivot.val, self.val + self.left = pivot.left + if self.left is not None: + self.left.parent = self + pivot.left = pivot.right + pivot.right = self.right + if pivot.right is not None: + pivot.right.parent = pivot + self.right = pivot + + def _rotate_left(self): + """Perform a single left tree rotation.""" + pivot = self.right + if pivot is None: + return + self.val, pivot.val = pivot.val, self.val + self.right = pivot.right + if self.right is not None: + self.right.parent = self + pivot.right = pivot.left + pivot.left = self.left + if pivot.left is not None: + pivot.left.parent = pivot + self.left = pivot + + def _self_balance(self): + """Balance the subtree from given node.""" + balance = self.balance() + # Tree is left heavy + if balance == 2: + if self.left.balance() <= -1: + # Double Right + self.left._rotate_left() + # Single Right + self._rotate_right() + if self.parent is not None: + self.parent._self_balance() + + # Tree is right heavy + elif balance == -2: + if self.right.balance() >= 1: + # Double Left + self.right._rotate_right() + # Single Left + self._rotate_left() + if self.parent is not None: + self.parent._self_balance() + else: + if self.parent is not None: + self.parent._self_balance() + + def in_order(self): + """Return a generator with tree values from in-order traversal""" + stack = [] + node = self + while stack or node: + if node: + stack.append(node) + node = node.left + else: + node = stack.pop() + yield node.val + node = node.right + + def pre_order(self): + """Return a generator with tree values from pre-order traversal""" + stack = [] + node = self + while stack or node: + if node: + yield node.val + stack.append(node) + node = node.left + else: + node = stack.pop() + node = node.right + + def post_order(self): + """Return a generator with tree values from post-order traversal""" + stack = [] + node = self + last = None + while stack or node: + if node: + stack.append(node) + node = node.left + else: + peek = stack[-1] + if peek.right is not None and last != peek.right: + node = peek.right + else: + yield peek.val + last = stack.pop() + node = None + + def breadth_first(self): + """Return a generator with tree values from breadth first traversal""" + q = Queue() + q.enqueue(self) + while q.size() > 0: + node = q.dequeue() + yield node.val + if node.left: + q.enqueue(node.left) + if node.right: + q.enqueue(node.right) + + def _children_count(self): + """Return a node's number of children.""" + cnt = 0 + if self.left: + cnt += 1 + if self.right: + cnt += 1 + return cnt + + def get_dot(self): + """Return the tree with root as a dot graph for visualization.""" + return "digraph G{\n%s}" % ("" if self.val is None else ( + "\t%s;\n%s\n" % ( + self.val, + "\n".join(self._get_dot()) + ) + )) + + def _get_dot(self): + """recursively prepare a dot graph entry for this node.""" + if self.left is not None: + yield "\t%s -> %s;" % (self.val, self.left.val) + for i in self.left._get_dot(): + yield i + elif self.right is not None: + r = random.randint(0, 1e9) + yield "\tnull%s [shape=point];" % r + yield "\t%s -> null%s;" % (self.val, r) + if self.right is not None: + yield "\t%s -> %s;" % (self.val, self.right.val) + for i in self.right._get_dot(): + yield i + elif self.left is not None: + r = random.randint(0, 1e9) + yield "\tnull%s [shape=point];" % r + yield "\t%s -> null%s;" % (self.val, r) + + def save_render(self, savefile="tree.gv"): + """Render and save a represntation of the tree. + + args: + savefile: the optional filename + """ + from graphviz import Source + src = Source(self.get_dot()) + path = 'graphviz/{}'.format(savefile) + src.render(path) + + @classmethod + def _sorted_list_to_bst(cls, items=[], start=None, end=None, parent=None): + """Create a balanced binary search tree from sorted list. + + args: + items: the sorted list of items to insert into tree + start: the start of the list + end: the end of the list + + returns: a balanced binary search tree (node) + """ + if start > end: + return None + mid = start + (end - start) // 2 + node = Node(items[mid], parent) + node.left = cls._sorted_list_to_bst(items, start, mid - 1, node) + node.right = cls._sorted_list_to_bst(items, mid + 1, end, node) + return node + + @classmethod + def create_best_case(cls, n): + """Create a balanced binary search tree from a given range. + + args: + n: the range of integers to insert into the tree + + returns: a balanced binary search tree (node) + """ + return cls._sorted_list_to_bst(range(n), 0, n - 1) + + @classmethod + def create_worst_case(cls, n): + """Create an unbalanced binary search tree from a given range. + + The tree will be one long linear branch to the right. + + args: + n: the range of integers to add to the tree + + returns: a (very) unbalanced binary search tree (node) + """ + node = Node(0) + parent = node + for i in range(1, n): + parent.right = Node(i, parent) + parent = parent.right + return node + +if __name__ == '__main__': + from timeit import Timer + + """Document the best and worst cases for searching for a value in the tree. + The worst case consists of a tree with one long linear branch. + The best case is a perfectly balanced tree. + """ + + SIZE = 900 + LOOKUP = 900 + + worst = Node.create_worst_case(SIZE) + best = Node.create_best_case(SIZE) + + worst_case = Timer( + 'worst.contains({})'.format(LOOKUP, SIZE), 'from __main__ import worst' + ).timeit(1000) + + best_case = Timer( + 'best.contains({})'.format(LOOKUP), 'from __main__ import best' + ).timeit(1000) + + print( + "\nLookup Time Comparison: Best and Worst Case\n" + "\nGiven a tree of {n} items, find a node with value of {l}.\n" + .format(n=SIZE, l=LOOKUP) + ) + + print( + "Worst case, with tree balanced at {b}.\n" + "Time: {t}\n" + .format(b=worst.balance(), t=worst_case) + ) + print( + "Best case, with tree balanced at {b}.\n" + "Time: {t}\n" + .format(b=best.balance(), t=best_case) + ) diff --git a/double_link_list.py b/double_link_list.py new file mode 100644 index 0000000..0e70e69 --- /dev/null +++ b/double_link_list.py @@ -0,0 +1,158 @@ +from __future__ import unicode_literals + + +class Node(object): + + def __init__(self, val, prev=None, next_=None): + self.val = val + self.prev = prev + self.next = next_ + + def __repr__(self): + """Print representation of node.""" + return "{val}".format(val=self.val) + + +class DoubleLinkList(object): + """Class for a doubly-linked list.""" + def __init__(self, iterable=()): + self._current = None + self.head = None + self.tail = None + self.length = 0 + for val in reversed(iterable): + self.insert(val) + + def __repr__(self): + """Print representation of DoubleLinkList.""" + node = self.head + output = "" + for node in self: + output += "{!r}, ".format(node.val) + return "({})".format(output.rstrip(' ,')) + + def __len__(self): + return self.length + + def __iter__(self): + if self.head is not None: + self._current = self.head + return self + + def next(self): + if self._current is None: + raise StopIteration + node = self._current + self._current = self._current.next + return node + + def size(self): + """Return current length of DoubleLinkList.""" + return len(self) + + def search(self, search_val): + """Return the node containing val if present, else None. + + args: + search_val: the value to search by + + returns: a node object or None + """ + for node in self: + if node.val == search_val: + return node + else: + return None + + def remove(self, search_val): + """Remove the first node from list matching the search value. + + args: + search_val: the val to be removed + """ + search_node = self.search(search_val) + + if search_node == self.head: + self.head = search_node.next + try: + self.head.prev = None + except AttributeError: + pass + self.length -= 1 + elif search_node == self.tail: + self.tail = search_node.prev + try: + self.tail.next = None + except AttributeError: + pass + self.length -= 1 + else: + for node in self: + if node == search_node: + node.prev.next = node.next + node.next.prev = node.prev + self.length -= 1 + return None + raise ValueError('value not in list') + + def insert(self, val): + """Insert value at head of list. + + args: + val: the value to add + """ + old_head = self.head + self.head = Node(val, prev=None, next_=old_head) + if old_head is None: + self.tail = self.head + else: + old_head.prev = self.head + self.length += 1 + return None + + def append(self, val): + """Append a node with value to end of list. + + args: + val: the value to add + """ + old_tail = self.tail + self.tail = Node(val, prev=old_tail, next_=None) + if old_tail is None: + self.head = self.tail + else: + old_tail.next = self.tail + self.length += 1 + return None + + def pop(self): + """Pop the first val off the head and return it.""" + if self.head is None: + raise IndexError('pop from empty list') + else: + to_return = self.head + self.head = to_return.next + try: + self.head.prev = None + except AttributeError: # List is now empty + self.tail = None + self.length -= 1 + return to_return.val + + def shift(self): + """Remove the last value from the tail and return.""" + if self.tail is None: + raise IndexError('pop from empty list') + else: + to_return = self.tail + self.tail = to_return.prev + try: + self.tail.next = None + except AttributeError: # List is now empty + self.head = None + self.length -= 1 + return to_return.val + + def display(self): + """Shows representation of DoubleLinkList.""" + return repr(self) diff --git a/graph.py b/graph.py new file mode 100644 index 0000000..00e0e89 --- /dev/null +++ b/graph.py @@ -0,0 +1,218 @@ +from __future__ import unicode_literals + +from queue import Queue +import priorityq as pq + + +class Graph(object): + """A class for a simple graph data structure.""" + def __init__(self): + self.graph = {} + + def __repr__(self): + return repr(self.graph) + + def __len__(self): + return len(self.graph) + + def __iter__(self): + return iter(self.graph) + + def __getitem__(self, index): + return self.graph[index] + + def __setitem__(self, index, value): + self.graph[index] = value + + def __delitem__(self, index): + del self.graph[index] + for edgeset in self.graph.values(): + edgeset.pop(index, None) + + def add_node(self, n): + """Add a new node to the graph. Will raise an error if node + already exists. + + Note that node name 'n' needs to be a hashable or immutable value + """ + if self.has_node(n): + raise KeyError('Node already in graph.') + self[n] = dict() + + def add_edge(self, node1, node2, edge_weight): + """Add a new edge connecting n1 to n2. Will implicitly create + n1 and n2 if either do not exist. + """ + if not self.has_node(node2): + self.add_node(node2) + try: + self[node1].update({node2: edge_weight}) + except KeyError: + self.add_node(node1) + self[node1].update({node2: edge_weight}) + + def del_node(self, n): + """Delete a node from the graph. Will cleanup all edges pointing + towards the node being deleted. + """ + del self[n] + + def del_edge(self, n1, n2): + """Delete stated edge connecting node n1 to n2. Will raise a KeyError + if the edge does not exist. + """ + self[n1].pop(n2) + + def has_node(self, n): + """Check if a given node is in the graph, return True if it exists, + False otherwise. + """ + return n in self + + def nodes(self): + """Return a list of all nodes in the graph.""" + return [node for node in self] + + def iter_edges(self): + """Generate an iterator packed with all existing edges in + (node, edge) format. + """ + for node in self: + for edge in self[node]: + yield (node, edge) + + def edges(self): + """Return a list of all edges in (node, edge_node) format, + where node points to edge_node. + """ + return list(self.iter_edges()) + + def iter_neighbors(self, n): + """Generate an iterator packed with all existing edges for node.""" + for node in self: + if n in self[node]: + yield node + + def neighbors(self, n): + """Return the set of edges that node n points towards.""" + return self[n] + + def adjacent(self, n1, n2): + """Check if there is an edge pointing from node n1 to n2.""" + return n2 in self[n1] + + def depth_first_traversal(self, start): + """Perform full depth-first traversal of graph from start. + + args: + start: the node to start traversal + + returns: the list of nodes traversed + """ + path = [] + visited = set() + + def step(node, path, visited): + if node not in visited: + path.append(node) + visited.add(node) + for child in iter(self[node]): + step(child, path, visited) + return + + step(start, path, visited) + return path + + def breadth_first_traversal(self, start): + """Perform a full breadth first traversal of graph from start. + + args: + start: the node to start traversal + + returns: a list of nodes traversed + """ + path = [] + visited = set() + temp = Queue([start]) + + while temp: + node = temp.dequeue() + if node not in visited: + path.append(node) + visited.add(node) + for child in self[node]: + if child not in visited: + temp.enqueue(child) + return path + + def uniform_cost_search(self, start, goal): + """Return the shortest path from start to goal node. + + args: + start: the node to begin the path + goal: the node to end the path + """ + q = pq.PriorityQ() + q.insert((0, start, []), priority=0) + seen = {} + + while q: + cost, point, path = q.pop() + if point in seen and seen[point] < cost: + continue + path = path + [point] + if point == goal: + return path + for child in self[point]: + child_cost = self[point][child] + if child not in seen: + tot_cost = child_cost + cost + q.insert((tot_cost, child, path), priority=tot_cost) + seen[point] = cost + return None + + def bellmanford(self, node1, node2): + """Find the shortest path from node1 to node2 + + It is possible to access a graph with negatives weights using this + algorithm + + This implementation is adapted for Python from the pseudocode + here: + https://en.wikipedia.org/wiki/Bellman%E2%80%93Ford_algorithm#Algorithm + + This algorithm is currently generating useful, extra data that + isn't being returned. May want to update the API to allow access + to this info. + """ + + distance = {node: float("inf") if node != node1 else 0 + for node in self} + + predecessor = {node: None for node in self} + + for _ in self: + for node in self: + # Point of this and inner loop is to grab each of the edges by + # node times; expect O( node * edge ); note that edges are nested + # under nodes dict ; hence two inner loops needed to grab these + for edgen, weight in self[node].iteritems(): + if distance[node] + weight < distance[edgen]: + distance[edgen] = distance[node] + weight + predecessor[edgen] = node + for node in self: + # Consistancy check per pseudo code; check for loops where + # cost is negative per cycle + for edgen, weight in self[node].iteritems(): + if distance[node] + weight < distance[edgen]: + raise ZeroDivisionError + # Now build a path from predecessor dict + rpath = [] + pointer = node2 + while True: + rpath.append(pointer) + if pointer == node1: + break + pointer = predecessor[pointer] + rpath.reverse() + return rpath diff --git a/hashtable.py b/hashtable.py new file mode 100644 index 0000000..c5b9f10 --- /dev/null +++ b/hashtable.py @@ -0,0 +1,69 @@ +"""Contains a HashTable class. The hash table is implemented on a list of +lists, with a default table size of 8192. This table size can be overridden on +initialization by passing a size keyword argument. Insertion and lookup time +complexity ranges from O(1) at best to O(n) at worst. +""" + + +class HashTable(object): + """A class for a hash table.""" + entries_count = 0 + alphabet_size = 52 + + def __init__(self, size=8192): + self.table_size = size + self.hashtable = [[] for i in range(size)] + + def __repr__(self): + return "".format(self.hashtable) + + def __len__(self): + count = 0 + for item in self.hashtable: + count += len(item) + return count + + def _hashing(self, key): + """Create a hash from a given key. + + args: + key: a string to hash + + returns: an integer corresponding to hashtable index + """ + hash_ = 0 + for i, c in enumerate(key): + hash_ += pow( + self.alphabet_size, len(key) - i - 1) * ord(c) + return hash_ % self.table_size + + def set(self, key, value): + """Add a key and value into the hash table. + + args: + key: the key to store + value: the value to store + """ + if not isinstance(key, str): + raise TypeError('Only strings may be used as keys.') + hash_ = self._hashing(key) + for i, item in enumerate(self.hashtable[hash_]): + if item[0] == key: + del self.hashtable[hash_][i] + self.entries_count -= 1 + self.hashtable[hash_].append((key, value)) + self.entries_count += 1 + + def get(self, key): + """Retrieve a value from the hash table by key. + + args: + key: a string to find the value in the hash table + + returns: the value stored with the key + """ + hash_ = self._hashing(key) + for i, item in enumerate(self.hashtable[hash_]): + if item[0] == key: + return item[1] + raise KeyError('Key not in hash table.') diff --git a/insertion_sort.py b/insertion_sort.py new file mode 100644 index 0000000..e7cc26b --- /dev/null +++ b/insertion_sort.py @@ -0,0 +1,48 @@ +"""This module contains the insertion sort method, which performs an +in-place sort on a passed in list. Insertion sort has a best case timeit +complexity of O(n) when list is nearly sorted, and a worst case of O(n2) +when the incoming list is already reverse sorted. While this not always +the most efficient algorithm, it is still popular when the data is nearly +sorted (because it is adaptive) or when the problem size is small. +See the excellent 'sortingalgorithms.com' for more information. + +""" + + +def in_sort(un_list): + """Sort a list in place using insertion sort. + + args: + un_list: the list to sort + """ + if type(un_list) is not list: + raise TypeError("You must pass a valid list as argument. Do it.") + + for idx in range(1, len(un_list)): + current = un_list[idx] + position = idx + + while position > 0 and un_list[position-1] > current: + un_list[position] = un_list[position-1] + position = position - 1 + + un_list[position] = current + +if __name__ == '__main__': + BEST_CASE = range(1000) + WORST_CASE = BEST_CASE[::-1] + + from timeit import Timer + + best = Timer( + 'insertion_sort({})'.format(BEST_CASE), + 'from __main__ import BEST_CASE, insertion_sort').timeit(1000) + + worst = Timer( + 'insertion_sort({})'.format(WORST_CASE), + 'from __main__ import WORST_CASE, insertion_sort').timeit(1000) + + print("""Best case represented as a list that is already sorted\n + Worst case represented as a list that is absolute reverse of sorted""") + print('Best Case: {}'.format(best)) + print('Worst Case: {}'.format(worst)) diff --git a/linked_list.py b/linked_list.py new file mode 100644 index 0000000..37998af --- /dev/null +++ b/linked_list.py @@ -0,0 +1,98 @@ +from __future__ import unicode_literals + + +class Node(object): + + def __init__(self, val, next=None): + self.val = val + self.next = next + + def __repr__(self): + """Print representation of node.""" + return "{val}".format(val=self.val) + + +class LinkedList(object): + """Class for a singly-linked list.""" + def __init__(self, iterable=()): + self._current = None + self.head = None + self.length = 0 + for val in reversed(iterable): + self.insert(val) + + def __repr__(self): + """Print representation of LinkedList.""" + node = self.head + output = "" + for node in self: + output += "{!r}, ".format(node.val) + return "({})".format(output.rstrip(' ,')) + + def __len__(self): + return self.length + + def __iter__(self): + if self.head is not None: + self._current = self.head + return self + + def next(self): + if self._current is None: + raise StopIteration + node = self._current + self._current = self._current.next + return node + + def insert(self, val): + """Insert value at head of LinkedList. + + args: + val: the value to add + """ + self.head = Node(val, self.head) + self.length += 1 + return None + + def pop(self): + """Pop the first val off the head and return it.""" + if self.head is None: + raise IndexError + else: + to_return = self.head + self.head = to_return.next + self.length -= 1 + return to_return.val + + def size(self): + """Return current length of LinkedList.""" + return len(self) + + def search(self, search_val): + """Return the node containing val if present, else None. + + args: + search_val: the value to search by + + returns: a node object or None + """ + for node in self: + if node.val == search_val: + return node + else: + return None + + def remove(self, search_node): + """Remove given node from list, return None. + + args: + search_node: the node to be removed + """ + for node in self: + if node.next == search_node: + node.next = node.next.next + return None + + def display(self): + """Shows representation of LinkedList.""" + return repr(self) diff --git a/merge_sort.py b/merge_sort.py new file mode 100644 index 0000000..7d9f977 --- /dev/null +++ b/merge_sort.py @@ -0,0 +1,63 @@ +"""This module contains the merge_srt method, which performs an +in-place merge sort on a passed in list. Merge sort has a best case time +complexity of O(n log n) when list is nearly sorted, and also a worst case of +O(n log n). Merge sort is a very predictable and stable sort, but it is not +adaptive. See the excellent 'sortingalgorithms.com' for more information. + +""" + + +def merge_srt(un_list): + """Perform an in-place merge sort on list. + + args: + un_list: the list to sort + """ + if len(un_list) > 1: + mid = len(un_list) // 2 + left_half = un_list[:mid] + right_half = un_list[mid:] + + merge_srt(left_half) + merge_srt(right_half) + + x = y = z = 0 + + while x < len(left_half) and y < len(right_half): + if left_half[x] < right_half[y]: + un_list[z] = left_half[x] + x += 1 + else: + un_list[z] = right_half[y] + y += 1 + z += 1 + + while x < len(left_half): + un_list[z] = left_half[x] + x += 1 + z += 1 + + while y < len(right_half): + un_list[z] = right_half[y] + y += 1 + z += 1 + + +if __name__ == '__main__': + even_half = range(0, 1001, 2) + odd_half = range(1, 1000, 2) + BEST_CASE = range(0, 1001) + WORST_CASE = even_half + odd_half + + from timeit import Timer + + SETUP = """from __main__ import BEST_CASE, WORST_CASE, merge_srt""" + + best = Timer('merge_srt({})'.format(BEST_CASE), SETUP).timeit(1000) + + worst = Timer('merge_srt({})'.format(WORST_CASE), SETUP).timeit(1000) + + print("""Best case represented as a list that is already sorted\n + Worst case represented as a list that is absolute reverse of sorted""") + print('Best Case: {}'.format(best)) + print('Worst Case: {}'.format(worst)) diff --git a/priorityq.py b/priorityq.py new file mode 100644 index 0000000..4bf4e37 --- /dev/null +++ b/priorityq.py @@ -0,0 +1,122 @@ +from __future__ import unicode_literals +from functools import total_ordering + +from binary_heap import BinaryHeap + + +@total_ordering # Will build out the remaining comparison methods +class QNode(object): + """A class for a queue node.""" + def __init__(self, val, priority=None, order=None): + """Initialize a QNode with a value and an optional priority. + + args: + val: the value to store + priority: an integer with 0 being most important + order: integer to store queue insertion order + """ + self.val = val + self.priority = priority + self.order = order + + def __repr__(self): + """Print representation of node.""" + return "({val}, {priority})".format(val=self.val, + priority=self.priority) + + def __str__(self): + """Pretty print node value and priority.""" + return "Value:{val}, Order:{o} Priority:{p}".format( + val=self.val, o=self.order, p=self.priority + ) + + def __eq__(self, other): + """Overloads equality comparison to check priority, then order.""" + if self.priority == other.priority: + return self.order == other.order + else: + return self.priority == other.priority + + def __lt__(self, other): + """Overloads lesser than comparison to check priority, then order.""" + if self.priority == other.priority: + return self.order < other.order + elif self.priority is None: + return False + elif other.priority is None: + return True + else: + return self.priority < other.priority + + +class PriorityQ(object): + """A class for a priority queue.""" + def __init__(self, iterable=()): + """Initialize a priority queue, optionally with items from iterable. + + The items in the priority queue are stored in a binary minheap. Items + are first sorted by priority, then queue insertion order. Priority is + expressed as an integer with 0 being the most important. + + args: + iterable: an optional iterable to add to the priority queue. Items + added this way will be given a priority of None. + + each item inside iterable can be either: + * A QNode object + * A container with value, priority + * A non-iterable value + + """ + self.heap = BinaryHeap(iterable=()) + for item in iterable: + try: + is_container = len(item) == 2 + except TypeError: # Case of QNode or non-iterable item + self.insert(item) + else: + if is_container: # Case of value, iterable + self.insert(item[0], item[1]) + else: + raise TypeError("More than two args: instantiation supports\ + non-iter value or iter of value, priority") + + def __repr__(self): + return repr(self.heap) + + def __len__(self): + return len(self.heap) + + def __iter__(self): + return iter(self.heap) + + def __getitem__(self, index): + return self.heap[index] + + def __setitem__(self, index, value): + self.heap[index] = value + + def insert(self, item, priority=None): + """Insert an item into the priority queue. + + If the item is a QNode object, it will be added tracking queue order. + If not, a new QNode object is created to hold the item with queue order + and optional priority assigned. + + args: + item: the item to add (QNode or other value) + priority: the optional integer priority (0 is most important) + """ + if isinstance(item, QNode): + item.order = len(self) + self.heap.push(item) + else: + self.heap.push(QNode(item, priority=priority, order=len(self))) + + def pop(self): + """Remove and return the most important item from the queue.""" + return self.heap.pop().val + + def peek(self): + """Return the most important item from queue without removal.""" + return self.heap[0].val diff --git a/queue.py b/queue.py new file mode 100644 index 0000000..f8f06dd --- /dev/null +++ b/queue.py @@ -0,0 +1,43 @@ +from __future__ import unicode_literals + +from linked_list import LinkedList, Node + + +class Queue(): + + def __init__(self, iterable=()): + self.other = LinkedList() + self.other.head = None + self.other.tail = None + self.other.length = 0 + for val in (iterable): + self.enqueue(val) + + def __repr__(self): + return repr(self.other) + + def __len__(self): + return self.other.length + + def enqueue(self, value): + """Add a value to the tail of a queue. + + args: + value: The value to add to the queue + """ + new_node = Node(value) + if self.other.tail is None: + self.other.head = self.other.tail = new_node + else: + self.other.tail.next = new_node + self.other.tail = new_node + self.other.length += 1 + + def dequeue(self): + """Remove and return a value from the head of the queue.""" + if len(self) == 1: + self.other.tail = None + return self.other.pop() + + def size(self): + return len(self) diff --git a/quick_sort.py b/quick_sort.py new file mode 100644 index 0000000..fc45373 --- /dev/null +++ b/quick_sort.py @@ -0,0 +1,142 @@ +"""This module contains the quick sort method (quick_srt), which performs an +in-place sort on a passed in list. Quick sort has a best case time +complexity of O(n log n) when all elements are equal, and a worst case of +O(n2). Quick sort is not stable or adaptive, but it is robust and has low +overhead. + +This module was completed with reference to: + +Quicksort: A Python Implementation +http://pfsensesetup.com/pythonscript.net/quicksort-a-python-implementation/ +by maximumdx + +See the excellent 'sortingalgorithms.com' for more information. +""" + + +def quick_srt(un_list): + """Sort a list in place with quick sort. + + args: + un_list: the list to be sorted + """ + _quick_srt(un_list, low=0, high=-1) + + +def _quick_srt(un_list, low=0, high=-1): + """Sort a list in place with quick sort. + + args: + un_list: the list to be sorted + low: index lower bound + high: the index upper bound + """ + if high == -1: + high = len(un_list) - 1 + if high > low: + pivot = _partition(un_list, low, high) + + if pivot > 0: + _quick_srt(un_list, low, pivot-1) + if pivot > -1: + _quick_srt(un_list, pivot+1, high) + + +def _partition(un_list, low=0, high=-1): + """Partition the list into values less than and greater than pivot. + + If the the partitioned sublist contains at least two unique values, + the pivot index is returned. Else, a value of -1 is returned, which + will end any further recursive calls to _quick_srt. + + args: + un_list: the list to be sorted + low: index lower bound + high: the index upper bound + + returns: the pivot index or -1 + """ + if high == -1: + high = len(un_list) - 1 + pivot = _choose_pivot(un_list, low, high) + _swap(un_list, pivot, high) + j = low + for i in range(low, high+1): + if un_list[i] < un_list[high]: + _swap(un_list, i, j) + j += 1 + + _swap(un_list, high, j) + for i in range(low, high): + if un_list[i] != un_list[i+1]: + return j + + return -1 + + +def _swap(un_list, x, y): + """Swap the values at two index positions in list. + + args: + un_list: the list to act upon + x: index to first element + y: index to second element + """ + temp = un_list[x] + un_list[x] = un_list[y] + un_list[y] = temp + + +def _choose_pivot(un_list, low=0, high=-1): + """Choose a pivot for quick sort. + + args: + un_list: the list to be sorted + low: index lower bound + high: the index upper bound + + returns: the pivot index + """ + if high == -1: + high = len(un_list) - 1 + mid = low + (high - low) // 2 + + if un_list[low] == un_list[mid] and un_list[mid] == un_list[high]: + return mid + + if (un_list[low] < un_list[mid] + and un_list[low] < un_list[high] + and un_list[mid] < un_list[high]): + return mid + + elif (un_list[mid] < un_list[low] + and un_list[mid] < un_list[high] + and un_list[low] < un_list[high]): + return low + + else: + return high + + +if __name__ == '__main__': + from random import randint + rands = [randint(1, 10001) for x in range(1, 10001)] + dupes = [1 for x in range(1, 10001)] + nums = range(0, 10001) + BEST_CASE = dupes + WORST_CASE = rands + + from timeit import Timer + + SETUP = """from __main__ import BEST_CASE, WORST_CASE, quick_srt""" + + best = Timer('quick_srt({})'.format(BEST_CASE), SETUP).timeit(100) + + worst = Timer('quick_srt({})'.format(WORST_CASE), SETUP).timeit(100) + + print(""" + Best case represented as a list that being a list of the same values. + Worst case represented as a list that is random numbers. + """) + print('Best Case: {}'.format(best)) + print('Worst Case: {}'.format(worst)) diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..e995f16 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,4 @@ +graphviz==0.4.5 +ipython==3.0.0 +py==1.4.29 +pytest==2.7.2 diff --git a/stack.py b/stack.py new file mode 100644 index 0000000..11d387c --- /dev/null +++ b/stack.py @@ -0,0 +1,25 @@ +from __future__ import unicode_literals + +from linked_list import LinkedList + + +class Stack(): + + def __init__(self, iterable=()): + self.other = LinkedList() + self.other.__init__(iterable) + + def __repr__(self): + return repr(self.other) + + def push(self, value): + """Add a value to the head of the stack. + + args: + value: The value to add to the stack + """ + self.other.insert(value) + + def pop(self): + """Remove a value from head of stack and return.""" + return self.other.pop() diff --git a/test_binary_heap.py b/test_binary_heap.py new file mode 100644 index 0000000..ac0ff25 --- /dev/null +++ b/test_binary_heap.py @@ -0,0 +1,224 @@ +from __future__ import unicode_literals +import pytest +from binary_heap import BinaryHeap + +# (in, expected) for constructors +valid_constructor_args = [ + ["qzdfwqefnsadnfoiaweod", "a"], + [[6, 7, 9, 4, 2, 1, 56, 8, 0, 43523], 0], + [[1243.12235, 13262.3, 26523.15, 98653.234], 1243.12235] +] + +valid_constructor_args_max = [ + ["qzdfwqefnsadnfoiaweod", "z"], + [[6, 7, 9, 4, 2, 1, 56, 8, 0, 43523], 43523], + [[1243.12235, 13262.3, 26523.15, 98653.234], 98653.234] +] + +pop_constructors = [ + [5, 8, 98, 43, 21, 1, 3, 7, 0, 3, 4, 7, 2345, 4, 64], + [33, 5314, 124, 243, 234, 1324, 2, 342, 1243, 134], + ] + +invalid_constructors = [ + 8, + None, + 4.523423 + ] + + +def is_minheap_sorted(heap): + """Confirm that heap is minheap sorted. + + Original idea from: + https://github.com/MigrantJ/data-structures/blob/binheap/binheap/binheap.py + """ + for i in range(len(heap)): + try: + if heap[i] > heap[(2*i + 1)]: + return False + if heap[i] > heap[(2*i) + 2]: + return False + except IndexError: + return True + + +def is_maxheap_sorted(heap): + """Confirm that heap is maxheap sorted.""" + for i in range(len(heap)): + try: + if heap[i] < heap[(2*i + 1)]: + return False + if heap[i] < heap[(2*i) + 2]: + return False + except IndexError: + return True + + +@pytest.fixture() +def minheap_empty(): + return BinaryHeap() + + +def test_find_parent(): + minheap = BinaryHeap([0, 1, 2, 3, 4, 5, 6]) + assert minheap._find_parent(2) == 0 + assert minheap._find_parent(6) == 2 + + +def test_find_children(): + minheap = BinaryHeap([0, 1, 2, 3, 4, 5, 6]) + assert minheap._find_children(0) == (1, 2) + assert minheap._find_children(2) == (5, 6) + + +def test_is_unsorted_minheap_comparison(): + minheap = BinaryHeap(minheap=True) + assert minheap._is_unsorted(1, 2) + + +def test_is_unsorted_maxheap_comparison(): + minheap = BinaryHeap(minheap=False) + assert minheap._is_unsorted(2, 1) + + +def test_swap(): + minheap = BinaryHeap([0, 1, 2, 3, 4, 5, 6]) + minheap._swap(0, 6) + assert minheap.tree[0] == 6 + assert minheap.tree[6] == 0 + + +def test_bubbledown_minheap(): + minheap = BinaryHeap([0, 1, 2, 3, 4, 5, 6]) + minheap[0] = 4000 + minheap._bubbledown(0) + assert minheap[0] == 1 + assert is_minheap_sorted(minheap) + + +def test_bubbledown_maxheap(): + maxheap = BinaryHeap([6, 5, 4, 3, 2, 1, 0], minheap=False) + maxheap[6] = 4000 + maxheap._bubbleup(6) + assert maxheap[0] == 4000 + assert is_maxheap_sorted(maxheap) + + +@pytest.mark.parametrize("input, output", valid_constructor_args) +def test_valid_instantiation_min(input, output): + """Test instantiation by creating and doing one pop""" + heap_under_test = BinaryHeap(input) + assert is_minheap_sorted(heap_under_test) + assert heap_under_test.pop() == output + + +@pytest.mark.parametrize("input, output", valid_constructor_args_max) +def test_valid_instantiation_max(input, output): + """Test instantiation by creating and doing one pop""" + heap_under_test = BinaryHeap(input, minheap=False) + assert is_maxheap_sorted(heap_under_test) + assert heap_under_test.pop() == output + + +@pytest.mark.parametrize("bad_input", invalid_constructors) +def test_invalid_instantiation(bad_input): + """Test that bad by creating and doing one pop""" + with pytest.raises(TypeError): + BinaryHeap(bad_input) + + +def test_push1(minheap_empty): + """ First push single item from list of [9, 5, 2, 1, 0, 7] """ + minheap_empty.push(9) + assert minheap_empty.pop() == 9 + + +def test_push2(minheap_empty): + """ First push two items from list of [9, 5, 2, 1, 0, 7]; current + test for min heap """ + minheap_empty.push(5) + minheap_empty.push(9) + assert minheap_empty.pop() == 5 + + +def test_push3(minheap_empty): + """ First push three items from list of [9, 5, 2, 1, 0, 7]; current + test for min heap """ + minheap_empty.push(5) + minheap_empty.push(9) + minheap_empty.push(2) + assert minheap_empty.pop() == 2 + + +def test_push4(minheap_empty): + """ First push four items from list of [9, 5, 2, 1, 0, 7]; current + test for min heap """ + minheap_empty.push(5) + minheap_empty.push(9) + minheap_empty.push(2) + minheap_empty.push(1) + assert minheap_empty.pop() == 1 + + +def test_push5(minheap_empty): + """ First push five items from list of [9, 5, 2, 1, 0, 7]; current + test for min heap """ + minheap_empty.push(5) + minheap_empty.push(9) + minheap_empty.push(2) + minheap_empty.push(1) + minheap_empty.push(0) + assert minheap_empty.pop() == 0 + + +def test_push6(minheap_empty): + """ First push six items from list of [9, 5, 2, 1, 0, 7]; current + test for min heap """ + minheap_empty.push(5) + minheap_empty.push(9) + minheap_empty.push(2) + minheap_empty.push(1) + minheap_empty.push(0) + minheap_empty.push(7) + assert minheap_empty.pop() == 0 + + +def test_pop_minheap(): + minheap = BinaryHeap([7, 9, 18, 1, 38, 5.4, 6]) + minheap.push(0) + length = len(minheap) + assert minheap.pop() == 0 + assert len(minheap) == length - 1 + + +def test_pop_maxheap(): + maxheap = BinaryHeap([7, 9, 18, 1, 38, 5.4, 6], minheap=False) + maxheap.push(400) + length = len(maxheap) + assert maxheap.pop() == 400 + assert len(maxheap) == length - 1 + + +def test_multipop_minheap(): + minheap = BinaryHeap([7, 9, 18, 1, 38, 5.4, 6, 200]) + length = len(minheap) + minheap.pop() + minheap.pop() + minheap.push(0) + minheap.pop() + minheap.pop() + assert minheap.pop() == 7 + assert len(minheap) == length - 4 + + +def test_multipop_maxheap(): + maxheap = BinaryHeap([7, 9, 18, 1, 38, 5.4, 6, 200], minheap=False) + length = len(maxheap) + maxheap.pop() + maxheap.pop() + maxheap.push(400) + maxheap.pop() + maxheap.pop() + assert maxheap.pop() == 9 + assert len(maxheap) == length - 4 diff --git a/test_bst.py b/test_bst.py new file mode 100644 index 0000000..386814d --- /dev/null +++ b/test_bst.py @@ -0,0 +1,562 @@ +from __future__ import unicode_literals +from random import randint +import types + +import pytest + +from bst import Node + + +@pytest.fixture() +def rand_setup(): + root = Node(randint(1, 100)) + for idx in range(20): + val = randint(1, 100) + root.insert(val, balanced=False) + return root + + +@pytest.fixture() +def fixed_setup(): + root = Node(25) + setup = [15, 30, 9, 17, 21, 39, 12, 24, 40, 14] + for item in setup: + root.insert(item, balanced=False) + return root + + +@pytest.fixture() +def traversal_setup(): + root = Node() + setup = ['F', 'B', 'A', 'D', 'C', 'E', 'G', 'I', 'H'] + for char in setup: + root.insert(char, balanced=False) + return root + + +@pytest.fixture() +def pivot_setup(): + root = Node(10) + root.left, root.right = Node(5, root), Node(15, root) + root.left.left = Node(1, root.left) + root.left.right = Node(7, root.left) + pivot = root.left + return root, pivot + + +def test_init_empty(): + root = Node() + assert root.val is None + assert root.left is None and root.right is None + + +def test_init_with_val(): + root = Node(10) + assert root.val == 10 + assert root.left is None and root.right is None + + +def test_insert_in_empty_root(): + root = Node() + expected = 10 + root.insert(expected, balanced=False) + actual = root.val + assert expected == actual + + +def test_insert_lesser_in_filled_root(): + root = Node(10) + expected = 5 + root.insert(expected, balanced=False) + actual = root.left.val + assert expected == actual + assert root.right is None + + +def test_insert_greater_in_filled_root(): + root = Node(10) + expected = 15 + root.insert(expected, balanced=False) + actual = root.right.val + assert expected == actual + assert root.left is None + + +def test_insert_lesser_in_filled_tree1(): + root = Node(10) + root.left, root.right = Node(5), Node(15) + root.insert(1, balanced=False) + assert root.left.left.val == 1 + assert root.left.right is None + + +def test_insert_lesser_in_filled_tree2(): + root = Node(10) + root.left, root.right = Node(5), Node(15) + root.insert(7, balanced=False) + assert root.left.right.val == 7 + assert root.left.left is None + + +def test_insert_greater_in_filled_tree1(): + root = Node(10) + root.left, root.right = Node(5), Node(15) + root.insert(17, balanced=False) + assert root.right.right.val == 17 + assert root.right.left is None + + +def test_insert_greater_in_filled_tree2(): + root = Node(10) + root.left, root.right = Node(5), Node(15) + root.insert(12, balanced=False) + assert root.right.left.val == 12 + assert root.right.right is None + + +def test_insert_duplicates(): + root = Node(10) + root.left, root.right = Node(5), Node(15) + root.insert(5, balanced=False) + assert root.size() == 3 + root.insert(15, balanced=False) + assert root.size() == 3 + + +def test_insert(rand_setup): + pre_size = rand_setup.size() + new = 200 + rand_setup.insert(new, balanced=False) + assert rand_setup.insert(new, balanced=False) is None + rand_setup.insert(new, balanced=False) + post_size = rand_setup.size() + assert post_size > pre_size + assert post_size == pre_size + 1 + + +def test_contains_val(): + root = Node(10) + root.left, root.right = Node(5), Node(15) + assert root.contains(15) and not root.contains(27) + root.left.left = Node(2) + assert root.contains(2) + + +def test_contains(rand_setup): + rand = randint(1, 100) + rand_setup.insert(rand, balanced=False) + assert rand_setup.contains(rand) is True + + +def test_size_with_filling(): + root = Node() + assert root.size() == 0 + root.val = 10 + assert root.size() == 1 + root.left, root.right = Node(5), Node(15) + assert root.size() == 3 + + +def test_size(rand_setup): + pre_size = rand_setup.size() + new = 200 + rand_setup.insert(new, balanced=False) + rand_setup.insert(new, balanced=False) + post_size = rand_setup.size() + assert post_size > pre_size + assert post_size == pre_size + 1 + + +def test_depth_1(): + root = Node() + assert root.depth() == 1 + root.val = 10 + assert root.depth() == 1 + + +def test_depth_2(): + root = Node(10) + root.left = Node(5) + assert root.depth() == 2 + root.right = Node(15) + assert root.depth() == 2 + + +def test_depth_3(): + root = Node(10) + root.left, root.right = Node(5), Node(15) + root.left.left, root.left.right = Node(3), Node(7) + assert root.depth() == 3 + root.right.left, root.right.right = Node(12), Node(17) + assert root.depth() == 3 + + +def test_depth_n(): + rand = randint(1, 100) + root = Node(0) + curr = root + for i in range(rand): + curr.right = Node(i) + curr = curr.right + assert root.depth() == rand + 1 + + +def test_depth(fixed_setup): + assert fixed_setup.left.depth() == 4 + assert fixed_setup.right.depth() == 3 + fixed_setup.insert(13, balanced=False) + assert fixed_setup.left.depth() == 5 + + +def test_balance_equal(): + root = Node(10) + assert root.balance() == 0 + root.left, root.right = Node(5), Node(15) + assert root.balance() == 0 + root.left.left, root.left.right = Node(3), Node(7) + root.right.right = Node(17) + assert root.balance() == 0 + + +def test_balance_positive(): + root = Node(10) + root.left = Node(5) + assert root.balance() == 1 + root.left.left, root.left.right = Node(3), Node(7) + assert root.balance() == 2 + root.right = Node(15) + assert root.balance() == 1 + + +def test_balance_negative(): + root = Node(10) + root.right = Node(5) + assert root.balance() == -1 + root.right.left, root.right.right = Node(3), Node(7) + assert root.balance() == -2 + root.left = Node(15) + assert root.balance() == -1 + + +def test_balance(rand_setup): + left = rand_setup.left.depth() if rand_setup.left is not None else 0 + right = rand_setup.right.depth() if rand_setup.right is not None else 0 + if left > right: + assert rand_setup.balance() > 0 + elif right > left: + assert rand_setup.balance() < 0 + else: + assert rand_setup.balance() == 0 + + +def test_in_order(traversal_setup): + root = traversal_setup + expected = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I'] + generator = root.in_order() + assert isinstance(generator, types.GeneratorType) + actual = list(generator) + assert expected == actual + + +def test_pre_order(traversal_setup): + root = traversal_setup + expected = ['F', 'B', 'A', 'D', 'C', 'E', 'G', 'I', 'H'] + generator = root.pre_order() + assert isinstance(generator, types.GeneratorType) + actual = list(generator) + assert expected == actual + + +def test_post_order(traversal_setup): + root = traversal_setup + expected = ['A', 'C', 'E', 'D', 'B', 'H', 'I', 'G', 'F'] + generator = root.post_order() + assert isinstance(generator, types.GeneratorType) + actual = list(generator) + assert expected == actual + + +def test_breadth_first(traversal_setup): + root = traversal_setup + expected = ['F', 'B', 'G', 'A', 'D', 'I', 'C', 'E', 'H'] + generator = root.breadth_first() + assert isinstance(generator, types.GeneratorType) + actual = list(generator) + assert expected == actual + + +def test_sorted_list_to_bst(): + nodes = range(100) + root = Node._sorted_list_to_bst(nodes, 0, 99) + assert isinstance(root, Node) + assert root.size() == 100 and root.balance() == 0 + + +def test_create_best_case(): + root = Node.create_best_case(100) + assert isinstance(root, Node) + assert root.size() == 100 and root.balance() == 0 + + +def test_create_worst_case(): + root = Node.create_worst_case(100) + assert isinstance(root, Node) + assert root.size() == 100 and root.balance() == -99 + + +def test_lookup(traversal_setup): + root = traversal_setup + expected = root.left + actual = root.lookup('B') + assert expected is actual + + +def test_is_left_no_parent(): + root = Node(10) + root.left, root.right = Node(5, root.left), Node(15) + assert root._is_left() is None + + +def test_is_left_left_node(): + root = Node(10) + root.left, root.right = Node(5, root.left), Node(15) + node = root.left + result = node._is_left() + assert result is None + + +def test_is_left_right_node(): + root = Node(10) + root.left, root.right = Node(5, root.left), Node(15) + node = root.right + assert not node._is_left() + + +def test_delete_root_only(): + root = Node('A') + root.delete('A', balanced=False) + assert root.val is None + assert root.left is None and root.right is None + + +def test_delete_node_without_children(traversal_setup): + root = traversal_setup + root.delete('A', balanced=False) + parent = root.left + assert parent.left is None + + +def test_delete_node_with_one_child(traversal_setup): + root = traversal_setup + root.delete('G', balanced=False) + parent = root + assert parent.right.val == 'I' + + +def test_delete_node_with_two_children(traversal_setup): + root = traversal_setup + root.delete('B', balanced=False) + parent = root + assert parent.left.val == 'C' + successor = parent.left + assert successor.left.val == 'A' and successor.right.val == 'D' + + +def test_delete_root_with_one_child(): + root = Node('F') + root.left = Node('B') + root.left.left, root.left.right = Node('A'), Node('D') + root.delete('F', balanced=False) + assert root.val == 'B' + assert root.left.val == 'A' and root.right.val == 'D' + + +def test_delete_root_with_two_children(traversal_setup): + root = traversal_setup + root.delete('F', balanced=False) + assert root.val == 'G' + assert root.left.val == 'B' and root.right.val == 'I' + + +# Tests for AVL Balanced Behavior Follow +def test_rotate_right(pivot_setup): + root, pivot = pivot_setup + root_val, pivot_val = root.val, pivot.val + root_right = root.right + pivot_right, pivot_left = pivot.right, pivot.left + root._rotate_right() + assert root.val == pivot_val and pivot.val == root_val + assert root_right is pivot.right and pivot_right is pivot.left + assert pivot_left is root.left + + +def test_rotate_left(pivot_setup): + root, pivot = pivot_setup + root_val, pivot_val = root.val, pivot.val + root_right = root.right + pivot_right, pivot_left = pivot.right, pivot.left + root._rotate_right() + root._rotate_left() + root._rotate_right() + assert root.val == pivot_val and pivot.val == root_val + assert root_right is pivot.right and pivot_right is pivot.left + assert pivot_left is root.left + + +def test_self_balance_single_right(): + root = Node(10) + root.left = Node(5, root) + root.left.left = Node(1, root.left) + leaf = root.left.left + leaf._self_balance() + assert root.val == 5 + assert root.left.val == 1 and root.right.val == 10 + + +def test_self_balance_double_right(): + root = Node(10) + root.left = Node(5, root) + root.left.right = Node(7, root.left) + leaf = root.left.right + leaf._self_balance() + assert root.val == 7 + assert root.left.val == 5 and root.right.val == 10 + + +def test_self_balance_single_left(): + root = Node(10) + root.right = Node(15, root) + root.right.right = Node(20, root.right) + leaf = root.right.right + leaf._self_balance() + assert root.val == 15 + assert root.left.val == 10 and root.right.val == 20 + + +def test_self_balance_double_left(): + root = Node(10) + root.right = Node(15, root) + root.right.left = Node(13, root.right) + leaf = root.right.left + leaf._self_balance() + assert root.val == 13 + assert root.left.val == 10 and root.right.val == 15 + + +def test_avl_insert_in_empty_root(): + root = Node() + expected = 10 + root.insert(expected) + actual = root.val + assert expected == actual + + +def test_avl_insert_lesser_in_filled_root(): + root = Node(10) + expected = 5 + root.insert(expected) + actual = root.left.val + assert expected == actual + assert root.right is None + + +def test_avl_insert_greater_in_filled_root(): + root = Node(10) + expected = 15 + root.insert(expected) + actual = root.right.val + assert expected == actual + assert root.left is None + + +def test_avl_insert_lesser_in_filled_tree1(): + root = Node(10) + root.left, root.right = Node(5, root), Node(15, root) + root.left.left = Node(2, root.left) + root.insert(1) + assert root.left.val == 2 + assert root.left.left.val == 1 and root.left.right.val == 5 + + +def test_avl_insert_lesser_in_filled_tree2(): + root = Node(10) + root.left, root.right = Node(5, root), Node(15, root) + root.left.left = Node(2, root.left) + root.insert(3) + assert root.left.val == 3 + assert root.left.left.val == 2 and root.left.right.val == 5 + + +def test_avl_insert_greater_in_filled_tree1(): + root = Node(10) + root.left, root.right = Node(5, root), Node(15, root) + root.right.right = Node(20, root.right) + root.insert(17) + assert root.right.val == 17 + assert root.right.left.val == 15 and root.right.right.val == 20 + + +def test_avl_insert_greater_in_filled_tree2(): + root = Node(10) + root.left, root.right = Node(5, root), Node(15, root) + root.right.right = Node(20, root.right) + root.insert(25) + assert root.right.val == 20 + assert root.right.left.val == 15 and root.right.right.val == 25 + + +def test_avl_delete_root_only(): + root = Node(0) + root.delete(0) + assert root.val is None + assert root.left is None and root.right is None + + +def test_avl_delete_root_with_one_child(): + root = Node(10) + root.left = Node(5, root) + root.delete(10) + assert root.val == 5 + assert root.left is None and root.right is None + + +def test_avl_delete_root_with_two_children(): + root = Node(10) + root.left, root.right = Node(5, root), Node(15, root) + root.delete(10) + assert root.val == 15 + assert root.left.val == 5 and root.right is None + + +def test_avl_delete_node_without_children(): + root = Node(10) + root.left, root.right = Node(5, root), Node(15, root) + root.right.right = Node(20, root.right) + root.delete(5) + assert root.val == 15 + assert root.left.val == 10 and root.right.val == 20 + + +def test_avl_delete_node_with_one_child(): + root = Node(10) + root.left, root.right = Node(5, root), Node(15, root) + root.right.left = Node(12, root.right) + root.right.right = Node(20, root.right) + root.right.left.right = Node(13, root.right.left) + root.left.right = Node(8, root.left) + root.delete(5) + assert root.val == 12 + assert root.left.val == 10 and root.right.val == 15 + + +def test_avl_delete_node_with_two_children(): + root = Node(10) + root.left, root.right = Node(5, root), Node(15, root) + root.right.left = Node(12, root.right) + root.right.right = Node(20, root.right) + root.right.left.right = Node(13, root.right.left) + root.left.right = Node(8, root.left) + root.delete(15) + assert root.right.val == 13 + assert root.right.left.val == 12 and root.right.right.val == 20 + diff --git a/test_double_link_list.py b/test_double_link_list.py new file mode 100644 index 0000000..b1891ee --- /dev/null +++ b/test_double_link_list.py @@ -0,0 +1,213 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals +import pytest +from double_link_list import DoubleLinkList + +# (Input, expected) for well constructed instantiation arguments, +# and one subsequent pop +valid_constructor_args = [ + ([1, 2, 3], 1), + ([[1, 2, 3], "string"], [1, 2, 3]), + ("string", 's') +] + +# Invalid instantiation arguments +invalid_constructor_args = [ + (None), + (1), + (4.5235) +] + +# (Input, remove, expected) for well constructed instantiation arguments, +# remove arg, and expected repr +remove_val_args = [ + ([1, 2, 3, 4, 5, 6, 7], 6) +] + + +@pytest.mark.parametrize("input, pop_val", valid_constructor_args) +def test_valid_contructor(input, pop_val): + """Test valid constructor using by dequeuing after instantiation""" + assert DoubleLinkList(input).pop() == pop_val + + +def test_empty_constructor(): + """Test valid empty constructor via dequeuing after instantiation""" + with pytest.raises(IndexError): + DoubleLinkList(()).pop() + + +@pytest.mark.parametrize("input", invalid_constructor_args) +def test_invalid_constructor(input): + """Test invalid constuctor arguments""" + with pytest.raises(TypeError): + DoubleLinkList(input) + + +def test_insert_via_len(): + """Tests insert function for doublelinked list; using len""" + adding_to = DoubleLinkList(()) + for i in xrange(40): + adding_to.insert(i) + + assert len(adding_to) == 40 + assert adding_to.pop() == 39 + + +def test_insert_via_pop(): + """Tests insert function for doublelinked list; using pop""" + adding_to = DoubleLinkList(()) + for i in xrange(40): + adding_to.insert(i) + + assert adding_to.pop() == 39 + + +def test_insert_via_shift(): + """Tests insert function for doublelinked list; using shift""" + adding_to = DoubleLinkList(()) + for i in xrange(40): + adding_to.insert(i) + + assert adding_to.shift() == 0 + + +def test_append_via_len(): + """Tests append method for doublelinked list; using len""" + adding_to = DoubleLinkList(()) + for i in xrange(40): + adding_to.append(i) + + assert len(adding_to) == 40 + + +def test_append_via_pop(): + """Tests append method for doublelinked list; using len""" + adding_to = DoubleLinkList(()) + for i in xrange(40): + adding_to.append(i) + + assert adding_to.pop() == 0 + + +def test_append_via_shift(): + """Tests append method for doublelinked list; using len""" + adding_to = DoubleLinkList(()) + for i in xrange(40): + adding_to.append(i) + + assert adding_to.shift() == 39 + + +def test_pop_1(): + """Fill a DoubleLinkList with (1,2,3), check first pop""" + adding_to = DoubleLinkList(()) + for i in xrange(3): + adding_to.insert(i) + assert adding_to.pop() == 2 + + +def test_pop_2(): + """Fill a DoubleLinkList with (1,2,3), check second pop""" + adding_to = DoubleLinkList(()) + for i in xrange(3): + adding_to.insert(i) + + adding_to.pop() # First action covered by test_pop_1 + assert adding_to.pop() == 1 + + +def test_pop_3(): + """Fill a DoubleLinkList with (1,2,3), check third pop""" + adding_to = DoubleLinkList(()) + for i in xrange(3): + adding_to.insert(i) + + adding_to.pop() # First action covered by test_pop_1 + adding_to.pop() # Second action covered by test_pop_2 + assert adding_to.pop() == 0 + + +def test_pop_4(): + """Fill a DoubleLinkList with (1,2,3), check fourth pop""" + adding_to = DoubleLinkList(()) + for i in xrange(3): + adding_to.insert(i) + + adding_to.pop() # First action covered by test_pop_1 + adding_to.pop() # Second action covered by test_pop_2 + adding_to.pop() + with pytest.raises(IndexError): + adding_to.pop() + + +def test_shift_1(): + """Fill a DoubleLinkList with (1,2,3), check first pop""" + adding_to = DoubleLinkList(()) + for i in xrange(3): + adding_to.insert(i) + assert adding_to.shift() == 0 + + +def test_shift_2(): + """Fill a DoubleLinkList with (1,2,3), check second pop""" + adding_to = DoubleLinkList(()) + for i in xrange(3): + adding_to.insert(i) + + adding_to.shift() # First action covered by test_pop_1 + assert adding_to.shift() == 1 + + +def test_shift_3(): + """Fill a DoubleLinkList with (1,2,3), check third pop""" + adding_to = DoubleLinkList(()) + for i in xrange(3): + adding_to.insert(i) + + adding_to.shift() # First action covered by test_pop_1 + adding_to.shift() # Second action covered by test_pop_2 + assert adding_to.shift() == 2 + + +def test_remove_0(): + """Fill a DoubleLinkList with (1,2,3), check removal of 0""" + adding_to = DoubleLinkList(()) + for i in xrange(3): + adding_to.insert(i) + + adding_to.remove(0) + assert adding_to.pop() == 2 + assert adding_to.shift() == 1 + + +def test_remove_1(): + """Fill a DoubleLinkList with (1,2,3), check removal of 1""" + adding_to = DoubleLinkList(()) + for i in xrange(3): + adding_to.insert(i) + + adding_to.remove(1) + assert adding_to.pop() == 2 + assert adding_to.shift() == 0 + + +def test_remove_2(): + """Fill a DoubleLinkList with (1,2,3), check removal of 3""" + adding_to = DoubleLinkList(()) + for i in xrange(3): + adding_to.insert(i) + + adding_to.remove(2) + assert adding_to.pop() == 1 + assert adding_to.shift() == 0 + + +def test_remove_5(): + """Fill a DoubleLinkList with (1,2,3), check removal of 5; + should result in ValueError""" + adding_to = DoubleLinkList(()) + for i in xrange(3): + adding_to.insert(i) + with pytest.raises(ValueError): + adding_to.remove(5) diff --git a/test_graph.py b/test_graph.py new file mode 100644 index 0000000..5ef2385 --- /dev/null +++ b/test_graph.py @@ -0,0 +1,341 @@ +from __future__ import unicode_literals +import pytest + +from graph import Graph + + +@pytest.fixture() +def graph_empty(): + g = Graph() + return g + + +@pytest.fixture() +def graph_filled(): + g = Graph() + g.graph = { + 'A': {'B': 10}, + 'B': {'A': 5, 'D': 15, 'C': 20}, + 'C': {}, + 'D': {'A': 5}, + 'E': {}, + 'F': {} + } + return g + + +@pytest.fixture() +def graph_filled_for_traversal(): + g = Graph() + g.graph = { + 'A': {'B': 10, 'C': 15}, + 'B': {'D': 15, 'E': 5, 'C': 2}, + 'C': {'F': 50, 'G': 25}, + 'D': {}, + 'E': {'C': 5}, + 'F': {'E': 10}, + 'G': {'F': 20} + } + return g + + +@pytest.fixture() +def graph_filled_for_traversal_neg_edges(): + g = Graph() + g.graph = { + 'A': {'B': -5, 'C': 15}, + 'B': {'D': 15, 'E': 5, 'C': 2}, + 'C': {'F': 50, 'G': -20}, + 'D': {}, + 'E': {'C': 5}, + 'F': {'E': 10}, + 'G': {'F': 20} + } + return g + + +@pytest.fixture() +def graph_filled_for_traversal_neg_edges_loop(): + g = Graph() + g.graph = { + 'A': {'B': -5, 'C': 15}, + 'B': {'D': 15, 'E': 5, 'C': 2}, + 'C': {'F': 50, 'G': -20}, + 'D': {}, + 'E': {'C': -50}, + 'F': {'E': 10}, + 'G': {'F': 20} + } + return g + + +def test_valid_constructor(): + g = Graph() + assert isinstance(g, Graph) + assert isinstance(g.graph, dict) + assert len(g.graph) == 0 and len(g) == 0 + + +def test_invalid_constructor(): + with pytest.raises(TypeError): + Graph(10) + + +def test_add_node_to_empty(graph_empty): + g = graph_empty + new = 'A' + g.add_node(new) + assert new in g + assert isinstance(g[new], dict) and len(g[new]) == 0 + + +def test_add_node_to_filled(graph_filled): + g = graph_filled + new = 'G' + g.add_node(new) + assert new in g + assert isinstance(g[new], dict) + assert len(g[new]) == 0 + + +def test_add_node_to_filled_existing_node(graph_filled): + with pytest.raises(KeyError): + graph_filled.add_node('B') + + +def test_add_node_wrong_type(graph_empty): + with pytest.raises(TypeError): + graph_empty.add_node([1, 2, 3]) + + +def test_add_edge_new_nodes(graph_empty): + g = graph_empty + n1, n2 = 'A', 'B' + g.add_edge(n1, n2, 10) + assert n1 in g and n2 in g + assert n2 in g[n1] + assert g[n1][n2] == 10 + + +def test_add_edge_n2_new(graph_filled): + g = graph_filled + n1, n2 = 'A', 'G' + g.add_edge(n1, n2, 10) + assert n1 in g and n2 in g + assert n2 in g[n1] + assert g[n1][n2] == 10 + + +def test_add_edge_n1_new(graph_filled): + g = graph_filled + n1, n2 = 'G', 'A' + g.add_edge(n1, n2, 10) + assert n1 in g and n2 in g + assert n2 in g[n1] + assert g[n1][n2] == 10 + + +def test_add_edge_n1_n2_exist_with_edges(graph_filled): + g = graph_filled + n1, n2 = 'D', 'A' + g.add_edge(n1, n2, 10) + assert n1 in g and n2 in g + assert n2 in g[n1] + assert g[n1][n2] == 10 + + +def test_add_edge_n1_n2_exist_without_edges(graph_filled): + g = graph_filled + n1, n2 = 'E', 'F' + g.add_edge(n1, n2, 10) + assert n1 in g and n2 in g + assert n2 in g[n1] + assert g[n1][n2] == 10 + + +def test_del_node_exists(graph_filled): + g = graph_filled + g.del_node('A') + assert 'A' not in g + assert 'A' not in g.graph.values() + + +def test_del_node_empty_error(graph_empty): + with pytest.raises(KeyError): + graph_empty.del_node('A') + + +def test_del_edge_exists(graph_filled): + g = graph_filled + n1, n2 = 'B', 'A' + g.del_edge(n1, n2) + assert n1 in g and n2 in g + assert n2 not in g[n1] + + +def test_del_edge_not_exist(graph_filled): + with pytest.raises(KeyError): + graph_filled.del_edge('X', 'Y') + + +def test_nodes_empty(graph_empty): + out = graph_empty.nodes() + assert str(out) == "[]" + assert len(out) == 0 + + +def test_nodes_filled(graph_filled): + out = graph_filled.nodes() + expected_nodes = set(['A', 'B', 'C', 'D', 'E', 'F']) + assert set(out) == expected_nodes + assert len(out) == 6 + + +def test_edges_empty(graph_empty): + out = graph_empty.edges() + assert str(out) == "[]" + assert len(out) == 0 + + +def test_edges_filled(graph_filled): + out = graph_filled.edges() + expected_edges = set([ + ('A', 'B'), ('B', 'A'), ('B', 'D'), ('B', 'C'), ('D', 'A') + ]) + assert set(out) == expected_edges + assert len(out) == 5 + + +def test_host_node_empty(graph_empty): + unexpected_nodes = set([0, 2, 7, 13, 27, 33]) + for node in unexpected_nodes: + assert graph_empty.has_node(node) is False + + +def test_has_node_filled(graph_filled): + expected_nodes = set(['A', 'B', 'C', 'D', 'E', 'F']) + unexpected_nodes = set(['G', 'H', 'I', 'J', 'K', 10]) + for node in expected_nodes: + assert graph_filled.has_node(node) is True + for node in unexpected_nodes: + assert graph_filled.has_node(node) is False + + +def test_neighbors_empty(graph_empty): + with pytest.raises(KeyError): + graph_empty.neighbors('G') + + +def test_neighbors_filled_not_present(graph_filled): + with pytest.raises(KeyError): + graph_filled.neighbors('G') + + +# input, expected output for neighbors in graph_filled +neighbor_params = [ + ('A', {'B': 10}), + ('B', {'A': 5, 'D': 15, 'C': 20}), + ('C', {}), + ('D', {'A': 5}), + ('E', {}), + ('F', {}) +] + + +@pytest.mark.parametrize("input, out", neighbor_params) +def test_neighbors_filled_present(input, out, graph_filled): + assert graph_filled.neighbors(input) == out + + +def test_adjacent_empty(graph_empty): + with pytest.raises(KeyError): + graph_empty.adjacent('A', 'B') + + +def test_adjacent_filled_existing(graph_filled): + expected_edges = set([ + ('A', 'B'), ('B', 'A'), ('B', 'D'), ('B', 'C'), ('D', 'A') + ]) + for a, b in expected_edges: + assert graph_filled.adjacent(a, b) is True + + +def test_adjacent_filled_existing_node_unexisting_edge(graph_filled): + bad_edges = set([('A', 'C'), ('D', 'B'), ('A', 'D')]) + for a, b in bad_edges: + assert graph_filled.adjacent(a, b) is False + + +def test_adjacent_filled_missing_node(graph_filled): + with pytest.raises(KeyError): + graph_filled.adjacent('G', 'H') + + +def test_depth_first_traversal(graph_filled_for_traversal): + level1 = set(['A']) + level2 = set(['B', 'C']) + level3 = set(['D', 'E', 'F', 'G']) + output = graph_filled_for_traversal.depth_first_traversal('A') + assert len(output) == 7 + assert output[0] in level1 + assert output[1] in level2 + assert output[2] in level3 + + +def test_breadth_first_traversal(graph_filled_for_traversal): + level1 = set(['A']) + level2 = set(['B', 'C']) + level3 = set(['D', 'E', 'F', 'G']) + output = graph_filled_for_traversal.breadth_first_traversal('A') + assert len(output) == 7 + assert output[0] in level1 + assert output[1] in level2 + assert output[2] in level2 + assert output[3] in level3 + + +def test_depth_first_traversal_no_arg(graph_filled_for_traversal): + with pytest.raises(TypeError): + graph_filled_for_traversal.depth_first_traversal() + + +def test_breadth_first_traversal_no_arg(graph_filled_for_traversal): + with pytest.raises(TypeError): + graph_filled_for_traversal.breadth_first_traversal() + + +def test_graph_weighted_edges(graph_filled): + assert graph_filled['A']['B'] == 10 + assert graph_filled['B']['A'] == 5 + assert graph_filled['B']['D'] == 15 + assert graph_filled['B']['C'] == 20 + assert graph_filled['D']['A'] == 5 + + +def test_uniform_cost_search(graph_filled_for_traversal): + g = graph_filled_for_traversal + expected = ['A', 'B', 'C', 'G', 'F'] + actual = g.uniform_cost_search('A', 'F') + assert expected == actual + + +def test_bellmanford(graph_filled_for_traversal): + g = graph_filled_for_traversal + expected = ['A', 'B', 'C', 'G', 'F'] + actual = g.bellmanford('A', 'F') + assert expected == actual + + +def test_bellmanford_neg_edges(graph_filled_for_traversal_neg_edges): + g = graph_filled_for_traversal_neg_edges + expected = ['A', 'B', 'C', 'G', 'F'] + actual = g.bellmanford('A', 'F') + assert expected == actual + + +def test_bellmanford_neg_edges_with_loop( + graph_filled_for_traversal_neg_edges_loop): + g = graph_filled_for_traversal_neg_edges_loop + expected = ['A', 'B', 'C', 'G', 'F'] + with pytest.raises(ZeroDivisionError): + g.bellmanford('A', 'F') \ No newline at end of file diff --git a/test_hash.py b/test_hash.py new file mode 100644 index 0000000..5b000f8 --- /dev/null +++ b/test_hash.py @@ -0,0 +1,77 @@ +import pytest + +from hashtable import HashTable + + +@pytest.fixture() +def word_list(): + with open('/usr/share/dict/words') as fh: + words = fh.read() + words = words.split('\n') + return words + + +def test_init_default(): + foo = HashTable() + assert foo.table_size == 8192 + assert len(foo.hashtable) == 8192 + + +def test_init_set_size(): + foo = HashTable(size=4096) + assert foo.table_size == 4096 + assert len(foo.hashtable) == 4096 + + +def test_len(): + foo = HashTable(size=1024) + empty_len = len(foo) + stuff = [('a', 'a'), ('b', 'b'), ('c', 'c')] + more = [('d', 'd'), ('e', 'e')] + foo.hashtable[0].extend(stuff) + foo.hashtable[500].extend(more) + filled_len = len(foo) + assert empty_len == 0 + assert filled_len == 5 + + +def test_set(): + foo = HashTable(size=1024) + foo.set('foo', 'foo') + foo.set('spoofs', 'spoofs') + foo.set('utopia', 'utopia') + assert foo.hashtable[91][0] == ('foo', 'foo') + assert foo.hashtable[91][1] == ('spoofs', 'spoofs') + assert foo.hashtable[885][0] == ('utopia', 'utopia') + + +def test_set_wrong_type(): + foo = HashTable() + with pytest.raises(TypeError): + foo.set(898, [838]) + + +def test_get(): + foo = HashTable(size=1024) + foo.hashtable[91].append(('foo', 'foo')) + foo.hashtable[91].append(('spoofs', 'spoofs')) + foo.hashtable[885].append(('utopia', 'utopia')) + assert foo.get('foo') == 'foo' + assert foo.get('spoofs') == 'spoofs' + assert foo.get('utopia') == 'utopia' + + +def test_get_missing_key(): + foo = HashTable() + with pytest.raises(KeyError): + foo.get('bar') + + +def test_set_and_get_word_list(word_list): + foo = HashTable() + words = word_list + for word in words: + foo.set(word, word) + for word in words: + value = foo.get(word) + assert word == value diff --git a/test_insertion_sort.py b/test_insertion_sort.py new file mode 100644 index 0000000..077a684 --- /dev/null +++ b/test_insertion_sort.py @@ -0,0 +1,28 @@ +from random import shuffle +import pytest + +from insertion_sort import in_sort + + +def test_insertion_sort(): + expected = range(20) + unsorted = expected[:] + shuffle(unsorted) + in_sort(unsorted) + actual = unsorted + assert expected == actual + + +def test_insertion_sort_with_duplicates(): + expected = [1, 3, 3, 6, 7, 8, 8, 8] + unsorted = expected[:] + shuffle(unsorted) + in_sort(unsorted) + actual = unsorted + assert expected == actual + + +def test_insertion_sort_wrong_type(): + with pytest.raises(TypeError): + in_sort('some string') + diff --git a/test_linked_list.py b/test_linked_list.py new file mode 100644 index 0000000..f8d5e40 --- /dev/null +++ b/test_linked_list.py @@ -0,0 +1,69 @@ +from __future__ import unicode_literals +import pytest +import linked_list as ll + + +@pytest.fixture +def base_llist(): + return ll.LinkedList([1, 2, 3]) + + +def test_construct_from_iterable_valid(base_llist): + expected_output = "(1, 2, 3)" + assert base_llist.display() == expected_output + + +def test_construct_from_nested_iterable_valid(): + arg = ([1, 2, 3], 'string') + expected_output = "([1, 2, 3], u'string')" + assert ll.LinkedList(arg).__repr__() == expected_output + + +def test_construct_from_string_valid(): + arg = "string" + expected_output = "(u's', u't', u'r', u'i', u'n', u'g')" + assert ll.LinkedList(arg).__repr__() == expected_output + + +def test_construct_empty_valid(): + expected_output = "()" + assert ll.LinkedList().__repr__() == expected_output + + +def test_construct_from_none_fails(): + with pytest.raises(TypeError): + ll.LinkedList(None) + + +def test_construct_from_single_integer_fails(): + with pytest.raises(TypeError): + ll.LinkedList(2) + + +def test_insert_single_value(base_llist): + base_llist.insert(4) + assert base_llist.__repr__() == "(4, 1, 2, 3)" + + +def test_pop(base_llist): + assert base_llist.pop() == 1 + assert base_llist.__repr__() == "(2, 3)" + + +def test_size(base_llist): + assert base_llist.size() == 3 + + +def test_search_val(base_llist): + searched_node = base_llist.search(2) + assert isinstance(searched_node, ll.Node) + assert searched_node.val == 2 + + +def test_remove_node(base_llist): + base_llist.remove(base_llist.search(2)) + assert base_llist.__repr__() == "(1, 3)" + + +def test_display(base_llist): + assert base_llist.display() == "(1, 2, 3)" diff --git a/test_merge_sort.py b/test_merge_sort.py new file mode 100644 index 0000000..bbd364c --- /dev/null +++ b/test_merge_sort.py @@ -0,0 +1,26 @@ +from random import shuffle +import pytest + +from merge_sort import merge_srt + + +def test_merge_sort(): + expected = range(20) + actual = expected[:] + shuffle(actual) + merge_srt(actual) + assert expected == actual + + +def test_merge_sort_with_duplicates(): + expected = [1, 3, 3, 6, 7, 8, 8, 8] + actual = expected[:] + shuffle(actual) + merge_srt(actual) + assert expected == actual + + +def test_merge_sort_wrong_type(): + with pytest.raises(TypeError): + merge_srt(15) + diff --git a/test_priorityq.py b/test_priorityq.py new file mode 100644 index 0000000..86011ee --- /dev/null +++ b/test_priorityq.py @@ -0,0 +1,239 @@ +from __future__ import unicode_literals +import pytest + +from priorityq import PriorityQ, QNode + + +@pytest.fixture() +def QNode_list(): + QNode_list = [ + QNode(10), + QNode(5, priority=2), + QNode(100, priority=1) + ] + return QNode_list + + +@pytest.fixture() +def base_pqueue(QNode_list): + return PriorityQ(QNode_list) + +valid_instantiation_args = [ + [QNode(1, 0), QNode(1, 1), QNode(2, 2)], + [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], + [(1, 2), (2, 3), (3, 4), (4, 5), (5, 6)], + [QNode(1, 0), QNode(1, 1), 1, 2, 3, 4, 5, 6], + [QNode(1, 0), QNode(1, 1), (1, 2), (2, 3), (3, 4)], + [1, 2, 3, 4, 5, 6, 7, (1, 2), (2, 3), (3, 4), (4, 5)] +] + +invalid_instantiation_args = [ + [(1, 2, 3), (2, 3), (3, 4), (4, 5), (5, 6)], + [(1, 2), (2, 3), (3, 4), (4, 5, 1), (5, 6)] +] + + +@pytest.mark.parametrize("input", valid_instantiation_args) +def test_valid_instantiation_args(input): + tpq = PriorityQ(input) + assert tpq.pop() is not None + + +def test_empty_instantiation_args(): + tpq = PriorityQ() + with pytest.raises(IndexError): + tpq.pop() + + +@pytest.mark.parametrize("input", invalid_instantiation_args) +def test_invalid_instantiation_args(input): + with pytest.raises(TypeError): + tpq = PriorityQ(input) + + +def test_invalid_number_args_priorityq(): + with pytest.raises(TypeError): + tpq = PriorityQ(1, 2) + + +def test_invalid_number_args_qnode(): + with pytest.raises(TypeError): + tpq = QNode(1, 2, 3, 4) + + +def test_QNode_init_no_priority(): + node1 = QNode(10) + assert node1.val == 10 + assert node1.priority is None + + +def test_QNode_init_with_priority(): + node1 = QNode(10, priority=0) + assert node1.val == 10 + assert node1.priority == 0 + + +def test_QNode_order_comparison(): + node1 = QNode(10, order=1) + node2 = QNode(5, order=2) + assert node1 < node2 + + +def test_QNode_priority_comparison(): + node1 = QNode(10, priority=0) + node2 = QNode(10) + assert node1 < node2 + + +def test_QNode_equal_priority_comparison(): + node1 = QNode(10, priority=1, order=1) + node2 = QNode(5, priority=1, order=2) + assert node1 < node2 + + +def test_QNode_equality_comparison(): + node1 = QNode(10, priority=10) + node2 = QNode(10, priority=10) + assert node1 == node2 + + +def test_PriorityQ_init_empty(): + pqueue = PriorityQ() + assert isinstance(pqueue, PriorityQ) + assert len(pqueue) == 0 + + +def test_PriorityQ_init_iterable_no_QNodes(): + pqueue = PriorityQ([10, 9, 8, 7, 6, 5]) + assert len(pqueue) == 6 + assert pqueue[0].val == 10 + assert pqueue[0].priority is None + + +def test_PriorityQ_init_iterable_with_QNodes(QNode_list): + pqueue = PriorityQ(QNode_list) + assert len(pqueue) == 3 + assert pqueue[0].val == 100 + assert pqueue[0].priority == 1 + + +def test_insert_item_not_QNode_to_empty(): + queue = PriorityQ() + queue.insert(50) + assert len(queue) == 1 + assert queue[0].val == 50 + assert queue[0].priority is None + + +def test_insert_item_QNode_to_empty(): + node1 = QNode(10, priority=0) + pqueue = PriorityQ() + pqueue.insert(node1) + assert len(pqueue) == 1 + assert pqueue[0].val == 10 + assert pqueue[0].priority == 0 + + +def test_insert_item_not_QNode_to_filled(base_pqueue): + base_pqueue.insert(500, priority=0) + assert len(base_pqueue) == 4 + assert base_pqueue[0].val == 500 + assert base_pqueue[0].order == 3 + assert base_pqueue[0].priority == 0 + + +def test_insert_QNode_to_filled(base_pqueue): + node1 = QNode(10, priority=0) + base_pqueue.insert(node1) + assert len(base_pqueue) == 4 + assert base_pqueue[0].val == 10 + assert base_pqueue[0].order == 3 + assert base_pqueue[0].priority == 0 + + +def test_pop1(base_pqueue): + top_priority = QNode(9000, priority=0) + length = len(base_pqueue) + base_pqueue.insert(top_priority) + assert base_pqueue.pop() == top_priority.val + assert len(base_pqueue) == length + + +def test_pop2(base_pqueue): + top_priority = QNode(9000, priority=0) + length = len(base_pqueue) + base_pqueue.insert(top_priority) + base_pqueue.pop() + assert base_pqueue.pop() == 100 + + +def test_pop2(base_pqueue): + top_priority = QNode(9000, priority=0) + length = len(base_pqueue) + base_pqueue.insert(top_priority) + base_pqueue.pop() + base_pqueue.pop() + assert base_pqueue.pop() == 5 + + +def test_pop3(base_pqueue): + top_priority = QNode(9000, priority=0) + length = len(base_pqueue) + base_pqueue.insert(top_priority) + base_pqueue.pop() + base_pqueue.pop() + base_pqueue.pop() + assert base_pqueue.pop() == 10 + + +def test_pop4(base_pqueue): + top_priority = QNode(9000, priority=0) + length = len(base_pqueue) + base_pqueue.insert(top_priority) + base_pqueue.pop() + base_pqueue.pop() + base_pqueue.pop() + base_pqueue.pop() + with pytest.raises(IndexError): + base_pqueue.pop() + + +def test_peek1(base_pqueue): + top_priority = QNode(9000, priority=0) + base_pqueue.insert(top_priority) + assert base_pqueue.peek() == top_priority.val + assert base_pqueue[0] is top_priority + + +def test_peek2(base_pqueue): + top_priority = QNode(9000, priority=0) + base_pqueue.insert(top_priority) + base_pqueue.pop() + assert base_pqueue.peek() == base_pqueue.pop() + + +def test_peek3(base_pqueue): + top_priority = QNode(9000, priority=0) + base_pqueue.insert(top_priority) + base_pqueue.pop() + base_pqueue.pop() + assert base_pqueue.peek() == base_pqueue.pop() + + +def test_peek4(base_pqueue): + top_priority = QNode(9000, priority=0) + base_pqueue.insert(top_priority) + base_pqueue.pop() + base_pqueue.pop() + assert base_pqueue.peek() == base_pqueue.pop() + + +def test_peek5(base_pqueue): + top_priority = QNode(9000, priority=0) + base_pqueue.insert(top_priority) + base_pqueue.pop() + base_pqueue.pop() + base_pqueue.pop() + base_pqueue.pop() + with pytest.raises(IndexError): + base_pqueue.peek() diff --git a/test_queue.py b/test_queue.py new file mode 100644 index 0000000..f0a01af --- /dev/null +++ b/test_queue.py @@ -0,0 +1,62 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals +import pytest + +from queue import Queue + +# (Input, expected) for well constructed instantiation arguments, +# and one subsequent dequeue +valid_constructor_args_dequeue = [ + ([1, 2, 3], 1), + ([[1, 2, 3], "string"], [1, 2, 3]), + ("string", 's') +] + +# Invalid instantiation arguments +invalid_constructor_args = [ + (None), + (1), + (4.5235) +] + + +@pytest.mark.parametrize("input,deque", valid_constructor_args_dequeue) +def test_valid_contructor(input, deque): + """Test valid constructor using by dequeuing after instantiation""" + assert Queue(input).dequeue() == deque + + +def test_empty_constructor(): + """Test valid empty constructor via dequeuing after instantiation""" + with pytest.raises(IndexError): + Queue(()).dequeue() + + +@pytest.mark.parametrize("input", invalid_constructor_args) +def test_invalid_constructor(input): + """Test invalid constuctor arguments""" + with pytest.raises(TypeError): + Queue(input) + + +def test_enqueue(): + """Fill an empty contructor argument and assert len, deque)""" + filling_this = Queue(()) + for i in xrange(40): + filling_this.enqueue(i) + assert len(filling_this) == 40 + assert filling_this.dequeue() == 0 + + +def test_dequeue(): + q = Queue([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]) + for x in range(5): + q.dequeue() + assert q.dequeue() == 6 + assert q.other.head.val == 7 + assert q.other.tail.val == 10 + assert len(q) == 4 + while len(q): + q.dequeue() + assert q.other.head is None + assert q.other.tail is None diff --git a/test_quick_sort.py b/test_quick_sort.py new file mode 100644 index 0000000..fe70a40 --- /dev/null +++ b/test_quick_sort.py @@ -0,0 +1,40 @@ +from random import shuffle +import pytest + +from quick_sort import quick_srt + + +def test_quick_srt(): + expected = range(20) + actual = expected[:] + shuffle(actual) + quick_srt(actual) + assert expected == actual + + +def test_quick_srt_with_duplicates(): + expected = [1, 3, 3, 6, 7, 8, 8, 8] + actual = expected[:] + shuffle(actual) + quick_srt(actual) + assert expected == actual + + +def test_quick_srt_with_zero_items(): + expected = [] + actual = [] + quick_srt(actual) + assert expected == actual + + +def test_quick_srt_with_one_item(): + expected = [1] + actual = [1] + quick_srt(actual) + assert expected == actual + + +def test_quick_sort_wrong_type(): + with pytest.raises(TypeError): + quick_srt(15) + diff --git a/test_stack.py b/test_stack.py new file mode 100644 index 0000000..ff4e483 --- /dev/null +++ b/test_stack.py @@ -0,0 +1,57 @@ +from __future__ import unicode_literals +import pytest + +from stack import Stack + + +@pytest.fixture +def base_stack(): + return Stack([1, 2, 3]) + + +def test_construct_from_iterable_valid(base_stack): + expected_output = "(1, 2, 3)" + assert base_stack.__repr__() == expected_output + + +def test_construct_from_nested_iterable_valid(): + arg = ([1, 2, 3], 'string') + expected_output = "([1, 2, 3], u'string')" + assert Stack(arg).__repr__() == expected_output + + +def test_construct_from_string_valid(): + arg = "string" + expected_output = "(u's', u't', u'r', u'i', u'n', u'g')" + assert Stack(arg).__repr__() == expected_output + + +def test_construct_empty_valid(): + expected_output = "()" + assert Stack().__repr__() == expected_output + + +def test_construct_from_none_fails(): + with pytest.raises(TypeError): + Stack(None) + + +def test_construct_from_single_integer_fails(): + with pytest.raises(TypeError): + Stack(2) + + +def test_push(base_stack): + base_stack.push(4) + assert base_stack.__repr__() == "(4, 1, 2, 3)" + + +def test_pop(base_stack): + assert base_stack.pop() == 1 + assert base_stack.__repr__() == "(2, 3)" + + +def test_pop_after_multi_push(base_stack): + for x in range(10): + base_stack.push(x) + assert base_stack.pop() == 9