diff --git a/CONTIRBUTORS.md b/CONTIRBUTORS.md index ff19646..5f53af9 100644 --- a/CONTIRBUTORS.md +++ b/CONTIRBUTORS.md @@ -17,3 +17,4 @@ - Sharad '[sharadbhat](https://github.com/sharadbhat)' Bhat - Alexey '[aesee](https://github.com/aesee)' Sarapulov - Anthony '[MrDupin](https://github.com/MrDupin)' Marakis + - Ashey '[asheywalke](https://github.com/ashaywalke)' Walke diff --git a/README.rst b/README.rst index 43a41db..1827a86 100644 --- a/README.rst +++ b/README.rst @@ -2,6 +2,15 @@ Pygorithm ========= + +.. image:: https://img.shields.io/packagist/l/doctrine/orm.svg + :target: https://github.com/OmkarPathak/pygorithm/blob/master/LICENSE + :alt: Packagist + +.. image:: http://pepy.tech/badge/pygorithm + :target: http://pepy.tech/project/pygorithm + :alt: Downloads + .. image:: https://readthedocs.org/projects/pygorithm/badge/?version=latest :target: http://pygorithm.readthedocs.io/en/latest/?badge=latest :alt: Documentation Status @@ -9,10 +18,22 @@ Pygorithm .. image:: https://img.shields.io/badge/Python-3.6-brightgreen.svg :target: https://github.com/OmkarPathak/pygorithm :alt: Python 3.6 + +.. image:: https://img.shields.io/badge/Say%20Thanks-%F0%9F%A6%89-1EAEDB.svg + :target: https://saythanks.io/to/omkarpathak27@gmail.com + :alt: Say Thanks! + +.. image:: https://img.shields.io/github/contributors/omkarpathak/pygorithm.svg + :target: https://github.com/OmkarPathak/pygorithm/graphs/contributors + :alt: Contributors | A Python module to learn all the major algorithms on the go! | Purely for educational purposes + +.. image:: https://images.gitads.io/pygorithm + :target: https://tracking.gitads.io/?campaign=gitads&repo=pygorithm&redirect=gitads.io + Features ~~~~~~~~ diff --git a/pygorithm/__init__.py b/pygorithm/__init__.py index 8117acc..05bf36e 100644 --- a/pygorithm/__init__.py +++ b/pygorithm/__init__.py @@ -30,6 +30,7 @@ Sharad 'sharadbhat' Bhat Alexey 'aesee' Sarapulov Anthony 'MrDupin' Marakis +Ashey 'asheywalke' Walke """ @@ -57,7 +58,8 @@ "Emil 'Skeen' Madsen", "Ian 'IanDoarn' Doarn", "Timothy 'Tjstretchalot' Moore", - "Sharad 'sharadbhat' Bhat" + "Sharad 'sharadbhat' Bhat", + "Ashey 'asheywalke' Walke" ] __all__ = [ diff --git a/pygorithm/data_structures/graph.py b/pygorithm/data_structures/graph.py index e477c66..2b6cbf0 100644 --- a/pygorithm/data_structures/graph.py +++ b/pygorithm/data_structures/graph.py @@ -6,7 +6,6 @@ import inspect import math - class Graph(object): """Graph object Creates the graph @@ -37,16 +36,23 @@ def get_code(self): """ return inspect.getsource(Graph) - class WeightedGraph(object): """WeightedGraph object A graph with a numerical value (weight) on edges """ def __init__(self): - self.edges_weighted = [] self.vertexes = set() - self.forest = None + self.graph = {} + self._forest = None + + def get_weight(self, u, v): + """ + Returns the weight of an edge between vertexes u and v. + If there isnt one: return None. + """ + return self.graph.get((u,v), self.graph.get((v,u), None)) + def add_edge(self, u, v, weight): """ @@ -54,39 +60,41 @@ def add_edge(self, u, v, weight): :param v: to vertex - type : integer :param weight: weight of the edge - type : numeric """ - edge = ((u, v), weight) - self.edges_weighted.append(edge) - self.vertexes.update((u, v)) + if self.get_weight(u, v) != None: + print("Such edge already exists!") + else: + self.vertexes.update((u, v)) + self.graph[(u,v)] = weight def print_graph(self): """ Print the graph :return: None """ - for (u, v), weight in self.edges_weighted: - print("%d -> %d weight: %d" % (u, v, weight)) + for (u, v) in self.graph: + print("%d -> %d weight: %d" % (u, v, self.graph[(u, v)])) - def __set_of(self, vertex): + def _set_of(self, vertex): """ Helper method :param vertex: :return: """ - for tree in self.forest: + for tree in self._forest: if vertex in tree: return tree return None - def __union(self, u_set, v_set): + def _union(self, u_set, v_set): """ Helper method :param u_set: :param v_set: :return: """ - self.forest.remove(u_set) - self.forest.remove(v_set) - self.forest.append(v_set + u_set) + self._forest.remove(u_set) + self._forest.remove(v_set) + self._forest.append(v_set + u_set) def kruskal_mst(self): """ @@ -96,14 +104,14 @@ def kruskal_mst(self): Author: Michele De Vita """ # sort by weight - self.edges_weighted.sort(key=lambda pair: pair[1]) + self.graph = {k: self.graph[k] for k in sorted(self.graph, key=self.graph.get, reverse=False)} edges_explored = [] - self.forest = [[v] for v in self.vertexes] - for (u, v), weight in self.edges_weighted: - u_set, v_set = self.__set_of(u), self.__set_of(v) + self._forest = [[v] for v in self.vertexes] + for (u, v) in self.graph: + u_set, v_set = self._set_of(u), self._set_of(v) if u_set != v_set: - self.__union(u_set, v_set) - edges_explored.append(((u, v), weight)) + self._union(u_set, v_set) + edges_explored.append(((u, v), self.graph[u, v])) return edges_explored # TODO: Is this necessary? diff --git a/pygorithm/data_structures/linked_list.py b/pygorithm/data_structures/linked_list.py index 0107b62..2df8cce 100644 --- a/pygorithm/data_structures/linked_list.py +++ b/pygorithm/data_structures/linked_list.py @@ -50,7 +50,7 @@ def _search(self, node, data): return False if node.data == data: return node - return self._search(node.get_next(), data) + return self._search(node.next, data) def get_data(self): """ diff --git a/pygorithm/data_structures/tree.py b/pygorithm/data_structures/tree.py index 2967bc7..1a9c96b 100644 --- a/pygorithm/data_structures/tree.py +++ b/pygorithm/data_structures/tree.py @@ -72,6 +72,16 @@ def __init__(self): self._pre_order = [] self._post_order = [] + def insert(self, data): + """ + insert data to root or create a root node + """ + if self.root: + self.root.set_data(data) + else: + self.root = Node() + self.root.set_data(data) + def inorder(self, root): """ in this we traverse first to the leftmost node, @@ -117,6 +127,24 @@ def postorder(self, root): self._post_order.append(root.get_data()) return self._post_order + def number_of_nodes(self, root): + """ + counting number of nodes + """ + # need testing + left_number = 0; + right_number = 0; + + #number of nodes left side + if root.get_left(): + left_number = self.number_of_nodes(root.get_left()) + + #numbeof nodes right side + if root.get_right(): + right_number = self.number_of_nodes(root.get_right()) + + return left_number + right_number + 1 + @staticmethod def get_code(): """ @@ -359,6 +387,24 @@ def postorder(self): if self.root is not None: return self.root.postorder(self.root) + def number_of_nodes(self, root): + """ + counting number of nodes + """ + # need testing + left_number = 0; + right_number = 0; + + #number of nodes left side + if root.get_left(): + left_number = self.number_of_nodes(root.get_left()) + + #numbeof nodes right side + if root.get_right(): + right_number = self.number_of_nodes(root.get_right()) + + return left_number + right_number + 1 + @staticmethod def get_code(): """ diff --git a/pygorithm/data_structures/trie.py b/pygorithm/data_structures/trie.py index 5637cc5..c9b2dee 100644 --- a/pygorithm/data_structures/trie.py +++ b/pygorithm/data_structures/trie.py @@ -1,7 +1,6 @@ -''' -Node class to create a node -for trie -''' +""" +Author: MrDupin +""" class Node: def __init__(self, v, p=None, w=False): diff --git a/pygorithm/dynamic_programming/__init__.py b/pygorithm/dynamic_programming/__init__.py index 4a7d594..b3dd710 100644 --- a/pygorithm/dynamic_programming/__init__.py +++ b/pygorithm/dynamic_programming/__init__.py @@ -3,8 +3,10 @@ """ from . import binary_knapsack from . import lis +from . import min_cost_path __all__ = [ 'binary_knapsack', - 'lis' + 'lis', + 'min_cost_path' ] diff --git a/pygorithm/dynamic_programming/fractional_knapsack.py b/pygorithm/dynamic_programming/fractional_knapsack.py new file mode 100644 index 0000000..83e88f5 --- /dev/null +++ b/pygorithm/dynamic_programming/fractional_knapsack.py @@ -0,0 +1,67 @@ +# https://en.wikipedia.org/wiki/Continuous_knapsack_problem +# https://www.guru99.com/fractional-knapsack-problem-greedy.html +# https://medium.com/walkinthecode/greedy-algorithm-fractional-knapsack-problem-9aba1daecc93 + +""" +Author : Anubhav Sharma +This is a pure Python implementation of Dynamic Programming solution to the Fractional Knapsack of a given items and weights. +The problem is : +Given N items and weights, to find the max weight of item to put in fractional knapsack in that given knapsack and +return it. +Example: Weight of knapsack to carry, Items and there weights as input will return + Items in fraction to put in the knapsack as per weight as output +""" + +def fractional_knapsack(value: list[int], weight: list[int], capacity: int) -> tuple[int, list[int]]: + """ + >>> value = [1, 3, 5, 7, 9] + >>> weight = [0.9, 0.7, 0.5, 0.3, 0.1] + >>> fractional_knapsack(value, weight, 5) + (25, [1, 1, 1, 1, 1]) + >>> fractional_knapsack(value, weight, 15) + (25, [1, 1, 1, 1, 1]) + >>> fractional_knapsack(value, weight, 25) + (25, [1, 1, 1, 1, 1]) + >>> fractional_knapsack(value, weight, 26) + (25, [1, 1, 1, 1, 1]) + >>> fractional_knapsack(value, weight, -1) + (-90.0, [0, 0, 0, 0, -10.0]) + >>> fractional_knapsack([1, 3, 5, 7], weight, 30) + (16, [1, 1, 1, 1]) + >>> fractional_knapsack(value, [0.9, 0.7, 0.5, 0.3, 0.1], 30) + (25, [1, 1, 1, 1, 1]) + >>> fractional_knapsack([], [], 30) + (0, []) + """ + index = list(range(len(value))) + ratio = [v / w for v, w in zip(value, weight)] + index.sort(key=lambda i: ratio[i], reverse=True) + + max_value = 0 + fractions = [0] * len(value) + for i in index: + if weight[i] <= capacity: + fractions[i] = 1 + max_value += value[i] + capacity -= weight[i] + else: + fractions[i] = capacity / weight[i] + max_value += value[i] * capacity / weight[i] + break + + return max_value, fractions + + +if __name__ == "__main__": + n = int(input("Enter number of items: ")) + value = input(f"Enter the values of the {n} item(s) in order: ").split() + value = [int(v) for v in value] + weight = input(f"Enter the positive weights of the {n} item(s) in order: ".split()) + weight = [int(w) for w in weight] + capacity = int(input("Enter maximum weight: ")) + + max_value, fractions = fractional_knapsack(value, weight, capacity) + print("The maximum value of items that can be carried:", max_value) + print("The fractions in which the items should be taken:", fractions) + + \ No newline at end of file diff --git a/pygorithm/dynamic_programming/lcs.py b/pygorithm/dynamic_programming/lcs.py new file mode 100644 index 0000000..e30e6ee --- /dev/null +++ b/pygorithm/dynamic_programming/lcs.py @@ -0,0 +1,45 @@ +""" +A subsequence is a sequence that can be derived from another +sequence by deleting some or no elements without changing the +order of the remaining elements. + +For example, 'abd' is a subsequence of 'abcd' whereas 'adc' is not + +Given 2 strings containing lowercase english alphabets, find the length +of the Longest Common Subsequence (L.C.S.). + +Example: + Input: 'abcdgh' + 'aedfhr' + Output: 3 + + Explanation: The longest subsequence common to both the string is "adh" + +Time Complexity : O(M*N) +Space Complexity : O(M*N), where M and N are the lengths of the 1st and 2nd string +respectively. + +""" + + +def longest_common_subsequence(s1, s2): + """ + :param s1: string + :param s2: string + :return: int + """ + m, n = len(s1), len(s2) + + dp = [[0] * (n + 1)] * (m + 1) + """ + dp[i][j] : contains length of LCS of s1[0..i-1] and s2[0..j-1] + """ + + for i in range(1, m + 1): + for j in range(1, n + 1): + if s1[i - 1] == s2[j - 1]: + dp[i][j] = dp[i - 1][j - 1] + 1 + else: + dp[i][j] = max(dp[i - 1][j], dp[i][j - 1]) + + return dp[m][n] diff --git a/pygorithm/dynamic_programming/lis.py b/pygorithm/dynamic_programming/lis.py index ae8ff5a..3a111a5 100644 --- a/pygorithm/dynamic_programming/lis.py +++ b/pygorithm/dynamic_programming/lis.py @@ -2,7 +2,7 @@ Author: Omkar Pathak Created At: 25th August 2017 """ - +import inspect def longest_increasing_subsequence(_list): """ diff --git a/pygorithm/dynamic_programming/longest_palindrome_substring.py b/pygorithm/dynamic_programming/longest_palindrome_substring.py new file mode 100644 index 0000000..4329364 --- /dev/null +++ b/pygorithm/dynamic_programming/longest_palindrome_substring.py @@ -0,0 +1,75 @@ +""" +Author : Anubhav Sharma +This is a pure Python implementation of Dynamic Programming solution to the longest +palindrome substring of a given string. +I use Manacher Algorithm which is amazing algorithm and find solution in linear time complexity. +The problem is : +Given a string, to find the longest palindrome sub-string in that given string and +return it. +Example: aabbabbaababa as input will return + aabbabbaa as output +""" +def manacher_algo_lps(s,n): + """ + PARAMETER + -------------- + s = string + n = string_len (int) + manacher Algorithm is the fastest technique to find the longest palindrome substring in any given string. + RETURN + --------------- + Longest Palindrome String(String) + """ + # variables to use + p = [0] * n + c = 0 + r = 0 + maxlen = 0 + + # Main Algorithm + for i in range(n): + mirror = 2*c-i # Finding the Mirror(i.e. Pivort to break) of the string + if i < r: + p[i] = (r - i) if (r - i) < p[mirror] else p[mirror] + a = i + (1 + p[i]) + b = i - (1 + p[i]) + + # Attempt to expand palindrome centered at currentRightPosition i + # Here for odd positions, we compare characters and + # if match then increment LPS Length by ONE + # If even position, we just increment LPS by ONE without + # any character comparison + while a=0 and s[a] == s[b]: + p[i] += 1 + a += 1 + b -= 1 + if (i + p[i]) > r: + c = i + r = i + p[i] + if p[i] > maxlen: # Track maxLPSLength + maxlen = p[i] + i = p.index(maxlen) + return s[i-maxlen:maxlen+i][1::2] + +def longest_palindrome(s: str) -> str: + s = '#'.join(s) + s = '#'+s+'#' + + # Calling Manacher Algorithm + return manacher_algo_lps(s,len(s)) + +def main(): + + # Input to enter + input_string = "abbbacdcaacdca" + + # Calling the longest palindrome algorithm + s = longest_palindrome(input_string) + print("LPS Using Manacher Algorithm {}".format(s)) + +# Calling Main Function +if __name__ == "__main__": + + main() + + \ No newline at end of file diff --git a/pygorithm/dynamic_programming/min_cost_path.py b/pygorithm/dynamic_programming/min_cost_path.py new file mode 100644 index 0000000..ad01edf --- /dev/null +++ b/pygorithm/dynamic_programming/min_cost_path.py @@ -0,0 +1,51 @@ +""" +Author: MrDupin +Created At: 25th August 2017 +""" +import inspect + +#Path(i, j) = min(Path(i-1, j), Path(i, j-1) + Matrix(i, j) + + +def calculate_path(i, j, matrix, s): + if(s[i][j] > 0): + #We have already calculated solution for i,j; return it. + return s[i][j] + + m1 = calculate_path(i-1, j, matrix, s) + matrix[i][j] #Optimal solution for i-1, j (top) + m2 = calculate_path(i, j-1, matrix, s) + matrix[i][j] #Optimal solution for i, j-1 (left) + + #Store and return the optimal (minimum) solution + if(m1 < m2): + s[i][j] = m1 + return m1 + else: + s[i][j] = m2 + return m2 + + +def find_path(matrix): + l = len(matrix); + #Initialize solution array. + #A node of i, j in solution has an equivalent node of i, j in matrix + s = [[0 for i in range(l)] for j in range(l)]; + + #Initialize first node as its matrix equivalent + s[0][0] = matrix[0][0] + + #Initialize first column as the matrix equivalent + the above solution + for i in range(1, l): + s[i][0] = matrix[i][0] + s[i-1][0] + + #Initialize first row as the matrix equivalent + the left solution + for j in range(1, l): + s[0][j] = matrix[0][j] + s[0][j-1] + + return calculate_path(l-1, l-1, matrix, s) + + +def get_code(): + """ + returns the code for the min cost path function + """ + return inspect.getsource(calculate_path) diff --git a/pygorithm/fibonacci/memoization.py b/pygorithm/fibonacci/memoization.py index 6374aa4..8a522e9 100644 --- a/pygorithm/fibonacci/memoization.py +++ b/pygorithm/fibonacci/memoization.py @@ -2,7 +2,6 @@ Fibonacci implementation through cache. """ import inspect -# TODO: Fix shadowed parameter names def get_sequence(n): @@ -11,20 +10,20 @@ def get_sequence(n): """ cache = {0: 0, 1: 1} - def fib(n): + def fib(num): """ Return Fibonacci value by specified number as integer. """ - if n in cache: - return cache[n] - cache[n] = fib(n - 1) + fib(n - 2) - return cache[n] + if num in cache: + return cache[num] + cache[num] = fib(num - 1) + fib(num - 2) + return cache[num] - def sequence(n): + def sequence(num): """ Return sequence of Fibonacci values as list. """ - return [fib(value) for value in range(n + 1)] + return [fib(value) for value in range(num + 1)] return sequence(n) diff --git a/pygorithm/fibonacci/recursion.py b/pygorithm/fibonacci/recursion.py index bf14730..beeee4c 100644 --- a/pygorithm/fibonacci/recursion.py +++ b/pygorithm/fibonacci/recursion.py @@ -8,20 +8,20 @@ def get_sequence(n): """ Return Fibonacci sequence from zero to specified number as list. """ - def fib(n): + def fib(num): """ Return Fibonacci value by specified number as integer. """ - if n <= 1: - return n + if num <= 1: + return num - return fib(n - 1) + fib(n - 2) + return fib(num - 1) + fib(num - 2) - def sequence(n): + def sequence(num): """ Return sequence of Fibonacci values as list. """ - return [fib(value) for value in range(n + 1)] + return [fib(value) for value in range(num + 1)] return sequence(n) diff --git a/pygorithm/math/GCD.py b/pygorithm/math/GCD.py new file mode 100644 index 0000000..424f2a1 --- /dev/null +++ b/pygorithm/math/GCD.py @@ -0,0 +1,18 @@ +def find_gcd(x, y): + + while(y): + x, y = y, x % y + + return x + + +l = [2, 4, 6, 8, 16] + +num1 = l[0] +num2 = l[1] +gcd = find_gcd(num1, num2) + +for i in range(2, len(l)): + gcd = find_gcd(gcd, l[i]) + +print(gcd) diff --git a/pygorithm/math/sieve_of_eratosthenes.py b/pygorithm/math/sieve_of_eratosthenes.py index 4aa4d52..0b877ee 100644 --- a/pygorithm/math/sieve_of_eratosthenes.py +++ b/pygorithm/math/sieve_of_eratosthenes.py @@ -29,7 +29,7 @@ def sieve_of_eratosthenes(n): p = 2 while p * p <= n: - # if p is not marked as False, this it is a prime + # if p is not marked as False, it is a prime if primes[p]: # mark all the multiples of number as False for i in range(p * 2, n + 1, p): @@ -37,7 +37,7 @@ def sieve_of_eratosthenes(n): p += 1 # getting all primes - primes = [element for element in range(2, n) if primes[element]] + primes = [element for element in range(2, n + 1) if primes[element]] return primes diff --git a/pygorithm/sorting/__init__.py b/pygorithm/sorting/__init__.py index 0781eea..e91f6f0 100644 --- a/pygorithm/sorting/__init__.py +++ b/pygorithm/sorting/__init__.py @@ -8,6 +8,7 @@ from . import counting_sort from . import insertion_sort from . import merge_sort +from . import radix_sort from . import selection_sort from . import shell_sort @@ -19,6 +20,7 @@ 'insertion_sort', 'merge_sort', 'quick_sort', + 'radix_sort', 'selection_sort', 'shell_sort' ] diff --git a/pygorithm/sorting/brick_sort.py b/pygorithm/sorting/brick_sort.py new file mode 100644 index 0000000..333f05e --- /dev/null +++ b/pygorithm/sorting/brick_sort.py @@ -0,0 +1,25 @@ +def brick_sort(arr): + """Performs an odd-even in-place sort, which is a variation of a bubble + sort. + + https://www.geeksforgeeks.org/odd-even-sort-brick-sort/ + + :param arr: the array of values to sort + :return: the sorted array + """ + # Initially array is unsorted + is_sorted = False + while not is_sorted: + is_sorted = True + + for i in range(1, len(arr) - 1, 2): + if arr[i] > arr[i + 1]: + arr[i], arr[i + 1] = arr[i + 1], arr[i] + is_sorted = False + + for i in range(0, len(arr) - 1, 2): + if arr[i] > arr[i + 1]: + arr[i], arr[i + 1] = arr[i + 1], arr[i] + is_sorted = False + + return arr diff --git a/pygorithm/sorting/bucket_sort.py b/pygorithm/sorting/bucket_sort.py index 4f6eb31..3fb0d05 100644 --- a/pygorithm/sorting/bucket_sort.py +++ b/pygorithm/sorting/bucket_sort.py @@ -14,7 +14,7 @@ def sort(_list, bucket_size=5): """ bucket sort algorithm - + :param _list: list of values to sort :param bucket_size: Size of the bucket :return: sorted values @@ -22,9 +22,8 @@ def sort(_list, bucket_size=5): string = False if len(_list) == 0: - # print("You don\'t have any elements in array!") - raise ValueError("Array can not be empty.") - + return [] + elif all(isinstance(element, str) for element in _list): string = True _list = [ord(element) for element in _list] diff --git a/pygorithm/sorting/cocktail_sort.py b/pygorithm/sorting/cocktail_sort.py new file mode 100644 index 0000000..f9f6ecb --- /dev/null +++ b/pygorithm/sorting/cocktail_sort.py @@ -0,0 +1,60 @@ +''' +Created by: Pratik Narola (https://github.com/Pratiknarola) +last modified: 14-10-2019 +''' + + +def cocktail_sort(arr): + ''' + Cocktail Sort is a variation of Bubble sort. + The Bubble sort algorithm always traverses elements from left + and moves the largest element to its correct position in first iteration + and second largest in second iteration and so on. + Cocktail Sort traverses through a given array in both directions alternatively. + + This is an in-place sort. + + :param arr: the array to sort + :return: the sorted array, which is the same reference as arr + ''' + swapped = True + start = 0 + end = len(arr) - 1 + while swapped: + # reset the swapped flag on entering the loop, + # because it might be true from a previous + # iteration. + swapped = False + + # loop from left to right same as the bubble + # sort + for i in range(start, end): + if arr[i] > arr[i + 1]: + arr[i], arr[i + 1] = arr[i + 1], arr[i] + swapped = True + + # if nothing moved, then array is sorted. + if not swapped: + break + + # otherwise, reset the swapped flag so that it + # can be used in the next stage + swapped = False + + # move the end point back by one, because + # item at the end is in its rightful spot + end -= 1 + + # from right to left, doing the same + # comparison as in the previous stage + for i in range(end - 1, start - 1, -1): + if arr[i] > arr[i + 1]: + arr[i], arr[i + 1] = arr[i + 1], arr[i] + swapped = True + + # increase the starting point, because + # the last stage would have moved the next + # smallest number to its rightful spot. + start = start + 1 + + return arr diff --git a/pygorithm/sorting/gnome_sort.py b/pygorithm/sorting/gnome_sort.py new file mode 100644 index 0000000..ecdfee5 --- /dev/null +++ b/pygorithm/sorting/gnome_sort.py @@ -0,0 +1,36 @@ +''' +Created by: Pratik Narola (https://github.com/Pratiknarola) +last modified: 14-10-2019 +''' + + + + + +# A function to sort the given list using Gnome sort +def gnome_sort(arr): + ''' + Gnome Sort also called Stupid sort is based on the concept of a Garden Gnome sorting his flower pots. + A garden gnome sorts the flower pots by the following method- + + He looks at the flower pot next to him and the previous one; + if they are in the right order he steps one pot forward, otherwise he swaps them and steps one pot backwards. + If there is no previous pot (he is at the starting of the pot line), he steps forwards; + if there is no pot next to him (he is at the end of the pot line), he is done. + + This is an in-place sort. + + :param arr: the array of values to sort + :return: the sorted array, which is the same reference as arr + ''' + index = 0 + while index < len(arr): + if index == 0: + index = index + 1 + elif arr[index] >= arr[index - 1]: + index = index + 1 + else: + arr[index], arr[index - 1] = arr[index - 1], arr[index] + index = index - 1 + + return arr diff --git a/pygorithm/sorting/heap_sort.py b/pygorithm/sorting/heap_sort.py index 9b02024..ffc177f 100644 --- a/pygorithm/sorting/heap_sort.py +++ b/pygorithm/sorting/heap_sort.py @@ -13,11 +13,15 @@ def sort(_list): """ heap sort algorithm + Create the heap using heapify(). + This is an implementation of max-heap, so after bullding the heap, the max element is at the top (_list[0]). + We move it to the end of the list (_list[end]), which will later become the sorted list. + After moving this element to the end, we take the element in the end to the top and shift it down to its right location in the heap. + We proceed to do the same for all elements in the heap, such that in the end we're left with the sorted list. :param _list: list of values to sort :return: sorted values """ - # TODO: Add description of how this works! # create the heap heapify(_list) diff --git a/pygorithm/sorting/merge_sort.py b/pygorithm/sorting/merge_sort.py index ccd79d9..8ad181e 100644 --- a/pygorithm/sorting/merge_sort.py +++ b/pygorithm/sorting/merge_sort.py @@ -3,16 +3,16 @@ Created On: 31st July 2017 - Best = Average = Worst = O(n log(n)) - + """ import inspect def merge(a, b): """ - Function to merge + Function to merge two arrays / separated lists - + :param a: Array 1 :param b: Array 2 :return: merged arrays @@ -34,20 +34,40 @@ def merge(a, b): def sort(_list): """ - Function to sort an array - using merge sort algorithm - + Function to sort an array + using merge sort algorithm + :param _list: list of values to sort :return: sorted """ if len(_list) == 0 or len(_list) == 1: - return _list + return list(_list) else: middle = len(_list)//2 a = sort(_list[:middle]) b = sort(_list[middle:]) return merge(a, b) +from itertools import zip_longest +def sorti(_list, verbose=True): + """ + Function to sort an array + using merge sort algorithm, iteratively + + :param _list: list of values to sort + :return: sorted + """ + # breakdown every element into its own list + series = [[i] for i in _list] + while len(series) > 1: + if verbose: print(series) + # iterator to handle two at a time in the zip_longest below + isl = iter(series) + series = [ + merge(a, b) if b else a + for a, b in zip_longest(isl, isl) + ] + return series[0] # TODO: Are these necessary? def time_complexities(): @@ -59,11 +79,13 @@ def time_complexities(): return "Best Case: O(nlogn), Average Case: O(nlogn), Worst Case: O(nlogn)" -def get_code(): +def get_code(iter=False): """ - easily retrieve the source code + easily retrieve the source code of the sort function :return: source code """ + if iter: + return inspect.getsource(sorti) + "\n" return inspect.getsource(sort) + "\n" + inspect.getsource(merge) diff --git a/pygorithm/sorting/quick_sort.py b/pygorithm/sorting/quick_sort.py index f3b65df..53d62cf 100644 --- a/pygorithm/sorting/quick_sort.py +++ b/pygorithm/sorting/quick_sort.py @@ -16,7 +16,7 @@ def sort(_list): :return: sorted list """ if len(_list) <= 1: - return _list + return list(_list) pivot = _list[len(_list) // 2] left = [x for x in _list if x < pivot] middle = [x for x in _list if x == pivot] diff --git a/pygorithm/sorting/radix_sort.py b/pygorithm/sorting/radix_sort.py new file mode 100644 index 0000000..bc4fe4b --- /dev/null +++ b/pygorithm/sorting/radix_sort.py @@ -0,0 +1,38 @@ +""" +Author: Ian Doarn +Date: 31st Oct 2017 + +Reference: + https://stackoverflow.com/questions/35419229/python-radix-sort +""" + + +def sort(_list, base=10): + """ + Radix Sort + + :param _list: array to sort + :param base: base radix number + :return: sorted list + """ + # TODO: comment this + + result_list = [] + power = 0 + while _list: + bs = [[] for _ in range(base)] + for x in _list: + bs[x // base ** power % base].append(x) + _list = [] + for b in bs: + for x in b: + if x < base ** (power + 1): + result_list.append(x) + else: + _list.append(x) + power += 1 + return result_list + + +if __name__ == '__main__': + print(sort([170, 45, 75, 90, 802, 24, 2, 66])) diff --git a/pygorithm/sorting/tim_sort.py b/pygorithm/sorting/tim_sort.py new file mode 100644 index 0000000..95d0a3c --- /dev/null +++ b/pygorithm/sorting/tim_sort.py @@ -0,0 +1,108 @@ +def inplace_insertion_sort(arr, start_ind, end_ind): + """ + Performs an in-place insertion sort over a continuous slice of an + array. A natural way to avoid this would be to use numpy arrays, + where slicing does not copy. + + This is in-place and has no result. + + :param arr: the array to sort + :param start_ind: the index to begin sorting at + :param end_ind: the index to end sorting at. This index is excluded + from the sort (i.e., len(arr) is ok) + """ + for i in range(start_ind + 1, end_ind): + current_number = arr[i] + + for j in range(i - 1, start_ind - 1, -1): + if arr[j] > current_number: + arr[j], arr[j + 1] = arr[j + 1], arr[j] + else: + arr[j + 1] = current_number + break + + +# iterative Timsort function to sort the +# array[0...n-1] (similar to merge sort) +def tim_sort(arr, run=32): + """ + Tim sort algorithm. See https://en.wikipedia.org/wiki/Timsort. + This is performed in-place. + + :param arr: list of values to sort + :param run: the largest array that is sorted with an insertion sort. + :return: the sorted array + """ + + # Sort individual subarrays of size run + + for i in range(0, len(arr), run): + inplace_insertion_sort(arr, i, min(i + run, len(arr))) + + # start merging from size RUN (or 32). It will merge + # to form size 64, then 128, 256 and so on .... + size = run + while size < len(arr): + # pick starting point of left sub array. We + # are going to merge arr[left..left+size-1] + # and arr[left+size, left+2*size-1] + # After every merge, we increase left by 2*size + for left in range(0, len(arr), 2 * size): + # find ending point of left sub array + # mid+1 is starting point of right sub array + mid = left + size + right = min(left + (2 * size), len(arr)) + + # merge sub array arr[left.....mid] & + # arr[mid+1....right] + merge(arr, left, mid, right) + + size = 2 * size + return arr + +def merge(arr, left, mid, right): + """ + Merge of two sections of array, both of which are individually + sorted. The result is that the entire chunk is sorted. Note that right + edges are exclusive (like slicing). + + This modifies the passed array, but requires a complete copy of the array. + + .. code:: python + + merge([0, -1, 1, 3, 2, 4], 2, 4, 6) # [0, -1, 1, 2, 3, 4] + + :param arr: the array which should have a portion sorted in-place + :param left: the left-most index which is included in the merge + :param mid: the first index that belongs to the second section + :param right: the right-edge in the merge, which is not included in the sort. + """ + # original array is broken in two parts + # left and right array + left_arr = arr[left:mid] + right_arr = arr[mid:right] + + left_pos = 0 + right_pos = 0 + arr_ind = left + # after comparing, we merge those two array + # in larger sub array + while left_pos < len(left_arr) and right_pos < len(right_arr): + if left_arr[left_pos] <= right_arr[right_pos]: + arr[arr_ind] = left_arr[left_pos] + left_pos += 1 + else: + arr[arr_ind] = right_arr[right_pos] + right_pos += 1 + + arr_ind += 1 + + # copy remaining elements of left, if any + for i in range(left_pos, len(left_arr)): + arr[arr_ind] = left_arr[i] + arr_ind += 1 + + # copy remaining element of right, if any + for i in range(right_pos, len(right_arr)): + arr[arr_ind] = right_arr[i] + arr_ind += 1 diff --git a/tests/test_dynamic_programming.py b/tests/test_dynamic_programming.py index 6489fc2..e6c6327 100644 --- a/tests/test_dynamic_programming.py +++ b/tests/test_dynamic_programming.py @@ -3,7 +3,8 @@ from pygorithm.dynamic_programming import ( binary_knapsack, - lis + lis, + min_cost_path ) @@ -21,5 +22,14 @@ def test_lis(self): self.assertEqual(ans[0], 5) self.assertEqual(ans[1], [10, 22, 33, 50, 60]) +class TestMinCostPath(unittest.TestCase): + def test_min_cost_path(self): + matrix = [[5, 3, 10, 17, 1], + [4, 2, 9, 8, 5], + [11, 12, 3, 9, 6], + [1, 3, 4, 2, 10], + [7, 11, 13, 7, 3]] + self.assertEqual(min_cost_path.find_path(matrix), 38) + if __name__ == '__main__': unittest.main() diff --git a/tests/test_math.py b/tests/test_math.py index 983e5e2..66c764c 100644 --- a/tests/test_math.py +++ b/tests/test_math.py @@ -17,7 +17,7 @@ def test_lcm_using_gcd(self): class TestSieveOfEratosthenes(unittest.TestCase): def test_sieve_of_eratosthenes(self): - self.assertEqual(sieve_of_eratosthenes.sieve_of_eratosthenes(10), [2, 3, 5, 7]) + self.assertEqual(sieve_of_eratosthenes.sieve_of_eratosthenes(11), [2, 3, 5, 7, 11]) class TestFactorial(unittest.TestCase): def test_factorial(self): diff --git a/tests/test_sorting.py b/tests/test_sorting.py index 7f08153..c9c650c 100644 --- a/tests/test_sorting.py +++ b/tests/test_sorting.py @@ -1,109 +1,209 @@ -import unittest -import random - -from pygorithm.sorting import ( - bubble_sort, - insertion_sort, - selection_sort, - merge_sort, - quick_sort, - counting_sort, - bucket_sort, - shell_sort, - heap_sort) - - -class TestSortingAlgorithm(unittest.TestCase): - def setUp(self): - # to test numeric numbers - self.array = list(range(15)) - random.shuffle(self.array) - self.sorted_array = list(range(15)) - - # to test alphabets - string = 'pythonisawesome' - self.alphaArray = list(string) - random.shuffle(self.alphaArray) - self.sorted_alpha_array = sorted(string) - - -class TestBubbleSort(TestSortingAlgorithm): - def test_bubble_sort(self): - self.result = bubble_sort.sort(self.array) - self.assertEqual(self.result, self.sorted_array) - - self.alphaResult = bubble_sort.sort(self.alphaArray) - self.assertEqual(self.alphaResult, self.sorted_alpha_array) - - -class TestInsertionSort(TestSortingAlgorithm): - def test_insertion_sort(self): - self.result = insertion_sort.sort(self.array) - self.assertEqual(self.result, self.sorted_array) - - self.alphaResult = insertion_sort.sort(self.alphaArray) - self.assertEqual(self.alphaResult, self.sorted_alpha_array) - - -class TestSelectionSort(TestSortingAlgorithm): - def test_selection_sort(self): - self.result = selection_sort.sort(self.array) - self.assertEqual(self.result, self.sorted_array) - - self.alphaResult = selection_sort.sort(self.alphaArray) - self.assertEqual(self.alphaResult, self.sorted_alpha_array) - - -class TestMergeSort(TestSortingAlgorithm): - def test_merge_sort(self): - self.result = merge_sort.sort(self.array) - self.assertEqual(self.result, self.sorted_array) - - self.alphaResult = merge_sort.sort(self.alphaArray) - self.assertEqual(self.alphaResult, self.sorted_alpha_array) - - -class TestQuickSort(TestSortingAlgorithm): - def test_quick_sort(self): - self.result = quick_sort.sort(self.array) - self.assertEqual(self.result, self.sorted_array) - - self.alphaResult = quick_sort.sort(self.alphaArray) - self.assertEqual(self.alphaResult, self.sorted_alpha_array) - - -class TestCountingSort(TestSortingAlgorithm): - def test_counting_sort(self): - # counting sort is an integer based sort - self.result = counting_sort.sort(self.array) - self.assertEqual(self.result, self.sorted_array) - - -class TestBucketSort(TestSortingAlgorithm): - def test_bucket_sort(self): - self.result = bucket_sort.sort(self.array) - self.assertEqual(self.result, self.sorted_array) - - self.alphaResult = bucket_sort.sort(self.alphaArray) - self.assertEqual(self.alphaResult, self.sorted_alpha_array) - - -class TestShellSort(TestSortingAlgorithm): - def test_shell_sort(self): - self.result = shell_sort.sort(self.array) - self.assertEqual(self.result, self.sorted_array) - - self.alphaResult = shell_sort.sort(self.alphaArray) - self.assertEqual(self.alphaResult, self.sorted_alpha_array) - - -class TestHeapSort(TestSortingAlgorithm): - def test_heap_sort(self): - self.result = heap_sort.sort(self.array) - self.assertEqual(self.result, self.sorted_array) - - self.alphaResult = heap_sort.sort(self.alphaArray) - self.assertEqual(self.alphaResult, self.sorted_alpha_array) - -if __name__ == '__main__': - unittest.main() +import unittest +import random + +from pygorithm.sorting import ( + bubble_sort, + insertion_sort, + selection_sort, + merge_sort, + quick_sort, + counting_sort, + bucket_sort, + shell_sort, + heap_sort, + brick_sort, + tim_sort, + cocktail_sort, + gnome_sort +) + + +class TestSortingAlgorithm: + def test_test_setup(self): + self.assertIsNotNone(getattr(self, 'sort', None)) + self.assertIsNotNone(getattr(self, 'inplace', None)) + self.assertIsNotNone(getattr(self, 'alph_support', None)) + + def _check_sort_list(self, arr, expected): + cp_arr = list(arr) + sarr = self.sort(cp_arr) + + self.assertTrue( + isinstance(sarr, list), 'weird result type: ' + str(type(sarr))) + self.assertEqual(len(sarr), len(arr)) + self.assertEqual(sarr, expected) + if self.inplace: + self.assertTrue(cp_arr is sarr, 'was not inplace') + else: + self.assertTrue(cp_arr is not sarr, 'was inplace') + self.assertEqual(cp_arr, arr, 'inplace modified list') + + def _check_sort_alph(self, inp, expected): + if not self.alph_support: + return + + self._check_sort_list(list(inp), list(expected)) + + def test_sort_empty(self): + self._check_sort_list([], []) + + def test_sort_single(self): + self._check_sort_list([5], [5]) + + def test_sort_single_alph(self): + self._check_sort_alph('a', 'a') + + def test_sort_two_inorder(self): + self._check_sort_list([1, 2], [1, 2]) + + def test_sort_two_outoforder(self): + self._check_sort_list([2, 1], [1, 2]) + + def test_sort_5_random_numeric(self): + arr = list(range(5)) + random.shuffle(arr) + self._check_sort_list(arr, list(range(5))) + + def test_sort_15_random_numeric(self): + arr = list(range(15)) + random.shuffle(arr) + self._check_sort_list(arr, list(range(15))) + + def test_sort_5_random_alph(self): + arr = ['a', 'b', 'c', 'd', 'e'] + random.shuffle(arr) + self._check_sort_alph(''.join(arr), 'abcde') + + def test_sort_15_random_alph(self): + arr = [chr(ord('a') + i) for i in range(15)] + exp = ''.join(arr) + random.shuffle(arr) + self._check_sort_alph(''.join(arr), exp) + + +class TestBubbleSort(unittest.TestCase, TestSortingAlgorithm): + inplace = True + alph_support = True + + @staticmethod + def sort(arr): + return bubble_sort.sort(arr) + + +class TestInsertionSort(unittest.TestCase, TestSortingAlgorithm): + inplace = True + alph_support = True + + @staticmethod + def sort(arr): + return insertion_sort.sort(arr) + + +class TestSelectionSort(unittest.TestCase, TestSortingAlgorithm): + inplace = True + alph_support = True + + @staticmethod + def sort(arr): + return selection_sort.sort(arr) + + +class TestMergeSort(unittest.TestCase, TestSortingAlgorithm): + inplace = False + alph_support = True + + @staticmethod + def sort(arr): + return merge_sort.sort(arr) + +class TestMergeSortIterative(unittest.TestCase, TestSortingAlgorithm): + inplace = False + alph_support = True + + @staticmethod + def sort(arr): + return merge_sort.sorti(arr, verbose=False) + +class TestQuickSort(unittest.TestCase, TestSortingAlgorithm): + inplace = False + alph_support = True + + @staticmethod + def sort(arr): + return quick_sort.sort(arr) + + +class TestCountingSort(unittest.TestCase, TestSortingAlgorithm): + inplace = True + alph_support = False + + @staticmethod + def sort(arr): + return counting_sort.sort(arr) + + +class TestBucketSort(unittest.TestCase, TestSortingAlgorithm): + inplace = False + alph_support = True + + @staticmethod + def sort(arr): + return bucket_sort.sort(arr) + + +class TestShellSort(unittest.TestCase, TestSortingAlgorithm): + inplace = True + alph_support = True + + @staticmethod + def sort(arr): + return shell_sort.sort(arr) + + +class TestHeapSort(unittest.TestCase, TestSortingAlgorithm): + inplace = True + alph_support = True + + @staticmethod + def sort(arr): + return heap_sort.sort(arr) + + +class TestBrickSort(unittest.TestCase, TestSortingAlgorithm): + inplace = True + alph_support = True + + @staticmethod + def sort(arr): + return brick_sort.brick_sort(arr) + + +class TestTimSort(unittest.TestCase, TestSortingAlgorithm): + inplace = True + alph_support = True + + @staticmethod + def sort(arr): + # use a smaller run for testing + return tim_sort.tim_sort(arr, run=4) + + +class TestCocktailSort(unittest.TestCase, TestSortingAlgorithm): + inplace = True + alph_support = True + + @staticmethod + def sort(arr): + return cocktail_sort.cocktail_sort(arr) + + +class TestGnomeSort(unittest.TestCase, TestSortingAlgorithm): + inplace = True + alph_support = True + + @staticmethod + def sort(arr): + return gnome_sort.gnome_sort(arr) + +if __name__ == '__main__': + unittest.main()