diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..3f621ab --- /dev/null +++ b/.travis.yml @@ -0,0 +1,8 @@ +language: python +python: + - "2.7" + - "3.6" +install: + - pip install . + - pip install -r requirements.txt +script: py.test \ No newline at end of file diff --git a/README.md b/README.md index 87c500a..e20120f 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,315 @@ -# data-structures -Data-Structures +# Data-Structures + +[![Build Status](https://travis-ci.org/markreynoso/data-structures.svg?branch=bst_traversals)](https://travis-ci.org/markreynoso/data-structures) + +Where a variety of data-structures found in python are being explored, such as: +* A simple class LinkedList for building linked lists as well as a subclass Stack to implement a stack. + +## Authors + +ChaiChaitanya Narukulla and Mark Reynoso created this code base as a project for Python 401 at Code Fellows Seattle. + +### Requirements for testing and development + +In order to create a development and testing environment, you will need the following: + +``` +python2, python3, ipython, pytest, pytest-watch, pytest-cov, tox +``` + +``` +To install environment: + +pip install -e .[testing][development] +``` + +### Linkded List Class Usage + +``` +To create an instance of a the LinkedList() class contained in package, from python: + +new = LinkedList() *you may choose and optional parameter of a single value or an iterable of one of the following: a tuple, a list, or a string.* + +LinkedList() contains the following methods: +* _push(val) (O1)_ - will insert the value ‘val’ at the head of the list. +* _pop() (O1)_ - will pop the first value off the head of the list and return it. Raises an exception with an appropriate message if there are no values to return. +* _size() (01)_ - will return the length of the list. +* _search(val) (On)_ - will return the node containing ‘val’ in the list, if present, else None. +* _remove(node) (On)_ - will remove the given node from the list, wherever it might be (node must be an item in the list). If the node is not in the list, it will raise an exception with an appropriate message. +* _display() (O1)_ - will return a unicode string representing the list as if it were a Python tuple literal: “(12, ‘sam’, 37, ‘tango’)” + +To access any contained methods: +new.push(val) +new.pop() +new.size() +new.search(val) +new.remove(node) +new.display() + +Additionally, LinkedList() will interact with these built-in Python functions: + +* _len(new)_ - returns the size of the list. +* _print(new)_ - returns what the display() method returns. + +``` + +### Stack Class Usage + +``` +To create an instance of a Stack() class contained in package, from python: + +new = Stack() *you may choose and optional parameter of a single value or an iterable of one of the following: a tuple, a list, or a string.* + +Stack() contains the following methods: +* _push(val) (O1)_ - will insert the value ‘val’ at the head of the stack. +* _pop() (O1)_ - will pop the first value off the head of the stack and return it. Raises an exception with an appropriate message if there are no values to return. + +To access any contained methods: +new.push(val) +new.pop() + +Additionally, Stack() will interact with these built-in Python functions: + +* _len(new)_ - returns the size of the list. + +``` + +### Doubly Linked List Class Usage + +``` +To create an instance of a Dll() class contained in package, from python: + +new = Dll() *you may not call Dll() with any parameters.* + +Dll() contains the following methods: +* _push(val) (O1)_ - will insert the value ‘val’ at the head of the list. +* _append(val) (O1)_ - will insert the value ‘val’ at the tail of the list. +* _pop() (O1)_ - will pop the first value off the head of the stack and return it. Raises an exception with an appropriate message if there are no values to return. +* _shift() (O1)_ - will remove the last value off the tail of the list and return it. Raises an exception with an appropriate message if there are no values to return. +* _remove(node) (On)_ - will remove the given node from the list, wherever it might be (node must be an item in the list). If the node is not in the list, it will raise an exception with an appropriate message. + +To access any contained methods: +new.push(val) +new.append(val) +new.pop() +new.shift() +new.remove() + +Additionally, Dll() will interact with these built-in Python functions: + +* _len(new)_ - returns the size of the list. + +``` + +### Queue Class Usage + +``` +To create an instance of a Queue() class contained in package, from python: + +new = Queue() *you may not call Queue() with any parameters.* + +Queue() contains the following methods: +* _enqueue(val) (O1)_ - will insert the value ‘val’ at the end of the queue. +* _dequeue() (O1)_ - will remove the last value off the front of the queue and return it. Raises an exception with an appropriate message if there are no values to return. +* _peek() (O1)_ - shows the value of the node at the end of the queue. +* _size() (O1)_ - displays the number of node in the queue. + +To access any contained methods: +new.enqueue(val) +new.dequeue() +new.peek() +new.size() + +Additionally, Queue will interact with these built-in Python functions: + +* _len(new)_ - returns the size of the list. + +``` + +### Deque Class Usage + +``` +To create an instance of a Deque() class contained in package, from python: + +new = Deque() *you may not call Deque() with any parameters.* + +Deque() contains the following methods: +* _append(val) (O1)_ - will insert the value ‘val’ at the end of the deque. +* _appendleft() (O1)_ - will insert a value to the front of the deque. +*_pop() (01)_ - removes the first val from the end of the deque. +*_popleft() (01)_ - removes the first val from the front of the deque. +* _peek() (O1)_ - shows the value of the item at the end of the deque. +* _peekleft() (O1)_ - shows the value of the item at the font of the deque. +* _size() (O1)_ - displays the number items in the deque. + +To access any contained methods: +new.append(val) +new.appendleft(val) +new.pop() +new.popleft() +new.peek() +new.peekleft() +new.size() + +``` + +### Binaary Heap (min-heap) Class Usage + +``` +To create an instance of a Binheap() class contained in package, from python: + +new = Binheap() *you may call Binheap() with any uniqe number or iterable of unique numbers.* + +Binheap() contains the following methods: +* _push(val) (Olog(n))_ - will insert the value ‘val’ at the end of the heap. +*_pop() (0log(n))_ - removes the first val from the end of the heap. + +To access any contained methods: +new.push(val) +new.pop() + +``` + +### Priorityq (Priorityq) Class Usage + +``` +To create an instance of a Priorityq() class contained in package, from python: + +new = Priorityq() *you may call Priorityq() with any uniqe number or iterable of unique numbers.* + +Priorityq() contains the following methods: +*_insert(val,priority) (O1)_ - will insert the value ‘val’ at the end of the heap. +*_pop() (01)_ - removes the higest priority and returns the higest priority. +*_peek() (01)_ - returns the higest priority. + +To access any contained methods: +new.insert(val) +new.pop() +new.peek() + +``` + +### Graph Class (weighted) Usage + +``` +To create an instance of a Graph() class contained in package, from python: + +new = Graph() *you may not call Graph() with any parameters.* + +Graph() contains the following methods: +* _nodes() (O1)_ - will return a list of all nodes in the graph. +* _edges() (On2)_ - will return a dictionary of edges in graph with weights. +*_add_node(val) (01)_ - adds a new node to the graph. +*_add_edge(val1, val2, weight) (0n)_ - add a new edge to nodes in the graph. +* _del_node(val) (On2)_ - delete node from graph. +* _del_edge(val1, val2) (On)_ - delete edge between two nodes. +* _has_node(val) (O1)_ - returns true if node exists and false if not. +* _neighbors(val) (On2)_ - return a list of nodes with edges to input node. +* _adjacent(val1, val2) (On)_ - return true if edge exists between two inputs, + otherwise false. +* _depth_first_traversal(start_val) (Ologn)_ - will return full visited path of traversal completed. +* _breadth_first_traversal(start_val)(Ologn)_ - will return full visited path of traversal completed. + +To access any contained methods: +new.nodes() +new.edges() +new.add_node(val) +new.add_edge(val1, val2, weight) +new.del_node(val) +new.del_edge(val1, val2) +new.has_node(val) +new.neighbors(val) +new.adjacent(val1, val2) +new.depth_first_traversal(start_val) +new.breadth_first_traversal(start_val) + +``` + +### Shortest Distance Problem + +``` +The problem of the traveling salesperson finding the shortest distance between points can be solved in a variety of ways. We have used Dijkstra's Algorith as our first method to a solution. + +_Dijkstra's Algorithm_ (On) - finds the shortest path exploring all edges of a graph from it's starting point. By comparing the distance or weight of the edges from the start to each child as it traverses the graph it is able to calculate the shortest route by saving the parent path which will have the lowest cost from start to finish. + +To use this function: +dijkstra(graph, start, end) + +``` + +### Self-Balancing Binary Search Tree + +``` +Tree will maintain a balance of no more that 1 or -1 and will automatically rebalance on any insert or delete into the tree using the methods described below. + +To create an instance if the binary search tree, Bst(), from python: + +new = Bst() *you may not call Bst() no parameters or with an iterable item containing unique integers.* + +Bst() contains the following methods: +* _insert(val) (Olog(n))_ - will insert the value ‘val’ in the appropriate position in the tree. +* _search(val) (0log(n))_ - will find a given value in the tree, if not in tree will return none. +* _size() (O(1))_ - will return the size of the tree. +* _depth(root) (0(n))_ - returns the depth of the tree. +* _contains(val) (Olog(n))_ - will return true or false if value is in tree. +*_ balance() (0(n))_ - returns the depth of the left minus the right side of the tree as an integer. +* _in_order() (O(n))_ - returns a generator the entire tree in order of lowest to highest value. +* _pre_order() (0(n)(log(n)))_ - returns a generator of parent followed by children from left side to right side. +* _post_order() (O(n)log(n)))_ - returns a generator of children followed by parent from left side to right side. +*_ breadth_first() (0(n))_ - returns generator of tree ordered from root one level at a time. +*_ delete(val) (0log(n))_ - delete a node with given val. + +To access any contained methods: +new.insert(val) +new.search(val) +new.size() +new.depth(root) +new.contains(val) +new.balance() +new.in_order() +new.pre_order() +new.post_order() +new.breadth_first() +new.delete(val) +``` + +### Hash Table + +``` +Using a naive hash and slightly more complex hash with Horner's Rule, HashTable() will store based on the supplied key, which must be a string. By default, HashTable() uses the naive hash method, but can be changed on initialization. + +To create an instance if the hash table, HashTable(), from python: + +new = HashTable() *you may initiate the table with any size, must be integer, as the first arguement in the function. You may also pass in an alternative hashing method. A naive hash will be used my defaul, but you may also choose `horner_hash` as the second arguement on initialization, or any hashing function you choose to import. For example: HashTable(100000, horner_hash).* + +HashTable() contains the following methods: +* _set(key, val) (O(n))_ - sets a value at a given key (must me string) in the table, if key already has value it will be set to new value. +* _get(key) (0(n))_ - retrieve the value of the item with the given key. +* _ _hash(key) (O(n))_ - hashes key and returns an integer between 0 and the size of the table. + +To access any contained methods: +new.set(key, val) +new.get(key) +``` + +### Trie + +``` +In order to efficiently store words, Trie() will provide a class by which nodes are only inserted if unshared by anthor word. All words share previous characters when possible. + +To create an instance if the trie, Trie(), from python: + +new = Trie() *you may not initiate the trie with any values.* + +Trie() contains the following methods: +* _insert(string) (O log(n))_ - inserts a new node for every letter in string (input must be word) if not able to share with another word. +* _contains(string) (0 log(n))_ - returns True if word is in trie and False if not. +* _size() (O(1))_ - retruns the number of words in the trie. +* _remove(string) (O log(n))_ - removes word from trie and raises error if word not in trie. + +To access any contained methods: +new.insert(string) +new.contains(string) +new.size() +new.remove(string) +``` \ No newline at end of file diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..4f44f7d --- /dev/null +++ b/requirements.txt @@ -0,0 +1,29 @@ +appnope==0.1.0 +argh==0.26.2 +colorama==0.3.9 +coverage==4.4.2 +-e git+https://github.com/markreynoso/data-structures.git@094d8f6f7f5c7e9af06a5e4442a872acbe11c94f#egg=data_structures +decorator==4.1.2 +docopt==0.6.2 +ipython-genutils==0.2.0 +jedi==0.11.0 +parso==0.1.0 +pathtools==0.1.2 +pexpect==4.3.0 +pickleshare==0.7.4 +pluggy==0.5.2 +prompt-toolkit==1.0.15 +ptyprocess==0.5.2 +py==1.5.2 +Pygments==2.2.0 +pytest==3.2.5 +pytest-cov==2.5.1 +pytest-watch==4.1.0 +PyYAML==3.12 +simplegeneric==0.8.1 +six==1.11.0 +tox==2.9.1 +traitlets==4.3.2 +virtualenv==15.1.0 +watchdog==0.8.3 +wcwidth==0.1.7 diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..b86eb54 --- /dev/null +++ b/setup.py @@ -0,0 +1,15 @@ +from setuptools import setup + +setup( + name='data-structures', + description='A package for building and running the data-structures module', + package_dir={'': 'src'}, + author='Mark and chai', + author_email='chaitanyanarukulla@gmail.com mreynoso@spu.edu', + py_modules=['linked_list', 'binheap'], + install_requires=[], + extras_require={ + 'testing': ['pytest', 'pytest-cov', 'pytest-watch', 'tox'], + 'development': ['ipython'] + }, +) diff --git a/src/binheap.py b/src/binheap.py new file mode 100644 index 0000000..88b09ed --- /dev/null +++ b/src/binheap.py @@ -0,0 +1,84 @@ +"""Binary heap class.""" + + +class Binheap(object): + """Iniitialize the class node.""" + + def __init__(self, iterable=None): + """Create a new min heap.""" + self.heaplist = [] + self._size = 0 + if isinstance(iterable, (list, tuple)): + for item in iterable: + self.push(item) + elif iterable is not None: + raise TypeError('You must Iniitialize with ' + 'an iterable or no value at all.') + + def push(self, val): + """Push a value to the end of heap and sort up.""" + if type(val) == int or type(val) == float: + self.heaplist.append(val) + self._size += 1 + sort_up = True + idx = self._size - 1 + while sort_up: + if idx > 0: + if idx % 2 == 0: + parent = (idx - 2) // 2 + else: + parent = (idx - 1) // 2 + if val < self.heaplist[parent]: + self.heaplist[idx] = self.heaplist[parent] + self.heaplist[parent] = val + idx = parent + else: + sort_up = False + else: + sort_up = False + else: + raise ValueError('You must input numbers only.') + + def pop(self): + """Remove first value in heap and sort down.""" + if self._size == 0: + raise IndexError('There are no values to pop.') + elif self._size == 1: + self._size -= 1 + return self.heaplist.pop() + elif self._size > 1: + pop_val = self.heaplist[0] + self.heaplist[0] = self.heaplist.pop() + self._size -= 1 + idx = 0 + sort_down = True + while sort_down: + l_child = idx * 2 + 1 + r_child = idx * 2 + 2 + if r_child <= self._size - 1 and l_child <= self._size - 1: + if self.heaplist[l_child] >= self.heaplist[r_child]: + if self.heaplist[r_child] < self.heaplist[idx]: + self.heaplist[r_child], self.heaplist[idx] =\ + self.heaplist[idx], self.heaplist[r_child] + idx = r_child + else: + sort_down = False + elif self.heaplist[l_child] < self.heaplist[r_child]: + if self.heaplist[l_child] < self.heaplist[idx]: + self.heaplist[idx], self.heaplist[l_child] =\ + self.heaplist[l_child], self.heaplist[idx] + idx = l_child + else: + sort_down = False + else: + sort_down = False + elif l_child == self._size - 1: + if self.heaplist[l_child] < self.heaplist[idx]: + self.heaplist[idx], self.heaplist[l_child] =\ + self.heaplist[l_child], self.heaplist[idx] + sort_down = False + else: + sort_down = False + else: + sort_down = False + return pop_val diff --git a/src/bst.py b/src/bst.py new file mode 100644 index 0000000..dc5ce3e --- /dev/null +++ b/src/bst.py @@ -0,0 +1,390 @@ +"""Binary Search Tree.""" + + +class Node(object): + """Node object for bst.""" + + def __init__(self, data, lc=None, rc=None, mom=None): + """Initialize a new node.""" + self.data = data + self.left = lc + self.right = rc + self.parent = mom + + +class Bst(object): + """Binary Search Tree class.""" + + def __init__(self, iterable=None): + """Initiate a new instance of bst.""" + self.root = None + self._size = 0 + if isinstance(iterable, (list, tuple)): + for item in iterable: + self.insert(item) + + def insert(self, val): + """Insert a value into bst.""" + if not isinstance(val, (int, float)): + raise ValueError('Sorry, I only take numbers right now.') + if self.root is None: + self.root = Node(val) + self._size += 1 + return + elif val == self.root.data: + raise ValueError('This node already exists.') + current = self.root + while current: + if val == current.data: + raise ValueError('This node already exists.') + elif val < current.data: + if current.left: + current = current.left + else: + current.left = Node(val) + current.left.parent = current + self._size += 1 + self._check_balance(current.left) + break + elif val > current.data: + if current.right: + current = current.right + else: + current.right = Node(val) + current.right.parent = current + self._size += 1 + self._check_balance(current.right) + break + + def search(self, val): + """Search bst for val and return node of this val, else none.""" + if self.root is None or not isinstance(val, (int, float)): + return + current = self.root + while current: + if val == current.data: + return current + elif val < current.data: + current = current.left + elif val > current.data: + current = current.right + + def size(self): + """Return size of bst.""" + return self._size + + def depth(self, root): + """Return total number of levels in bst.""" + if root is None: + return 0 + elif root == self.root and not root.left and not root.right: + return 0 + elif not root.left and not root.right: + return 1 + elif root.left and not root.right: + return self.depth(root.left) + 1 + elif root.right and not root.left: + return self.depth(root.right) + 1 + else: + return max(self.depth(root.left), self.depth(root.right)) + 1 + + def contains(self, val): + """Search for val in tree and return boolean.""" + if self.search(val) is not None: + return True + else: + return False + + def balance(self, node): + """Return integer indicating balance of tree.""" + if node is None: + return 'There are no nodes in this tree.' + if not node.left and not node.right: + return 0 + else: + return self.depth(node.left) - self.depth(node.right) + + def breadth_first(self): + """Return generator of breadth first search.""" + if self.root is None: + raise ValueError('There are no nodes in this tree.') + bfs = [self.root] + while bfs: + current = bfs.pop(0) + if current.left: + bfs.append(current.left) + if current.right: + bfs.append(current.right) + yield current.data + + def in_order(self): + """Return generator of in order search.""" + if self.root is None: + raise ValueError('There are no nodes in this tree.') + current = self.root + ios = [] + while current or ios: + if current: + ios.append(current) + current = current.left + else: + current = ios.pop() + yield current.data + current = current.right + + def pre_order(self): + """Return generator of pre order search.""" + if self.root is None: + raise ValueError('There are no nodes in this tree.') + current = self.root + ios = [] + while current or ios: + if current: + yield current.data + if current.right: + ios.append(current.right) + current = current.left + else: + current = ios.pop() + + def post_order(self): + """Return generator of post order wearch.""" + if self.root is None: + raise ValueError('There are no nodes in this tree.') + current = self.root + child = None + ios = [] + while current or ios: + if current: + ios.append(current) + current = current.left + else: + if ios[-1].right and ios[-1].right is not child: + current = ios[-1].right + else: + child = ios.pop() + yield child.data + + def delete(self, val): + """Delete node with given val.""" + if self.root is None or not isinstance(val, (int, float)): + return + on_deck = self.search(val) + if not on_deck.left and not on_deck.right: + self._delete_no_children(on_deck) + elif on_deck.left and not on_deck.right: + self._delete_one_child(on_deck) + elif on_deck.right and not on_deck.left: + self._delete_one_child(on_deck) + elif on_deck.left and on_deck.right: + self._delete_two_children(on_deck) + + def _delete_no_children(self, on_deck): + """Delete node with no children.""" + self._size -= 1 + parent = on_deck.parent + if not on_deck.parent: + self.root = None + elif on_deck.parent.data < on_deck.data: + on_deck.parent.right = None + self._check_balance(parent) + elif on_deck.parent.data > on_deck.data: + on_deck.parent.left = None + self._check_balance(parent) + + def _delete_one_child(self, on_deck): + """Delete node with only one child.""" + self._size -= 1 + if on_deck == self.root: + if self.root.right: + self.root = self.root.right + self.root.right = on_deck.right + self.root.left = on_deck.left + else: + self.root = self.root.left + self.root.right = on_deck.right + self.root.left = on_deck.left + self.root.parent is None + self._check_balance(self.root) + elif on_deck.parent.data < on_deck.data: + if on_deck.left: + on_deck.parent.right = on_deck.left + on_deck.left.parent = on_deck.parent + elif on_deck.right: + on_deck.parent.right = on_deck.right + on_deck.right.parent = on_deck.parent + self._check_balance(on_deck.parent) + else: + if on_deck.left: + on_deck.parent.left = on_deck.left + on_deck.left.parent = on_deck.parent + elif on_deck.right: + on_deck.parent.left = on_deck.right + on_deck.right.parent = on_deck.parent + self._check_balance(on_deck.parent) + + def _delete_two_children(self, on_deck): + """Delete node with two children.""" + self._size -= 1 + current = on_deck.right + while current: + if current.left: + current = current.left + else: + break + if current.parent == on_deck: + current.parent = on_deck.parent + current.left = on_deck.left + if current.left: + current.left.parent = current + if current.parent: + if current.parent.data < current.data: + current.parent.right = current + else: + current.parent.left = current + else: + self.root = current + elif current.right: + current.right.parent = current.parent + current.parent.left = current.right + current.parent = on_deck.parent + current.right = on_deck.right + current.left = on_deck.left + current.left.parent = current + current.right.parent = current + if current.parent: + if current.parent.data < current.data: + current.parent.right = current + else: + current.parent.left = current + else: + self.root = current + else: + current.parent.left = None + current.parent = on_deck.parent + current.right = on_deck.right + current.left = on_deck.left + current.left.parent = current + current.right.parent = current + if current.parent: + if current.parent.data < current.data: + current.parent.right = current + else: + current.parent.left = current + else: + self.root = current + self._check_balance(current) + + def _check_balance(self, node): + """Check parent nodes for balance on insert or delete.""" + check = node + while check is not None: + if self.balance(check) > 1: + if self.balance(check.left) >= 0: + self._right_rotation(check) + else: + self._left_rotation(check.left) + self._right_rotation(check) + elif self.balance(check) < -1: + if self.balance(check.right) <= 0: + self._left_rotation(check) + else: + self._right_rotation(check.right) + self._left_rotation(check) + else: + check = check.parent + + def _right_rotation(self, node): + """Rotate node to the right.""" + swing = node.left + + swing.parent = node.parent + node.left = swing.right + if swing.right: + swing.right.parent = node + swing.right = node + if node.right: + node.left.parent = node + if node.parent: + if node.parent.data > swing.data: + node.parent.left = swing + else: + node.parent.right = swing + node.parent = swing + if swing.parent is None: + self.root = swing + + def _left_rotation(self, node): + """Rotate node to the left.""" + swing = node.right + + swing.parent = node.parent + node.right = swing.left + if swing.left: + swing.left.parent = node + swing.left = node + if node.right: + node.right.parent = node + if node.parent: + if node.parent.data > swing.data: + node.parent.left = swing + else: + node.parent.right = swing + node.parent = swing + if swing.parent is None: + self.root = swing + + +if __name__ == '__main__': # pragma: no cover + import timeit + + insert_time_ub = Bst() + num = (x for x in range(1000)) + a = timeit.timeit('insert_time_ub.insert(next(num))', + setup='from __main__ import insert_time_ub, num', + number=1000) + search_time_ub = Bst() + for i in range(100): + search_time_ub.insert(i) + b = timeit.timeit('search_time_ub.search(99)', + setup='from __main__ import search_time_ub', + number=1000) + c = timeit.timeit('search_time_ub.search(0)', + setup='from __main__ import search_time_ub', + number=1000) + insert_time_b = Bst() + + def insert_time(val): + """.""" + if (500 + val) % 2 == 0: + insert_time_b.insert(500 + val) + else: + insert_time_b.insert(500 - val) + + num_b = (x for x in range(1000)) + d = timeit.timeit('insert_time(next(num_b))', + setup='from __main__ import insert_time, num_b', + number=1000) + + search_time_b = Bst() + for i in range(1000): + if (500 + i) % 2 == 0: + search_time_b.insert(500 + i) + else: + search_time_b.insert(500 - i) + e = timeit.timeit('search_time_b.search(999)', + setup='from __main__ import search_time_b', + number=1000) + f = timeit.timeit('search_time_b.search(500)', + setup='from __main__ import search_time_b', + number=1000) + + print('The following time relates to worst case insert.') + print('Insert unbalanced: {}'.format(a)) + print('Insert balanced: {}'.format(d)) + print('\nThe following time relates to worst case search.') + print('Search unbalanced leaf: {}'.format(b)) + print('Search balanced leaf: {}'.format(e)) + print('\nThe following time relates to best base search.') + print('Search unbalanced head: {}'.format(c)) + print('Search balanced head: {}'.format(f)) diff --git a/src/conftest.py b/src/conftest.py new file mode 100644 index 0000000..ca6d44b --- /dev/null +++ b/src/conftest.py @@ -0,0 +1,84 @@ +"""Fixutres for data structures.""" +import pytest + + +@pytest.fixture +def ebh(): + """Initialize an empty binary heap.""" + from binheap import Binheap + return Binheap() + + +@pytest.fixture +def pq(): + """Initialize a empty pq.""" + from priorityq import Priorityq + return Priorityq() + + +@pytest.fixture +def g(): + """Initialize a empty pq.""" + from graph import Graph + return Graph() + + +@pytest.fixture +def bst(): + """Initialize empty bst for tests.""" + from bst import Bst + return Bst() + + +@pytest.fixture +def bst_full(): + """Create bst will values for tests.""" + from bst import Bst + new = Bst() + new.insert(3254) + new.insert(908543) + new.insert(58490543) + return new + + +@pytest.fixture +def bst_big(): + """Create bst will values for tests.""" + from bst import Bst + new = Bst() + new.insert(50) + new.insert(40) + new.insert(68) + new.insert(10) + new.insert(110) + new.insert(1) + new.insert(500) + new.insert(18) + new.insert(80) + new.insert(5000) + return new + + +@pytest.fixture +def trie(): + """Create a new Trie.""" + from trie import Trie + return Trie() + + +@pytest.fixture +def trie_10_words(): + """Create a new Trie.""" + from trie import Trie + new = Trie() + new.insert('Christmas') + new.insert('Lights') + new.insert('Carol') + new.insert('Stocking') + new.insert('Present') + new.insert('Tree') + new.insert('Cookie') + new.insert('Spirit') + new.insert('Party') + new.insert('Sweater') + return new diff --git a/src/doubly_linked_list.py b/src/doubly_linked_list.py new file mode 100644 index 0000000..48b03dd --- /dev/null +++ b/src/doubly_linked_list.py @@ -0,0 +1,121 @@ +"""Create a new instance of a doubly linked list.""" + + +class Node(object): + """Create new instance of Node class.""" + + def __init__(self, data=None, prve=None, next=None, tail=None): + """Initiate new Node with no defined values.""" + self.data = data + self.next = next + self.prve = prve + + def get_prve(self): + """Get previous node.""" + return self.prve + + def get_data(self): + """Get data of current node.""" + return self.data + + def get_next(self): + """Get next node.""" + return self.next + + def set_next(self, new_next): + """Set next node.""" + self.next = new_next + + def set_prve(self, new_prve): + """Set previous node.""" + self.prve = new_prve + + +class Dll(object): + """Create instance of doubly linked link class.""" + + def __init__(self): + """Initialize doubly linked list with no default values.""" + self.head = None + self.tail = None + self._size = 0 + + def push(self, val): + """Push value to top of list.""" + new_node = Node(val) + if self._size < 1: + self.head = new_node + self.tail = new_node + else: + new_node.set_next(self.head) + self.head.set_prve(new_node) + self.head = new_node + self._size += 1 + + def append(self, val): + """Append value to tail of list.""" + new_node = Node(val) + if self._size < 1: + self.head = new_node + self.tail = new_node + else: + new_node.set_prve(self.tail) + self.tail.set_next(new_node) + self.tail = new_node + self._size += 1 + + def pop(self): + """Remove current head of list.""" + current = self.head + if self.head is None: + raise IndexError('This is an empty list. No values to pop.') + elif self._size == 1: + self.tail = None + self.head = None + self._size -= 1 + elif self._size > 1: + self.head = current.get_next() + self.head.set_prve(None) + self._size -= 1 + return current.data + + def shift(self): + """Remove current tail of list.""" + current = self.tail + if self.tail is None: + raise IndexError('This is an empty list. No values to shift.') + elif self._size == 1: + self.tail = None + self.head = None + self._size -= 1 + elif self._size > 1: + self.tail = current.get_prve() + self.tail.set_next(None) + self._size -= 1 + return current.data + + def remove(self, val): + """Remove inputted value.""" + current = self.head + while current: + if current.get_data() == val: + if current == self.head: + self.pop() + return None + if current == self.tail: + self.shift() + return None + else: + npn = current.get_prve() + nnn = current.get_next() + npn.set_next(nnn) + nnn.set_prve(npn) + self._size -= 1 + return None + current = current.get_next() + else: + raise ValueError('Your node does not exist in this linked list.') + + def __len__(self): + """Display size of list.""" + return self._size diff --git a/src/graph.py b/src/graph.py new file mode 100644 index 0000000..529aecb --- /dev/null +++ b/src/graph.py @@ -0,0 +1,71 @@ +"""Implement a class for a graph data structure.""" + + +class Graph(object): + """Graph class.""" + + def __init__(self): + """Initialize a graph.""" + self._graph = {} + + def nodes(self): + """Return a list of all nodes in graph.""" + return list(self._graph.keys()) + + def edges(self): + """Return a list of edges in graph.""" + edges = [] + for key in self._graph: + for child in self._graph[key]: + edges.append((key, child)) + return edges + + def add_node(self, val): + """Add a node with value of val to graph.""" + self._graph.setdefault(val, {}) + + def add_edge(self, val1, val2, weight=0): + """Add a new edge to graph between val1 & val2 as well as add vals.""" + self.add_node(val1) + self.add_node(val2) + self._graph[val1][val2] = weight + + def del_node(self, val): + """Delete node w/val from graph, raises exception if not exist.""" + if val in self._graph: + del self._graph[val] + for key in self._graph: + if val in self._graph[key]: + del self._graph[key][val] + else: + raise ValueError('There is no node of that value in the graph.') + + def del_edge(self, val1, val2): + """Delete edge between val1 & val2 from graph.""" + try: + del self._graph[val1][val2] + except KeyError: + raise ValueError('This edge does not exist.') + + def has_node(self, val): + """Return true or false if node has value.""" + return val in self._graph + + def neighbors(self, val): + """Return list of nodes connected node(val).""" + try: + return list(self._graph[val].keys()) + except KeyError: + raise ValueError('This node does not exist') + + def adjacent(self, val1, val2): + """Return true if edge between vals, else false, & error if no val.""" + try: + self._graph[val1] + self._graph[val2] + if val2 in self._graph[val1]: + return True + else: + return False + except KeyError: + raise ValueError('Node is not a node in this graph') diff --git a/src/hash_table.py b/src/hash_table.py new file mode 100644 index 0000000..851fadd --- /dev/null +++ b/src/hash_table.py @@ -0,0 +1,62 @@ +"""Hash table for storing strings.""" + + +def naive_hash(word, buckets): + """A simple hash for strings.""" + hash_val = 0 + for letter in word: + hash_val += ord(letter) + return hash_val % buckets + + +def horner_hash(word, buckets): + """Using horner's rule for hashing key.""" + constant = 37 + result = 0 + for letter in word: + result = result * constant + ord(letter) + return result % buckets + + +class HashTable(object): + """Hash table class for hashing strings.""" + + def __init__(self, size=10, hash_func=naive_hash): + """Initialize a new hashtable.""" + self.hash_func = hash_func + self._size = size + self._buckets = [[] for x in range(self._size)] + + def get(self, key): + """Return the value of the key given.""" + hash_key = self._hash(key) + if self._buckets[hash_key] == []: + return 'There are no items with that key.' + else: + for idx, item in enumerate(self._buckets[hash_key]): + if item[0] == key: + return self._buckets[hash_key][idx][1] + else: + return 'There are not items with that key.' + + def set(self, key, val): + """Pass a value into the table for storage.""" + if not isinstance(key, str): + raise TypeError('You must enter a word as a key.') + hash_key = self._hash(key) + if self._buckets[hash_key] == []: + self._buckets[hash_key].append((key, val)) + else: + for idx, item in enumerate(self._buckets[hash_key]): + if item[0] == key: + gone = item[1] + self._buckets[hash_key][idx] = (key, val) + return 'Your data {} has been updated to {} at key {}.'\ + .format(gone, val, key) + else: + self._buckets[hash_key].append((key, val)) + + def _hash(self, key): + """Hash the data given on set.""" + buckets = self._size + return self.hash_func(key, buckets) diff --git a/src/linked_list.py b/src/linked_list.py new file mode 100644 index 0000000..dd4cb8b --- /dev/null +++ b/src/linked_list.py @@ -0,0 +1,107 @@ +"""DATA Structure for linked list.""" + + +class Node(object): + """Create a class of Node.""" + + def __init__(self, data=None, next=None): + """Create instance of class Node.""" + self.data = data + self.next = next + + def get_data(self): + """Set self to data.""" + return self.data + + def get_next(self): + """Return next of data.""" + return self.next + + def set_next(self, new_next): + """Set data to next data.""" + self.next = new_next + + +class LinkedList(object): + """Create a class of LinkedList.""" + + def __init__(self, iterable=None): + """Create instance of LinkedList.""" + self.head = None + self._size = 0 + if isinstance(iterable, (tuple, list)): + for item in iterable: + self.push(item) + elif isinstance(iterable, (str, int)): + self.push(iterable) + + def push(self, val): + """Create new node and makes it a head.""" + new_node = Node(val) + new_node.set_next(self.head) + self.head = new_node + self._size += 1 + + def pop(self): + """Delete the first node and make next node a head.""" + if self.head is None: + raise IndexError('This is an empty list. No values to pop.') + current = self.head + self.head = current.get_next() + self._size -= 1 + return current.data + + def size(self): + """Return the length.""" + return self._size + + def search(self, val): + """Search the list.""" + current = self.head + while current: + if current.get_data() == val: + return current + else: + current = current.get_next() + return None + + def remove(self, node): + """Search and removes node of that value, then links adjecent nodes.""" + current = self.head + prev = None + while current: + if current == node: + if current == self.head: + self.head = current.get_next() + self._size -= 1 + else: + prev.set_next(current.get_next()) + self._size -= 1 + return current + prev = current + current = current.get_next() + else: + raise ValueError('Your node does not exist in this linked list.') + + def display(self): + """Display the data in str looking tuple.""" + if self._size > 0: + print_list = '(' + current = self.head + while current: + if current.get_next(): + print_list = print_list + str(current.data) + ', ' + else: + print_list = print_list + str(current.data) + ')' + current = current.get_next() + return print_list + else: + return '()' + + def __str__(self): + """Display of data in str looking tuple.""" + return str(self.display()) + + def __len__(self): + """Display size of list.""" + return self._size diff --git a/src/priorityq.py b/src/priorityq.py index 04fa8a3..44e316c 100644 --- a/src/priorityq.py +++ b/src/priorityq.py @@ -12,6 +12,9 @@ def __init__(self): def insert(self, val, priority=0): """Insert a new value into the queue.""" + if len(self._que) == 0: + self._highest = priority + self._lowest = priority if priority <= self._highest: self._highest = priority if priority >= self._lowest: @@ -39,4 +42,7 @@ def pop(self): def peek(self): """View the hightest priority item.""" - return self._que[self._highest][0] + if len(self._que) == 0: + return None + else: + return self._que[self._highest][0] diff --git a/src/que_.py b/src/que_.py new file mode 100644 index 0000000..9e0510c --- /dev/null +++ b/src/que_.py @@ -0,0 +1,36 @@ +"""Create queue class composed from instance of Dll().""" +from doubly_linked_list import Dll + + +class Queue(object): + """Simple class from Dll() instance.""" + + def __init__(self): + """Initiate an empty stack.""" + self.func = Dll() + + def enqueue(self, val): + """Push an item into stack.""" + return self.func.push(val) + + def dequeue(self): + """Remove first item pushed into stack.""" + try: + return self.func.shift().data + except IndexError: + raise IndexError('There are no nodes to dequeue.') + + def peek(self): + """Return the value of the next node to be removed.""" + try: + return self.func.tail.data + except AttributeError: + return None + + def size(self): + """Return the size.""" + return self.func._size + + def __len__(self): + """Print length of stack.""" + return self.func._size diff --git a/src/shortest_dis.py b/src/shortest_dis.py new file mode 100644 index 0000000..0c40a95 --- /dev/null +++ b/src/shortest_dis.py @@ -0,0 +1,41 @@ +"""Using Dijkstra's algorithm to solve the shortest path.""" + + +def dijkstra(graph, start, end): + """Dijkysta algorithm to calculate the shortest path.""" + distance = {start: 0} + parents = {} + + not_visited = list(graph._graph.keys()) + + while not_visited: + min_node = None + for val in not_visited: + if val in distance: + if min_node is None: + min_node = val + elif distance[val] < distance[min_node]: + min_node = val + not_visited.remove(min_node) + if graph._graph[min_node] == {}: + return parents + else: + for edge in graph._graph[min_node]: + length = distance[min_node] + graph._graph[min_node][edge] + if edge not in distance or length < distance[edge]: + distance[edge] = length + parents[edge] = min_node + + return parents + + +def shortest_distance(graph, start, end): + """Utilize dijkstra to find the shortest path.""" + d = dijkstra(graph, start, end) + if d == {}: + raise ValueError('This start node has no edges.') + path = [end] + while end != start: + path.insert(0, d[end]) + end = d[end] + return path diff --git a/src/stack.py b/src/stack.py new file mode 100644 index 0000000..9830a3c --- /dev/null +++ b/src/stack.py @@ -0,0 +1,25 @@ +"""Create a Stack class composed from instance of LinkedList().""" +from linked_list import LinkedList + + +class Stack(object): + """Simple class from LinkdedList() instance.""" + + def __init__(self, iterable=()): + """Initiate an empty stack.""" + self.func = LinkedList(iterable) + + def push(self, val): + """Push an item or iterable into stack.""" + return self.func.push(val) + + def pop(self): + """Remove last item pushed into stack.""" + try: + return self.func.pop().data + except IndexError: + raise IndexError('There are no nodes to pop.') + + def __len__(self): + """Print length of stack.""" + return self.func._size diff --git a/src/test_binheap.py b/src/test_binheap.py new file mode 100644 index 0000000..814f682 --- /dev/null +++ b/src/test_binheap.py @@ -0,0 +1,90 @@ +"""Test binheap.py.""" +import pytest + + +def test_initialize_empty_heap(ebh): + """Test if init binary is empty.""" + assert ebh + + +def test_push_one_value(ebh): + """Test size after push one value into heap.""" + ebh.push(9) + assert ebh._size == 1 + + +def test_push_mulitp_value(ebh): + """Test size after push multiple values into heap.""" + for i in range(20): + ebh.push(i) + assert ebh._size == 20 + + +def test_push_one_non_num(ebh): + """Test for erron if pushing non numerical value into heap.""" + with pytest.raises(ValueError): + ebh.push('NOOOOOOOO!!!!!!!!!') + + +def test_push_iterable(): + """Test if push successfully takes iterable.""" + from binheap import Binheap + heap = Binheap([1, 2, 3, 4, 5, 6, 7, 8]) + assert heap._size == 8 + + +def test_pop_on_empty_heap_raises_indexerror(ebh): + """Test for index error trying to pop from empty heap.""" + with pytest.raises(IndexError): + ebh.pop() + + +def test_pop_on_heap_with_one_item(ebh): + """Test pop returns item when only one item in heap.""" + ebh.push(1) + assert ebh.pop() == 1 + + +def test_pop_twice_on_heap_with_one_item(ebh): + """Test pop on fully popped heap.""" + ebh.push(1) + ebh.pop() + with pytest.raises(IndexError): + ebh.pop() + + +def test_pop_returns_sorted_values(): + """Test pop entire bin returns sorted values.""" + from binheap import Binheap + import random + rand_nums = list(set([random.randint(0, 1000) + for i in range(20)])) + heap = Binheap(rand_nums) + # import pdb; pdb.set_trace() + all_popped = [heap.pop() for i in range(heap._size)] + assert all_popped == sorted(rand_nums) + + +def test_pop_returns_sorted_values_limited(ebh): + """Test pop in controlled environment.""" + ebh.push(23), + ebh.push(2), + ebh.push(30), + ebh.push(50), + ebh.push(6), + ebh.push(17), + ebh.push(29) + all_popped = [ebh.pop() for i in range(7)] + assert all_popped == [2, 6, 17, 23, 29, 30, 50] + + +def test_push_pop_(): + """Test push iterable and pop.""" + from binheap import Binheap + b = Binheap([5, 546, 7, 3, 54, 376, 87, 432, 432, 543, + 654, 63, 32, 325, 4, 654, 234, 32, 543, 6, 2, + 2, 4, 6, 87, 5, 6, 34, 668, 675, 5]) + out = [2, 2, 3, 4, 4, 5, 5, 5, 6, 6, 6, 7, 32, 32, 34, 54, + 63, 87, 87, 234, 325, 376, 432, 432, 543, 543, 546, + 654, 654, 668, 675] + assert out == [b.pop() for x in out] diff --git a/src/test_bst.py b/src/test_bst.py new file mode 100644 index 0000000..7b3ec60 --- /dev/null +++ b/src/test_bst.py @@ -0,0 +1,436 @@ +# """Test binary search tree.""" +# import pytest + + +# def test_initialize_bst_returns_empty_bst(bst): +# """Test initialize empty bst returns empty bst.""" +# assert bst.root is None + + +# def test_initialize_bst_iteratble_returns_size_n(): +# """Test initialize with iterable returns proper size tree.""" +# from bst import Bst +# tree = Bst([2, 6, 3, 7, 8, 1]) +# assert tree._size == 6 + + +# def test_initialize_bst_iteratble_root_is_first_item(): +# """Test initialize with iterable root is first item in.""" +# from bst import Bst +# tree = Bst([2, 6, 3, 7, 8, 1]) +# assert tree.root.data == 2 + + +# def test_size_returns_0_if_bst_is_empty(bst): +# """Test size method returns 0 on empty tree.""" +# assert bst.size() == 0 + + +# def test_size_returns_appropriate_size_after_iterable(): +# """Test size returns proper size when initiated by iterable.""" +# from bst import Bst +# tree = Bst([2, 6, 3, 7, 8, 1]) +# assert tree.size() == 6 + + +# def test_size_returns_10_if_insert_manually_10_items(bst_big): +# """Test if 10 items inserted that size is 10.""" +# assert bst_big.size() == 10 + + +# def test_insert_10_items_returns_first_in_as_root(bst_big): +# """Test that root is first in if 10 items inserted.""" +# assert bst_big.root.data == 50 + + +# def test_insert_identicle_values_raise_valueerror(bst): +# """Test insert same values raises value error.""" +# bst.insert(1) +# with pytest.raises(ValueError): +# bst.insert(1) + + +# def test_insert_non_int_raises_valueerror(bst): +# """Test insert non number raises value error.""" +# with pytest.raises(ValueError): +# bst.insert('howdy') + + +# def test_insert_iterable_raises_valueerror(bst): +# """Test insert iterable raises appropriate error.""" +# with pytest.raises(ValueError): +# bst.insert([1, 3, 5, 9]) + + +# def test_search_node_on_empty_tree_returns_none(bst): +# """Test search on empty tree returns none.""" +# assert bst.search(10) is None + + +# def test_search_node_not_in_tree_returns_none(bst_full): +# """Test search for node not in tree returns none.""" +# assert bst_full.search(1) is None + + +# def test_search_for_non_int_returns_non(bst_full): +# """Test search for non number returns none.""" +# assert bst_full.search('cake') is None + + +# def test_search_val_in_tree_returns_node(bst_full): +# """Test search for val in tree returns node.""" +# bst_full.insert(5) +# assert isinstance(bst_full.search(5), object) + + +# def test_search_val_in_tree_retruns_node_with_data(bst_full): +# """Test search for val in tree returns node with data of val.""" +# bst_full.insert(5) +# assert bst_full.search(5).data == 5 + + +# def test_depth_returns_int_of_total_depth_of_tree(bst_full): +# """Test depth returns integer of depth of tree.""" +# assert bst_full.depth(bst_full.root) == 3 + + +# def test_depth_empty_tree_returns_none(bst): +# """Test depth on epmty tree returns None.""" +# assert bst.depth(bst.root) == 0 + + +# def test_depth_on_large_tree_returns_full_size(bst_big): +# """Test depth on large tree returns actual size.""" +# assert bst_big.depth(bst_big.root) == 5 + + +# def test_depth_of_tree_with_only_root_is_0(bst): +# """Test if only root in tree that depth is 0.""" +# bst.insert(1) +# assert bst.depth(bst.root) == 0 + + +# def test_contains_returns_false_if_val_not_in_tree(bst_big): +# """Test contains returns false if val not in tree.""" +# assert bst_big.contains(102948686) is False + + +# def test_contains_returns_false_if_non_int_entered(bst_big): +# """"Test contains returns false if non int entered.""" +# assert bst_big.contains('pie') is False + + +# def test_contains_returns_true_if_val_in_tree(bst_big): +# """Test contains returns true if val in tree.""" +# assert bst_big.contains(40) is True +# assert bst_big.contains(50) is True +# assert bst_big.contains(68) is True + + +# def test_balance_returns_string_if_bst_is_empty(bst): +# """Test balance returns none if bst is empty.""" +# assert bst.balance(bst.root) == 'There are no nodes in this tree.' + + +# def test_balance_returns_int(bst_big): +# """Test balancde returns int..""" +# assert isinstance(bst_big.balance(bst_big.root), int) + + +# def test_balance_returns_int_of_r_minus_l_of_tree(bst_big): +# """Test balance returns int of left minus right sides of tree.""" +# assert bst_big.balance(bst_big.root) == -1 + + +# def test_balance_returns_int_of_r_minus_l_of_tree_two(bst_full, bst): +# """Test balance returns int of left minus right sides of tree.""" +# bst.insert(2) +# bst.insert(1) +# bst.insert(3) +# assert bst.balance(bst.root) == 0 +# assert bst_full.balance(bst_full.root) == -2 + + +# def test_balance_returns_int_of_r_minus_l_of_tree_three(bst): +# """Test balance returns int of left minus right sides of tree.""" +# bst.insert(1) +# bst.insert(2) +# bst.insert(3) +# bst.insert(4) +# bst.insert(5) +# bst.insert(6) +# bst.insert(7) +# assert bst.balance(bst.root) == -6 + + +# def test_breadth_first_returns_object(bst_big): +# """Test breadth first returns generator object.""" +# b = bst_big.breadth_first() +# assert isinstance(b, object) + + +# def test_breadth_first_is_valid_generator(bst_big): +# """Test breadth first returns valid generator.""" +# g = bst_big.breadth_first() +# assert next(g) == 50 + + +# def test_breadth_first_return_children_l_to_r(bst_big): +# """Test breadth first returns all children l to r.""" +# g = bst_big.breadth_first() +# output = [] +# for i in range(10): +# output.append(next(g)) +# assert output == [50, 40, 68, 10, 110, 1, 18, 80, 500, 5000] + + +# def test_breadth_first_on_empty_bst_raises_value_error(bst): +# """Test breadth first search raises value error if bst empty.""" +# g = bst.breadth_first() +# with pytest.raises(ValueError): +# next(g) + + +# def test_in_order_returns_object(bst): +# """Test in order returns object.""" +# g = bst.in_order() +# assert isinstance(g, object) + + +# def test_in_order_is_valid_generator(bst_big): +# """Test in order returns valid generator.""" +# g = bst_big.in_order() +# assert next(g) == 1 + + +# def test_in_order_returns_tree_in_ascending_order(bst_big): +# """Test in order returns ordered vals.""" +# g = bst_big.in_order() +# output = [] +# for i in range(10): +# output.append(next(g)) +# assert output == [1, 10, 18, 40, 50, 68, 80, 110, 500, 5000] + + +# def test_in_order_on_empty_bst_raises_value_error(bst): +# """Test in order search raises value error if bst empty.""" +# g = bst.in_order() +# with pytest.raises(ValueError): +# next(g) + + +# def test_pre_order_retuns_object(bst): +# """Test pre order returns object.""" +# g = bst.pre_order() +# assert isinstance(g, object) + + +# def test_pre_order_returns_valid_generator(bst_big): +# """Test pre order returns valid generator object.""" +# g = bst_big.pre_order() +# assert next(g) == 50 + + +# def test_pre_order_returns_left_side_of_all_nodes_first(bst_big): +# """Test pre order returns left side of each node first.""" +# g = bst_big.pre_order() +# output = [] +# for i in range(10): +# output.append(next(g)) +# assert output == [50, 40, 10, 1, 18, 68, 110, 80, 500, 5000] + + +# def test_pre_order_returns_proper_order_unabalances(bst_full): +# """Test pre order returns proper order from unbalanced tree.""" +# g = bst_full.pre_order() +# output = [] +# for i in range(3): +# output.append(next(g)) +# assert output == [3254, 908543, 58490543] + + +# def test_pre_order_on_empty_bst_raises_value_error(bst): +# """Test pre order search raises value error if bst empty.""" +# g = bst.pre_order() +# with pytest.raises(ValueError): +# next(g) + + +# def test_post_order_returns_object(bst): +# """Test post order returns object.""" +# g = bst.post_order() +# assert isinstance(g, object) + + +# def test_post_order_returns_valid_generator(bst_big): +# """Test post order returns valid generator object.""" +# g = bst_big.post_order() +# assert next(g) == 1 + + +# def test_post_order_returns_root_last(bst_big): +# """Test post order returns left side then right with root in middle.""" +# g = bst_big.post_order() +# output = [] +# for i in range(10): +# output.append(next(g)) +# assert output[-1] == 50 + + +# def test_post_order_returns_proper_order_unbalanced(bst_full): +# """Test post order returns proper order on unbalanced tree.""" +# g = bst_full.post_order() +# output = [] +# for i in range(3): +# output.append(next(g)) +# assert output == [58490543, 908543, 3254] + + +# def test_post_order_returns_proper_order_on_balanced(bst_big): +# """Test post order returns proper order on balanced tree.""" +# g = bst_big.post_order() +# output = [] +# for i in range(10): +# output.append(next(g)) +# assert output == [1, 18, 10, 40, 80, 5000, 500, 110, 68, 50] + + +# def test_post_order_on_empty_bst_raises_value_error(bst): +# """Test post order search raises value error if bst empty.""" +# g = bst.post_order() +# with pytest.raises(ValueError): +# next(g) + + +# def test_delete_empty_tree_returns_none(bst): +# """Test delete on empty tree returns none.""" +# assert bst.delete(5) is None + + +# def test_delete_on_tree_with_only_root(bst): +# """Test delete on tree with only root node.""" +# bst.insert(5) +# bst.delete(5) +# assert bst.size() == 0 +# assert bst.search(5) is None + + +# def test_delete_on_root_in_large_bst(bst_big): +# """Test delete on root node in large bst.""" +# bst_big.delete(50) +# output = [] +# b_output = [] +# o = bst_big.in_order() +# b = bst_big.breadth_first() +# for i in range(9): +# output.append(next(o)) +# b_output.append(next(b)) +# assert bst_big.search(50) is None +# assert 50 not in output +# assert b_output == [68, 40, 110, 10, 80, 500, 1, 18, 5000] + + +# def test_delete_on_node_with_no_chirdren(bst_big): +# """Test delete on node with no children.""" +# bst_big.delete(5000) +# output = [] +# b_output = [] +# o = bst_big.in_order() +# b = bst_big.breadth_first() +# for i in range(9): +# output.append(next(o)) +# b_output.append(next(b)) +# assert bst_big.search(5000) is None +# assert 5000 not in output +# assert b_output == [50, 40, 68, 10, 110, 1, 18, 80, 500] + + +# def test_delete_on_node_with_one_child_right(bst_big): +# """Test delete on node with only one right child.""" +# bst_big.delete(500) +# output = [] +# b_output = [] +# o = bst_big.in_order() +# b = bst_big.breadth_first() +# for i in range(9): +# output.append(next(o)) +# b_output.append(next(b)) +# assert bst_big.search(500) is None +# assert 500 not in output +# assert b_output == [50, 40, 68, 10, 110, 1, 18, 80, 5000] + + +# def test_delete_on_node_with_two_children(bst_big): +# """Test delete on node with one left child.""" +# bst_big.delete(10) +# output = [] +# b_output = [] +# o = bst_big.in_order() +# b = bst_big.breadth_first() +# for i in range(9): +# output.append(next(o)) +# b_output.append(next(b)) +# assert bst_big.search(10) is None +# assert 10 not in output +# assert b_output == [50, 40, 68, 18, 110, 1, 80, 500, 5000] + + +# def test_delete_on_root_unbalanced_left(bst): +# """Test delete when only left nodes.""" +# bst.insert(100) +# bst.insert(80) +# bst.insert(60) +# bst.insert(40) +# bst.delete(100) +# assert bst.search(100) is None +# assert bst.size() == 3 + + +# def test_delete_on_root_on_unbalanced_right(bst): +# """Test unbalanced when only right nodes.""" +# bst.insert(40) +# bst.insert(60) +# bst.insert(80) +# bst.insert(100) +# bst.delete(40) +# assert bst.search(40) is None +# assert bst.size() == 3 + + +# def test_delete_back_to_back(bst_big): +# """Test delete multiple times.""" +# bst_big.delete(110) +# bst_big.delete(18) +# bst_big.delete(50) +# output = [] +# o = bst_big.in_order() +# for i in range(7): +# output.append(next(o)) +# assert 110 not in output +# assert 18 not in output +# assert 50 not in output +# assert output == [1, 10, 40, 68, 80, 500, 5000] +# assert bst_big.search(110) is None +# assert bst_big.search(18) is None +# assert bst_big.search(50) is None + + +# def test_delete_one_child_parent_greater(bst_big): +# """Test delete node with one child when parent is greater.""" +# bst_big.delete(40) +# assert bst_big.search(40) is None + + +# def test_delete_two_children_if_next_has_one_child(bst_big): +# """Test delete node with one child when parent is greater.""" +# bst_big.insert(85) +# bst_big.insert(55) +# bst_big.delete(68) +# assert bst_big.search(68) is None + + +# def test_delete_two_children_if_next_no_child(bst_big): +# """Test delete node with one child when parent is greater.""" +# bst_big.insert(55) +# bst_big.delete(68) +# assert bst_big.search(68) is None diff --git a/src/test_doubly_linked_list.py b/src/test_doubly_linked_list.py new file mode 100644 index 0000000..b7b057f --- /dev/null +++ b/src/test_doubly_linked_list.py @@ -0,0 +1,192 @@ +"""Test doubly_linked_list.py.""" +import pytest +from doubly_linked_list import Node +from doubly_linked_list import Dll + + +def test_node_has_attributes(): + """A node must have a data, next, previous attribute.""" + new = Node() + assert hasattr(new, 'data') + assert hasattr(new, 'next') + assert hasattr(new, 'prve') + + +def test_dll_has_no_head(): + """New Dll should have a head.""" + l = Dll() + assert l.head is None + + +def test_dll_has_tail(): + """New Dll should have a head.""" + l = Dll() + assert l.tail is None + + +def test_dll_push_adds_new_item(): + """New item should become new head of list.""" + l = Dll() + l.push('val') + assert l.head.data == 'val' + + +def test_dll_push_two_values_in_last_is_head(): + """New item should become new head of list.""" + l = Dll() + l.push('val') + l.push('val2') + assert l.head.data == 'val2' + + +def test_dll_push_two_values_in_first_is_tail(): + """New item should become new head of list.""" + l = Dll() + l.push('val') + l.push('val2') + assert l.tail.data == 'val' + + +def test_dll_push_moves_old_head_to_new_next(): + """New item should become new head of list.""" + l = Dll() + l.push('val') + l.push('val2') + assert l.head.next.data == 'val' + + +def test_dll_push_moves_new_head_to_new_prev(): + """New item should become new head of list.""" + l = Dll() + l.push('val') + l.push('val2') + assert l.tail.prve.data == 'val2' + + +def test_dll_append_two_values_in_last_is_tail(): + """New item should become new tail of list.""" + l = Dll() + l.append('val') + l.append('val2') + assert l.tail.data == 'val2' + + +def test_dll_append_two_values_in_first_is_head(): + """Last item should become new head of list.""" + l = Dll() + l.append('val') + l.append('val2') + assert l.head.data == 'val' + + +def test_dll_append_moves_old_tail_to_new_previous(): + """New item should become new tail of list.""" + l = Dll() + l.append('val') + l.append('val2') + assert l.tail.prve.data == 'val' + + +def test_dll_append_moves_new_nove_to_tail(): + """New item should become new head of list.""" + l = Dll() + l.append('val') + l.append('val2') + assert l.tail.data == 'val2' + + +def test_dll_pop_removes_head(): + """Test Dll for pop function and if it removes the head.""" + l = Dll() + l.push('val') + l.pop() + assert l.head is None + + +def test_dll_shift_removes_tail(): + """Test Dll for pop function and if it removes the tail.""" + l = Dll() + l.push('val') + l.shift() + assert l.tail is None + + +def test_dll_pop_returns_removed_head(): + """Test Dll for pop function if it returns proper head.""" + l = Dll() + l.push('val') + former_head = l.pop() + assert former_head.data == 'val' + + +def test_dll_pop_with_multiple_items_in_list(): + """Test Dll for pop with multiple items in list.""" + l = Dll() + l.push('val') + l.push('val2') + former_head = l.pop() + assert l.head.data == 'val' + + +def test_dll_shift_returns_removed_tail(): + """Test Dll for pop function if it returns proper head.""" + l = Dll() + l.push('val') + former_tail = l.shift() + assert former_tail.data == 'val' + + +def test_dll__with_multiple_items_in_list(): + """Test Dll for pop with multiple items in list.""" + l = Dll() + l.push('val') + l.push('val2') + former_head = l.shift() + assert l.tail.data == 'val2' + + +def test_dll_pop_on_empty_list_returns_exception(): + """Test Dll for pop on empty list if it returns exception.""" + l = Dll() + with pytest.raises(IndexError): + l.pop() + + +def test_dll_shift_on_empty_list_returns_exception(): + """Test Dll for pop on empty list if it returns exception.""" + l = Dll() + with pytest.raises(IndexError): + l.pop() + + +@pytest.mark.parametrize('n', range(20)) +def test_dll_len_uses_length_function(n): + """Test Dll should successfully interact with the len() in Python.""" + l = Dll() + for i in range(n): + l.push(i) + assert len(l) == n + + +def test_dll_remove_node_removes_input_value(): + """Test Dll to remove node by value.""" + l = Dll() + for i in range(10): + l.push(i) + assert l.remove(3).data == 3 + + +def test_dll_remove_node_removes_head_value(): + """Test Dll to remove head.""" + l = Dll() + l.push('val') + assert l.remove('val').data == 'val' + + +def test_linked_list_remove_node_raises_valueerror_if_input_not_exist(): + """Test Dll to remove node raises ValueError if input not exist.""" + l = Dll() + for i in range(10): + l.append(i) + with pytest.raises(ValueError): + l.remove(17) diff --git a/src/test_graph.py b/src/test_graph.py new file mode 100644 index 0000000..997b0d0 --- /dev/null +++ b/src/test_graph.py @@ -0,0 +1,247 @@ +"""Testing graph.py.""" +from graph import Graph + +import pytest + + +def test_graph_iinitialize_empty_dict(g): + """Test initialize an empty pq.""" + assert g._graph == {} + + +def test_add_node(g): + """Test it adds note.""" + g.add_node(5) + assert g._graph[5] == {} + + +def test_nodes_returns_list_of_all_nodes(g): + """Test nodes return list of all nodes.""" + g.add_node(5) + g.add_node(7) + g.add_node(8) + assert 5 in g.nodes() + assert 7 in g.nodes() + assert 8 in g.nodes() + assert type(g.nodes()) == list + + +def test_edges_return_list_of_tuples_of_all_edge(g): + """Test if edges returns dict of tuples as edge and weight as value.""" + g.add_node(5) + g.add_node(7) + g.edges() + assert g.edges() == [] + + +def test_add_edges_returns_dict_of_edges_as_tuple_keys(g): + """Test if edges are added if 2 val are given with default weight.""" + g.add_node(5) + g.add_node(7) + g.add_edge(5, 7) + assert g.edges() == [(5, 7)] + + +def test_add_two_edges(g): + """Test if edges are added if 2 val are given.""" + g.add_node(5) + g.add_node(7) + g.add_node(9) + g.add_edge(5, 7) + g.add_edge(7, 9) + assert g.edges() == [(5, 7), (7, 9)] + + +def test_add_edges_will_add_mutipule_edges_to_a_node(g): + """Test if edges are added if 2 val are given.""" + g.add_node(5) + g.add_node(7) + g.add_node(9) + g.add_node(10) + g.add_edge(5, 7) + g.add_edge(5, 9) + g.add_edge(5, 10) + assert g.edges() == [(5, 7), (5, 9), (5, 10)] + + +def test_add_edges_with_one_val_in_graph_add_second_val(g): + """Test if edges are added if 2 val are given.""" + g.add_node(5) + g.add_node(7) + g.add_node(9) + g.add_node(10) + g.add_edge(5, 7) + g.add_edge(5, 9) + g.add_edge(5, 15) + assert g.edges() == [(5, 7), (5, 9), (5, 15)] + + +def test_add_edges_with_first_val_new_and_second_val_in_graph(g): + """Test if edges are added if 2 val are given.""" + g.add_node(5) + g.add_node(7) + g.add_node(9) + g.add_node(10) + g.add_edge(5, 7) + g.add_edge(5, 9) + g.add_edge(15, 5) + assert (15, 5) in g.edges() + assert (5, 7) in g.edges() + assert (5, 9) in g.edges() + + +def test_add_edges_with_two_new_nodes(g): + """Test add edges when both nodes are new to graph.""" + g.add_edge(19, 100) + assert g.edges() == [(19, 100)] + + +def test_del_node_deletes_the_node_given(g): + """Test del node delets the node.""" + g.add_node(5) + g.add_node(7) + g.add_node(9) + g.del_node(7) + assert 7 not in g.nodes() + + +def test_del_node_deletes_the_node_and_edges_given(g): + """Test del node delets the node.""" + g.add_node(5) + g.add_node(7) + g.add_node(9) + g.add_edge(7, 9) + g.add_edge(5, 7) + g.del_node(7) + assert g.edges() == [] + assert g.neighbors(9) == [] + assert g.neighbors(5) == [] + assert 7 not in g.nodes() + + +def test_del_node_raises_value_error(g): + """Test del node raises value error if node has been deleted.""" + g.add_node(5) + g.add_node(7) + g.add_node(9) + g.del_node(5) + g.del_node(7) + g.del_node(9) + with pytest.raises(ValueError): + g.del_node(5) + + +def test_del_edge_deletes_all_edges(g): + """Test if del_edges deletes all edges.""" + g.add_node(5) + g.add_node(7) + g.add_edge(5, 7) + g.del_edge(5, 7) + assert g.edges() == [] + + +def test_del_edge_raises_value_error_if_too_many_edges_deleted(g): + """Test if del_edges raises value error.""" + g.add_node(5) + g.add_node(7) + g.add_edge(5, 7) + g.del_edge(5, 7) + with pytest.raises(ValueError): + g.del_edge(5, 7) + + +def test_del_edge_raises_value_error_if_values_not_in_dict(g): + """Test if del_edges raises value error for values not in dict.""" + g.add_node(5) + g.add_node(7) + g.add_edge(5, 7) + g.del_edge(5, 7) + with pytest.raises(ValueError): + g.del_edge(9, 0) + + +def test_has_node_is_false_on_empty_graph(g): + """Test false if there are no nodes in graph.""" + assert g.has_node(2) is False + + +def test_has_node_if_value_in_graph(g): + """Test false if node does not exist.""" + g.add_node(5) + assert g.has_node(5) is True + + +def test_neighbors_return_list_of_nodes_connected_to_input(g): + """Test neighbor retutns the list of nodes connected input value.""" + g.add_node(5) + g.add_node(7) + g.add_edge(5, 7) + g.add_node(8) + g.add_node(9) + g.add_edge(5, 9) + g.add_edge(5, 8) + assert sorted(g.neighbors(5)) == [7, 8, 9] + + +def test_neighbors_raises_valueerror_if_node_not_exist(g): + """Test neighbor raises value error if input not a node.""" + g.add_node(5) + g.add_node(7) + g.add_edge(5, 7) + g.add_node(8) + g.add_node(9) + g.add_edge(5, 9) + with pytest.raises(ValueError): + g.neighbors(20) + + +def test_adjacent_if_two_nodes_have_edge_is_true(g): + """Test adjacent has edges returns true when edge exists.""" + g.add_node(5) + g.add_node(7) + g.add_edge(5, 7) + assert g.adjacent(5, 7) is True + + +def test_adjacent_when_edge_when_no_edges(g): + """Test adjacent has no edges returns false.""" + g.add_node(5) + g.add_node(7) + assert g.adjacent(5, 7) is False + + +def test_adjacent_raises_valueerror_if_input_not_node(g): + """Test adjacent raises value error if node does not exist.""" + g.add_node(5) + g.add_node(7) + with pytest.raises(ValueError): + g.adjacent(9, 0) + + +def test_add_edges_w_weights_returns_dict_of_edges_as_tuple_keys(g): + """Test if edges are added if 2 val are given with default weight.""" + g.add_node(5) + g.add_node(7) + g.add_edge(5, 7) + assert g.edges() == [(5, 7)] + + +def test_add_two_edges_w_weights_(g): + """Test if edges are added if 2 val are given with weights returns dict.""" + g.add_node(5) + g.add_node(7) + g.add_node(9) + g.add_edge(5, 7, 100) + g.add_edge(7, 9, 1) + assert g.edges() == [(5, 7), (7, 9)] + + +def test_neighbor_returns_list_of_attached_node(): + """Test neighbors returns list of attached nodes.""" + g = Graph() + g.add_node(0) + g.add_node(1) + g.add_node('node') + g.add_edge(1, 0) + g.add_edge(0, 1) + assert g.neighbors(0) == [1] diff --git a/src/test_hash_table.py b/src/test_hash_table.py new file mode 100644 index 0000000..21e7124 --- /dev/null +++ b/src/test_hash_table.py @@ -0,0 +1,127 @@ +"""Test hash table.""" +import os + +import pytest + + +def test_naive_hashtable_set_and_get_one_word(): + """Just seeing if the thing works.""" + from hash_table import HashTable + table = HashTable(100) + table.set('Mark', 'Mark') + mark = table.get('Mark') + assert mark == 'Mark' + + +def test_naive_input_same_key_twice_resets_val(): + """Test set at same key twice updates value at key.""" + from hash_table import HashTable + table = HashTable(10) + table.set('Mark', 'Mark') + assert table.set('Mark', 'Reynoso') == ('Your data Mark has been ' + 'updated to Reynoso at key Mark.') + mark = table.get('Mark') + assert table.get('Mark') == 'Reynoso' + + +def test_naive_hash_returns_int_between_zero_and_input(): + """Test navie hash returns an int between zero and len buckets.""" + from hash_table import naive_hash + word = 'Hashtale' + length = 100 + assert naive_hash(word, length) < 100 + assert naive_hash(word, length) >= 0 + + +def test_naive_get_if_item_not_in_table_returns_error_message(): + """Test naive get if item not in table returns error message.""" + from hash_table import HashTable + table = HashTable() + assert table.get('Christmas') == 'There are no items with that key.' + + +def test_naive_get_item_not_in_table_returns_error_if_other_item_has_key(): + """Test naive get if item not in table returns error message.""" + from hash_table import HashTable + table = HashTable() + table.set('Christmas', 'Christmas') + assert table.get('Thanksgiving') == 'There are no items with that key.' + + +def test_hashtable_raises_type_error_if_not_input_string(): + """Test hashtable raises typeerror if input is not string.""" + from hash_table import HashTable + table = HashTable() + with pytest.raises(TypeError): + table.set(5, 'Test') + + +def test_horners_hashtable_set_and_get_one_word(): + """Just seeing if the thing works.""" + from hash_table import HashTable, horner_hash + table = HashTable(100, horner_hash) + table.set('Mark', 'Mark') + mark = table.get('Mark') + assert mark == 'Mark' + + +def test_horners_input_same_key_twice_resets_val(): + """Test set at same key twice updates value at key.""" + from hash_table import HashTable, horner_hash + table = HashTable(10, horner_hash) + table.set('Mark', 'Mark') + assert table.set('Mark', 'Reynoso') == ('Your data Mark has been ' + 'updated to Reynoso at key Mark.') + mark = table.get('Mark') + assert table.get('Mark') == 'Reynoso' + + +def test_horners_hash_returns_int_between_zero_and_input(): + """Test navie hash returns an int between zero and len buckets.""" + from hash_table import horner_hash + word = 'Hashtale' + length = 100 + assert horner_hash(word, length) < 100 + assert horner_hash(word, length) >= 0 + + +def test_horners_get_if_item_not_in_table_returns_error_message(): + """Test horners get if item not in table returns error message.""" + from hash_table import HashTable, horner_hash + table = HashTable(10, horner_hash) + assert table.get('Christmas') == 'There are no items with that key.' + + +def test_horners_get_item_not_in_table_returns_error_if_other_item_has_key(): + """Test horners get if item not in table returns error message.""" + from hash_table import HashTable, horner_hash + table = HashTable(10, horner_hash) + table.set('Christmas', 'Christmas') + assert table.get('Thanksgiving') == 'There are no items with that key.' + + +# <-------- Test entire dictionary ---------> +# word_list = [] +# with open('/usr/share/dict/words', 'r') as dictionary: +# for line in dictionary: +# word_list.append((line, line)) + + +# @pytest.mark.parametrize('input_word, output_word', word_list) +# def test_naive_all_dictionary_words(input_word, output_word): +# """Test all the entire dictionary.""" +# from hash_table import HashTable +# table = HashTable(len(word_list)) +# table.set(input_word, input_word) +# output = table.get(input_word) +# assert output == output_word + + +# @pytest.mark.parametrize('input_word, output_word', word_list) +# def test_horners_all_dictionary_words(input_word, output_word): +# """Test all the entire dictionary.""" +# from hash_table import HashTable, horner_hash +# table = HashTable(len(word_list), horner_hash) +# table.set(input_word, input_word) +# output = table.get(input_word) +# assert output == output_word diff --git a/src/test_linked_list.py b/src/test_linked_list.py new file mode 100644 index 0000000..7dce273 --- /dev/null +++ b/src/test_linked_list.py @@ -0,0 +1,201 @@ +"""Tests for linked_list.py.""" +import pytest + + +def test_node_has_attributes(): + """A node must have a data and next attribute.""" + from linked_list import Node + new = Node() + assert hasattr(new, 'data') + assert hasattr(new, 'next') + + +def test_empty_linked_list_has_no_head(): + """New linked list should have a head.""" + from linked_list import LinkedList + l = LinkedList() + assert l.head is None + + +def test_linked_list_push_adds_new_item(): + """New item should become new head of list.""" + from linked_list import LinkedList + l = LinkedList() + l.push('val') + assert l.head.data == 'val' + + +def test_linked_list_push_two_values_in_last_is_head(): + """New item should become new head of list.""" + from linked_list import LinkedList + l = LinkedList() + l.push('val') + l.push('val2') + assert l.head.data == 'val2' + + +def test_linked_list_push_moves_old_head_to_new_next(): + """New item should become new head of list.""" + from linked_list import LinkedList + l = LinkedList() + l.push('val') + l.push('val2') + assert l.head.next.data == 'val' + + +def test_linked_list_pop_removes_head(): + """Test linkedlist for pop function and if it removes the head.""" + from linked_list import LinkedList + l = LinkedList() + l.push('val') + l.pop() + assert l.head is None + + +def test_linked_list_pop_returns_removed_head(): + """Test linkedlist for pop function if it returns proper head.""" + from linked_list import LinkedList + l = LinkedList() + l.push('val') + former_head = l.pop() + assert former_head.data == 'val' + + +def test_linked_list_pop_with_multiple_items_in_list(): + """Test linkedlist for pop with multiple items in list.""" + from linked_list import LinkedList + l = LinkedList() + l.push('val') + l.push('val2') + l.pop() + assert l.head.data == 'val' + + +def test_linked_list_pop_on_empty_list_returns_exception(): + """Test linkedlist for pop on empty list if it returns exception.""" + from linked_list import LinkedList + l = LinkedList() + with pytest.raises(IndexError): + l.pop() + + +def test_linked_list_size_returns_none_on_empty_list(): + """Test linkedlist for te size returns none on empty list.""" + from linked_list import LinkedList + l = LinkedList() + assert l.size() == 0 + + +@pytest.mark.parametrize('n', range(20)) +def test_linked_list_size_returns_list_length(n): + """Test Linked List should calculate its own size.""" + from linked_list import LinkedList + l = LinkedList() + for i in range(n): + l.push(i) + assert l.size() == n + + +@pytest.mark.parametrize('n', range(20)) +def test_linked_list_len_uses_length_function(n): + """Test Linked List should successfully interact with the len().""" + from linked_list import LinkedList + l = LinkedList() + for i in range(n): + l.push(i) + assert len(l) == n + + +def test_linked_list_search_empty_returns_none(): + """Test Linked List for search that empty data returns none.""" + from linked_list import LinkedList + l = LinkedList() + assert l.search(0) is None + + +def test_linked_list_search_one_node_list_returns_head(): + """Test Linked List if it serches node and returns head.""" + from linked_list import LinkedList + l = LinkedList() + l.push('val') + assert l.search('val') == l.head + + +@pytest.mark.parametrize('n', range(1, 10)) +def test_linked_list_search_returns_input_value(n): + """Test Linked List for search returns input value.""" + from linked_list import LinkedList + from random import randint + l = LinkedList() + for i in range(1, n + 1): + l.push(i) + searching = randint(1, n) + assert l.search(searching).data == searching + + +def test_linked_list_accepts_iterables(): + """Test Linked List to accepts for iterables.""" + from linked_list import LinkedList + the_list = [1, 2, 3, 4, 5] + l = LinkedList(the_list) + for item in the_list: + assert l.search(item).data == item + + +def test_linked_list_accepts_each_iterable_value(): + """Test Linked List if accepts each iterable value.""" + from linked_list import LinkedList + the_list = [1, 2, 3, 4, 5] + l = LinkedList(the_list) + for item in the_list: + assert l.search(item).data == item + + +def test_linked_list_remove_removes_input_value(): + """Test Linked List to remove node by value.""" + from linked_list import LinkedList + the_list = [1, 2, 3, 4, 5] + l = LinkedList(the_list) + assert l.remove(l.search(3)).data == 3 + + +def test_linked_list_remove_removes_sets_next_as_head(): + """Test Linked List to remove node removes sets next as head.""" + from linked_list import LinkedList + the_list = [1, 2, 3, 4, 5] + l = LinkedList(the_list) + l.remove(l.search(5)) + assert l.head.data == 4 + + +def test_linked_list_remove_raises_valueerror_if_input_not_exist(): + """Test Linked List to remove node raises ValueError if input not exist.""" + from linked_list import LinkedList + the_list = [1, 2, 3, 4, 5] + l = LinkedList(the_list) + with pytest.raises(ValueError): + l.remove(7) + + +def test_linked_list_display_returns_tuple_of_list_values(): + """Test for test linked list display returns tuple of list values.""" + from linked_list import LinkedList + the_list = [1, 2, 3, 4, 5] + l = LinkedList(the_list) + assert l.display() == '(5, 4, 3, 2, 1)' + + +def test_linked_list_len_returns_list_length(): + """Test linked list for len returns list length.""" + from linked_list import LinkedList + the_list = [1, 2, 3, 4, 5] + l = LinkedList(the_list) + assert len(l) == 5 + + +def test_linked_list_str_calls_display(): + """Test for str calls display.""" + from linked_list import LinkedList + the_list = [1, 2, 3, 4, 5] + l = LinkedList(the_list) + assert str(l) == '(5, 4, 3, 2, 1)' diff --git a/src/test_self_balancing_bst.py b/src/test_self_balancing_bst.py new file mode 100644 index 0000000..5d633c2 --- /dev/null +++ b/src/test_self_balancing_bst.py @@ -0,0 +1,220 @@ +# """Test self-balancing binary search tree.""" + + +def test_bst_imbalance_neg_two_now_balanced(bst_full): + """Test imbalanced bst becomes balanced.""" + output = [] + g = bst_full.breadth_first() + for i in range(3): + output.append(next(g)) + assert output == [908543, 3254, 58490543] + + +def test_bst_imbalance_pos_two_now_balanced(bst): + """Test left imbalanced bst becomes balanced.""" + bst.insert(10) + bst.insert(8) + bst.insert(6) + assert bst.root.data == 8 + assert bst.root.right.data == 10 + assert bst.root.left.data == 6 + + +def test_bst_insert_6_larger_nodes_stays_balanced(bst): + """Test insert 6 increasingly larger nodes stays balanced.""" + bst.insert(10) + bst.insert(15) + bst.insert(20) + bst.insert(25) + bst.insert(30) + bst.insert(35) + output = [] + g = bst.breadth_first() + for i in range(6): + output.append(next(g)) + assert output == [25, 15, 30, 10, 20, 35] + + +def test_bst_insert_7_larger_nodes_stays_balanced(bst): + """Test insert 6 increasingly larger nodes stays balanced.""" + bst.insert(10) + bst.insert(15) + bst.insert(20) + bst.insert(25) + bst.insert(30) + bst.insert(35) + bst.insert(40) + output = [] + g = bst.breadth_first() + for i in range(7): + output.append(next(g)) + assert output == [25, 15, 35, 10, 20, 30, 40] + + +def test_bst_insert_varied_size_stays_balanced(bst): + """Test insert varying size nodes stays balanced.""" + bst.insert(10) + bst.insert(15) + bst.insert(20) + bst.insert(25) + bst.insert(30) + bst.insert(35) + bst.insert(40) + bst.insert(5) + bst.insert(12) + output = [] + g = bst.breadth_first() + for i in range(9): + output.append(next(g)) + assert output == [25, 15, 35, 10, 20, 30, 40, 5, 12] + + +def test_bst_says_balanced_after_delete(bst): + """Test bst balance after delete node.""" + bst.insert(10) + bst.insert(15) + bst.insert(20) + bst.insert(25) + bst.insert(30) + bst.insert(35) + bst.insert(40) + bst.insert(5) + bst.insert(12) + bst.delete(20) + output = [] + g = bst.breadth_first() + for i in range(8): + output.append(next(g)) + assert output == [25, 10, 35, 5, 15, 30, 40, 12] + + +def test_r_l_rotation_sequence(bst): + """Test simple tree with right-left rotation.""" + bst.insert(10) + bst.insert(20) + bst.insert(15) + assert bst.root.data == 15 + assert bst.root.right.data == 20 + assert bst.root.left.data == 10 + + +def test_l_r_rotation_sequence(bst): + """Test simple tree with left-right rotation.""" + bst.insert(10) + bst.insert(2) + bst.insert(7) + assert bst.root.data == 7 + assert bst.root.right.data == 10 + assert bst.root.left.data == 2 + + +def test_tree_after_delete_two_nodes(bst): + bst.insert(10) + bst.insert(15) + bst.insert(20) + bst.insert(25) + bst.insert(30) + bst.insert(35) + bst.insert(40) + bst.insert(5) + bst.insert(12) + bst.delete(20) + bst.delete(10) + output = [] + for i in bst.breadth_first(): + print(i) + output.append(i) + assert output == [25, 12, 35, 5, 15, 30, 40] + + +def test_tree_after_delete_three_nodes(bst): + """Test balance after deleting three nodes.""" + bst.insert(10) + bst.insert(15) + bst.insert(20) + bst.insert(25) + bst.insert(30) + bst.insert(35) + bst.insert(40) + bst.insert(5) + bst.insert(12) + bst.delete(20) + bst.delete(10) + bst.delete(12) + output = [] + for i in bst.breadth_first(): + print(i) + output.append(i) + assert output == [25, 15, 35, 5, 30, 40] + + +def test_tree_after_delete_four_nodes(bst): + """Test delete after removing four nodes.""" + bst.insert(10) + bst.insert(15) + bst.insert(20) + bst.insert(25) + bst.insert(30) + bst.insert(35) + bst.insert(40) + bst.insert(5) + bst.insert(12) + bst.delete(20) + bst.delete(10) + bst.delete(12) + bst.delete(5) + output = [] + for i in bst.breadth_first(): + print(i) + output.append(i) + assert output == [25, 15, 35, 30, 40] + + +def test_tree_after_delete_five_nodes(bst): + """Test delete after removing five nodes.""" + bst.insert(10) + bst.insert(15) + bst.insert(20) + bst.insert(25) + bst.insert(30) + bst.insert(35) + bst.insert(40) + bst.insert(5) + bst.insert(12) + bst.delete(20) + bst.delete(10) + bst.delete(12) + bst.delete(5) + bst.delete(15) + output = [] + for i in bst.breadth_first(): + print(i) + output.append(i) + assert output == [35, 25, 40, 30] + + +def test_tree_stays_balanced_after_fifteen_inserts(): + """Test tree balanced after 15 inserts.""" + from bst import Bst + bst = Bst([40, 30, 50, 42, 10, 1, 100, 67, 6, 31, 4, 90, 99, 57, 44]) + output = [] + for i in bst.breadth_first(): + print(i) + output.append(i) + assert output == [40, 10, 90, 4, 30, 50, 100, 1, 6, 31, 42, 67, 99, 44, 57] + + +def test_tree_stays_balanced_after_three_deletes_two_inserts(): + """Test tree stays balanced after three deletes and two inserts.""" + from bst import Bst + bst = Bst([40, 30, 50, 42, 10, 1, 100, 67, 6, 31, 4, 90, 99, 57, 44]) + bst.delete(99) + bst.delete(40) + bst.delete(10) + bst.insert(7) + bst.insert(68) + output = [] + for i in bst.breadth_first(): + print(i) + output.append(i) + assert output == [42, 6, 67, 4, 30, 50, 90, 1, 7, 31, 44, 57, 68, 100] diff --git a/src/test_shortest_dis.py b/src/test_shortest_dis.py new file mode 100644 index 0000000..03dab09 --- /dev/null +++ b/src/test_shortest_dis.py @@ -0,0 +1,104 @@ +"""Test for Shorest distance graph.""" +from graph import Graph + +import pytest + +graph = { + 'A': {'C': 4, + 'B': 2, + 'D': 5 + }, + 'C': {'G': 20, + 'D': 8 + }, + 'B': {'E': 8 + }, + 'D': {'C': 4, + 'B': 3, + 'F': 10, + 'G': 15 + }, + 'E': {'F': 8, + 'D': 8 + }, + 'F': {'G': 5 + }, + 'G': {} +} + +test_graph = { + 'A': {'B': 5, + 'C': 6 + }, + 'B': {'D': 2 + }, + 'C': {'E': 8, + 'F': 4 + }, + 'D': {'E': 2, + 'G': 10 + }, + 'E': {'G': 10, + 'F': 4 + }, +} + + +def test_dijkstra_returns_best_parents(): + """Test dijkstra returns each nodes best parents.""" + g = Graph() + g._graph = test_graph + assert g.dijkstra('A') == {'B': 'A', 'C': 'A', 'D': + 'B', 'E': 'D', 'F': 'C', 'G': 'D'} + + +def test_dijkstra_returns_best_parents_on_diffrent_path(): + """Test dijkstra returns each nodes best parents.""" + g = Graph() + g._graph = test_graph + assert g.dijkstra('A') == {'B': 'A', 'C': 'A', 'D': 'B', 'E': + 'D', 'F': 'C', 'G': 'D'} + + +def test_dijkstra_returns_best_parents_of_the_node(): + """Test dijkstra returns each nodes best parents.""" + g = Graph() + g._graph = graph + assert g.dijkstra('A') == {'B': 'A', 'C': 'A', 'D': 'A', + 'E': 'B', 'F': 'D', 'G': 'D'} + + +def test_shortest_distance(): + """Test for Shortest path from start node to end node.""" + g = Graph() + g._graph = graph + assert g.shortest_distance('A', 'F') == ['A', 'D', 'F'] + + +def test_shortest_distance_returns_list(): + """Test for Shortest path from start node to end node.""" + g = Graph() + g._graph = test_graph + assert g.shortest_distance('A', 'E') == ['A', 'B', 'D', 'E'] + + +def test_shortest_distance_returns_list_from_path_a_g(): + """Test for Shortest path from start node to end node.""" + g = Graph() + g._graph = test_graph + assert g.shortest_distance('A', 'G') == ['A', 'B', 'D', 'G'] + + +def test_dijkstra_that_start_no_children_return_emty_dict(): + """Test dijkstra returns each nodes best parents.""" + g = Graph() + g._graph = graph + assert g.dijkstra('G') == {} + + +def test_shortest_distance_raises_valueerror_if_start_has_no_edges(): + """Test shortest distance raises ValueError if start has no edges.""" + g = Graph() + g._graph = test_graph + with pytest.raises(ValueError): + g.shortest_distance('G', 'A') diff --git a/src/test_stack.py b/src/test_stack.py new file mode 100644 index 0000000..d71ec1e --- /dev/null +++ b/src/test_stack.py @@ -0,0 +1,69 @@ +"""Test stack.py class constructor.""" +import pytest +from stack import Stack + + +def test_create_stack_len_at_0(): + """Check if len returns stack length at 0.""" + s = Stack() + assert len(s) == 0 + + +LENGTH = [ + (0, 1), + ([1, 2], 2), + (['flerg', 'the', 'blerg'], 3), + ([1, 2, 3, 4, 5, 6, 7], 7) +] + + +@pytest.mark.parametrize('val, result', LENGTH) +def test_create_stack_len(val, result): + """Check if len returns stack length.""" + s = Stack(val) + assert len(s) == result + + +@pytest.mark.parametrize('val, result', LENGTH) +def test_create_stack_len_is_same_as_num_pushed_values(val, result): + """Check if len returns stack length.""" + s = Stack(val) + assert len(s) == result + + +POP = [ + (5, 5), + ([1, 2], 2), + (['flerg', 'the', 'blerg'], 'blerg'), + ([1, 2, 3, 4, 5, 6, 7], 7) +] + + +@pytest.mark.parametrize('val, result', POP) +def test_stack_pop_returns_head(val, result): + """Check if len returns stack length.""" + s = Stack(val) + assert s.pop() == result + + +def test_stack_pop_on_empty_stack(): + """Check for exception message on empty stack.""" + s = Stack() + with pytest.raises(IndexError): + s.pop() + + +PUSH = [ + ([1, 2], 2), + (['flerg', 'the', 'blerg'], 3), + ([1, 2, 3, 4, 5, 6, 7], 7) +] + + +@pytest.mark.parametrize('val, result', PUSH) +def test_push_val_into_stack(val, result): + """Check if push function workds.""" + s = Stack() + for i in val: + s.push(val) + assert len(s) == result diff --git a/src/test_trie.py b/src/test_trie.py new file mode 100644 index 0000000..670c1a2 --- /dev/null +++ b/src/test_trie.py @@ -0,0 +1,133 @@ +"""Test trie tree class.""" +import pytest + + +def test_instantiate_new_trie_has_root(trie): + """Test that new trie tree has root with value of root.""" + assert trie.root.letter == 'root' + + +def test_insert_word_creates_node_for_each_letter(trie): + """Test insert on empty tree creates a node for each letter.""" + trie.insert('Christmas') + assert trie.size() == 1 + + +def test_insert_two_words_with_same_first_letter_share(trie): + """Test insert two words with same first letter share node.""" + trie.insert('Christmas') + trie.insert('Carols') + childs = trie.root.children['c'].children + assert 'a' in list(childs.keys()) + assert 'h' in list(childs.keys()) + assert trie.size() == 2 + + +def test_insert_word_creates_end_node(trie): + """Test insert word creates end node following word.""" + trie.insert('Tree') + end = trie.root.children['t'].children['r'].children['e'].children['e'] + assert end.end is True + + +def test_contains_returns_false_if_word_not_in_tree(trie): + """Test return false if world not in tree.""" + trie.insert('Christmas') + assert trie.contains('carols') is False + + +def test_contains_returns_true_if_word_in_tree(trie): + """Test contains returns true if word in tree.""" + trie.insert('Christmas') + assert trie.contains('christmas') is True + + +def test_contains_word_with_shared_letters(trie): + """Test contains with word sharing multple letters.""" + trie.insert('word') + trie.insert('world') + trie.insert('wordsmith') + assert trie.contains('world') is True + assert trie.contains('word') is True + assert trie.contains('wordsmith') is True + + +def test_size_returns_size_with_three_words(trie): + """Test size with three words inserted.""" + trie.insert('word') + trie.insert('world') + trie.insert('wordsmith') + assert trie.size() == 3 + + +def test_trie_remove_reduces_size(trie_10_words): + """Test trie remove reduces size as needed.""" + trie_10_words.remove('carol') + trie_10_words.remove('cookie') + assert trie_10_words.size() == 8 + + +def test_trie_remove_word_gone_from_contains(trie_10_words): + """Test trie remove word is gone from contains.""" + trie_10_words.remove('party') + assert trie_10_words.contains('party') is False + + +def test_trie_remove_word_with_shared_letters_leaves_other_word(trie_10_words): + """Test trie remove does not change other words when sharing letters.""" + trie_10_words.insert('Sweet') + trie_10_words.insert('Sweetheart') + trie_10_words.insert('Sweetie') + trie_10_words.insert('Sweat') + trie_10_words.insert('Sweatshirt') + trie_10_words.insert('Sweaty') + trie_10_words.remove('sweat') + assert trie_10_words.contains('sweat') is False + assert trie_10_words.contains('sweet') is True + assert trie_10_words.contains('sweetheart') is True + assert trie_10_words.contains('Sweetie') is True + assert trie_10_words.contains('sweaty') is True + assert trie_10_words.contains('sweatshirt') is True + assert trie_10_words.size() == 15 + + +def test_trie_remove_multiple_times_in_chained_words(trie_10_words): + """Test trie remove many words from shared letters only changes removed.""" + trie_10_words.insert('Sweet') + trie_10_words.insert('Sweetheart') + trie_10_words.insert('Sweetie') + trie_10_words.insert('Sweat') + trie_10_words.insert('Sweatshirt') + trie_10_words.insert('Sweaty') + trie_10_words.remove('sweaty') + trie_10_words.remove('sweater') + trie_10_words.remove('sweatshirt') + trie_10_words.remove('sweet') + assert trie_10_words.contains('sweat') is False + assert trie_10_words.contains('sweet') is False + assert trie_10_words.contains('sweetheart') is True + assert trie_10_words.contains('Sweetie') is True + assert trie_10_words.contains('sweaty') is False + assert trie_10_words.contains('sweatshirt') is False + assert trie_10_words.size() == 12 + + +def test_remove_if_word_reaches_root(trie): + """Test remove when word reaches root.""" + trie.insert('humbug') + trie.remove('humbug') + assert len(trie.root.children) == 0 + assert trie.contains('humbug') is False + assert trie.size() == 0 + + +def test_remove_raises_valueerror_if_word_not_in_tree(trie): + """Test remove raises value error if word not in tree.""" + with pytest.raises(ValueError): + trie.remove('humbug') + + +def test_insert_raises_typeerror_if_input_not_string(trie): + """Test insert raises type error if input not string.""" + with pytest.raises(TypeError): + trie.insert(40) diff --git a/src/trie.py b/src/trie.py new file mode 100644 index 0000000..c737192 --- /dev/null +++ b/src/trie.py @@ -0,0 +1,84 @@ +"""Trie tree for storing strings.""" + + +class Node(object): + """Node class for trie object.""" + + def __init__(self, letter): + """Initialize a new node instance for trie.""" + self.letter = letter + self.children = {} + self.parent = None + self.end = False + + +class Trie(object): + """Trie class for implementing a trie tree object.""" + + def __init__(self): + """Initialize a new instance of trie.""" + self.root = Node('root') + self._size = 0 + + def insert(self, string): + """Insert the given string into the trie. + + If character in the string is already present, it will be ignored. + """ + if not isinstance(string, str): + raise TypeError('You must enter a word.') + string = string.lower() + trace = self.root + for letter in string: + if letter in trace.children: + trace = trace.children[letter] + else: + trace.children[letter] = Node(letter) + trace.children[letter].parent = trace + trace = trace.children[letter] + trace.end = True + self._size += 1 + + def contains(self, string): + """Return True if the string is in the trie, False if not.""" + if not isinstance(string, str): + raise TypeError('You must search for a word.') + string = string.lower() + trace = self.root + for idx, letter in enumerate(string): + if letter in trace.children: + trace = trace.children[letter] + if trace.end is True and idx == len(string) - 1: + return True + elif trace.end is False and idx == len(string) - 1: + return False + else: + return False + + def size(self): + """Return the total number of words in trie. 0 if empty.""" + return self._size + + def remove(self, string): + """Remove given string from trie. If not in tree, raise exception.""" + if self.contains(string) is False: + raise ValueError('This word is not in the tree.') + string = string.lower() + trace = self.root + for letter in string: + trace = trace.children[letter] + if len(trace.children) > 0: + trace.end = False + else: + last = None + while True: + if trace.letter == 'root': + del trace.children[last] + break + elif len(trace.children) <= 1: + last = trace.letter + trace = trace.parent + else: + del trace.children[last] + break + self._size -= 1 diff --git a/tox.ini b/tox.ini new file mode 100644 index 0000000..06c5283 --- /dev/null +++ b/tox.ini @@ -0,0 +1,9 @@ +[tox] + +envlist = py27, py36 + +[testenv] +commands = py.test src --cov=src --cov-report term-missing +deps = + pytest + pytest-cov \ No newline at end of file