diff --git a/.gitignore b/.gitignore index c1d16d0..f4b321e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,5 @@ +*.swp +.DS_Store *.pyc algo_venv/ venv/ @@ -5,3 +7,7 @@ build/ *~ /dist/ /*.egg-info +*.ropeproject/ +docs/_build +.idea/* +.cache/ diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..482ebae --- /dev/null +++ b/.travis.yml @@ -0,0 +1,26 @@ +language: python + +sudo: false + +env: + - TOX_ENV=py27 + - TOX_ENV=py32 + - TOX_ENV=py33 + - TOX_ENV=py34 + - TOX_ENV=py35 + +matrix: + fast_finish: true + allow_failures: + - env: TOX_ENV=py35 + +install: + # virtualenv>=14.0.0 has dropped Python 3.2 support + - travis_retry pip install "virtualenv<14.0.0" "tox>=1.9" + +script: + - tox -e $TOX_ENV + +after_success: + - pip install codecov + - codecov -e TOX_ENV diff --git a/AUTHORS.rst b/AUTHORS.rst index 6b8efef..1ee1d3d 100644 --- a/AUTHORS.rst +++ b/AUTHORS.rst @@ -2,10 +2,13 @@ Development Lead: ----------------- - `Nic Young `_ +- `JoaoGFarias `_ List of contributors: -------------------- +- `dotslash `_ +- `travistrle `_ - `jxtcman `_ - `derv82 `_ - `ppinette `_ @@ -19,3 +22,9 @@ List of contributors: - `skirkpatrick `_ - `conanchou `_ - `lcheung90 `_ +- `rasbt `_ +- `JoaoGFarias `_ +- `kabrapratik28 `_ +- `JulianGriggs `_ +- `oprblackout `_ +- `NoahTheDuke `_ diff --git a/LICENSE.rst b/LICENSE.rst new file mode 100644 index 0000000..f876a15 --- /dev/null +++ b/LICENSE.rst @@ -0,0 +1,16 @@ +License: +======== + +Copyright (c) 2012-215 by Nic Young + +Some rights reserved. + +Redistribution and use in source and binary forms of the software as well as documentation, with or without modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + +* The names of the contributors may not be used to endorse or promote products derived from this software without specific prior written permission. + +THIS SOFTWARE AND DOCUMENTATION IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE AND DOCUMENTATION, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/README.rst b/README.rst index 51d08ab..7964a88 100644 --- a/README.rst +++ b/README.rst @@ -1,116 +1,73 @@ +**This repository is no longer maintained, but is being kept around for educational purposes. If you want a more complete algorithms repo check out: https://github.com/keon/algorithms** +===== + Algorithms ========== -This is an attempt to build a cohesive module of algorithms in Python. - -The purpose of this repo is to be a learning tool for myself and others. +.. image:: https://travis-ci.org/nryoung/algorithms.svg?branch=master + :target: https://travis-ci.org/nryoung/algorithms -I used psuedo code from various sources and I have listed them as references in the source code of each algorithm. +.. image:: http://codecov.io/github/nryoung/algorithms/coverage.svg?branch=master + :target: http://codecov.io/github/nryoung/algorithms?branch=master -Algorithms implemented so far: ------------------------------- +.. image:: https://readthedocs.org/projects/algorithms/badge/?version=latest + :target: http://algorithms.readthedocs.org/en/latest/?badge=latest -**Sorting:** +.. image:: https://badge.fury.io/py/algorithms.svg + :target: https://badge.fury.io/py/algorithms -- Bogo Sort -- Bubble Sort -- Cocktail Sort -- Comb Sort -- Heap Sort -- Insertion Sort -- Merge Sort -- Quick Sort -- In Place Quick Sort -- Selection Sort -- Shell Sort +Algorithms is a library of algorithms and data structures implemented in Python. -**Searching:** +The main purpose of this library is to be an educational tool. You probably +shouldn't use these in production, instead, opting for the optimized versions of +these algorithms that can be found else where. -- Binary Search -- Boyer-Moore-Horspool -- Knuth-Morris-Pratt -- Rabin-Karp -- Depth First Search (Recursive) +You should totally check out the `docs`_ for implementation details, complexities +and further info. -**Shuffling:** +Usage +----- -- Knuth/Fisher-Yates Shuffle +If you want to use the algorithms in your code it is as simple as: -**Math:** +:: -- Extended GCD + from algorithms.sorting import bubble_sort -**Random:** + my_list = bubble_sort.sort(my_list) -- Mersenne Twister +Features +-------- +- Pseudo code, algorithm complexities and futher info with each algorithm. +- Test coverage for each algorithm and data structure. +- Super sweet `documentation`_. Installation: ------------- -If you want to use the algorithms directly, simply +Installation is as easy as: :: $ pip install algorithms -If you want to examine the algorithms source, then you should clone this repo. - -Usage: ------- - -Once installed you can simply do the following in your program: - -:: - - from algorithms.sorting import bubble_sort - - my_list = bubble_sort.sort(my_list) - - -All prequisites for the algorithms are listed in the source code for each algorithm. - Tests: ------ -Nose is used as the main test runner and all Unit Tests can be run by: +Pytest is used as the main test runner and all Unit Tests can be run with: :: - $ python algorithms/run_tests.py + $ ./run_tests.py Contributing: ------------- -If there is an algorithm or data structure that you do not see, but would like to add please feel free to do a pull request. I only ask two things: - -1. For each algorithm and data structure you implement please have corresponding unit tests to prove correctness. -2. Please make sure that your module follows similar style guidelines that are laid out in the other modules. - -I want to personally thank everybody that has contributed so far and your names will be added to `AUTHORS.rst`. - - -TODO: ------ - -See `TODO.rst`. - - -License: --------- - -Copyright (c) 2012 by Nic Young and contributors. See AUTHORS.rst for more details - -Some rights reserved. - -Redistribution and use in source and binary forms of the software as well as documentation, with or without modification, are permitted provided that the following conditions are met: - -* Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. - -* Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. - -* The names of the contributors may not be used to endorse or promote products derived from this software without specific prior written permission. +Contributions are always welcome. Check out the contributing guidelines to get +started. -THIS SOFTWARE AND DOCUMENTATION IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE AND DOCUMENTATION, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +.. _`docs`: http://algorithms.readthedocs.org/en/latest/ +.. _`documentation`: http://algorithms.readthedocs.org/en/latest/ diff --git a/TODO.rst b/TODO.rst index cd38ccb..0acbd04 100644 --- a/TODO.rst +++ b/TODO.rst @@ -11,16 +11,11 @@ Below is an ever changing list of things that I would like to accomplish or impl - Binary Tree Sort - Cycle Sort - Smoothsort - - Strand Sort - - Gnome Sort - Divide and Conquer - Maximum Subarray - Strassen's Matrix Multiplication - *k*-Selection (Minimum, Maximum, Median, Arbitrary *k*) - Data Structures - - Stacks - - Queues - - Linked Lists - Hash Tables - Binary Search Trees - Red-Black Trees @@ -29,7 +24,6 @@ Below is an ever changing list of things that I would like to accomplish or impl - van Emde Boas Trees - Dynamic Programming - Matrix-Chain Multiplication - - Longest Common Subsequence - Huffman Encoding - Graph Algorithms - Breadth-First Search @@ -54,6 +48,13 @@ Below is an ever changing list of things that I would like to accomplish or impl - Primality Testing - Integer Factorization - Closest Pair of Points + - Feature Subset Search Algorithms + - Sequential Search Algorithms + - Sequential Forward Selection (SBS) + - Sequential Backward Selection (SFS) + - 'plus l take away r' - algorithm (SBS + SFS) + - Sequential Floating Forward Algorithm (SFFS) + **Misc.:** diff --git a/algorithms/tests/__init__.py b/algorithms/data_structures/__init__.py similarity index 100% rename from algorithms/tests/__init__.py rename to algorithms/data_structures/__init__.py diff --git a/algorithms/data_structures/binary_search_tree.py b/algorithms/data_structures/binary_search_tree.py new file mode 100644 index 0000000..eb6db79 --- /dev/null +++ b/algorithms/data_structures/binary_search_tree.py @@ -0,0 +1,395 @@ +""" + Binary Search Tree + ------------------ + The Binary Search Tree represents an ordered symbol table of generic + key-value pairs. Keys must be comparable. Does not permit duplicate keys. + When assocating a value with a key already present in the BST, the previous + value is replaced by the new one. This implementation is for an unbalanced + BST. + + Pseudo Code: http://algs4.cs.princeton.edu/32bst +""" + + +class Node(object): + """ + Implementation of a Node in a Binary Search Tree. + """ + + def __init__(self, key=None, val=None, size_of_subtree=1): + self.key = key + self.val = val + self.size_of_subtree = size_of_subtree + self.left = None + self.right = None + + +class BinarySearchTree(object): + """ + Implementation of a Binary Search Tree. + """ + + def __init__(self): + self.root = None + + def _size(self, node): + if node is None: + return 0 + else: + return node.size_of_subtree + + def size(self): + """ + Return the number of nodes in the BST + + Worst Case Complexity: O(1) + + Balanced Tree Complexity: O(1) + """ + return self._size(self.root) + + def is_empty(self): + """ + Returns True if the BST is empty, False otherwise + + Worst Case Complexity: O(1) + + Balanced Tree Complexity: O(1) + """ + return self.size() == 0 + + def _get(self, key, node): + if node is None: + return None + + if key < node.key: + return self._get(key, node.left) + elif key > node.key: + return self._get(key, node.right) + else: + return node.val + + def get(self, key): + """ + Return the value paired with 'key' + + Worst Case Complexity: O(N) + + Balanced Tree Complexity: O(lg N) + """ + return self._get(key, self.root) + + def contains(self, key): + """ + Returns True if the BST contains 'key', False otherwise + + Worst Case Complexity: O(N) + + Balanced Tree Complexity: O(lg N) + """ + return self.get(key) is not None + + def _put(self, key, val, node): + + # If we hit the end of a branch, create a new node + if node is None: + return Node(key, val) + + # Follow left branch + if key < node.key: + node.left = self._put(key, val, node.left) + # Follow right branch + elif key > node.key: + node.right = self._put(key, val, node.right) + # Overwrite value + else: + node.val = val + + node.size_of_subtree = self._size(node.left) + self._size(node.right)+1 + return node + + def put(self, key, val): + """ + Add a new key-value pair. + + Worst Case Complexity: O(N) + + Balanced Tree Complexity: O(lg N) + """ + self.root = self._put(key, val, self.root) + + def _min_node(self): + """ + Return the node with the minimum key in the BST + """ + min_node = self.root + # Return none if empty BST + if min_node is None: + return None + + while min_node.left is not None: + min_node = min_node.left + + return min_node + + def min_key(self): + """ + Return the minimum key in the BST + + Worst Case Complexity: O(N) + + Balanced Tree Complexity: O(lg N) + """ + min_node = self._min_node() + if min_node is None: + return None + else: + return min_node.key + + def _max_node(self): + """ + Return the node with the maximum key in the BST + """ + max_node = self.root + # Return none if empty BST + if max_node is None: + return None + + while max_node.right is not None: + max_node = max_node.right + + return max_node + + def max_key(self): + """ + Return the maximum key in the BST + + Worst Case Complexity: O(N) + + Balanced Tree Complexity: O(lg N) + """ + max_node = self._max_node() + if max_node is None: + return None + else: + return max_node.key + + def _floor_node(self, key, node): + """ + Returns the node with the biggest key that is less than or equal to the + given value 'key' + """ + if node is None: + return None + + if key < node.key: + # Floor must be in left subtree + return self._floor_node(key, node.left) + + elif key > node.key: + # Floor is either in right subtree or is this node + attempt_in_right = self._floor_node(key, node.right) + if attempt_in_right is None: + return node + else: + return attempt_in_right + + else: + # Keys are equal so floor is node with this key + return node + + def floor_key(self, key): + """ + Returns the biggest key that is less than or equal to the given value + 'key' + + Worst Case Complexity: O(N) + + Balanced Tree Complexity: O(lg N) + """ + floor_node = self._floor_node(key, self.root) + if floor_node is None: + return None + else: + return floor_node.key + + def _ceiling_node(self, key, node): + """ + Returns the node with the smallest key that is greater than or equal to + the given value 'key' + """ + if node is None: + return None + + if key < node.key: + # Ceiling is either in left subtree or is this node + attempt_in_left = self._ceiling_node(key, node.left) + if attempt_in_left is None: + return node + else: + return attempt_in_left + elif key > node.key: + # Ceiling must be in right subtree + return self._ceiling_node(key, node.right) + else: + # Keys are equal so ceiling is node with this key + return node + + def ceiling_key(self, key): + """ + Returns the smallest key that is greater than or equal to the given + value 'key' + + Worst Case Complexity: O(N) + + Balanced Tree Complexity: O(lg N) + """ + ceiling_node = self._ceiling_node(key, self.root) + if ceiling_node is None: + return None + else: + return ceiling_node.key + + def _select_node(self, rank, node): + """ + Return the node with rank equal to 'rank' + """ + if node is None: + return None + + left_size = self._size(node.left) + if left_size < rank: + return self._select_node(rank - left_size - 1, node.right) + elif left_size > rank: + return self._select_node(rank, node.left) + else: + return node + + def select_key(self, rank): + """ + Return the key with rank equal to 'rank' + + Worst Case Complexity: O(N) + + Balanced Tree Complexity: O(lg N) + """ + select_node = self._select_node(rank, self.root) + if select_node is None: + return None + else: + return select_node.key + + def _rank(self, key, node): + if node is None: + return None + + if key < node.key: + return self._rank(key, node.left) + elif key > node.key: + return self._size(node.left) + self._rank(key, node.right) + 1 + + else: + return self._size(node.left) + + def rank(self, key): + """ + Return the number of keys less than a given 'key'. + + Worst Case Complexity: O(N) + + Balanced Tree Complexity: O(lg N) + """ + return self._rank(key, self.root) + + def _delete(self, key, node): + if node is None: + return None + if key < node.key: + node.left = self._delete(key, node.left) + elif key > node.key: + node.right = self._delete(key, node.right) + + else: + if node.right is None: + return node.left + elif node.left is None: + return node.right + else: + old_node = node + node = self._ceiling_node(key, node.right) + node.right = self._delete_min(old_node.right) + node.left = old_node.left + node.size_of_subtree = self._size(node.left) + self._size(node.right)+1 + return node + + def delete(self, key): + """ + Remove the node with key equal to 'key' + + Worst Case Complexity: O(N) + + Balanced Tree Complexity: O(lg N) + """ + self.root = self._delete(key, self.root) + + def _delete_min(self, node): + if node.left is None: + return node.right + + node.left = self._delete_min(node.left) + node.size_of_subtree = self._size(node.left) + self._size(node.right)+1 + return node + + def delete_min(self): + """ + Remove the key-value pair with the smallest key. + + + Worst Case Complexity: O(N) + + Balanced Tree Complexity: O(lg N) + """ + self.root = self._delete_min(self.root) + + def _delete_max(self, node): + if node.right is None: + return node.left + + node.right = self._delete_max(node.right) + node.size_of_subtree = self._size(node.left) + self._size(node.right)+1 + return node + + def delete_max(self): + """ + Remove the key-value pair with the largest key. + + Worst Case Complexity: O(N) + + Balanced Tree Complexity: O(lg N) + """ + self.root = self._delete_max(self.root) + + def _keys(self, node, keys): + if node is None: + return keys + + if node.left is not None: + keys = self._keys(node.left, keys) + + keys.append(node.key) + + if node.right is not None: + keys = self._keys(node.right, keys) + + return keys + + def keys(self): + """ + Return all of the keys in the BST in aschending order + + Worst Case Complexity: O(N) + + Balanced Tree Complexity: O(N) + """ + keys = [] + return self._keys(self.root, keys) diff --git a/algorithms/data_structures/digraph.py b/algorithms/data_structures/digraph.py new file mode 100644 index 0000000..f0d4d7a --- /dev/null +++ b/algorithms/data_structures/digraph.py @@ -0,0 +1,107 @@ +""" + Directed Graph + -------------- + The Digraph class represents a directed graph of vertices + which can be any hashable value. Parallel edges and self-loops are permitted. + + Pseudo Code: http://algs4.cs.princeton.edu/42directed/Digraph.java.html + """ + + +class Digraph(): + def __init__(self): + self.__adj = {} + self.__v_count = 0 + self.__e_count = 0 + + def vertex_count(self): + """ + Returns the number of vertices in the graph. + + Worst Case Complexity: O(1) + """ + + return self.__v_count + + def edge_count(self): + """ + Returns the number of edges in the graph. + + Worst Case Complexity: O(1) + """ + + return self.__e_count + + def add_edge(self, src, dest): + """ + Adds an undirected edge 'src'-'dest' to the graph. + + Worst Case Complexity O(1) + """ + + if src in self.__adj: + self.__adj[src].append(dest) + else: + self.__adj[src] = [dest] + self.__v_count += 1 + + if dest in self.__adj: + pass + else: + self.__adj[dest] = [] + self.__v_count += 1 + + self.__e_count += 1 + + def adj(self, src): + """ + Returns the vertices adjacent to vertex 'src'. + + Worst Case Complexity: O(1) + """ + return self.__adj[src] + + def outdegree(self, src): + """ + Returns the degree of the vertex 'src' + + Worst Case Complexity: O(1) + """ + if src in self.__adj: + return len(self.__adj[src]) + else: + raise LookupError("This vertex is not in the graph.") + + def vertices(self): + """ + Returns an iterable of all the vertices in the graph. + + Worst Case Complexity: O(V) + """ + return self.__adj.keys() + + def reverse(self): + """ + Returns the reverse of this digraph + + Worst Case Complexity: O(V+E) + """ + digraph_reversed = Digraph() + old_vertices = self.vertices() + + for src in old_vertices: + for dest in self.adj(src): + digraph_reversed.add_edge(dest, src) + return digraph_reversed + + def __str__(self): + s = [] + s.append("{0} vertices and {1} edges \n".format(self.__v_count, + self.__e_count)) + for key in self.vertices(): + s.append("{0}: ".format(key)) + for val in self.adj(key): + s.append("{0} ".format(val)) + s.append("\n") + + return "".join(s) diff --git a/algorithms/data_structures/lcp_array.py b/algorithms/data_structures/lcp_array.py new file mode 100644 index 0000000..6252b2d --- /dev/null +++ b/algorithms/data_structures/lcp_array.py @@ -0,0 +1,99 @@ +import math + +""" + Suffix Array + ------------------ + In computer science, a suffix array is a sorted array of all suffixes + of a string. It is a data structure used, among others, in full text + indices, data compression algorithms and within the field + of bioinformatics. + + for more info : http://algs4.cs.princeton.edu/63suffix/ + Complexity : + worst case : O(n log(n)) +""" + + +def suffix_array(t): + """ + Suffix array of a string t + :param t: the string to extract suffix array from + :return (s_array, rank): return a tuple that contain the suffix array + and the rank array which is the reversed version of the suffix array + """ + length = len(t) + rank = [0] * length + s_array = [0] * length + tuple_array = [0] * length + iterations = int(math.log(length, 2)) + 1 + size = 1 + + for i, t in enumerate(t): + s_array[i] = ord(t) + + for _ in range(iterations): + for i, ele in enumerate(tuple_array): + if i + size < length: + tuple_array[i] = ((s_array[i], s_array[i + size]), i) + else: + tuple_array[i] = ((s_array[i], -1), i) + tuple_array.sort() + s_array[tuple_array[0][1]] = 0 + for i in range(1, len(tuple_array)): + cls, idx = tuple_array[i] + if cls == tuple_array[i - 1][0]: + s_array[idx] = s_array[tuple_array[i - 1][1]] + else: + s_array[idx] = s_array[tuple_array[i - 1][1]] + 1 + size *= 2 + + for i, p in enumerate(s_array): + rank[p] = i + + return s_array, rank + + +""" + LCP Array + ------------------ + the longest common prefix array (LCP array) is an auxiliary data + structure to the suffix array. It stores the lengths of the longest + common prefixes (LCPs) between all pairs of consecutive suffixes + in a sorted suffix array. + + I use Kasai's algorithm in implementation : + Pseudo Code: http://algs4.cs.princeton.edu/32bst + + Complexity : + worst case :O(n) + +""" + + +def lcp_array(t_str, s_array, rank): + """ + + :param t_str: the string to calculate the lcp array for + :param s_array: the suffix array of the string + :param rank: the suffix array reversed + :return: the lcp array + """ + t_length = len(t_str) + lcp = [0] * t_length + last_lcp = 1 + + for i, ele in enumerate(s_array): + last_lcp = last_lcp - 1 if last_lcp > 1 else 0 + if ele == t_length - 1: + last_lcp = 0 + lcp[ele] = last_lcp + continue + n_suffix = rank[ele + 1] + + while i + last_lcp < t_length \ + and n_suffix + last_lcp < t_length \ + and t_str[i + last_lcp] == t_str[n_suffix + last_lcp]: + last_lcp += 1 + lcp[ele] = last_lcp + + return lcp diff --git a/algorithms/data_structures/queue.py b/algorithms/data_structures/queue.py new file mode 100644 index 0000000..1570c04 --- /dev/null +++ b/algorithms/data_structures/queue.py @@ -0,0 +1,53 @@ +""" + Queue + ----- + A Queue is a linear data structure, or more abstractly a sequential + collection. The entities in the collection are kept in order and the + principal (or only) operations on the collection are the addition of + entities to the rear terminal position, known as enqueue, and removal of + entities from the front terminal position, known as dequeue. This makes the + queue a First-In-First-Out (FIFO) data structure. In a FIFO data structure, + the first element added to the queue will be the first one to be removed. + + Pseudo Code: https://en.wikipedia.org/wiki/Queue_%28abstract_data_type%29 +""" +from collections import deque + + +class Queue: + + def __init__(self): + self._queue = deque([]) + + def add(self, value): + """ + Add element as the last item in the Queue. + + Worst Case Complexity: O(1) + """ + self._queue.append(value) + + def remove(self): + """ + Remove element from the front of the Queue and return it's value. + + Worst Case Complexity: O(1) + """ + + return self._queue.popleft() + + def is_empty(self): + """ + Returns a boolean indicating if the Queue is empty. + + Worst Case Complexity: O(1) + """ + return not len(self._queue) + + def size(self): + """ + Return size of the Queue. + + Worst Case Complexity: O(1) + """ + return len(self._queue) diff --git a/algorithms/data_structures/singly_linked_list.py b/algorithms/data_structures/singly_linked_list.py new file mode 100644 index 0000000..c08a0d8 --- /dev/null +++ b/algorithms/data_structures/singly_linked_list.py @@ -0,0 +1,92 @@ +""" + Singly Linked List + ------------------ + A linked list is a data structure consisting of a group of nodes which + together represent a sequence. Under the simplest form, each node is + composed of data and a reference (in other words, a link) to the next + node in the sequence; more complex variants add additional links. This + structure allows for efficient insertion or removal of elements from any + position in the sequence. + + Pseudo Code: https://en.wikipedia.org/wiki/Linked_list +""" + + +class Node: + + def __init__(self, data=None, next=None): + self.data = data + self.next = next + + def set_data(self, data): + self.data = data + + def get_data(self): + return self.data + + def set_next(self, next): + self.next = next + + def get_next(self): + return self.next + + +class SinglyLinkedList: + + def __init__(self): + self.head = None + self.size = 0 + + def add(self, value): + """ + Add element to list + + Time Complexity: O(N) + """ + node = Node(value) + node.set_next(self.head) + self.head = node + self.size += 1 + + def _search_node(self, value, remove=False): + current = self.head + previous = None + + while current: + if current.data == value: + break + else: + previous = current + current = current.next + + if remove and current: + if previous is None: # Head node + self.head = current.next + else: # None head node + previous.set_next(current.next) + self.size -= 1 + + return current is not None + + def remove(self, value): + """ + Remove element from list + + Time Complexity: O(N) + """ + + return self._search_node(value, True) + + def search(self, value): + """ + Search for value in list + + Time Complexity: O(N) + """ + return self._search_node(value) + + def size(self): + """ + Return size of list + """ + return self.size diff --git a/algorithms/data_structures/stack.py b/algorithms/data_structures/stack.py new file mode 100644 index 0000000..56e687b --- /dev/null +++ b/algorithms/data_structures/stack.py @@ -0,0 +1,49 @@ +""" + Stack + ----- + A stack or LIFO (last in, first out) is an abstract data type that serves + as a collection of elements, with two principal operations: push, which + adds an element to the collection, and pop, which removes the last element + that was added. + + Pseudo Code: https://en.wikipedia.org/wiki/Stack_%28abstract_data_type%29 +""" + + +class Stack: + + def __init__(self): + self.stack_list = [] + + def add(self, value): + """ + Add element at last + + Time Complexity: O(1) + """ + self.stack_list.append(value) + + def remove(self): + """ + Remove element from last return value + + Time Complexity: O(1) + """ + + return self.stack_list.pop() + + def is_empty(self): + """ + 1 value returned on empty 0 value returned on not empty + + Time Complexity: O(1) + """ + return not self.size() + + def size(self): + """ + Return size of stack + + Time Complexity: O(1) + """ + return len(self.stack_list) diff --git a/algorithms/data_structures/undirected_graph.py b/algorithms/data_structures/undirected_graph.py new file mode 100644 index 0000000..2c70e36 --- /dev/null +++ b/algorithms/data_structures/undirected_graph.py @@ -0,0 +1,92 @@ +""" + Undirected Graph + ---------------- + The Undirected_Graph class represents an undirected graph of vertices + which can be any hashable value. + + Pseudo Code: http://algs4.cs.princeton.edu/41undirected/Graph.java.html +""" + + +class Undirected_Graph: + def __init__(self): + self.__adj = {} + self.__v_count = 0 + self.__e_count = 0 + + def vertex_count(self): + """ + Returns the number of vertices in the graph. + + Time Complexity: O(1) + """ + + return self.__v_count + + def edge_count(self): + """ + Returns the number of edges in the graph. + + Time Complexity: O(1) + """ + + return self.__e_count + + def add_edge(self, src, dest): + """ + Adds an undirected edge 'src'-'dest' to the graph. + + Time Complexity: O(1) + """ + if src in self.__adj: + self.__adj[src].append(dest) + else: + self.__adj[src] = [dest] + self.__v_count += 1 + + if dest in self.__adj: + self.__adj[dest].append(src) + else: + self.__adj[dest] = [src] + self.__v_count += 1 + + self.__e_count += 1 + + def adj(self, src): + """ + Returns the vertices adjacent to vertex 'src'. + + Time Complexity: O(1) + """ + return self.__adj[src] + + def degree(self, src): + """ + Returns the degree of the vertex 'src' + + Time Complexity: O(1) + """ + if src in self.__adj: + return len(self.__adj[src]) + else: + raise LookupError("This vertex is not in the graph.") + + def vertices(self): + """ + Returns an iterable of all the vertices in the graph. + + Time Complexity: O(V) + """ + return self.__adj.keys() + + def __str__(self): + s = [] + s.append("{0} vertices and {1} edges \n".format(self.__v_count, + self.__e_count)) + for key in self.vertices(): + s.append("{0}: ".format(key)) + for val in self.adj(key): + s.append("{0} ".format(val)) + s.append("\n") + + return "".join(s) diff --git a/algorithms/data_structures/union_find.py b/algorithms/data_structures/union_find.py new file mode 100644 index 0000000..3b13d75 --- /dev/null +++ b/algorithms/data_structures/union_find.py @@ -0,0 +1,69 @@ +""" + Union Find: + ----------- + A disjoint-set data structure, also called union-find data structure + implements two functions: + + union(A, B) - merge A's set with B's set + + find(A) - finds what set A belongs to + + + Naive approach: + + Find follows parent nodes until it reaches the root. + Union combines two trees into one by attaching the root of one to the + root of the other + + Time Complexity : O(N) (a highly unbalanced tree might be created, + nothing better a linked-list) + + Psuedo Code: http://en.wikipedia.org/wiki/Disjoint-set_data_structure +""" + + +class UnionFind: + def __init__(self, N): + if type(N) != int: + raise TypeError("size must be integer") + if N < 0: + raise ValueError("N cannot be a negative integer") + self.__parent = [] + self.__N = N + for i in range(0, N): + self.__parent.append(i) + + def make_set(self, x): + if type(x) != int: + raise TypeError("x must be integer") + if x != self.__N: + raise ValueError( + "a new element must have index {0}".format(self.__N) + ) + self.__parent.append(x) + self.__N = self.__N + 1 + + def union(self, x, y): + self.__validate_ele(x) + self.__validate_ele(y) + x_root = self.find(x) + y_root = self.find(y) + self.__parent[x_root] = y_root + + def find(self, x): + self.__validate_ele(x) + if self.__parent[x] == x: + return x + else: + return self.find(self.__parent[x]) + + def is_connected(self, x, y): + self.__validate_ele(x) + self.__validate_ele(y) + return self.find(x) == self.find(y) + + def __validate_ele(self, x): + if type(x) != int: + raise TypeError("{0} is not an integer".format(x)) + if x < 0 or x >= self.__N: + raise ValueError("{0} is not in [0,{1})".format(x, self.__N)) diff --git a/algorithms/data_structures/union_find_by_rank.py b/algorithms/data_structures/union_find_by_rank.py new file mode 100644 index 0000000..cb8dda8 --- /dev/null +++ b/algorithms/data_structures/union_find_by_rank.py @@ -0,0 +1,76 @@ +""" + Union Find by Rank + ------------------ + A disjoint-set data structure, also called union-find data structure + implements two functions: + + union(A, B) - merge A's set with B's set + + find(A) - finds what set A belongs to + + Union by rank approach: + attach the smaller tree to the root of the larger tree + + Time Complexity : O(logn) + + Psuedo Code: http://en.wikipedia.org/wiki/Disjoint-set_data_structure +""" + + +class UnionFindByRank: + def __init__(self, N): + if type(N) != int: + raise TypeError("size must be integer") + if N < 0: + raise ValueError("N cannot be a negative integer") + self.__parent = [] + self.__rank = [] + self.__N = N + for i in range(0, N): + self.__parent.append(i) + self.__rank.append(0) + + def make_set(self, x): + if type(x) != int: + raise TypeError("x must be integer") + if x != self.__N: + raise ValueError( + "a new element must have index {0}".format(self.__N) + ) + self.__parent.append(x) + self.__rank.append(0) + self.__N = self.__N + 1 + + def union(self, x, y): + self.__validate_ele(x) + self.__validate_ele(y) + x_root = self.find(x) + y_root = self.find(y) + if x_root == y_root: + return + # x and y are not already in same set. Merge them + if self.__rank[x_root] < self.__rank[y_root]: + self.__parent[x_root] = y_root + elif self.__rank[x_root] > self.__rank[y_root]: + self.__parent[y_root] = x_root + else: + self.__parent[y_root] = x_root + self.__rank[x_root] = self.__rank[x_root] + 1 + + def find(self, x): + self.__validate_ele(x) + if self.__parent[x] == x: + return x + else: + return self.find(self.__parent[x]) + + def is_connected(self, x, y): + self.__validate_ele(x) + self.__validate_ele(y) + return self.find(x) == self.find(y) + + def __validate_ele(self, x): + if type(x) != int: + raise TypeError("{0} is not an integer".format(x)) + if x < 0 or x >= self.__N: + raise ValueError("{0} is not in [0,{1})".format(x, self.__N)) diff --git a/algorithms/data_structures/union_find_with_path_compression.py b/algorithms/data_structures/union_find_with_path_compression.py new file mode 100644 index 0000000..3d2fa9b --- /dev/null +++ b/algorithms/data_structures/union_find_with_path_compression.py @@ -0,0 +1,88 @@ +""" + Union Find with path compression + -------------------------------- + A disjoint-set data structure, also called union-find data structure + implements two functions: + + union(A, B) - merge A's set with B's set + + find(A) - finds what set A belongs to + + Union with path compression approach: + + Each node visited on the way to a root node may as well be attached + directly to the root node. + attach the smaller tree to the root of the larger tree + + Time Complexity : O(a(n)), where a(n) is the inverse of the function + n=f(x)=A(x,x) and A is the extremely fast-growing Ackermann function. + + Psuedo Code: http://en.wikipedia.org/wiki/Disjoint-set_data_structure +""" + + +class UnionFindWithPathCompression: + def __init__(self, N): + if type(N) != int: + raise TypeError("size must be integer") + if N < 0: + raise ValueError("N cannot be a negative integer") + self.__parent = [] + self.__rank = [] + self.__N = N + for i in range(0, N): + self.__parent.append(i) + self.__rank.append(0) + + def make_set(self, x): + if type(x) != int: + raise TypeError("x must be integer") + if x != self.__N: + raise ValueError( + "a new element must have index {0}".format(self.__N)) + self.__parent.append(x) + self.__rank.append(0) + self.__N = self.__N + 1 + + def union(self, x, y): + self.__validate_ele(x) + self.__validate_ele(y) + x_root = self.__find(x) + y_root = self.__find(y) + if x_root == y_root: + return + # x and y are not already in same set. Merge them + if self.__rank[x_root] < self.__rank[y_root]: + self.__parent[x_root] = y_root + elif self.__rank[x_root] > self.__rank[y_root]: + self.__parent[y_root] = x_root + else: + self.__parent[y_root] = x_root + self.__rank[x_root] = self.__rank[x_root] + 1 + + def __find(self, x): + if self.__parent[x] != x: + self.__parent[x] = self.__find(self.__parent[x]) + return self.__parent[x] + + def find(self, x): + self.__validate_ele(x) + if self.__parent[x] == x: + return x + else: + return self.find(self.__parent[x]) + + def is_connected(self, x, y): + self.__validate_ele(x) + self.__validate_ele(y) + return self.find(x) == self.find(y) + + # use for unit testing check if the path is compressed + def parent(self, x): + return self.__parent[x] + + def __validate_ele(self, x): + if type(x) != int: + raise TypeError("{0} is not an integer".format(x)) + if x < 0 or x >= self.__N: + raise ValueError("{0} is not in [0,{1})".format(x, self.__N)) diff --git a/algorithms/dynamic_programming/__init__.py b/algorithms/dynamic_programming/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/algorithms/dynamic_programming/lcs.py b/algorithms/dynamic_programming/lcs.py new file mode 100644 index 0000000..f386b7a --- /dev/null +++ b/algorithms/dynamic_programming/lcs.py @@ -0,0 +1,49 @@ +""" + Longest Common Subsequence + -------------------------- + Implements the dynamic programming solution to the longest common + subsequence algorithm. + + Pseudo Code: + http://en.wikipedia.org/wiki/Longest_common_subsequence_problem +""" + + +def build_lengths_matrix(str1, str2): + """ + XXX: Needs documentation written. + """ + matrix = [[0 for j in range(len(str2)+1)] for i in range(len(str1)+1)] + for i, x in enumerate(str1): + for j, y in enumerate(str2): + if x == y: + matrix[i+1][j+1] = matrix[i][j] + 1 + else: + matrix[i+1][j+1] = max(matrix[i+1][j], matrix[i][j+1]) + return matrix + + +def read_from_matrix(matrix, str1, str2): + """ + XXX: Needs documentation written. + """ + result = "" + i, j = len(str1), len(str2) + while i != 0 and j != 0: + if matrix[i][j] == matrix[i-1][j]: + i -= 1 + elif matrix[i][j] == matrix[i][j-1]: + j -= 1 + else: + result += str1[i-1] + i -= 1 + j -= 1 + return result[::-1] + + +def lcs(str1, str2): + """ + XXX: Needs documentation written. + """ + lengths = build_lengths_matrix(str1, str2) + return read_from_matrix(lengths, str1, str2) diff --git a/algorithms/factorization/__init__.py b/algorithms/factorization/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/algorithms/factorization/fermat.py b/algorithms/factorization/fermat.py new file mode 100644 index 0000000..e132557 --- /dev/null +++ b/algorithms/factorization/fermat.py @@ -0,0 +1,33 @@ +""" + Fermat Factorization + -------------------- + Fermat's factorization method is based on the representation + of an odd integer as the difference of two squares: + + N = a*a-b*b = (a-b)*(a+b) + +""" +from math import sqrt + + +def fermat(n): + """ + Factorization of the integer `n`. + + :param n: An integer to be factored. + :rtype: The factorization of `n`. + """ + if n & 1 == 0: + return [n >> 1, 2] + x = int(sqrt(n)) + if x*x == n: + return [x, x] + x += 1 + while True: + y2 = x*x-n + y = int(sqrt(y2)) + if y*y == y2: + break + else: + x += 1 + return [x-y, x+y] diff --git a/algorithms/factorization/pollard_rho.py b/algorithms/factorization/pollard_rho.py new file mode 100644 index 0000000..a6aaf7d --- /dev/null +++ b/algorithms/factorization/pollard_rho.py @@ -0,0 +1,63 @@ +""" + Pollard Rho Algorithm + --------------------- + Pollard's rho algorithm is a special-purpose integer factorization + algorithm. It was invented by John Pollard in 1975. It is particularly + effective for a composite number having a small prime factor. + +""" +import random + +from algorithms.math.primality_test import is_prime +from fractions import gcd + + +def f(x): + """ + """ + return x*x+1 + + +def rho(n, x1=2, x2=2): + """ + """ + if n % 2 == 0: + return 2 + i = 0 + while True: + x1 = f(x1) % n + x2 = f(f(x2)) % n + divisor = gcd(abs(x1-x2), n) + i += 1 + if(divisor != 1): + break + if i > 500: + x1 = random.randint(1, 10) + x2 = random.randint(1, 10) + i = 0 + return divisor + + +def pollard_rho_rec(x, factors): + """ + """ + if x == 1: + return + + if is_prime(x): + factors.append(x) + return + + divisor = rho(int(x), random.randint(1, 10), random.randint(1, 10)) + pollard_rho_rec(int(divisor), factors) + pollard_rho_rec(int(x/divisor), factors) + + +def pollard_rho(x): + """ + """ + if x == 1 or x == 0: + return [x] + factors = [] + pollard_rho_rec(x, factors) + return factors diff --git a/algorithms/factorization/trial_division.py b/algorithms/factorization/trial_division.py new file mode 100644 index 0000000..dc1804d --- /dev/null +++ b/algorithms/factorization/trial_division.py @@ -0,0 +1,30 @@ +""" + Trial Division + -------------- + Trial division is the most laborious but easiest + to understand of the integer factorization algorithms. + Try to divide a number n by all prime numbers < sqrt(n). + +""" +from algorithms.math.sieve_eratosthenes import eratosthenes + + +def trial_division(n): + """ + Uses trial division to find prime factors of `n`. + + :param n: An integer to factor. + :rtype: The prime factors of `n` + """ + prime_factors = [] + if n < 2: + return prime_factors + for p in eratosthenes(int(n**0.5) + 1): + if p*p > n: + break + while n % p == 0: + prime_factors.append(p) + n //= p + if n > 1: + prime_factors.append(n) + return prime_factors diff --git a/algorithms/math/approx_cdf.py b/algorithms/math/approx_cdf.py new file mode 100644 index 0000000..346d24f --- /dev/null +++ b/algorithms/math/approx_cdf.py @@ -0,0 +1,36 @@ +""" + Approximate Cumulative Distribution Function + -------------------------------------------- + Calculates the cumulative distribution function (CDF) + of the normal distribution based on an approximation by George Marsaglia: + Marsaglia, George (2004). "Evaluating the Normal Distribution". + Journal of Statistical Software 11 (4). + + 16 digit precision for 300 iterations when x = 10. + + Equation: + + + f(x) = 1/2 + pdf(x) * (x + (x^3/3) + (x^5/3*5) + (x^7/3*7) + ...) +""" + +from algorithms.math import std_normal_pdf + + +def cdf(x, iterations=300): + """ + Calculates the cumulative distribution function of the normal distribution. + Uses a taylor exponent to calculate this. + + :param x: An integer that represents the taylor exponent. + :param iterations: An integer representing the number of iterations. + :rtype: The normal distribution + """ + product = 1.0 + taylor_exp = [x] + for i in range(3, iterations, 2): + product *= i + taylor_exp.append(float(x**i)/product) + taylor_fact = sum(taylor_exp) + + return (0.5 + (taylor_fact * std_normal_pdf.pdf(x, mean=0, std_dev=1))) diff --git a/algorithms/math/extended_gcd.py b/algorithms/math/extended_gcd.py index 4083d84..ec9ed66 100644 --- a/algorithms/math/extended_gcd.py +++ b/algorithms/math/extended_gcd.py @@ -1,17 +1,20 @@ """ - extended_gcd.py - - This module implements the extended greatest common divider algorithm. - - Pre: two integers a and b - Post: a tuple (x, y) where a*x + b*y = gcd(a, b) + Extended Greatest Common Divisor + -------------------------------- + Implementation of the extended greatest common divisor algorithm. Pseudo Code: http://en.wikipedia.org/wiki/Extended_Euclidean_algorithm """ def extended_gcd(p, q): + """ + Find the greatest common divisor and returns them. + :param a: An integer. + :param b: An integer. + :rtype: A tuple representing the greatest common divisor. + """ (a, b) = (p, q) if a < 0: @@ -27,7 +30,7 @@ def extended_gcd(p, q): y0 = 1 while(b != 0): - quotient = a / b + quotient = a // b (a, b) = (b, a % b) (x1, x0) = (x0 - quotient * x1, x1) (y1, y0) = (y0 - quotient * y1, y1) diff --git a/algorithms/math/lcm.py b/algorithms/math/lcm.py new file mode 100644 index 0000000..4801ef6 --- /dev/null +++ b/algorithms/math/lcm.py @@ -0,0 +1,21 @@ +""" + Lowest Common Multiple + ---------------------- + Simple implementation of the Lowest Common Multiple Algorithm. + + Pseudo Code: https://en.wikipedia.org/wiki/Least_common_multiple +""" + + +def lcm(a, b): + """ + Simple version of lcm, that does not have any dependencies. + + :param a: Integer + :param b: Integer + :rtype: The lowest common multiple of integers a and b + """ + tmp_a = a + while (tmp_a % b) != 0: + tmp_a += a + return tmp_a diff --git a/algorithms/math/primality_test.py b/algorithms/math/primality_test.py new file mode 100644 index 0000000..e6f5c5e --- /dev/null +++ b/algorithms/math/primality_test.py @@ -0,0 +1,53 @@ +""" + Primality Test + -------------- + Implementation of a Primality Test that uses a cache to improve + performance. + +""" +from math import sqrt + +from algorithms.math.sieve_eratosthenes import eratosthenes + + +CACHE_LIMIT = 10 ** 6 +primes_cache_list = [] +primes_cache_bool = [] + + +def is_prime(number, cache=True): + """ + Takes `number` and determines if it is prime. + + :param number: The integer to be tested for primality. + :param cache: A boolean to determine if a cache should be used to + improve performance. + :rtype: A boolean that signifies if `number` is prime. + """ + if number < 2: + return False + global primes_cache_list, primes_cache_bool + if cache and len(primes_cache_list) == 0: + primes_cache_list, primes_cache_bool = eratosthenes( + CACHE_LIMIT, return_boolean=True + ) + for prime in primes_cache_list: + primes_cache_bool[prime] = True + if number < len(primes_cache_bool): + return primes_cache_bool[number] + + sqrt_number = sqrt(number) + for prime in primes_cache_list: + if prime > sqrt_number: + return True + if number % prime == 0: + return False + + to_check = 2 + if len(primes_cache_list) > 0: + to_check = primes_cache_list[-1] + 1 + while to_check <= sqrt_number: + if number % to_check == 0: + return False + to_check += 1 + return True diff --git a/algorithms/math/sieve_atkin.py b/algorithms/math/sieve_atkin.py new file mode 100644 index 0000000..c54e416 --- /dev/null +++ b/algorithms/math/sieve_atkin.py @@ -0,0 +1,52 @@ +""" + Sieve of Atkin + -------------- + It is an optimized version of the ancient sieve of Eratosthenes + which does some preliminary work and then marks off + multiples of the square of each prime, rather than multiples of the prime + itself. It was created in 2004 by A. O. L. Atkin and Daniel J. Bernstein. + + Time Complexity: O(n/log log n) + + Pseudocode: https://en.wikipedia.org/wiki/Sieve_of_Atkin +""" +from math import sqrt + + +def atkin(limit): + """ + :param limit: The upper limit in which to find all primes less than this + value. + """ + if limit == 2: + return [2] + if limit == 3: + return [2, 3] + if limit == 5: + return [2, 3, 5] + if limit < 2: + return [] + primes = [2, 3, 5] + is_prime = [False] * (limit + 1) + sqrt_limit = int(sqrt(limit)) + 1 + + for x in range(1, sqrt_limit): + for y in range(1, sqrt_limit): + n = 4 * x ** 2 + y ** 2 + if n <= limit and (n % 12 == 1 or n % 12 == 5): + is_prime[n] = not is_prime[n] + n = 3 * x ** 2 + y ** 2 + if n <= limit and (n % 12 == 7): + is_prime[n] = not is_prime[n] + n = 3 * x ** 2 - y ** 2 + if x > y and (n <= limit) and (n % 12 == 11): + is_prime[n] = not is_prime[n] + + for index in range(5, sqrt_limit): + if is_prime[index]: + for composite in range(index ** 2, limit, index ** 2): + is_prime[composite] = False + for index in range(7, limit): + if is_prime[index]: + primes.append(index) + return primes diff --git a/algorithms/math/sieve_eratosthenes.py b/algorithms/math/sieve_eratosthenes.py new file mode 100644 index 0000000..ffcbbd5 --- /dev/null +++ b/algorithms/math/sieve_eratosthenes.py @@ -0,0 +1,44 @@ +""" + Sieve of Eratosthenes + --------------------- + Is a simple, ancient algorithm for finding all prime numbers + up to any given limit. It does so by iteratively marking as composite + (i.e. not prime) the multiples of each prime, starting with the multiples + of 2. + + The sieve of Eratosthenes is one of the most efficient ways + to find all of the smaller primes (below 10 million or so). + + Time Complexity: O(n log log n) + + Pseudocode: https://en.wikipedia.org/wiki/Sieve_of_Eratosthenes +""" + + +def eratosthenes(end, start=2, return_boolean=False): + """ + Finds all primes < `end`. + + :param end: An integer. The upper limit of the range to look for primes. + :param start: An integer. The start of the range to look for primes. + :param return_boolean: A boolean. Represents the type of return type. + :rtype: Depending on `return_boolean` either returns boolean and primes or + just the primes. + """ + primes = [] + if end < start or end < 2: + return [] + is_prime = [True for i in range(end + 1)] + is_prime[0] = is_prime[1] = False + for i in range(2, end + 1): + if not is_prime[i]: + continue + if start <= i <= end: + primes.append(i) + j = i * i + while j <= end: + is_prime[j] = False + j += i + if return_boolean: + return primes, is_prime + return primes diff --git a/algorithms/math/std_normal_pdf.py b/algorithms/math/std_normal_pdf.py new file mode 100644 index 0000000..1199e2b --- /dev/null +++ b/algorithms/math/std_normal_pdf.py @@ -0,0 +1,28 @@ +""" + Standard Normal Probability Density Function + -------------------------------------------- + Calculates the normal distribution's probability density + function (PDF). + Calculates Standard normal pdf for mean=0, std_dev=1. + + Equation: + f(x) = 1 / sqrt(2*pi) * e^(-(x-mean)^2/ 2*std_dev^2) +""" + + +def pdf(x, mean=0, std_dev=1): + """ + Calculates the normal distribution's probability density + function. + + :param x: An integer. + :param mean: An integer. + :param std_dev: An integer. + :rtype: The normal distribution + """ + PI = 3.141592653589793 + E = 2.718281828459045 + term1 = 1.0 / ((2 * PI)**0.5) + term2 = E**(-1.0*(x-mean)**2.0 / 2.0*(std_dev**2.0)) + + return term1 * term2 diff --git a/algorithms/random/mersenne_twister.py b/algorithms/random/mersenne_twister.py index 608747e..6d28daf 100644 --- a/algorithms/random/mersenne_twister.py +++ b/algorithms/random/mersenne_twister.py @@ -1,10 +1,6 @@ """ - mersenne_twister.py - - Implementation of Mersenne Twister pseudo random number generator - - Mersenne Twister Overview: - --------------------------- + Mersenne Twister + ---------------- Generates high quality pseudo random integers with a long period. Used as the default random number generator for several languages (including Python). @@ -21,7 +17,11 @@ def __init__(self): self.index = 0 def seed(self, seed): - """Initialize generator""" + """ + Initialize generator. + + :param seed: An integer value to seed the generator with + """ self.state = [] self.index = 0 self.state.append(seed) @@ -30,27 +30,32 @@ def seed(self, seed): n &= 0xffffffff self.state.append(n) - def randint(self): - """Extract random number""" + """ + Extracts a random number. + + :rtype: A random integer + """ if self.index == 0: self.generate() - + y = self.state[self.index] y ^= y >> 11 y ^= (y << 7) & 0x9d2c5680 y ^= (y << 15) & 0xefc60000 y ^= y >> 18 - + self.index = (self.index + 1) % 624 return y - def generate(self): - """Generate 624 new random numbers""" + """ + Generates 624 random numbers and stores in the state list. + + """ for i in range(624): n = self.state[i] & 0x80000000 - n += self.state[(i+1)%624] & 0x7fffffff - self.state[i] = self.state[(i+397)%624] ^ (n >> 1) - if n%2 != 0: + n += self.state[(i+1) % 624] & 0x7fffffff + self.state[i] = self.state[(i+397) % 624] ^ (n >> 1) + if n % 2 != 0: self.state[i] ^= 0x9908b0df diff --git a/algorithms/searching/binary_search.py b/algorithms/searching/binary_search.py index ae76eb3..1af6514 100644 --- a/algorithms/searching/binary_search.py +++ b/algorithms/searching/binary_search.py @@ -1,11 +1,7 @@ """ - binary_search.py - - Implementation of binary search on a sorted list. - - Binary Search Overview: - ------------------------ - Recursively partitions the list until the key is found. + Binary Search + ------------- + Recursively partitions the list until the `key` is found. Time Complexity: O(lg n) @@ -15,6 +11,16 @@ def search(seq, key): + """ + Takes a list of integers and searches if the `key` is contained within + the list. + + :param seq: A list of integers + :param key: The integer to be searched for + :rtype: The index of where the `key` is located in the list. If `key` is + not found then False is returned. + """ + lo = 0 hi = len(seq) - 1 diff --git a/algorithms/searching/bmh_search.py b/algorithms/searching/bmh_search.py index 7090287..ca4371e 100644 --- a/algorithms/searching/bmh_search.py +++ b/algorithms/searching/bmh_search.py @@ -1,12 +1,8 @@ """ - bmh_search.py - - Implementation of bmh search to find a substring in a string - - BMH Search Overview: - -------------------- - Uses a bad-character shift of the rightmost character of the window to - compute shifts. + BMH Search + ---------- + Search that attempts to find a substring in a string. Uses a bad-character + shift of the rightmost character of the window to compute shifts. Time: Complexity: O(m + n), where m is the substring to be found. @@ -18,6 +14,16 @@ def search(text, pattern): + """ + Takes a string and searches if the `pattern` is substring within `text`. + + :param text: A string that will be searched. + :param pattern: A string that will be searched as a substring within + `text`. + :rtype: The indices of all occurences of where the substring `pattern` + was found in `text`. + """ + pattern_length = len(pattern) text_length = len(text) offsets = [] diff --git a/algorithms/searching/breadth_first_search.py b/algorithms/searching/breadth_first_search.py new file mode 100644 index 0000000..6b08a00 --- /dev/null +++ b/algorithms/searching/breadth_first_search.py @@ -0,0 +1,29 @@ +""" + breadth_first_search.py + + Iterative implementation of BFS algorithm on a graph. + + Breadth First Search Overview: + ------------------------ + Used to traverse trees, tree structures or graphs. + Starts at a selected node (root) and explores the nearest + neighbor branches before proceeding further. + + Time Complexity: O(E + V) + E = Number of edges + V = Number of vertices (nodes) + + Pseudocode: https://en.wikipedia.org/wiki/Breadth-first_search +""" + + +def bfs(graph, start): + if start not in graph or graph[start] is None or graph[start] == []: + return None + visited, queue = set(), [start] + while queue: + vertex = queue.pop(0) + if vertex not in visited: + visited.add(vertex) + queue.extend(graph[vertex] - visited) + return visited diff --git a/algorithms/searching/depth_first_search.py b/algorithms/searching/depth_first_search.py index 336e743..0454434 100644 --- a/algorithms/searching/depth_first_search.py +++ b/algorithms/searching/depth_first_search.py @@ -1,25 +1,35 @@ """ - depth_first_search.py - - Recursive implementation of DFS algorithm on a graph. - - Depth First Search Overview: - ------------------------ - Used to traverse trees, tree structures or graphs. - Starts at a selected node (root) and explores the branch - as far as possible before backtracking. + Depth First Search + ------------------ + Recursive implementation of the depth first search algorithm used to + traverse trees or graphs. Starts at a selected node (root) and explores the + branch as far as possible before backtracking. Time Complexity: O(E + V) + E = Number of edges + V = Number of vertices (nodes) - Pseudocode: https://en.wikipedia.org/wiki/Depth-first_search + Pseudocode: https://en.wikipedia.org/wiki/Depth-first_search """ -def dfs(graph,start,path = []): - if start not in graph or graph[start] == None or graph[start] == []: + + +def dfs(graph, start, path=[]): + """ + Depth first search that recursively searches the path. Backtracking occurs + only when the last node in the path is visited. + + :param graph: A dictionary of nodes and edges. + :param start: The node to start the recursive search with. + :param path: A list of edges to search. + :rtype: A boolean indicating whether the node is included in the path. + + """ + if start not in graph or graph[start] is None or graph[start] == []: return None path = path + [start] for edge in graph[start]: if edge not in path: - path = dfs(graph, edge,path) + path = dfs(graph, edge, path) return path diff --git a/algorithms/searching/kmp_search.py b/algorithms/searching/kmp_search.py index 46a48b3..094a629 100644 --- a/algorithms/searching/kmp_search.py +++ b/algorithms/searching/kmp_search.py @@ -1,11 +1,9 @@ """ - kmp_search.py - Implementation of kmp search on a sorted list. - - KMP Search Overview: - ------------------------ - Uses a prefix function to reduce the searching time. + KMP Search + ---------- + Implementation of kmp search on string. Uses a prefix function to reduce + the searching time. Time Complexity: O(n + k), where k is the substring to be found @@ -15,6 +13,17 @@ def search(string, word): + """ + Searches for occurrences of a "word" within a main "string" by employing + the observation that when a mismatch occurs, the word itself embodies + sufficient information to determine where the next match could begin, + thus bypassing re-examination of previously matched characters. + + :param string: The string to be searched. + :param word: The sub string to be searched for. + :rtype: The indices of all occurences of where the substring is found in + the string. + """ word_length = len(word) string_length = len(string) offsets = [] @@ -36,11 +45,17 @@ def search(string, word): def compute_prefix(word): + """ + Returns the prefix of the word. + + :param word: The sub string that the prefix will be computed for. + :rtype: Returns computed prefix of the word. + """ word_length = len(word) prefix = [0] * word_length k = 0 - for q in xrange(1, word_length): + for q in range(1, word_length): while k > 0 and word[k] != word[q]: k = prefix[k - 1] diff --git a/algorithms/searching/rabinkarp_search.py b/algorithms/searching/rabinkarp_search.py index e779b5b..94baf15 100644 --- a/algorithms/searching/rabinkarp_search.py +++ b/algorithms/searching/rabinkarp_search.py @@ -1,10 +1,8 @@ """ - rabinkarp_search.py - Implementation of Rabin-Karp search on a given string. - Rabin-Karp Search Overview: - ------------------------ + Rabin-Karp Search + ----------------- Search for a substring in a given string, by comparing hash values of the strings. @@ -18,14 +16,22 @@ def search(s, sub): + """ + Uses hashing to find any one of a set of pattern strings in a text. + + :param s: The string to be searched. + :param sub: The substring to be searched for. + :rtype: The indices of all occurences of where the substring is found in + the string. + """ n, m = len(s), len(sub) - hsub_digest = md5(sub).digest() + hsub_digest = md5(sub.encode('utf-8')).digest() offsets = [] if m > n: return offsets - for i in xrange(n - m + 1): - if md5(s[i:i + m]).digest() == hsub_digest: + for i in range(n - m + 1): + if md5(s[i:i + m].encode('utf-8')).digest() == hsub_digest: if s[i:i + m] == sub: offsets.append(i) diff --git a/algorithms/searching/ternary_search.py b/algorithms/searching/ternary_search.py new file mode 100644 index 0000000..6cb51de --- /dev/null +++ b/algorithms/searching/ternary_search.py @@ -0,0 +1,22 @@ +""" + Ternary search + --------------- + Finds the maximum of unimodal function fn() within [left, right] + To find the minimum, revert the if/else statement or revert the comparison. + + Time Complexity: O(log(n)) + +""" + + +def search(fn, left, right, precision): + while abs(right - left) > precision: + left_third = left + (right - left) / 3 + right_third = right - (right - left) / 3 + + if fn(left_third) < fn(right_third): + left = left_third + else: + right = right_third + + return (left + right) / 2 diff --git a/algorithms/shuffling/knuth.py b/algorithms/shuffling/knuth.py index 1439647..f84248f 100644 --- a/algorithms/shuffling/knuth.py +++ b/algorithms/shuffling/knuth.py @@ -1,21 +1,26 @@ """ - knuth.py - Implementation of the Fisher-Yates/Knuth shuffle - - Fisher-Yates/Knuth Overview: - ---------------------------- + Fisher-Yates/Knuth + ------------------ Randomly picks integers to swap elements in an ubiased manner. Time Complexity: O(n) + Space Complexity: O(n)n - Pseudocode: http://en.wikipedia.org/wiki/Fisher%E1%80%93Yates_shuffle + Pseudocode: http://http://rosettacode.org/wiki/Knuth_shuffle """ from random import seed, randint def shuffle(seq): + """ + Takes a list of integers and randomly swaps the elements in an unbiased + manner. + + :param seq: A list of integers + :rtype: A list of shuffled integers + """ seed() for i in reversed(range(len(seq))): j = randint(0, i) diff --git a/algorithms/sorting/bogo_sort.py b/algorithms/sorting/bogo_sort.py index c3f087d..e8f903a 100644 --- a/algorithms/sorting/bogo_sort.py +++ b/algorithms/sorting/bogo_sort.py @@ -1,10 +1,6 @@ """ - bogo_sort.py - - Implementation of bogo sort on a list and returns a sorted list. - - Bogo Sort Overview: - ------------------- + Bogo Sort + --------- A naive sorting that picks two elements at random and swaps them. Time Complexity: O(n * n!) @@ -13,13 +9,23 @@ Stable: No - WARNING: This algorithm may never sort the list correctly. + Psuedo code: None + + **WARNING**: This algorithm may never sort the list correctly. """ import random def sort(seq): + """ + Takes a list of integers and sorts them in ascending order. This sorted + list is then returned. + + :param seq: A list of integers + :rtype: A list of sorted integers + """ + if len(seq) == 1: return seq random.seed() @@ -35,4 +41,10 @@ def sort(seq): def is_sorted(seq): - return all(seq[i - 1] <= seq[i] for i in xrange(1, len(seq))) + """ + Takes a list of integers and checks if the list is in sorted order. + + :param seq: A list of integers + :rtype: Boolean + """ + return all(seq[i - 1] <= seq[i] for i in range(1, len(seq))) diff --git a/algorithms/sorting/bubble_sort.py b/algorithms/sorting/bubble_sort.py index aaa823e..b8b0964 100644 --- a/algorithms/sorting/bubble_sort.py +++ b/algorithms/sorting/bubble_sort.py @@ -1,11 +1,7 @@ """ - bubble_sort.py - - Implementation of bubble sort on a list and returns a sorted list. - - Bubble Sort Overview: - --------------------- - A naive sorting that compares and swaps adjacent elements + Bubble Sort + ----------- + A naive sorting that compares and swaps adjacent elements. Time Complexity: O(n**2) @@ -19,9 +15,16 @@ def sort(seq): + """ + Takes a list of integers and sorts them in ascending order. This sorted + list is then returned. + + :param seq: A list of integers + :rtype: A list of sorted integers + """ L = len(seq) - for _ in range(L): - for n in range(1, L): + for i in range(L): + for n in range(1, L - i): if seq[n] < seq[n - 1]: seq[n - 1], seq[n] = seq[n], seq[n - 1] return seq diff --git a/algorithms/sorting/cocktail_sort.py b/algorithms/sorting/cocktail_sort.py index 6ca6756..635da03 100644 --- a/algorithms/sorting/cocktail_sort.py +++ b/algorithms/sorting/cocktail_sort.py @@ -1,13 +1,8 @@ """ - cocktail_sort.py - - Implementation of cocktail sort (aka bidirectional bubble sort, - or the happy hour sort) on a list. - - Cocktail Sort Overview: - ------------------------ - Walk the list bidirectionally, swapping neighbors if one should come - before/after the other. + Cocktail Sort + ------------- + A bidirectional bubble sort. Walks the elements bidirectionally, swapping + neighbors if one should come before/after the other. Time Complexity: O(n**2) @@ -16,10 +11,19 @@ Stable: Yes Psuedo Code: http://en.wikipedia.org/wiki/Cocktail_sort + """ def sort(seq): + """ + Takes a list of integers and sorts them in ascending order. This sorted + list is then returned. + + :param seq: A list of integers + :rtype: A list of sorted integers + """ + lower_bound = -1 upper_bound = len(seq) - 1 swapped = True diff --git a/algorithms/sorting/comb_sort.py b/algorithms/sorting/comb_sort.py index a50379b..ede33d1 100644 --- a/algorithms/sorting/comb_sort.py +++ b/algorithms/sorting/comb_sort.py @@ -1,10 +1,6 @@ """ - comb_sort.py - - Implementation of comb sort on a list and returns a sorted list. - - Comb Sort Overview: - ------------------- + Comb Sort + --------- Improves on bubble sort by using a gap sequence to remove turtles. Time Complexity: O(n**2) @@ -19,6 +15,14 @@ def sort(seq): + """ + Takes a list of integers and sorts them in ascending order. This sorted + list is then returned. + + :param seq: A list of integers + :rtype: A list of sorted integers + """ + gap = len(seq) swap = True diff --git a/algorithms/sorting/gnome_sort.py b/algorithms/sorting/gnome_sort.py new file mode 100644 index 0000000..6c4d178 --- /dev/null +++ b/algorithms/sorting/gnome_sort.py @@ -0,0 +1,42 @@ +""" + Gnome Sort + ---------- + A sorting algorithm similar to insertion sort except that the element is + moved to its proper place by a series of swaps. + + Time Complexity: O(n**2) + + Space Complexity: O(1) auxillary + + Stable: No + + Psuedo code: http://en.wikipedia.org/wiki/Gnome_sort + +""" + + +def sort(seq): + """ + Takes a list of integers and sorts them in ascending order. This sorted + list is then returned. + + :param seq: A list of integers + :rtype: A list of sorted integers + """ + i = 1 + last = 0 + while i < len(seq): + if seq[i] < seq[i-1]: + seq[i], seq[i-1] = seq[i-1], seq[i] + if i > 1: + if last == 0: + last = i + i -= 1 + else: + i += 1 + else: + if last != 0: + i = last + last = 0 + i += 1 + return seq diff --git a/algorithms/sorting/heap_sort.py b/algorithms/sorting/heap_sort.py index 5c564c9..d10729c 100644 --- a/algorithms/sorting/heap_sort.py +++ b/algorithms/sorting/heap_sort.py @@ -1,10 +1,6 @@ """ - heap_sort.py - - Implementation of heap sort on a list and returns a sorted list. - - Heap Sort Overview: - ------------------- + Heap Sort + --------- Uses the max heap data structure implemented in a list. Time Complexity: O(n log n) @@ -19,6 +15,15 @@ def max_heapify(seq, i, n): + """ + The function of max_heapify is to let the value at seq[i] "float down" in + the max-heap so that the subtree rooted at index i becomes a max-heap. + + :param seq: A list of integers + :param i: An integer that is an index in to the list that represents the + root of a subtree that max heapify is called on. + :param n: length of the list + """ l = 2 * i + 1 r = 2 * i + 2 @@ -35,12 +40,24 @@ def max_heapify(seq, i, n): def build_heap(seq): + """ + Continously calls max_heapify on the list for each subtree. + + :param seq: A list of integers + """ n = len(seq) - 1 - for i in range(n/2, -1, -1): + for i in range(n//2, -1, -1): max_heapify(seq, i, n) def sort(seq): + """ + Takes a list of integers and sorts them in ascending order. This sorted + list is then returned. + + :param seq: A list of integers + :rtype: A list of sorted integers + """ build_heap(seq) heap_size = len(seq) - 1 for x in range(heap_size, 0, -1): diff --git a/algorithms/sorting/insertion_sort.py b/algorithms/sorting/insertion_sort.py index 91d2167..24d4360 100644 --- a/algorithms/sorting/insertion_sort.py +++ b/algorithms/sorting/insertion_sort.py @@ -1,11 +1,7 @@ """ - insertion_sort.py - - Implemenation of insertion sort on a list and returns a sorted list. - - Insertion Sort Overview: - ------------------------ - Uses insertion of elements in to the list to sort the list. + Insertion Sort + -------------- + A sort that uses the insertion of elements in to the list to sort the list. Time Complexity: O(n**2) @@ -19,6 +15,13 @@ def sort(seq): + """ + Takes a list of integers and sorts them in ascending order. This sorted + list is then returned. + + :param seq: A list of integers + :rtype: A list of integers + """ for n in range(1, len(seq)): item = seq[n] hole = n diff --git a/algorithms/sorting/merge_sort.py b/algorithms/sorting/merge_sort.py index 05ca530..9cdaa83 100644 --- a/algorithms/sorting/merge_sort.py +++ b/algorithms/sorting/merge_sort.py @@ -1,10 +1,6 @@ """ - merge_sort.py - - Implementation of merge sort on a list and returns a sorted list. - - Merge Sort Overview: - ------------------------ + Merge Sort + ---------- Uses divide and conquer to recursively divide and sort the list Time Complexity: O(n log n) @@ -19,6 +15,14 @@ def merge(left, right): + """ + Takes two sorted sub lists and merges them in to a single sorted sub list + and returns it. + + :param left: A list of sorted integers + :param right: A list of sorted integers + :rtype: A list of sorted integers + """ result = [] n, m = 0, 0 while n < len(left) and m < len(right): @@ -35,10 +39,17 @@ def merge(left, right): def sort(seq): + """ + Takes a list of integers and sorts them in ascending order. This sorted + list is then returned. + + :param seq: A list of integers + :rtype: A list of sorted integers + """ if len(seq) <= 1: return seq - middle = len(seq) / 2 + middle = int(len(seq) / 2) left = sort(seq[:middle]) right = sort(seq[middle:]) return merge(left, right) diff --git a/algorithms/sorting/quick_sort.py b/algorithms/sorting/quick_sort.py index 0fa0b64..2888102 100644 --- a/algorithms/sorting/quick_sort.py +++ b/algorithms/sorting/quick_sort.py @@ -1,10 +1,6 @@ """ - quick_sort.py - - Implementation of quick sort on a list and returns a sorted list. - - Quick Sort Overview: - ------------------------ + Quick Sort + ---------- Uses partitioning to recursively divide and sort the list Time Complexity: O(n**2) worst case @@ -19,11 +15,21 @@ def sort(seq): - - if len(seq) < 1: + """ + Takes a list of integers and sorts them in ascending order. This sorted + list is then returned. + + :param seq: A list of integers + :rtype: A list of sorted integers + """ + if len(seq) <= 1: return seq else: pivot = seq[0] - left = sort([x for x in seq[1:] if x < pivot]) - right = sort([x for x in seq[1:] if x >= pivot]) - return left + [pivot] + right + left, right = [], [] + for x in seq[1:]: + if x < pivot: + left.append(x) + else: + right.append(x) + return sort(left) + [pivot] + sort(right) diff --git a/algorithms/sorting/quick_sort_in_place.py b/algorithms/sorting/quick_sort_in_place.py index 375261f..512ead0 100644 --- a/algorithms/sorting/quick_sort_in_place.py +++ b/algorithms/sorting/quick_sort_in_place.py @@ -1,11 +1,6 @@ """ - quick_sort_in_place.py - - Implementation of quick sort on a list and returns a sorted list. - In-place version. - - Quick Sort Overview: - ------------------------ + Quick Sort in Place + ------------------- Uses partitioning to recursively divide and sort the list Time Complexity: O(n**2) worst case @@ -14,28 +9,49 @@ Stable: No - Psuedo Code: http://en.wikipedia.org/wiki/Quicksort#In-place_version + Psuedo Code: http://rosettacode.org/wiki/Quick_Sort """ +from random import randrange + def partition(seq, left, right, pivot_index): + """ + Reorders the slice with values lower than the pivot at the left side, + and values bigger than it at the right side. + Also returns the store index. + + :param seq: A list of integers + :param left: An integer representing left index + :param right: An integer representing left index + :param pivot_index: An integer that we're pivoting off + :rtype: An stored_index integer + """ pivot_value = seq[pivot_index] seq[pivot_index], seq[right] = seq[right], seq[pivot_index] store_index = left - for i in range( left, right ): + for i in range(left, right): if seq[i] < pivot_value: seq[i], seq[store_index] = seq[store_index], seq[i] store_index += 1 seq[store_index], seq[right] = seq[right], seq[store_index] return store_index + def sort(seq, left, right): - """in-place version of quicksort""" - from random import randrange + """ + Takes a list of integers and sorts them in ascending order. This sorted + list is then returned. + + :param seq: A list of integers + :param left: An integer representing the beginning index + :param right: An integer representing the end index + :rtype: A list of sorted integers + """ + if len(seq) <= 1: return seq elif left < right: - #pivot = (left+right)/2 pivot = randrange(left, right) pivot_new_index = partition(seq, left, right, pivot) sort(seq, left, pivot_new_index - 1) diff --git a/algorithms/sorting/selection_sort.py b/algorithms/sorting/selection_sort.py index 94df4f1..881054c 100644 --- a/algorithms/sorting/selection_sort.py +++ b/algorithms/sorting/selection_sort.py @@ -1,11 +1,7 @@ """ - selection_sort.py - - Implementation of selection sort on a list and returns a sorted list. - - Selection Sort Overview: - ------------------------ - Uses in-place comparision to sort the list + Selection Sort + -------------- + A sorting that uses in-place comparison. Time Complexity: O(n**2) @@ -19,16 +15,19 @@ def sort(seq): + """ + Takes a list of integers and sorts them in ascending order. This sorted + list is then returned. + :param seq: A list of integers + :rtype: A list of sorted integers + """ for i in range(0, len(seq)): - minat = i - minum = seq[i] - for j in range(i + 1, len(seq)): - if minum > seq[j]: - minat = j - minum = seq[j] - temp = seq[i] - seq[i] = seq[minat] - seq[minat] = temp + iMin = i + for j in range(i+1, len(seq)): + if seq[iMin] > seq[j]: + iMin = j + if i != iMin: + seq[i], seq[iMin] = seq[iMin], seq[i] return seq diff --git a/algorithms/sorting/shell_sort.py b/algorithms/sorting/shell_sort.py index c5b640a..6b2346e 100644 --- a/algorithms/sorting/shell_sort.py +++ b/algorithms/sorting/shell_sort.py @@ -1,10 +1,6 @@ """ - shell_sort.py - - Implementation of shell sort on an list and returns a sorted list. - - Shell Sort Overview: - ------------------------ + Shell Sort + ---------- Comparision sort that sorts far away elements first to sort the list Time Complexity: O(n**2) @@ -19,8 +15,15 @@ def sort(seq): + """ + Takes a list of integers and sorts them in ascending order. This sorted + list is then returned. + + :param seq: A list of integers + :rtype: A list of sorted integers + """ - gaps = [x for x in range(len(seq) / 2, 0, -1)] + gaps = [x for x in range(len(seq) // 2, 0, -1)] for gap in gaps: for i in range(gap, len(seq)): diff --git a/algorithms/sorting/strand_sort.py b/algorithms/sorting/strand_sort.py new file mode 100644 index 0000000..c205c5a --- /dev/null +++ b/algorithms/sorting/strand_sort.py @@ -0,0 +1,64 @@ +""" + strand_sort.py + + Implementation of strand sort on a list and returns a sorted list. + + Strand Sort Overview: + ------------------------ + Repeatedly pulls sorted sublists out of the unsorted list and merges them + with a result array. + + Time Complexity: O(n**2) worst case + + Space Complexity: O(1) auxiliary + + Stable: Yes + + Psuedo Code: https://en.wikipedia.org/wiki/Strand_sort +""" + + +def sort(array): + if len(array) < 2: + return array + result = [] + while array: + sublist = [array.pop(0)] + leftovers = [] + last = sublist[0] + # For speed, frequently invoked functions are assigned to locally- + # scoped variables, which greatly reduces overhead in calling them. + sublist_append = sublist.append + leftovers_append = leftovers.append + for item in array: + if item >= last: + sublist_append(item) + last = item + else: + leftovers_append(item) + result = merge(result, sublist) + array = leftovers + return result + + +def merge(left, right): + if not left: + return right + if not right: + return left + + if left[-1] > right[-1]: + left, right = right, left + + it = iter(right) + y = next(it) + result = [] + + for x in left: + while y < x: + result.append(y) + y = next(it) + result.append(x) + result.append(y) + result.extend(it) + return result diff --git a/algorithms/tests/test_math.py b/algorithms/tests/test_math.py deleted file mode 100644 index c382f77..0000000 --- a/algorithms/tests/test_math.py +++ /dev/null @@ -1,26 +0,0 @@ -import unittest -from ..math.extended_gcd import extended_gcd - - -class TestExtendedGCD(unittest.TestCase): - - def test_extended_gcd(self): - # Find extended_gcd of 35 and 77 - (a, b) = extended_gcd(35, 77) - self.assertIs(35 * a + 77 * b, 7) - - # Find extended_gcd of 15 and 19 - (a, b) = extended_gcd(15, 19) - self.assertIs(15 * a + 19 * b, 1) - - # Find extended_gcd of 18 and 9 - (a, b) = extended_gcd(18, 9) - self.assertIs(18 * a + 9 * b, 9) - - # Find extended_gcd of 99 and 81 - (a, b) = extended_gcd(99, 81) - self.assertIs(99 * a + 81 * b, 9) - - # Find extended_gcd of 50 and 15 - (a, b) = extended_gcd(50, 15) - self.assertIs(50 * a + 15 * b, 5) diff --git a/algorithms/tests/test_searching.py b/algorithms/tests/test_searching.py deleted file mode 100644 index 8994c16..0000000 --- a/algorithms/tests/test_searching.py +++ /dev/null @@ -1,134 +0,0 @@ -""" Unit Tests for searching """ -import unittest -from ..searching import binary_search, kmp_search, rabinkarp_search, bmh_search, depth_first_search - - -class TestBinarySearch(unittest.TestCase): - """ - Tests Binary Search on a small range from 0-9 - """ - - def test_binarysearch(self): - self.seq = range(10) - rv1 = binary_search.search(self.seq, 0) - rv2 = binary_search.search(self.seq, 9) - rv3 = binary_search.search(self.seq, -1) - rv4 = binary_search.search(self.seq, 10) - rv5 = binary_search.search(self.seq, 4) - self.assertIs(rv1, 0) - self.assertIs(rv2, 9) - self.assertFalse(rv3) - self.assertFalse(rv4) - self.assertIs(rv5, 4) - self.seq = range(9) - rv1 = binary_search.search(self.seq, 0) - rv2 = binary_search.search(self.seq, 8) - rv3 = binary_search.search(self.seq, -1) - rv4 = binary_search.search(self.seq, 10) - rv5 = binary_search.search(self.seq, 4) - self.assertIs(rv1, 0) - self.assertIs(rv2, 8) - self.assertFalse(rv3) - self.assertFalse(rv4) - self.assertIs(rv5, 4) - -class TestKMPSearch(unittest.TestCase): - """ - Tests KMP search on string "ABCDE FG ABCDEABCDEF" - """ - - def test_kmpsearch(self): - self.string = "ABCDE FG ABCDEABCDEF" - rv1 = kmp_search.search(self.string, "ABCDEA") - rv2 = kmp_search.search(self.string, "ABCDER") - self.assertIs(rv1[0], 9) - self.assertFalse(rv2) - - -class TestRabinKarpSearch(unittest.TestCase): - """ - Tests Rabin-Karp search on string "ABCDEFGHIJKLMNOP" - """ - - def test_rabinkarpsearch(self): - self.string = "ABCDEFGHIJKLMNOP" - rv1 = rabinkarp_search.search(self.string, "MNOP") - rv2 = rabinkarp_search.search(self.string, "BCA") - self.assertIs(rv1[0], 12) - self.assertFalse(rv2) - - -class TestBMHSearch(unittest.TestCase): - """ - Tests BMH search on string "ABCDE FG ABCDEABCDEF" - """ - - def test_bmhsearch(self): - self.string = "ABCDE FG ABCDEABCDEF" - rv1 = bmh_search.search(self.string, "ABCDEA") - rv2 = bmh_search.search(self.string, "ABCDER") - self.assertIs(rv1[0], 9) - self.assertFalse(rv2) - -class TestDepthFirstSearch(unittest.TestCase): - """ - Tests DFS on a graph represented by a adjacency list - """ - - def test_dfs(self): - self.graph = {'A': ['B','C','E'], - 'B': ['A','D','F'], - 'C': ['A','G'], - 'D': ['B'], - 'F': ['B'], - 'E': ['A'], - 'G': ['C']} - rv1 = depth_first_search.dfs(self.graph, "A") - rv2 = depth_first_search.dfs(self.graph, "G") - rv1e = depth_first_search.dfs(self.graph, "Z") - self.assertEqual(rv1, ['A', 'B', 'D', 'F', 'C', 'G', 'E']) - self.assertEqual(rv2, ['G', 'C', 'A', 'B', 'D', 'F', 'E']) - self.assertEqual(rv1e, None) - self.graph = {1:[2,3,4], - 2:[1,6,10], - 3:[1,5,10], - 4:[1,10,11], - 5:[3,10], - 6:[2,7,8,9], - 7:[6,8], - 8:[6,7], - 9:[6,10], - 10:[3,5,9,12], - 11:[4], - 12:[10]} - rv3 = depth_first_search.dfs(self.graph,1) - rv4 = depth_first_search.dfs(self.graph,5) - rv5 = depth_first_search.dfs(self.graph,6) - rv2e = depth_first_search.dfs(self.graph,99) - self.assertEqual(rv3, [1, 2, 6, 7, 8, 9, 10, 3, 5, 12, 4, 11]) - self.assertEqual(rv4, [5, 3, 1, 2, 6, 7, 8, 9, 10, 12, 4, 11]) - self.assertEqual(rv5, [6, 2, 1, 3, 5, 10, 9, 12, 4, 11, 7, 8]) - self.assertEqual(rv2e, None) - self.graph = {1:[2,3,4,5,6], - 2:[1,4,7,8,9], - 3:[1,10], - 4:[1,2,11,12], - 5:[1,13,14,15], - 6:[1,15], - 7:[2], - 8:[2], - 9:[2,10], - 10:[3,9], - 11:[4], - 12:[4], - 13:[5], - 14:[5], - 15:[5,6]} - rv6 = depth_first_search.dfs(self.graph,1) - rv7 = depth_first_search.dfs(self.graph,10) - rv8 = depth_first_search.dfs(self.graph,5) - rv3e = depth_first_search.dfs(self.graph,-1) - self.assertEqual(rv6, [1, 2, 4, 11, 12, 7, 8, 9, 10, 3, 5, 13, 14, 15, 6]) - self.assertEqual(rv7, [10, 3, 1, 2, 4, 11, 12, 7, 8, 9, 5, 13, 14, 15, 6]) - self.assertEqual(rv8, [5, 1, 2, 4, 11, 12, 7, 8, 9, 10, 3, 6, 15, 13, 14]) - self.assertEqual(rv3e, None) diff --git a/docs/Makefile b/docs/Makefile new file mode 100644 index 0000000..4a75883 --- /dev/null +++ b/docs/Makefile @@ -0,0 +1,192 @@ +# Makefile for Sphinx documentation +# + +# You can set these variables from the command line. +SPHINXOPTS = +SPHINXBUILD = sphinx-build +PAPER = +BUILDDIR = _build + +# User-friendly check for sphinx-build +ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1) +$(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/) +endif + +# Internal variables. +PAPEROPT_a4 = -D latex_paper_size=a4 +PAPEROPT_letter = -D latex_paper_size=letter +ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . +# the i18n builder cannot share the environment and doctrees with the others +I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . + +.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest coverage gettext + +help: + @echo "Please use \`make ' where is one of" + @echo " html to make standalone HTML files" + @echo " dirhtml to make HTML files named index.html in directories" + @echo " singlehtml to make a single large HTML file" + @echo " pickle to make pickle files" + @echo " json to make JSON files" + @echo " htmlhelp to make HTML files and a HTML help project" + @echo " qthelp to make HTML files and a qthelp project" + @echo " applehelp to make an Apple Help Book" + @echo " devhelp to make HTML files and a Devhelp project" + @echo " epub to make an epub" + @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" + @echo " latexpdf to make LaTeX files and run them through pdflatex" + @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx" + @echo " text to make text files" + @echo " man to make manual pages" + @echo " texinfo to make Texinfo files" + @echo " info to make Texinfo files and run them through makeinfo" + @echo " gettext to make PO message catalogs" + @echo " changes to make an overview of all changed/added/deprecated items" + @echo " xml to make Docutils-native XML files" + @echo " pseudoxml to make pseudoxml-XML files for display purposes" + @echo " linkcheck to check all external links for integrity" + @echo " doctest to run all doctests embedded in the documentation (if enabled)" + @echo " coverage to run coverage check of the documentation (if enabled)" + +clean: + rm -rf $(BUILDDIR)/* + +html: + $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html + @echo + @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." + +dirhtml: + $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml + @echo + @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." + +singlehtml: + $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml + @echo + @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." + +pickle: + $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle + @echo + @echo "Build finished; now you can process the pickle files." + +json: + $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json + @echo + @echo "Build finished; now you can process the JSON files." + +htmlhelp: + $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp + @echo + @echo "Build finished; now you can run HTML Help Workshop with the" \ + ".hhp project file in $(BUILDDIR)/htmlhelp." + +qthelp: + $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp + @echo + @echo "Build finished; now you can run "qcollectiongenerator" with the" \ + ".qhcp project file in $(BUILDDIR)/qthelp, like this:" + @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/algorithms.qhcp" + @echo "To view the help file:" + @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/algorithms.qhc" + +applehelp: + $(SPHINXBUILD) -b applehelp $(ALLSPHINXOPTS) $(BUILDDIR)/applehelp + @echo + @echo "Build finished. The help book is in $(BUILDDIR)/applehelp." + @echo "N.B. You won't be able to view it unless you put it in" \ + "~/Library/Documentation/Help or install it in your application" \ + "bundle." + +devhelp: + $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp + @echo + @echo "Build finished." + @echo "To view the help file:" + @echo "# mkdir -p $$HOME/.local/share/devhelp/algorithms" + @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/algorithms" + @echo "# devhelp" + +epub: + $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub + @echo + @echo "Build finished. The epub file is in $(BUILDDIR)/epub." + +latex: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo + @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." + @echo "Run \`make' in that directory to run these through (pdf)latex" \ + "(use \`make latexpdf' here to do that automatically)." + +latexpdf: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo "Running LaTeX files through pdflatex..." + $(MAKE) -C $(BUILDDIR)/latex all-pdf + @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." + +latexpdfja: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo "Running LaTeX files through platex and dvipdfmx..." + $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja + @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." + +text: + $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text + @echo + @echo "Build finished. The text files are in $(BUILDDIR)/text." + +man: + $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man + @echo + @echo "Build finished. The manual pages are in $(BUILDDIR)/man." + +texinfo: + $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo + @echo + @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." + @echo "Run \`make' in that directory to run these through makeinfo" \ + "(use \`make info' here to do that automatically)." + +info: + $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo + @echo "Running Texinfo files through makeinfo..." + make -C $(BUILDDIR)/texinfo info + @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." + +gettext: + $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale + @echo + @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." + +changes: + $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes + @echo + @echo "The overview file is in $(BUILDDIR)/changes." + +linkcheck: + $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck + @echo + @echo "Link check complete; look for any errors in the above output " \ + "or in $(BUILDDIR)/linkcheck/output.txt." + +doctest: + $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest + @echo "Testing of doctests in the sources finished, look at the " \ + "results in $(BUILDDIR)/doctest/output.txt." + +coverage: + $(SPHINXBUILD) -b coverage $(ALLSPHINXOPTS) $(BUILDDIR)/coverage + @echo "Testing of coverage in the sources finished, look at the " \ + "results in $(BUILDDIR)/coverage/python.txt." + +xml: + $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml + @echo + @echo "Build finished. The XML files are in $(BUILDDIR)/xml." + +pseudoxml: + $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml + @echo + @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." diff --git a/docs/algorithms.rst b/docs/algorithms.rst new file mode 100644 index 0000000..bed6ece --- /dev/null +++ b/docs/algorithms.rst @@ -0,0 +1,14 @@ +Algorithms +========== + +.. toctree:: + :maxdepth: 2 + + data_structures + dynamic_programming + factorization + math + random + searching + shuffling + sorting diff --git a/docs/conf.py b/docs/conf.py new file mode 100644 index 0000000..22e2f80 --- /dev/null +++ b/docs/conf.py @@ -0,0 +1,289 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +# +# algorithms documentation build configuration file, created by +# sphinx-quickstart on Thu Oct 8 22:36:00 2015. +# +# This file is execfile()d with the current directory set to its +# containing dir. +# +# Note that not all possible configuration values are present in this +# autogenerated file. +# +# All configuration values have a default; values that are commented out +# serve to show the default. + +import sys +import os +import shlex + +# If extensions (or modules to document with autodoc) are in another directory, +# add these directories to sys.path here. If the directory is relative to the +# documentation root, use os.path.abspath to make it absolute, like shown here. +sys.path.insert(0, os.path.abspath('..')) + +# -- General configuration ------------------------------------------------ + +# If your documentation needs a minimal Sphinx version, state it here. +#needs_sphinx = '1.0' + +# Add any Sphinx extension module names here, as strings. They can be +# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom +# ones. +extensions = [ + 'sphinx.ext.autodoc', + 'sphinx.ext.coverage', + 'sphinx.ext.viewcode', +] + +# Add any paths that contain templates here, relative to this directory. +templates_path = ['_templates'] + +# The suffix(es) of source filenames. +# You can specify multiple suffix as a list of string: +# source_suffix = ['.rst', '.md'] +source_suffix = '.rst' + +# The encoding of source files. +#source_encoding = 'utf-8-sig' + +# The master toctree document. +master_doc = 'index' + +# General information about the project. +project = 'algorithms' +copyright = '2015, Nic Young' +author = 'Nic Young' + +# The version info for the project you're documenting, acts as replacement for +# |version| and |release|, also used in various other places throughout the +# built documents. +# +# The short X.Y version. +version = '1.0' +# The full version, including alpha/beta/rc tags. +release = '1.0.0' + +# The language for content autogenerated by Sphinx. Refer to documentation +# for a list of supported languages. +# +# This is also used if you do content translation via gettext catalogs. +# Usually you set "language" from the command line for these cases. +language = None + +# There are two options for replacing |today|: either, you set today to some +# non-false value, then it is used: +#today = '' +# Else, today_fmt is used as the format for a strftime call. +#today_fmt = '%B %d, %Y' + +# List of patterns, relative to source directory, that match files and +# directories to ignore when looking for source files. +exclude_patterns = ['_build'] + +# The reST default role (used for this markup: `text`) to use for all +# documents. +#default_role = None + +# If true, '()' will be appended to :func: etc. cross-reference text. +#add_function_parentheses = True + +# If true, the current module name will be prepended to all description +# unit titles (such as .. function::). +#add_module_names = True + +# If true, sectionauthor and moduleauthor directives will be shown in the +# output. They are ignored by default. +#show_authors = False + +# The name of the Pygments (syntax highlighting) style to use. +pygments_style = 'sphinx' + +# A list of ignored prefixes for module index sorting. +#modindex_common_prefix = [] + +# If true, keep warnings as "system message" paragraphs in the built documents. +#keep_warnings = False + +# If true, `todo` and `todoList` produce output, else they produce nothing. +todo_include_todos = False + + +# -- Options for HTML output ---------------------------------------------- + +# The theme to use for HTML and HTML Help pages. See the documentation for +# a list of builtin themes. +html_theme = 'alabaster' + +# Theme options are theme-specific and customize the look and feel of a theme +# further. For a list of options available for each theme, see the +# documentation. +#html_theme_options = {} + +# Add any paths that contain custom themes here, relative to this directory. +#html_theme_path = [] + +# The name for this set of Sphinx documents. If None, it defaults to +# " v documentation". +#html_title = None + +# A shorter title for the navigation bar. Default is the same as html_title. +#html_short_title = None + +# The name of an image file (relative to this directory) to place at the top +# of the sidebar. +#html_logo = None + +# The name of an image file (within the static path) to use as favicon of the +# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 +# pixels large. +#html_favicon = None + +# Add any paths that contain custom static files (such as style sheets) here, +# relative to this directory. They are copied after the builtin static files, +# so a file named "default.css" will overwrite the builtin "default.css". +html_static_path = ['_static'] + +# Add any extra paths that contain custom files (such as robots.txt or +# .htaccess) here, relative to this directory. These files are copied +# directly to the root of the documentation. +#html_extra_path = [] + +# If not '', a 'Last updated on:' timestamp is inserted at every page bottom, +# using the given strftime format. +#html_last_updated_fmt = '%b %d, %Y' + +# If true, SmartyPants will be used to convert quotes and dashes to +# typographically correct entities. +#html_use_smartypants = True + +# Custom sidebar templates, maps document names to template names. +#html_sidebars = {} + +# Additional templates that should be rendered to pages, maps page names to +# template names. +#html_additional_pages = {} + +# If false, no module index is generated. +#html_domain_indices = True + +# If false, no index is generated. +#html_use_index = True + +# If true, the index is split into individual pages for each letter. +#html_split_index = False + +# If true, links to the reST sources are added to the pages. +#html_show_sourcelink = True + +# If true, "Created using Sphinx" is shown in the HTML footer. Default is True. +#html_show_sphinx = True + +# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. +#html_show_copyright = True + +# If true, an OpenSearch description file will be output, and all pages will +# contain a tag referring to it. The value of this option must be the +# base URL from which the finished HTML is served. +#html_use_opensearch = '' + +# This is the file name suffix for HTML files (e.g. ".xhtml"). +#html_file_suffix = None + +# Language to be used for generating the HTML full-text search index. +# Sphinx supports the following languages: +# 'da', 'de', 'en', 'es', 'fi', 'fr', 'h', 'it', 'ja' +# 'nl', 'no', 'pt', 'ro', 'r', 'sv', 'tr' +#html_search_language = 'en' + +# A dictionary with options for the search language support, empty by default. +# Now only 'ja' uses this config value +#html_search_options = {'type': 'default'} + +# The name of a javascript file (relative to the configuration directory) that +# implements a search results scorer. If empty, the default will be used. +#html_search_scorer = 'scorer.js' + +# Output file base name for HTML help builder. +htmlhelp_basename = 'algorithmsdoc' + +# -- Options for LaTeX output --------------------------------------------- + +latex_elements = { +# The paper size ('letterpaper' or 'a4paper'). +#'papersize': 'letterpaper', + +# The font size ('10pt', '11pt' or '12pt'). +#'pointsize': '10pt', + +# Additional stuff for the LaTeX preamble. +#'preamble': '', + +# Latex figure (float) alignment +#'figure_align': 'htbp', +} + +# Grouping the document tree into LaTeX files. List of tuples +# (source start file, target name, title, +# author, documentclass [howto, manual, or own class]). +latex_documents = [ + (master_doc, 'algorithms.tex', 'algorithms Documentation', + 'Nic Young', 'manual'), +] + +# The name of an image file (relative to this directory) to place at the top of +# the title page. +#latex_logo = None + +# For "manual" documents, if this is true, then toplevel headings are parts, +# not chapters. +#latex_use_parts = False + +# If true, show page references after internal links. +#latex_show_pagerefs = False + +# If true, show URL addresses after external links. +#latex_show_urls = False + +# Documents to append as an appendix to all manuals. +#latex_appendices = [] + +# If false, no module index is generated. +#latex_domain_indices = True + + +# -- Options for manual page output --------------------------------------- + +# One entry per manual page. List of tuples +# (source start file, name, description, authors, manual section). +man_pages = [ + (master_doc, 'algorithms', 'algorithms Documentation', + [author], 1) +] + +# If true, show URL addresses after external links. +#man_show_urls = False + + +# -- Options for Texinfo output ------------------------------------------- + +# Grouping the document tree into Texinfo files. List of tuples +# (source start file, target name, title, author, +# dir menu entry, description, category) +texinfo_documents = [ + (master_doc, 'algorithms', 'algorithms Documentation', + author, 'algorithms', 'One line description of project.', + 'Miscellaneous'), +] + +# Documents to append as an appendix to all manuals. +#texinfo_appendices = [] + +# If false, no module index is generated. +#texinfo_domain_indices = True + +# How to display URL addresses: 'footnote', 'no', or 'inline'. +#texinfo_show_urls = 'footnote' + +# If true, do not generate a @detailmenu in the "Top" node's menu. +#texinfo_no_detailmenu = False diff --git a/docs/data_structures.rst b/docs/data_structures.rst new file mode 100644 index 0000000..ab58d3a --- /dev/null +++ b/docs/data_structures.rst @@ -0,0 +1,47 @@ +Data Structures +=============== + +.. automodule:: algorithms.data_structures.binary_search_tree + :members: + :undoc-members: + :show-inheritance: + +.. automodule:: algorithms.data_structures.digraph + :members: + :undoc-members: + :show-inheritance: + +.. automodule:: algorithms.data_structures.queue + :members: + :undoc-members: + :show-inheritance: + +.. automodule:: algorithms.data_structures.singly_linked_list + :members: + :undoc-members: + :show-inheritance: + +.. automodule:: algorithms.data_structures.stack + :members: + :undoc-members: + :show-inheritance: + +.. automodule:: algorithms.data_structures.undirected_graph + :members: + :undoc-members: + :show-inheritance: + +.. automodule:: algorithms.data_structures.union_find + :members: + :undoc-members: + :show-inheritance: + +.. automodule:: algorithms.data_structures.union_find_by_rank + :members: + :undoc-members: + :show-inheritance: + +.. automodule:: algorithms.data_structures.union_find_with_path_compression + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/dynamic_programming.rst b/docs/dynamic_programming.rst new file mode 100644 index 0000000..f031664 --- /dev/null +++ b/docs/dynamic_programming.rst @@ -0,0 +1,7 @@ +Dynamic Programming +=================== + +.. automodule:: algorithms.dynamic_programming.lcs + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/factorization.rst b/docs/factorization.rst new file mode 100644 index 0000000..608785d --- /dev/null +++ b/docs/factorization.rst @@ -0,0 +1,17 @@ +Factorization +============= + +.. automodule:: algorithms.factorization.fermat + :members: + :undoc-members: + :show-inheritance: + +.. automodule:: algorithms.factorization.pollard_rho + :members: + :undoc-members: + :show-inheritance: + +.. automodule:: algorithms.factorization.trial_division + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/index.rst b/docs/index.rst new file mode 100644 index 0000000..8ec52ab --- /dev/null +++ b/docs/index.rst @@ -0,0 +1,71 @@ +.. algorithms documentation master file, created by + sphinx-quickstart on Thu Oct 8 22:36:00 2015. + You can adapt this file completely to your liking, but it should at least + contain the root `toctree` directive. + +Algorithms +========== + +Algorithms is a library of algorithms and data structures implemented in Python. + +The main purpose of this library is to be an educational tool. You probably +shouldn't use these in production, instead, opting for the optimized versions of +these algorithms that can be found else where. + +You should totally check out the `docs`_ for implementation details, complexities +and further info. + +Usage +----- + +If you want to use the algorithms in your code it is as simple as: + +:: + + from algorithms.sorting import bubble_sort + + my_list = bubble_sort.sort(my_list) + +Features +-------- + +- Pseudo code, algorithm complexities and futher info with each algorithm. +- Test coverage for each algorithm and data structure. +- Super sweet `documentation`_. + +Installation: +------------- + +Installation is as easy as: + +:: + + $ pip install algorithms + + +Tests: +------ + +Pytest is used as the main test runner and all Unit Tests can be run with: + +:: + + $ ./run_tests.py + + +Contributing: +------------- + +Contributions are always welcome. Check out the contributing guidelines to get +started. + +.. _`docs`: http://algorithms.readthedocs.org/en/latest/ +.. _`documentation`: http://algorithms.readthedocs.org/en/latest/ + +Table of Contents: +------------------ + +.. toctree:: + :maxdepth: 2 + + algorithms diff --git a/docs/make.bat b/docs/make.bat new file mode 100644 index 0000000..7ec5491 --- /dev/null +++ b/docs/make.bat @@ -0,0 +1,263 @@ +@ECHO OFF + +REM Command file for Sphinx documentation + +if "%SPHINXBUILD%" == "" ( + set SPHINXBUILD=sphinx-build +) +set BUILDDIR=_build +set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% . +set I18NSPHINXOPTS=%SPHINXOPTS% . +if NOT "%PAPER%" == "" ( + set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% + set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS% +) + +if "%1" == "" goto help + +if "%1" == "help" ( + :help + echo.Please use `make ^` where ^ is one of + echo. html to make standalone HTML files + echo. dirhtml to make HTML files named index.html in directories + echo. singlehtml to make a single large HTML file + echo. pickle to make pickle files + echo. json to make JSON files + echo. htmlhelp to make HTML files and a HTML help project + echo. qthelp to make HTML files and a qthelp project + echo. devhelp to make HTML files and a Devhelp project + echo. epub to make an epub + echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter + echo. text to make text files + echo. man to make manual pages + echo. texinfo to make Texinfo files + echo. gettext to make PO message catalogs + echo. changes to make an overview over all changed/added/deprecated items + echo. xml to make Docutils-native XML files + echo. pseudoxml to make pseudoxml-XML files for display purposes + echo. linkcheck to check all external links for integrity + echo. doctest to run all doctests embedded in the documentation if enabled + echo. coverage to run coverage check of the documentation if enabled + goto end +) + +if "%1" == "clean" ( + for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i + del /q /s %BUILDDIR%\* + goto end +) + + +REM Check if sphinx-build is available and fallback to Python version if any +%SPHINXBUILD% 2> nul +if errorlevel 9009 goto sphinx_python +goto sphinx_ok + +:sphinx_python + +set SPHINXBUILD=python -m sphinx.__init__ +%SPHINXBUILD% 2> nul +if errorlevel 9009 ( + echo. + echo.The 'sphinx-build' command was not found. Make sure you have Sphinx + echo.installed, then set the SPHINXBUILD environment variable to point + echo.to the full path of the 'sphinx-build' executable. Alternatively you + echo.may add the Sphinx directory to PATH. + echo. + echo.If you don't have Sphinx installed, grab it from + echo.http://sphinx-doc.org/ + exit /b 1 +) + +:sphinx_ok + + +if "%1" == "html" ( + %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The HTML pages are in %BUILDDIR%/html. + goto end +) + +if "%1" == "dirhtml" ( + %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. + goto end +) + +if "%1" == "singlehtml" ( + %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. + goto end +) + +if "%1" == "pickle" ( + %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle + if errorlevel 1 exit /b 1 + echo. + echo.Build finished; now you can process the pickle files. + goto end +) + +if "%1" == "json" ( + %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json + if errorlevel 1 exit /b 1 + echo. + echo.Build finished; now you can process the JSON files. + goto end +) + +if "%1" == "htmlhelp" ( + %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp + if errorlevel 1 exit /b 1 + echo. + echo.Build finished; now you can run HTML Help Workshop with the ^ +.hhp project file in %BUILDDIR%/htmlhelp. + goto end +) + +if "%1" == "qthelp" ( + %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp + if errorlevel 1 exit /b 1 + echo. + echo.Build finished; now you can run "qcollectiongenerator" with the ^ +.qhcp project file in %BUILDDIR%/qthelp, like this: + echo.^> qcollectiongenerator %BUILDDIR%\qthelp\algorithms.qhcp + echo.To view the help file: + echo.^> assistant -collectionFile %BUILDDIR%\qthelp\algorithms.ghc + goto end +) + +if "%1" == "devhelp" ( + %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. + goto end +) + +if "%1" == "epub" ( + %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The epub file is in %BUILDDIR%/epub. + goto end +) + +if "%1" == "latex" ( + %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex + if errorlevel 1 exit /b 1 + echo. + echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. + goto end +) + +if "%1" == "latexpdf" ( + %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex + cd %BUILDDIR%/latex + make all-pdf + cd %~dp0 + echo. + echo.Build finished; the PDF files are in %BUILDDIR%/latex. + goto end +) + +if "%1" == "latexpdfja" ( + %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex + cd %BUILDDIR%/latex + make all-pdf-ja + cd %~dp0 + echo. + echo.Build finished; the PDF files are in %BUILDDIR%/latex. + goto end +) + +if "%1" == "text" ( + %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The text files are in %BUILDDIR%/text. + goto end +) + +if "%1" == "man" ( + %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The manual pages are in %BUILDDIR%/man. + goto end +) + +if "%1" == "texinfo" ( + %SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo. + goto end +) + +if "%1" == "gettext" ( + %SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The message catalogs are in %BUILDDIR%/locale. + goto end +) + +if "%1" == "changes" ( + %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes + if errorlevel 1 exit /b 1 + echo. + echo.The overview file is in %BUILDDIR%/changes. + goto end +) + +if "%1" == "linkcheck" ( + %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck + if errorlevel 1 exit /b 1 + echo. + echo.Link check complete; look for any errors in the above output ^ +or in %BUILDDIR%/linkcheck/output.txt. + goto end +) + +if "%1" == "doctest" ( + %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest + if errorlevel 1 exit /b 1 + echo. + echo.Testing of doctests in the sources finished, look at the ^ +results in %BUILDDIR%/doctest/output.txt. + goto end +) + +if "%1" == "coverage" ( + %SPHINXBUILD% -b coverage %ALLSPHINXOPTS% %BUILDDIR%/coverage + if errorlevel 1 exit /b 1 + echo. + echo.Testing of coverage in the sources finished, look at the ^ +results in %BUILDDIR%/coverage/python.txt. + goto end +) + +if "%1" == "xml" ( + %SPHINXBUILD% -b xml %ALLSPHINXOPTS% %BUILDDIR%/xml + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The XML files are in %BUILDDIR%/xml. + goto end +) + +if "%1" == "pseudoxml" ( + %SPHINXBUILD% -b pseudoxml %ALLSPHINXOPTS% %BUILDDIR%/pseudoxml + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The pseudo-XML files are in %BUILDDIR%/pseudoxml. + goto end +) + +:end diff --git a/docs/math.rst b/docs/math.rst new file mode 100644 index 0000000..dca4977 --- /dev/null +++ b/docs/math.rst @@ -0,0 +1,37 @@ +Math +==== + +.. automodule:: algorithms.math.approx_cdf + :members: + :undoc-members: + :show-inheritance: + +.. automodule:: algorithms.math.extended_gcd + :members: + :undoc-members: + :show-inheritance: + +.. automodule:: algorithms.math.lcm + :members: + :undoc-members: + :show-inheritance: + +.. automodule:: algorithms.math.primality_test + :members: + :undoc-members: + :show-inheritance: + +.. automodule:: algorithms.math.sieve_atkin + :members: + :undoc-members: + :show-inheritance: + +.. automodule:: algorithms.math.sieve_eratosthenes + :members: + :undoc-members: + :show-inheritance: + +.. automodule:: algorithms.math.std_normal_pdf + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/random.rst b/docs/random.rst new file mode 100644 index 0000000..18f3fbb --- /dev/null +++ b/docs/random.rst @@ -0,0 +1,7 @@ +Random +====== + +.. automodule:: algorithms.random.mersenne_twister + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/searching.rst b/docs/searching.rst new file mode 100644 index 0000000..5ac96c7 --- /dev/null +++ b/docs/searching.rst @@ -0,0 +1,32 @@ +Searching +========= + +.. automodule:: algorithms.searching.binary_search + :members: + :undoc-members: + :show-inheritance: + +.. automodule:: algorithms.searching.bmh_search + :members: + :undoc-members: + :show-inheritance: + +.. automodule:: algorithms.searching.depth_first_search + :members: + :undoc-members: + :show-inheritance: + +.. automodule:: algorithms.searching.kmp_search + :members: + :undoc-members: + :show-inheritance: + +.. automodule:: algorithms.searching.rabinkarp_search + :members: + :undoc-members: + :show-inheritance: + +.. automodule:: algorithms.searching.ternary_search + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/shuffling.rst b/docs/shuffling.rst new file mode 100644 index 0000000..058b5cc --- /dev/null +++ b/docs/shuffling.rst @@ -0,0 +1,7 @@ +Shuffling +========= + +.. automodule:: algorithms.shuffling.knuth + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/sorting.rst b/docs/sorting.rst new file mode 100644 index 0000000..55c51c7 --- /dev/null +++ b/docs/sorting.rst @@ -0,0 +1,67 @@ +Sorting +======= + +.. automodule:: algorithms.sorting.bogo_sort + :members: + :undoc-members: + :show-inheritance: + +.. automodule:: algorithms.sorting.bubble_sort + :members: + :undoc-members: + :show-inheritance: + +.. automodule:: algorithms.sorting.cocktail_sort + :members: + :undoc-members: + :show-inheritance: + +.. automodule:: algorithms.sorting.comb_sort + :members: + :undoc-members: + :show-inheritance: + +.. automodule:: algorithms.sorting.gnome_sort + :members: + :undoc-members: + :show-inheritance: + +.. automodule:: algorithms.sorting.heap_sort + :members: + :undoc-members: + :show-inheritance: + +.. automodule:: algorithms.sorting.insertion_sort + :members: + :undoc-members: + :show-inheritance: + +.. automodule:: algorithms.sorting.merge_sort + :members: + :undoc-members: + :show-inheritance: + +.. automodule:: algorithms.sorting.quick_sort + :members: + :undoc-members: + :show-inheritance: + +.. automodule:: algorithms.sorting.quick_sort_in_place + :members: + :undoc-members: + :show-inheritance: + +.. automodule:: algorithms.sorting.selection_sort + :members: + :undoc-members: + :show-inheritance: + +.. automodule:: algorithms.sorting.shell_sort + :members: + :undoc-members: + :show-inheritance: + +.. automodule:: algorithms.data_structures.lcp_array + :members: + :undoc-members: + :show-inheritance: diff --git a/requirements.txt b/requirements.txt index 65d9a46..878c520 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,5 @@ -argparse==1.2.1 -distribute==0.6.27 -nose==1.1.2 -wsgiref==0.1.2 +# This will install *all* of the requirements. If you only want to install +# a subset of the requirements check out the requirements directory + +-r requirements/requirements-testing.txt +-r requirements/requirements-documentation.txt diff --git a/requirements/requirements-documentation.txt b/requirements/requirements-documentation.txt new file mode 100644 index 0000000..6c957a5 --- /dev/null +++ b/requirements/requirements-documentation.txt @@ -0,0 +1,18 @@ +alabaster==0.7.6 +argh==0.26.1 +Babel==2.1.1 +docutils==0.12 +Jinja2==2.8 +livereload==2.4.0 +MarkupSafe==0.23 +pathtools==0.1.2 +Pygments==2.0.2 +pytz==2015.6 +PyYAML==3.11 +six==1.10.0 +snowballstemmer==1.2.0 +Sphinx==1.3.1 +sphinx-autobuild==0.5.2 +sphinx-rtd-theme==0.1.9 +tornado==4.2.1 +watchdog==0.8.3 diff --git a/requirements/requirements-testing.txt b/requirements/requirements-testing.txt new file mode 100644 index 0000000..05bce13 --- /dev/null +++ b/requirements/requirements-testing.txt @@ -0,0 +1,12 @@ +cov-core==1.15.0 +coverage==3.7.1 +flake8==2.4.1 +mccabe==0.3.1 +pep8==1.5.7 +pluggy==0.3.1 +py==1.4.30 +pyflakes==0.8.1 +pytest==2.8.2 +pytest-cov==2.2.0 +tox==2.1.1 +virtualenv==13.1.2 diff --git a/run_tests.py b/run_tests.py old mode 100644 new mode 100755 index e2bb3e7..6c3c8da --- a/run_tests.py +++ b/run_tests.py @@ -1,4 +1,59 @@ -import nose +#! /usr/bin/env python +# -*- coding: utf-8 -*- +from __future__ import print_function + +import os +import subprocess +import sys + +import pytest + + +FLAKE8_ARGS = ['algorithms', 'tests'] + +sys.path.append(os.path.dirname(__file__)) + + +def exit_on_failure(ret, message=None): + if ret: + sys.exit(ret) + + +def flake8_main(args): + print('Running flake8 code linting') + ret = subprocess.call(['flake8'] + args) + print('flake8 failed' if ret else 'flake8 passed') + return ret + if __name__ == '__main__': - nose.main() + pytest_args = sys.argv[1:] + run_tests = True + run_flake8 = True + + # Logic to run flake8 only + try: + sys.argv.remove('--lintonly') + except ValueError: + run_tests = True + else: + run_tests = False + run_flake8 = True + + # Logic to run pytest with coverage turned on + try: + pytest_args.remove('--coverage') + except ValueError: + pass + else: + pytest_args = [ + '--cov-report', + 'xml', + '--cov', + 'algorithms'] + pytest_args + + if run_tests: + exit_on_failure(pytest.main(pytest_args)) + + if run_flake8: + exit_on_failure(flake8_main(FLAKE8_ARGS)) diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 0000000..bfead2c --- /dev/null +++ b/setup.cfg @@ -0,0 +1,2 @@ +[flake8] +max-line-length = 79 diff --git a/setup.py b/setup.py index 8684d60..480a0ff 100644 --- a/setup.py +++ b/setup.py @@ -1,19 +1,26 @@ -from setuptools import setup +import io +from setuptools import find_packages, setup -#Read in the README for the long description on PyPI + +# Read in the README for the long description on PyPI def long_description(): - with open('README.rst', 'r') as f: - readme = unicode(f.read()) + with io.open('README.rst', 'r', encoding='utf-8') as f: + readme = f.read() return readme setup(name='algorithms', - version='0.1', + version='1.0', description='module of algorithms for Python', long_description=long_description(), url='https://github.com/nryoung/algorithms', author='Nic Young', author_email='nryoung@gmail.com', license='BSD', - packages=['algorithms', 'algorithms.sorting', 'algorithms.shuffling', - 'algorithms.searching', 'algorithms.math', 'algorithms.tests'], + packages=find_packages(), + classifiers=[ + 'Programming Language :: Python :: 2.7', + 'Programming Language :: Python :: 3', + 'Programming Language :: Python :: 3.4', + 'Programming Language :: Python :: 3.5', + ], zip_safe=False) diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/test_data_structures.py b/tests/test_data_structures.py new file mode 100644 index 0000000..98fdc82 --- /dev/null +++ b/tests/test_data_structures.py @@ -0,0 +1,717 @@ +from random import shuffle +import unittest + +from algorithms.data_structures import ( + binary_search_tree, + digraph, + queue, + singly_linked_list, + stack, + undirected_graph, + union_find, + union_find_by_rank, + union_find_with_path_compression, + lcp_array +) + + +class TestBinarySearchTree(unittest.TestCase): + """ + Test Binary Search Tree Implementation + """ + key_val = [ + ("a", 1), ("b", 2), ("c", 3), + ("d", 4), ("e", 5), ("f", 6), + ("g", 7), ("h", 8), ("i", 9) + ] + + def shuffle_list(self, ls): + shuffle(ls) + return ls + + def test_size(self): + # Size starts at 0 + self.bst = binary_search_tree.BinarySearchTree() + self.assertEqual(self.bst.size(), 0) + # Doing a put increases the size to 1 + self.bst.put("one", 1) + self.assertEqual(self.bst.size(), 1) + # Putting a key that is already in doesn't change size + self.bst.put("one", 1) + self.assertEqual(self.bst.size(), 1) + self.bst.put("one", 2) + self.assertEqual(self.bst.size(), 1) + + self.bst = binary_search_tree.BinarySearchTree() + size = 0 + for pair in self.key_val: + k, v = pair + self.bst.put(k, v) + size += 1 + self.assertEqual(self.bst.size(), size) + + shuffled = self.shuffle_list(self.key_val[:]) + + self.bst = binary_search_tree.BinarySearchTree() + size = 0 + for pair in shuffled: + k, v = pair + self.bst.put(k, v) + size += 1 + self.assertEqual(self.bst.size(), size) + + def test_is_empty(self): + self.bst = binary_search_tree.BinarySearchTree() + self.assertTrue(self.bst.is_empty()) + self.bst.put("a", 1) + self.assertFalse(self.bst.is_empty()) + + def test_get(self): + self.bst = binary_search_tree.BinarySearchTree() + # Getting a key not in BST returns None + self.assertEqual(self.bst.get("one"), None) + + # Get with a present key returns proper value + self.bst.put("one", 1) + self.assertEqual(self.bst.get("one"), 1) + + self.bst = binary_search_tree.BinarySearchTree() + for pair in self.key_val: + k, v = pair + self.bst.put(k, v) + self.assertEqual(self.bst.get(k), v) + + shuffled = self.shuffle_list(self.key_val[:]) + + self.bst = binary_search_tree.BinarySearchTree() + for pair in shuffled: + k, v = pair + self.bst.put(k, v) + self.assertEqual(self.bst.get(k), v) + + def test_contains(self): + self.bst = binary_search_tree.BinarySearchTree() + self.assertFalse(self.bst.contains("a")) + self.bst.put("a", 1) + self.assertTrue(self.bst.contains("a")) + + def test_put(self): + self.bst = binary_search_tree.BinarySearchTree() + + # When BST is empty first put becomes root + self.bst.put("bbb", 1) + self.assertEqual(self.bst.root.key, "bbb") + self.assertEqual(self.bst.root.left, None) + + # Adding a key greater than root doesn't update the left tree + # but does update the right + self.bst.put("ccc", 2) + self.assertEqual(self.bst.root.key, "bbb") + self.assertEqual(self.bst.root.left, None) + self.assertEqual(self.bst.root.right.key, "ccc") + + self.bst = binary_search_tree.BinarySearchTree() + self.bst.put("bbb", 1) + # Adding a key less than root doesn't update the right tree + # but does update the left + self.bst.put("aaa", 2) + self.assertEqual(self.bst.root.key, "bbb") + self.assertEqual(self.bst.root.right, None) + self.assertEqual(self.bst.root.left.key, "aaa") + + self.bst = binary_search_tree.BinarySearchTree() + size = 0 + for pair in self.key_val: + k, v = pair + self.bst.put(k, v) + size += 1 + self.assertEqual(self.bst.get(k), v) + self.assertEqual(self.bst.size(), size) + + self.bst = binary_search_tree.BinarySearchTree() + + shuffled = self.shuffle_list(self.key_val[:]) + + size = 0 + for pair in shuffled: + k, v = pair + self.bst.put(k, v) + size += 1 + self.assertEqual(self.bst.get(k), v) + self.assertEqual(self.bst.size(), size) + + def test_min_key(self): + self.bst = binary_search_tree.BinarySearchTree() + for pair in self.key_val[::-1]: + k, v = pair + self.bst.put(k, v) + self.assertEqual(self.bst.min_key(), k) + + shuffled = self.shuffle_list(self.key_val[:]) + + self.bst = binary_search_tree.BinarySearchTree() + for pair in shuffled: + k, v = pair + self.bst.put(k, v) + self.assertEqual(self.bst.min_key(), "a") + + def test_max_key(self): + self.bst = binary_search_tree.BinarySearchTree() + for pair in self.key_val: + k, v = pair + self.bst.put(k, v) + self.assertEqual(self.bst.max_key(), k) + + shuffled = self.shuffle_list(self.key_val[:]) + + self.bst = binary_search_tree.BinarySearchTree() + for pair in shuffled: + k, v = pair + self.bst.put(k, v) + self.assertEqual(self.bst.max_key(), "i") + + def test_floor_key(self): + self.bst = binary_search_tree.BinarySearchTree() + self.bst.put("a", 1) + self.bst.put("c", 3) + self.bst.put("e", 5) + self.bst.put("g", 7) + self.assertEqual(self.bst.floor_key("a"), "a") + self.assertEqual(self.bst.floor_key("b"), "a") + self.assertEqual(self.bst.floor_key("g"), "g") + self.assertEqual(self.bst.floor_key("h"), "g") + + self.bst = binary_search_tree.BinarySearchTree() + self.bst.put("c", 3) + self.bst.put("e", 5) + self.bst.put("a", 1) + self.bst.put("g", 7) + self.assertEqual(self.bst.floor_key("a"), "a") + self.assertEqual(self.bst.floor_key("b"), "a") + self.assertEqual(self.bst.floor_key("g"), "g") + self.assertEqual(self.bst.floor_key("h"), "g") + + def test_ceiling_key(self): + self.bst = binary_search_tree.BinarySearchTree() + self.bst.put("a", 1) + self.bst.put("c", 3) + self.bst.put("e", 5) + self.bst.put("g", 7) + self.assertEqual(self.bst.ceiling_key("a"), "a") + self.assertEqual(self.bst.ceiling_key("b"), "c") + self.assertEqual(self.bst.ceiling_key("g"), "g") + self.assertEqual(self.bst.ceiling_key("f"), "g") + + self.bst = binary_search_tree.BinarySearchTree() + self.bst.put("c", 3) + self.bst.put("e", 5) + self.bst.put("a", 1) + self.bst.put("g", 7) + self.assertEqual(self.bst.ceiling_key("a"), "a") + self.assertEqual(self.bst.ceiling_key("b"), "c") + self.assertEqual(self.bst.ceiling_key("g"), "g") + self.assertEqual(self.bst.ceiling_key("f"), "g") + + def test_select_key(self): + shuffled = self.shuffle_list(self.key_val[:]) + + self.bst = binary_search_tree.BinarySearchTree() + for pair in shuffled: + k, v = pair + self.bst.put(k, v) + self.assertEqual(self.bst.select_key(0), "a") + self.assertEqual(self.bst.select_key(1), "b") + self.assertEqual(self.bst.select_key(2), "c") + + def test_rank(self): + self.bst = binary_search_tree.BinarySearchTree() + for pair in self.key_val: + k, v = pair + self.bst.put(k, v) + + self.assertEqual(self.bst.rank("a"), 0) + self.assertEqual(self.bst.rank("b"), 1) + self.assertEqual(self.bst.rank("c"), 2) + self.assertEqual(self.bst.rank("d"), 3) + + shuffled = self.shuffle_list(self.key_val[:]) + self.bst = binary_search_tree.BinarySearchTree() + for pair in shuffled: + k, v = pair + self.bst.put(k, v) + + self.assertEqual(self.bst.rank("a"), 0) + self.assertEqual(self.bst.rank("b"), 1) + self.assertEqual(self.bst.rank("c"), 2) + self.assertEqual(self.bst.rank("d"), 3) + + def test_delete_min(self): + self.bst = binary_search_tree.BinarySearchTree() + for pair in self.key_val: + k, v = pair + self.bst.put(k, v) + for i in range(self.bst.size() - 1): + self.bst.delete_min() + self.assertEqual(self.bst.min_key(), self.key_val[i+1][0]) + self.bst.delete_min() + self.assertEqual(self.bst.min_key(), None) + + shuffled = self.shuffle_list(self.key_val[:]) + self.bst = binary_search_tree.BinarySearchTree() + for pair in shuffled: + k, v = pair + self.bst.put(k, v) + for i in range(self.bst.size() - 1): + self.bst.delete_min() + self.assertEqual(self.bst.min_key(), self.key_val[i+1][0]) + self.bst.delete_min() + self.assertEqual(self.bst.min_key(), None) + + def test_delete_max(self): + self.bst = binary_search_tree.BinarySearchTree() + for pair in self.key_val: + k, v = pair + self.bst.put(k, v) + for i in range(self.bst.size() - 1, 0, -1): + self.bst.delete_max() + self.assertEqual(self.bst.max_key(), self.key_val[i-1][0]) + self.bst.delete_max() + self.assertEqual(self.bst.max_key(), None) + + shuffled = self.shuffle_list(self.key_val[:]) + + for pair in shuffled: + k, v = pair + self.bst.put(k, v) + for i in range(self.bst.size() - 1, 0, -1): + self.bst.delete_max() + self.assertEqual(self.bst.max_key(), self.key_val[i-1][0]) + self.bst.delete_max() + self.assertEqual(self.bst.max_key(), None) + + def test_delete(self): + # delete key from an empty bst + self.bst = binary_search_tree.BinarySearchTree() + self.bst.delete("a") + self.assertEqual(self.bst.root, None) + self.assertEqual(self.bst.size(), 0) + + # delete key not present in bst + self.bst = binary_search_tree.BinarySearchTree() + self.bst.put("a", 1) + self.bst.delete("b") + self.assertEqual(self.bst.root.key, "a") + self.assertEqual(self.bst.size(), 1) + + # delete key when bst only contains one key + self.bst = binary_search_tree.BinarySearchTree() + self.bst.put("a", 1) + self.assertEqual(self.bst.root.key, "a") + self.bst.delete("a") + self.assertEqual(self.bst.root, None) + self.assertEqual(self.bst.size(), 0) + + # delete parent key when it only has a left child + self.bst = binary_search_tree.BinarySearchTree() + self.bst.put("b", 2) + self.bst.put("a", 1) + self.assertEqual(self.bst.root.left.key, "a") + self.bst.delete("b") + self.assertEqual(self.bst.root.key, "a") + self.assertEqual(self.bst.size(), 1) + + # delete parent key when it only has a right child + self.bst = binary_search_tree.BinarySearchTree() + self.bst.put("a", 1) + self.bst.put("b", 2) + self.assertEqual(self.bst.root.right.key, "b") + self.bst.delete("a") + self.assertEqual(self.bst.root.key, "b") + self.assertEqual(self.bst.size(), 1) + + # delete left child key + self.bst = binary_search_tree.BinarySearchTree() + self.bst.put("b", 2) + self.bst.put("a", 1) + self.assertEqual(self.bst.root.left.key, "a") + self.bst.delete("a") + self.assertEqual(self.bst.root.key, "b") + self.assertEqual(self.bst.size(), 1) + + # delete right child key + self.bst = binary_search_tree.BinarySearchTree() + self.bst.put("a", 1) + self.bst.put("b", 2) + self.assertEqual(self.bst.root.right.key, "b") + self.bst.delete("b") + self.assertEqual(self.bst.root.key, "a") + self.assertEqual(self.bst.size(), 1) + + # delete parent key when it has a left and right child + self.bst = binary_search_tree.BinarySearchTree() + self.bst.put("b", 2) + self.bst.put("a", 1) + self.bst.put("c", 3) + self.bst.delete("b") + self.assertEqual(self.bst.root.key, "c") + self.assertEqual(self.bst.size(), 2) + + def test_keys(self): + self.bst = binary_search_tree.BinarySearchTree() + for pair in self.key_val: + k, v = pair + self.bst.put(k, v) + self.assertEqual( + self.bst.keys(), + ["a", "b", "c", "d", "e", "f", "g", "h", "i"] + ) + + +class TestDirectedGraph(unittest.TestCase): + """ + Test Undirected Graph Implementation + """ + def test_directed_graph(self): + + # init + self.dg0 = digraph.Digraph() + self.dg1 = digraph.Digraph() + self.dg2 = digraph.Digraph() + self.dg3 = digraph.Digraph() + + # populating + self.dg1.add_edge(1, 2) + + self.dg1_rev = self.dg1.reverse() # reverse + + self.dg2.add_edge(1, 2) + self.dg2.add_edge(1, 2) + + self.dg3.add_edge(1, 2) + self.dg3.add_edge(1, 2) + self.dg3.add_edge(3, 1) + + # test adj + self.assertTrue(2 in self.dg1.adj(1)) + self.assertEqual(len(self.dg1.adj(1)), 1) + self.assertTrue(1 not in self.dg1.adj(2)) + self.assertEqual(len(self.dg1.adj(2)), 0) + + self.assertTrue(1 in self.dg1_rev.adj(2)) + self.assertEqual(len(self.dg1_rev.adj(2)), 1) + self.assertTrue(2 not in self.dg1_rev.adj(1)) + self.assertEqual(len(self.dg1_rev.adj(1)), 0) + + self.assertTrue(2 in self.dg2.adj(1)) + self.assertEqual(len(self.dg2.adj(1)), 2) + self.assertTrue(1 not in self.dg2.adj(2)) + self.assertEqual(len(self.dg2.adj(2)), 0) + + self.assertTrue(2 in self.dg3.adj(1)) + self.assertTrue(1 in self.dg3.adj(3)) + self.assertEqual(len(self.dg3.adj(1)), 2) + self.assertTrue(1 not in self.dg3.adj(2)) + self.assertEqual(len(self.dg3.adj(2)), 0) + self.assertTrue(3 not in self.dg3.adj(1)) + self.assertEqual(len(self.dg3.adj(3)), 1) + + # test degree + self.assertEqual(self.dg1.outdegree(1), 1) + self.assertEqual(self.dg1.outdegree(2), 0) + + self.assertEqual(self.dg1_rev.outdegree(2), 1) + self.assertEqual(self.dg1_rev.outdegree(1), 0) + + self.assertEqual(self.dg2.outdegree(1), 2) + self.assertEqual(self.dg2.outdegree(2), 0) + + self.assertEqual(self.dg3.outdegree(1), 2) + self.assertEqual(self.dg3.outdegree(2), 0) + self.assertEqual(self.dg3.outdegree(3), 1) + + # test vertices + self.assertEqual(list(self.dg0.vertices()), []) + self.assertEqual(len(self.dg0.vertices()), 0) + + self.assertTrue(1 in self.dg1.vertices()) + self.assertTrue(2 in self.dg1.vertices()) + self.assertEqual(len(self.dg1.vertices()), 2) + + self.assertTrue(2 in self.dg1_rev.vertices()) + self.assertTrue(1 in self.dg1_rev.vertices()) + self.assertEqual(len(self.dg1_rev.vertices()), 2) + + self.assertTrue(1 in self.dg2.vertices()) + self.assertTrue(2 in self.dg2.vertices()) + self.assertEqual(len(self.dg2.vertices()), 2) + + self.assertTrue(1 in self.dg3.vertices()) + self.assertTrue(2 in self.dg3.vertices()) + self.assertTrue(3 in self.dg3.vertices()) + self.assertEqual(len(self.dg3.vertices()), 3) + + # test vertex_count + self.assertEqual(self.dg0.vertex_count(), 0) + self.assertEqual(self.dg1.vertex_count(), 2) + self.assertEqual(self.dg1_rev.vertex_count(), 2) + self.assertEqual(self.dg2.vertex_count(), 2) + self.assertEqual(self.dg3.vertex_count(), 3) + + # test edge_count + self.assertEqual(self.dg0.edge_count(), 0) + self.assertEqual(self.dg1.edge_count(), 1) + self.assertEqual(self.dg1_rev.edge_count(), 1) + self.assertEqual(self.dg2.edge_count(), 2) + self.assertEqual(self.dg3.edge_count(), 3) + + +class TestQueue(unittest.TestCase): + """ + Test Queue Implementation + """ + def test_queue(self): + self.que = queue.Queue() + self.que.add(1) + self.que.add(2) + self.que.add(8) + self.que.add(5) + self.que.add(6) + + self.assertEqual(self.que.remove(), 1) + self.assertEqual(self.que.size(), 4) + self.assertEqual(self.que.remove(), 2) + self.assertEqual(self.que.remove(), 8) + self.assertEqual(self.que.remove(), 5) + self.assertEqual(self.que.remove(), 6) + self.assertEqual(self.que.is_empty(), True) + + +class TestSinglyLinkedList(unittest.TestCase): + """ + Test Singly Linked List Implementation + """ + + def test_singly_linked_list(self): + self.sl = singly_linked_list.SinglyLinkedList() + self.sl.add(10) + self.sl.add(5) + self.sl.add(30) + self.sl.remove(30) + + self.assertEqual(self.sl.size, 2) + self.assertEqual(self.sl.search(30), False) + self.assertEqual(self.sl.search(5), True) + self.assertEqual(self.sl.search(10), True) + self.assertEqual(self.sl.remove(5), True) + self.assertEqual(self.sl.remove(10), True) + self.assertEqual(self.sl.size, 0) + + +class TestStack(unittest.TestCase): + """ + Test Stack Implementation + """ + def test_stack(self): + self.sta = stack.Stack() + self.sta.add(5) + self.sta.add(8) + self.sta.add(10) + self.sta.add(2) + + self.assertEqual(self.sta.remove(), 2) + self.assertEqual(self.sta.is_empty(), False) + self.assertEqual(self.sta.size(), 3) + + +class TestUndirectedGraph(unittest.TestCase): + """ + Test Undirected Graph Implementation + """ + def test_undirected_graph(self): + + # init + self.ug0 = undirected_graph.Undirected_Graph() + self.ug1 = undirected_graph.Undirected_Graph() + self.ug2 = undirected_graph.Undirected_Graph() + self.ug3 = undirected_graph.Undirected_Graph() + + # populating + self.ug1.add_edge(1, 2) + + self.ug2.add_edge(1, 2) + self.ug2.add_edge(1, 2) + + self.ug3.add_edge(1, 2) + self.ug3.add_edge(1, 2) + self.ug3.add_edge(3, 1) + + # test adj + self.assertTrue(2 in self.ug1.adj(1)) + self.assertEqual(len(self.ug1.adj(1)), 1) + self.assertTrue(1 in self.ug1.adj(2)) + self.assertEqual(len(self.ug1.adj(1)), 1) + + self.assertTrue(2 in self.ug2.adj(1)) + self.assertEqual(len(self.ug2.adj(1)), 2) + self.assertTrue(1 in self.ug2.adj(2)) + self.assertEqual(len(self.ug2.adj(1)), 2) + + self.assertTrue(2 in self.ug3.adj(1)) + self.assertTrue(3 in self.ug3.adj(1)) + self.assertEqual(len(self.ug3.adj(1)), 3) + self.assertTrue(1 in self.ug3.adj(2)) + self.assertEqual(len(self.ug3.adj(2)), 2) + self.assertTrue(1 in self.ug3.adj(3)) + self.assertEqual(len(self.ug3.adj(3)), 1) + + # test degree + self.assertEqual(self.ug1.degree(1), 1) + self.assertEqual(self.ug1.degree(2), 1) + self.assertEqual(self.ug2.degree(1), 2) + self.assertEqual(self.ug2.degree(2), 2) + self.assertEqual(self.ug3.degree(1), 3) + self.assertEqual(self.ug3.degree(2), 2) + self.assertEqual(self.ug3.degree(3), 1) + + # test vertices + self.assertEqual(list(self.ug0.vertices()), []) + self.assertEqual(len(self.ug0.vertices()), 0) + + self.assertTrue(1 in self.ug1.vertices()) + self.assertTrue(2 in self.ug1.vertices()) + self.assertEqual(len(self.ug1.vertices()), 2) + + self.assertTrue(1 in self.ug2.vertices()) + self.assertTrue(2 in self.ug2.vertices()) + self.assertEqual(len(self.ug2.vertices()), 2) + + self.assertTrue(1 in self.ug3.vertices()) + self.assertTrue(2 in self.ug3.vertices()) + self.assertTrue(3 in self.ug3.vertices()) + self.assertEqual(len(self.ug3.vertices()), 3) + + # test vertex_count + self.assertEqual(self.ug0.vertex_count(), 0) + self.assertEqual(self.ug1.vertex_count(), 2) + self.assertEqual(self.ug2.vertex_count(), 2) + self.assertEqual(self.ug3.vertex_count(), 3) + + # test edge_count + self.assertEqual(self.ug0.edge_count(), 0) + self.assertEqual(self.ug1.edge_count(), 1) + self.assertEqual(self.ug2.edge_count(), 2) + self.assertEqual(self.ug3.edge_count(), 3) + + +class TestUnionFind(unittest.TestCase): + """ + Test Union Find Implementation + """ + def test_union_find(self): + self.uf = union_find.UnionFind(4) + self.uf.make_set(4) + self.uf.union(1, 0) + self.uf.union(3, 4) + + self.assertEqual(self.uf.find(1), 0) + self.assertEqual(self.uf.find(3), 4) + self.assertEqual(self.uf.is_connected(0, 1), True) + self.assertEqual(self.uf.is_connected(3, 4), True) + + +class TestUnionFindByRank(unittest.TestCase): + """ + Test Union Find Implementation + """ + def test_union_find_by_rank(self): + self.uf = union_find_by_rank.UnionFindByRank(6) + self.uf.make_set(6) + self.uf.union(1, 0) + self.uf.union(3, 4) + self.uf.union(2, 4) + self.uf.union(5, 2) + self.uf.union(6, 5) + + self.assertEqual(self.uf.find(1), 1) + self.assertEqual(self.uf.find(3), 3) + # test tree is created by rank + self.uf.union(5, 0) + self.assertEqual(self.uf.find(2), 3) + self.assertEqual(self.uf.find(5), 3) + self.assertEqual(self.uf.find(6), 3) + self.assertEqual(self.uf.find(0), 3) + + self.assertEqual(self.uf.is_connected(0, 1), True) + self.assertEqual(self.uf.is_connected(3, 4), True) + self.assertEqual(self.uf.is_connected(5, 3), True) + + +class TestUnionFindWithPathCompression(unittest.TestCase): + """ + Test Union Find Implementation + """ + def test_union_find_with_path_compression(self): + self.uf = ( + union_find_with_path_compression + .UnionFindWithPathCompression(5) + ) + + self.uf.make_set(5) + self.uf.union(0, 1) + self.uf.union(2, 3) + self.uf.union(1, 3) + self.uf.union(4, 5) + self.assertEqual(self.uf.find(1), 0) + self.assertEqual(self.uf.find(3), 0) + self.assertEqual(self.uf.parent(3), 2) + self.assertEqual(self.uf.parent(5), 4) + self.assertEqual(self.uf.is_connected(3, 5), False) + self.assertEqual(self.uf.is_connected(4, 5), True) + self.assertEqual(self.uf.is_connected(2, 3), True) + # test tree is created by path compression + self.uf.union(5, 3) + self.assertEqual(self.uf.parent(3), 0) + + self.assertEqual(self.uf.is_connected(3, 5), True) + + +class TestLCPSuffixArrays(unittest.TestCase): + def setUp(self): + super(TestLCPSuffixArrays, self).setUp() + self.case_1 = "aaaaaa" + self.s_array_1 = [5, 4, 3, 2, 1, 0] + self.rank_1 = [5, 4, 3, 2, 1, 0] + self.lcp_1 = [1, 2, 3, 4, 5, 0] + + self.case_2 = "abcabcdd" + self.s_array_2 = [0, 2, 4, 1, 3, 5, 7, 6] + self.rank_2 = [0, 3, 1, 4, 2, 5, 7, 6] + self.lcp_2 = [3, 0, 2, 0, 1, 0, 1, 0] + + self.case_3 = "kmckirrrmppp" + self.s_array_3 = [3, 4, 0, 2, 1, 11, 10, 9, 5, 8, 7, 6] + self.rank_3 = [2, 4, 3, 0, 1, 8, 11, 10, 9, 7, 6, 5] + self.lcp_3 = [0, 0, 1, 0, 1, 0, 1, 2, 0, 1, 2, 0] + + def test_lcp_array(self): + lcp = lcp_array.lcp_array(self.case_1, self.s_array_1, self.rank_1) + self.assertEqual(lcp, self.lcp_1) + + lcp = lcp_array.lcp_array(self.case_2, self.s_array_2, self.rank_2) + self.assertEqual(lcp, self.lcp_2) + + lcp = lcp_array.lcp_array(self.case_3, self.s_array_3, self.rank_3) + self.assertEqual(lcp, self.lcp_3) + + def test_suffix_array(self): + s_array, rank = lcp_array.suffix_array(self.case_1) + self.assertEqual(s_array, self.s_array_1) + self.assertEqual(rank, self.rank_1) + + s_array, rank = lcp_array.suffix_array(self.case_2) + self.assertEqual(s_array, self.s_array_2) + self.assertEqual(rank, self.rank_2) + + s_array, rank = lcp_array.suffix_array(self.case_3) + self.assertEqual(s_array, self.s_array_3) + self.assertEqual(rank, self.rank_3) diff --git a/tests/test_dynamic_programming.py b/tests/test_dynamic_programming.py new file mode 100644 index 0000000..932ea54 --- /dev/null +++ b/tests/test_dynamic_programming.py @@ -0,0 +1,23 @@ +import unittest + +from algorithms.dynamic_programming.lcs import lcs + + +class TestLCS(unittest.TestCase): + """ + Tests the Longest Common Subsequence of several strings + """ + + def test_lcs(self): + str1 = "BANANA" + str2 = "ABA" + str3 = "BCAD" + str4 = "NNAD" + + self.assertEqual(lcs(str1, str1), str1) + self.assertEqual(lcs(str1, str2), "BA") + self.assertEqual(lcs(str1, str3), "BA") + self.assertEqual(lcs(str1, str4), "NNA") + self.assertEqual(lcs(str2, str3), "BA") + self.assertEqual(lcs(str2, str4), "A") + self.assertEqual(lcs(str3, str4), "AD") diff --git a/tests/test_factorization.py b/tests/test_factorization.py new file mode 100644 index 0000000..4181118 --- /dev/null +++ b/tests/test_factorization.py @@ -0,0 +1,47 @@ +import random +import unittest + +from algorithms.factorization.pollard_rho import pollard_rho +from algorithms.factorization.trial_division import trial_division +from algorithms.factorization.fermat import fermat + + +class TestFermat(unittest.TestCase): + + def test_fermat(self): + x = random.randint(1, 100000000) + factors = fermat(x) + res = 1 + for i in factors: + res *= i + self.assertEqual(x, res) + + +class TestPollardRho(unittest.TestCase): + + def test_pollard_rho(self): + x = random.randint(1, 100000000000) + factors = pollard_rho(x) + res = 1 + for j in factors: + res *= j + self.assertEqual(x, res) + + def test_pollard_rho_x_is_zero(self): + x = 0 + factors = pollard_rho(x) + res = 1 + for j in factors: + res *= j + self.assertEqual(x, res) + + +class TestTrialDivision(unittest.TestCase): + + def test_trial_division(self): + x = random.randint(0, 10000000000) + factors = trial_division(x) + res = 1 + for i in factors: + res *= i + self.assertEqual(x, res) diff --git a/tests/test_math.py b/tests/test_math.py new file mode 100644 index 0000000..fc23463 --- /dev/null +++ b/tests/test_math.py @@ -0,0 +1,140 @@ +import unittest + +from algorithms.math.approx_cdf import cdf +from algorithms.math.extended_gcd import extended_gcd +from algorithms.math.lcm import lcm +from algorithms.math.primality_test import is_prime +from algorithms.math.sieve_atkin import atkin +from algorithms.math.sieve_eratosthenes import eratosthenes +from algorithms.math.std_normal_pdf import pdf + + +class TestApproxCdf(unittest.TestCase): + + def test_cdf(self): + # Calculate cumulative distribution function for x=1 + a = cdf(1) + self.assertAlmostEqual(a, 0.841344746068543) + + # Calculate cumulative distribution function x=0 + a = cdf(0) + self.assertAlmostEqual(a, 0.5) + + # Calculate cumulative distribution function for x=(-1) + a = cdf(-1) + self.assertAlmostEqual(a, 0.15865525393145702) + + +class TestExtendedGCD(unittest.TestCase): + + def test_extended_gcd(self): + # Find extended_gcd of 35 and 77 + (a, b) = extended_gcd(35, 77) + print(a, b) + self.assertIs(35 * a + 77 * b, 7) + + # Find extended_gcd of 15 and 19 + (a, b) = extended_gcd(15, 19) + self.assertIs(15 * a + 19 * b, 1) + + # Find extended_gcd of 18 and 9 + (a, b) = extended_gcd(18, 9) + self.assertIs(18 * a + 9 * b, 9) + + # Find extended_gcd of 99 and 81 + (a, b) = extended_gcd(99, 81) + self.assertIs(99 * a + 81 * b, 9) + + # Find extended_gcd of 50 and 15 + (a, b) = extended_gcd(50, 15) + self.assertIs(50 * a + 15 * b, 5) + + +class TestLCM(unittest.TestCase): + def test_lcm(self): + # Find lcm of (16, 20) and (20, 16) + r, r2 = lcm(16, 20), lcm(20, 16) + self.assertEqual(r, 80) + + # Checks that lcm function is commutative + self.assertEqual(r, r2) + + +class TestPrimalityTest(unittest.TestCase): + def test_is_prime(self): + self.assertIs(is_prime(3), True) + self.assertIs(is_prime(15), False) + self.assertIs(is_prime(20), False) + self.assertIs(is_prime(37), True) + self.assertIs(is_prime(63), False) + self.assertIs(is_prime(87), False) + self.assertIs(is_prime(103), True) + + +class TestSieveOfAtkin(unittest.TestCase): + + def test_atkin(self): + rv1 = atkin(10) + rv2 = atkin(100) + rv3 = atkin(1000) + rv4 = atkin(-10) + self.assertEqual(rv1, [2, 3, 5, 7]) + self.assertEqual( + rv2, + [2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, + 61, 67, 71, 73, 79, 83, 89, 97] + ) + self.assertEqual( + rv3, + [2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, + 61, 67, 71, 73, 79, 83, 89, 97, 101, 103, 107, 109, 113, 127, + 131, 137, 139, 149, 151, 157, 163, 167, 173, 179, 181, 191, 193, + 197, 199, 211, 223, 227, 229, 233, 239, 241, 251, 257, 263, 269, + 271, 277, 281, 283, 293, 307, 311, 313, 317, 331, 337, 347, 349, + 353, 359, 367, 373, 379, 383, 389, 397, 401, 409, 419, 421, 431, + 433, 439, 443, 449, 457, 461, 463, 467, 479, 487, 491, 499, 503, + 509, 521, 523, 541, 547, 557, 563, 569, 571, 577, 587, 593, 599, + 601, 607, 613, 617, 619, 631, 641, 643, 647, 653, 659, 661, 673, + 677, 683, 691, 701, 709, 719, 727, 733, 739, 743, 751, 757, 761, + 769, 773, 787, 797, 809, 811, 821, 823, 827, 829, 839, 853, 857, + 859, 863, 877, 881, 883, 887, 907, 911, 919, 929, 937, 941, 947, + 953, 967, 971, 977, 983, 991, 997] + ) + self.assertEqual(rv4, []) + + +class TestSieveOfEratosthenes(unittest.TestCase): + + def test_eratosthenes(self): + rv1 = eratosthenes(-10) + rv2 = eratosthenes(10) + rv3 = eratosthenes(100, 5) + rv4 = eratosthenes(100, -10) + self.assertEqual(rv1, []) + self.assertEqual(rv2, [2, 3, 5, 7]) + self.assertEqual( + rv3, + [5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, + 67, 71, 73, 79, 83, 89, 97] + ) + self.assertEqual( + rv4, + [2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, + 61, 67, 71, 73, 79, 83, 89, 97] + ) + + +class TestStdNormPDF(unittest.TestCase): + + def test_pdf(self): + # Calculate standard normal pdf for x=1 + a = pdf(1) + self.assertAlmostEqual(a, 0.24197072451914337) + + # Calculate standard normal pdf for x=(-1) + a = pdf(-1) + self.assertAlmostEqual(a, 0.24197072451914337) + + # Calculate standard normal pdf for x=13, mean=10, std_dev=1 + a = pdf(x=13, mean=10, std_dev=1) + self.assertAlmostEqual(a, 0.004431848411938008) diff --git a/algorithms/tests/test_random.py b/tests/test_random.py similarity index 61% rename from algorithms/tests/test_random.py rename to tests/test_random.py index 8cb4198..0dbf6b1 100644 --- a/algorithms/tests/test_random.py +++ b/tests/test_random.py @@ -1,5 +1,6 @@ import unittest -from ..random import mersenne_twister + +from algorithms.random import mersenne_twister class TestMersenneTwister(unittest.TestCase): @@ -7,42 +8,48 @@ class TestMersenneTwister(unittest.TestCase): Tests Mersenne Twister values for several seeds comparing against expected values from C++ STL's Mersenne Twister implementation """ - + def test_mersenne_twister(self): mt = mersenne_twister.MersenneTwister() - #Test seed 1 + # Test seed 1 mt.seed(1) - self.expected = [1791095845, 4282876139, 3093770124, - 4005303368, 491263, 550290313, 1298508491, - 4290846341, 630311759, 1013994432] + self.expected = [ + 1791095845, 4282876139, 3093770124, + 4005303368, 491263, 550290313, 1298508491, + 4290846341, 630311759, 1013994432 + ] self.results = [] for i in range(10): self.results.append(mt.randint()) self.assertEqual(self.expected, self.results) - #Test seed 42 + # Test seed 42 mt.seed(42) - self.expected = [1608637542, 3421126067, 4083286876, - 787846414, 3143890026, 3348747335, - 2571218620, 2563451924, 670094950, 1914837113] + self.expected = [ + 1608637542, 3421126067, 4083286876, + 787846414, 3143890026, 3348747335, + 2571218620, 2563451924, 670094950, 1914837113 + ] self.results = [] for i in range(10): self.results.append(mt.randint()) self.assertEqual(self.expected, self.results) - #Test seed 2147483647 + # Test seed 2147483647 mt.seed(2147483647) - self.expected = [1689602031, 3831148394, 2820341149, - 2744746572, 370616153, 3004629480, - 4141996784, 3942456616, 2667712047, 1179284407] + self.expected = [ + 1689602031, 3831148394, 2820341149, + 2744746572, 370616153, 3004629480, + 4141996784, 3942456616, 2667712047, 1179284407 + ] self.results = [] for i in range(10): self.results.append(mt.randint()) self.assertEqual(self.expected, self.results) - #Test seed -1 - #Hex is used to force 32-bit -1 + # Test seed -1 + # Hex is used to force 32-bit -1 mt.seed(0xffffffff) self.expected = [419326371, 479346978, 3918654476, 2416749639, 3388880820, 2260532800, diff --git a/tests/test_searching.py b/tests/test_searching.py new file mode 100644 index 0000000..a3ee914 --- /dev/null +++ b/tests/test_searching.py @@ -0,0 +1,207 @@ +""" Unit Tests for searching """ +import unittest +import math + +from algorithms.searching import ( + binary_search, + bmh_search, + breadth_first_search, + depth_first_search, + kmp_search, + rabinkarp_search, + ternary_search +) + + +class TestBinarySearch(unittest.TestCase): + """ + Tests Binary Search on a small range from 0-9 + """ + + def test_binarysearch(self): + self.seq = range(10) + rv1 = binary_search.search(self.seq, 0) + rv2 = binary_search.search(self.seq, 9) + rv3 = binary_search.search(self.seq, -1) + rv4 = binary_search.search(self.seq, 10) + rv5 = binary_search.search(self.seq, 4) + self.assertIs(rv1, 0) + self.assertIs(rv2, 9) + self.assertFalse(rv3) + self.assertFalse(rv4) + self.assertIs(rv5, 4) + self.seq = range(9) + rv1 = binary_search.search(self.seq, 0) + rv2 = binary_search.search(self.seq, 8) + rv3 = binary_search.search(self.seq, -1) + rv4 = binary_search.search(self.seq, 10) + rv5 = binary_search.search(self.seq, 4) + self.assertIs(rv1, 0) + self.assertIs(rv2, 8) + self.assertFalse(rv3) + self.assertFalse(rv4) + self.assertIs(rv5, 4) + + +class TestBMHSearch(unittest.TestCase): + """ + Tests BMH search on string "ABCDE FG ABCDEABCDEF" + """ + + def test_bmhsearch(self): + self.string = "ABCDE FG ABCDEABCDEF" + rv1 = bmh_search.search(self.string, "ABCDEA") + rv2 = bmh_search.search(self.string, "ABCDER") + self.assertIs(rv1[0], 9) + self.assertFalse(rv2) + + +class TestBreadthFirstSearch(unittest.TestCase): + """ + Tests DFS on a graph represented by a adjacency list + """ + def test_bfs(self): + self.graph = { + 'A': {'B', 'C'}, + 'B': {'A', 'D', 'E'}, + 'C': {'A', 'F'}, + 'D': {'B'}, + 'E': {'B', 'F'}, + 'F': {'C', 'E'} + } + rv1 = breadth_first_search.bfs(self.graph, 'A') + self.assertEqual(rv1, {'C', 'A', 'B', 'D', 'F', 'E'}) + self.graph = { + 'A': {'B', 'C', 'E'}, + 'B': {'A', 'D', 'F'}, + 'C': {'A', 'G'}, + 'D': {'B'}, + 'F': {'B'}, + 'E': {'A'}, + 'G': {'C'} + } + rv1 = breadth_first_search.bfs(self.graph, "A") + rv2 = breadth_first_search.bfs(self.graph, "G") + rv1e = breadth_first_search.bfs(self.graph, "Z") + self.assertEqual(rv1, {'A', 'B', 'D', 'F', 'C', 'G', 'E'}) + self.assertEqual(rv2, {'G', 'C', 'A', 'B', 'D', 'F', 'E'}) + self.assertEqual(rv1e, None) + + +class TestDepthFirstSearch(unittest.TestCase): + """ + Tests DFS on a graph represented by a adjacency list + """ + + def test_dfs(self): + self.graph = { + 'A': ['B', 'C', 'E'], + 'B': ['A', 'D', 'F'], + 'C': ['A', 'G'], + 'D': ['B'], + 'F': ['B'], + 'E': ['A'], + 'G': ['C'] + } + rv1 = depth_first_search.dfs(self.graph, "A") + rv2 = depth_first_search.dfs(self.graph, "G") + rv1e = depth_first_search.dfs(self.graph, "Z") + self.assertEqual(rv1, ['A', 'B', 'D', 'F', 'C', 'G', 'E']) + self.assertEqual(rv2, ['G', 'C', 'A', 'B', 'D', 'F', 'E']) + self.assertEqual(rv1e, None) + self.graph = { + 1: [2, 3, 4], + 2: [1, 6, 10], + 3: [1, 5, 10], + 4: [1, 10, 11], + 5: [3, 10], + 6: [2, 7, 8, 9], + 7: [6, 8], + 8: [6, 7], + 9: [6, 10], + 10: [3, 5, 9, 12], + 11: [4], + 12: [10] + } + rv3 = depth_first_search.dfs(self.graph, 1) + rv4 = depth_first_search.dfs(self.graph, 5) + rv5 = depth_first_search.dfs(self.graph, 6) + rv2e = depth_first_search.dfs(self.graph, 99) + self.assertEqual(rv3, [1, 2, 6, 7, 8, 9, 10, 3, 5, 12, 4, 11]) + self.assertEqual(rv4, [5, 3, 1, 2, 6, 7, 8, 9, 10, 12, 4, 11]) + self.assertEqual(rv5, [6, 2, 1, 3, 5, 10, 9, 12, 4, 11, 7, 8]) + self.assertEqual(rv2e, None) + self.graph = { + 1: [2, 3, 4, 5, 6], + 2: [1, 4, 7, 8, 9], + 3: [1, 10], + 4: [1, 2, 11, 12], + 5: [1, 13, 14, 15], + 6: [1, 15], + 7: [2], + 8: [2], + 9: [2, 10], + 10: [3, 9], + 11: [4], + 12: [4], + 13: [5], + 14: [5], + 15: [5, 6]} + rv6 = depth_first_search.dfs(self.graph, 1) + rv7 = depth_first_search.dfs(self.graph, 10) + rv8 = depth_first_search.dfs(self.graph, 5) + rv3e = depth_first_search.dfs(self.graph, -1) + self.assertEqual( + rv6, + [1, 2, 4, 11, 12, 7, 8, 9, 10, 3, 5, 13, 14, 15, 6] + ) + self.assertEqual( + rv7, + [10, 3, 1, 2, 4, 11, 12, 7, 8, 9, 5, 13, 14, 15, 6] + ) + self.assertEqual( + rv8, + [5, 1, 2, 4, 11, 12, 7, 8, 9, 10, 3, 6, 15, 13, 14] + ) + self.assertEqual(rv3e, None) + + +class TestKMPSearch(unittest.TestCase): + """ + Tests KMP search on string "ABCDE FG ABCDEABCDEF" + """ + + def test_kmpsearch(self): + self.string = "ABCDE FG ABCDEABCDEF" + rv1 = kmp_search.search(self.string, "ABCDEA") + rv2 = kmp_search.search(self.string, "ABCDER") + self.assertIs(rv1[0], 9) + self.assertFalse(rv2) + + +class TestRabinKarpSearch(unittest.TestCase): + """ + Tests Rabin-Karp search on string "ABCDEFGHIJKLMNOP" + """ + + def test_rabinkarpsearch(self): + self.string = "ABCDEFGHIJKLMNOP" + rv1 = rabinkarp_search.search(self.string, "MNOP") + rv2 = rabinkarp_search.search(self.string, "BCA") + self.assertIs(rv1[0], 12) + self.assertFalse(rv2) + + +class TestTernarySearch(unittest.TestCase): + """ + Tests teranry search algorithm on unimodal functions + """ + + def test_terarysearch(self): + self.function1 = lambda x: -(x - 2) ** 2 + self.function2 = lambda x: math.cos(x) + self.eps = 1e-6 + rv1 = ternary_search.search(self.function1, -2.0, 2.0, self.eps) + rv2 = ternary_search.search(self.function2, -2.0, 2.0, self.eps) + self.assertAlmostEqual(rv1, 2.0, 6) + self.assertAlmostEqual(rv2, 0.0, 6) diff --git a/algorithms/tests/test_shuffling.py b/tests/test_shuffling.py similarity index 78% rename from algorithms/tests/test_shuffling.py rename to tests/test_shuffling.py index 413c44b..72acbc5 100644 --- a/algorithms/tests/test_shuffling.py +++ b/tests/test_shuffling.py @@ -1,5 +1,6 @@ import unittest -from ..shuffling import knuth + +from algorithms.shuffling import knuth class ShufflingAlgorithmTestCase(unittest.TestCase): @@ -16,11 +17,11 @@ class TestKnuthShuffle(ShufflingAlgorithmTestCase): Tests Knuth shuffle on a small range from 0-9 """ def test_knuthshuffle(self): - self.shuffle = knuth.shuffle(range(10)) + self.shuffle = knuth.shuffle(list(range(10))) self.not_shuffled = 0 for i in self.sorted: if i == self.shuffle[i]: - self.not_shuffled = self.not_shuffled + 1 + self.not_shuffled += 1 self.assertGreater(5, self.not_shuffled) diff --git a/algorithms/tests/test_sorting.py b/tests/test_sorting.py similarity index 63% rename from algorithms/tests/test_sorting.py rename to tests/test_sorting.py index 23f3665..4bbc8b0 100644 --- a/algorithms/tests/test_sorting.py +++ b/tests/test_sorting.py @@ -1,8 +1,21 @@ import random import unittest -from ..sorting import bubble_sort, selection_sort, insertion_sort, \ - merge_sort, quick_sort, heap_sort, shell_sort, comb_sort, cocktail_sort, \ - quick_sort_in_place + +from algorithms.sorting import ( + bogo_sort, + bubble_sort, + cocktail_sort, + comb_sort, + gnome_sort, + heap_sort, + insertion_sort, + merge_sort, + quick_sort, + quick_sort_in_place, + selection_sort, + shell_sort, + strand_sort, +) class SortingAlgorithmTestCase(unittest.TestCase): @@ -11,9 +24,19 @@ class SortingAlgorithmTestCase(unittest.TestCase): """ def setUp(self): - self.input = range(10) + self.input = list(range(10)) random.shuffle(self.input) - self.correct = range(10) + self.correct = list(range(10)) + + +class TestBogoSort(SortingAlgorithmTestCase): + """ + Tests Bogo sort on a small range from 0-9 + """ + + def test_bogosort(self): + self.output = bogo_sort.sort(self.input) + self.assertEqual(self.correct, self.input) class TestBubbleSort(SortingAlgorithmTestCase): @@ -26,13 +49,43 @@ def test_bubblesort(self): self.assertEqual(self.correct, self.output) -class TestSelectionSort(SortingAlgorithmTestCase): +class TestCocktailSort(SortingAlgorithmTestCase): """ - Tests Selection sort on a small range from 0-9 + Tests Cocktail sort on a small range from 0-9 """ - def test_selectionsort(self): - self.output = selection_sort.sort(self.input) + def test_cocktailsort(self): + self.output = cocktail_sort.sort(self.input) + self.assertEqual(self.correct, self.output) + + +class TestCombSort(SortingAlgorithmTestCase): + """ + Tests Comb sort on a small range from 0-9 + """ + + def test_combsort(self): + self.output = comb_sort.sort(self.input) + self.assertEqual(self.correct, self.output) + + +class TestGnomeSort(SortingAlgorithmTestCase): + """ + Tests Gnome sort on a small range from 0-9 + """ + + def test_gnomesort(self): + self.output = gnome_sort.sort(self.input) + self.assertEqual(self.correct, self.output) + + +class TestHeapSort(SortingAlgorithmTestCase): + """ + Test Heap sort on a small range from 0-9 + """ + + def test_heapsort(self): + self.output = heap_sort.sort(self.input) self.assertEqual(self.correct, self.output) @@ -41,7 +94,7 @@ class TestInsertionSort(SortingAlgorithmTestCase): Tests Insertion sort on a small range from 0-9 """ - def test_selectionsort(self): + def test_insertionsort(self): self.output = insertion_sort.sort(self.input) self.assertEqual(self.correct, self.output) @@ -57,8 +110,8 @@ def test_mergesort(self): self.assertEqual(self.correct, self.output) def test_merge(self): - self.seq1 = range(0, 5) - self.seq2 = range(5, 10) + self.seq1 = list(range(0, 5)) + self.seq2 = list(range(5, 10)) self.seq = merge_sort.merge(self.seq1, self.seq2) self.assertIs(self.seq[0], 0) self.assertIs(self.seq[-1], 9) @@ -80,23 +133,29 @@ class TestQuickSortInPlace(SortingAlgorithmTestCase): also tests partition function included in quick sort """ def test_quicksort_in_place(self): - self.output = quick_sort_in_place.sort(self.input, 0, - len(self.input)-1) + self.output = quick_sort_in_place.sort( + self.input, 0, + len(self.input)-1 + ) self.assertEqual(self.correct, self.output) def test_partition(self): - self.seq = range(10) - self.assertIs(quick_sort_in_place.partition(self.seq, 0, - len(self.seq)-1, 5), 5) + self.seq = list(range(10)) + self.assertIs( + quick_sort_in_place.partition( + self.seq, 0, + len(self.seq)-1, 5), + 5 + ) -class TestHeapSort(SortingAlgorithmTestCase): +class TestSelectionort(SortingAlgorithmTestCase): """ - Test Heap sort on a small range from 0-9 + Test Selection sort on a small range from 0-9 """ - def test_heapsort(self): - self.output = heap_sort.sort(self.input) + def test_selectionsort(self): + self.output = selection_sort.sort(self.input) self.assertEqual(self.correct, self.output) @@ -110,21 +169,11 @@ def test_shellsort(self): self.assertEqual(self.correct, self.output) -class TestCombSort(SortingAlgorithmTestCase): - """ - Test Comb sort on a small range from 0-9 - """ - - def test_combsort(self): - self.output = comb_sort.sort(self.input) - self.assertEqual(self.correct, self.output) - - -class TestCocktailSort(SortingAlgorithmTestCase): +class TestStrandSort(SortingAlgorithmTestCase): """ - Tests Cocktail sort on a small range from 0-9 + Tests Strand sort on a small range from 0-9 """ - def test_cocktailsort(self): - self.output = cocktail_sort.sort(self.input) + def test_strandsort(self): + self.output = strand_sort.sort(self.input) self.assertEqual(self.correct, self.output) diff --git a/tox.ini b/tox.ini new file mode 100644 index 0000000..3a253ba --- /dev/null +++ b/tox.ini @@ -0,0 +1,8 @@ +[tox] +envlist = py27, py32, py33, py34, py35 + +[testenv] +commands = ./run_tests.py --coverage + +deps = + -rrequirements/requirements-testing.txt