From 30a358298385e0e29b70a841d0b4019dc235f3a3 Mon Sep 17 00:00:00 2001 From: Lorenz Nickel Date: Wed, 8 May 2019 21:48:30 +0200 Subject: [PATCH 0001/1071] fix: replaced outdated url (#791) http://www.lpb-riannetrujillo.com/blog/python-fractal/ moved to http://www.riannetrujillo.com/blog/python-fractal/ --- other/sierpinski_triangle.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/other/sierpinski_triangle.py b/other/sierpinski_triangle.py index 6a06058fe03e..329a8ce5c43f 100644 --- a/other/sierpinski_triangle.py +++ b/other/sierpinski_triangle.py @@ -21,7 +21,7 @@ Usage: - $python sierpinski_triangle.py -Credits: This code was written by editing the code from http://www.lpb-riannetrujillo.com/blog/python-fractal/ +Credits: This code was written by editing the code from http://www.riannetrujillo.com/blog/python-fractal/ ''' import turtle @@ -64,4 +64,4 @@ def triangle(points,depth): depth-1) -triangle(points,int(sys.argv[1])) \ No newline at end of file +triangle(points,int(sys.argv[1])) From 56513cb21f759ac26b31ac1edcb45d886a97f715 Mon Sep 17 00:00:00 2001 From: Junth Basnet <25685098+Junth19@users.noreply.github.com> Date: Fri, 10 May 2019 16:48:05 +0545 Subject: [PATCH 0002/1071] add-binary-exponentiation (#790) --- maths/BinaryExponentiation.py | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 maths/BinaryExponentiation.py diff --git a/maths/BinaryExponentiation.py b/maths/BinaryExponentiation.py new file mode 100644 index 000000000000..2411cd58a76b --- /dev/null +++ b/maths/BinaryExponentiation.py @@ -0,0 +1,25 @@ +#Author : Junth Basnet +#Time Complexity : O(logn) + +def binary_exponentiation(a, n): + + if (n == 0): + return 1 + + elif (n % 2 == 1): + return binary_exponentiation(a, n - 1) * a + + else: + b = binary_exponentiation(a, n / 2) + return b * b + + +try: + base = int(input('Enter Base : ')) + power = int(input("Enter Power : ")) +except ValueError: + print ("Invalid literal for integer") + +result = binary_exponentiation(base, power) +print("{}^({}) : {}".format(base, power, result)) + From 36828b106f7905ecc0c0776e40c99929728a91a9 Mon Sep 17 00:00:00 2001 From: Julien Castiaux Date: Sat, 11 May 2019 13:20:25 +0200 Subject: [PATCH 0003/1071] [FIX] maths/PrimeCheck (#796) Current implementation is buggy and hard to read. * Negative values were raising a TypeError due to `math.sqrt` * 1 was considered prime, it is not. * 2 was considered not prime, it is. The implementation has been corrected to fix the bugs and to enhance readability. A docstring has been added with the definition of a prime number. A complete test suite has been written, it tests the 10 first primes, a negative value, 0, 1 and some not prime numbers. closes #795 --- maths/PrimeCheck.py | 55 +++++++++++++++++++++++++++++++++++++++------ 1 file changed, 48 insertions(+), 7 deletions(-) diff --git a/maths/PrimeCheck.py b/maths/PrimeCheck.py index e0c51d77a038..8c5c181689dd 100644 --- a/maths/PrimeCheck.py +++ b/maths/PrimeCheck.py @@ -1,13 +1,54 @@ import math +import unittest + + def primeCheck(number): - if number % 2 == 0 and number > 2: + """ + A number is prime if it has exactly two dividers: 1 and itself. + """ + if number < 2: + # Negatives, 0 and 1 are not primes return False - return all(number % i for i in range(3, int(math.sqrt(number)) + 1, 2)) + if number < 4: + # 2 and 3 are primes + return True + if number % 2 == 0: + # Even values are not primes + return False + + # Except 2, all primes are odd. If any odd value divide + # the number, then that number is not prime. + odd_numbers = range(3, int(math.sqrt(number)) + 1, 2) + return not any(number % i == 0 for i in odd_numbers) + + +class Test(unittest.TestCase): + def test_primes(self): + self.assertTrue(primeCheck(2)) + self.assertTrue(primeCheck(3)) + self.assertTrue(primeCheck(5)) + self.assertTrue(primeCheck(7)) + self.assertTrue(primeCheck(11)) + self.assertTrue(primeCheck(13)) + self.assertTrue(primeCheck(17)) + self.assertTrue(primeCheck(19)) + self.assertTrue(primeCheck(23)) + self.assertTrue(primeCheck(29)) + + def test_not_primes(self): + self.assertFalse(primeCheck(-19), + "Negative numbers are not prime.") + self.assertFalse(primeCheck(0), + "Zero doesn't have any divider, primes must have two") + self.assertFalse(primeCheck(1), + "One just have 1 divider, primes must have two.") + self.assertFalse(primeCheck(2 * 2)) + self.assertFalse(primeCheck(2 * 3)) + self.assertFalse(primeCheck(3 * 3)) + self.assertFalse(primeCheck(3 * 5)) + self.assertFalse(primeCheck(3 * 5 * 7)) -def main(): - print(primeCheck(37)) - print(primeCheck(100)) - print(primeCheck(77)) if __name__ == '__main__': - main() + unittest.main() + From d8badcc6d5568e3ed8b060305f6d02e74019f1a4 Mon Sep 17 00:00:00 2001 From: Anup Kumar Panwar <1anuppanwar@gmail.com> Date: Sun, 12 May 2019 09:10:56 +0530 Subject: [PATCH 0004/1071] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 1e43deb6bdef..9b61f1b63287 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ # The Algorithms - Python -[![Donate](https://img.shields.io/badge/Donate-PayPal-green.svg)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=JP3BLXA6KMDGW) +[![Donate](https://img.shields.io/badge/Donate-PayPal-green.svg)](https://www.paypal.me/TheAlgorithms/100) ### All algorithms implemented in Python (for education) From 3f7bec6c00c089490c8b5d38686373ca6e1ea97b Mon Sep 17 00:00:00 2001 From: Bhushan Borole <37565807+bhushan-borole@users.noreply.github.com> Date: Sun, 12 May 2019 17:16:47 +0530 Subject: [PATCH 0005/1071] Added page-rank algorithm implementation (#780) * Added page-rank algorithm implementation * changed init variables --- Graphs/pagerank.py | 72 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 72 insertions(+) create mode 100644 Graphs/pagerank.py diff --git a/Graphs/pagerank.py b/Graphs/pagerank.py new file mode 100644 index 000000000000..59f15a99e6b2 --- /dev/null +++ b/Graphs/pagerank.py @@ -0,0 +1,72 @@ +''' +Author: https://github.com/bhushan-borole +''' +''' +The input graph for the algorithm is: + + A B C +A 0 1 1 +B 0 0 1 +C 1 0 0 + +''' + +graph = [[0, 1, 1], + [0, 0, 1], + [1, 0, 0]] + + +class Node: + def __init__(self, name): + self.name = name + self.inbound = [] + self.outbound = [] + + def add_inbound(self, node): + self.inbound.append(node) + + def add_outbound(self, node): + self.outbound.append(node) + + def __repr__(self): + return 'Node {}: Inbound: {} ; Outbound: {}'.format(self.name, + self.inbound, + self.outbound) + + +def page_rank(nodes, limit=3, d=0.85): + ranks = {} + for node in nodes: + ranks[node.name] = 1 + + outbounds = {} + for node in nodes: + outbounds[node.name] = len(node.outbound) + + for i in range(limit): + print("======= Iteration {} =======".format(i+1)) + for j, node in enumerate(nodes): + ranks[node.name] = (1 - d) + d * sum([ ranks[ib]/outbounds[ib] for ib in node.inbound ]) + print(ranks) + + +def main(): + names = list(input('Enter Names of the Nodes: ').split()) + + nodes = [Node(name) for name in names] + + for ri, row in enumerate(graph): + for ci, col in enumerate(row): + if col == 1: + nodes[ci].add_inbound(names[ri]) + nodes[ri].add_outbound(names[ci]) + + print("======= Nodes =======") + for node in nodes: + print(node) + + page_rank(nodes) + + +if __name__ == '__main__': + main() \ No newline at end of file From 70bb6b2f18bec6cadca052d96e526d014d18ff32 Mon Sep 17 00:00:00 2001 From: Ravi Patel Date: Mon, 13 May 2019 01:15:27 -0400 Subject: [PATCH 0006/1071] Added Huffman Coding Algorithm (#798) --- compression/huffman.py | 87 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 87 insertions(+) create mode 100644 compression/huffman.py diff --git a/compression/huffman.py b/compression/huffman.py new file mode 100644 index 000000000000..b6238b66e9fd --- /dev/null +++ b/compression/huffman.py @@ -0,0 +1,87 @@ +import sys + +class Letter: + def __init__(self, letter, freq): + self.letter = letter + self.freq = freq + self.bitstring = "" + + def __repr__(self): + return f'{self.letter}:{self.freq}' + + +class TreeNode: + def __init__(self, freq, left, right): + self.freq = freq + self.left = left + self.right = right + + +def parse_file(file_path): + """ + Read the file and build a dict of all letters and their + frequences, then convert the dict into a list of Letters. + """ + chars = {} + with open(file_path) as f: + while True: + c = f.read(1) + if not c: + break + chars[c] = chars[c] + 1 if c in chars.keys() else 1 + letters = [] + for char, freq in chars.items(): + letter = Letter(char, freq) + letters.append(letter) + letters.sort(key=lambda l: l.freq) + return letters + +def build_tree(letters): + """ + Run through the list of Letters and build the min heap + for the Huffman Tree. + """ + while len(letters) > 1: + left = letters.pop(0) + right = letters.pop(0) + total_freq = left.freq + right.freq + node = TreeNode(total_freq, left, right) + letters.append(node) + letters.sort(key=lambda l: l.freq) + return letters[0] + +def traverse_tree(root, bitstring): + """ + Recursively traverse the Huffman Tree to set each + Letter's bitstring, and return the list of Letters + """ + if type(root) is Letter: + root.bitstring = bitstring + return [root] + letters = [] + letters += traverse_tree(root.left, bitstring + "0") + letters += traverse_tree(root.right, bitstring + "1") + return letters + +def huffman(file_path): + """ + Parse the file, build the tree, then run through the file + again, using the list of Letters to find and print out the + bitstring for each letter. + """ + letters_list = parse_file(file_path) + root = build_tree(letters_list) + letters = traverse_tree(root, "") + print(f'Huffman Coding of {file_path}: ') + with open(file_path) as f: + while True: + c = f.read(1) + if not c: + break + le = list(filter(lambda l: l.letter == c, letters))[0] + print(le.bitstring, end=" ") + print() + +if __name__ == "__main__": + # pass the file path to the huffman function + huffman(sys.argv[1]) From 3c40fda6a3ed8f59f1afc11b653be505557a41ef Mon Sep 17 00:00:00 2001 From: "Tommy.Liu" <447569003@qq.com> Date: Tue, 14 May 2019 18:17:25 +0800 Subject: [PATCH 0007/1071] More elegant coding for merge_sort_fastest (#804) * More elegant coding for merge_sort_fastest * More elegant coding for merge_sort --- sorts/merge_sort.py | 46 +++++++++++--------------------- sorts/merge_sort_fastest.py | 53 ++++++++++++++++++++++++++++--------- 2 files changed, 55 insertions(+), 44 deletions(-) diff --git a/sorts/merge_sort.py b/sorts/merge_sort.py index ca4d319fa7f1..4a6201a40cb4 100644 --- a/sorts/merge_sort.py +++ b/sorts/merge_sort.py @@ -29,36 +29,20 @@ def merge_sort(collection): >>> merge_sort([-2, -5, -45]) [-45, -5, -2] """ - length = len(collection) - if length > 1: - midpoint = length // 2 - left_half = merge_sort(collection[:midpoint]) - right_half = merge_sort(collection[midpoint:]) - i = 0 - j = 0 - k = 0 - left_length = len(left_half) - right_length = len(right_half) - while i < left_length and j < right_length: - if left_half[i] < right_half[j]: - collection[k] = left_half[i] - i += 1 - else: - collection[k] = right_half[j] - j += 1 - k += 1 - - while i < left_length: - collection[k] = left_half[i] - i += 1 - k += 1 - - while j < right_length: - collection[k] = right_half[j] - j += 1 - k += 1 - - return collection + def merge(left, right): + '''merge left and right + :param left: left collection + :param right: right collection + :return: merge result + ''' + result = [] + while left and right: + result.append(left.pop(0) if left[0] <= right[0] else right.pop(0)) + return result + left + right + if len(collection) <= 1: + return collection + mid = len(collection) // 2 + return merge(merge_sort(collection[:mid]), merge_sort(collection[mid:])) if __name__ == '__main__': @@ -69,4 +53,4 @@ def merge_sort(collection): user_input = raw_input('Enter numbers separated by a comma:\n').strip() unsorted = [int(item) for item in user_input.split(',')] - print(merge_sort(unsorted)) + print(*merge_sort(unsorted), sep=',') diff --git a/sorts/merge_sort_fastest.py b/sorts/merge_sort_fastest.py index 9fc9275aacba..86cb8ae1a699 100644 --- a/sorts/merge_sort_fastest.py +++ b/sorts/merge_sort_fastest.py @@ -1,19 +1,46 @@ ''' -Python implementation of merge sort algorithm. +Python implementation of the fastest merge sort algorithm. Takes an average of 0.6 microseconds to sort a list of length 1000 items. Best Case Scenario : O(n) Worst Case Scenario : O(n) ''' -def merge_sort(LIST): - start = [] - end = [] - while len(LIST) > 1: - a = min(LIST) - b = max(LIST) - start.append(a) - end.append(b) - LIST.remove(a) - LIST.remove(b) - if LIST: start.append(LIST[0]) +from __future__ import print_function + + +def merge_sort(collection): + """Pure implementation of the fastest merge sort algorithm in Python + + :param collection: some mutable ordered collection with heterogeneous + comparable items inside + :return: a collection ordered by ascending + + Examples: + >>> merge_sort([0, 5, 3, 2, 2]) + [0, 2, 2, 3, 5] + + >>> merge_sort([]) + [] + + >>> merge_sort([-2, -5, -45]) + [-45, -5, -2] + """ + start, end = [], [] + while len(collection) > 1: + min_one, max_one = min(collection), max(collection) + start.append(min_one) + end.append(max_one) + collection.remove(min_one) + collection.remove(max_one) end.reverse() - return (start + end) + return start + collection + end + + +if __name__ == '__main__': + try: + raw_input # Python 2 + except NameError: + raw_input = input # Python 3 + + user_input = raw_input('Enter numbers separated by a comma:\n').strip() + unsorted = [int(item) for item in user_input.split(',')] + print(*merge_sort(unsorted), sep=',') From c4d16820bc062ebb1f311e74885e7ca0e2fa5973 Mon Sep 17 00:00:00 2001 From: Erfan Alimohammadi Date: Tue, 14 May 2019 21:45:53 +0430 Subject: [PATCH 0008/1071] Fix typo (#806) --- strings/manacher.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/strings/manacher.py b/strings/manacher.py index 9a44b19ba77a..e73e173b43e0 100644 --- a/strings/manacher.py +++ b/strings/manacher.py @@ -1,4 +1,4 @@ -# calculate palindromic length from center with incresmenting difference +# calculate palindromic length from center with incrementing difference def palindromic_length( center, diff, string): if center-diff == -1 or center+diff == len(string) or string[center-diff] != string[center+diff] : return 0 From 76061ab2cc7f4e07fa7b2c952ca715cc6d09d7c2 Mon Sep 17 00:00:00 2001 From: Reshad Hasan Date: Thu, 16 May 2019 17:20:27 +0600 Subject: [PATCH 0009/1071] added eulerian path and circuit finding algorithm (#787) --- ...n path and circuit for undirected graph.py | 93 +++++++++++++++++++ 1 file changed, 93 insertions(+) create mode 100644 Graphs/Eulerian path and circuit for undirected graph.py diff --git a/Graphs/Eulerian path and circuit for undirected graph.py b/Graphs/Eulerian path and circuit for undirected graph.py new file mode 100644 index 000000000000..c6c6a1a25f03 --- /dev/null +++ b/Graphs/Eulerian path and circuit for undirected graph.py @@ -0,0 +1,93 @@ +# Eulerian Path is a path in graph that visits every edge exactly once. +# Eulerian Circuit is an Eulerian Path which starts and ends on the same +# vertex. +# time complexity is O(V+E) +# space complexity is O(VE) + + +# using dfs for finding eulerian path traversal +def dfs(u, graph, visited_edge, path=[]): + path = path + [u] + for v in graph[u]: + if visited_edge[u][v] == False: + visited_edge[u][v], visited_edge[v][u] = True, True + path = dfs(v, graph, visited_edge, path) + return path + + +# for checking in graph has euler path or circuit +def check_circuit_or_path(graph, max_node): + odd_degree_nodes = 0 + odd_node = -1 + for i in range(max_node): + if i not in graph.keys(): + continue + if len(graph[i]) % 2 == 1: + odd_degree_nodes += 1 + odd_node = i + if odd_degree_nodes == 0: + return 1, odd_node + if odd_degree_nodes == 2: + return 2, odd_node + return 3, odd_node + + +def check_euler(graph, max_node): + visited_edge = [[False for _ in range(max_node + 1)] for _ in range(max_node + 1)] + check, odd_node = check_circuit_or_path(graph, max_node) + if check == 3: + print("graph is not Eulerian") + print("no path") + return + start_node = 1 + if check == 2: + start_node = odd_node + print("graph has a Euler path") + if check == 1: + print("graph has a Euler cycle") + path = dfs(start_node, graph, visited_edge) + print(path) + + +def main(): + G1 = { + 1: [2, 3, 4], + 2: [1, 3], + 3: [1, 2], + 4: [1, 5], + 5: [4] + } + G2 = { + 1: [2, 3, 4, 5], + 2: [1, 3], + 3: [1, 2], + 4: [1, 5], + 5: [1, 4] + } + G3 = { + 1: [2, 3, 4], + 2: [1, 3, 4], + 3: [1, 2], + 4: [1, 2, 5], + 5: [4] + } + G4 = { + 1: [2, 3], + 2: [1, 3], + 3: [1, 2], + } + G5 = { + 1: [], + 2: [] + # all degree is zero + } + max_node = 10 + check_euler(G1, max_node) + check_euler(G2, max_node) + check_euler(G3, max_node) + check_euler(G4, max_node) + check_euler(G5, max_node) + + +if __name__ == "__main__": + main() From c47c1ab03ce80963d5dcd2136d03555f3b283055 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?N=C3=ADkolas=20Vargas?= Date: Thu, 16 May 2019 08:20:42 -0300 Subject: [PATCH 0010/1071] enhancement (#803) --- compression/huffman.py | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/compression/huffman.py b/compression/huffman.py index b6238b66e9fd..7417551ba209 100644 --- a/compression/huffman.py +++ b/compression/huffman.py @@ -18,7 +18,7 @@ def __init__(self, freq, left, right): def parse_file(file_path): - """ + """ Read the file and build a dict of all letters and their frequences, then convert the dict into a list of Letters. """ @@ -29,15 +29,10 @@ def parse_file(file_path): if not c: break chars[c] = chars[c] + 1 if c in chars.keys() else 1 - letters = [] - for char, freq in chars.items(): - letter = Letter(char, freq) - letters.append(letter) - letters.sort(key=lambda l: l.freq) - return letters + return sorted([Letter(c, f) for c, f in chars.items()], key=lambda l: l.freq) def build_tree(letters): - """ + """ Run through the list of Letters and build the min heap for the Huffman Tree. """ @@ -51,7 +46,7 @@ def build_tree(letters): return letters[0] def traverse_tree(root, bitstring): - """ + """ Recursively traverse the Huffman Tree to set each Letter's bitstring, and return the list of Letters """ @@ -64,9 +59,9 @@ def traverse_tree(root, bitstring): return letters def huffman(file_path): - """ + """ Parse the file, build the tree, then run through the file - again, using the list of Letters to find and print out the + again, using the list of Letters to find and print out the bitstring for each letter. """ letters_list = parse_file(file_path) From 13c0c166d8f80398de39ab41632fd54be86ae2cc Mon Sep 17 00:00:00 2001 From: ImNandha <49323522+ImNandha@users.noreply.github.com> Date: Thu, 16 May 2019 16:53:23 +0530 Subject: [PATCH 0011/1071] Update graph.py (#809) --- graphs/graph.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/graphs/graph.py b/graphs/graph.py index 9bd61559dcbf..0c981c39d320 100644 --- a/graphs/graph.py +++ b/graphs/graph.py @@ -4,7 +4,7 @@ from __future__ import print_function # Author: OMKAR PATHAK -# We can use Python's dictionary for constructing the graph +# We can use Python's dictionary for constructing the graph. class AdjacencyList(object): def __init__(self): From a65efd42c4683b628338b84c822d22eab199c058 Mon Sep 17 00:00:00 2001 From: Erfan Alimohammadi Date: Thu, 16 May 2019 15:54:56 +0430 Subject: [PATCH 0012/1071] Implement check_bipartite_graph using DFS. (#808) --- graphs/check_bipartite_graph_dfs.py | 33 +++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 graphs/check_bipartite_graph_dfs.py diff --git a/graphs/check_bipartite_graph_dfs.py b/graphs/check_bipartite_graph_dfs.py new file mode 100644 index 000000000000..eeb3a84b7a15 --- /dev/null +++ b/graphs/check_bipartite_graph_dfs.py @@ -0,0 +1,33 @@ +# Check whether Graph is Bipartite or Not using DFS + +# A Bipartite Graph is a graph whose vertices can be divided into two independent sets, +# U and V such that every edge (u, v) either connects a vertex from U to V or a vertex +# from V to U. In other words, for every edge (u, v), either u belongs to U and v to V, +# or u belongs to V and v to U. We can also say that there is no edge that connects +# vertices of same set. +def check_bipartite_dfs(l): + visited = [False] * len(l) + color = [-1] * len(l) + + def dfs(v, c): + visited[v] = True + color[v] = c + for u in l[v]: + if not visited[u]: + dfs(u, 1 - c) + + for i in range(len(l)): + if not visited[i]: + dfs(i, 0) + + for i in range(len(l)): + for j in l[i]: + if color[i] == color[j]: + return False + + return True + + +# Adjacency list of graph +l = {0:[1,3], 1:[0,2], 2:[1,3], 3:[0,2], 4: []} +print(check_bipartite_dfs(l)) From 5b86928c4b6ab23cbff51ddf9023ac230d4dff26 Mon Sep 17 00:00:00 2001 From: cclauss Date: Thu, 16 May 2019 13:26:46 +0200 Subject: [PATCH 0013/1071] Use ==/!= to compare str, bytes, and int literals (#767) * Travis CI: Add more flake8 tests * Use ==/!= to compare str, bytes, and int literals ./project_euler/problem_17/sol1.py:25:7: F632 use ==/!= to compare str, bytes, and int literals if i%100 is not 0: ^ * Use ==/!= to compare str, bytes, and int literals * Update sol1.py --- .travis.yml | 2 +- project_euler/problem_17/sol1.py | 4 ++-- project_euler/problem_19/sol1.py | 8 ++++---- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/.travis.yml b/.travis.yml index 5fba6987bb66..2440899e4f25 100644 --- a/.travis.yml +++ b/.travis.yml @@ -16,7 +16,7 @@ install: - pip install flake8 # pytest # add another testing frameworks later before_script: # stop the build if there are Python syntax errors or undefined names - - flake8 . --count --select=E901,E999,F821,F822,F823 --show-source --statistics + - flake8 . --count --select=E9,F63,F72,F82 --show-source --statistics # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide - flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics script: diff --git a/project_euler/problem_17/sol1.py b/project_euler/problem_17/sol1.py index 9de5d80b9b29..8dd6f1af2093 100644 --- a/project_euler/problem_17/sol1.py +++ b/project_euler/problem_17/sol1.py @@ -22,7 +22,7 @@ if i >= 100: count += ones_counts[i/100] + 7 #add number of letters for "n hundred" - if i%100 is not 0: + if i%100 != 0: count += 3 #add number of letters for "and" if number is not multiple of 100 if 0 < i%100 < 20: @@ -32,4 +32,4 @@ else: count += ones_counts[i/1000] + 8 -print(count) \ No newline at end of file +print(count) diff --git a/project_euler/problem_19/sol1.py b/project_euler/problem_19/sol1.py index 94cf117026a4..13e520ca76e4 100644 --- a/project_euler/problem_19/sol1.py +++ b/project_euler/problem_19/sol1.py @@ -30,10 +30,10 @@ day += 7 if (year%4 == 0 and not year%100 == 0) or (year%400 == 0): - if day > days_per_month[month-1] and month is not 2: + if day > days_per_month[month-1] and month != 2: month += 1 day = day-days_per_month[month-2] - elif day > 29 and month is 2: + elif day > 29 and month == 2: month += 1 day = day-29 else: @@ -45,7 +45,7 @@ year += 1 month = 1 - if year < 2001 and day is 1: + if year < 2001 and day == 1: sundays += 1 -print(sundays) \ No newline at end of file +print(sundays) From f3608acfd5c3c66531942434769c8260c983e877 Mon Sep 17 00:00:00 2001 From: Sarvesh Dubey <38752758+dubesar@users.noreply.github.com> Date: Fri, 17 May 2019 08:42:06 +0530 Subject: [PATCH 0014/1071] Created shortest path using bfs (#794) * Created shortest path using bfs --- graphs/bfs-shortestpath.py | 43 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 graphs/bfs-shortestpath.py diff --git a/graphs/bfs-shortestpath.py b/graphs/bfs-shortestpath.py new file mode 100644 index 000000000000..5853351a53a3 --- /dev/null +++ b/graphs/bfs-shortestpath.py @@ -0,0 +1,43 @@ +graph = {'A': ['B', 'C', 'E'], + 'B': ['A','D', 'E'], + 'C': ['A', 'F', 'G'], + 'D': ['B'], + 'E': ['A', 'B','D'], + 'F': ['C'], + 'G': ['C']} + +def bfs_shortest_path(graph, start, goal): + # keep track of explored nodes + explored = [] + # keep track of all the paths to be checked + queue = [[start]] + + # return path if start is goal + if start == goal: + return "That was easy! Start = goal" + + # keeps looping until all possible paths have been checked + while queue: + # pop the first path from the queue + path = queue.pop(0) + # get the last node from the path + node = path[-1] + if node not in explored: + neighbours = graph[node] + # go through all neighbour nodes, construct a new path and + # push it into the queue + for neighbour in neighbours: + new_path = list(path) + new_path.append(neighbour) + queue.append(new_path) + # return path if neighbour is goal + if neighbour == goal: + return new_path + + # mark node as explored + explored.append(node) + + # in case there's no path between the 2 nodes + return "So sorry, but a connecting path doesn't exist :(" + +bfs_shortest_path(graph, 'G', 'D') # returns ['G', 'C', 'A', 'B', 'D'] From b6c3fa8992e1f3430e623b6c4b1268c89e26f71f Mon Sep 17 00:00:00 2001 From: weixuanhu <44716380+weixuanhu@users.noreply.github.com> Date: Sat, 18 May 2019 10:59:12 +0800 Subject: [PATCH 0015/1071] Interpolation search - fix endless loop bug, divide 0 bug and update description (#793) * fix endless loop bug, divide 0 bug and update description fix an endless bug, for example, if collection = [10,30,40,45,50,66,77,93], item = 67. fix divide 0 bug, when right=left it is not OK to point = left + ((item - sorted_collection[left]) * (right - left)) // (sorted_collection[right] - sorted_collection[left]) update 'sorted' to 'ascending sorted' in description to avoid confusion * delete swap files * delete 'address' and add input validation --- searches/interpolation_search.py | 80 +++++++++++++++++------ searches/quick_select.py | 11 +++- searches/test_interpolation_search.py | 93 +++++++++++++++++++++++++++ 3 files changed, 160 insertions(+), 24 deletions(-) create mode 100644 searches/test_interpolation_search.py diff --git a/searches/interpolation_search.py b/searches/interpolation_search.py index db9893bdb5d4..329596d340a5 100644 --- a/searches/interpolation_search.py +++ b/searches/interpolation_search.py @@ -11,9 +11,9 @@ def interpolation_search(sorted_collection, item): """Pure implementation of interpolation search algorithm in Python - Be careful collection must be sorted, otherwise result will be + Be careful collection must be ascending sorted, otherwise result will be unpredictable - :param sorted_collection: some sorted collection with comparable items + :param sorted_collection: some ascending sorted collection with comparable items :param item: item value to search :return: index of found item or None if item is not found """ @@ -21,6 +21,13 @@ def interpolation_search(sorted_collection, item): right = len(sorted_collection) - 1 while left <= right: + #avoid devided by 0 during interpolation + if sorted_collection[left]==sorted_collection[right]: + if sorted_collection[left]==item: + return left + else: + return None + point = left + ((item - sorted_collection[left]) * (right - left)) // (sorted_collection[right] - sorted_collection[left]) #out of range check @@ -31,66 +38,97 @@ def interpolation_search(sorted_collection, item): if current_item == item: return point else: - if item < current_item: - right = point - 1 - else: - left = point + 1 + if pointright: + left = right + right = point + else: + if item < current_item: + right = point - 1 + else: + left = point + 1 return None - def interpolation_search_by_recursion(sorted_collection, item, left, right): """Pure implementation of interpolation search algorithm in Python by recursion - Be careful collection must be sorted, otherwise result will be + Be careful collection must be ascending sorted, otherwise result will be unpredictable First recursion should be started with left=0 and right=(len(sorted_collection)-1) - :param sorted_collection: some sorted collection with comparable items + :param sorted_collection: some ascending sorted collection with comparable items :param item: item value to search :return: index of found item or None if item is not found """ - point = left + ((item - sorted_collection[left]) * (right - left)) // (sorted_collection[right] - sorted_collection[left]) + #avoid devided by 0 during interpolation + if sorted_collection[left]==sorted_collection[right]: + if sorted_collection[left]==item: + return left + else: + return None + + point = left + ((item - sorted_collection[left]) * (right - left)) // (sorted_collection[right] - sorted_collection[left]) + #out of range check if point<0 or point>=len(sorted_collection): return None if sorted_collection[point] == item: return point - elif sorted_collection[point] > item: - return interpolation_search_by_recursion(sorted_collection, item, left, point-1) + elif pointright: + return interpolation_search_by_recursion(sorted_collection, item, right, left) else: - return interpolation_search_by_recursion(sorted_collection, item, point+1, right) + if sorted_collection[point] > item: + return interpolation_search_by_recursion(sorted_collection, item, left, point-1) + else: + return interpolation_search_by_recursion(sorted_collection, item, point+1, right) def __assert_sorted(collection): - """Check if collection is sorted, if not - raises :py:class:`ValueError` + """Check if collection is ascending sorted, if not - raises :py:class:`ValueError` :param collection: collection - :return: True if collection is sorted - :raise: :py:class:`ValueError` if collection is not sorted + :return: True if collection is ascending sorted + :raise: :py:class:`ValueError` if collection is not ascending sorted Examples: >>> __assert_sorted([0, 1, 2, 4]) True >>> __assert_sorted([10, -1, 5]) Traceback (most recent call last): ... - ValueError: Collection must be sorted + ValueError: Collection must be ascending sorted """ if collection != sorted(collection): - raise ValueError('Collection must be sorted') + raise ValueError('Collection must be ascending sorted') return True if __name__ == '__main__': import sys - - user_input = raw_input('Enter numbers separated by comma:\n').strip() + + """ + user_input = raw_input('Enter numbers separated by comma:\n').strip() collection = [int(item) for item in user_input.split(',')] try: __assert_sorted(collection) except ValueError: - sys.exit('Sequence must be sorted to apply interpolation search') + sys.exit('Sequence must be ascending sorted to apply interpolation search') target_input = raw_input('Enter a single number to be found in the list:\n') target = int(target_input) + """ + + debug = 0 + if debug == 1: + collection = [10,30,40,45,50,66,77,93] + try: + __assert_sorted(collection) + except ValueError: + sys.exit('Sequence must be ascending sorted to apply interpolation search') + target = 67 + result = interpolation_search(collection, target) if result is not None: print('{} found at positions: {}'.format(target, result)) diff --git a/searches/quick_select.py b/searches/quick_select.py index 1596cf040e0c..76d09cb97f97 100644 --- a/searches/quick_select.py +++ b/searches/quick_select.py @@ -14,9 +14,9 @@ def _partition(data, pivot): """ less, equal, greater = [], [], [] for element in data: - if element.address < pivot.address: + if element < pivot: less.append(element) - elif element.address > pivot.address: + elif element > pivot: greater.append(element) else: equal.append(element) @@ -24,6 +24,11 @@ def _partition(data, pivot): def quickSelect(list, k): #k = len(list) // 2 when trying to find the median (index that value would be when list is sorted) + + #invalid input + if k>=len(list) or k<0: + return None + smaller = [] larger = [] pivot = random.randint(0, len(list) - 1) @@ -41,4 +46,4 @@ def quickSelect(list, k): return quickSelect(smaller, k) #must be in larger else: - return quickSelect(larger, k - (m + count)) + return quickSelect(larger, k - (m + count)) \ No newline at end of file diff --git a/searches/test_interpolation_search.py b/searches/test_interpolation_search.py new file mode 100644 index 000000000000..60bb3af22e0f --- /dev/null +++ b/searches/test_interpolation_search.py @@ -0,0 +1,93 @@ +import unittest +from interpolation_search import interpolation_search, interpolation_search_by_recursion + +class Test_interpolation_search(unittest.TestCase): + def setUp(self): + # un-sorted case + self.collection1 = [5,3,4,6,7] + self.item1 = 4 + # sorted case, result exists + self.collection2 = [10,30,40,45,50,66,77,93] + self.item2 = 66 + # sorted case, result doesn't exist + self.collection3 = [10,30,40,45,50,66,77,93] + self.item3 = 67 + # equal elements case, result exists + self.collection4 = [10,10,10,10,10] + self.item4 = 10 + # equal elements case, result doesn't exist + self.collection5 = [10,10,10,10,10] + self.item5 = 3 + # 1 element case, result exists + self.collection6 = [10] + self.item6 = 10 + # 1 element case, result doesn't exists + self.collection7 = [10] + self.item7 = 1 + + def tearDown(self): + pass + + def test_interpolation_search(self): + self.assertEqual(interpolation_search(self.collection1, self.item1), None) + + self.assertEqual(interpolation_search(self.collection2, self.item2), self.collection2.index(self.item2)) + + self.assertEqual(interpolation_search(self.collection3, self.item3), None) + + self.assertEqual(interpolation_search(self.collection4, self.item4), self.collection4.index(self.item4)) + + self.assertEqual(interpolation_search(self.collection5, self.item5), None) + + self.assertEqual(interpolation_search(self.collection6, self.item6), self.collection6.index(self.item6)) + + self.assertEqual(interpolation_search(self.collection7, self.item7), None) + + + +class Test_interpolation_search_by_recursion(unittest.TestCase): + def setUp(self): + # un-sorted case + self.collection1 = [5,3,4,6,7] + self.item1 = 4 + # sorted case, result exists + self.collection2 = [10,30,40,45,50,66,77,93] + self.item2 = 66 + # sorted case, result doesn't exist + self.collection3 = [10,30,40,45,50,66,77,93] + self.item3 = 67 + # equal elements case, result exists + self.collection4 = [10,10,10,10,10] + self.item4 = 10 + # equal elements case, result doesn't exist + self.collection5 = [10,10,10,10,10] + self.item5 = 3 + # 1 element case, result exists + self.collection6 = [10] + self.item6 = 10 + # 1 element case, result doesn't exists + self.collection7 = [10] + self.item7 = 1 + + def tearDown(self): + pass + + def test_interpolation_search_by_recursion(self): + self.assertEqual(interpolation_search_by_recursion(self.collection1, self.item1, 0, len(self.collection1)-1), None) + + self.assertEqual(interpolation_search_by_recursion(self.collection2, self.item2, 0, len(self.collection2)-1), self.collection2.index(self.item2)) + + self.assertEqual(interpolation_search_by_recursion(self.collection3, self.item3, 0, len(self.collection3)-1), None) + + self.assertEqual(interpolation_search_by_recursion(self.collection4, self.item4, 0, len(self.collection4)-1), self.collection4.index(self.item4)) + + self.assertEqual(interpolation_search_by_recursion(self.collection5, self.item5, 0, len(self.collection5)-1), None) + + self.assertEqual(interpolation_search_by_recursion(self.collection6, self.item6, 0, len(self.collection6)-1), self.collection6.index(self.item6)) + + self.assertEqual(interpolation_search_by_recursion(self.collection7, self.item7, 0, len(self.collection7)-1), None) + + + +if __name__ == '__main__': + unittest.main() From f5abc04176b9635da963ca701f643acde5da24dc Mon Sep 17 00:00:00 2001 From: Andy Lau Date: Sun, 19 May 2019 17:00:54 +0800 Subject: [PATCH 0016/1071] Update bucket_sort.py (#821) * Some simplification --- sorts/bucket_sort.py | 64 +++++++++++++++++++------------------------- 1 file changed, 27 insertions(+), 37 deletions(-) diff --git a/sorts/bucket_sort.py b/sorts/bucket_sort.py index bd4281e463eb..5c17703c26f0 100644 --- a/sorts/bucket_sort.py +++ b/sorts/bucket_sort.py @@ -13,45 +13,35 @@ # Time Complexity of Solution: # Best Case O(n); Average Case O(n); Worst Case O(n) -from __future__ import print_function -from insertion_sort import insertion_sort -import math - -DEFAULT_BUCKET_SIZE = 5 - -def bucketSort(myList, bucketSize=DEFAULT_BUCKET_SIZE): - if(len(myList) == 0): - print('You don\'t have any elements in array!') - - minValue = myList[0] - maxValue = myList[0] - - # For finding minimum and maximum values - for i in range(0, len(myList)): - if myList[i] < minValue: - minValue = myList[i] - elif myList[i] > maxValue: - maxValue = myList[i] - - # Initialize buckets - bucketCount = math.floor((maxValue - minValue) / bucketSize) + 1 - buckets = [] - for i in range(0, bucketCount): +DEFAULT_BUCKET_SIZE=5 +def bucket_sort(my_list,bucket_size=DEFAULT_BUCKET_SIZE): + if(my_list==0): + print("you don't have any elements in array!") + + + min_value=min(my_list) + max_value=max(my_list) + + bucket_count=(max_value-min_value)//bucket_size+1 + buckets=[] + for i in range(bucket_count): buckets.append([]) + for i in range(len(my_list)): + buckets[(my_list[i]-min_value)//bucket_size].append(my_list[i]) + + + sorted_array=[] + for i in range(len(buckets)): + buckets[i].sort() + for j in range(len(buckets[i])): + sorted_array.append(buckets[i][j]) + return sorted_array - # For putting values in buckets - for i in range(0, len(myList)): - buckets[math.floor((myList[i] - minValue) / bucketSize)].append(myList[i]) - # Sort buckets and place back into input array - sortedArray = [] - for i in range(0, len(buckets)): - insertion_sort(buckets[i]) - for j in range(0, len(buckets[i])): - sortedArray.append(buckets[i][j]) - return sortedArray -if __name__ == '__main__': - sortedArray = bucketSort([12, 23, 4, 5, 3, 2, 12, 81, 56, 95]) - print(sortedArray) +#test +#besd on python 3.7.3 +user_input =input('Enter numbers separated by a comma:').strip() +unsorted =[int(item) for item in user_input.split(',')] +print(bucket_sort(unsorted)) From 316d5ffa374a35cb1a0237a7da1e12309da7aece Mon Sep 17 00:00:00 2001 From: DaveAxiom Date: Sun, 19 May 2019 16:36:46 -0400 Subject: [PATCH 0017/1071] Add NQueens backtracking search implementation (#504) --- other/nqueens.py | 77 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 77 insertions(+) create mode 100644 other/nqueens.py diff --git a/other/nqueens.py b/other/nqueens.py new file mode 100644 index 000000000000..1b1c75878ae6 --- /dev/null +++ b/other/nqueens.py @@ -0,0 +1,77 @@ +#! /usr/bin/python3 +import sys + +def nqueens(board_width): + board = [0] + current_row = 0 + while True: + conflict = False + + for review_index in range(0, current_row): + left = board[review_index] - (current_row - review_index) + right = board[review_index] + (current_row - review_index); + if (board[current_row] == board[review_index] or (left >= 0 and left == board[current_row]) or (right < board_width and right == board[current_row])): + conflict = True; + break + + if (current_row == 0 and conflict == False): + board.append(0) + current_row = 1 + continue + + if (conflict == True): + board[current_row] += 1 + + if (current_row == 0 and board[current_row] == board_width): + print("No solution exists for specificed board size.") + return None + + while True: + if (board[current_row] == board_width): + board[current_row] = 0 + if (current_row == 0): + print("No solution exists for specificed board size.") + return None + + board.pop() + current_row -= 1 + board[current_row] += 1 + + if board[current_row] != board_width: + break + else: + current_row += 1 + if (current_row == board_width): + break + + board.append(0) + return board + +def print_board(board): + if (board == None): + return + + board_width = len(board) + for row in range(board_width): + line_print = [] + for column in range(board_width): + if column == board[row]: + line_print.append("Q") + else: + line_print.append(".") + print(line_print) + + +if __name__ == '__main__': + default_width = 8 + for arg in sys.argv: + if (arg.isdecimal() and int(arg) > 3): + default_width = int(arg) + break + + if (default_width == 8): + print("Running algorithm with board size of 8. Specify an alternative Chess board size for N-Queens as a command line argument.") + + board = nqueens(default_width) + print(board) + print_board(board) \ No newline at end of file From c1130490d7534412bea66cb3864e2bb7f7e13dd7 Mon Sep 17 00:00:00 2001 From: Adam <34916469+coderpower0@users.noreply.github.com> Date: Mon, 20 May 2019 21:22:20 +0800 Subject: [PATCH 0018/1071] fix spelling on line 44 of bucket sort (#824) * change besd to best --- sorts/bucket_sort.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sorts/bucket_sort.py b/sorts/bucket_sort.py index 5c17703c26f0..aba0124ad909 100644 --- a/sorts/bucket_sort.py +++ b/sorts/bucket_sort.py @@ -41,7 +41,7 @@ def bucket_sort(my_list,bucket_size=DEFAULT_BUCKET_SIZE): #test -#besd on python 3.7.3 +#best on python 3.7.3 user_input =input('Enter numbers separated by a comma:').strip() unsorted =[int(item) for item in user_input.split(',')] print(bucket_sort(unsorted)) From b5667e5ee98f9f68c8f40dd9691bb9006a5ac832 Mon Sep 17 00:00:00 2001 From: Anirudh Ajith Date: Tue, 21 May 2019 11:36:05 +0530 Subject: [PATCH 0019/1071] Removed the (incorrectly named) redundant file graph_list.py and renamed graph.py to graph_list.py (#820) --- graphs/graph.py | 44 -------------------------- graphs/graph_list.py | 73 ++++++++++++++++++++++++++------------------ 2 files changed, 43 insertions(+), 74 deletions(-) delete mode 100644 graphs/graph.py diff --git a/graphs/graph.py b/graphs/graph.py deleted file mode 100644 index 0c981c39d320..000000000000 --- a/graphs/graph.py +++ /dev/null @@ -1,44 +0,0 @@ -#!/usr/bin/python -# encoding=utf8 - -from __future__ import print_function -# Author: OMKAR PATHAK - -# We can use Python's dictionary for constructing the graph. - -class AdjacencyList(object): - def __init__(self): - self.List = {} - - def addEdge(self, fromVertex, toVertex): - # check if vertex is already present - if fromVertex in self.List.keys(): - self.List[fromVertex].append(toVertex) - else: - self.List[fromVertex] = [toVertex] - - def printList(self): - for i in self.List: - print((i,'->',' -> '.join([str(j) for j in self.List[i]]))) - -if __name__ == '__main__': - al = AdjacencyList() - al.addEdge(0, 1) - al.addEdge(0, 4) - al.addEdge(4, 1) - al.addEdge(4, 3) - al.addEdge(1, 0) - al.addEdge(1, 4) - al.addEdge(1, 3) - al.addEdge(1, 2) - al.addEdge(2, 3) - al.addEdge(3, 4) - - al.printList() - - # OUTPUT: - # 0 -> 1 -> 4 - # 1 -> 0 -> 4 -> 3 -> 2 - # 2 -> 3 - # 3 -> 4 - # 4 -> 1 -> 3 diff --git a/graphs/graph_list.py b/graphs/graph_list.py index d67bc96c4a81..0c981c39d320 100644 --- a/graphs/graph_list.py +++ b/graphs/graph_list.py @@ -1,31 +1,44 @@ -from __future__ import print_function - - -class Graph: - def __init__(self, vertex): - self.vertex = vertex - self.graph = [[0] for i in range(vertex)] - - def add_edge(self, u, v): - self.graph[u - 1].append(v - 1) - - def show(self): - for i in range(self.vertex): - print('%d: '% (i + 1), end=' ') - for j in self.graph[i]: - print('%d-> '% (j + 1), end=' ') - print(' ') - - - -g = Graph(100) - -g.add_edge(1,3) -g.add_edge(2,3) -g.add_edge(3,4) -g.add_edge(3,5) -g.add_edge(4,5) - - -g.show() +#!/usr/bin/python +# encoding=utf8 +from __future__ import print_function +# Author: OMKAR PATHAK + +# We can use Python's dictionary for constructing the graph. + +class AdjacencyList(object): + def __init__(self): + self.List = {} + + def addEdge(self, fromVertex, toVertex): + # check if vertex is already present + if fromVertex in self.List.keys(): + self.List[fromVertex].append(toVertex) + else: + self.List[fromVertex] = [toVertex] + + def printList(self): + for i in self.List: + print((i,'->',' -> '.join([str(j) for j in self.List[i]]))) + +if __name__ == '__main__': + al = AdjacencyList() + al.addEdge(0, 1) + al.addEdge(0, 4) + al.addEdge(4, 1) + al.addEdge(4, 3) + al.addEdge(1, 0) + al.addEdge(1, 4) + al.addEdge(1, 3) + al.addEdge(1, 2) + al.addEdge(2, 3) + al.addEdge(3, 4) + + al.printList() + + # OUTPUT: + # 0 -> 1 -> 4 + # 1 -> 0 -> 4 -> 3 -> 2 + # 2 -> 3 + # 3 -> 4 + # 4 -> 1 -> 3 From 023f5e092d38f7e220ae68a23f7183eeb8fd9e04 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?N=C3=ADkolas=20Vargas?= Date: Wed, 22 May 2019 09:09:36 -0300 Subject: [PATCH 0020/1071] fix empty list validation and code data structures (#826) * fix empty list validation and code data structures * Update bucket_sort.py https://github.com/TheAlgorithms/Python/pull/826#pullrequestreview-240357549 --- sorts/bucket_sort.py | 38 +++++++++++++------------------------- 1 file changed, 13 insertions(+), 25 deletions(-) diff --git a/sorts/bucket_sort.py b/sorts/bucket_sort.py index aba0124ad909..cca913328e40 100644 --- a/sorts/bucket_sort.py +++ b/sorts/bucket_sort.py @@ -14,34 +14,22 @@ # Best Case O(n); Average Case O(n); Worst Case O(n) DEFAULT_BUCKET_SIZE=5 -def bucket_sort(my_list,bucket_size=DEFAULT_BUCKET_SIZE): - if(my_list==0): - print("you don't have any elements in array!") +def bucket_sort(my_list, bucket_size=DEFAULT_BUCKET_SIZE): + if len(my_list) == 0: + raise Exception("Please add some elements in the array.") - min_value=min(my_list) - max_value=max(my_list) + min_value, max_value = (min(my_list), max(my_list)) + bucket_count = ((max_value - min_value) // bucket_size + 1) + buckets = [[] for _ in range(int(bucket_count))] - bucket_count=(max_value-min_value)//bucket_size+1 - buckets=[] - for i in range(bucket_count): - buckets.append([]) for i in range(len(my_list)): - buckets[(my_list[i]-min_value)//bucket_size].append(my_list[i]) + buckets[int((my_list[i] - min_value) // bucket_size)].append(my_list[i]) + return sorted([buckets[i][j] for i in range(len(buckets)) + for j in range(len(buckets[i]))]) - sorted_array=[] - for i in range(len(buckets)): - buckets[i].sort() - for j in range(len(buckets[i])): - sorted_array.append(buckets[i][j]) - return sorted_array - - - - -#test -#best on python 3.7.3 -user_input =input('Enter numbers separated by a comma:').strip() -unsorted =[int(item) for item in user_input.split(',')] -print(bucket_sort(unsorted)) +if __name__ == "__main__": + user_input = input('Enter numbers separated by a comma:').strip() + unsorted = [float(n) for n in user_input.split(',') if len(user_input) > 0] + print(bucket_sort(unsorted)) From a0ab3ce098c95c7edf3f32fedc9d3930d2d641e8 Mon Sep 17 00:00:00 2001 From: BruceLee569 <49506152+BruceLee569@users.noreply.github.com> Date: Fri, 24 May 2019 23:54:03 +0800 Subject: [PATCH 0021/1071] Update quick_sort.py (#830) Modify the list comprehensions to reduce the number of judgments, the speed has increased by more than 50%. --- sorts/quick_sort.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/sorts/quick_sort.py b/sorts/quick_sort.py index e01d319a4b29..223c26fde1fe 100644 --- a/sorts/quick_sort.py +++ b/sorts/quick_sort.py @@ -34,8 +34,16 @@ def quick_sort(collection): return collection else: pivot = collection[0] - greater = [element for element in collection[1:] if element > pivot] - lesser = [element for element in collection[1:] if element <= pivot] + # Modify the list comprehensions to reduce the number of judgments, the speed has increased by more than 50%. + greater = [] + lesser = [] + for element in collection[1:]: + if element > pivot: + greater.append(element) + else: + lesser.append(element) + # greater = [element for element in collection[1:] if element > pivot] + # lesser = [element for element in collection[1:] if element <= pivot] return quick_sort(lesser) + [pivot] + quick_sort(greater) From 9f982a83c8a1f9f5b06a8720eaf78d60485a18fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Henrique=20Ivanchechen?= Date: Fri, 24 May 2019 14:16:39 -0300 Subject: [PATCH 0022/1071] add pigeon hole sort (#833) --- sorts/pigeon_sort.py | 50 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) create mode 100644 sorts/pigeon_sort.py diff --git a/sorts/pigeon_sort.py b/sorts/pigeon_sort.py new file mode 100644 index 000000000000..65eb8896ea9c --- /dev/null +++ b/sorts/pigeon_sort.py @@ -0,0 +1,50 @@ +''' + This is an implementation of Pigeon Hole Sort. +''' + +from __future__ import print_function + +def pigeon_sort(array): + # Manually finds the minimum and maximum of the array. + min = array[0] + max = array[0] + + for i in range(len(array)): + if(array[i] < min): min = array[i] + elif(array[i] > max): max = array[i] + + # Compute the variables + holes_range = max-min + 1 + holes = [0 for _ in range(holes_range)] + holes_repeat = [0 for _ in range(holes_range)] + + # Make the sorting. + for i in range(len(array)): + index = array[i] - min + if(holes[index] != array[i]): + holes[index] = array[i] + holes_repeat[index] += 1 + else: holes_repeat[index] += 1 + + # Makes the array back by replacing the numbers. + index = 0 + for i in range(holes_range): + while(holes_repeat[i] > 0): + array[index] = holes[i] + index += 1 + holes_repeat[i] -= 1 + + # Returns the sorted array. + return array + +if __name__ == '__main__': + try: + raw_input # Python2 + except NameError: + raw_input = input # Python 3 + + user_input = raw_input('Enter numbers separated by comma:\n') + unsorted = [int(x) for x in user_input.split(',')] + sorted = pigeon_sort(unsorted) + + print(sorted) From 02c0daf9e5c7dc44205b9270507348109888877a Mon Sep 17 00:00:00 2001 From: Mehdi ALAOUI Date: Sat, 25 May 2019 15:41:24 +0200 Subject: [PATCH 0023/1071] Adding unit tests for sorting functions, and improving readability on some sorting algorithms (#784) * Adding variable to fade out ambiguity * More readability on merge sorting algorithm * Updating merge_sort_fastest description and explaining why * Adding tests file with imports * Standardazing filenames and function names * Adding test cases and test functions * Adding test loop * Putting 'user oriented code' inside main condition for having valid imports * Fixing condition * Updating tests: adding cases and todo list * Refactoring first euler problem's first solution --- project_euler/problem_01/sol1.py | 6 +-- sorts/{bogosort.py => bogo_sort.py} | 16 +++--- sorts/bucket_sort.py | 2 +- sorts/{cyclesort.py => cycle_sort.py} | 16 +++--- sorts/insertion_sort.py | 10 ++-- sorts/merge_sort.py | 2 +- sorts/merge_sort_fastest.py | 2 +- sorts/pancake_sort.py | 5 +- sorts/radix_sort.py | 2 +- sorts/tests.py | 74 +++++++++++++++++++++++++++ sorts/{timsort.py => tim_sort.py} | 4 +- sorts/topological_sort.py | 6 +-- sorts/tree_sort.py | 5 +- sorts/wiggle_sort.py | 18 +++---- 14 files changed, 120 insertions(+), 48 deletions(-) rename sorts/{bogosort.py => bogo_sort.py} (81%) rename sorts/{cyclesort.py => cycle_sort.py} (83%) create mode 100644 sorts/tests.py rename sorts/{timsort.py => tim_sort.py} (97%) diff --git a/project_euler/problem_01/sol1.py b/project_euler/problem_01/sol1.py index 27031c3cfa9a..c9a8c0f1ebeb 100644 --- a/project_euler/problem_01/sol1.py +++ b/project_euler/problem_01/sol1.py @@ -10,8 +10,4 @@ except NameError: raw_input = input # Python 3 n = int(raw_input().strip()) -sum=0 -for a in range(3,n): - if(a%3==0 or a%5==0): - sum+=a -print(sum) +print(sum([e for e in range(3, n) if e % 3 == 0 or e % 5 == 0])) diff --git a/sorts/bogosort.py b/sorts/bogo_sort.py similarity index 81% rename from sorts/bogosort.py rename to sorts/bogo_sort.py index 33eac66bf21c..056e8e68a92e 100644 --- a/sorts/bogosort.py +++ b/sorts/bogo_sort.py @@ -1,28 +1,28 @@ """ This is a pure python implementation of the bogosort algorithm For doctests run following command: -python -m doctest -v bogosort.py +python -m doctest -v bogo_sort.py or -python3 -m doctest -v bogosort.py +python3 -m doctest -v bogo_sort.py For manual testing run: -python bogosort.py +python bogo_sort.py """ from __future__ import print_function import random -def bogosort(collection): +def bogo_sort(collection): """Pure implementation of the bogosort algorithm in Python :param collection: some mutable ordered collection with heterogeneous comparable items inside :return: the same collection ordered by ascending Examples: - >>> bogosort([0, 5, 3, 2, 2]) + >>> bogo_sort([0, 5, 3, 2, 2]) [0, 2, 2, 3, 5] - >>> bogosort([]) + >>> bogo_sort([]) [] - >>> bogosort([-2, -5, -45]) + >>> bogo_sort([-2, -5, -45]) [-45, -5, -2] """ @@ -46,4 +46,4 @@ def isSorted(collection): user_input = raw_input('Enter numbers separated by a comma:\n').strip() unsorted = [int(item) for item in user_input.split(',')] - print(bogosort(unsorted)) + print(bogo_sort(unsorted)) diff --git a/sorts/bucket_sort.py b/sorts/bucket_sort.py index cca913328e40..c4d61874fc47 100644 --- a/sorts/bucket_sort.py +++ b/sorts/bucket_sort.py @@ -32,4 +32,4 @@ def bucket_sort(my_list, bucket_size=DEFAULT_BUCKET_SIZE): if __name__ == "__main__": user_input = input('Enter numbers separated by a comma:').strip() unsorted = [float(n) for n in user_input.split(',') if len(user_input) > 0] - print(bucket_sort(unsorted)) + print(bucket_sort(unsorted)) \ No newline at end of file diff --git a/sorts/cyclesort.py b/sorts/cycle_sort.py similarity index 83% rename from sorts/cyclesort.py rename to sorts/cycle_sort.py index ee19a1ade360..492022164427 100644 --- a/sorts/cyclesort.py +++ b/sorts/cycle_sort.py @@ -50,11 +50,11 @@ def cycle_sort(array): except NameError: raw_input = input # Python 3 -user_input = raw_input('Enter numbers separated by a comma:\n') -unsorted = [int(item) for item in user_input.split(',')] -n = len(unsorted) -cycle_sort(unsorted) - -print("After sort : ") -for i in range(0, n): - print(unsorted[i], end=' ') + user_input = raw_input('Enter numbers separated by a comma:\n') + unsorted = [int(item) for item in user_input.split(',')] + n = len(unsorted) + cycle_sort(unsorted) + + print("After sort : ") + for i in range(0, n): + print(unsorted[i], end=' ') diff --git a/sorts/insertion_sort.py b/sorts/insertion_sort.py index 59917ac059a7..e088705947d4 100644 --- a/sorts/insertion_sort.py +++ b/sorts/insertion_sort.py @@ -29,10 +29,12 @@ def insertion_sort(collection): >>> insertion_sort([-2, -5, -45]) [-45, -5, -2] """ - for index in range(1, len(collection)): - while index > 0 and collection[index - 1] > collection[index]: - collection[index], collection[index - 1] = collection[index - 1], collection[index] - index -= 1 + + for loop_index in range(1, len(collection)): + insertion_index = loop_index + while insertion_index > 0 and collection[insertion_index - 1] > collection[insertion_index]: + collection[insertion_index], collection[insertion_index - 1] = collection[insertion_index - 1], collection[insertion_index] + insertion_index -= 1 return collection diff --git a/sorts/merge_sort.py b/sorts/merge_sort.py index 4a6201a40cb4..ecbad7075119 100644 --- a/sorts/merge_sort.py +++ b/sorts/merge_sort.py @@ -53,4 +53,4 @@ def merge(left, right): user_input = raw_input('Enter numbers separated by a comma:\n').strip() unsorted = [int(item) for item in user_input.split(',')] - print(*merge_sort(unsorted), sep=',') + print(*merge_sort(unsorted), sep=',') \ No newline at end of file diff --git a/sorts/merge_sort_fastest.py b/sorts/merge_sort_fastest.py index 86cb8ae1a699..bd356c935ca0 100644 --- a/sorts/merge_sort_fastest.py +++ b/sorts/merge_sort_fastest.py @@ -2,7 +2,7 @@ Python implementation of the fastest merge sort algorithm. Takes an average of 0.6 microseconds to sort a list of length 1000 items. Best Case Scenario : O(n) -Worst Case Scenario : O(n) +Worst Case Scenario : O(n^2) because native python functions:min, max and remove are already O(n) ''' from __future__ import print_function diff --git a/sorts/pancake_sort.py b/sorts/pancake_sort.py index 26fd40b7f67c..478a9a967d27 100644 --- a/sorts/pancake_sort.py +++ b/sorts/pancake_sort.py @@ -1,7 +1,7 @@ # Pancake sort algorithm # Only can reverse array from 0 to i -def pancakesort(arr): +def pancake_sort(arr): cur = len(arr) while cur > 1: # Find the maximum number in arr @@ -13,4 +13,5 @@ def pancakesort(arr): cur -= 1 return arr -print(pancakesort([0,10,15,3,2,9,14,13])) +if __name__ == '__main__': + print(pancake_sort([0,10,15,3,2,9,14,13])) diff --git a/sorts/radix_sort.py b/sorts/radix_sort.py index e4cee61f35e3..8dfc66b17b23 100644 --- a/sorts/radix_sort.py +++ b/sorts/radix_sort.py @@ -1,4 +1,4 @@ -def radixsort(lst): +def radix_sort(lst): RADIX = 10 placement = 1 diff --git a/sorts/tests.py b/sorts/tests.py new file mode 100644 index 000000000000..225763625f51 --- /dev/null +++ b/sorts/tests.py @@ -0,0 +1,74 @@ +from bogo_sort import bogo_sort +from bubble_sort import bubble_sort +from bucket_sort import bucket_sort +from cocktail_shaker_sort import cocktail_shaker_sort +from comb_sort import comb_sort +from counting_sort import counting_sort +from cycle_sort import cycle_sort +from gnome_sort import gnome_sort +from heap_sort import heap_sort +from insertion_sort import insertion_sort +from merge_sort_fastest import merge_sort as merge_sort_fastest +from merge_sort import merge_sort +from pancake_sort import pancake_sort +from quick_sort_3_partition import quick_sort_3partition +from quick_sort import quick_sort +from radix_sort import radix_sort +from random_pivot_quick_sort import quick_sort_random +from selection_sort import selection_sort +from shell_sort import shell_sort +from tim_sort import tim_sort +from topological_sort import topological_sort +from tree_sort import tree_sort +from wiggle_sort import wiggle_sort + + +TEST_CASES = [ + {'input': [8, 7, 6, 5, 4, 3, -2, -5], 'expected': [-5, -2, 3, 4, 5, 6, 7, 8]}, + {'input': [-5, -2, 3, 4, 5, 6, 7, 8], 'expected': [-5, -2, 3, 4, 5, 6, 7, 8]}, + {'input': [5, 6, 1, 4, 0, 1, -2, -5, 3, 7], 'expected': [-5, -2, 0, 1, 1, 3, 4, 5, 6, 7]}, + {'input': [2, -2], 'expected': [-2, 2]}, + {'input': [1], 'expected': [1]}, + {'input': [], 'expected': []}, +] + +''' + TODO: + - Fix some broken tests in particular cases (as [] for example), + - Unify the input format: should always be function(input_collection) (no additional args) + - Unify the output format: should always be a collection instead of updating input elements + and returning None + - Rewrite some algorithms in function format (in case there is no function definition) +''' + +TEST_FUNCTIONS = [ + bogo_sort, + bubble_sort, + bucket_sort, + cocktail_shaker_sort, + comb_sort, + counting_sort, + cycle_sort, + gnome_sort, + heap_sort, + insertion_sort, + merge_sort_fastest, + merge_sort, + pancake_sort, + quick_sort_3partition, + quick_sort, + radix_sort, + quick_sort_random, + selection_sort, + shell_sort, + tim_sort, + topological_sort, + tree_sort, + wiggle_sort, +] + + +for function in TEST_FUNCTIONS: + for case in TEST_CASES: + result = function(case['input']) + assert result == case['expected'], 'Executed function: {}, {} != {}'.format(function.__name__, result, case['expected']) diff --git a/sorts/timsort.py b/sorts/tim_sort.py similarity index 97% rename from sorts/timsort.py rename to sorts/tim_sort.py index 80c5cd1e8d3f..b4032b91aec1 100644 --- a/sorts/timsort.py +++ b/sorts/tim_sort.py @@ -41,7 +41,7 @@ def merge(left, right): return [right[0]] + merge(left, right[1:]) -def timsort(lst): +def tim_sort(lst): runs, sorted_runs = [], [] length = len(lst) new_run = [lst[0]] @@ -75,7 +75,7 @@ def timsort(lst): def main(): lst = [5,9,10,3,-4,5,178,92,46,-18,0,7] - sorted_lst = timsort(lst) + sorted_lst = tim_sort(lst) print(sorted_lst) if __name__ == '__main__': diff --git a/sorts/topological_sort.py b/sorts/topological_sort.py index 52dc34f4f733..db4dd250a119 100644 --- a/sorts/topological_sort.py +++ b/sorts/topological_sort.py @@ -28,6 +28,6 @@ def topological_sort(start, visited, sort): # return sort return sort - -sort = topological_sort('a', [], []) -print(sort) +if __name__ == '__main__': + sort = topological_sort('a', [], []) + print(sort) diff --git a/sorts/tree_sort.py b/sorts/tree_sort.py index f8ecf84c6ff8..d06b0de28e56 100644 --- a/sorts/tree_sort.py +++ b/sorts/tree_sort.py @@ -30,7 +30,7 @@ def inorder(root, res): res.append(root.val) inorder(root.right,res) -def treesort(arr): +def tree_sort(arr): # Build BST if len(arr) == 0: return arr @@ -42,4 +42,5 @@ def treesort(arr): inorder(root,res) return res -print(treesort([10,1,3,2,9,14,13])) \ No newline at end of file +if __name__ == '__main__': + print(tree_sort([10,1,3,2,9,14,13])) diff --git a/sorts/wiggle_sort.py b/sorts/wiggle_sort.py index cc83487bdeb1..0d4f20e3f96b 100644 --- a/sorts/wiggle_sort.py +++ b/sorts/wiggle_sort.py @@ -9,13 +9,11 @@ def wiggle_sort(nums): if (i % 2 == 1) == (nums[i-1] > nums[i]): nums[i-1], nums[i] = nums[i], nums[i-1] - -print("Enter the array elements:\n") -array=list(map(int,input().split())) -print("The unsorted array is:\n") -print(array) -wiggle_sort(array) -print("Array after Wiggle sort:\n") -print(array) - - +if __name__ == '__main__': + print("Enter the array elements:\n") + array=list(map(int,input().split())) + print("The unsorted array is:\n") + print(array) + wiggle_sort(array) + print("Array after Wiggle sort:\n") + print(array) From 94380a17a806e28fc73695d84581d638eabd286a Mon Sep 17 00:00:00 2001 From: Artyom Belousov Date: Sun, 26 May 2019 01:20:37 +0300 Subject: [PATCH 0024/1071] Added treap (#797) * Added treap * Added comments to treap --- data_structures/binary tree/treap.py | 129 +++++++++++++++++++++++++++ 1 file changed, 129 insertions(+) create mode 100644 data_structures/binary tree/treap.py diff --git a/data_structures/binary tree/treap.py b/data_structures/binary tree/treap.py new file mode 100644 index 000000000000..0399ff67030a --- /dev/null +++ b/data_structures/binary tree/treap.py @@ -0,0 +1,129 @@ +from random import random +from typing import Tuple + + +class Node: + """ + Treap's node + Treap is a binary tree by key and heap by priority + """ + def __init__(self, key: int): + self.key = key + self.prior = random() + self.l = None + self.r = None + + +def split(root: Node, key: int) -> Tuple[Node, Node]: + """ + We split current tree into 2 trees with key: + + Left tree contains all keys less than split key. + Right tree contains all keys greater or equal, than split key + """ + if root is None: # None tree is split into 2 Nones + return (None, None) + if root.key >= key: + """ + Right tree's root will be current node. + Now we split(with the same key) current node's left son + Left tree: left part of that split + Right tree's left son: right part of that split + """ + l, root.l = split(root.l, key) + return (l, root) + else: + """ + Just symmetric to previous case + """ + root.r, r = split(root.r, key) + return (root, r) + + +def merge(left: Node, right: Node) -> Node: + """ + We merge 2 trees into one. + Note: all left tree's keys must be less than all right tree's + """ + if (not left) or (not right): + """ + If one node is None, return the other + """ + return left or right + if left.key > right.key: + """ + Left will be root because it has more priority + Now we need to merge left's right son and right tree + """ + left.r = merge(left.r, right) + return left + else: + """ + Symmetric as well + """ + right.l = merge(left, right.l) + return right + + +def insert(root: Node, key: int) -> Node: + """ + Insert element + + Split current tree with a key into l, r, + Insert new node into the middle + Merge l, node, r into root + """ + node = Node(key) + l, r = split(root, key) + root = merge(l, node) + root = merge(root, r) + return root + + +def erase(root: Node, key: int) -> Node: + """ + Erase element + + Split all nodes with keys less into l, + Split all nodes with keys greater into r. + Merge l, r + """ + l, r = split(root, key) + _, r = split(r, key + 1) + return merge(l, r) + + +def node_print(root: Node): + """ + Just recursive print of a tree + """ + if not root: + return + node_print(root.l) + print(root.key, end=" ") + node_print(root.r) + + +def interactTreap(): + """ + Commands: + + key to add key into treap + - key to erase all nodes with key + + After each command, program prints treap + """ + root = None + while True: + cmd = input().split() + cmd[1] = int(cmd[1]) + if cmd[0] == "+": + root = insert(root, cmd[1]) + elif cmd[0] == "-": + root = erase(root, cmd[1]) + else: + print("Unknown command") + node_print(root) + + +if __name__ == "__main__": + interactTreap() From 23938708ac53f6544f013a6dbb292beb35e14177 Mon Sep 17 00:00:00 2001 From: Anup Kumar Panwar <1anuppanwar@gmail.com> Date: Sun, 26 May 2019 17:11:41 +0530 Subject: [PATCH 0025/1071] Update README.md --- README.md | 354 ++---------------------------------------------------- 1 file changed, 9 insertions(+), 345 deletions(-) diff --git a/README.md b/README.md index 9b61f1b63287..8172df62309f 100644 --- a/README.md +++ b/README.md @@ -4,352 +4,16 @@ ### All algorithms implemented in Python (for education) -These implementations are for demonstration purposes. They are less efficient than the implementations in the Python standard library. +These implementations are for learning purposes. They may be efficient than the implementations in the Python standard library. -## Sorting Algorithms +## Contribution Guidelines +* File name should be in camel case. +* Write proper documentation of the code. +* Avoid input methods as far as possible. Assign values to the variables statically. This will make the code easy to understand and algorithm can be traced easily. +* Add a corresponding explaination to [Algorithms-Explanation](https://github.com/TheAlgorithms/Algorithms-Explanation) (Optional but recommended). +* Avoid importing external libraries for basic algorithms. -### Bubble Sort -![alt text][bubble-image] +## Community Channel -**Bubble sort**, sometimes referred to as *sinking sort*, is a simple sorting algorithm that repeatedly steps through the list to be sorted, compares each pair of adjacent items and swaps them if they are in the wrong order. The pass through the list is repeated until no swaps are needed, which indicates that the list is sorted. - -__Properties__ -* Worst case performance O(n2) -* Best case performance O(n) -* Average case performance O(n2) - -###### Source: [Wikipedia][bubble-wiki] -###### View the algorithm in [action][bubble-toptal] - -### Bucket -![alt text][bucket-image-1] -![alt text][bucket-image-2] - -**Bucket sort**, or _bin sort_, is a sorting algorithm that works by distributing the elements of an array into a number of buckets. Each bucket is then sorted individually, either using a different sorting algorithm, or by recursively applying the bucket sorting algorithm. - -__Properties__ -* Worst case performance O(n2) -* Best case performance O(n+k) -* Average case performance O(n+k) - -###### Source: [Wikipedia][bucket-wiki] - - -### Cocktail shaker -![alt text][cocktail-shaker-image] - -**Cocktail shaker sort**, also known as _bidirectional bubble sort_, _cocktail sort_, _shaker sort_ (which can also refer to a variant of _selection sort_), _ripple sort_, _shuffle sort_, or _shuttle sort_, is a variation of bubble sort that is both a stable sorting algorithm and a comparison sort. The algorithm differs from a bubble sort in that it sorts in both directions on each pass through the list. - -__Properties__ -* Worst case performance O(n2) -* Best case performance O(n) -* Average case performance O(n2) - -###### Source: [Wikipedia][cocktail-shaker-wiki] - - -### Insertion Sort -![alt text][insertion-image] - -**Insertion sort** is a simple sorting algorithm that builds the final sorted array (or list) one item at a time. It is much less efficient on *large* lists than more advanced algorithms such as quicksort, heapsort, or merge sort. - -__Properties__ -* Worst case performance O(n2) -* Best case performance O(n) -* Average case performance O(n2) - -###### Source: [Wikipedia][insertion-wiki] -###### View the algorithm in [action][insertion-toptal] - - -### Merge Sort -![alt text][merge-image] - -**Merge sort** (also commonly spelled *mergesort*) is an efficient, general-purpose, comparison-based sorting algorithm. Most implementations produce a stable sort, which means that the implementation preserves the input order of equal elements in the sorted output. Mergesort is a divide and conquer algorithm that was invented by John von Neumann in 1945. - -__Properties__ -* Worst case performance O(n log n) -* Best case performance O(n log n) -* Average case performance O(n log n) - -###### Source: [Wikipedia][merge-wiki] -###### View the algorithm in [action][merge-toptal] - -### Quick -![alt text][quick-image] - -**Quicksort** (sometimes called *partition-exchange sort*) is an efficient sorting algorithm, serving as a systematic method for placing the elements of an array in order. - -__Properties__ -* Worst case performance O(n2) -* Best case performance O(*n* log *n*) or O(n) with three-way partition -* Average case performance O(*n* log *n*) - -###### Source: [Wikipedia][quick-wiki] -###### View the algorithm in [action][quick-toptal] - - -### Heap -![alt text][heapsort-image] - -**Heapsort** is a _comparison-based_ sorting algorithm. It can be thought of as an improved selection sort. It divides its input into a sorted and an unsorted region, and it iteratively shrinks the unsorted region by extracting the largest element and moving that to the sorted region. - -__Properties__ -* Worst case performance O(*n* log *n*) -* Best case performance O(*n* log *n*) -* Average case performance O(*n* log *n*) - -###### Source: [Wikipedia][heap-wiki] -###### View the algorithm in [action](https://www.toptal.com/developers/sorting-algorithms/heap-sort) - - -### Radix - -From [Wikipedia][radix-wiki]: Radix sort is a non-comparative integer sorting algorithm that sorts data with integer keys by grouping keys by the individual digits which share the same significant position and value. - -__Properties__ -* Worst case performance O(wn) -* Best case performance O(wn) -* Average case performance O(wn) - -###### Source: [Wikipedia][radix-wiki] - - -### Selection -![alt text][selection-image] - -**Selection sort** is an algorithm that divides the input list into two parts: the sublist of items already sorted, which is built up from left to right at the front (left) of the list, and the sublist of items remaining to be sorted that occupy the rest of the list. Initially, the sorted sublist is empty and the unsorted sublist is the entire input list. The algorithm proceeds by finding the smallest (or largest, depending on sorting order) element in the unsorted sublist, exchanging (swapping) it with the leftmost unsorted element (putting it in sorted order), and moving the sublist boundaries one element to the right. - -__Properties__ -* Worst case performance O(n2) -* Best case performance O(n2) -* Average case performance O(n2) - -###### Source: [Wikipedia][selection-wiki] -###### View the algorithm in [action][selection-toptal] - - -### Shell -![alt text][shell-image] - -**Shellsort** is a generalization of *insertion sort* that allows the exchange of items that are far apart. The idea is to arrange the list of elements so that, starting anywhere, considering every nth element gives a sorted list. Such a list is said to be h-sorted. Equivalently, it can be thought of as h interleaved lists, each individually sorted. - -__Properties__ -* Worst case performance O(*n*log2*n*) -* Best case performance O(*n* log *n*) -* Average case performance depends on gap sequence - -###### Source: [Wikipedia][shell-wiki] -###### View the algorithm in [action][shell-toptal] - - -### Topological - -From [Wikipedia][topological-wiki]: **Topological sort**, or _topological ordering of a directed graph_ is a linear ordering of its vertices such that for every directed edge _uv_ from vertex _u_ to vertex _v_, _u_ comes before _v_ in the ordering. For instance, the vertices of the graph may represent tasks to be performed, and the edges may represent constraints that one task must be performed before another; in this application, a topological ordering is just a valid sequence for the tasks. A topological ordering is possible if and only if the graph has no directed cycles, that is, if it is a _directed acyclic graph_ (DAG). Any DAG has at least one topological ordering, and algorithms are known for constructing a topological ordering of any DAG in linear time. - -### Time-Complexity Graphs - -Comparing the complexity of sorting algorithms (*Bubble Sort*, *Insertion Sort*, *Selection Sort*) - -![Complexity Graphs](https://github.com/prateekiiest/Python/blob/master/sorts/sortinggraphs.png) - -Comparing the sorting algorithms: -
-Quicksort is a very fast algorithm but can be pretty tricky to implement -
-Bubble sort is a slow algorithm but is very easy to implement. To sort small sets of data, bubble sort may be a better option since it can be implemented quickly, but for larger datasets, the speedup from quicksort might be worth the trouble implementing the algorithm. - ----------------------------------------------------------------------------------- - -## Search Algorithms - -### Linear -![alt text][linear-image] - -**Linear search** or *sequential search* is a method for finding a target value within a list. It sequentially checks each element of the list for the target value until a match is found or until all the elements have been searched. Linear search runs in at worst linear time and makes at most n comparisons, where n is the length of the list. - -__Properties__ -* Worst case performance O(n) -* Best case performance O(1) -* Average case performance O(n) -* Worst case space complexity O(1) iterative - -###### Source: [Wikipedia][linear-wiki] - - -### Binary -![alt text][binary-image] - -**Binary search**, also known as *half-interval search* or *logarithmic search*, is a search algorithm that finds the position of a target value within a sorted array. It compares the target value to the middle element of the array; if they are unequal, the half in which the target cannot lie is eliminated and the search continues on the remaining half until it is successful. - -__Properties__ -* Worst case performance O(log n) -* Best case performance O(1) -* Average case performance O(log n) -* Worst case space complexity O(1) - -###### Source: [Wikipedia][binary-wiki] - - -## Interpolation -**Interpolation search** is an algorithm for searching for a key in an array that has been ordered by numerical values assigned to the keys (key values). It was first described by W. W. Peterson in 1957. Interpolation search resembles the method by which people search a telephone directory for a name (the key value by which the book's entries are ordered): in each step the algorithm calculates where in the remaining search space the sought item might be, based on the key values at the bounds of the search space and the value of the sought key, usually via a linear interpolation. The key value actually found at this estimated position is then compared to the key value being sought. If it is not equal, then depending on the comparison, the remaining search space is reduced to the part before or after the estimated position. This method will only work if calculations on the size of differences between key values are sensible. - -By comparison, binary search always chooses the middle of the remaining search space, discarding one half or the other, depending on the comparison between the key found at the estimated position and the key sought — it does not require numerical values for the keys, just a total order on them. The remaining search space is reduced to the part before or after the estimated position. The linear search uses equality only as it compares elements one-by-one from the start, ignoring any sorting. - -On average the interpolation search makes about log(log(n)) comparisons (if the elements are uniformly distributed), where n is the number of elements to be searched. In the worst case (for instance where the numerical values of the keys increase exponentially) it can make up to O(n) comparisons. - -In interpolation-sequential search, interpolation is used to find an item near the one being searched for, then linear search is used to find the exact item. - -###### Source: [Wikipedia][interpolation-wiki] - - -## Jump Search -**Jump search** or _block search_ refers to a search algorithm for ordered lists. It works by first checking all items Lkm, where {\displaystyle k\in \mathbb {N} } k\in \mathbb {N} and m is the block size, until an item is found that is larger than the search key. To find the exact position of the search key in the list a linear search is performed on the sublist L[(k-1)m, km]. - -The optimal value of m is √n, where n is the length of the list L. Because both steps of the algorithm look at, at most, √n items the algorithm runs in O(√n) time. This is better than a linear search, but worse than a binary search. The advantage over the latter is that a jump search only needs to jump backwards once, while a binary can jump backwards up to log n times. This can be important if a jumping backwards takes significantly more time than jumping forward. - -The algorithm can be modified by performing multiple levels of jump search on the sublists, before finally performing the linear search. For an k-level jump search the optimum block size ml for the lth level (counting from 1) is n(k-l)/k. The modified algorithm will perform k backward jumps and runs in O(kn1/(k+1)) time. - -###### Source: [Wikipedia][jump-wiki] - - -## Quick Select -![alt text][QuickSelect-image] - -**Quick Select** is a selection algorithm to find the kth smallest element in an unordered list. It is related to the quicksort sorting algorithm. Like quicksort, it was developed by Tony Hoare, and thus is also known as Hoare's selection algorithm.[1] Like quicksort, it is efficient in practice and has good average-case performance, but has poor worst-case performance. Quickselect and its variants are the selection algorithms most often used in efficient real-world implementations. - -Quickselect uses the same overall approach as quicksort, choosing one element as a pivot and partitioning the data in two based on the pivot, accordingly as less than or greater than the pivot. However, instead of recursing into both sides, as in quicksort, quickselect only recurses into one side – the side with the element it is searching for. This reduces the average complexity from O(n log n) to O(n), with a worst case of O(n2). - -As with quicksort, quickselect is generally implemented as an in-place algorithm, and beyond selecting the k'th element, it also partially sorts the data. See selection algorithm for further discussion of the connection with sorting. - -###### Source: [Wikipedia][quick-wiki] - - -## Tabu -**Tabu search** uses a local or neighborhood search procedure to iteratively move from one potential solution {\displaystyle x} x to an improved solution {\displaystyle x'} x' in the neighborhood of {\displaystyle x} x, until some stopping criterion has been satisfied (generally, an attempt limit or a score threshold). Local search procedures often become stuck in poor-scoring areas or areas where scores plateau. In order to avoid these pitfalls and explore regions of the search space that would be left unexplored by other local search procedures, tabu search carefully explores the neighborhood of each solution as the search progresses. The solutions admitted to the new neighborhood, {\displaystyle N^{*}(x)} N^*(x), are determined through the use of memory structures. Using these memory structures, the search progresses by iteratively moving from the current solution {\displaystyle x} x to an improved solution {\displaystyle x'} x' in {\displaystyle N^{*}(x)} N^*(x). - -These memory structures form what is known as the tabu list, a set of rules and banned solutions used to filter which solutions will be admitted to the neighborhood {\displaystyle N^{*}(x)} N^*(x) to be explored by the search. In its simplest form, a tabu list is a short-term set of the solutions that have been visited in the recent past (less than {\displaystyle n} n iterations ago, where {\displaystyle n} n is the number of previous solutions to be stored — is also called the tabu tenure). More commonly, a tabu list consists of solutions that have changed by the process of moving from one solution to another. It is convenient, for ease of description, to understand a “solution” to be coded and represented by such attributes. - -###### Source: [Wikipedia][tabu-wiki] - ----------------------------------------------------------------------------------------------------------------------- - -## Ciphers - -### Caesar -![alt text][caesar] - -**Caesar cipher**, also known as _Caesar's cipher_, the _shift cipher_, _Caesar's code_ or _Caesar shift_, is one of the simplest and most widely known encryption techniques.
-It is **a type of substitution cipher** in which each letter in the plaintext is replaced by a letter some fixed number of positions down the alphabet. For example, with a left shift of 3, D would be replaced by A, E would become B, and so on.
-The method is named after **Julius Caesar**, who used it in his private correspondence.
-The encryption step performed by a Caesar cipher is often incorporated as part of more complex schemes, such as the Vigenère cipher, and still has modern application in the ROT13 system. As with all single-alphabet substitution ciphers, the Caesar cipher is easily broken and in modern practice offers essentially no communication security. - -###### Source: [Wikipedia](https://en.wikipedia.org/wiki/Caesar_cipher) - - -### Vigenère - -**Vigenère cipher** is a method of encrypting alphabetic text by using a series of **interwoven Caesar ciphers** based on the letters of a keyword. It is **a form of polyalphabetic substitution**.
-The Vigenère cipher has been reinvented many times. The method was originally described by Giovan Battista Bellaso in his 1553 book La cifra del. Sig. Giovan Battista Bellaso; however, the scheme was later misattributed to Blaise de Vigenère in the 19th century, and is now widely known as the "Vigenère cipher".
-Though the cipher is easy to understand and implement, for three centuries it resisted all attempts to break it; this earned it the description **le chiffre indéchiffrable**(French for 'the indecipherable cipher'). -Many people have tried to implement encryption schemes that are essentially Vigenère ciphers. Friedrich Kasiski was the first to publish a general method of deciphering a Vigenère cipher in 1863. - -###### Source: [Wikipedia](https://en.wikipedia.org/wiki/Vigen%C3%A8re_cipher) - - -### Transposition - -**Transposition cipher** is a method of encryption by which the positions held by units of *plaintext* (which are commonly characters or groups of characters) are shifted according to a regular system, so that the *ciphertext* constitutes a permutation of the plaintext. That is, the order of the units is changed (the plaintext is reordered).
- -Mathematically a bijective function is used on the characters' positions to encrypt and an inverse function to decrypt. - -###### Source: [Wikipedia](https://en.wikipedia.org/wiki/Transposition_cipher) - - -### RSA (Rivest–Shamir–Adleman) -**RSA** _(Rivest–Shamir–Adleman)_ is one of the first public-key cryptosystems and is widely used for secure data transmission. In such a cryptosystem, the encryption key is public and it is different from the decryption key which is kept secret (private). In RSA, this asymmetry is based on the practical difficulty of the factorization of the product of two large prime numbers, the "factoring problem". The acronym RSA is made of the initial letters of the surnames of Ron Rivest, Adi Shamir, and Leonard Adleman, who first publicly described the algorithm in 1978. Clifford Cocks, an English mathematician working for the British intelligence agency Government Communications Headquarters (GCHQ), had developed an equivalent system in 1973, but this was not declassified until 1997.[1] - -A user of RSA creates and then publishes a public key based on two large prime numbers, along with an auxiliary value. The prime numbers must be kept secret. Anyone can use the public key to encrypt a message, but with currently published methods, and if the public key is large enough, only someone with knowledge of the prime numbers can decode the message feasibly.[2] Breaking RSA encryption is known as the RSA problem. Whether it is as difficult as the factoring problem remains an open question. - -###### Source: [Wikipedia](https://en.wikipedia.org/wiki/RSA_(cryptosystem)) - - -## ROT13 -![alt text][ROT13-image] - -**ROT13** ("rotate by 13 places", sometimes hyphenated _ROT-13_) is a simple letter substitution cipher that replaces a letter with the 13th letter after it, in the alphabet. ROT13 is a special case of the Caesar cipher which was developed in ancient Rome. - -Because there are 26 letters (2×13) in the basic Latin alphabet, ROT13 is its own inverse; that is, to undo ROT13, the same algorithm is applied, so the same action can be used for encoding and decoding. The algorithm provides virtually no cryptographic security, and is often cited as a canonical example of weak encryption.[1] - -###### Source: [Wikipedia](https://en.wikipedia.org/wiki/ROT13) - - -## XOR -**XOR cipher** is a simple type of additive cipher,[1] an encryption algorithm that operates according to the principles: - -A {\displaystyle \oplus } \oplus 0 = A, -A {\displaystyle \oplus } \oplus A = 0, -(A {\displaystyle \oplus } \oplus B) {\displaystyle \oplus } \oplus C = A {\displaystyle \oplus } \oplus (B {\displaystyle \oplus } \oplus C), -(B {\displaystyle \oplus } \oplus A) {\displaystyle \oplus } \oplus A = B {\displaystyle \oplus } \oplus 0 = B, -where {\displaystyle \oplus } \oplus denotes the exclusive disjunction (XOR) operation. This operation is sometimes called modulus 2 addition (or subtraction, which is identical).[2] With this logic, a string of text can be encrypted by applying the bitwise XOR operator to every character using a given key. To decrypt the output, merely reapplying the XOR function with the key will remove the cipher. - -###### Source: [Wikipedia](https://en.wikipedia.org/wiki/XOR_cipher) - - -[bubble-toptal]: https://www.toptal.com/developers/sorting-algorithms/bubble-sort -[bubble-wiki]: https://en.wikipedia.org/wiki/Bubble_sort -[bubble-image]: https://upload.wikimedia.org/wikipedia/commons/thumb/8/83/Bubblesort-edited-color.svg/220px-Bubblesort-edited-color.svg.png "Bubble Sort" - -[bucket-wiki]: https://en.wikipedia.org/wiki/Bucket_sort -[bucket-image-1]: https://upload.wikimedia.org/wikipedia/commons/thumb/6/61/Bucket_sort_1.svg/311px-Bucket_sort_1.svg.png "Bucket Sort" -[bucket-image-2]: https://upload.wikimedia.org/wikipedia/commons/thumb/e/e3/Bucket_sort_2.svg/311px-Bucket_sort_2.svg.png "Bucket Sort" - -[cocktail-shaker-wiki]: https://en.wikipedia.org/wiki/Cocktail_shaker_sort -[cocktail-shaker-image]: https://upload.wikimedia.org/wikipedia/commons/e/ef/Sorting_shaker_sort_anim.gif "Cocktail Shaker Sort" - -[insertion-toptal]: https://www.toptal.com/developers/sorting-algorithms/insertion-sort -[insertion-wiki]: https://en.wikipedia.org/wiki/Insertion_sort -[insertion-image]: https://upload.wikimedia.org/wikipedia/commons/7/7e/Insertionsort-edited.png "Insertion Sort" - -[quick-toptal]: https://www.toptal.com/developers/sorting-algorithms/quick-sort -[quick-wiki]: https://en.wikipedia.org/wiki/Quicksort -[quick-image]: https://upload.wikimedia.org/wikipedia/commons/6/6a/Sorting_quicksort_anim.gif "Quick Sort" - -[heapsort-image]: https://upload.wikimedia.org/wikipedia/commons/4/4d/Heapsort-example.gif "Heap Sort" -[heap-wiki]: https://en.wikipedia.org/wiki/Heapsort - -[radix-wiki]: https://en.wikipedia.org/wiki/Radix_sort - -[merge-toptal]: https://www.toptal.com/developers/sorting-algorithms/merge-sort -[merge-wiki]: https://en.wikipedia.org/wiki/Merge_sort -[merge-image]: https://upload.wikimedia.org/wikipedia/commons/c/cc/Merge-sort-example-300px.gif "Merge Sort" - -[selection-toptal]: https://www.toptal.com/developers/sorting-algorithms/selection-sort -[selection-wiki]: https://en.wikipedia.org/wiki/Selection_sort -[selection-image]: https://upload.wikimedia.org/wikipedia/commons/thumb/b/b0/Selection_sort_animation.gif/250px-Selection_sort_animation.gif "Selection Sort Sort" - -[shell-toptal]: https://www.toptal.com/developers/sorting-algorithms/shell-sort -[shell-wiki]: https://en.wikipedia.org/wiki/Shellsort -[shell-image]: https://upload.wikimedia.org/wikipedia/commons/d/d8/Sorting_shellsort_anim.gif "Shell Sort" - -[topological-wiki]: https://en.wikipedia.org/wiki/Topological_sorting - -[linear-wiki]: https://en.wikipedia.org/wiki/Linear_search -[linear-image]: http://www.tutorialspoint.com/data_structures_algorithms/images/linear_search.gif "Linear Search" - -[binary-wiki]: https://en.wikipedia.org/wiki/Binary_search_algorithm -[binary-image]: https://upload.wikimedia.org/wikipedia/commons/f/f7/Binary_search_into_array.png "Binary Search" - - -[interpolation-wiki]: https://en.wikipedia.org/wiki/Interpolation_search - -[jump-wiki]: https://en.wikipedia.org/wiki/Jump_search - -[quick-wiki]: https://en.wikipedia.org/wiki/Quickselect - -[tabu-wiki]: https://en.wikipedia.org/wiki/Tabu_search - -[ROT13-image]: https://upload.wikimedia.org/wikipedia/commons/3/33/ROT13_table_with_example.svg "ROT13" - -[JumpSearch-image]: https://i1.wp.com/theoryofprogramming.com/wp-content/uploads/2016/11/jump-search-1.jpg "Jump Search" - -[QuickSelect-image]: https://upload.wikimedia.org/wikipedia/commons/0/04/Selecting_quickselect_frames.gif "Quick Select" +https://gitter.im/TheAlgorithms From 3033c1e06ec477dd62a84470926492e28fadecf5 Mon Sep 17 00:00:00 2001 From: Ayman Mohamed Ali Date: Sun, 26 May 2019 15:46:45 +0300 Subject: [PATCH 0026/1071] Implement check_bipartite_graph using DFS. (#836) From 71be23999c44c5c41d48ee2b64bc155ec2c4da18 Mon Sep 17 00:00:00 2001 From: Anup Kumar Panwar <1anuppanwar@gmail.com> Date: Sun, 26 May 2019 21:56:10 +0530 Subject: [PATCH 0027/1071] refactor --- License | 2 +- README.md | 3 ++- data_structures/binary tree/{AVLtree.py => AVL_tree.py} | 0 data_structures/queue/{deqeue.py => double_ended_queue.py} | 0 .../{FractionalKnapsack.py => Fractional_Knapsack.py} | 0 dynamic_programming/{fastfibonacci.py => fast_fibonacci.py} | 0 ...d) Graph.py => Directed_and_Undirected_(Weighted)_Graph.py} | 0 .../Eulerian_path_and_circuit_for_undirected_graph.py | 0 graphs/{bfs-shortestpath.py => bfs_shortest_path.py} | 0 .../edmonds_karp_multiple_source_and_sink.py | 0 Graphs/pagerank.py => graphs/page_rank.py | 0 maths/{BinaryExponentiation.py => Binary_Exponentiation.py} | 0 maths/{FindMax.py => Find_Max.py} | 0 maths/{FindMin.py => Find_Min.py} | 0 maths/{PrimeCheck.py => Prime_Check.py} | 0 maths/{absMax.py => abs_Max.py} | 0 maths/{absMin.py => abs_Min.py} | 0 other/{findingPrimes.py => finding_Primes.py} | 0 sorts/{BitonicSort.py => Bitonic_Sort.py} | 0 sorts/{external-sort.py => external_sort.py} | 0 strings/{naiveStringSearch.py => naive_String_Search.py} | 0 21 files changed, 3 insertions(+), 2 deletions(-) rename data_structures/binary tree/{AVLtree.py => AVL_tree.py} (100%) rename data_structures/queue/{deqeue.py => double_ended_queue.py} (100%) rename dynamic_programming/{FractionalKnapsack.py => Fractional_Knapsack.py} (100%) rename dynamic_programming/{fastfibonacci.py => fast_fibonacci.py} (100%) rename graphs/{Directed and Undirected (Weighted) Graph.py => Directed_and_Undirected_(Weighted)_Graph.py} (100%) rename Graphs/Eulerian path and circuit for undirected graph.py => graphs/Eulerian_path_and_circuit_for_undirected_graph.py (100%) rename graphs/{bfs-shortestpath.py => bfs_shortest_path.py} (100%) rename Graphs/edmonds_karp_Multiple_SourceAndSink.py => graphs/edmonds_karp_multiple_source_and_sink.py (100%) rename Graphs/pagerank.py => graphs/page_rank.py (100%) rename maths/{BinaryExponentiation.py => Binary_Exponentiation.py} (100%) rename maths/{FindMax.py => Find_Max.py} (100%) rename maths/{FindMin.py => Find_Min.py} (100%) rename maths/{PrimeCheck.py => Prime_Check.py} (100%) rename maths/{absMax.py => abs_Max.py} (100%) rename maths/{absMin.py => abs_Min.py} (100%) rename other/{findingPrimes.py => finding_Primes.py} (100%) rename sorts/{BitonicSort.py => Bitonic_Sort.py} (100%) rename sorts/{external-sort.py => external_sort.py} (100%) rename strings/{naiveStringSearch.py => naive_String_Search.py} (100%) diff --git a/License b/License index c84ae570c084..a20869d96300 100644 --- a/License +++ b/License @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2016 The Algorithms +Copyright (c) 2019 The Algorithms Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index 8172df62309f..f8faf38736b6 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,6 @@ # The Algorithms - Python -[![Donate](https://img.shields.io/badge/Donate-PayPal-green.svg)](https://www.paypal.me/TheAlgorithms/100) +[![Donate](https://img.shields.io/badge/Donate-PayPal-green.svg)](https://www.paypal.me/TheAlgorithms/100)   +[![Gitter chat](https://badges.gitter.im/gitterHQ/gitter.png)](https://gitter.im/TheAlgorithms) ### All algorithms implemented in Python (for education) diff --git a/data_structures/binary tree/AVLtree.py b/data_structures/binary tree/AVL_tree.py similarity index 100% rename from data_structures/binary tree/AVLtree.py rename to data_structures/binary tree/AVL_tree.py diff --git a/data_structures/queue/deqeue.py b/data_structures/queue/double_ended_queue.py similarity index 100% rename from data_structures/queue/deqeue.py rename to data_structures/queue/double_ended_queue.py diff --git a/dynamic_programming/FractionalKnapsack.py b/dynamic_programming/Fractional_Knapsack.py similarity index 100% rename from dynamic_programming/FractionalKnapsack.py rename to dynamic_programming/Fractional_Knapsack.py diff --git a/dynamic_programming/fastfibonacci.py b/dynamic_programming/fast_fibonacci.py similarity index 100% rename from dynamic_programming/fastfibonacci.py rename to dynamic_programming/fast_fibonacci.py diff --git a/graphs/Directed and Undirected (Weighted) Graph.py b/graphs/Directed_and_Undirected_(Weighted)_Graph.py similarity index 100% rename from graphs/Directed and Undirected (Weighted) Graph.py rename to graphs/Directed_and_Undirected_(Weighted)_Graph.py diff --git a/Graphs/Eulerian path and circuit for undirected graph.py b/graphs/Eulerian_path_and_circuit_for_undirected_graph.py similarity index 100% rename from Graphs/Eulerian path and circuit for undirected graph.py rename to graphs/Eulerian_path_and_circuit_for_undirected_graph.py diff --git a/graphs/bfs-shortestpath.py b/graphs/bfs_shortest_path.py similarity index 100% rename from graphs/bfs-shortestpath.py rename to graphs/bfs_shortest_path.py diff --git a/Graphs/edmonds_karp_Multiple_SourceAndSink.py b/graphs/edmonds_karp_multiple_source_and_sink.py similarity index 100% rename from Graphs/edmonds_karp_Multiple_SourceAndSink.py rename to graphs/edmonds_karp_multiple_source_and_sink.py diff --git a/Graphs/pagerank.py b/graphs/page_rank.py similarity index 100% rename from Graphs/pagerank.py rename to graphs/page_rank.py diff --git a/maths/BinaryExponentiation.py b/maths/Binary_Exponentiation.py similarity index 100% rename from maths/BinaryExponentiation.py rename to maths/Binary_Exponentiation.py diff --git a/maths/FindMax.py b/maths/Find_Max.py similarity index 100% rename from maths/FindMax.py rename to maths/Find_Max.py diff --git a/maths/FindMin.py b/maths/Find_Min.py similarity index 100% rename from maths/FindMin.py rename to maths/Find_Min.py diff --git a/maths/PrimeCheck.py b/maths/Prime_Check.py similarity index 100% rename from maths/PrimeCheck.py rename to maths/Prime_Check.py diff --git a/maths/absMax.py b/maths/abs_Max.py similarity index 100% rename from maths/absMax.py rename to maths/abs_Max.py diff --git a/maths/absMin.py b/maths/abs_Min.py similarity index 100% rename from maths/absMin.py rename to maths/abs_Min.py diff --git a/other/findingPrimes.py b/other/finding_Primes.py similarity index 100% rename from other/findingPrimes.py rename to other/finding_Primes.py diff --git a/sorts/BitonicSort.py b/sorts/Bitonic_Sort.py similarity index 100% rename from sorts/BitonicSort.py rename to sorts/Bitonic_Sort.py diff --git a/sorts/external-sort.py b/sorts/external_sort.py similarity index 100% rename from sorts/external-sort.py rename to sorts/external_sort.py diff --git a/strings/naiveStringSearch.py b/strings/naive_String_Search.py similarity index 100% rename from strings/naiveStringSearch.py rename to strings/naive_String_Search.py From 974088d872df4cc697c5c816fa961fc75ace857b Mon Sep 17 00:00:00 2001 From: CHIANGTUNGHUA Date: Mon, 27 May 2019 00:33:53 +0800 Subject: [PATCH 0028/1071] Update .gitignore (#841) --- .gitignore | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 5f9132236c26..0c3f33058614 100644 --- a/.gitignore +++ b/.gitignore @@ -89,4 +89,5 @@ ENV/ # Rope project settings .ropeproject .idea -.DS_Store \ No newline at end of file +.DS_Store +.try \ No newline at end of file From 7f4f565e6291e876f45bb100843fdd021177fbb0 Mon Sep 17 00:00:00 2001 From: Ambuj81 Date: Sun, 26 May 2019 22:07:40 +0530 Subject: [PATCH 0029/1071] subset_generation (#326) * subset_generation generate all possible subset of size n of a given array of size r * Rename subset_generation to subset_generation.py * Update subset_generation.py I made all changes I could . What I mean is I removed all the empty space ....... There some comment extra if you feel removing those comments please do so yourself pls provide spacing as it should be * Create morse_Code_implementation.py * Any more changes pls let me know --- ciphers/morse_Code_implementation.py | 82 ++++++++++++++++++++++++ dynamic_programming/subset_generation.py | 39 +++++++++++ 2 files changed, 121 insertions(+) create mode 100644 ciphers/morse_Code_implementation.py create mode 100644 dynamic_programming/subset_generation.py diff --git a/ciphers/morse_Code_implementation.py b/ciphers/morse_Code_implementation.py new file mode 100644 index 000000000000..7b2d0a94b24b --- /dev/null +++ b/ciphers/morse_Code_implementation.py @@ -0,0 +1,82 @@ +# Python program to implement Morse Code Translator + + +# Dictionary representing the morse code chart +MORSE_CODE_DICT = { 'A':'.-', 'B':'-...', + 'C':'-.-.', 'D':'-..', 'E':'.', + 'F':'..-.', 'G':'--.', 'H':'....', + 'I':'..', 'J':'.---', 'K':'-.-', + 'L':'.-..', 'M':'--', 'N':'-.', + 'O':'---', 'P':'.--.', 'Q':'--.-', + 'R':'.-.', 'S':'...', 'T':'-', + 'U':'..-', 'V':'...-', 'W':'.--', + 'X':'-..-', 'Y':'-.--', 'Z':'--..', + '1':'.----', '2':'..---', '3':'...--', + '4':'....-', '5':'.....', '6':'-....', + '7':'--...', '8':'---..', '9':'----.', + '0':'-----', ', ':'--..--', '.':'.-.-.-', + '?':'..--..', '/':'-..-.', '-':'-....-', + '(':'-.--.', ')':'-.--.-'} + + +def encrypt(message): + cipher = '' + for letter in message: + if letter != ' ': + + + cipher += MORSE_CODE_DICT[letter] + ' ' + else: + + cipher += ' ' + + return cipher + + +def decrypt(message): + + message += ' ' + + decipher = '' + citext = '' + for letter in message: + + if (letter != ' '): + + + i = 0 + + + citext += letter + + else: + + i += 1 + + + if i == 2 : + + + decipher += ' ' + else: + + + decipher += list(MORSE_CODE_DICT.keys())[list(MORSE_CODE_DICT + .values()).index(citext)] + citext = '' + + return decipher + + +def main(): + message = "Morse code here" + result = encrypt(message.upper()) + print (result) + + message = result + result = decrypt(message) + print (result) + + +if __name__ == '__main__': + main() diff --git a/dynamic_programming/subset_generation.py b/dynamic_programming/subset_generation.py new file mode 100644 index 000000000000..4b7a2bf87fd5 --- /dev/null +++ b/dynamic_programming/subset_generation.py @@ -0,0 +1,39 @@ +# python program to print all subset combination of n element in given set of r element . +#arr[] ---> Input Array +#data[] ---> Temporary array to store current combination +# start & end ---> Staring and Ending indexes in arr[] +# index ---> Current index in data[] +#r ---> Size of a combination to be printed +def combinationUtil(arr,n,r,index,data,i): +#Current combination is ready to be printed, +# print it + if(index == r): + for j in range(r): + print(data[j],end =" ") + print(" ") + return +# When no more elements are there to put in data[] + if(i >= n): + return +#current is included, put next at next +# location + data[index] = arr[i] + combinationUtil(arr,n,r,index+1,data,i+1) + # current is excluded, replace it with + # next (Note that i+1 is passed, but + # index is not changed) + combinationUtil(arr,n,r,index,data,i+1) + # The main function that prints all combinations + #of size r in arr[] of size n. This function + #mainly uses combinationUtil() +def printcombination(arr,n,r): +# A temporary array to store all combination +# one by one + data = [0]*r +#Print all combination using temprary +#array 'data[]' + combinationUtil(arr,n,r,0,data,0) +# Driver function to check for above function +arr = [10,20,30,40,50] +printcombination(arr,len(arr),3) +#This code is contributed by Ambuj sahu From 5be32f4022135a10585cf094b6fb8118dd87a2f6 Mon Sep 17 00:00:00 2001 From: Shubhayu Das <43082352+sateslayer@users.noreply.github.com> Date: Sun, 26 May 2019 22:10:04 +0530 Subject: [PATCH 0030/1071] Add files via upload (#396) --- ciphers/Atbash.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 ciphers/Atbash.py diff --git a/ciphers/Atbash.py b/ciphers/Atbash.py new file mode 100644 index 000000000000..67bd69425fdd --- /dev/null +++ b/ciphers/Atbash.py @@ -0,0 +1,14 @@ +def Atbash(): + inp=raw_input("Enter the sentence to be encrypted ") + output="" + for i in inp: + extract=ord(i) + if extract>=65 and extract<=90: + output+=(unichr(155-extract)) + elif extract>=97 and extract<=122: + output+=(unichr(219-extract)) + else: + output+=i + print output + +Atbash() ; \ No newline at end of file From 89f15bef0a32e8e2c1c26f773daebfbe4dd3564f Mon Sep 17 00:00:00 2001 From: Bruno Santos <7022432+dunderbruno@users.noreply.github.com> Date: Sun, 26 May 2019 13:41:46 -0300 Subject: [PATCH 0031/1071] Create prim.py (#397) --- Graphs/prim.py | 82 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 82 insertions(+) create mode 100644 Graphs/prim.py diff --git a/Graphs/prim.py b/Graphs/prim.py new file mode 100644 index 000000000000..c9f91d4b0700 --- /dev/null +++ b/Graphs/prim.py @@ -0,0 +1,82 @@ +""" +Prim's Algorithm. + +Determines the minimum spanning tree(MST) of a graph using the Prim's Algorithm + +Create a list to store x the vertices. +G = [vertex(n) for n in range(x)] + +For each vertex in G, add the neighbors: +G[x].addNeighbor(G[y]) +G[y].addNeighbor(G[x]) + +For each vertex in G, add the edges: +G[x].addEdge(G[y], w) +G[y].addEdge(G[x], w) + +To solve run: +MST = prim(G, G[0]) +""" + +import math + + +class vertex(): + """Class Vertex.""" + + def __init__(self, id): + """ + Arguments: + id - input an id to identify the vertex + + Attributes: + neighbors - a list of the vertices it is linked to + edges - a dict to store the edges's weight + """ + self.id = str(id) + self.key = None + self.pi = None + self.neighbors = [] + self.edges = {} # [vertex:distance] + + def __lt__(self, other): + """Comparison rule to < operator.""" + return (self.key < other.key) + + def __repr__(self): + """Return the vertex id.""" + return self.id + + def addNeighbor(self, vertex): + """Add a pointer to a vertex at neighbor's list.""" + self.neighbors.append(vertex) + + def addEdge(self, vertex, weight): + """Destination vertex and weight.""" + self.edges[vertex.id] = weight + + +def prim(graph, root): + """ + Prim's Algorithm. + + Return a list with the edges of a Minimum Spanning Tree + + prim(graph, graph[0]) + """ + A = [] + for u in graph: + u.key = math.inf + u.pi = None + root.key = 0 + Q = graph[:] + while Q: + u = min(Q) + Q.remove(u) + for v in u.neighbors: + if (v in Q) and (u.edges[v.id] < v.key): + v.pi = u + v.key = u.edges[v.id] + for i in range(1, len(graph)): + A.append([graph[i].id, graph[i].pi.id]) + return(A) From 559951c181d880140a7a11d57b6c54d50018d8aa Mon Sep 17 00:00:00 2001 From: victoni <32034171+victoni@users.noreply.github.com> Date: Sun, 26 May 2019 18:42:55 +0200 Subject: [PATCH 0032/1071] Lucas series added (#399) --- Maths/lucasSeries.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 Maths/lucasSeries.py diff --git a/Maths/lucasSeries.py b/Maths/lucasSeries.py new file mode 100644 index 000000000000..91ea1ba72a56 --- /dev/null +++ b/Maths/lucasSeries.py @@ -0,0 +1,13 @@ +# Lucas Sequence Using Recursion + +def recur_luc(n): + if n == 1: + return n + if n == 0: + return 2 + return (recur_luc(n-1) + recur_luc(n-2)) + +limit = int(input("How many terms to include in Lucas series:")) +print("Lucas series:") +for i in range(limit): + print(recur_luc(i)) From cb4be75941af8058c2efa132151cd64bf73f3101 Mon Sep 17 00:00:00 2001 From: Anup Kumar Panwar <1anuppanwar@gmail.com> Date: Sun, 26 May 2019 22:21:22 +0530 Subject: [PATCH 0033/1071] Rename nqueens.py to n_queens.py --- other/{nqueens.py => n_queens.py} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename other/{nqueens.py => n_queens.py} (98%) diff --git a/other/nqueens.py b/other/n_queens.py similarity index 98% rename from other/nqueens.py rename to other/n_queens.py index 1b1c75878ae6..0e80a0cff5e9 100644 --- a/other/nqueens.py +++ b/other/n_queens.py @@ -74,4 +74,4 @@ def print_board(board): board = nqueens(default_width) print(board) - print_board(board) \ No newline at end of file + print_board(board) From f56dd7f8e3a35354f8bee34a43175139908cbe4b Mon Sep 17 00:00:00 2001 From: Anup Kumar Panwar <1anuppanwar@gmail.com> Date: Sun, 26 May 2019 22:59:07 +0530 Subject: [PATCH 0034/1071] Update Atbash.py --- ciphers/Atbash.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ciphers/Atbash.py b/ciphers/Atbash.py index 67bd69425fdd..4920e3049756 100644 --- a/ciphers/Atbash.py +++ b/ciphers/Atbash.py @@ -9,6 +9,6 @@ def Atbash(): output+=(unichr(219-extract)) else: output+=i - print output + print (output) -Atbash() ; \ No newline at end of file +Atbash() ; From dd62f1b8023817ae5637812b3fa9acd815e4ef38 Mon Sep 17 00:00:00 2001 From: alpylmz <41516584+alpylmz@users.noreply.github.com> Date: Sun, 26 May 2019 21:04:49 +0300 Subject: [PATCH 0035/1071] Create sol5.py (#425) added a solve for the problem --- Project Euler/Problem 01/sol5.py | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 Project Euler/Problem 01/sol5.py diff --git a/Project Euler/Problem 01/sol5.py b/Project Euler/Problem 01/sol5.py new file mode 100644 index 000000000000..2cb67d2524e2 --- /dev/null +++ b/Project Euler/Problem 01/sol5.py @@ -0,0 +1,8 @@ +a=3 +result=0 +while a=<1000: + if(a%3==0 and a%5==0): + result+=a + elif(a%15==0): + result-=a +print(result) From fc95e7a91a4407ea5a293dbe80dc7a76d4af0976 Mon Sep 17 00:00:00 2001 From: Erfan Alimohammadi Date: Tue, 28 May 2019 17:21:48 +0430 Subject: [PATCH 0036/1071] Fermat's little theorem (#847) * Fix typo * Add fermat's little theorem * Update fermat_little_theorem.py * Fix comments * Add Wikipedia reference --- maths/fermat_little_theorem.py | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 maths/fermat_little_theorem.py diff --git a/maths/fermat_little_theorem.py b/maths/fermat_little_theorem.py new file mode 100644 index 000000000000..93af98684894 --- /dev/null +++ b/maths/fermat_little_theorem.py @@ -0,0 +1,30 @@ +# Python program to show the usage of Fermat's little theorem in a division +# According to Fermat's little theorem, (a / b) mod p always equals a * (b ^ (p - 2)) mod p +# Here we assume that p is a prime number, b divides a, and p doesn't divide b +# Wikipedia reference: https://en.wikipedia.org/wiki/Fermat%27s_little_theorem + + +def binary_exponentiation(a, n, mod): + + if (n == 0): + return 1 + + elif (n % 2 == 1): + return (binary_exponentiation(a, n - 1, mod) * a) % mod + + else: + b = binary_exponentiation(a, n / 2, mod) + return (b * b) % mod + + +# a prime number +p = 701 + +a = 1000000000 +b = 10 + +# using binary exponentiation function, O(log(p)): +print((a / b) % p == (a * binary_exponentiation(b, p - 2, p)) % p) + +# using Python operators: +print((a / b) % p == (a * b ** (p - 2)) % p) From 9037abae110408e8c7cf45f613d84059be07993c Mon Sep 17 00:00:00 2001 From: cedricfarinazzo Date: Thu, 30 May 2019 02:47:00 +0200 Subject: [PATCH 0037/1071] Fix spelling in neural_network/convolution_neural_network.py (#849) * Fix spelling in neural_network/convolution_neural_network.py * fix import Signed-off-by: cedric.farinazzo --- neural_network/convolution_neural_network.py | 67 ++++++++++---------- 1 file changed, 33 insertions(+), 34 deletions(-) diff --git a/neural_network/convolution_neural_network.py b/neural_network/convolution_neural_network.py index 0dca2bc485d1..0e72f0c0dca2 100644 --- a/neural_network/convolution_neural_network.py +++ b/neural_network/convolution_neural_network.py @@ -14,15 +14,16 @@ Github: 245885195@qq.com Date: 2017.9.20 - - - - - -- - - - - - - - - - - - - - - - - - - - - - - - ''' +''' from __future__ import print_function +import pickle import numpy as np import matplotlib.pyplot as plt class CNN(): - def __init__(self,conv1_get,size_p1,bp_num1,bp_num2,bp_num3,rate_w=0.2,rate_t=0.2): + def __init__(self, conv1_get, size_p1, bp_num1, bp_num2, bp_num3, rate_w=0.2, rate_t=0.2): ''' :param conv1_get: [a,c,d],size, number, step of convolution kernel :param size_p1: pooling size @@ -48,32 +49,30 @@ def __init__(self,conv1_get,size_p1,bp_num1,bp_num2,bp_num3,rate_w=0.2,rate_t=0. self.thre_bp3 = -2*np.random.rand(self.num_bp3)+1 - def save_model(self,save_path): + def save_model(self, save_path): #save model dict with pickle - import pickle model_dic = {'num_bp1':self.num_bp1, - 'num_bp2':self.num_bp2, - 'num_bp3':self.num_bp3, - 'conv1':self.conv1, - 'step_conv1':self.step_conv1, - 'size_pooling1':self.size_pooling1, - 'rate_weight':self.rate_weight, - 'rate_thre':self.rate_thre, - 'w_conv1':self.w_conv1, - 'wkj':self.wkj, - 'vji':self.vji, - 'thre_conv1':self.thre_conv1, - 'thre_bp2':self.thre_bp2, - 'thre_bp3':self.thre_bp3} + 'num_bp2':self.num_bp2, + 'num_bp3':self.num_bp3, + 'conv1':self.conv1, + 'step_conv1':self.step_conv1, + 'size_pooling1':self.size_pooling1, + 'rate_weight':self.rate_weight, + 'rate_thre':self.rate_thre, + 'w_conv1':self.w_conv1, + 'wkj':self.wkj, + 'vji':self.vji, + 'thre_conv1':self.thre_conv1, + 'thre_bp2':self.thre_bp2, + 'thre_bp3':self.thre_bp3} with open(save_path, 'wb') as f: pickle.dump(model_dic, f) print('Model saved: %s'% save_path) @classmethod - def ReadModel(cls,model_path): + def ReadModel(cls, model_path): #read saved model - import pickle with open(model_path, 'rb') as f: model_dic = pickle.load(f) @@ -97,13 +96,13 @@ def ReadModel(cls,model_path): return conv_ins - def sig(self,x): + def sig(self, x): return 1 / (1 + np.exp(-1*x)) - def do_round(self,x): + def do_round(self, x): return round(x, 3) - def convolute(self,data,convs,w_convs,thre_convs,conv_step): + def convolute(self, data, convs, w_convs, thre_convs, conv_step): #convolution process size_conv = convs[0] num_conv =convs[1] @@ -132,7 +131,7 @@ def convolute(self,data,convs,w_convs,thre_convs,conv_step): focus_list = np.asarray(focus1_list) return focus_list,data_featuremap - def pooling(self,featuremaps,size_pooling,type='average_pool'): + def pooling(self, featuremaps, size_pooling, type='average_pool'): #pooling process size_map = len(featuremaps[0]) size_pooled = int(size_map/size_pooling) @@ -153,7 +152,7 @@ def pooling(self,featuremaps,size_pooling,type='average_pool'): featuremap_pooled.append(map_pooled) return featuremap_pooled - def _expand(self,datas): + def _expand(self, datas): #expanding three dimension data to one dimension list data_expanded = [] for i in range(len(datas)): @@ -164,14 +163,14 @@ def _expand(self,datas): data_expanded = np.asarray(data_expanded) return data_expanded - def _expand_mat(self,data_mat): + def _expand_mat(self, data_mat): #expanding matrix to one dimension list data_mat = np.asarray(data_mat) shapes = np.shape(data_mat) data_expanded = data_mat.reshape(1,shapes[0]*shapes[1]) return data_expanded - def _calculate_gradient_from_pool(self,out_map,pd_pool,num_map,size_map,size_pooling): + def _calculate_gradient_from_pool(self, out_map, pd_pool,num_map, size_map, size_pooling): ''' calcluate the gradient from the data slice of pool layer pd_pool: list of matrix @@ -190,7 +189,7 @@ def _calculate_gradient_from_pool(self,out_map,pd_pool,num_map,size_map,size_poo pd_all.append(pd_conv2) return pd_all - def trian(self,patterns,datas_train, datas_teach, n_repeat, error_accuracy,draw_e = bool): + def train(self, patterns, datas_train, datas_teach, n_repeat, error_accuracy, draw_e = bool): #model traning print('----------------------Start Training-------------------------') print((' - - Shape: Train_Data ',np.shape(datas_train))) @@ -206,7 +205,7 @@ def trian(self,patterns,datas_train, datas_teach, n_repeat, error_accuracy,draw_ data_train = np.asmatrix(datas_train[p]) data_teach = np.asarray(datas_teach[p]) data_focus1,data_conved1 = self.convolute(data_train,self.conv1,self.w_conv1, - self.thre_conv1,conv_step=self.step_conv1) + self.thre_conv1,conv_step=self.step_conv1) data_pooled1 = self.pooling(data_conved1,self.size_pooling1) shape_featuremap1 = np.shape(data_conved1) ''' @@ -231,7 +230,7 @@ def trian(self,patterns,datas_train, datas_teach, n_repeat, error_accuracy,draw_ pd_conv1_pooled = pd_i_all / (self.size_pooling1*self.size_pooling1) pd_conv1_pooled = pd_conv1_pooled.T.getA().tolist() pd_conv1_all = self._calculate_gradient_from_pool(data_conved1,pd_conv1_pooled,shape_featuremap1[0], - shape_featuremap1[1],self.size_pooling1) + shape_featuremap1[1],self.size_pooling1) #weight and threshold learning process--------- #convolution layer for k_conv in range(self.conv1[1]): @@ -268,7 +267,7 @@ def draw_error(): draw_error() return mse - def predict(self,datas_test): + def predict(self, datas_test): #model predict produce_out = [] print('-------------------Start Testing-------------------------') @@ -276,7 +275,7 @@ def predict(self,datas_test): for p in range(len(datas_test)): data_test = np.asmatrix(datas_test[p]) data_focus1, data_conved1 = self.convolute(data_test, self.conv1, self.w_conv1, - self.thre_conv1, conv_step=self.step_conv1) + self.thre_conv1, conv_step=self.step_conv1) data_pooled1 = self.pooling(data_conved1, self.size_pooling1) data_bp_input = self._expand(data_pooled1) @@ -289,11 +288,11 @@ def predict(self,datas_test): res = [list(map(self.do_round,each)) for each in produce_out] return np.asarray(res) - def convolution(self,data): + def convolution(self, data): #return the data of image after convoluting process so we can check it out data_test = np.asmatrix(data) data_focus1, data_conved1 = self.convolute(data_test, self.conv1, self.w_conv1, - self.thre_conv1, conv_step=self.step_conv1) + self.thre_conv1, conv_step=self.step_conv1) data_pooled1 = self.pooling(data_conved1, self.size_pooling1) return data_conved1,data_pooled1 @@ -303,4 +302,4 @@ def convolution(self,data): pass ''' I will put the example on other file - ''' \ No newline at end of file +''' From aeab9f10685c93e043ad143bd583bc11e7bd8744 Mon Sep 17 00:00:00 2001 From: Sven Efftinge Date: Fri, 31 May 2019 10:02:56 +0200 Subject: [PATCH 0038/1071] Added gitpod info (#848) --- README.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index f8faf38736b6..f84dcaf90cff 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ # The Algorithms - Python -[![Donate](https://img.shields.io/badge/Donate-PayPal-green.svg)](https://www.paypal.me/TheAlgorithms/100)   +[![Donate](https://img.shields.io/badge/Donate-PayPal-green.svg)](https://www.paypal.me/TheAlgorithms/100)   [![Gitter chat](https://badges.gitter.im/gitterHQ/gitter.png)](https://gitter.im/TheAlgorithms) @@ -7,6 +7,10 @@ These implementations are for learning purposes. They may be efficient than the implementations in the Python standard library. +Run, edit and contribute using Gitpod.io a free online dev environment. + +[![Open in Gitpod](https://gitpod.io/button/open-in-gitpod.svg)](https://gitpod.io/#https://github.com/TheAlgorithms/Python) + ## Contribution Guidelines * File name should be in camel case. From 4d9a1611bda8124bf4ea21699c12c81c9fcdd1b4 Mon Sep 17 00:00:00 2001 From: Dave Kerr Date: Fri, 31 May 2019 04:03:55 -0400 Subject: [PATCH 0039/1071] implementations may be *less* efficient than python standard libs (#854) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index f84dcaf90cff..e2412d9b57dd 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ ### All algorithms implemented in Python (for education) -These implementations are for learning purposes. They may be efficient than the implementations in the Python standard library. +These implementations are for learning purposes. They may be less efficient than the implementations in the Python standard library. Run, edit and contribute using Gitpod.io a free online dev environment. From f386fce820fb60abfe1b18c141dfd8ce268c5f4f Mon Sep 17 00:00:00 2001 From: luanjerry <50862638+luanjerry@users.noreply.github.com> Date: Fri, 31 May 2019 16:05:24 +0800 Subject: [PATCH 0040/1071] Update queue_on_list.py (#851) * Fixed error in queue_on_list.py --- data_structures/queue/queue_on_list.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/data_structures/queue/queue_on_list.py b/data_structures/queue/queue_on_list.py index c8d0b41de5d5..2ec9bac8398a 100644 --- a/data_structures/queue/queue_on_list.py +++ b/data_structures/queue/queue_on_list.py @@ -24,8 +24,9 @@ def put(self, item): def get(self): self.length = self.length - 1 dequeued = self.entries[self.front] - self.front-=1 - self.entries = self.entries[self.front:] + #self.front-=1 + #self.entries = self.entries[self.front:] + self.entries = self.entries[1:] return dequeued """Rotates the queue {@code rotation} times From c2552cdfcdc787b73bd07ee2fe5145c73827e8a3 Mon Sep 17 00:00:00 2001 From: John Law Date: Sun, 2 Jun 2019 12:14:18 +0800 Subject: [PATCH 0041/1071] Create CONTRIBUTING.md (#864) * Create CONTRIBUTING.md * Create a tailor-made contribution guide Closes #802 * Update README.md * Rename License to LICENSE.md --- CONTRIBUTING.md | 124 ++++++++++++++++++++++++++++++++++++++++++ License => LICENSE.md | 0 README.md | 15 ++--- 3 files changed, 128 insertions(+), 11 deletions(-) create mode 100644 CONTRIBUTING.md rename License => LICENSE.md (100%) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 000000000000..9b2ac0025dca --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,124 @@ +# Contributing guidelines + +## Before contributing + +Welcome to [TheAlgorithms/Python](https://github.com/TheAlgorithms/Python)! Before sending your pull requests, make sure that you **read the whole guidelines**. If you have any doubt on the contributing guide, please feel free to [state it clearly in an issue](https://github.com/TheAlgorithms/Python/issues/new) or ask the community in [Gitter](https://gitter.im/TheAlgorithms). + +## Contributing + +### Contributor + +We are very happy that you consider implementing algorithms and data structure for others! This repository is referenced and used by learners from all over the globe. Being one of our contributors, you agree and confirm that: + +- your did your work - no plagiarism allowed + - Any plagiarized work will not be merged. +- your work will be distributed under [MIT License](License) once your pull request is merged +- you submitted work fulfils or mostly fulfils our styles and standards + +**New implementation** is welcome! For example, new solutions for a problem, different representations for a graph data structure or algorithm designs with different complexity. + +**Improving comments** and **writing proper tests** are also highly welcome. + +### Contribution + +We appreciate any contribution, from fixing a grammar mistake in a comment to implementing complex algorithms. Please read this section if you are contributing your work. + +#### Coding Style + +We want your work to be readable by others; therefore, we encourage you to note the following: + +- Please write in Python 3.x. + +- If you know [PEP 8](https://www.python.org/dev/peps/pep-0008/) already, you will have no problem in coding style, though we do not follow it strictly. Read the remaining section and have fun coding! + +- Always use 4 spaces to indent. + +- Original code submission requires comments to describe your work. + +- More on comments and docstrings: + + The following are considered to be bad and may be requested to be improved: + + ```python + x = x + 2 # increased by 2 + ``` + + This is too trivial. Comments are expected to be explanatory. For comments, you can write them above, on or below a line of code, as long as you are consistent within the same piece of code. + + *Sometimes, docstrings are avoided.* This will happen if you are using some editors and not careful with indentation: + + ```python + """ + This function sums a and b + """ + def sum(a, b): + return a + b + ``` + + However, if you insist to use docstrings, we encourage you to put docstrings inside functions. Also, please pay attention to indentation to docstrings. The following is acceptable in this case: + + ```python + def sumab(a, b): + """ + This function sums two integers a and b + Return: a + b + """ + return a + b + ``` + +- `lambda`, `map`, `filter`, `reduce` and complicated list comprehension are welcome and acceptable to demonstrate the power of Python, as long as they are simple enough to read. + + - This is arguable: **write comments** and assign appropriate variable names, so that the code is easy to read! + +- Write tests to illustrate your work. + + The following "testing" approaches are not encouraged: + + ```python + input('Enter your input:') + # Or even worse... + input = eval(raw_input("Enter your input: ")) + ``` + + Please write down your test case, like the following: + + ```python + def sumab(a, b): + return a + b + # Write tests this way: + print(sumab(1,2)) # 1+2 = 3 + print(sumab(6,4)) # 6+4 = 10 + # Or this way: + print("1 + 2 = ", sumab(1,2)) # 1+2 = 3 + print("6 + 4 = ", sumab(6,4)) # 6+4 = 10 + ``` + +- Avoid importing external libraries for basic algorithms. Use those libraries for complicated algorithms. + +#### Other Standard While Submitting Your Work + +- File extension for code should be `.py`. + +- Please file your work to let others use it in the future. Here are the examples that are acceptable: + + - Camel cases + - `-` Hyphenated names + - `_` Underscore-separated names + + If possible, follow the standard *within* the folder you are submitting to. + +- If you have modified/added code work, make sure the code compiles before submitting. + +- If you have modified/added documentation work, make sure your language is concise and contains no grammar mistake. + +- Add a corresponding explanation to [Algorithms-Explanation](https://github.com/TheAlgorithms/Algorithms-Explanation) (Optional but recommended). + +- Most importantly, + + - **be consistent with this guidelines while submitting.** + - **join** [Gitter](https://gitter.im/TheAlgorithms) **now!** + - Happy coding! + + + +Writer [@poyea](https://github.com/poyea), Jun 2019. diff --git a/License b/LICENSE.md similarity index 100% rename from License rename to LICENSE.md diff --git a/README.md b/README.md index e2412d9b57dd..527b80269fdc 100644 --- a/README.md +++ b/README.md @@ -1,24 +1,17 @@ # The Algorithms - Python [![Donate](https://img.shields.io/badge/Donate-PayPal-green.svg)](https://www.paypal.me/TheAlgorithms/100)   -[![Gitter chat](https://badges.gitter.im/gitterHQ/gitter.png)](https://gitter.im/TheAlgorithms) - +[![Gitter chat](https://badges.gitter.im/gitterHQ/gitter.png)](https://gitter.im/TheAlgorithms)   +[![Open in Gitpod](https://gitpod.io/button/open-in-gitpod.svg)](https://gitpod.io/#https://github.com/TheAlgorithms/Python) ### All algorithms implemented in Python (for education) These implementations are for learning purposes. They may be less efficient than the implementations in the Python standard library. -Run, edit and contribute using Gitpod.io a free online dev environment. - -[![Open in Gitpod](https://gitpod.io/button/open-in-gitpod.svg)](https://gitpod.io/#https://github.com/TheAlgorithms/Python) ## Contribution Guidelines -* File name should be in camel case. -* Write proper documentation of the code. -* Avoid input methods as far as possible. Assign values to the variables statically. This will make the code easy to understand and algorithm can be traced easily. -* Add a corresponding explaination to [Algorithms-Explanation](https://github.com/TheAlgorithms/Algorithms-Explanation) (Optional but recommended). -* Avoid importing external libraries for basic algorithms. +Read our [Contribution Guidelines](CONTRIBUTING.md) before you contribute. ## Community Channel -https://gitter.im/TheAlgorithms +We're on [Gitter](https://gitter.im/TheAlgorithms)! Please join us. From f9b8dbf9db6c5b6cdd4ec9b5e35fbc1d9939a45e Mon Sep 17 00:00:00 2001 From: Guo Date: Tue, 4 Jun 2019 16:34:28 +0800 Subject: [PATCH 0042/1071] Correct the wrong iterative DFS implementation (#867) * Update DFS.py * Update DFS.py --- graphs/DFS.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/graphs/DFS.py b/graphs/DFS.py index d3c34fabb7b3..c9843ca25382 100644 --- a/graphs/DFS.py +++ b/graphs/DFS.py @@ -18,10 +18,15 @@ def dfs(graph, start): explored, stack = set(), [start] explored.add(start) while stack: - v = stack.pop() # the only difference from BFS is to pop last element here instead of first one + v = stack.pop() # one difference from BFS is to pop last element here instead of first one + + if v in explored: + continue + + explored.add(v) + for w in graph[v]: if w not in explored: - explored.add(w) stack.append(w) return explored From 0f229e0870050478a6b3fa1ecf60d73b694b98da Mon Sep 17 00:00:00 2001 From: cclauss Date: Wed, 5 Jun 2019 03:09:04 +0200 Subject: [PATCH 0043/1071] Atbash.py: Both raw_input() and unichr() were removed in Python 3 (#855) * Atbash.py: Both raw_input() and unichr() were removed in Python 3 @sateslayer and @AnupKumarPanwar your reviews please. * Remove any leading / trailing whitespace from user input --- ciphers/Atbash.py | 25 ++++++++++++++++--------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/ciphers/Atbash.py b/ciphers/Atbash.py index 4920e3049756..162614c727ee 100644 --- a/ciphers/Atbash.py +++ b/ciphers/Atbash.py @@ -1,14 +1,21 @@ +try: # Python 2 + raw_input + unichr +except NameError: # Python 3 + raw_input = input + unichr = chr + + def Atbash(): - inp=raw_input("Enter the sentence to be encrypted ") output="" - for i in inp: - extract=ord(i) - if extract>=65 and extract<=90: - output+=(unichr(155-extract)) - elif extract>=97 and extract<=122: - output+=(unichr(219-extract)) + for i in raw_input("Enter the sentence to be encrypted ").strip(): + extract = ord(i) + if 65 <= extract <= 90: + output += unichr(155-extract) + elif 97 <= extract <= 122: + output += unichr(219-extract) else: output+=i - print (output) + print(output) -Atbash() ; +Atbash() From ebe227c38646fc6720124eae5aedc68b61a4b664 Mon Sep 17 00:00:00 2001 From: Daniel Xu Date: Tue, 4 Jun 2019 21:37:05 -0400 Subject: [PATCH 0044/1071] Removed Graphs and move prim.py to graphs (#872) * Move prim.py from Graphs to graphs * Removed prim.py from Graphs * Update prim.py --- {Graphs => graphs}/prim.py | 3 --- 1 file changed, 3 deletions(-) rename {Graphs => graphs}/prim.py (99%) diff --git a/Graphs/prim.py b/graphs/prim.py similarity index 99% rename from Graphs/prim.py rename to graphs/prim.py index c9f91d4b0700..f7e08278966d 100644 --- a/Graphs/prim.py +++ b/graphs/prim.py @@ -28,7 +28,6 @@ def __init__(self, id): """ Arguments: id - input an id to identify the vertex - Attributes: neighbors - a list of the vertices it is linked to edges - a dict to store the edges's weight @@ -59,9 +58,7 @@ def addEdge(self, vertex, weight): def prim(graph, root): """ Prim's Algorithm. - Return a list with the edges of a Minimum Spanning Tree - prim(graph, graph[0]) """ A = [] From 6e894ba3e8bab4e9e0515b91e685904ec828cf43 Mon Sep 17 00:00:00 2001 From: CharlesRitter Date: Fri, 7 Jun 2019 11:38:43 -0400 Subject: [PATCH 0045/1071] Odd-Even Transposition Sort (#769) * -Added a single-threaded implementation of odd-even transposition sort. This is a modified bubble sort meant to work with multiple processors. Since this is running on a single thread, it has the same running time as bubble sort. * -Added a parallel implementation of Odd-Even Transposition sort This implementation uses multiprocessing to perform the swaps at each step of the algorithm simultaneously. --- sorts/Odd-Even_transposition_parallel.py | 127 ++++++++++++++++++ .../Odd-Even_transposition_single-threaded.py | 32 +++++ 2 files changed, 159 insertions(+) create mode 100644 sorts/Odd-Even_transposition_parallel.py create mode 100644 sorts/Odd-Even_transposition_single-threaded.py diff --git a/sorts/Odd-Even_transposition_parallel.py b/sorts/Odd-Even_transposition_parallel.py new file mode 100644 index 000000000000..d7f983fc0469 --- /dev/null +++ b/sorts/Odd-Even_transposition_parallel.py @@ -0,0 +1,127 @@ +""" +This is an implementation of odd-even transposition sort. + +It works by performing a series of parallel swaps between odd and even pairs of +variables in the list. + +This implementation represents each variable in the list with a process and +each process communicates with its neighboring processes in the list to perform +comparisons. +They are synchronized with locks and message passing but other forms of +synchronization could be used. +""" +from multiprocessing import Process, Pipe, Lock + +#lock used to ensure that two processes do not access a pipe at the same time +processLock = Lock() + +""" +The function run by the processes that sorts the list + +position = the position in the list the prcoess represents, used to know which + neighbor we pass our value to +value = the initial value at list[position] +LSend, RSend = the pipes we use to send to our left and right neighbors +LRcv, RRcv = the pipes we use to receive from our left and right neighbors +resultPipe = the pipe used to send results back to main +""" +def oeProcess(position, value, LSend, RSend, LRcv, RRcv, resultPipe): + global processLock + + #we perform n swaps since after n swaps we know we are sorted + #we *could* stop early if we are sorted already, but it takes as long to + #find out we are sorted as it does to sort the list with this algorithm + for i in range(0, 10): + + if( (i + position) % 2 == 0 and RSend != None): + #send your value to your right neighbor + processLock.acquire() + RSend[1].send(value) + processLock.release() + + #receive your right neighbor's value + processLock.acquire() + temp = RRcv[0].recv() + processLock.release() + + #take the lower value since you are on the left + value = min(value, temp) + elif( (i + position) % 2 != 0 and LSend != None): + #send your value to your left neighbor + processLock.acquire() + LSend[1].send(value) + processLock.release() + + #receive your left neighbor's value + processLock.acquire() + temp = LRcv[0].recv() + processLock.release() + + #take the higher value since you are on the right + value = max(value, temp) + #after all swaps are performed, send the values back to main + resultPipe[1].send(value) + +""" +the function which creates the processes that perform the parallel swaps + +arr = the list to be sorted +""" +def OddEvenTransposition(arr): + + processArray = [] + tempRrcv = None + tempLrcv = None + + resultPipe = [] + + #initialize the list of pipes where the values will be retrieved + for a in arr: + resultPipe.append(Pipe()) + + #creates the processes + #the first and last process only have one neighbor so they are made outside + #of the loop + tempRs = Pipe() + tempRr = Pipe() + processArray.append(Process(target = oeProcess, args = (0, arr[0], None, tempRs, None, tempRr, resultPipe[0]))) + tempLr = tempRs + tempLs = tempRr + + for i in range(1, len(arr) - 1): + tempRs = Pipe() + tempRr = Pipe() + processArray.append(Process(target = oeProcess, args = (i, arr[i], tempLs, tempRs, tempLr, tempRr, resultPipe[i]))) + tempLr = tempRs + tempLs = tempRr + + processArray.append(Process(target = oeProcess, args = (len(arr) - 1, arr[len(arr) - 1], tempLs, None, tempLr, None, resultPipe[len(arr) - 1]))) + + #start the processes + for p in processArray: + p.start() + + #wait for the processes to end and write their values to the list + for p in range(0, len(resultPipe)): + arr[p] = resultPipe[p][0].recv() + processArray[p].join() + + return(arr) + + +#creates a reverse sorted list and sorts it +def main(): + arr = [] + + for i in range(10, 0, -1): + arr.append(i) + print("Initial List") + print(*arr) + + list = OddEvenTransposition(arr) + + print("Sorted List\n") + print(*arr) + +if __name__ == "__main__": + main() diff --git a/sorts/Odd-Even_transposition_single-threaded.py b/sorts/Odd-Even_transposition_single-threaded.py new file mode 100644 index 000000000000..ec5f3cf14e55 --- /dev/null +++ b/sorts/Odd-Even_transposition_single-threaded.py @@ -0,0 +1,32 @@ +""" +This is a non-parallelized implementation of odd-even transpostiion sort. + +Normally the swaps in each set happen simultaneously, without that the algorithm +is no better than bubble sort. +""" + +def OddEvenTransposition(arr): + for i in range(0, len(arr)): + for i in range(i % 2, len(arr) - 1, 2): + if arr[i + 1] < arr[i]: + arr[i], arr[i + 1] = arr[i + 1], arr[i] + print(*arr) + + return arr + +#creates a list and sorts it +def main(): + list = [] + + for i in range(10, 0, -1): + list.append(i) + print("Initial List") + print(*list) + + list = OddEvenTransposition(list) + + print("Sorted List\n") + print(*list) + +if __name__ == "__main__": + main() From 9b945cb2b4ad77418e6576b7960fda8228214de9 Mon Sep 17 00:00:00 2001 From: StephenGemin <45926479+StephenGemin@users.noreply.github.com> Date: Sat, 8 Jun 2019 08:25:34 -0400 Subject: [PATCH 0046/1071] Iterative fibonacci with unittests from slash (#882) * iterative and formula fibonacci methods Added two ways to calculate the fibonacci sequence: (1) iterative (2) formula. I've also added a timer decorator so someone can see the difference in computation time between these two methods. Added two unittests using the slash framework. * Update test_fibonacci.py * remove inline comments per Contributing Guidelines * Update sol5.py * Create placeholder.py * Update and rename maths/test_fibonacci.py to maths/tests/test_fibonacci.py * Delete placeholder.py * Create __init__.py * Update test_fibonacci.py * Rename Maths/lucasSeries.py to maths/lucasSeries.py * Update and rename Project Euler/Problem 01/sol5.py to project_euler/problem_01/sol6.py --- Project Euler/Problem 01/sol5.py | 8 --- maths/fibonacci.py | 120 +++++++++++++++++++++++++++++++ {Maths => maths}/lucasSeries.py | 0 maths/tests/__init__.py | 1 + maths/tests/test_fibonacci.py | 34 +++++++++ project_euler/problem_01/sol6.py | 9 +++ 6 files changed, 164 insertions(+), 8 deletions(-) delete mode 100644 Project Euler/Problem 01/sol5.py create mode 100644 maths/fibonacci.py rename {Maths => maths}/lucasSeries.py (100%) create mode 100644 maths/tests/__init__.py create mode 100644 maths/tests/test_fibonacci.py create mode 100644 project_euler/problem_01/sol6.py diff --git a/Project Euler/Problem 01/sol5.py b/Project Euler/Problem 01/sol5.py deleted file mode 100644 index 2cb67d2524e2..000000000000 --- a/Project Euler/Problem 01/sol5.py +++ /dev/null @@ -1,8 +0,0 @@ -a=3 -result=0 -while a=<1000: - if(a%3==0 and a%5==0): - result+=a - elif(a%15==0): - result-=a -print(result) diff --git a/maths/fibonacci.py b/maths/fibonacci.py new file mode 100644 index 000000000000..0a0611f21379 --- /dev/null +++ b/maths/fibonacci.py @@ -0,0 +1,120 @@ +# fibonacci.py +""" +1. Calculates the iterative fibonacci sequence + +2. Calculates the fibonacci sequence with a formula + an = [ Phin - (phi)n ]/Sqrt[5] + reference-->Su, Francis E., et al. "Fibonacci Number Formula." Math Fun Facts. +""" +import math +import functools +import time +from decimal import getcontext, Decimal + +getcontext().prec = 100 + + +def timer_decorator(func): + @functools.wraps(func) + def timer_wrapper(*args, **kwargs): + start = time.time() + func(*args, **kwargs) + end = time.time() + if int(end - start) > 0: + print(f'Run time for {func.__name__}: {(end - start):0.2f}s') + else: + print(f'Run time for {func.__name__}: {(end - start)*1000:0.2f}ms') + return func(*args, **kwargs) + return timer_wrapper + + +# define Python user-defined exceptions +class Error(Exception): + """Base class for other exceptions""" + + +class ValueTooLargeError(Error): + """Raised when the input value is too large""" + + +class ValueTooSmallError(Error): + """Raised when the input value is not greater than one""" + + +class ValueLessThanZero(Error): + """Raised when the input value is less than zero""" + + +def _check_number_input(n, min_thresh, max_thresh=None): + """ + :param n: single integer + :type n: int + :param min_thresh: min threshold, single integer + :type min_thresh: int + :param max_thresh: max threshold, single integer + :type max_thresh: int + :return: boolean + """ + try: + if n >= min_thresh and max_thresh is None: + return True + elif min_thresh <= n <= max_thresh: + return True + elif n < 0: + raise ValueLessThanZero + elif n < min_thresh: + raise ValueTooSmallError + elif n > max_thresh: + raise ValueTooLargeError + except ValueLessThanZero: + print("Incorrect Input: number must not be less than 0") + except ValueTooSmallError: + print(f'Incorrect Input: input number must be > {min_thresh} for the recursive calculation') + except ValueTooLargeError: + print(f'Incorrect Input: input number must be < {max_thresh} for the recursive calculation') + return False + + +@timer_decorator +def fib_iterative(n): + """ + :param n: calculate Fibonacci to the nth integer + :type n:int + :return: Fibonacci sequence as a list + """ + n = int(n) + if _check_number_input(n, 2): + seq_out = [0, 1] + a, b = 0, 1 + for _ in range(n-len(seq_out)): + a, b = b, a+b + seq_out.append(b) + return seq_out + + +@timer_decorator +def fib_formula(n): + """ + :param n: calculate Fibonacci to the nth integer + :type n:int + :return: Fibonacci sequence as a list + """ + seq_out = [0, 1] + n = int(n) + if _check_number_input(n, 2, 1000000): + sqrt = Decimal(math.sqrt(5)) + phi_1 = Decimal(1 + sqrt) / Decimal(2) + phi_2 = Decimal(1 - sqrt) / Decimal(2) + for i in range(2, n): + temp_out = ((phi_1**Decimal(i)) - (phi_2**Decimal(i))) * (Decimal(sqrt) ** Decimal(-1)) + seq_out.append(int(temp_out)) + return seq_out + + +if __name__ == '__main__': + num = 20 + # print(f'{fib_recursive(num)}\n') + # print(f'{fib_iterative(num)}\n') + # print(f'{fib_formula(num)}\n') + fib_iterative(num) + fib_formula(num) diff --git a/Maths/lucasSeries.py b/maths/lucasSeries.py similarity index 100% rename from Maths/lucasSeries.py rename to maths/lucasSeries.py diff --git a/maths/tests/__init__.py b/maths/tests/__init__.py new file mode 100644 index 000000000000..2c4a6048556c --- /dev/null +++ b/maths/tests/__init__.py @@ -0,0 +1 @@ +from .. import fibonacci diff --git a/maths/tests/test_fibonacci.py b/maths/tests/test_fibonacci.py new file mode 100644 index 000000000000..7d36c755e346 --- /dev/null +++ b/maths/tests/test_fibonacci.py @@ -0,0 +1,34 @@ +""" +To run with slash: +1. run pip install slash (may need to install C++ builds from Visual Studio website) +2. In the command prompt navigate to your project folder +3. then type--> slash run -vv -k tags:fibonacci .. + -vv indicates the level of verbosity (how much stuff you want the test to spit out after running) + -k is a way to select the tests you want to run. This becomes much more important in large scale projects. +""" + +import slash +from .. import fibonacci + +default_fib = [0, 1, 1, 2, 3, 5, 8] + + +@slash.tag('fibonacci') +@slash.parametrize(('n', 'seq'), [(2, [0, 1]), (3, [0, 1, 1]), (9, [0, 1, 1, 2, 3, 5, 8, 13, 21])]) +def test_different_sequence_lengths(n, seq): + """Test output of varying fibonacci sequence lengths""" + iterative = fibonacci.fib_iterative(n) + formula = fibonacci.fib_formula(n) + assert iterative == seq + assert formula == seq + + +@slash.tag('fibonacci') +@slash.parametrize('n', [7.3, 7.8, 7.0]) +def test_float_input_iterative(n): + """Test when user enters a float value""" + iterative = fibonacci.fib_iterative(n) + formula = fibonacci.fib_formula(n) + assert iterative == default_fib + assert formula == default_fib + diff --git a/project_euler/problem_01/sol6.py b/project_euler/problem_01/sol6.py new file mode 100644 index 000000000000..54c3073f3897 --- /dev/null +++ b/project_euler/problem_01/sol6.py @@ -0,0 +1,9 @@ +a = 3 +result = 0 +while a < 1000: + if(a % 3 == 0 or a % 5 == 0): + result += a + elif(a % 15 == 0): + result -= a + a += 1 +print(result) From 066f37402d87e31b35a7d9439b394445a1404461 Mon Sep 17 00:00:00 2001 From: guij15 <43374716+guij15@users.noreply.github.com> Date: Mon, 10 Jun 2019 14:46:36 +0800 Subject: [PATCH 0047/1071] Update newton_raphson.py (#891) --- maths/newton_raphson.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/maths/newton_raphson.py b/maths/newton_raphson.py index c08bcedc9a4d..cc6c92734fd4 100644 --- a/maths/newton_raphson.py +++ b/maths/newton_raphson.py @@ -33,7 +33,7 @@ def newton_raphson(f, x0=0, maxiter=100, step=0.0001, maxerror=1e-6,logsteps=Fal if error < maxerror: break else: - raise ValueError("Itheration limit reached, no converging solution found") + raise ValueError("Iteration limit reached, no converging solution found") if logsteps: #If logstep is true, then log intermediate steps return a, error, steps @@ -47,4 +47,4 @@ def newton_raphson(f, x0=0, maxiter=100, step=0.0001, maxerror=1e-6,logsteps=Fal plt.xlabel("step") plt.ylabel("error") plt.show() - print("solution = {%f}, error = {%f}" % (solution, error)) \ No newline at end of file + print("solution = {%f}, error = {%f}" % (solution, error)) From 05e5172093dbd0633ce83044603073dd2be675c4 Mon Sep 17 00:00:00 2001 From: Hector S Date: Tue, 11 Jun 2019 07:24:53 -0400 Subject: [PATCH 0048/1071] .vs/ directory, matrix_multiplication_addition file and binary tree directory (#894) * Added print function into matrix_multiplication_addition.py and removed blank space in data_structures/binary tree directory * Removed .vs/ folder per #893 * Rename matrix_multiplication_addition.py to matrix_operation.py --- .vs/Python/v15/.suo | Bin 16896 -> 0 bytes .vs/slnx.sqlite | Bin 176128 -> 0 bytes .../{binary tree => binary_tree}/AVL_tree.py | 0 .../binary_search_tree.py | 0 .../fenwick_tree.py | 0 .../lazy_segment_tree.py | 0 .../segment_tree.py | 0 .../{binary tree => binary_tree}/treap.py | 0 ...ication_addition.py => matrix_operation.py} | 15 ++++++++------- 9 files changed, 8 insertions(+), 7 deletions(-) delete mode 100644 .vs/Python/v15/.suo delete mode 100644 .vs/slnx.sqlite rename data_structures/{binary tree => binary_tree}/AVL_tree.py (100%) rename data_structures/{binary tree => binary_tree}/binary_search_tree.py (100%) rename data_structures/{binary tree => binary_tree}/fenwick_tree.py (100%) rename data_structures/{binary tree => binary_tree}/lazy_segment_tree.py (100%) rename data_structures/{binary tree => binary_tree}/segment_tree.py (100%) rename data_structures/{binary tree => binary_tree}/treap.py (100%) rename matrix/{matrix_multiplication_addition.py => matrix_operation.py} (80%) diff --git a/.vs/Python/v15/.suo b/.vs/Python/v15/.suo deleted file mode 100644 index 0e3f4807567d1e684e2302bf9cfb6cc11e1c136d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 16896 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 diff --git a/.vs/slnx.sqlite b/.vs/slnx.sqlite deleted file mode 100644 index 2fe4a449f121e419a91e4b74caa326bd9c11d003..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 176128 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 diff --git a/data_structures/binary tree/AVL_tree.py b/data_structures/binary_tree/AVL_tree.py similarity index 100% rename from data_structures/binary tree/AVL_tree.py rename to data_structures/binary_tree/AVL_tree.py diff --git a/data_structures/binary tree/binary_search_tree.py b/data_structures/binary_tree/binary_search_tree.py similarity index 100% rename from data_structures/binary tree/binary_search_tree.py rename to data_structures/binary_tree/binary_search_tree.py diff --git a/data_structures/binary tree/fenwick_tree.py b/data_structures/binary_tree/fenwick_tree.py similarity index 100% rename from data_structures/binary tree/fenwick_tree.py rename to data_structures/binary_tree/fenwick_tree.py diff --git a/data_structures/binary tree/lazy_segment_tree.py b/data_structures/binary_tree/lazy_segment_tree.py similarity index 100% rename from data_structures/binary tree/lazy_segment_tree.py rename to data_structures/binary_tree/lazy_segment_tree.py diff --git a/data_structures/binary tree/segment_tree.py b/data_structures/binary_tree/segment_tree.py similarity index 100% rename from data_structures/binary tree/segment_tree.py rename to data_structures/binary_tree/segment_tree.py diff --git a/data_structures/binary tree/treap.py b/data_structures/binary_tree/treap.py similarity index 100% rename from data_structures/binary tree/treap.py rename to data_structures/binary_tree/treap.py diff --git a/matrix/matrix_multiplication_addition.py b/matrix/matrix_operation.py similarity index 80% rename from matrix/matrix_multiplication_addition.py rename to matrix/matrix_operation.py index dd50db729e43..dd7c01582681 100644 --- a/matrix/matrix_multiplication_addition.py +++ b/matrix/matrix_operation.py @@ -1,3 +1,5 @@ +from __future__ import print_function + def add(matrix_a, matrix_b): rows = len(matrix_a) columns = len(matrix_a[0]) @@ -63,13 +65,12 @@ def main(): matrix_b = [[3, 4], [7, 4]] matrix_c = [[11, 12, 13, 14], [21, 22, 23, 24], [31, 32, 33, 34], [41, 42, 43, 44]] matrix_d = [[3, 0, 2], [2, 0, -2], [0, 1, 1]] - - print(add(matrix_a, matrix_b)) - print(multiply(matrix_a, matrix_b)) - print(identity(5)) - print(minor(matrix_c , 1 , 2)) - print(determinant(matrix_b)) - print(inverse(matrix_d)) + print('Add Operation, %s + %s = %s \n' %(matrix_a, matrix_b, (add(matrix_a, matrix_b)))) + print('Multiply Operation, %s * %s = %s \n' %(matrix_a, matrix_b, multiply(matrix_a, matrix_b))) + print('Identity: %s \n' %identity(5)) + print('Minor of %s = %s \n' %(matrix_c, minor(matrix_c , 1 , 2))) + print('Determinant of %s = %s \n' %(matrix_b, determinant(matrix_b))) + print('Inverse of %s = %s\n'%(matrix_d, inverse(matrix_d))) if __name__ == '__main__': main() From af1925bcd9926c36a418acb5f5269455cc6c6f34 Mon Sep 17 00:00:00 2001 From: StephenGemin <45926479+StephenGemin@users.noreply.github.com> Date: Wed, 12 Jun 2019 10:54:30 -0400 Subject: [PATCH 0049/1071] Remove empty folder in analysis/compression_analysis (#897) * Add compression_analysis removing analysis/compression_analysis to just compression_analysis * Delete PSNR-example-base.png * Delete PSNR-example-comp-10.jpg * Delete compressed_image.png * Delete example_image.jpg * Delete example_wikipedia_image.jpg * Delete original_image.png * Delete psnr.py --- .../PSNR-example-base.png | Bin .../PSNR-example-comp-10.jpg | Bin .../compressed_image.png | Bin .../example_image.jpg | Bin .../example_wikipedia_image.jpg | Bin .../original_image.png | Bin .../psnr.py | 0 7 files changed, 0 insertions(+), 0 deletions(-) rename {analysis/compression_analysis => compression_analysis}/PSNR-example-base.png (100%) rename {analysis/compression_analysis => compression_analysis}/PSNR-example-comp-10.jpg (100%) rename {analysis/compression_analysis => compression_analysis}/compressed_image.png (100%) rename {analysis/compression_analysis => compression_analysis}/example_image.jpg (100%) rename {analysis/compression_analysis => compression_analysis}/example_wikipedia_image.jpg (100%) rename {analysis/compression_analysis => compression_analysis}/original_image.png (100%) rename {analysis/compression_analysis => compression_analysis}/psnr.py (100%) diff --git a/analysis/compression_analysis/PSNR-example-base.png b/compression_analysis/PSNR-example-base.png similarity index 100% rename from analysis/compression_analysis/PSNR-example-base.png rename to compression_analysis/PSNR-example-base.png diff --git a/analysis/compression_analysis/PSNR-example-comp-10.jpg b/compression_analysis/PSNR-example-comp-10.jpg similarity index 100% rename from analysis/compression_analysis/PSNR-example-comp-10.jpg rename to compression_analysis/PSNR-example-comp-10.jpg diff --git a/analysis/compression_analysis/compressed_image.png b/compression_analysis/compressed_image.png similarity index 100% rename from analysis/compression_analysis/compressed_image.png rename to compression_analysis/compressed_image.png diff --git a/analysis/compression_analysis/example_image.jpg b/compression_analysis/example_image.jpg similarity index 100% rename from analysis/compression_analysis/example_image.jpg rename to compression_analysis/example_image.jpg diff --git a/analysis/compression_analysis/example_wikipedia_image.jpg b/compression_analysis/example_wikipedia_image.jpg similarity index 100% rename from analysis/compression_analysis/example_wikipedia_image.jpg rename to compression_analysis/example_wikipedia_image.jpg diff --git a/analysis/compression_analysis/original_image.png b/compression_analysis/original_image.png similarity index 100% rename from analysis/compression_analysis/original_image.png rename to compression_analysis/original_image.png diff --git a/analysis/compression_analysis/psnr.py b/compression_analysis/psnr.py similarity index 100% rename from analysis/compression_analysis/psnr.py rename to compression_analysis/psnr.py From 1b3affc2eddc8288f8bb4a73167d99b3eb2c4fc8 Mon Sep 17 00:00:00 2001 From: Erfan Alimohammadi Date: Sun, 16 Jun 2019 01:07:23 +0430 Subject: [PATCH 0050/1071] fix typo (#902) --- machine_learning/linear_regression.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/machine_learning/linear_regression.py b/machine_learning/linear_regression.py index 8c23f1f77908..03f16629e451 100644 --- a/machine_learning/linear_regression.py +++ b/machine_learning/linear_regression.py @@ -1,6 +1,6 @@ """ Linear regression is the most basic type of regression commonly used for -predictive analysis. The idea is preety simple, we have a dataset and we have +predictive analysis. The idea is pretty simple, we have a dataset and we have a feature's associated with it. The Features should be choose very cautiously as they determine, how much our model will be able to make future predictions. We try to set these Feature weights, over many iterations, so that they best From 6e2fb22f5e9a821f226d87298b08d8da4b3b3efd Mon Sep 17 00:00:00 2001 From: archithadge <45902236+archithadge@users.noreply.github.com> Date: Sun, 16 Jun 2019 18:49:20 +0530 Subject: [PATCH 0051/1071] Problem 234 project Euler (#883) * Problem 234 project Euler * Update project_euler/problem_234 Co-Authored-By: Emmanuel Arias * Update project_euler/problem_234 Co-Authored-By: Emmanuel Arias * Update project_euler/problem_234 Co-Authored-By: Emmanuel Arias * Update project_euler/problem_234 Co-Authored-By: Emmanuel Arias * Update project_euler/problem_234 Co-Authored-By: Emmanuel Arias * Update project_euler/problem_234 Co-Authored-By: Emmanuel Arias * Update and rename problem_234 to problem_234.py * Made suggested changes else was not required temp declared afterwards suggested changes are correct.Thank u! * Rename project_euler/problem_234.py to project_euler/problem_234/sol1.py --- project_euler/problem_234/sol1.py | 32 +++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 project_euler/problem_234/sol1.py diff --git a/project_euler/problem_234/sol1.py b/project_euler/problem_234/sol1.py new file mode 100644 index 000000000000..c7a6bd97d66b --- /dev/null +++ b/project_euler/problem_234/sol1.py @@ -0,0 +1,32 @@ +# https://projecteuler.net/problem=234 +def fib(a, b, n): + + if n==1: + return a + elif n==2: + return b + elif n==3: + return str(a)+str(b) + + temp = 0 + for x in range(2,n): + c=str(a) + str(b) + temp = b + b = c + a = temp + return c + + +q=int(input()) +for x in range(q): + l=[i for i in input().split()] + c1=0 + c2=1 + while(1): + + if len(fib(l[0],l[1],c2)) Date: Mon, 17 Jun 2019 03:13:36 -0700 Subject: [PATCH 0052/1071] Corrected wrong DFS implementation (#903) --- graphs/DFS.py | 1 - 1 file changed, 1 deletion(-) diff --git a/graphs/DFS.py b/graphs/DFS.py index c9843ca25382..68bf60e3c298 100644 --- a/graphs/DFS.py +++ b/graphs/DFS.py @@ -16,7 +16,6 @@ def dfs(graph, start): to the node's children onto the iterator stack. When the iterator at the top of the stack terminates, we'll pop it off the stack.""" explored, stack = set(), [start] - explored.add(start) while stack: v = stack.pop() # one difference from BFS is to pop last element here instead of first one From ef147484ab456b6c55128d809bee05c1bf4638cf Mon Sep 17 00:00:00 2001 From: Daniel Xu Date: Mon, 17 Jun 2019 06:17:53 -0400 Subject: [PATCH 0053/1071] Added script for automatically generating DIRECTORY.md (#889) * Added script for automatically generating DIRECTORY.md * Sort and list files by alphabetical order * Rename script.py to ~script.py --- DIRECTORY.md | 374 +++++++++++++++++++++++++++++++++++++++++++++++++++ ~script.py | 70 ++++++++++ 2 files changed, 444 insertions(+) create mode 100644 DIRECTORY.md create mode 100644 ~script.py diff --git a/DIRECTORY.md b/DIRECTORY.md new file mode 100644 index 000000000000..ad25772b56b6 --- /dev/null +++ b/DIRECTORY.md @@ -0,0 +1,374 @@ +## Analysis + * Compression Analysis + * [psnr](https://github.com/TheAlgorithms/Python/blob/master/analysis/compression_analysis/psnr.py) +## Arithmetic Analysis + * [bisection](https://github.com/TheAlgorithms/Python/blob/master/arithmetic_analysis/bisection.py) + * [intersection](https://github.com/TheAlgorithms/Python/blob/master/arithmetic_analysis/intersection.py) + * [lu decomposition](https://github.com/TheAlgorithms/Python/blob/master/arithmetic_analysis/lu_decomposition.py) + * [newton method](https://github.com/TheAlgorithms/Python/blob/master/arithmetic_analysis/newton_method.py) + * [newton raphson method](https://github.com/TheAlgorithms/Python/blob/master/arithmetic_analysis/newton_raphson_method.py) +## Binary Tree + * [basic binary tree](https://github.com/TheAlgorithms/Python/blob/master/binary_tree/basic_binary_tree.py) +## Boolean Algebra + * [quine mc cluskey](https://github.com/TheAlgorithms/Python/blob/master/boolean_algebra/quine_mc_cluskey.py) +## Ciphers + * [affine cipher](https://github.com/TheAlgorithms/Python/blob/master/ciphers/affine_cipher.py) + * [Atbash](https://github.com/TheAlgorithms/Python/blob/master/ciphers/Atbash.py) + * [base16](https://github.com/TheAlgorithms/Python/blob/master/ciphers/base16.py) + * [base32](https://github.com/TheAlgorithms/Python/blob/master/ciphers/base32.py) + * [base64 cipher](https://github.com/TheAlgorithms/Python/blob/master/ciphers/base64_cipher.py) + * [base85](https://github.com/TheAlgorithms/Python/blob/master/ciphers/base85.py) + * [brute force caesar cipher](https://github.com/TheAlgorithms/Python/blob/master/ciphers/brute_force_caesar_cipher.py) + * [caesar cipher](https://github.com/TheAlgorithms/Python/blob/master/ciphers/caesar_cipher.py) + * [cryptomath module](https://github.com/TheAlgorithms/Python/blob/master/ciphers/cryptomath_module.py) + * [elgamal key generator](https://github.com/TheAlgorithms/Python/blob/master/ciphers/elgamal_key_generator.py) + * [hill cipher](https://github.com/TheAlgorithms/Python/blob/master/ciphers/hill_cipher.py) + * [morse Code implementation](https://github.com/TheAlgorithms/Python/blob/master/ciphers/morse_Code_implementation.py) + * [onepad cipher](https://github.com/TheAlgorithms/Python/blob/master/ciphers/onepad_cipher.py) + * [playfair cipher](https://github.com/TheAlgorithms/Python/blob/master/ciphers/playfair_cipher.py) + * [prehistoric men](https://github.com/TheAlgorithms/Python/blob/master/ciphers/prehistoric_men.txt) + * [rabin miller](https://github.com/TheAlgorithms/Python/blob/master/ciphers/rabin_miller.py) + * [rot13](https://github.com/TheAlgorithms/Python/blob/master/ciphers/rot13.py) + * [rsa cipher](https://github.com/TheAlgorithms/Python/blob/master/ciphers/rsa_cipher.py) + * [rsa key generator](https://github.com/TheAlgorithms/Python/blob/master/ciphers/rsa_key_generator.py) + * [simple substitution cipher](https://github.com/TheAlgorithms/Python/blob/master/ciphers/simple_substitution_cipher.py) + * [trafid cipher](https://github.com/TheAlgorithms/Python/blob/master/ciphers/trafid_cipher.py) + * [transposition cipher](https://github.com/TheAlgorithms/Python/blob/master/ciphers/transposition_cipher.py) + * [transposition cipher encrypt decrypt file](https://github.com/TheAlgorithms/Python/blob/master/ciphers/transposition_cipher_encrypt_decrypt_file.py) + * [vigenere cipher](https://github.com/TheAlgorithms/Python/blob/master/ciphers/vigenere_cipher.py) + * [xor cipher](https://github.com/TheAlgorithms/Python/blob/master/ciphers/xor_cipher.py) +## Compression + * [huffman](https://github.com/TheAlgorithms/Python/blob/master/compression/huffman.py) +## Data Structures + * [arrays](https://github.com/TheAlgorithms/Python/blob/master/data_structures/arrays.py) + * [avl](https://github.com/TheAlgorithms/Python/blob/master/data_structures/avl.py) + * [LCA](https://github.com/TheAlgorithms/Python/blob/master/data_structures/LCA.py) + * Binary Tree + * [AVL tree](https://github.com/TheAlgorithms/Python/blob/master/data_structures/binary%20tree/AVL_tree.py) + * [binary search tree](https://github.com/TheAlgorithms/Python/blob/master/data_structures/binary%20tree/binary_search_tree.py) + * [fenwick tree](https://github.com/TheAlgorithms/Python/blob/master/data_structures/binary%20tree/fenwick_tree.py) + * [lazy segment tree](https://github.com/TheAlgorithms/Python/blob/master/data_structures/binary%20tree/lazy_segment_tree.py) + * [segment tree](https://github.com/TheAlgorithms/Python/blob/master/data_structures/binary%20tree/segment_tree.py) + * [treap](https://github.com/TheAlgorithms/Python/blob/master/data_structures/binary%20tree/treap.py) + * Hashing + * [double hash](https://github.com/TheAlgorithms/Python/blob/master/data_structures/hashing/double_hash.py) + * [hash table](https://github.com/TheAlgorithms/Python/blob/master/data_structures/hashing/hash_table.py) + * [hash table with linked list](https://github.com/TheAlgorithms/Python/blob/master/data_structures/hashing/hash_table_with_linked_list.py) + * [quadratic probing](https://github.com/TheAlgorithms/Python/blob/master/data_structures/hashing/quadratic_probing.py) + * Number Theory + * [prime numbers](https://github.com/TheAlgorithms/Python/blob/master/data_structures/hashing/number_theory/prime_numbers.py) + * Heap + * [heap](https://github.com/TheAlgorithms/Python/blob/master/data_structures/heap/heap.py) + * Linked List + * [doubly linked list](https://github.com/TheAlgorithms/Python/blob/master/data_structures/linked_list/doubly_linked_list.py) + * [is Palindrome](https://github.com/TheAlgorithms/Python/blob/master/data_structures/linked_list/is_Palindrome.py) + * [singly linked list](https://github.com/TheAlgorithms/Python/blob/master/data_structures/linked_list/singly_linked_list.py) + * [swapNodes](https://github.com/TheAlgorithms/Python/blob/master/data_structures/linked_list/swapNodes.py) + * Queue + * [double ended queue](https://github.com/TheAlgorithms/Python/blob/master/data_structures/queue/double_ended_queue.py) + * [queue on list](https://github.com/TheAlgorithms/Python/blob/master/data_structures/queue/queue_on_list.py) + * [queue on pseudo stack](https://github.com/TheAlgorithms/Python/blob/master/data_structures/queue/queue_on_pseudo_stack.py) + * Stacks + * [balanced parentheses](https://github.com/TheAlgorithms/Python/blob/master/data_structures/stacks/balanced_parentheses.py) + * [infix to postfix conversion](https://github.com/TheAlgorithms/Python/blob/master/data_structures/stacks/infix_to_postfix_conversion.py) + * [infix to prefix conversion](https://github.com/TheAlgorithms/Python/blob/master/data_structures/stacks/infix_to_prefix_conversion.py) + * [next](https://github.com/TheAlgorithms/Python/blob/master/data_structures/stacks/next.py) + * [postfix evaluation](https://github.com/TheAlgorithms/Python/blob/master/data_structures/stacks/postfix_evaluation.py) + * [stack](https://github.com/TheAlgorithms/Python/blob/master/data_structures/stacks/stack.py) + * [stock span problem](https://github.com/TheAlgorithms/Python/blob/master/data_structures/stacks/stock_span_problem.py) + * Trie + * [trie](https://github.com/TheAlgorithms/Python/blob/master/data_structures/trie/trie.py) + * Union Find + * [tests union find](https://github.com/TheAlgorithms/Python/blob/master/data_structures/union_find/tests_union_find.py) + * [union find](https://github.com/TheAlgorithms/Python/blob/master/data_structures/union_find/union_find.py) +## Digital Image Processing + * Filters + * [median filter](https://github.com/TheAlgorithms/Python/blob/master/digital_image_processing/filters/median_filter.py) +## Dynamic Programming + * [abbreviation](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/abbreviation.py) + * [bitmask](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/bitmask.py) + * [coin change](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/coin_change.py) + * [edit distance](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/edit_distance.py) + * [fast fibonacci](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/fast_fibonacci.py) + * [fibonacci](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/fibonacci.py) + * [floyd warshall](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/floyd_warshall.py) + * [Fractional Knapsack](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/Fractional_Knapsack.py) + * [integer partition](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/integer_partition.py) + * [k means clustering tensorflow](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/k_means_clustering_tensorflow.py) + * [knapsack](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/knapsack.py) + * [longest common subsequence](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/longest_common_subsequence.py) + * [longest increasing subsequence](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/longest_increasing_subsequence.py) + * [longest increasing subsequence O(nlogn)](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/longest_increasing_subsequence_O(nlogn).py) + * [longest sub array](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/longest_sub_array.py) + * [matrix chain order](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/matrix_chain_order.py) + * [max sub array](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/max_sub_array.py) + * [minimum partition](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/minimum_partition.py) + * [rod cutting](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/rod_cutting.py) + * [subset generation](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/subset_generation.py) +## File Transfer Protocol + * [ftp client server](https://github.com/TheAlgorithms/Python/blob/master/file_transfer_protocol/ftp_client_server.py) + * [ftp send receive](https://github.com/TheAlgorithms/Python/blob/master/file_transfer_protocol/ftp_send_receive.py) +## Graphs + * [a star](https://github.com/TheAlgorithms/Python/blob/master/graphs/a_star.py) + * [articulation points](https://github.com/TheAlgorithms/Python/blob/master/graphs/articulation_points.py) + * [basic graphs](https://github.com/TheAlgorithms/Python/blob/master/graphs/basic_graphs.py) + * [bellman ford](https://github.com/TheAlgorithms/Python/blob/master/graphs/bellman_ford.py) + * [BFS](https://github.com/TheAlgorithms/Python/blob/master/graphs/BFS.py) + * [bfs shortest path](https://github.com/TheAlgorithms/Python/blob/master/graphs/bfs_shortest_path.py) + * [breadth first search](https://github.com/TheAlgorithms/Python/blob/master/graphs/breadth_first_search.py) + * [check bipartite graph bfs](https://github.com/TheAlgorithms/Python/blob/master/graphs/check_bipartite_graph_bfs.py) + * [check bipartite graph dfs](https://github.com/TheAlgorithms/Python/blob/master/graphs/check_bipartite_graph_dfs.py) + * [depth first search](https://github.com/TheAlgorithms/Python/blob/master/graphs/depth_first_search.py) + * [DFS](https://github.com/TheAlgorithms/Python/blob/master/graphs/DFS.py) + * [dijkstra](https://github.com/TheAlgorithms/Python/blob/master/graphs/dijkstra.py) + * [dijkstra 2](https://github.com/TheAlgorithms/Python/blob/master/graphs/dijkstra_2.py) + * [dijkstra algorithm](https://github.com/TheAlgorithms/Python/blob/master/graphs/dijkstra_algorithm.py) + * [Directed and Undirected (Weighted) Graph](https://github.com/TheAlgorithms/Python/blob/master/graphs/Directed_and_Undirected_(Weighted)_Graph.py) + * [edmonds karp multiple source and sink](https://github.com/TheAlgorithms/Python/blob/master/graphs/edmonds_karp_multiple_source_and_sink.py) + * [Eulerian path and circuit for undirected graph](https://github.com/TheAlgorithms/Python/blob/master/graphs/Eulerian_path_and_circuit_for_undirected_graph.py) + * [even tree](https://github.com/TheAlgorithms/Python/blob/master/graphs/even_tree.py) + * [finding bridges](https://github.com/TheAlgorithms/Python/blob/master/graphs/finding_bridges.py) + * [floyd warshall](https://github.com/TheAlgorithms/Python/blob/master/graphs/floyd_warshall.py) + * [graph list](https://github.com/TheAlgorithms/Python/blob/master/graphs/graph_list.py) + * [graph matrix](https://github.com/TheAlgorithms/Python/blob/master/graphs/graph_matrix.py) + * [kahns algorithm long](https://github.com/TheAlgorithms/Python/blob/master/graphs/kahns_algorithm_long.py) + * [kahns algorithm topo](https://github.com/TheAlgorithms/Python/blob/master/graphs/kahns_algorithm_topo.py) + * [minimum spanning tree kruskal](https://github.com/TheAlgorithms/Python/blob/master/graphs/minimum_spanning_tree_kruskal.py) + * [minimum spanning tree prims](https://github.com/TheAlgorithms/Python/blob/master/graphs/minimum_spanning_tree_prims.py) + * [multi hueristic astar](https://github.com/TheAlgorithms/Python/blob/master/graphs/multi_hueristic_astar.py) + * [page rank](https://github.com/TheAlgorithms/Python/blob/master/graphs/page_rank.py) + * [prim](https://github.com/TheAlgorithms/Python/blob/master/graphs/prim.py) + * [scc kosaraju](https://github.com/TheAlgorithms/Python/blob/master/graphs/scc_kosaraju.py) + * [tarjans scc](https://github.com/TheAlgorithms/Python/blob/master/graphs/tarjans_scc.py) +## Hashes + * [chaos machine](https://github.com/TheAlgorithms/Python/blob/master/hashes/chaos_machine.py) + * [md5](https://github.com/TheAlgorithms/Python/blob/master/hashes/md5.py) + * [sha1](https://github.com/TheAlgorithms/Python/blob/master/hashes/sha1.py) +## Linear Algebra Python + * Src + * [lib](https://github.com/TheAlgorithms/Python/blob/master/linear_algebra_python/src/lib.py) + * [tests](https://github.com/TheAlgorithms/Python/blob/master/linear_algebra_python/src/tests.py) +## Machine Learning + * [decision tree](https://github.com/TheAlgorithms/Python/blob/master/machine_learning/decision_tree.py) + * [gradient descent](https://github.com/TheAlgorithms/Python/blob/master/machine_learning/gradient_descent.py) + * [k means clust](https://github.com/TheAlgorithms/Python/blob/master/machine_learning/k_means_clust.py) + * [linear regression](https://github.com/TheAlgorithms/Python/blob/master/machine_learning/linear_regression.py) + * [logistic regression](https://github.com/TheAlgorithms/Python/blob/master/machine_learning/logistic_regression.py) + * [perceptron](https://github.com/TheAlgorithms/Python/blob/master/machine_learning/perceptron.py) + * [scoring functions](https://github.com/TheAlgorithms/Python/blob/master/machine_learning/scoring_functions.py) + * Random Forest Classification + * [random forest classification](https://github.com/TheAlgorithms/Python/blob/master/machine_learning/Random%20Forest%20Classification/random_forest_classification.py) + * [Social Network Ads](https://github.com/TheAlgorithms/Python/blob/master/machine_learning/Random%20Forest%20Classification/Social_Network_Ads.csv) + * Random Forest Regression + * [Position Salaries](https://github.com/TheAlgorithms/Python/blob/master/machine_learning/Random%20Forest%20Regression/Position_Salaries.csv) + * [random forest regression](https://github.com/TheAlgorithms/Python/blob/master/machine_learning/Random%20Forest%20Regression/random_forest_regression.py) +## Maths + * [3n+1](https://github.com/TheAlgorithms/Python/blob/master/maths/3n+1.py) + * [abs](https://github.com/TheAlgorithms/Python/blob/master/maths/abs.py) + * [abs Max](https://github.com/TheAlgorithms/Python/blob/master/maths/abs_Max.py) + * [abs Min](https://github.com/TheAlgorithms/Python/blob/master/maths/abs_Min.py) + * [average](https://github.com/TheAlgorithms/Python/blob/master/maths/average.py) + * [basic maths](https://github.com/TheAlgorithms/Python/blob/master/maths/basic_maths.py) + * [Binary Exponentiation](https://github.com/TheAlgorithms/Python/blob/master/maths/Binary_Exponentiation.py) + * [extended euclidean algorithm](https://github.com/TheAlgorithms/Python/blob/master/maths/extended_euclidean_algorithm.py) + * [factorial python](https://github.com/TheAlgorithms/Python/blob/master/maths/factorial_python.py) + * [factorial recursive](https://github.com/TheAlgorithms/Python/blob/master/maths/factorial_recursive.py) + * [fermat little theorem](https://github.com/TheAlgorithms/Python/blob/master/maths/fermat_little_theorem.py) + * [fibonacci](https://github.com/TheAlgorithms/Python/blob/master/maths/fibonacci.py) + * [fibonacci sequence recursion](https://github.com/TheAlgorithms/Python/blob/master/maths/fibonacci_sequence_recursion.py) + * [find lcm](https://github.com/TheAlgorithms/Python/blob/master/maths/find_lcm.py) + * [Find Max](https://github.com/TheAlgorithms/Python/blob/master/maths/Find_Max.py) + * [Find Min](https://github.com/TheAlgorithms/Python/blob/master/maths/Find_Min.py) + * [greater common divisor](https://github.com/TheAlgorithms/Python/blob/master/maths/greater_common_divisor.py) + * [Hanoi](https://github.com/TheAlgorithms/Python/blob/master/maths/Hanoi.py) + * [lucasSeries](https://github.com/TheAlgorithms/Python/blob/master/maths/lucasSeries.py) + * [modular exponential](https://github.com/TheAlgorithms/Python/blob/master/maths/modular_exponential.py) + * [newton raphson](https://github.com/TheAlgorithms/Python/blob/master/maths/newton_raphson.py) + * [Prime Check](https://github.com/TheAlgorithms/Python/blob/master/maths/Prime_Check.py) + * [segmented sieve](https://github.com/TheAlgorithms/Python/blob/master/maths/segmented_sieve.py) + * [sieve of eratosthenes](https://github.com/TheAlgorithms/Python/blob/master/maths/sieve_of_eratosthenes.py) + * [simpson rule](https://github.com/TheAlgorithms/Python/blob/master/maths/simpson_rule.py) + * [trapezoidal rule](https://github.com/TheAlgorithms/Python/blob/master/maths/trapezoidal_rule.py) + * Tests + * [test fibonacci](https://github.com/TheAlgorithms/Python/blob/master/maths/tests/test_fibonacci.py) +## Matrix + * [matrix multiplication addition](https://github.com/TheAlgorithms/Python/blob/master/matrix/matrix_multiplication_addition.py) + * [searching in sorted matrix](https://github.com/TheAlgorithms/Python/blob/master/matrix/searching_in_sorted_matrix.py) +## Networking Flow + * [ford fulkerson](https://github.com/TheAlgorithms/Python/blob/master/networking_flow/ford_fulkerson.py) + * [minimum cut](https://github.com/TheAlgorithms/Python/blob/master/networking_flow/minimum_cut.py) +## Neural Network + * [bpnn](https://github.com/TheAlgorithms/Python/blob/master/neural_network/bpnn.py) + * [convolution neural network](https://github.com/TheAlgorithms/Python/blob/master/neural_network/convolution_neural_network.py) + * [perceptron](https://github.com/TheAlgorithms/Python/blob/master/neural_network/perceptron.py) +## Other + * [anagrams](https://github.com/TheAlgorithms/Python/blob/master/other/anagrams.py) + * [binary exponentiation](https://github.com/TheAlgorithms/Python/blob/master/other/binary_exponentiation.py) + * [binary exponentiation 2](https://github.com/TheAlgorithms/Python/blob/master/other/binary_exponentiation_2.py) + * [detecting english programmatically](https://github.com/TheAlgorithms/Python/blob/master/other/detecting_english_programmatically.py) + * [dictionary](https://github.com/TheAlgorithms/Python/blob/master/other/dictionary.txt) + * [euclidean gcd](https://github.com/TheAlgorithms/Python/blob/master/other/euclidean_gcd.py) + * [finding Primes](https://github.com/TheAlgorithms/Python/blob/master/other/finding_Primes.py) + * [fischer yates shuffle](https://github.com/TheAlgorithms/Python/blob/master/other/fischer_yates_shuffle.py) + * [frequency finder](https://github.com/TheAlgorithms/Python/blob/master/other/frequency_finder.py) + * [linear congruential generator](https://github.com/TheAlgorithms/Python/blob/master/other/linear_congruential_generator.py) + * [n queens](https://github.com/TheAlgorithms/Python/blob/master/other/n_queens.py) + * [nested brackets](https://github.com/TheAlgorithms/Python/blob/master/other/nested_brackets.py) + * [palindrome](https://github.com/TheAlgorithms/Python/blob/master/other/palindrome.py) + * [password generator](https://github.com/TheAlgorithms/Python/blob/master/other/password_generator.py) + * [primelib](https://github.com/TheAlgorithms/Python/blob/master/other/primelib.py) + * [sierpinski triangle](https://github.com/TheAlgorithms/Python/blob/master/other/sierpinski_triangle.py) + * [tower of hanoi](https://github.com/TheAlgorithms/Python/blob/master/other/tower_of_hanoi.py) + * [two sum](https://github.com/TheAlgorithms/Python/blob/master/other/two_sum.py) + * [word patterns](https://github.com/TheAlgorithms/Python/blob/master/other/word_patterns.py) + * [words](https://github.com/TheAlgorithms/Python/blob/master/other/words) + * Game Of Life + * [game o life](https://github.com/TheAlgorithms/Python/blob/master/other/game_of_life/game_o_life.py) + * [sample](https://github.com/TheAlgorithms/Python/blob/master/other/game_of_life/sample.gif) +## Project Euler + * Problem 01 + * [sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_01/sol1.py) + * [sol2](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_01/sol2.py) + * [sol3](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_01/sol3.py) + * [sol4](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_01/sol4.py) + * [sol5](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_01/sol5.py) + * [sol6](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_01/sol6.py) + * Problem 02 + * [sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_02/sol1.py) + * [sol2](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_02/sol2.py) + * [sol3](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_02/sol3.py) + * [sol4](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_02/sol4.py) + * Problem 03 + * [sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_03/sol1.py) + * [sol2](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_03/sol2.py) + * Problem 04 + * [sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_04/sol1.py) + * [sol2](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_04/sol2.py) + * Problem 05 + * [sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_05/sol1.py) + * [sol2](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_05/sol2.py) + * Problem 06 + * [sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_06/sol1.py) + * [sol2](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_06/sol2.py) + * [sol3](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_06/sol3.py) + * Problem 07 + * [sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_07/sol1.py) + * [sol2](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_07/sol2.py) + * [sol3](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_07/sol3.py) + * Problem 08 + * [sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_08/sol1.py) + * [sol2](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_08/sol2.py) + * Problem 09 + * [sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_09/sol1.py) + * [sol2](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_09/sol2.py) + * [sol3](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_09/sol3.py) + * Problem 10 + * [sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_10/sol1.py) + * [sol2](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_10/sol2.py) + * Problem 11 + * [grid](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_11/grid.txt) + * [sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_11/sol1.py) + * [sol2](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_11/sol2.py) + * Problem 12 + * [sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_12/sol1.py) + * [sol2](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_12/sol2.py) + * Problem 13 + * [sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_13/sol1.py) + * Problem 14 + * [sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_14/sol1.py) + * [sol2](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_14/sol2.py) + * Problem 15 + * [sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_15/sol1.py) + * Problem 16 + * [sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_16/sol1.py) + * Problem 17 + * [sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_17/sol1.py) + * Problem 19 + * [sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_19/sol1.py) + * Problem 20 + * [sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_20/sol1.py) + * [sol2](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_20/sol2.py) + * Problem 21 + * [sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_21/sol1.py) + * Problem 22 + * [p022 names](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_22/p022_names.txt) + * [sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_22/sol1.py) + * [sol2](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_22/sol2.py) + * Problem 24 + * [sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_24/sol1.py) + * Problem 25 + * [sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_25/sol1.py) + * [sol2](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_25/sol2.py) + * Problem 28 + * [sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_28/sol1.py) + * Problem 29 + * [solution](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_29/solution.py) + * Problem 31 + * [sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_31/sol1.py) + * Problem 36 + * [sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_36/sol1.py) + * Problem 40 + * [sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_40/sol1.py) + * Problem 48 + * [sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_48/sol1.py) + * Problem 52 + * [sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_52/sol1.py) + * Problem 53 + * [sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_53/sol1.py) + * Problem 76 + * [sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_76/sol1.py) +## Searches + * [binary search](https://github.com/TheAlgorithms/Python/blob/master/searches/binary_search.py) + * [interpolation search](https://github.com/TheAlgorithms/Python/blob/master/searches/interpolation_search.py) + * [jump search](https://github.com/TheAlgorithms/Python/blob/master/searches/jump_search.py) + * [linear search](https://github.com/TheAlgorithms/Python/blob/master/searches/linear_search.py) + * [quick select](https://github.com/TheAlgorithms/Python/blob/master/searches/quick_select.py) + * [sentinel linear search](https://github.com/TheAlgorithms/Python/blob/master/searches/sentinel_linear_search.py) + * [tabu search](https://github.com/TheAlgorithms/Python/blob/master/searches/tabu_search.py) + * [tabu test data](https://github.com/TheAlgorithms/Python/blob/master/searches/tabu_test_data.txt) + * [ternary search](https://github.com/TheAlgorithms/Python/blob/master/searches/ternary_search.py) + * [test interpolation search](https://github.com/TheAlgorithms/Python/blob/master/searches/test_interpolation_search.py) + * [test tabu search](https://github.com/TheAlgorithms/Python/blob/master/searches/test_tabu_search.py) +## Simple Client + * [client](https://github.com/TheAlgorithms/Python/blob/master/simple_client/client.py) + * [server](https://github.com/TheAlgorithms/Python/blob/master/simple_client/server.py) +## Sorts + * [Bitonic Sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/Bitonic_Sort.py) + * [bogo sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/bogo_sort.py) + * [bubble sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/bubble_sort.py) + * [bucket sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/bucket_sort.py) + * [cocktail shaker sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/cocktail_shaker_sort.py) + * [comb sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/comb_sort.py) + * [counting sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/counting_sort.py) + * [cycle sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/cycle_sort.py) + * [external sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/external_sort.py) + * [gnome sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/gnome_sort.py) + * [heap sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/heap_sort.py) + * [insertion sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/insertion_sort.py) + * [merge sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/merge_sort.py) + * [merge sort fastest](https://github.com/TheAlgorithms/Python/blob/master/sorts/merge_sort_fastest.py) + * [Odd-Even transposition parallel](https://github.com/TheAlgorithms/Python/blob/master/sorts/Odd-Even_transposition_parallel.py) + * [Odd-Even transposition single-threaded](https://github.com/TheAlgorithms/Python/blob/master/sorts/Odd-Even_transposition_single-threaded.py) + * [pancake sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/pancake_sort.py) + * [pigeon sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/pigeon_sort.py) + * [quick sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/quick_sort.py) + * [quick sort 3 partition](https://github.com/TheAlgorithms/Python/blob/master/sorts/quick_sort_3_partition.py) + * [radix sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/radix_sort.py) + * [random normal distribution quicksort](https://github.com/TheAlgorithms/Python/blob/master/sorts/random_normal_distribution_quicksort.py) + * [random pivot quick sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/random_pivot_quick_sort.py) + * [selection sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/selection_sort.py) + * [shell sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/shell_sort.py) + * [tests](https://github.com/TheAlgorithms/Python/blob/master/sorts/tests.py) + * [tim sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/tim_sort.py) + * [topological sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/topological_sort.py) + * [tree sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/tree_sort.py) + * [wiggle sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/wiggle_sort.py) +## Strings + * [knuth morris pratt](https://github.com/TheAlgorithms/Python/blob/master/strings/knuth_morris_pratt.py) + * [levenshtein distance](https://github.com/TheAlgorithms/Python/blob/master/strings/levenshtein_distance.py) + * [manacher](https://github.com/TheAlgorithms/Python/blob/master/strings/manacher.py) + * [min cost string conversion](https://github.com/TheAlgorithms/Python/blob/master/strings/min_cost_string_conversion.py) + * [naive String Search](https://github.com/TheAlgorithms/Python/blob/master/strings/naive_String_Search.py) + * [rabin karp](https://github.com/TheAlgorithms/Python/blob/master/strings/rabin_karp.py) +## Traversals + * [binary tree traversals](https://github.com/TheAlgorithms/Python/blob/master/traversals/binary_tree_traversals.py) diff --git a/~script.py b/~script.py new file mode 100644 index 000000000000..4a2c61c83563 --- /dev/null +++ b/~script.py @@ -0,0 +1,70 @@ +""" +This is a simple script that will scan through the current directory +and generate the corresponding DIRECTORY.md file, can also specify +files or folders to be ignored. +""" +import os + + +# Target URL (master) +URL = "https://github.com/TheAlgorithms/Python/blob/master/" + + +def tree(d, ignores, ignores_ext): + return _markdown(d, ignores, ignores_ext, 0) + + +def _markdown(parent, ignores, ignores_ext, depth): + out = "" + dirs, files = [], [] + for i in os.listdir(parent): + full = os.path.join(parent, i) + name, ext = os.path.splitext(i) + if i in ignores or ext in ignores_ext: + continue + if os.path.isfile(full): + # generate list + pre = parent.replace("./", "").replace(" ", "%20") + # replace all spaces to safe URL + child = i.replace(" ", "%20") + files.append((pre, child, name)) + else: + dirs.append(i) + # Sort files + files.sort(key=lambda e: e[2].lower()) + for f in files: + pre, child, name = f + out += " " * depth + "* [" + name.replace("_", " ") + "](" + URL + pre + "/" + child + ")\n" + # Sort directories + dirs.sort() + for i in dirs: + full = os.path.join(parent, i) + i = i.replace("_", " ").title() + if depth == 0: + out += "## " + i + "\n" + else: + out += " " * depth + "* " + i + "\n" + out += _markdown(full, ignores, ignores_ext, depth+1) + return out + + +# Specific files or folders with the given names will be ignored +ignores = [".vs", + ".gitignore", + ".git", + "script.py", + "__init__.py", +] +# Files with given entensions will be ignored +ignores_ext = [ + ".md", + ".ipynb", + ".png", + ".jpg", + ".yml" +] + + +if __name__ == "__main__": + with open("DIRECTORY.md", "w+") as f: + f.write(tree(".", ignores, ignores_ext)) From b8937364dc18a8ba59ef5a3cdbc0ffc85b604c86 Mon Sep 17 00:00:00 2001 From: Hector S Date: Tue, 18 Jun 2019 06:27:08 -0400 Subject: [PATCH 0054/1071] Fixed typo and capitalized some words (#900) File looks more elegant now ;-) --- CONTRIBUTING.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 9b2ac0025dca..19b928c187f9 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -10,10 +10,10 @@ Welcome to [TheAlgorithms/Python](https://github.com/TheAlgorithms/Python)! Befo We are very happy that you consider implementing algorithms and data structure for others! This repository is referenced and used by learners from all over the globe. Being one of our contributors, you agree and confirm that: -- your did your work - no plagiarism allowed +- You did your work - no plagiarism allowed - Any plagiarized work will not be merged. -- your work will be distributed under [MIT License](License) once your pull request is merged -- you submitted work fulfils or mostly fulfils our styles and standards +- Your work will be distributed under [MIT License](License) once your pull request is merged +- You submitted work fulfils or mostly fulfils our styles and standards **New implementation** is welcome! For example, new solutions for a problem, different representations for a graph data structure or algorithm designs with different complexity. @@ -115,8 +115,8 @@ We want your work to be readable by others; therefore, we encourage you to note - Most importantly, - - **be consistent with this guidelines while submitting.** - - **join** [Gitter](https://gitter.im/TheAlgorithms) **now!** + - **Be consistent with this guidelines while submitting.** + - **Join** [Gitter](https://gitter.im/TheAlgorithms) **now!** - Happy coding! From a99acae32da09285b1a87c9e3dc9bf956c9077f6 Mon Sep 17 00:00:00 2001 From: StephenGemin <45926479+StephenGemin@users.noreply.github.com> Date: Tue, 18 Jun 2019 06:28:01 -0400 Subject: [PATCH 0055/1071] Add docstring and comments per Issue #727 (#895) I've added comments to make understanding this method a little easier for those that are not familiar. This should close out #727 . Other changes: 1. added if __name__ == '__main__' rather than the "# MAIN" comment 2. put in return for distances and vertices. Previously everything was just printed out, but someone may find it useful to have the algorithm return something. 3. Other PEP8 changes 4. Added example input and expected output as a check to make sure any future changes will give the same output. --- graphs/floyd_warshall.py | 102 ++++++++++++++++++++++++++++++--------- 1 file changed, 78 insertions(+), 24 deletions(-) diff --git a/graphs/floyd_warshall.py b/graphs/floyd_warshall.py index fae8b19b351a..a1d12aac02b4 100644 --- a/graphs/floyd_warshall.py +++ b/graphs/floyd_warshall.py @@ -1,9 +1,16 @@ +# floyd_warshall.py +""" + The problem is to find the shortest distance between all pairs of vertices in a weighted directed graph that can + have negative edge weights. +""" + from __future__ import print_function -def printDist(dist, V): + +def _print_dist(dist, v): print("\nThe shortest path matrix using Floyd Warshall algorithm\n") - for i in range(V): - for j in range(V): + for i in range(v): + for j in range(v): if dist[i][j] != float('inf') : print(int(dist[i][j]),end = "\t") else: @@ -12,37 +19,84 @@ def printDist(dist, V): -def FloydWarshall(graph, V): - dist=[[float('inf') for i in range(V)] for j in range(V)] +def floyd_warshall(graph, v): + """ + :param graph: 2D array calculated from weight[edge[i, j]] + :type graph: List[List[float]] + :param v: number of vertices + :type v: int + :return: shortest distance between all vertex pairs + distance[u][v] will contain the shortest distance from vertex u to v. + + 1. For all edges from v to n, distance[i][j] = weight(edge(i, j)). + 3. The algorithm then performs distance[i][j] = min(distance[i][j], distance[i][k] + distance[k][j]) for each + possible pair i, j of vertices. + 4. The above is repeated for each vertex k in the graph. + 5. Whenever distance[i][j] is given a new minimum value, next vertex[i][j] is updated to the next vertex[i][k]. + """ + + dist=[[float('inf') for _ in range(v)] for _ in range(v)] - for i in range(V): - for j in range(V): + for i in range(v): + for j in range(v): dist[i][j] = graph[i][j] - for k in range(V): - for i in range(V): - for j in range(V): + # check vertex k against all other vertices (i, j) + for k in range(v): + # looping through rows of graph array + for i in range(v): + # looping through columns of graph array + for j in range(v): if dist[i][k]!=float('inf') and dist[k][j]!=float('inf') and dist[i][k]+dist[k][j] < dist[i][j]: dist[i][j] = dist[i][k] + dist[k][j] - printDist(dist, V) + _print_dist(dist, v) + return dist, v -#MAIN -V = int(input("Enter number of vertices: ")) -E = int(input("Enter number of edges: ")) +if __name__== '__main__': + v = int(input("Enter number of vertices: ")) + e = int(input("Enter number of edges: ")) + + graph = [[float('inf') for i in range(v)] for j in range(v)] + + for i in range(v): + graph[i][i] = 0.0 + + # src and dst are indices that must be within the array size graph[e][v] + # failure to follow this will result in an error + for i in range(e): + print("\nEdge ",i+1) + src = int(input("Enter source:")) + dst = int(input("Enter destination:")) + weight = float(input("Enter weight:")) + graph[src][dst] = weight + + floyd_warshall(graph, v) + + + # Example Input + # Enter number of vertices: 3 + # Enter number of edges: 2 -graph = [[float('inf') for i in range(V)] for j in range(V)] + # # generated graph from vertex and edge inputs + # [[inf, inf, inf], [inf, inf, inf], [inf, inf, inf]] + # [[0.0, inf, inf], [inf, 0.0, inf], [inf, inf, 0.0]] -for i in range(V): - graph[i][i] = 0.0 + # specify source, destination and weight for edge #1 + # Edge 1 + # Enter source:1 + # Enter destination:2 + # Enter weight:2 -for i in range(E): - print("\nEdge ",i+1) - src = int(input("Enter source:")) - dst = int(input("Enter destination:")) - weight = float(input("Enter weight:")) - graph[src][dst] = weight + # specify source, destination and weight for edge #2 + # Edge 2 + # Enter source:2 + # Enter destination:1 + # Enter weight:1 -FloydWarshall(graph, V) + # # Expected Output from the vertice, edge and src, dst, weight inputs!! + # 0 INF INF + # INF 0 2 + # INF 1 0 From 12a16d63b7bdc1da0e0b215dfd5d8b938234f4ed Mon Sep 17 00:00:00 2001 From: Adeoti Ayodeji <33290249+Lord-sarcastic@users.noreply.github.com> Date: Sat, 22 Jun 2019 05:42:28 +0100 Subject: [PATCH 0056/1071] Update average.py (#908) Reduced lines of code and extra processing on the line: n += 1 --- maths/average.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/maths/average.py b/maths/average.py index dc70836b5e83..d15601dd64ca 100644 --- a/maths/average.py +++ b/maths/average.py @@ -1,11 +1,10 @@ def average(nums): sum = 0 - n = 0 for x in nums: sum += x - n += 1 - avg = sum / n + avg = sum / len(nums) print(avg) + return avg def main(): average([2, 4, 6, 8, 20, 50, 70]) From a212efee5b44312c8b4b626ae412bacc5f4117fd Mon Sep 17 00:00:00 2001 From: zachzhu2016 <48337051+zachzhu2016@users.noreply.github.com> Date: Sun, 23 Jun 2019 08:32:12 -0700 Subject: [PATCH 0057/1071] Corrected wrong Dijkstra priority queue implementation (#909) * Corrected wrong DFS implementation * changed list into hash set for dijkstra priority queue implementation. --- graphs/dijkstra.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/graphs/dijkstra.py b/graphs/dijkstra.py index 6b08b28fcfd3..4b6bc347b061 100644 --- a/graphs/dijkstra.py +++ b/graphs/dijkstra.py @@ -20,12 +20,12 @@ def dijkstra(graph, start, end): heap = [(0, start)] # cost from start node,end node - visited = [] + visited = set() while heap: (cost, u) = heapq.heappop(heap) if u in visited: continue - visited.append(u) + visited.add(u) if u == end: return cost for v, c in G[u]: From b7cff04574f5288c0483040c11be3bcc2b396a32 Mon Sep 17 00:00:00 2001 From: wuminbin Date: Mon, 24 Jun 2019 18:11:07 +0800 Subject: [PATCH 0058/1071] better implementation for midpoint (#914) --- arithmetic_analysis/bisection.py | 4 ++-- searches/binary_search.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/arithmetic_analysis/bisection.py b/arithmetic_analysis/bisection.py index c81fa84f81e1..8bf3f09782a3 100644 --- a/arithmetic_analysis/bisection.py +++ b/arithmetic_analysis/bisection.py @@ -14,7 +14,7 @@ def bisection(function, a, b): # finds where the function becomes 0 in [a,b] us print("couldn't find root in [a,b]") return else: - mid = (start + end) / 2 + mid = start + (end - start) / 2.0 while abs(start - mid) > 10**-7: # until we achieve precise equals to 10^-7 if function(mid) == 0: return mid @@ -22,7 +22,7 @@ def bisection(function, a, b): # finds where the function becomes 0 in [a,b] us end = mid else: start = mid - mid = (start + end) / 2 + mid = start + (end - start) / 2.0 return mid diff --git a/searches/binary_search.py b/searches/binary_search.py index 1d5da96586cd..e658dac2a3ef 100644 --- a/searches/binary_search.py +++ b/searches/binary_search.py @@ -45,7 +45,7 @@ def binary_search(sorted_collection, item): right = len(sorted_collection) - 1 while left <= right: - midpoint = (left + right) // 2 + midpoint = left + (right - left) // 2 current_item = sorted_collection[midpoint] if current_item == item: return midpoint From be4150c720187391488786053ac7a13eeb965446 Mon Sep 17 00:00:00 2001 From: brajesh-rit Date: Wed, 26 Jun 2019 10:57:08 -0500 Subject: [PATCH 0059/1071] Create spiralPrint.py (#844) * Create spiralPrint.py * Update spiralPrint.py --- matrix/spiralPrint.py | 66 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 66 insertions(+) create mode 100644 matrix/spiralPrint.py diff --git a/matrix/spiralPrint.py b/matrix/spiralPrint.py new file mode 100644 index 000000000000..447881e508e7 --- /dev/null +++ b/matrix/spiralPrint.py @@ -0,0 +1,66 @@ +""" +This program print the matix in spiral form. +This problem has been solved through recursive way. + + Matrix must satisfy below conditions + i) matrix should be only one or two dimensional + ii)column of all the row should be equal +""" +def checkMatrix(a): + # must be + if type(a) == list and len(a) > 0: + if type(a[0]) == list: + prevLen = 0 + for i in a: + if prevLen == 0: + prevLen = len(i) + result = True + elif prevLen == len(i): + result = True + else: + result = False + else: + result = True + else: + result = False + return result + + +def spiralPrint(a): + + if checkMatrix(a) and len(a) > 0: + + matRow = len(a) + if type(a[0]) == list: + matCol = len(a[0]) + else: + for dat in a: + print(dat), + return + + # horizotal printing increasing + for i in range(0, matCol): + print(a[0][i]), + # vertical printing down + for i in range(1, matRow): + print(a[i][matCol - 1]), + # horizotal printing decreasing + if matRow > 1: + for i in range(matCol - 2, -1, -1): + print(a[matRow - 1][i]), + # vertical printing up + for i in range(matRow - 2, 0, -1): + print(a[i][0]), + remainMat = [row[1:matCol - 1] for row in a[1:matRow - 1]] + if len(remainMat) > 0: + spiralPrint(remainMat) + else: + return + else: + print("Not a valid matrix") + return + + +# driver code +a = [[1 , 2, 3, 4],[5, 6, 7, 8],[9, 10, 11, 12]] +spiralPrint(a) From 34889fc6d8d3ac1cf5af11039cc1ec40185f778e Mon Sep 17 00:00:00 2001 From: BruceLee569 <49506152+BruceLee569@users.noreply.github.com> Date: Fri, 28 Jun 2019 23:55:31 +0800 Subject: [PATCH 0060/1071] Update quick_sort.py (#928) Use the last element as the first pivot, for it's easy to pop, this saves one element space. Iterating with the original list saves half the space, instead of generate a new shallow copy list by slice method. --- sorts/quick_sort.py | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/sorts/quick_sort.py b/sorts/quick_sort.py index 223c26fde1fe..7e8c868ebb06 100644 --- a/sorts/quick_sort.py +++ b/sorts/quick_sort.py @@ -33,17 +33,16 @@ def quick_sort(collection): if length <= 1: return collection else: - pivot = collection[0] - # Modify the list comprehensions to reduce the number of judgments, the speed has increased by more than 50%. - greater = [] - lesser = [] - for element in collection[1:]: + # Use the last element as the first pivot + pivot = collection.pop() + # Put elements greater than pivot in greater list + # Put elements lesser than pivot in lesser list + greater, lesser = [], [] + for element in collection: if element > pivot: greater.append(element) else: lesser.append(element) - # greater = [element for element in collection[1:] if element > pivot] - # lesser = [element for element in collection[1:] if element <= pivot] return quick_sort(lesser) + [pivot] + quick_sort(greater) From 2333f933236fbd57f9ed8644b0f8f7a19d10a2e7 Mon Sep 17 00:00:00 2001 From: PatOnTheBack <51241310+PatOnTheBack@users.noreply.github.com> Date: Sun, 30 Jun 2019 00:41:26 -0400 Subject: [PATCH 0061/1071] Change Declaration of Var 'j' to None (#921) Since `j` is redefined before it is used, it makes more sense to declare it with the value `None` instead of `1`. This fixes a [warning from lgtm](https://lgtm.com/projects/g/TheAlgorithms/Python/snapshot/66c4afbd0f28f9989f35ddbeb5c9263390c5d192/files/other/primelib.py?sort=name&dir=ASC&mode=heatmap) --- other/primelib.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/other/primelib.py b/other/primelib.py index 19572f8611cb..c371bc1b9861 100644 --- a/other/primelib.py +++ b/other/primelib.py @@ -283,7 +283,7 @@ def goldbach(number): # run variable for while-loops. i = 0 - j = 1 + j = None # exit variable. for break up the loops loop = True From bd4017928ed3054016ea21b8464f36db5fa007dc Mon Sep 17 00:00:00 2001 From: PatOnTheBack <51241310+PatOnTheBack@users.noreply.github.com> Date: Mon, 1 Jul 2019 04:10:18 -0400 Subject: [PATCH 0062/1071] Added Whitespace and Docstring (#924) * Added Whitespace and Docstring I modified the file to make Pylint happier and make the code more readable. * Beautified Code and Added Docstring I modified the file to make Pylint happier and make the code more readable. * Added DOCSTRINGS, Wikipedia link, and whitespace I added DOCSTRINGS and whitespace to make the code more readable and understandable. * Improved Formatting * Wrapped comments * Fixed spelling error for `movement` variable * Added DOCSTRINGs * Improved Formatting * Corrected whitespace to improve readability. * Added docstrings. * Made comments fit inside an 80 column layout. --- arithmetic_analysis/lu_decomposition.py | 48 +++++++++++++------------ arithmetic_analysis/newton_method.py | 29 +++++++++------ maths/Hanoi.py | 21 ++++++----- maths/abs.py | 11 ++++-- maths/average.py | 13 +++++-- maths/find_lcm.py | 7 ++++ sorts/bucket_sort.py | 31 ++++++++++------ sorts/gnome_sort.py | 20 ++++++----- sorts/tests.py | 8 +++-- sorts/topological_sort.py | 3 ++ sorts/tree_sort.py | 33 ++++++++++------- sorts/wiggle_sort.py | 17 ++++++--- 12 files changed, 154 insertions(+), 87 deletions(-) diff --git a/arithmetic_analysis/lu_decomposition.py b/arithmetic_analysis/lu_decomposition.py index f291d2dfe003..19e259afb826 100644 --- a/arithmetic_analysis/lu_decomposition.py +++ b/arithmetic_analysis/lu_decomposition.py @@ -1,32 +1,36 @@ +"""Lower-Upper (LU) Decomposition.""" + # lower–upper (LU) decomposition - https://en.wikipedia.org/wiki/LU_decomposition import numpy -def LUDecompose (table): + +def LUDecompose(table): # Table that contains our data # Table has to be a square array so we need to check first - rows,columns=numpy.shape(table) - L=numpy.zeros((rows,columns)) - U=numpy.zeros((rows,columns)) - if rows!=columns: + rows, columns = numpy.shape(table) + L = numpy.zeros((rows, columns)) + U = numpy.zeros((rows, columns)) + if rows != columns: return [] - for i in range (columns): - for j in range(i-1): - sum=0 - for k in range (j-1): - sum+=L[i][k]*U[k][j] - L[i][j]=(table[i][j]-sum)/U[j][j] - L[i][i]=1 - for j in range(i-1,columns): - sum1=0 - for k in range(i-1): - sum1+=L[i][k]*U[k][j] - U[i][j]=table[i][j]-sum1 - return L,U + for i in range(columns): + for j in range(i - 1): + sum = 0 + for k in range(j - 1): + sum += L[i][k] * U[k][j] + L[i][j] = (table[i][j] - sum) / U[j][j] + L[i][i] = 1 + for j in range(i - 1, columns): + sum1 = 0 + for k in range(i - 1): + sum1 += L[i][k] * U[k][j] + U[i][j] = table[i][j] - sum1 + return L, U + if __name__ == "__main__": - matrix =numpy.array([[2,-2,1], - [0,1,2], - [5,3,1]]) - L,U = LUDecompose(matrix) + matrix = numpy.array([[2, -2, 1], + [0, 1, 2], + [5, 3, 1]]) + L, U = LUDecompose(matrix) print(L) print(U) diff --git a/arithmetic_analysis/newton_method.py b/arithmetic_analysis/newton_method.py index 2ed29502522e..cf5649ee3f3b 100644 --- a/arithmetic_analysis/newton_method.py +++ b/arithmetic_analysis/newton_method.py @@ -1,18 +1,25 @@ +"""Newton's Method.""" + # Newton's Method - https://en.wikipedia.org/wiki/Newton%27s_method -def newton(function,function1,startingInt): #function is the f(x) and function1 is the f'(x) - x_n=startingInt - while True: - x_n1=x_n-function(x_n)/function1(x_n) - if abs(x_n-x_n1) < 10**-5: - return x_n1 - x_n=x_n1 - + +# function is the f(x) and function1 is the f'(x) +def newton(function, function1, startingInt): + x_n = startingInt + while True: + x_n1 = x_n - function(x_n) / function1(x_n) + if abs(x_n - x_n1) < 10**-5: + return x_n1 + x_n = x_n1 + + def f(x): - return (x**3) - (2 * x) -5 + return (x**3) - (2 * x) - 5 + def f1(x): - return 3 * (x**2) -2 + return 3 * (x**2) - 2 + if __name__ == "__main__": - print(newton(f,f1,3)) + print(newton(f, f1, 3)) diff --git a/maths/Hanoi.py b/maths/Hanoi.py index dd04d0fa58d8..c7b435a8fe3e 100644 --- a/maths/Hanoi.py +++ b/maths/Hanoi.py @@ -1,5 +1,8 @@ +"""Tower of Hanoi.""" + # @author willx75 -# Tower of Hanoi recursion game algorithm is a game, it consists of three rods and a number of disks of different sizes, which can slide onto any rod +# Tower of Hanoi recursion game algorithm is a game, it consists of three rods +# and a number of disks of different sizes, which can slide onto any rod import logging @@ -7,18 +10,20 @@ logging.basicConfig(level=logging.DEBUG) -def Tower_Of_Hanoi(n, source, dest, by, mouvement): +def Tower_Of_Hanoi(n, source, dest, by, movement): + """Tower of Hanoi - Move plates to different rods.""" if n == 0: return n elif n == 1: - mouvement += 1 - # no print statement (you could make it an optional flag for printing logs) + movement += 1 + # no print statement + # (you could make it an optional flag for printing logs) logging.debug('Move the plate from', source, 'to', dest) - return mouvement + return movement else: - mouvement = mouvement + Tower_Of_Hanoi(n-1, source, by, dest, 0) + movement = movement + Tower_Of_Hanoi(n - 1, source, by, dest, 0) logging.debug('Move the plate from', source, 'to', dest) - mouvement = mouvement + 1 + Tower_Of_Hanoi(n-1, by, dest, source, 0) - return mouvement + movement = movement + 1 + Tower_Of_Hanoi(n - 1, by, dest, source, 0) + return movement diff --git a/maths/abs.py b/maths/abs.py index 6d0596478d5f..624823fc183e 100644 --- a/maths/abs.py +++ b/maths/abs.py @@ -1,6 +1,10 @@ +"""Absolute Value.""" + + def absVal(num): """ - Function to fins absolute value of numbers. + Find the absolute value of a number. + >>absVal(-5) 5 >>absVal(0) @@ -11,8 +15,11 @@ def absVal(num): else: return num + def main(): - print(absVal(-34)) # = 34 + """Print absolute value of -34.""" + print(absVal(-34)) # = 34 + if __name__ == '__main__': main() diff --git a/maths/average.py b/maths/average.py index d15601dd64ca..78387111022d 100644 --- a/maths/average.py +++ b/maths/average.py @@ -1,13 +1,20 @@ +"""Find mean of a list of numbers.""" + + def average(nums): + """Find mean of a list of numbers.""" sum = 0 for x in nums: - sum += x + sum += x avg = sum / len(nums) print(avg) return avg + def main(): - average([2, 4, 6, 8, 20, 50, 70]) + """Call average module to find mean of a specific list of numbers.""" + average([2, 4, 6, 8, 20, 50, 70]) + if __name__ == '__main__': - main() + main() diff --git a/maths/find_lcm.py b/maths/find_lcm.py index 126242699ab7..779cb128898e 100644 --- a/maths/find_lcm.py +++ b/maths/find_lcm.py @@ -1,4 +1,10 @@ +"""Find Least Common Multiple.""" + +# https://en.wikipedia.org/wiki/Least_common_multiple + + def find_lcm(num_1, num_2): + """Find the LCM of two numbers.""" max = num_1 if num_1 > num_2 else num_2 lcm = max while (True): @@ -9,6 +15,7 @@ def find_lcm(num_1, num_2): def main(): + """Use test numbers to run the find_lcm algorithm.""" num_1 = 12 num_2 = 76 print(find_lcm(num_1, num_2)) diff --git a/sorts/bucket_sort.py b/sorts/bucket_sort.py index c4d61874fc47..5c4a71513ed3 100644 --- a/sorts/bucket_sort.py +++ b/sorts/bucket_sort.py @@ -1,19 +1,26 @@ #!/usr/bin/env python + +"""Illustrate how to implement bucket sort algorithm.""" + # Author: OMKAR PATHAK # This program will illustrate how to implement bucket sort algorithm -# Wikipedia says: Bucket sort, or bin sort, is a sorting algorithm that works by distributing the -# elements of an array into a number of buckets. Each bucket is then sorted individually, either using -# a different sorting algorithm, or by recursively applying the bucket sorting algorithm. It is a -# distribution sort, and is a cousin of radix sort in the most to least significant digit flavour. -# Bucket sort is a generalization of pigeonhole sort. Bucket sort can be implemented with comparisons -# and therefore can also be considered a comparison sort algorithm. The computational complexity estimates -# involve the number of buckets. +# Wikipedia says: Bucket sort, or bin sort, is a sorting algorithm that works +# by distributing the elements of an array into a number of buckets. +# Each bucket is then sorted individually, either using a different sorting +# algorithm, or by recursively applying the bucket sorting algorithm. It is a +# distribution sort, and is a cousin of radix sort in the most to least +# significant digit flavour. +# Bucket sort is a generalization of pigeonhole sort. Bucket sort can be +# implemented with comparisons and therefore can also be considered a +# comparison sort algorithm. The computational complexity estimates involve the +# number of buckets. # Time Complexity of Solution: # Best Case O(n); Average Case O(n); Worst Case O(n) -DEFAULT_BUCKET_SIZE=5 +DEFAULT_BUCKET_SIZE = 5 + def bucket_sort(my_list, bucket_size=DEFAULT_BUCKET_SIZE): if len(my_list) == 0: @@ -24,12 +31,14 @@ def bucket_sort(my_list, bucket_size=DEFAULT_BUCKET_SIZE): buckets = [[] for _ in range(int(bucket_count))] for i in range(len(my_list)): - buckets[int((my_list[i] - min_value) // bucket_size)].append(my_list[i]) + buckets[int((my_list[i] - min_value) // bucket_size) + ].append(my_list[i]) return sorted([buckets[i][j] for i in range(len(buckets)) - for j in range(len(buckets[i]))]) + for j in range(len(buckets[i]))]) + if __name__ == "__main__": user_input = input('Enter numbers separated by a comma:').strip() unsorted = [float(n) for n in user_input.split(',') if len(user_input) > 0] - print(bucket_sort(unsorted)) \ No newline at end of file + print(bucket_sort(unsorted)) diff --git a/sorts/gnome_sort.py b/sorts/gnome_sort.py index 2927b097f11d..075749e37663 100644 --- a/sorts/gnome_sort.py +++ b/sorts/gnome_sort.py @@ -1,29 +1,31 @@ +"""Gnome Sort Algorithm.""" + from __future__ import print_function + def gnome_sort(unsorted): - """ - Pure implementation of the gnome sort algorithm in Python. - """ + """Pure implementation of the gnome sort algorithm in Python.""" if len(unsorted) <= 1: return unsorted - + i = 1 - + while i < len(unsorted): - if unsorted[i-1] <= unsorted[i]: + if unsorted[i - 1] <= unsorted[i]: i += 1 else: - unsorted[i-1], unsorted[i] = unsorted[i], unsorted[i-1] + unsorted[i - 1], unsorted[i] = unsorted[i], unsorted[i - 1] i -= 1 if (i == 0): i = 1 - + + if __name__ == '__main__': try: raw_input # Python 2 except NameError: raw_input = input # Python 3 - + user_input = raw_input('Enter numbers separated by a comma:\n').strip() unsorted = [int(item) for item in user_input.split(',')] gnome_sort(unsorted) diff --git a/sorts/tests.py b/sorts/tests.py index 225763625f51..ec8c8361912f 100644 --- a/sorts/tests.py +++ b/sorts/tests.py @@ -1,3 +1,5 @@ +"""Test Sort Algorithms for Errors.""" + from bogo_sort import bogo_sort from bubble_sort import bubble_sort from bucket_sort import bucket_sort @@ -36,8 +38,8 @@ TODO: - Fix some broken tests in particular cases (as [] for example), - Unify the input format: should always be function(input_collection) (no additional args) - - Unify the output format: should always be a collection instead of updating input elements - and returning None + - Unify the output format: should always be a collection instead of + updating input elements and returning None - Rewrite some algorithms in function format (in case there is no function definition) ''' @@ -71,4 +73,4 @@ for function in TEST_FUNCTIONS: for case in TEST_CASES: result = function(case['input']) - assert result == case['expected'], 'Executed function: {}, {} != {}'.format(function.__name__, result, case['expected']) + assert result == case['expected'], 'Executed function: {}, {} != {}'.format(function.__name__, result, case['expected']) diff --git a/sorts/topological_sort.py b/sorts/topological_sort.py index db4dd250a119..400cfb4ca270 100644 --- a/sorts/topological_sort.py +++ b/sorts/topological_sort.py @@ -1,3 +1,5 @@ +"""Topological Sort.""" + from __future__ import print_function # a # / \ @@ -28,6 +30,7 @@ def topological_sort(start, visited, sort): # return sort return sort + if __name__ == '__main__': sort = topological_sort('a', [], []) print(sort) diff --git a/sorts/tree_sort.py b/sorts/tree_sort.py index d06b0de28e56..baa4fc1acc20 100644 --- a/sorts/tree_sort.py +++ b/sorts/tree_sort.py @@ -1,14 +1,18 @@ -# Tree_sort algorithm -# Build a BST and in order traverse. +""" +Tree_sort algorithm. + +Build a BST and in order traverse. +""" + class node(): # BST data structure def __init__(self, val): self.val = val - self.left = None - self.right = None - - def insert(self,val): + self.left = None + self.right = None + + def insert(self, val): if self.val: if val < self.val: if self.left is None: @@ -23,24 +27,27 @@ def insert(self,val): else: self.val = val + def inorder(root, res): - # Recursive travesal + # Recursive travesal if root: - inorder(root.left,res) + inorder(root.left, res) res.append(root.val) - inorder(root.right,res) + inorder(root.right, res) + def tree_sort(arr): # Build BST if len(arr) == 0: return arr root = node(arr[0]) - for i in range(1,len(arr)): + for i in range(1, len(arr)): root.insert(arr[i]) - # Traverse BST in order. + # Traverse BST in order. res = [] - inorder(root,res) + inorder(root, res) return res + if __name__ == '__main__': - print(tree_sort([10,1,3,2,9,14,13])) + print(tree_sort([10, 1, 3, 2, 9, 14, 13])) diff --git a/sorts/wiggle_sort.py b/sorts/wiggle_sort.py index 0d4f20e3f96b..606feb4d3dd1 100644 --- a/sorts/wiggle_sort.py +++ b/sorts/wiggle_sort.py @@ -1,17 +1,24 @@ """ -Given an unsorted array nums, reorder it such that nums[0] < nums[1] > nums[2] < nums[3].... +Wiggle Sort. + +Given an unsorted array nums, reorder it such +that nums[0] < nums[1] > nums[2] < nums[3].... For example: -if input numbers = [3, 5, 2, 1, 6, 4] +if input numbers = [3, 5, 2, 1, 6, 4] one possible Wiggle Sorted answer is [3, 5, 1, 6, 2, 4]. """ + + def wiggle_sort(nums): + """Perform Wiggle Sort.""" for i in range(len(nums)): - if (i % 2 == 1) == (nums[i-1] > nums[i]): - nums[i-1], nums[i] = nums[i], nums[i-1] + if (i % 2 == 1) == (nums[i - 1] > nums[i]): + nums[i - 1], nums[i] = nums[i], nums[i - 1] + if __name__ == '__main__': print("Enter the array elements:\n") - array=list(map(int,input().split())) + array = list(map(int, input().split())) print("The unsorted array is:\n") print(array) wiggle_sort(array) From a2236cfb97ce96b13c03be9761a3d053997aace9 Mon Sep 17 00:00:00 2001 From: PatOnTheBack <51241310+PatOnTheBack@users.noreply.github.com> Date: Tue, 2 Jul 2019 00:05:43 -0400 Subject: [PATCH 0063/1071] Improve Formatting and Code Quality (#934) * Improved Formatting of basic_maths.py - Added docstrings. - Improved whitespace formatting. - Renamed functions to match snake_case. * Improved Formatting of factorial_python.py - Added docstrings. - Improved whitespace formatting. - Renamed constants to match UPPER_CASE. * Improved Formatting of factorial_recursive.py - Improved whitespace formatting to meet PyLint standards. * Improved Code to Conform to PyLint - Renamed `max` to `max_num` to avoid redefining built-in 'max' [pylint] - Removed unnecessary parens after 'while' keyword [pylint] * Improved Formatting of factorial_recursive.py - Added docstrings. - Improved whitespace formatting. --- maths/basic_maths.py | 66 +++++++++++++++++++++--------------- maths/factorial_python.py | 22 ++++++------ maths/factorial_recursive.py | 17 +++++----- maths/find_lcm.py | 8 ++--- sorts/pancake_sort.py | 15 ++++---- 5 files changed, 71 insertions(+), 57 deletions(-) diff --git a/maths/basic_maths.py b/maths/basic_maths.py index 6e8c919a001d..cd7bac0113b8 100644 --- a/maths/basic_maths.py +++ b/maths/basic_maths.py @@ -1,74 +1,84 @@ +"""Implementation of Basic Math in Python.""" import math -def primeFactors(n): + +def prime_factors(n): + """Find Prime Factors.""" pf = [] while n % 2 == 0: pf.append(2) n = int(n / 2) - - for i in range(3, int(math.sqrt(n))+1, 2): + + for i in range(3, int(math.sqrt(n)) + 1, 2): while n % i == 0: pf.append(i) n = int(n / i) - + if n > 2: pf.append(n) - + return pf -def numberOfDivisors(n): + +def number_of_divisors(n): + """Calculate Number of Divisors of an Integer.""" div = 1 - + temp = 1 while n % 2 == 0: temp += 1 n = int(n / 2) - div = div * (temp) - - for i in range(3, int(math.sqrt(n))+1, 2): + div = div * (temp) + + for i in range(3, int(math.sqrt(n)) + 1, 2): temp = 1 while n % i == 0: temp += 1 n = int(n / i) div = div * (temp) - + return div -def sumOfDivisors(n): + +def sum_of_divisors(n): + """Calculate Sum of Divisors.""" s = 1 - + temp = 1 while n % 2 == 0: temp += 1 n = int(n / 2) if temp > 1: - s *= (2**temp - 1) / (2 - 1) - - for i in range(3, int(math.sqrt(n))+1, 2): + s *= (2**temp - 1) / (2 - 1) + + for i in range(3, int(math.sqrt(n)) + 1, 2): temp = 1 while n % i == 0: temp += 1 n = int(n / i) if temp > 1: s *= (i**temp - 1) / (i - 1) - + return s -def eulerPhi(n): - l = primeFactors(n) + +def euler_phi(n): + """Calculte Euler's Phi Function.""" + l = prime_factors(n) l = set(l) s = n for x in l: - s *= (x - 1)/x - return s + s *= (x - 1) / x + return s + def main(): - print(primeFactors(100)) - print(numberOfDivisors(100)) - print(sumOfDivisors(100)) - print(eulerPhi(100)) - + """Print the Results of Basic Math Operations.""" + print(prime_factors(100)) + print(number_of_divisors(100)) + print(sum_of_divisors(100)) + print(euler_phi(100)) + + if __name__ == '__main__': main() - - \ No newline at end of file diff --git a/maths/factorial_python.py b/maths/factorial_python.py index 376983e08dab..6c1349fd5f4c 100644 --- a/maths/factorial_python.py +++ b/maths/factorial_python.py @@ -1,19 +1,19 @@ -# Python program to find the factorial of a number provided by the user. +"""Python program to find the factorial of a number provided by the user.""" # change the value for a different result -num = 10 +NUM = 10 # uncomment to take input from the user -#num = int(input("Enter a number: ")) +# num = int(input("Enter a number: ")) -factorial = 1 +FACTORIAL = 1 # check if the number is negative, positive or zero -if num < 0: - print("Sorry, factorial does not exist for negative numbers") -elif num == 0: - print("The factorial of 0 is 1") +if NUM < 0: + print("Sorry, factorial does not exist for negative numbers") +elif NUM == 0: + print("The factorial of 0 is 1") else: - for i in range(1,num + 1): - factorial = factorial*i - print("The factorial of",num,"is",factorial) + for i in range(1, NUM + 1): + FACTORIAL = FACTORIAL * i + print("The factorial of", NUM, "is", FACTORIAL) diff --git a/maths/factorial_recursive.py b/maths/factorial_recursive.py index 41391a2718f6..06173dcbcd7d 100644 --- a/maths/factorial_recursive.py +++ b/maths/factorial_recursive.py @@ -1,13 +1,14 @@ def fact(n): - """ - Return 1, if n is 1 or below, - otherwise, return n * fact(n-1). - """ - return 1 if n <= 1 else n * fact(n-1) + """ + Return 1, if n is 1 or below, + otherwise, return n * fact(n-1). + """ + return 1 if n <= 1 else n * fact(n - 1) + """ -Shown factorial for i, +Show factorial for i, where i ranges from 1 to 20. """ -for i in range(1,21): - print(i, ": ", fact(i), sep='') +for i in range(1, 21): + print(i, ": ", fact(i), sep='') diff --git a/maths/find_lcm.py b/maths/find_lcm.py index 779cb128898e..9062d462b8b3 100644 --- a/maths/find_lcm.py +++ b/maths/find_lcm.py @@ -5,12 +5,12 @@ def find_lcm(num_1, num_2): """Find the LCM of two numbers.""" - max = num_1 if num_1 > num_2 else num_2 - lcm = max - while (True): + max_num = num_1 if num_1 > num_2 else num_2 + lcm = max_num + while True: if ((lcm % num_1 == 0) and (lcm % num_2 == 0)): break - lcm += max + lcm += max_num return lcm diff --git a/sorts/pancake_sort.py b/sorts/pancake_sort.py index 478a9a967d27..3b48bc6e46d9 100644 --- a/sorts/pancake_sort.py +++ b/sorts/pancake_sort.py @@ -1,17 +1,20 @@ -# Pancake sort algorithm +"""Pancake Sort Algorithm.""" # Only can reverse array from 0 to i + def pancake_sort(arr): + """Sort Array with Pancake Sort.""" cur = len(arr) while cur > 1: # Find the maximum number in arr mi = arr.index(max(arr[0:cur])) - # Reverse from 0 to mi - arr = arr[mi::-1] + arr[mi+1:len(arr)] - # Reverse whole list - arr = arr[cur-1::-1] + arr[cur:len(arr)] + # Reverse from 0 to mi + arr = arr[mi::-1] + arr[mi + 1:len(arr)] + # Reverse whole list + arr = arr[cur - 1::-1] + arr[cur:len(arr)] cur -= 1 return arr + if __name__ == '__main__': - print(pancake_sort([0,10,15,3,2,9,14,13])) + print(pancake_sort([0, 10, 15, 3, 2, 9, 14, 13])) From 27a8184ccf871a3afa22839761bf507306c52d58 Mon Sep 17 00:00:00 2001 From: Dharni0607 <30770547+Dharni0607@users.noreply.github.com> Date: Tue, 2 Jul 2019 17:49:31 +0530 Subject: [PATCH 0064/1071] add ons in string directory - Bayer_Moore_Search (#933) --- strings/Boyer_Moore_Search.py | 88 +++++++++++++++++++++++++++++++++++ 1 file changed, 88 insertions(+) create mode 100644 strings/Boyer_Moore_Search.py diff --git a/strings/Boyer_Moore_Search.py b/strings/Boyer_Moore_Search.py new file mode 100644 index 000000000000..781ff0ca6106 --- /dev/null +++ b/strings/Boyer_Moore_Search.py @@ -0,0 +1,88 @@ +""" +The algorithm finds the pattern in given text using following rule. + +The bad-character rule considers the mismatched character in Text. +The next occurrence of that character to the left in Pattern is found, + +If the mismatched character occurs to the left in Pattern, +a shift is proposed that aligns text block and pattern. + +If the mismatched character does not occur to the left in Pattern, +a shift is proposed that moves the entirety of Pattern past +the point of mismatch in the text. + +If there no mismatch then the pattern matches with text block. + +Time Complexity : O(n/m) + n=length of main string + m=length of pattern string +""" + + +class BoyerMooreSearch: + + + def __init__(self, text, pattern): + self.text, self.pattern = text, pattern + self.textLen, self.patLen = len(text), len(pattern) + + + def match_in_pattern(self, char): + """ finds the index of char in pattern in reverse order + + Paremeters : + char (chr): character to be searched + + Returns : + i (int): index of char from last in pattern + -1 (int): if char is not found in pattern + """ + + for i in range(self.patLen-1, -1, -1): + if char == self.pattern[i]: + return i + return -1 + + + def mismatch_in_text(self, currentPos): + """ finds the index of mis-matched character in text when compared with pattern from last + + Paremeters : + currentPos (int): current index position of text + + Returns : + i (int): index of mismatched char from last in text + -1 (int): if there is no mis-match between pattern and text block + """ + + for i in range(self.patLen-1, -1, -1): + if self.pattern[i] != self.text[currentPos + i]: + return currentPos + i + return -1 + + + def bad_character_heuristic(self): + # searches pattern in text and returns index positions + positions = [] + for i in range(self.textLen - self.patLen + 1): + mismatch_index = self.mismatch_in_text(i) + if mismatch_index == -1: + positions.append(i) + else: + match_index = self.match_in_pattern(self.text[mismatch_index]) + i = mismatch_index - match_index #shifting index + return positions + + +text = "ABAABA" +pattern = "AB" +bms = BoyerMooreSearch(text, pattern) +positions = bms.bad_character_heuristic() + +if len(positions) == 0: + print("No match found") +else: + print("Pattern found in following positions: ") + print(positions) + + From 0f56ab5c3cfe50505d12e48493b0f043d1a6fc7a Mon Sep 17 00:00:00 2001 From: Dharni0607 <30770547+Dharni0607@users.noreply.github.com> Date: Tue, 2 Jul 2019 17:50:25 +0530 Subject: [PATCH 0065/1071] Divide and conquer Algorithms Issue#817 (#938) * add ons in string directory - Bayer_Moore_Search * created divide_and_conquer folder and added max_sub_array_sum.py under it (issue #817) --- divide_and_conquer/max_sub_array_sum.py | 72 +++++++++++++++++++++++++ 1 file changed, 72 insertions(+) create mode 100644 divide_and_conquer/max_sub_array_sum.py diff --git a/divide_and_conquer/max_sub_array_sum.py b/divide_and_conquer/max_sub_array_sum.py new file mode 100644 index 000000000000..531a45abca6f --- /dev/null +++ b/divide_and_conquer/max_sub_array_sum.py @@ -0,0 +1,72 @@ +""" +Given a array of length n, max_sub_array_sum() finds the maximum of sum of contiguous sub-array using divide and conquer method. + +Time complexity : O(n log n) + +Ref : INTRODUCTION TO ALGORITHMS THIRD EDITION (section : 4, sub-section : 4.1, page : 70) + +""" + + +def max_sum_from_start(array): + """ This function finds the maximum contiguous sum of array from 0 index + + Parameters : + array (list[int]) : given array + + Returns : + max_sum (int) : maximum contiguous sum of array from 0 index + + """ + array_sum = 0 + max_sum = float("-inf") + for num in array: + array_sum += num + if array_sum > max_sum: + max_sum = array_sum + return max_sum + + +def max_cross_array_sum(array, left, mid, right): + """ This function finds the maximum contiguous sum of left and right arrays + + Parameters : + array, left, mid, right (list[int], int, int, int) + + Returns : + (int) : maximum of sum of contiguous sum of left and right arrays + + """ + + max_sum_of_left = max_sum_from_start(array[left:mid+1][::-1]) + max_sum_of_right = max_sum_from_start(array[mid+1: right+1]) + return max_sum_of_left + max_sum_of_right + + +def max_sub_array_sum(array, left, right): + """ This function finds the maximum of sum of contiguous sub-array using divide and conquer method + + Parameters : + array, left, right (list[int], int, int) : given array, current left index and current right index + + Returns : + int : maximum of sum of contiguous sub-array + + """ + + # base case: array has only one element + if left == right: + return array[right] + + # Recursion + mid = (left + right) // 2 + left_half_sum = max_sub_array_sum(array, left, mid) + right_half_sum = max_sub_array_sum(array, mid + 1, right) + cross_sum = max_cross_array_sum(array, left, mid, right) + return max(left_half_sum, right_half_sum, cross_sum) + + +array = [-2, -5, 6, -2, -3, 1, 5, -6] +array_length = len(array) +print("Maximum sum of contiguous subarray:", max_sub_array_sum(array, 0, array_length - 1)) + From 65a12fa317935e04ed0e747db234fe4601e96084 Mon Sep 17 00:00:00 2001 From: Jigyasa G <33327397+jpg-130@users.noreply.github.com> Date: Tue, 2 Jul 2019 20:53:35 +0530 Subject: [PATCH 0066/1071] Adding sum of subsets (#929) --- dynamic_programming/sum_of_subset.py | 34 ++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 dynamic_programming/sum_of_subset.py diff --git a/dynamic_programming/sum_of_subset.py b/dynamic_programming/sum_of_subset.py new file mode 100644 index 000000000000..f6509a259c5d --- /dev/null +++ b/dynamic_programming/sum_of_subset.py @@ -0,0 +1,34 @@ +def isSumSubset(arr, arrLen, requiredSum): + + # a subset value says 1 if that subset sum can be formed else 0 + #initially no subsets can be formed hence False/0 + subset = ([[False for i in range(requiredSum + 1)] for i in range(arrLen + 1)]) + + #for each arr value, a sum of zero(0) can be formed by not taking any element hence True/1 + for i in range(arrLen + 1): + subset[i][0] = True + + #sum is not zero and set is empty then false + for i in range(1, requiredSum + 1): + subset[0][i] = False + + for i in range(1, arrLen + 1): + for j in range(1, requiredSum + 1): + if arr[i-1]>j: + subset[i][j] = subset[i-1][j] + if arr[i-1]<=j: + subset[i][j] = (subset[i-1][j] or subset[i-1][j-arr[i-1]]) + + #uncomment to print the subset + # for i in range(arrLen+1): + # print(subset[i]) + + return subset[arrLen][requiredSum] + +arr = [2, 4, 6, 8] +requiredSum = 5 +arrLen = len(arr) +if isSumSubset(arr, arrLen, requiredSum): + print("Found a subset with required sum") +else: + print("No subset with required sum") \ No newline at end of file From 4fb4cb4fd1023e742ba7c06bce70e042294cd157 Mon Sep 17 00:00:00 2001 From: cclauss Date: Wed, 3 Jul 2019 09:21:03 +0200 Subject: [PATCH 0067/1071] Travis CI: Simplify the testing (#887) * Travis CI: Simplify the testing * flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics * python: 3.7 * dist: xenial for python: 3.7 * Delete .lgtm.yml These changes were created to get around the fact that Travis CI was not enabled on this repo. Now that Travis is enabled, we can remove these modifications. The problems are that: 1. [LGTM does not want us running flake8 on their infrstructure](https://discuss.lgtm.com/t/can-i-get-lgtm-to-run-flake8-tests/1445/6) and 2. when we do, it [does not work as expected](https://discuss.lgtm.com/t/tests-are-not-automatically-run-when-lgtm-yml-is-modified/1446/4). --- .lgtm.yml | 12 ------------ .travis.yml | 29 ++++------------------------- 2 files changed, 4 insertions(+), 37 deletions(-) delete mode 100644 .lgtm.yml diff --git a/.lgtm.yml b/.lgtm.yml deleted file mode 100644 index ec550ab72705..000000000000 --- a/.lgtm.yml +++ /dev/null @@ -1,12 +0,0 @@ -extraction: - python: - python_setup: - version: 3 - after_prepare: - - python3 -m pip install --upgrade --user flake8 - before_index: - - python3 -m flake8 --version # flake8 3.6.0 on CPython 3.6.5 on Linux - # stop the build if there are Python syntax errors or undefined names - - python3 -m flake8 . --count --select=E901,E999,F821,F822,F823 --show-source --statistics - # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide - - python3 -m flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics diff --git a/.travis.yml b/.travis.yml index 2440899e4f25..8676e5127334 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,26 +1,5 @@ language: python -cache: pip -python: - - 2.7 - - 3.6 - #- nightly - #- pypy - #- pypy3 -matrix: - allow_failures: - - python: nightly - - python: pypy - - python: pypy3 -install: - #- pip install -r requirements.txt - - pip install flake8 # pytest # add another testing frameworks later -before_script: - # stop the build if there are Python syntax errors or undefined names - - flake8 . --count --select=E9,F63,F72,F82 --show-source --statistics - # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide - - flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics -script: - - true # pytest --capture=sys # add other tests here -notifications: - on_success: change - on_failure: change # `always` will be the setting once code changes slow down +dist: xenial # required for Python >= 3.7 +python: 3.7 +install: pip install flake8 +script: flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics From 03f994077557e67493f41ba4c3f7aca08da000d4 Mon Sep 17 00:00:00 2001 From: Ashok Bakthavathsalam Date: Wed, 3 Jul 2019 21:01:10 +0530 Subject: [PATCH 0068/1071] Refactored to one pop() (#917) --- sorts/merge_sort.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sorts/merge_sort.py b/sorts/merge_sort.py index ecbad7075119..714861e72642 100644 --- a/sorts/merge_sort.py +++ b/sorts/merge_sort.py @@ -37,7 +37,7 @@ def merge(left, right): ''' result = [] while left and right: - result.append(left.pop(0) if left[0] <= right[0] else right.pop(0)) + result.append((left if left[0] <= right[0] else right).pop(0)) return result + left + right if len(collection) <= 1: return collection @@ -53,4 +53,4 @@ def merge(left, right): user_input = raw_input('Enter numbers separated by a comma:\n').strip() unsorted = [int(item) for item in user_input.split(',')] - print(*merge_sort(unsorted), sep=',') \ No newline at end of file + print(*merge_sort(unsorted), sep=',') From 035457f569dc79e8b7b6d3a493895c67182d3539 Mon Sep 17 00:00:00 2001 From: Dharni0607 <30770547+Dharni0607@users.noreply.github.com> Date: Thu, 4 Jul 2019 13:19:14 +0530 Subject: [PATCH 0069/1071] closest pair of points algo (#943) * created divide_and_conquer folder and added max_sub_array_sum.py under it (issue #817) * additional file in divide_and_conqure (closest pair of points) --- divide_and_conquer/closest_pair_of_points.py | 113 +++++++++++++++++++ divide_and_conquer/max_subarray_sum.py | 75 ++++++++++++ 2 files changed, 188 insertions(+) create mode 100644 divide_and_conquer/closest_pair_of_points.py create mode 100644 divide_and_conquer/max_subarray_sum.py diff --git a/divide_and_conquer/closest_pair_of_points.py b/divide_and_conquer/closest_pair_of_points.py new file mode 100644 index 000000000000..cc5be428db79 --- /dev/null +++ b/divide_and_conquer/closest_pair_of_points.py @@ -0,0 +1,113 @@ +""" +The algorithm finds distance btw closest pair of points in the given n points. +Approach used -> Divide and conquer +The points are sorted based on Xco-ords +& by applying divide and conquer approach, +minimum distance is obtained recursively. + +>> closest points lie on different sides of partition +This case handled by forming a strip of points +whose Xco-ords distance is less than closest_pair_dis +from mid-point's Xco-ords. +Closest pair distance is found in the strip of points. (closest_in_strip) + +min(closest_pair_dis, closest_in_strip) would be the final answer. + +Time complexity: O(n * (logn)^2) +""" + + +import math + + +def euclidean_distance_sqr(point1, point2): + return pow(point1[0] - point2[0], 2) + pow(point1[1] - point2[1], 2) + + +def column_based_sort(array, column = 0): + return sorted(array, key = lambda x: x[column]) + + +def dis_between_closest_pair(points, points_counts, min_dis = float("inf")): + """ brute force approach to find distance between closest pair points + + Parameters : + points, points_count, min_dis (list(tuple(int, int)), int, int) + + Returns : + min_dis (float): distance between closest pair of points + + """ + + for i in range(points_counts - 1): + for j in range(i+1, points_counts): + current_dis = euclidean_distance_sqr(points[i], points[j]) + if current_dis < min_dis: + min_dis = current_dis + return min_dis + + +def dis_between_closest_in_strip(points, points_counts, min_dis = float("inf")): + """ closest pair of points in strip + + Parameters : + points, points_count, min_dis (list(tuple(int, int)), int, int) + + Returns : + min_dis (float): distance btw closest pair of points in the strip (< min_dis) + + """ + + for i in range(min(6, points_counts - 1), points_counts): + for j in range(max(0, i-6), i): + current_dis = euclidean_distance_sqr(points[i], points[j]) + if current_dis < min_dis: + min_dis = current_dis + return min_dis + + +def closest_pair_of_points_sqr(points, points_counts): + """ divide and conquer approach + + Parameters : + points, points_count (list(tuple(int, int)), int) + + Returns : + (float): distance btw closest pair of points + + """ + + # base case + if points_counts <= 3: + return dis_between_closest_pair(points, points_counts) + + # recursion + mid = points_counts//2 + closest_in_left = closest_pair_of_points(points[:mid], mid) + closest_in_right = closest_pair_of_points(points[mid:], points_counts - mid) + closest_pair_dis = min(closest_in_left, closest_in_right) + + """ cross_strip contains the points, whose Xcoords are at a + distance(< closest_pair_dis) from mid's Xcoord + """ + + cross_strip = [] + for point in points: + if abs(point[0] - points[mid][0]) < closest_pair_dis: + cross_strip.append(point) + + cross_strip = column_based_sort(cross_strip, 1) + closest_in_strip = dis_between_closest_in_strip(cross_strip, + len(cross_strip), closest_pair_dis) + return min(closest_pair_dis, closest_in_strip) + + +def closest_pair_of_points(points, points_counts): + return math.sqrt(closest_pair_of_points_sqr(points, points_counts)) + + +points = [(2, 3), (12, 30), (40, 50), (5, 1), (12, 10), (0, 2), (5, 6), (1, 2)] +points = column_based_sort(points) +print("Distance:", closest_pair_of_points(points, len(points))) + + diff --git a/divide_and_conquer/max_subarray_sum.py b/divide_and_conquer/max_subarray_sum.py new file mode 100644 index 000000000000..0428f4e13768 --- /dev/null +++ b/divide_and_conquer/max_subarray_sum.py @@ -0,0 +1,75 @@ +""" +Given a array of length n, max_subarray_sum() finds +the maximum of sum of contiguous sub-array using divide and conquer method. + +Time complexity : O(n log n) + +Ref : INTRODUCTION TO ALGORITHMS THIRD EDITION +(section : 4, sub-section : 4.1, page : 70) + +""" + + +def max_sum_from_start(array): + """ This function finds the maximum contiguous sum of array from 0 index + + Parameters : + array (list[int]) : given array + + Returns : + max_sum (int) : maximum contiguous sum of array from 0 index + + """ + array_sum = 0 + max_sum = float("-inf") + for num in array: + array_sum += num + if array_sum > max_sum: + max_sum = array_sum + return max_sum + + +def max_cross_array_sum(array, left, mid, right): + """ This function finds the maximum contiguous sum of left and right arrays + + Parameters : + array, left, mid, right (list[int], int, int, int) + + Returns : + (int) : maximum of sum of contiguous sum of left and right arrays + + """ + + max_sum_of_left = max_sum_from_start(array[left:mid+1][::-1]) + max_sum_of_right = max_sum_from_start(array[mid+1: right+1]) + return max_sum_of_left + max_sum_of_right + + +def max_subarray_sum(array, left, right): + """ Maximum contiguous sub-array sum, using divide and conquer method + + Parameters : + array, left, right (list[int], int, int) : + given array, current left index and current right index + + Returns : + int : maximum of sum of contiguous sub-array + + """ + + # base case: array has only one element + if left == right: + return array[right] + + # Recursion + mid = (left + right) // 2 + left_half_sum = max_subarray_sum(array, left, mid) + right_half_sum = max_subarray_sum(array, mid + 1, right) + cross_sum = max_cross_array_sum(array, left, mid, right) + return max(left_half_sum, right_half_sum, cross_sum) + + +array = [-2, -5, 6, -2, -3, 1, 5, -6] +array_length = len(array) +print("Maximum sum of contiguous subarray:", max_subarray_sum(array, 0, array_length - 1)) + From 05fc7f8a33df7d08dba1f162d7618f4e353b534f Mon Sep 17 00:00:00 2001 From: Hector S Date: Thu, 4 Jul 2019 11:18:57 -0400 Subject: [PATCH 0070/1071] Added '~script.py' to ignore files and updated DIRECTORY.md (#926) * Added print function into matrix_multiplication_addition.py and removed blank space in data_structures/binary tree directory * Removed .vs/ folder per #893 * Rename matrix_multiplication_addition.py to matrix_operation.py * Added '~script.py' to ignore files and updated DIRECTORY.md --- DIRECTORY.md | 22 ++++++++++++---------- ~script.py | 2 +- 2 files changed, 13 insertions(+), 11 deletions(-) diff --git a/DIRECTORY.md b/DIRECTORY.md index ad25772b56b6..befd634c1eb0 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -1,6 +1,3 @@ -## Analysis - * Compression Analysis - * [psnr](https://github.com/TheAlgorithms/Python/blob/master/analysis/compression_analysis/psnr.py) ## Arithmetic Analysis * [bisection](https://github.com/TheAlgorithms/Python/blob/master/arithmetic_analysis/bisection.py) * [intersection](https://github.com/TheAlgorithms/Python/blob/master/arithmetic_analysis/intersection.py) @@ -39,17 +36,19 @@ * [xor cipher](https://github.com/TheAlgorithms/Python/blob/master/ciphers/xor_cipher.py) ## Compression * [huffman](https://github.com/TheAlgorithms/Python/blob/master/compression/huffman.py) +## Compression Analysis + * [psnr](https://github.com/TheAlgorithms/Python/blob/master/compression_analysis/psnr.py) ## Data Structures * [arrays](https://github.com/TheAlgorithms/Python/blob/master/data_structures/arrays.py) * [avl](https://github.com/TheAlgorithms/Python/blob/master/data_structures/avl.py) * [LCA](https://github.com/TheAlgorithms/Python/blob/master/data_structures/LCA.py) * Binary Tree - * [AVL tree](https://github.com/TheAlgorithms/Python/blob/master/data_structures/binary%20tree/AVL_tree.py) - * [binary search tree](https://github.com/TheAlgorithms/Python/blob/master/data_structures/binary%20tree/binary_search_tree.py) - * [fenwick tree](https://github.com/TheAlgorithms/Python/blob/master/data_structures/binary%20tree/fenwick_tree.py) - * [lazy segment tree](https://github.com/TheAlgorithms/Python/blob/master/data_structures/binary%20tree/lazy_segment_tree.py) - * [segment tree](https://github.com/TheAlgorithms/Python/blob/master/data_structures/binary%20tree/segment_tree.py) - * [treap](https://github.com/TheAlgorithms/Python/blob/master/data_structures/binary%20tree/treap.py) + * [AVL tree](https://github.com/TheAlgorithms/Python/blob/master/data_structures/binary_tree/AVL_tree.py) + * [binary search tree](https://github.com/TheAlgorithms/Python/blob/master/data_structures/binary_tree/binary_search_tree.py) + * [fenwick tree](https://github.com/TheAlgorithms/Python/blob/master/data_structures/binary_tree/fenwick_tree.py) + * [lazy segment tree](https://github.com/TheAlgorithms/Python/blob/master/data_structures/binary_tree/lazy_segment_tree.py) + * [segment tree](https://github.com/TheAlgorithms/Python/blob/master/data_structures/binary_tree/segment_tree.py) + * [treap](https://github.com/TheAlgorithms/Python/blob/master/data_structures/binary_tree/treap.py) * Hashing * [double hash](https://github.com/TheAlgorithms/Python/blob/master/data_structures/hashing/double_hash.py) * [hash table](https://github.com/TheAlgorithms/Python/blob/master/data_structures/hashing/hash_table.py) @@ -192,8 +191,9 @@ * Tests * [test fibonacci](https://github.com/TheAlgorithms/Python/blob/master/maths/tests/test_fibonacci.py) ## Matrix - * [matrix multiplication addition](https://github.com/TheAlgorithms/Python/blob/master/matrix/matrix_multiplication_addition.py) + * [matrix operation](https://github.com/TheAlgorithms/Python/blob/master/matrix/matrix_operation.py) * [searching in sorted matrix](https://github.com/TheAlgorithms/Python/blob/master/matrix/searching_in_sorted_matrix.py) + * [spiralPrint](https://github.com/TheAlgorithms/Python/blob/master/matrix/spiralPrint.py) ## Networking Flow * [ford fulkerson](https://github.com/TheAlgorithms/Python/blob/master/networking_flow/ford_fulkerson.py) * [minimum cut](https://github.com/TheAlgorithms/Python/blob/master/networking_flow/minimum_cut.py) @@ -294,6 +294,8 @@ * [p022 names](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_22/p022_names.txt) * [sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_22/sol1.py) * [sol2](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_22/sol2.py) + * Problem 234 + * [sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_234/sol1.py) * Problem 24 * [sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_24/sol1.py) * Problem 25 diff --git a/~script.py b/~script.py index 4a2c61c83563..c44f3436fcec 100644 --- a/~script.py +++ b/~script.py @@ -52,7 +52,7 @@ def _markdown(parent, ignores, ignores_ext, depth): ignores = [".vs", ".gitignore", ".git", - "script.py", + "~script.py", "__init__.py", ] # Files with given entensions will be ignored From 408c5deb3adb0f128f51d3d72e7664f5b864e9b7 Mon Sep 17 00:00:00 2001 From: Shoujue Xu Date: Fri, 5 Jul 2019 16:20:11 +0800 Subject: [PATCH 0071/1071] add gaussian filter algorithm and lena.jpg (#955) --- .../filters/gaussian_filter.py | 53 ++++++++++++++++++ .../filters/median_filter.py | 2 +- digital_image_processing/image_data/lena.jpg | Bin 0 -> 104428 bytes 3 files changed, 54 insertions(+), 1 deletion(-) create mode 100644 digital_image_processing/filters/gaussian_filter.py create mode 100644 digital_image_processing/image_data/lena.jpg diff --git a/digital_image_processing/filters/gaussian_filter.py b/digital_image_processing/filters/gaussian_filter.py new file mode 100644 index 000000000000..ff85ce047220 --- /dev/null +++ b/digital_image_processing/filters/gaussian_filter.py @@ -0,0 +1,53 @@ +""" +Implementation of gaussian filter algorithm +""" +from cv2 import imread, cvtColor, COLOR_BGR2GRAY, imshow, waitKey +from numpy import pi, mgrid, exp, square, zeros, ravel, dot, uint8 + + +def gen_gaussian_kernel(k_size, sigma): + center = k_size // 2 + x, y = mgrid[0-center:k_size-center, 0-center:k_size-center] + g = 1/(2*pi*sigma) * exp(-(square(x) + square(y))/(2*square(sigma))) + return g + + +def gaussian_filter(image, k_size, sigma): + height, width = image.shape[0], image.shape[1] + # dst image height and width + dst_height = height-k_size+1 + dst_width = width-k_size+1 + + # im2col, turn the k_size*k_size pixels into a row and np.vstack all rows + image_array = zeros((dst_height*dst_width, k_size*k_size)) + row = 0 + for i in range(0, dst_height): + for j in range(0, dst_width): + window = ravel(image[i:i + k_size, j:j + k_size]) + image_array[row, :] = window + row += 1 + + # turn the kernel into shape(k*k, 1) + gaussian_kernel = gen_gaussian_kernel(k_size, sigma) + filter_array = ravel(gaussian_kernel) + + # reshape and get the dst image + dst = dot(image_array, filter_array).reshape(dst_height, dst_width).astype(uint8) + + return dst + + +if __name__ == '__main__': + # read original image + img = imread(r'../image_data/lena.jpg') + # turn image in gray scale value + gray = cvtColor(img, COLOR_BGR2GRAY) + + # get values with two different mask size + gaussian3x3 = gaussian_filter(gray, 3, sigma=1) + gaussian5x5 = gaussian_filter(gray, 5, sigma=0.8) + + # show result images + imshow('gaussian filter with 3x3 mask', gaussian3x3) + imshow('gaussian filter with 5x5 mask', gaussian5x5) + waitKey() diff --git a/digital_image_processing/filters/median_filter.py b/digital_image_processing/filters/median_filter.py index eea4295632a1..ed20b1ab7f78 100644 --- a/digital_image_processing/filters/median_filter.py +++ b/digital_image_processing/filters/median_filter.py @@ -28,7 +28,7 @@ def median_filter(gray_img, mask=3): if __name__ == '__main__': # read original image - img = imread('lena.jpg') + img = imread('../image_data/lena.jpg') # turn image in gray scale value gray = cvtColor(img, COLOR_BGR2GRAY) diff --git a/digital_image_processing/image_data/lena.jpg b/digital_image_processing/image_data/lena.jpg new file mode 100644 index 0000000000000000000000000000000000000000..15c4d9764effe5ad9d84dc0e4af553788f80dc73 GIT binary patch literal 104428 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 z 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 literal 0 HcmV?d00001 From 217615abf68592e69725bbb4ef8d86713e9be1ce Mon Sep 17 00:00:00 2001 From: PatOnTheBack <51241310+PatOnTheBack@users.noreply.github.com> Date: Fri, 5 Jul 2019 04:34:46 -0400 Subject: [PATCH 0072/1071] Removed Unused Variables (#949) - Removed two unused variables. - Changed `a` to `_` since the `a` variable is never used. This addresses [3 alerts from lgtm](https://lgtm.com/projects/g/TheAlgorithms/Python/snapshot/d55bbcd204dfbd436914a5f9031a6a8fdf22f6f4/files/sorts/Odd-Even_transposition_parallel.py?sort=name&dir=ASC&mode=heatmap). --- sorts/Odd-Even_transposition_parallel.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/sorts/Odd-Even_transposition_parallel.py b/sorts/Odd-Even_transposition_parallel.py index d7f983fc0469..9bf81a39e27a 100644 --- a/sorts/Odd-Even_transposition_parallel.py +++ b/sorts/Odd-Even_transposition_parallel.py @@ -70,13 +70,11 @@ def oeProcess(position, value, LSend, RSend, LRcv, RRcv, resultPipe): def OddEvenTransposition(arr): processArray = [] - tempRrcv = None - tempLrcv = None resultPipe = [] #initialize the list of pipes where the values will be retrieved - for a in arr: + for _ in arr: resultPipe.append(Pipe()) #creates the processes From 1c9d995b9eb05f439fee5892210af3ab659f9760 Mon Sep 17 00:00:00 2001 From: PatOnTheBack <51241310+PatOnTheBack@users.noreply.github.com> Date: Fri, 5 Jul 2019 04:36:48 -0400 Subject: [PATCH 0073/1071] Implement Three New Algorithms (#948) * Create average_median.py I created a program to calculate the median of a list of numbers. * Changed Odd to Even in String * Create decimal_to_binary.py - Added 'conversions' folder. - Created a decimal to binary converter. * Implemented Decimal to Octal Algorithm - I created a decimal to octal converter based on the converter in the TheAlgorithms/Python project. - I added two newlines to make the output of decimal_to_binary.py better. --- conversions/decimal_to_binary.py | 25 +++++++++++++++++++ conversions/decimal_to_octal.py | 38 +++++++++++++++++++++++++++++ maths/average_median.py | 41 ++++++++++++++++++++++++++++++++ 3 files changed, 104 insertions(+) create mode 100644 conversions/decimal_to_binary.py create mode 100644 conversions/decimal_to_octal.py create mode 100644 maths/average_median.py diff --git a/conversions/decimal_to_binary.py b/conversions/decimal_to_binary.py new file mode 100644 index 000000000000..43ceee61a388 --- /dev/null +++ b/conversions/decimal_to_binary.py @@ -0,0 +1,25 @@ +"""Convert a Decimal Number to a Binary Number.""" + + +def decimal_to_binary(num): + """Convert a Decimal Number to a Binary Number.""" + binary = [] + while num > 0: + binary.insert(0, num % 2) + num >>= 1 + return "".join(str(e) for e in binary) + + +def main(): + """Print binary equivelents of decimal numbers.""" + print("\n2 in binary is:") + print(decimal_to_binary(2)) # = 10 + print("\n7 in binary is:") + print(decimal_to_binary(7)) # = 111 + print("\n35 in binary is:") + print(decimal_to_binary(35)) # = 100011 + print("\n") + + +if __name__ == '__main__': + main() diff --git a/conversions/decimal_to_octal.py b/conversions/decimal_to_octal.py new file mode 100644 index 000000000000..187a0300e33a --- /dev/null +++ b/conversions/decimal_to_octal.py @@ -0,0 +1,38 @@ +"""Convert a Decimal Number to an Octal Number.""" + +import math + +# Modified from: +# https://github.com/TheAlgorithms/Javascript/blob/master/Conversions/DecimalToOctal.js + + +def decimal_to_octal(num): + """Convert a Decimal Number to an Octal Number.""" + octal = 0 + counter = 0 + while num > 0: + remainder = num % 8 + octal = octal + (remainder * math.pow(10, counter)) + counter += 1 + num = math.floor(num / 8) # basically /= 8 without remainder if any + # This formatting removes trailing '.0' from `octal`. + return'{0:g}'.format(float(octal)) + + +def main(): + """Print octal equivelents of decimal numbers.""" + print("\n2 in octal is:") + print(decimal_to_octal(2)) # = 2 + print("\n8 in octal is:") + print(decimal_to_octal(8)) # = 10 + print("\n65 in octal is:") + print(decimal_to_octal(65)) # = 101 + print("\n216 in octal is:") + print(decimal_to_octal(216)) # = 330 + print("\n512 in octal is:") + print(decimal_to_octal(512)) # = 1000 + print("\n") + + +if __name__ == '__main__': + main() diff --git a/maths/average_median.py b/maths/average_median.py new file mode 100644 index 000000000000..565bb4afd112 --- /dev/null +++ b/maths/average_median.py @@ -0,0 +1,41 @@ +""" +Find median of a list of numbers. + +Read more about medians: + https://en.wikipedia.org/wiki/Median +""" + + +def median(nums): + """Find median of a list of numbers.""" + # Sort list + sorted_list = sorted(nums) + print("List of numbers:") + print(sorted_list) + + # Is number of items in list even? + if len(sorted_list) % 2 == 0: + # Find index for first middle value. + mid_index_1 = len(sorted_list) / 2 + # Find index for second middle value. + mid_index_2 = -(len(sorted_list) / 2) - 1 + # Divide middle values by 2 to get average (mean). + med = (sorted_list[mid_index_1] + sorted_list[mid_index_2]) / float(2) + return med # Return makes `else:` unnecessary. + # Number of items is odd. + mid_index = (len(sorted_list) - 1) / 2 + # Middle index is median. + med = sorted_list[mid_index] + return med + + +def main(): + """Call average module to find median of a specific list of numbers.""" + print("Odd number of numbers:") + print(median([2, 4, 6, 8, 20, 50, 70])) + print("Even number of numbers:") + print(median([2, 4, 6, 8, 20, 50])) + + +if __name__ == '__main__': + main() From 506bb5ccfe97fe5b37faa2bcd9df0fd0fab07ac0 Mon Sep 17 00:00:00 2001 From: Jarred Allen Date: Fri, 5 Jul 2019 01:43:16 -0700 Subject: [PATCH 0074/1071] Add Red-Black Binary Search Trees (#954) * Wrote most of an rbt, missing just removal * Added some convenience methods. * Added a color method * Wrote code to delete, but has issues :( * Fixed a bug in Red-Black trees * Fixed bug in tree color validation and delete repairing * Clean up == comparison to None --- data_structures/binary_tree/red_black_tree.py | 665 ++++++++++++++++++ 1 file changed, 665 insertions(+) create mode 100644 data_structures/binary_tree/red_black_tree.py diff --git a/data_structures/binary_tree/red_black_tree.py b/data_structures/binary_tree/red_black_tree.py new file mode 100644 index 000000000000..4ca1301dd8fe --- /dev/null +++ b/data_structures/binary_tree/red_black_tree.py @@ -0,0 +1,665 @@ +class RedBlackTree: + """ + A Red-Black tree, which is a self-balancing BST (binary search + tree). + + This tree has similar performance to AVL trees, but the balancing is + less strict, so it will perform faster for writing/deleting nodes + and slower for reading in the average case, though, because they're + both balanced binary search trees, both will get the same asymptotic + perfomance. + + To read more about them, https://en.wikipedia.org/wiki/Red–black_tree + + Unless otherwise specified, all asymptotic runtimes are specified in + terms of the size of the tree. + """ + def __init__(self, label=None, color=0, parent=None, left=None, right=None): + """Initialize a new Red-Black Tree node with the given values: + label: The value associated with this node + color: 0 if black, 1 if red + parent: The parent to this node + left: This node's left child + right: This node's right child + """ + self.label = label + self.parent = parent + self.left = left + self.right = right + self.color = color + + # Here are functions which are specific to red-black trees + + def rotate_left(self): + """Rotate the subtree rooted at this node to the left and + returns the new root to this subtree. + + Perfoming one rotation can be done in O(1). + """ + parent = self.parent + right = self.right + self.right = right.left + if self.right: + self.right.parent = self + self.parent = right + right.left = self + if parent is not None: + if parent.left is self: + parent.left = right + else: + parent.right = right + right.parent = parent + return right + + def rotate_right(self): + """Rotate the subtree rooted at this node to the right and + returns the new root to this subtree. + + Performing one rotation can be done in O(1). + """ + parent = self.parent + left = self.left + self.left = left.right + if self.left: + self.left.parent = self + self.parent = left + left.right = self + if parent is not None: + if parent.right is self: + parent.right = left + else: + parent.left = left + left.parent = parent + return left + + def insert(self, label): + """Inserts label into the subtree rooted at self, performs any + rotations necessary to maintain balance, and then returns the + new root to this subtree (likely self). + + This is guaranteed to run in O(log(n)) time. + """ + if self.label is None: + # Only possible with an empty tree + self.label = label + return self + if self.label == label: + return self + elif self.label > label: + if self.left: + self.left.insert(label) + else: + self.left = RedBlackTree(label, 1, self) + self.left._insert_repair() + else: + if self.right: + self.right.insert(label) + else: + self.right = RedBlackTree(label, 1, self) + self.right._insert_repair() + return self.parent or self + + def _insert_repair(self): + """Repair the coloring from inserting into a tree.""" + if self.parent is None: + # This node is the root, so it just needs to be black + self.color = 0 + elif color(self.parent) == 0: + # If the parent is black, then it just needs to be red + self.color = 1 + else: + uncle = self.parent.sibling + if color(uncle) == 0: + if self.is_left() and self.parent.is_right(): + self.parent.rotate_right() + self.right._insert_repair() + elif self.is_right() and self.parent.is_left(): + self.parent.rotate_left() + self.left._insert_repair() + elif self.is_left(): + self.grandparent.rotate_right() + self.parent.color = 0 + self.parent.right.color = 1 + else: + self.grandparent.rotate_left() + self.parent.color = 0 + self.parent.left.color = 1 + else: + self.parent.color = 0 + uncle.color = 0 + self.grandparent.color = 1 + self.grandparent._insert_repair() + + def remove(self, label): + """Remove label from this tree.""" + if self.label == label: + if self.left and self.right: + # It's easier to balance a node with at most one child, + # so we replace this node with the greatest one less than + # it and remove that. + value = self.left.get_max() + self.label = value + self.left.remove(value) + else: + # This node has at most one non-None child, so we don't + # need to replace + child = self.left or self.right + if self.color == 1: + # This node is red, and its child is black + # The only way this happens to a node with one child + # is if both children are None leaves. + # We can just remove this node and call it a day. + if self.is_left(): + self.parent.left = None + else: + self.parent.right = None + else: + # The node is black + if child is None: + # This node and its child are black + if self.parent is None: + # The tree is now empty + return RedBlackTree(None) + else: + self._remove_repair() + if self.is_left(): + self.parent.left = None + else: + self.parent.right = None + self.parent = None + else: + # This node is black and its child is red + # Move the child node here and make it black + self.label = child.label + self.left = child.left + self.right = child.right + if self.left: + self.left.parent = self + if self.right: + self.right.parent = self + elif self.label > label: + if self.left: + self.left.remove(label) + else: + if self.right: + self.right.remove(label) + return self.parent or self + + def _remove_repair(self): + """Repair the coloring of the tree that may have been messed up.""" + if color(self.sibling) == 1: + self.sibling.color = 0 + self.parent.color = 1 + if self.is_left(): + self.parent.rotate_left() + else: + self.parent.rotate_right() + if color(self.parent) == 0 and color(self.sibling) == 0 \ + and color(self.sibling.left) == 0 \ + and color(self.sibling.right) == 0: + self.sibling.color = 1 + self.parent._remove_repair() + return + if color(self.parent) == 1 and color(self.sibling) == 0 \ + and color(self.sibling.left) == 0 \ + and color(self.sibling.right) == 0: + self.sibling.color = 1 + self.parent.color = 0 + return + if (self.is_left() + and color(self.sibling) == 0 + and color(self.sibling.right) == 0 + and color(self.sibling.left) == 1): + self.sibling.rotate_right() + self.sibling.color = 0 + self.sibling.right.color = 1 + if (self.is_right() + and color(self.sibling) == 0 + and color(self.sibling.right) == 1 + and color(self.sibling.left) == 0): + self.sibling.rotate_left() + self.sibling.color = 0 + self.sibling.left.color = 1 + if (self.is_left() + and color(self.sibling) == 0 + and color(self.sibling.right) == 1): + self.parent.rotate_left() + self.grandparent.color = self.parent.color + self.parent.color = 0 + self.parent.sibling.color = 0 + if (self.is_right() + and color(self.sibling) == 0 + and color(self.sibling.left) == 1): + self.parent.rotate_right() + self.grandparent.color = self.parent.color + self.parent.color = 0 + self.parent.sibling.color = 0 + + def check_color_properties(self): + """Check the coloring of the tree, and return True iff the tree + is colored in a way which matches these five properties: + (wording stolen from wikipedia article) + 1. Each node is either red or black. + 2. The root node is black. + 3. All leaves are black. + 4. If a node is red, then both its children are black. + 5. Every path from any node to all of its descendent NIL nodes + has the same number of black nodes. + + This function runs in O(n) time, because properties 4 and 5 take + that long to check. + """ + # I assume property 1 to hold because there is nothing that can + # make the color be anything other than 0 or 1. + + # Property 2 + if self.color: + # The root was red + print('Property 2') + return False; + + # Property 3 does not need to be checked, because None is assumed + # to be black and is all the leaves. + + # Property 4 + if not self.check_coloring(): + print('Property 4') + return False + + # Property 5 + if self.black_height() is None: + print('Property 5') + return False + # All properties were met + return True + + def check_coloring(self): + """A helper function to recursively check Property 4 of a + Red-Black Tree. See check_color_properties for more info. + """ + if self.color == 1: + if color(self.left) == 1 or color(self.right) == 1: + return False + if self.left and not self.left.check_coloring(): + return False + if self.right and not self.right.check_coloring(): + return False + return True + + def black_height(self): + """Returns the number of black nodes from this node to the + leaves of the tree, or None if there isn't one such value (the + tree is color incorrectly). + """ + if self is None: + # If we're already at a leaf, there is no path + return 1 + left = RedBlackTree.black_height(self.left) + right = RedBlackTree.black_height(self.right) + if left is None or right is None: + # There are issues with coloring below children nodes + return None + if left != right: + # The two children have unequal depths + return None + # Return the black depth of children, plus one if this node is + # black + return left + (1-self.color) + + # Here are functions which are general to all binary search trees + + def __contains__(self, label): + """Search through the tree for label, returning True iff it is + found somewhere in the tree. + + Guaranteed to run in O(log(n)) time. + """ + return self.search(label) is not None + + def search(self, label): + """Search through the tree for label, returning its node if + it's found, and None otherwise. + + This method is guaranteed to run in O(log(n)) time. + """ + if self.label == label: + return self + elif label > self.label: + if self.right is None: + return None + else: + return self.right.search(label) + else: + if self.left is None: + return None + else: + return self.left.search(label) + + def floor(self, label): + """Returns the largest element in this tree which is at most label. + + This method is guaranteed to run in O(log(n)) time.""" + if self.label == label: + return self.label + elif self.label > label: + if self.left: + return self.left.floor(label) + else: + return None + else: + if self.right: + attempt = self.right.floor(label) + if attempt is not None: + return attempt + return self.label + + def ceil(self, label): + """Returns the smallest element in this tree which is at least label. + + This method is guaranteed to run in O(log(n)) time. + """ + if self.label == label: + return self.label + elif self.label < label: + if self.right: + return self.right.ceil(label) + else: + return None + else: + if self.left: + attempt = self.left.ceil(label) + if attempt is not None: + return attempt + return self.label + + def get_max(self): + """Returns the largest element in this tree. + + This method is guaranteed to run in O(log(n)) time. + """ + if self.right: + # Go as far right as possible + return self.right.get_max() + else: + return self.label + + def get_min(self): + """Returns the smallest element in this tree. + + This method is guaranteed to run in O(log(n)) time. + """ + if self.left: + # Go as far left as possible + return self.left.get_min() + else: + return self.label + + @property + def grandparent(self): + """Get the current node's grandparent, or None if it doesn't exist.""" + if self.parent is None: + return None + else: + return self.parent.parent + + @property + def sibling(self): + """Get the current node's sibling, or None if it doesn't exist.""" + if self.parent is None: + return None + elif self.parent.left is self: + return self.parent.right + else: + return self.parent.left + + def is_left(self): + """Returns true iff this node is the left child of its parent.""" + return self.parent and self.parent.left is self + + def is_right(self): + """Returns true iff this node is the right child of its parent.""" + return self.parent and self.parent.right is self + + def __bool__(self): + return True + + def __len__(self): + """ + Return the number of nodes in this tree. + """ + ln = 1 + if self.left: + ln += len(self.left) + if self.right: + ln += len(self.right) + return ln + + def preorder_traverse(self): + yield self.label + if self.left: + yield from self.left.preorder_traverse() + if self.right: + yield from self.right.preorder_traverse() + + def inorder_traverse(self): + if self.left: + yield from self.left.inorder_traverse() + yield self.label + if self.right: + yield from self.right.inorder_traverse() + + + def postorder_traverse(self): + if self.left: + yield from self.left.postorder_traverse() + if self.right: + yield from self.right.postorder_traverse() + yield self.label + + def __repr__(self): + from pprint import pformat + if self.left is None and self.right is None: + return "'%s %s'" % (self.label, (self.color and 'red') or 'blk') + return pformat({'%s %s' % (self.label, (self.color and 'red') or 'blk'): + (self.left, self.right)}, + indent=1) + + def __eq__(self, other): + """Test if two trees are equal.""" + if self.label == other.label: + return self.left == other.left and self.right == other.right + else: + return False + +def color(node): + """Returns the color of a node, allowing for None leaves.""" + if node is None: + return 0 + else: + return node.color + +""" +Code for testing the various functions of the red-black tree. +""" + +def test_rotations(): + """Test that the rotate_left and rotate_right functions work.""" + # Make a tree to test on + tree = RedBlackTree(0) + tree.left = RedBlackTree(-10, parent=tree) + tree.right = RedBlackTree(10, parent=tree) + tree.left.left = RedBlackTree(-20, parent=tree.left) + tree.left.right = RedBlackTree(-5, parent=tree.left) + tree.right.left = RedBlackTree(5, parent=tree.right) + tree.right.right = RedBlackTree(20, parent=tree.right) + # Make the right rotation + left_rot = RedBlackTree(10) + left_rot.left = RedBlackTree(0, parent=left_rot) + left_rot.left.left = RedBlackTree(-10, parent=left_rot.left) + left_rot.left.right = RedBlackTree(5, parent=left_rot.left) + left_rot.left.left.left = RedBlackTree(-20, parent=left_rot.left.left) + left_rot.left.left.right = RedBlackTree(-5, parent=left_rot.left.left) + left_rot.right = RedBlackTree(20, parent=left_rot) + tree = tree.rotate_left() + if tree != left_rot: + return False + tree = tree.rotate_right() + tree = tree.rotate_right() + # Make the left rotation + right_rot = RedBlackTree(-10) + right_rot.left = RedBlackTree(-20, parent=right_rot) + right_rot.right = RedBlackTree(0, parent=right_rot) + right_rot.right.left = RedBlackTree(-5, parent=right_rot.right) + right_rot.right.right = RedBlackTree(10, parent=right_rot.right) + right_rot.right.right.left = RedBlackTree(5, parent=right_rot.right.right) + right_rot.right.right.right = RedBlackTree(20, parent=right_rot.right.right) + if tree != right_rot: + return False + return True + +def test_insertion_speed(): + """Test that the tree balances inserts to O(log(n)) by doing a lot + of them. + """ + tree = RedBlackTree(-1) + for i in range(300000): + tree = tree.insert(i) + return True + +def test_insert(): + """Test the insert() method of the tree correctly balances, colors, + and inserts. + """ + tree = RedBlackTree(0) + tree.insert(8) + tree.insert(-8) + tree.insert(4) + tree.insert(12) + tree.insert(10) + tree.insert(11) + ans = RedBlackTree(0, 0) + ans.left = RedBlackTree(-8, 0, ans) + ans.right = RedBlackTree(8, 1, ans) + ans.right.left = RedBlackTree(4, 0, ans.right) + ans.right.right = RedBlackTree(11, 0, ans.right) + ans.right.right.left = RedBlackTree(10, 1, ans.right.right) + ans.right.right.right = RedBlackTree(12, 1, ans.right.right) + return tree == ans + +def test_insert_and_search(): + """Tests searching through the tree for values.""" + tree = RedBlackTree(0) + tree.insert(8) + tree.insert(-8) + tree.insert(4) + tree.insert(12) + tree.insert(10) + tree.insert(11) + if 5 in tree or -6 in tree or -10 in tree or 13 in tree: + # Found something not in there + return False + if not (11 in tree and 12 in tree and -8 in tree and 0 in tree): + # Didn't find something in there + return False + return True + +def test_insert_delete(): + """Test the insert() and delete() method of the tree, verifying the + insertion and removal of elements, and the balancing of the tree. + """ + tree = RedBlackTree(0) + tree = tree.insert(-12) + tree = tree.insert(8) + tree = tree.insert(-8) + tree = tree.insert(15) + tree = tree.insert(4) + tree = tree.insert(12) + tree = tree.insert(10) + tree = tree.insert(9) + tree = tree.insert(11) + tree = tree.remove(15) + tree = tree.remove(-12) + tree = tree.remove(9) + if not tree.check_color_properties(): + return False + if list(tree.inorder_traverse()) != [-8, 0, 4, 8, 10, 11, 12]: + return False + return True + +def test_floor_ceil(): + """Tests the floor and ceiling functions in the tree.""" + tree = RedBlackTree(0) + tree.insert(-16) + tree.insert(16) + tree.insert(8) + tree.insert(24) + tree.insert(20) + tree.insert(22) + tuples = [(-20, None, -16), (-10, -16, 0), (8, 8, 8), (50, 24, None)] + for val, floor, ceil in tuples: + if tree.floor(val) != floor or tree.ceil(val) != ceil: + return False + return True + +def test_min_max(): + """Tests the min and max functions in the tree.""" + tree = RedBlackTree(0) + tree.insert(-16) + tree.insert(16) + tree.insert(8) + tree.insert(24) + tree.insert(20) + tree.insert(22) + if tree.get_max() != 22 or tree.get_min() != -16: + return False + return True + +def test_tree_traversal(): + """Tests the three different tree traversal functions.""" + tree = RedBlackTree(0) + tree.insert(-16) + tree.insert(16) + tree.insert(8) + tree.insert(24) + tree.insert(20) + tree.insert(22) + if list(tree.inorder_traverse()) != [-16, 0, 8, 16, 20, 22, 24]: + return False + if list(tree.preorder_traverse()) != [0, -16, 16, 8, 22, 20, 24]: + return False + if list(tree.postorder_traverse()) != [-16, 8, 20, 24, 22, 16, 0]: + return False + return True + +def main(): + if test_rotations(): + print('Rotating right and left works!') + else: + print('Rotating right and left doesn\'t work. :(') + if test_insert(): + print('Inserting works!') + else: + print('Inserting doesn\'t work :(') + if test_insert_and_search(): + print('Searching works!') + else: + print('Searching doesn\'t work :(') + if test_insert_delete(): + print('Deleting works!') + else: + print('Deleting doesn\'t work :(') + if test_floor_ceil(): + print('Floor and ceil work!') + else: + print('Floor and ceil don\'t work :(') + if test_tree_traversal(): + print('Tree traversal works!') + else: + print('Tree traversal doesn\'t work :(') + print('Testing tree balancing...') + print('This should only be a few seconds.') + test_insertion_speed() + print('Done!') + +if __name__ == '__main__': + main() From afb98e6c232dac73fb895f9e06a645a82410fd9e Mon Sep 17 00:00:00 2001 From: Dhandarah Date: Fri, 5 Jul 2019 05:47:18 -0300 Subject: [PATCH 0075/1071] KNN (#944) Creates an example of KNN algorithm using sklearn library. --- machine_learning/knn_sklearn.py | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 machine_learning/knn_sklearn.py diff --git a/machine_learning/knn_sklearn.py b/machine_learning/knn_sklearn.py new file mode 100644 index 000000000000..64582564304f --- /dev/null +++ b/machine_learning/knn_sklearn.py @@ -0,0 +1,28 @@ +from sklearn.model_selection import train_test_split +from sklearn.datasets import load_iris +from sklearn.neighbors import KNeighborsClassifier + +#Load iris file +iris = load_iris() +iris.keys() + + +print('Target names: \n {} '.format(iris.target_names)) +print('\n Features: \n {}'.format(iris.feature_names)) + +#Train set e Test set +X_train, X_test, y_train, y_test = train_test_split(iris['data'],iris['target'], random_state=4) + +#KNN + +knn = KNeighborsClassifier (n_neighbors = 1) +knn.fit(X_train, y_train) + +#new array to test +X_new = [[1,2,1,4], + [2,3,4,5]] + +prediction = knn.predict(X_new) + +print('\nNew array: \n {}' + '\n\nTarget Names Prediction: \n {}'.format(X_new, iris['target_names'][prediction])) From 831558d38dd00c7b4d64743ca4f3fe62d16e71d1 Mon Sep 17 00:00:00 2001 From: Hetal Kuvadia Date: Fri, 5 Jul 2019 14:18:36 +0530 Subject: [PATCH 0076/1071] #945 Backtracking Algorithms (#953) * Adding nqueens.py for backtracking * Adding sum_of_subsets.py for backtracking * Update nqueens.py * Rename nqueens.py to n_queens.py * Deleting /other/n_queens.py --- backtracking/n_queens.py | 84 ++++++++++++++++++++++++++++++++++ backtracking/sum_of_subsets.py | 45 ++++++++++++++++++ other/n_queens.py | 77 ------------------------------- 3 files changed, 129 insertions(+), 77 deletions(-) create mode 100644 backtracking/n_queens.py create mode 100644 backtracking/sum_of_subsets.py delete mode 100644 other/n_queens.py diff --git a/backtracking/n_queens.py b/backtracking/n_queens.py new file mode 100644 index 000000000000..dfd4498b166b --- /dev/null +++ b/backtracking/n_queens.py @@ -0,0 +1,84 @@ +''' + + The nqueens problem is of placing N queens on a N * N + chess board such that no queen can attack any other queens placed + on that chess board. + This means that one queen cannot have any other queen on its horizontal, vertical and + diagonal lines. + +''' +solution = [] + +def isSafe(board, row, column): + ''' + This function returns a boolean value True if it is safe to place a queen there considering + the current state of the board. + + Parameters : + board(2D matrix) : board + row ,column : coordinates of the cell on a board + + Returns : + Boolean Value + + ''' + for i in range(len(board)): + if board[row][i] == 1: + return False + for i in range(len(board)): + if board[i][column] == 1: + return False + for i,j in zip(range(row,-1,-1),range(column,-1,-1)): + if board[i][j] == 1: + return False + for i,j in zip(range(row,-1,-1),range(column,len(board))): + if board[i][j] == 1: + return False + return True + +def solve(board, row): + ''' + It creates a state space tree and calls the safe function untill it receives a + False Boolean and terminates that brach and backtracks to the next + poosible solution branch. + ''' + if row >= len(board): + ''' + If the row number exceeds N we have board with a successful combination + and that combination is appended to the solution list and the board is printed. + + ''' + solution.append(board) + printboard(board) + print() + return + for i in range(len(board)): + ''' + For every row it iterates through each column to check if it is feesible to place a + queen there. + If all the combinations for that particaular branch are successfull the board is + reinitialized for the next possible combination. + ''' + if isSafe(board,row,i): + board[row][i] = 1 + solve(board,row+1) + board[row][i] = 0 + return False + +def printboard(board): + ''' + Prints the boards that have a successfull combination. + ''' + for i in range(len(board)): + for j in range(len(board)): + if board[i][j] == 1: + print("Q", end = " ") + else : + print(".", end = " ") + print() + +#n=int(input("The no. of queens")) +n = 8 +board = [[0 for i in range(n)]for j in range(n)] +solve(board, 0) +print("The total no. of solutions are :", len(solution)) diff --git a/backtracking/sum_of_subsets.py b/backtracking/sum_of_subsets.py new file mode 100644 index 000000000000..b01bffbb651d --- /dev/null +++ b/backtracking/sum_of_subsets.py @@ -0,0 +1,45 @@ +''' + The sum-of-subsetsproblem states that a set of non-negative integers, and a value M, + determine all possible subsets of the given set whose summation sum equal to given M. + + Summation of the chosen numbers must be equal to given number M and one number can + be used only once. +''' + +def generate_sum_of_subsets_soln(nums, max_sum): + result = [] + path = [] + num_index = 0 + remaining_nums_sum = sum(nums) + create_state_space_tree(nums, max_sum, num_index, path,result, remaining_nums_sum) + return result + +def create_state_space_tree(nums,max_sum,num_index,path,result, remaining_nums_sum): + ''' + Creates a state space tree to iterate through each branch using DFS. + It terminates the branching of a node when any of the two conditions + given below satisfy. + This algorithm follows depth-fist-search and backtracks when the node is not branchable. + + ''' + if sum(path) > max_sum or (remaining_nums_sum + sum(path)) < max_sum: + return + if sum(path) == max_sum: + result.append(path) + return + for num_index in range(num_index,len(nums)): + create_state_space_tree(nums, max_sum, num_index + 1, path + [nums[num_index]], result, remaining_nums_sum - nums[num_index]) + +''' +remove the comment to take an input from the user + +print("Enter the elements") +nums = list(map(int, input().split())) +print("Enter max_sum sum") +max_sum = int(input()) + +''' +nums = [3, 34, 4, 12, 5, 2] +max_sum = 9 +result = generate_sum_of_subsets_soln(nums,max_sum) +print(*result) \ No newline at end of file diff --git a/other/n_queens.py b/other/n_queens.py deleted file mode 100644 index 0e80a0cff5e9..000000000000 --- a/other/n_queens.py +++ /dev/null @@ -1,77 +0,0 @@ -#! /usr/bin/python3 -import sys - -def nqueens(board_width): - board = [0] - current_row = 0 - while True: - conflict = False - - for review_index in range(0, current_row): - left = board[review_index] - (current_row - review_index) - right = board[review_index] + (current_row - review_index); - if (board[current_row] == board[review_index] or (left >= 0 and left == board[current_row]) or (right < board_width and right == board[current_row])): - conflict = True; - break - - if (current_row == 0 and conflict == False): - board.append(0) - current_row = 1 - continue - - if (conflict == True): - board[current_row] += 1 - - if (current_row == 0 and board[current_row] == board_width): - print("No solution exists for specificed board size.") - return None - - while True: - if (board[current_row] == board_width): - board[current_row] = 0 - if (current_row == 0): - print("No solution exists for specificed board size.") - return None - - board.pop() - current_row -= 1 - board[current_row] += 1 - - if board[current_row] != board_width: - break - else: - current_row += 1 - if (current_row == board_width): - break - - board.append(0) - return board - -def print_board(board): - if (board == None): - return - - board_width = len(board) - for row in range(board_width): - line_print = [] - for column in range(board_width): - if column == board[row]: - line_print.append("Q") - else: - line_print.append(".") - print(line_print) - - -if __name__ == '__main__': - default_width = 8 - for arg in sys.argv: - if (arg.isdecimal() and int(arg) > 3): - default_width = int(arg) - break - - if (default_width == 8): - print("Running algorithm with board size of 8. Specify an alternative Chess board size for N-Queens as a command line argument.") - - board = nqueens(default_width) - print(board) - print_board(board) From 4e413c018342e71e064c3bdd4a692121bda14fcb Mon Sep 17 00:00:00 2001 From: Anup Kumar Panwar <1anuppanwar@gmail.com> Date: Sat, 6 Jul 2019 11:11:20 +0530 Subject: [PATCH 0077/1071] Updated README --- CONTRIBUTING.md | 12 +- DIRECTORY.py | 50 +++ README.md | 346 ++++++++++++++++++ .../image_data}/PSNR-example-base.png | Bin .../image_data}/PSNR-example-comp-10.jpg | Bin .../image_data}/compressed_image.png | Bin .../image_data}/example_image.jpg | Bin .../image_data}/example_wikipedia_image.jpg | Bin .../image_data}/original_image.png | Bin .../peak_signal_to_noise_ratio.py | 8 +- data_structures/__init__.py | 0 data_structures/arrays.py | 3 - data_structures/avl.py | 181 --------- data_structures/{ => binary_tree}/LCA.py | 0 .../binary_tree}/basic_binary_tree.py | 0 data_structures/hashing/__init__.py | 6 - .../hashing/number_theory/__init__.py | 0 .../{swapNodes.py => swap_nodes.py} | 0 data_structures/queue/__init__.py | 0 .../{next.py => next_greater_element.py} | 0 data_structures/union_find/__init__.py | 0 .../union_find/tests_union_find.py | 78 ---- data_structures/union_find/union_find.py | 87 ----- .../Social_Network_Ads.csv | 0 .../random_forest_classification.py | 0 .../random_forest_classifier.ipynb} | 0 .../Position_Salaries.csv | 0 .../random_forest_regression.ipynb} | 0 .../random_forest_regression.py | 0 maths/Hanoi.py | 29 -- maths/{lucasSeries.py => lucas series.py} | 0 maths/tests/__init__.py | 1 - maths/tests/test_fibonacci.py | 34 -- matrix/{spiralPrint.py => spiral_print.py} | 0 ....py => back_propagation_neural_network.py} | 0 ...b => fully_connected_neural_network.ipynb} | 0 .../game_o_life.py => game_of_life.py} | 0 other/game_of_life/sample.gif | Bin 228847 -> 0 bytes searches/test_interpolation_search.py | 93 ----- searches/test_tabu_search.py | 46 --- simple_client/README.md | 6 - simple_client/client.py | 29 -- simple_client/server.py | 21 -- sorts/sorting_graphs.png | Bin 10362 -> 0 bytes sorts/tests.py | 76 ---- 45 files changed, 404 insertions(+), 702 deletions(-) create mode 100644 DIRECTORY.py rename {compression_analysis => compression/image_data}/PSNR-example-base.png (100%) rename {compression_analysis => compression/image_data}/PSNR-example-comp-10.jpg (100%) rename {compression_analysis => compression/image_data}/compressed_image.png (100%) rename {compression_analysis => compression/image_data}/example_image.jpg (100%) rename {compression_analysis => compression/image_data}/example_wikipedia_image.jpg (100%) rename {compression_analysis => compression/image_data}/original_image.png (100%) rename compression_analysis/psnr.py => compression/peak_signal_to_noise_ratio.py (71%) delete mode 100644 data_structures/__init__.py delete mode 100644 data_structures/arrays.py delete mode 100644 data_structures/avl.py rename data_structures/{ => binary_tree}/LCA.py (100%) rename {binary_tree => data_structures/binary_tree}/basic_binary_tree.py (100%) delete mode 100644 data_structures/hashing/__init__.py delete mode 100644 data_structures/hashing/number_theory/__init__.py rename data_structures/linked_list/{swapNodes.py => swap_nodes.py} (100%) delete mode 100644 data_structures/queue/__init__.py rename data_structures/stacks/{next.py => next_greater_element.py} (100%) delete mode 100644 data_structures/union_find/__init__.py delete mode 100644 data_structures/union_find/tests_union_find.py delete mode 100644 data_structures/union_find/union_find.py rename machine_learning/{Random Forest Classification => random_forest_classification}/Social_Network_Ads.csv (100%) rename machine_learning/{Random Forest Classification => random_forest_classification}/random_forest_classification.py (100%) rename machine_learning/{Random Forest Classification/Random Forest Classifier.ipynb => random_forest_classification/random_forest_classifier.ipynb} (100%) rename machine_learning/{Random Forest Regression => random_forest_regression}/Position_Salaries.csv (100%) rename machine_learning/{Random Forest Regression/Random Forest Regression.ipynb => random_forest_regression/random_forest_regression.ipynb} (100%) rename machine_learning/{Random Forest Regression => random_forest_regression}/random_forest_regression.py (100%) delete mode 100644 maths/Hanoi.py rename maths/{lucasSeries.py => lucas series.py} (100%) delete mode 100644 maths/tests/__init__.py delete mode 100644 maths/tests/test_fibonacci.py rename matrix/{spiralPrint.py => spiral_print.py} (100%) rename neural_network/{bpnn.py => back_propagation_neural_network.py} (100%) rename neural_network/{fcn.ipynb => fully_connected_neural_network.ipynb} (100%) rename other/{game_of_life/game_o_life.py => game_of_life.py} (100%) delete mode 100644 other/game_of_life/sample.gif delete mode 100644 searches/test_interpolation_search.py delete mode 100644 searches/test_tabu_search.py delete mode 100644 simple_client/README.md delete mode 100644 simple_client/client.py delete mode 100644 simple_client/server.py delete mode 100644 sorts/sorting_graphs.png delete mode 100644 sorts/tests.py diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 19b928c187f9..ac632574e870 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -72,9 +72,9 @@ We want your work to be readable by others; therefore, we encourage you to note - Write tests to illustrate your work. - The following "testing" approaches are not encouraged: + The following "testing" approaches are **not** encouraged: - ```python + ```python* input('Enter your input:') # Or even worse... input = eval(raw_input("Enter your input: ")) @@ -97,13 +97,9 @@ We want your work to be readable by others; therefore, we encourage you to note #### Other Standard While Submitting Your Work -- File extension for code should be `.py`. - -- Please file your work to let others use it in the future. Here are the examples that are acceptable: +- File extension for code should be `.py`. Jupiter notebook files are acceptable in machine learning algorithms. - - Camel cases - - `-` Hyphenated names - - `_` Underscore-separated names +- Strictly use snake case (underscore separated) in your file name, as it will be easy to parse in future using scripts. If possible, follow the standard *within* the folder you are submitting to. diff --git a/DIRECTORY.py b/DIRECTORY.py new file mode 100644 index 000000000000..434b2a3dd3ed --- /dev/null +++ b/DIRECTORY.py @@ -0,0 +1,50 @@ +import os + +def getListOfFiles(dirName): + # create a list of file and sub directories + # names in the given directory + listOfFile = os.listdir(dirName) + allFiles = list() + # Iterate over all the entries + for entry in listOfFile: + # if entry == listOfFile[len(listOfFile)-1]: + # continue + if entry=='.git': + continue + # Create full path + fullPath = os.path.join(dirName, entry) + entryName = entry.split('_') + # print(entryName) + ffname = '' + try: + for word in entryName: + temp = word[0].upper() + word[1:] + ffname = ffname + ' ' + temp + # print(temp) + final_fn = ffname.replace('.py', '') + final_fn = final_fn.strip() + print('* ['+final_fn+']('+fullPath+')') + # pass + except: + pass + # If entry is a directory then get the list of files in this directory + if os.path.isdir(fullPath): + print ('\n## '+entry) + filesInCurrDir = getListOfFiles(fullPath) + for file in filesInCurrDir: + fileName = file.split('/') + fileName = fileName[len(fileName)-1] + + # print (fileName) + allFiles = allFiles + filesInCurrDir + else: + allFiles.append(fullPath) + + return allFiles + + +dirName = './'; + +# Get the list of all files in directory tree at given path +listOfFiles = getListOfFiles(dirName) +# print (listOfFiles) \ No newline at end of file diff --git a/README.md b/README.md index 527b80269fdc..9edddb60552a 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,5 @@ # The Algorithms - Python + [![Donate](https://img.shields.io/badge/Donate-PayPal-green.svg)](https://www.paypal.me/TheAlgorithms/100)   [![Gitter chat](https://badges.gitter.im/gitterHQ/gitter.png)](https://gitter.im/TheAlgorithms)   [![Open in Gitpod](https://gitpod.io/button/open-in-gitpod.svg)](https://gitpod.io/#https://github.com/TheAlgorithms/Python) @@ -7,6 +8,17 @@ These implementations are for learning purposes. They may be less efficient than the implementations in the Python standard library. +## Owners + +Anup Kumar Panwar +  [[Gmail](mailto:1anuppanwar@gmail.com?Subject=The%20Algorithms%20-%20Python) +  [Gihub](https://github.com/anupkumarpanwar) +  [LinkedIn](https://www.linkedin.com/in/anupkumarpanwar/)] + +Chetan Kaushik +  [[Gmail](mailto:dynamitechetan@gmail.com?Subject=The%20Algorithms%20-%20Python) +  [Gihub](https://github.com/dynamitechetan) +  [LinkedIn](https://www.linkedin.com/in/chetankaushik/)] ## Contribution Guidelines @@ -15,3 +27,337 @@ Read our [Contribution Guidelines](CONTRIBUTING.md) before you contribute. ## Community Channel We're on [Gitter](https://gitter.im/TheAlgorithms)! Please join us. + +# Algorithms + +## Hashes + +- [Md5](./hashes/md5.py) +- [Chaos Machine](./hashes/chaos_machine.py) +- [Sha1](./hashes/sha1.py) + +## File Transfer Protocol + +- [Ftp Client Server](./file_transfer_protocol/ftp_client_server.py) +- [Ftp Send Receive](./file_transfer_protocol/ftp_send_receive.py) + +## Backtracking + +- [N Queens](./backtracking/n_queens.py) +- [Sum Of Subsets](./backtracking/sum_of_subsets.py) + +## Ciphers + +- [Transposition Cipher](./ciphers/transposition_cipher.py) +- [Atbash](./ciphers/Atbash.py) +- [Rot13](./ciphers/rot13.py) +- [Rabin Miller](./ciphers/rabin_miller.py) +- [Transposition Cipher Encrypt Decrypt File](./ciphers/transposition_cipher_encrypt_decrypt_file.py) +- [Affine Cipher](./ciphers/affine_cipher.py) +- [Trafid Cipher](./ciphers/trafid_cipher.py) +- [Base16](./ciphers/base16.py) +- [Elgamal Key Generator](./ciphers/elgamal_key_generator.py) +- [Rsa Cipher](./ciphers/rsa_cipher.py) +- [Prehistoric Men.txt](./ciphers/prehistoric_men.txt) +- [Vigenere Cipher](./ciphers/vigenere_cipher.py) +- [Xor Cipher](./ciphers/xor_cipher.py) +- [Brute Force Caesar Cipher](./ciphers/brute_force_caesar_cipher.py) +- [Rsa Key Generator](./ciphers/rsa_key_generator.py) +- [Simple Substitution Cipher](./ciphers/simple_substitution_cipher.py) +- [Playfair Cipher](./ciphers/playfair_cipher.py) +- [Morse Code Implementation](./ciphers/morse_Code_implementation.py) +- [Base32](./ciphers/base32.py) +- [Base85](./ciphers/base85.py) +- [Base64 Cipher](./ciphers/base64_cipher.py) +- [Onepad Cipher](./ciphers/onepad_cipher.py) +- [Caesar Cipher](./ciphers/caesar_cipher.py) +- [Hill Cipher](./ciphers/hill_cipher.py) +- [Cryptomath Module](./ciphers/cryptomath_module.py) + +## Arithmetic Analysis + +- [Bisection](./arithmetic_analysis/bisection.py) +- [Newton Method](./arithmetic_analysis/newton_method.py) +- [Newton Raphson Method](./arithmetic_analysis/newton_raphson_method.py) +- [Intersection](./arithmetic_analysis/intersection.py) +- [Lu Decomposition](./arithmetic_analysis/lu_decomposition.py) + +## Boolean Algebra + +- [Quine Mc Cluskey](./boolean_algebra/quine_mc_cluskey.py) + +## Traversals + +- [Binary Tree Traversals](./traversals/binary_tree_traversals.py) + +## Maths + +- [Average](./maths/average.py) +- [Abs Max](./maths/abs_Max.py) +- [Average Median](./maths/average_median.py) +- [Trapezoidal Rule](./maths/trapezoidal_rule.py) +- [Prime Check](./maths/Prime_Check.py) +- [Modular Exponential](./maths/modular_exponential.py) +- [Newton Raphson](./maths/newton_raphson.py) +- [Factorial Recursive](./maths/factorial_recursive.py) +- [Extended Euclidean Algorithm](./maths/extended_euclidean_algorithm.py) +- [Greater Common Divisor](./maths/greater_common_divisor.py) +- [Fibonacci](./maths/fibonacci.py) +- [Find Lcm](./maths/find_lcm.py) +- [Find Max](./maths/Find_Max.py) +- [Fermat Little Theorem](./maths/fermat_little_theorem.py) +- [Factorial Python](./maths/factorial_python.py) +- [Fibonacci Sequence Recursion](./maths/fibonacci_sequence_recursion.py) +- [Sieve Of Eratosthenes](./maths/sieve_of_eratosthenes.py) +- [Abs Min](./maths/abs_Min.py) +- [Lucas Series](./maths/lucasSeries.py) +- [Segmented Sieve](./maths/segmented_sieve.py) +- [Find Min](./maths/Find_Min.py) +- [Abs](./maths/abs.py) +- [Simpson Rule](./maths/simpson_rule.py) +- [Basic Maths](./maths/basic_maths.py) +- [3n+1](./maths/3n+1.py) +- [Binary Exponentiation](./maths/Binary_Exponentiation.py) + +## Digital Image Processing + +- ## Filters + + - [Median Filter](./digital_image_processing/filters/median_filter.py) + - [Gaussian Filter](./digital_image_processing/filters/gaussian_filter.py) + + +## Compression + +- [Peak Signal To Noise Ratio](./compression_analysis/peak_signal_to_noise_ratio.py) +- [Huffman](./compression/huffman.py) + +## Graphs + +- [BFS Shortest Path](./graphs/bfs_shortest_path.py) +- [Directed And Undirected (Weighted) Graph](<./graphs/Directed_and_Undirected_(Weighted)_Graph.py>) +- [Minimum Spanning Tree Prims](./graphs/minimum_spanning_tree_prims.py) +- [Graph Matrix](./graphs/graph_matrix.py) +- [Basic Graphs](./graphs/basic_graphs.py) +- [Dijkstra 2](./graphs/dijkstra_2.py) +- [Tarjans Strongly Connected Components](./graphs/tarjans_scc.py) +- [Check Bipartite Graph BFS](./graphs/check_bipartite_graph_bfs.py) +- [Depth First Search](./graphs/depth_first_search.py) +- [Kahns Algorithm Long](./graphs/kahns_algorithm_long.py) +- [Breadth First Search](./graphs/breadth_first_search.py) +- [Dijkstra](./graphs/dijkstra.py) +- [Articulation Points](./graphs/articulation_points.py) +- [Bellman Ford](./graphs/bellman_ford.py) +- [Check Bipartite Graph Dfs](./graphs/check_bipartite_graph_dfs.py) +- [Strongly Connected Components Kosaraju](./graphs/scc_kosaraju.py) +- [Multi Hueristic Astar](./graphs/multi_hueristic_astar.py) +- [Page Rank](./graphs/page_rank.py) +- [Eulerian Path And Circuit For Undirected Graph](./graphs/Eulerian_path_and_circuit_for_undirected_graph.py) +- [Edmonds Karp Multiple Source And Sink](./graphs/edmonds_karp_multiple_source_and_sink.py) +- [Floyd Warshall](./graphs/floyd_warshall.py) +- [Minimum Spanning Tree Kruskal](./graphs/minimum_spanning_tree_kruskal.py) +- [Prim](./graphs/prim.py) +- [Kahns Algorithm Topo](./graphs/kahns_algorithm_topo.py) +- [BFS](./graphs/BFS.py) +- [Finding Bridges](./graphs/finding_bridges.py) +- [Graph List](./graphs/graph_list.py) +- [Dijkstra Algorithm](./graphs/dijkstra_algorithm.py) +- [A Star](./graphs/a_star.py) +- [Even Tree](./graphs/even_tree.py) +- [DFS](./graphs/DFS.py) + +## Networking Flow + +- [Minimum Cut](./networking_flow/minimum_cut.py) +- [Ford Fulkerson](./networking_flow/ford_fulkerson.py) + +## Matrix + +- [Matrix Operation](./matrix/matrix_operation.py) +- [Searching In Sorted Matrix](./matrix/searching_in_sorted_matrix.py) +- [Spiral Print](./matrix/spiral_print.py) + +## Searches + +- [Quick Select](./searches/quick_select.py) +- [Binary Search](./searches/binary_search.py) +- [Interpolation Search](./searches/interpolation_search.py) +- [Jump Search](./searches/jump_search.py) +- [Linear Search](./searches/linear_search.py) +- [Ternary Search](./searches/ternary_search.py) +- [Tabu Search](./searches/tabu_search.py) +- [Sentinel Linear Search](./searches/sentinel_linear_search.py) + +## Conversions + +- [Decimal To Binary](./conversions/decimal_to_binary.py) +- [Decimal To Octal](./conversions/decimal_to_octal.py) + +## Dynamic Programming + +- [Fractional Knapsack](./dynamic_programming/Fractional_Knapsack.py) +- [Sum Of Subset](./dynamic_programming/sum_of_subset.py) +- [Fast Fibonacci](./dynamic_programming/fast_fibonacci.py) +- [Bitmask](./dynamic_programming/bitmask.py) +- [Abbreviation](./dynamic_programming/abbreviation.py) +- [Rod Cutting](./dynamic_programming/rod_cutting.py) +- [Knapsack](./dynamic_programming/knapsack.py) +- [Max Sub Array](./dynamic_programming/max_sub_array.py) +- [Fibonacci](./dynamic_programming/fibonacci.py) +- [Minimum Partition](./dynamic_programming/minimum_partition.py) +- [K Means Clustering Tensorflow](./dynamic_programming/k_means_clustering_tensorflow.py) +- [Coin Change](./dynamic_programming/coin_change.py) +- [Subset Generation](./dynamic_programming/subset_generation.py) +- [Floyd Warshall](./dynamic_programming/floyd_warshall.py) +- [Longest Sub Array](./dynamic_programming/longest_sub_array.py) +- [Integer Partition](./dynamic_programming/integer_partition.py) +- [Matrix Chain Order](./dynamic_programming/matrix_chain_order.py) +- [Edit Distance](./dynamic_programming/edit_distance.py) +- [Longest Common Subsequence](./dynamic_programming/longest_common_subsequence.py) +- [Longest Increasing Subsequence O(nlogn)](<./dynamic_programming/longest_increasing_subsequence_O(nlogn).py>) +- [Longest Increasing Subsequence](./dynamic_programming/longest_increasing_subsequence.py) + +## Divide And Conquer + +- [Max Subarray Sum](./divide_and_conquer/max_subarray_sum.py) +- [Max Sub Array Sum](./divide_and_conquer/max_sub_array_sum.py) +- [Closest Pair Of Points](./divide_and_conquer/closest_pair_of_points.py) + +## Strings + +- [Knuth Morris Pratt](./strings/knuth_morris_pratt.py) +- [Rabin Karp](./strings/rabin_karp.py) +- [Naive String Search](./strings/naive_String_Search.py) +- [Levenshtein Distance](./strings/levenshtein_distance.py) +- [Min Cost String Conversion](./strings/min_cost_string_conversion.py) +- [Boyer Moore Search](./strings/Boyer_Moore_Search.py) +- [Manacher](./strings/manacher.py) + +## Sorts + +- [Quick Sort](./sorts/quick_sort.py) +- [Selection Sort](./sorts/selection_sort.py) +- [Bitonic Sort](./sorts/Bitonic_Sort.py) +- [Cycle Sort](./sorts/cycle_sort.py) +- [Comb Sort](./sorts/comb_sort.py) +- [Topological Sort](./sorts/topological_sort.py) +- [Merge Sort Fastest](./sorts/merge_sort_fastest.py) +- [Random Pivot Quick Sort](./sorts/random_pivot_quick_sort.py) +- [Heap Sort](./sorts/heap_sort.py) +- [Insertion Sort](./sorts/insertion_sort.py) +- [Counting Sort](./sorts/counting_sort.py) +- [Bucket Sort](./sorts/bucket_sort.py) +- [Quick Sort 3 Partition](./sorts/quick_sort_3_partition.py) +- [Bogo Sort](./sorts/bogo_sort.py) +- [Shell Sort](./sorts/shell_sort.py) +- [Pigeon Sort](./sorts/pigeon_sort.py) +- [Odd-Even Transposition Parallel](./sorts/Odd-Even_transposition_parallel.py) +- [Tree Sort](./sorts/tree_sort.py) +- [Cocktail Shaker Sort](./sorts/cocktail_shaker_sort.py) +- [Random Normal Distribution Quicksort](./sorts/random_normal_distribution_quicksort.py) +- [Wiggle Sort](./sorts/wiggle_sort.py) +- [Pancake Sort](./sorts/pancake_sort.py) +- [External Sort](./sorts/external_sort.py) +- [Tim Sort](./sorts/tim_sort.py) +- [Sorting Graphs.png](./sorts/sorting_graphs.png) +- [Radix Sort](./sorts/radix_sort.py) +- [Odd-Even Transposition Single-threaded](./sorts/Odd-Even_transposition_single-threaded.py) +- [Bubble Sort](./sorts/bubble_sort.py) +- [Gnome Sort](./sorts/gnome_sort.py) +- [Merge Sort](./sorts/merge_sort.py) + +## Machine Learning + +- [Perceptron](./machine_learning/perceptron.py) +- [Random Forest Classifier](./machine_learning/random_forest_classification/random_forest_classifier.ipynb) +- [NaiveBayes.ipynb](./machine_learning/NaiveBayes.ipynb) +- [Scoring Functions](./machine_learning/scoring_functions.py) +- [Logistic Regression](./machine_learning/logistic_regression.py) +- [Gradient Descent](./machine_learning/gradient_descent.py) +- [Linear Regression](./machine_learning/linear_regression.py) +- [Random Forest Regression](./machine_learning/random_forest_regression/random_forest_regression.py) +- [Random Forest Regression](./machine_learning/random_forest_regression/random_forest_regression.ipynb) +- [Reuters One Vs Rest Classifier.ipynb](./machine_learning/reuters_one_vs_rest_classifier.ipynb) +- [Decision Tree](./machine_learning/decision_tree.py) +- [Knn Sklearn](./machine_learning/knn_sklearn.py) +- [K Means Clust](./machine_learning/k_means_clust.py) + +## Neural Network + +- [Perceptron](./neural_network/perceptron.py) +- [Fully Connected Neural Network](./neural_network/fully_connected_neural_network.ipynb) +- [Convolution Neural Network](./neural_network/convolution_neural_network.py) +- [Back Propagation Neural Network](./neural_network/back_propagation_neural_network.py) + +## Data Structures + +- ## Binary Tree + + - [Basic Binary Tree](./data_structures/binary_tree/basic_binary_tree.py) + - [Red Black Tree](./data_structures/binary_tree/red_black_tree.py) + - [Fenwick Tree](./data_structures/binary_tree/fenwick_tree.py) + - [Treap](./data_structures/binary_tree/treap.py) + - [AVL Tree](./data_structures/binary_tree/AVL_tree.py) + - [Segment Tree](./data_structures/binary_tree/segment_tree.py) + - [Lazy Segment Tree](./data_structures/binary_tree/lazy_segment_tree.py) + - [Binary Search Tree](./data_structures/binary_tree/binary_search_tree.py) + +- ## Trie + + - [Trie](./data_structures/trie/trie.py) + +- ## Linked List + + - [Swap Nodes](./data_structures/linked_list/swap_nodes.py) + - [Doubly Linked List](./data_structures/linked_list/doubly_linked_list.py) + - [Singly Linked List](./data_structures/linked_list/singly_linked_list.py) + - [Is Palindrome](./data_structures/linked_list/is_Palindrome.py) + +- ## Stacks + + - [Postfix Evaluation](./data_structures/stacks/postfix_evaluation.py) + - [Balanced Parentheses](./data_structures/stacks/balanced_parentheses.py) + - [Infix To Prefix Conversion](./data_structures/stacks/infix_to_prefix_conversion.py) + - [Stack](./data_structures/stacks/stack.py) + - [Infix To Postfix Conversion](./data_structures/stacks/infix_to_postfix_conversion.py) + - [Next Greater Element](./data_structures/stacks/next_greater_element.py) + - [Stock Span Problem](./data_structures/stacks/stock_span_problem.py) + +- ## Queue + + - [Queue On Pseudo Stack](./data_structures/queue/queue_on_pseudo_stack.py) + - [Double Ended Queue](./data_structures/queue/double_ended_queue.py) + - [Queue On List](./data_structures/queue/queue_on_list.py) + +- ## Heap + + - [Heap](./data_structures/heap/heap.py) + +- ## Hashing + + - [Hash Table With Linked List](./data_structures/hashing/hash_table_with_linked_list.py) + - [Quadratic Probing](./data_structures/hashing/quadratic_probing.py) + - [Hash Table](./data_structures/hashing/hash_table.py) + - [Double Hash](./data_structures/hashing/double_hash.py) + + +## Other + +- [Detecting English Programmatically](./other/detecting_english_programmatically.py) +- [Fischer Yates Shuffle](./other/fischer_yates_shuffle.py) +- [Primelib](./other/primelib.py) +- [Binary Exponentiation 2](./other/binary_exponentiation_2.py) +- [Anagrams](./other/anagrams.py) +- [Palindrome](./other/palindrome.py) +- [Finding Primes](./other/finding_Primes.py) +- [Two Sum](./other/two_sum.py) +- [Password Generator](./other/password_generator.py) +- [Linear Congruential Generator](./other/linear_congruential_generator.py) +- [Frequency Finder](./other/frequency_finder.py) +- [Euclidean Gcd](./other/euclidean_gcd.py) +- [Word Patterns](./other/word_patterns.py) +- [Nested Brackets](./other/nested_brackets.py) +- [Binary Exponentiation](./other/binary_exponentiation.py) +- [Sierpinski Triangle](./other/sierpinski_triangle.py) +- [Game Of Life](./other/game_of_life.py) +- [Tower Of Hanoi](./other/tower_of_hanoi.py) diff --git a/compression_analysis/PSNR-example-base.png b/compression/image_data/PSNR-example-base.png similarity index 100% rename from compression_analysis/PSNR-example-base.png rename to compression/image_data/PSNR-example-base.png diff --git a/compression_analysis/PSNR-example-comp-10.jpg b/compression/image_data/PSNR-example-comp-10.jpg similarity index 100% rename from compression_analysis/PSNR-example-comp-10.jpg rename to compression/image_data/PSNR-example-comp-10.jpg diff --git a/compression_analysis/compressed_image.png b/compression/image_data/compressed_image.png similarity index 100% rename from compression_analysis/compressed_image.png rename to compression/image_data/compressed_image.png diff --git a/compression_analysis/example_image.jpg b/compression/image_data/example_image.jpg similarity index 100% rename from compression_analysis/example_image.jpg rename to compression/image_data/example_image.jpg diff --git a/compression_analysis/example_wikipedia_image.jpg b/compression/image_data/example_wikipedia_image.jpg similarity index 100% rename from compression_analysis/example_wikipedia_image.jpg rename to compression/image_data/example_wikipedia_image.jpg diff --git a/compression_analysis/original_image.png b/compression/image_data/original_image.png similarity index 100% rename from compression_analysis/original_image.png rename to compression/image_data/original_image.png diff --git a/compression_analysis/psnr.py b/compression/peak_signal_to_noise_ratio.py similarity index 71% rename from compression_analysis/psnr.py rename to compression/peak_signal_to_noise_ratio.py index 0f21aac07d34..b0efb1462dcc 100644 --- a/compression_analysis/psnr.py +++ b/compression/peak_signal_to_noise_ratio.py @@ -21,11 +21,11 @@ def psnr(original, contrast): def main(): dir_path = os.path.dirname(os.path.realpath(__file__)) # Loading images (original image and compressed image) - original = cv2.imread(os.path.join(dir_path, 'original_image.png')) - contrast = cv2.imread(os.path.join(dir_path, 'compressed_image.png'), 1) + original = cv2.imread(os.path.join(dir_path, 'image_data/original_image.png')) + contrast = cv2.imread(os.path.join(dir_path, 'image_data/compressed_image.png'), 1) - original2 = cv2.imread(os.path.join(dir_path, 'PSNR-example-base.png')) - contrast2 = cv2.imread(os.path.join(dir_path, 'PSNR-example-comp-10.jpg'), 1) + original2 = cv2.imread(os.path.join(dir_path, 'image_data/PSNR-example-base.png')) + contrast2 = cv2.imread(os.path.join(dir_path, 'image_data/PSNR-example-comp-10.jpg'), 1) # Value expected: 29.73dB print("-- First Test --") diff --git a/data_structures/__init__.py b/data_structures/__init__.py deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/data_structures/arrays.py b/data_structures/arrays.py deleted file mode 100644 index feb061013556..000000000000 --- a/data_structures/arrays.py +++ /dev/null @@ -1,3 +0,0 @@ -arr = [10, 20, 30, 40] -arr[1] = 30 # set element 1 (20) of array to 30 -print(arr) diff --git a/data_structures/avl.py b/data_structures/avl.py deleted file mode 100644 index d01e8f825368..000000000000 --- a/data_structures/avl.py +++ /dev/null @@ -1,181 +0,0 @@ -""" -An AVL tree -""" -from __future__ import print_function - - -class Node: - - def __init__(self, label): - self.label = label - self._parent = None - self._left = None - self._right = None - self.height = 0 - - @property - def right(self): - return self._right - - @right.setter - def right(self, node): - if node is not None: - node._parent = self - self._right = node - - @property - def left(self): - return self._left - - @left.setter - def left(self, node): - if node is not None: - node._parent = self - self._left = node - - @property - def parent(self): - return self._parent - - @parent.setter - def parent(self, node): - if node is not None: - self._parent = node - self.height = self.parent.height + 1 - else: - self.height = 0 - - -class AVL: - - def __init__(self): - self.root = None - self.size = 0 - - def insert(self, value): - node = Node(value) - - if self.root is None: - self.root = node - self.root.height = 0 - self.size = 1 - else: - # Same as Binary Tree - dad_node = None - curr_node = self.root - - while True: - if curr_node is not None: - - dad_node = curr_node - - if node.label < curr_node.label: - curr_node = curr_node.left - else: - curr_node = curr_node.right - else: - node.height = dad_node.height - dad_node.height += 1 - if node.label < dad_node.label: - dad_node.left = node - else: - dad_node.right = node - self.rebalance(node) - self.size += 1 - break - - def rebalance(self, node): - n = node - - while n is not None: - height_right = n.height - height_left = n.height - - if n.right is not None: - height_right = n.right.height - - if n.left is not None: - height_left = n.left.height - - if abs(height_left - height_right) > 1: - if height_left > height_right: - left_child = n.left - if left_child is not None: - h_right = (left_child.right.height - if (left_child.right is not None) else 0) - h_left = (left_child.left.height - if (left_child.left is not None) else 0) - if (h_left > h_right): - self.rotate_left(n) - break - else: - self.double_rotate_right(n) - break - else: - right_child = n.right - if right_child is not None: - h_right = (right_child.right.height - if (right_child.right is not None) else 0) - h_left = (right_child.left.height - if (right_child.left is not None) else 0) - if (h_left > h_right): - self.double_rotate_left(n) - break - else: - self.rotate_right(n) - break - n = n.parent - - def rotate_left(self, node): - aux = node.parent.label - node.parent.label = node.label - node.parent.right = Node(aux) - node.parent.right.height = node.parent.height + 1 - node.parent.left = node.right - - - def rotate_right(self, node): - aux = node.parent.label - node.parent.label = node.label - node.parent.left = Node(aux) - node.parent.left.height = node.parent.height + 1 - node.parent.right = node.right - - def double_rotate_left(self, node): - self.rotate_right(node.getRight().getRight()) - self.rotate_left(node) - - def double_rotate_right(self, node): - self.rotate_left(node.getLeft().getLeft()) - self.rotate_right(node) - - def empty(self): - if self.root is None: - return True - return False - - def preShow(self, curr_node): - if curr_node is not None: - self.preShow(curr_node.left) - print(curr_node.label, end=" ") - self.preShow(curr_node.right) - - def preorder(self, curr_node): - if curr_node is not None: - self.preShow(curr_node.left) - self.preShow(curr_node.right) - print(curr_node.label, end=" ") - - def getRoot(self): - return self.root - -t = AVL() -t.insert(1) -t.insert(2) -t.insert(3) -# t.preShow(t.root) -# print("\n") -# t.insert(4) -# t.insert(5) -# t.preShow(t.root) -# t.preorden(t.root) diff --git a/data_structures/LCA.py b/data_structures/binary_tree/LCA.py similarity index 100% rename from data_structures/LCA.py rename to data_structures/binary_tree/LCA.py diff --git a/binary_tree/basic_binary_tree.py b/data_structures/binary_tree/basic_binary_tree.py similarity index 100% rename from binary_tree/basic_binary_tree.py rename to data_structures/binary_tree/basic_binary_tree.py diff --git a/data_structures/hashing/__init__.py b/data_structures/hashing/__init__.py deleted file mode 100644 index b96ddd478458..000000000000 --- a/data_structures/hashing/__init__.py +++ /dev/null @@ -1,6 +0,0 @@ -from .hash_table import HashTable - -class QuadraticProbing(HashTable): - - def __init__(self): - super(self.__class__, self).__init__() diff --git a/data_structures/hashing/number_theory/__init__.py b/data_structures/hashing/number_theory/__init__.py deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/data_structures/linked_list/swapNodes.py b/data_structures/linked_list/swap_nodes.py similarity index 100% rename from data_structures/linked_list/swapNodes.py rename to data_structures/linked_list/swap_nodes.py diff --git a/data_structures/queue/__init__.py b/data_structures/queue/__init__.py deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/data_structures/stacks/next.py b/data_structures/stacks/next_greater_element.py similarity index 100% rename from data_structures/stacks/next.py rename to data_structures/stacks/next_greater_element.py diff --git a/data_structures/union_find/__init__.py b/data_structures/union_find/__init__.py deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/data_structures/union_find/tests_union_find.py b/data_structures/union_find/tests_union_find.py deleted file mode 100644 index b0708778ddbd..000000000000 --- a/data_structures/union_find/tests_union_find.py +++ /dev/null @@ -1,78 +0,0 @@ -from __future__ import absolute_import -from .union_find import UnionFind -import unittest - - -class TestUnionFind(unittest.TestCase): - def test_init_with_valid_size(self): - uf = UnionFind(5) - self.assertEqual(uf.size, 5) - - def test_init_with_invalid_size(self): - with self.assertRaises(ValueError): - uf = UnionFind(0) - - with self.assertRaises(ValueError): - uf = UnionFind(-5) - - def test_union_with_valid_values(self): - uf = UnionFind(10) - - for i in range(11): - for j in range(11): - uf.union(i, j) - - def test_union_with_invalid_values(self): - uf = UnionFind(10) - - with self.assertRaises(ValueError): - uf.union(-1, 1) - - with self.assertRaises(ValueError): - uf.union(11, 1) - - def test_same_set_with_valid_values(self): - uf = UnionFind(10) - - for i in range(11): - for j in range(11): - if i == j: - self.assertTrue(uf.same_set(i, j)) - else: - self.assertFalse(uf.same_set(i, j)) - - uf.union(1, 2) - self.assertTrue(uf.same_set(1, 2)) - - uf.union(3, 4) - self.assertTrue(uf.same_set(3, 4)) - - self.assertFalse(uf.same_set(1, 3)) - self.assertFalse(uf.same_set(1, 4)) - self.assertFalse(uf.same_set(2, 3)) - self.assertFalse(uf.same_set(2, 4)) - - uf.union(1, 3) - self.assertTrue(uf.same_set(1, 3)) - self.assertTrue(uf.same_set(1, 4)) - self.assertTrue(uf.same_set(2, 3)) - self.assertTrue(uf.same_set(2, 4)) - - uf.union(4, 10) - self.assertTrue(uf.same_set(1, 10)) - self.assertTrue(uf.same_set(2, 10)) - self.assertTrue(uf.same_set(3, 10)) - self.assertTrue(uf.same_set(4, 10)) - - def test_same_set_with_invalid_values(self): - uf = UnionFind(10) - - with self.assertRaises(ValueError): - uf.same_set(-1, 1) - - with self.assertRaises(ValueError): - uf.same_set(11, 0) - - -if __name__ == '__main__': - unittest.main() diff --git a/data_structures/union_find/union_find.py b/data_structures/union_find/union_find.py deleted file mode 100644 index 40eea67ac944..000000000000 --- a/data_structures/union_find/union_find.py +++ /dev/null @@ -1,87 +0,0 @@ -class UnionFind(): - """ - https://en.wikipedia.org/wiki/Disjoint-set_data_structure - - The union-find is a disjoint-set data structure - - You can merge two sets and tell if one set belongs to - another one. - - It's used on the Kruskal Algorithm - (https://en.wikipedia.org/wiki/Kruskal%27s_algorithm) - - The elements are in range [0, size] - """ - def __init__(self, size): - if size <= 0: - raise ValueError("size should be greater than 0") - - self.size = size - - # The below plus 1 is because we are using elements - # in range [0, size]. It makes more sense. - - # Every set begins with only itself - self.root = [i for i in range(size+1)] - - # This is used for heuristic union by rank - self.weight = [0 for i in range(size+1)] - - def union(self, u, v): - """ - Union of the sets u and v. - Complexity: log(n). - Amortized complexity: < 5 (it's very fast). - """ - - self._validate_element_range(u, "u") - self._validate_element_range(v, "v") - - if u == v: - return - - # Using union by rank will guarantee the - # log(n) complexity - rootu = self._root(u) - rootv = self._root(v) - weight_u = self.weight[rootu] - weight_v = self.weight[rootv] - if weight_u >= weight_v: - self.root[rootv] = rootu - if weight_u == weight_v: - self.weight[rootu] += 1 - else: - self.root[rootu] = rootv - - def same_set(self, u, v): - """ - Return true if the elements u and v belongs to - the same set - """ - - self._validate_element_range(u, "u") - self._validate_element_range(v, "v") - - return self._root(u) == self._root(v) - - def _root(self, u): - """ - Get the element set root. - This uses the heuristic path compression - See wikipedia article for more details. - """ - - if u != self.root[u]: - self.root[u] = self._root(self.root[u]) - - return self.root[u] - - def _validate_element_range(self, u, element_name): - """ - Raises ValueError if element is not in range - """ - if u < 0 or u > self.size: - msg = ("element {0} with value {1} " - "should be in range [0~{2}]")\ - .format(element_name, u, self.size) - raise ValueError(msg) diff --git a/machine_learning/Random Forest Classification/Social_Network_Ads.csv b/machine_learning/random_forest_classification/Social_Network_Ads.csv similarity index 100% rename from machine_learning/Random Forest Classification/Social_Network_Ads.csv rename to machine_learning/random_forest_classification/Social_Network_Ads.csv diff --git a/machine_learning/Random Forest Classification/random_forest_classification.py b/machine_learning/random_forest_classification/random_forest_classification.py similarity index 100% rename from machine_learning/Random Forest Classification/random_forest_classification.py rename to machine_learning/random_forest_classification/random_forest_classification.py diff --git a/machine_learning/Random Forest Classification/Random Forest Classifier.ipynb b/machine_learning/random_forest_classification/random_forest_classifier.ipynb similarity index 100% rename from machine_learning/Random Forest Classification/Random Forest Classifier.ipynb rename to machine_learning/random_forest_classification/random_forest_classifier.ipynb diff --git a/machine_learning/Random Forest Regression/Position_Salaries.csv b/machine_learning/random_forest_regression/Position_Salaries.csv similarity index 100% rename from machine_learning/Random Forest Regression/Position_Salaries.csv rename to machine_learning/random_forest_regression/Position_Salaries.csv diff --git a/machine_learning/Random Forest Regression/Random Forest Regression.ipynb b/machine_learning/random_forest_regression/random_forest_regression.ipynb similarity index 100% rename from machine_learning/Random Forest Regression/Random Forest Regression.ipynb rename to machine_learning/random_forest_regression/random_forest_regression.ipynb diff --git a/machine_learning/Random Forest Regression/random_forest_regression.py b/machine_learning/random_forest_regression/random_forest_regression.py similarity index 100% rename from machine_learning/Random Forest Regression/random_forest_regression.py rename to machine_learning/random_forest_regression/random_forest_regression.py diff --git a/maths/Hanoi.py b/maths/Hanoi.py deleted file mode 100644 index c7b435a8fe3e..000000000000 --- a/maths/Hanoi.py +++ /dev/null @@ -1,29 +0,0 @@ -"""Tower of Hanoi.""" - -# @author willx75 -# Tower of Hanoi recursion game algorithm is a game, it consists of three rods -# and a number of disks of different sizes, which can slide onto any rod - -import logging - -log = logging.getLogger() -logging.basicConfig(level=logging.DEBUG) - - -def Tower_Of_Hanoi(n, source, dest, by, movement): - """Tower of Hanoi - Move plates to different rods.""" - if n == 0: - return n - elif n == 1: - movement += 1 - # no print statement - # (you could make it an optional flag for printing logs) - logging.debug('Move the plate from', source, 'to', dest) - return movement - else: - - movement = movement + Tower_Of_Hanoi(n - 1, source, by, dest, 0) - logging.debug('Move the plate from', source, 'to', dest) - - movement = movement + 1 + Tower_Of_Hanoi(n - 1, by, dest, source, 0) - return movement diff --git a/maths/lucasSeries.py b/maths/lucas series.py similarity index 100% rename from maths/lucasSeries.py rename to maths/lucas series.py diff --git a/maths/tests/__init__.py b/maths/tests/__init__.py deleted file mode 100644 index 2c4a6048556c..000000000000 --- a/maths/tests/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from .. import fibonacci diff --git a/maths/tests/test_fibonacci.py b/maths/tests/test_fibonacci.py deleted file mode 100644 index 7d36c755e346..000000000000 --- a/maths/tests/test_fibonacci.py +++ /dev/null @@ -1,34 +0,0 @@ -""" -To run with slash: -1. run pip install slash (may need to install C++ builds from Visual Studio website) -2. In the command prompt navigate to your project folder -3. then type--> slash run -vv -k tags:fibonacci .. - -vv indicates the level of verbosity (how much stuff you want the test to spit out after running) - -k is a way to select the tests you want to run. This becomes much more important in large scale projects. -""" - -import slash -from .. import fibonacci - -default_fib = [0, 1, 1, 2, 3, 5, 8] - - -@slash.tag('fibonacci') -@slash.parametrize(('n', 'seq'), [(2, [0, 1]), (3, [0, 1, 1]), (9, [0, 1, 1, 2, 3, 5, 8, 13, 21])]) -def test_different_sequence_lengths(n, seq): - """Test output of varying fibonacci sequence lengths""" - iterative = fibonacci.fib_iterative(n) - formula = fibonacci.fib_formula(n) - assert iterative == seq - assert formula == seq - - -@slash.tag('fibonacci') -@slash.parametrize('n', [7.3, 7.8, 7.0]) -def test_float_input_iterative(n): - """Test when user enters a float value""" - iterative = fibonacci.fib_iterative(n) - formula = fibonacci.fib_formula(n) - assert iterative == default_fib - assert formula == default_fib - diff --git a/matrix/spiralPrint.py b/matrix/spiral_print.py similarity index 100% rename from matrix/spiralPrint.py rename to matrix/spiral_print.py diff --git a/neural_network/bpnn.py b/neural_network/back_propagation_neural_network.py similarity index 100% rename from neural_network/bpnn.py rename to neural_network/back_propagation_neural_network.py diff --git a/neural_network/fcn.ipynb b/neural_network/fully_connected_neural_network.ipynb similarity index 100% rename from neural_network/fcn.ipynb rename to neural_network/fully_connected_neural_network.ipynb diff --git a/other/game_of_life/game_o_life.py b/other/game_of_life.py similarity index 100% rename from other/game_of_life/game_o_life.py rename to other/game_of_life.py diff --git a/other/game_of_life/sample.gif b/other/game_of_life/sample.gif deleted file mode 100644 index 0bf2ae1f95e4604f6840d5804846924ed72ea966..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 228847 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

6v*)o0{zx~#g1z6Kd#b7nEf6f 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

*nO~I~nPKp^a3hFd*cJfSF14Cq5P7iJF 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 diff --git a/searches/test_interpolation_search.py b/searches/test_interpolation_search.py deleted file mode 100644 index 60bb3af22e0f..000000000000 --- a/searches/test_interpolation_search.py +++ /dev/null @@ -1,93 +0,0 @@ -import unittest -from interpolation_search import interpolation_search, interpolation_search_by_recursion - -class Test_interpolation_search(unittest.TestCase): - def setUp(self): - # un-sorted case - self.collection1 = [5,3,4,6,7] - self.item1 = 4 - # sorted case, result exists - self.collection2 = [10,30,40,45,50,66,77,93] - self.item2 = 66 - # sorted case, result doesn't exist - self.collection3 = [10,30,40,45,50,66,77,93] - self.item3 = 67 - # equal elements case, result exists - self.collection4 = [10,10,10,10,10] - self.item4 = 10 - # equal elements case, result doesn't exist - self.collection5 = [10,10,10,10,10] - self.item5 = 3 - # 1 element case, result exists - self.collection6 = [10] - self.item6 = 10 - # 1 element case, result doesn't exists - self.collection7 = [10] - self.item7 = 1 - - def tearDown(self): - pass - - def test_interpolation_search(self): - self.assertEqual(interpolation_search(self.collection1, self.item1), None) - - self.assertEqual(interpolation_search(self.collection2, self.item2), self.collection2.index(self.item2)) - - self.assertEqual(interpolation_search(self.collection3, self.item3), None) - - self.assertEqual(interpolation_search(self.collection4, self.item4), self.collection4.index(self.item4)) - - self.assertEqual(interpolation_search(self.collection5, self.item5), None) - - self.assertEqual(interpolation_search(self.collection6, self.item6), self.collection6.index(self.item6)) - - self.assertEqual(interpolation_search(self.collection7, self.item7), None) - - - -class Test_interpolation_search_by_recursion(unittest.TestCase): - def setUp(self): - # un-sorted case - self.collection1 = [5,3,4,6,7] - self.item1 = 4 - # sorted case, result exists - self.collection2 = [10,30,40,45,50,66,77,93] - self.item2 = 66 - # sorted case, result doesn't exist - self.collection3 = [10,30,40,45,50,66,77,93] - self.item3 = 67 - # equal elements case, result exists - self.collection4 = [10,10,10,10,10] - self.item4 = 10 - # equal elements case, result doesn't exist - self.collection5 = [10,10,10,10,10] - self.item5 = 3 - # 1 element case, result exists - self.collection6 = [10] - self.item6 = 10 - # 1 element case, result doesn't exists - self.collection7 = [10] - self.item7 = 1 - - def tearDown(self): - pass - - def test_interpolation_search_by_recursion(self): - self.assertEqual(interpolation_search_by_recursion(self.collection1, self.item1, 0, len(self.collection1)-1), None) - - self.assertEqual(interpolation_search_by_recursion(self.collection2, self.item2, 0, len(self.collection2)-1), self.collection2.index(self.item2)) - - self.assertEqual(interpolation_search_by_recursion(self.collection3, self.item3, 0, len(self.collection3)-1), None) - - self.assertEqual(interpolation_search_by_recursion(self.collection4, self.item4, 0, len(self.collection4)-1), self.collection4.index(self.item4)) - - self.assertEqual(interpolation_search_by_recursion(self.collection5, self.item5, 0, len(self.collection5)-1), None) - - self.assertEqual(interpolation_search_by_recursion(self.collection6, self.item6, 0, len(self.collection6)-1), self.collection6.index(self.item6)) - - self.assertEqual(interpolation_search_by_recursion(self.collection7, self.item7, 0, len(self.collection7)-1), None) - - - -if __name__ == '__main__': - unittest.main() diff --git a/searches/test_tabu_search.py b/searches/test_tabu_search.py deleted file mode 100644 index e6f73e6a9002..000000000000 --- a/searches/test_tabu_search.py +++ /dev/null @@ -1,46 +0,0 @@ -import unittest -import os -from tabu_search import generate_neighbours, generate_first_solution, find_neighborhood, tabu_search - -TEST_FILE = os.path.join(os.path.dirname(__file__), './tabu_test_data.txt') - -NEIGHBOURS_DICT = {'a': [['b', '20'], ['c', '18'], ['d', '22'], ['e', '26']], - 'c': [['a', '18'], ['b', '10'], ['d', '23'], ['e', '24']], - 'b': [['a', '20'], ['c', '10'], ['d', '11'], ['e', '12']], - 'e': [['a', '26'], ['b', '12'], ['c', '24'], ['d', '40']], - 'd': [['a', '22'], ['b', '11'], ['c', '23'], ['e', '40']]} - -FIRST_SOLUTION = ['a', 'c', 'b', 'd', 'e', 'a'] - -DISTANCE = 105 - -NEIGHBOURHOOD_OF_SOLUTIONS = [['a', 'e', 'b', 'd', 'c', 'a', 90], - ['a', 'c', 'd', 'b', 'e', 'a', 90], - ['a', 'd', 'b', 'c', 'e', 'a', 93], - ['a', 'c', 'b', 'e', 'd', 'a', 102], - ['a', 'c', 'e', 'd', 'b', 'a', 113], - ['a', 'b', 'c', 'd', 'e', 'a', 119]] - - -class TestClass(unittest.TestCase): - def test_generate_neighbours(self): - neighbours = generate_neighbours(TEST_FILE) - - self.assertEqual(NEIGHBOURS_DICT, neighbours) - - def test_generate_first_solutions(self): - first_solution, distance = generate_first_solution(TEST_FILE, NEIGHBOURS_DICT) - - self.assertEqual(FIRST_SOLUTION, first_solution) - self.assertEqual(DISTANCE, distance) - - def test_find_neighbours(self): - neighbour_of_solutions = find_neighborhood(FIRST_SOLUTION, NEIGHBOURS_DICT) - - self.assertEqual(NEIGHBOURHOOD_OF_SOLUTIONS, neighbour_of_solutions) - - def test_tabu_search(self): - best_sol, best_cost = tabu_search(FIRST_SOLUTION, DISTANCE, NEIGHBOURS_DICT, 4, 3) - - self.assertEqual(['a', 'd', 'b', 'e', 'c', 'a'], best_sol) - self.assertEqual(87, best_cost) diff --git a/simple_client/README.md b/simple_client/README.md deleted file mode 100644 index f51947f2105a..000000000000 --- a/simple_client/README.md +++ /dev/null @@ -1,6 +0,0 @@ -# simple client server - -#### Note: -- Run **`server.py`** first. -- Now, run **`client.py`**. -- verify the output. diff --git a/simple_client/client.py b/simple_client/client.py deleted file mode 100644 index db162f43c78a..000000000000 --- a/simple_client/client.py +++ /dev/null @@ -1,29 +0,0 @@ -# client.py - -import socket - -HOST, PORT = '127.0.0.1', 1400 - -s = socket.socket( - - socket.AF_INET, # ADDRESS FAMILIES - #Name Purpose - #AF_UNIX, AF_LOCAL Local communication - #AF_INET IPv4 Internet protocols - #AF_INET6 IPv6 Internet protocols - #AF_APPLETALK Appletalk - #AF_BLUETOOTH Bluetooth - - - socket.SOCK_STREAM # SOCKET TYPES - #Name Way of Interaction - #SOCK_STREAM TCP - #SOCK_DGRAM UDP -) -s.connect((HOST, PORT)) - -s.send('Hello World'.encode('ascii'))#in UDP use sendto() -data = s.recv(1024)#in UDP use recvfrom() - -s.close()#end the connection -print(repr(data.decode('ascii'))) diff --git a/simple_client/server.py b/simple_client/server.py deleted file mode 100644 index c23075608a90..000000000000 --- a/simple_client/server.py +++ /dev/null @@ -1,21 +0,0 @@ -# server.py - -import socket - -HOST, PORT = '127.0.0.1', 1400 - -s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)#refer to client.py -s.bind((HOST, PORT)) -s.listen(1)#listen for 1 connection - -conn, addr = s.accept()#start the actual data flow - -print('connected to:', addr) - -while 1: - data = conn.recv(1024).decode('ascii')#receive 1024 bytes and decode using ascii - if not data: - break - conn.send((data + ' [ addition by server ]').encode('ascii')) - -conn.close() diff --git a/sorts/sorting_graphs.png b/sorts/sorting_graphs.png deleted file mode 100644 index 628245f3eb398f3617a994de89154630e1287402..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 10362 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 diff --git a/sorts/tests.py b/sorts/tests.py deleted file mode 100644 index ec8c8361912f..000000000000 --- a/sorts/tests.py +++ /dev/null @@ -1,76 +0,0 @@ -"""Test Sort Algorithms for Errors.""" - -from bogo_sort import bogo_sort -from bubble_sort import bubble_sort -from bucket_sort import bucket_sort -from cocktail_shaker_sort import cocktail_shaker_sort -from comb_sort import comb_sort -from counting_sort import counting_sort -from cycle_sort import cycle_sort -from gnome_sort import gnome_sort -from heap_sort import heap_sort -from insertion_sort import insertion_sort -from merge_sort_fastest import merge_sort as merge_sort_fastest -from merge_sort import merge_sort -from pancake_sort import pancake_sort -from quick_sort_3_partition import quick_sort_3partition -from quick_sort import quick_sort -from radix_sort import radix_sort -from random_pivot_quick_sort import quick_sort_random -from selection_sort import selection_sort -from shell_sort import shell_sort -from tim_sort import tim_sort -from topological_sort import topological_sort -from tree_sort import tree_sort -from wiggle_sort import wiggle_sort - - -TEST_CASES = [ - {'input': [8, 7, 6, 5, 4, 3, -2, -5], 'expected': [-5, -2, 3, 4, 5, 6, 7, 8]}, - {'input': [-5, -2, 3, 4, 5, 6, 7, 8], 'expected': [-5, -2, 3, 4, 5, 6, 7, 8]}, - {'input': [5, 6, 1, 4, 0, 1, -2, -5, 3, 7], 'expected': [-5, -2, 0, 1, 1, 3, 4, 5, 6, 7]}, - {'input': [2, -2], 'expected': [-2, 2]}, - {'input': [1], 'expected': [1]}, - {'input': [], 'expected': []}, -] - -''' - TODO: - - Fix some broken tests in particular cases (as [] for example), - - Unify the input format: should always be function(input_collection) (no additional args) - - Unify the output format: should always be a collection instead of - updating input elements and returning None - - Rewrite some algorithms in function format (in case there is no function definition) -''' - -TEST_FUNCTIONS = [ - bogo_sort, - bubble_sort, - bucket_sort, - cocktail_shaker_sort, - comb_sort, - counting_sort, - cycle_sort, - gnome_sort, - heap_sort, - insertion_sort, - merge_sort_fastest, - merge_sort, - pancake_sort, - quick_sort_3partition, - quick_sort, - radix_sort, - quick_sort_random, - selection_sort, - shell_sort, - tim_sort, - topological_sort, - tree_sort, - wiggle_sort, -] - - -for function in TEST_FUNCTIONS: - for case in TEST_CASES: - result = function(case['input']) - assert result == case['expected'], 'Executed function: {}, {} != {}'.format(function.__name__, result, case['expected']) From f30f8493e6ef91f9d95ae88b0d2b59ef1f102681 Mon Sep 17 00:00:00 2001 From: Anup Kumar Panwar <1anuppanwar@gmail.com> Date: Sat, 6 Jul 2019 11:12:26 +0530 Subject: [PATCH 0078/1071] Updated README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 9edddb60552a..a609dc077a77 100644 --- a/README.md +++ b/README.md @@ -129,7 +129,7 @@ We're on [Gitter](https://gitter.im/TheAlgorithms)! Please join us. ## Compression -- [Peak Signal To Noise Ratio](./compression_analysis/peak_signal_to_noise_ratio.py) +- [Peak Signal To Noise Ratio](./compression/peak_signal_to_noise_ratio.py) - [Huffman](./compression/huffman.py) ## Graphs From b23834062c5a78e2aeda70336eb66c07c7209181 Mon Sep 17 00:00:00 2001 From: Anup Kumar Panwar <1anuppanwar@gmail.com> Date: Sat, 6 Jul 2019 11:14:55 +0530 Subject: [PATCH 0079/1071] refactor --- DIRECTORY.py | 50 -------------------------------------------------- 1 file changed, 50 deletions(-) delete mode 100644 DIRECTORY.py diff --git a/DIRECTORY.py b/DIRECTORY.py deleted file mode 100644 index 434b2a3dd3ed..000000000000 --- a/DIRECTORY.py +++ /dev/null @@ -1,50 +0,0 @@ -import os - -def getListOfFiles(dirName): - # create a list of file and sub directories - # names in the given directory - listOfFile = os.listdir(dirName) - allFiles = list() - # Iterate over all the entries - for entry in listOfFile: - # if entry == listOfFile[len(listOfFile)-1]: - # continue - if entry=='.git': - continue - # Create full path - fullPath = os.path.join(dirName, entry) - entryName = entry.split('_') - # print(entryName) - ffname = '' - try: - for word in entryName: - temp = word[0].upper() + word[1:] - ffname = ffname + ' ' + temp - # print(temp) - final_fn = ffname.replace('.py', '') - final_fn = final_fn.strip() - print('* ['+final_fn+']('+fullPath+')') - # pass - except: - pass - # If entry is a directory then get the list of files in this directory - if os.path.isdir(fullPath): - print ('\n## '+entry) - filesInCurrDir = getListOfFiles(fullPath) - for file in filesInCurrDir: - fileName = file.split('/') - fileName = fileName[len(fileName)-1] - - # print (fileName) - allFiles = allFiles + filesInCurrDir - else: - allFiles.append(fullPath) - - return allFiles - - -dirName = './'; - -# Get the list of all files in directory tree at given path -listOfFiles = getListOfFiles(dirName) -# print (listOfFiles) \ No newline at end of file From 1161393b39516f97b359a14dc9043d298bc897be Mon Sep 17 00:00:00 2001 From: Anup Kumar Panwar <1anuppanwar@gmail.com> Date: Sat, 6 Jul 2019 11:21:08 +0530 Subject: [PATCH 0080/1071] updated CONTRIBUTING.md --- CONTRIBUTING.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index ac632574e870..03de387a8acd 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -107,6 +107,8 @@ We want your work to be readable by others; therefore, we encourage you to note - If you have modified/added documentation work, make sure your language is concise and contains no grammar mistake. +- Update the README file if you have added any new algorithm. Only entry corresponding to the algorithm is to be made, not need to add sample data, test files or solutions to problems like Project Euler, in the README. + - Add a corresponding explanation to [Algorithms-Explanation](https://github.com/TheAlgorithms/Algorithms-Explanation) (Optional but recommended). - Most importantly, From aa663037f6f5d09b9cb47baa34c61aa1d7d2cbe1 Mon Sep 17 00:00:00 2001 From: Anup Kumar Panwar <1anuppanwar@gmail.com> Date: Sat, 6 Jul 2019 13:47:56 +0530 Subject: [PATCH 0081/1071] Create FUNDING.yml --- .github/FUNDING.yml | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 .github/FUNDING.yml diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 000000000000..f07cea8a90f8 --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1,12 @@ +# These are supported funding model platforms + +github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] +patreon: # Replace with a single Patreon username +open_collective: # Replace with a single Open Collective username +ko_fi: # Replace with a single Ko-fi username +tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel +community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry +liberapay: TheAlgorithms +issuehunt: # Replace with a single IssueHunt username +otechie: # Replace with a single Otechie username +custom: ['http://paypal.me/TheAlgorithms/1000'] From 1951b4ca79665ea34253cd845540a34cd656d2f1 Mon Sep 17 00:00:00 2001 From: Anup Kumar Panwar <1anuppanwar@gmail.com> Date: Sat, 6 Jul 2019 14:04:27 +0530 Subject: [PATCH 0082/1071] Update FUNDING.yml --- .github/FUNDING.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml index f07cea8a90f8..514c9327e231 100644 --- a/.github/FUNDING.yml +++ b/.github/FUNDING.yml @@ -9,4 +9,4 @@ community_bridge: # Replace with a single Community Bridge project-name e.g., cl liberapay: TheAlgorithms issuehunt: # Replace with a single IssueHunt username otechie: # Replace with a single Otechie username -custom: ['http://paypal.me/TheAlgorithms/1000'] +custom: ['http://paypal.me/TheAlgorithms/1000', 'https://donorbox.org/thealgorithms'] From cc4cf3ece7e10f15435365ee331c3530f6c777f5 Mon Sep 17 00:00:00 2001 From: Erfan Alimohammadi Date: Sat, 6 Jul 2019 17:43:50 +0430 Subject: [PATCH 0083/1071] Generate all subsequences using backtracking (#961) * Add all_subsequences to backtracking directory --- backtracking/all_subsequences.py | 42 ++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 backtracking/all_subsequences.py diff --git a/backtracking/all_subsequences.py b/backtracking/all_subsequences.py new file mode 100644 index 000000000000..d868377234a8 --- /dev/null +++ b/backtracking/all_subsequences.py @@ -0,0 +1,42 @@ +''' + In this problem, we want to determine all possible subsequences + of the given sequence. We use backtracking to solve this problem. + + Time complexity: O(2^n), + where n denotes the length of the given sequence. +''' + + +def generate_all_subsequences(sequence): + create_state_space_tree(sequence, [], 0) + + +def create_state_space_tree(sequence, current_subsequence, index): + ''' + Creates a state space tree to iterate through each branch using DFS. + We know that each state has exactly two children. + It terminates when it reaches the end of the given sequence. + ''' + + if index == len(sequence): + print(current_subsequence) + return + + create_state_space_tree(sequence, current_subsequence, index + 1) + current_subsequence.append(sequence[index]) + create_state_space_tree(sequence, current_subsequence, index + 1) + current_subsequence.pop() + + +''' +remove the comment to take an input from the user + +print("Enter the elements") +sequence = list(map(int, input().split())) +''' + +sequence = [3, 1, 2, 4] +generate_all_subsequences(sequence) + +sequence = ["A", "B", "C"] +generate_all_subsequences(sequence) From 839160f83a46413b94ba9817edc2ec37bcad36fc Mon Sep 17 00:00:00 2001 From: Erfan Alimohammadi Date: Sat, 6 Jul 2019 17:49:36 +0430 Subject: [PATCH 0084/1071] Generate all permutations of a sequence, using backtracking (#962) * Fix typo * Add all_permutations algorithm to backtracking directory --- backtracking/all_permutations.py | 45 ++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100644 backtracking/all_permutations.py diff --git a/backtracking/all_permutations.py b/backtracking/all_permutations.py new file mode 100644 index 000000000000..8c332970ba53 --- /dev/null +++ b/backtracking/all_permutations.py @@ -0,0 +1,45 @@ +''' + In this problem, we want to determine all possible permutations + of the given sequence. We use backtracking to solve this problem. + + Time complexity: O(n!), + where n denotes the length of the given sequence. +''' + + +def generate_all_permutations(sequence): + create_state_space_tree(sequence, [], 0, [0 for i in range(len(sequence))]) + + +def create_state_space_tree(sequence, current_sequence, index, index_used): + ''' + Creates a state space tree to iterate through each branch using DFS. + We know that each state has exactly len(sequence) - index children. + It terminates when it reaches the end of the given sequence. + ''' + + if index == len(sequence): + print(current_sequence) + return + + for i in range(len(sequence)): + if not index_used[i]: + current_sequence.append(sequence[i]) + index_used[i] = True + create_state_space_tree(sequence, current_sequence, index + 1, index_used) + current_sequence.pop() + index_used[i] = False + + +''' +remove the comment to take an input from the user + +print("Enter the elements") +sequence = list(map(int, input().split())) +''' + +sequence = [3, 1, 2, 4] +generate_all_permutations(sequence) + +sequence = ["A", "B", "C"] +generate_all_permutations(sequence) From 781b7f86e720b9c5164047e46f2dedd807b9165b Mon Sep 17 00:00:00 2001 From: Erfan Alimohammadi Date: Sat, 6 Jul 2019 19:02:06 +0430 Subject: [PATCH 0085/1071] Fix readme and duplicate (#967) * Fix typo * Add all_permutations algorithm to backtracking directory * Update backtracking and D&C algorithms in README Update backtracking and divide_and_conquer algorithms in README * Remove the duplicated file --- README.md | 3 +- divide_and_conquer/max_sub_array_sum.py | 72 ------------------------- 2 files changed, 2 insertions(+), 73 deletions(-) delete mode 100644 divide_and_conquer/max_sub_array_sum.py diff --git a/README.md b/README.md index a609dc077a77..a28475791432 100644 --- a/README.md +++ b/README.md @@ -45,6 +45,8 @@ We're on [Gitter](https://gitter.im/TheAlgorithms)! Please join us. - [N Queens](./backtracking/n_queens.py) - [Sum Of Subsets](./backtracking/sum_of_subsets.py) +- [All Subsequences](./backtracking/all_subsequences.py) +- [All Permutations](./backtracking/all_permutations.py) ## Ciphers @@ -220,7 +222,6 @@ We're on [Gitter](https://gitter.im/TheAlgorithms)! Please join us. ## Divide And Conquer - [Max Subarray Sum](./divide_and_conquer/max_subarray_sum.py) -- [Max Sub Array Sum](./divide_and_conquer/max_sub_array_sum.py) - [Closest Pair Of Points](./divide_and_conquer/closest_pair_of_points.py) ## Strings diff --git a/divide_and_conquer/max_sub_array_sum.py b/divide_and_conquer/max_sub_array_sum.py deleted file mode 100644 index 531a45abca6f..000000000000 --- a/divide_and_conquer/max_sub_array_sum.py +++ /dev/null @@ -1,72 +0,0 @@ -""" -Given a array of length n, max_sub_array_sum() finds the maximum of sum of contiguous sub-array using divide and conquer method. - -Time complexity : O(n log n) - -Ref : INTRODUCTION TO ALGORITHMS THIRD EDITION (section : 4, sub-section : 4.1, page : 70) - -""" - - -def max_sum_from_start(array): - """ This function finds the maximum contiguous sum of array from 0 index - - Parameters : - array (list[int]) : given array - - Returns : - max_sum (int) : maximum contiguous sum of array from 0 index - - """ - array_sum = 0 - max_sum = float("-inf") - for num in array: - array_sum += num - if array_sum > max_sum: - max_sum = array_sum - return max_sum - - -def max_cross_array_sum(array, left, mid, right): - """ This function finds the maximum contiguous sum of left and right arrays - - Parameters : - array, left, mid, right (list[int], int, int, int) - - Returns : - (int) : maximum of sum of contiguous sum of left and right arrays - - """ - - max_sum_of_left = max_sum_from_start(array[left:mid+1][::-1]) - max_sum_of_right = max_sum_from_start(array[mid+1: right+1]) - return max_sum_of_left + max_sum_of_right - - -def max_sub_array_sum(array, left, right): - """ This function finds the maximum of sum of contiguous sub-array using divide and conquer method - - Parameters : - array, left, right (list[int], int, int) : given array, current left index and current right index - - Returns : - int : maximum of sum of contiguous sub-array - - """ - - # base case: array has only one element - if left == right: - return array[right] - - # Recursion - mid = (left + right) // 2 - left_half_sum = max_sub_array_sum(array, left, mid) - right_half_sum = max_sub_array_sum(array, mid + 1, right) - cross_sum = max_cross_array_sum(array, left, mid, right) - return max(left_half_sum, right_half_sum, cross_sum) - - -array = [-2, -5, 6, -2, -3, 1, 5, -6] -array_length = len(array) -print("Maximum sum of contiguous subarray:", max_sub_array_sum(array, 0, array_length - 1)) - From 69bed590368a10479a9ad225402aa540628a0457 Mon Sep 17 00:00:00 2001 From: Erfan Alimohammadi Date: Sat, 6 Jul 2019 20:01:52 +0430 Subject: [PATCH 0086/1071] Fix backtrack time complexity (#965) * Update backtracking/all_permutations.py --- backtracking/all_permutations.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backtracking/all_permutations.py b/backtracking/all_permutations.py index 8c332970ba53..299b708fef4e 100644 --- a/backtracking/all_permutations.py +++ b/backtracking/all_permutations.py @@ -2,7 +2,7 @@ In this problem, we want to determine all possible permutations of the given sequence. We use backtracking to solve this problem. - Time complexity: O(n!), + Time complexity: O(n! * n), where n denotes the length of the given sequence. ''' From 26df2aab1ee79de5a4b288a64ad9c118bfccab73 Mon Sep 17 00:00:00 2001 From: PatOnTheBack <51241310+PatOnTheBack@users.noreply.github.com> Date: Sat, 6 Jul 2019 11:35:12 -0400 Subject: [PATCH 0087/1071] Removed Unused `import sys` (#922) I removed `import sys` because it is not used in the program. This addresses a [recommendation from lgtm](https://lgtm.com/projects/g/TheAlgorithms/Python/snapshot/66c4afbd0f28f9989f35ddbeb5c9263390c5d192/files/ciphers/caesar_cipher.py?sort=name&dir=ASC&mode=heatmap) --- ciphers/caesar_cipher.py | 1 - 1 file changed, 1 deletion(-) diff --git a/ciphers/caesar_cipher.py b/ciphers/caesar_cipher.py index 39c069c95a7c..e22f19b4851d 100644 --- a/ciphers/caesar_cipher.py +++ b/ciphers/caesar_cipher.py @@ -1,4 +1,3 @@ -import sys def encrypt(strng, key): encrypted = '' for x in strng: From 4ff2a9dd4e1a517cb0526ff51233bb6f1fc3fc8d Mon Sep 17 00:00:00 2001 From: Aditi Agarwal <31546143+aditiagarwal34550@users.noreply.github.com> Date: Sat, 6 Jul 2019 21:59:58 -0700 Subject: [PATCH 0088/1071] minimax (#947) * minimax.py minimax algorithm is used for game like tic tac toe. It traces the path and selects the optimal move. * minimax.py Minimax is used in decision making and game theory to find the optimal move for a player, when your opponent also plays optimally. It is widely used in games like Tic-Tac-Toe, Chess. * Delete minimax.py * Update minimax.py * Minimax is a backtracking algorithm that is used in game theory to find the optimal move for a player, assuming that your opponent also plays optimally --- backtracking/minimax.py | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 backtracking/minimax.py diff --git a/backtracking/minimax.py b/backtracking/minimax.py new file mode 100644 index 000000000000..5168306e71fc --- /dev/null +++ b/backtracking/minimax.py @@ -0,0 +1,28 @@ +import math + +''' Minimax helps to achieve maximum score in a game by checking all possible moves + depth is current depth in game tree. + nodeIndex is index of current node in scores[]. + if move is of maximizer return true else false + leaves of game tree is stored in scores[] + height is maximum height of Game tree +''' + +def minimax (Depth, nodeIndex, isMax, scores, height): + + if Depth == height: + return scores[nodeIndex] + + if isMax: + return (max(minimax(Depth + 1, nodeIndex * 2, False, scores, height), + minimax(Depth + 1, nodeIndex * 2 + 1, False, scores, height))) + return (min(minimax(Depth + 1, nodeIndex * 2, True, scores, height), + minimax(Depth + 1, nodeIndex * 2 + 1, True, scores, height))) + +if __name__ == "__main__": + + scores = [90, 23, 6, 33, 21, 65, 123, 34423] + height = math.log(len(scores), 2) + + print("Optimal value : ", end = "") + print(minimax(0, 0, True, scores, height)) From 95324927288135c740688d16db34381298139d66 Mon Sep 17 00:00:00 2001 From: Shahabaldin Mohammadi <45038855+stevelex-elex@users.noreply.github.com> Date: Sun, 7 Jul 2019 11:19:15 +0430 Subject: [PATCH 0089/1071] added enigma machine algorithm (#932) --- hashes/enigma_machine.py | 61 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 61 insertions(+) create mode 100644 hashes/enigma_machine.py diff --git a/hashes/enigma_machine.py b/hashes/enigma_machine.py new file mode 100644 index 000000000000..bd410c5cb21d --- /dev/null +++ b/hashes/enigma_machine.py @@ -0,0 +1,61 @@ +from __future__ import print_function + +alphabets = [chr(i) for i in range(32, 126)] +gear_one = [i for i in range(len(alphabets))] +gear_two = [i for i in range(len(alphabets))] +gear_three = [i for i in range(len(alphabets))] +reflector = [i for i in reversed(range(len(alphabets)))] +code = [] +gear_one_pos = gear_two_pos = gear_three_pos = 0 + + +def rotator(): + global gear_one_pos + global gear_two_pos + global gear_three_pos + i = gear_one[0] + gear_one.append(i) + del gear_one[0] + gear_one_pos += 1 + if gear_one_pos % int(len(alphabets)) == 0: + i = gear_two[0] + gear_two.append(i) + del gear_two[0] + gear_two_pos += 1 + if gear_two_pos % int(len(alphabets)) == 0: + i = gear_three[0] + gear_three.append(i) + del gear_three[0] + gear_three_pos += 1 + + +def engine(input_character): + target = alphabets.index(input_character) + target = gear_one[target] + target = gear_two[target] + target = gear_three[target] + target = reflector[target] + target = gear_three.index(target) + target = gear_two.index(target) + target = gear_one.index(target) + code.append(alphabets[target]) + rotator() + + +if __name__ == '__main__': + decode = input("Type your message:\n") + decode = list(decode) + while True: + try: + token = int(input("Please set token:(must be only digits)\n")) + break + except Exception as error: + print(error) + for i in range(token): + rotator() + for i in decode: + engine(i) + print("\n" + "".join(code)) + print( + f"\nYour Token is {token} please write it down.\nIf you want to decode " + f"this message again you should input same digits as token!") From 234b0a77c4d6186c9f0326233af84a8f75b35b6e Mon Sep 17 00:00:00 2001 From: Hector S Date: Sun, 7 Jul 2019 11:17:38 -0400 Subject: [PATCH 0090/1071] Simplied password_generator.py (#968) * Added print function into matrix_multiplication_addition.py and removed blank space in data_structures/binary tree directory * Removed .vs/ folder per #893 * Rename matrix_multiplication_addition.py to matrix_operation.py * Added main() function and simplified password generation. * Modified password_generator.py file according to suggestions in #968 --- other/password_generator.py | 66 +++++++++++++++++++++++-------------- 1 file changed, 42 insertions(+), 24 deletions(-) diff --git a/other/password_generator.py b/other/password_generator.py index 8916079fc758..fd0701041240 100644 --- a/other/password_generator.py +++ b/other/password_generator.py @@ -1,35 +1,53 @@ +"""Password generator allows you to generate a random password of length N.""" from __future__ import print_function -import string -import random - -letters = [letter for letter in string.ascii_letters] -digits = [digit for digit in string.digits] -symbols = [symbol for symbol in string.punctuation] -chars = letters + digits + symbols -random.shuffle(chars) - -min_length = 8 -max_length = 16 -password = ''.join(random.choice(chars) for x in range(random.randint(min_length, max_length))) -print('Password: ' + password) -print('[ If you are thinking of using this passsword, You better save it. ]') - - -# ALTERNATIVE METHODS +from random import choice +from string import ascii_letters, digits, punctuation + + +def password_generator(length=8): + """ + >>> len(password_generator()) + 8 + >>> len(password_generator(length=16)) + 16 + >>> len(password_generator(257)) + 257 + >>> len(password_generator(length=0)) + 0 + >>> len(password_generator(-1)) + 0 + """ + chars = tuple(ascii_letters) + tuple(digits) + tuple(punctuation) + return ''.join(choice(chars) for x in range(length)) + + +# ALTERNATIVE METHODS # ctbi= characters that must be in password -# i= how many letters or characters the password length will be -def password_generator(ctbi, i): - # Password generator = full boot with random_number, random_letters, and random_character FUNCTIONS - pass # Put your code here... +# i= how many letters or characters the password length will be +def alternative_password_generator(ctbi, i): + # Password generator = full boot with random_number, random_letters, and + # random_character FUNCTIONS + pass # Put your code here... def random_number(ctbi, i): - pass # Put your code here... + pass # Put your code here... def random_letters(ctbi, i): - pass # Put your code here... + pass # Put your code here... def random_characters(ctbi, i): - pass # Put your code here... + pass # Put your code here... + + +def main(): + length = int( + input('Please indicate the max length of your password: ').strip()) + print('Password generated:', password_generator(length)) + print('[If you are thinking of using this passsword, You better save it.]') + + +if __name__ == '__main__': + main() From 2b365284c80bbc2c7e5676481ed56308a5b1d888 Mon Sep 17 00:00:00 2001 From: PatOnTheBack <51241310+PatOnTheBack@users.noreply.github.com> Date: Sun, 7 Jul 2019 11:45:42 -0400 Subject: [PATCH 0091/1071] Removed Unnecessary Assignment for 'error' Var (#920) `error = abs(f(a))` was declared on line 24 and line 32. It is unnecessary to have in both places. I removed the second instance since it wastes resources to keep redefining the variable inside the for loop. This fixes an [issue found by lgtm](https://lgtm.com/projects/g/TheAlgorithms/Python/snapshot/66c4afbd0f28f9989f35ddbeb5c9263390c5d192/files/maths/newton_raphson.py?sort=name&dir=ASC&mode=heatmap) --- maths/newton_raphson.py | 1 - 1 file changed, 1 deletion(-) diff --git a/maths/newton_raphson.py b/maths/newton_raphson.py index cc6c92734fd4..d89f264acdd8 100644 --- a/maths/newton_raphson.py +++ b/maths/newton_raphson.py @@ -29,7 +29,6 @@ def newton_raphson(f, x0=0, maxiter=100, step=0.0001, maxerror=1e-6,logsteps=Fal a = a - f(a)/f1(a) #Calculate the next estimate if logsteps: steps.append(a) - error = abs(f(a)) if error < maxerror: break else: From b7f13d991cccd370d8ff5b27c1bdc36237a13473 Mon Sep 17 00:00:00 2001 From: cclauss Date: Mon, 8 Jul 2019 17:27:51 +0200 Subject: [PATCH 0092/1071] Travis CI: Run black, doctest, flake8, mypy, and pytest (#964) * Travis CI: Add type checking with mypy * Create requirements.txt * script: mypy --ignore-missing-stubs=cv2,numpy . * Delete requirements.txt * script: mypy --ignore-missing-imports . * Run doctests * Disable doctest -v other/detecting_english_programmatically.py * Pytest * No | * pytest || true * Run black doctest flake8 mypy pytest * after_success: Build Directory.md * Typo in filename: Dictionary.txt --> dictionary.txt' Discovered via doctest run in #964 * python -m doctest -v * pip install black flake8 mypy pytest * pytest --doctest-glob='*.py' * pytest --doctest-modules * pytest --doctest-modules ./sorts * pytest --doctest-modules ./ciphers ./other ./searches ./sorts ./strings || true * if __name__ == "__main__": * if __name__ == "__main__": * if __name__ == '__main__': * if __name__ == '__main__': * if __name__ == '__main__': * Create requirements.txt * Update requirements.txt * if __name__ == "__main__": * Lose the doctests * if __name__ == '__main__': * Remove print-a-tuple * doctest: Added missing spaces * Update tabu_search.py * The >>> are not doctests so change to >>) * Travis CI: Run black, doctest, flake8, mypy, and pytest * Link to the separate DIRECTORY.md file * Update README.md --- .travis.yml | 13 +- README.md | 339 +------------------- ciphers/Atbash.py | 4 +- ciphers/caesar_cipher.py | 5 +- other/detecting_english_programmatically.py | 2 +- other/sierpinski_triangle.py | 17 +- other/tower_of_hanoi.py | 4 +- requirements.txt | 6 + searches/tabu_search.py | 4 +- sorts/Bitonic_Sort.py | 25 +- sorts/bubble_sort.py | 2 +- 11 files changed, 56 insertions(+), 365 deletions(-) create mode 100644 requirements.txt diff --git a/.travis.yml b/.travis.yml index 8676e5127334..e67bd431a7c1 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,5 +1,14 @@ language: python dist: xenial # required for Python >= 3.7 python: 3.7 -install: pip install flake8 -script: flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics +cache: pip +install: pip install -r requirements.txt +before_script: + - black --check . || true + - flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics +script: + - mypy --ignore-missing-imports . + - pytest --doctest-modules ./ciphers ./other ./searches ./sorts ./strings +after_success: + - python ./~script.py + - cat DIRECTORY.md diff --git a/README.md b/README.md index a28475791432..30eccd361673 100644 --- a/README.md +++ b/README.md @@ -12,12 +12,12 @@ These implementations are for learning purposes. They may be less efficient than Anup Kumar Panwar   [[Gmail](mailto:1anuppanwar@gmail.com?Subject=The%20Algorithms%20-%20Python) -  [Gihub](https://github.com/anupkumarpanwar) +  [GitHub](https://github.com/anupkumarpanwar)   [LinkedIn](https://www.linkedin.com/in/anupkumarpanwar/)] Chetan Kaushik   [[Gmail](mailto:dynamitechetan@gmail.com?Subject=The%20Algorithms%20-%20Python) -  [Gihub](https://github.com/dynamitechetan) +  [GitHub](https://github.com/dynamitechetan)   [LinkedIn](https://www.linkedin.com/in/chetankaushik/)] ## Contribution Guidelines @@ -28,337 +28,6 @@ Read our [Contribution Guidelines](CONTRIBUTING.md) before you contribute. We're on [Gitter](https://gitter.im/TheAlgorithms)! Please join us. -# Algorithms +## Algorithms -## Hashes - -- [Md5](./hashes/md5.py) -- [Chaos Machine](./hashes/chaos_machine.py) -- [Sha1](./hashes/sha1.py) - -## File Transfer Protocol - -- [Ftp Client Server](./file_transfer_protocol/ftp_client_server.py) -- [Ftp Send Receive](./file_transfer_protocol/ftp_send_receive.py) - -## Backtracking - -- [N Queens](./backtracking/n_queens.py) -- [Sum Of Subsets](./backtracking/sum_of_subsets.py) -- [All Subsequences](./backtracking/all_subsequences.py) -- [All Permutations](./backtracking/all_permutations.py) - -## Ciphers - -- [Transposition Cipher](./ciphers/transposition_cipher.py) -- [Atbash](./ciphers/Atbash.py) -- [Rot13](./ciphers/rot13.py) -- [Rabin Miller](./ciphers/rabin_miller.py) -- [Transposition Cipher Encrypt Decrypt File](./ciphers/transposition_cipher_encrypt_decrypt_file.py) -- [Affine Cipher](./ciphers/affine_cipher.py) -- [Trafid Cipher](./ciphers/trafid_cipher.py) -- [Base16](./ciphers/base16.py) -- [Elgamal Key Generator](./ciphers/elgamal_key_generator.py) -- [Rsa Cipher](./ciphers/rsa_cipher.py) -- [Prehistoric Men.txt](./ciphers/prehistoric_men.txt) -- [Vigenere Cipher](./ciphers/vigenere_cipher.py) -- [Xor Cipher](./ciphers/xor_cipher.py) -- [Brute Force Caesar Cipher](./ciphers/brute_force_caesar_cipher.py) -- [Rsa Key Generator](./ciphers/rsa_key_generator.py) -- [Simple Substitution Cipher](./ciphers/simple_substitution_cipher.py) -- [Playfair Cipher](./ciphers/playfair_cipher.py) -- [Morse Code Implementation](./ciphers/morse_Code_implementation.py) -- [Base32](./ciphers/base32.py) -- [Base85](./ciphers/base85.py) -- [Base64 Cipher](./ciphers/base64_cipher.py) -- [Onepad Cipher](./ciphers/onepad_cipher.py) -- [Caesar Cipher](./ciphers/caesar_cipher.py) -- [Hill Cipher](./ciphers/hill_cipher.py) -- [Cryptomath Module](./ciphers/cryptomath_module.py) - -## Arithmetic Analysis - -- [Bisection](./arithmetic_analysis/bisection.py) -- [Newton Method](./arithmetic_analysis/newton_method.py) -- [Newton Raphson Method](./arithmetic_analysis/newton_raphson_method.py) -- [Intersection](./arithmetic_analysis/intersection.py) -- [Lu Decomposition](./arithmetic_analysis/lu_decomposition.py) - -## Boolean Algebra - -- [Quine Mc Cluskey](./boolean_algebra/quine_mc_cluskey.py) - -## Traversals - -- [Binary Tree Traversals](./traversals/binary_tree_traversals.py) - -## Maths - -- [Average](./maths/average.py) -- [Abs Max](./maths/abs_Max.py) -- [Average Median](./maths/average_median.py) -- [Trapezoidal Rule](./maths/trapezoidal_rule.py) -- [Prime Check](./maths/Prime_Check.py) -- [Modular Exponential](./maths/modular_exponential.py) -- [Newton Raphson](./maths/newton_raphson.py) -- [Factorial Recursive](./maths/factorial_recursive.py) -- [Extended Euclidean Algorithm](./maths/extended_euclidean_algorithm.py) -- [Greater Common Divisor](./maths/greater_common_divisor.py) -- [Fibonacci](./maths/fibonacci.py) -- [Find Lcm](./maths/find_lcm.py) -- [Find Max](./maths/Find_Max.py) -- [Fermat Little Theorem](./maths/fermat_little_theorem.py) -- [Factorial Python](./maths/factorial_python.py) -- [Fibonacci Sequence Recursion](./maths/fibonacci_sequence_recursion.py) -- [Sieve Of Eratosthenes](./maths/sieve_of_eratosthenes.py) -- [Abs Min](./maths/abs_Min.py) -- [Lucas Series](./maths/lucasSeries.py) -- [Segmented Sieve](./maths/segmented_sieve.py) -- [Find Min](./maths/Find_Min.py) -- [Abs](./maths/abs.py) -- [Simpson Rule](./maths/simpson_rule.py) -- [Basic Maths](./maths/basic_maths.py) -- [3n+1](./maths/3n+1.py) -- [Binary Exponentiation](./maths/Binary_Exponentiation.py) - -## Digital Image Processing - -- ## Filters - - - [Median Filter](./digital_image_processing/filters/median_filter.py) - - [Gaussian Filter](./digital_image_processing/filters/gaussian_filter.py) - - -## Compression - -- [Peak Signal To Noise Ratio](./compression/peak_signal_to_noise_ratio.py) -- [Huffman](./compression/huffman.py) - -## Graphs - -- [BFS Shortest Path](./graphs/bfs_shortest_path.py) -- [Directed And Undirected (Weighted) Graph](<./graphs/Directed_and_Undirected_(Weighted)_Graph.py>) -- [Minimum Spanning Tree Prims](./graphs/minimum_spanning_tree_prims.py) -- [Graph Matrix](./graphs/graph_matrix.py) -- [Basic Graphs](./graphs/basic_graphs.py) -- [Dijkstra 2](./graphs/dijkstra_2.py) -- [Tarjans Strongly Connected Components](./graphs/tarjans_scc.py) -- [Check Bipartite Graph BFS](./graphs/check_bipartite_graph_bfs.py) -- [Depth First Search](./graphs/depth_first_search.py) -- [Kahns Algorithm Long](./graphs/kahns_algorithm_long.py) -- [Breadth First Search](./graphs/breadth_first_search.py) -- [Dijkstra](./graphs/dijkstra.py) -- [Articulation Points](./graphs/articulation_points.py) -- [Bellman Ford](./graphs/bellman_ford.py) -- [Check Bipartite Graph Dfs](./graphs/check_bipartite_graph_dfs.py) -- [Strongly Connected Components Kosaraju](./graphs/scc_kosaraju.py) -- [Multi Hueristic Astar](./graphs/multi_hueristic_astar.py) -- [Page Rank](./graphs/page_rank.py) -- [Eulerian Path And Circuit For Undirected Graph](./graphs/Eulerian_path_and_circuit_for_undirected_graph.py) -- [Edmonds Karp Multiple Source And Sink](./graphs/edmonds_karp_multiple_source_and_sink.py) -- [Floyd Warshall](./graphs/floyd_warshall.py) -- [Minimum Spanning Tree Kruskal](./graphs/minimum_spanning_tree_kruskal.py) -- [Prim](./graphs/prim.py) -- [Kahns Algorithm Topo](./graphs/kahns_algorithm_topo.py) -- [BFS](./graphs/BFS.py) -- [Finding Bridges](./graphs/finding_bridges.py) -- [Graph List](./graphs/graph_list.py) -- [Dijkstra Algorithm](./graphs/dijkstra_algorithm.py) -- [A Star](./graphs/a_star.py) -- [Even Tree](./graphs/even_tree.py) -- [DFS](./graphs/DFS.py) - -## Networking Flow - -- [Minimum Cut](./networking_flow/minimum_cut.py) -- [Ford Fulkerson](./networking_flow/ford_fulkerson.py) - -## Matrix - -- [Matrix Operation](./matrix/matrix_operation.py) -- [Searching In Sorted Matrix](./matrix/searching_in_sorted_matrix.py) -- [Spiral Print](./matrix/spiral_print.py) - -## Searches - -- [Quick Select](./searches/quick_select.py) -- [Binary Search](./searches/binary_search.py) -- [Interpolation Search](./searches/interpolation_search.py) -- [Jump Search](./searches/jump_search.py) -- [Linear Search](./searches/linear_search.py) -- [Ternary Search](./searches/ternary_search.py) -- [Tabu Search](./searches/tabu_search.py) -- [Sentinel Linear Search](./searches/sentinel_linear_search.py) - -## Conversions - -- [Decimal To Binary](./conversions/decimal_to_binary.py) -- [Decimal To Octal](./conversions/decimal_to_octal.py) - -## Dynamic Programming - -- [Fractional Knapsack](./dynamic_programming/Fractional_Knapsack.py) -- [Sum Of Subset](./dynamic_programming/sum_of_subset.py) -- [Fast Fibonacci](./dynamic_programming/fast_fibonacci.py) -- [Bitmask](./dynamic_programming/bitmask.py) -- [Abbreviation](./dynamic_programming/abbreviation.py) -- [Rod Cutting](./dynamic_programming/rod_cutting.py) -- [Knapsack](./dynamic_programming/knapsack.py) -- [Max Sub Array](./dynamic_programming/max_sub_array.py) -- [Fibonacci](./dynamic_programming/fibonacci.py) -- [Minimum Partition](./dynamic_programming/minimum_partition.py) -- [K Means Clustering Tensorflow](./dynamic_programming/k_means_clustering_tensorflow.py) -- [Coin Change](./dynamic_programming/coin_change.py) -- [Subset Generation](./dynamic_programming/subset_generation.py) -- [Floyd Warshall](./dynamic_programming/floyd_warshall.py) -- [Longest Sub Array](./dynamic_programming/longest_sub_array.py) -- [Integer Partition](./dynamic_programming/integer_partition.py) -- [Matrix Chain Order](./dynamic_programming/matrix_chain_order.py) -- [Edit Distance](./dynamic_programming/edit_distance.py) -- [Longest Common Subsequence](./dynamic_programming/longest_common_subsequence.py) -- [Longest Increasing Subsequence O(nlogn)](<./dynamic_programming/longest_increasing_subsequence_O(nlogn).py>) -- [Longest Increasing Subsequence](./dynamic_programming/longest_increasing_subsequence.py) - -## Divide And Conquer - -- [Max Subarray Sum](./divide_and_conquer/max_subarray_sum.py) -- [Closest Pair Of Points](./divide_and_conquer/closest_pair_of_points.py) - -## Strings - -- [Knuth Morris Pratt](./strings/knuth_morris_pratt.py) -- [Rabin Karp](./strings/rabin_karp.py) -- [Naive String Search](./strings/naive_String_Search.py) -- [Levenshtein Distance](./strings/levenshtein_distance.py) -- [Min Cost String Conversion](./strings/min_cost_string_conversion.py) -- [Boyer Moore Search](./strings/Boyer_Moore_Search.py) -- [Manacher](./strings/manacher.py) - -## Sorts - -- [Quick Sort](./sorts/quick_sort.py) -- [Selection Sort](./sorts/selection_sort.py) -- [Bitonic Sort](./sorts/Bitonic_Sort.py) -- [Cycle Sort](./sorts/cycle_sort.py) -- [Comb Sort](./sorts/comb_sort.py) -- [Topological Sort](./sorts/topological_sort.py) -- [Merge Sort Fastest](./sorts/merge_sort_fastest.py) -- [Random Pivot Quick Sort](./sorts/random_pivot_quick_sort.py) -- [Heap Sort](./sorts/heap_sort.py) -- [Insertion Sort](./sorts/insertion_sort.py) -- [Counting Sort](./sorts/counting_sort.py) -- [Bucket Sort](./sorts/bucket_sort.py) -- [Quick Sort 3 Partition](./sorts/quick_sort_3_partition.py) -- [Bogo Sort](./sorts/bogo_sort.py) -- [Shell Sort](./sorts/shell_sort.py) -- [Pigeon Sort](./sorts/pigeon_sort.py) -- [Odd-Even Transposition Parallel](./sorts/Odd-Even_transposition_parallel.py) -- [Tree Sort](./sorts/tree_sort.py) -- [Cocktail Shaker Sort](./sorts/cocktail_shaker_sort.py) -- [Random Normal Distribution Quicksort](./sorts/random_normal_distribution_quicksort.py) -- [Wiggle Sort](./sorts/wiggle_sort.py) -- [Pancake Sort](./sorts/pancake_sort.py) -- [External Sort](./sorts/external_sort.py) -- [Tim Sort](./sorts/tim_sort.py) -- [Sorting Graphs.png](./sorts/sorting_graphs.png) -- [Radix Sort](./sorts/radix_sort.py) -- [Odd-Even Transposition Single-threaded](./sorts/Odd-Even_transposition_single-threaded.py) -- [Bubble Sort](./sorts/bubble_sort.py) -- [Gnome Sort](./sorts/gnome_sort.py) -- [Merge Sort](./sorts/merge_sort.py) - -## Machine Learning - -- [Perceptron](./machine_learning/perceptron.py) -- [Random Forest Classifier](./machine_learning/random_forest_classification/random_forest_classifier.ipynb) -- [NaiveBayes.ipynb](./machine_learning/NaiveBayes.ipynb) -- [Scoring Functions](./machine_learning/scoring_functions.py) -- [Logistic Regression](./machine_learning/logistic_regression.py) -- [Gradient Descent](./machine_learning/gradient_descent.py) -- [Linear Regression](./machine_learning/linear_regression.py) -- [Random Forest Regression](./machine_learning/random_forest_regression/random_forest_regression.py) -- [Random Forest Regression](./machine_learning/random_forest_regression/random_forest_regression.ipynb) -- [Reuters One Vs Rest Classifier.ipynb](./machine_learning/reuters_one_vs_rest_classifier.ipynb) -- [Decision Tree](./machine_learning/decision_tree.py) -- [Knn Sklearn](./machine_learning/knn_sklearn.py) -- [K Means Clust](./machine_learning/k_means_clust.py) - -## Neural Network - -- [Perceptron](./neural_network/perceptron.py) -- [Fully Connected Neural Network](./neural_network/fully_connected_neural_network.ipynb) -- [Convolution Neural Network](./neural_network/convolution_neural_network.py) -- [Back Propagation Neural Network](./neural_network/back_propagation_neural_network.py) - -## Data Structures - -- ## Binary Tree - - - [Basic Binary Tree](./data_structures/binary_tree/basic_binary_tree.py) - - [Red Black Tree](./data_structures/binary_tree/red_black_tree.py) - - [Fenwick Tree](./data_structures/binary_tree/fenwick_tree.py) - - [Treap](./data_structures/binary_tree/treap.py) - - [AVL Tree](./data_structures/binary_tree/AVL_tree.py) - - [Segment Tree](./data_structures/binary_tree/segment_tree.py) - - [Lazy Segment Tree](./data_structures/binary_tree/lazy_segment_tree.py) - - [Binary Search Tree](./data_structures/binary_tree/binary_search_tree.py) - -- ## Trie - - - [Trie](./data_structures/trie/trie.py) - -- ## Linked List - - - [Swap Nodes](./data_structures/linked_list/swap_nodes.py) - - [Doubly Linked List](./data_structures/linked_list/doubly_linked_list.py) - - [Singly Linked List](./data_structures/linked_list/singly_linked_list.py) - - [Is Palindrome](./data_structures/linked_list/is_Palindrome.py) - -- ## Stacks - - - [Postfix Evaluation](./data_structures/stacks/postfix_evaluation.py) - - [Balanced Parentheses](./data_structures/stacks/balanced_parentheses.py) - - [Infix To Prefix Conversion](./data_structures/stacks/infix_to_prefix_conversion.py) - - [Stack](./data_structures/stacks/stack.py) - - [Infix To Postfix Conversion](./data_structures/stacks/infix_to_postfix_conversion.py) - - [Next Greater Element](./data_structures/stacks/next_greater_element.py) - - [Stock Span Problem](./data_structures/stacks/stock_span_problem.py) - -- ## Queue - - - [Queue On Pseudo Stack](./data_structures/queue/queue_on_pseudo_stack.py) - - [Double Ended Queue](./data_structures/queue/double_ended_queue.py) - - [Queue On List](./data_structures/queue/queue_on_list.py) - -- ## Heap - - - [Heap](./data_structures/heap/heap.py) - -- ## Hashing - - - [Hash Table With Linked List](./data_structures/hashing/hash_table_with_linked_list.py) - - [Quadratic Probing](./data_structures/hashing/quadratic_probing.py) - - [Hash Table](./data_structures/hashing/hash_table.py) - - [Double Hash](./data_structures/hashing/double_hash.py) - - -## Other - -- [Detecting English Programmatically](./other/detecting_english_programmatically.py) -- [Fischer Yates Shuffle](./other/fischer_yates_shuffle.py) -- [Primelib](./other/primelib.py) -- [Binary Exponentiation 2](./other/binary_exponentiation_2.py) -- [Anagrams](./other/anagrams.py) -- [Palindrome](./other/palindrome.py) -- [Finding Primes](./other/finding_Primes.py) -- [Two Sum](./other/two_sum.py) -- [Password Generator](./other/password_generator.py) -- [Linear Congruential Generator](./other/linear_congruential_generator.py) -- [Frequency Finder](./other/frequency_finder.py) -- [Euclidean Gcd](./other/euclidean_gcd.py) -- [Word Patterns](./other/word_patterns.py) -- [Nested Brackets](./other/nested_brackets.py) -- [Binary Exponentiation](./other/binary_exponentiation.py) -- [Sierpinski Triangle](./other/sierpinski_triangle.py) -- [Game Of Life](./other/game_of_life.py) -- [Tower Of Hanoi](./other/tower_of_hanoi.py) +See our [directory](DIRECTORY.md). diff --git a/ciphers/Atbash.py b/ciphers/Atbash.py index 162614c727ee..5653f0213745 100644 --- a/ciphers/Atbash.py +++ b/ciphers/Atbash.py @@ -18,4 +18,6 @@ def Atbash(): output+=i print(output) -Atbash() + +if __name__ == '__main__': + Atbash() diff --git a/ciphers/caesar_cipher.py b/ciphers/caesar_cipher.py index e22f19b4851d..872b5d8195c1 100644 --- a/ciphers/caesar_cipher.py +++ b/ciphers/caesar_cipher.py @@ -59,4 +59,7 @@ def main(): elif choice == '4': print ("Goodbye.") break -main() + + +if __name__ == '__main__': + main() diff --git a/other/detecting_english_programmatically.py b/other/detecting_english_programmatically.py index 005fd3c10ca3..8b73ff6cf0c3 100644 --- a/other/detecting_english_programmatically.py +++ b/other/detecting_english_programmatically.py @@ -6,7 +6,7 @@ def loadDictionary(): path = os.path.split(os.path.realpath(__file__)) englishWords = {} - with open(path[0] + '/Dictionary.txt') as dictionaryFile: + with open(path[0] + '/dictionary.txt') as dictionaryFile: for word in dictionaryFile.read().split('\n'): englishWords[word] = None return englishWords diff --git a/other/sierpinski_triangle.py b/other/sierpinski_triangle.py index 329a8ce5c43f..fc22aad96059 100644 --- a/other/sierpinski_triangle.py +++ b/other/sierpinski_triangle.py @@ -27,13 +27,6 @@ import turtle import sys PROGNAME = 'Sierpinski Triangle' -if len(sys.argv) !=2: - raise Exception('right format for using this script: $python fractals.py ') - -myPen = turtle.Turtle() -myPen.ht() -myPen.speed(5) -myPen.pencolor('red') points = [[-175,-125],[0,175],[175,-125]] #size of triangle @@ -64,4 +57,12 @@ def triangle(points,depth): depth-1) -triangle(points,int(sys.argv[1])) +if __name__ == '__main__': + if len(sys.argv) !=2: + raise ValueError('right format for using this script: ' + '$python fractals.py ') + myPen = turtle.Turtle() + myPen.ht() + myPen.speed(5) + myPen.pencolor('red') + triangle(points,int(sys.argv[1])) diff --git a/other/tower_of_hanoi.py b/other/tower_of_hanoi.py index dc15b2ce8e58..9cc5b9e40543 100644 --- a/other/tower_of_hanoi.py +++ b/other/tower_of_hanoi.py @@ -16,10 +16,10 @@ def moveTower(height, fromPole, toPole, withPole): moveTower(height-1, withPole, toPole, fromPole) def moveDisk(fp,tp): - print(('moving disk from', fp, 'to', tp)) + print('moving disk from', fp, 'to', tp) def main(): - height = int(input('Height of hanoi: ')) + height = int(input('Height of hanoi: ').strip()) moveTower(height, 'A', 'B', 'C') if __name__ == '__main__': diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 000000000000..30179ac345b3 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,6 @@ +black +flake8 +matplotlib +mypy +numpy +pytest diff --git a/searches/tabu_search.py b/searches/tabu_search.py index e21ddd53cc78..ffd84f8ac031 100644 --- a/searches/tabu_search.py +++ b/searches/tabu_search.py @@ -38,7 +38,7 @@ def generate_neighbours(path): and the cost (distance) for each neighbor. Example of dict_of_neighbours: - >>> dict_of_neighbours[a] + >>) dict_of_neighbours[a] [[b,20],[c,18],[d,22],[e,26]] This indicates the neighbors of node (city) 'a', which has neighbor the node 'b' with distance 20, @@ -130,7 +130,7 @@ def find_neighborhood(solution, dict_of_neighbours): Example: - >>> find_neighborhood(['a','c','b','d','e','a']) + >>) find_neighborhood(['a','c','b','d','e','a']) [['a','e','b','d','c','a',90], [['a','c','d','b','e','a',90],['a','d','b','c','e','a',93], ['a','c','b','e','d','a',102], ['a','c','e','d','b','a',113], ['a','b','c','d','e','a',93]] diff --git a/sorts/Bitonic_Sort.py b/sorts/Bitonic_Sort.py index bae95b4346f6..ba40a1f698ee 100644 --- a/sorts/Bitonic_Sort.py +++ b/sorts/Bitonic_Sort.py @@ -42,15 +42,16 @@ def sort(a, N, up): bitonicSort(a, 0, N, up) -# Driver code to test above -a = [] - -n = int(input()) -for i in range(n): - a.append(int(input())) -up = 1 - -sort(a, n, up) -print("\n\nSorted array is") -for i in range(n): - print("%d" % a[i]) +if __name__ == "__main__": + # Driver code to test above + a = [] + + n = int(input().strip()) + for i in range(n): + a.append(int(input().strip())) + up = 1 + + sort(a, n, up) + print("\n\nSorted array is") + for i in range(n): + print("%d" % a[i]) diff --git a/sorts/bubble_sort.py b/sorts/bubble_sort.py index e17fc3358d53..4e2c19b65e02 100644 --- a/sorts/bubble_sort.py +++ b/sorts/bubble_sort.py @@ -19,7 +19,7 @@ def bubble_sort(collection): [-45, -5, -2] >>> bubble_sort([-23,0,6,-4,34]) - [-23,-4,0,6,34] + [-23, -4, 0, 6, 34] """ length = len(collection) for i in range(length-1): From 78cd3df3fc5935922f13db36c61c3b2680f138d7 Mon Sep 17 00:00:00 2001 From: cclauss Date: Mon, 8 Jul 2019 17:38:47 +0200 Subject: [PATCH 0093/1071] Update CONTRIBUTING.md to match #964 (#969) * Update CONTRIBUTING.md to match #964 Blocked by #964 * Do not modify README or DIRECTORY file. * Update CONTRIBUTING.md --- CONTRIBUTING.md | 27 ++++++++++++++++++++++----- 1 file changed, 22 insertions(+), 5 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 03de387a8acd..02235ee89973 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -28,6 +28,13 @@ We appreciate any contribution, from fixing a grammar mistake in a comment to im We want your work to be readable by others; therefore, we encourage you to note the following: - Please write in Python 3.x. +- Please consider running [__python/black__](https://github.com/python/black) on your Python file(s) before submitting your pull request. This is not a requirement but it does make your code more readable. There are other code formatters (autopep8, yapf) but the __black__ style is now the recommendation of the Python core team. To use it, + ```bash + pip3 install black # only required the first time + black my-submission.py + ``` + +- All submissions will need to pass the test __flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics__ before they will be accepted so if possible, try this test locally on your Python file(s) before submitting your pull request. - If you know [PEP 8](https://www.python.org/dev/peps/pep-0008/) already, you will have no problem in coding style, though we do not follow it strictly. Read the remaining section and have fun coding! @@ -74,11 +81,17 @@ We want your work to be readable by others; therefore, we encourage you to note The following "testing" approaches are **not** encouraged: - ```python* + ```python input('Enter your input:') # Or even worse... input = eval(raw_input("Enter your input: ")) ``` + + However, if your code uses __input()__ then we encourage you to gracefully deal with leading and trailing whitespace in user input by adding __.strip()__ to the end as in: + + ```python + starting_value = int(input("Please enter a starting value: ").strip()) + ``` Please write down your test case, like the following: @@ -92,8 +105,10 @@ We want your work to be readable by others; therefore, we encourage you to note print("1 + 2 = ", sumab(1,2)) # 1+2 = 3 print("6 + 4 = ", sumab(6,4)) # 6+4 = 10 ``` + + Better yet, if you know how to write [__doctests__](https://docs.python.org/3/library/doctest.html), please consider adding them. -- Avoid importing external libraries for basic algorithms. Use those libraries for complicated algorithms. +- Avoid importing external libraries for basic algorithms. Only use those libraries for complicated algorithms. #### Other Standard While Submitting Your Work @@ -105,15 +120,17 @@ We want your work to be readable by others; therefore, we encourage you to note - If you have modified/added code work, make sure the code compiles before submitting. -- If you have modified/added documentation work, make sure your language is concise and contains no grammar mistake. +- If you have modified/added documentation work, ensure your language is concise and contains no grammar errors. -- Update the README file if you have added any new algorithm. Only entry corresponding to the algorithm is to be made, not need to add sample data, test files or solutions to problems like Project Euler, in the README. +- Do not update the README.md or DIRECTORY.md file which will be periodically autogenerated by our Travis CI processes. - Add a corresponding explanation to [Algorithms-Explanation](https://github.com/TheAlgorithms/Algorithms-Explanation) (Optional but recommended). +- All submissions will be tested with [__mypy__](http://www.mypy-lang.org) so we encourage to add [__Python type hints__](https://docs.python.org/3/library/typing.html) where it makes sense to do so. + - Most importantly, - - **Be consistent with this guidelines while submitting.** + - **Be consistent in the use of these guidelines when submitting.** - **Join** [Gitter](https://gitter.im/TheAlgorithms) **now!** - Happy coding! From 32d5c1a9b270dec417bb93ecf0e5d9a25170224f Mon Sep 17 00:00:00 2001 From: Animesh Singh <46104817+Blues1998@users.noreply.github.com> Date: Mon, 8 Jul 2019 22:28:58 +0530 Subject: [PATCH 0094/1071] Project Euler Problem #13 Python Solution (#935) * Create text file for numbers * Create sol2.py * Pythonic version of Problem #16 solution * Update sol2.py * Valid Python code for Python version 2-3 * Update sol2.py --- project_euler/problem_13/num.txt | 100 +++++++++++++++++++++++++++++++ project_euler/problem_13/sol2.py | 5 ++ project_euler/problem_16/sol2.py | 6 ++ 3 files changed, 111 insertions(+) create mode 100644 project_euler/problem_13/num.txt create mode 100644 project_euler/problem_13/sol2.py create mode 100644 project_euler/problem_16/sol2.py diff --git a/project_euler/problem_13/num.txt b/project_euler/problem_13/num.txt new file mode 100644 index 000000000000..43b568e812a8 --- /dev/null +++ b/project_euler/problem_13/num.txt @@ -0,0 +1,100 @@ +37107287533902102798797998220837590246510135740250 +46376937677490009712648124896970078050417018260538 +74324986199524741059474233309513058123726617309629 +91942213363574161572522430563301811072406154908250 +23067588207539346171171980310421047513778063246676 +89261670696623633820136378418383684178734361726757 +28112879812849979408065481931592621691275889832738 +44274228917432520321923589422876796487670272189318 +47451445736001306439091167216856844588711603153276 +70386486105843025439939619828917593665686757934951 +62176457141856560629502157223196586755079324193331 +64906352462741904929101432445813822663347944758178 +92575867718337217661963751590579239728245598838407 +58203565325359399008402633568948830189458628227828 +80181199384826282014278194139940567587151170094390 +35398664372827112653829987240784473053190104293586 +86515506006295864861532075273371959191420517255829 +71693888707715466499115593487603532921714970056938 +54370070576826684624621495650076471787294438377604 +53282654108756828443191190634694037855217779295145 +36123272525000296071075082563815656710885258350721 +45876576172410976447339110607218265236877223636045 +17423706905851860660448207621209813287860733969412 +81142660418086830619328460811191061556940512689692 +51934325451728388641918047049293215058642563049483 +62467221648435076201727918039944693004732956340691 +15732444386908125794514089057706229429197107928209 +55037687525678773091862540744969844508330393682126 +18336384825330154686196124348767681297534375946515 +80386287592878490201521685554828717201219257766954 +78182833757993103614740356856449095527097864797581 +16726320100436897842553539920931837441497806860984 +48403098129077791799088218795327364475675590848030 +87086987551392711854517078544161852424320693150332 +59959406895756536782107074926966537676326235447210 +69793950679652694742597709739166693763042633987085 +41052684708299085211399427365734116182760315001271 +65378607361501080857009149939512557028198746004375 +35829035317434717326932123578154982629742552737307 +94953759765105305946966067683156574377167401875275 +88902802571733229619176668713819931811048770190271 +25267680276078003013678680992525463401061632866526 +36270218540497705585629946580636237993140746255962 +24074486908231174977792365466257246923322810917141 +91430288197103288597806669760892938638285025333403 +34413065578016127815921815005561868836468420090470 +23053081172816430487623791969842487255036638784583 +11487696932154902810424020138335124462181441773470 +63783299490636259666498587618221225225512486764533 +67720186971698544312419572409913959008952310058822 +95548255300263520781532296796249481641953868218774 +76085327132285723110424803456124867697064507995236 +37774242535411291684276865538926205024910326572967 +23701913275725675285653248258265463092207058596522 +29798860272258331913126375147341994889534765745501 +18495701454879288984856827726077713721403798879715 +38298203783031473527721580348144513491373226651381 +34829543829199918180278916522431027392251122869539 +40957953066405232632538044100059654939159879593635 +29746152185502371307642255121183693803580388584903 +41698116222072977186158236678424689157993532961922 +62467957194401269043877107275048102390895523597457 +23189706772547915061505504953922979530901129967519 +86188088225875314529584099251203829009407770775672 +11306739708304724483816533873502340845647058077308 +82959174767140363198008187129011875491310547126581 +97623331044818386269515456334926366572897563400500 +42846280183517070527831839425882145521227251250327 +55121603546981200581762165212827652751691296897789 +32238195734329339946437501907836945765883352399886 +75506164965184775180738168837861091527357929701337 +62177842752192623401942399639168044983993173312731 +32924185707147349566916674687634660915035914677504 +99518671430235219628894890102423325116913619626622 +73267460800591547471830798392868535206946944540724 +76841822524674417161514036427982273348055556214818 +97142617910342598647204516893989422179826088076852 +87783646182799346313767754307809363333018982642090 +10848802521674670883215120185883543223812876952786 +71329612474782464538636993009049310363619763878039 +62184073572399794223406235393808339651327408011116 +66627891981488087797941876876144230030984490851411 +60661826293682836764744779239180335110989069790714 +85786944089552990653640447425576083659976645795096 +66024396409905389607120198219976047599490197230297 +64913982680032973156037120041377903785566085089252 +16730939319872750275468906903707539413042652315011 +94809377245048795150954100921645863754710598436791 +78639167021187492431995700641917969777599028300699 +15368713711936614952811305876380278410754449733078 +40789923115535562561142322423255033685442488917353 +44889911501440648020369068063960672322193204149535 +41503128880339536053299340368006977710650566631954 +81234880673210146739058568557934581403627822703280 +82616570773948327592232845941706525094512325230608 +22918802058777319719839450180888072429661980811197 +77158542502016545090413245809786882778948721859617 +72107838435069186155435662884062257473692284509516 +20849603980134001723930671666823555245252804609722 +53503534226472524250874054075591789781264330331690 diff --git a/project_euler/problem_13/sol2.py b/project_euler/problem_13/sol2.py new file mode 100644 index 000000000000..c1416bcd6e7d --- /dev/null +++ b/project_euler/problem_13/sol2.py @@ -0,0 +1,5 @@ +sum = 0 +with open("num.txt",'r') as f: + for line in f: + sum += int(line) +print(str(sum)[:10]) diff --git a/project_euler/problem_16/sol2.py b/project_euler/problem_16/sol2.py new file mode 100644 index 000000000000..cce3d2354bb1 --- /dev/null +++ b/project_euler/problem_16/sol2.py @@ -0,0 +1,6 @@ +from __future__ import print_function +n = 2**1000 +r = 0 +while n: + r, n = r + n % 10, n // 10 +print(r) From e2d9953952053130c63c51ce62c5ceb6cb084e91 Mon Sep 17 00:00:00 2001 From: Shoujue Xu Date: Tue, 9 Jul 2019 01:26:26 +0800 Subject: [PATCH 0095/1071] convolve and sobel (#971) * add gaussian filter algorithm and lena.jpg * add img_convolve algorithm and sobel_filter --- digital_image_processing/filters/convolve.py | 49 +++++++++++++++++++ .../filters/sobel_filter.py | 31 ++++++++++++ 2 files changed, 80 insertions(+) create mode 100644 digital_image_processing/filters/convolve.py create mode 100644 digital_image_processing/filters/sobel_filter.py diff --git a/digital_image_processing/filters/convolve.py b/digital_image_processing/filters/convolve.py new file mode 100644 index 000000000000..b7600d74c294 --- /dev/null +++ b/digital_image_processing/filters/convolve.py @@ -0,0 +1,49 @@ +# @Author : lightXu +# @File : convolve.py +# @Time : 2019/7/8 0008 下午 16:13 +from cv2 import imread, cvtColor, COLOR_BGR2GRAY, imshow, waitKey +from numpy import array, zeros, ravel, pad, dot, uint8 + + +def im2col(image, block_size): + rows, cols = image.shape + dst_height = cols - block_size[1] + 1 + dst_width = rows - block_size[0] + 1 + image_array = zeros((dst_height * dst_width, block_size[1] * block_size[0])) + row = 0 + for i in range(0, dst_height): + for j in range(0, dst_width): + window = ravel(image[i:i + block_size[0], j:j + block_size[1]]) + image_array[row, :] = window + row += 1 + + return image_array + + +def img_convolve(image, filter_kernel): + height, width = image.shape[0], image.shape[1] + k_size = filter_kernel.shape[0] + pad_size = k_size//2 + # Pads image with the edge values of array. + image_tmp = pad(image, pad_size, mode='edge') + + # im2col, turn the k_size*k_size pixels into a row and np.vstack all rows + image_array = im2col(image_tmp, (k_size, k_size)) + + # turn the kernel into shape(k*k, 1) + kernel_array = ravel(filter_kernel) + # reshape and get the dst image + dst = dot(image_array, kernel_array).reshape(height, width) + return dst + + +if __name__ == '__main__': + # read original image + img = imread(r'../image_data/lena.jpg') + # turn image in gray scale value + gray = cvtColor(img, COLOR_BGR2GRAY) + # Laplace operator + Laplace_kernel = array([[0, 1, 0], [1, -4, 1], [0, 1, 0]]) + out = img_convolve(gray, Laplace_kernel).astype(uint8) + imshow('Laplacian', out) + waitKey(0) diff --git a/digital_image_processing/filters/sobel_filter.py b/digital_image_processing/filters/sobel_filter.py new file mode 100644 index 000000000000..0c797320a110 --- /dev/null +++ b/digital_image_processing/filters/sobel_filter.py @@ -0,0 +1,31 @@ +# @Author : lightXu +# @File : sobel_filter.py +# @Time : 2019/7/8 0008 下午 16:26 +import numpy as np +from cv2 import imread, cvtColor, COLOR_BGR2GRAY, imshow, waitKey +from digital_image_processing.filters.convolve import img_convolve + + +def sobel_filter(image): + kernel_x = np.array([[-1, 0, 1], [-2, 0, 2], [-1, 0, 1]]) + kernel_y = np.array([[1, 2, 1], [0, 0, 0], [-1, -2, -1]]) + + dst_x = img_convolve(image, kernel_x) + dst_y = img_convolve(image, kernel_y) + dst = np.sqrt((np.square(dst_x)) + (np.square(dst_y))).astype(np.uint8) + degree = np.arctan2(dst_y, dst_x) + return dst, degree + + +if __name__ == '__main__': + # read original image + img = imread('../image_data/lena.jpg') + # turn image in gray scale value + gray = cvtColor(img, COLOR_BGR2GRAY) + + sobel, d = sobel_filter(gray) + + # show result images + imshow('sobel filter', sobel) + imshow('sobel degree', d) + waitKey(0) From 8b2d1b7f509a1124abdd037803db32880402da19 Mon Sep 17 00:00:00 2001 From: Jasper <46252815+jasper256@users.noreply.github.com> Date: Tue, 9 Jul 2019 03:03:18 -0400 Subject: [PATCH 0096/1071] added decimal to hexadecimal conversion (#977) * added decimal to hexadecimal conversion * fixed error occuring as more digits were needed --- conversions/decimal_to_hexadecimal.py | 43 +++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 conversions/decimal_to_hexadecimal.py diff --git a/conversions/decimal_to_hexadecimal.py b/conversions/decimal_to_hexadecimal.py new file mode 100644 index 000000000000..f91fac063adc --- /dev/null +++ b/conversions/decimal_to_hexadecimal.py @@ -0,0 +1,43 @@ +""" Convert Base 10 (Decimal) Values to Hexadecimal Representations """ + +# set decimal value for each hexadecimal digit +values = { + 0:'0', + 1:'1', + 2:'2', + 3:'3', + 4:'4', + 5:'5', + 6:'6', + 7:'7', + 8:'8', + 9:'9', + 10:'a', + 11:'b', + 12:'c', + 13:'d', + 14:'e', + 15:'f' +} + +def decimal_to_hexadecimal(decimal): + """ take decimal value, return hexadecimal representation as str """ + hexadecimal = '' + while decimal > 0: + remainder = decimal % 16 + decimal -= remainder + hexadecimal = values[remainder] + hexadecimal + decimal /= 16 + return hexadecimal + +def main(): + """ print test cases """ + print("5 in hexadecimal is", decimal_to_hexadecimal(5)) + print("15 in hexadecimal is", decimal_to_hexadecimal(15)) + print("37 in hexadecimal is", decimal_to_hexadecimal(37)) + print("255 in hexadecimal is", decimal_to_hexadecimal(255)) + print("4096 in hexadecimal is", decimal_to_hexadecimal(4096)) + print("999098 in hexadecimal is", decimal_to_hexadecimal(999098)) + +if __name__ == '__main__': + main() \ No newline at end of file From c85312da89dcc5bb1ad397feffc0e055dc576e85 Mon Sep 17 00:00:00 2001 From: Dharni0607 <30770547+Dharni0607@users.noreply.github.com> Date: Tue, 9 Jul 2019 20:50:43 +0530 Subject: [PATCH 0097/1071] updates in closest pair of points algorithm (#979) * updated closest pair of points (n*(logn)^2) to (n*logn) --- divide_and_conquer/closest_pair_of_points.py | 49 +++++++++++--------- 1 file changed, 28 insertions(+), 21 deletions(-) diff --git a/divide_and_conquer/closest_pair_of_points.py b/divide_and_conquer/closest_pair_of_points.py index cc5be428db79..ee06d27063df 100644 --- a/divide_and_conquer/closest_pair_of_points.py +++ b/divide_and_conquer/closest_pair_of_points.py @@ -1,27 +1,27 @@ """ -The algorithm finds distance btw closest pair of points in the given n points. +The algorithm finds distance between closest pair of points +in the given n points. Approach used -> Divide and conquer -The points are sorted based on Xco-ords -& by applying divide and conquer approach, +The points are sorted based on Xco-ords and +then based on Yco-ords separately. +And by applying divide and conquer approach, minimum distance is obtained recursively. ->> closest points lie on different sides of partition +>> Closest points can lie on different sides of partition. This case handled by forming a strip of points whose Xco-ords distance is less than closest_pair_dis -from mid-point's Xco-ords. +from mid-point's Xco-ords. Points sorted based on Yco-ords +are used in this step to reduce sorting time. Closest pair distance is found in the strip of points. (closest_in_strip) min(closest_pair_dis, closest_in_strip) would be the final answer. -Time complexity: O(n * (logn)^2) +Time complexity: O(n * log n) """ -import math - - def euclidean_distance_sqr(point1, point2): - return pow(point1[0] - point2[0], 2) + pow(point1[1] - point2[1], 2) + return (point1[0] - point2[0]) ** 2 + (point1[1] - point2[1]) ** 2 def column_based_sort(array, column = 0): @@ -66,7 +66,7 @@ def dis_between_closest_in_strip(points, points_counts, min_dis = float("inf")): return min_dis -def closest_pair_of_points_sqr(points, points_counts): +def closest_pair_of_points_sqr(points_sorted_on_x, points_sorted_on_y, points_counts): """ divide and conquer approach Parameters : @@ -79,12 +79,16 @@ def closest_pair_of_points_sqr(points, points_counts): # base case if points_counts <= 3: - return dis_between_closest_pair(points, points_counts) + return dis_between_closest_pair(points_sorted_on_x, points_counts) # recursion mid = points_counts//2 - closest_in_left = closest_pair_of_points(points[:mid], mid) - closest_in_right = closest_pair_of_points(points[mid:], points_counts - mid) + closest_in_left = closest_pair_of_points_sqr(points_sorted_on_x, + points_sorted_on_y[:mid], + mid) + closest_in_right = closest_pair_of_points_sqr(points_sorted_on_y, + points_sorted_on_y[mid:], + points_counts - mid) closest_pair_dis = min(closest_in_left, closest_in_right) """ cross_strip contains the points, whose Xcoords are at a @@ -92,22 +96,25 @@ def closest_pair_of_points_sqr(points, points_counts): """ cross_strip = [] - for point in points: - if abs(point[0] - points[mid][0]) < closest_pair_dis: + for point in points_sorted_on_x: + if abs(point[0] - points_sorted_on_x[mid][0]) < closest_pair_dis: cross_strip.append(point) - cross_strip = column_based_sort(cross_strip, 1) closest_in_strip = dis_between_closest_in_strip(cross_strip, len(cross_strip), closest_pair_dis) return min(closest_pair_dis, closest_in_strip) def closest_pair_of_points(points, points_counts): - return math.sqrt(closest_pair_of_points_sqr(points, points_counts)) + points_sorted_on_x = column_based_sort(points, column = 0) + points_sorted_on_y = column_based_sort(points, column = 1) + return (closest_pair_of_points_sqr(points_sorted_on_x, + points_sorted_on_y, + points_counts)) ** 0.5 -points = [(2, 3), (12, 30), (40, 50), (5, 1), (12, 10), (0, 2), (5, 6), (1, 2)] -points = column_based_sort(points) -print("Distance:", closest_pair_of_points(points, len(points))) +if __name__ == "__main__": + points = [(2, 3), (12, 30), (40, 50), (5, 1), (12, 10), (3, 4)] + print("Distance:", closest_pair_of_points(points, len(points))) From e6eaa078e292dc856f1027331e8de30b43a918f9 Mon Sep 17 00:00:00 2001 From: cclauss Date: Wed, 10 Jul 2019 06:59:39 +0200 Subject: [PATCH 0098/1071] Pytest the entire repo (#980) * Pytest the entire repo * Do each directory for now... * YAML files hate tabs * Add more requirements * pip install opencv-python * Comment out FTP * Add pandas and sklearn to requirements * Comment out FTP, graphs, machine_learning, maths, neural_network, project_euler * Update .travis.yml * Comment out Data structures * if __name__ == "__main__": * pytest --ignore= * pytest . * Update .travis.yml * pytest . --doctest-modules --ignore=${IGNORE} * Ignore --ignore because it just hangs --- .travis.yml | 24 ++++++++++++++++++- ...longest_increasing_subsequence_O(nlogn).py | 5 ++-- requirements.txt | 5 ++++ 3 files changed, 31 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index e67bd431a7c1..0e35fd084268 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,13 +2,35 @@ language: python dist: xenial # required for Python >= 3.7 python: 3.7 cache: pip +before_install: pip install --upgrade pip setuptools install: pip install -r requirements.txt before_script: - black --check . || true - flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics script: - mypy --ignore-missing-imports . - - pytest --doctest-modules ./ciphers ./other ./searches ./sorts ./strings + #- IGNORE="data_structures,file_transfer_protocol,graphs,machine_learning,maths,neural_network,project_euler" + #- pytest . --doctest-modules --ignore=${IGNORE} + - pytest --doctest-modules + arithmetic_analysis + backtracking + boolean_algebra + ciphers + compression + conversions + digital_image_processing + divide_and_conquer + dynamic_programming + hashes + linear_algebra_python + matrix + networking_flow + other + searches + sorts + strings + traversals + after_success: - python ./~script.py - cat DIRECTORY.md diff --git a/dynamic_programming/longest_increasing_subsequence_O(nlogn).py b/dynamic_programming/longest_increasing_subsequence_O(nlogn).py index 21122a04d69f..86bec089adc7 100644 --- a/dynamic_programming/longest_increasing_subsequence_O(nlogn).py +++ b/dynamic_programming/longest_increasing_subsequence_O(nlogn).py @@ -37,5 +37,6 @@ def LongestIncreasingSubsequenceLength(v): return length -v = [2, 5, 3, 7, 11, 8, 10, 13, 6] -print(LongestIncreasingSubsequenceLength(v)) +if __name__ == "__main__": + v = [2, 5, 3, 7, 11, 8, 10, 13, 6] + print(LongestIncreasingSubsequenceLength(v)) diff --git a/requirements.txt b/requirements.txt index 30179ac345b3..91d3df33323d 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,4 +3,9 @@ flake8 matplotlib mypy numpy +opencv-python +pandas pytest +sklearn +sympy +tensorflow From add1aef0645790f2688c8ad44ddfdf6638ab8cb1 Mon Sep 17 00:00:00 2001 From: PatOnTheBack <51241310+PatOnTheBack@users.noreply.github.com> Date: Wed, 10 Jul 2019 01:21:04 -0400 Subject: [PATCH 0099/1071] Rename average.py to average_mean.py (#939) 'average.py' is ambiguous. There are several kinds of averages, so 'average_mean.py' is a more precise name. --- maths/{average.py => average_mean.py} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename maths/{average.py => average_mean.py} (100%) diff --git a/maths/average.py b/maths/average_mean.py similarity index 100% rename from maths/average.py rename to maths/average_mean.py From 34dee749a725b0d47811161b0598b9f806ce88cd Mon Sep 17 00:00:00 2001 From: Shoujue Xu Date: Wed, 10 Jul 2019 22:41:05 +0800 Subject: [PATCH 0100/1071] add canny edge detection algorithm and modify sobel_filter (#991) * add gaussian filter algorithm and lena.jpg * add img_convolve algorithm and sobel_filter * add canny edge detection algorithm and modify sobel_filter * format to avoid the backslashes --- .../edge_detection/__init__.py | 0 .../edge_detection/canny.py | 107 ++++++++++++++++++ .../filters/sobel_filter.py | 23 ++-- 3 files changed, 122 insertions(+), 8 deletions(-) create mode 100644 digital_image_processing/edge_detection/__init__.py create mode 100644 digital_image_processing/edge_detection/canny.py diff --git a/digital_image_processing/edge_detection/__init__.py b/digital_image_processing/edge_detection/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/digital_image_processing/edge_detection/canny.py b/digital_image_processing/edge_detection/canny.py new file mode 100644 index 000000000000..7fde75a90a48 --- /dev/null +++ b/digital_image_processing/edge_detection/canny.py @@ -0,0 +1,107 @@ +import cv2 +import numpy as np +from digital_image_processing.filters.convolve import img_convolve +from digital_image_processing.filters.sobel_filter import sobel_filter + +PI = 180 + + +def gen_gaussian_kernel(k_size, sigma): + center = k_size // 2 + x, y = np.mgrid[0 - center:k_size - center, 0 - center:k_size - center] + g = 1 / (2 * np.pi * sigma) * np.exp(-(np.square(x) + np.square(y)) / (2 * np.square(sigma))) + return g + + +def canny(image, threshold_low=15, threshold_high=30, weak=128, strong=255): + image_row, image_col = image.shape[0], image.shape[1] + # gaussian_filter + gaussian_out = img_convolve(image, gen_gaussian_kernel(9, sigma=1.4)) + # get the gradient and degree by sobel_filter + sobel_grad, sobel_theta = sobel_filter(gaussian_out) + gradient_direction = np.rad2deg(sobel_theta) + gradient_direction += PI + + dst = np.zeros((image_row, image_col)) + + """ + Non-maximum suppression. If the edge strength of the current pixel is the largest compared to the other pixels + in the mask with the same direction, the value will be preserved. Otherwise, the value will be suppressed. + """ + for row in range(1, image_row - 1): + for col in range(1, image_col - 1): + direction = gradient_direction[row, col] + + if ( + 0 <= direction < 22.5 + or 15 * PI / 8 <= direction <= 2 * PI + or 7 * PI / 8 <= direction <= 9 * PI / 8 + ): + W = sobel_grad[row, col - 1] + E = sobel_grad[row, col + 1] + if sobel_grad[row, col] >= W and sobel_grad[row, col] >= E: + dst[row, col] = sobel_grad[row, col] + + elif (PI / 8 <= direction < 3 * PI / 8) or (9 * PI / 8 <= direction < 11 * PI / 8): + SW = sobel_grad[row + 1, col - 1] + NE = sobel_grad[row - 1, col + 1] + if sobel_grad[row, col] >= SW and sobel_grad[row, col] >= NE: + dst[row, col] = sobel_grad[row, col] + + elif (3 * PI / 8 <= direction < 5 * PI / 8) or (11 * PI / 8 <= direction < 13 * PI / 8): + N = sobel_grad[row - 1, col] + S = sobel_grad[row + 1, col] + if sobel_grad[row, col] >= N and sobel_grad[row, col] >= S: + dst[row, col] = sobel_grad[row, col] + + elif (5 * PI / 8 <= direction < 7 * PI / 8) or (13 * PI / 8 <= direction < 15 * PI / 8): + NW = sobel_grad[row - 1, col - 1] + SE = sobel_grad[row + 1, col + 1] + if sobel_grad[row, col] >= NW and sobel_grad[row, col] >= SE: + dst[row, col] = sobel_grad[row, col] + + """ + High-Low threshold detection. If an edge pixel’s gradient value is higher than the high threshold + value, it is marked as a strong edge pixel. If an edge pixel’s gradient value is smaller than the high + threshold value and larger than the low threshold value, it is marked as a weak edge pixel. If an edge + pixel's value is smaller than the low threshold value, it will be suppressed. + """ + if dst[row, col] >= threshold_high: + dst[row, col] = strong + elif dst[row, col] <= threshold_low: + dst[row, col] = 0 + else: + dst[row, col] = weak + + """ + Edge tracking. Usually a weak edge pixel caused from true edges will be connected to a strong edge pixel while + noise responses are unconnected. As long as there is one strong edge pixel that is involved in its 8-connected + neighborhood, that weak edge point can be identified as one that should be preserved. + """ + for row in range(1, image_row): + for col in range(1, image_col): + if dst[row, col] == weak: + if 255 in ( + dst[row, col + 1], + dst[row, col - 1], + dst[row - 1, col], + dst[row + 1, col], + dst[row - 1, col - 1], + dst[row + 1, col - 1], + dst[row - 1, col + 1], + dst[row + 1, col + 1], + ): + dst[row, col] = strong + else: + dst[row, col] = 0 + + return dst + + +if __name__ == '__main__': + # read original image in gray mode + lena = cv2.imread(r'../image_data/lena.jpg', 0) + # canny edge detection + canny_dst = canny(lena) + cv2.imshow('canny', canny_dst) + cv2.waitKey(0) diff --git a/digital_image_processing/filters/sobel_filter.py b/digital_image_processing/filters/sobel_filter.py index 0c797320a110..f3ef407d49e5 100644 --- a/digital_image_processing/filters/sobel_filter.py +++ b/digital_image_processing/filters/sobel_filter.py @@ -10,11 +10,18 @@ def sobel_filter(image): kernel_x = np.array([[-1, 0, 1], [-2, 0, 2], [-1, 0, 1]]) kernel_y = np.array([[1, 2, 1], [0, 0, 0], [-1, -2, -1]]) - dst_x = img_convolve(image, kernel_x) - dst_y = img_convolve(image, kernel_y) - dst = np.sqrt((np.square(dst_x)) + (np.square(dst_y))).astype(np.uint8) - degree = np.arctan2(dst_y, dst_x) - return dst, degree + dst_x = np.abs(img_convolve(image, kernel_x)) + dst_y = np.abs(img_convolve(image, kernel_y)) + # modify the pix within [0, 255] + dst_x = dst_x * 255/np.max(dst_x) + dst_y = dst_y * 255/np.max(dst_y) + + dst_xy = np.sqrt((np.square(dst_x)) + (np.square(dst_y))) + dst_xy = dst_xy * 255/np.max(dst_xy) + dst = dst_xy.astype(np.uint8) + + theta = np.arctan2(dst_y, dst_x) + return dst, theta if __name__ == '__main__': @@ -23,9 +30,9 @@ def sobel_filter(image): # turn image in gray scale value gray = cvtColor(img, COLOR_BGR2GRAY) - sobel, d = sobel_filter(gray) + sobel_grad, sobel_theta = sobel_filter(gray) # show result images - imshow('sobel filter', sobel) - imshow('sobel degree', d) + imshow('sobel filter', sobel_grad) + imshow('sobel theta', sobel_theta) waitKey(0) From 2ad5be99192ade89c26752fb9d6fb7c32bc0337f Mon Sep 17 00:00:00 2001 From: PatOnTheBack <51241310+PatOnTheBack@users.noreply.github.com> Date: Wed, 10 Jul 2019 16:00:30 -0400 Subject: [PATCH 0101/1071] Modified Docstrings to Fix Errors (#975) I modified the Docstrings at the beginning of the file to fix D400, W0105, and E402. --- graphs/BFS.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/graphs/BFS.py b/graphs/BFS.py index bf9b572cec50..6bbdd9e25435 100644 --- a/graphs/BFS.py +++ b/graphs/BFS.py @@ -1,6 +1,8 @@ -"""pseudo-code""" - """ +BFS. + +pseudo-code: + BFS(graph G, start vertex s): // all nodes initially unexplored mark s as explored From 897f1d0fb4c4f78dd8a7e82820e843f7f9f38738 Mon Sep 17 00:00:00 2001 From: PatOnTheBack <51241310+PatOnTheBack@users.noreply.github.com> Date: Wed, 10 Jul 2019 16:09:24 -0400 Subject: [PATCH 0102/1071] Improved Formatting and Style of Math Algos (#960) * Improved Formatting and Style I improved formatting and style to make PyLama happier. Linters used: - mccabe - pep257 - pydocstyle - pep8 - pycodestyle - pyflakes - pylint - isort * Create volume.py This script calculates the volumes of various shapes. * Delete lucasSeries.py * Revert "Delete lucasSeries.py" This reverts commit 64c19f7a6c8b74e15bed07f0f0337598a001ceb4. * Update lucasSeries.py --- maths/Binary_Exponentiation.py | 26 +++---- maths/Find_Min.py | 17 +++-- maths/Prime_Check.py | 49 +++++++------ maths/abs.py | 13 ++-- maths/extended_euclidean_algorithm.py | 53 +++++++++----- maths/fermat_little_theorem.py | 6 +- maths/greater_common_divisor.py | 18 +++-- maths/modular_exponential.py | 31 ++++---- maths/segmented_sieve.py | 43 ++++++----- maths/sieve_of_eratosthenes.py | 31 ++++---- maths/simpson_rule.py | 60 ++++++++-------- maths/trapezoidal_rule.py | 24 +++---- maths/volume.py | 100 ++++++++++++++++++++++++++ 13 files changed, 314 insertions(+), 157 deletions(-) create mode 100644 maths/volume.py diff --git a/maths/Binary_Exponentiation.py b/maths/Binary_Exponentiation.py index 2411cd58a76b..cf789afc6f22 100644 --- a/maths/Binary_Exponentiation.py +++ b/maths/Binary_Exponentiation.py @@ -1,25 +1,27 @@ -#Author : Junth Basnet -#Time Complexity : O(logn) +"""Binary Exponentiation.""" + +# Author : Junth Basnet +# Time Complexity : O(logn) + def binary_exponentiation(a, n): - + if (n == 0): return 1 - + elif (n % 2 == 1): return binary_exponentiation(a, n - 1) * a - + else: b = binary_exponentiation(a, n / 2) return b * b - + try: - base = int(input('Enter Base : ')) - power = int(input("Enter Power : ")) + BASE = int(input('Enter Base : ')) + POWER = int(input("Enter Power : ")) except ValueError: - print ("Invalid literal for integer") + print("Invalid literal for integer") -result = binary_exponentiation(base, power) -print("{}^({}) : {}".format(base, power, result)) - +RESULT = binary_exponentiation(BASE, POWER) +print("{}^({}) : {}".format(BASE, POWER, RESULT)) diff --git a/maths/Find_Min.py b/maths/Find_Min.py index 86207984e3da..c720da268a25 100644 --- a/maths/Find_Min.py +++ b/maths/Find_Min.py @@ -1,12 +1,17 @@ +"""Find Minimum Number in a List.""" + + def main(): - def findMin(x): - minNum = x[0] + """Find Minimum Number in a List.""" + def find_min(x): + min_num = x[0] for i in x: - if minNum > i: - minNum = i - return minNum + if min_num > i: + min_num = i + return min_num + + print(find_min([0, 1, 2, 3, 4, 5, -3, 24, -56])) # = -56 - print(findMin([0,1,2,3,4,5,-3,24,-56])) # = -56 if __name__ == '__main__': main() diff --git a/maths/Prime_Check.py b/maths/Prime_Check.py index 8c5c181689dd..9249834dc069 100644 --- a/maths/Prime_Check.py +++ b/maths/Prime_Check.py @@ -1,9 +1,13 @@ +"""Prime Check.""" + import math import unittest -def primeCheck(number): +def prime_check(number): """ + Check to See if a Number is Prime. + A number is prime if it has exactly two dividers: 1 and itself. """ if number < 2: @@ -24,31 +28,30 @@ def primeCheck(number): class Test(unittest.TestCase): def test_primes(self): - self.assertTrue(primeCheck(2)) - self.assertTrue(primeCheck(3)) - self.assertTrue(primeCheck(5)) - self.assertTrue(primeCheck(7)) - self.assertTrue(primeCheck(11)) - self.assertTrue(primeCheck(13)) - self.assertTrue(primeCheck(17)) - self.assertTrue(primeCheck(19)) - self.assertTrue(primeCheck(23)) - self.assertTrue(primeCheck(29)) + self.assertTrue(prime_check(2)) + self.assertTrue(prime_check(3)) + self.assertTrue(prime_check(5)) + self.assertTrue(prime_check(7)) + self.assertTrue(prime_check(11)) + self.assertTrue(prime_check(13)) + self.assertTrue(prime_check(17)) + self.assertTrue(prime_check(19)) + self.assertTrue(prime_check(23)) + self.assertTrue(prime_check(29)) def test_not_primes(self): - self.assertFalse(primeCheck(-19), - "Negative numbers are not prime.") - self.assertFalse(primeCheck(0), - "Zero doesn't have any divider, primes must have two") - self.assertFalse(primeCheck(1), - "One just have 1 divider, primes must have two.") - self.assertFalse(primeCheck(2 * 2)) - self.assertFalse(primeCheck(2 * 3)) - self.assertFalse(primeCheck(3 * 3)) - self.assertFalse(primeCheck(3 * 5)) - self.assertFalse(primeCheck(3 * 5 * 7)) + self.assertFalse(prime_check(-19), + "Negative numbers are not prime.") + self.assertFalse(prime_check(0), + "Zero doesn't have any divider, primes must have two") + self.assertFalse(prime_check(1), + "One just have 1 divider, primes must have two.") + self.assertFalse(prime_check(2 * 2)) + self.assertFalse(prime_check(2 * 3)) + self.assertFalse(prime_check(3 * 3)) + self.assertFalse(prime_check(3 * 5)) + self.assertFalse(prime_check(3 * 5 * 7)) if __name__ == '__main__': unittest.main() - diff --git a/maths/abs.py b/maths/abs.py index 624823fc183e..2734e58ceee6 100644 --- a/maths/abs.py +++ b/maths/abs.py @@ -1,24 +1,25 @@ """Absolute Value.""" -def absVal(num): +def abs_val(num): """ Find the absolute value of a number. - >>absVal(-5) + >>abs_val(-5) 5 - >>absVal(0) + >>abs_val(0) 0 """ if num < 0: return -num - else: - return num + + # Returns if number is not < 0 + return num def main(): """Print absolute value of -34.""" - print(absVal(-34)) # = 34 + print(abs_val(-34)) # = 34 if __name__ == '__main__': diff --git a/maths/extended_euclidean_algorithm.py b/maths/extended_euclidean_algorithm.py index f5a3cc88e474..fc3798e7e432 100644 --- a/maths/extended_euclidean_algorithm.py +++ b/maths/extended_euclidean_algorithm.py @@ -1,20 +1,38 @@ +""" +Extended Euclidean Algorithm. + +Finds 2 numbers a and b such that it satisfies +the equation am + bn = gcd(m, n) (a.k.a Bezout's Identity) +""" + # @Author: S. Sharma # @Date: 2019-02-25T12:08:53-06:00 # @Email: silentcat@protonmail.com -# @Last modified by: silentcat -# @Last modified time: 2019-02-26T07:07:38-06:00 +# @Last modified by: PatOnTheBack +# @Last modified time: 2019-07-05 import sys -# Finds 2 numbers a and b such that it satisfies -# the equation am + bn = gcd(m, n) (a.k.a Bezout's Identity) + def extended_euclidean_algorithm(m, n): - a = 0; aprime = 1; b = 1; bprime = 0 - q = 0; r = 0 + """ + Extended Euclidean Algorithm. + + Finds 2 numbers a and b such that it satisfies + the equation am + bn = gcd(m, n) (a.k.a Bezout's Identity) + """ + a = 0 + a_prime = 1 + b = 1 + b_prime = 0 + q = 0 + r = 0 if m > n: - c = m; d = n + c = m + d = n else: - c = n; d = m + c = n + d = m while True: q = int(c / d) @@ -24,22 +42,24 @@ def extended_euclidean_algorithm(m, n): c = d d = r - t = aprime - aprime = a - a = t - q*a + t = a_prime + a_prime = a + a = t - q * a - t = bprime - bprime = b - b = t - q*b + t = b_prime + b_prime = b + b = t - q * b pair = None if m > n: - pair = (a,b) + pair = (a, b) else: - pair = (b,a) + pair = (b, a) return pair + def main(): + """Call Extended Euclidean Algorithm.""" if len(sys.argv) < 3: print('2 integer arguments required') exit(1) @@ -47,5 +67,6 @@ def main(): n = int(sys.argv[2]) print(extended_euclidean_algorithm(m, n)) + if __name__ == '__main__': main() diff --git a/maths/fermat_little_theorem.py b/maths/fermat_little_theorem.py index 93af98684894..8cf60dafe3ca 100644 --- a/maths/fermat_little_theorem.py +++ b/maths/fermat_little_theorem.py @@ -5,13 +5,13 @@ def binary_exponentiation(a, n, mod): - + if (n == 0): return 1 - + elif (n % 2 == 1): return (binary_exponentiation(a, n - 1, mod) * a) % mod - + else: b = binary_exponentiation(a, n / 2, mod) return (b * b) % mod diff --git a/maths/greater_common_divisor.py b/maths/greater_common_divisor.py index 15adaca1fb8d..adc7811e8317 100644 --- a/maths/greater_common_divisor.py +++ b/maths/greater_common_divisor.py @@ -1,15 +1,25 @@ -# Greater Common Divisor - https://en.wikipedia.org/wiki/Greatest_common_divisor +""" +Greater Common Divisor. + +Wikipedia reference: https://en.wikipedia.org/wiki/Greatest_common_divisor +""" + + def gcd(a, b): + """Calculate Greater Common Divisor (GCD).""" return b if a == 0 else gcd(b % a, a) + def main(): + """Call GCD Function.""" try: nums = input("Enter two Integers separated by comma (,): ").split(',') - num1 = int(nums[0]); num2 = int(nums[1]) + num_1 = int(nums[0]) + num_2 = int(nums[1]) except (IndexError, UnboundLocalError, ValueError): print("Wrong Input") - print(f"gcd({num1}, {num2}) = {gcd(num1, num2)}") + print(f"gcd({num_1}, {num_2}) = {gcd(num_1, num_2)}") + if __name__ == '__main__': main() - diff --git a/maths/modular_exponential.py b/maths/modular_exponential.py index b3f4c00bd5d8..750de7cba99e 100644 --- a/maths/modular_exponential.py +++ b/maths/modular_exponential.py @@ -1,20 +1,25 @@ -def modularExponential(base, power, mod): - if power < 0: - return -1 - base %= mod - result = 1 +"""Modular Exponential.""" - while power > 0: - if power & 1: - result = (result * base) % mod - power = power >> 1 - base = (base * base) % mod - return result + +def modular_exponential(base, power, mod): + """Calculate Modular Exponential.""" + if power < 0: + return -1 + base %= mod + result = 1 + + while power > 0: + if power & 1: + result = (result * base) % mod + power = power >> 1 + base = (base * base) % mod + return result def main(): - print(modularExponential(3, 200, 13)) + """Call Modular Exponential Function.""" + print(modular_exponential(3, 200, 13)) if __name__ == '__main__': - main() + main() diff --git a/maths/segmented_sieve.py b/maths/segmented_sieve.py index 52ca6fbe601d..b15ec2480678 100644 --- a/maths/segmented_sieve.py +++ b/maths/segmented_sieve.py @@ -1,46 +1,51 @@ +"""Segmented Sieve.""" + import math + def sieve(n): + """Segmented Sieve.""" in_prime = [] start = 2 - end = int(math.sqrt(n)) # Size of every segment + end = int(math.sqrt(n)) # Size of every segment temp = [True] * (end + 1) prime = [] - - while(start <= end): - if temp[start] == True: + + while start <= end: + if temp[start] is True: in_prime.append(start) - for i in range(start*start, end+1, start): - if temp[i] == True: + for i in range(start * start, end + 1, start): + if temp[i] is True: temp[i] = False start += 1 prime += in_prime - + low = end + 1 high = low + end - 1 if high > n: high = n - - while(low <= n): - temp = [True] * (high-low+1) + + while low <= n: + temp = [True] * (high - low + 1) for each in in_prime: - + t = math.floor(low / each) * each if t < low: t += each - - for j in range(t, high+1, each): + + for j in range(t, high + 1, each): temp[j - low] = False - + for j in range(len(temp)): - if temp[j] == True: - prime.append(j+low) - + if temp[j] is True: + prime.append(j + low) + low = high + 1 high = low + end - 1 if high > n: high = n - + return prime -print(sieve(10**6)) \ No newline at end of file + +print(sieve(10**6)) diff --git a/maths/sieve_of_eratosthenes.py b/maths/sieve_of_eratosthenes.py index 26c17fa6ffec..11c123693694 100644 --- a/maths/sieve_of_eratosthenes.py +++ b/maths/sieve_of_eratosthenes.py @@ -1,24 +1,29 @@ +"""Sieve of Eratosthones.""" + import math -n = int(input("Enter n: ")) + +N = int(input("Enter n: ")) + def sieve(n): - l = [True] * (n+1) + """Sieve of Eratosthones.""" + l = [True] * (n + 1) prime = [] start = 2 - end = int(math.sqrt(n)) - while(start <= end): - if l[start] == True: + end = int(math.sqrt(n)) + while start <= end: + if l[start] is True: prime.append(start) - for i in range(start*start, n+1, start): - if l[i] == True: + for i in range(start * start, n + 1, start): + if l[i] is True: l[i] = False start += 1 - - for j in range(end+1,n+1): - if l[j] == True: + + for j in range(end + 1, n + 1): + if l[j] is True: prime.append(j) - + return prime -print(sieve(n)) - + +print(sieve(N)) diff --git a/maths/simpson_rule.py b/maths/simpson_rule.py index 091c86c17f1b..2b237d2e1a4e 100644 --- a/maths/simpson_rule.py +++ b/maths/simpson_rule.py @@ -1,49 +1,49 @@ -''' +""" Numerical integration or quadrature for a smooth function f with known values at x_i -This method is the classical approch of suming 'Equally Spaced Abscissas' +This method is the classical approch of suming 'Equally Spaced Abscissas' -method 2: +method 2: "Simpson Rule" -''' +""" from __future__ import print_function def method_2(boundary, steps): # "Simpson Rule" # int(f) = delta_x/2 * (b-a)/3*(f1 + 4f2 + 2f_3 + ... + fn) - h = (boundary[1] - boundary[0]) / steps - a = boundary[0] - b = boundary[1] - x_i = makePoints(a,b,h) - y = 0.0 - y += (h/3.0)*f(a) - cnt = 2 - for i in x_i: - y += (h/3)*(4-2*(cnt%2))*f(i) - cnt += 1 - y += (h/3.0)*f(b) - return y - -def makePoints(a,b,h): - x = a + h - while x < (b-h): - yield x - x = x + h + h = (boundary[1] - boundary[0]) / steps + a = boundary[0] + b = boundary[1] + x_i = make_points(a,b,h) + y = 0.0 + y += (h/3.0)*f(a) + cnt = 2 + for i in x_i: + y += (h/3)*(4-2*(cnt%2))*f(i) + cnt += 1 + y += (h/3.0)*f(b) + return y + +def make_points(a,b,h): + x = a + h + while x < (b-h): + yield x + x = x + h def f(x): #enter your function here - y = (x-0)*(x-0) - return y + y = (x-0)*(x-0) + return y def main(): - a = 0.0 #Lower bound of integration - b = 1.0 #Upper bound of integration - steps = 10.0 #define number of steps or resolution - boundary = [a, b] #define boundary of integration - y = method_2(boundary, steps) - print('y = {0}'.format(y)) + a = 0.0 #Lower bound of integration + b = 1.0 #Upper bound of integration + steps = 10.0 #define number of steps or resolution + boundary = [a, b] #define boundary of integration + y = method_2(boundary, steps) + print('y = {0}'.format(y)) if __name__ == '__main__': main() diff --git a/maths/trapezoidal_rule.py b/maths/trapezoidal_rule.py index 52310c1ed3b0..789f263c6991 100644 --- a/maths/trapezoidal_rule.py +++ b/maths/trapezoidal_rule.py @@ -1,12 +1,12 @@ -''' +""" Numerical integration or quadrature for a smooth function f with known values at x_i -This method is the classical approch of suming 'Equally Spaced Abscissas' +This method is the classical approch of suming 'Equally Spaced Abscissas' -method 1: +method 1: "extended trapezoidal rule" -''' +""" from __future__ import print_function def method_1(boundary, steps): @@ -15,21 +15,21 @@ def method_1(boundary, steps): h = (boundary[1] - boundary[0]) / steps a = boundary[0] b = boundary[1] - x_i = makePoints(a,b,h) - y = 0.0 + x_i = make_points(a,b,h) + y = 0.0 y += (h/2.0)*f(a) for i in x_i: #print(i) y += h*f(i) - y += (h/2.0)*f(b) - return y + y += (h/2.0)*f(b) + return y -def makePoints(a,b,h): - x = a + h +def make_points(a,b,h): + x = a + h while x < (b-h): yield x x = x + h - + def f(x): #enter your function here y = (x-0)*(x-0) return y @@ -37,7 +37,7 @@ def f(x): #enter your function here def main(): a = 0.0 #Lower bound of integration b = 1.0 #Upper bound of integration - steps = 10.0 #define number of steps or resolution + steps = 10.0 #define number of steps or resolution boundary = [a, b] #define boundary of integration y = method_1(boundary, steps) print('y = {0}'.format(y)) diff --git a/maths/volume.py b/maths/volume.py new file mode 100644 index 000000000000..171bc538f5a4 --- /dev/null +++ b/maths/volume.py @@ -0,0 +1,100 @@ +""" +Find Volumes of Various Shapes. + +Wikipedia reference: https://en.wikipedia.org/wiki/Volume +""" + +from math import pi + +PI = pi + + +def vol_cube(side_length): + """Calculate the Volume of a Cube.""" + # Cube side_length. + return float(side_length ** 3) + + +def vol_cuboid(width, height, length): + """Calculate the Volume of a Cuboid.""" + # Multiply lengths together. + return float(width * height * length) + + +def vol_cone(area_of_base, height): + """ + Calculate the Volume of a Cone. + + Wikipedia reference: https://en.wikipedia.org/wiki/Cone + volume = (1/3) * area_of_base * height + """ + return (float(1) / 3) * area_of_base * height + + +def vol_right_circ_cone(radius, height): + """ + Calculate the Volume of a Right Circular Cone. + + Wikipedia reference: https://en.wikipedia.org/wiki/Cone + volume = (1/3) * pi * radius^2 * height + """ + + import math + + return (float(1) / 3) * PI * (radius ** 2) * height + + +def vol_prism(area_of_base, height): + """ + Calculate the Volume of a Prism. + + V = Bh + Wikipedia reference: https://en.wikipedia.org/wiki/Prism_(geometry) + """ + return float(area_of_base * height) + + +def vol_pyramid(area_of_base, height): + """ + Calculate the Volume of a Prism. + + V = (1/3) * Bh + Wikipedia reference: https://en.wikipedia.org/wiki/Pyramid_(geometry) + """ + return (float(1) / 3) * area_of_base * height + + +def vol_sphere(radius): + """ + Calculate the Volume of a Sphere. + + V = (4/3) * pi * r^3 + Wikipedia reference: https://en.wikipedia.org/wiki/Sphere + """ + return (float(4) / 3) * PI * radius ** 3 + + +def vol_circular_cylinder(radius, height): + """Calculate the Volume of a Circular Cylinder. + + Wikipedia reference: https://en.wikipedia.org/wiki/Cylinder + volume = pi * radius^2 * height + """ + return PI * radius ** 2 * height + + +def main(): + """Print the Results of Various Volume Calculations.""" + print("Volumes:") + print("Cube: " + str(vol_cube(2))) # = 8 + print("Cuboid: " + str(vol_cuboid(2, 2, 2))) # = 8 + print("Cone: " + str(vol_cone(2, 2))) # ~= 1.33 + print("Right Circular Cone: " + str(vol_right_circ_cone(2, 2))) # ~= 8.38 + print("Prism: " + str(vol_prism(2, 2))) # = 4 + print("Pyramid: " + str(vol_pyramid(2, 2))) # ~= 1.33 + print("Sphere: " + str(vol_sphere(2))) # ~= 33.5 + print("Circular Cylinder: " + str(vol_circular_cylinder(2, 2))) # ~= 25.1 + + +if __name__ == "__main__": + main() From 37fbd8ca2ed404767ad8514edd6e0330c0306a58 Mon Sep 17 00:00:00 2001 From: Sanders Lin <45224617+SandersLin@users.noreply.github.com> Date: Thu, 11 Jul 2019 04:38:10 +0800 Subject: [PATCH 0103/1071] Update average_median.py (#998) added doctest, fixed TypeError: list indices must be integers or slices, not float error due to number/2 producing float as index. --- maths/average_median.py | 43 +++++++++++++++++------------------------ 1 file changed, 18 insertions(+), 25 deletions(-) diff --git a/maths/average_median.py b/maths/average_median.py index 565bb4afd112..eab0107d8da8 100644 --- a/maths/average_median.py +++ b/maths/average_median.py @@ -1,41 +1,34 @@ -""" -Find median of a list of numbers. +def median(nums): + """ + Find median of a list of numbers. -Read more about medians: - https://en.wikipedia.org/wiki/Median -""" + >>> median([0]) + 0 + >>> median([4,1,3,2]) + 2.5 + Args: + nums: List of nums -def median(nums): - """Find median of a list of numbers.""" - # Sort list + Returns: + Median. + """ sorted_list = sorted(nums) - print("List of numbers:") - print(sorted_list) - - # Is number of items in list even? + med = None if len(sorted_list) % 2 == 0: - # Find index for first middle value. - mid_index_1 = len(sorted_list) / 2 - # Find index for second middle value. - mid_index_2 = -(len(sorted_list) / 2) - 1 - # Divide middle values by 2 to get average (mean). + mid_index_1 = len(sorted_list) // 2 + mid_index_2 = (len(sorted_list) // 2) - 1 med = (sorted_list[mid_index_1] + sorted_list[mid_index_2]) / float(2) - return med # Return makes `else:` unnecessary. - # Number of items is odd. - mid_index = (len(sorted_list) - 1) / 2 - # Middle index is median. - med = sorted_list[mid_index] + else: + mid_index = (len(sorted_list) - 1) // 2 + med = sorted_list[mid_index] return med - def main(): - """Call average module to find median of a specific list of numbers.""" print("Odd number of numbers:") print(median([2, 4, 6, 8, 20, 50, 70])) print("Even number of numbers:") print(median([2, 4, 6, 8, 20, 50])) - if __name__ == '__main__': main() From b79a197e8c05251cf9443cbc5c15bb66ae23f3d8 Mon Sep 17 00:00:00 2001 From: Sanders Lin <45224617+SandersLin@users.noreply.github.com> Date: Thu, 11 Jul 2019 12:43:03 +0800 Subject: [PATCH 0104/1071] Update abs_Max.py (#997) * Update abs_Max.py fix docstring for doctest to work properly (add space after >>>) * Update abs_Max.py --- maths/abs_Max.py | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/maths/abs_Max.py b/maths/abs_Max.py index 7ff9e4d3ca09..28f631f0100e 100644 --- a/maths/abs_Max.py +++ b/maths/abs_Max.py @@ -1,8 +1,10 @@ -def absMax(x): +from typing import List + +def abs_max(x: List[int]) -> int: """ - #>>>absMax([0,5,1,11]) + >>> abs_max([0,5,1,11]) 11 - >>absMax([3,-10,-2]) + >>> abs_max([3,-10,-2]) -10 """ j =x[0] @@ -11,15 +13,20 @@ def absMax(x): j = i return j +def abs_max_sort(x): + """ + >>> abs_max_sort([0,5,1,11]) + 11 + >>> abs_max_sort([3,-10,-2]) + -10 + """ + return sorted(x,key=abs)[-1] def main(): a = [1,2,-11] - print(absMax(a)) # = -11 - + assert abs_max(a) == -11 + assert abs_max_sort(a) == -11 if __name__ == '__main__': main() -""" -print abs Max -""" From 5f991f7740f8bbb801d5442e07f6e2881e3a7e1d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alfonso=20Rodr=C3=ADguez=20Pereira?= Date: Thu, 11 Jul 2019 11:16:42 +0200 Subject: [PATCH 0105/1071] #315 Renamed all files to snake_case (#993) --- ciphers/{Atbash.py => atbash.py} | 0 ...{morse_Code_implementation.py => morse_code_implementation.py} | 0 data_structures/binary_tree/{AVL_tree.py => avl_tree.py} | 0 data_structures/binary_tree/{LCA.py => lca.py} | 0 .../linked_list/{is_Palindrome.py => is_palindrome.py} | 0 .../{Fractional_Knapsack.py => fractional_knapsack.py} | 0 ...nce_O(nlogn).py => longest_increasing_subsequence_o(nlogn).py} | 0 graphs/{BFS.py => bfs.py} | 0 graphs/{DFS.py => dfs.py} | 0 ...hted)_Graph.py => directed_and_undirected_(weighted)_graph.py} | 0 ...graph.py => eulerian_path_and_circuit_for_undirected_graph.py} | 0 maths/{abs_Max.py => abs_max.py} | 0 maths/{abs_Min.py => abs_min.py} | 0 maths/{Binary_Exponentiation.py => binary_exponentiation.py} | 0 maths/{Find_Max.py => find_max.py} | 0 maths/{Find_Min.py => find_min.py} | 0 maths/{Prime_Check.py => prime_check.py} | 0 other/{finding_Primes.py => finding_primes.py} | 0 sorts/{Bitonic_Sort.py => bitonic_sort.py} | 0 ...ansposition_parallel.py => odd_even_transposition_parallel.py} | 0 ...ngle-threaded.py => odd_even_transposition_single_threaded.py} | 0 strings/{Boyer_Moore_Search.py => boyer_moore_search.py} | 0 strings/{naive_String_Search.py => naive_string_search.py} | 0 23 files changed, 0 insertions(+), 0 deletions(-) rename ciphers/{Atbash.py => atbash.py} (100%) rename ciphers/{morse_Code_implementation.py => morse_code_implementation.py} (100%) rename data_structures/binary_tree/{AVL_tree.py => avl_tree.py} (100%) rename data_structures/binary_tree/{LCA.py => lca.py} (100%) rename data_structures/linked_list/{is_Palindrome.py => is_palindrome.py} (100%) rename dynamic_programming/{Fractional_Knapsack.py => fractional_knapsack.py} (100%) rename dynamic_programming/{longest_increasing_subsequence_O(nlogn).py => longest_increasing_subsequence_o(nlogn).py} (100%) rename graphs/{BFS.py => bfs.py} (100%) rename graphs/{DFS.py => dfs.py} (100%) rename graphs/{Directed_and_Undirected_(Weighted)_Graph.py => directed_and_undirected_(weighted)_graph.py} (100%) rename graphs/{Eulerian_path_and_circuit_for_undirected_graph.py => eulerian_path_and_circuit_for_undirected_graph.py} (100%) rename maths/{abs_Max.py => abs_max.py} (100%) rename maths/{abs_Min.py => abs_min.py} (100%) rename maths/{Binary_Exponentiation.py => binary_exponentiation.py} (100%) rename maths/{Find_Max.py => find_max.py} (100%) rename maths/{Find_Min.py => find_min.py} (100%) rename maths/{Prime_Check.py => prime_check.py} (100%) rename other/{finding_Primes.py => finding_primes.py} (100%) rename sorts/{Bitonic_Sort.py => bitonic_sort.py} (100%) rename sorts/{Odd-Even_transposition_parallel.py => odd_even_transposition_parallel.py} (100%) rename sorts/{Odd-Even_transposition_single-threaded.py => odd_even_transposition_single_threaded.py} (100%) rename strings/{Boyer_Moore_Search.py => boyer_moore_search.py} (100%) rename strings/{naive_String_Search.py => naive_string_search.py} (100%) diff --git a/ciphers/Atbash.py b/ciphers/atbash.py similarity index 100% rename from ciphers/Atbash.py rename to ciphers/atbash.py diff --git a/ciphers/morse_Code_implementation.py b/ciphers/morse_code_implementation.py similarity index 100% rename from ciphers/morse_Code_implementation.py rename to ciphers/morse_code_implementation.py diff --git a/data_structures/binary_tree/AVL_tree.py b/data_structures/binary_tree/avl_tree.py similarity index 100% rename from data_structures/binary_tree/AVL_tree.py rename to data_structures/binary_tree/avl_tree.py diff --git a/data_structures/binary_tree/LCA.py b/data_structures/binary_tree/lca.py similarity index 100% rename from data_structures/binary_tree/LCA.py rename to data_structures/binary_tree/lca.py diff --git a/data_structures/linked_list/is_Palindrome.py b/data_structures/linked_list/is_palindrome.py similarity index 100% rename from data_structures/linked_list/is_Palindrome.py rename to data_structures/linked_list/is_palindrome.py diff --git a/dynamic_programming/Fractional_Knapsack.py b/dynamic_programming/fractional_knapsack.py similarity index 100% rename from dynamic_programming/Fractional_Knapsack.py rename to dynamic_programming/fractional_knapsack.py diff --git a/dynamic_programming/longest_increasing_subsequence_O(nlogn).py b/dynamic_programming/longest_increasing_subsequence_o(nlogn).py similarity index 100% rename from dynamic_programming/longest_increasing_subsequence_O(nlogn).py rename to dynamic_programming/longest_increasing_subsequence_o(nlogn).py diff --git a/graphs/BFS.py b/graphs/bfs.py similarity index 100% rename from graphs/BFS.py rename to graphs/bfs.py diff --git a/graphs/DFS.py b/graphs/dfs.py similarity index 100% rename from graphs/DFS.py rename to graphs/dfs.py diff --git a/graphs/Directed_and_Undirected_(Weighted)_Graph.py b/graphs/directed_and_undirected_(weighted)_graph.py similarity index 100% rename from graphs/Directed_and_Undirected_(Weighted)_Graph.py rename to graphs/directed_and_undirected_(weighted)_graph.py diff --git a/graphs/Eulerian_path_and_circuit_for_undirected_graph.py b/graphs/eulerian_path_and_circuit_for_undirected_graph.py similarity index 100% rename from graphs/Eulerian_path_and_circuit_for_undirected_graph.py rename to graphs/eulerian_path_and_circuit_for_undirected_graph.py diff --git a/maths/abs_Max.py b/maths/abs_max.py similarity index 100% rename from maths/abs_Max.py rename to maths/abs_max.py diff --git a/maths/abs_Min.py b/maths/abs_min.py similarity index 100% rename from maths/abs_Min.py rename to maths/abs_min.py diff --git a/maths/Binary_Exponentiation.py b/maths/binary_exponentiation.py similarity index 100% rename from maths/Binary_Exponentiation.py rename to maths/binary_exponentiation.py diff --git a/maths/Find_Max.py b/maths/find_max.py similarity index 100% rename from maths/Find_Max.py rename to maths/find_max.py diff --git a/maths/Find_Min.py b/maths/find_min.py similarity index 100% rename from maths/Find_Min.py rename to maths/find_min.py diff --git a/maths/Prime_Check.py b/maths/prime_check.py similarity index 100% rename from maths/Prime_Check.py rename to maths/prime_check.py diff --git a/other/finding_Primes.py b/other/finding_primes.py similarity index 100% rename from other/finding_Primes.py rename to other/finding_primes.py diff --git a/sorts/Bitonic_Sort.py b/sorts/bitonic_sort.py similarity index 100% rename from sorts/Bitonic_Sort.py rename to sorts/bitonic_sort.py diff --git a/sorts/Odd-Even_transposition_parallel.py b/sorts/odd_even_transposition_parallel.py similarity index 100% rename from sorts/Odd-Even_transposition_parallel.py rename to sorts/odd_even_transposition_parallel.py diff --git a/sorts/Odd-Even_transposition_single-threaded.py b/sorts/odd_even_transposition_single_threaded.py similarity index 100% rename from sorts/Odd-Even_transposition_single-threaded.py rename to sorts/odd_even_transposition_single_threaded.py diff --git a/strings/Boyer_Moore_Search.py b/strings/boyer_moore_search.py similarity index 100% rename from strings/Boyer_Moore_Search.py rename to strings/boyer_moore_search.py diff --git a/strings/naive_String_Search.py b/strings/naive_string_search.py similarity index 100% rename from strings/naive_String_Search.py rename to strings/naive_string_search.py From c2f2fa8b231999905564006cc3dd2db55de5ecc7 Mon Sep 17 00:00:00 2001 From: Sanders Lin <45224617+SandersLin@users.noreply.github.com> Date: Fri, 12 Jul 2019 00:20:41 +0800 Subject: [PATCH 0106/1071] Update abs_Min.py (#1004) * Update abs_Min.py * Create __init__.py * Rename abs_Min.py to abs_min.py * Update abs_min.py --- maths/__init__.py | 1 + maths/abs_min.py | 5 +++-- 2 files changed, 4 insertions(+), 2 deletions(-) create mode 100644 maths/__init__.py diff --git a/maths/__init__.py b/maths/__init__.py new file mode 100644 index 000000000000..8b137891791f --- /dev/null +++ b/maths/__init__.py @@ -0,0 +1 @@ + diff --git a/maths/abs_min.py b/maths/abs_min.py index 67d510551907..d546196aa1b5 100644 --- a/maths/abs_min.py +++ b/maths/abs_min.py @@ -1,4 +1,5 @@ -from Maths.abs import absVal +from abs import abs_val + def absMin(x): """ # >>>absMin([0,5,1,11]) @@ -8,7 +9,7 @@ def absMin(x): """ j = x[0] for i in x: - if absVal(i) < absVal(j): + if abs_val(i) < abs_val(j): j = i return j From f2eb965604ef704d1ffc1a79acaba8b8c58e204b Mon Sep 17 00:00:00 2001 From: FrogBattle <44649323+FrogBattle@users.noreply.github.com> Date: Thu, 11 Jul 2019 17:21:48 +0100 Subject: [PATCH 0107/1071] Update ~script.py (#990) Changing the boolean expression avoids the use of a continue statement. This way the code becomes easier/faster to compute on lower level and it has a better coding style. --- ~script.py | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/~script.py b/~script.py index c44f3436fcec..1fbb1e838c62 100644 --- a/~script.py +++ b/~script.py @@ -20,16 +20,15 @@ def _markdown(parent, ignores, ignores_ext, depth): for i in os.listdir(parent): full = os.path.join(parent, i) name, ext = os.path.splitext(i) - if i in ignores or ext in ignores_ext: - continue - if os.path.isfile(full): - # generate list - pre = parent.replace("./", "").replace(" ", "%20") - # replace all spaces to safe URL - child = i.replace(" ", "%20") - files.append((pre, child, name)) - else: - dirs.append(i) + if i not in ignores and ext not in ignores_ext: + if os.path.isfile(full): + # generate list + pre = parent.replace("./", "").replace(" ", "%20") + # replace all spaces to safe URL + child = i.replace(" ", "%20") + files.append((pre, child, name)) + else: + dirs.append(i) # Sort files files.sort(key=lambda e: e[2].lower()) for f in files: From 1dc9ec8fb2c51372800762b8e3f734eb93ec1814 Mon Sep 17 00:00:00 2001 From: obelisk0114 Date: Fri, 12 Jul 2019 08:16:14 -0700 Subject: [PATCH 0108/1071] Update Bucket Sort time complexity analysis (#918) --- sorts/bucket_sort.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/sorts/bucket_sort.py b/sorts/bucket_sort.py index 5c4a71513ed3..0678b1194657 100644 --- a/sorts/bucket_sort.py +++ b/sorts/bucket_sort.py @@ -17,7 +17,12 @@ # number of buckets. # Time Complexity of Solution: -# Best Case O(n); Average Case O(n); Worst Case O(n) +# Worst case scenario occurs when all the elements are placed in a single bucket. The overall performance +# would then be dominated by the algorithm used to sort each bucket. In this case, O(n log n), because of TimSort +# +# Average Case O(n + (n^2)/k + k), where k is the number of buckets +# +# If k = O(n), time complexity is O(n) DEFAULT_BUCKET_SIZE = 5 From 1e0b33d3dd7eda32a97fe73df09df20e2004eb98 Mon Sep 17 00:00:00 2001 From: Sanders Lin <45224617+SandersLin@users.noreply.github.com> Date: Sat, 13 Jul 2019 15:04:43 +0800 Subject: [PATCH 0109/1071] Update 3n+1.py (#996) * Update 3n+1.py Made variable names more meaningful and removed nested functions. * Update 3n+1.py * Update 3n+1.py * Update 3n+1.py --- maths/3n+1.py | 41 ++++++++++++++++++++++++++--------------- 1 file changed, 26 insertions(+), 15 deletions(-) diff --git a/maths/3n+1.py b/maths/3n+1.py index 6424fe0d8f15..d6c14ff0f47d 100644 --- a/maths/3n+1.py +++ b/maths/3n+1.py @@ -1,19 +1,30 @@ -def main(): - def n31(a):# a = initial number - c = 0 - l = [a] - while a != 1: - if a % 2 == 0:#if even divide it by 2 - a = a // 2 - elif a % 2 == 1:#if odd 3n+1 - a = 3*a +1 - c += 1#counter - l += [a] +from typing import Tuple, List + +def n31(a: int) -> Tuple[List[int], int]: + """ + Returns the Collatz sequence and its length of any postiver integer. + >>> n31(4) + ([4, 2, 1], 3) + """ - return l , c - print(n31(43)) - print(n31(98)[0][-1])# = a - print("It took {0} steps.".format(n31(13)[1]))#optional finish + if not isinstance(a, int): + raise TypeError('Must be int, not {0}'.format(type(a).__name__)) + if a < 1: + raise ValueError('Given integer must be greater than 1, not {0}'.format(a)) + + path = [a] + while a != 1: + if a % 2 == 0: + a = a // 2 + else: + a = 3*a +1 + path += [a] + return path, len(path) + +def main(): + num = 4 + path , length = n31(num) + print("The Collatz sequence of {0} took {1} steps. \nPath: {2}".format(num,length, path)) if __name__ == '__main__': main() From 7a6ebb85a2e8eda43131803f2104cdbba07cb164 Mon Sep 17 00:00:00 2001 From: Sanders Lin <45224617+SandersLin@users.noreply.github.com> Date: Sat, 13 Jul 2019 15:10:02 +0800 Subject: [PATCH 0110/1071] Update edit_distance.py (#1001) added bottom up method. --- dynamic_programming/edit_distance.py | 34 ++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/dynamic_programming/edit_distance.py b/dynamic_programming/edit_distance.py index 335e5196ed53..b71f80d68935 100644 --- a/dynamic_programming/edit_distance.py +++ b/dynamic_programming/edit_distance.py @@ -52,6 +52,35 @@ def solve(self, A, B): return self.__solveDP(len(A)-1, len(B)-1) + +def min_distance_bottom_up(word1: str, word2: str) -> int: + """ + >>> min_distance_bottom_up("intention", "execution") + 5 + >>> min_distance_bottom_up("intention", "") + 9 + >>> min_distance_bottom_up("", "") + 0 + """ + m = len(word1) + n = len(word2) + dp = [[0 for _ in range(n+1) ] for _ in range(m+1)] + for i in range(m+1): + for j in range(n+1): + + if i == 0: #first string is empty + dp[i][j] = j + elif j == 0: #second string is empty + dp[i][j] = i + elif word1[i-1] == word2[j-1]: #last character of both substing is equal + dp[i][j] = dp[i-1][j-1] + else: + insert = dp[i][j-1] + delete = dp[i-1][j] + replace = dp[i-1][j-1] + dp[i][j] = 1 + min(insert, delete, replace) + return dp[m][n] + if __name__ == '__main__': try: raw_input # Python 2 @@ -71,5 +100,10 @@ def solve(self, A, B): print() print("The minimum Edit Distance is: %d" % (solver.solve(S1, S2))) + print("The minimum Edit Distance is: %d" % (min_distance_bottom_up(S1, S2))) print() print("*************** End of Testing Edit Distance DP Algorithm ***************") + + + + From 7271c0d64acf4881636f08c4c2dcb00cbe757798 Mon Sep 17 00:00:00 2001 From: Sanders Lin <45224617+SandersLin@users.noreply.github.com> Date: Sat, 13 Jul 2019 15:12:54 +0800 Subject: [PATCH 0111/1071] Update rod_cutting.py (#995) * Update rod_cutting.py A hopefully clearer implementation without dependence on global variables. * Update rod_cutting.py added doctests * Update rod_cutting.py * Update rod_cutting.py --- dynamic_programming/rod_cutting.py | 115 ++++++++++++++--------------- 1 file changed, 57 insertions(+), 58 deletions(-) diff --git a/dynamic_programming/rod_cutting.py b/dynamic_programming/rod_cutting.py index 34350cb8202b..c3111dcfc8a1 100644 --- a/dynamic_programming/rod_cutting.py +++ b/dynamic_programming/rod_cutting.py @@ -1,58 +1,57 @@ -### PROBLEM ### -""" -We are given a rod of length n and we are given the array of prices, also of -length n. This array contains the price for selling a rod at a certain length. -For example, prices[5] shows the price we can sell a rod of length 5. -Generalising, prices[x] shows the price a rod of length x can be sold. -We are tasked to find the optimal solution to sell the given rod. -""" - -### SOLUTION ### -""" -Profit(n) = max(1 m): - m = yesCut[i] - - solutions[n] = m - return m - - - -### EXAMPLE ### -length = 5 -#The first price, 0, is for when we have no rod. -prices = [0, 1, 3, 7, 9, 11, 13, 17, 21, 21, 30] -solutions = [-1 for x in range(length+1)] - -print(CutRod(length)) +from typing import List + +def rod_cutting(prices: List[int],length: int) -> int: + """ + Given a rod of length n and array of prices that indicate price at each length. + Determine the maximum value obtainable by cutting up the rod and selling the pieces + + >>> rod_cutting([1,5,8,9],4) + 10 + >>> rod_cutting([1,1,1],3) + 3 + >>> rod_cutting([1,2,3], -1) + Traceback (most recent call last): + ValueError: Given integer must be greater than 1, not -1 + >>> rod_cutting([1,2,3], 3.2) + Traceback (most recent call last): + TypeError: Must be int, not float + >>> rod_cutting([], 3) + Traceback (most recent call last): + AssertionError: prices list is shorted than length: 3 + + + + Args: + prices: list indicating price at each length, where prices[0] = 0 indicating rod of zero length has no value + length: length of rod + + Returns: + Maximum revenue attainable by cutting up the rod in any way. + """ + + prices.insert(0, 0) + if not isinstance(length, int): + raise TypeError('Must be int, not {0}'.format(type(length).__name__)) + if length < 0: + raise ValueError('Given integer must be greater than 1, not {0}'.format(length)) + assert len(prices) - 1 >= length, "prices list is shorted than length: {0}".format(length) + + return rod_cutting_recursive(prices, length) + +def rod_cutting_recursive(prices: List[int],length: int) -> int: + #base case + if length == 0: + return 0 + value = float('-inf') + for firstCutLocation in range(1,length+1): + value = max(value, prices[firstCutLocation]+rod_cutting_recursive(prices,length - firstCutLocation)) + return value + + +def main(): + assert rod_cutting([1,5,8,9,10,17,17,20,24,30],10) == 30 + # print(rod_cutting([],0)) + +if __name__ == '__main__': + main() + From d72586c5f4164ed2b518d6badea9d9a89e756689 Mon Sep 17 00:00:00 2001 From: Hector S Date: Sat, 13 Jul 2019 15:50:37 -0400 Subject: [PATCH 0112/1071] Updated ~script.py per #978 (#1013) * Added print function into matrix_multiplication_addition.py and removed blank space in data_structures/binary tree directory * Removed .vs/ folder per #893 * Rename matrix_multiplication_addition.py to matrix_operation.py * Moved and renamed ~script.py to scripts/build_directory_md.py Updated DIRECTORY.MD file * Modified .travis.yml per suggestion in #1013 * Fixed typo per suggestions in #1013 --- .travis.yml | 2 +- DIRECTORY.md | 123 +++++++++++--------- ~script.py => scripts/build_directory_md.py | 4 +- 3 files changed, 73 insertions(+), 56 deletions(-) rename ~script.py => scripts/build_directory_md.py (97%) diff --git a/.travis.yml b/.travis.yml index 0e35fd084268..9afc0c93a037 100644 --- a/.travis.yml +++ b/.travis.yml @@ -32,5 +32,5 @@ script: traversals after_success: - - python ./~script.py + - python scripts/build_directory_md.py - cat DIRECTORY.md diff --git a/DIRECTORY.md b/DIRECTORY.md index befd634c1eb0..66128228abc3 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -4,13 +4,17 @@ * [lu decomposition](https://github.com/TheAlgorithms/Python/blob/master/arithmetic_analysis/lu_decomposition.py) * [newton method](https://github.com/TheAlgorithms/Python/blob/master/arithmetic_analysis/newton_method.py) * [newton raphson method](https://github.com/TheAlgorithms/Python/blob/master/arithmetic_analysis/newton_raphson_method.py) -## Binary Tree - * [basic binary tree](https://github.com/TheAlgorithms/Python/blob/master/binary_tree/basic_binary_tree.py) +## Backtracking + * [all permutations](https://github.com/TheAlgorithms/Python/blob/master/backtracking/all_permutations.py) + * [all subsequences](https://github.com/TheAlgorithms/Python/blob/master/backtracking/all_subsequences.py) + * [minimax](https://github.com/TheAlgorithms/Python/blob/master/backtracking/minimax.py) + * [n queens](https://github.com/TheAlgorithms/Python/blob/master/backtracking/n_queens.py) + * [sum of subsets](https://github.com/TheAlgorithms/Python/blob/master/backtracking/sum_of_subsets.py) ## Boolean Algebra * [quine mc cluskey](https://github.com/TheAlgorithms/Python/blob/master/boolean_algebra/quine_mc_cluskey.py) ## Ciphers * [affine cipher](https://github.com/TheAlgorithms/Python/blob/master/ciphers/affine_cipher.py) - * [Atbash](https://github.com/TheAlgorithms/Python/blob/master/ciphers/Atbash.py) + * [atbash](https://github.com/TheAlgorithms/Python/blob/master/ciphers/atbash.py) * [base16](https://github.com/TheAlgorithms/Python/blob/master/ciphers/base16.py) * [base32](https://github.com/TheAlgorithms/Python/blob/master/ciphers/base32.py) * [base64 cipher](https://github.com/TheAlgorithms/Python/blob/master/ciphers/base64_cipher.py) @@ -20,7 +24,7 @@ * [cryptomath module](https://github.com/TheAlgorithms/Python/blob/master/ciphers/cryptomath_module.py) * [elgamal key generator](https://github.com/TheAlgorithms/Python/blob/master/ciphers/elgamal_key_generator.py) * [hill cipher](https://github.com/TheAlgorithms/Python/blob/master/ciphers/hill_cipher.py) - * [morse Code implementation](https://github.com/TheAlgorithms/Python/blob/master/ciphers/morse_Code_implementation.py) + * [morse code implementation](https://github.com/TheAlgorithms/Python/blob/master/ciphers/morse_code_implementation.py) * [onepad cipher](https://github.com/TheAlgorithms/Python/blob/master/ciphers/onepad_cipher.py) * [playfair cipher](https://github.com/TheAlgorithms/Python/blob/master/ciphers/playfair_cipher.py) * [prehistoric men](https://github.com/TheAlgorithms/Python/blob/master/ciphers/prehistoric_men.txt) @@ -36,17 +40,21 @@ * [xor cipher](https://github.com/TheAlgorithms/Python/blob/master/ciphers/xor_cipher.py) ## Compression * [huffman](https://github.com/TheAlgorithms/Python/blob/master/compression/huffman.py) -## Compression Analysis - * [psnr](https://github.com/TheAlgorithms/Python/blob/master/compression_analysis/psnr.py) + * [peak signal to noise ratio](https://github.com/TheAlgorithms/Python/blob/master/compression/peak_signal_to_noise_ratio.py) + * Image Data +## Conversions + * [decimal to binary](https://github.com/TheAlgorithms/Python/blob/master/conversions/decimal_to_binary.py) + * [decimal to hexadecimal](https://github.com/TheAlgorithms/Python/blob/master/conversions/decimal_to_hexadecimal.py) + * [decimal to octal](https://github.com/TheAlgorithms/Python/blob/master/conversions/decimal_to_octal.py) ## Data Structures - * [arrays](https://github.com/TheAlgorithms/Python/blob/master/data_structures/arrays.py) - * [avl](https://github.com/TheAlgorithms/Python/blob/master/data_structures/avl.py) - * [LCA](https://github.com/TheAlgorithms/Python/blob/master/data_structures/LCA.py) * Binary Tree - * [AVL tree](https://github.com/TheAlgorithms/Python/blob/master/data_structures/binary_tree/AVL_tree.py) + * [avl tree](https://github.com/TheAlgorithms/Python/blob/master/data_structures/binary_tree/avl_tree.py) + * [basic binary tree](https://github.com/TheAlgorithms/Python/blob/master/data_structures/binary_tree/basic_binary_tree.py) * [binary search tree](https://github.com/TheAlgorithms/Python/blob/master/data_structures/binary_tree/binary_search_tree.py) * [fenwick tree](https://github.com/TheAlgorithms/Python/blob/master/data_structures/binary_tree/fenwick_tree.py) * [lazy segment tree](https://github.com/TheAlgorithms/Python/blob/master/data_structures/binary_tree/lazy_segment_tree.py) + * [lca](https://github.com/TheAlgorithms/Python/blob/master/data_structures/binary_tree/lca.py) + * [red black tree](https://github.com/TheAlgorithms/Python/blob/master/data_structures/binary_tree/red_black_tree.py) * [segment tree](https://github.com/TheAlgorithms/Python/blob/master/data_structures/binary_tree/segment_tree.py) * [treap](https://github.com/TheAlgorithms/Python/blob/master/data_structures/binary_tree/treap.py) * Hashing @@ -60,9 +68,9 @@ * [heap](https://github.com/TheAlgorithms/Python/blob/master/data_structures/heap/heap.py) * Linked List * [doubly linked list](https://github.com/TheAlgorithms/Python/blob/master/data_structures/linked_list/doubly_linked_list.py) - * [is Palindrome](https://github.com/TheAlgorithms/Python/blob/master/data_structures/linked_list/is_Palindrome.py) + * [is palindrome](https://github.com/TheAlgorithms/Python/blob/master/data_structures/linked_list/is_palindrome.py) * [singly linked list](https://github.com/TheAlgorithms/Python/blob/master/data_structures/linked_list/singly_linked_list.py) - * [swapNodes](https://github.com/TheAlgorithms/Python/blob/master/data_structures/linked_list/swapNodes.py) + * [swap nodes](https://github.com/TheAlgorithms/Python/blob/master/data_structures/linked_list/swap_nodes.py) * Queue * [double ended queue](https://github.com/TheAlgorithms/Python/blob/master/data_structures/queue/double_ended_queue.py) * [queue on list](https://github.com/TheAlgorithms/Python/blob/master/data_structures/queue/queue_on_list.py) @@ -71,18 +79,24 @@ * [balanced parentheses](https://github.com/TheAlgorithms/Python/blob/master/data_structures/stacks/balanced_parentheses.py) * [infix to postfix conversion](https://github.com/TheAlgorithms/Python/blob/master/data_structures/stacks/infix_to_postfix_conversion.py) * [infix to prefix conversion](https://github.com/TheAlgorithms/Python/blob/master/data_structures/stacks/infix_to_prefix_conversion.py) - * [next](https://github.com/TheAlgorithms/Python/blob/master/data_structures/stacks/next.py) + * [next greater element](https://github.com/TheAlgorithms/Python/blob/master/data_structures/stacks/next_greater_element.py) * [postfix evaluation](https://github.com/TheAlgorithms/Python/blob/master/data_structures/stacks/postfix_evaluation.py) * [stack](https://github.com/TheAlgorithms/Python/blob/master/data_structures/stacks/stack.py) * [stock span problem](https://github.com/TheAlgorithms/Python/blob/master/data_structures/stacks/stock_span_problem.py) * Trie * [trie](https://github.com/TheAlgorithms/Python/blob/master/data_structures/trie/trie.py) - * Union Find - * [tests union find](https://github.com/TheAlgorithms/Python/blob/master/data_structures/union_find/tests_union_find.py) - * [union find](https://github.com/TheAlgorithms/Python/blob/master/data_structures/union_find/union_find.py) ## Digital Image Processing + * Edge Detection + * [canny](https://github.com/TheAlgorithms/Python/blob/master/digital_image_processing/edge_detection/canny.py) * Filters + * [convolve](https://github.com/TheAlgorithms/Python/blob/master/digital_image_processing/filters/convolve.py) + * [gaussian filter](https://github.com/TheAlgorithms/Python/blob/master/digital_image_processing/filters/gaussian_filter.py) * [median filter](https://github.com/TheAlgorithms/Python/blob/master/digital_image_processing/filters/median_filter.py) + * [sobel filter](https://github.com/TheAlgorithms/Python/blob/master/digital_image_processing/filters/sobel_filter.py) + * Image Data +## Divide And Conquer + * [closest pair of points](https://github.com/TheAlgorithms/Python/blob/master/divide_and_conquer/closest_pair_of_points.py) + * [max subarray sum](https://github.com/TheAlgorithms/Python/blob/master/divide_and_conquer/max_subarray_sum.py) ## Dynamic Programming * [abbreviation](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/abbreviation.py) * [bitmask](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/bitmask.py) @@ -91,19 +105,20 @@ * [fast fibonacci](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/fast_fibonacci.py) * [fibonacci](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/fibonacci.py) * [floyd warshall](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/floyd_warshall.py) - * [Fractional Knapsack](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/Fractional_Knapsack.py) + * [fractional knapsack](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/fractional_knapsack.py) * [integer partition](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/integer_partition.py) * [k means clustering tensorflow](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/k_means_clustering_tensorflow.py) * [knapsack](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/knapsack.py) * [longest common subsequence](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/longest_common_subsequence.py) * [longest increasing subsequence](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/longest_increasing_subsequence.py) - * [longest increasing subsequence O(nlogn)](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/longest_increasing_subsequence_O(nlogn).py) + * [longest increasing subsequence o(nlogn)](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/longest_increasing_subsequence_o(nlogn).py) * [longest sub array](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/longest_sub_array.py) * [matrix chain order](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/matrix_chain_order.py) * [max sub array](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/max_sub_array.py) * [minimum partition](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/minimum_partition.py) * [rod cutting](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/rod_cutting.py) * [subset generation](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/subset_generation.py) + * [sum of subset](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/sum_of_subset.py) ## File Transfer Protocol * [ftp client server](https://github.com/TheAlgorithms/Python/blob/master/file_transfer_protocol/ftp_client_server.py) * [ftp send receive](https://github.com/TheAlgorithms/Python/blob/master/file_transfer_protocol/ftp_send_receive.py) @@ -112,19 +127,19 @@ * [articulation points](https://github.com/TheAlgorithms/Python/blob/master/graphs/articulation_points.py) * [basic graphs](https://github.com/TheAlgorithms/Python/blob/master/graphs/basic_graphs.py) * [bellman ford](https://github.com/TheAlgorithms/Python/blob/master/graphs/bellman_ford.py) - * [BFS](https://github.com/TheAlgorithms/Python/blob/master/graphs/BFS.py) + * [bfs](https://github.com/TheAlgorithms/Python/blob/master/graphs/bfs.py) * [bfs shortest path](https://github.com/TheAlgorithms/Python/blob/master/graphs/bfs_shortest_path.py) * [breadth first search](https://github.com/TheAlgorithms/Python/blob/master/graphs/breadth_first_search.py) * [check bipartite graph bfs](https://github.com/TheAlgorithms/Python/blob/master/graphs/check_bipartite_graph_bfs.py) * [check bipartite graph dfs](https://github.com/TheAlgorithms/Python/blob/master/graphs/check_bipartite_graph_dfs.py) * [depth first search](https://github.com/TheAlgorithms/Python/blob/master/graphs/depth_first_search.py) - * [DFS](https://github.com/TheAlgorithms/Python/blob/master/graphs/DFS.py) + * [dfs](https://github.com/TheAlgorithms/Python/blob/master/graphs/dfs.py) * [dijkstra](https://github.com/TheAlgorithms/Python/blob/master/graphs/dijkstra.py) * [dijkstra 2](https://github.com/TheAlgorithms/Python/blob/master/graphs/dijkstra_2.py) * [dijkstra algorithm](https://github.com/TheAlgorithms/Python/blob/master/graphs/dijkstra_algorithm.py) - * [Directed and Undirected (Weighted) Graph](https://github.com/TheAlgorithms/Python/blob/master/graphs/Directed_and_Undirected_(Weighted)_Graph.py) + * [directed and undirected (weighted) graph](https://github.com/TheAlgorithms/Python/blob/master/graphs/directed_and_undirected_(weighted)_graph.py) * [edmonds karp multiple source and sink](https://github.com/TheAlgorithms/Python/blob/master/graphs/edmonds_karp_multiple_source_and_sink.py) - * [Eulerian path and circuit for undirected graph](https://github.com/TheAlgorithms/Python/blob/master/graphs/Eulerian_path_and_circuit_for_undirected_graph.py) + * [eulerian path and circuit for undirected graph](https://github.com/TheAlgorithms/Python/blob/master/graphs/eulerian_path_and_circuit_for_undirected_graph.py) * [even tree](https://github.com/TheAlgorithms/Python/blob/master/graphs/even_tree.py) * [finding bridges](https://github.com/TheAlgorithms/Python/blob/master/graphs/finding_bridges.py) * [floyd warshall](https://github.com/TheAlgorithms/Python/blob/master/graphs/floyd_warshall.py) @@ -141,6 +156,7 @@ * [tarjans scc](https://github.com/TheAlgorithms/Python/blob/master/graphs/tarjans_scc.py) ## Hashes * [chaos machine](https://github.com/TheAlgorithms/Python/blob/master/hashes/chaos_machine.py) + * [enigma machine](https://github.com/TheAlgorithms/Python/blob/master/hashes/enigma_machine.py) * [md5](https://github.com/TheAlgorithms/Python/blob/master/hashes/md5.py) * [sha1](https://github.com/TheAlgorithms/Python/blob/master/hashes/sha1.py) ## Linear Algebra Python @@ -151,24 +167,26 @@ * [decision tree](https://github.com/TheAlgorithms/Python/blob/master/machine_learning/decision_tree.py) * [gradient descent](https://github.com/TheAlgorithms/Python/blob/master/machine_learning/gradient_descent.py) * [k means clust](https://github.com/TheAlgorithms/Python/blob/master/machine_learning/k_means_clust.py) + * [knn sklearn](https://github.com/TheAlgorithms/Python/blob/master/machine_learning/knn_sklearn.py) * [linear regression](https://github.com/TheAlgorithms/Python/blob/master/machine_learning/linear_regression.py) * [logistic regression](https://github.com/TheAlgorithms/Python/blob/master/machine_learning/logistic_regression.py) * [perceptron](https://github.com/TheAlgorithms/Python/blob/master/machine_learning/perceptron.py) * [scoring functions](https://github.com/TheAlgorithms/Python/blob/master/machine_learning/scoring_functions.py) * Random Forest Classification - * [random forest classification](https://github.com/TheAlgorithms/Python/blob/master/machine_learning/Random%20Forest%20Classification/random_forest_classification.py) - * [Social Network Ads](https://github.com/TheAlgorithms/Python/blob/master/machine_learning/Random%20Forest%20Classification/Social_Network_Ads.csv) + * [random forest classification](https://github.com/TheAlgorithms/Python/blob/master/machine_learning/random_forest_classification/random_forest_classification.py) + * [Social Network Ads](https://github.com/TheAlgorithms/Python/blob/master/machine_learning/random_forest_classification/Social_Network_Ads.csv) * Random Forest Regression - * [Position Salaries](https://github.com/TheAlgorithms/Python/blob/master/machine_learning/Random%20Forest%20Regression/Position_Salaries.csv) - * [random forest regression](https://github.com/TheAlgorithms/Python/blob/master/machine_learning/Random%20Forest%20Regression/random_forest_regression.py) + * [Position Salaries](https://github.com/TheAlgorithms/Python/blob/master/machine_learning/random_forest_regression/Position_Salaries.csv) + * [random forest regression](https://github.com/TheAlgorithms/Python/blob/master/machine_learning/random_forest_regression/random_forest_regression.py) ## Maths * [3n+1](https://github.com/TheAlgorithms/Python/blob/master/maths/3n+1.py) * [abs](https://github.com/TheAlgorithms/Python/blob/master/maths/abs.py) - * [abs Max](https://github.com/TheAlgorithms/Python/blob/master/maths/abs_Max.py) - * [abs Min](https://github.com/TheAlgorithms/Python/blob/master/maths/abs_Min.py) - * [average](https://github.com/TheAlgorithms/Python/blob/master/maths/average.py) + * [abs max](https://github.com/TheAlgorithms/Python/blob/master/maths/abs_max.py) + * [abs min](https://github.com/TheAlgorithms/Python/blob/master/maths/abs_min.py) + * [average mean](https://github.com/TheAlgorithms/Python/blob/master/maths/average_mean.py) + * [average median](https://github.com/TheAlgorithms/Python/blob/master/maths/average_median.py) * [basic maths](https://github.com/TheAlgorithms/Python/blob/master/maths/basic_maths.py) - * [Binary Exponentiation](https://github.com/TheAlgorithms/Python/blob/master/maths/Binary_Exponentiation.py) + * [binary exponentiation](https://github.com/TheAlgorithms/Python/blob/master/maths/binary_exponentiation.py) * [extended euclidean algorithm](https://github.com/TheAlgorithms/Python/blob/master/maths/extended_euclidean_algorithm.py) * [factorial python](https://github.com/TheAlgorithms/Python/blob/master/maths/factorial_python.py) * [factorial recursive](https://github.com/TheAlgorithms/Python/blob/master/maths/factorial_recursive.py) @@ -176,29 +194,27 @@ * [fibonacci](https://github.com/TheAlgorithms/Python/blob/master/maths/fibonacci.py) * [fibonacci sequence recursion](https://github.com/TheAlgorithms/Python/blob/master/maths/fibonacci_sequence_recursion.py) * [find lcm](https://github.com/TheAlgorithms/Python/blob/master/maths/find_lcm.py) - * [Find Max](https://github.com/TheAlgorithms/Python/blob/master/maths/Find_Max.py) - * [Find Min](https://github.com/TheAlgorithms/Python/blob/master/maths/Find_Min.py) + * [find max](https://github.com/TheAlgorithms/Python/blob/master/maths/find_max.py) + * [find min](https://github.com/TheAlgorithms/Python/blob/master/maths/find_min.py) * [greater common divisor](https://github.com/TheAlgorithms/Python/blob/master/maths/greater_common_divisor.py) - * [Hanoi](https://github.com/TheAlgorithms/Python/blob/master/maths/Hanoi.py) - * [lucasSeries](https://github.com/TheAlgorithms/Python/blob/master/maths/lucasSeries.py) + * [lucas series](https://github.com/TheAlgorithms/Python/blob/master/maths/lucas%20series.py) * [modular exponential](https://github.com/TheAlgorithms/Python/blob/master/maths/modular_exponential.py) * [newton raphson](https://github.com/TheAlgorithms/Python/blob/master/maths/newton_raphson.py) - * [Prime Check](https://github.com/TheAlgorithms/Python/blob/master/maths/Prime_Check.py) + * [prime check](https://github.com/TheAlgorithms/Python/blob/master/maths/prime_check.py) * [segmented sieve](https://github.com/TheAlgorithms/Python/blob/master/maths/segmented_sieve.py) * [sieve of eratosthenes](https://github.com/TheAlgorithms/Python/blob/master/maths/sieve_of_eratosthenes.py) * [simpson rule](https://github.com/TheAlgorithms/Python/blob/master/maths/simpson_rule.py) * [trapezoidal rule](https://github.com/TheAlgorithms/Python/blob/master/maths/trapezoidal_rule.py) - * Tests - * [test fibonacci](https://github.com/TheAlgorithms/Python/blob/master/maths/tests/test_fibonacci.py) + * [volume](https://github.com/TheAlgorithms/Python/blob/master/maths/volume.py) ## Matrix * [matrix operation](https://github.com/TheAlgorithms/Python/blob/master/matrix/matrix_operation.py) * [searching in sorted matrix](https://github.com/TheAlgorithms/Python/blob/master/matrix/searching_in_sorted_matrix.py) - * [spiralPrint](https://github.com/TheAlgorithms/Python/blob/master/matrix/spiralPrint.py) + * [spiral print](https://github.com/TheAlgorithms/Python/blob/master/matrix/spiral_print.py) ## Networking Flow * [ford fulkerson](https://github.com/TheAlgorithms/Python/blob/master/networking_flow/ford_fulkerson.py) * [minimum cut](https://github.com/TheAlgorithms/Python/blob/master/networking_flow/minimum_cut.py) ## Neural Network - * [bpnn](https://github.com/TheAlgorithms/Python/blob/master/neural_network/bpnn.py) + * [back propagation neural network](https://github.com/TheAlgorithms/Python/blob/master/neural_network/back_propagation_neural_network.py) * [convolution neural network](https://github.com/TheAlgorithms/Python/blob/master/neural_network/convolution_neural_network.py) * [perceptron](https://github.com/TheAlgorithms/Python/blob/master/neural_network/perceptron.py) ## Other @@ -208,11 +224,11 @@ * [detecting english programmatically](https://github.com/TheAlgorithms/Python/blob/master/other/detecting_english_programmatically.py) * [dictionary](https://github.com/TheAlgorithms/Python/blob/master/other/dictionary.txt) * [euclidean gcd](https://github.com/TheAlgorithms/Python/blob/master/other/euclidean_gcd.py) - * [finding Primes](https://github.com/TheAlgorithms/Python/blob/master/other/finding_Primes.py) + * [finding primes](https://github.com/TheAlgorithms/Python/blob/master/other/finding_primes.py) * [fischer yates shuffle](https://github.com/TheAlgorithms/Python/blob/master/other/fischer_yates_shuffle.py) * [frequency finder](https://github.com/TheAlgorithms/Python/blob/master/other/frequency_finder.py) + * [game of life](https://github.com/TheAlgorithms/Python/blob/master/other/game_of_life.py) * [linear congruential generator](https://github.com/TheAlgorithms/Python/blob/master/other/linear_congruential_generator.py) - * [n queens](https://github.com/TheAlgorithms/Python/blob/master/other/n_queens.py) * [nested brackets](https://github.com/TheAlgorithms/Python/blob/master/other/nested_brackets.py) * [palindrome](https://github.com/TheAlgorithms/Python/blob/master/other/palindrome.py) * [password generator](https://github.com/TheAlgorithms/Python/blob/master/other/password_generator.py) @@ -222,9 +238,8 @@ * [two sum](https://github.com/TheAlgorithms/Python/blob/master/other/two_sum.py) * [word patterns](https://github.com/TheAlgorithms/Python/blob/master/other/word_patterns.py) * [words](https://github.com/TheAlgorithms/Python/blob/master/other/words) - * Game Of Life - * [game o life](https://github.com/TheAlgorithms/Python/blob/master/other/game_of_life/game_o_life.py) - * [sample](https://github.com/TheAlgorithms/Python/blob/master/other/game_of_life/sample.gif) + * Pycache + * [password generator.cpython-37](https://github.com/TheAlgorithms/Python/blob/master/other/__pycache__/password_generator.cpython-37.pyc) ## Project Euler * Problem 01 * [sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_01/sol1.py) @@ -273,7 +288,9 @@ * [sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_12/sol1.py) * [sol2](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_12/sol2.py) * Problem 13 + * [num](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_13/num.txt) * [sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_13/sol1.py) + * [sol2](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_13/sol2.py) * Problem 14 * [sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_14/sol1.py) * [sol2](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_14/sol2.py) @@ -281,6 +298,7 @@ * [sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_15/sol1.py) * Problem 16 * [sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_16/sol1.py) + * [sol2](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_16/sol2.py) * Problem 17 * [sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_17/sol1.py) * Problem 19 @@ -319,6 +337,8 @@ * [sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_53/sol1.py) * Problem 76 * [sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_76/sol1.py) +## Scripts + * [build directory md](https://github.com/TheAlgorithms/Python/blob/master/scripts/build_directory_md.py) ## Searches * [binary search](https://github.com/TheAlgorithms/Python/blob/master/searches/binary_search.py) * [interpolation search](https://github.com/TheAlgorithms/Python/blob/master/searches/interpolation_search.py) @@ -329,13 +349,8 @@ * [tabu search](https://github.com/TheAlgorithms/Python/blob/master/searches/tabu_search.py) * [tabu test data](https://github.com/TheAlgorithms/Python/blob/master/searches/tabu_test_data.txt) * [ternary search](https://github.com/TheAlgorithms/Python/blob/master/searches/ternary_search.py) - * [test interpolation search](https://github.com/TheAlgorithms/Python/blob/master/searches/test_interpolation_search.py) - * [test tabu search](https://github.com/TheAlgorithms/Python/blob/master/searches/test_tabu_search.py) -## Simple Client - * [client](https://github.com/TheAlgorithms/Python/blob/master/simple_client/client.py) - * [server](https://github.com/TheAlgorithms/Python/blob/master/simple_client/server.py) ## Sorts - * [Bitonic Sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/Bitonic_Sort.py) + * [bitonic sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/bitonic_sort.py) * [bogo sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/bogo_sort.py) * [bubble sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/bubble_sort.py) * [bucket sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/bucket_sort.py) @@ -349,8 +364,8 @@ * [insertion sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/insertion_sort.py) * [merge sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/merge_sort.py) * [merge sort fastest](https://github.com/TheAlgorithms/Python/blob/master/sorts/merge_sort_fastest.py) - * [Odd-Even transposition parallel](https://github.com/TheAlgorithms/Python/blob/master/sorts/Odd-Even_transposition_parallel.py) - * [Odd-Even transposition single-threaded](https://github.com/TheAlgorithms/Python/blob/master/sorts/Odd-Even_transposition_single-threaded.py) + * [odd even transposition parallel](https://github.com/TheAlgorithms/Python/blob/master/sorts/odd_even_transposition_parallel.py) + * [odd even transposition single threaded](https://github.com/TheAlgorithms/Python/blob/master/sorts/odd_even_transposition_single_threaded.py) * [pancake sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/pancake_sort.py) * [pigeon sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/pigeon_sort.py) * [quick sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/quick_sort.py) @@ -360,17 +375,17 @@ * [random pivot quick sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/random_pivot_quick_sort.py) * [selection sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/selection_sort.py) * [shell sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/shell_sort.py) - * [tests](https://github.com/TheAlgorithms/Python/blob/master/sorts/tests.py) * [tim sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/tim_sort.py) * [topological sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/topological_sort.py) * [tree sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/tree_sort.py) * [wiggle sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/wiggle_sort.py) ## Strings + * [boyer moore search](https://github.com/TheAlgorithms/Python/blob/master/strings/boyer_moore_search.py) * [knuth morris pratt](https://github.com/TheAlgorithms/Python/blob/master/strings/knuth_morris_pratt.py) * [levenshtein distance](https://github.com/TheAlgorithms/Python/blob/master/strings/levenshtein_distance.py) * [manacher](https://github.com/TheAlgorithms/Python/blob/master/strings/manacher.py) * [min cost string conversion](https://github.com/TheAlgorithms/Python/blob/master/strings/min_cost_string_conversion.py) - * [naive String Search](https://github.com/TheAlgorithms/Python/blob/master/strings/naive_String_Search.py) + * [naive string search](https://github.com/TheAlgorithms/Python/blob/master/strings/naive_string_search.py) * [rabin karp](https://github.com/TheAlgorithms/Python/blob/master/strings/rabin_karp.py) ## Traversals * [binary tree traversals](https://github.com/TheAlgorithms/Python/blob/master/traversals/binary_tree_traversals.py) diff --git a/~script.py b/scripts/build_directory_md.py similarity index 97% rename from ~script.py rename to scripts/build_directory_md.py index 1fbb1e838c62..47192701880d 100644 --- a/~script.py +++ b/scripts/build_directory_md.py @@ -51,8 +51,10 @@ def _markdown(parent, ignores, ignores_ext, depth): ignores = [".vs", ".gitignore", ".git", - "~script.py", + "scripts", "__init__.py", + "requirements.txt", + ".github" ] # Files with given entensions will be ignored ignores_ext = [ From 0d615398830e8a6fd1c3aa4bd6896de1b8200561 Mon Sep 17 00:00:00 2001 From: Rakshit Parashar <34675136+rishu2403@users.noreply.github.com> Date: Sat, 13 Jul 2019 12:54:38 -0700 Subject: [PATCH 0113/1071] Log_likelihood update (#1008) * Add files via upload This is a simple exploratory notebook that heavily expolits pandas and seaborn * Update logistic_regression.py * Update logistic_regression.py * Rename Food wastage analysis from 1961-2013 (FAO).ipynb to other/Food wastage analysis from 1961-2013 (FAO).ipynb * Update logistic_regression.py * Update logistic_regression.py * Update logistic_regression.py * Update logistic_regression.py * Update logistic_regression.py * Update logistic_regression.py * Update logistic_regression.py --- machine_learning/logistic_regression.py | 28 +- ...astage analysis from 1961-2013 (FAO).ipynb | 5916 +++++++++++++++++ 2 files changed, 5933 insertions(+), 11 deletions(-) create mode 100644 other/Food wastage analysis from 1961-2013 (FAO).ipynb diff --git a/machine_learning/logistic_regression.py b/machine_learning/logistic_regression.py index 71952e792e81..9a60831862da 100644 --- a/machine_learning/logistic_regression.py +++ b/machine_learning/logistic_regression.py @@ -31,13 +31,16 @@ def sigmoid_function(z): def cost_function(h, y): return (-y * np.log(h) - (1 - y) * np.log(1 - h)).mean() +def log_likelihood(X, Y, weights): + scores = np.dot(X, weights) + return np.sum(Y*scores - np.log(1 + np.exp(scores)) ) # here alpha is the learning rate, X is the feature matrix,y is the target matrix - def logistic_reg( alpha, X, y, + num_steps, max_iterations=70000, ): converged = False @@ -49,21 +52,24 @@ def logistic_reg( h = sigmoid_function(z) gradient = np.dot(X.T, h - y) / y.size theta = theta - alpha * gradient - z = np.dot(X, theta) h = sigmoid_function(z) J = cost_function(h, y) - iterations += 1 # update iterations - - if iterations == max_iterations: - print ('Maximum iterations exceeded!') - print ('Minimal cost function J=', J) - converged = True - + weights = np.zeros(X.shape[1]) + for step in range(num_steps): + scores = np.dot(X, weights) + predictions = sigmoid_function(scores) + if step % 10000 == 0: + print(log_likelihood(X,y,weights)) # Print log-likelihood every so often + return weights + + if iterations == max_iterations: + print ('Maximum iterations exceeded!') + print ('Minimal cost function J=', J) + converged = True return theta - # In[68]: if __name__ == '__main__': @@ -72,7 +78,7 @@ def logistic_reg( y = (iris.target != 0) * 1 alpha = 0.1 - theta = logistic_reg(alpha, X, y, max_iterations=70000) + theta = logistic_reg(alpha,X,y,max_iterations=70000,num_steps=30000) print (theta) diff --git a/other/Food wastage analysis from 1961-2013 (FAO).ipynb b/other/Food wastage analysis from 1961-2013 (FAO).ipynb new file mode 100644 index 000000000000..384314c7e8f1 --- /dev/null +++ b/other/Food wastage analysis from 1961-2013 (FAO).ipynb @@ -0,0 +1,5916 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": { + "_cell_guid": "1eecdb4a-89ca-4a1e-9c4c-7c44b2e628a1", + "_uuid": "110a8132a8179a9bed2fc8f1096592dc791f1661" + }, + "source": [ + "# About the dataset\n", + "\n", + "Context\n", + "Our world population is expected to grow from 7.3 billion today to 9.7 billion in the year 2050. Finding solutions for feeding the growing world population has become a hot topic for food and agriculture organizations, entrepreneurs and philanthropists. These solutions range from changing the way we grow our food to changing the way we eat. To make things harder, the world's climate is changing and it is both affecting and affected by the way we grow our food – agriculture. This dataset provides an insight on our worldwide food production - focusing on a comparison between food produced for human consumption and feed produced for animals.\n", + "\n", + "Content\n", + "The Food and Agriculture Organization of the United Nations provides free access to food and agriculture data for over 245 countries and territories, from the year 1961 to the most recent update (depends on the dataset). One dataset from the FAO's database is the Food Balance Sheets. It presents a comprehensive picture of the pattern of a country's food supply during a specified reference period, the last time an update was loaded to the FAO database was in 2013. The food balance sheet shows for each food item the sources of supply and its utilization. This chunk of the dataset is focused on two utilizations of each food item available:\n", + "\n", + "Food - refers to the total amount of the food item available as human food during the reference period.\n", + "Feed - refers to the quantity of the food item available for feeding to the livestock and poultry during the reference period.\n", + "Dataset's attributes:\n", + "\n", + "Area code - Country name abbreviation\n", + "Area - County name\n", + "Item - Food item\n", + "Element - Food or Feed\n", + "Latitude - geographic coordinate that specifies the north–south position of a point on the Earth's surface\n", + "Longitude - geographic coordinate that specifies the east-west position of a point on the Earth's surface\n", + "Production per year - Amount of food item produced in 1000 tonnes\n", + "\n", + "This is a simple exploratory notebook that heavily expolits pandas and seaborn" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": { + "_cell_guid": "b1076dfc-b9ad-4769-8c92-a6c4dae69d19", + "_uuid": "8f2839f25d086af736a60e9eeb907d3b93b6e0e5" + }, + "outputs": [], + "source": [ + "# Importing libraries\n", + "import numpy as np\n", + "import pandas as pd\n", + "import matplotlib.pyplot as plt\n", + "import seaborn as sns\n", + "%matplotlib inline\n", + "# importing data\n", + "df = pd.read_csv(\"FAO.csv\", encoding = \"ISO-8859-1\")\n", + "pd.options.mode.chained_assignment = None\n", + "from sklearn.linear_model import LinearRegression" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
Area AbbreviationArea CodeAreaItem CodeItemElement CodeElementUnitlatitudelongitude...Y2004Y2005Y2006Y2007Y2008Y2009Y2010Y2011Y2012Y2013
0AFG2Afghanistan2511Wheat and products5142Food1000 tonnes33.9467.71...3249.03486.03704.04164.04252.04538.04605.04711.048104895
1AFG2Afghanistan2805Rice (Milled Equivalent)5142Food1000 tonnes33.9467.71...419.0445.0546.0455.0490.0415.0442.0476.0425422
2AFG2Afghanistan2513Barley and products5521Feed1000 tonnes33.9467.71...58.0236.0262.0263.0230.0379.0315.0203.0367360
3AFG2Afghanistan2513Barley and products5142Food1000 tonnes33.9467.71...185.043.044.048.062.055.060.072.07889
4AFG2Afghanistan2514Maize and products5521Feed1000 tonnes33.9467.71...120.0208.0233.0249.0247.0195.0178.0191.0200200
5AFG2Afghanistan2514Maize and products5142Food1000 tonnes33.9467.71...231.067.082.067.069.071.082.073.07776
6AFG2Afghanistan2517Millet and products5142Food1000 tonnes33.9467.71...15.021.011.019.021.018.014.014.01412
7AFG2Afghanistan2520Cereals, Other5142Food1000 tonnes33.9467.71...2.01.01.00.00.00.00.00.000
8AFG2Afghanistan2531Potatoes and products5142Food1000 tonnes33.9467.71...276.0294.0294.0260.0242.0250.0192.0169.0196230
9AFG2Afghanistan2536Sugar cane5521Feed1000 tonnes33.9467.71...50.029.061.065.054.0114.083.083.06981
10AFG2Afghanistan2537Sugar beet5521Feed1000 tonnes33.9467.71...0.00.00.00.00.00.00.00.000
11AFG2Afghanistan2542Sugar (Raw Equivalent)5142Food1000 tonnes33.9467.71...124.0152.0169.0192.0217.0231.0240.0240.0250255
12AFG2Afghanistan2543Sweeteners, Other5142Food1000 tonnes33.9467.71...9.015.012.06.011.02.09.021.02416
13AFG2Afghanistan2745Honey5142Food1000 tonnes33.9467.71...3.03.03.03.03.03.03.02.022
14AFG2Afghanistan2549Pulses, Other and products5521Feed1000 tonnes33.9467.71...3.02.03.03.03.05.04.05.044
15AFG2Afghanistan2549Pulses, Other and products5142Food1000 tonnes33.9467.71...17.035.037.040.054.080.066.081.06374
16AFG2Afghanistan2551Nuts and products5142Food1000 tonnes33.9467.71...11.013.024.034.042.028.066.071.07044
17AFG2Afghanistan2560Coconuts - Incl Copra5142Food1000 tonnes33.9467.71...0.00.00.00.00.00.00.00.000
18AFG2Afghanistan2561Sesame seed5142Food1000 tonnes33.9467.71...16.016.013.016.016.016.019.017.01616
19AFG2Afghanistan2563Olives (including preserved)5142Food1000 tonnes33.9467.71...1.01.00.00.02.03.02.02.022
20AFG2Afghanistan2571Soyabean Oil5142Food1000 tonnes33.9467.71...6.035.018.021.011.06.015.016.01616
21AFG2Afghanistan2572Groundnut Oil5142Food1000 tonnes33.9467.71...0.00.00.00.00.00.00.00.000
22AFG2Afghanistan2573Sunflowerseed Oil5142Food1000 tonnes33.9467.71...4.06.05.09.03.08.015.016.01723
23AFG2Afghanistan2574Rape and Mustard Oil5142Food1000 tonnes33.9467.71...0.01.03.05.06.06.01.02.022
24AFG2Afghanistan2575Cottonseed Oil5142Food1000 tonnes33.9467.71...2.03.03.03.03.04.03.03.034
25AFG2Afghanistan2577Palm Oil5142Food1000 tonnes33.9467.71...71.069.056.051.036.053.059.051.06164
26AFG2Afghanistan2579Sesameseed Oil5142Food1000 tonnes33.9467.71...1.01.01.02.02.01.01.02.011
27AFG2Afghanistan2580Olive Oil5142Food1000 tonnes33.9467.71...0.00.00.00.00.01.01.01.011
28AFG2Afghanistan2586Oilcrops Oil, Other5142Food1000 tonnes33.9467.71...0.01.00.00.03.01.02.02.022
29AFG2Afghanistan2601Tomatoes and products5142Food1000 tonnes33.9467.71...2.02.08.01.00.00.00.00.000
..................................................................
21447ZWE181Zimbabwe2765Crustaceans5142Food1000 tonnes-19.0229.15...0.00.00.00.00.00.00.00.000
21448ZWE181Zimbabwe2766Cephalopods5142Food1000 tonnes-19.0229.15...0.00.00.00.00.00.00.00.000
21449ZWE181Zimbabwe2767Molluscs, Other5142Food1000 tonnes-19.0229.15...0.00.00.00.00.00.01.00.000
21450ZWE181Zimbabwe2775Aquatic Plants5142Food1000 tonnes-19.0229.15...0.00.00.00.00.00.00.00.000
21451ZWE181Zimbabwe2680Infant food5142Food1000 tonnes-19.0229.15...0.00.00.00.00.00.00.00.000
21452ZWE181Zimbabwe2905Cereals - Excluding Beer5521Feed1000 tonnes-19.0229.15...75.054.075.055.063.062.055.055.05555
21453ZWE181Zimbabwe2905Cereals - Excluding Beer5142Food1000 tonnes-19.0229.15...1844.01842.01944.01962.01918.01980.02011.02094.020712016
21454ZWE181Zimbabwe2907Starchy Roots5142Food1000 tonnes-19.0229.15...223.0236.0238.0228.0245.0258.0258.0269.0272276
21455ZWE181Zimbabwe2908Sugar Crops5142Food1000 tonnes-19.0229.15...0.00.00.00.00.00.00.00.000
21456ZWE181Zimbabwe2909Sugar & Sweeteners5142Food1000 tonnes-19.0229.15...335.0313.0339.0302.0285.0287.0314.0336.0396416
21457ZWE181Zimbabwe2911Pulses5142Food1000 tonnes-19.0229.15...63.059.061.057.069.078.068.056.05255
21458ZWE181Zimbabwe2912Treenuts5142Food1000 tonnes-19.0229.15...1.02.01.02.02.03.04.02.043
21459ZWE181Zimbabwe2913Oilcrops5521Feed1000 tonnes-19.0229.15...36.046.041.033.031.019.024.017.02730
21460ZWE181Zimbabwe2913Oilcrops5142Food1000 tonnes-19.0229.15...60.059.061.062.048.044.041.040.03838
21461ZWE181Zimbabwe2914Vegetable Oils5142Food1000 tonnes-19.0229.15...111.0114.0112.0114.0134.0135.0137.0147.0159160
21462ZWE181Zimbabwe2918Vegetables5142Food1000 tonnes-19.0229.15...161.0166.0208.0185.0137.0179.0215.0217.0227227
21463ZWE181Zimbabwe2919Fruits - Excluding Wine5142Food1000 tonnes-19.0229.15...191.0134.0167.0177.0185.0184.0211.0230.0246217
21464ZWE181Zimbabwe2922Stimulants5142Food1000 tonnes-19.0229.15...7.021.014.024.016.011.023.011.01010
21465ZWE181Zimbabwe2923Spices5142Food1000 tonnes-19.0229.15...7.011.07.012.016.016.014.011.01212
21466ZWE181Zimbabwe2924Alcoholic Beverages5142Food1000 tonnes-19.0229.15...294.0290.0316.0355.0398.0437.0448.0476.0525516
21467ZWE181Zimbabwe2943Meat5142Food1000 tonnes-19.0229.15...222.0228.0233.0238.0242.0265.0262.0277.0280258
21468ZWE181Zimbabwe2945Offals5142Food1000 tonnes-19.0229.15...20.020.021.021.021.021.021.021.02222
21469ZWE181Zimbabwe2946Animal fats5142Food1000 tonnes-19.0229.15...26.026.029.029.027.031.030.025.02620
21470ZWE181Zimbabwe2949Eggs5142Food1000 tonnes-19.0229.15...15.018.018.021.022.027.027.024.02425
21471ZWE181Zimbabwe2948Milk - Excluding Butter5521Feed1000 tonnes-19.0229.15...21.021.021.021.021.023.025.025.03031
21472ZWE181Zimbabwe2948Milk - Excluding Butter5142Food1000 tonnes-19.0229.15...373.0357.0359.0356.0341.0385.0418.0457.0426451
21473ZWE181Zimbabwe2960Fish, Seafood5521Feed1000 tonnes-19.0229.15...5.04.09.06.09.05.015.015.01515
21474ZWE181Zimbabwe2960Fish, Seafood5142Food1000 tonnes-19.0229.15...18.014.017.014.015.018.029.040.04040
21475ZWE181Zimbabwe2961Aquatic Products, Other5142Food1000 tonnes-19.0229.15...0.00.00.00.00.00.00.00.000
21476ZWE181Zimbabwe2928Miscellaneous5142Food1000 tonnes-19.0229.15...0.00.00.00.00.00.00.00.000
\n", + "

21477 rows × 63 columns

\n", + "
" + ], + "text/plain": [ + " Area Abbreviation Area Code Area Item Code \\\n", + "0 AFG 2 Afghanistan 2511 \n", + "1 AFG 2 Afghanistan 2805 \n", + "2 AFG 2 Afghanistan 2513 \n", + "3 AFG 2 Afghanistan 2513 \n", + "4 AFG 2 Afghanistan 2514 \n", + "5 AFG 2 Afghanistan 2514 \n", + "6 AFG 2 Afghanistan 2517 \n", + "7 AFG 2 Afghanistan 2520 \n", + "8 AFG 2 Afghanistan 2531 \n", + "9 AFG 2 Afghanistan 2536 \n", + "10 AFG 2 Afghanistan 2537 \n", + "11 AFG 2 Afghanistan 2542 \n", + "12 AFG 2 Afghanistan 2543 \n", + "13 AFG 2 Afghanistan 2745 \n", + "14 AFG 2 Afghanistan 2549 \n", + "15 AFG 2 Afghanistan 2549 \n", + "16 AFG 2 Afghanistan 2551 \n", + "17 AFG 2 Afghanistan 2560 \n", + "18 AFG 2 Afghanistan 2561 \n", + "19 AFG 2 Afghanistan 2563 \n", + "20 AFG 2 Afghanistan 2571 \n", + "21 AFG 2 Afghanistan 2572 \n", + "22 AFG 2 Afghanistan 2573 \n", + "23 AFG 2 Afghanistan 2574 \n", + "24 AFG 2 Afghanistan 2575 \n", + "25 AFG 2 Afghanistan 2577 \n", + "26 AFG 2 Afghanistan 2579 \n", + "27 AFG 2 Afghanistan 2580 \n", + "28 AFG 2 Afghanistan 2586 \n", + "29 AFG 2 Afghanistan 2601 \n", + "... ... ... ... ... \n", + "21447 ZWE 181 Zimbabwe 2765 \n", + "21448 ZWE 181 Zimbabwe 2766 \n", + "21449 ZWE 181 Zimbabwe 2767 \n", + "21450 ZWE 181 Zimbabwe 2775 \n", + "21451 ZWE 181 Zimbabwe 2680 \n", + "21452 ZWE 181 Zimbabwe 2905 \n", + "21453 ZWE 181 Zimbabwe 2905 \n", + "21454 ZWE 181 Zimbabwe 2907 \n", + "21455 ZWE 181 Zimbabwe 2908 \n", + "21456 ZWE 181 Zimbabwe 2909 \n", + "21457 ZWE 181 Zimbabwe 2911 \n", + "21458 ZWE 181 Zimbabwe 2912 \n", + "21459 ZWE 181 Zimbabwe 2913 \n", + "21460 ZWE 181 Zimbabwe 2913 \n", + "21461 ZWE 181 Zimbabwe 2914 \n", + "21462 ZWE 181 Zimbabwe 2918 \n", + "21463 ZWE 181 Zimbabwe 2919 \n", + "21464 ZWE 181 Zimbabwe 2922 \n", + "21465 ZWE 181 Zimbabwe 2923 \n", + "21466 ZWE 181 Zimbabwe 2924 \n", + "21467 ZWE 181 Zimbabwe 2943 \n", + "21468 ZWE 181 Zimbabwe 2945 \n", + "21469 ZWE 181 Zimbabwe 2946 \n", + "21470 ZWE 181 Zimbabwe 2949 \n", + "21471 ZWE 181 Zimbabwe 2948 \n", + "21472 ZWE 181 Zimbabwe 2948 \n", + "21473 ZWE 181 Zimbabwe 2960 \n", + "21474 ZWE 181 Zimbabwe 2960 \n", + "21475 ZWE 181 Zimbabwe 2961 \n", + "21476 ZWE 181 Zimbabwe 2928 \n", + "\n", + " Item Element Code Element Unit \\\n", + "0 Wheat and products 5142 Food 1000 tonnes \n", + "1 Rice (Milled Equivalent) 5142 Food 1000 tonnes \n", + "2 Barley and products 5521 Feed 1000 tonnes \n", + "3 Barley and products 5142 Food 1000 tonnes \n", + "4 Maize and products 5521 Feed 1000 tonnes \n", + "5 Maize and products 5142 Food 1000 tonnes \n", + "6 Millet and products 5142 Food 1000 tonnes \n", + "7 Cereals, Other 5142 Food 1000 tonnes \n", + "8 Potatoes and products 5142 Food 1000 tonnes \n", + "9 Sugar cane 5521 Feed 1000 tonnes \n", + "10 Sugar beet 5521 Feed 1000 tonnes \n", + "11 Sugar (Raw Equivalent) 5142 Food 1000 tonnes \n", + "12 Sweeteners, Other 5142 Food 1000 tonnes \n", + "13 Honey 5142 Food 1000 tonnes \n", + "14 Pulses, Other and products 5521 Feed 1000 tonnes \n", + "15 Pulses, Other and products 5142 Food 1000 tonnes \n", + "16 Nuts and products 5142 Food 1000 tonnes \n", + "17 Coconuts - Incl Copra 5142 Food 1000 tonnes \n", + "18 Sesame seed 5142 Food 1000 tonnes \n", + "19 Olives (including preserved) 5142 Food 1000 tonnes \n", + "20 Soyabean Oil 5142 Food 1000 tonnes \n", + "21 Groundnut Oil 5142 Food 1000 tonnes \n", + "22 Sunflowerseed Oil 5142 Food 1000 tonnes \n", + "23 Rape and Mustard Oil 5142 Food 1000 tonnes \n", + "24 Cottonseed Oil 5142 Food 1000 tonnes \n", + "25 Palm Oil 5142 Food 1000 tonnes \n", + "26 Sesameseed Oil 5142 Food 1000 tonnes \n", + "27 Olive Oil 5142 Food 1000 tonnes \n", + "28 Oilcrops Oil, Other 5142 Food 1000 tonnes \n", + "29 Tomatoes and products 5142 Food 1000 tonnes \n", + "... ... ... ... ... \n", + "21447 Crustaceans 5142 Food 1000 tonnes \n", + "21448 Cephalopods 5142 Food 1000 tonnes \n", + "21449 Molluscs, Other 5142 Food 1000 tonnes \n", + "21450 Aquatic Plants 5142 Food 1000 tonnes \n", + "21451 Infant food 5142 Food 1000 tonnes \n", + "21452 Cereals - Excluding Beer 5521 Feed 1000 tonnes \n", + "21453 Cereals - Excluding Beer 5142 Food 1000 tonnes \n", + "21454 Starchy Roots 5142 Food 1000 tonnes \n", + "21455 Sugar Crops 5142 Food 1000 tonnes \n", + "21456 Sugar & Sweeteners 5142 Food 1000 tonnes \n", + "21457 Pulses 5142 Food 1000 tonnes \n", + "21458 Treenuts 5142 Food 1000 tonnes \n", + "21459 Oilcrops 5521 Feed 1000 tonnes \n", + "21460 Oilcrops 5142 Food 1000 tonnes \n", + "21461 Vegetable Oils 5142 Food 1000 tonnes \n", + "21462 Vegetables 5142 Food 1000 tonnes \n", + "21463 Fruits - Excluding Wine 5142 Food 1000 tonnes \n", + "21464 Stimulants 5142 Food 1000 tonnes \n", + "21465 Spices 5142 Food 1000 tonnes \n", + "21466 Alcoholic Beverages 5142 Food 1000 tonnes \n", + "21467 Meat 5142 Food 1000 tonnes \n", + "21468 Offals 5142 Food 1000 tonnes \n", + "21469 Animal fats 5142 Food 1000 tonnes \n", + "21470 Eggs 5142 Food 1000 tonnes \n", + "21471 Milk - Excluding Butter 5521 Feed 1000 tonnes \n", + "21472 Milk - Excluding Butter 5142 Food 1000 tonnes \n", + "21473 Fish, Seafood 5521 Feed 1000 tonnes \n", + "21474 Fish, Seafood 5142 Food 1000 tonnes \n", + "21475 Aquatic Products, Other 5142 Food 1000 tonnes \n", + "21476 Miscellaneous 5142 Food 1000 tonnes \n", + "\n", + " latitude longitude ... Y2004 Y2005 Y2006 Y2007 Y2008 \\\n", + "0 33.94 67.71 ... 3249.0 3486.0 3704.0 4164.0 4252.0 \n", + "1 33.94 67.71 ... 419.0 445.0 546.0 455.0 490.0 \n", + "2 33.94 67.71 ... 58.0 236.0 262.0 263.0 230.0 \n", + "3 33.94 67.71 ... 185.0 43.0 44.0 48.0 62.0 \n", + "4 33.94 67.71 ... 120.0 208.0 233.0 249.0 247.0 \n", + "5 33.94 67.71 ... 231.0 67.0 82.0 67.0 69.0 \n", + "6 33.94 67.71 ... 15.0 21.0 11.0 19.0 21.0 \n", + "7 33.94 67.71 ... 2.0 1.0 1.0 0.0 0.0 \n", + "8 33.94 67.71 ... 276.0 294.0 294.0 260.0 242.0 \n", + "9 33.94 67.71 ... 50.0 29.0 61.0 65.0 54.0 \n", + "10 33.94 67.71 ... 0.0 0.0 0.0 0.0 0.0 \n", + "11 33.94 67.71 ... 124.0 152.0 169.0 192.0 217.0 \n", + "12 33.94 67.71 ... 9.0 15.0 12.0 6.0 11.0 \n", + "13 33.94 67.71 ... 3.0 3.0 3.0 3.0 3.0 \n", + "14 33.94 67.71 ... 3.0 2.0 3.0 3.0 3.0 \n", + "15 33.94 67.71 ... 17.0 35.0 37.0 40.0 54.0 \n", + "16 33.94 67.71 ... 11.0 13.0 24.0 34.0 42.0 \n", + "17 33.94 67.71 ... 0.0 0.0 0.0 0.0 0.0 \n", + "18 33.94 67.71 ... 16.0 16.0 13.0 16.0 16.0 \n", + "19 33.94 67.71 ... 1.0 1.0 0.0 0.0 2.0 \n", + "20 33.94 67.71 ... 6.0 35.0 18.0 21.0 11.0 \n", + "21 33.94 67.71 ... 0.0 0.0 0.0 0.0 0.0 \n", + "22 33.94 67.71 ... 4.0 6.0 5.0 9.0 3.0 \n", + "23 33.94 67.71 ... 0.0 1.0 3.0 5.0 6.0 \n", + "24 33.94 67.71 ... 2.0 3.0 3.0 3.0 3.0 \n", + "25 33.94 67.71 ... 71.0 69.0 56.0 51.0 36.0 \n", + "26 33.94 67.71 ... 1.0 1.0 1.0 2.0 2.0 \n", + "27 33.94 67.71 ... 0.0 0.0 0.0 0.0 0.0 \n", + "28 33.94 67.71 ... 0.0 1.0 0.0 0.0 3.0 \n", + "29 33.94 67.71 ... 2.0 2.0 8.0 1.0 0.0 \n", + "... ... ... ... ... ... ... ... ... \n", + "21447 -19.02 29.15 ... 0.0 0.0 0.0 0.0 0.0 \n", + "21448 -19.02 29.15 ... 0.0 0.0 0.0 0.0 0.0 \n", + "21449 -19.02 29.15 ... 0.0 0.0 0.0 0.0 0.0 \n", + "21450 -19.02 29.15 ... 0.0 0.0 0.0 0.0 0.0 \n", + "21451 -19.02 29.15 ... 0.0 0.0 0.0 0.0 0.0 \n", + "21452 -19.02 29.15 ... 75.0 54.0 75.0 55.0 63.0 \n", + "21453 -19.02 29.15 ... 1844.0 1842.0 1944.0 1962.0 1918.0 \n", + "21454 -19.02 29.15 ... 223.0 236.0 238.0 228.0 245.0 \n", + "21455 -19.02 29.15 ... 0.0 0.0 0.0 0.0 0.0 \n", + "21456 -19.02 29.15 ... 335.0 313.0 339.0 302.0 285.0 \n", + "21457 -19.02 29.15 ... 63.0 59.0 61.0 57.0 69.0 \n", + "21458 -19.02 29.15 ... 1.0 2.0 1.0 2.0 2.0 \n", + "21459 -19.02 29.15 ... 36.0 46.0 41.0 33.0 31.0 \n", + "21460 -19.02 29.15 ... 60.0 59.0 61.0 62.0 48.0 \n", + "21461 -19.02 29.15 ... 111.0 114.0 112.0 114.0 134.0 \n", + "21462 -19.02 29.15 ... 161.0 166.0 208.0 185.0 137.0 \n", + "21463 -19.02 29.15 ... 191.0 134.0 167.0 177.0 185.0 \n", + "21464 -19.02 29.15 ... 7.0 21.0 14.0 24.0 16.0 \n", + "21465 -19.02 29.15 ... 7.0 11.0 7.0 12.0 16.0 \n", + "21466 -19.02 29.15 ... 294.0 290.0 316.0 355.0 398.0 \n", + "21467 -19.02 29.15 ... 222.0 228.0 233.0 238.0 242.0 \n", + "21468 -19.02 29.15 ... 20.0 20.0 21.0 21.0 21.0 \n", + "21469 -19.02 29.15 ... 26.0 26.0 29.0 29.0 27.0 \n", + "21470 -19.02 29.15 ... 15.0 18.0 18.0 21.0 22.0 \n", + "21471 -19.02 29.15 ... 21.0 21.0 21.0 21.0 21.0 \n", + "21472 -19.02 29.15 ... 373.0 357.0 359.0 356.0 341.0 \n", + "21473 -19.02 29.15 ... 5.0 4.0 9.0 6.0 9.0 \n", + "21474 -19.02 29.15 ... 18.0 14.0 17.0 14.0 15.0 \n", + "21475 -19.02 29.15 ... 0.0 0.0 0.0 0.0 0.0 \n", + "21476 -19.02 29.15 ... 0.0 0.0 0.0 0.0 0.0 \n", + "\n", + " Y2009 Y2010 Y2011 Y2012 Y2013 \n", + "0 4538.0 4605.0 4711.0 4810 4895 \n", + "1 415.0 442.0 476.0 425 422 \n", + "2 379.0 315.0 203.0 367 360 \n", + "3 55.0 60.0 72.0 78 89 \n", + "4 195.0 178.0 191.0 200 200 \n", + "5 71.0 82.0 73.0 77 76 \n", + "6 18.0 14.0 14.0 14 12 \n", + "7 0.0 0.0 0.0 0 0 \n", + "8 250.0 192.0 169.0 196 230 \n", + "9 114.0 83.0 83.0 69 81 \n", + "10 0.0 0.0 0.0 0 0 \n", + "11 231.0 240.0 240.0 250 255 \n", + "12 2.0 9.0 21.0 24 16 \n", + "13 3.0 3.0 2.0 2 2 \n", + "14 5.0 4.0 5.0 4 4 \n", + "15 80.0 66.0 81.0 63 74 \n", + "16 28.0 66.0 71.0 70 44 \n", + "17 0.0 0.0 0.0 0 0 \n", + "18 16.0 19.0 17.0 16 16 \n", + "19 3.0 2.0 2.0 2 2 \n", + "20 6.0 15.0 16.0 16 16 \n", + "21 0.0 0.0 0.0 0 0 \n", + "22 8.0 15.0 16.0 17 23 \n", + "23 6.0 1.0 2.0 2 2 \n", + "24 4.0 3.0 3.0 3 4 \n", + "25 53.0 59.0 51.0 61 64 \n", + "26 1.0 1.0 2.0 1 1 \n", + "27 1.0 1.0 1.0 1 1 \n", + "28 1.0 2.0 2.0 2 2 \n", + "29 0.0 0.0 0.0 0 0 \n", + "... ... ... ... ... ... \n", + "21447 0.0 0.0 0.0 0 0 \n", + "21448 0.0 0.0 0.0 0 0 \n", + "21449 0.0 1.0 0.0 0 0 \n", + "21450 0.0 0.0 0.0 0 0 \n", + "21451 0.0 0.0 0.0 0 0 \n", + "21452 62.0 55.0 55.0 55 55 \n", + "21453 1980.0 2011.0 2094.0 2071 2016 \n", + "21454 258.0 258.0 269.0 272 276 \n", + "21455 0.0 0.0 0.0 0 0 \n", + "21456 287.0 314.0 336.0 396 416 \n", + "21457 78.0 68.0 56.0 52 55 \n", + "21458 3.0 4.0 2.0 4 3 \n", + "21459 19.0 24.0 17.0 27 30 \n", + "21460 44.0 41.0 40.0 38 38 \n", + "21461 135.0 137.0 147.0 159 160 \n", + "21462 179.0 215.0 217.0 227 227 \n", + "21463 184.0 211.0 230.0 246 217 \n", + "21464 11.0 23.0 11.0 10 10 \n", + "21465 16.0 14.0 11.0 12 12 \n", + "21466 437.0 448.0 476.0 525 516 \n", + "21467 265.0 262.0 277.0 280 258 \n", + "21468 21.0 21.0 21.0 22 22 \n", + "21469 31.0 30.0 25.0 26 20 \n", + "21470 27.0 27.0 24.0 24 25 \n", + "21471 23.0 25.0 25.0 30 31 \n", + "21472 385.0 418.0 457.0 426 451 \n", + "21473 5.0 15.0 15.0 15 15 \n", + "21474 18.0 29.0 40.0 40 40 \n", + "21475 0.0 0.0 0.0 0 0 \n", + "21476 0.0 0.0 0.0 0 0 \n", + "\n", + "[21477 rows x 63 columns]" + ] + }, + "execution_count": 2, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "df" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "_cell_guid": "731a952c-b292-46e3-be7a-4afffe2b4ff1", + "_uuid": "5d165c279ce22afc0a874e32931d7b0ebb0717f9" + }, + "source": [ + "Let's see what the data looks like..." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "_cell_guid": "79c7e3d0-c299-4dcb-8224-4455121ee9b0", + "_uuid": "d629ff2d2480ee46fbb7e2d37f6b5fab8052498a", + "scrolled": true + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "metadata": { + "_cell_guid": "25c3f986-fd14-4a3f-baff-02571ad665eb", + "_uuid": "5a7da58320ab35ab1bcf83a62209afbe40b672fe" + }, + "source": [ + "# Plot for annual produce of different countries with quantity in y-axis and years in x-axis" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
Area AbbreviationArea CodeAreaItem CodeItemElement CodeElementUnitlatitudelongitude...Y2004Y2005Y2006Y2007Y2008Y2009Y2010Y2011Y2012Y2013
0AFG2Afghanistan2511Wheat and products5142Food1000 tonnes33.9467.71...3249.03486.03704.04164.04252.04538.04605.04711.048104895
1AFG2Afghanistan2805Rice (Milled Equivalent)5142Food1000 tonnes33.9467.71...419.0445.0546.0455.0490.0415.0442.0476.0425422
2AFG2Afghanistan2513Barley and products5521Feed1000 tonnes33.9467.71...58.0236.0262.0263.0230.0379.0315.0203.0367360
3AFG2Afghanistan2513Barley and products5142Food1000 tonnes33.9467.71...185.043.044.048.062.055.060.072.07889
4AFG2Afghanistan2514Maize and products5521Feed1000 tonnes33.9467.71...120.0208.0233.0249.0247.0195.0178.0191.0200200
5AFG2Afghanistan2514Maize and products5142Food1000 tonnes33.9467.71...231.067.082.067.069.071.082.073.07776
6AFG2Afghanistan2517Millet and products5142Food1000 tonnes33.9467.71...15.021.011.019.021.018.014.014.01412
7AFG2Afghanistan2520Cereals, Other5142Food1000 tonnes33.9467.71...2.01.01.00.00.00.00.00.000
8AFG2Afghanistan2531Potatoes and products5142Food1000 tonnes33.9467.71...276.0294.0294.0260.0242.0250.0192.0169.0196230
9AFG2Afghanistan2536Sugar cane5521Feed1000 tonnes33.9467.71...50.029.061.065.054.0114.083.083.06981
10AFG2Afghanistan2537Sugar beet5521Feed1000 tonnes33.9467.71...0.00.00.00.00.00.00.00.000
11AFG2Afghanistan2542Sugar (Raw Equivalent)5142Food1000 tonnes33.9467.71...124.0152.0169.0192.0217.0231.0240.0240.0250255
12AFG2Afghanistan2543Sweeteners, Other5142Food1000 tonnes33.9467.71...9.015.012.06.011.02.09.021.02416
13AFG2Afghanistan2745Honey5142Food1000 tonnes33.9467.71...3.03.03.03.03.03.03.02.022
14AFG2Afghanistan2549Pulses, Other and products5521Feed1000 tonnes33.9467.71...3.02.03.03.03.05.04.05.044
15AFG2Afghanistan2549Pulses, Other and products5142Food1000 tonnes33.9467.71...17.035.037.040.054.080.066.081.06374
16AFG2Afghanistan2551Nuts and products5142Food1000 tonnes33.9467.71...11.013.024.034.042.028.066.071.07044
17AFG2Afghanistan2560Coconuts - Incl Copra5142Food1000 tonnes33.9467.71...0.00.00.00.00.00.00.00.000
18AFG2Afghanistan2561Sesame seed5142Food1000 tonnes33.9467.71...16.016.013.016.016.016.019.017.01616
19AFG2Afghanistan2563Olives (including preserved)5142Food1000 tonnes33.9467.71...1.01.00.00.02.03.02.02.022
20AFG2Afghanistan2571Soyabean Oil5142Food1000 tonnes33.9467.71...6.035.018.021.011.06.015.016.01616
21AFG2Afghanistan2572Groundnut Oil5142Food1000 tonnes33.9467.71...0.00.00.00.00.00.00.00.000
22AFG2Afghanistan2573Sunflowerseed Oil5142Food1000 tonnes33.9467.71...4.06.05.09.03.08.015.016.01723
23AFG2Afghanistan2574Rape and Mustard Oil5142Food1000 tonnes33.9467.71...0.01.03.05.06.06.01.02.022
24AFG2Afghanistan2575Cottonseed Oil5142Food1000 tonnes33.9467.71...2.03.03.03.03.04.03.03.034
25AFG2Afghanistan2577Palm Oil5142Food1000 tonnes33.9467.71...71.069.056.051.036.053.059.051.06164
26AFG2Afghanistan2579Sesameseed Oil5142Food1000 tonnes33.9467.71...1.01.01.02.02.01.01.02.011
27AFG2Afghanistan2580Olive Oil5142Food1000 tonnes33.9467.71...0.00.00.00.00.01.01.01.011
28AFG2Afghanistan2586Oilcrops Oil, Other5142Food1000 tonnes33.9467.71...0.01.00.00.03.01.02.02.022
29AFG2Afghanistan2601Tomatoes and products5142Food1000 tonnes33.9467.71...2.02.08.01.00.00.00.00.000
..................................................................
21447ZWE181Zimbabwe2765Crustaceans5142Food1000 tonnes-19.0229.15...0.00.00.00.00.00.00.00.000
21448ZWE181Zimbabwe2766Cephalopods5142Food1000 tonnes-19.0229.15...0.00.00.00.00.00.00.00.000
21449ZWE181Zimbabwe2767Molluscs, Other5142Food1000 tonnes-19.0229.15...0.00.00.00.00.00.01.00.000
21450ZWE181Zimbabwe2775Aquatic Plants5142Food1000 tonnes-19.0229.15...0.00.00.00.00.00.00.00.000
21451ZWE181Zimbabwe2680Infant food5142Food1000 tonnes-19.0229.15...0.00.00.00.00.00.00.00.000
21452ZWE181Zimbabwe2905Cereals - Excluding Beer5521Feed1000 tonnes-19.0229.15...75.054.075.055.063.062.055.055.05555
21453ZWE181Zimbabwe2905Cereals - Excluding Beer5142Food1000 tonnes-19.0229.15...1844.01842.01944.01962.01918.01980.02011.02094.020712016
21454ZWE181Zimbabwe2907Starchy Roots5142Food1000 tonnes-19.0229.15...223.0236.0238.0228.0245.0258.0258.0269.0272276
21455ZWE181Zimbabwe2908Sugar Crops5142Food1000 tonnes-19.0229.15...0.00.00.00.00.00.00.00.000
21456ZWE181Zimbabwe2909Sugar & Sweeteners5142Food1000 tonnes-19.0229.15...335.0313.0339.0302.0285.0287.0314.0336.0396416
21457ZWE181Zimbabwe2911Pulses5142Food1000 tonnes-19.0229.15...63.059.061.057.069.078.068.056.05255
21458ZWE181Zimbabwe2912Treenuts5142Food1000 tonnes-19.0229.15...1.02.01.02.02.03.04.02.043
21459ZWE181Zimbabwe2913Oilcrops5521Feed1000 tonnes-19.0229.15...36.046.041.033.031.019.024.017.02730
21460ZWE181Zimbabwe2913Oilcrops5142Food1000 tonnes-19.0229.15...60.059.061.062.048.044.041.040.03838
21461ZWE181Zimbabwe2914Vegetable Oils5142Food1000 tonnes-19.0229.15...111.0114.0112.0114.0134.0135.0137.0147.0159160
21462ZWE181Zimbabwe2918Vegetables5142Food1000 tonnes-19.0229.15...161.0166.0208.0185.0137.0179.0215.0217.0227227
21463ZWE181Zimbabwe2919Fruits - Excluding Wine5142Food1000 tonnes-19.0229.15...191.0134.0167.0177.0185.0184.0211.0230.0246217
21464ZWE181Zimbabwe2922Stimulants5142Food1000 tonnes-19.0229.15...7.021.014.024.016.011.023.011.01010
21465ZWE181Zimbabwe2923Spices5142Food1000 tonnes-19.0229.15...7.011.07.012.016.016.014.011.01212
21466ZWE181Zimbabwe2924Alcoholic Beverages5142Food1000 tonnes-19.0229.15...294.0290.0316.0355.0398.0437.0448.0476.0525516
21467ZWE181Zimbabwe2943Meat5142Food1000 tonnes-19.0229.15...222.0228.0233.0238.0242.0265.0262.0277.0280258
21468ZWE181Zimbabwe2945Offals5142Food1000 tonnes-19.0229.15...20.020.021.021.021.021.021.021.02222
21469ZWE181Zimbabwe2946Animal fats5142Food1000 tonnes-19.0229.15...26.026.029.029.027.031.030.025.02620
21470ZWE181Zimbabwe2949Eggs5142Food1000 tonnes-19.0229.15...15.018.018.021.022.027.027.024.02425
21471ZWE181Zimbabwe2948Milk - Excluding Butter5521Feed1000 tonnes-19.0229.15...21.021.021.021.021.023.025.025.03031
21472ZWE181Zimbabwe2948Milk - Excluding Butter5142Food1000 tonnes-19.0229.15...373.0357.0359.0356.0341.0385.0418.0457.0426451
21473ZWE181Zimbabwe2960Fish, Seafood5521Feed1000 tonnes-19.0229.15...5.04.09.06.09.05.015.015.01515
21474ZWE181Zimbabwe2960Fish, Seafood5142Food1000 tonnes-19.0229.15...18.014.017.014.015.018.029.040.04040
21475ZWE181Zimbabwe2961Aquatic Products, Other5142Food1000 tonnes-19.0229.15...0.00.00.00.00.00.00.00.000
21476ZWE181Zimbabwe2928Miscellaneous5142Food1000 tonnes-19.0229.15...0.00.00.00.00.00.00.00.000
\n", + "

21477 rows × 63 columns

\n", + "
" + ], + "text/plain": [ + " Area Abbreviation Area Code Area Item Code \\\n", + "0 AFG 2 Afghanistan 2511 \n", + "1 AFG 2 Afghanistan 2805 \n", + "2 AFG 2 Afghanistan 2513 \n", + "3 AFG 2 Afghanistan 2513 \n", + "4 AFG 2 Afghanistan 2514 \n", + "5 AFG 2 Afghanistan 2514 \n", + "6 AFG 2 Afghanistan 2517 \n", + "7 AFG 2 Afghanistan 2520 \n", + "8 AFG 2 Afghanistan 2531 \n", + "9 AFG 2 Afghanistan 2536 \n", + "10 AFG 2 Afghanistan 2537 \n", + "11 AFG 2 Afghanistan 2542 \n", + "12 AFG 2 Afghanistan 2543 \n", + "13 AFG 2 Afghanistan 2745 \n", + "14 AFG 2 Afghanistan 2549 \n", + "15 AFG 2 Afghanistan 2549 \n", + "16 AFG 2 Afghanistan 2551 \n", + "17 AFG 2 Afghanistan 2560 \n", + "18 AFG 2 Afghanistan 2561 \n", + "19 AFG 2 Afghanistan 2563 \n", + "20 AFG 2 Afghanistan 2571 \n", + "21 AFG 2 Afghanistan 2572 \n", + "22 AFG 2 Afghanistan 2573 \n", + "23 AFG 2 Afghanistan 2574 \n", + "24 AFG 2 Afghanistan 2575 \n", + "25 AFG 2 Afghanistan 2577 \n", + "26 AFG 2 Afghanistan 2579 \n", + "27 AFG 2 Afghanistan 2580 \n", + "28 AFG 2 Afghanistan 2586 \n", + "29 AFG 2 Afghanistan 2601 \n", + "... ... ... ... ... \n", + "21447 ZWE 181 Zimbabwe 2765 \n", + "21448 ZWE 181 Zimbabwe 2766 \n", + "21449 ZWE 181 Zimbabwe 2767 \n", + "21450 ZWE 181 Zimbabwe 2775 \n", + "21451 ZWE 181 Zimbabwe 2680 \n", + "21452 ZWE 181 Zimbabwe 2905 \n", + "21453 ZWE 181 Zimbabwe 2905 \n", + "21454 ZWE 181 Zimbabwe 2907 \n", + "21455 ZWE 181 Zimbabwe 2908 \n", + "21456 ZWE 181 Zimbabwe 2909 \n", + "21457 ZWE 181 Zimbabwe 2911 \n", + "21458 ZWE 181 Zimbabwe 2912 \n", + "21459 ZWE 181 Zimbabwe 2913 \n", + "21460 ZWE 181 Zimbabwe 2913 \n", + "21461 ZWE 181 Zimbabwe 2914 \n", + "21462 ZWE 181 Zimbabwe 2918 \n", + "21463 ZWE 181 Zimbabwe 2919 \n", + "21464 ZWE 181 Zimbabwe 2922 \n", + "21465 ZWE 181 Zimbabwe 2923 \n", + "21466 ZWE 181 Zimbabwe 2924 \n", + "21467 ZWE 181 Zimbabwe 2943 \n", + "21468 ZWE 181 Zimbabwe 2945 \n", + "21469 ZWE 181 Zimbabwe 2946 \n", + "21470 ZWE 181 Zimbabwe 2949 \n", + "21471 ZWE 181 Zimbabwe 2948 \n", + "21472 ZWE 181 Zimbabwe 2948 \n", + "21473 ZWE 181 Zimbabwe 2960 \n", + "21474 ZWE 181 Zimbabwe 2960 \n", + "21475 ZWE 181 Zimbabwe 2961 \n", + "21476 ZWE 181 Zimbabwe 2928 \n", + "\n", + " Item Element Code Element Unit \\\n", + "0 Wheat and products 5142 Food 1000 tonnes \n", + "1 Rice (Milled Equivalent) 5142 Food 1000 tonnes \n", + "2 Barley and products 5521 Feed 1000 tonnes \n", + "3 Barley and products 5142 Food 1000 tonnes \n", + "4 Maize and products 5521 Feed 1000 tonnes \n", + "5 Maize and products 5142 Food 1000 tonnes \n", + "6 Millet and products 5142 Food 1000 tonnes \n", + "7 Cereals, Other 5142 Food 1000 tonnes \n", + "8 Potatoes and products 5142 Food 1000 tonnes \n", + "9 Sugar cane 5521 Feed 1000 tonnes \n", + "10 Sugar beet 5521 Feed 1000 tonnes \n", + "11 Sugar (Raw Equivalent) 5142 Food 1000 tonnes \n", + "12 Sweeteners, Other 5142 Food 1000 tonnes \n", + "13 Honey 5142 Food 1000 tonnes \n", + "14 Pulses, Other and products 5521 Feed 1000 tonnes \n", + "15 Pulses, Other and products 5142 Food 1000 tonnes \n", + "16 Nuts and products 5142 Food 1000 tonnes \n", + "17 Coconuts - Incl Copra 5142 Food 1000 tonnes \n", + "18 Sesame seed 5142 Food 1000 tonnes \n", + "19 Olives (including preserved) 5142 Food 1000 tonnes \n", + "20 Soyabean Oil 5142 Food 1000 tonnes \n", + "21 Groundnut Oil 5142 Food 1000 tonnes \n", + "22 Sunflowerseed Oil 5142 Food 1000 tonnes \n", + "23 Rape and Mustard Oil 5142 Food 1000 tonnes \n", + "24 Cottonseed Oil 5142 Food 1000 tonnes \n", + "25 Palm Oil 5142 Food 1000 tonnes \n", + "26 Sesameseed Oil 5142 Food 1000 tonnes \n", + "27 Olive Oil 5142 Food 1000 tonnes \n", + "28 Oilcrops Oil, Other 5142 Food 1000 tonnes \n", + "29 Tomatoes and products 5142 Food 1000 tonnes \n", + "... ... ... ... ... \n", + "21447 Crustaceans 5142 Food 1000 tonnes \n", + "21448 Cephalopods 5142 Food 1000 tonnes \n", + "21449 Molluscs, Other 5142 Food 1000 tonnes \n", + "21450 Aquatic Plants 5142 Food 1000 tonnes \n", + "21451 Infant food 5142 Food 1000 tonnes \n", + "21452 Cereals - Excluding Beer 5521 Feed 1000 tonnes \n", + "21453 Cereals - Excluding Beer 5142 Food 1000 tonnes \n", + "21454 Starchy Roots 5142 Food 1000 tonnes \n", + "21455 Sugar Crops 5142 Food 1000 tonnes \n", + "21456 Sugar & Sweeteners 5142 Food 1000 tonnes \n", + "21457 Pulses 5142 Food 1000 tonnes \n", + "21458 Treenuts 5142 Food 1000 tonnes \n", + "21459 Oilcrops 5521 Feed 1000 tonnes \n", + "21460 Oilcrops 5142 Food 1000 tonnes \n", + "21461 Vegetable Oils 5142 Food 1000 tonnes \n", + "21462 Vegetables 5142 Food 1000 tonnes \n", + "21463 Fruits - Excluding Wine 5142 Food 1000 tonnes \n", + "21464 Stimulants 5142 Food 1000 tonnes \n", + "21465 Spices 5142 Food 1000 tonnes \n", + "21466 Alcoholic Beverages 5142 Food 1000 tonnes \n", + "21467 Meat 5142 Food 1000 tonnes \n", + "21468 Offals 5142 Food 1000 tonnes \n", + "21469 Animal fats 5142 Food 1000 tonnes \n", + "21470 Eggs 5142 Food 1000 tonnes \n", + "21471 Milk - Excluding Butter 5521 Feed 1000 tonnes \n", + "21472 Milk - Excluding Butter 5142 Food 1000 tonnes \n", + "21473 Fish, Seafood 5521 Feed 1000 tonnes \n", + "21474 Fish, Seafood 5142 Food 1000 tonnes \n", + "21475 Aquatic Products, Other 5142 Food 1000 tonnes \n", + "21476 Miscellaneous 5142 Food 1000 tonnes \n", + "\n", + " latitude longitude ... Y2004 Y2005 Y2006 Y2007 Y2008 \\\n", + "0 33.94 67.71 ... 3249.0 3486.0 3704.0 4164.0 4252.0 \n", + "1 33.94 67.71 ... 419.0 445.0 546.0 455.0 490.0 \n", + "2 33.94 67.71 ... 58.0 236.0 262.0 263.0 230.0 \n", + "3 33.94 67.71 ... 185.0 43.0 44.0 48.0 62.0 \n", + "4 33.94 67.71 ... 120.0 208.0 233.0 249.0 247.0 \n", + "5 33.94 67.71 ... 231.0 67.0 82.0 67.0 69.0 \n", + "6 33.94 67.71 ... 15.0 21.0 11.0 19.0 21.0 \n", + "7 33.94 67.71 ... 2.0 1.0 1.0 0.0 0.0 \n", + "8 33.94 67.71 ... 276.0 294.0 294.0 260.0 242.0 \n", + "9 33.94 67.71 ... 50.0 29.0 61.0 65.0 54.0 \n", + "10 33.94 67.71 ... 0.0 0.0 0.0 0.0 0.0 \n", + "11 33.94 67.71 ... 124.0 152.0 169.0 192.0 217.0 \n", + "12 33.94 67.71 ... 9.0 15.0 12.0 6.0 11.0 \n", + "13 33.94 67.71 ... 3.0 3.0 3.0 3.0 3.0 \n", + "14 33.94 67.71 ... 3.0 2.0 3.0 3.0 3.0 \n", + "15 33.94 67.71 ... 17.0 35.0 37.0 40.0 54.0 \n", + "16 33.94 67.71 ... 11.0 13.0 24.0 34.0 42.0 \n", + "17 33.94 67.71 ... 0.0 0.0 0.0 0.0 0.0 \n", + "18 33.94 67.71 ... 16.0 16.0 13.0 16.0 16.0 \n", + "19 33.94 67.71 ... 1.0 1.0 0.0 0.0 2.0 \n", + "20 33.94 67.71 ... 6.0 35.0 18.0 21.0 11.0 \n", + "21 33.94 67.71 ... 0.0 0.0 0.0 0.0 0.0 \n", + "22 33.94 67.71 ... 4.0 6.0 5.0 9.0 3.0 \n", + "23 33.94 67.71 ... 0.0 1.0 3.0 5.0 6.0 \n", + "24 33.94 67.71 ... 2.0 3.0 3.0 3.0 3.0 \n", + "25 33.94 67.71 ... 71.0 69.0 56.0 51.0 36.0 \n", + "26 33.94 67.71 ... 1.0 1.0 1.0 2.0 2.0 \n", + "27 33.94 67.71 ... 0.0 0.0 0.0 0.0 0.0 \n", + "28 33.94 67.71 ... 0.0 1.0 0.0 0.0 3.0 \n", + "29 33.94 67.71 ... 2.0 2.0 8.0 1.0 0.0 \n", + "... ... ... ... ... ... ... ... ... \n", + "21447 -19.02 29.15 ... 0.0 0.0 0.0 0.0 0.0 \n", + "21448 -19.02 29.15 ... 0.0 0.0 0.0 0.0 0.0 \n", + "21449 -19.02 29.15 ... 0.0 0.0 0.0 0.0 0.0 \n", + "21450 -19.02 29.15 ... 0.0 0.0 0.0 0.0 0.0 \n", + "21451 -19.02 29.15 ... 0.0 0.0 0.0 0.0 0.0 \n", + "21452 -19.02 29.15 ... 75.0 54.0 75.0 55.0 63.0 \n", + "21453 -19.02 29.15 ... 1844.0 1842.0 1944.0 1962.0 1918.0 \n", + "21454 -19.02 29.15 ... 223.0 236.0 238.0 228.0 245.0 \n", + "21455 -19.02 29.15 ... 0.0 0.0 0.0 0.0 0.0 \n", + "21456 -19.02 29.15 ... 335.0 313.0 339.0 302.0 285.0 \n", + "21457 -19.02 29.15 ... 63.0 59.0 61.0 57.0 69.0 \n", + "21458 -19.02 29.15 ... 1.0 2.0 1.0 2.0 2.0 \n", + "21459 -19.02 29.15 ... 36.0 46.0 41.0 33.0 31.0 \n", + "21460 -19.02 29.15 ... 60.0 59.0 61.0 62.0 48.0 \n", + "21461 -19.02 29.15 ... 111.0 114.0 112.0 114.0 134.0 \n", + "21462 -19.02 29.15 ... 161.0 166.0 208.0 185.0 137.0 \n", + "21463 -19.02 29.15 ... 191.0 134.0 167.0 177.0 185.0 \n", + "21464 -19.02 29.15 ... 7.0 21.0 14.0 24.0 16.0 \n", + "21465 -19.02 29.15 ... 7.0 11.0 7.0 12.0 16.0 \n", + "21466 -19.02 29.15 ... 294.0 290.0 316.0 355.0 398.0 \n", + "21467 -19.02 29.15 ... 222.0 228.0 233.0 238.0 242.0 \n", + "21468 -19.02 29.15 ... 20.0 20.0 21.0 21.0 21.0 \n", + "21469 -19.02 29.15 ... 26.0 26.0 29.0 29.0 27.0 \n", + "21470 -19.02 29.15 ... 15.0 18.0 18.0 21.0 22.0 \n", + "21471 -19.02 29.15 ... 21.0 21.0 21.0 21.0 21.0 \n", + "21472 -19.02 29.15 ... 373.0 357.0 359.0 356.0 341.0 \n", + "21473 -19.02 29.15 ... 5.0 4.0 9.0 6.0 9.0 \n", + "21474 -19.02 29.15 ... 18.0 14.0 17.0 14.0 15.0 \n", + "21475 -19.02 29.15 ... 0.0 0.0 0.0 0.0 0.0 \n", + "21476 -19.02 29.15 ... 0.0 0.0 0.0 0.0 0.0 \n", + "\n", + " Y2009 Y2010 Y2011 Y2012 Y2013 \n", + "0 4538.0 4605.0 4711.0 4810 4895 \n", + "1 415.0 442.0 476.0 425 422 \n", + "2 379.0 315.0 203.0 367 360 \n", + "3 55.0 60.0 72.0 78 89 \n", + "4 195.0 178.0 191.0 200 200 \n", + "5 71.0 82.0 73.0 77 76 \n", + "6 18.0 14.0 14.0 14 12 \n", + "7 0.0 0.0 0.0 0 0 \n", + "8 250.0 192.0 169.0 196 230 \n", + "9 114.0 83.0 83.0 69 81 \n", + "10 0.0 0.0 0.0 0 0 \n", + "11 231.0 240.0 240.0 250 255 \n", + "12 2.0 9.0 21.0 24 16 \n", + "13 3.0 3.0 2.0 2 2 \n", + "14 5.0 4.0 5.0 4 4 \n", + "15 80.0 66.0 81.0 63 74 \n", + "16 28.0 66.0 71.0 70 44 \n", + "17 0.0 0.0 0.0 0 0 \n", + "18 16.0 19.0 17.0 16 16 \n", + "19 3.0 2.0 2.0 2 2 \n", + "20 6.0 15.0 16.0 16 16 \n", + "21 0.0 0.0 0.0 0 0 \n", + "22 8.0 15.0 16.0 17 23 \n", + "23 6.0 1.0 2.0 2 2 \n", + "24 4.0 3.0 3.0 3 4 \n", + "25 53.0 59.0 51.0 61 64 \n", + "26 1.0 1.0 2.0 1 1 \n", + "27 1.0 1.0 1.0 1 1 \n", + "28 1.0 2.0 2.0 2 2 \n", + "29 0.0 0.0 0.0 0 0 \n", + "... ... ... ... ... ... \n", + "21447 0.0 0.0 0.0 0 0 \n", + "21448 0.0 0.0 0.0 0 0 \n", + "21449 0.0 1.0 0.0 0 0 \n", + "21450 0.0 0.0 0.0 0 0 \n", + "21451 0.0 0.0 0.0 0 0 \n", + "21452 62.0 55.0 55.0 55 55 \n", + "21453 1980.0 2011.0 2094.0 2071 2016 \n", + "21454 258.0 258.0 269.0 272 276 \n", + "21455 0.0 0.0 0.0 0 0 \n", + "21456 287.0 314.0 336.0 396 416 \n", + "21457 78.0 68.0 56.0 52 55 \n", + "21458 3.0 4.0 2.0 4 3 \n", + "21459 19.0 24.0 17.0 27 30 \n", + "21460 44.0 41.0 40.0 38 38 \n", + "21461 135.0 137.0 147.0 159 160 \n", + "21462 179.0 215.0 217.0 227 227 \n", + "21463 184.0 211.0 230.0 246 217 \n", + "21464 11.0 23.0 11.0 10 10 \n", + "21465 16.0 14.0 11.0 12 12 \n", + "21466 437.0 448.0 476.0 525 516 \n", + "21467 265.0 262.0 277.0 280 258 \n", + "21468 21.0 21.0 21.0 22 22 \n", + "21469 31.0 30.0 25.0 26 20 \n", + "21470 27.0 27.0 24.0 24 25 \n", + "21471 23.0 25.0 25.0 30 31 \n", + "21472 385.0 418.0 457.0 426 451 \n", + "21473 5.0 15.0 15.0 15 15 \n", + "21474 18.0 29.0 40.0 40 40 \n", + "21475 0.0 0.0 0.0 0 0 \n", + "21476 0.0 0.0 0.0 0 0 \n", + "\n", + "[21477 rows x 63 columns]" + ] + }, + "execution_count": 3, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "df" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": { + "_cell_guid": "347e620f-b0e4-448e-81c7-e164f560c5a3", + "_uuid": "0acdd759950f5df3298224b0804562973663a11d", + "scrolled": false + }, + "outputs": [ + { + "data": { + "image/png": "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D37wg6vmzZtXfKZthwwZUrVgwYKYH/7wh6Uvv/zy125w9PDDD5eMHDmyX3x8fGNGRkadJP3617/uVFVV5T71pkG5ubnBaWlpDfPmzSuprq52bdq0KVzSMY/HY+vr601ISIg9088LgaglM3Uvp4SEBO/UqVNL33zzzfi77777mCRVVla6U1JSGiWp+dIeF8rn8+mVV16JmT17dumCBQviRowY8bWbcdXU1LgkKTExsam8vNy1ZMmS0842RCvkwO8dWVlZIS6XS80zaDdv3hyW1cPc5AAAIABJREFUlJTUcLHnUVRUlLeiouKsBeb48eOrn3322f3Tpk3rlZmZuevUG2hGR0d7T/0jSnR0tLeqqurk/rKzs4P79etXn56efmTv3r0hW7ZsCbv11ltPPhfO9rPImYwaNary5Zdfjvuv//qv4nfeeafDufKHhITYOXPmHP7d736XeO2111ZK0o033ljx29/+NmHBggUHXC6XPv/887DRo0fXStKdd955/D//8z8TKysr3SNGjPjWf2rs2LEjZOzYsdVjx46tXr58ece9e/cGJyYmnvY/OgAAuJzaZwHskOZrAEsn/pL8wgsv7PN4vv0pmD59evmLL77YqXfv3v179OhRN3jw4GpJ+uMf/xhXXl7uvu2223pKUkJCQsOaNWu4lhROy+VyafHixXseeeSR5GeffTYxJCTEJiUl1dfW1rqOHDkSNGTIkH6SNHHixLJnn3226F//9V8P3nTTTb19Pp+CgoLs/PnzD9x0003V8+fP33f77bf39Pl8iouLa1y7du3ulhz/wQcfPD5p0qSeAwYM6Jeenl6TlpZ21jud48pxuVxasmTJnh/96EfJ8+fPT4yNjW0KDw/3/vKXvywcP3581fPPP1/fp0+f9D59+tT279+/RpKuueaa2gEDBtT06tUrPSUlpf7UyzrMmjWrZNKkSb06d+7cuH79+tw//vGP+2bMmHFVQ0ODkaRf/OIXB1taALfkvOHcwpWSkJDgXbZsWe6YMWP6durUqem+++4re+CBB4499dRT3R544IFvzfRq9vvf/77g3nvvTZs/f37ihAkTyiIjI0/+4Sw5ObmpR48edVOnTi1rHnvuuecSg4KCbPPPCPfff//RsLAw3/z58xM9Ho8NDw/3vvHGG/mSdO+99x7t169f/wEDBtS8/fbb+0738wIuj3/5l385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\n", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "area_list = list(df['Area'].unique())\n", + "year_list = list(df.iloc[:,10:].columns)\n", + "\n", + "plt.figure(figsize=(24,12))\n", + "for ar in area_list:\n", + " yearly_produce = []\n", + " for yr in year_list:\n", + " yearly_produce.append(df[yr][df['Area'] == ar].sum())\n", + " plt.plot(yearly_produce, label=ar)\n", + "plt.xticks(np.arange(53), tuple(year_list), rotation=60)\n", + "plt.legend(bbox_to_anchor=(0., 1.02, 1., .102), loc=3, ncol=8, mode=\"expand\", borderaxespad=0.)\n", + "plt.savefig('p.png')\n", + "plt.show()" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "
" + ] + }, + "execution_count": 5, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "plt.figure(figsize=(24,12))" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "_cell_guid": "2ebe07e3-739b-4f39-8736-a512426c05bf", + "_uuid": "70900ec0ff5e248cd382ee53b5927cb671efa80e", + "collapsed": true + }, + "source": [ + "Clearly, China, India and US stand out here. So, these are the countries with most food and feed production.\n", + "\n", + "Now, let's have a close look at their food and feed data\n", + "\n", + "# Food and feed plot for the whole dataset" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": { + "_cell_guid": "ec0c911d-e154-4f8a-a79f-ced4896d5115", + "_uuid": "683dc56125b3a4c66b1e140098ec91490cbbe96f", + "scrolled": true + }, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/anaconda3/lib/python3.7/site-packages/seaborn/categorical.py:3666: UserWarning: The `factorplot` function has been renamed to `catplot`. The original name will be removed in a future release. Please update your code. Note that the default `kind` in `factorplot` (`'point'`) has changed `'strip'` in `catplot`.\n", + " warnings.warn(msg)\n" + ] + }, + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "sns.factorplot(\"Element\", data=df, kind=\"count\")\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "_cell_guid": "189c74af-e6e4-4ddd-a73c-3725f3aa8124", + "_uuid": "bfd404fb5dbb48c3e3bd1dcd45fb27a5fb475a00" + }, + "source": [ + "So, there is a huge difference in food and feed production. Now, we have obvious assumptions about the following plots after looking at this huge difference.\n", + "\n", + "# Food and feed plot for the largest producers(India, USA, China)" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": { + "_cell_guid": "0bf44e4e-d4c4-4f74-ae9f-82f52139d182", + "_uuid": "be1bc3d49c8cee62f48a09ada0db3170adcedc17" + }, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/anaconda3/lib/python3.7/site-packages/seaborn/categorical.py:3666: UserWarning: The `factorplot` function has been renamed to `catplot`. The original name will be removed in a future release. Please update your code. Note that the default `kind` in `factorplot` (`'point'`) has changed `'strip'` in `catplot`.\n", + " warnings.warn(msg)\n", + "/anaconda3/lib/python3.7/site-packages/seaborn/categorical.py:3672: UserWarning: The `size` paramter has been renamed to `height`; please update your code.\n", + " warnings.warn(msg, UserWarning)\n" + ] + }, + { + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 7, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "sns.factorplot(\"Area\", data=df[(df['Area'] == \"India\") | (df['Area'] == \"China, mainland\") | (df['Area'] == \"United States of America\")], kind=\"count\", hue=\"Element\", size=8, aspect=.8)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "_cell_guid": "94c19dc8-b1e7-4b61-b81f-422c27184c4e", + "_uuid": "0d1cfc7acc74847dbc5813b9b3bd0eb9db450985" + }, + "source": [ + "Though, there is a huge difference between feed and food production, these countries' total production and their ranks depend on feed production." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "_cell_guid": "9dba87b4-fa51-43ef-95ae-f31396c20146", + "_uuid": "43e0f00abf706ab1782ebb78cefc38aca17316e6" + }, + "source": [ + "Now, we create a dataframe with countries as index and their annual produce as columns from 1961 to 2013." + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": { + "_cell_guid": "c4a5f859-0384-4c8e-b894-3f747aec8cf9", + "_uuid": "84dd7a2b601479728dd172d3100951553c2daff5", + "scrolled": true + }, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
AfghanistanAlbaniaAlgeriaAngolaAntigua and BarbudaArgentinaArmeniaAustraliaAustriaAzerbaijan...United Republic of TanzaniaUnited States of AmericaUruguayUzbekistanVanuatuVenezuela (Bolivarian Republic of)Viet NamYemenZambiaZimbabwe
09481.01706.07488.04834.092.043402.00.025795.022542.00.0...12367.0559347.04631.00.097.09523.023856.02982.02976.03260.0
19414.01749.07235.04775.094.040784.00.027618.022627.00.0...12810.0556319.04448.00.0101.09369.025220.03038.03057.03503.0
29194.01767.06861.05240.0105.040219.00.028902.023637.00.0...13109.0552630.04682.00.0103.09788.026053.03147.03069.03479.0
310170.01889.07255.05286.095.041638.00.029107.024099.00.0...12965.0555677.04723.00.0102.010539.026377.03224.03121.03738.0
410473.01884.07509.05527.084.044936.00.028961.022664.00.0...13742.0589288.04581.00.0107.010641.026961.03328.03236.03940.0
\n", + "

5 rows × 174 columns

\n", + "
" + ], + "text/plain": [ + " Afghanistan Albania Algeria Angola Antigua and Barbuda Argentina \\\n", + "0 9481.0 1706.0 7488.0 4834.0 92.0 43402.0 \n", + "1 9414.0 1749.0 7235.0 4775.0 94.0 40784.0 \n", + "2 9194.0 1767.0 6861.0 5240.0 105.0 40219.0 \n", + "3 10170.0 1889.0 7255.0 5286.0 95.0 41638.0 \n", + "4 10473.0 1884.0 7509.0 5527.0 84.0 44936.0 \n", + "\n", + " Armenia Australia Austria Azerbaijan ... \\\n", + "0 0.0 25795.0 22542.0 0.0 ... \n", + "1 0.0 27618.0 22627.0 0.0 ... \n", + "2 0.0 28902.0 23637.0 0.0 ... \n", + "3 0.0 29107.0 24099.0 0.0 ... \n", + "4 0.0 28961.0 22664.0 0.0 ... \n", + "\n", + " United Republic of Tanzania United States of America Uruguay Uzbekistan \\\n", + "0 12367.0 559347.0 4631.0 0.0 \n", + "1 12810.0 556319.0 4448.0 0.0 \n", + "2 13109.0 552630.0 4682.0 0.0 \n", + "3 12965.0 555677.0 4723.0 0.0 \n", + "4 13742.0 589288.0 4581.0 0.0 \n", + "\n", + " Vanuatu Venezuela (Bolivarian Republic of) Viet Nam Yemen Zambia \\\n", + "0 97.0 9523.0 23856.0 2982.0 2976.0 \n", + "1 101.0 9369.0 25220.0 3038.0 3057.0 \n", + "2 103.0 9788.0 26053.0 3147.0 3069.0 \n", + "3 102.0 10539.0 26377.0 3224.0 3121.0 \n", + "4 107.0 10641.0 26961.0 3328.0 3236.0 \n", + "\n", + " Zimbabwe \n", + "0 3260.0 \n", + "1 3503.0 \n", + "2 3479.0 \n", + "3 3738.0 \n", + "4 3940.0 \n", + "\n", + "[5 rows x 174 columns]" + ] + }, + "execution_count": 8, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "new_df_dict = {}\n", + "for ar in area_list:\n", + " yearly_produce = []\n", + " for yr in year_list:\n", + " yearly_produce.append(df[yr][df['Area']==ar].sum())\n", + " new_df_dict[ar] = yearly_produce\n", + "new_df = pd.DataFrame(new_df_dict)\n", + "\n", + "new_df.head()" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "_cell_guid": "15fbe29c-5cea-4ac3-9b95-f92acd89b336", + "_uuid": "ea48f75e9824a0c4c1a5f19cbd63e59a6cb44fe1" + }, + "source": [ + "Now, this is not perfect so we transpose this dataframe and add column names." + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": { + "_cell_guid": "145f751e-4f5b-4811-a68c-9d20b3c36e10", + "_uuid": "28e765d82bb4ebec3be49200a30fc4e0eabb24d7" + }, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
Y1961Y1962Y1963Y1964Y1965Y1966Y1967Y1968Y1969Y1970...Y2004Y2005Y2006Y2007Y2008Y2009Y2010Y2011Y2012Y2013
Afghanistan9481.09414.09194.010170.010473.010169.011289.011508.011815.010454.0...16542.017658.018317.019248.019381.020661.021030.021100.022706.023007.0
Albania1706.01749.01767.01889.01884.01995.02046.02169.02230.02395.0...6637.06719.06911.06744.07168.07316.07907.08114.08221.08271.0
Algeria7488.07235.06861.07255.07509.07536.07986.08839.09003.09355.0...48619.049562.051067.049933.050916.057505.060071.065852.069365.072161.0
Angola4834.04775.05240.05286.05527.05677.05833.05685.06219.06460.0...25541.026696.028247.029877.032053.036985.038400.040573.038064.048639.0
Antigua and Barbuda92.094.0105.095.084.073.064.059.068.077.0...92.0115.0110.0122.0115.0114.0115.0118.0113.0119.0
\n", + "

5 rows × 53 columns

\n", + "
" + ], + "text/plain": [ + " Y1961 Y1962 Y1963 Y1964 Y1965 Y1966 \\\n", + "Afghanistan 9481.0 9414.0 9194.0 10170.0 10473.0 10169.0 \n", + "Albania 1706.0 1749.0 1767.0 1889.0 1884.0 1995.0 \n", + "Algeria 7488.0 7235.0 6861.0 7255.0 7509.0 7536.0 \n", + "Angola 4834.0 4775.0 5240.0 5286.0 5527.0 5677.0 \n", + "Antigua and Barbuda 92.0 94.0 105.0 95.0 84.0 73.0 \n", + "\n", + " Y1967 Y1968 Y1969 Y1970 ... Y2004 \\\n", + "Afghanistan 11289.0 11508.0 11815.0 10454.0 ... 16542.0 \n", + "Albania 2046.0 2169.0 2230.0 2395.0 ... 6637.0 \n", + "Algeria 7986.0 8839.0 9003.0 9355.0 ... 48619.0 \n", + "Angola 5833.0 5685.0 6219.0 6460.0 ... 25541.0 \n", + "Antigua and Barbuda 64.0 59.0 68.0 77.0 ... 92.0 \n", + "\n", + " Y2005 Y2006 Y2007 Y2008 Y2009 Y2010 \\\n", + "Afghanistan 17658.0 18317.0 19248.0 19381.0 20661.0 21030.0 \n", + "Albania 6719.0 6911.0 6744.0 7168.0 7316.0 7907.0 \n", + "Algeria 49562.0 51067.0 49933.0 50916.0 57505.0 60071.0 \n", + "Angola 26696.0 28247.0 29877.0 32053.0 36985.0 38400.0 \n", + "Antigua and Barbuda 115.0 110.0 122.0 115.0 114.0 115.0 \n", + "\n", + " Y2011 Y2012 Y2013 \n", + "Afghanistan 21100.0 22706.0 23007.0 \n", + "Albania 8114.0 8221.0 8271.0 \n", + "Algeria 65852.0 69365.0 72161.0 \n", + "Angola 40573.0 38064.0 48639.0 \n", + "Antigua and Barbuda 118.0 113.0 119.0 \n", + "\n", + "[5 rows x 53 columns]" + ] + }, + "execution_count": 9, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "new_df = pd.DataFrame.transpose(new_df)\n", + "new_df.columns = year_list\n", + "\n", + "new_df.head()" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "_cell_guid": "57929d23-e3d7-4955-92d1-6fa388eb774d", + "_uuid": "605f908af9ff88120fce2a2b59160816fcdcfa67" + }, + "source": [ + "Perfect! Now, we will do some feature engineering.\n", + "\n", + "# First, a new column which indicates mean produce of each state over the given years. Second, a ranking column which ranks countries on the basis of mean produce." + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": { + "_cell_guid": "ab91a322-0cb9-4edf-b5a2-cde82a237824", + "_uuid": "979f875019abef3ed85af75e000fe59d1de5a381" + }, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
Y1961Y1962Y1963Y1964Y1965Y1966Y1967Y1968Y1969Y1970...Y2006Y2007Y2008Y2009Y2010Y2011Y2012Y2013Mean_ProduceRank
Afghanistan9481.09414.09194.010170.010473.010169.011289.011508.011815.010454.0...18317.019248.019381.020661.021030.021100.022706.023007.013003.05660469.0
Albania1706.01749.01767.01889.01884.01995.02046.02169.02230.02395.0...6911.06744.07168.07316.07907.08114.08221.08271.04475.509434104.0
Algeria7488.07235.06861.07255.07509.07536.07986.08839.09003.09355.0...51067.049933.050916.057505.060071.065852.069365.072161.028879.49056638.0
Angola4834.04775.05240.05286.05527.05677.05833.05685.06219.06460.0...28247.029877.032053.036985.038400.040573.038064.048639.013321.05660468.0
Antigua and Barbuda92.094.0105.095.084.073.064.059.068.077.0...110.0122.0115.0114.0115.0118.0113.0119.083.886792172.0
\n", + "

5 rows × 55 columns

\n", + "
" + ], + "text/plain": [ + " Y1961 Y1962 Y1963 Y1964 Y1965 Y1966 \\\n", + "Afghanistan 9481.0 9414.0 9194.0 10170.0 10473.0 10169.0 \n", + "Albania 1706.0 1749.0 1767.0 1889.0 1884.0 1995.0 \n", + "Algeria 7488.0 7235.0 6861.0 7255.0 7509.0 7536.0 \n", + "Angola 4834.0 4775.0 5240.0 5286.0 5527.0 5677.0 \n", + "Antigua and Barbuda 92.0 94.0 105.0 95.0 84.0 73.0 \n", + "\n", + " Y1967 Y1968 Y1969 Y1970 ... Y2006 \\\n", + "Afghanistan 11289.0 11508.0 11815.0 10454.0 ... 18317.0 \n", + "Albania 2046.0 2169.0 2230.0 2395.0 ... 6911.0 \n", + "Algeria 7986.0 8839.0 9003.0 9355.0 ... 51067.0 \n", + "Angola 5833.0 5685.0 6219.0 6460.0 ... 28247.0 \n", + "Antigua and Barbuda 64.0 59.0 68.0 77.0 ... 110.0 \n", + "\n", + " Y2007 Y2008 Y2009 Y2010 Y2011 Y2012 \\\n", + "Afghanistan 19248.0 19381.0 20661.0 21030.0 21100.0 22706.0 \n", + "Albania 6744.0 7168.0 7316.0 7907.0 8114.0 8221.0 \n", + "Algeria 49933.0 50916.0 57505.0 60071.0 65852.0 69365.0 \n", + "Angola 29877.0 32053.0 36985.0 38400.0 40573.0 38064.0 \n", + "Antigua and Barbuda 122.0 115.0 114.0 115.0 118.0 113.0 \n", + "\n", + " Y2013 Mean_Produce Rank \n", + "Afghanistan 23007.0 13003.056604 69.0 \n", + "Albania 8271.0 4475.509434 104.0 \n", + "Algeria 72161.0 28879.490566 38.0 \n", + "Angola 48639.0 13321.056604 68.0 \n", + "Antigua and Barbuda 119.0 83.886792 172.0 \n", + "\n", + "[5 rows x 55 columns]" + ] + }, + "execution_count": 10, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "mean_produce = []\n", + "for i in range(174):\n", + " mean_produce.append(new_df.iloc[i,:].values.mean())\n", + "new_df['Mean_Produce'] = mean_produce\n", + "\n", + "new_df['Rank'] = new_df['Mean_Produce'].rank(ascending=False)\n", + "\n", + "new_df.head()" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "_cell_guid": "6f7c4fb7-1475-439f-9929-4cf4b29d8de7", + "_uuid": "da6c9c98eaff45edba1179103ae539bbfbe9753b" + }, + "source": [ + "Now, we create another dataframe with items and their total production each year from 1961 to 2013" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": { + "_cell_guid": "bfd692bc-dce4-4870-9ab9-9775cf69a87f", + "_uuid": "9e11017d381f175eee714643bc5fa763600aaa0b" + }, + "outputs": [], + "source": [ + "item_list = list(df['Item'].unique())\n", + "\n", + "item_df = pd.DataFrame()\n", + "item_df['Item_Name'] = item_list\n", + "\n", + "for yr in year_list:\n", + " item_produce = []\n", + " for it in item_list:\n", + " item_produce.append(df[yr][df['Item']==it].sum())\n", + " item_df[yr] = item_produce\n" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": { + "_cell_guid": "3b7ed0c2-6140-4285-861c-d0cd2324a1f5", + "_uuid": "cb4641df5ce90f516f88c536e8a6c6870c5b4f65" + }, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
Item_NameY1961Y1962Y1963Y1964Y1965Y1966Y1967Y1968Y1969...Y2004Y2005Y2006Y2007Y2008Y2009Y2010Y2011Y2012Y2013
0Wheat and products138829.0144643.0147325.0156273.0168822.0169832.0171469.0179530.0189658.0...527394.0532263.0537279.0529271.0562239.0557245.0549926.0578179.0576597587492
1Rice (Milled Equivalent)122700.0131842.0139507.0148304.0150056.0155583.0158587.0164614.0167922.0...361107.0366025.0372629.0378698.0389708.0394221.0398559.0404152.0406787410880
2Barley and products46180.048915.051642.054184.054945.055463.056424.060455.065501.0...102055.097185.0100981.093310.098209.099135.092563.092570.08876699452
3Maize and products168039.0168305.0172905.0175468.0190304.0200860.0213050.0215613.0221953.0...545024.0549036.0543280.0573892.0592231.0557940.0584337.0603297.0608730671300
4Millet and products19075.019019.019740.020353.018377.020860.022997.021785.023966.0...25789.025496.025997.026750.026373.024575.027039.025740.02610526346
\n", + "

5 rows × 54 columns

\n", + "
" + ], + "text/plain": [ + " Item_Name Y1961 Y1962 Y1963 Y1964 Y1965 \\\n", + "0 Wheat and products 138829.0 144643.0 147325.0 156273.0 168822.0 \n", + "1 Rice (Milled Equivalent) 122700.0 131842.0 139507.0 148304.0 150056.0 \n", + "2 Barley and products 46180.0 48915.0 51642.0 54184.0 54945.0 \n", + "3 Maize and products 168039.0 168305.0 172905.0 175468.0 190304.0 \n", + "4 Millet and products 19075.0 19019.0 19740.0 20353.0 18377.0 \n", + "\n", + " Y1966 Y1967 Y1968 Y1969 ... Y2004 Y2005 \\\n", + "0 169832.0 171469.0 179530.0 189658.0 ... 527394.0 532263.0 \n", + "1 155583.0 158587.0 164614.0 167922.0 ... 361107.0 366025.0 \n", + "2 55463.0 56424.0 60455.0 65501.0 ... 102055.0 97185.0 \n", + "3 200860.0 213050.0 215613.0 221953.0 ... 545024.0 549036.0 \n", + "4 20860.0 22997.0 21785.0 23966.0 ... 25789.0 25496.0 \n", + "\n", + " Y2006 Y2007 Y2008 Y2009 Y2010 Y2011 Y2012 Y2013 \n", + "0 537279.0 529271.0 562239.0 557245.0 549926.0 578179.0 576597 587492 \n", + "1 372629.0 378698.0 389708.0 394221.0 398559.0 404152.0 406787 410880 \n", + "2 100981.0 93310.0 98209.0 99135.0 92563.0 92570.0 88766 99452 \n", + "3 543280.0 573892.0 592231.0 557940.0 584337.0 603297.0 608730 671300 \n", + "4 25997.0 26750.0 26373.0 24575.0 27039.0 25740.0 26105 26346 \n", + "\n", + "[5 rows x 54 columns]" + ] + }, + "execution_count": 12, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "item_df.head()" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "_cell_guid": "3fa01e1f-bedd-431b-90c3-8d7d70545f34", + "_uuid": "56a647293f1c1aba7c184f249021e008a4d5a8f2" + }, + "source": [ + "# Some more feature engineering\n", + "\n", + "This time, we will use the new features to get some good conclusions.\n", + "\n", + "# 1. Total amount of item produced from 1961 to 2013\n", + "# 2. Providing a rank to the items to know the most produced item" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "metadata": { + "_cell_guid": "3a6bb102-6749-4818-860d-59aaad6de07f", + "_uuid": "9e816786e7a161227ae72d164b25c0029e01e5b4", + "scrolled": true + }, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
Item_NameY1961Y1962Y1963Y1964Y1965Y1966Y1967Y1968Y1969...Y2006Y2007Y2008Y2009Y2010Y2011Y2012Y2013SumProduction_Rank
0Wheat and products138829.0144643.0147325.0156273.0168822.0169832.0171469.0179530.0189658.0...537279.0529271.0562239.0557245.0549926.0578179.057659758749219194671.06.0
1Rice (Milled Equivalent)122700.0131842.0139507.0148304.0150056.0155583.0158587.0164614.0167922.0...372629.0378698.0389708.0394221.0398559.0404152.040678741088014475448.08.0
2Barley and products46180.048915.051642.054184.054945.055463.056424.060455.065501.0...100981.093310.098209.099135.092563.092570.088766994524442742.020.0
3Maize and products168039.0168305.0172905.0175468.0190304.0200860.0213050.0215613.0221953.0...543280.0573892.0592231.0557940.0584337.0603297.060873067130019960640.05.0
4Millet and products19075.019019.019740.020353.018377.020860.022997.021785.023966.0...25997.026750.026373.024575.027039.025740.026105263461225400.038.0
\n", + "

5 rows × 56 columns

\n", + "
" + ], + "text/plain": [ + " Item_Name Y1961 Y1962 Y1963 Y1964 Y1965 \\\n", + "0 Wheat and products 138829.0 144643.0 147325.0 156273.0 168822.0 \n", + "1 Rice (Milled Equivalent) 122700.0 131842.0 139507.0 148304.0 150056.0 \n", + "2 Barley and products 46180.0 48915.0 51642.0 54184.0 54945.0 \n", + "3 Maize and products 168039.0 168305.0 172905.0 175468.0 190304.0 \n", + "4 Millet and products 19075.0 19019.0 19740.0 20353.0 18377.0 \n", + "\n", + " Y1966 Y1967 Y1968 Y1969 ... Y2006 \\\n", + "0 169832.0 171469.0 179530.0 189658.0 ... 537279.0 \n", + "1 155583.0 158587.0 164614.0 167922.0 ... 372629.0 \n", + "2 55463.0 56424.0 60455.0 65501.0 ... 100981.0 \n", + "3 200860.0 213050.0 215613.0 221953.0 ... 543280.0 \n", + "4 20860.0 22997.0 21785.0 23966.0 ... 25997.0 \n", + "\n", + " Y2007 Y2008 Y2009 Y2010 Y2011 Y2012 Y2013 \\\n", + "0 529271.0 562239.0 557245.0 549926.0 578179.0 576597 587492 \n", + "1 378698.0 389708.0 394221.0 398559.0 404152.0 406787 410880 \n", + "2 93310.0 98209.0 99135.0 92563.0 92570.0 88766 99452 \n", + "3 573892.0 592231.0 557940.0 584337.0 603297.0 608730 671300 \n", + "4 26750.0 26373.0 24575.0 27039.0 25740.0 26105 26346 \n", + "\n", + " Sum Production_Rank \n", + "0 19194671.0 6.0 \n", + "1 14475448.0 8.0 \n", + "2 4442742.0 20.0 \n", + "3 19960640.0 5.0 \n", + "4 1225400.0 38.0 \n", + "\n", + "[5 rows x 56 columns]" + ] + }, + "execution_count": 13, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "sum_col = []\n", + "for i in range(115):\n", + " sum_col.append(item_df.iloc[i,1:].values.sum())\n", + "item_df['Sum'] = sum_col\n", + "item_df['Production_Rank'] = item_df['Sum'].rank(ascending=False)\n", + "\n", + "item_df.head()" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "_cell_guid": "7e20740c-565b-4969-a52e-d986e462b750", + "_uuid": "f483c9add5f6af9af9162b5425f6d65eb1c5f4aa" + }, + "source": [ + "# Now, we find the most produced food items in the last half-century" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "metadata": { + "_cell_guid": "3130fe83-404c-4b3c-addc-560b2e2f32bf", + "_uuid": "0403e9ab2e13587588e3a30d64b8b6638571d3d5" + }, + "outputs": [ + { + "data": { + "text/plain": [ + "56 Cereals - Excluding Beer\n", + "65 Fruits - Excluding Wine\n", + "3 Maize and products\n", + "53 Milk - Excluding Butter\n", + "6 Potatoes and products\n", + "1 Rice (Milled Equivalent)\n", + "57 Starchy Roots\n", + "64 Vegetables\n", + "27 Vegetables, Other\n", + "0 Wheat and products\n", + "Name: Item_Name, dtype: object" + ] + }, + "execution_count": 14, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "item_df['Item_Name'][item_df['Production_Rank'] < 11.0].sort_values()" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "_cell_guid": "b6212fed-588b-426e-9271-6d857cd6aacb", + "_uuid": "e2c83f4c851b755ea6cf19f1bca168e705bd4edd" + }, + "source": [ + "So, cereals, fruits and maize are the most produced items in the last 50 years\n", + "\n", + "# Food and feed plot for most produced items " + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "metadata": { + "_cell_guid": "493f9940-1762-4718-acb4-fba5c4c73f4b", + "_uuid": "f8454c5200bdeb3995b9a0ada3deb5ca1c31f181" + }, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/anaconda3/lib/python3.7/site-packages/seaborn/categorical.py:3666: UserWarning: The `factorplot` function has been renamed to `catplot`. The original name will be removed in a future release. Please update your code. Note that the default `kind` in `factorplot` (`'point'`) has changed `'strip'` in `catplot`.\n", + " warnings.warn(msg)\n", + "/anaconda3/lib/python3.7/site-packages/seaborn/categorical.py:3672: UserWarning: The `size` paramter has been renamed to `height`; please update your code.\n", + " warnings.warn(msg, UserWarning)\n" + ] + }, + { + "data": { + "image/png": "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AAAAtCWOAQAAANCWOAYAAABAW+IYAAAAAG2JYwAAAAC0JY4BAAAA0JY4BgAAAEBb4hgAAAAAbYljAAAAALQljgEAAADQljgGAAAAQFviGAAAAABtiWMAAAAAtCWOAQAAANCWOAYAAABAW+IYAAAAAG2JYwAAAAC0JY4BAAAA0JY4BgAAAEBb4hgAAAAAbYljAAAAALQljgEAAADQljgGAAAAQFviGAAAAABtiWMAAAAAtCWOAQAAANCWOAYAAABAW+IYAAAAAG2JYwAAAAC0JY4BAAAA0JY4BgAAAEBb4hgAAAAAbYljAAAAALQljgEAAADQljgGAAAAQFviGAAAAABtiWMAAAAAtCWOAQAAANCWOAYAAABAW+IYAAAAAG2JYwAAAAC0JY4BAAAA0JY4BgAAAEBb4hgAAAAAbYljAAAAALQljgEAAADQljgGAAAAQFviGAAAAABtiWMAAAAAtCWOAQAAANCWOAYAAABAW+IYAAAAAG2JYwAAAAC0JY4BAAAA0JY4BgAAAEBb4hgAAAAAbYljAAAAALQljgEAAADQljgGAAAAQFviGAAAAABtiWMAAAAAtCWOAQAAANCWOAYAAABAW+IYAAAAAG2JYwAAAAC0JY4BAAAA0JY4BgAAAEBb4hgAAAAAbYljAAAAALQljgEAAADQljgGAAAAQFviGAAAAABtiWMAAAAAtCWOAQAAANCWOAYAAABAW+IYAAAAAG2JYwAAAAC0JY4BAAAA0JY4BgAAAEBb4hgAAAAAbYljAAAAALQljgEAAADQljgGAAAAQFviGAAAAABtiWMAAAAAtCWOAQAAANCWOAYAAABAW+IYAAAAAG2JYwAAAAC0JY4BAAAA0JY4BgAAAEBb4hgAAAAAbYljAAAAALQljgEAAADQljgGAAAAQFviGAAAAABtiWMAAAAAtCWOAQAAANCWOAYAAABAW+IYAAAAAG2JYwAAAAC0JY4BAAAA0JY4BgAAAEBb4hgAAAAAbYljAAAAALQljgEAAADQljgGAAAAQFviGAAAAABtiWMAAAAAtCWOAQAAANCWOAYAAABAW+IYAAAAAG2JYwAAAAC0JY4BAAAA0JY4BgAAAEBb4hgAAAAAbYljAAAAALQljgEAAADQljgGAAAAQFviGAAAAABtiWMAAAAAtCWOAQAAANCWOAYAAABAW+IYAAAAAG2JYwAA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\n", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "sns.factorplot(\"Item\", data=df[(df['Item']=='Wheat and products') | (df['Item']=='Rice (Milled Equivalent)') | (df['Item']=='Maize and products') | (df['Item']=='Potatoes and products') | (df['Item']=='Vegetables, Other') | (df['Item']=='Milk - Excluding Butter') | (df['Item']=='Cereals - Excluding Beer') | (df['Item']=='Starchy Roots') | (df['Item']=='Vegetables') | (df['Item']=='Fruits - Excluding Wine')], kind=\"count\", hue=\"Element\", size=20, aspect=.8)\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "_cell_guid": "45dda825-49a0-41ab-9ebd-eaa609aac986", + "_uuid": "ce5b2d38ff24ea08da632c4e2773dbd0bd026b9d", + "collapsed": true + }, + "source": [ + "# Now, we plot a heatmap of correlation of produce in difference years" + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "metadata": { + "_cell_guid": "b1bab0ec-6615-452c-8d06-a81d4f2ae252", + "_uuid": "a2ed2aae2364810ce640648cf50880adcf2cdcc4" + }, + "outputs": [ + { + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 16, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAA2QAAAJYCAYAAAANJyWqAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAALEgAACxIB0t1+/AAAADl0RVh0U29mdHdhcmUAbWF0cGxvdGxpYiB2ZXJzaW9uIDMuMC4yLCBodHRwOi8vbWF0cGxvdGxpYi5vcmcvOIA7rQAAIABJREFUeJzs3X+QXOV95/v3p+eHhMaSk9goldjcJa615LKNpGDWcCsOZkOFQNjF67gQTCjuboktXXnlW3XvJgRSgQ0kUTZFvCorlZQpRRYs4BVyvCaghUireA0yjlKLUIQYsCxbFInHSiyDsQ36wcx0f+8f5wg37Z7pPl8PTc/o86rqmp7znO95nnP6nNPzzDnn+ygiMDMzMzMzs96rvdkNMDMzMzMzO1O5Q2ZmZmZmZvYmcYfMzMzMzMzsTeIOmZmZmZmZ2ZvEHTIzMzMzM7M3iTtkZmZmZmZmbxJ3yMzMzMzMzN4k7pCZmZmZmZm9SdwhMzMzMzMze5O4Q2ZmZmZmZvYmGXyzG5A1+cJzUTmoPlk5JCYnKscAxInvV47R4HCurqlcG1PUmz58vPxCT+opKmvk4iZfrV7Vi/+Yq2swcahOTeXqymyPRnIbnjpROSRe/E6qKg0NVa/r5KlUXfGd76biUnVNVj+vZbZFUVn1z3nqH15MVaXhgeoxIwuqVzSRO05isl45ZuqF6ueMrFe+mft6b0xVP8dPTeS+F+qN6nEnT+b23RdPnVU55hVV3wcBjg1WX6/sN+tA9b+EeCVZ2T8MJPZ5Eg0EvhPV/645Tu5Yfql+snLMK43qx/IrU7nvk+Fa7lh+6p/+RqnAN1Hqb/ukobe/q6+2z4yHpQqPS7qiadpqSTslbZV0TNJYS8xKSXslPS1ph6QlTWUryrJnyvKF5fQNkr4p6ZXZXkEzMzMzM7N+NWOHLCICWAdslLRQ0giwAVgP3A1c3iZsC3BzRJwHPADcCCBpELgPWBcR7wMuAU7/a3cH8MEfd2XMzMzMzGwOatR79+ozHa+DRsSYpB3ATcAIcE9EHAGOSDq3TchyYE/5fjewC7gVuAw4GBFPlct97T6WiPhbAKmvrh6amZmZmZm9obq9MfV2YD8wAVzQYd4x4CrgQeBq4Jxy+jIgJO0Czgbuj4g7KrfYzMzMzMzml+wz/fNAV492RsRxYDtwb0R0epJxDbBe0pPAYopOHBSdvw8B15U/Pyrp0iqNlbRW0j5J+7bcs61KqJmZmZmZWd+pkrqlUb5mFBGHKG5PRNIy4MqyaBx4LCJeKMseAc4HvthtAyJiM7AZepuJxczMzMzM7I0w6znMJS0tf9aAW4A7y6JdwApJi8oEHx8Gnp3t+s3MzMzMbI5pNHr36jPpDpmkbcBeYLmkcUk3lEWjkg4Dh4CjwF0AEfESsBF4AjgA7I+Ih8tl3SFpHFhULuu2bLvMzMzMzMzmChWZ7eeeyW9/rXrDBxIDSiYGkwaIU8erB9Vyg1Cm0ndmH5zs0cDQAPHKSz2ri3r1ASWjkRho/HvHKscAaKj6gLcxUX2wy7QeDgzN93IDDacG1z6V24bxYqKNjeS5uJ44/mu9y2hb/2Zyn88MDH1WYmBoIE4ljuVXc98N9RcT+/xU9X3j5LeqVwMw9Wr1c/zkqdx3V6NefT88cWI4Vde3Ty6qHHM8OTD0Pw1V34ZKHv6Zb+T0wNC16t+Tk+mBoasPvHwicgNDf7de/Zh8pZ4b5DkzOPRQ8m/DQ8eemHOpyyeOPtOzTsnwz76vr7ZP7/66Nqug3ztjZjY/ZDpjWanOmJnNC5nOmJ05ZuyQqfC4pCuapq2WtFPSVknHJI21xKyUtFfS05J2SFrSVLaiLHumLF9YPlP2sKRD5fQ/mv3VNDMzMzOzvuVnyNqL4n7GdcDGsvM0AmwA1gN3A5e3CdsC3BwR5wEPADcClIk87gPWRcT7gEuA0/+a/GREvAf4eeAXmjuAZmZmZmZm81XHhyoiYkzSDuAmYAS4JyKOAEckndsmZDmwp3y/myK74q0UqfAPRsRT5XJPP2RxAvhSOW1C0n7gndkVMjMzMzOzOcYDQ3d0O/DrwBXAHR3mHQOuKt9fDZxTvl8GhKRdkvZL+q3WQEk/AfxrKoxNZmZmZmZmNld11SGLiOPAduDeiI6pb9YA6yU9CSwGJsrpg8CHgOvKnx+VdOnpoPKWxm3An0TEc+0WLGmtpH2S9m25d3s3TTczMzMzs37XqPfu1Weq5IFulK8ZRcQhitsTkbQMuLIsGgcei4gXyrJHgPP54dWwzcDXI+JTMyx7czlfLu29mZmZmZlZH5n1tPeSlpY/a8AtwJ1l0S5gRZlVcRD4MPBsOe8fAG8F/t/Zbo+ZmZmZmfW5aPTu1WfSHTJJ24C9wHJJ45JuKItGJR0GDgFHgbsAIuIlYCPwBHAA2B8RD0t6J/A7wHuB/ZIOSPr36TUyMzMzMzObI7q+ZTEibmv5fXSa+TYBm6Ypu48i9X3ztHGgr0bLNjMzMzOzHurD8cF6pcozZH0lJic6z9Qi1esbGMpEobMWVw+qT6Xqih5eei3uRK0mMus1fFb1mKzk9tNkp/w2bYy8NVVXZj/UwpFcXZn1ykp8zumHRwcTp7tTJ1JVpc412S+iTFxyn49G9a1fO5ncnwYGKodowXD1ehZW/y4B0KlT1YMmkuf4evXPa+gHue1eG+zd49mNevUjZWgy9zB+7WQqLCVzdNWS/5ZOHJJk0xlk1quRPGPXE7VlYiDXxuhRDEAjnDLhTDDjX9cqPN48ULOk1ZJ2Stoq6ZiksZaYlZL2Snpa0g5JS5rKVpRlz5TlC8vpOyU9VU6/U1L1b2IzMzMzM5uTIho9e/WbGTtkERHAOmCjpIWSRoANwHrgbuDyNmFbgJsj4jzgAeBGeC2t/X3Auoh4H3AJMFnGrI6IlcD7gbMpxi8zMzMzMzOb1zrewxMRY5J2ADcBI8A9EXEEOCLp3DYhy4E95fvdFNkVb6VIhX8wIp4ql/tiUx0/aGrPMD/GXUlmZmZmZmZzRbcPVdwO7KcY5PmCDvOOAVcBD1Jc6TqnnL4MCEm7KK6C3R8Rd5wOKqd/EPgr4PPdroCZmZmZmc1xZ3BSj64yNETEcWA7cG9EdHpSeA2wXtKTwGKKThwUnb8PAdeVPz8q6dKmOn4F+BlgAfBL7RYsaa2kfZL2bfms+2xmZmZmZja3VUk71qCLJDsRcYji9kQkLQOuLIvGgcci4oWy7BHgfOCLTbGnJD0EfITidsfWZW8GNgNMjD/t2xrNzMzMzOaDPky20SvpgaGnI2lp+bMG3ALcWRbtAlZIWlQm+Pgw8Kykt0j6mTJmEPhVikGlzczMzMzM5rV0h0zSNmAvsFzSuKQbyqJRSYcpOlVHgbsAIuIlYCPwBHAA2B8RD1MkCnlI0kHgKeAYP+zEmZmZmZnZfNeo9+7VZ7q+ZTEibmv5fXSa+TYBm6Ypu48i9X3ztG8D/6LbdpiZmZmZmc0XVZ4h6ytx4vvVgxYsqhyisxZXrweglhjbuj6Vq6uHWWkic021Ptl5nla1Wb+bdnrZzZdp48BQqioNLqgcE5H8D5AS65W973sgcQqa6pRXqL3UNhwcTtWVOiaz2zBTV/KcoXpinzp5MlUXg4l9Y6j68aV0+6qf43UqcS4EmKz+3TCwaKLzTG1Vfzx7cCq3PzXqqhwzcDK57/ZwNJ1ePgVTq74JaSRiACKxDevJ7Z7ZhsXQudXVE+feRqKuevI7eSDznTxX+RkyMzMzMzMz67UZO2QqPC7piqZpqyXtlLRV0jFJYy0xKyXtlfS0pB2SljSVrSjLninLF7bEPtS6PDMzMzMzm+cajd69+syMHbIorv+uAzZKWihpBNgArAfuBi5vE7YFuDkizgMeAG6E1zIo3gesi4j3AZcAr92/IenXgFd+zPUxMzMzMzObMzrepB8RY5J2ADdRZES8JyKOAEckndsmZDmwp3y/myLd/a0UY5MdjIinyuW+eDpA0luA/wisBT6XXRkzMzMzM5uDzuBnyLp9avp2YD8wAVzQYd4x4CrgQeBq4Jxy+jIgJO0Czgbuj4g7yrLfB/4LcKL7ppuZmZmZmc1tXSX1iIjjwHbg3ojolOJsDbBe0pPAYopOHBSdvw8B15U/PyrpUkmrgH8eEQ90aoektZL2Sdq3ZftD3TTdzMzMzMz63Rn8DFmVvMINushEGhGHKG5PRNIy4MqyaBx4LCJeKMseAc6neG7sA5KeL9uzVNKjEXFJm2VvBjYDvHr48d7lsDUzMzMzM3sDzHrae0lLy5814BbgzrJoF7BC0qIywceHgWcj4tMR8bMRcS7FlbPD7TpjZmZmZmY2P0XUe/bqN+kOmaRtwF5guaRxSTeURaOSDgOHgKPAXQAR8RKwEXgCOADsj4iHf5zGm5mZmZmZzWVd37IYEbe1/D46zXybgE3TlN1Hkfp+ujqeB97fTXs0ONzNbK9XG6geU5+qHpONG1qQqkoDVe48LfUyk02mfZNDubpqif8xZO8lri/sPM+PxCT3p8Q2VHa9BpLbPiGmOj2S+qM0mDtOUvtGZt/ttXriP33pfb76/hsnTqaq0lBiP6ypeszwEJw8VTkspqpv95hMHv8T1eMaE7m7+hsTnedpVZ9MbHegUa8eV5/K/R95IvH/50nl1iuzObL/Ha8lPuZJcvvGyc5PrfyIiUQMwMnGZOeZWhzP7LzAqUTciXr1764BDfDyZPXcdVO1/ruaY7NvDvy1YWZm9gZJdMbMzKrKdMbOOGdw2vsZ/ymjwuOSrmiatlrSTklbJR2TNNYSs1LSXklPS9ohaUlT2Yqy7JmyfGE5/VFJX5N0oHwtne0VNTMzMzMz6zczXiGLiJC0DvgLSV8CBoANwOXAO4A/Be5pCdsC/GZEPCZpDXAjcGuZyOM+4PqIeErS24Dma9LXRcS+WVkrMzMzMzObO/owHX2vdLxlMSLGJO0AbgJGgHsi4ghwRNK5bUKWA3vK97spsiveSpEK/2BEPFUu98Ufu/VmZmZmZmZzWLfPkN0O7KcY5PmCDvOOAVcBDwJXA+eU05cBIWkXcDZwf0Tc0RR3l6Q68N+BP4gIjzNmZmZmZnYm8DNkM4uI48B24N6I6JRaZg2wXtKTwGKKThwUnb8PAdeVPz8q6dKy7LqIOA/4xfJ1fbsFS1oraZ+kfVu2PdBN083MzMzMzPpWlSyLjfI1o4g4RHF7IpKWAVeWRePAYxHxQln2CHA+8MWI+FYZ+7Kk/wZ8kB99No2I2AxsBph47n/7CpqZmZmZ2XzQOHNT/KcHhp7O6QyJkmrALcCdZdEuYIWkRWWCjw8Dz0oalPT2MmYI+FcUtz2amZmZmZnNa+kOmaRtwF5guaRxSTeURaOSDgOHgKPAXQAR8RKwEXgCOADsj4iHgQXALkkHy+nfAv482y4zMzMzM5tjotG7V5/RXM2d8erhxys3XAsWVa9oaEH1GEil7tTwwlxdtYHqMXPhsnB9qnJIZA+yHqZajZMvV47RQG4M90hsQ+qTneeZLVMTnedpEa989w1oyDR1TZzMBX7vheoxmc8K4FSijdm6EsdJ41tHU1VpMHFeW5Q4x0NqcOg4kds3Gi98v3pdr+aOycnx6us1dVypuk79oPo5Khq5ul7+fvXvyn848ZbKMS8NJPZB4OhQKiyllvgT7geZIODvqb4/nYrc3xov1KsPonwicsfJ9yaPV445PpU7/l+ZrL4Nh5Pf///0va/mDrA30an//Rc965Qs/ODVfbV9cp+y2Rst+wdjRp93xtJ19XIbms1Vic5YVqYzlpXpjGVlOmNZmc6YWT/IdMbOOGfwOGQz3rKowuOSrmiatlrSTklbJR2TNNYSs1LSXklPS9ohaUlT2Yqy7JmyfGE5fVjSZkmHJR2S9LHZXlEzMzMzM7N+M+O/tSIiJK0D/kLSl4ABYANwOfAO4E/50WyIW4DfjIjHJK0BbgRuLRN53AdcHxFPSXobcPr68u8AxyJiWZkM5Kdmaf3MzMzMzKzf9eGzXb3S8T6DiBiTtAO4CRgB7omII8ARSee2CVkO7Cnf76bIrngrRSr8gxHxVLncF5ti1gDvKac3gMQDGGZmZmZmZnNLtzd+3w7spxjk+YIO844BVwEPAlcD55TTlwEhaRdwNnB/RNwh6SfK8t+XdAlwBPhERHy767UwMzMzM7O5y8+QzSwijgPbgXsj4tUOs68B1kt6ElhM0YmDovP3IeC68udHJV1aTn8n8JWIOJ8ilf4n2y1Y0lpJ+yTt27L9oW6abmZmZmZm1jVJl0v6mqRvSLq5Tfk/k/RFSQclPSrpnU1l/4ek/ynpq5KeneaOwtepkhqpUb5mFBGHKG5PRNIy4MqyaBx4LCJeKMseAc4H/hdwAnignO8vgBtoIyI2A5shl/bezMzMzMxsOpIGgD8Dfpmi//KEpIci4tmm2T5J8RjXf5X0S8B/Bq4vy+4BNkTEbklvoYv+U3pg6OlIWlr+rAG3AHeWRbuAFZIWlQk+Pgw8G8VAaDuAS8r5LgWexczMzMzMzgyNRu9eM/sg8I2IeC4iJoD7gY+0zPNe4Ivl+y+dLpf0XmAwInYDRMQrEdFxYL10h0zSNorbC5dLGpd0+qrWqKTDwCHgKHBX2aCXgI3AE8ABYH9EPFzG3ATcJukgRe/yN7LtMjMzMzMzS3oH8M2m38fLac2eAk4P0/VRYHGZQX4Z8D1JX5D0d5L+uLziNqOub1mMiNtafh+dZr5NwKZpyu6jSH3fOv3vgYu7bYuZmZmZmc0fEfWe1SVpLbC2adLm8tEoALUJaX1U6jeBP5X07yiyy38LmKLoW/0i8PPAP1Dk4Ph3wGdmak+VZ8jmvn4f3yDbvkxYrWNnfZq6enSw1HIXb5XYFjHrN+7OILleqbj0ig0l4xIy+/xA705bGlqQiouh4epB2X2jPtW7uhIZsLQgsS0ABhLnqKHEvpvM6qV69XOhFiSPrVq7vw06hCzslH+rvYFG9cezhyZy3wuNevX1Gl6Q2N+B4RPVP+cFyXPogqi+Xlm1xNP0C5JP4A8rsT2Sm2K48wWFH1FP/TEEw7Xq3ykTterH8vBAct9NtM86a85L0cY4P8wSD0XywaMt8UeBXwMonxP7WER8X9I48HcR8VxZ9pfARXTokM14dKnwuKQrmqatlrRT0lZJxySNtcSslLRX0tOSdkha0lS2oix7pixfKGmxpANNrxckfWqmdpmZmZmZ2TzSP8+QPQG8W9LPSRoGrgVel95d0tvLfBkAvw1sbYr9SUlnl7//El3kxpixQ1Ym3FgHbCw7TyPABmA9cDdweZuwLcDNEXEeRebEG8uGD1LcrrguIt5HkcRjMiJejohVp1/A3wNf6NRwMzMzMzOz2RQRU8AnKBISfhX4XEQ8I+n3JF1VznYJ8LUyb8ZPU/SPiOK+y98EvijpaYrrxH/eqc6O10EjYkzSDorEGyMUKR6PAEemyau/nOJeSoDd5crcSpEK/2BEPFUu98XWQEnvBpYCX+7ULjMzMzMzmyf66NGiiHgEeKRl2n9qev954PPTxO4GVlSpr9sbU28H9lMM8nxBh3nHgKuAB4Gr+eE9mMuAkLQLOBu4PyLuaIkdBbaXV+bMzMzMzMzmta6e0IyI4xRZQu6NiE5PCq8B1kt6ElhM0YmDovP3IeC68udHJV3aEnstsG26BUtaK2mfpH1btj803WxmZmZmZjaX9M8zZD1XJXVLgy7y+UXEIYrbE5G0DLiyLBoHHouIF8qyR4DzKQdVk7SSYiC1J2dY9msZUV49/LivopmZmZmZ2Zw26wm/JS0tf9aAW4A7y6JdwApJi8oEHx/m9VlHRpnh6piZmZmZmc1T0ejdq8+kO2SStgF7geWSxiXdUBaNlhlHDlHk7L8LICJeAjZSpIM8AOyPiIebFrkad8jMzMzMzOwM0vUtixFxW8vvo9PMtwnYNE3ZfRSp79uVvavbtpiZmZmZ2TzSh8929crcHf47NWJ89Rhl6gFi1m8GnWWNei6uNtCbupIHZfTyMnR9qnrMZKecOO2lHpjMtA+gPlk9JnmcpNo4NdF5nnYSbYzk58Vkoo3ZzytzrGS/9DJx9ey5JrFP9XBbRCYue35qVD8DRCIGIBK7YYRydTWqx2ViAIJcXEYv/6TMnHnP3D9524vcN2zPNPq8fTY75m6HzMzMzMzM5oc+fLarV2b854oKj0u6omnaakk7JW2VdEzSWEvMSkl7JT0taYekJU1lK8qyZ8ryheX00fL3g+Wy3z7bK2pmZmZmZtZvZuyQlQM0rwM2SlooaQTYAKwH7gYubxO2Bbg5Is4DHgBuBCgzK94HrIuI9wGXAJPl9E3Av4yIFcBB4BM//qqZmZmZmZn1t463LEbEmKQdwE3ACHBPRBwBjkg6t03IcmBP+X43Rbr7WynGJjsYEU+Vy30RQNIQIGBE0ovAEuAbP8Y6mZmZmZnZXOKkHh3dDuwHJoALOsw7BlwFPAhcDZxTTl8GhKRdwNnA/RFxR0RMSvo48DRwHPg6xRU4MzMzMzOzea2rBD0RcRzYDtwbEZ3Sjq0B1kt6ElhM0YmDovP3IeC68udHJV1aXiH7OPDzwM9S3LL42+0WLGmtpH2S9m25/8Fumm5mZmZmZv2u0ejdq89UybLYoItsqRFxiOL2RCQtA64si8aBxyLihbLsEeB84Adl3JFy+ueAm6dZ9mZgM8CrX/8b5wE1MzMzM7M5bdZHy5K0tPxZA24B7iyLdgErJC0qE3l8GHgW+BbwXklnl/P9MvDV2W6XmZmZmZn1qWj07tVn0h0ySduAvcBySeOSbiiLRiUdBg4BR4G7ACLiJWAj8ARwANgfEQ9HxFGKZ9T2SDoIrAL+MNsuMzMzMzOzuaLrWxYj4raW30enmW8TRRr7dmX3UaS+b51+Jz+8ktZXoj6VC6xPVo8ZmAPjdDfq1WNqA7mYxDZU5n8MyXuJo98/r8xnBaBZv3DeH3r5H7FMXdn21ROfc/b++UQbYyq3HyqzH2a24fAQnDxVPa5R/a75mEpu98TnFcmvrl7uupF48CATA108bzFLMdm47Fm3oWRgQp3qG7+R/MAy27CerKue2IEbUf28dtbAMC9PnqwcJ3r4Ib/Z+vDZrl6Zp3952ZyX6dCamVWV6YyZmVWU6YzZmWPGDpkKj0u6omnaakk7JW2VdEzSWEvMSkl7JT0taYekJU1lK8qyZ8ryheX0ayQdLKffMdsraWZmZmZmfczPkLUXEQGsAzZKWihpBNhAMU7Y3cDlbcK2ADdHxHnAA8CNAGUij/uAdRHxPuASYFLS24A/Bi4tp/+0pEtnYd3MzMzMzMz6WseHYCJiTNIO4CZgBLinTFF/RNK5bUKWA3vK97spsiveSpEK/2BEPFUu90UASe8CDkfEd8qYvwY+BnwxuU5mZmZmZjaXnMHPkHWbleB2YD/FIM8XdJh3DLgKeBC4GjinnL4MCEm7gLOB+yPiDuAbwHvKzt048G+A4e5XwczMzMzMbG7qKqlHRBwHtgP3RsSrHWZfA6yX9CSwmKITB0Xn70PAdeXPj0q6tEyH//Fy+V8Gngfa5oeStFbSPkn7ttz/YDdNNzMzMzOzfncGP0NWJW93gy4ykUbEIYrbE5G0DLiyLBoHHouIF8qyR4DzgS9GxA5gRzl9LdA2n2hEbAY2A7z69b9JJr81MzMzMzPrD7Oe9l7S0vJnDbiFH44vtgtYIWlRmeDjw8CzLTE/CfwHisQgZmZmZmZ2Jmg0evfqM+kOmaRtwF5guaRxSTeURaOSDgOHgKPAXQDlrYkbgSeAA8D+iHi4jNkk6VngK8AfRcThbLvMzMzMzMzmiq5vWYyI21p+H51mvk3ApmnK7qNIfd86ve2yzMzMzMzM5rMqz5D1lXj5hepBC0aqxwyfVT0GoJa4+Dg5lKpKA4mPMdM+yF3mzTw8OZDbFtQnc3EJGkwkAz1rca6y2kD1mMx+AaDEvpF8QDYmEuuVOY4h1UZltgUQZ72letDkROd52skck5PJ4yRRlQYTnzFATbm4yvUkz4U9fCg8Xm2b52rmmOohADQmq2/3yVO5zzga1es69Wruu+H7iXPoS4O5ffC7tcS5JlVTzsuZAxn4TuNU5ZiJaJsSoKMXp16pHHO83innXHvfm6he14mp6nUdn6i+/QAGM9//c1Uf3krYKzN+E6nwuKQrmqatlrRT0lZJxySNtcSslLRX0tOSdkhaUk6/TtKBpldD0qqy7APl/N+Q9CeSenluMjMzMzMze1PM2CGLiADWARslLZQ0AmwA1gN3A5e3CdsC3BwR5wEPADeWy/psRKyKiFXA9cDzEXGgjPk0sBZ4d/lqt1wzMzMzM5uPInr36jMd79WIiDGKlPQ3Ab8L3BMRRyJiD/DdNiHLgT3l+93Ax9rMMwpsA5D0M8CSiNhbdgDvoRgc2szMzMzMbF7r9iGT24H9FIM8X9Bh3jHgKuBB4GrgnDbzXAN8pHz/Dooxyk4bL6eZmZmZmdmZwM+QzSwijgPbgXsjotOTjGuA9ZKeBBZTdOJeI+lC4ER55Q3aP8va9lqipLWS9kna95kv7Oqm6WZmZmZmZn2rShq2Bl3k2IqIQ8BlAJKWAVe2zHIt5e2KpXHgnU2/v5Ni/LJ2y94MbAY4tf+h/rsB1MzMzMzMqvMVstkjaWn5swbcAtzZVFajuI3x/tPTIuIfgZclXVRmV/y/KG53NDMzMzMzm9fSHTJJ24C9wHJJ45JuKItGJR0GDlFc6bqrKexiYDwinmtZ3McpsjN+AzgC/FW2XWZmZmZmNsdEo3evPtP1LYsRcVvL76PTzLcJ2DRN2aPARW2m7wPe321bzMzMzMzM5oMqz5DZG62Wu2AZiZ6+kv8cSNWVuRBbn6weAzAwVD1G9VxdiW0RUxOdZ2pDAz08VHv5n6PEemlwQaqqiMTnnN3uJ1+uHpM8/lOf19BUrq56Im54OFfXYGLfWDRSOSaU2+7tslF1jDl+MlfXYPU2Dnwvea6pVX88Oxq5/alRr74VF03m1mvxS9WP/0Z9IFXXDxLHcmKzA7n9sFbLRMFPqPqxPJH8Y2NioPo+VUsey1OJ74aactswY7iX3/9vNj9D1p4Kj0u6omnaakk7JW1Y6dHeAAAgAElEQVSVdEzSWEvMSkl7JT0taYekJeX06yQdaHo1JK0qyzZI+qakV96IlTQzMzMzM+tHM3bIyoGa1wEbJS2UNAJsANYDdwOXtwnbAtwcEecBDwA3lsv6bESsiohVwPXA8xFxoIzZAXxwFtbHzMzMzMzmmojevfpMx+ugETEmaQdwEzAC3BMRR4Ajks5tE7Ic2FO+3w3sAm5tmWeUptT3EfG3AOrhJWAzMzMzM7M3W7c3pt4O7KcY5PmCDvOOAVdRpK6/GjinzTzXAB/psm4zMzMzM5vP/AzZzCLiOLAduDciXu0w+xpgvaQngcUUnbjXSLoQOBERY+2CZyJpraR9kvZ95gu7qoabmZmZmZn1lSqpWxrla0YRcQi4DEDSMuDKllmupel2xSoiYjOwGeDU/of67wZQMzMzMzOr7gy+QjbruTQlLY2IY5JqwC3AnU1lNYrbGC+e7XrNzMzMzMzmmuTANyBpG7AXWC5pXNINZdGopMPAIeAocFdT2MXAeEQ817KsOySNA4vKZd2WbZeZmZmZmdlc0fUVsoi4reX30Wnm2wRsmqbsUeCiNtN/C/itbttiZmZmZmbzSPiWxbkn86HVq4/8nt45MmE9vHc20tdGE3p5T7Dq1WNqA7m66j3aBwFU/QOLHp7YlGhfVjQmc4GZNmb33cznnN03Mm3Mrlcmrp44JgFqic9rKrPdk+2bSsRN5j7jmKheV+NU7jHr+qnqMZOncsd/Y6p63Kuncn+2nEic51+p5YbieUWJbZ8c9WcwUdUr5Pb541TffyciV9crjU75437UiXr1GIBXJk9Wjjk+Wf1AOTGZa9+r2b9RbE6Zux0yMzMzMzObF6Jx5ubrm/HfUyo8LumKpmmrJe2UtFXSMUljLTErJe2V9LSkHZKWlNOvk3Sg6dWQtErSIkkPSzok6RlJf/TGrKqZmZmZmVl/mbFDFhEBrAM2SlooaQTYAKwH7gYubxO2Bbg5Is4DHgBuLJf12YhYFRGrgOuB5yPiQBnzyYh4D/DzwC80dwDNzMzMzGyeazR69+ozHW9ZjIgxSTuAm4AR4J6IOAIckXRum5DlwJ7y/W5gF3BryzyjlGORRcQJ4Evl+wlJ+4F3Vl4TMzMzMzOzOabbZ8huB/YDE8AFHeYdA64CHqQYc+ycNvNcA3ykdaKknwD+NdNkaTQzMzMzs3noDM6y2FWKo4g4DmwH7o2ITmli1gDrJT0JLKboxL1G0oXAiYhoffZskOKq2Z+0jlPWNM9aSfsk7fvMF/5nN003MzMzMzPrW1WyLDboIpl7RBwCLgOQtAy4smWWaylvV2yxGfh6RHxqhmVvLufj1JN/eeamYjEzMzMzm0/O4CyLs572XtLSiDimYmCiW4A7m8pqFLcxXtwS8wfAW4F/P9vtMTMzMzMz61fp0VwlbQP2AssljUu6oSwalXQYOAQcBe5qCrsYGG++JVHSO4HfAd4L7C9T4rtjZmZmZmZ2pnCWxc4i4raW30enmW8T0yTliIhHgYtapo2TGaM+MeJ5DFS/IKjkyOrUEn3d+sJcXQNDubiM+lTlkMx2B9DgcPWgzAOh9eSBmdjuWvTWXF2J/UmJz6oITP+fpnpVU4nja+isXGWZfbeW3DeGE8fy5ETneWYrrp6rKnVeG0qenwYHqsdkjpNFi4hXE/thrfrXlhYkzmkAjertG1iUPY6r7/ODJ7PHSfW4Bady57VFJ6vv9K8q9921KKrvG1mZT/ms5P/iF5I4JpOb4ixVP29ELXe726LB5N9eFY0MLeT45KnKccPJv6FsbvGnbH0p1RkzM6so1RkzM6so0xk74/ThlatemfHfJCo83jxQs6TVknZK2irpmKTWbIkrJe2V9LSkHZKWlNOvK29HPP1qSFpVlu2U9JSkZyTdKSnxbxgzMzMzM7O5ZcYOWUQEsA7YKGmhpBFgA7AeuBu4vE3YFuDmiDgPeAC4sVzWZyNiVUSsAq4Hno+IA2XM6ohYCbwfOJsi8YeZmZmZmZ0JInr36jMdb1mMiDFJO4CbgBHgnog4AhyRdG6bkOXAnvL9bmAXcGvLPKM0pb6PiB80tWcY6L8tZWZmZmZmNsu6fYbsdmA/xSDPF3SYdwy4CniQ4krXOW3muQb4SPMESbuADwJ/BXy+y3aZmZmZmZnNWV2l2omI48B24N6I6PQE9BpgvaQngcUUnbjXSLoQOBERr3v2LCJ+BfgZYAHwS+0WLGmtpH2S9n3mL/+6m6abmZmZmVm/c9r7rjToIh9uRBwCLgOQtAy4smWWa2m6XbEl9pSkhyiunu1uU74Z2Axw6m+3+7ZGMzMzMzOb02Y97b2kpRFxTFINuAW4s6msRnEb48VN094CLI6If5Q0CPwq8OXZbpeZmZmZmfWpxpl7rSU9AqykbcBeYLmkcUk3lEWjkg4Dh4CjwF1NYRcD4xHxXNO0EeAhSQeBp4BjNHXizMzMzMzM5quur5BFxG0tv49OM98mYNM0ZY8CF7VM+zbwL7pth5mZmZmZzTPRf8929cqs37LYK/HiP1YPGlmSiHlr9RiAgaHqMfWpXF0LRqrH1JIXRyc75XSZJWctToXF1ETnmVolt7sWVd83tDDxWQE06tVjBnp3eEd2380YPisXV5+sHKLE7gTAW36qeszEyVRVqRs8dCJVV6qqn3pbz+piwYLKIRrq3XZXPXEcA9RUOWTgZ3K3/ujFxL7R6N3xX5/MHZRv/V6ijcmP61St+rm3h2dQasnv/+/XhivHnMpuxMTX18lG9fYBDCU+r1eHFlWOeWXoVOUYgKHaQCrO5pYZj0oVHpd0RdO01ZJ2Stoq6ZiksZaYlZL2Snpa0g5JS8rp10k60PRqSFrVEvtQ6/LMzMzMzGyea0TvXn1mxg5ZRASwDtgoaaGkEWADsB64G7i8TdgW4OaIOA94ALixXNZnI2JVRKwCrgeej4gDp4Mk/Rrwyo+/SmZmZmZmZnNDx+u0ETEmaQdwE0UCjnsi4ghwRNK5bUKWA3vK97uBXcCtLfOM0pT6vsy0+B+BtcDnqq2CmZmZmZnNZdGH44P1Src3zt4O7KcY5PmCDvOOAVcBD1KkuD+nzTzXUIw1dtrvA/8F6N2DDWZmZmZmZm+yrp7sjIjjwHbg3ojolNVhDbBe0pPAYopO3GskXQiciIix8vdVwD+PiAc6tUPSWkn7JO37zM6/6abpZmZmZmbW787gZ8iqpJZplK8ZRcQh4DIAScuAK1tmuZam2xWB/xP4gKTny/YslfRoRFzSZtmbgc0AJx/+VP9tTTMzMzMzswpmPS+2pKURcUxSDbiFpkGey2lXUwwQDUBEfBr4dFl+LvA/2nXGzMzMzMxsnjqDxyFLDkYFkrYBe4HlksYl3VAWjUo6DBwCjgJ3NYVdDIxHxHPZes3MzMzMzOaLrq+QRcRtLb+PTjPfJmDTNGWPAhfNUMfzwPu7bZOZmZmZmc0DffhsV6/M+i2LPTNYvekaWlC9noGh6jGABjN15T4OZeJquYujPTtUkiPTp7aFkheKM9uwUU/WldgePbzyn9ruQCSOE6VqInV8Zfd3JfaNdF2TE51nmqW6UhYkh5fMHF/DC3tTD6BEeuaYnEzVxVT184YmplJVaVH146R2KnleS5yjhs7K1TU8UD1uwVTue2gocYDlaso5lYwbTpx9G8kbsRYk4iaV/LwSddUTdQ0l/64ZTK6XzS0z7oUqPC7piqZpqyXtlLRV0jFJYy0xKyXtlfS0pB2SlpTTr5N0oOnVKDMsIulRSV9rKlv6RqysmZmZmZlZP5mxQxYRAawDNkpaKGkE2ACsB+4GLm8TtgW4OSLOAx4AbiyX9dmIWBURq4Drgecj4kBT3HWnyyPi2I+7YmZmZmZmNkc0Gr179ZmO9yZExJikHcBNwAhwT0QcAY6UWRFbLQf2lO93A7uAW1vmGeX1qe/NzMzMzMzOON3eOHs78OvAFcAdHeYdA64q318NnNNmnmv40Q7ZXeXtirdKSj8mYmZmZmZmc0wfDQwt6fLycapvSLq5Tfk/k/RFSQfLR6/e2VT2byV9vXz9225WvasOWUQcB7YD90bEqx1mXwOsl/QksBh43RPnki4ETkRE87Nn15W3OP5i+bq+3YIlrZW0T9K+zzzylW6abmZmZmZm1hVJA8CfUVyIei/FkF7vbZntkxR3Da4Afg/4z2XsTwG/C1wIfBD4XUk/2anOKqllGnSREykiDkXEZRHxAYqrYEdaZrmWlqtjEfGt8ufLwH+jWIF2y94cERdExAU3/OovVGi6mZmZmZn1rWj07jWzDwLfiIjnImICuB/4SMs87wW+WL7/UlP5rwC7I+K7EfESxeNb7XJuvE56YOjpnM6QKKkG3ALc2VRWo7iN8f6maYOS3l6+HwL+FcVtj2ZmZmZmZr30DuCbTb+Pl9OaPQV8rHz/UWCxpLd1Gfsj0h0ySduAvcBySeOSbiiLRiUdBg4BR4G7msIuBsYj4rmmaQuAXZIOAgeAbwF/nm2XmZmZmZnNMT18hqz5MajytbapJe1yWbQ+ePabwIcl/R3wYYr+y1SXsT+i6xEgI+K2lt9Hp5lvE7BpmrJHgYtaph0HPtBtO8zMzMzMzLIiYjOweZricV6flPCdFBeZmuOPAr8GIOktwMci4vuSxoFLWmIf7dSerjtkfWdqqnJITJysHKOFI5VjACLq1etKjosQ9erbgkheHM3U1ai+LRjo3a4Zne8lbkuZbZFdr0wTawPJuhKfV5IS2yP7eZHYHNl0r5k2anBBrq4FC6vXlaopd15juHr70jJ1ZcejGRpOxAylqtKCRF3DuXONFlRvY21h4lwIxFT1bV8byn1egwPV44bqubqGonMGt1aN5FFZvSYYygQBQ4k2RvJGrMFE3ALl6hquVT9W6l1k6Ws1qNx38lAybi6K/hkf7Ang3ZJ+juLK17UU2eZfUz5u9d0ovvB/G9haFu0C/rApkcdlZfmMZv0ZMjMzMzMzs7koIqaAT1B0rr4KfC4inpH0e5JOD+11CfC18jGtnwY2lLHfBX6folP3BPB75bQZzfhvgXI8sC8DGyLir8ppqylS2x+lSMBxLCLe3xSzkiKRx1uA5ylS2v9A0nXAjU2LXwGcHxEHJA0Df1quXAP4nYj4750ab2ZmZmZm80DiyuMbJSIeAR5pmfafmt5/Hvj8NLFb+eEVs67MeIUsIgJYB2yUtFDSCEUPcD1wN+3TOG4Bbi7HFXuAshMWEZ+NiFURsYpinLHnI+JAGfM7FB27ZRRpJB+rshJmZmZmZmZzUccbZyNiTNIO4CZghGIQtCPAEUnntglZDuwp3++muNx3a8s8o7x+LLI1wHvK+hrAC92vgpmZmZmZzWl9dIWs17p9kvF2YD8wAVzQYd4x4CrgQYoxx85pM881lAOoSfqJctrvS7qEYiDpT0TEt7tsm5mZmZmZ2ZzUVVKPMjX9duDeiHi1w+xrgPWSngQWU3TiXiPpQuBERJwe/HmQIiXkVyLifIqxzT7ZbsHNYwZ8ZuffdNN0MzMzMzPrd9Ho3avPVMn12aCL5NsRcYgixSOSlgFXtsxyLa+/XfFF4ATF82YAfwHcQBvNYwacfPhTZ+51TTMzMzMzmxdmPe29pKXlzxpwC0XGRZqmXQ3cf3pamThkBz8cRO1S4NnZbpeZmZmZmVm/SXfIJG2juL1wuaRxSaevao2WOfkPUaTGv6sp7GJgPCKea1ncTcBtkg5SZGD8jWy7zMzMzMxsjmlE7159putbFiPitpbfR6eZbxOwaZqyR4GL2kz/e4rOWvd6df/nZKdH5qaRGTF+YChXV30yEdTDuhLbIk4dR2ctrl5XL+8LznzGvdSo5+JqA4m6clWlArPbvZf7RuZYrk/l6spsj1puGyrxP7xI1tWz42vhIpic6Dxfq4Hqx4kGE8cWEIm6Mu0D0GBiuw8qV1di36gN5o7jgVr1uAFyf7TVEmG1ZF2NxKYfiNznNUj1uMnsNlT1upRcr1pivTIxSwbO4pX6qcpxSmwLm3uqPENm1jOpzpiZWVWZzpiZWUWZztiZJvrwylWvzPjvKRUel3RF07TVknZK2irpmKSxlpiVkvZKelrSDklLyunXSTrQ9GpIWiVpccv0FyR96o1ZXTMzMzMzs/4xY4esTLixDtgoaaGkEWADsB64G7i8TdgW4OaIOI8ic+KN5bI+GxGrImIVxXNiz0fEgYh4+fT0suzvgS/M0vqZmZmZmVm/8zNk04uIMUk7KBJvjAD3RMQR4Iikc9uELAf2lO93A7uAW1vmGeX1qe8BkPRuYCnw5S7bb2ZmZmZmNmd1+wzZ7cB+ikGeL+gw7xhwFfAgRYr7c9rMcw3wkTbTR4Ht5ZU5MzMzMzM7EzT6b8DmXukqxVFEHAe2A/dGRKe0g2uA9ZKeBBZTdOJeI+lC4EREjLWJbR00+nUkrZW0T9K+z+zc203TzczMzMzM+laVLIsNushRHRGHgMsAJC0DrmyZpW2nS9JKYDAinpxh2ZuBzQAn/8dGX0UzMzMzM5sP+vDZrl6Z9bT3kpZGxDFJNeAW4M6mshrFbYztxhxr+1yZmZmZmZnZfJUeeVPSNmAvsFzSuKQbyqJRSYeBQ8BR4K6msIuB8Yh4rs0iV+MOmZmZmZnZmcdZFjuLiNtafh+dZr5NwKZpyh4FLpqm7F3dtsXMzMzMzGw+mPVbFnsmk4mll9lbYp5milH6omo1Pdx+6tU6AVGfSsVpoIeHambT1wZmvRnTyu4bmbDsds98ztm6Boaqxwxlt2Eirpf77tBw9Zjs/pSpK7sthhOf8WDymByq3kYNJ+tK/Jdaw7lz6OBA9c95sJbbN4Z6+N/3BqocM5xs3kCirsFEzFyoa0CZmNzfGgP5m9nmnDM5yfqMn7IKj0u6omnaakk7JW2VdEzSWEvMSkl7JT0taYekJeX06yQdaHo1JK0qy0bL+Q+Wy377G7GyZmZmZmZm/WTGDlk5Htg6YKOkhZJGgA3AeuBu4PI2YVuAmyPiPOAB4MZyWZ+NiFURsQq4Hng+Ig5IGqS4xfFfRsQK4CDwiVlZOzMzMzMz639n8DNkHa+DluOF7QBuAn4XuCcijkTEHuC7bUKWA3vK97uBj7WZpzmjosrXiCQBSyiSgZiZmZmZmc1r3d4sfjuwn2KQ5ws6zDsGXAU8SJHi/pw281wDfAQgIiYlfRx4GjgOfJ3iCpyZmZmZmdm81tWTghFxHNgO3BsRr3aYfQ2wXtKTwGKKTtxrJF0InCivvCFpCPg48PPAz1Lcsvjb7RYsaa2kfZL2fWbX33bTdDMzMzMz63dn8C2LVdIpNegiT1lEHAIuA5C0DLiyZZZref14Y6vKuCNlzOeAm6dZ9mZgM8DJhz7Zf1vTzMzMzMysglnPRyxpaUQcU5FL/BbgzqayGsVtjBc3hXwLeK+ksyPiO8AvA1+d7XaZmZmZmVl/ij68ctUr6cENJG0D9gLLJY1LuqEsGpV0GDhEkZzjrqawi4HxiHju9ISIOErxjNoeSQcprpj9YbZdZmZmZmZmc0XXV8gi4raW30enmW8TRRr7dmWPAhe1mX4nTVfSzMzMzMzsDHIGXyGb9VsWe+bUieoxA4nVHT6rekyyrpjqlC+lvdTY9NHxccD26lO5uIpiYiAXmPmMk5T8vDJicEHlGKW3RXLfyKhV/5zT65XZpbJ1JY6TqOVuWFCj+ucV2fXKnDcWLsrVperbQwuqn6/T2yJjInmOH0jsvMdPpurK/EFUm8x9L8RU9f1p4C2TqboWnFW9jRNTue+hhad6dw6NqP4XwGTi2AJYlKhrMHkj1gkltn3qjyE4pXousKJ6LdfZGM5sC5tzZjxSVHhc0hVN01ZL2ilpq6RjksZaYlZK2ivpaUk7JC0pp18n6UDTqyFpVVl2jaSDkp6RdMcbsaJmZmZmZtanGj189ZkZO2QREcA6YKOkhZJGgA0U44TdDVzeJmwLcHNEnAc8ANxYLuuzEbEqIlYB1wPPR8QBSW8D/hi4NCLeB/y0pEtnZ/XMzMzMzMz6V8d7NSJiTNIO4CZgBLinTFF/RNK5bUKWA3vK97uBXcCtLfOM8sPU9+8CDpcZFgH+GvgY8MXuV8PMzMzMzOaqMznLYrc3z98O7KcY5PmCDvOOAVcBD1KkuD+nzTzXAB8p338DeE/ZuRsH/g0w3GW7zMzMzMzM5qyunraMiOPAduDeiOj0VPIaYL2kJ4HFFJ2410i6EDgREWPlsl8CPl4u/8vA80Dbp28lrZW0T9K+z/z1vm6abmZmZmZm/a4RvXv1mSrppbp6DC4iDgGXAUhaBlzZMsu1/PB2xdMxO4AdZcxaoG3Km4jYDGwGOPm53+u/rWlmZmZmZlbBrOf7lbQ0Io5JqgG30DS+WDntaooBotvF/CTwH4DVs90uMzMzMzPrU32Y/bBXcgNEAJK2AXuB5ZLGJd1QFo1KOgwcAo4CdzWFXQyMR8RzLYvbJOlZ4CvAH0XE4Wy7zMzMzMzM5oqur5BFxG0tv49OM98mYNM0ZY8CF7WZ3nZZZmZmZmY2/znL4hwUL36n80wtNDlZvZ7KEaWpTrlPfpQGF6SqiqFEUsqB5Ec/NdF5ntmwYCQVltmG0ai+XwAwdFb1mOFEDKBETETy2r8SF86TdSmzHw4Mpeqi0fbR1Jll7yFIfM6q5/bDGGqbA2nmurL7RsbC3LGcMlj9XJg5tgCiXn27M5w7x9NIfF4jyXNNvfpxEgtyx6QGqq+XFg6k6hoYrF7XYKJ9AAsSfzlkj8jM3yhDyT9sFiTiQrkjbDhxZNaTJ+yzVP17SIn1qif/ohzMfCfbnONP2czMzMzM7E0yY4dMhcclXdE0bbWknZK2SjomaawlZqWkvZKelrRD0pJy+pCk/1pO/6qk326KuVzS1yR9Q9LNs72SZmZmZmbWxxo9fPWZGTtkERHAOmCjpIWSRoANwHrgbuDyNmFbgJsj4jzgAeDGcvrVwIJy+geA/1vSuZIGgD8DrgDeS5EU5L0/9pqZmZmZmZn1uY43zkbEmKQdwE3ACHBPRBwBjkg6t03IcmBP+X43sAu4leJW5xFJg8BZFANG/wD4IPCN05kXJd0PfAR4Nr9aZmZmZmY2VzipR2e3A/spOlEXdJh3DLgKeJDiqtg55fTPU3S0/hFYBPx/EfFdSe8AvtkUPw5c2GW7zMzMzMzM5qyuknpExHFgO3BvRHRKH7gGWC/pSWAxRScOiithdeBngZ8DfkPSu2if5KptF1nSWkn7JO3b+vhYu1nMzMzMzGyuOYOfIauS67OrVYiIQ8BlAJKWAVeWRb8O7IyISeCYpK9QXG37Jj+8igbwTooBpdstezOwGeDEp/+fM/e6ppmZmZmZzQuznvZe0tLyZw24BbizLPoH4JfKzI0jFANEHwKeAN4t6eckDQPXAg/NdrvMzOz/Z+/+4+yq7/vOv97zWxKSTeIoDzumBadBDmtV2NKybLNJqb1QFFJI6kWbofE2KxZMonQTSgnyo1BEW/rosjZB6XrDQ8UaDG1kEmIetraOCPU6VQElZlAtNCBFWC7GMmwmCSFYQtL8uJ/943wVpuM7c+/5IN/emXk/9bgP3fu953O+33PuOefe75xzPl8zM7PuFI3OPbpNukMmaRewD1gj6Zik68tbw5KOUHW2XgFGSvmngXOo7jF7BhiJiOciYgr4JarkH4eA34qI57PtMjMzMzMzWyjavmQxIrbNej08x3Tbge1Nyo9TJfloFvMl4EvttgVA/f11Jq/01R+NPRUDqG+wflDPAhinOzNifOZPEadPwMCy+lXFdP26MssEMD2ViJnM1dWb2A57c1WlPq/sX5sybWwkPmOAnkRlyeVS4vOK5P7f7CbclnVltieARmKFTLW67fjsSR13gUjsl0ocN2JyovVEzfQP1A5Rsq7MvQDZb66Yqr8v90wmjrvAsnPfSMVlnJpI7l8JEfWPAH1Tid9PwKmp+tvh6eTGkTmGHlfugN2v+uvwTepvu9/XO8CJqL/99p39i9m6VxeeueqUJfQp24KS6IyZmdWV6YyZmdWV6YzZ0jFvh6zc7/WkpI0zyjZJ2iNpp6RxSWOzYtZJ2ifpoKTdklaV8n5Jny3lhyR9YkZM03mZmZmZmdni53vI5hARAdwE3CtpqCTjuBvYAjwIXNkk7AFga0SsBR4Dbi3l1wKDpXw98PEZA0vPNS8zMzMzM7NFq+VFuhExJmk3cBuwAngoIo4CR2d0qGZaA+wtz5+gStZxB9Wl6Ssk9QHLqMYne6PUsXeOeZmZmZmZ2WLXhWeuOqXduybvAvZTdaI2tJh2DLga+ALVWbEzY4w9ClwDvAosB26OiNfqNtjMzMzMzGyxaCupR0ScAB4BHo6IVimzNgNbJD0LrKTqxAFcAkwD7wEuAG6R9L46jZV0o6RRSaOf2ftcnVAzMzMzM+tSS/kesjp5RRu0cTIxIg4DVwBIuhC4qrx1HbAnIiaBcUlPUZ1t+0a7DYiIHcAOgJMP/MNMdl4zMzMzM7OucdbT3ktaXf7vAW4H7i9vvQx8uGRuXAFcSjV4tJmZmZmZ2ZKU7pBJ2gXsA9ZIOibp+vLWsKQjVJ2tV4CRUv5p4Byqe8yeAUYi4rkW8zIzMzMzs0XOlyy2ISK2zXo9PMd024HtTcqPUyX5aBbTdF5mZmZmZmaLWZ17yLpKnDxVO0b9A/UrOvVm/Rgg+hJ19SY/junJ2iHqH0xVFZOtcrqcHdUVrwmZddjI/akkeurHaaL1NE3rSsQoV1VOdtvNxGXP62c+5p7eXF3TU7VDstt8JNqo5OcV1F+utMz6yCxX4viZrqsnufFm1kW2rsxy9Sb3k8SxV3255Uqtwr7cd0NvT/0jdmTvilf9wL5EDEBv4puoJ3LfRJlPuS9Zl1Q/Th38hu1JtG+h6sYzV50y7zZf7vd6UtLGGWWbJO2RtFPSuKSxWTHrJO2TdFDSbkmrSnm/pHafqDkAACAASURBVM+W8kOSPlHKz5P0lVL2vKRf/l4sqJmZmZmZWbeZt0MWEQHcBNwraagk47gb2AI8CFzZJOwBYGtErAUeA24t5dcCg6V8PfDxMhj0FHBLRPwoVaKPLZIuepvLZWZmZmZmC0Woc48u0/LahIgYk7QbuA1YATwUEUeBo6VDNdsaYG95/gTwOHAH1VVXKyT1Acuoxid7owwO/Wqp6zuSDgE/BLzwNpbLzMzMzMys67V7sfhdwH6qTtSGFtOOAVcDX6A6K3ZeKX8UuIaq87UcuLl0xv5S6eB9EPjDNttlZmZmZmYLnO8hayEiTgCPAA9HRKusDpupLjt8FlhJ1YkDuASYBt4DXADcIul9Z4IknQP8DvArEfFGsxlLulHSqKTRnU/5BJqZmZmZmS1sddIpNWgjT1lEHAauAJB0IXBVees6YE9ETALjkp6iOtv2DUn9VJ2xfxsRn59n3juAHQBv/qtfzOYkMjMzMzOzLhKN7ru3q1PSA0PPRdLq8n8PcDtwf3nrZeDDJXPjCqoEHodV5Rv9DHAoIu492+0xMzMzMzPrVukOmaRdwD5gjaRjkq4vbw1LOgIcBl4BRkr5p4FzqO4xewYYiYjngB8DPkbVWftaefxktl1mZmZmZrawRKNzj27T9iWLEbFt1uvhOabbDmxvUn6cKsnH7PIn6fAYtmZmZmZmZt2gzj1kXSX+5LXWE802OVk7JN1TbHSw+91b/2OM/oFcXZMTraf5rsrqr4tYdk79egBOfqd+zPRUrq6Bofox53xfqir11D+ZHdk/AfX214/JrsNM3MCyVFVK7Cfp5eofrB/TmM7Vlfmck3Wl1uHyd6bqSlH9/USJGKjGcald1znn5urqTRzXkvu/Mt8NPclvyqnEdtjI3T4+sLpprrB59fTl9v+p0ydrx0RyXKTp6fpxfSdz28bpk/X3lVPJ/auX3toxbya3w+U99Y9rpxLtO67cT+7eJXTOIrsfLAbz7inlfq8nJW2cUbZJ0h5JOyWNSxqbFbNO0j5JByXtlrSqlPdL+mwpPyTpE6V8SNJXJR2Q9Lyku74XC2pmZmZmZtZt5u2QRUQANwH3lo7TCuBuYAvwIHBlk7AHgK0RsRZ4DLi1lF8LDJby9cDHy7hjp4EPR8Q64GLgSkmXvs3lMjMzMzOzBcL3kM0jIsYk7QZuA1YAD0XEUeBo6VDNtgbYW54/ATwO3EF1hccKSX3AMqrxyd4onb7jZfr+8nBKezMzMzMzW/Tavbj3LqpxxDYC97SYdgy4ujy/FjivPH8UOAG8SpUC/5MR8RqApF5JXwPGgSci4g/bXgIzMzMzM7MFqq0OWUScAB4BHo6I0y0m3wxskfQssJLqTBjAJcA08B7gAuAWSe8r85+OiIuB9wKXSPpAsxlLulHSqKTRnfuPttN0MzMzMzPrctFQxx7dpk76m0Z5zCsiDkfEFRGxHtgFnOk5XQfsiYjJiBgHngI2zIp9Hfh9mt+bRkTsiIgNEbFh84d+uEbTzczMzMzMuk96YOi5SFpd/u8BbgfuL2+9TDX4s0pykEuBw5J+QNI7S8wy4H+kGlTazMzMzMyWgIjOPbpNukMmaRewD1gj6Zik68tbw5KOUHWqXgFGSvmngXOo7jF7BhiJiOeAdwNfkfRcKX8iIv6fbLvMzMzMzMwWirZHqYuIbbNeD88x3XZge5Py41RJPmaXPwd8sN12mJmZmZnZ4tKN93Z1Sm7Y8IWqkThH2UgOVtDJQQ6mp+rH9CRPjmbqyqyLyYnW0zSTWa7MMkGujRMnU1Vlzq6rbzBVV2p99OYOJZH4vDQ92bm6lNxPGtP1Y3p6U1Upse7TV2skjofKHkMzMutiKvcZK3Fci+SxJvMTJVtXSvIzVqaNk7n9v/fcoUTUqVRdy0/W/27I/hBtTNeP6+vLfV4TU/WPUaenct8NPYlNY1nyd01/b/11eFL1YwaSx/i+Lry8zs6+pdUhMzMzMzOzrrOUz5DN++eEkoDjSUkbZ5RtkrRH0k5J45LGZsWsk7RP0kFJuyWtKuX9kj5byg9J+sSsuF5J/0mS7x8zMzMzM7MlYd4OWUQEcBNwr6Shkh3xbmAL8CDN09M/AGyNiLXAY8CtpfxaYLCUrwc+Lun8GXG/DBxKL4mZmZmZmS1I3ZRlUdKVkv5I0tclbW3y/l+R9JVyMuk5ST/Z5P3jkv5RO8ve8oLbiBgDdgO3AXcCD0XE0YjYC7zWJGQNsLc8fwL46JlZASsk9QHLqAaMfqM0+r3AVVSdOTMzMzMzs46T1EuVHX4jcBFVBvmLZk12O/BbEfFB4GeB/3vW+78G/G67dbZ7D9ldwH6qTtSGFtOOAVcDX6A6K3ZeKX8UuAZ4FVgO3BwRZzp09wG/Cqxst+FmZmZmZrY4dNE9ZJcAX4+IbwBI+hxVH+aFGdMEsKo8fwfVUF+U6X8a+AZwot0K20pJExEngEeAhyPidIvJNwNbJD1L1cE6k27oEmAaeA9wAXCLpPdJ+ilgPCKebdUOSTdKGpU0unP/0XaabmZmZmZm1q4fAr414/WxUjbTNuDnJB0DvgT8A4Bye9dtVCez2lYnR2ijPOYVEYcj4oqIWA/sAs70nK4D9kTEZESMA09RnW37MeBqSS8BnwM+LOnfzDHvHRGxISI2bP7QD9doupmZmZmZdasIdewx8yRPedw4oynNTtXNvvNsGHgwIt4L/CTwsKqxcu4Cfq2Mv9y2s572XtLqiBgvjboduL+89TJvdbaWA5cC90XEbwGfKLGXAf8oIn7ubLfLzMzMzMwsInYAO+Z4+xhv3XIF8F5mXJJYXE9JbhgR+yQNAe8C/jvgf5J0D/BOoCHpVET8X/O1JznqKUjaBewD1kg6Jun68tawpCPA4dL4kVL+aeAcqnvMngFGIuK5bP1mZmZmZrY4RKNzjxaeAX5E0gWSBqiSdnxx1jQvAx8BkPSjwBDwJxHx4xFxfkScT5Uj41+06oxBjTNkEbFt1uvhOabbDmxvUn6cKsnHfHX8PvD7bbVncrKdyf4Lmp6uHUMjN6J9Ki7TvnRdUx2sK7Fcp09Df3/9uDb2su+S/YwnJ1pPM0sbmVabUqauwaFkZYm/0/QmPitAiXUf/bltN3OrcPT0purKbIfqTV6wkFj3ynzGkFqu7Daf0lN/udQ/CBMna8dF32D9uvrq78cAkTmu9Q2k6mIgUddQbrky30NaVv+zAtCK+sfDnuncd0P/G6dqx0Qjt6dE4nAo5eo653T9z3lgIve7plF/FdLfyF70Vf84399T/xtlVQPeSBx6z/qlbNZSRExJ+iXgcaoNZGdEPC/pnwKjEfFF4BbgX0u6meqr7ufLcGEp/pytO2U6Y2ZmdSU6Y2ZmdWU6Y/ZfT0R8iSpZx8yyfzLj+QtUeTDmm8e2duubd/NQ5UlJG2eUbZK0R9JOSeOSxmbFrJO0T9JBSbslrSrl/ZI+W8oPSfrEjJiXSvnXJI2223gzMzMzM1v4GqGOPbrNvB2ycurtJuBeSUMllePdwBbgQcrNbLM8AGyNiLXAY8CtpfxaYLCUrwc+Lun8GXF/KyIujohW45yZmZmZmZktCi0vWYyIMUm7qXLqrwAeioijwNFZHaoz1gB7y/MnqK6/vIPq+soVkvqAZVTjk73xdhfAzMzMzMwWtujCM1ed0u4VrXdRjSO2EbinxbRjwNXl+bW8lTbyUaoRq1+lykzyyYh4rbwXwO9JenbWOABmZmZmZmaLVlsdsog4ATwCPBwRp1tMvhnYIulZYCXVmTCAS4Bp4D3ABcAtkt5X3vuxiPgQVYdvi6SfaDbjmYO47fzaf26n6WZmZmZm1uWioY49uk2dnC+N8phXRByOiCsiYj2wCzha3roO2BMRkxExDjwFbCgxr5T/x6nuO7tkjnnviIgNEbFh88UX1Gi6mZmZmZlZ9znrSTglrS7/9wC3A/eXt14GPlwyN64ALgUOS1ohaWWJWQFcQXXZo5mZmZmZLQERnXt0m3SHTNIuYB+wRtIxSdeXt4YlHQEOA68AI6X808A5VJ2tZ4CRiHgO+EHgSUkHgK8C/y4i9mTbZWZmZmZmtlC0PTD07MHNImJ4jum2A9ublB+nSvIxu/wbwLp222FmZmZmZotLN97b1Sltd8i6jfr76wf1JD7oaHnbXHONRFwmBmB6qn5MT/LkaKeWa3KyfgxAf2JdpNd7IkZvpqrKnF1PH9Yy20Z/bh1Gb/1DkJL7ZKquRAwAjfobR/YKiurq8Jp6epO1JeImJ1pP00xiO8ysi+gbrB2Trms6d1xLbYfJuuivvz4i+32S+e5KHq97Vp9bO0ZDid8ZAI3E3pyJASIR1/dGYr0D6jlZO2bqdG7b6H+9/jH09ETueL3s9EDtmJNT9es6J3OsBvrT3w62kMy7dZT7vZ6UtHFG2SZJeyTtlDQuaWxWzDpJ+yQdlLRb0qpS3i/ps6X8kKRPzIh5p6RHJR0u7/33Z3tBzczMzMysOzVCHXt0m3k7ZBERwE3AvZKGStKNu4EtwIPAlU3CHgC2RsRaqoyJt5bya4HBUr4e+PiMgaW3U2VgfD/V5YuH3sYymZmZmZmZLQgtz7lGxJik3cBtwArgoYg4Chyd0aGaaQ2wtzx/AngcuIPqipwVkvqAZVTjk71RzqD9BPDzpb4J3hq7zMzMzMzMFrnowjNXndLuBa13UY0jthG4p8W0Y8DV5fm1wHnl+aPACeBVqhT4n4yI14D3AX8CjEj6T5IeKGfizMzMzMzMFrW2OmQRcQJ4BHg4Ik63mHwzsEXSs8BK3jrbdQlVGoT3ABcAt0h6H9VZug8BvxERH6TqtG1tNmNJN0oalTS6c//RZpOYmZmZmdkC43HI2tMoj3lFxOGIuCIi1gO7gDM9p+uo7hObjIhx4ClgA3AMOBYRf1ime5Sqg9Zs3jsiYkNEbNj8oR+u0XQzMzMzM7Pukx4Yei6SVpf/e4DbgfvLWy8DHy6ZG1cAlwKHI+L/A74laU2Z7iPAC2e7XWZmZmZmZt0m3SGTtAvYB6yRdEzS9eWtYUlHgMPAK8BIKf80cA7VPWbPACMR8Vx57x8A/1bSc8DFwL/ItsvMzMzMzBaWpZz2vu2R7SJi26zXw3NMt50qjf3s8uNUST6axXyN6vJFMzMzMzOzJSM3rHk3iJa3s52lanJ3/mm6/ijzTE+l6qKRWBeZmGxc5rPKfryZdZhdFz1n/YrfsyoisQ0Cypw4z67DDu3HQKqNQW6fVG/i0NrRddibqyshtS6gc/tXJOuJxDrsH8zVldHXwbomB3JxSqz7waFcXQOJNg7mlkuDmf0/+VsjE7c8V1ffsvrHQ/Xk6ho4mfv+ypicqr8vx3T9MyxTyUQSC/eHen1Oez+Hcr/Xk5I2zijbJGmPpJ2SxiWNzYpZJ2mfpIOSdpdxxpDUL+mzpfyQpE+U8jWSvjbj8YakX/leLKyZmZmZmVk3mbdDFhEB3ATcK2moJOO4G9gCPAhc2STsAWBrRKwFHgNuLeXXAoOlfD3wcUnnR8QfRcTFEXFxKX+zxJmZmZmZ2RKwlNPetzwTGhFjknYDtwErgIci4ihwVNL5TULWAHvL8yeAx4E7gABWSOoDllGNT/bGrNiPAEcj4pv1F8XMzMzMzGxhaffS1LuA/VSdqFbJN8aAq4EvUJ0VO6+UPwpcA7wKLAdujojXZsX+LNXYZWZmZmZmtkR0Y/bDTmnrjtqIOAE8AjwcEadbTL4Z2CLpWWAlVScO4BJgGngPcAFwi6T3nQmSNEDVkfvtuWYs6UZJo5JGd+7/RjtNNzMzMzMz61p1krc0aCP3XUQcBq4AkHQhcFV56zpgT0RMAuOSnqI623amZ7UR2B8RfzzPvHcAOwBO3LGpC68ANTMzMzOzupxl8SyStLr83wPcDtxf3noZ+HDJ3LgCuJRq8OgzhvHlimZmZmZmtoSkO2SSdgH7gDWSjkm6vrw1LOkIVWfrFWCklH8aOIfqHrNngJGIeK7MazlwOfD5bHvMzMzMzGxhaoQ69ug2bV+yGBHbZr0enmO67cD2JuXHqZJ8NIt5E/j+dttiZmZmZma2GCzYAcCnXv6z2jE973izfszJVjlM5nDyZO2QeLN+TJYGB3KB09O1Q2Kqfoz6emvHADCQWK7EMgHQ3187RN+X/LvD4PH6MQNDqaqiJ3HivDd5KBlanohZkatrKrkvZyx/Z+0QNVreottU6mbayYnW0zShzOfcP5iqi0Ziv4z667C6uj5RVSJOA8tydSW2DS1/R66u0yfq15WqCUgsVyQ+YwC9s/4+SV/uuNbTSOyVyf0/8/2l5adSVQ301f8NFady3699y+q3cepkbktcfrz+8XDyVP3fKCdP5n53SUsnZcLSWdLvdtbvITMzMzMzM7P2zNshKwk4npS0cUbZJkl7JO2UNC5pbFbMOkn7JB2UtFvSqlLeL+mzpfyQpE/MiLlZ0vOSxiTtkpT7076ZmZmZmS04S/kesnk7ZBERwE3AvZKGSnbEu4EtwIPAlU3CHgC2RsRa4DHg1lJ+LTBYytcDH5d0vqQfAv53YENEfADopRog2szMzMzMbFFreYF0RIxJ2g3cBqwAHoqIo8BRSec3CVkD7C3PnwAeB+6gujR0haQ+YBnVgNFvlOd9wDJJk8ByquyMZmZmZma2BCzlccjavWP1LmA/VSdqQ4tpx4CrgS9QnRU7r5Q/ClwDvErV6bo5Il4DkPRJqnHKTgK/FxG/V2MZzMzMzMzMFqS2knpExAngEeDhiGiVqmwzsEXSs8BKqk4cwCXANPAe4ALgFknvk3QuVUftgvLeCkk/12zGkm6UNCpp9MEj326n6WZmZmZmZl2rTk7XRnnMKyIOA1cASLoQuKq8dR2wJyImgXFJT1GdbQvgP0fEn5SYzwN/A/g3Tea9A9gB8Bd//yNLOTummZmZmdmikRz8YVE462nvJa0u//cAtwP3l7deBj5cMjeuAC4FDpfySyUtlyTgI8Chs90uMzMzMzOzbpPukEnaBewD1kg6Jun68tawpCNUna1XgJFS/mngHKp7zJ4BRiLiuYj4Q6r7y/YDB0ubdmTbZWZmZmZmC0ugjj26TduXLEbEtlmvh+eYbjuwvUn5caokH81i7gTubLctABqoP0p6JobeRAxAX52rQSvq78/VFYmTvNnl6qnfh69OlmbqSuwwifWeWaaqruQ6zMi2MSP7eXV7XRnd3r6s7PaUiWtMJ+tK7F/TiWNhdl1EIi7TPsi1cSpXl1R/vUd6P0m0cSHsk43EcjWSd2Fk4zpVV3KTb0zVj4lG7kd2Y7p+3HSj/nY4MDjFm28O1I7r7VnKF/ItHYlfr2YdkOmMmZmZmXWhTGdsqenk3xe6zbxd/HK/15OSNs4o2yRpj6SdksYljc2KWSdpn6SDknZLWlXK+yV9tpQfkvSJGTG/LGlM0vOSfuVsL6SZmZmZmVk3mrdDFhEB3ATcK2moJOO4G9gCPAhc2STsAWBrRKwFHgNuLeXXAoOlfD3wcUnnS/oAcANVWvx1wE9J+pG3vWRmZmZmZrYgNFDHHt2m5UWwETEG7AZuo7rP66GIOBoRe4HXmoSsAfaW508AHz0zK6oxxvqAZVTjk70B/CjwBxHxZkRMAf8B+Jn8IpmZmZmZmS0M7d5DdhdVFsQJqrHD5jMGXA18geqs2Hml/FGqAaBfBZYDN0fEa+WSx7slfT9wEvhJYLTOQpiZmZmZ2cLVjdkPO6WtNDERcQJ4BHg4Ik63mHwzsEXSs8BKqk4cVJckTgPvAS4AbpH0vog4BPwfVGfT9gAHgKb5dSTdKGlU0ujI4WPtNN3MzMzMzKxr1cnb2aCNBKYRcTgiroiI9cAu4Gh56zpgT0RMRsQ48BTlbFtEfCYiPhQRP0F1GeSLc8x7R0RsiIgN/+v731uj6WZmZmZm1q0aHXx0m7M+oIek1eX/HuB24P7y1svAh0vmxhXApVSDR8+M+SvA36XqyJmZmZmZmS1q6Q6ZpF3APmCNpGOSri9vDUs6QtXZegUYKeWfBs6husfsGWAkIp4r7/2OpBeokodsiYg/z7bLzMzMzMwWlkAde3SbtgeGjohts14PzzHddmB7k/LjVEk+msX8eLvtMDMzMzMzWyza7pB1G60YrB+zLBEzmBxZvb+/fkxPsseeGdo80z6ARuLK2+jc1bpavqJ+0FTTHDKt9SROMA/W3wYBGBjqTExWf24/0eCy+kF9ybr6Euu+N3mIVGLbyNaV2A6VaV9Wdv+fTsT1Jo5rjen6MeTWYeJI3fG6InLro2M6+H2yIGR/NyxC2U0jGl6H3WIp793zHuXL/V5PSto4o2yTpD2SdkoaL2nrZ8ask7RP0kFJuyWtKuUDkkZK+QFJl82IWV/Kvy7p1yV57zAzMzMzs0Vv3g5ZRARwE3CvpKGSjONuYAvwIHBlk7AHgK0RsRZ4DLi1lN9Q5rkWuBz4lN76s99vADcCP1IezeZrZmZmZma2qLS8DiIixqiSbdwG3Ak8FBFHI2IvVYr62dYAe8vzJ4CPlucXAV8u8xwHXgc2SHo3sCoi9pUO4EPAT+cXyczMzMzMFpKlnPa+3ZsW7gL2Uw3yvKHFtGPA1cAXqJJ4nFfKDwDXSPpcKVtf/m8AM0d5Pgb8UJvtMjMzMzMzW7DaulM4Ik4AjwAPR8TpFpNvBrZIehZYSdWJA9hJ1dkaBe4DngamoGnuyab3I0u6UdKopNGRg99sp+lmZmZmZtblnPa+PW2d5YuIw8AVAJIuBK4q5VPAzWemk/Q08CLw58B7Z8zivVTjlzWb9w5gB8B3fuXvZJNImZmZmZmZdYWznvtY0uryfw9wO3B/eb28JAVB0uXAVES8EBGvAt+RdGnJrvi/UF3uaGZmZmZmS0BDnXt0m3SHTNIuYB+wRtIxSdeXt4YlHQEOU53pGinlq4H9kg5RJQj52IzZ/QJVdsavA0eB3822y8zMzMzMbKFo+5LFiNg26/XwHNNtB7Y3KX+JKgNjs5hR4APttsXMzMzMzBaPRhfe29Upde4h6y4TU7VD4tRk/XqGJlpP04ROnqwfND2dqotG4na6RjLpZyYus1w9uZO3oURcdr339tYOUX9iu4Dc+sh+xr2Jw0Lk6opEXdnDdUwn9v9MDKDEdhhTuW1eiXUffYOpuoj6bcysCyC5zWeONfX347TkftLJujL7V/qG7un63+MazH0nx0D9bV5D9dsHwLKh2iGR/k6uv/azx9CeN+uv++jLfb/2TdSvq6cv+T0U9T/n3tOd25d7e7oxSbudbQu3Q2ZmZmZmZovCUs7WN++fIFV5UtLGGWWbJO2RtFPSuKSxWTHrJO2TdFDSbkmrSvmApJFSfkDSZTNi7pb0LUnHz/LymZmZmZmZda15O2QREcBNwL2ShkqWxLuBLcCDwJVNwh4AtkbEWuAx4NZSfkOZ51rgcuBTeutalt3AJW9vUczMzMzMbCFqdPDRbVpepB8RY1QdptuAO4GHIuJoROwFXmsSsgbYW54/AXy0PL8I+HKZ5zjwOrChvP6Dkv7ezMzMzMxsyWj3HrK7gP3ABKUTNY8x4GqqscSuBc4r5QeAayR9rpStL/9/tWabzczMzMxsEWlo6WZZbCuNVUScAB4BHo6I0y0m3wxskfQssJKqEwewEzgGjAL3AU8DtVLbSLpR0qik0ZEXvlUn1MzMzMzMrOvUybLY1mWXEXEYuAJA0oXAVaV8Crj5zHSSngZerNPYiNgB7AD4zi9uXMrJWMzMzMzMFo2l/MM+OUDM3CStLv/3ALcD95fXy0tSECRdDkxFxAtnu34zMzMzM7OFIt0hk7QL2AeskXRM0vXlrWFJR4DDwCvASClfDeyXdIgqQcjHZszrHknHgOVlXtuy7TIzMzMzM1so2r5kMSK2zXo9PMd024HtTcpfosrA2CzmV4FfbbctADGZGP399GTtEJ06Vb8egL7e2iExlRvRnun6cUrEAEQjkSy0kTgJHbmkpKnbQbPrvad+bdnT8cqs9/6BXGW99bfddF0JMV3r1tO/9NYoGzX01rmq+y2Zz1nJbT76BuvXlVkXAJE4rqXrqh+XXq6MnvrrItu+aCSOUZOtbveeK26i9TSzJbddJk7WryoRk9aX2/8z3w3K/n28L/E9dDpbVyJuOrdt9Czv3G8oqf4Ru6e3fkymHoDe/m5M0v69sXSW9Lt18NvLzMzMzMzMZpq3Q6bKk5I2zijbJGmPpJ2SxiWNzYpZJ2mfpIOSdktaVcoHJI2U8gOSLivlyyX9O0mHJT0v6V9+D5bTzMzMzMy6VEOde3SbeTtkERHATcC9koZKUo67gS3Ag8CVTcIeALZGxFrgMeDWUn5Dmeda4HLgU3rr+o1PRsT7gQ8CPzazA2hmZmZmZrZYtbxAOiLGJO2mSsSxAngoIo4CRyWd3yRkDbC3PH8CeBy4A7gI+HKZ57ik14ENEfFV4CulfELSfuC9b2ehzMzMzMxs4WjkMgEsCu3eQ3YXcB2wEbinxbRjwNXl+bXAeeX5AeAaSX2SLgDWz3gPAEnvBP4OpeNmZmZmZma2mLXVIYuIE8AjwMMR0Sp102Zgi6RngZXAmZRNO4FjwChwH/A08Jfp0iT1AbuAX4+IbzSbsaQbJY1KGh05fKydppuZmZmZWZeLDj66TZ2crg3ayEgZEYeBKwAkXQhcVcqngJvPTCfpaeDFGaE7gBcj4r555r2jTMcbN1zRjevTzMzMzMysbclBNuYmaXW5R6wHuB24v5QvBxQRJyRdDkxFxAvlvX8OvAP43852e8zMzMzMrLt1Y/bDTkmPQyZpF7APWCPpmKTry1vDko4Ah4FXgJFSvhrYL+kQVYKQj5X5vBf4x1RJP/ZL+pokd8zMzMzMzGzRa/sMWURsm/V6eI7ptgPbm5S/RJWBcXb5MaifVmXqT1vdyvbdepZPtZ5otolEDKBTk7VjYjJXF43EiPGD/bm6ov446jHVubHXM/N/bgAAIABJREFUdeJk/aDketfgQP2Y6elUXTFZf3uiP/cZq6+3flBv8mT7RP39mIHBVFUxOdF6otl6cn+z0jnn1o6J6eR22Fd/uWI6sT0B9Ndf9xpYlqtrOnGsydQzNZHaft8ataWGxPoDUGY7TNbFZP19UsltN5Yn9sllK3N1ZY5Rp99M1aWhxDbf6OD35JsncnHf9536QacTnzEQJ0/Vjuk7mfg+AQb+ov7nHJP1v8vfwTSN44l9pW/pnDbq3F7QfdJnyMzMzBa87B8TzMxqSHXG7L8aSVdK+iNJX5e0tcn7v1au6vuapCNlOK8z790j6XlJhyT9uqSWvep5O2SqPDlzoGZJmyTtkbRT0riksVkx6yTtk3RQ0m5Jq0r5gKSRUn5A0mUzYvaUsucl3S8p8Sd6MzMzMzNbiLoly2Lph3yaarivi6hux7rov2hrxM0RcXFEXAz8K+DzJfZvAD8G/HXgA8B/C/zNVss+b4csIgK4CbhX0pCkFcDdwBbgQeDKJmEPAFsjYi3wGHBrKb+hzHMtcDnwKb11zcemiFhXGv4DVOOXmZmZmZmZddIlwNcj4hsRMQF8DrhmnumHqYbugqq/NwQMAINAP/DHrSpsecliRIwBu6kScdwJPBQRRyNiL/Bak5A1wN7y/Ango+X5RZQBnyNiHHgd2FBev1Gm6SsL4JT2ZmZmZmZLREOde7TwQ8C3Zrw+Vsq+i6S/ClwA/L8AEbEP+Arwank8HhGHWlXY7j1kdwHXUZ26u6fFtGPA1eX5tcB55fkB4BpJfZIuANbPeA9JjwPjwHeAR9tsl5mZmZmZWdsk3ShpdMbjxplvNwmZ62TRzwKPRsR0me9fA34UeC9VJ+7Dkn6iVXva6pBFxAngEeDhiGiVxmYzsEXSs8BK4EyKnZ1UPcxR4D7gaeAv73CMiL8NvJvq9N6Hm8145sr77EuvttN0MzMzMzOzvxQROyJiw4zHjhlvH2PGSSOqztUrc8zqZ3nrckWAnwH+ICKOR8Rx4HeBS1u1p06WxQZtZKSMiMMRcUVErC8NPFrKp2bcAHcN8E7gxVmxp4AvMsd1mjNX3t8//901mm5mZmZmZt2q0cFHC88APyLpAkkDVJ2uL86eSNIa4FyqcZnPeBn4m+WKwH6qhB5n7ZLFtklaXf7vAW4H7i+vl5ekIEi6HJiKiBcknSPp3aW8D/hJqkGlzczMzMzMOiYipoBfAh6n6kz9VkQ8L+mfSrp6xqTDwOdKEsQzHqU6GXWQ6natAxGxu1Wd6QFYJO0CLgPeJekYcGdEfIYqNeSWMtnngZHyfDXwuKQG8G3gY6V8BfBFSYNAL9VNcfdn22VmZmZmZgtLNw0MHRFfAr40q+yfzHq9rUncNPDxuvW13SGbXWlEDM8x3XZge5Pyl6gyMM4u/2OqHP1mZmZmZmZLSvoM2YI0VT+bfkwn++uTiRHZJ3KjuKfa2NM652dTjcSIBI367YvTuXWhvvpX4cbEdKouGq3y2zSRXe9T9duowYFUVdGbGJd9oD9VlzJ1JbYnAPoT60O5q7qj9zv1q0rVBBH114d6O3foj+zn1VN/3Sv5eWVEI7FPJpYJgN76+5ey631gWe2QmE4er7PrI+P0ifoxifUOEFOJ9ZHYj4Hc8bA/ebxOfF5xOvE9Cag/cYxKfudlvpeV+I2n/ty6YGDp/FSP7BfhIjDv3qXKk5I2zijbJGmPpJ2SxiWNzYpZJ2mfpIOSdktaVcoHJI2U8gOSLmtS3xdnz8/MzMzMzGyxmrdDVm5Suwm4V9JQScpxN7AFeBC4sknYA8DWiFgLPAbcWspvKPNcC1wOfEoz/pwp6e8Cx9/W0piZmZmZ2YLTRVkWO67l+eeIGAN2A7cBdwIPRcTRiNgLvNYkZA2wtzx/AvhoeX4R8OUyz3HgdWADgKRzgH8I/PP0kpiZmZmZmS0w7V6Yehewn2qQ5w0tph0Drga+AFzLWwOrHQCukfS5Ura+/P9V4J8BnwLerNN4MzMzMzNb+LrxzFWntHWHZkScAB4BHo6IVnclbga2SHoWWEnViQPYSTXy9ShwH/A0MCXpYuCvRcRjrdoh6UZJo5JGP/vSq+003czMzMzMrGvVSd3S1mWXEXEYuAJA0oXAVaV8Crj5zHSSngZepBrBer2kl0p7Vkv6/Yi4rMm8dwA7AF77mb+ZSPdnZmZmZmbdZin/sD/rOWclrS7/9wC3UwZ5lrS8JAVB0uXAVES8EBG/ERHviYjzgf8BONKsM2ZmZmZmZrbYpDtkknYB+4A1ko5Jur68NSzpCHAYeAUYKeWrgf2SDlElCPlYvtlmZmZmZrZYNNS5R7dp+5LFiNg26/XwHNNtB7Y3KX+JKgPjfHW8BHyg3TaZmZmZmZktZAt2+O/j36rf9P6h+vlb+t/Ijazeu3yi9USzNCaSV88m0tL0DOWWKxr12xj1B7RPxQD0vp5Y76dy6713ef0TzL3vztWlicQKGUju3r299WP6EjEAJ07Wj1mxLFWVJutvG/QkLyKI+jtlTCc3+r6B+jHTk8m6BmuHaPk7cnVNJdZhpp7EZwXAZOIY2l9//QGokWhjsq7MtqHkT4lI7F/pP2wPraodEr39ubpiun5M5jOG3PY7cSpXV+bzmkwea5bVP86njvFALE98p0zX/4zjZO53Fz1deDrne8RZFs3MzMzMzKzj5u2QqfKkpI0zyjZJ2iNpp6RxSWOzYtZJ2ifpoKTdklaV8gFJI6X8gKTLZsT8vqQ/kvS18lh9lpfTzMzMzMys68zbIYuIAG4C7pU0VLIk3g1sAR4ErmwS9gCwNSLWAo8Bt5byG8o81wKXA58qmRjP+HsRcXF5jL+NZTIzMzMzswWk0cFHt2l5yWJEjAG7qTIj3gk8FBFHI2Iv8FqTkDXA3vL8CeCj5flFwJfLPMeB14ENb6v1ZmZmZmZmC1i795DdBVwHbATuaTHtGHB1eX4tcF55fgC4RlKfpAuA9TPeAxgplyveIWnp3MFoZmZmZrbERQcf3aatDllEnAAeAR6OiFZpYjYDWyQ9C6wEzqS92QkcA0aB+4CngTNpxf5euZTxx8uj6Rhlkm6UNCpp9Df/5NvtNN3MzMzMzKxr1clV29ZllxFxGLgCQNKFwFWlfAq4+cx0kp4GXizvfbv8/x1JvwlcAjzUZN47gB0AL2/4SDd2cM3MzMzMrKZuHLC5U8562vszGRJLwo7bgfvL6+UlKQiSLgemIuKFcgnju0p5P/BTVJc9mpmZmZmZLWrpgaEl7QIuA94l6RhwZ0R8BhiWtKVM9nlgpDxfDTwuqQF8m7cuSxws5f1AL/DvgX+dbZeZmZmZmS0s3Zj9sFPa7pBFxLZZr4fnmG47sL1J+UtUGRhnl5+gSvBRS2Oq/sm9qcQg6T192Ssj68c1coPME9P1z/H2NnLLFVOtp/mumMQe1pjMnbdWT/3lmj6VqorMoUN/9maqJi2v/7cTDfbn6upLnDjvT/5tJ7Edano6VVVqi+/NLZf6B1JxKQOJHax/8Oy3Yw5x+kQqTuqtX1ckto2Jk2hgWf24ycQBezLxJQSQad/0ZK6u3sRxQ7l9Uokvh8isC4ChFbVD1JO8iKiRWB+ZL0qARmIdJo9rmboYSPxoAMjkdutLfuclYmKq/mes5cvhVOIHR2/9Y6EtPOkzZGZmZgtdqjNmZlZXpjO2xCzl5BDz/vlHlSclbZxRtknSHkk7JY1LGpsVs07SPkkHJe2WtKqUD0gaKeUHJF02I2ZA0g5JRyQdlvRRzMzMzMzMFrl5O2QREcBNwL2ShkpSjruBLcCDwJVNwh4AtpY09o8Bt5byG8o81wKXA58qiT8A/jEwHhEXUg0g/R/ezkKZmZmZmdnC0SA69ug2LS9ZjIgxSbuB24AVwEMRcRQ4Kun8JiFrgL3l+RPA48AdVB2tL5d5jkt6HdgAfJVq7LL3l/cawJ/mF8nMzMzMzGxhaPeO1buA64CNwD0tph0Dri7PrwXOK88PANeUNPcXUCXyOE/SO8v7/0zSfkm/LekH214CMzMzMzNb0BodfHSbtjpkJRPiI8DDEdEqTdRmYIukZ4GVwJlUVDuBY8AocB/wNDBFdZbuvcBTEfEhYB/wyWYzlnSjpFFJo7/5Z8faabqZmZmZmVnXqpNlsa1OZUQcBq4AkHQhcFUpnwJuPjOdpKeBF4E/A96kut8M4LeB6+eY9w5gB8BLF1/efReAmpmZmZlZbUv5h31ykI25SVpd/u8BbgfuL6+Xl6QgSLocmIqIF0rikN1Ug0wDfAR44Wy3y8zMzMzMrNukO2SSdlFdXrhG0jFJZ85qDUs6AhwGXgFGSvlqYL+kQ1QJQj42Y3a3AdskPVfKb8m2y8zMzMzMbKFo+5LFiNg26/XwHNNtB7Y3KX+JKgNjs5hvAj/RblsApibq9yUb05nx2HP6purfMjg9mWvf9GT9ddE/UX+UeYCI+m2M5N2Tk6fqj04fjalEPbm/S/SdTCxYon0APafqf149Q7m66Kv/GWug/mcF0DNZv40x2J+rKxPUm1suehL7ciO5owxNtJ5mluhJ/i1ucqB2SPaoGzrrF3A0r2fiJGQGh04c2DSd2ycjEadadyTMDEx8N/Qk95PEJq/e5HINDNUOyW6DqW0+uf9H1P+81D+YqgslvpOnJ3N19dc/1jBZ/1hY1VX/O0XTif3kHcCpk/Xjst9DC1A3JtvolM5845nVlOmMmZnVlumMmZnVlemM2ZIxb4dMlSclbZxRtknSHkk7JY1LGpsVs07SPkkHJe2WtKqUD0gaKeUHJF1WyldK+tqMx59Kuu97sKxmZmZmZtaFGurco9vM2yErCTduAu6VNFSSctwNbAEeBK5sEvYAsDUi1lJlTry1lN9Q5rkWuBz4lKSeiPhORFx85gF8E/j82180MzMzMzOz7tbyYuyIGJO0myrxxgrgoYg4ChyVdH6TkDXA3vL8CeBx4A7gIuDLZZ7jkl4HNgBfPRMo6Ueokn/8x+TymJmZmZnZAtNYwonv272H7C7gOmAjcE+LaceAq8vza4HzyvMDwDWS+iRdAKyf8d4Zw8Aj5cycmZmZmZnZotZWhywiTgCPAA9HxOkWk28Gtkh6FlgJnEl7sxM4BowC9wFPA7NTR/0ssGuuGUu6UdKopNHP/fmxdppuZmZmZmZdLjr46DZ18sc2aCMjZUQcBq4AkHQhcFUpnwJuPjOdpKeBF2e8Xgf0RcSz88x7B7AD4OsX/e1uXJ9mZmZmZmZtSw7oMTdJq8s9Yj3A7cD9pXw5oIg4IelyYCoiXpgROsw8Z8fMzMzMzGxx8jhkCZJ2AfuANZKOSbq+vDUs6QhwGHgFGCnlq4H9kg5RJQj52KxZbsIdMjMzMzMzW0LaPkMWEdtmvR6eY7rtwPYm5S9RZWCca/7va7ctZmZmZma2eCzlLItn/ZLFTpluZE7u1T8Z2pjOjR6XicvWFYkR7jpZVyZnZqYeSK73qeSJ4oEOnlxPVBVTufapJ7E+GrmDaKaN6s0tV0xN1w9qJD/jRF2anp3jqE2ZuGxdymwb2f2kQ/tXdl1MnKwdEssnWk/URGafjMx+DCgS6z37UfX0dq6uzLabXIe5upJVZb4bMu2D3PqIDq7D7HL1D9SPaZnfronexPYO0Ltgf6pbDfNuvao8KWnjjLJNkvZI2ilpXNLYrJh1kvZJOihpt6RVpXxA0kgpPyDpshkxw6X8uTLvd53l5TQzMzMzsy61lLMsztshK+OB3QTcK2lI0grgbmAL8CBwZZOwB4CtEbEWeAy4tZTfUOa5Frgc+JSkHkl9VJc4/q2I+OvAc8Avvd0FMzMzMzMz63Ytz+9GxBiwmyoRx53AQxFxNCL2Aq81CVkD7C3PnwA+Wp5fBHy5zHMceB3YAKg8VkgSsIoqGYiZmZmZmS0BjQ4+uk27F9zeBVwHbATuaTHtGHB1eX4tcF55fgC4RlKfpAuA9cB5ETEJ/AJwkKojdhHwmbaXwMzMzMzMbIFqq0MWESeAR4CHI1reybgZ2CLpWWAlcOYu5p3AMWAUuA94GpiS1E/VIfsg8B6qSxY/0WzGkm6UNCpp9JHXv9VO083MzMzMzLpWndQtbZ3li4jDwBUAki4ErirlU8DNZ6aT9DTwInBxef9oKf8tYOsc894B7AD4o/dv7MZ78szMzMzMrKalnPY+PTD0XCStLv/3ALcD95fXy0tSECRdDkxFxAvAt4GLJP1AmcXlwKGz3S4zMzMzM7Nukx7cQNIu4DLgXZKOAXdGxGeAYUlbymSfB0bK89XA45IaVJ2wjwFExCuS7gL2SpoEvgn8fLZdZmZmZma2sCzd82M1OmQRsW3W6+E5pttOlcZ+dvlLVBkYm8XcTzmTZmZmZmZmtlQs2OG/T57srx3To/p97/7J6doxAL0n6yfVnJ7KXUE6NVV/9PeBwalUXdFQ/ZjEnzxOna7/+QIsn5xoPdEsp0/ldoPBU/XX4XSifQD9y+pvhz39ucSuPX314zSQ2556z5msX9dQ/e0doGeyfhvVl7yqu5HY6CfrrwsALTtZP6iRTPo7OFQ7JCJZlxLrPlGXBnP7ZEwk1vuylam6MuofqSsxsKx+Xb3JnxKZTaMnt/8rsVzZujr6V/7EvqypgVRV0VN/n9R07ruB3vq/AWI6dwxl4lT9mMx3eaYegOz+tQB1Yzr6Tjnr95CZmZmZmZlZe+btkKnypKSNM8o2SdojaaekcUljs2LWSdon6aCk3ZJWlfIBSSOl/ICky2bE/M+SnpP0vKRW45yZmZmZmdkiEh38123m7ZBFRAA3AfdKGipZEu8GtgAPAlc2CXsA2BoRa4HHgFtL+Q1lnmupMil+SlKPpO8H/k/gIxHx3wA/KOkjb3vJzMzMzMzMulzLC1MjYkzSbuA2YAXwUBkz7Kik85uErAH2ludPAI8DdwAXAV8u8xyX9Dqwgepy6yMR8Scl5t8DHz0zrZmZmZmZLW6+h6y1u4DrgI1Aq0sKx4Cry/NrgfPK8wPANZL6JF0ArC/vfR14v6TzJfUBPz0jxszMzMzMbNFqq0MWESeAR4CHI+J0i8k3A1skPQusBM6kotkJHANGgfuAp6kGh/5z4BfK/P8j8BLQNC2PpBsljUoa/Z3j32yn6WZmZmZm1uUaRMce3aZOLs0GbZxNjIjDwBUAki4ErirlU8DNZ6aT9DTwYnlvN7C7lN8INM3xHRE7gB0AX/urV3ff2jQzMzMzM6vhrKe9l7S6/N8D3E4Z8FnS8pIUBEmXU50de2FWzLnAL1IlBjEzMzMzsyUgOvjoNukOmaRdwD5gjaRjkq4vbw1LOgIcBl4BRkr5amC/pENUCUI+NmN22yW9ADwF/MuIOJJtl5mZmZmZ2ULR9iWLEbFt1uvhOabbDmxvUv4SVQbGZjFN5zWfPzu1rG5IKntLz8lEEKBE/3si2T+eRrVjBt7M5bKJRF2Zmv6ipzcRBSv/vOnVrvN6M1nX8pP163rH601vj2xpoLd+XX29uc+4t6d+XLauwWX110dvX66uZee+UTtGyT9ZDayuX1fvuUOpurSiflzP6nNTdTEwUDtE73xnrq4OCYCBwc7U1VvnLoEZTp+oHzO0KlfX0Ir6MQO5bTezg2mg/nc/AP31P2MlYgCUXR8ZjfrHw5jOfQ9p2cqO1cVkq3QF302R/F0zkfihNzXReppmMnVljxsLUDfe29UpZ/2SRTMzswWjQ50xM1viMp0xWzLm7ZCp8qSkjTPKNkn6sqSvSDok6XlJvzzj/e+T9ISkF8v/586Y169L+vr/3969x9tV1ffe/3z3JeQGEhRiDChWY4W2CDRiTvGCUiyhl9g+h6g9hcjDaQ6VVqjYQw72QWgfeuJpm9PS9tjG4qtAsYKVSrCxkEawBoESQ4SErSbeIBKJBbnkurP3/p0/5tgw2azbHNlZe62d7zuv+craY87f+o0511xzrbHmnGNIekjSqaWYJWn5LZKWHIwVNTMzMzMz6zQNG2QREcBFwApJU1OnHNcAVwKXRcQJwAKKbu5PTGHLgLURMY9icOdlqXwhMC9NS4FPQNGAAz4GvAU4DfjYaCPOzMzMzMwmv5E2Tp2m6SWLEbGJokv6yykaTjdExD0RsSHNfw4YAOamkEXA9enx9RQDPY+W3xCF+4AjJc0BfgFYExFPpTHJ1gBnj8vamZmZmZmZdbBW7xS8GthAMcjz/PIMSccDpwD3p6LZEbEdICK2j3ZpT9Fge6wUui2V1Ss3MzMzM7NDQLhTj8YiYhdwM3BjRDzf9Y2kmcDngEsjolm3YrW654sG5S99AmmppPWS1n9hz7dbqbqZmZmZmVnHqtLL4osuu5TUT9EYuykibi0t90S6FJH0/45Uvg04rrTcsRTjlNUrf4mIWBkR8yNi/i9Ne12FqpuZmZmZWafyPWQVSRJwHTAQESvGzF4FjPaUuAS4rVR+fuptcQHwTLq08Q7g3ZJmpc483p3KzMzMzMzMJrXc0eZOB84DHpa0MZVdERGrgeXALZIuBB4Fzk3zVwPnAFuB3cAFABHxlKQ/BB5Iy/1BRDyVWS8zMzMzM+syh/I9ZC03yCLiqtLjddS+94uIeBI4s0Z5ABfXifkU8KlW6wKwU71VFi/yVI5or/2quUmb2pcRd1i0b0zw3FPDP+6rvl4jw9X3C4CdPdVz7VP13zN+TB+zhocqxx02VH29+ofztnxvxjulrycv12DGevX1tu9ig56+vFw9fdVfY9iblyvjddbU/qxcHDalekxf7u9+7bITTZ1aPSxnvb7/LXjlsdXjequ/XrFvL0w/vHKceqp/NoQyP08yctGTd4xXfxsHAM/dHjkyUinzt/iIjGNN7rbIiIuMz1YAjWQc5zO+g9I/jdifMTh0b6cfQ208+FW2jpTTGMuV0xjLldMYM7ODJ6sxliunMZYrozFmZgdPVmPsENOJ93a1S8OfINL9XuskLSyVLZa0VtJdkgYkbZZ0SWn+UZLWSNqS/p9Veq5rJW2V9JCkU0sx/yLpaUlfOBgraWZmZmZm1okaNsjSZYYXASskTZU0A7gGuBK4LCJOABYAF0s6MYUtA9ZGxDxgbfobYCEwL01LgU+UUv0xxT1pZmZmZmZ2iBmJaNvUaZpepBsRm4DbgcuBjwE3RMQ9EbEhzX8OGOCFwZwXAdenx9cD7ymV3xCF+4AjR7vHj4i1wHPjs0pmZmZmZmbdodV7yK4GNgCDwPzyDEnHA6cA96ei2ak7eyJiu6RjUvlc4LFS6LZUtj2n4mZmZmZmNjl03nmr9mmpG5uI2AXcDNwYEftGyyXNpBgc+tKIeLbJ09TqOaHStpe0VNJ6Sevv2L21SqiZmZmZmVnHqdKv6IsGt5bUT9EYuykibi0t98TopYjp/x2pfBtwXGm5Y4HHq1Q2IlZGxPyImP8L019fJdTMzMzMzDrUCNG2qdNkDRAhScB1wEBErBgzexWwJD1eAtxWKj8/9ba4AHhm9NJGMzMzMzOzQ1HuOGSnU/SK+LCkjansiohYDSwHbpF0IfAocG6avxo4B9gK7AYuGH0ySV8B3gjMlLQNuDAi7sism5mZmZmZWVdouUEWEVeVHq+j9j1hRMSTwJk1ygO4uE7M21qth5mZmZmZTS7RQZcSSjob+HOgF/jbiFg+Zv7/Bt6Z/pwOHBMRR0o6mWJoryOAYeCaiLi5Wb7cM2QTbkdf9asth2s2IRvLHTU8J25/Rv1y4w6LvGQ565UT81RP3pZ/tqf6frFTeQeA6RnbcG9P3luuP6OK/ZnjbPTk5BrJyzV1b/XX+bDMA/bewerbvjdnYwBD+/ZUjpm+ZzArV/+ze6sHZb5eOqz6NuzJzMVI7tG3omlT8+J6qr//NXVaVqoYGsoIGs7KxUj1uMyPLlD143Xu1zVNyXidM+oHQE9vXlyOnLdJb9565bzOEZnv44xtqN68z9fI+N7AcPX3ZG79rP0k9QJ/BZxF0QfGA5JWRcQjo8tExO+Wlv8dih7nobgK8PyI2CLpVcDXJN0REU83ytlwL0z3e62TtLBUtljSWkl3SRqQtFnSJaX5R0laI2lL+n9W6bmulbRV0kOSTk3lJ0u6Nz3PQ5Le29rmMjMzMzOzyWCkjVMTpwFbI+I7ETEIfIZiPOV63g/8A0BEfCsitqTHj1N0bnh0s4QNG2TpMsOLgBWSpkqaAVwDXAlcFhEnAAuAiyWdmMKWAWsjYh6wNv0NsBCYl6alFKfz4IWW5E8BZwN/JunIZhU3MzMzMzMbZ/XGTn4JSa8BXgt8qca804ApwLebJWx6/jQiNkm6HbgcmAHcEBH3lOY/J2kgVfQRihbkGWn29cDdKXZRig3gPklHSpoTEd8qPdfjkkZbkg1P7ZmZmZmZ2eTQzu7oJS2lOEE0amVErBydXSOkXuXeB/xjxIuvE09Df90ILIkWrt1t9YLWq4ENwCAwf0zC4ymum7w/Fc0e7c4+IrZLOiaV12ttPt/1fZWWpJmZmZmZWVWp8bWyzuwqYye/jzGdFko6Avhn4Pcj4r5W6tPSnYwRsQu4GbgxIvaVEs6kGBz60oh4tsnTNGxtllqSF9RrSUpaKmm9pPX/tmtLK1U3MzMzM7MOF23818QDwDxJr5U0haLRtWrsQpJ+EpgF3FsqmwL8E8VVgZ9tdd2rdC3zovvgJPVTNMZuiohbS8s9kRpXo42sHam8bmuz1ZZkRKyMiPkRMf/tM+ZVqLqZmZmZmVljETEE/DZwBzAA3BIRmyX9gaRfKS36fuAz6XasUYuBtwMfkLQxTSc3y5nVB6ckAdcBAxGxYszsVcASigGilwC3lcp/W9JngLcAz6RLGrNakmZmZmZmNjm0abCTlkTEamD1mLIrx/x9VY24vwf+vmq+zEE2OB04D3hXqfV3Tpq3HDhL0haK/vtHB1JbDXwH2Ap8EvhgKs9qSZqZmZmZmXW7ls+QlVuBEbGOOmMERsSTwJk1yoMxN72l8qyWpJlk2BYWAAAgAElEQVSZmZmZTQ4vvvLv0NK1w4bnnNobyXide3KGps+Ue7qy0+WsV+5m78l5L7fxNR7KjOvNiBnJXLGeNnY7m6OdlzTkfjZEVN/2MZL3ekXOgS0nJjduJPMVy61jRZFZP+Uc2XK3RfMek7sz12T90GunnoxPh5Hh5svUzFX9BVPubpgTlLMtAKn6euXVzzu81de1DTIzMzMzM5sc2jkOWadp2FxXYZ2khaWyxZLWSrpL0oCkzZIuKc0/StIaSVvS/7NKz3WtpK2SHpJ0aip/jaSvpXvHNku66GCtrJmZmZmZWSdp2CBL931dBKyQNFXSDOAa4Ergsog4AVgAXCzpxBS2DFgbEfOAtelvgIXAvDQtBT6RyrcDPxcRJ1P0vrhM0qvGawXNzMzMzKyzjbRx6jRNL1mMiE2SbgcuB2ZQdE9/T2n+c5IGgLnAI8Ai4Iw0+3rg7hS7KMUGcJ+kIyXNiYjtpXSH4avKzczMzMzsENHqPWRXAxuAQWB+eYak44FTgPtT0ezRRlYaZ+yYVD4XeKwUui2VbZd0HMXA0K8Hfi8iHq+8JmZmZmZmZl2mpbNREbELuBm4MSL2jZZLmgl8Drg0Ip5t8jS1uhCL9PyPRcRJFA2yJZJm13wCaamk9ZLWf3nXllaqbmZmZmZmHS7a+K/TVLk88EWXXUrqp2iM3RQRt5aWe0LSnLTMHGBHKt8GHFda7ljgRWfC0pmxzcDbalUgIlZGxPyImP+OGfMqVN3MzMzMzKzzZN2vJUnAdcBARKwYM3sVsCQ9XgLcVio/P/W2uAB4Jl3SeKykael5ZwGnA9/MqZeZmZmZmXWfEaJtU6fJHYfsdOA84GFJG1PZFRGxGlgO3CLpQuBR4Nw0fzVwDrAV2A1ckMpPAP5UUlBc1vgnEfFwZr3MzMzMzMy6RssNsoi4qvR4HbXvCSMingTOrFEewMU1ytcAJ7Vaj1G9GY3bkZo1bhKT2YjuycjVk5srJ6aNuXK2e66cVH1t3BbtlPv7T9b7JGvLQ0T1uNz1ysmF8rIND1fPNZIRAxBDGTGZBzblxA0PZ+XKOvjmHHh374GpU6vH9bXxwDaS0UlzZHbsnJErIu81Vk4Vc7ZFblzuQT6nij29mcnamGu4jZ2FK2PjZ+7zkfteqaq3H/bva77cWD2d/m1j/BRNhUPTofMqm5mZjZXTGDMzqyqnMWaHjIYNsnS/1zpJC0tliyWtlXSXpAFJmyVdUpp/lKQ1krak/2eVnutaSVslPSTp1DG5jpD0A0l/Od4raWZmZmZmnetQHhi6YYMsXWZ4EbBC0lRJM4BrgCuByyLiBGABcLGkE1PYMmBtRMwD1qa/ARYC89K0FPjEmHR/CHz5wFfJzMzMzMysOzS9hywiNkm6HbgcmAHcEBH3lOY/J2mAYpDnR4BFwBlp9vXA3Sl2UYoN4D5JR0qak3pa/FlgNvAvjBl42szMzMzMJrdOHB+sXVrt1ONqYAMwyJgGk6TjgVOA+1PR7IjYDpAaW8ek8rnAY6XQbcBcSU8Af0rRa+NLOgMxMzMzMzObrFrq1CMidgE3AzdGxPN3JUqaSTE49KUR8WyTp6nVJVUAHwRWR8RjNea/+AmkpZLWS1p/964trVTdzMzMzMw6nMcha82L7oOT1E/RGLspIm4tLfdE6VLEOcCOVL4NOK603LHA48B/At4m6YPATGCKpJ0RsYwxImIlsBLg7+b+RudtTTMzMzMzswqyur2XJOA6YCAiVoyZvQpYkh4vAW4rlZ+feltcADwTEdsj4r9ExKsj4njgIxT3mb2kMWZmZmZmZpNTRLRt6jRVzpCVnU5xz9fDkjamsisiYjWwHLhF0oXAo8C5af5q4BxgK7AbuCC71mZmZmZmZpOAOrGV2Iq/PK76JYv7a93F1oLhjJiRzFz7M65r3ZeZ67CMl76dYzf8h6pv+ZfRm5VrZ8arPC1zXPVZI9Xj+rMyQX/Ga9ybeUiYkhE3NfP4k7NeR43sz8rVp+rJjpy6NyvXzMOrDxw69fC89Zo2p/p69UzPe3/1Hj0jK65dNCNjcGjlvf97XnNs9aCXHZmVi8Mz4qbNzEqlGRm5Mrehph1ePebwV+Tl6s343TonJldP3nsyy0jOtyEgqn9ziOGhvFwjGd9ShvOOoTE0mJErc70yHPaGt2Z+O5w47zz2rLY1Su7atqajtk/e0fAQknn4yZLTGMuV0xhrp5zGWK6cxliunMZYrpxGS66cxliudq5XTmMsV05jLFdOYyzXpGyMZcpqjOXKaYxlymqM5ebKaIxl52pnw2qyymiMZctpjGXq9MaYdZ+G3w7T/V7rJC0slS2WtFbSXZIGJG2WdElp/lGS1kjakv6fVXquayVtlfSQpFNLMcOSNqZp1cFYUTMzMzMz60zRxn+dpmGDLA3ifBGwQtJUSTOAa4Argcsi4gRgAXCxpBNT2DJgbUTMA9amvwEWAvPStBT4RCnVnog4OU2/Mk7rZmZmZmZm1tGano+PiE2SbgcuB2ZQ9IJ4T2n+c5IGKAZ+fgRYBJyRZl8P3J1iF6XYAO6TdORo9/jjuD5mZmZmZmZdo9ULpK8GNgCDwPzyDEnHA6cA96ei2aONrDQW2TGpfC5QHvx5WyrbDkyVtB4YApZHxOcrr4mZmZmZmXWlkS7taHA8tNQgi4hdkm4GdkbE83eeS5pJMTj0pRHxbJOnqdWbyeiWf3VEPC7pJ4AvSXo4Ir79kieQllJc7sj7jjyN02fOa6X6ZmZmZmZmHalKl28jlHo9l9RP0Ri7KSJuLS33hKQ5aZk5wI5Uvg04rrTcscDjABEx+v93KC5xPKVWBSJiZUTMj4j5boyZmZmZmU0O0cap02T1wS1JwHXAQESsGDN7FbAkPV4C3FYqPz/1trgAeCZd0jhL0mHpeV9BMej0Izn1MjMzMzMz6ya5g2ycDpwHPCxpYyq7IiJWA8uBWyRdCDwKnJvmrwbOAbYCu4ELUvkJwN9IGqFoIC6PCDfIzMzMzMwOESMdee6qPVpukEXEVaXH66h9TxgR8SRwZo3yAC6uUf5V4GdarYeZmZmZmdlk0bXD0D/aO1w5Zn9Gyzt33PecQef2ZGYbzIiboqyrVbMMZ2yLH43szcp1pKZUjtnFUFauqfRWjnmmp3r9AKbU/v2jof6MGIC+jLjezFzTo3rcYZk/oO0dqr7tezN/rdu3p/r7a3Co+v4EMHPfYOUY9ezJytU3rfp7ZUrf7qxcjLTnl9Ke3dW3HwB91V9jHfVcVir1ZByvc2IARjI+h5S37+bUMTLXS9MOr54r8j6Ts46Gua/XcM7rlZmrJ+d1zts3lFPHzNeL3oyvwr391WOG9jVf5hB3KJ8ha7jHp/u91klaWCpbLGmtpLskDUjaLOmS0vyjJK2RtCX9P6v0XNdK2irpIUmnlmJeLenO9HyPpK70zczMzMzMJrWGDbJ0meFFwApJUyXNAK4BrgQui4gTgAXAxZJOTGHLgLURMQ9Ym/4GWAjMS9NS4BOlVDcAf5ye7zRe6JnRzMzMzMwmuYho29Rpmp6njYhNkm4HLgdmADdExD2l+c9JGqAY5PkRYBFwRpp9PUU39pen8htSI+8+SUembvFnAX0RsSY9385xWjczMzMzM7OO1uqFs1cDG4BBYH55Rrq88BTg/lQ0OyK2A6Ru7Y9J5XOBx0qh21LZscDTkm4FXgv8K7AsIqrfJGZmZmZmZl3H95A1ERG7gJuBGyPi+bsSJc2kGBz60oh4tsnT1LrPNSgahW8DPgK8GfgJ4AM1n0BaKmm9pPVff25rK1U3MzMzMzPrWFW6sRmh1OmgpH6KxthNEXFrabkn0qWIpP9H7wfbBhxXWu5Y4PFU/mBEfCcihoDPA6dSQ0SsjIj5ETH/TYe/vkLVzczMzMysU0Ub/3WarL5PJQm4DhiIiBVjZq8ClqTHS4DbSuXnp94WFwDPpEsbHwBmSTo6LfcuinvRzMzMzMzMJrXccchOB84DHpa0MZVdERGrgeXALZIuBB4Fzk3zVwPnAFuB3cAFABExLOkjwNrU0Psa8MnMepmZmZmZWZfpxN4P26XlBllEXFV6vI46Yx9GxJPAmTXKA7i4Tswa4KRW62JmZmZmZjYZ5J4hm3BDGdd/7s+Iye3xZTgjbpC8Ueb35nRIWbM5fXCMZPziMZjZyeagqm/D3Fw523AveblGMq4ujrwrkrPeJ32ZO1RfznopL9e+jM3RE3m59qp6sn1DeYfjKYPV96mhnI0BqKf6vhF7M99feYfDyqIvs37DGRXcN5iVKvbta77QGNq/PysXU4Yqh8RwZq6ovh9quHr9ACIjThnvY4CI6vtGxkdXvrzVAnrHsxbjL/P1atvBJrt+dijo2gaZmZmZmZlNDu72vo7UAcc6SQtLZYslrZV0l6QBSZslXVKaf5SkNZK2pP9nlZ7rWklbJT0k6dRU/k5JG0vTXknvOVgrbGZmZmZm1ikaniGLiJB0EfBZSXdRnK++hmKcsD0RsUHS4cDXJK2JiEeAZcDaiFguaVn6+3JgITAvTW8BPgG8JSLuAk6GojFH0enHneO/qmZmZmZm1oncqUcDEbFJ0u0UjaoZwA0RcU9p/nOSBoC5FN3VLwLOSLOvB+5OsYtSbAD3STpS0pzU9f2o/wx8MSJ2H/CamZmZmZmZdbhW7yG7GtgADALzyzMkHQ+cAtyfimaPNrIiYrukY1L5XOCxUui2VFZukL0PGDuumZmZmZmZTWK+h6yJiNgF3AzcGBHPd/ckaSbwOeDSiHi2ydPU6q7s+S0vaQ7wM8AddZ9AWippvaT1Dz337VaqbmZmZmZm1rGq9ME5QqlvUEn9FI2xmyLi1tJyT6TG1Wgja0cq3wYcV1ruWODx0t+LgX+KiLr950bEyoiYHxHzTzr8dRWqbmZmZmZmnSra+K/TZA2KIEnAdcBARIy9xHAVsCQ9XgLcVio/P/W2uAB4Zsz9Y+8H/iGnPmZmZmZmZt0odxyy04HzgIclbUxlV0TEamA5cIukC4FHgXPT/NXAORS9KO4GLhh9snQf2nHAlzPrY2ZmZmZmXWrEvSw2FxFXlR6vo/Y9YUTEk8CZNcoDuLhOzPcoOvho2Y9isMriAAxmjMY+nDmCe07UnpG6V2s2NBjDlWOmqDcrV468LQhPDu2sHDPYO1Q5ZufIvuYL1TBN/dWDMn8COSzjZHZf3glwelTzrd1Qb+3DQVO7M/bDKZm51Ft94+dtQeil+nr1VN91ARjZWz2m/+nqxwyAKXuqx/VNy6ggMJK5PSr7j0H6jqge1jO9+msce/K2hfozDhzTpmXlIuP9T/+UzFwZ77DejOMuwP6M43xO/QB6MvaNvEx5dYy89VLu9siRsQ2zRfVvKcr5MO/tI4YzDmw9bdzuNmFyz5CZHVQ5jTEzs6pyGmNmZlVlNcYOMZ14b1e7NGx2p/u91klaWCpbLGmtpLskDUjaLOmS0vyjJK2RtCX9P6v0XNdK2irpIUmnlmL+V3qegbRM3k/gZmZmZmZmXaRhgyxdZngRsELSVEkzgGuAK4HLIuIEYAFwsaQTU9gyYG1EzAPWpr8BFgLz0rQU+ASApJ+juCftJOCngTcD7xi3NTQzMzMzs442EtG2qdM0vWQxIjZJuh24HJgB3BAR95TmPydpgOIesEeARcAZafb1wN0pdlGKDeA+SUembvEDmApMobgvrR94YlzWzszMzMzMrIO1eg/Z1cAGYBCYX56Rekg8Bbg/Fc0e7c4+IrZLOiaVzwUeK4VuA+ZGxL2S7gK2UzTI/jIiBqqvipmZmZmZdSPfQ9ZEROwCbgZujIjnuyqSNJNicOhLI+LZJk9T676wkPR64ASKgaLnAu+S9PaaTyAtlbRe0votO7/bStXNzMzMzMw6VpW+NEco9WAuqZ+iMXZTRNxaWu6JdCki6f8dqXwbxVhjo44FHgd+FbgvInZGxE7gixT3pb1ERKyMiPkRMX/ezNdWqLqZmZmZmVnnyRrcIPWCeB0wEBErxsxeBSxJj5cAt5XKz0+9LS4AnkmXNj4KvENSX2rkvQPwJYtmZmZmZoeIQ7lTj9zR5k4HzqO4vHBjms5J85YDZ0naApyV/gZYDXwH2Ap8EvhgKv9H4NvAw8DXga9HxO2Z9TIzMzMzM+saLQ8MHRFXlR6vo/Y9YUTEk8CZNcoDuLhG+TDw31qth5mZmZmZTS6HcqceLTfIOs0uqo94vj9Gmi80xjDVYwAi43TorpHBrFz7M+qYu145hnO2xfC+5gvV0KPqJ313Z+aKnurrtWdkSlau/eqtHHNYxrYAUFQfl72v9u8zLSSrHjKceWJ/p6rv830Z2wJgd0/1uGk9eevVP1L9ML5vsH2H/qE9edswRqrHZRzi6enLPMYPDVeO6duTd6zhsOrHDe3P+zyhr796TG6ujGNUDO/PS5Wxc8Rw9e8ZAOrNeH/1VD/GA3k7fe7Hf06uzM+hturN2Oepvh+qe79yWxs0fKek+73WSVpYKlssaa2kuyQNSNos6ZLS/KMkrZG0Jf0/q/Rc10raKukhSaeWYj4uaVOa3nswVtTMzMzMzDqT7yGrI11meBGwQtJUSTOAa4Argcsi4gSKHhEvlnRiClsGrI2IecDa9DfAQmBempYCnwCQ9IvAqcDJwFuA35N0xPitopmZmZmZWWdqev40IjZJuh24HJgB3BAR95TmPydpgGIMsUeARcAZafb1wN0pdlGKDeA+SUembvFPBL4cEUPAkKSvA2cDt4zPKpqZmZmZWSfzPWTNXQ1sAAaB+eUZko4HTgHuT0WzU3f2RMR2Scek8rnAY6XQbans68DHJK0ApgPvpGjYmZmZmZmZTWotNcgiYpekm4GdEfH8XcmSZlIMDn1pRDzb5Glq3aEdEXGnpDcDXwV+BNwLtXvskLSU4nJHTj7qJF478zWtVN/MzMzMzDpY5HQcM0lU6f5mhFLfPGkQ588BN0XEraXlnkiXIpL+35HKtwHHlZY7FngcICKuiYiTI+IsiobblloViIiVETE/Iua7MWZmZmZmZt0uqz9SSQKuAwYiYsWY2auAJenxEuC2Uvn5qbfFBcAz6ZLGXkkvT897EnAScGdOvczMzMzMrPuMEG2bOk3uoAinA+cBD0vamMquiIjVwHLgFkkXAo8C56b5q4FzgK3AbuCCVN4PfKVo4/Es8Bupgw8zMzMzM7NJreUGWURcVXq8jjpDukbEk8CZNcoDuLhG+V6KnhbNzMzMzOwQFB04Pli7dO2w4T8e3lM5ZjCGK8fkntYczrgxce/IYFau/SPV12tKT95Ln9Mlac62eHpwZ+UYgKGM13jn/ur7EsD0vqmVY/ozt3t/xtXFua9xT+3fWhrqzYgB2Kvqr9c0ZW5DVa+jMmIApmds+/7evFzQWzli2r4pWZn2D1XPNX1n3nFtZLj69oiRjJjMCzKk6sfCKc/szspFT8Z6TZ+WlSprL+zvz8pFf8Z+OLg3K1UMVj/OaySvg4HoqX68lrLuIMnqBEF9ee9/enOOvZmdNOR07tCbuR/myMmV8XkH5G0L6zoNjwDpfq91khaWyhZLWivpLkkDkjZLuqQ0/yhJayRtSf/PSuVvlHSvpH2SPjImz9mSvilpq6RlmJmZmZnZIeNQvoesYYMsXWZ4EbBC0lRJM4BrgCuByyLiBGABcLGk0csOlwFrI2IesDb9DfAU8CHgT8o5JPUCfwUspLh08f2l5zIzMzMzM5u0mp4jj4hNwO3A5cDHgBsi4p6I2JDmPwcMUAzyDLAIuD49vh54T1puR0Q8AOwfk+I0YGtEfCciBoHPpOcwMzMzMzOb1Fq9IPhqYAMwCMwvz5B0PHAKcH8qmh0R2wFSt/bHNHnuucBjpb+3AW9psV5mZmZmZtblDuVOPVq6izQidgE3AzdGxL7RckkzKQaHvjQins2sQ617iGu+IpKWSlovaf3ju7ZlpjMzMzMzM+sMVbr1GaHUXY6kforG2E0RcWtpuSckzUnLzAF2NHnebcBxpb+PBR6vtWBErIyI+REx/1Uzjq1QdTMzMzMz61QjEW2bOk1WP6sq+oK+DhiIiBVjZq8ClqTHS4DbmjzdA8A8Sa+VNAV4X3oOMzMzMzOzSS1v4As4HTgPeJekjWk6J81bDpwlaQtwVvobSa+UtA34MPD7krZJOiKKQWB+G7iDonOQWyJi8wGsk5mZmZmZdZFo479mWhmSKw0F9kgaAuzTpfJXS7ozDQ/2SOpvo6GWR/mLiKtKj9dRZ/zIiHgSOLNG+Q8pLkesFbMaWN1qXczMzMzMzMZbaUiusyhurXpA0qqIeKS0zDzgfwCnR8SPx3RieANwTUSsSf1tNB3dO2fY9Y6wc2Rf84XGGBwZqhzTSiu6lpzrU3cPV18ngKGM9Rrsad+I9iNRfXT63UN526JHNX8naGjX/r1ZuXLs65+eFTes3uoxI3n7bk/t31oa6s3Y7rmUmWs31fdDZWwLgL1Uf732ZK5Xf0/1uD1DeYf+GK6ea//e6tsCYHgk9wKOanr3Nf2crKmnt/r7K/ZX3wcBtL/6MZ7hvFwxlPE+ycxFZBzn9w/m5RrKiMs47gIwnPNdo32yc/W273uDsr6ejh1VqUXtWq+ezP0p7xDVlTqol8Xnh+QCkDQ6JNcjpWV+E/iriPgxFMN7pWVPBPoiYk0q39lKwvZ84pmZmZmZmXW+WkNyzR2zzBuAN0i6R9J9ks4ulT8t6VZJD0r643TGraGGDTIV1klaWCpbLGmtpLvStZGbJV1Smn+UpDWStqT/Z6XyN0q6V9I+SR8Zk+dTknZI2tSswmZmZmZmNrmMEG2bykNppWlpqSqtDMnVB8wDzgDeD/ytpCNT+duAjwBvBn4C+ECzdW/YIIvi3OFFwApJUyXNAK4BrgQui4gTgAXAxekUHcAyYG1EzAPWpr8BngI+BPxJjVR/B5xdo9zMzMzMzGzclIfSStPK0uxWhuTaBtwWEfsj4rvANykaaNuAByPiO6njws8DpzarT9NLFiNiE3A7cDnwMeCGiLgnIjak+c9R9I44eipvEXB9enw98J603I6IeIAaF/lGxL9RNNjMzMzMzOwQExFtm5poZUiuzwPvBJD0CopLFb+TYmdJOjot9y5efO9ZTa3eNXk1sAEYBOaXZ6SuHE8B7k9FsyNiO0BEbB/T64iZmZmZmVlHioghSaNDcvUCn4qIzZL+AFgfEavSvHdLegQYBn4v9TRPujVrbRq3+WvAJ5vlbKlBFhG7JN0M7Ix4oVuk1JXj54BLI+LZKiubI13fuRTgNS97PUdPn3OwU5qZmZmZ2UGW00P5wVJrSK6IuLL0OCjGVv5wjdg1wElV8lXpZXGEUuebkvopGmM3RcStpeWekDQnLTMH2FGlQo2Ur/d0Y8zMzMzMzLpdVrf36RTcdcBARKwYM3sVsCQ9XgLcll89MzMzMzOb7DroHrK2yx2H7HTgPOBdkjam6Zw0bzlwlqQtFCNcLweQ9EpJ2yhO7f2+pG2Sjkjz/gG4F/jJVH7hAayTmZmZmZlZV2h5KPSIuKr0eB21++gn3dB2Zo3yH1J0G1kr5v2t1mPUzqG9VUMYHBmqHBOZY9oPx3DlmN1D+5ovVCvXSPVh3Kf0Vt8WuYZGqm+L/p5ent676yDU5qV278/b7jl29lffb6HYHlX1NR+HsKae2m/thnqV99vOcE/199dw5nvysJ7c35+q26mWD63Pm5LxGueamfl6DWVs+j17puTlGm7P67V79xSmTx+sHCdV3xiH78w71qi/elzsycyV8z45bE9WLnoz9vnBvGMog9XrmPsbunqrv//JPT5lfP5ny/mOknmsydn2av0r7ZjA6t9RaOPxuq25bMJk7r1mB1e7GmNmdmjLaYyZmdn4G8n+GaT7NfzpQoV1khaWyhZLWivpLkkDkjZLuqQ0/yhJayRtSf/PSuVvlHSvpH2pO8jR5Y+r91xmZmZmZmaTWcMzZBERki4CPivpLoq++K8BPgDsiYgNkg4HviZpTUQ8AiwD1kbEcknL0t+XUwz8/CHSQNElQ8BldZ7LzMzMzMwmuU7sbKNdml7cGxGbgNspGlUfA26IiHsiYkOa/xwwAMxNIYuA69Pj60kNsIjYEREPAPvHPP/2Bs9lZmZmZmY2abV6D9nVwAZgEJhfniHpeOAU4P5UNDsitkPR2JJ0TKuVqfFcZmZmZmY2yXXSwNDt1lKDLCJ2SboZ2BkRz3ezI2kmxeDQl0bEswdSkVaeS9JSYCnAK2e+hiOntdzWMzMzMzMz6zhV+iMdSRMAkvopGlA3RcStpeWekDQnLTMH2NHsiRs814tExMqImB8R890YMzMzMzObHKKN/zpN1gARkgRcBwxExIoxs1cBS9LjJcBtB/BcZmZmZmZmk1buOGSnA+cBD0vamMquiIjVwHLgFkkXAo8C5wJIeiWwHjgCGJF0KXAicFKD5zIzMzMzs0nO95C1ICKuKj1eB6jOck8CZ9Yo/yFwbI2Qus9lZmZmZmY2meWeIZtwU3qqVz3nmtHc1nqvql8NOtQznJVrWCPNFxojZ/tB3ijqymhv9/X0Vo4BmNJbfb32tTFXf2auPlWP68+IASiuIq6mN+/qZ6Zk1LEv470F0JdRx56MbQHQm7PPZ/4wmPNO7s+8fj4nl5R5DO2pflxrZ57e/oy4vszfHqdkbPmezFy9GceNnBiAjGNoVky7c7VTT97xsOO1c70i472cc9jI/Pw/lHgcsjpUWCdpYalssaS1ku6SNCBps6RLSvOPkrRG0pb0/6xU/kZJ90raJ+kjpeWnSvp3SV9Pz3X1wVhRMzMzMzOzTtOwQRZFU/UiYEVqOM0ArgGuBC6LiBOABcDFkk5MYcuAtRExD1ib/gZ4CvgQ8Cdj0uwD3hURbwJOBs6WtODAV83MzMzMzLrBodzLYtPz8RGxSdLtwOXADOCGiLinNP85SQPAXOARYBFwRpp9PXA3cHlE7CtyVn0AABZASURBVAB2SPrFMc8fwM70Z3+aOm9LmZmZmZmZjbNWL5C+GtgADALzyzMkHQ+cAtyfimZHxHaAiNguqemAYZJ6ga8Brwf+KiLubxJiZmZmZmaThO8hayIidgE3AzdGxL7RckkzKQZ0vjQins2tREQMR8TJFL0wnibpp2stJ2mppPWS1j+5+4ncdGZmZmZmZh2hSjc2I5T6lZHUT9EYuykibi0t94SkOWmZOcCOVhNExNMUlzieXWf+yoiYHxHzXz59doWqm5mZmZmZdZ6sfkVV9It9HTAQESvGzF4FLEmPlwC3NXmuoyUdmR5PA34e+EZOvczMzMzMrPtERNumTpM7yMbpwHnAw5I2prIrImI1sBy4RdKFwKPAuQCSXgmsB44ARiRdCpwIzAGuT/eR9QC3RMQXclfIzMzMzMysW7TcIIuIq0qP10HtkU8j4kngzBrlP6S4R2yshyg6BTEzMzMzs0NQ5523aqN2nh5s0ynIpe2Kcy7ncq7OytXp9XMu53KuyZGr0+vnXM7lqbumCa/AuK8QrG9XnHM5l3N1Vq5Or59zOZdzTY5cnV4/53IuT901ZXXqYWZmZmZmZgfODTIzMzMzM7MJMhkbZCvbGOdczuVcnZWr0+vnXM7lXJMjV6fXz7mcy7qI0jWoZmZmZmZm1maT8QyZmZmZmZlZV3CDzMzMzMzMbIK0PDC0mZmZWTeT9DLgbGAuxTi0jwN3RMTTE1qxRNIrASLih5KOBt4GfDMiNld8nj+KiCsORh3bSdLbgSci4puS3gosAAYi4p8nuGpm42pSniGTdGWT+b8g6UJJx48p/3/rLC9JiyWdmx6fKelaSR+UVGkbSvpSk/mvGPP3b6RcSyWpQdyvSjoqPT5a0g2SHpZ0s6Rj68SskHR6lfqnuKMkXSnpv6bt8VFJX5D0x5JmNYh7p6S/lHSbpM9JWi7p9S3k+wVJn5C0KsV+QtLZVeudnsv7xkHaN3L3ixRbed+Q9EZJl6dt8Ofp8QlV6jzm+S5okutMSTPHlDfcDyWdJunN6fGJkj4s6ZyK9bqhyvIp5q0p17sbLPMWSUekx9MkXS3pdkkfV/GltV7chyQdV7E+UySdL+nn09+/nl7viyX1N4l9naSPpNf4TyVd1Kh+KWbcjhnp+eoeN6oeM9K8cTluNDtmpGUqHzdyjhlp2bYdNzKPGecDG4AzgOnADOCdwNfSvEokndVk/hGSXlej/KQ6y/834F7gPkm/BXwB+CXgVkkXNshz7ZjpL4APjv7d4rq8VtKvSXpjk+VeLWlqeixJF0j6C0m/Janmj/ySfmU0pgpJfwYsB26U9IfA/wKmAb8r6Y8bxM2U9J8l/a6k35F0divvK43jZ4oafJ6UclX6TNE4fJ5Y55qUnXpIejQiXl1n3h8Bb6U4KP8y8GcR8Rdp3oaIOLVGzP8BjgGmAM8ChwG3A+dQ/HJzSZ1cD40tAt4AfBMgIl5yUC7XQdLvU/w69mmKg/K2iPjdOrkeiYgT0+ObgfuAzwI/D/yXiHjJB4ekHwHfB44Gbgb+ISIerPX8Y+JWAw8DRwAnpMe3AGcBb4qIRTVilgOzgbXAe4DvAt8CPgj8UUR8tk6uP6PYZjcA21LxscD5wJZ6275B3b1vHKR9I2e/SHGV9w1JlwPvBz7Di/eL9wGfiYjljepapx419w1JHwIuBgaAk4FLIuK2NK/mfpHmfQxYSHElwhrgLcDdFNv9joi4pkbMqrFFFF8YvwQQEb9SJ9e/R8Rp6fFvpvr+E/Bu4PZa20PSZorXZUjSSmA38I/Aman81+rkegbYBXwb+AfgsxHxo1rLlmJuotgO04GngZnArSmXImJJnbgPUbwXv0zxntoI/Bj4VeCDEXF3jZhxPWak56y3b1Q+ZqR5lY8bOceMsfVo9biRc8xIy7bluHEAnyffBN4y9mxYavjdHxFvaFTXGs/X6PNkMfBnwA6gH/hARDyQ5tX7PHmY4jgxjWI7vj6dKZsF3BURJ9fJtY3i2HInxX4B8CfARwAi4voaMZ+PiPekx4tSXe8Gfg74nxHxd3VybQJOi4jdkj4OvA74PPCulOslP0RI2kNxzPgixTHjjogYrvX8Y+I2Az9NsT1+AMxNefuBByPip2vELAZ+D/g6xbHzqxQnH36GYt99uE6ucf1MabJvVP5Myfk8sS4z0SNT504UH2K1pueAoQZxDwN96fGRwGrgf6e/H6wXk/7vB54EpqS/+0bn1YlbBfw98EbgNcDxwGPp8WvqxDxYerwBmFHK3SjXN0uPvzZm3sZGuYB5wP8HbAa+AXwMeEODXBvT/wJ+0GKuh0uP+4B70uNZwKYGub5Vp1wUX668b3TIvpGzX+TuGxRfvvprlE+pt1+k+Q/VmR4G9jXYL2amx8cD6yk+QOvuF6W4XopGyLPAEal8GvBQnZgNab84A3hH+n97evyOBrnK+8YDwNHp8Yx6+wbFZT/P563wej1I8QXn3cB1wI+AfwGWAIfX2+6l1/cJoLe0r9TcFuVtmB5PB+5Oj19db9uTccxI8ysfN8g4ZpT3eSocN8g4ZtTYN1o6bpBxzCjn4iAfNziAzxPgZTXKX1Zv30jbvdZ0O7CrQa6NwJz0+LS0HX6t0b5B6X0IfL3e61gj7nCKBtWnKRotAN+pt3yN/eKrwGvT41eMzT0m7pHyvgH01KtzOVd6bX6TohH9BPDXNDimpbhN6f+pFD/ETEt/95brMSbmIWB6aV3uSI9PAr7aZN+o9JlCxufJ6P5Lxc8UMj5PPHXX1M2XLD4NzIuII8ZMh1N8gamnLyKGAKL4leyXgSMkfZbijVfL6PL7gQciYjD9PQTU/ZUnil+zP0cxTsSbIuJ7wP6I+H5EfL9O2DRJp0j6WYovIrtKuRv9onS3pD+QNC09Hv3l653AM/WqmJ57S0T8YUT8FLCY4uC3ukGunvSL3XHATKVLdSS9nPrbcETpEhjgVRQHFiLix7zwi14teyWdVqP8zcDeOjHeN16sXftGzn4BefvGSFp2rDlpXj2zKc6U/HKN6ck6Mb0RsTPV6XsUjaSFklY0qB8UX+KHI2I38O2IeDY9x54GdZxP8SXno8AzUZwB2hMRX46ILzfI1SNpVtrWinTGKu0jQ3ViNpUuq/m6pPkAkt4A7G+QKyJiJCLujIgLKV6H/0NxX853GtRvCsUXx+kUX4ChODPU8JJFXrjX+bAUT0Q82iAu55gBeceNnGMGZBw3Mo8ZkHfcyDlmQPuOG7mfJ9cAG1RcwnpFmv6aoqFa7wzD24C/Af60xrSzQa7eiNie6vXvFGdrPprOjkSD9Rrdr39xtFDF5X51v69FxHMRcWmq099L+kij5UfDSo/7IuK76bn+g8bH0MckvSs9/h7F6zb6ejWoYvw4Ij4ZEWcCbwIeAZZLeqxB3D9L+grwFeBvgVskfZTiTNu/1YkRsCc93kVxJpqIeIjiLGw9OZ8pOZ8nkPeZkvN5Yt1koluEuRPw/1OcNq817+MN4r5AjV9l0vON1In5IunXjDHlrwT+vYW6zgBWUPyqtq3JsneNmUZ/YXs5sL5BXD9wFfBomkYoftn9NPDqOjF1f3FrUsf3U/zC9QTw/wD/mqYfAEvrxLyX4hKMO1P9fjGVHw18ukGunwXupzh435mmgVT2s4fYvnF3J+8bdfaLNY32i9x9g+LL/9a0/Vem6V9S2dkNcl0HvLXOvHq5vgScPKasj+KSuOEGue7nhV9qy78iv4wxZ6RqxB5LcYnYXwKPtrDtv0fRGPpu+v+VqXwm9c8yvAz4O4pLD++naIR9h+LywDc1yNXol/ppdcp/Nz3394EPUfxK/kmKX30/1uD5LqH4xXklxRmGC0r7xr/ViTmViseMFFf5uEHGMSPNzz5uUOGYkZav/JlCxjGj2b7RIKbycYPMz5O0zCyKy9Auo7ik733ArCav1TvrzKu5D6Z5XwVeN6bs8LTv1zsb/2pqn6WZC/x8i9tTFJfD/X2T5YZ54QzwIC8cM6bQ+Kz1cWk/+jeKs4Q/pjhGPgicWXW/oMHZ3TT/PwEL0uPXpddsMaVj6pjlPw7cAVxB0ZC7IpUfBWxukKfyZwoZnydpXuXPFA7g88RTd0yT8h6yRtIvfkTxq8LYeXMj4gcVnmsGxeUfO1pc/k3Af4qIv241Rym2B5gaxa8jzZZ9GcUvXo1+oUHSzEi/0mTUp5fil/ghFTfynkxxuUndM1DpF82fALZGxR6tVPQ8NZfiw2ZbRPwwp95NcnTrvtELHNYJ+0bOfpHiKu8b6T1xGqX9guJsQ9N7E6pQ0YnBUK19TtLpEXFPnbjDImJfjfJXUHwprnkvw5hlfxE4PTJ7S5M0HZgd6dfvOsscTrHt+yjeW080ec43RMS3MuryKoCIeFzSkRT3PjwaxdmDRnE/RXFv0aaI+EaFfF11zEgxLR83DuSYkeJbOm60esxIy7btuHGAnyezKfWy2Gyfz5Fen90RsWVMeT+wOCJuGu/6jcd6pffmCRFxb5PlTqC4h7GPF469Nc/USDojatzvWaFOldZLRUcXJ1JcQrkmlfVQNHZfckwuxXXsZ8p4fJ5YZ+vaBlm6/GV/pBVIl1KcSnFd8RfHM865JjTXSVFcatCynBjnmpiYA4x7NfBsRDydLnOaT3FfVMPuoevEfSMiNo1njHNNXK4UN5/i1/whintAWmrM5cQ518Tkqhoj6WSKe5deRvFlWxRno5+m6CRmQ4PYg95IGlO/0cb8aP1+K+p0kNIkbsLXKzdmItarznNV/qHhAH6caFsu6zDRAafpciaKHnRmpce/R3F5wO9TXOqwPDPuf45XzEHKdSiu1zDFJQN/CJzY4r5ROca5uq5+yyguz/sG8F/T/9dRdCLw4fGMc66uy/UOipvk/5XicqovAPdQXPJ7XINcleOca2JyHUD9NlL0sji2fAH1O6Q4haKXyQFeuDz/G6ns1Aa5Tm4Qd8p41e8grVfN+rWwXjW3R07MAaxXVq5GEy1cNj4eMe3O5amzpgmvQHbFSz0ppQPzaO87fTS+/rlynHNNaK4HKbq9vYbiS/vXKb6kHT+eMc7VdfXbTNG71Msp7oEo9yrYqJe1ynHO1XW5Hiwt91rgn9Ljs4A7m+yHleKca2JyHUD9GvWyubVOeTsbSZXr1yXrlZurnev14TrTZcBT4xXT7lyeumfq5l4Wn5U0OgbFf1D04gTFF/xG65UT51wTlysiYlNEfDQiXk/Rbe4xwFckfXUcY5yru+o3HMV9O09T9Kj1ZHqiXQ3y5MY5V3fl6o0XxkV7lKJbeKK4l2TuOMc518Tkyq3fFyX9s6T3Svq5NL1X0j9TdOBQy4yIuH9sYUTcR/HDQD05cTn164b1ys3VzvX6I4oOXw4fM82k/neUnJh257Iu0c33kJ0E3EjxazrA6RS9g50ErIiIT49XnHNNaK4HI+KUGuUC3h41ugLPiXGurqvf31H0BjaDYkDjIYoP6HdRjIW1uE6uynHO1XW5PkVx38haYBFF5xAfVtHByYaIeGOdXJXjnGticuXWL8UuTDHljhtWRUTNbvklXUvRu98NFOO+QXHf2vnAdyPit8c5rlL9umG9cnO1eb2+CvxORHytxrzHIuK48Yhpdy7rHl3bIANQ0TPTu3lxTz93RJMel3LinGtickn69XqNtfGMca6JiTmAXH3AuRRfyv4ReAtF99mPAn8Vdc6g5MQ5V9fl6qc4y3oixQ9An4qIYRU9Ih4TdcbsyolzronJlVu/XO1qJLVbO9erndsis34/SXHp349qzJsdNToFyYlpdy7rHl3dIDMzMzNrhYou/P8HxZf1Y1LxDuA2is6lKnWfP95y69fp65Vrsq6XWS1de92ppJmS/kDSZknPSPqRpPskfWC845zLuZyra+q3JDNX3Tjn6tpcmzL3w5bjnGticuXWD7iFolfGd0bEyyPi5cA7Ke5R/GydXC+TtFzSgKQn0zSQyo5sUMecuMr164b1ys01Qev1jYz1ajmm3bmse3TtGTJJtwH/RNGl6WKKeww+Q9GV+g+izkCqOXHO5VzO1f31cy7ncq7JkesA6vfNiPjJKvMk3QF8Cbg+0kC+KgYd/wBwZkScVef5Ksfl1K9L1is3Vyes1xLg5yuuV92YdueyLhId0NVjzsSY7kspRlOH4qzfN8Yzzrmcy7m6v37O5VzONTlyHUD97gT+OzC7VDYbuBz41zox32zwfOM6L6d+XbJeuc83Wderbbk8dc/UtZcsArskvRVA0i8DTwFExAigcY5zLudyru6vn3M5l3NNjly59XsvxZh2X5b0lKSnKAaTPoriTFst35f03yXNHi2QNFvS5bzQi994xeXUrxvWKzfXZF2vduaybjHRLcLciaK79H+nuJZ4HfCGVH408KHxjHMu53Ku7q+fczmXc02OXLn1y5koxn76OPANiobfU8BAKjtqvOPaNbVzvdq5LbphvTp9G3qamKlr7yEzMzMzq0LSGym6Q78vSsMmSDo7IhoNvtwWufXr9PXKNVnXy2ysbr5ksS5JF7Qrzrmcy7kOToxzOZdzOdd4xkj6EEWX6b8DbJa0qDT7jxrEvVHSmZJmjCk/u0ldKsUdQP06er0OIGZSrle7c1mXmOhTdAdjAh5tV5xzOZdzdX/9nMu5nGty5GoUAzwMzEyPjwfWA5ekvx+sE/Mh4JvA54HvAYtK8zY0yFU5Lqd+XbJeubkm63q1LZen7pn66FKSHqo3i6IXnnGLcy7ncq7ur59zOZdzTY5cufUDeiNiJ0BEfE/SGcA/SnpNiq3lN4GfjYidko5Pyx8fEX/eICY3Lqd+3bBeubkm63q1M5d1ia5tkFEcdH+BYtDAMgFfHec453Iu5+r++jmXcznX5MiVW78fSjo5IjYCpC+3vwR8CviZOjHtbCTl1K8b1is312Rdr3bmsi7RzQ2yL1Ccyt44doaku8c5zrmcy7m6v37O5VzONTly5dZvBJhaLoiIIeB8SX9TJ6adjaSc+nXDeuXmmqzr1c5c1iW6uVOPVwE/qDUjIn59nOOcy7mcq/vr51zO5VyTI1du/VYCN0j6qKT+MXH31Imp2SiIiPOBtzfIlROXU7/cuHauV26uybpe7cxl3SI64Ea2nIliUMBvAR8F+g9mnHM5l3N1f/2cy7mca3Lkyq1fip1BMXbT14GPAB8enSZ6W+TUrxvWa7K+Xt2Qy1P3TF09DpmKrj+vBM4GbqT4BQGAiFgxnnHO5VzO1f31cy7ncq7JkesA6jcFWAb8OnDzmLirxzlXznpVrl+XrNdkfb06Ppd1h26+hwxgP7ALOAw4nNLOeRDinMu5nKv76+dczuVckyNX5RgV4zWtAFYBp0bE7hby5Navclxu/Tp9vXJjJut6TUAu6wYTfYoud6L4heARYDkw/WDGOZdzOVf318+5nMu5JkeuA6jfV4CfanX5CdgWlevXJes1WV+vjs/lqXumCa9AdsXbe+ByLudyri6vn3M5l3NNjly59cuZ2rkt2jl1+ms8mder07ehp4mZuvoeMjMzMzMzs27Wzd3em5mZmZmZdTU3yMzMzMzMzCaIG2RmZmZmZmYTxA0yMzMzMzOzCeIGmZmZmZmZ2QRxg8zMzMzMzGyC/F91IG84yAlWbAAAAABJRU5ErkJggg==\n", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "year_df = df.iloc[:,10:]\n", + "fig, ax = plt.subplots(figsize=(16,10))\n", + "sns.heatmap(year_df.corr(), ax=ax)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "_cell_guid": "43e1af94-ba07-4b95-8da3-1d774db940cd", + "_uuid": "70d2b0a7db9b8a5535b3c5b3c2eb927b904bf6d3" + }, + "source": [ + "So, we gather that a given year's production is more similar to its immediate previous and immediate following years." + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "metadata": { + "_cell_guid": "58cde27d-5ddc-4ebe-a8e1-80a8257f44c1", + "_uuid": "6f48b52c09ea6a207644044cace5a88c983bf316" + }, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/anaconda3/lib/python3.7/site-packages/scipy/stats/stats.py:1713: FutureWarning: Using a non-tuple sequence for multidimensional indexing is deprecated; use `arr[tuple(seq)]` instead of `arr[seq]`. In the future this will be interpreted as an array index, `arr[np.array(seq)]`, which will result either in an error or a different result.\n", + " return np.add.reduce(sorted[indexer] * weights, axis=axis) / sumval\n" + ] + }, + { + "data": { + "image/png": "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/tndf2FmW4AnzOw24B3g48H+TwK/A+wGuoBbANy9xcz+GtgS7Hevu7cErz8HPArUAD8PvgC+foJziIiIjNgDG/cQCRu1lZlfn7WVFXTFkzywcQ8rlmi6u5SPvAWI7r4HOHeA9qPAygHaHfj8CY71CPDIAO1bgXOGeg4REZHRaGztYnJNJKutJhJmf2tXgXokkh+qpCIiIjJE86bU0p1IZbV1J1LMnVJboB6J5IcCRBERkSG686pFJFJOVzyJe+Z7IuXcedWiQndNJKcUIIqIiAzRiiUN3Hvt2TTUV3O8O0FDfTX3Xnu25h9K2cl3HkQREZGysmJJgwJCKXsaQRQRERGRLAoQRURERCSLAkQRERERyaIAUURERESyKEAUERERkSwKEEVEREQkiwJEEREREcmiAFFEREREsihAFBEREZEsChBFREREJIsCRBERERHJogBRRERERLIoQBQRERGRLAoQRURERCSLAkQRERERyaIAUURERESyKEAUERERkSwKEEVEREQkiwJEEREREclSUegOiIiIFNqGHU08sHEPja1dzJtSy51XLWLFkoZCd0ukYDSCKCIi49qGHU18ec02mtqjTK6J0NQe5ctrtrFhR1OhuyZSMAoQRURkXHtg4x4iYaO2sgKzzPdI2Hhg455Cd02kYBQgiojIuNbY2kVNJJzVVhMJs7+1q0A9Eik8BYgiIjKuzZtSS3cildXWnUgxd0ptgXokMjh3z/s5FCCKiMi4dudVi0iknK54EvfM90TKufOqRYXumsj7xJNpDh6P5v08ChBFRGRcW7GkgXuvPZuG+mqOdydoqK/m3mvP1ipmKTodsSQHj3WTSKbzfi6luRERkXFvxZIGBYRStNydo51x2roTAITM8n5OBYgiIiIiRSqRStPUHiPWb55svilAFBERESlCXfEkze0xUun8L0rpL+9zEM0sbGa/NrOfBe8XmtkLZrbLzH5oZpVBe1XwfnewfUGfY3wxaH/TzD7Sp31V0LbbzO7p0z7gOURERERKQWtnnHePRwsSHMLYLFK5C9je5/03gG+5+2KgFbgtaL8NaHX304FvBfthZmcBNwBnA6uA/x0EnWHgH4GPAmcBNwb7DnYOERERkaKVSjuHjnfT2hUvaD/yGiCa2Vzgd4GHgvcGXA38ONjlMeD64PV1wXuC7SuD/a8DVrt7zN33AruBi4Ov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\n", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "f, ((ax1, ax2), (ax3, ax4)) = plt.subplots(2, 2, sharex='col', sharey='row', figsize=(10,10))\n", + "ax1.set(xlabel='Y1968', ylabel='Y1961')\n", + "ax2.set(xlabel='Y1968', ylabel='Y1963')\n", + "ax3.set(xlabel='Y1968', ylabel='Y1986')\n", + "ax4.set(xlabel='Y1968', ylabel='Y2013')\n", + "sns.jointplot(x=\"Y1968\", y=\"Y1961\", data=df, kind=\"reg\", ax=ax1)\n", + "sns.jointplot(x=\"Y1968\", y=\"Y1963\", data=df, kind=\"reg\", ax=ax2)\n", + "sns.jointplot(x=\"Y1968\", y=\"Y1986\", data=df, kind=\"reg\", ax=ax3)\n", + "sns.jointplot(x=\"Y1968\", y=\"Y2013\", data=df, kind=\"reg\", ax=ax4)\n", + "plt.close(2)\n", + "plt.close(3)\n", + "plt.close(4)\n", + "plt.close(5)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "_cell_guid": "8a297a06-977f-4ff7-a9ad-c7e8804930a8", + "_uuid": "6b738ce8b15a764fab90fac96f9534f94c14342e" + }, + "source": [ + "# Heatmap of production of food items over years\n", + "\n", + "This will detect the items whose production has drastically increased over the years" + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "metadata": { + "_cell_guid": "588cebd9-e97c-460d-8ed5-e663ac293711", + "_uuid": "16ce47d43a3038874a74d8bbb9a2e26f6ee54437" + }, + "outputs": [ + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "new_item_df = item_df.drop([\"Item_Name\",\"Sum\",\"Production_Rank\"], axis = 1)\n", + "fig, ax = plt.subplots(figsize=(12,24))\n", + "sns.heatmap(new_item_df,ax=ax)\n", + "ax.set_yticklabels(item_df.Item_Name.values[::-1])\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "_cell_guid": "825620f9-7ab5-4fe2-9529-c4f1a300138e", + "_uuid": "5c42595537332ea71089d8c3dc041d3bf7d41b55" + }, + "source": [ + "There is considerable growth in production of Palmkernel oil, Meat/Aquatic animals, ricebran oil, cottonseed, seafood, offals, roots, poultry meat, mutton, bear, cocoa, coffee and soyabean oil.\n", + "There has been exceptional growth in production of onions, cream, sugar crops, treenuts, butter/ghee and to some extent starchy roots." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "_cell_guid": "80428f51-2fd4-468d-9530-9279215b4218", + "_uuid": "4c9bb27cd76099c5348243a99448c509ef0c5ded" + }, + "source": [ + "Now, we look at clustering." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "_cell_guid": "a3f1db3a-1b82-4e42-8e7d-f1a26915693b", + "_uuid": "da167de5a5b92e164fc6993b32ebbfab4ef9a6e3", + "collapsed": true + }, + "source": [ + "# What is clustering?\n", + "Cluster analysis or clustering is the task of grouping a set of objects in such a way that objects in the same group (called a cluster) are more similar (in some sense) to each other than to those in other groups (clusters). It is a main task of exploratory data mining, and a common technique for statistical data analysis, used in many fields, including machine learning, pattern recognition, image analysis, information retrieval, bioinformatics, data compression, and computer graphics." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "_cell_guid": "136315a0-b37d-4d89-bd0d-037727062c34", + "_uuid": "04ab802ec92eaf6a27706f2008933dcf3865855a" + }, + "source": [ + "# Today, we will form clusters to classify countries based on productivity scale" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "_cell_guid": "27ba0b5d-c57e-485d-9588-017e16fe1904", + "_uuid": "659afdada04e8854765b5e7208394915b30f859a" + }, + "source": [ + "For this, we will use k-means clustering algorithm.\n", + "# K-means clustering\n", + "(Source [Wikipedia](https://en.wikipedia.org/wiki/K-means_clustering#Standard_algorithm) )\n", + "![http://gdurl.com/5BbP](http://gdurl.com/5BbP)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "_cell_guid": "7aeb3175-33bd-4f49-903a-57d43380e90e", + "_uuid": "6b0b4881e623ed3c133b68b98e6fb6755e18fd78" + }, + "source": [ + "This is the data we will use." + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "metadata": { + "_cell_guid": "a5b99ea8-975f-4467-9895-bffe1db876eb", + "_uuid": "57aba4000bfc422e848b14ad24b02a570d6c0554" + }, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
Y1961Y1962Y1963Y1964Y1965Y1966Y1967Y1968Y1969Y1970...Y2006Y2007Y2008Y2009Y2010Y2011Y2012Y2013Mean_ProduceRank
Afghanistan9481.09414.09194.010170.010473.010169.011289.011508.011815.010454.0...18317.019248.019381.020661.021030.021100.022706.023007.013003.05660469.0
Albania1706.01749.01767.01889.01884.01995.02046.02169.02230.02395.0...6911.06744.07168.07316.07907.08114.08221.08271.04475.509434104.0
Algeria7488.07235.06861.07255.07509.07536.07986.08839.09003.09355.0...51067.049933.050916.057505.060071.065852.069365.072161.028879.49056638.0
Angola4834.04775.05240.05286.05527.05677.05833.05685.06219.06460.0...28247.029877.032053.036985.038400.040573.038064.048639.013321.05660468.0
Antigua and Barbuda92.094.0105.095.084.073.064.059.068.077.0...110.0122.0115.0114.0115.0118.0113.0119.083.886792172.0
\n", + "

5 rows × 55 columns

\n", + "
" + ], + "text/plain": [ + " Y1961 Y1962 Y1963 Y1964 Y1965 Y1966 \\\n", + "Afghanistan 9481.0 9414.0 9194.0 10170.0 10473.0 10169.0 \n", + "Albania 1706.0 1749.0 1767.0 1889.0 1884.0 1995.0 \n", + "Algeria 7488.0 7235.0 6861.0 7255.0 7509.0 7536.0 \n", + "Angola 4834.0 4775.0 5240.0 5286.0 5527.0 5677.0 \n", + "Antigua and Barbuda 92.0 94.0 105.0 95.0 84.0 73.0 \n", + "\n", + " Y1967 Y1968 Y1969 Y1970 ... Y2006 \\\n", + "Afghanistan 11289.0 11508.0 11815.0 10454.0 ... 18317.0 \n", + "Albania 2046.0 2169.0 2230.0 2395.0 ... 6911.0 \n", + "Algeria 7986.0 8839.0 9003.0 9355.0 ... 51067.0 \n", + "Angola 5833.0 5685.0 6219.0 6460.0 ... 28247.0 \n", + "Antigua and Barbuda 64.0 59.0 68.0 77.0 ... 110.0 \n", + "\n", + " Y2007 Y2008 Y2009 Y2010 Y2011 Y2012 \\\n", + "Afghanistan 19248.0 19381.0 20661.0 21030.0 21100.0 22706.0 \n", + "Albania 6744.0 7168.0 7316.0 7907.0 8114.0 8221.0 \n", + "Algeria 49933.0 50916.0 57505.0 60071.0 65852.0 69365.0 \n", + "Angola 29877.0 32053.0 36985.0 38400.0 40573.0 38064.0 \n", + "Antigua and Barbuda 122.0 115.0 114.0 115.0 118.0 113.0 \n", + "\n", + " Y2013 Mean_Produce Rank \n", + "Afghanistan 23007.0 13003.056604 69.0 \n", + "Albania 8271.0 4475.509434 104.0 \n", + "Algeria 72161.0 28879.490566 38.0 \n", + "Angola 48639.0 13321.056604 68.0 \n", + "Antigua and Barbuda 119.0 83.886792 172.0 \n", + "\n", + "[5 rows x 55 columns]" + ] + }, + "execution_count": 19, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "new_df.head()" + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "metadata": { + "_cell_guid": "66964df2-892d-4e55-a4b1-f94d10e4c7dd", + "_uuid": "19bdd89a3ad9df962959ad6b996946f6f3916d58" + }, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/anaconda3/lib/python3.7/site-packages/ipykernel_launcher.py:4: FutureWarning: convert_objects is deprecated. To re-infer data dtypes for object columns, use DataFrame.infer_objects()\n", + "For all other conversions use the data-type specific converters pd.to_datetime, pd.to_timedelta and pd.to_numeric.\n", + " after removing the cwd from sys.path.\n" + ] + } + ], + "source": [ + "X = new_df.iloc[:,:-2].values\n", + "\n", + "X = pd.DataFrame(X)\n", + "X = X.convert_objects(convert_numeric=True)\n", + "X.columns = year_list" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "_cell_guid": "461e5bcc-0101-4ea1-ae13-20600f883929", + "_uuid": "0d3e50235c9505ebc255053d4a5aae547fc17d8d" + }, + "source": [ + "# Elbow method to select number of clusters\n", + "This method looks at the percentage of variance explained as a function of the number of clusters: One should choose a number of clusters so that adding another cluster doesn't give much better modeling of the data. More precisely, if one plots the percentage of variance explained by the clusters against the number of clusters, the first clusters will add much information (explain a lot of variance), but at some point the marginal gain will drop, giving an angle in the graph. The number of clusters is chosen at this point, hence the \"elbow criterion\". This \"elbow\" cannot always be unambiguously identified. Percentage of variance explained is the ratio of the between-group variance to the total variance, also known as an F-test. A slight variation of this method plots the curvature of the within group variance.\n", + "# Basically, number of clusters = the x-axis value of the point that is the corner of the \"elbow\"(the plot looks often looks like an elbow)" + ] + }, + { + "cell_type": "code", + "execution_count": 21, + "metadata": { + "_cell_guid": "06271223-bd32-48ac-a373-6c1e6bbf7c7b", + "_uuid": "c57d7277510a8c11fdc3d311e4d8a22539617ed9" + }, + "outputs": [ + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "from sklearn.cluster import KMeans\n", + "wcss = []\n", + "for i in range(1,11):\n", + " kmeans = KMeans(n_clusters=i,init='k-means++',max_iter=300,n_init=10,random_state=0)\n", + " kmeans.fit(X)\n", + " wcss.append(kmeans.inertia_)\n", + "plt.plot(range(1,11),wcss)\n", + "plt.title('The Elbow Method')\n", + "plt.xlabel('Number of clusters')\n", + "plt.ylabel('WCSS')\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "_cell_guid": "ad4bc40a-9540-497d-95e3-3fee6088ea95", + "_uuid": "6450dd1c3d7a8114931dc358d2f09a0424b52fd7" + }, + "source": [ + "As the elbow corner coincides with x=2, we will have to form **2 clusters**. Personally, I would have liked to select 3 to 4 clusters. But trust me, only selecting 2 clusters can lead to best results.\n", + "Now, we apply k-means algorithm." + ] + }, + { + "cell_type": "code", + "execution_count": 22, + "metadata": { + "_cell_guid": "eed3f672-e089-4dbb-aad8-b9618967abf3", + "_uuid": "d92d758ee7213ddcd84e9b8b2f61c9e260ed6ba2" + }, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/anaconda3/lib/python3.7/site-packages/ipykernel_launcher.py:4: FutureWarning: Method .as_matrix will be removed in a future version. Use .values instead.\n", + " after removing the cwd from sys.path.\n" + ] + } + ], + "source": [ + "kmeans = KMeans(n_clusters=2,init='k-means++',max_iter=300,n_init=10,random_state=0) \n", + "y_kmeans = kmeans.fit_predict(X)\n", + "\n", + "X = X.as_matrix(columns=None)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "_cell_guid": "ef07bd6d-679d-4375-b7b3-abeca3421e37", + "_uuid": "6f93a4bd3f17427f4b2dbe08af8e015a1e4a2f89" + }, + "source": [ + "Now, let's visualize the results." + ] + }, + { + "cell_type": "code", + "execution_count": 23, + "metadata": { + "_cell_guid": "5a7fe139-13df-453b-8c16-891929bc595e", + "_uuid": "a57e0a38f4c0f0385be75fd9f71d4a2d8213aea3" + }, + "outputs": [ + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "plt.scatter(X[y_kmeans == 0, 0], X[y_kmeans == 0,1],s=100,c='red',label='Others')\n", + "plt.scatter(X[y_kmeans == 1, 0], X[y_kmeans == 1,1],s=100,c='blue',label='China(mainland),USA,India')\n", + "plt.scatter(kmeans.cluster_centers_[:,0],kmeans.cluster_centers_[:,1],s=300,c='yellow',label='Centroids')\n", + "plt.title('Clusters of countries by Productivity')\n", + "plt.legend()\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "_cell_guid": "923d4536-2bce-4b99-b98a-33b801a56a8b", + "_uuid": "fe531e8c41eec0eb5dc52a9890871841f5d27211" + }, + "source": [ + "So, the blue cluster represents China(Mainland), USA and India while the red cluster represents all the other countries.\n", + "This result was highly probable. Just take a look at the plot of cell 3 above. See how China, USA and India stand out. That has been observed here in clustering too.\n", + "\n", + "You should try this algorithm for 3 or 4 clusters. Looking at the distribution, you will realise why 2 clusters is the best choice for the given data" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "_cell_guid": "6dee7acb-0f08-4ae1-85b4-f4704026694a", + "_uuid": "179a1ede21ae330664a0b7c63e36574acdc0428c" + }, + "source": [ + "This is not the end! More is yet to come." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**Now, lets try to predict the production using regression for 2020. We will predict the production for USA,India and Pakistan.**\n" + ] + }, + { + "cell_type": "code", + "execution_count": 24, + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + }, + { + "ename": "ValueError", + "evalue": "Expected 2D array, got scalar array instead:\narray=2020.\nReshape your data either using array.reshape(-1, 1) if your data has a single feature or array.reshape(1, -1) if it contains a single sample.", + "output_type": "error", + "traceback": [ + "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[0;31mValueError\u001b[0m Traceback (most recent call last)", + "\u001b[0;32m\u001b[0m in \u001b[0;36m\u001b[0;34m\u001b[0m\n\u001b[1;32m 27\u001b[0m \u001b[0mplt\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mplot\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mreset\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0mpredictions\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 28\u001b[0m \u001b[0mplt\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mshow\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m---> 29\u001b[0;31m \u001b[0mprint\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mreg\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mpredict\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;36m2020\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 30\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 31\u001b[0m \u001b[0mdf\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mdf\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mArea\u001b[0m\u001b[0;34m==\u001b[0m\u001b[0;34m'India'\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;34m&\u001b[0m \u001b[0;34m(\u001b[0m\u001b[0mdf\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mElement\u001b[0m\u001b[0;34m==\u001b[0m\u001b[0;34m'Food'\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;34m'Y1961'\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mmean\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[0;32m/anaconda3/lib/python3.7/site-packages/sklearn/linear_model/base.py\u001b[0m in \u001b[0;36mpredict\u001b[0;34m(self, X)\u001b[0m\n\u001b[1;32m 211\u001b[0m \u001b[0mReturns\u001b[0m \u001b[0mpredicted\u001b[0m \u001b[0mvalues\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 212\u001b[0m \"\"\"\n\u001b[0;32m--> 213\u001b[0;31m \u001b[0;32mreturn\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_decision_function\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mX\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 214\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 215\u001b[0m \u001b[0m_preprocess_data\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mstaticmethod\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0m_preprocess_data\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[0;32m/anaconda3/lib/python3.7/site-packages/sklearn/linear_model/base.py\u001b[0m in \u001b[0;36m_decision_function\u001b[0;34m(self, X)\u001b[0m\n\u001b[1;32m 194\u001b[0m \u001b[0mcheck_is_fitted\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m\"coef_\"\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 195\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 196\u001b[0;31m \u001b[0mX\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mcheck_array\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mX\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0maccept_sparse\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;34m'csr'\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m'csc'\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m'coo'\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 197\u001b[0m return safe_sparse_dot(X, self.coef_.T,\n\u001b[1;32m 198\u001b[0m dense_output=True) + self.intercept_\n", + "\u001b[0;32m/anaconda3/lib/python3.7/site-packages/sklearn/utils/validation.py\u001b[0m in \u001b[0;36mcheck_array\u001b[0;34m(array, accept_sparse, accept_large_sparse, dtype, order, copy, force_all_finite, ensure_2d, allow_nd, ensure_min_samples, ensure_min_features, warn_on_dtype, estimator)\u001b[0m\n\u001b[1;32m 543\u001b[0m \u001b[0;34m\"Reshape your data either using array.reshape(-1, 1) if \"\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 544\u001b[0m \u001b[0;34m\"your data has a single feature or array.reshape(1, -1) \"\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 545\u001b[0;31m \"if it contains a single sample.\".format(array))\n\u001b[0m\u001b[1;32m 546\u001b[0m \u001b[0;31m# If input is 1D raise error\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 547\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0marray\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mndim\u001b[0m \u001b[0;34m==\u001b[0m \u001b[0;36m1\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[0;31mValueError\u001b[0m: Expected 2D array, got scalar array instead:\narray=2020.\nReshape your data either using array.reshape(-1, 1) if your data has a single feature or array.reshape(1, -1) if it contains a single sample." + ] + } + ], + "source": [ + "india_list=[]\n", + "year_list = list(df.iloc[:,10:].columns)\n", + "for i in year_list:\n", + " x=df[(df.Area=='India') & (df.Element=='Food')][i].mean()\n", + " india_list.append(x) \n", + "\n", + "reset=[]\n", + "for i in year_list:\n", + " reset.append(int(i[1:]))\n", + "\n", + "\n", + "reset=np.array(reset)\n", + "reset=reset.reshape(-1,1)\n", + "\n", + "\n", + "india_list=np.array(india_list)\n", + "india_list=india_list.reshape(-1,1)\n", + "\n", + "\n", + "reg = LinearRegression()\n", + "reg.fit(reset,india_list)\n", + "predictions = reg.predict(reset)\n", + "plt.title(\"India\")\n", + "plt.xlabel(\"Year\")\n", + "plt.ylabel(\"Production\")\n", + "plt.scatter(reset,india_list)\n", + "plt.plot(reset,predictions)\n", + "plt.show()\n", + "print(reg.predict(2020))\n", + "\n", + "df[(df.Area=='India') & (df.Element=='Food')]['Y1961'].mean()\n", + "\n", + "df[(df.Area=='Pakistan') & (df.Element=='Food')]\n", + "\n", + "Pak_list=[]\n", + "year_list = list(df.iloc[:,10:].columns)\n", + "for i in year_list:\n", + " yx=df[(df.Area=='Pakistan') & (df.Element=='Food')][i].mean()\n", + " Pak_list.append(yx) \n", + "\n", + "Pak_list=np.array(Pak_list)\n", + "Pak_list=Pak_list.reshape(-1,1)\n", + "Pak_list\n", + "reg = LinearRegression()\n", + "reg.fit(reset,Pak_list)\n", + "predictions = reg.predict(reset)\n", + "plt.title(\"Pakistan\")\n", + "plt.xlabel(\"Year\")\n", + "plt.ylabel(\"Production\")\n", + "plt.scatter(reset,Pak_list)\n", + "plt.plot(reset,predictions)\n", + "plt.show()\n", + "print(reg.predict(2020))\n", + "\n", + "\n", + "\n", + "usa_list=[]\n", + "year_list = list(df.iloc[:,10:].columns)\n", + "for i in year_list:\n", + " xu=df[(df.Area=='United States of America') & (df.Element=='Food')][i].mean()\n", + " usa_list.append(xu)\n", + "\n", + "usa_list=np.array(usa_list)\n", + "usa_list=india_list.reshape(-1,1)\n", + "\n", + "\n", + "reg = LinearRegression()\n", + "reg.fit(reset,usa_list)\n", + "predictions = reg.predict(reset)\n", + "plt.title(\"USA\")\n", + "plt.xlabel(\"Year\")\n", + "plt.ylabel(\"Production\")\n", + "plt.scatter(reset,usa_list)\n", + "plt.plot(reset,predictions)\n", + "plt.show()\n", + "print(reg.predict(2020))" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.7.1" + } + }, + "nbformat": 4, + "nbformat_minor": 1 +} From 628794d89de355e87ae51ed022cf242dac30ea47 Mon Sep 17 00:00:00 2001 From: obelisk0114 Date: Sat, 13 Jul 2019 22:45:54 -0700 Subject: [PATCH 0114/1071] Add combinations (#1015) * Update Bucket Sort time complexity analysis * Add combinations * Adding doctest * Fix doctest problem --- backtracking/all_combinations.py | 41 ++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 backtracking/all_combinations.py diff --git a/backtracking/all_combinations.py b/backtracking/all_combinations.py new file mode 100644 index 000000000000..63425aeabbd1 --- /dev/null +++ b/backtracking/all_combinations.py @@ -0,0 +1,41 @@ +# -*- coding: utf-8 -*- + +""" + In this problem, we want to determine all possible combinations of k + numbers out of 1 ... n. We use backtracking to solve this problem. + Time complexity: O(C(n,k)) which is O(n choose k) = O((n!/(k! * (n - k)!))) +""" + + +def generate_all_combinations(n: int, k: int) -> [[int]]: + """ + >>> generate_all_combinations(n=4, k=2) + [[1, 2], [1, 3], [1, 4], [2, 3], [2, 4], [3, 4]] + """ + + result = [] + create_all_state(1, n, k, [], result) + return result + + +def create_all_state(increment, total_number, level, current_list, total_list): + if level == 0: + total_list.append(current_list[:]) + return + + for i in range(increment, total_number - level + 2): + current_list.append(i) + create_all_state(i + 1, total_number, level - 1, current_list, total_list) + current_list.pop() + + +def print_all_state(total_list): + for i in total_list: + print(*i) + + +if __name__ == '__main__': + n = 4 + k = 2 + total_list = generate_all_combinations(n, k) + print_all_state(total_list) From 3b2738ed89a4ecb1cfb6f16aa96bb90701914796 Mon Sep 17 00:00:00 2001 From: obelisk0114 Date: Sun, 14 Jul 2019 23:48:35 -0700 Subject: [PATCH 0115/1071] Add rotate matrix problem (#1021) * Add rotate matrix problem * Fix doctest * Adding return matrix to enable doctest --- matrix/rotate_matrix.py | 99 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 99 insertions(+) create mode 100644 matrix/rotate_matrix.py diff --git a/matrix/rotate_matrix.py b/matrix/rotate_matrix.py new file mode 100644 index 000000000000..e3495e647954 --- /dev/null +++ b/matrix/rotate_matrix.py @@ -0,0 +1,99 @@ +# -*- coding: utf-8 -*- + +""" + In this problem, we want to rotate the matrix elements by 90, 180, 270 (counterclockwise) + Discussion in stackoverflow: + https://stackoverflow.com/questions/42519/how-do-you-rotate-a-two-dimensional-array +""" + + +def rotate_90(matrix: [[]]): + """ + >>> rotate_90([[1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12], [13, 14, 15, 16]]) + [[4, 8, 12, 16], [3, 7, 11, 15], [2, 6, 10, 14], [1, 5, 9, 13]] + """ + + transpose(matrix) + reverse_row(matrix) + return matrix + + +def rotate_180(matrix: [[]]): + """ + >>> rotate_180([[1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12], [13, 14, 15, 16]]) + [[16, 15, 14, 13], [12, 11, 10, 9], [8, 7, 6, 5], [4, 3, 2, 1]] + """ + + reverse_column(matrix) + reverse_row(matrix) + + """ + OR + + reverse_row(matrix) + reverse_column(matrix) + """ + + return matrix + + +def rotate_270(matrix: [[]]): + """ + >>> rotate_270([[1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12], [13, 14, 15, 16]]) + [[13, 9, 5, 1], [14, 10, 6, 2], [15, 11, 7, 3], [16, 12, 8, 4]] + """ + + transpose(matrix) + reverse_column(matrix) + + """ + OR + + reverse_row(matrix) + transpose(matrix) + """ + + return matrix + + +def transpose(matrix: [[]]): + matrix[:] = [list(x) for x in zip(*matrix)] + return matrix + + +def reverse_row(matrix: [[]]): + matrix[:] = matrix[::-1] + return matrix + + +def reverse_column(matrix: [[]]): + matrix[:] = [x[::-1] for x in matrix] + return matrix + + +def print_matrix(matrix: [[]]): + for i in matrix: + print(*i) + + +if __name__ == '__main__': + matrix = [[1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12], [13, 14, 15, 16]] + print("\norigin:\n") + print_matrix(matrix) + rotate_90(matrix) + print("\nrotate 90 counterclockwise:\n") + print_matrix(matrix) + + matrix = [[1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12], [13, 14, 15, 16]] + print("\norigin:\n") + print_matrix(matrix) + rotate_180(matrix) + print("\nrotate 180:\n") + print_matrix(matrix) + + matrix = [[1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12], [13, 14, 15, 16]] + print("\norigin:\n") + print_matrix(matrix) + rotate_270(matrix) + print("\nrotate 270 counterclockwise:\n") + print_matrix(matrix) From 1e55bfd4da5a15d8e8536a1a87ad148c69b16a1e Mon Sep 17 00:00:00 2001 From: Sanders Lin <45224617+SandersLin@users.noreply.github.com> Date: Tue, 16 Jul 2019 00:17:41 +0800 Subject: [PATCH 0116/1071] Create climbing_stairs.py (#1002) a simple dp problem seen on LeetCode: https://leetcode.com/problems/climbing-stairs/ --- dynamic_programming/climbing_stairs.py | 27 ++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 dynamic_programming/climbing_stairs.py diff --git a/dynamic_programming/climbing_stairs.py b/dynamic_programming/climbing_stairs.py new file mode 100644 index 000000000000..8a6213b22323 --- /dev/null +++ b/dynamic_programming/climbing_stairs.py @@ -0,0 +1,27 @@ +def climb_stairs(n: int) -> int: + """ + LeetCdoe No.70: Climbing Stairs + Distinct ways to climb a n step staircase where + each time you can either climb 1 or 2 steps. + + Args: + n: number of steps of staircase + + Returns: + Distinct ways to climb a n step staircase + + Raises: + AssertionError: n not positive integer + + >>> climb_stairs(3) + 3 + >>> climb_stairs(1) + 1 + """ + assert isinstance(n,int) and n > 0, "n needs to be positive integer, your input {0}".format(0) + if n == 1: return 1 + dp = [0]*(n+1) + dp[0], dp[1] = (1, 1) + for i in range(2,n+1): + dp[i] = dp[i-1] + dp[i-2] + return dp[n] From 2fb3beeaf1215a1be4a7bc97097ef6a33fd65aeb Mon Sep 17 00:00:00 2001 From: cclauss Date: Tue, 16 Jul 2019 07:26:28 +0200 Subject: [PATCH 0117/1071] Fix error message and format with python/black (#1025) @SandersLin Your review please? --- dynamic_programming/climbing_stairs.py | 67 ++++++++++++++++---------- 1 file changed, 41 insertions(+), 26 deletions(-) diff --git a/dynamic_programming/climbing_stairs.py b/dynamic_programming/climbing_stairs.py index 8a6213b22323..79605261f981 100644 --- a/dynamic_programming/climbing_stairs.py +++ b/dynamic_programming/climbing_stairs.py @@ -1,27 +1,42 @@ +#!/usr/bin/env python3 + + def climb_stairs(n: int) -> int: - """ - LeetCdoe No.70: Climbing Stairs - Distinct ways to climb a n step staircase where - each time you can either climb 1 or 2 steps. - - Args: - n: number of steps of staircase - - Returns: - Distinct ways to climb a n step staircase - - Raises: - AssertionError: n not positive integer - - >>> climb_stairs(3) - 3 - >>> climb_stairs(1) - 1 - """ - assert isinstance(n,int) and n > 0, "n needs to be positive integer, your input {0}".format(0) - if n == 1: return 1 - dp = [0]*(n+1) - dp[0], dp[1] = (1, 1) - for i in range(2,n+1): - dp[i] = dp[i-1] + dp[i-2] - return dp[n] + """ + LeetCdoe No.70: Climbing Stairs + Distinct ways to climb a n step staircase where + each time you can either climb 1 or 2 steps. + + Args: + n: number of steps of staircase + + Returns: + Distinct ways to climb a n step staircase + + Raises: + AssertionError: n not positive integer + + >>> climb_stairs(3) + 3 + >>> climb_stairs(1) + 1 + >>> climb_stairs(-7) # doctest: +ELLIPSIS + Traceback (most recent call last): + ... + AssertionError: n needs to be positive integer, your input -7 + """ + fmt = "n needs to be positive integer, your input {}" + assert isinstance(n, int) and n > 0, fmt.format(n) + if n == 1: + return 1 + dp = [0] * (n + 1) + dp[0], dp[1] = (1, 1) + for i in range(2, n + 1): + dp[i] = dp[i - 1] + dp[i - 2] + return dp[n] + + +if __name__ == "__main__": + import doctest + + doctest.testmod() From 267b5eff40409034322d046b3b0054ac3462cf9e Mon Sep 17 00:00:00 2001 From: Bruno Simas Hadlich Date: Tue, 16 Jul 2019 20:09:53 -0300 Subject: [PATCH 0118/1071] Added doctest and more explanation about Dijkstra execution. (#1014) * Added doctest and more explanation about Dijkstra execution. * tests were not passing with python2 due to missing __init__.py file at number_theory folder * Removed the dot at the beginning of the imported modules names because 'python3 -m doctest -v data_structures/hashing/*.py' and 'python3 -m doctest -v data_structures/stacks/*.py' were failing not finding hash_table.py and stack.py modules. * Moved global code to main scope and added doctest for project euler problems 1 to 14. * Added test case for negative input. * Changed N variable to do not use end of line scape because in case there is a space after it the script will break making it much more error prone. * Added problems description and doctests to the ones that were missing. Limited line length to 79 and executed python black over all scripts. * Changed the way files are loaded to support pytest call. * Added __init__.py to problems to make them modules and allow pytest execution. * Added project_euler folder to test units execution * Changed 'os.path.split(os.path.realpath(__file__))' to 'os.path.dirname()' --- .travis.yml | 1 + data_structures/hashing/double_hash.py | 2 +- .../hashing/hash_table_with_linked_list.py | 2 +- .../hashing/number_theory/__init__.py | 0 data_structures/hashing/quadratic_probing.py | 2 +- .../stacks/infix_to_postfix_conversion.py | 2 +- graphs/dijkstra.py | 117 +++- project_euler/problem_01/__init__.py | 0 project_euler/problem_01/sol1.py | 31 +- project_euler/problem_01/sol2.py | 43 +- project_euler/problem_01/sol3.py | 98 +-- project_euler/problem_01/sol4.py | 47 +- project_euler/problem_01/sol5.py | 30 +- project_euler/problem_01/sol6.py | 49 +- project_euler/problem_02/__init__.py | 0 project_euler/problem_02/sol1.py | 57 +- project_euler/problem_02/sol2.py | 46 +- project_euler/problem_02/sol3.py | 63 +- project_euler/problem_02/sol4.py | 48 +- project_euler/problem_03/__init__.py | 0 project_euler/problem_03/sol1.py | 76 ++- project_euler/problem_03/sol2.py | 52 +- project_euler/problem_04/__init__.py | 0 project_euler/problem_04/sol1.py | 61 +- project_euler/problem_04/sol2.py | 47 +- project_euler/problem_05/__init__.py | 0 project_euler/problem_05/sol1.py | 59 +- project_euler/problem_05/sol2.py | 60 +- project_euler/problem_06/__init__.py | 0 project_euler/problem_06/sol1.py | 52 +- project_euler/problem_06/sol2.py | 47 +- project_euler/problem_06/sol3.py | 47 +- project_euler/problem_07/__init__.py | 0 project_euler/problem_07/sol1.py | 73 ++- project_euler/problem_07/sol2.py | 67 +- project_euler/problem_07/sol3.py | 47 +- project_euler/problem_08/__init__.py | 0 project_euler/problem_08/sol1.py | 75 ++- project_euler/problem_08/sol2.py | 77 ++- project_euler/problem_09/__init__.py | 0 project_euler/problem_09/sol1.py | 49 +- project_euler/problem_09/sol2.py | 60 +- project_euler/problem_09/sol3.py | 43 +- project_euler/problem_10/__init__.py | 0 project_euler/problem_10/sol1.py | 78 ++- project_euler/problem_10/sol2.py | 49 +- project_euler/problem_11/__init__.py | 0 project_euler/problem_11/sol1.py | 97 ++- project_euler/problem_11/sol2.py | 129 ++-- project_euler/problem_12/__init__.py | 0 project_euler/problem_12/sol1.py | 73 ++- project_euler/problem_12/sol2.py | 59 +- project_euler/problem_13/__init__.py | 0 project_euler/problem_13/sol1.py | 38 +- project_euler/problem_13/sol2.py | 5 - project_euler/problem_14/__init__.py | 0 project_euler/problem_14/sol1.py | 92 ++- project_euler/problem_14/sol2.py | 83 ++- project_euler/problem_15/__init__.py | 0 project_euler/problem_15/sol1.py | 71 ++- project_euler/problem_16/__init__.py | 0 project_euler/problem_16/sol1.py | 37 +- project_euler/problem_16/sol2.py | 34 +- project_euler/problem_17/__init__.py | 0 project_euler/problem_17/sol1.py | 92 ++- project_euler/problem_19/__init__.py | 0 project_euler/problem_19/sol1.py | 75 ++- project_euler/problem_20/__init__.py | 0 project_euler/problem_20/sol1.py | 50 +- project_euler/problem_20/sol2.py | 36 +- project_euler/problem_21/__init__.py | 0 project_euler/problem_21/sol1.py | 69 ++- project_euler/problem_22/__init__.py | 0 project_euler/problem_22/sol1.py | 59 +- project_euler/problem_22/sol2.py | 572 ++---------------- project_euler/problem_234/__init__.py | 0 project_euler/problem_234/sol1.py | 52 +- project_euler/problem_24/__init__.py | 0 project_euler/problem_24/sol1.py | 30 +- project_euler/problem_25/__init__.py | 0 project_euler/problem_25/sol1.py | 84 ++- project_euler/problem_25/sol2.py | 67 +- project_euler/problem_28/__init__.py | 0 project_euler/problem_28/sol1.py | 77 ++- project_euler/problem_29/__init__.py | 0 project_euler/problem_29/solution.py | 64 +- project_euler/problem_31/__init__.py | 0 project_euler/problem_31/sol1.py | 34 +- project_euler/problem_36/__init__.py | 0 project_euler/problem_36/sol1.py | 61 +- project_euler/problem_40/__init__.py | 0 project_euler/problem_40/sol1.py | 47 +- project_euler/problem_48/__init__.py | 0 project_euler/problem_48/sol1.py | 26 +- project_euler/problem_52/__init__.py | 0 project_euler/problem_52/sol1.py | 46 +- project_euler/problem_53/__init__.py | 0 project_euler/problem_53/sol1.py | 46 +- project_euler/problem_76/__init__.py | 0 project_euler/problem_76/sol1.py | 57 +- 100 files changed, 2651 insertions(+), 1468 deletions(-) create mode 100644 data_structures/hashing/number_theory/__init__.py create mode 100644 project_euler/problem_01/__init__.py create mode 100644 project_euler/problem_02/__init__.py create mode 100644 project_euler/problem_03/__init__.py create mode 100644 project_euler/problem_04/__init__.py create mode 100644 project_euler/problem_05/__init__.py create mode 100644 project_euler/problem_06/__init__.py create mode 100644 project_euler/problem_07/__init__.py create mode 100644 project_euler/problem_08/__init__.py create mode 100644 project_euler/problem_09/__init__.py create mode 100644 project_euler/problem_10/__init__.py create mode 100644 project_euler/problem_11/__init__.py create mode 100644 project_euler/problem_12/__init__.py create mode 100644 project_euler/problem_13/__init__.py delete mode 100644 project_euler/problem_13/sol2.py create mode 100644 project_euler/problem_14/__init__.py create mode 100644 project_euler/problem_15/__init__.py create mode 100644 project_euler/problem_16/__init__.py create mode 100644 project_euler/problem_17/__init__.py create mode 100644 project_euler/problem_19/__init__.py create mode 100644 project_euler/problem_20/__init__.py create mode 100644 project_euler/problem_21/__init__.py create mode 100644 project_euler/problem_22/__init__.py create mode 100644 project_euler/problem_234/__init__.py create mode 100644 project_euler/problem_24/__init__.py create mode 100644 project_euler/problem_25/__init__.py create mode 100644 project_euler/problem_28/__init__.py create mode 100644 project_euler/problem_29/__init__.py create mode 100644 project_euler/problem_31/__init__.py create mode 100644 project_euler/problem_36/__init__.py create mode 100644 project_euler/problem_40/__init__.py create mode 100644 project_euler/problem_48/__init__.py create mode 100644 project_euler/problem_52/__init__.py create mode 100644 project_euler/problem_53/__init__.py create mode 100644 project_euler/problem_76/__init__.py diff --git a/.travis.yml b/.travis.yml index 9afc0c93a037..3b55045ac33f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -26,6 +26,7 @@ script: matrix networking_flow other + project_euler searches sorts strings diff --git a/data_structures/hashing/double_hash.py b/data_structures/hashing/double_hash.py index 60098cda0ce1..7a0ce0b3a67b 100644 --- a/data_structures/hashing/double_hash.py +++ b/data_structures/hashing/double_hash.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 -from .hash_table import HashTable +from hash_table import HashTable from number_theory.prime_numbers import next_prime, check_prime diff --git a/data_structures/hashing/hash_table_with_linked_list.py b/data_structures/hashing/hash_table_with_linked_list.py index 9689e4fc9fcf..a45876df49bd 100644 --- a/data_structures/hashing/hash_table_with_linked_list.py +++ b/data_structures/hashing/hash_table_with_linked_list.py @@ -1,4 +1,4 @@ -from .hash_table import HashTable +from hash_table import HashTable from collections import deque diff --git a/data_structures/hashing/number_theory/__init__.py b/data_structures/hashing/number_theory/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/data_structures/hashing/quadratic_probing.py b/data_structures/hashing/quadratic_probing.py index f7a9ac1ae347..1e61100a81fa 100644 --- a/data_structures/hashing/quadratic_probing.py +++ b/data_structures/hashing/quadratic_probing.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 -from .hash_table import HashTable +from hash_table import HashTable class QuadraticProbing(HashTable): diff --git a/data_structures/stacks/infix_to_postfix_conversion.py b/data_structures/stacks/infix_to_postfix_conversion.py index 75211fed258d..e71dccf1f45c 100644 --- a/data_structures/stacks/infix_to_postfix_conversion.py +++ b/data_structures/stacks/infix_to_postfix_conversion.py @@ -2,7 +2,7 @@ from __future__ import absolute_import import string -from .Stack import Stack +from stack import Stack __author__ = 'Omkar Pathak' diff --git a/graphs/dijkstra.py b/graphs/dijkstra.py index 4b6bc347b061..52354b5c916b 100644 --- a/graphs/dijkstra.py +++ b/graphs/dijkstra.py @@ -1,24 +1,50 @@ """pseudo-code""" """ -DIJKSTRA(graph G, start vertex s,destination vertex d): -// all nodes initially unexplored -let H = min heap data structure, initialized with 0 and s [here 0 indicates the distance from start vertex] -while H is non-empty: - remove the first node and cost of H, call it U and cost - if U is not explored - mark U as explored - if U is d: - return cost // total cost from start to destination vertex - for each edge(U, V): c=cost of edge(u,V) // for V in graph[U] - if V unexplored: - next=cost+c - add next,V to H (at the end) +DIJKSTRA(graph G, start vertex s, destination vertex d): + +//all nodes initially unexplored + +1 - let H = min heap data structure, initialized with 0 and s [here 0 indicates + the distance from start vertex s] +2 - while H is non-empty: +3 - remove the first node and cost of H, call it U and cost +4 - if U has been previously explored: +5 - go to the while loop, line 2 //Once a node is explored there is no need + to make it again +6 - mark U as explored +7 - if U is d: +8 - return cost // total cost from start to destination vertex +9 - for each edge(U, V): c=cost of edge(U,V) // for V in graph[U] +10 - if V explored: +11 - go to next V in line 9 +12 - total_cost = cost + c +13 - add (total_cost,V) to H + +You can think at cost as a distance where Dijkstra finds the shortest distance +between vertexes s and v in a graph G. The use of a min heap as H guarantees +that if a vertex has already been explored there will be no other path with +shortest distance, that happens because heapq.heappop will always return the +next vertex with the shortest distance, considering that the heap stores not +only the distance between previous vertex and current vertex but the entire +distance between each vertex that makes up the path from start vertex to target +vertex. """ + import heapq def dijkstra(graph, start, end): + """Return the cost of the shortest path between vertexes start and end. + + >>> dijkstra(G, "E", "C") + 6 + >>> dijkstra(G2, "E", "F") + 3 + >>> dijkstra(G3, "E", "F") + 3 + """ + heap = [(0, start)] # cost from start node,end node visited = set() while heap: @@ -28,20 +54,65 @@ def dijkstra(graph, start, end): visited.add(u) if u == end: return cost - for v, c in G[u]: + for v, c in graph[u]: if v in visited: continue next = cost + c heapq.heappush(heap, (next, v)) - return (-1, -1) + return -1 + + +G = { + "A": [["B", 2], ["C", 5]], + "B": [["A", 2], ["D", 3], ["E", 1], ["F", 1]], + "C": [["A", 5], ["F", 3]], + "D": [["B", 3]], + "E": [["B", 4], ["F", 3]], + "F": [["C", 3], ["E", 3]], +} + +""" +Layout of G2: + +E -- 1 --> B -- 1 --> C -- 1 --> D -- 1 --> F + \ /\ + \ || + ----------------- 3 -------------------- +""" +G2 = { + "B": [["C", 1]], + "C": [["D", 1]], + "D": [["F", 1]], + "E": [["B", 1], ["F", 3]], + "F": [], +} + +""" +Layout of G3: + +E -- 1 --> B -- 1 --> C -- 1 --> D -- 1 --> F + \ /\ + \ || + -------- 2 ---------> G ------- 1 ------ +""" +G3 = { + "B": [["C", 1]], + "C": [["D", 1]], + "D": [["F", 1]], + "E": [["B", 1], ["G", 2]], + "F": [], + "G": [["F", 1]], +} + +shortDistance = dijkstra(G, "E", "C") +print(shortDistance) # E -- 3 --> F -- 3 --> C == 6 +shortDistance = dijkstra(G2, "E", "F") +print(shortDistance) # E -- 3 --> F == 3 -G = {'A': [['B', 2], ['C', 5]], - 'B': [['A', 2], ['D', 3], ['E', 1]], - 'C': [['A', 5], ['F', 3]], - 'D': [['B', 3]], - 'E': [['B', 1], ['F', 3]], - 'F': [['C', 3], ['E', 3]]} +shortDistance = dijkstra(G3, "E", "F") +print(shortDistance) # E -- 2 --> G -- 1 --> F == 3 -shortDistance = dijkstra(G, 'E', 'C') -print(shortDistance) +if __name__ == "__main__": + import doctest + doctest.testmod() diff --git a/project_euler/problem_01/__init__.py b/project_euler/problem_01/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/project_euler/problem_01/sol1.py b/project_euler/problem_01/sol1.py index c9a8c0f1ebeb..1433129af303 100644 --- a/project_euler/problem_01/sol1.py +++ b/project_euler/problem_01/sol1.py @@ -1,13 +1,34 @@ -''' +""" Problem Statement: If we list all the natural numbers below 10 that are multiples of 3 or 5, we get 3,5,6 and 9. The sum of these multiples is 23. Find the sum of all the multiples of 3 or 5 below N. -''' +""" from __future__ import print_function + try: - raw_input # Python 2 + raw_input # Python 2 except NameError: raw_input = input # Python 3 -n = int(raw_input().strip()) -print(sum([e for e in range(3, n) if e % 3 == 0 or e % 5 == 0])) + + +def solution(n): + """Returns the sum of all the multiples of 3 or 5 below n. + + >>> solution(3) + 0 + >>> solution(4) + 3 + >>> solution(10) + 23 + >>> solution(600) + 83700 + >>> solution(-7) + 0 + """ + + return sum([e for e in range(3, n) if e % 3 == 0 or e % 5 == 0]) + + +if __name__ == "__main__": + print(solution(int(raw_input().strip()))) diff --git a/project_euler/problem_01/sol2.py b/project_euler/problem_01/sol2.py index 2b7760e0bfff..e58fb03a8fb0 100644 --- a/project_euler/problem_01/sol2.py +++ b/project_euler/problem_01/sol2.py @@ -1,20 +1,39 @@ -''' +""" Problem Statement: If we list all the natural numbers below 10 that are multiples of 3 or 5, we get 3,5,6 and 9. The sum of these multiples is 23. Find the sum of all the multiples of 3 or 5 below N. -''' +""" from __future__ import print_function + try: - raw_input # Python 2 + raw_input # Python 2 except NameError: raw_input = input # Python 3 -n = int(raw_input().strip()) -sum = 0 -terms = (n-1)//3 -sum+= ((terms)*(6+(terms-1)*3))//2 #sum of an A.P. -terms = (n-1)//5 -sum+= ((terms)*(10+(terms-1)*5))//2 -terms = (n-1)//15 -sum-= ((terms)*(30+(terms-1)*15))//2 -print(sum) + + +def solution(n): + """Returns the sum of all the multiples of 3 or 5 below n. + + >>> solution(3) + 0 + >>> solution(4) + 3 + >>> solution(10) + 23 + >>> solution(600) + 83700 + """ + + sum = 0 + terms = (n - 1) // 3 + sum += ((terms) * (6 + (terms - 1) * 3)) // 2 # sum of an A.P. + terms = (n - 1) // 5 + sum += ((terms) * (10 + (terms - 1) * 5)) // 2 + terms = (n - 1) // 15 + sum -= ((terms) * (30 + (terms - 1) * 15)) // 2 + return sum + + +if __name__ == "__main__": + print(solution(int(raw_input().strip()))) diff --git a/project_euler/problem_01/sol3.py b/project_euler/problem_01/sol3.py index f4f3aefcc5de..013ce5e54fdf 100644 --- a/project_euler/problem_01/sol3.py +++ b/project_euler/problem_01/sol3.py @@ -1,50 +1,66 @@ -from __future__ import print_function - -''' +""" Problem Statement: If we list all the natural numbers below 10 that are multiples of 3 or 5, we get 3,5,6 and 9. The sum of these multiples is 23. Find the sum of all the multiples of 3 or 5 below N. -''' -''' -This solution is based on the pattern that the successive numbers in the series follow: 0+3,+2,+1,+3,+1,+2,+3. -''' +""" +from __future__ import print_function try: - raw_input # Python 2 + raw_input # Python 2 except NameError: raw_input = input # Python 3 -n = int(raw_input().strip()) -sum=0 -num=0 -while(1): - num+=3 - if(num>=n): - break - sum+=num - num+=2 - if(num>=n): - break - sum+=num - num+=1 - if(num>=n): - break - sum+=num - num+=3 - if(num>=n): - break - sum+=num - num+=1 - if(num>=n): - break - sum+=num - num+=2 - if(num>=n): - break - sum+=num - num+=3 - if(num>=n): - break - sum+=num -print(sum); + +def solution(n): + """ + This solution is based on the pattern that the successive numbers in the + series follow: 0+3,+2,+1,+3,+1,+2,+3. + Returns the sum of all the multiples of 3 or 5 below n. + + >>> solution(3) + 0 + >>> solution(4) + 3 + >>> solution(10) + 23 + >>> solution(600) + 83700 + """ + + sum = 0 + num = 0 + while 1: + num += 3 + if num >= n: + break + sum += num + num += 2 + if num >= n: + break + sum += num + num += 1 + if num >= n: + break + sum += num + num += 3 + if num >= n: + break + sum += num + num += 1 + if num >= n: + break + sum += num + num += 2 + if num >= n: + break + sum += num + num += 3 + if num >= n: + break + sum += num + return sum + + +if __name__ == "__main__": + print(solution(int(raw_input().strip()))) diff --git a/project_euler/problem_01/sol4.py b/project_euler/problem_01/sol4.py index 7941f5fcd3fe..90403c3bd6a3 100644 --- a/project_euler/problem_01/sol4.py +++ b/project_euler/problem_01/sol4.py @@ -1,4 +1,30 @@ -def mulitples(limit): +""" +Problem Statement: +If we list all the natural numbers below 10 that are multiples of 3 or 5, +we get 3,5,6 and 9. The sum of these multiples is 23. +Find the sum of all the multiples of 3 or 5 below N. +""" +from __future__ import print_function + +try: + raw_input # Python 2 +except NameError: + raw_input = input # Python 3 + + +def solution(n): + """Returns the sum of all the multiples of 3 or 5 below n. + + >>> solution(3) + 0 + >>> solution(4) + 3 + >>> solution(10) + 23 + >>> solution(600) + 83700 + """ + xmulti = [] zmulti = [] z = 3 @@ -6,7 +32,7 @@ def mulitples(limit): temp = 1 while True: result = z * temp - if (result < limit): + if result < n: zmulti.append(result) temp += 1 else: @@ -14,17 +40,14 @@ def mulitples(limit): break while True: result = x * temp - if (result < limit): + if result < n: xmulti.append(result) temp += 1 else: break - collection = list(set(xmulti+zmulti)) - return (sum(collection)) - - - - - - -print (mulitples(1000)) + collection = list(set(xmulti + zmulti)) + return sum(collection) + + +if __name__ == "__main__": + print(solution(int(raw_input().strip()))) diff --git a/project_euler/problem_01/sol5.py b/project_euler/problem_01/sol5.py index e261cc8fc729..302fe44f8bfa 100644 --- a/project_euler/problem_01/sol5.py +++ b/project_euler/problem_01/sol5.py @@ -1,16 +1,34 @@ -''' +""" Problem Statement: If we list all the natural numbers below 10 that are multiples of 3 or 5, we get 3,5,6 and 9. The sum of these multiples is 23. Find the sum of all the multiples of 3 or 5 below N. -''' +""" from __future__ import print_function + try: - input = raw_input #python3 + raw_input # Python 2 except NameError: - pass #python 2 + raw_input = input # Python 3 """A straightforward pythonic solution using list comprehension""" -n = int(input().strip()) -print(sum([i for i in range(n) if i%3==0 or i%5==0])) + +def solution(n): + """Returns the sum of all the multiples of 3 or 5 below n. + + >>> solution(3) + 0 + >>> solution(4) + 3 + >>> solution(10) + 23 + >>> solution(600) + 83700 + """ + + return sum([i for i in range(n) if i % 3 == 0 or i % 5 == 0]) + + +if __name__ == "__main__": + print(solution(int(raw_input().strip()))) diff --git a/project_euler/problem_01/sol6.py b/project_euler/problem_01/sol6.py index 54c3073f3897..cf6e751d4c05 100644 --- a/project_euler/problem_01/sol6.py +++ b/project_euler/problem_01/sol6.py @@ -1,9 +1,40 @@ -a = 3 -result = 0 -while a < 1000: - if(a % 3 == 0 or a % 5 == 0): - result += a - elif(a % 15 == 0): - result -= a - a += 1 -print(result) +""" +Problem Statement: +If we list all the natural numbers below 10 that are multiples of 3 or 5, +we get 3,5,6 and 9. The sum of these multiples is 23. +Find the sum of all the multiples of 3 or 5 below N. +""" +from __future__ import print_function + +try: + raw_input # Python 2 +except NameError: + raw_input = input # Python 3 + + +def solution(n): + """Returns the sum of all the multiples of 3 or 5 below n. + + >>> solution(3) + 0 + >>> solution(4) + 3 + >>> solution(10) + 23 + >>> solution(600) + 83700 + """ + + a = 3 + result = 0 + while a < n: + if a % 3 == 0 or a % 5 == 0: + result += a + elif a % 15 == 0: + result -= a + a += 1 + return result + + +if __name__ == "__main__": + print(solution(int(raw_input().strip()))) diff --git a/project_euler/problem_02/__init__.py b/project_euler/problem_02/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/project_euler/problem_02/sol1.py b/project_euler/problem_02/sol1.py index 44ea980f2df0..f61d04e3dfce 100644 --- a/project_euler/problem_02/sol1.py +++ b/project_euler/problem_02/sol1.py @@ -1,24 +1,47 @@ -''' +""" Problem: -Each new term in the Fibonacci sequence is generated by adding the previous two terms. By starting with 1 and 2, -the first 10 terms will be: - 1,2,3,5,8,13,21,34,55,89,.. -By considering the terms in the Fibonacci sequence whose values do not exceed n, find the sum of the even-valued terms. -e.g. for n=10, we have {2,8}, sum is 10. -''' +Each new term in the Fibonacci sequence is generated by adding the previous two +terms. By starting with 1 and 2, the first 10 terms will be: + + 1,2,3,5,8,13,21,34,55,89,.. + +By considering the terms in the Fibonacci sequence whose values do not exceed +n, find the sum of the even-valued terms. e.g. for n=10, we have {2,8}, sum is +10. +""" from __future__ import print_function try: - raw_input # Python 2 + raw_input # Python 2 except NameError: raw_input = input # Python 3 -n = int(raw_input().strip()) -i=1 -j=2 -sum=0 -while(j<=n): - if j%2 == 0: - sum+=j - i , j = j, i+j -print(sum) + +def solution(n): + """Returns the sum of all fibonacci sequence even elements that are lower + or equals to n. + + >>> solution(10) + 10 + >>> solution(15) + 10 + >>> solution(2) + 2 + >>> solution(1) + 0 + >>> solution(34) + 44 + """ + i = 1 + j = 2 + sum = 0 + while j <= n: + if j % 2 == 0: + sum += j + i, j = j, i + j + + return sum + + +if __name__ == "__main__": + print(solution(int(raw_input().strip()))) diff --git a/project_euler/problem_02/sol2.py b/project_euler/problem_02/sol2.py index a2772697bb79..3e103a6a4373 100644 --- a/project_euler/problem_02/sol2.py +++ b/project_euler/problem_02/sol2.py @@ -1,15 +1,45 @@ -def fib(n): - """ - Returns a list of all the even terms in the Fibonacci sequence that are less than n. +""" +Problem: +Each new term in the Fibonacci sequence is generated by adding the previous two +terms. By starting with 1 and 2, the first 10 terms will be: + + 1,2,3,5,8,13,21,34,55,89,.. + +By considering the terms in the Fibonacci sequence whose values do not exceed +n, find the sum of the even-valued terms. e.g. for n=10, we have {2,8}, sum is +10. +""" +from __future__ import print_function + +try: + raw_input # Python 2 +except NameError: + raw_input = input # Python 3 + + +def solution(n): + """Returns the sum of all fibonacci sequence even elements that are lower + or equals to n. + + >>> solution(10) + [2, 8] + >>> solution(15) + [2, 8] + >>> solution(2) + [2] + >>> solution(1) + [] + >>> solution(34) + [2, 8, 34] """ ls = [] a, b = 0, 1 - while b < n: + while b <= n: if b % 2 == 0: ls.append(b) - a, b = b, a+b + a, b = b, a + b return ls -if __name__ == '__main__': - n = int(input("Enter max number: ").strip()) - print(sum(fib(n))) + +if __name__ == "__main__": + print(solution(int(raw_input().strip()))) diff --git a/project_euler/problem_02/sol3.py b/project_euler/problem_02/sol3.py index 0eb46d879704..abd9d6c753b8 100644 --- a/project_euler/problem_02/sol3.py +++ b/project_euler/problem_02/sol3.py @@ -1,18 +1,47 @@ -''' +""" Problem: -Each new term in the Fibonacci sequence is generated by adding the previous two terms. - 0,1,1,2,3,5,8,13,21,34,55,89,.. -Every third term from 0 is even So using this I have written a simple code -By considering the terms in the Fibonacci sequence whose values do not exceed n, find the sum of the even-valued terms. -e.g. for n=10, we have {2,8}, sum is 10. -''' -"""Python 3""" -n = int(input()) -a=0 -b=2 -count=0 -while 4*b+a>> solution(10) + 10 + >>> solution(15) + 10 + >>> solution(2) + 2 + >>> solution(1) + 0 + >>> solution(34) + 44 + """ + if n <= 1: + return 0 + a = 0 + b = 2 + count = 0 + while 4 * b + a <= n: + a, b = b, 4 * b + a + count += a + return count + b + + +if __name__ == "__main__": + print(solution(int(raw_input().strip()))) diff --git a/project_euler/problem_02/sol4.py b/project_euler/problem_02/sol4.py index 64bae65f49b4..ba13b12a15e9 100644 --- a/project_euler/problem_02/sol4.py +++ b/project_euler/problem_02/sol4.py @@ -1,13 +1,47 @@ +""" +Problem: +Each new term in the Fibonacci sequence is generated by adding the previous two +terms. By starting with 1 and 2, the first 10 terms will be: + + 1,2,3,5,8,13,21,34,55,89,.. + +By considering the terms in the Fibonacci sequence whose values do not exceed +n, find the sum of the even-valued terms. e.g. for n=10, we have {2,8}, sum is +10. +""" +from __future__ import print_function import math from decimal import * -getcontext().prec = 100 -phi = (Decimal(5) ** Decimal(0.5) + 1) / Decimal(2) +try: + raw_input # Python 2 +except NameError: + raw_input = input # Python 3 + + +def solution(n): + """Returns the sum of all fibonacci sequence even elements that are lower + or equals to n. + + >>> solution(10) + 10 + >>> solution(15) + 10 + >>> solution(2) + 2 + >>> solution(1) + 0 + >>> solution(34) + 44 + """ + getcontext().prec = 100 + phi = (Decimal(5) ** Decimal(0.5) + 1) / Decimal(2) -n = Decimal(int(input()) - 1) + index = (math.floor(math.log(n * (phi + 2), phi) - 1) // 3) * 3 + 2 + num = Decimal(round(phi ** Decimal(index + 1))) / (phi + 2) + sum = num // 2 + return int(sum) -index = (math.floor(math.log(n * (phi + 2), phi) - 1) // 3) * 3 + 2 -num = round(phi ** Decimal(index + 1)) / (phi + 2) -sum = num // 2 -print(int(sum)) +if __name__ == "__main__": + print(solution(int(raw_input().strip()))) diff --git a/project_euler/problem_03/__init__.py b/project_euler/problem_03/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/project_euler/problem_03/sol1.py b/project_euler/problem_03/sol1.py index bb9f8ca9ad12..c2e601bd0040 100644 --- a/project_euler/problem_03/sol1.py +++ b/project_euler/problem_03/sol1.py @@ -1,39 +1,61 @@ -''' +""" Problem: -The prime factors of 13195 are 5,7,13 and 29. What is the largest prime factor of a given number N? +The prime factors of 13195 are 5,7,13 and 29. What is the largest prime factor +of a given number N? + e.g. for 10, largest prime factor = 5. For 17, largest prime factor = 17. -''' +""" from __future__ import print_function, division - import math +try: + raw_input # Python 2 +except NameError: + raw_input = input # Python 3 + + def isprime(no): - if(no==2): + if no == 2: return True - elif (no%2==0): + elif no % 2 == 0: return False - sq = int(math.sqrt(no))+1 - for i in range(3,sq,2): - if(no%i==0): + sq = int(math.sqrt(no)) + 1 + for i in range(3, sq, 2): + if no % i == 0: return False return True -maxNumber = 0 -n=int(input()) -if(isprime(n)): - print(n) -else: - while (n%2==0): - n=n/2 - if(isprime(n)): - print(n) + +def solution(n): + """Returns the largest prime factor of a given number n. + + >>> solution(13195) + 29 + >>> solution(10) + 5 + >>> solution(17) + 17 + """ + maxNumber = 0 + if isprime(n): + return n else: - n1 = int(math.sqrt(n))+1 - for i in range(3,n1,2): - if(n%i==0): - if(isprime(n/i)): - maxNumber = n/i - break - elif(isprime(i)): - maxNumber = i - print(maxNumber) + while n % 2 == 0: + n = n / 2 + if isprime(n): + return int(n) + else: + n1 = int(math.sqrt(n)) + 1 + for i in range(3, n1, 2): + if n % i == 0: + if isprime(n / i): + maxNumber = n / i + break + elif isprime(i): + maxNumber = i + return maxNumber + return int(sum) + + +if __name__ == "__main__": + print(solution(int(raw_input().strip()))) diff --git a/project_euler/problem_03/sol2.py b/project_euler/problem_03/sol2.py index 44f9c63dfb6a..497db3965cc3 100644 --- a/project_euler/problem_03/sol2.py +++ b/project_euler/problem_03/sol2.py @@ -1,18 +1,40 @@ -''' +""" Problem: -The prime factors of 13195 are 5,7,13 and 29. What is the largest prime factor of a given number N? +The prime factors of 13195 are 5,7,13 and 29. What is the largest prime factor +of a given number N? + e.g. for 10, largest prime factor = 5. For 17, largest prime factor = 17. -''' +""" +from __future__ import print_function, division +import math + +try: + raw_input # Python 2 +except NameError: + raw_input = input # Python 3 + + +def solution(n): + """Returns the largest prime factor of a given number n. + + >>> solution(13195) + 29 + >>> solution(10) + 5 + >>> solution(17) + 17 + """ + prime = 1 + i = 2 + while i * i <= n: + while n % i == 0: + prime = i + n //= i + i += 1 + if n > 1: + prime = n + return int(prime) + -from __future__ import print_function -n=int(input()) -prime=1 -i=2 -while(i*i<=n): - while(n%i==0): - prime=i - n//=i - i+=1 -if(n>1): - prime=n -print(prime) +if __name__ == "__main__": + print(solution(int(raw_input().strip()))) diff --git a/project_euler/problem_04/__init__.py b/project_euler/problem_04/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/project_euler/problem_04/sol1.py b/project_euler/problem_04/sol1.py index 05fdd9ebab55..7a255f7308e6 100644 --- a/project_euler/problem_04/sol1.py +++ b/project_euler/problem_04/sol1.py @@ -1,29 +1,50 @@ -''' +""" Problem: -A palindromic number reads the same both ways. The largest palindrome made from the product of two 2-digit numbers is 9009 = 91 x 99. -Find the largest palindrome made from the product of two 3-digit numbers which is less than N. -''' +A palindromic number reads the same both ways. The largest palindrome made from +the product of two 2-digit numbers is 9009 = 91 x 99. + +Find the largest palindrome made from the product of two 3-digit numbers which +is less than N. +""" from __future__ import print_function -limit = int(input("limit? ")) -# fetchs the next number -for number in range(limit-1,10000,-1): +try: + raw_input # Python 2 +except NameError: + raw_input = input # Python 3 + + +def solution(n): + """Returns the largest palindrome made from the product of two 3-digit + numbers which is less than n. + + >>> solution(20000) + 19591 + >>> solution(30000) + 29992 + >>> solution(40000) + 39893 + """ + # fetchs the next number + for number in range(n - 1, 10000, -1): - # converts number into string. - strNumber = str(number) + # converts number into string. + strNumber = str(number) - # checks whether 'strNumber' is a palindrome. - if(strNumber == strNumber[::-1]): + # checks whether 'strNumber' is a palindrome. + if strNumber == strNumber[::-1]: - divisor = 999 + divisor = 999 - # if 'number' is a product of two 3-digit numbers - # then number is the answer otherwise fetch next number. - while(divisor != 99): - - if((number % divisor == 0) and (len(str(number / divisor)) == 3)): + # if 'number' is a product of two 3-digit numbers + # then number is the answer otherwise fetch next number. + while divisor != 99: + if (number % divisor == 0) and ( + len(str(int(number / divisor))) == 3 + ): + return number + divisor -= 1 - print(number) - exit(0) - divisor -=1 +if __name__ == "__main__": + print(solution(int(raw_input().strip()))) diff --git a/project_euler/problem_04/sol2.py b/project_euler/problem_04/sol2.py index 70810c38986f..45c6b256daf8 100644 --- a/project_euler/problem_04/sol2.py +++ b/project_euler/problem_04/sol2.py @@ -1,17 +1,38 @@ -''' +""" Problem: -A palindromic number reads the same both ways. The largest palindrome made from the product of two 2-digit numbers is 9009 = 91 x 99. -Find the largest palindrome made from the product of two 3-digit numbers which is less than N. -''' +A palindromic number reads the same both ways. The largest palindrome made from +the product of two 2-digit numbers is 9009 = 91 x 99. + +Find the largest palindrome made from the product of two 3-digit numbers which +is less than N. +""" from __future__ import print_function -n = int(input().strip()) -answer = 0 -for i in range(999,99,-1): #3 digit nimbers range from 999 down to 100 - for j in range(999,99,-1): - t = str(i*j) - if t == t[::-1] and i*j < n: - answer = max(answer,i*j) -print(answer) -exit(0) + +try: + raw_input # Python 2 +except NameError: + raw_input = input # Python 3 + + +def solution(n): + """Returns the largest palindrome made from the product of two 3-digit + numbers which is less than n. + + >>> solution(20000) + 19591 + >>> solution(30000) + 29992 + >>> solution(40000) + 39893 + """ + answer = 0 + for i in range(999, 99, -1): # 3 digit nimbers range from 999 down to 100 + for j in range(999, 99, -1): + t = str(i * j) + if t == t[::-1] and i * j < n: + answer = max(answer, i * j) + return answer +if __name__ == "__main__": + print(solution(int(raw_input().strip()))) diff --git a/project_euler/problem_05/__init__.py b/project_euler/problem_05/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/project_euler/problem_05/sol1.py b/project_euler/problem_05/sol1.py index 7896d75e3456..609f02102a08 100644 --- a/project_euler/problem_05/sol1.py +++ b/project_euler/problem_05/sol1.py @@ -1,21 +1,46 @@ -''' +""" Problem: -2520 is the smallest number that can be divided by each of the numbers from 1 to 10 without any remainder. -What is the smallest positive number that is evenly divisible(divisible with no remainder) by all of the numbers from 1 to N? -''' +2520 is the smallest number that can be divided by each of the numbers from 1 +to 10 without any remainder. + +What is the smallest positive number that is evenly divisible(divisible with no +remainder) by all of the numbers from 1 to N? +""" from __future__ import print_function -n = int(input()) -i = 0 -while 1: - i+=n*(n-1) - nfound=0 - for j in range(2,n): - if (i%j != 0): - nfound=1 +try: + raw_input # Python 2 +except NameError: + raw_input = input # Python 3 + + +def solution(n): + """Returns the smallest positive number that is evenly divisible(divisible + with no remainder) by all of the numbers from 1 to n. + + >>> solution(10) + 2520 + >>> solution(15) + 360360 + >>> solution(20) + 232792560 + >>> solution(22) + 232792560 + """ + i = 0 + while 1: + i += n * (n - 1) + nfound = 0 + for j in range(2, n): + if i % j != 0: + nfound = 1 + break + if nfound == 0: + if i == 0: + i = 1 + return i break - if(nfound==0): - if(i==0): - i=1 - print(i) - break + + +if __name__ == "__main__": + print(solution(int(raw_input().strip()))) diff --git a/project_euler/problem_05/sol2.py b/project_euler/problem_05/sol2.py index cd11437f30db..293dd96f2294 100644 --- a/project_euler/problem_05/sol2.py +++ b/project_euler/problem_05/sol2.py @@ -1,20 +1,50 @@ -#!/bin/python3 -''' +""" Problem: -2520 is the smallest number that can be divided by each of the numbers from 1 to 10 without any remainder. -What is the smallest positive number that is evenly divisible(divisible with no remainder) by all of the numbers from 1 to N? -''' +2520 is the smallest number that can be divided by each of the numbers from 1 +to 10 without any remainder. + +What is the smallest positive number that is evenly divisible(divisible with no +remainder) by all of the numbers from 1 to N? +""" +from __future__ import print_function + +try: + raw_input # Python 2 +except NameError: + raw_input = input # Python 3 """ Euclidean GCD Algorithm """ -def gcd(x,y): - return x if y==0 else gcd(y,x%y) + + +def gcd(x, y): + return x if y == 0 else gcd(y, x % y) + """ Using the property lcm*gcd of two numbers = product of them """ -def lcm(x,y): - return (x*y)//gcd(x,y) - -n = int(input()) -g=1 -for i in range(1,n+1): - g=lcm(g,i) -print(g) + + +def lcm(x, y): + return (x * y) // gcd(x, y) + + +def solution(n): + """Returns the smallest positive number that is evenly divisible(divisible + with no remainder) by all of the numbers from 1 to n. + + >>> solution(10) + 2520 + >>> solution(15) + 360360 + >>> solution(20) + 232792560 + >>> solution(22) + 232792560 + """ + g = 1 + for i in range(1, n + 1): + g = lcm(g, i) + return g + + +if __name__ == "__main__": + print(solution(int(raw_input().strip()))) diff --git a/project_euler/problem_06/__init__.py b/project_euler/problem_06/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/project_euler/problem_06/sol1.py b/project_euler/problem_06/sol1.py index 852d4e2f9fc4..728701e167c3 100644 --- a/project_euler/problem_06/sol1.py +++ b/project_euler/problem_06/sol1.py @@ -1,20 +1,48 @@ # -*- coding: utf-8 -*- -''' +""" Problem: + The sum of the squares of the first ten natural numbers is, 1^2 + 2^2 + ... + 10^2 = 385 + The square of the sum of the first ten natural numbers is, (1 + 2 + ... + 10)^2 = 552 = 3025 -Hence the difference between the sum of the squares of the first ten natural numbers and the square of the sum is 3025 − 385 = 2640. -Find the difference between the sum of the squares of the first N natural numbers and the square of the sum. -''' + +Hence the difference between the sum of the squares of the first ten natural +numbers and the square of the sum is 3025 − 385 = 2640. + +Find the difference between the sum of the squares of the first N natural +numbers and the square of the sum. +""" from __future__ import print_function -suma = 0 -sumb = 0 -n = int(input()) -for i in range(1,n+1): - suma += i**2 - sumb += i -sum = sumb**2 - suma -print(sum) +try: + raw_input # Python 2 +except NameError: + raw_input = input # Python 3 + + +def solution(n): + """Returns the difference between the sum of the squares of the first n + natural numbers and the square of the sum. + + >>> solution(10) + 2640 + >>> solution(15) + 13160 + >>> solution(20) + 41230 + >>> solution(50) + 1582700 + """ + suma = 0 + sumb = 0 + for i in range(1, n + 1): + suma += i ** 2 + sumb += i + sum = sumb ** 2 - suma + return sum + + +if __name__ == "__main__": + print(solution(int(raw_input().strip()))) diff --git a/project_euler/problem_06/sol2.py b/project_euler/problem_06/sol2.py index aa8aea58fd7b..2c64812d56f8 100644 --- a/project_euler/problem_06/sol2.py +++ b/project_euler/problem_06/sol2.py @@ -1,16 +1,45 @@ # -*- coding: utf-8 -*- -''' +""" Problem: + The sum of the squares of the first ten natural numbers is, 1^2 + 2^2 + ... + 10^2 = 385 + The square of the sum of the first ten natural numbers is, (1 + 2 + ... + 10)^2 = 552 = 3025 -Hence the difference between the sum of the squares of the first ten natural numbers and the square of the sum is 3025 − 385 = 2640. -Find the difference between the sum of the squares of the first N natural numbers and the square of the sum. -''' + +Hence the difference between the sum of the squares of the first ten natural +numbers and the square of the sum is 3025 − 385 = 2640. + +Find the difference between the sum of the squares of the first N natural +numbers and the square of the sum. +""" from __future__ import print_function -n = int(input()) -suma = n*(n+1)/2 -suma **= 2 -sumb = n*(n+1)*(2*n+1)/6 -print(suma-sumb) + +try: + raw_input # Python 2 +except NameError: + raw_input = input # Python 3 + + +def solution(n): + """Returns the difference between the sum of the squares of the first n + natural numbers and the square of the sum. + + >>> solution(10) + 2640 + >>> solution(15) + 13160 + >>> solution(20) + 41230 + >>> solution(50) + 1582700 + """ + suma = n * (n + 1) / 2 + suma **= 2 + sumb = n * (n + 1) * (2 * n + 1) / 6 + return int(suma - sumb) + + +if __name__ == "__main__": + print(solution(int(raw_input().strip()))) diff --git a/project_euler/problem_06/sol3.py b/project_euler/problem_06/sol3.py index b2d9f444d9a9..7d94b1e2254f 100644 --- a/project_euler/problem_06/sol3.py +++ b/project_euler/problem_06/sol3.py @@ -1,20 +1,45 @@ -''' +# -*- coding: utf-8 -*- +""" Problem: + The sum of the squares of the first ten natural numbers is, 1^2 + 2^2 + ... + 10^2 = 385 + The square of the sum of the first ten natural numbers is, (1 + 2 + ... + 10)^2 = 552 = 3025 -Hence the difference between the sum of the squares of the first ten natural numbers and the square of the sum is 3025 − 385 = 2640. -Find the difference between the sum of the squares of the first N natural numbers and the square of the sum. -''' + +Hence the difference between the sum of the squares of the first ten natural +numbers and the square of the sum is 3025 − 385 = 2640. + +Find the difference between the sum of the squares of the first N natural +numbers and the square of the sum. +""" from __future__ import print_function import math -def problem6(number=100): - sum_of_squares = sum([i*i for i in range(1,number+1)]) - square_of_sum = int(math.pow(sum(range(1,number+1)),2)) + +try: + raw_input # Python 2 +except NameError: + raw_input = input # Python 3 + + +def solution(n): + """Returns the difference between the sum of the squares of the first n + natural numbers and the square of the sum. + + >>> solution(10) + 2640 + >>> solution(15) + 13160 + >>> solution(20) + 41230 + >>> solution(50) + 1582700 + """ + sum_of_squares = sum([i * i for i in range(1, n + 1)]) + square_of_sum = int(math.pow(sum(range(1, n + 1)), 2)) return square_of_sum - sum_of_squares -def main(): - print(problem6()) -if __name__ == '__main__': - main() \ No newline at end of file + +if __name__ == "__main__": + print(solution(int(raw_input().strip()))) diff --git a/project_euler/problem_07/__init__.py b/project_euler/problem_07/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/project_euler/problem_07/sol1.py b/project_euler/problem_07/sol1.py index ea31d0b2bb2c..403ded568dda 100644 --- a/project_euler/problem_07/sol1.py +++ b/project_euler/problem_07/sol1.py @@ -1,30 +1,61 @@ -''' +# -*- coding: utf-8 -*- +""" By listing the first six prime numbers: -2, 3, 5, 7, 11, and 13, we can see that the 6th prime is 13. -What is the Nth prime number? -''' + + 2, 3, 5, 7, 11, and 13 + +We can see that the 6th prime is 13. What is the Nth prime number? +""" from __future__ import print_function from math import sqrt + +try: + raw_input # Python 2 +except NameError: + raw_input = input # Python 3 + + def isprime(n): - if (n==2): + if n == 2: return True - elif (n%2==0): + elif n % 2 == 0: return False else: - sq = int(sqrt(n))+1 - for i in range(3,sq,2): - if(n%i==0): + sq = int(sqrt(n)) + 1 + for i in range(3, sq, 2): + if n % i == 0: return False return True -n = int(input()) -i=0 -j=1 -while(i!=n and j<3): - j+=1 - if (isprime(j)): - i+=1 -while(i!=n): - j+=2 - if(isprime(j)): - i+=1 -print(j) + + +def solution(n): + """Returns the n-th prime number. + + >>> solution(6) + 13 + >>> solution(1) + 2 + >>> solution(3) + 5 + >>> solution(20) + 71 + >>> solution(50) + 229 + >>> solution(100) + 541 + """ + i = 0 + j = 1 + while i != n and j < 3: + j += 1 + if isprime(j): + i += 1 + while i != n: + j += 2 + if isprime(j): + i += 1 + return j + + +if __name__ == "__main__": + print(solution(int(raw_input().strip()))) diff --git a/project_euler/problem_07/sol2.py b/project_euler/problem_07/sol2.py index fdf39cbc4d26..630e5196796d 100644 --- a/project_euler/problem_07/sol2.py +++ b/project_euler/problem_07/sol2.py @@ -1,16 +1,53 @@ -# By listing the first six prime numbers: 2, 3, 5, 7, 11, and 13, we can see that the 6th prime is 13. What is the Nth prime number? +# -*- coding: utf-8 -*- +""" +By listing the first six prime numbers: + + 2, 3, 5, 7, 11, and 13 + +We can see that the 6th prime is 13. What is the Nth prime number? +""" +from __future__ import print_function +from math import sqrt + +try: + raw_input # Python 2 +except NameError: + raw_input = input # Python 3 + + def isprime(number): - for i in range(2,int(number**0.5)+1): - if number%i==0: - return False - return True -n = int(input('Enter The N\'th Prime Number You Want To Get: ')) # Ask For The N'th Prime Number Wanted -primes = [] -num = 2 -while len(primes) < n: - if isprime(num): - primes.append(num) - num += 1 - else: - num += 1 -print(primes[len(primes) - 1]) + for i in range(2, int(number ** 0.5) + 1): + if number % i == 0: + return False + return True + + +def solution(n): + """Returns the n-th prime number. + + >>> solution(6) + 13 + >>> solution(1) + 2 + >>> solution(3) + 5 + >>> solution(20) + 71 + >>> solution(50) + 229 + >>> solution(100) + 541 + """ + primes = [] + num = 2 + while len(primes) < n: + if isprime(num): + primes.append(num) + num += 1 + else: + num += 1 + return primes[len(primes) - 1] + + +if __name__ == "__main__": + print(solution(int(raw_input().strip()))) diff --git a/project_euler/problem_07/sol3.py b/project_euler/problem_07/sol3.py index 0001e4318cc9..bc94762604b3 100644 --- a/project_euler/problem_07/sol3.py +++ b/project_euler/problem_07/sol3.py @@ -1,28 +1,53 @@ -''' +# -*- coding: utf-8 -*- +""" By listing the first six prime numbers: -2, 3, 5, 7, 11, and 13, we can see that the 6th prime is 13. -What is the Nth prime number? -''' + + 2, 3, 5, 7, 11, and 13 + +We can see that the 6th prime is 13. What is the Nth prime number? +""" from __future__ import print_function -# from Python.Math import PrimeCheck import math import itertools + +try: + raw_input # Python 2 +except NameError: + raw_input = input # Python 3 + + def primeCheck(number): if number % 2 == 0 and number > 2: return False return all(number % i for i in range(3, int(math.sqrt(number)) + 1, 2)) + def prime_generator(): num = 2 while True: if primeCheck(num): yield num - num+=1 + num += 1 + -def main(): - n = int(input('Enter The N\'th Prime Number You Want To Get: ')) # Ask For The N'th Prime Number Wanted - print(next(itertools.islice(prime_generator(),n-1,n))) +def solution(n): + """Returns the n-th prime number. + + >>> solution(6) + 13 + >>> solution(1) + 2 + >>> solution(3) + 5 + >>> solution(20) + 71 + >>> solution(50) + 229 + >>> solution(100) + 541 + """ + return next(itertools.islice(prime_generator(), n - 1, n)) -if __name__ == '__main__': - main() \ No newline at end of file +if __name__ == "__main__": + print(solution(int(raw_input().strip()))) diff --git a/project_euler/problem_08/__init__.py b/project_euler/problem_08/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/project_euler/problem_08/sol1.py b/project_euler/problem_08/sol1.py index 817fd3f87507..6752fae3de60 100644 --- a/project_euler/problem_08/sol1.py +++ b/project_euler/problem_08/sol1.py @@ -1,15 +1,72 @@ +# -*- coding: utf-8 -*- +""" +The four adjacent digits in the 1000-digit number that have the greatest +product are 9 × 9 × 8 × 9 = 5832. + +73167176531330624919225119674426574742355349194934 +96983520312774506326239578318016984801869478851843 +85861560789112949495459501737958331952853208805511 +12540698747158523863050715693290963295227443043557 +66896648950445244523161731856403098711121722383113 +62229893423380308135336276614282806444486645238749 +30358907296290491560440772390713810515859307960866 +70172427121883998797908792274921901699720888093776 +65727333001053367881220235421809751254540594752243 +52584907711670556013604839586446706324415722155397 +53697817977846174064955149290862569321978468622482 +83972241375657056057490261407972968652414535100474 +82166370484403199890008895243450658541227588666881 +16427171479924442928230863465674813919123162824586 +17866458359124566529476545682848912883142607690042 +24219022671055626321111109370544217506941658960408 +07198403850962455444362981230987879927244284909188 +84580156166097919133875499200524063689912560717606 +05886116467109405077541002256983155200055935729725 +71636269561882670428252483600823257530420752963450 + +Find the thirteen adjacent digits in the 1000-digit number that have the +greatest product. What is the value of this product? +""" import sys -def main(): - LargestProduct = -sys.maxsize-1 - number=input().strip() - for i in range(len(number)-12): - product=1 + +N = """73167176531330624919225119674426574742355349194934\ +96983520312774506326239578318016984801869478851843\ +85861560789112949495459501737958331952853208805511\ +12540698747158523863050715693290963295227443043557\ +66896648950445244523161731856403098711121722383113\ +62229893423380308135336276614282806444486645238749\ +30358907296290491560440772390713810515859307960866\ +70172427121883998797908792274921901699720888093776\ +65727333001053367881220235421809751254540594752243\ +52584907711670556013604839586446706324415722155397\ +53697817977846174064955149290862569321978468622482\ +83972241375657056057490261407972968652414535100474\ +82166370484403199890008895243450658541227588666881\ +16427171479924442928230863465674813919123162824586\ +17866458359124566529476545682848912883142607690042\ +24219022671055626321111109370544217506941658960408\ +07198403850962455444362981230987879927244284909188\ +84580156166097919133875499200524063689912560717606\ +05886116467109405077541002256983155200055935729725\ +71636269561882670428252483600823257530420752963450""" + + +def solution(n): + """Find the thirteen adjacent digits in the 1000-digit number n that have + the greatest product and returns it. + + >>> solution(N) + 23514624000 + """ + LargestProduct = -sys.maxsize - 1 + for i in range(len(n) - 12): + product = 1 for j in range(13): - product *= int(number[i+j]) + product *= int(n[i + j]) if product > LargestProduct: LargestProduct = product - print(LargestProduct) + return LargestProduct -if __name__ == '__main__': - main() +if __name__ == "__main__": + print(solution(N)) diff --git a/project_euler/problem_08/sol2.py b/project_euler/problem_08/sol2.py index ae03f3ad0aa6..bae96e373d6c 100644 --- a/project_euler/problem_08/sol2.py +++ b/project_euler/problem_08/sol2.py @@ -1,8 +1,73 @@ +# -*- coding: utf-8 -*- +""" +The four adjacent digits in the 1000-digit number that have the greatest +product are 9 × 9 × 8 × 9 = 5832. + +73167176531330624919225119674426574742355349194934 +96983520312774506326239578318016984801869478851843 +85861560789112949495459501737958331952853208805511 +12540698747158523863050715693290963295227443043557 +66896648950445244523161731856403098711121722383113 +62229893423380308135336276614282806444486645238749 +30358907296290491560440772390713810515859307960866 +70172427121883998797908792274921901699720888093776 +65727333001053367881220235421809751254540594752243 +52584907711670556013604839586446706324415722155397 +53697817977846174064955149290862569321978468622482 +83972241375657056057490261407972968652414535100474 +82166370484403199890008895243450658541227588666881 +16427171479924442928230863465674813919123162824586 +17866458359124566529476545682848912883142607690042 +24219022671055626321111109370544217506941658960408 +07198403850962455444362981230987879927244284909188 +84580156166097919133875499200524063689912560717606 +05886116467109405077541002256983155200055935729725 +71636269561882670428252483600823257530420752963450 + +Find the thirteen adjacent digits in the 1000-digit number that have the +greatest product. What is the value of this product? +""" + from functools import reduce -def main(): - number=input().strip() - print(max([reduce(lambda x,y: int(x)*int(y),number[i:i+13]) for i in range(len(number)-12)])) - -if __name__ == '__main__': - main() +N = ( + "73167176531330624919225119674426574742355349194934" + "96983520312774506326239578318016984801869478851843" + "85861560789112949495459501737958331952853208805511" + "12540698747158523863050715693290963295227443043557" + "66896648950445244523161731856403098711121722383113" + "62229893423380308135336276614282806444486645238749" + "30358907296290491560440772390713810515859307960866" + "70172427121883998797908792274921901699720888093776" + "65727333001053367881220235421809751254540594752243" + "52584907711670556013604839586446706324415722155397" + "53697817977846174064955149290862569321978468622482" + "83972241375657056057490261407972968652414535100474" + "82166370484403199890008895243450658541227588666881" + "16427171479924442928230863465674813919123162824586" + "17866458359124566529476545682848912883142607690042" + "24219022671055626321111109370544217506941658960408" + "07198403850962455444362981230987879927244284909188" + "84580156166097919133875499200524063689912560717606" + "05886116467109405077541002256983155200055935729725" + "71636269561882670428252483600823257530420752963450" +) + + +def solution(n): + """Find the thirteen adjacent digits in the 1000-digit number n that have + the greatest product and returns it. + + >>> solution(N) + 23514624000 + """ + return max( + [ + reduce(lambda x, y: int(x) * int(y), n[i : i + 13]) + for i in range(len(n) - 12) + ] + ) + + +if __name__ == "__main__": + print(solution(str(N))) diff --git a/project_euler/problem_09/__init__.py b/project_euler/problem_09/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/project_euler/problem_09/sol1.py b/project_euler/problem_09/sol1.py index e54c543b4721..0f368e48d2e3 100644 --- a/project_euler/problem_09/sol1.py +++ b/project_euler/problem_09/sol1.py @@ -1,15 +1,36 @@ +""" +Problem Statement: +A Pythagorean triplet is a set of three natural numbers, a < b < c, for which, + a^2 + b^2 = c^2 +For example, 3^2 + 4^2 = 9 + 16 = 25 = 5^2. + +There exists exactly one Pythagorean triplet for which a + b + c = 1000. +Find the product abc. +""" from __future__ import print_function -# Program to find the product of a,b,c which are Pythagorean Triplet that satisfice the following: -# 1. a < b < c -# 2. a**2 + b**2 = c**2 -# 3. a + b + c = 1000 - -print("Please Wait...") -for a in range(300): - for b in range(400): - for c in range(500): - if(a < b < c): - if((a**2) + (b**2) == (c**2)): - if((a+b+c) == 1000): - print(("Product of",a,"*",b,"*",c,"=",(a*b*c))) - break + + +def solution(): + """ + Returns the product of a,b,c which are Pythagorean Triplet that satisfies + the following: + 1. a < b < c + 2. a**2 + b**2 = c**2 + 3. a + b + c = 1000 + + >>> solution() + 31875000 + """ + for a in range(300): + for b in range(400): + for c in range(500): + if a < b < c: + if (a ** 2) + (b ** 2) == (c ** 2): + if (a + b + c) == 1000: + return a * b * c + break + + +if __name__ == "__main__": + print("Please Wait...") + print(solution()) diff --git a/project_euler/problem_09/sol2.py b/project_euler/problem_09/sol2.py index 933f5c557d71..674daae9ec8e 100644 --- a/project_euler/problem_09/sol2.py +++ b/project_euler/problem_09/sol2.py @@ -1,18 +1,44 @@ -"""A Pythagorean triplet is a set of three natural numbers, for which, -a^2+b^2=c^2 -Given N, Check if there exists any Pythagorean triplet for which a+b+c=N -Find maximum possible value of product of a,b,c among all such Pythagorean triplets, If there is no such Pythagorean triplet print -1.""" -#!/bin/python3 +""" +Problem Statement: +A Pythagorean triplet is a set of three natural numbers, a < b < c, for which, + a^2 + b^2 = c^2 +For example, 3^2 + 4^2 = 9 + 16 = 25 = 5^2. -product=-1 -d=0 -N = int(input()) -for a in range(1,N//3): - """Solving the two equations a**2+b**2=c**2 and a+b+c=N eliminating c """ - b=(N*N-2*a*N)//(2*N-2*a) - c=N-a-b - if c*c==(a*a+b*b): - d=(a*b*c) - if d>=product: - product=d -print(product) +There exists exactly one Pythagorean triplet for which a + b + c = 1000. +Find the product abc. +""" +from __future__ import print_function + +try: + raw_input # Python 2 +except NameError: + raw_input = input # Python 3 + + +def solution(n): + """ + Return the product of a,b,c which are Pythagorean Triplet that satisfies + the following: + 1. a < b < c + 2. a**2 + b**2 = c**2 + 3. a + b + c = 1000 + + >>> solution(1000) + 31875000 + """ + product = -1 + d = 0 + for a in range(1, n // 3): + """Solving the two equations a**2+b**2=c**2 and a+b+c=N eliminating c + """ + b = (n * n - 2 * a * n) // (2 * n - 2 * a) + c = n - a - b + if c * c == (a * a + b * b): + d = a * b * c + if d >= product: + product = d + return product + + +if __name__ == "__main__": + print(solution(int(raw_input().strip()))) diff --git a/project_euler/problem_09/sol3.py b/project_euler/problem_09/sol3.py index 5ebf38e76e1a..f749b8a61f11 100644 --- a/project_euler/problem_09/sol3.py +++ b/project_euler/problem_09/sol3.py @@ -1,6 +1,37 @@ -def main(): - print([a*b*c for a in range(1,999) for b in range(a,999) for c in range(b,999) - if (a*a+b*b==c*c) and (a+b+c==1000 ) ][0]) - -if __name__ == '__main__': - main() +""" +Problem Statement: + +A Pythagorean triplet is a set of three natural numbers, a < b < c, for which, + + a^2 + b^2 = c^2 + +For example, 3^2 + 4^2 = 9 + 16 = 25 = 5^2. + +There exists exactly one Pythagorean triplet for which a + b + c = 1000. +Find the product abc. +""" +from __future__ import print_function + + +def solution(): + """ + Returns the product of a,b,c which are Pythagorean Triplet that satisfies + the following: + + 1. a**2 + b**2 = c**2 + 2. a + b + c = 1000 + + >>> solution() + 31875000 + """ + return [ + a * b * c + for a in range(1, 999) + for b in range(a, 999) + for c in range(b, 999) + if (a * a + b * b == c * c) and (a + b + c == 1000) + ][0] + + +if __name__ == "__main__": + print(solution()) diff --git a/project_euler/problem_10/__init__.py b/project_euler/problem_10/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/project_euler/problem_10/sol1.py b/project_euler/problem_10/sol1.py index 94e5b7362114..038da96e6352 100644 --- a/project_euler/problem_10/sol1.py +++ b/project_euler/problem_10/sol1.py @@ -1,38 +1,60 @@ +""" +Problem Statement: +The sum of the primes below 10 is 2 + 3 + 5 + 7 = 17. + +Find the sum of all the primes below two million. +""" from __future__ import print_function from math import sqrt try: - xrange #Python 2 + raw_input # Python 2 +except NameError: + raw_input = input # Python 3 + +try: + xrange # Python 2 except NameError: - xrange = range #Python 3 + xrange = range # Python 3 + def is_prime(n): - for i in xrange(2, int(sqrt(n))+1): - if n%i == 0: - return False + for i in xrange(2, int(sqrt(n)) + 1): + if n % i == 0: + return False + + return True - return True def sum_of_primes(n): - if n > 2: - sumOfPrimes = 2 - else: - return 0 - - for i in xrange(3, n, 2): - if is_prime(i): - sumOfPrimes += i - - return sumOfPrimes - -if __name__ == '__main__': - import sys - - if len(sys.argv) == 1: - print(sum_of_primes(2000000)) - else: - try: - n = int(sys.argv[1]) - print(sum_of_primes(n)) - except ValueError: - print('Invalid entry - please enter a number.') + if n > 2: + sumOfPrimes = 2 + else: + return 0 + + for i in xrange(3, n, 2): + if is_prime(i): + sumOfPrimes += i + + return sumOfPrimes + + +def solution(n): + """Returns the sum of all the primes below n. + + >>> solution(2000000) + 142913828922 + >>> solution(1000) + 76127 + >>> solution(5000) + 1548136 + >>> solution(10000) + 5736396 + >>> solution(7) + 10 + """ + return sum_of_primes(n) + + +if __name__ == "__main__": + print(solution(int(raw_input().strip()))) diff --git a/project_euler/problem_10/sol2.py b/project_euler/problem_10/sol2.py index 22df95c063e2..9e51d61b8749 100644 --- a/project_euler/problem_10/sol2.py +++ b/project_euler/problem_10/sol2.py @@ -1,22 +1,49 @@ -#from Python.Math import prime_generator -import math -from itertools import takewhile +""" +Problem Statement: +The sum of the primes below 10 is 2 + 3 + 5 + 7 = 17. + +Find the sum of all the primes below two million. +""" +from __future__ import print_function +import math +from itertools import takewhile + +try: + raw_input # Python 2 +except NameError: + raw_input = input # Python 3 + def primeCheck(number): if number % 2 == 0 and number > 2: return False return all(number % i for i in range(3, int(math.sqrt(number)) + 1, 2)) - + + def prime_generator(): num = 2 while True: if primeCheck(num): yield num - num+=1 - -def main(): - n = int(input('Enter The upper limit of prime numbers: ')) - print(sum(takewhile(lambda x: x < n,prime_generator()))) + num += 1 + + +def solution(n): + """Returns the sum of all the primes below n. -if __name__ == '__main__': - main() + >>> solution(2000000) + 142913828922 + >>> solution(1000) + 76127 + >>> solution(5000) + 1548136 + >>> solution(10000) + 5736396 + >>> solution(7) + 10 + """ + return sum(takewhile(lambda x: x < n, prime_generator())) + + +if __name__ == "__main__": + print(solution(int(raw_input().strip()))) diff --git a/project_euler/problem_11/__init__.py b/project_euler/problem_11/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/project_euler/problem_11/sol1.py b/project_euler/problem_11/sol1.py index b882dc449156..3bdddc89d917 100644 --- a/project_euler/problem_11/sol1.py +++ b/project_euler/problem_11/sol1.py @@ -1,6 +1,6 @@ -from __future__ import print_function -''' -What is the greatest product of four adjacent numbers (horizontally, vertically, or diagonally) in this 20x20 array? +""" +What is the greatest product of four adjacent numbers (horizontally, +vertically, or diagonally) in this 20x20 array? 08 02 22 97 38 15 00 40 00 75 04 05 07 78 52 12 50 77 91 08 49 49 99 40 17 81 18 57 60 87 17 40 98 43 69 48 04 56 62 00 @@ -22,47 +22,78 @@ 20 69 36 41 72 30 23 88 34 62 99 69 82 67 59 85 74 04 36 16 20 73 35 29 78 31 90 01 74 31 49 71 48 86 81 16 23 57 05 54 01 70 54 71 83 51 54 69 16 92 33 48 61 43 52 01 89 19 67 48 -''' +""" + +from __future__ import print_function +import os try: - xrange #Python 2 + xrange # Python 2 except NameError: - xrange = range #Python 2 + xrange = range # Python 2 + def largest_product(grid): - nColumns = len(grid[0]) - nRows = len(grid) + nColumns = len(grid[0]) + nRows = len(grid) + + largest = 0 + lrDiagProduct = 0 + rlDiagProduct = 0 + + # Check vertically, horizontally, diagonally at the same time (only works + # for nxn grid) + for i in xrange(nColumns): + for j in xrange(nRows - 3): + vertProduct = ( + grid[j][i] * grid[j + 1][i] * grid[j + 2][i] * grid[j + 3][i] + ) + horzProduct = ( + grid[i][j] * grid[i][j + 1] * grid[i][j + 2] * grid[i][j + 3] + ) + + # Left-to-right diagonal (\) product + if i < nColumns - 3: + lrDiagProduct = ( + grid[i][j] + * grid[i + 1][j + 1] + * grid[i + 2][j + 2] + * grid[i + 3][j + 3] + ) - largest = 0 - lrDiagProduct = 0 - rlDiagProduct = 0 + # Right-to-left diagonal(/) product + if i > 2: + rlDiagProduct = ( + grid[i][j] + * grid[i - 1][j + 1] + * grid[i - 2][j + 2] + * grid[i - 3][j + 3] + ) - #Check vertically, horizontally, diagonally at the same time (only works for nxn grid) - for i in xrange(nColumns): - for j in xrange(nRows-3): - vertProduct = grid[j][i]*grid[j+1][i]*grid[j+2][i]*grid[j+3][i] - horzProduct = grid[i][j]*grid[i][j+1]*grid[i][j+2]*grid[i][j+3] + maxProduct = max( + vertProduct, horzProduct, lrDiagProduct, rlDiagProduct + ) + if maxProduct > largest: + largest = maxProduct - #Left-to-right diagonal (\) product - if (i < nColumns-3): - lrDiagProduct = grid[i][j]*grid[i+1][j+1]*grid[i+2][j+2]*grid[i+3][j+3] + return largest - #Right-to-left diagonal(/) product - if (i > 2): - rlDiagProduct = grid[i][j]*grid[i-1][j+1]*grid[i-2][j+2]*grid[i-3][j+3] - maxProduct = max(vertProduct, horzProduct, lrDiagProduct, rlDiagProduct) - if maxProduct > largest: - largest = maxProduct +def solution(): + """Returns the sum of all the multiples of 3 or 5 below n. + + >>> solution() + 70600674 + """ + grid = [] + with open(os.path.dirname(__file__) + "/grid.txt") as file: + for line in file: + grid.append(line.strip("\n").split(" ")) - return largest + grid = [[int(i) for i in grid[j]] for j in xrange(len(grid))] -if __name__ == '__main__': - grid = [] - with open('grid.txt') as file: - for line in file: - grid.append(line.strip('\n').split(' ')) + return largest_product(grid) - grid = [[int(i) for i in grid[j]] for j in xrange(len(grid))] - print(largest_product(grid)) \ No newline at end of file +if __name__ == "__main__": + print(solution()) diff --git a/project_euler/problem_11/sol2.py b/project_euler/problem_11/sol2.py index b03395f01697..0a5785b42b2c 100644 --- a/project_euler/problem_11/sol2.py +++ b/project_euler/problem_11/sol2.py @@ -1,39 +1,90 @@ -def main(): - with open ("grid.txt", "r") as f: - l = [] - for i in range(20): - l.append([int(x) for x in f.readline().split()]) - - maximum = 0 - - # right - for i in range(20): - for j in range(17): - temp = l[i][j] * l[i][j+1] * l[i][j+2] * l[i][j+3] - if temp > maximum: - maximum = temp - - # down - for i in range(17): - for j in range(20): - temp = l[i][j] * l[i+1][j] * l[i+2][j] * l[i+3][j] - if temp > maximum: - maximum = temp - - #diagonal 1 - for i in range(17): - for j in range(17): - temp = l[i][j] * l[i+1][j+1] * l[i+2][j+2] * l[i+3][j+3] - if temp > maximum: - maximum = temp - - #diagonal 2 - for i in range(17): - for j in range(3, 20): - temp = l[i][j] * l[i+1][j-1] * l[i+2][j-2] * l[i+3][j-3] - if temp > maximum: - maximum = temp - print(maximum) - -if __name__ == '__main__': - main() \ No newline at end of file +""" +What is the greatest product of four adjacent numbers (horizontally, +vertically, or diagonally) in this 20x20 array? + +08 02 22 97 38 15 00 40 00 75 04 05 07 78 52 12 50 77 91 08 +49 49 99 40 17 81 18 57 60 87 17 40 98 43 69 48 04 56 62 00 +81 49 31 73 55 79 14 29 93 71 40 67 53 88 30 03 49 13 36 65 +52 70 95 23 04 60 11 42 69 24 68 56 01 32 56 71 37 02 36 91 +22 31 16 71 51 67 63 89 41 92 36 54 22 40 40 28 66 33 13 80 +24 47 32 60 99 03 45 02 44 75 33 53 78 36 84 20 35 17 12 50 +32 98 81 28 64 23 67 10 26 38 40 67 59 54 70 66 18 38 64 70 +67 26 20 68 02 62 12 20 95 63 94 39 63 08 40 91 66 49 94 21 +24 55 58 05 66 73 99 26 97 17 78 78 96 83 14 88 34 89 63 72 +21 36 23 09 75 00 76 44 20 45 35 14 00 61 33 97 34 31 33 95 +78 17 53 28 22 75 31 67 15 94 03 80 04 62 16 14 09 53 56 92 +16 39 05 42 96 35 31 47 55 58 88 24 00 17 54 24 36 29 85 57 +86 56 00 48 35 71 89 07 05 44 44 37 44 60 21 58 51 54 17 58 +19 80 81 68 05 94 47 69 28 73 92 13 86 52 17 77 04 89 55 40 +04 52 08 83 97 35 99 16 07 97 57 32 16 26 26 79 33 27 98 66 +88 36 68 87 57 62 20 72 03 46 33 67 46 55 12 32 63 93 53 69 +04 42 16 73 38 25 39 11 24 94 72 18 08 46 29 32 40 62 76 36 +20 69 36 41 72 30 23 88 34 62 99 69 82 67 59 85 74 04 36 16 +20 73 35 29 78 31 90 01 74 31 49 71 48 86 81 16 23 57 05 54 +01 70 54 71 83 51 54 69 16 92 33 48 61 43 52 01 89 19 67 48 +""" + +from __future__ import print_function +import os + +try: + xrange # Python 2 +except NameError: + xrange = range # Python 2 + + +def solution(): + """Returns the sum of all the multiples of 3 or 5 below n. + + >>> solution() + 70600674 + """ + with open(os.path.dirname(__file__) + "/grid.txt") as f: + l = [] + for i in xrange(20): + l.append([int(x) for x in f.readline().split()]) + + maximum = 0 + + # right + for i in xrange(20): + for j in xrange(17): + temp = l[i][j] * l[i][j + 1] * l[i][j + 2] * l[i][j + 3] + if temp > maximum: + maximum = temp + + # down + for i in xrange(17): + for j in xrange(20): + temp = l[i][j] * l[i + 1][j] * l[i + 2][j] * l[i + 3][j] + if temp > maximum: + maximum = temp + + # diagonal 1 + for i in xrange(17): + for j in xrange(17): + temp = ( + l[i][j] + * l[i + 1][j + 1] + * l[i + 2][j + 2] + * l[i + 3][j + 3] + ) + if temp > maximum: + maximum = temp + + # diagonal 2 + for i in xrange(17): + for j in xrange(3, 20): + temp = ( + l[i][j] + * l[i + 1][j - 1] + * l[i + 2][j - 2] + * l[i + 3][j - 3] + ) + if temp > maximum: + maximum = temp + return maximum + + +if __name__ == "__main__": + print(solution()) diff --git a/project_euler/problem_12/__init__.py b/project_euler/problem_12/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/project_euler/problem_12/sol1.py b/project_euler/problem_12/sol1.py index 73d48a2ec897..baf9babab686 100644 --- a/project_euler/problem_12/sol1.py +++ b/project_euler/problem_12/sol1.py @@ -1,9 +1,9 @@ -from __future__ import print_function -from math import sqrt -''' +""" Highly divisible triangular numbers Problem 12 -The sequence of triangle numbers is generated by adding the natural numbers. So the 7th triangle number would be 1 + 2 + 3 + 4 + 5 + 6 + 7 = 28. The first ten terms would be: +The sequence of triangle numbers is generated by adding the natural numbers. So +the 7th triangle number would be 1 + 2 + 3 + 4 + 5 + 6 + 7 = 28. The first ten +terms would be: 1, 3, 6, 10, 15, 21, 28, 36, 45, 55, ... @@ -18,31 +18,48 @@ 28: 1,2,4,7,14,28 We can see that 28 is the first triangle number to have over five divisors. -What is the value of the first triangle number to have over five hundred divisors? -''' +What is the value of the first triangle number to have over five hundred +divisors? +""" +from __future__ import print_function +from math import sqrt + try: - xrange #Python 2 + xrange # Python 2 except NameError: - xrange = range #Python 3 + xrange = range # Python 3 + def count_divisors(n): - nDivisors = 0 - for i in xrange(1, int(sqrt(n))+1): - if n%i == 0: - nDivisors += 2 - #check if n is perfect square - if n**0.5 == int(n**0.5): - nDivisors -= 1 - return nDivisors - -tNum = 1 -i = 1 - -while True: - i += 1 - tNum += i - - if count_divisors(tNum) > 500: - break - -print(tNum) + nDivisors = 0 + for i in xrange(1, int(sqrt(n)) + 1): + if n % i == 0: + nDivisors += 2 + # check if n is perfect square + if n ** 0.5 == int(n ** 0.5): + nDivisors -= 1 + return nDivisors + + +def solution(): + """Returns the value of the first triangle number to have over five hundred + divisors. + + >>> solution() + 76576500 + """ + tNum = 1 + i = 1 + + while True: + i += 1 + tNum += i + + if count_divisors(tNum) > 500: + break + + return tNum + + +if __name__ == "__main__": + print(solution()) diff --git a/project_euler/problem_12/sol2.py b/project_euler/problem_12/sol2.py index 479ab2b900cb..071d7516ac0f 100644 --- a/project_euler/problem_12/sol2.py +++ b/project_euler/problem_12/sol2.py @@ -1,8 +1,51 @@ -def triangle_number_generator(): - for n in range(1,1000000): - yield n*(n+1)//2 - -def count_divisors(n): - return sum([2 for i in range(1,int(n**0.5)+1) if n%i==0 and i*i != n]) - -print(next(i for i in triangle_number_generator() if count_divisors(i) > 500)) +""" +Highly divisible triangular numbers +Problem 12 +The sequence of triangle numbers is generated by adding the natural numbers. So +the 7th triangle number would be 1 + 2 + 3 + 4 + 5 + 6 + 7 = 28. The first ten +terms would be: + +1, 3, 6, 10, 15, 21, 28, 36, 45, 55, ... + +Let us list the factors of the first seven triangle numbers: + + 1: 1 + 3: 1,3 + 6: 1,2,3,6 +10: 1,2,5,10 +15: 1,3,5,15 +21: 1,3,7,21 +28: 1,2,4,7,14,28 +We can see that 28 is the first triangle number to have over five divisors. + +What is the value of the first triangle number to have over five hundred +divisors? +""" +from __future__ import print_function + + +def triangle_number_generator(): + for n in range(1, 1000000): + yield n * (n + 1) // 2 + + +def count_divisors(n): + return sum( + [2 for i in range(1, int(n ** 0.5) + 1) if n % i == 0 and i * i != n] + ) + + +def solution(): + """Returns the value of the first triangle number to have over five hundred + divisors. + + >>> solution() + 76576500 + """ + return next( + i for i in triangle_number_generator() if count_divisors(i) > 500 + ) + + +if __name__ == "__main__": + print(solution()) diff --git a/project_euler/problem_13/__init__.py b/project_euler/problem_13/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/project_euler/problem_13/sol1.py b/project_euler/problem_13/sol1.py index faaaad5e88c1..983347675b3f 100644 --- a/project_euler/problem_13/sol1.py +++ b/project_euler/problem_13/sol1.py @@ -1,14 +1,36 @@ -''' +""" Problem Statement: -Work out the first ten digits of the sum of the N 50-digit numbers. -''' +Work out the first ten digits of the sum of the following one-hundred 50-digit +numbers. +""" from __future__ import print_function +import os -n = int(input().strip()) +try: + raw_input # Python 2 +except NameError: + raw_input = input # Python 3 -array = [] -for i in range(n): - array.append(int(input().strip())) -print(str(sum(array))[:10]) +def solution(array): + """Returns the first ten digits of the sum of the array elements. + + >>> sum = 0 + >>> array = [] + >>> with open(os.path.dirname(__file__) + "/num.txt","r") as f: + ... for line in f: + ... array.append(int(line)) + ... + >>> solution(array) + '5537376230' + """ + return str(sum(array))[:10] + +if __name__ == "__main__": + n = int(input().strip()) + + array = [] + for i in range(n): + array.append(int(input().strip())) + print(solution(array)) diff --git a/project_euler/problem_13/sol2.py b/project_euler/problem_13/sol2.py deleted file mode 100644 index c1416bcd6e7d..000000000000 --- a/project_euler/problem_13/sol2.py +++ /dev/null @@ -1,5 +0,0 @@ -sum = 0 -with open("num.txt",'r') as f: - for line in f: - sum += int(line) -print(str(sum)[:10]) diff --git a/project_euler/problem_14/__init__.py b/project_euler/problem_14/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/project_euler/problem_14/sol1.py b/project_euler/problem_14/sol1.py index 9037f6eb8bd5..6b80cd7cb24b 100644 --- a/project_euler/problem_14/sol1.py +++ b/project_euler/problem_14/sol1.py @@ -1,21 +1,73 @@ +# -*- coding: utf-8 -*- +""" +Problem Statement: +The following iterative sequence is defined for the set of positive integers: + + n → n/2 (n is even) + n → 3n + 1 (n is odd) + +Using the rule above and starting with 13, we generate the following sequence: + + 13 → 40 → 20 → 10 → 5 → 16 → 8 → 4 → 2 → 1 + +It can be seen that this sequence (starting at 13 and finishing at 1) contains +10 terms. Although it has not been proved yet (Collatz Problem), it is thought +that all starting numbers finish at 1. + +Which starting number, under one million, produces the longest chain? +""" from __future__ import print_function -largest_number = 0 -pre_counter = 0 - -for input1 in range(750000,1000000): - counter = 1 - number = input1 - - while number > 1: - if number % 2 == 0: - number /=2 - counter += 1 - else: - number = (3*number)+1 - counter += 1 - - if counter > pre_counter: - largest_number = input1 - pre_counter = counter - -print(('Largest Number:',largest_number,'->',pre_counter,'digits')) + +try: + raw_input # Python 2 +except NameError: + raw_input = input # Python 3 + + +def solution(n): + """Returns the number under n that generates the longest sequence using the + formula: + n → n/2 (n is even) + n → 3n + 1 (n is odd) + + >>> solution(1000000) + {'counter': 525, 'largest_number': 837799} + >>> solution(200) + {'counter': 125, 'largest_number': 171} + >>> solution(5000) + {'counter': 238, 'largest_number': 3711} + >>> solution(15000) + {'counter': 276, 'largest_number': 13255} + """ + largest_number = 0 + pre_counter = 0 + + for input1 in range(n): + counter = 1 + number = input1 + + while number > 1: + if number % 2 == 0: + number /= 2 + counter += 1 + else: + number = (3 * number) + 1 + counter += 1 + + if counter > pre_counter: + largest_number = input1 + pre_counter = counter + return {"counter": pre_counter, "largest_number": largest_number} + + +if __name__ == "__main__": + result = solution(int(raw_input().strip())) + print( + ( + "Largest Number:", + result["largest_number"], + "->", + result["counter"], + "digits", + ) + ) diff --git a/project_euler/problem_14/sol2.py b/project_euler/problem_14/sol2.py index b9de42be1108..59fa79515148 100644 --- a/project_euler/problem_14/sol2.py +++ b/project_euler/problem_14/sol2.py @@ -1,16 +1,69 @@ +# -*- coding: utf-8 -*- +""" +Collatz conjecture: start with any positive integer n. Next term obtained from +the previous term as follows: + +If the previous term is even, the next term is one half the previous term. +If the previous term is odd, the next term is 3 times the previous term plus 1. +The conjecture states the sequence will always reach 1 regardless of starting +n. + +Problem Statement: +The following iterative sequence is defined for the set of positive integers: + + n → n/2 (n is even) + n → 3n + 1 (n is odd) + +Using the rule above and starting with 13, we generate the following sequence: + + 13 → 40 → 20 → 10 → 5 → 16 → 8 → 4 → 2 → 1 + +It can be seen that this sequence (starting at 13 and finishing at 1) contains +10 terms. Although it has not been proved yet (Collatz Problem), it is thought +that all starting numbers finish at 1. + +Which starting number, under one million, produces the longest chain? +""" +from __future__ import print_function + +try: + raw_input # Python 2 +except NameError: + raw_input = input # Python 3 + + def collatz_sequence(n): - """Collatz conjecture: start with any positive integer n.Next termis obtained from the previous term as follows: - if the previous term is even, the next term is one half the previous term. - If the previous term is odd, the next term is 3 times the previous term plus 1. - The conjecture states the sequence will always reach 1 regaardess of starting n.""" - sequence = [n] - while n != 1: - if n % 2 == 0:# even - n //= 2 - else: - n = 3*n +1 - sequence.append(n) - return sequence - -answer = max([(len(collatz_sequence(i)), i) for i in range(1,1000000)]) -print("Longest Collatz sequence under one million is %d with length %d" % (answer[1],answer[0])) \ No newline at end of file + """Returns the Collatz sequence for n.""" + sequence = [n] + while n != 1: + if n % 2 == 0: + n //= 2 + else: + n = 3 * n + 1 + sequence.append(n) + return sequence + + +def solution(n): + """Returns the number under n that generates the longest Collatz sequence. + + >>> solution(1000000) + {'counter': 525, 'largest_number': 837799} + >>> solution(200) + {'counter': 125, 'largest_number': 171} + >>> solution(5000) + {'counter': 238, 'largest_number': 3711} + >>> solution(15000) + {'counter': 276, 'largest_number': 13255} + """ + + result = max([(len(collatz_sequence(i)), i) for i in range(1, n)]) + return {"counter": result[0], "largest_number": result[1]} + + +if __name__ == "__main__": + result = solution(int(raw_input().strip())) + print( + "Longest Collatz sequence under one million is %d with length %d" + % (result["largest_number"], result["counter"]) + ) diff --git a/project_euler/problem_15/__init__.py b/project_euler/problem_15/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/project_euler/problem_15/sol1.py b/project_euler/problem_15/sol1.py index d24748011ef9..de58bb436d68 100644 --- a/project_euler/problem_15/sol1.py +++ b/project_euler/problem_15/sol1.py @@ -1,20 +1,57 @@ -from __future__ import print_function +""" +Starting in the top left corner of a 2×2 grid, and only being able to move to +the right and down, there are exactly 6 routes to the bottom right corner. +How many such routes are there through a 20×20 grid? +""" from math import factorial + def lattice_paths(n): - n = 2*n #middle entry of odd rows starting at row 3 is the solution for n = 1, 2, 3,... - k = n/2 - - return factorial(n)/(factorial(k)*factorial(n-k)) - -if __name__ == '__main__': - import sys - - if len(sys.argv) == 1: - print(lattice_paths(20)) - else: - try: - n = int(sys.argv[1]) - print(lattice_paths(n)) - except ValueError: - print('Invalid entry - please enter a number.') + """ + Returns the number of paths possible in a n x n grid starting at top left + corner going to bottom right corner and being able to move right and down + only. + +bruno@bruno-laptop:~/git/Python/project_euler/problem_15$ python3 sol1.py 50 +1.008913445455642e+29 +bruno@bruno-laptop:~/git/Python/project_euler/problem_15$ python3 sol1.py 25 +126410606437752.0 +bruno@bruno-laptop:~/git/Python/project_euler/problem_15$ python3 sol1.py 23 +8233430727600.0 +bruno@bruno-laptop:~/git/Python/project_euler/problem_15$ python3 sol1.py 15 +155117520.0 +bruno@bruno-laptop:~/git/Python/project_euler/problem_15$ python3 sol1.py 1 +2.0 + + >>> lattice_paths(25) + 126410606437752 + >>> lattice_paths(23) + 8233430727600 + >>> lattice_paths(20) + 137846528820 + >>> lattice_paths(15) + 155117520 + >>> lattice_paths(1) + 2 + + """ + n = ( + 2 * n + ) # middle entry of odd rows starting at row 3 is the solution for n = 1, + # 2, 3,... + k = n / 2 + + return int(factorial(n) / (factorial(k) * factorial(n - k))) + + +if __name__ == "__main__": + import sys + + if len(sys.argv) == 1: + print(lattice_paths(20)) + else: + try: + n = int(sys.argv[1]) + print(lattice_paths(n)) + except ValueError: + print("Invalid entry - please enter a number.") diff --git a/project_euler/problem_16/__init__.py b/project_euler/problem_16/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/project_euler/problem_16/sol1.py b/project_euler/problem_16/sol1.py index 05c7916bd10a..67c50ac87876 100644 --- a/project_euler/problem_16/sol1.py +++ b/project_euler/problem_16/sol1.py @@ -1,15 +1,34 @@ -power = int(input("Enter the power of 2: ")) -num = 2**power +""" +2^15 = 32768 and the sum of its digits is 3 + 2 + 7 + 6 + 8 = 26. -string_num = str(num) +What is the sum of the digits of the number 2^1000? +""" -list_num = list(string_num) -sum_of_num = 0 +def solution(power): + """Returns the sum of the digits of the number 2^power. + >>> solution(1000) + 1366 + >>> solution(50) + 76 + >>> solution(20) + 31 + >>> solution(15) + 26 + """ + num = 2 ** power + string_num = str(num) + list_num = list(string_num) + sum_of_num = 0 -print("2 ^",power,"=",num) + for i in list_num: + sum_of_num += int(i) -for i in list_num: - sum_of_num += int(i) + return sum_of_num -print("Sum of the digits are:",sum_of_num) + +if __name__ == "__main__": + power = int(input("Enter the power of 2: ").strip()) + print("2 ^ ", power, " = ", 2 ** power) + result = solution(power) + print("Sum of the digits is: ", result) diff --git a/project_euler/problem_16/sol2.py b/project_euler/problem_16/sol2.py index cce3d2354bb1..88672e9a9e54 100644 --- a/project_euler/problem_16/sol2.py +++ b/project_euler/problem_16/sol2.py @@ -1,6 +1,28 @@ -from __future__ import print_function -n = 2**1000 -r = 0 -while n: - r, n = r + n % 10, n // 10 -print(r) +""" +2^15 = 32768 and the sum of its digits is 3 + 2 + 7 + 6 + 8 = 26. + +What is the sum of the digits of the number 2^1000? +""" + + +def solution(power): + """Returns the sum of the digits of the number 2^power. + + >>> solution(1000) + 1366 + >>> solution(50) + 76 + >>> solution(20) + 31 + >>> solution(15) + 26 + """ + n = 2 ** power + r = 0 + while n: + r, n = r + n % 10, n // 10 + return r + + +if __name__ == "__main__": + print(solution(int(str(input()).strip()))) diff --git a/project_euler/problem_17/__init__.py b/project_euler/problem_17/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/project_euler/problem_17/sol1.py b/project_euler/problem_17/sol1.py index 8dd6f1af2093..d585d81a0825 100644 --- a/project_euler/problem_17/sol1.py +++ b/project_euler/problem_17/sol1.py @@ -1,35 +1,63 @@ -from __future__ import print_function -''' +""" Number letter counts Problem 17 -If the numbers 1 to 5 are written out in words: one, two, three, four, five, then there are 3 + 3 + 5 + 4 + 4 = 19 letters used in total. - -If all the numbers from 1 to 1000 (one thousand) inclusive were written out in words, how many letters would be used? - - -NOTE: Do not count spaces or hyphens. For example, 342 (three hundred and forty-two) contains 23 letters and 115 (one hundred and fifteen) -contains 20 letters. The use of "and" when writing out numbers is in compliance with British usage. -''' - -ones_counts = [0, 3, 3, 5, 4, 4, 3, 5, 5, 4, 3, 6, 6, 8, 8, 7, 7, 9, 8, 8] #number of letters in zero, one, two, ..., nineteen (0 for zero since it's never said aloud) -tens_counts = [0, 0, 6, 6, 5, 5, 5, 7, 6, 6] #number of letters in twenty, thirty, ..., ninety (0 for numbers less than 20 due to inconsistency in teens) - -count = 0 - -for i in range(1, 1001): - if i < 1000: - if i >= 100: - count += ones_counts[i/100] + 7 #add number of letters for "n hundred" - - if i%100 != 0: - count += 3 #add number of letters for "and" if number is not multiple of 100 - - if 0 < i%100 < 20: - count += ones_counts[i%100] #add number of letters for one, two, three, ..., nineteen (could be combined with below if not for inconsistency in teens) - else: - count += ones_counts[i%10] + tens_counts[(i%100-i%10)/10] #add number of letters for twenty, twenty one, ..., ninety nine - else: - count += ones_counts[i/1000] + 8 - -print(count) +If the numbers 1 to 5 are written out in words: one, two, three, four, five, +then there are 3 + 3 + 5 + 4 + 4 = 19 letters used in total. + +If all the numbers from 1 to 1000 (one thousand) inclusive were written out in +words, how many letters would be used? + + +NOTE: Do not count spaces or hyphens. For example, 342 (three hundred and +forty-two) contains 23 letters and 115 (one hundred and fifteen) contains 20 +letters. The use of "and" when writing out numbers is in compliance withBritish +usage. +""" + + +def solution(n): + """Returns the number of letters used to write all numbers from 1 to n. + where n is lower or equals to 1000. + >>> solution(1000) + 21124 + >>> solution(5) + 19 + """ + # number of letters in zero, one, two, ..., nineteen (0 for zero since it's + # never said aloud) + ones_counts = [0, 3, 3, 5, 4, 4, 3, 5, 5, 4, 3, 6, 6, 8, 8, 7, 7, 9, 8, 8] + # number of letters in twenty, thirty, ..., ninety (0 for numbers less than + # 20 due to inconsistency in teens) + tens_counts = [0, 0, 6, 6, 5, 5, 5, 7, 6, 6] + + count = 0 + + for i in range(1, n + 1): + if i < 1000: + if i >= 100: + # add number of letters for "n hundred" + count += ones_counts[i // 100] + 7 + + if i % 100 != 0: + # add number of letters for "and" if number is not multiple + # of 100 + count += 3 + + if 0 < i % 100 < 20: + # add number of letters for one, two, three, ..., nineteen + # (could be combined with below if not for inconsistency in + # teens) + count += ones_counts[i % 100] + else: + # add number of letters for twenty, twenty one, ..., ninety + # nine + count += ones_counts[i % 10] + count += tens_counts[(i % 100 - i % 10) // 10] + else: + count += ones_counts[i // 1000] + 8 + return count + + +if __name__ == "__main__": + print(solution(int(input().strip()))) diff --git a/project_euler/problem_19/__init__.py b/project_euler/problem_19/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/project_euler/problem_19/sol1.py b/project_euler/problem_19/sol1.py index 13e520ca76e4..6e4e29ec19c6 100644 --- a/project_euler/problem_19/sol1.py +++ b/project_euler/problem_19/sol1.py @@ -1,9 +1,9 @@ -from __future__ import print_function -''' +""" Counting Sundays Problem 19 -You are given the following information, but you may prefer to do some research for yourself. +You are given the following information, but you may prefer to do some research +for yourself. 1 Jan 1900 was a Monday. Thirty days has September, @@ -13,39 +13,52 @@ Which has twenty-eight, rain or shine. And on leap years, twenty-nine. -A leap year occurs on any year evenly divisible by 4, but not on a century unless it is divisible by 400. +A leap year occurs on any year evenly divisible by 4, but not on a century +unless it is divisible by 400. -How many Sundays fell on the first of the month during the twentieth century (1 Jan 1901 to 31 Dec 2000)? -''' +How many Sundays fell on the first of the month during the twentieth century +(1 Jan 1901 to 31 Dec 2000)? +""" -days_per_month = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31] -day = 6 -month = 1 -year = 1901 +def solution(): + """Returns the number of mondays that fall on the first of the month during + the twentieth century (1 Jan 1901 to 31 Dec 2000)? -sundays = 0 + >>> solution() + 171 + """ + days_per_month = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31] -while year < 2001: - day += 7 + day = 6 + month = 1 + year = 1901 - if (year%4 == 0 and not year%100 == 0) or (year%400 == 0): - if day > days_per_month[month-1] and month != 2: - month += 1 - day = day-days_per_month[month-2] - elif day > 29 and month == 2: - month += 1 - day = day-29 - else: - if day > days_per_month[month-1]: - month += 1 - day = day-days_per_month[month-2] - - if month > 12: - year += 1 - month = 1 + sundays = 0 - if year < 2001 and day == 1: - sundays += 1 + while year < 2001: + day += 7 -print(sundays) + if (year % 4 == 0 and not year % 100 == 0) or (year % 400 == 0): + if day > days_per_month[month - 1] and month != 2: + month += 1 + day = day - days_per_month[month - 2] + elif day > 29 and month == 2: + month += 1 + day = day - 29 + else: + if day > days_per_month[month - 1]: + month += 1 + day = day - days_per_month[month - 2] + + if month > 12: + year += 1 + month = 1 + + if year < 2001 and day == 1: + sundays += 1 + return sundays + + +if __name__ == "__main__": + print(solution(171)) diff --git a/project_euler/problem_20/__init__.py b/project_euler/problem_20/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/project_euler/problem_20/sol1.py b/project_euler/problem_20/sol1.py index 73e41d5cc8fa..13b3c987f046 100644 --- a/project_euler/problem_20/sol1.py +++ b/project_euler/problem_20/sol1.py @@ -1,27 +1,51 @@ -# Finding the factorial. +""" +n! means n × (n − 1) × ... × 3 × 2 × 1 + +For example, 10! = 10 × 9 × ... × 3 × 2 × 1 = 3628800, +and the sum of the digits in the number 10! is 3 + 6 + 2 + 8 + 8 + 0 + 0 = 27. + +Find the sum of the digits in the number 100! +""" + + def factorial(n): fact = 1 - for i in range(1,n+1): + for i in range(1, n + 1): fact *= i return fact -# Spliting the digits and adding it. + def split_and_add(number): + """Split number digits and add them.""" sum_of_digits = 0 - while(number>0): + while number > 0: last_digit = number % 10 sum_of_digits += last_digit - number = int(number/10) # Removing the last_digit from the given number. + number = number // 10 # Removing the last_digit from the given number return sum_of_digits -# Taking the user input. -number = int(input("Enter the Number: ")) -# Assigning the factorial from the factorial function. -factorial = factorial(number) +def solution(n): + """Returns the sum of the digits in the number 100! + >>> solution(100) + 648 + >>> solution(50) + 216 + >>> solution(10) + 27 + >>> solution(5) + 3 + >>> solution(3) + 6 + >>> solution(2) + 2 + >>> solution(1) + 1 + """ + f = factorial(n) + result = split_and_add(f) + return result -# Spliting and adding the factorial into answer. -answer = split_and_add(factorial) -# Printing the answer. -print(answer) +if __name__ == "__main__": + print(solution(int(input("Enter the Number: ").strip()))) diff --git a/project_euler/problem_20/sol2.py b/project_euler/problem_20/sol2.py index bca9af9cb9ef..14e591795292 100644 --- a/project_euler/problem_20/sol2.py +++ b/project_euler/problem_20/sol2.py @@ -1,5 +1,33 @@ +""" +n! means n × (n − 1) × ... × 3 × 2 × 1 + +For example, 10! = 10 × 9 × ... × 3 × 2 × 1 = 3628800, +and the sum of the digits in the number 10! is 3 + 6 + 2 + 8 + 8 + 0 + 0 = 27. + +Find the sum of the digits in the number 100! +""" from math import factorial -def main(): - print(sum([int(x) for x in str(factorial(100))])) -if __name__ == '__main__': - main() \ No newline at end of file + + +def solution(n): + """Returns the sum of the digits in the number 100! + >>> solution(100) + 648 + >>> solution(50) + 216 + >>> solution(10) + 27 + >>> solution(5) + 3 + >>> solution(3) + 6 + >>> solution(2) + 2 + >>> solution(1) + 1 + """ + return sum([int(x) for x in str(factorial(n))]) + + +if __name__ == "__main__": + print(solution(int(input("Enter the Number: ").strip()))) diff --git a/project_euler/problem_21/__init__.py b/project_euler/problem_21/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/project_euler/problem_21/sol1.py b/project_euler/problem_21/sol1.py index da29a5c7b631..9cf2a64cf2a9 100644 --- a/project_euler/problem_21/sol1.py +++ b/project_euler/problem_21/sol1.py @@ -1,30 +1,61 @@ -#-.- coding: latin-1 -.- -from __future__ import print_function +# -.- coding: latin-1 -.- from math import sqrt -''' + +""" Amicable Numbers Problem 21 -Let d(n) be defined as the sum of proper divisors of n (numbers less than n which divide evenly into n). -If d(a) = b and d(b) = a, where a ≠ b, then a and b are an amicable pair and each of a and b are called amicable numbers. +Let d(n) be defined as the sum of proper divisors of n (numbers less than n +which divide evenly into n). +If d(a) = b and d(b) = a, where a ≠ b, then a and b are an amicable pair and +each of a and b are called amicable numbers. -For example, the proper divisors of 220 are 1, 2, 4, 5, 10, 11, 20, 22, 44, 55 and 110; therefore d(220) = 284. The proper divisors of 284 are 1, 2, 4, 71 and 142; so d(284) = 220. +For example, the proper divisors of 220 are 1, 2, 4, 5, 10, 11, 20, 22, 44, 55 +and 110; therefore d(220) = 284. The proper divisors of 284 are 1, 2, 4, 71 and +142; so d(284) = 220. Evaluate the sum of all the amicable numbers under 10000. -''' +""" try: - xrange #Python 2 + xrange # Python 2 except NameError: - xrange = range #Python 3 + xrange = range # Python 3 + def sum_of_divisors(n): - total = 0 - for i in xrange(1, int(sqrt(n)+1)): - if n%i == 0 and i != sqrt(n): - total += i + n//i - elif i == sqrt(n): - total += i - return total-n - -total = [i for i in range(1,10000) if sum_of_divisors(sum_of_divisors(i)) == i and sum_of_divisors(i) != i] -print(sum(total)) + total = 0 + for i in xrange(1, int(sqrt(n) + 1)): + if n % i == 0 and i != sqrt(n): + total += i + n // i + elif i == sqrt(n): + total += i + return total - n + + +def solution(n): + """Returns the sum of all the amicable numbers under n. + + >>> solution(10000) + 31626 + >>> solution(5000) + 8442 + >>> solution(1000) + 504 + >>> solution(100) + 0 + >>> solution(50) + 0 + """ + total = sum( + [ + i + for i in range(1, n) + if sum_of_divisors(sum_of_divisors(i)) == i + and sum_of_divisors(i) != i + ] + ) + return total + + +if __name__ == "__main__": + print(solution(int(str(input()).strip()))) diff --git a/project_euler/problem_22/__init__.py b/project_euler/problem_22/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/project_euler/problem_22/sol1.py b/project_euler/problem_22/sol1.py index 7754306583dc..aa779f222eaa 100644 --- a/project_euler/problem_22/sol1.py +++ b/project_euler/problem_22/sol1.py @@ -1,37 +1,52 @@ # -*- coding: latin-1 -*- -from __future__ import print_function -''' +""" Name scores Problem 22 -Using names.txt (right click and 'Save Link/Target As...'), a 46K text file containing over five-thousand first names, begin by sorting it -into alphabetical order. Then working out the alphabetical value for each name, multiply this value by its alphabetical position in the list -to obtain a name score. +Using names.txt (right click and 'Save Link/Target As...'), a 46K text file +containing over five-thousand first names, begin by sorting it into +alphabetical order. Then working out the alphabetical value for each name, +multiply this value by its alphabetical position in the list to obtain a name +score. -For example, when the list is sorted into alphabetical order, COLIN, which is worth 3 + 15 + 12 + 9 + 14 = 53, is the 938th name in the list. -So, COLIN would obtain a score of 938 × 53 = 49714. +For example, when the list is sorted into alphabetical order, COLIN, which is +worth 3 + 15 + 12 + 9 + 14 = 53, is the 938th name in the list. So, COLIN would +obtain a score of 938 × 53 = 49714. What is the total of all the name scores in the file? -''' +""" +import os + + try: - xrange #Python 2 + xrange # Python 2 except NameError: - xrange = range #Python 3 + xrange = range # Python 3 + + +def solution(): + """Returns the total of all the name scores in the file. + + >>> solution() + 871198282 + """ + with open(os.path.dirname(__file__) + "/p022_names.txt") as file: + names = str(file.readlines()[0]) + names = names.replace('"', "").split(",") -with open('p022_names.txt') as file: - names = str(file.readlines()[0]) - names = names.replace('"', '').split(',') + names.sort() -names.sort() + name_score = 0 + total_score = 0 -name_score = 0 -total_score = 0 + for i, name in enumerate(names): + for letter in name: + name_score += ord(letter) - 64 -for i, name in enumerate(names): - for letter in name: - name_score += ord(letter) - 64 + total_score += (i + 1) * name_score + name_score = 0 + return total_score - total_score += (i+1)*name_score - name_score = 0 -print(total_score) \ No newline at end of file +if __name__ == "__main__": + print(solution()) diff --git a/project_euler/problem_22/sol2.py b/project_euler/problem_22/sol2.py index d7f9abf09d49..69acd2fb8ef3 100644 --- a/project_euler/problem_22/sol2.py +++ b/project_euler/problem_22/sol2.py @@ -1,533 +1,43 @@ -def main(): - name = [ - "MARY", "PATRICIA", "LINDA", "BARBARA", "ELIZABETH", "JENNIFER", "MARIA", "SUSAN", "MARGARET", "DOROTHY", - "LISA", "NANCY", "KAREN", "BETTY", "HELEN", "SANDRA", "DONNA", "CAROL", "RUTH", "SHARON", - "MICHELLE", "LAURA", "SARAH", "KIMBERLY", "DEBORAH", "JESSICA", "SHIRLEY", "CYNTHIA", "ANGELA", "MELISSA", - "BRENDA", "AMY", "ANNA", "REBECCA", "VIRGINIA", "KATHLEEN", "PAMELA", "MARTHA", "DEBRA", "AMANDA", - "STEPHANIE", "CAROLYN", "CHRISTINE", "MARIE", "JANET", "CATHERINE", "FRANCES", "ANN", "JOYCE", "DIANE", - "ALICE", "JULIE", "HEATHER", "TERESA", "DORIS", "GLORIA", "EVELYN", "JEAN", "CHERYL", "MILDRED", - "KATHERINE", "JOAN", "ASHLEY", "JUDITH", "ROSE", "JANICE", "KELLY", "NICOLE", "JUDY", "CHRISTINA", - "KATHY", "THERESA", "BEVERLY", "DENISE", "TAMMY", "IRENE", "JANE", "LORI", "RACHEL", "MARILYN", - "ANDREA", "KATHRYN", "LOUISE", "SARA", "ANNE", "JACQUELINE", "WANDA", "BONNIE", "JULIA", "RUBY", - "LOIS", "TINA", "PHYLLIS", "NORMA", "PAULA", "DIANA", "ANNIE", "LILLIAN", "EMILY", "ROBIN", - "PEGGY", "CRYSTAL", "GLADYS", "RITA", "DAWN", "CONNIE", "FLORENCE", "TRACY", "EDNA", "TIFFANY", - "CARMEN", "ROSA", "CINDY", "GRACE", "WENDY", "VICTORIA", "EDITH", "KIM", "SHERRY", "SYLVIA", - "JOSEPHINE", "THELMA", "SHANNON", "SHEILA", "ETHEL", "ELLEN", "ELAINE", "MARJORIE", "CARRIE", "CHARLOTTE", - "MONICA", "ESTHER", "PAULINE", "EMMA", "JUANITA", "ANITA", "RHONDA", "HAZEL", "AMBER", "EVA", - "DEBBIE", "APRIL", "LESLIE", "CLARA", "LUCILLE", "JAMIE", "JOANNE", "ELEANOR", "VALERIE", "DANIELLE", - "MEGAN", "ALICIA", "SUZANNE", "MICHELE", "GAIL", "BERTHA", "DARLENE", "VERONICA", "JILL", "ERIN", - "GERALDINE", "LAUREN", "CATHY", "JOANN", "LORRAINE", "LYNN", "SALLY", "REGINA", "ERICA", "BEATRICE", - "DOLORES", "BERNICE", "AUDREY", "YVONNE", "ANNETTE", "JUNE", "SAMANTHA", "MARION", "DANA", "STACY", - "ANA", "RENEE", "IDA", "VIVIAN", "ROBERTA", "HOLLY", "BRITTANY", "MELANIE", "LORETTA", "YOLANDA", - "JEANETTE", "LAURIE", "KATIE", "KRISTEN", "VANESSA", "ALMA", "SUE", "ELSIE", "BETH", "JEANNE", - "VICKI", "CARLA", "TARA", "ROSEMARY", "EILEEN", "TERRI", "GERTRUDE", "LUCY", "TONYA", "ELLA", - "STACEY", "WILMA", "GINA", "KRISTIN", "JESSIE", "NATALIE", "AGNES", "VERA", "WILLIE", "CHARLENE", - "BESSIE", "DELORES", "MELINDA", "PEARL", "ARLENE", "MAUREEN", "COLLEEN", "ALLISON", "TAMARA", "JOY", - "GEORGIA", "CONSTANCE", "LILLIE", "CLAUDIA", "JACKIE", "MARCIA", "TANYA", "NELLIE", "MINNIE", "MARLENE", - "HEIDI", "GLENDA", "LYDIA", "VIOLA", "COURTNEY", "MARIAN", "STELLA", "CAROLINE", "DORA", "JO", - "VICKIE", "MATTIE", "TERRY", "MAXINE", "IRMA", "MABEL", "MARSHA", "MYRTLE", "LENA", "CHRISTY", - "DEANNA", "PATSY", "HILDA", "GWENDOLYN", "JENNIE", "NORA", "MARGIE", "NINA", "CASSANDRA", "LEAH", - "PENNY", "KAY", "PRISCILLA", "NAOMI", "CAROLE", "BRANDY", "OLGA", "BILLIE", "DIANNE", "TRACEY", - "LEONA", "JENNY", "FELICIA", "SONIA", "MIRIAM", "VELMA", "BECKY", "BOBBIE", "VIOLET", "KRISTINA", - "TONI", "MISTY", "MAE", "SHELLY", "DAISY", "RAMONA", "SHERRI", "ERIKA", "KATRINA", "CLAIRE", - "LINDSEY", "LINDSAY", "GENEVA", "GUADALUPE", "BELINDA", "MARGARITA", "SHERYL", "CORA", "FAYE", "ADA", - "NATASHA", "SABRINA", "ISABEL", "MARGUERITE", "HATTIE", "HARRIET", "MOLLY", "CECILIA", "KRISTI", "BRANDI", - "BLANCHE", "SANDY", "ROSIE", "JOANNA", "IRIS", "EUNICE", "ANGIE", "INEZ", "LYNDA", "MADELINE", - "AMELIA", "ALBERTA", "GENEVIEVE", "MONIQUE", "JODI", "JANIE", "MAGGIE", "KAYLA", "SONYA", "JAN", - "LEE", "KRISTINE", "CANDACE", "FANNIE", "MARYANN", "OPAL", "ALISON", "YVETTE", "MELODY", "LUZ", - "SUSIE", "OLIVIA", "FLORA", "SHELLEY", "KRISTY", "MAMIE", "LULA", "LOLA", "VERNA", "BEULAH", - "ANTOINETTE", "CANDICE", "JUANA", "JEANNETTE", "PAM", "KELLI", "HANNAH", "WHITNEY", "BRIDGET", "KARLA", - "CELIA", "LATOYA", "PATTY", "SHELIA", "GAYLE", "DELLA", "VICKY", "LYNNE", "SHERI", "MARIANNE", - "KARA", "JACQUELYN", "ERMA", "BLANCA", "MYRA", "LETICIA", "PAT", "KRISTA", "ROXANNE", "ANGELICA", - "JOHNNIE", "ROBYN", "FRANCIS", "ADRIENNE", "ROSALIE", "ALEXANDRA", "BROOKE", "BETHANY", "SADIE", "BERNADETTE", - "TRACI", "JODY", "KENDRA", "JASMINE", "NICHOLE", "RACHAEL", "CHELSEA", "MABLE", "ERNESTINE", "MURIEL", - "MARCELLA", "ELENA", "KRYSTAL", "ANGELINA", "NADINE", "KARI", "ESTELLE", "DIANNA", "PAULETTE", "LORA", - "MONA", "DOREEN", "ROSEMARIE", "ANGEL", "DESIREE", "ANTONIA", "HOPE", "GINGER", "JANIS", "BETSY", - "CHRISTIE", "FREDA", "MERCEDES", "MEREDITH", "LYNETTE", "TERI", "CRISTINA", "EULA", "LEIGH", "MEGHAN", - "SOPHIA", "ELOISE", "ROCHELLE", "GRETCHEN", "CECELIA", "RAQUEL", "HENRIETTA", "ALYSSA", "JANA", "KELLEY", - "GWEN", "KERRY", "JENNA", "TRICIA", "LAVERNE", "OLIVE", "ALEXIS", "TASHA", "SILVIA", "ELVIRA", - "CASEY", "DELIA", "SOPHIE", "KATE", "PATTI", "LORENA", "KELLIE", "SONJA", "LILA", "LANA", - "DARLA", "MAY", "MINDY", "ESSIE", "MANDY", "LORENE", "ELSA", "JOSEFINA", "JEANNIE", "MIRANDA", - "DIXIE", "LUCIA", "MARTA", "FAITH", "LELA", "JOHANNA", "SHARI", "CAMILLE", "TAMI", "SHAWNA", - "ELISA", "EBONY", "MELBA", "ORA", "NETTIE", "TABITHA", "OLLIE", "JAIME", "WINIFRED", "KRISTIE", - "MARINA", "ALISHA", "AIMEE", "RENA", "MYRNA", "MARLA", "TAMMIE", "LATASHA", "BONITA", "PATRICE", - "RONDA", "SHERRIE", "ADDIE", "FRANCINE", "DELORIS", "STACIE", "ADRIANA", "CHERI", "SHELBY", "ABIGAIL", - "CELESTE", "JEWEL", "CARA", "ADELE", "REBEKAH", "LUCINDA", "DORTHY", "CHRIS", "EFFIE", "TRINA", - "REBA", "SHAWN", "SALLIE", "AURORA", "LENORA", "ETTA", "LOTTIE", "KERRI", "TRISHA", "NIKKI", - "ESTELLA", "FRANCISCA", "JOSIE", "TRACIE", "MARISSA", "KARIN", "BRITTNEY", "JANELLE", "LOURDES", "LAUREL", - "HELENE", "FERN", "ELVA", "CORINNE", "KELSEY", "INA", "BETTIE", "ELISABETH", "AIDA", "CAITLIN", - "INGRID", "IVA", "EUGENIA", "CHRISTA", "GOLDIE", "CASSIE", "MAUDE", "JENIFER", "THERESE", "FRANKIE", - "DENA", "LORNA", "JANETTE", "LATONYA", "CANDY", "MORGAN", "CONSUELO", "TAMIKA", "ROSETTA", "DEBORA", - "CHERIE", "POLLY", "DINA", "JEWELL", "FAY", "JILLIAN", "DOROTHEA", "NELL", "TRUDY", "ESPERANZA", - "PATRICA", "KIMBERLEY", "SHANNA", "HELENA", "CAROLINA", "CLEO", "STEFANIE", "ROSARIO", "OLA", "JANINE", - "MOLLIE", "LUPE", "ALISA", "LOU", "MARIBEL", "SUSANNE", "BETTE", "SUSANA", "ELISE", "CECILE", - "ISABELLE", "LESLEY", "JOCELYN", "PAIGE", "JONI", "RACHELLE", "LEOLA", "DAPHNE", "ALTA", "ESTER", - "PETRA", "GRACIELA", "IMOGENE", "JOLENE", "KEISHA", "LACEY", "GLENNA", "GABRIELA", "KERI", "URSULA", - "LIZZIE", "KIRSTEN", "SHANA", "ADELINE", "MAYRA", "JAYNE", "JACLYN", "GRACIE", "SONDRA", "CARMELA", - "MARISA", "ROSALIND", "CHARITY", "TONIA", "BEATRIZ", "MARISOL", "CLARICE", "JEANINE", "SHEENA", "ANGELINE", - "FRIEDA", "LILY", "ROBBIE", "SHAUNA", "MILLIE", "CLAUDETTE", "CATHLEEN", "ANGELIA", "GABRIELLE", "AUTUMN", - "KATHARINE", "SUMMER", "JODIE", "STACI", "LEA", "CHRISTI", "JIMMIE", "JUSTINE", "ELMA", "LUELLA", - "MARGRET", "DOMINIQUE", "SOCORRO", "RENE", "MARTINA", "MARGO", "MAVIS", "CALLIE", "BOBBI", "MARITZA", - "LUCILE", "LEANNE", "JEANNINE", "DEANA", "AILEEN", "LORIE", "LADONNA", "WILLA", "MANUELA", "GALE", - "SELMA", "DOLLY", "SYBIL", "ABBY", "LARA", "DALE", "IVY", "DEE", "WINNIE", "MARCY", - "LUISA", "JERI", "MAGDALENA", "OFELIA", "MEAGAN", "AUDRA", "MATILDA", "LEILA", "CORNELIA", "BIANCA", - "SIMONE", "BETTYE", "RANDI", "VIRGIE", "LATISHA", "BARBRA", "GEORGINA", "ELIZA", "LEANN", "BRIDGETTE", - "RHODA", "HALEY", "ADELA", "NOLA", "BERNADINE", "FLOSSIE", "ILA", "GRETA", "RUTHIE", "NELDA", - "MINERVA", "LILLY", "TERRIE", "LETHA", "HILARY", "ESTELA", "VALARIE", "BRIANNA", "ROSALYN", "EARLINE", - "CATALINA", "AVA", "MIA", "CLARISSA", "LIDIA", "CORRINE", "ALEXANDRIA", "CONCEPCION", "TIA", "SHARRON", - "RAE", "DONA", "ERICKA", "JAMI", "ELNORA", "CHANDRA", "LENORE", "NEVA", "MARYLOU", "MELISA", - "TABATHA", "SERENA", "AVIS", "ALLIE", "SOFIA", "JEANIE", "ODESSA", "NANNIE", "HARRIETT", "LORAINE", - "PENELOPE", "MILAGROS", "EMILIA", "BENITA", "ALLYSON", "ASHLEE", "TANIA", "TOMMIE", "ESMERALDA", "KARINA", - "EVE", "PEARLIE", "ZELMA", "MALINDA", "NOREEN", "TAMEKA", "SAUNDRA", "HILLARY", "AMIE", "ALTHEA", - "ROSALINDA", "JORDAN", "LILIA", "ALANA", "GAY", "CLARE", "ALEJANDRA", "ELINOR", "MICHAEL", "LORRIE", - "JERRI", "DARCY", "EARNESTINE", "CARMELLA", "TAYLOR", "NOEMI", "MARCIE", "LIZA", "ANNABELLE", "LOUISA", - "EARLENE", "MALLORY", "CARLENE", "NITA", "SELENA", "TANISHA", "KATY", "JULIANNE", "JOHN", "LAKISHA", - "EDWINA", "MARICELA", "MARGERY", "KENYA", "DOLLIE", "ROXIE", "ROSLYN", "KATHRINE", "NANETTE", "CHARMAINE", - "LAVONNE", "ILENE", "KRIS", "TAMMI", "SUZETTE", "CORINE", "KAYE", "JERRY", "MERLE", "CHRYSTAL", - "LINA", "DEANNE", "LILIAN", "JULIANA", "ALINE", "LUANN", "KASEY", "MARYANNE", "EVANGELINE", "COLETTE", - "MELVA", "LAWANDA", "YESENIA", "NADIA", "MADGE", "KATHIE", "EDDIE", "OPHELIA", "VALERIA", "NONA", - "MITZI", "MARI", "GEORGETTE", "CLAUDINE", "FRAN", "ALISSA", "ROSEANN", "LAKEISHA", "SUSANNA", "REVA", - "DEIDRE", "CHASITY", "SHEREE", "CARLY", "JAMES", "ELVIA", "ALYCE", "DEIRDRE", "GENA", "BRIANA", - "ARACELI", "KATELYN", "ROSANNE", "WENDI", "TESSA", "BERTA", "MARVA", "IMELDA", "MARIETTA", "MARCI", - "LEONOR", "ARLINE", "SASHA", "MADELYN", "JANNA", "JULIETTE", "DEENA", "AURELIA", "JOSEFA", "AUGUSTA", - "LILIANA", "YOUNG", "CHRISTIAN", "LESSIE", "AMALIA", "SAVANNAH", "ANASTASIA", "VILMA", "NATALIA", "ROSELLA", - "LYNNETTE", "CORINA", "ALFREDA", "LEANNA", "CAREY", "AMPARO", "COLEEN", "TAMRA", "AISHA", "WILDA", - "KARYN", "CHERRY", "QUEEN", "MAURA", "MAI", "EVANGELINA", "ROSANNA", "HALLIE", "ERNA", "ENID", - "MARIANA", "LACY", "JULIET", "JACKLYN", "FREIDA", "MADELEINE", "MARA", "HESTER", "CATHRYN", "LELIA", - "CASANDRA", "BRIDGETT", "ANGELITA", "JANNIE", "DIONNE", "ANNMARIE", "KATINA", "BERYL", "PHOEBE", "MILLICENT", - "KATHERYN", "DIANN", "CARISSA", "MARYELLEN", "LIZ", "LAURI", "HELGA", "GILDA", "ADRIAN", "RHEA", - "MARQUITA", "HOLLIE", "TISHA", "TAMERA", "ANGELIQUE", "FRANCESCA", "BRITNEY", "KAITLIN", "LOLITA", "FLORINE", - "ROWENA", "REYNA", "TWILA", "FANNY", "JANELL", "INES", "CONCETTA", "BERTIE", "ALBA", "BRIGITTE", - "ALYSON", "VONDA", "PANSY", "ELBA", "NOELLE", "LETITIA", "KITTY", "DEANN", "BRANDIE", "LOUELLA", - "LETA", "FELECIA", "SHARLENE", "LESA", "BEVERLEY", "ROBERT", "ISABELLA", "HERMINIA", "TERRA", "CELINA", - "TORI", "OCTAVIA", "JADE", "DENICE", "GERMAINE", "SIERRA", "MICHELL", "CORTNEY", "NELLY", "DORETHA", - "SYDNEY", "DEIDRA", "MONIKA", "LASHONDA", "JUDI", "CHELSEY", "ANTIONETTE", "MARGOT", "BOBBY", "ADELAIDE", - "NAN", "LEEANN", "ELISHA", "DESSIE", "LIBBY", "KATHI", "GAYLA", "LATANYA", "MINA", "MELLISA", - "KIMBERLEE", "JASMIN", "RENAE", "ZELDA", "ELDA", "MA", "JUSTINA", "GUSSIE", "EMILIE", "CAMILLA", - "ABBIE", "ROCIO", "KAITLYN", "JESSE", "EDYTHE", "ASHLEIGH", "SELINA", "LAKESHA", "GERI", "ALLENE", - "PAMALA", "MICHAELA", "DAYNA", "CARYN", "ROSALIA", "SUN", "JACQULINE", "REBECA", "MARYBETH", "KRYSTLE", - "IOLA", "DOTTIE", "BENNIE", "BELLE", "AUBREY", "GRISELDA", "ERNESTINA", "ELIDA", "ADRIANNE", "DEMETRIA", - "DELMA", "CHONG", "JAQUELINE", "DESTINY", "ARLEEN", "VIRGINA", "RETHA", "FATIMA", "TILLIE", "ELEANORE", - "CARI", "TREVA", "BIRDIE", "WILHELMINA", "ROSALEE", "MAURINE", "LATRICE", "YONG", "JENA", "TARYN", - "ELIA", "DEBBY", "MAUDIE", "JEANNA", "DELILAH", "CATRINA", "SHONDA", "HORTENCIA", "THEODORA", "TERESITA", - "ROBBIN", "DANETTE", "MARYJANE", "FREDDIE", "DELPHINE", "BRIANNE", "NILDA", "DANNA", "CINDI", "BESS", - "IONA", "HANNA", "ARIEL", "WINONA", "VIDA", "ROSITA", "MARIANNA", "WILLIAM", "RACHEAL", "GUILLERMINA", - "ELOISA", "CELESTINE", "CAREN", "MALISSA", "LONA", "CHANTEL", "SHELLIE", "MARISELA", "LEORA", "AGATHA", - "SOLEDAD", "MIGDALIA", "IVETTE", "CHRISTEN", "ATHENA", "JANEL", "CHLOE", "VEDA", "PATTIE", "TESSIE", - "TERA", "MARILYNN", "LUCRETIA", "KARRIE", "DINAH", "DANIELA", "ALECIA", "ADELINA", "VERNICE", "SHIELA", - "PORTIA", "MERRY", "LASHAWN", "DEVON", "DARA", "TAWANA", "OMA", "VERDA", "CHRISTIN", "ALENE", - "ZELLA", "SANDI", "RAFAELA", "MAYA", "KIRA", "CANDIDA", "ALVINA", "SUZAN", "SHAYLA", "LYN", - "LETTIE", "ALVA", "SAMATHA", "ORALIA", "MATILDE", "MADONNA", "LARISSA", "VESTA", "RENITA", "INDIA", - "DELOIS", "SHANDA", "PHILLIS", "LORRI", "ERLINDA", "CRUZ", "CATHRINE", "BARB", "ZOE", "ISABELL", - "IONE", "GISELA", "CHARLIE", "VALENCIA", "ROXANNA", "MAYME", "KISHA", "ELLIE", "MELLISSA", "DORRIS", - "DALIA", "BELLA", "ANNETTA", "ZOILA", "RETA", "REINA", "LAURETTA", "KYLIE", "CHRISTAL", "PILAR", - "CHARLA", "ELISSA", "TIFFANI", "TANA", "PAULINA", "LEOTA", "BREANNA", "JAYME", "CARMEL", "VERNELL", - "TOMASA", "MANDI", "DOMINGA", "SANTA", "MELODIE", "LURA", "ALEXA", "TAMELA", "RYAN", "MIRNA", - "KERRIE", "VENUS", "NOEL", "FELICITA", "CRISTY", "CARMELITA", "BERNIECE", "ANNEMARIE", "TIARA", "ROSEANNE", - "MISSY", "CORI", "ROXANA", "PRICILLA", "KRISTAL", "JUNG", "ELYSE", "HAYDEE", "ALETHA", "BETTINA", - "MARGE", "GILLIAN", "FILOMENA", "CHARLES", "ZENAIDA", "HARRIETTE", "CARIDAD", "VADA", "UNA", "ARETHA", - "PEARLINE", "MARJORY", "MARCELA", "FLOR", "EVETTE", "ELOUISE", "ALINA", "TRINIDAD", "DAVID", "DAMARIS", - "CATHARINE", "CARROLL", "BELVA", "NAKIA", "MARLENA", "LUANNE", "LORINE", "KARON", "DORENE", "DANITA", - "BRENNA", "TATIANA", "SAMMIE", "LOUANN", "LOREN", "JULIANNA", "ANDRIA", "PHILOMENA", "LUCILA", "LEONORA", - "DOVIE", "ROMONA", "MIMI", "JACQUELIN", "GAYE", "TONJA", "MISTI", "JOE", "GENE", "CHASTITY", - "STACIA", "ROXANN", "MICAELA", "NIKITA", "MEI", "VELDA", "MARLYS", "JOHNNA", "AURA", "LAVERN", - "IVONNE", "HAYLEY", "NICKI", "MAJORIE", "HERLINDA", "GEORGE", "ALPHA", "YADIRA", "PERLA", "GREGORIA", - "DANIEL", "ANTONETTE", "SHELLI", "MOZELLE", "MARIAH", "JOELLE", "CORDELIA", "JOSETTE", "CHIQUITA", "TRISTA", - "LOUIS", "LAQUITA", "GEORGIANA", "CANDI", "SHANON", "LONNIE", "HILDEGARD", "CECIL", "VALENTINA", "STEPHANY", - "MAGDA", "KAROL", "GERRY", "GABRIELLA", "TIANA", "ROMA", "RICHELLE", "RAY", "PRINCESS", "OLETA", - "JACQUE", "IDELLA", "ALAINA", "SUZANNA", "JOVITA", "BLAIR", "TOSHA", "RAVEN", "NEREIDA", "MARLYN", - "KYLA", "JOSEPH", "DELFINA", "TENA", "STEPHENIE", "SABINA", "NATHALIE", "MARCELLE", "GERTIE", "DARLEEN", - "THEA", "SHARONDA", "SHANTEL", "BELEN", "VENESSA", "ROSALINA", "ONA", "GENOVEVA", "COREY", "CLEMENTINE", - "ROSALBA", "RENATE", "RENATA", "MI", "IVORY", "GEORGIANNA", "FLOY", "DORCAS", "ARIANA", "TYRA", - "THEDA", "MARIAM", "JULI", "JESICA", "DONNIE", "VIKKI", "VERLA", "ROSELYN", "MELVINA", "JANNETTE", - "GINNY", "DEBRAH", "CORRIE", "ASIA", "VIOLETA", "MYRTIS", "LATRICIA", "COLLETTE", "CHARLEEN", "ANISSA", - "VIVIANA", "TWYLA", "PRECIOUS", "NEDRA", "LATONIA", "LAN", "HELLEN", "FABIOLA", "ANNAMARIE", "ADELL", - "SHARYN", "CHANTAL", "NIKI", "MAUD", "LIZETTE", "LINDY", "KIA", "KESHA", "JEANA", "DANELLE", - "CHARLINE", "CHANEL", "CARROL", "VALORIE", "LIA", "DORTHA", "CRISTAL", "SUNNY", "LEONE", "LEILANI", - "GERRI", "DEBI", "ANDRA", "KESHIA", "IMA", "EULALIA", "EASTER", "DULCE", "NATIVIDAD", "LINNIE", - "KAMI", "GEORGIE", "CATINA", "BROOK", "ALDA", "WINNIFRED", "SHARLA", "RUTHANN", "MEAGHAN", "MAGDALENE", - "LISSETTE", "ADELAIDA", "VENITA", "TRENA", "SHIRLENE", "SHAMEKA", "ELIZEBETH", "DIAN", "SHANTA", "MICKEY", - "LATOSHA", "CARLOTTA", "WINDY", "SOON", "ROSINA", "MARIANN", "LEISA", "JONNIE", "DAWNA", "CATHIE", - "BILLY", "ASTRID", "SIDNEY", "LAUREEN", "JANEEN", "HOLLI", "FAWN", "VICKEY", "TERESSA", "SHANTE", - "RUBYE", "MARCELINA", "CHANDA", "CARY", "TERESE", "SCARLETT", "MARTY", "MARNIE", "LULU", "LISETTE", - "JENIFFER", "ELENOR", "DORINDA", "DONITA", "CARMAN", "BERNITA", "ALTAGRACIA", "ALETA", "ADRIANNA", "ZORAIDA", - "RONNIE", "NICOLA", "LYNDSEY", "KENDALL", "JANINA", "CHRISSY", "AMI", "STARLA", "PHYLIS", "PHUONG", - "KYRA", "CHARISSE", "BLANCH", "SANJUANITA", "RONA", "NANCI", "MARILEE", "MARANDA", "CORY", "BRIGETTE", - "SANJUANA", "MARITA", "KASSANDRA", "JOYCELYN", "IRA", "FELIPA", "CHELSIE", "BONNY", "MIREYA", "LORENZA", - "KYONG", "ILEANA", "CANDELARIA", "TONY", "TOBY", "SHERIE", "OK", "MARK", "LUCIE", "LEATRICE", - "LAKESHIA", "GERDA", "EDIE", "BAMBI", "MARYLIN", "LAVON", "HORTENSE", "GARNET", "EVIE", "TRESSA", - "SHAYNA", "LAVINA", "KYUNG", "JEANETTA", "SHERRILL", "SHARA", "PHYLISS", "MITTIE", "ANABEL", "ALESIA", - "THUY", "TAWANDA", "RICHARD", "JOANIE", "TIFFANIE", "LASHANDA", "KARISSA", "ENRIQUETA", "DARIA", "DANIELLA", - "CORINNA", "ALANNA", "ABBEY", "ROXANE", "ROSEANNA", "MAGNOLIA", "LIDA", "KYLE", "JOELLEN", "ERA", - "CORAL", "CARLEEN", "TRESA", "PEGGIE", "NOVELLA", "NILA", "MAYBELLE", "JENELLE", "CARINA", "NOVA", - "MELINA", "MARQUERITE", "MARGARETTE", "JOSEPHINA", "EVONNE", "DEVIN", "CINTHIA", "ALBINA", "TOYA", "TAWNYA", - "SHERITA", "SANTOS", "MYRIAM", "LIZABETH", "LISE", "KEELY", "JENNI", "GISELLE", "CHERYLE", "ARDITH", - "ARDIS", "ALESHA", "ADRIANE", "SHAINA", "LINNEA", "KAROLYN", "HONG", "FLORIDA", "FELISHA", "DORI", - "DARCI", "ARTIE", "ARMIDA", "ZOLA", "XIOMARA", "VERGIE", "SHAMIKA", "NENA", "NANNETTE", "MAXIE", - "LOVIE", "JEANE", "JAIMIE", "INGE", "FARRAH", "ELAINA", "CAITLYN", "STARR", "FELICITAS", "CHERLY", - "CARYL", "YOLONDA", "YASMIN", "TEENA", "PRUDENCE", "PENNIE", "NYDIA", "MACKENZIE", "ORPHA", "MARVEL", - "LIZBETH", "LAURETTE", "JERRIE", "HERMELINDA", "CAROLEE", "TIERRA", "MIRIAN", "META", "MELONY", "KORI", - "JENNETTE", "JAMILA", "ENA", "ANH", "YOSHIKO", "SUSANNAH", "SALINA", "RHIANNON", "JOLEEN", "CRISTINE", - "ASHTON", "ARACELY", "TOMEKA", "SHALONDA", "MARTI", "LACIE", "KALA", "JADA", "ILSE", "HAILEY", - "BRITTANI", "ZONA", "SYBLE", "SHERRYL", "RANDY", "NIDIA", "MARLO", "KANDICE", "KANDI", "DEB", - "DEAN", "AMERICA", "ALYCIA", "TOMMY", "RONNA", "NORENE", "MERCY", "JOSE", "INGEBORG", "GIOVANNA", - "GEMMA", "CHRISTEL", "AUDRY", "ZORA", "VITA", "VAN", "TRISH", "STEPHAINE", "SHIRLEE", "SHANIKA", - "MELONIE", "MAZIE", "JAZMIN", "INGA", "HOA", "HETTIE", "GERALYN", "FONDA", "ESTRELLA", "ADELLA", - "SU", "SARITA", "RINA", "MILISSA", "MARIBETH", "GOLDA", "EVON", "ETHELYN", "ENEDINA", "CHERISE", - "CHANA", "VELVA", "TAWANNA", "SADE", "MIRTA", "LI", "KARIE", "JACINTA", "ELNA", "DAVINA", - "CIERRA", "ASHLIE", "ALBERTHA", "TANESHA", "STEPHANI", "NELLE", "MINDI", "LU", "LORINDA", "LARUE", - "FLORENE", "DEMETRA", "DEDRA", "CIARA", "CHANTELLE", "ASHLY", "SUZY", "ROSALVA", "NOELIA", "LYDA", - "LEATHA", "KRYSTYNA", "KRISTAN", "KARRI", "DARLINE", "DARCIE", "CINDA", "CHEYENNE", "CHERRIE", "AWILDA", - "ALMEDA", "ROLANDA", "LANETTE", "JERILYN", "GISELE", "EVALYN", "CYNDI", "CLETA", "CARIN", "ZINA", - "ZENA", "VELIA", "TANIKA", "PAUL", "CHARISSA", "THOMAS", "TALIA", "MARGARETE", "LAVONDA", "KAYLEE", - "KATHLENE", "JONNA", "IRENA", "ILONA", "IDALIA", "CANDIS", "CANDANCE", "BRANDEE", "ANITRA", "ALIDA", - "SIGRID", "NICOLETTE", "MARYJO", "LINETTE", "HEDWIG", "CHRISTIANA", "CASSIDY", "ALEXIA", "TRESSIE", "MODESTA", - "LUPITA", "LITA", "GLADIS", "EVELIA", "DAVIDA", "CHERRI", "CECILY", "ASHELY", "ANNABEL", "AGUSTINA", - "WANITA", "SHIRLY", "ROSAURA", "HULDA", "EUN", "BAILEY", "YETTA", "VERONA", "THOMASINA", "SIBYL", - "SHANNAN", "MECHELLE", "LUE", "LEANDRA", "LANI", "KYLEE", "KANDY", "JOLYNN", "FERNE", "EBONI", - "CORENE", "ALYSIA", "ZULA", "NADA", "MOIRA", "LYNDSAY", "LORRETTA", "JUAN", "JAMMIE", "HORTENSIA", - "GAYNELL", "CAMERON", "ADRIA", "VINA", "VICENTA", "TANGELA", "STEPHINE", "NORINE", "NELLA", "LIANA", - "LESLEE", "KIMBERELY", "ILIANA", "GLORY", "FELICA", "EMOGENE", "ELFRIEDE", "EDEN", "EARTHA", "CARMA", - "BEA", "OCIE", "MARRY", "LENNIE", "KIARA", "JACALYN", "CARLOTA", "ARIELLE", "YU", "STAR", - "OTILIA", "KIRSTIN", "KACEY", "JOHNETTA", "JOEY", "JOETTA", "JERALDINE", "JAUNITA", "ELANA", "DORTHEA", - "CAMI", "AMADA", "ADELIA", "VERNITA", "TAMAR", "SIOBHAN", "RENEA", "RASHIDA", "OUIDA", "ODELL", - "NILSA", "MERYL", "KRISTYN", "JULIETA", "DANICA", "BREANNE", "AUREA", "ANGLEA", "SHERRON", "ODETTE", - "MALIA", "LORELEI", "LIN", "LEESA", "KENNA", "KATHLYN", "FIONA", "CHARLETTE", "SUZIE", "SHANTELL", - "SABRA", "RACQUEL", "MYONG", "MIRA", "MARTINE", "LUCIENNE", "LAVADA", "JULIANN", "JOHNIE", "ELVERA", - "DELPHIA", "CLAIR", "CHRISTIANE", "CHAROLETTE", "CARRI", "AUGUSTINE", "ASHA", "ANGELLA", "PAOLA", "NINFA", - "LEDA", "LAI", "EDA", "SUNSHINE", "STEFANI", "SHANELL", "PALMA", "MACHELLE", "LISSA", "KECIA", - "KATHRYNE", "KARLENE", "JULISSA", "JETTIE", "JENNIFFER", "HUI", "CORRINA", "CHRISTOPHER", "CAROLANN", "ALENA", - "TESS", "ROSARIA", "MYRTICE", "MARYLEE", "LIANE", "KENYATTA", "JUDIE", "JANEY", "IN", "ELMIRA", - "ELDORA", "DENNA", "CRISTI", "CATHI", "ZAIDA", "VONNIE", "VIVA", "VERNIE", "ROSALINE", "MARIELA", - "LUCIANA", "LESLI", "KARAN", "FELICE", "DENEEN", "ADINA", "WYNONA", "TARSHA", "SHERON", "SHASTA", - "SHANITA", "SHANI", "SHANDRA", "RANDA", "PINKIE", "PARIS", "NELIDA", "MARILOU", "LYLA", "LAURENE", - "LACI", "JOI", "JANENE", "DOROTHA", "DANIELE", "DANI", "CAROLYNN", "CARLYN", "BERENICE", "AYESHA", - "ANNELIESE", "ALETHEA", "THERSA", "TAMIKO", "RUFINA", "OLIVA", "MOZELL", "MARYLYN", "MADISON", "KRISTIAN", - "KATHYRN", "KASANDRA", "KANDACE", "JANAE", "GABRIEL", "DOMENICA", "DEBBRA", "DANNIELLE", "CHUN", "BUFFY", - "BARBIE", "ARCELIA", "AJA", "ZENOBIA", "SHAREN", "SHAREE", "PATRICK", "PAGE", "MY", "LAVINIA", - "KUM", "KACIE", "JACKELINE", "HUONG", "FELISA", "EMELIA", "ELEANORA", "CYTHIA", "CRISTIN", "CLYDE", - "CLARIBEL", "CARON", "ANASTACIA", "ZULMA", "ZANDRA", "YOKO", "TENISHA", "SUSANN", "SHERILYN", "SHAY", - "SHAWANDA", "SABINE", "ROMANA", "MATHILDA", "LINSEY", "KEIKO", "JOANA", "ISELA", "GRETTA", "GEORGETTA", - "EUGENIE", "DUSTY", "DESIRAE", "DELORA", "CORAZON", "ANTONINA", "ANIKA", "WILLENE", "TRACEE", "TAMATHA", - "REGAN", "NICHELLE", "MICKIE", "MAEGAN", "LUANA", "LANITA", "KELSIE", "EDELMIRA", "BREE", "AFTON", - "TEODORA", "TAMIE", "SHENA", "MEG", "LINH", "KELI", "KACI", "DANYELLE", "BRITT", "ARLETTE", - "ALBERTINE", "ADELLE", "TIFFINY", "STORMY", "SIMONA", "NUMBERS", "NICOLASA", "NICHOL", "NIA", "NAKISHA", - "MEE", "MAIRA", "LOREEN", "KIZZY", "JOHNNY", "JAY", "FALLON", "CHRISTENE", "BOBBYE", "ANTHONY", - "YING", "VINCENZA", "TANJA", "RUBIE", "RONI", "QUEENIE", "MARGARETT", "KIMBERLI", "IRMGARD", "IDELL", - "HILMA", "EVELINA", "ESTA", "EMILEE", "DENNISE", "DANIA", "CARL", "CARIE", "ANTONIO", "WAI", - "SANG", "RISA", "RIKKI", "PARTICIA", "MUI", "MASAKO", "MARIO", "LUVENIA", "LOREE", "LONI", - "LIEN", "KEVIN", "GIGI", "FLORENCIA", "DORIAN", "DENITA", "DALLAS", "CHI", "BILLYE", "ALEXANDER", - "TOMIKA", "SHARITA", "RANA", "NIKOLE", "NEOMA", "MARGARITE", "MADALYN", "LUCINA", "LAILA", "KALI", - "JENETTE", "GABRIELE", "EVELYNE", "ELENORA", "CLEMENTINA", "ALEJANDRINA", "ZULEMA", "VIOLETTE", "VANNESSA", "THRESA", - "RETTA", "PIA", "PATIENCE", "NOELLA", "NICKIE", "JONELL", "DELTA", "CHUNG", "CHAYA", "CAMELIA", - "BETHEL", "ANYA", "ANDREW", "THANH", "SUZANN", "SPRING", "SHU", "MILA", "LILLA", "LAVERNA", - "KEESHA", "KATTIE", "GIA", "GEORGENE", "EVELINE", "ESTELL", "ELIZBETH", "VIVIENNE", "VALLIE", "TRUDIE", - "STEPHANE", "MICHEL", "MAGALY", "MADIE", "KENYETTA", "KARREN", "JANETTA", "HERMINE", "HARMONY", "DRUCILLA", - "DEBBI", "CELESTINA", "CANDIE", "BRITNI", "BECKIE", "AMINA", "ZITA", "YUN", "YOLANDE", "VIVIEN", - "VERNETTA", "TRUDI", "SOMMER", "PEARLE", "PATRINA", "OSSIE", "NICOLLE", "LOYCE", "LETTY", "LARISA", - "KATHARINA", "JOSELYN", "JONELLE", "JENELL", "IESHA", "HEIDE", "FLORINDA", "FLORENTINA", "FLO", "ELODIA", - "DORINE", "BRUNILDA", "BRIGID", "ASHLI", "ARDELLA", "TWANA", "THU", "TARAH", "SUNG", "SHEA", - "SHAVON", "SHANE", "SERINA", "RAYNA", "RAMONITA", "NGA", "MARGURITE", "LUCRECIA", "KOURTNEY", "KATI", - "JESUS", "JESENIA", "DIAMOND", "CRISTA", "AYANA", "ALICA", "ALIA", "VINNIE", "SUELLEN", "ROMELIA", - "RACHELL", "PIPER", "OLYMPIA", "MICHIKO", "KATHALEEN", "JOLIE", "JESSI", "JANESSA", "HANA", "HA", - "ELEASE", "CARLETTA", "BRITANY", "SHONA", "SALOME", "ROSAMOND", "REGENA", "RAINA", "NGOC", "NELIA", - "LOUVENIA", "LESIA", "LATRINA", "LATICIA", "LARHONDA", "JINA", "JACKI", "HOLLIS", "HOLLEY", "EMMY", - "DEEANN", "CORETTA", "ARNETTA", "VELVET", "THALIA", "SHANICE", "NETA", "MIKKI", "MICKI", "LONNA", - "LEANA", "LASHUNDA", "KILEY", "JOYE", "JACQULYN", "IGNACIA", "HYUN", "HIROKO", "HENRY", "HENRIETTE", - "ELAYNE", "DELINDA", "DARNELL", "DAHLIA", "COREEN", "CONSUELA", "CONCHITA", "CELINE", "BABETTE", "AYANNA", - "ANETTE", "ALBERTINA", "SKYE", "SHAWNEE", "SHANEKA", "QUIANA", "PAMELIA", "MIN", "MERRI", "MERLENE", - "MARGIT", "KIESHA", "KIERA", "KAYLENE", "JODEE", "JENISE", "ERLENE", "EMMIE", "ELSE", "DARYL", - "DALILA", "DAISEY", "CODY", "CASIE", "BELIA", "BABARA", "VERSIE", "VANESA", "SHELBA", "SHAWNDA", - "SAM", "NORMAN", "NIKIA", "NAOMA", "MARNA", "MARGERET", "MADALINE", "LAWANA", "KINDRA", "JUTTA", - "JAZMINE", "JANETT", "HANNELORE", "GLENDORA", "GERTRUD", "GARNETT", "FREEDA", "FREDERICA", "FLORANCE", "FLAVIA", - "DENNIS", "CARLINE", "BEVERLEE", "ANJANETTE", "VALDA", "TRINITY", "TAMALA", "STEVIE", "SHONNA", "SHA", - "SARINA", "ONEIDA", "MICAH", "MERILYN", "MARLEEN", "LURLINE", "LENNA", "KATHERIN", "JIN", "JENI", - "HAE", "GRACIA", "GLADY", "FARAH", "ERIC", "ENOLA", "EMA", "DOMINQUE", "DEVONA", "DELANA", - "CECILA", "CAPRICE", "ALYSHA", "ALI", "ALETHIA", "VENA", "THERESIA", "TAWNY", "SONG", "SHAKIRA", - "SAMARA", "SACHIKO", "RACHELE", "PAMELLA", "NICKY", "MARNI", "MARIEL", "MAREN", "MALISA", "LIGIA", - "LERA", "LATORIA", "LARAE", "KIMBER", "KATHERN", "KAREY", "JENNEFER", "JANETH", "HALINA", "FREDIA", - "DELISA", "DEBROAH", "CIERA", "CHIN", "ANGELIKA", "ANDREE", "ALTHA", "YEN", "VIVAN", "TERRESA", - "TANNA", "SUK", "SUDIE", "SOO", "SIGNE", "SALENA", "RONNI", "REBBECCA", "MYRTIE", "MCKENZIE", - "MALIKA", "MAIDA", "LOAN", "LEONARDA", "KAYLEIGH", "FRANCE", "ETHYL", "ELLYN", "DAYLE", "CAMMIE", - "BRITTNI", "BIRGIT", "AVELINA", "ASUNCION", "ARIANNA", "AKIKO", "VENICE", "TYESHA", "TONIE", "TIESHA", - "TAKISHA", "STEFFANIE", "SINDY", "SANTANA", "MEGHANN", "MANDA", "MACIE", "LADY", "KELLYE", "KELLEE", - "JOSLYN", "JASON", "INGER", "INDIRA", "GLINDA", "GLENNIS", "FERNANDA", "FAUSTINA", "ENEIDA", "ELICIA", - "DOT", "DIGNA", "DELL", "ARLETTA", "ANDRE", "WILLIA", "TAMMARA", "TABETHA", "SHERRELL", "SARI", - "REFUGIO", "REBBECA", "PAULETTA", "NIEVES", "NATOSHA", "NAKITA", "MAMMIE", "KENISHA", "KAZUKO", "KASSIE", - "GARY", "EARLEAN", "DAPHINE", "CORLISS", "CLOTILDE", "CAROLYNE", "BERNETTA", "AUGUSTINA", "AUDREA", "ANNIS", - "ANNABELL", "YAN", "TENNILLE", "TAMICA", "SELENE", "SEAN", "ROSANA", "REGENIA", "QIANA", "MARKITA", - "MACY", "LEEANNE", "LAURINE", "KYM", "JESSENIA", "JANITA", "GEORGINE", "GENIE", "EMIKO", "ELVIE", - "DEANDRA", "DAGMAR", "CORIE", "COLLEN", "CHERISH", "ROMAINE", "PORSHA", "PEARLENE", "MICHELINE", "MERNA", - "MARGORIE", "MARGARETTA", "LORE", "KENNETH", "JENINE", "HERMINA", "FREDERICKA", "ELKE", "DRUSILLA", "DORATHY", - "DIONE", "DESIRE", "CELENA", "BRIGIDA", "ANGELES", "ALLEGRA", "THEO", "TAMEKIA", "SYNTHIA", "STEPHEN", - "SOOK", "SLYVIA", "ROSANN", "REATHA", "RAYE", "MARQUETTA", "MARGART", "LING", "LAYLA", "KYMBERLY", - "KIANA", "KAYLEEN", "KATLYN", "KARMEN", "JOELLA", "IRINA", "EMELDA", "ELENI", "DETRA", "CLEMMIE", - "CHERYLL", "CHANTELL", "CATHEY", "ARNITA", "ARLA", "ANGLE", "ANGELIC", "ALYSE", "ZOFIA", "THOMASINE", - "TENNIE", "SON", "SHERLY", "SHERLEY", "SHARYL", "REMEDIOS", "PETRINA", "NICKOLE", "MYUNG", "MYRLE", - "MOZELLA", "LOUANNE", "LISHA", "LATIA", "LANE", "KRYSTA", "JULIENNE", "JOEL", "JEANENE", "JACQUALINE", - "ISAURA", "GWENDA", "EARLEEN", "DONALD", "CLEOPATRA", "CARLIE", "AUDIE", "ANTONIETTA", "ALISE", "ALEX", - "VERDELL", "VAL", "TYLER", "TOMOKO", "THAO", "TALISHA", "STEVEN", "SO", "SHEMIKA", "SHAUN", - "SCARLET", "SAVANNA", "SANTINA", "ROSIA", "RAEANN", "ODILIA", "NANA", "MINNA", "MAGAN", "LYNELLE", - "LE", "KARMA", "JOEANN", "IVANA", "INELL", "ILANA", "HYE", "HONEY", "HEE", "GUDRUN", - "FRANK", "DREAMA", "CRISSY", "CHANTE", "CARMELINA", "ARVILLA", "ARTHUR", "ANNAMAE", "ALVERA", "ALEIDA", - "AARON", "YEE", "YANIRA", "VANDA", "TIANNA", "TAM", "STEFANIA", "SHIRA", "PERRY", "NICOL", - "NANCIE", "MONSERRATE", "MINH", "MELYNDA", "MELANY", "MATTHEW", "LOVELLA", "LAURE", "KIRBY", "KACY", - "JACQUELYNN", "HYON", "GERTHA", "FRANCISCO", "ELIANA", "CHRISTENA", "CHRISTEEN", "CHARISE", "CATERINA", "CARLEY", - "CANDYCE", "ARLENA", "AMMIE", "YANG", "WILLETTE", "VANITA", "TUYET", "TINY", "SYREETA", "SILVA", - "SCOTT", "RONALD", "PENNEY", "NYLA", "MICHAL", "MAURICE", "MARYAM", "MARYA", "MAGEN", "LUDIE", - "LOMA", "LIVIA", "LANELL", "KIMBERLIE", "JULEE", "DONETTA", "DIEDRA", "DENISHA", "DEANE", "DAWNE", - "CLARINE", "CHERRYL", "BRONWYN", "BRANDON", "ALLA", "VALERY", "TONDA", "SUEANN", "SORAYA", "SHOSHANA", - "SHELA", "SHARLEEN", "SHANELLE", "NERISSA", "MICHEAL", "MERIDITH", "MELLIE", "MAYE", "MAPLE", "MAGARET", - "LUIS", "LILI", "LEONILA", "LEONIE", "LEEANNA", "LAVONIA", "LAVERA", "KRISTEL", "KATHEY", "KATHE", - "JUSTIN", "JULIAN", "JIMMY", "JANN", "ILDA", "HILDRED", "HILDEGARDE", "GENIA", "FUMIKO", "EVELIN", - "ERMELINDA", "ELLY", "DUNG", "DOLORIS", "DIONNA", "DANAE", "BERNEICE", "ANNICE", "ALIX", "VERENA", - "VERDIE", "TRISTAN", "SHAWNNA", "SHAWANA", "SHAUNNA", "ROZELLA", "RANDEE", "RANAE", "MILAGRO", "LYNELL", - "LUISE", "LOUIE", "LOIDA", "LISBETH", "KARLEEN", "JUNITA", "JONA", "ISIS", "HYACINTH", "HEDY", - "GWENN", "ETHELENE", "ERLINE", "EDWARD", "DONYA", "DOMONIQUE", "DELICIA", "DANNETTE", "CICELY", "BRANDA", - "BLYTHE", "BETHANN", "ASHLYN", "ANNALEE", "ALLINE", "YUKO", "VELLA", "TRANG", "TOWANDA", "TESHA", - "SHERLYN", "NARCISA", "MIGUELINA", "MERI", "MAYBELL", "MARLANA", "MARGUERITA", "MADLYN", "LUNA", "LORY", - "LORIANN", "LIBERTY", "LEONORE", "LEIGHANN", "LAURICE", "LATESHA", "LARONDA", "KATRICE", "KASIE", "KARL", - "KALEY", "JADWIGA", "GLENNIE", "GEARLDINE", "FRANCINA", "EPIFANIA", "DYAN", "DORIE", "DIEDRE", "DENESE", - "DEMETRICE", "DELENA", "DARBY", "CRISTIE", "CLEORA", "CATARINA", "CARISA", "BERNIE", "BARBERA", "ALMETA", - "TRULA", "TEREASA", "SOLANGE", "SHEILAH", "SHAVONNE", "SANORA", "ROCHELL", "MATHILDE", "MARGARETA", "MAIA", - "LYNSEY", "LAWANNA", "LAUNA", "KENA", "KEENA", "KATIA", "JAMEY", "GLYNDA", "GAYLENE", "ELVINA", - "ELANOR", "DANUTA", "DANIKA", "CRISTEN", "CORDIE", "COLETTA", "CLARITA", "CARMON", "BRYNN", "AZUCENA", - "AUNDREA", "ANGELE", "YI", "WALTER", "VERLIE", "VERLENE", "TAMESHA", "SILVANA", "SEBRINA", "SAMIRA", - "REDA", "RAYLENE", "PENNI", "PANDORA", "NORAH", "NOMA", "MIREILLE", "MELISSIA", "MARYALICE", "LARAINE", - "KIMBERY", "KARYL", "KARINE", "KAM", "JOLANDA", "JOHANA", "JESUSA", "JALEESA", "JAE", "JACQUELYNE", - "IRISH", "ILUMINADA", "HILARIA", "HANH", "GENNIE", "FRANCIE", "FLORETTA", "EXIE", "EDDA", "DREMA", - "DELPHA", "BEV", "BARBAR", "ASSUNTA", "ARDELL", "ANNALISA", "ALISIA", "YUKIKO", "YOLANDO", "WONDA", - "WEI", "WALTRAUD", "VETA", "TEQUILA", "TEMEKA", "TAMEIKA", "SHIRLEEN", "SHENITA", "PIEDAD", "OZELLA", - "MIRTHA", "MARILU", "KIMIKO", "JULIANE", "JENICE", "JEN", "JANAY", "JACQUILINE", "HILDE", "FE", - "FAE", "EVAN", "EUGENE", "ELOIS", "ECHO", "DEVORAH", "CHAU", "BRINDA", "BETSEY", "ARMINDA", - "ARACELIS", "APRYL", "ANNETT", "ALISHIA", "VEOLA", "USHA", "TOSHIKO", "THEOLA", "TASHIA", "TALITHA", - "SHERY", "RUDY", "RENETTA", "REIKO", "RASHEEDA", "OMEGA", "OBDULIA", "MIKA", "MELAINE", "MEGGAN", - "MARTIN", "MARLEN", "MARGET", "MARCELINE", "MANA", "MAGDALEN", "LIBRADA", "LEZLIE", "LEXIE", "LATASHIA", - "LASANDRA", "KELLE", "ISIDRA", "ISA", "INOCENCIA", "GWYN", "FRANCOISE", "ERMINIA", "ERINN", "DIMPLE", - "DEVORA", "CRISELDA", "ARMANDA", "ARIE", "ARIANE", "ANGELO", "ANGELENA", "ALLEN", "ALIZA", "ADRIENE", - "ADALINE", "XOCHITL", "TWANNA", "TRAN", "TOMIKO", "TAMISHA", "TAISHA", "SUSY", "SIU", "RUTHA", - "ROXY", "RHONA", "RAYMOND", "OTHA", "NORIKO", "NATASHIA", "MERRIE", "MELVIN", "MARINDA", "MARIKO", - "MARGERT", "LORIS", "LIZZETTE", "LEISHA", "KAILA", "KA", "JOANNIE", "JERRICA", "JENE", "JANNET", - "JANEE", "JACINDA", "HERTA", "ELENORE", "DORETTA", "DELAINE", "DANIELL", "CLAUDIE", "CHINA", "BRITTA", - "APOLONIA", "AMBERLY", "ALEASE", "YURI", "YUK", "WEN", "WANETA", "UTE", "TOMI", "SHARRI", - "SANDIE", "ROSELLE", "REYNALDA", "RAGUEL", "PHYLICIA", "PATRIA", "OLIMPIA", "ODELIA", "MITZIE", "MITCHELL", - "MISS", "MINDA", "MIGNON", "MICA", "MENDY", "MARIVEL", "MAILE", "LYNETTA", "LAVETTE", "LAURYN", - "LATRISHA", "LAKIESHA", "KIERSTEN", "KARY", "JOSPHINE", "JOLYN", "JETTA", "JANISE", "JACQUIE", "IVELISSE", - "GLYNIS", "GIANNA", "GAYNELLE", "EMERALD", "DEMETRIUS", "DANYELL", "DANILLE", "DACIA", "CORALEE", "CHER", - "CEOLA", "BRETT", "BELL", "ARIANNE", "ALESHIA", "YUNG", "WILLIEMAE", "TROY", "TRINH", "THORA", - "TAI", "SVETLANA", "SHERIKA", "SHEMEKA", "SHAUNDA", "ROSELINE", "RICKI", "MELDA", "MALLIE", "LAVONNA", - "LATINA", "LARRY", "LAQUANDA", "LALA", "LACHELLE", "KLARA", "KANDIS", "JOHNA", "JEANMARIE", "JAYE", - "HANG", "GRAYCE", "GERTUDE", "EMERITA", "EBONIE", "CLORINDA", "CHING", "CHERY", "CAROLA", "BREANN", - "BLOSSOM", "BERNARDINE", "BECKI", "ARLETHA", "ARGELIA", "ARA", "ALITA", "YULANDA", "YON", "YESSENIA", - "TOBI", "TASIA", "SYLVIE", "SHIRL", "SHIRELY", "SHERIDAN", "SHELLA", "SHANTELLE", "SACHA", "ROYCE", - "REBECKA", "REAGAN", "PROVIDENCIA", "PAULENE", "MISHA", "MIKI", "MARLINE", "MARICA", "LORITA", "LATOYIA", - "LASONYA", "KERSTIN", "KENDA", "KEITHA", "KATHRIN", "JAYMIE", "JACK", "GRICELDA", "GINETTE", "ERYN", - "ELINA", "ELFRIEDA", "DANYEL", "CHEREE", "CHANELLE", "BARRIE", "AVERY", "AURORE", "ANNAMARIA", "ALLEEN", - "AILENE", "AIDE", "YASMINE", "VASHTI", "VALENTINE", "TREASA", "TORY", "TIFFANEY", "SHERYLL", "SHARIE", - "SHANAE", "SAU", "RAISA", "PA", "NEDA", "MITSUKO", "MIRELLA", "MILDA", "MARYANNA", "MARAGRET", - "MABELLE", "LUETTA", "LORINA", "LETISHA", "LATARSHA", "LANELLE", "LAJUANA", "KRISSY", "KARLY", "KARENA", - "JON", "JESSIKA", "JERICA", "JEANELLE", "JANUARY", "JALISA", "JACELYN", "IZOLA", "IVEY", "GREGORY", - "EUNA", "ETHA", "DREW", "DOMITILA", "DOMINICA", "DAINA", "CREOLA", "CARLI", "CAMIE", "BUNNY", - "BRITTNY", "ASHANTI", "ANISHA", "ALEEN", "ADAH", "YASUKO", "WINTER", "VIKI", "VALRIE", "TONA", - "TINISHA", "THI", "TERISA", "TATUM", "TANEKA", "SIMONNE", "SHALANDA", "SERITA", "RESSIE", "REFUGIA", - "PAZ", "OLENE", "NA", "MERRILL", "MARGHERITA", "MANDIE", "MAN", "MAIRE", "LYNDIA", "LUCI", - "LORRIANE", "LORETA", "LEONIA", "LAVONA", "LASHAWNDA", "LAKIA", "KYOKO", "KRYSTINA", "KRYSTEN", "KENIA", - "KELSI", "JUDE", "JEANICE", "ISOBEL", "GEORGIANN", "GENNY", "FELICIDAD", "EILENE", "DEON", "DELOISE", - "DEEDEE", "DANNIE", "CONCEPTION", "CLORA", "CHERILYN", "CHANG", "CALANDRA", "BERRY", "ARMANDINA", "ANISA", - "ULA", "TIMOTHY", "TIERA", "THERESSA", "STEPHANIA", "SIMA", "SHYLA", "SHONTA", "SHERA", "SHAQUITA", - "SHALA", "SAMMY", "ROSSANA", "NOHEMI", "NERY", "MORIAH", "MELITA", "MELIDA", "MELANI", "MARYLYNN", - "MARISHA", "MARIETTE", "MALORIE", "MADELENE", "LUDIVINA", "LORIA", "LORETTE", "LORALEE", "LIANNE", "LEON", - "LAVENIA", "LAURINDA", "LASHON", "KIT", "KIMI", "KEILA", "KATELYNN", "KAI", "JONE", "JOANE", - "JI", "JAYNA", "JANELLA", "JA", "HUE", "HERTHA", "FRANCENE", "ELINORE", "DESPINA", "DELSIE", - "DEEDRA", "CLEMENCIA", "CARRY", "CAROLIN", "CARLOS", "BULAH", "BRITTANIE", "BOK", "BLONDELL", "BIBI", - "BEAULAH", "BEATA", "ANNITA", "AGRIPINA", "VIRGEN", "VALENE", "UN", "TWANDA", "TOMMYE", "TOI", - "TARRA", "TARI", "TAMMERA", "SHAKIA", "SADYE", "RUTHANNE", "ROCHEL", "RIVKA", "PURA", "NENITA", - "NATISHA", "MING", "MERRILEE", "MELODEE", "MARVIS", "LUCILLA", "LEENA", "LAVETA", "LARITA", "LANIE", - "KEREN", "ILEEN", "GEORGEANN", "GENNA", "GENESIS", "FRIDA", "EWA", "EUFEMIA", "EMELY", "ELA", - "EDYTH", "DEONNA", "DEADRA", "DARLENA", "CHANELL", "CHAN", "CATHERN", "CASSONDRA", "CASSAUNDRA", "BERNARDA", - "BERNA", "ARLINDA", "ANAMARIA", "ALBERT", "WESLEY", "VERTIE", "VALERI", "TORRI", "TATYANA", "STASIA", - "SHERISE", "SHERILL", "SEASON", "SCOTTIE", "SANDA", "RUTHE", "ROSY", "ROBERTO", "ROBBI", "RANEE", - "QUYEN", "PEARLY", "PALMIRA", "ONITA", "NISHA", "NIESHA", "NIDA", "NEVADA", "NAM", "MERLYN", - "MAYOLA", "MARYLOUISE", "MARYLAND", "MARX", "MARTH", "MARGENE", "MADELAINE", "LONDA", "LEONTINE", "LEOMA", - "LEIA", "LAWRENCE", "LAURALEE", "LANORA", "LAKITA", "KIYOKO", "KETURAH", "KATELIN", "KAREEN", "JONIE", - "JOHNETTE", "JENEE", "JEANETT", "IZETTA", "HIEDI", "HEIKE", "HASSIE", "HAROLD", "GIUSEPPINA", "GEORGANN", - "FIDELA", "FERNANDE", "ELWANDA", "ELLAMAE", "ELIZ", "DUSTI", "DOTTY", "CYNDY", "CORALIE", "CELESTA", - "ARGENTINA", "ALVERTA", "XENIA", "WAVA", "VANETTA", "TORRIE", "TASHINA", "TANDY", "TAMBRA", "TAMA", - "STEPANIE", "SHILA", "SHAUNTA", "SHARAN", "SHANIQUA", "SHAE", "SETSUKO", "SERAFINA", "SANDEE", "ROSAMARIA", - "PRISCILA", "OLINDA", "NADENE", "MUOI", "MICHELINA", "MERCEDEZ", "MARYROSE", "MARIN", "MARCENE", "MAO", - "MAGALI", "MAFALDA", "LOGAN", "LINN", "LANNIE", "KAYCE", "KAROLINE", "KAMILAH", "KAMALA", "JUSTA", - "JOLINE", "JENNINE", "JACQUETTA", "IRAIDA", "GERALD", "GEORGEANNA", "FRANCHESCA", "FAIRY", "EMELINE", "ELANE", - "EHTEL", "EARLIE", "DULCIE", "DALENE", "CRIS", "CLASSIE", "CHERE", "CHARIS", "CAROYLN", "CARMINA", - "CARITA", "BRIAN", "BETHANIE", "AYAKO", "ARICA", "AN", "ALYSA", "ALESSANDRA", "AKILAH", "ADRIEN", - "ZETTA", "YOULANDA", "YELENA", "YAHAIRA", "XUAN", "WENDOLYN", "VICTOR", "TIJUANA", "TERRELL", "TERINA", - "TERESIA", "SUZI", "SUNDAY", "SHERELL", "SHAVONDA", "SHAUNTE", "SHARDA", "SHAKITA", "SENA", "RYANN", - "RUBI", "RIVA", "REGINIA", "REA", "RACHAL", "PARTHENIA", "PAMULA", "MONNIE", "MONET", "MICHAELE", - "MELIA", "MARINE", "MALKA", "MAISHA", "LISANDRA", "LEO", "LEKISHA", "LEAN", "LAURENCE", "LAKENDRA", - "KRYSTIN", "KORTNEY", "KIZZIE", "KITTIE", "KERA", "KENDAL", "KEMBERLY", "KANISHA", "JULENE", "JULE", - "JOSHUA", "JOHANNE", "JEFFREY", "JAMEE", "HAN", "HALLEY", "GIDGET", "GALINA", "FREDRICKA", "FLETA", - "FATIMAH", "EUSEBIA", "ELZA", "ELEONORE", "DORTHEY", "DORIA", "DONELLA", "DINORAH", "DELORSE", "CLARETHA", - "CHRISTINIA", "CHARLYN", "BONG", "BELKIS", "AZZIE", "ANDERA", "AIKO", "ADENA", "YER", "YAJAIRA", - "WAN", "VANIA", "ULRIKE", "TOSHIA", "TIFANY", "STEFANY", "SHIZUE", "SHENIKA", "SHAWANNA", "SHAROLYN", - "SHARILYN", "SHAQUANA", "SHANTAY", "SEE", "ROZANNE", "ROSELEE", "RICKIE", "REMONA", "REANNA", "RAELENE", - "QUINN", "PHUNG", "PETRONILA", "NATACHA", "NANCEY", "MYRL", "MIYOKO", "MIESHA", "MERIDETH", "MARVELLA", - "MARQUITTA", "MARHTA", "MARCHELLE", "LIZETH", "LIBBIE", "LAHOMA", "LADAWN", "KINA", "KATHELEEN", "KATHARYN", - "KARISA", "KALEIGH", "JUNIE", "JULIEANN", "JOHNSIE", "JANEAN", "JAIMEE", "JACKQUELINE", "HISAKO", "HERMA", - "HELAINE", "GWYNETH", "GLENN", "GITA", "EUSTOLIA", "EMELINA", "ELIN", "EDRIS", "DONNETTE", "DONNETTA", - "DIERDRE", "DENAE", "DARCEL", "CLAUDE", "CLARISA", "CINDERELLA", "CHIA", "CHARLESETTA", "CHARITA", "CELSA", - "CASSY", "CASSI", "CARLEE", "BRUNA", "BRITTANEY", "BRANDE", "BILLI", "BAO", "ANTONETTA", "ANGLA", - "ANGELYN", "ANALISA", "ALANE", "WENONA", "WENDIE", "VERONIQUE", "VANNESA", "TOBIE", "TEMPIE", "SUMIKO", - "SULEMA", "SPARKLE", "SOMER", "SHEBA", "SHAYNE", "SHARICE", "SHANEL", "SHALON", "SAGE", "ROY", - "ROSIO", "ROSELIA", "RENAY", "REMA", "REENA", "PORSCHE", "PING", "PEG", "OZIE", "ORETHA", - "ORALEE", "ODA", "NU", "NGAN", "NAKESHA", "MILLY", "MARYBELLE", "MARLIN", "MARIS", "MARGRETT", - "MARAGARET", "MANIE", "LURLENE", "LILLIA", "LIESELOTTE", "LAVELLE", "LASHAUNDA", "LAKEESHA", "KEITH", "KAYCEE", - "KALYN", "JOYA", "JOETTE", "JENAE", "JANIECE", "ILLA", "GRISEL", "GLAYDS", "GENEVIE", "GALA", - "FREDDA", "FRED", "ELMER", "ELEONOR", "DEBERA", "DEANDREA", "DAN", "CORRINNE", "CORDIA", "CONTESSA", - "COLENE", "CLEOTILDE", "CHARLOTT", "CHANTAY", "CECILLE", "BEATRIS", "AZALEE", "ARLEAN", "ARDATH", "ANJELICA", - "ANJA", "ALFREDIA", "ALEISHA", "ADAM", "ZADA", "YUONNE", "XIAO", "WILLODEAN", "WHITLEY", "VENNIE", - "VANNA", "TYISHA", "TOVA", "TORIE", "TONISHA", "TILDA", "TIEN", "TEMPLE", "SIRENA", "SHERRIL", - "SHANTI", "SHAN", "SENAIDA", "SAMELLA", "ROBBYN", "RENDA", "REITA", "PHEBE", "PAULITA", "NOBUKO", - "NGUYET", "NEOMI", "MOON", "MIKAELA", "MELANIA", "MAXIMINA", "MARG", "MAISIE", "LYNNA", "LILLI", - "LAYNE", "LASHAUN", "LAKENYA", "LAEL", "KIRSTIE", "KATHLINE", "KASHA", "KARLYN", "KARIMA", "JOVAN", - "JOSEFINE", "JENNELL", "JACQUI", "JACKELYN", "HYO", "HIEN", "GRAZYNA", "FLORRIE", "FLORIA", "ELEONORA", - "DWANA", "DORLA", "DONG", "DELMY", "DEJA", "DEDE", "DANN", "CRYSTA", "CLELIA", "CLARIS", - "CLARENCE", "CHIEKO", "CHERLYN", "CHERELLE", "CHARMAIN", "CHARA", "CAMMY", "BEE", "ARNETTE", "ARDELLE", - "ANNIKA", "AMIEE", "AMEE", "ALLENA", "YVONE", "YUKI", "YOSHIE", "YEVETTE", "YAEL", "WILLETTA", - "VONCILE", "VENETTA", "TULA", "TONETTE", "TIMIKA", "TEMIKA", "TELMA", "TEISHA", "TAREN", "TA", - "STACEE", "SHIN", "SHAWNTA", "SATURNINA", "RICARDA", "POK", "PASTY", "ONIE", "NUBIA", "MORA", - "MIKE", "MARIELLE", "MARIELLA", "MARIANELA", "MARDELL", "MANY", "LUANNA", "LOISE", "LISABETH", "LINDSY", - "LILLIANA", "LILLIAM", "LELAH", "LEIGHA", "LEANORA", "LANG", "KRISTEEN", "KHALILAH", "KEELEY", "KANDRA", - "JUNKO", "JOAQUINA", "JERLENE", "JANI", "JAMIKA", "JAME", "HSIU", "HERMILA", "GOLDEN", "GENEVIVE", - "EVIA", "EUGENA", "EMMALINE", "ELFREDA", "ELENE", "DONETTE", "DELCIE", "DEEANNA", "DARCEY", "CUC", - "CLARINDA", "CIRA", "CHAE", "CELINDA", "CATHERYN", "CATHERIN", "CASIMIRA", "CARMELIA", "CAMELLIA", "BREANA", - "BOBETTE", "BERNARDINA", "BEBE", "BASILIA", "ARLYNE", "AMAL", "ALAYNA", "ZONIA", "ZENIA", "YURIKO", - "YAEKO", "WYNELL", "WILLOW", "WILLENA", "VERNIA", "TU", "TRAVIS", "TORA", "TERRILYN", "TERICA", - "TENESHA", "TAWNA", "TAJUANA", "TAINA", "STEPHNIE", "SONA", "SOL", "SINA", "SHONDRA", "SHIZUKO", - "SHERLENE", "SHERICE", "SHARIKA", "ROSSIE", "ROSENA", "RORY", "RIMA", "RIA", "RHEBA", "RENNA", - "PETER", "NATALYA", "NANCEE", "MELODI", "MEDA", "MAXIMA", "MATHA", "MARKETTA", "MARICRUZ", "MARCELENE", - "MALVINA", "LUBA", "LOUETTA", "LEIDA", "LECIA", "LAURAN", "LASHAWNA", "LAINE", "KHADIJAH", "KATERINE", - "KASI", "KALLIE", "JULIETTA", "JESUSITA", "JESTINE", "JESSIA", "JEREMY", "JEFFIE", "JANYCE", "ISADORA", - "GEORGIANNE", "FIDELIA", "EVITA", "EURA", "EULAH", "ESTEFANA", "ELSY", "ELIZABET", "ELADIA", "DODIE", - "DION", "DIA", "DENISSE", "DELORAS", "DELILA", "DAYSI", "DAKOTA", "CURTIS", "CRYSTLE", "CONCHA", - "COLBY", "CLARETTA", "CHU", "CHRISTIA", "CHARLSIE", "CHARLENA", "CARYLON", "BETTYANN", "ASLEY", "ASHLEA", - "AMIRA", "AI", "AGUEDA", "AGNUS", "YUETTE", "VINITA", "VICTORINA", "TYNISHA", "TREENA", "TOCCARA", - "TISH", "THOMASENA", "TEGAN", "SOILA", "SHILOH", "SHENNA", "SHARMAINE", "SHANTAE", "SHANDI", "SEPTEMBER", - "SARAN", "SARAI", "SANA", "SAMUEL", "SALLEY", "ROSETTE", "ROLANDE", "REGINE", "OTELIA", "OSCAR", - "OLEVIA", "NICHOLLE", "NECOLE", "NAIDA", "MYRTA", "MYESHA", "MITSUE", "MINTA", "MERTIE", "MARGY", - "MAHALIA", "MADALENE", "LOVE", "LOURA", "LOREAN", "LEWIS", "LESHA", "LEONIDA", "LENITA", "LAVONE", - "LASHELL", "LASHANDRA", "LAMONICA", "KIMBRA", "KATHERINA", "KARRY", "KANESHA", "JULIO", "JONG", "JENEVA", - "JAQUELYN", "HWA", "GILMA", "GHISLAINE", "GERTRUDIS", "FRANSISCA", "FERMINA", "ETTIE", "ETSUKO", "ELLIS", - "ELLAN", "ELIDIA", "EDRA", "DORETHEA", "DOREATHA", "DENYSE", "DENNY", "DEETTA", "DAINE", "CYRSTAL", - "CORRIN", "CAYLA", "CARLITA", "CAMILA", "BURMA", "BULA", "BUENA", "BLAKE", "BARABARA", "AVRIL", - "AUSTIN", "ALAINE", "ZANA", "WILHEMINA", "WANETTA", "VIRGIL", "VI", "VERONIKA", "VERNON", "VERLINE", - "VASILIKI", "TONITA", "TISA", "TEOFILA", "TAYNA", "TAUNYA", "TANDRA", "TAKAKO", "SUNNI", "SUANNE", - "SIXTA", "SHARELL", "SEEMA", "RUSSELL", "ROSENDA", "ROBENA", "RAYMONDE", "PEI", "PAMILA", "OZELL", - "NEIDA", "NEELY", "MISTIE", "MICHA", "MERISSA", "MAURITA", "MARYLN", "MARYETTA", "MARSHALL", "MARCELL", - "MALENA", "MAKEDA", "MADDIE", "LOVETTA", "LOURIE", "LORRINE", "LORILEE", "LESTER", "LAURENA", "LASHAY", - "LARRAINE", "LAREE", "LACRESHA", "KRISTLE", "KRISHNA", "KEVA", "KEIRA", "KAROLE", "JOIE", "JINNY", - "JEANNETTA", "JAMA", "HEIDY", "GILBERTE", "GEMA", "FAVIOLA", "EVELYNN", "ENDA", "ELLI", "ELLENA", - "DIVINA", "DAGNY", "COLLENE", "CODI", "CINDIE", "CHASSIDY", "CHASIDY", "CATRICE", "CATHERINA", "CASSEY", - "CAROLL", "CARLENA", "CANDRA", "CALISTA", "BRYANNA", "BRITTENY", "BEULA", "BARI", "AUDRIE", "AUDRIA", - "ARDELIA", "ANNELLE", "ANGILA", "ALONA", "ALLYN", "DOUGLAS", "ROGER", "JONATHAN", "RALPH", "NICHOLAS", - "BENJAMIN", "BRUCE", "HARRY", "WAYNE", "STEVE", "HOWARD", "ERNEST", "PHILLIP", "TODD", "CRAIG", - "ALAN", "PHILIP", "EARL", "DANNY", "BRYAN", "STANLEY", "LEONARD", "NATHAN", "MANUEL", "RODNEY", - "MARVIN", "VINCENT", "JEFFERY", "JEFF", "CHAD", "JACOB", "ALFRED", "BRADLEY", "HERBERT", "FREDERICK", - "EDWIN", "DON", "RICKY", "RANDALL", "BARRY", "BERNARD", "LEROY", "MARCUS", "THEODORE", "CLIFFORD", - "MIGUEL", "JIM", "TOM", "CALVIN", "BILL", "LLOYD", "DEREK", "WARREN", "DARRELL", "JEROME", - "FLOYD", "ALVIN", "TIM", "GORDON", "GREG", "JORGE", "DUSTIN", "PEDRO", "DERRICK", "ZACHARY", - "HERMAN", "GLEN", "HECTOR", "RICARDO", "RICK", "BRENT", "RAMON", "GILBERT", "MARC", "REGINALD", - "RUBEN", "NATHANIEL", "RAFAEL", "EDGAR", "MILTON", "RAUL", "BEN", "CHESTER", "DUANE", "FRANKLIN", - "BRAD", "RON", "ROLAND", "ARNOLD", "HARVEY", "JARED", "ERIK", "DARRYL", "NEIL", "JAVIER", - "FERNANDO", "CLINTON", "TED", "MATHEW", "TYRONE", "DARREN", "LANCE", "KURT", "ALLAN", "NELSON", - "GUY", "CLAYTON", "HUGH", "MAX", "DWAYNE", "DWIGHT", "ARMANDO", "FELIX", "EVERETT", "IAN", - "WALLACE", "KEN", "BOB", "ALFREDO", "ALBERTO", "DAVE", "IVAN", "BYRON", "ISAAC", "MORRIS", - "CLIFTON", "WILLARD", "ROSS", "ANDY", "SALVADOR", "KIRK", "SERGIO", "SETH", "KENT", "TERRANCE", - "EDUARDO", "TERRENCE", "ENRIQUE", "WADE", "STUART", "FREDRICK", "ARTURO", "ALEJANDRO", "NICK", "LUTHER", - "WENDELL", "JEREMIAH", "JULIUS", "OTIS", "TREVOR", "OLIVER", "LUKE", "HOMER", "GERARD", "DOUG", - "KENNY", "HUBERT", "LYLE", "MATT", "ALFONSO", "ORLANDO", "REX", "CARLTON", "ERNESTO", "NEAL", - "PABLO", "LORENZO", "OMAR", "WILBUR", "GRANT", "HORACE", "RODERICK", "ABRAHAM", "WILLIS", "RICKEY", - "ANDRES", "CESAR", "JOHNATHAN", "MALCOLM", "RUDOLPH", "DAMON", "KELVIN", "PRESTON", "ALTON", "ARCHIE", - "MARCO", "WM", "PETE", "RANDOLPH", "GARRY", "GEOFFREY", "JONATHON", "FELIPE", "GERARDO", "ED", - "DOMINIC", "DELBERT", "COLIN", "GUILLERMO", "EARNEST", "LUCAS", "BENNY", "SPENCER", "RODOLFO", "MYRON", - "EDMUND", "GARRETT", "SALVATORE", "CEDRIC", "LOWELL", "GREGG", "SHERMAN", "WILSON", "SYLVESTER", "ROOSEVELT", - "ISRAEL", "JERMAINE", "FORREST", "WILBERT", "LELAND", "SIMON", "CLARK", "IRVING", "BRYANT", "OWEN", - "RUFUS", "WOODROW", "KRISTOPHER", "MACK", "LEVI", "MARCOS", "GUSTAVO", "JAKE", "LIONEL", "GILBERTO", - "CLINT", "NICOLAS", "ISMAEL", "ORVILLE", "ERVIN", "DEWEY", "AL", "WILFRED", "JOSH", "HUGO", - "IGNACIO", "CALEB", "TOMAS", "SHELDON", "ERICK", "STEWART", "DOYLE", "DARREL", "ROGELIO", "TERENCE", - "SANTIAGO", "ALONZO", "ELIAS", "BERT", "ELBERT", "RAMIRO", "CONRAD", "NOAH", "GRADY", "PHIL", - "CORNELIUS", "LAMAR", "ROLANDO", "CLAY", "PERCY", "DEXTER", "BRADFORD", "DARIN", "AMOS", "MOSES", - "IRVIN", "SAUL", "ROMAN", "RANDAL", "TIMMY", "DARRIN", "WINSTON", "BRENDAN", "ABEL", "DOMINICK", - "BOYD", "EMILIO", "ELIJAH", "DOMINGO", "EMMETT", "MARLON", "EMANUEL", "JERALD", "EDMOND", "EMIL", - "DEWAYNE", "WILL", "OTTO", "TEDDY", "REYNALDO", "BRET", "JESS", "TRENT", "HUMBERTO", "EMMANUEL", - "STEPHAN", "VICENTE", "LAMONT", "GARLAND", "MILES", "EFRAIN", "HEATH", "RODGER", "HARLEY", "ETHAN", - "ELDON", "ROCKY", "PIERRE", "JUNIOR", "FREDDY", "ELI", "BRYCE", "ANTOINE", "STERLING", "CHASE", - "GROVER", "ELTON", "CLEVELAND", "DYLAN", "CHUCK", "DAMIAN", "REUBEN", "STAN", "AUGUST", "LEONARDO", - "JASPER", "RUSSEL", "ERWIN", "BENITO", "HANS", "MONTE", "BLAINE", "ERNIE", "CURT", "QUENTIN", - "AGUSTIN", "MURRAY", "JAMAL", "ADOLFO", "HARRISON", "TYSON", "BURTON", "BRADY", "ELLIOTT", "WILFREDO", - "BART", "JARROD", "VANCE", "DENIS", "DAMIEN", "JOAQUIN", "HARLAN", "DESMOND", "ELLIOT", "DARWIN", - "GREGORIO", "BUDDY", "XAVIER", "KERMIT", "ROSCOE", "ESTEBAN", "ANTON", "SOLOMON", "SCOTTY", "NORBERT", - "ELVIN", "WILLIAMS", "NOLAN", "ROD", "QUINTON", "HAL", "BRAIN", "ROB", "ELWOOD", "KENDRICK", - "DARIUS", "MOISES", "FIDEL", "THADDEUS", "CLIFF", "MARCEL", "JACKSON", "RAPHAEL", "BRYON", "ARMAND", - "ALVARO", "JEFFRY", "DANE", "JOESPH", "THURMAN", "NED", "RUSTY", "MONTY", "FABIAN", "REGGIE", - "MASON", "GRAHAM", "ISAIAH", "VAUGHN", "GUS", "LOYD", "DIEGO", "ADOLPH", "NORRIS", "MILLARD", - "ROCCO", "GONZALO", "DERICK", "RODRIGO", "WILEY", "RIGOBERTO", "ALPHONSO", "TY", "NOE", "VERN", - "REED", "JEFFERSON", "ELVIS", "BERNARDO", "MAURICIO", "HIRAM", "DONOVAN", "BASIL", "RILEY", "NICKOLAS", - "MAYNARD", "SCOT", "VINCE", "QUINCY", "EDDY", "SEBASTIAN", "FEDERICO", "ULYSSES", "HERIBERTO", "DONNELL", - "COLE", "DAVIS", "GAVIN", "EMERY", "WARD", "ROMEO", "JAYSON", "DANTE", "CLEMENT", "COY", - "MAXWELL", "JARVIS", "BRUNO", "ISSAC", "DUDLEY", "BROCK", "SANFORD", "CARMELO", "BARNEY", "NESTOR", - "STEFAN", "DONNY", "ART", "LINWOOD", "BEAU", "WELDON", "GALEN", "ISIDRO", "TRUMAN", "DELMAR", - "JOHNATHON", "SILAS", "FREDERIC", "DICK", "IRWIN", "MERLIN", "CHARLEY", "MARCELINO", "HARRIS", "CARLO", - "TRENTON", "KURTIS", "HUNTER", "AURELIO", "WINFRED", "VITO", "COLLIN", "DENVER", "CARTER", "LEONEL", - "EMORY", "PASQUALE", "MOHAMMAD", "MARIANO", "DANIAL", "LANDON", "DIRK", "BRANDEN", "ADAN", "BUFORD", - "GERMAN", "WILMER", "EMERSON", "ZACHERY", "FLETCHER", "JACQUES", "ERROL", "DALTON", "MONROE", "JOSUE", - "EDWARDO", "BOOKER", "WILFORD", "SONNY", "SHELTON", "CARSON", "THERON", "RAYMUNDO", "DAREN", "HOUSTON", - "ROBBY", "LINCOLN", "GENARO", "BENNETT", "OCTAVIO", "CORNELL", "HUNG", "ARRON", "ANTONY", "HERSCHEL", - "GIOVANNI", "GARTH", "CYRUS", "CYRIL", "RONNY", "LON", "FREEMAN", "DUNCAN", "KENNITH", "CARMINE", - "ERICH", "CHADWICK", "WILBURN", "RUSS", "REID", "MYLES", "ANDERSON", "MORTON", "JONAS", "FOREST", - "MITCHEL", "MERVIN", "ZANE", "RICH", "JAMEL", "LAZARO", "ALPHONSE", "RANDELL", "MAJOR", "JARRETT", - "BROOKS", "ABDUL", "LUCIANO", "SEYMOUR", "EUGENIO", "MOHAMMED", "VALENTIN", "CHANCE", "ARNULFO", "LUCIEN", - "FERDINAND", "THAD", "EZRA", "ALDO", "RUBIN", "ROYAL", "MITCH", "EARLE", "ABE", "WYATT", - "MARQUIS", "LANNY", "KAREEM", "JAMAR", "BORIS", "ISIAH", "EMILE", "ELMO", "ARON", "LEOPOLDO", - "EVERETTE", "JOSEF", "ELOY", "RODRICK", "REINALDO", "LUCIO", "JERROD", "WESTON", "HERSHEL", "BARTON", - "PARKER", "LEMUEL", "BURT", "JULES", "GIL", "ELISEO", "AHMAD", "NIGEL", "EFREN", "ANTWAN", - "ALDEN", "MARGARITO", "COLEMAN", "DINO", "OSVALDO", "LES", "DEANDRE", "NORMAND", "KIETH", "TREY", - "NORBERTO", "NAPOLEON", "JEROLD", "FRITZ", "ROSENDO", "MILFORD", "CHRISTOPER", "ALFONZO", "LYMAN", "JOSIAH", - "BRANT", "WILTON", "RICO", "JAMAAL", "DEWITT", "BRENTON", "OLIN", "FOSTER", "FAUSTINO", "CLAUDIO", - "JUDSON", "GINO", "EDGARDO", "ALEC", "TANNER", "JARRED", "DONN", "TAD", "PRINCE", "PORFIRIO", - "ODIS", "LENARD", "CHAUNCEY", "TOD", "MEL", "MARCELO", "KORY", "AUGUSTUS", "KEVEN", "HILARIO", - "BUD", "SAL", "ORVAL", "MAURO", "ZACHARIAH", "OLEN", "ANIBAL", "MILO", "JED", "DILLON", - "AMADO", "NEWTON", "LENNY", "RICHIE", "HORACIO", "BRICE", "MOHAMED", "DELMER", "DARIO", "REYES", - "MAC", "JONAH", "JERROLD", "ROBT", "HANK", "RUPERT", "ROLLAND", "KENTON", "DAMION", "ANTONE", - "WALDO", "FREDRIC", "BRADLY", "KIP", "BURL", "WALKER", "TYREE", "JEFFEREY", "AHMED", "WILLY", - "STANFORD", "OREN", "NOBLE", "MOSHE", "MIKEL", "ENOCH", "BRENDON", "QUINTIN", "JAMISON", "FLORENCIO", - "DARRICK", "TOBIAS", "HASSAN", "GIUSEPPE", "DEMARCUS", "CLETUS", "TYRELL", "LYNDON", "KEENAN", "WERNER", - "GERALDO", "COLUMBUS", "CHET", "BERTRAM", "MARKUS", "HUEY", "HILTON", "DWAIN", "DONTE", "TYRON", - "OMER", "ISAIAS", "HIPOLITO", "FERMIN", "ADALBERTO", "BO", "BARRETT", "TEODORO", "MCKINLEY", "MAXIMO", - "GARFIELD", "RALEIGH", "LAWERENCE", "ABRAM", "RASHAD", "KING", "EMMITT", "DARON", "SAMUAL", "MIQUEL", - "EUSEBIO", "DOMENIC", "DARRON", "BUSTER", "WILBER", "RENATO", "JC", "HOYT", "HAYWOOD", "EZEKIEL", - "CHAS", "FLORENTINO", "ELROY", "CLEMENTE", "ARDEN", "NEVILLE", "EDISON", "DESHAWN", "NATHANIAL", "JORDON", - "DANILO", "CLAUD", "SHERWOOD", "RAYMON", "RAYFORD", "CRISTOBAL", "AMBROSE", "TITUS", "HYMAN", "FELTON", - "EZEQUIEL", "ERASMO", "STANTON", "LONNY", "LEN", "IKE", "MILAN", "LINO", "JAROD", "HERB", - "ANDREAS", "WALTON", "RHETT", "PALMER", "DOUGLASS", "CORDELL", "OSWALDO", "ELLSWORTH", "VIRGILIO", "TONEY", - "NATHANAEL", "DEL", "BENEDICT", "MOSE", "JOHNSON", "ISREAL", "GARRET", "FAUSTO", "ASA", "ARLEN", - "ZACK", "WARNER", "MODESTO", "FRANCESCO", "MANUAL", "GAYLORD", "GASTON", "FILIBERTO", "DEANGELO", "MICHALE", - "GRANVILLE", "WES", "MALIK", "ZACKARY", "TUAN", "ELDRIDGE", "CRISTOPHER", "CORTEZ", "ANTIONE", "MALCOM", - "LONG", "KOREY", "JOSPEH", "COLTON", "WAYLON", "VON", "HOSEA", "SHAD", "SANTO", "RUDOLF", - "ROLF", "REY", "RENALDO", "MARCELLUS", "LUCIUS", "KRISTOFER", "BOYCE", "BENTON", "HAYDEN", "HARLAND", - "ARNOLDO", "RUEBEN", "LEANDRO", "KRAIG", "JERRELL", "JEROMY", "HOBERT", "CEDRICK", "ARLIE", "WINFORD", - "WALLY", "LUIGI", "KENETH", "JACINTO", "GRAIG", "FRANKLYN", "EDMUNDO", "SID", "PORTER", "LEIF", - "JERAMY", "BUCK", "WILLIAN", "VINCENZO", "SHON", "LYNWOOD", "JERE", "HAI", "ELDEN", "DORSEY", - "DARELL", "BRODERICK", "ALONSO" - ] - total_sum = 0 - temp_sum = 0 - name.sort() - for i in range(len(name)): - for j in name[i]: - temp_sum += ord(j) - ord('A') + 1 - total_sum += (i + 1) * temp_sum - temp_sum = 0 - print(total_sum) +# -*- coding: latin-1 -*- +""" +Name scores +Problem 22 +Using names.txt (right click and 'Save Link/Target As...'), a 46K text file +containing over five-thousand first names, begin by sorting it into +alphabetical order. Then working out the alphabetical value for each name, +multiply this value by its alphabetical position in the list to obtain a name +score. -if __name__ == '__main__': - main() +For example, when the list is sorted into alphabetical order, COLIN, which is +worth 3 + 15 + 12 + 9 + 14 = 53, is the 938th name in the list. So, COLIN would +obtain a score of 938 × 53 = 49714. + +What is the total of all the name scores in the file? +""" +import os + + +def solution(): + """Returns the total of all the name scores in the file. + + >>> solution() + 871198282 + """ + total_sum = 0 + temp_sum = 0 + with open(os.path.dirname(__file__) + "/p022_names.txt") as file: + name = str(file.readlines()[0]) + name = name.replace('"', "").split(",") + + name.sort() + for i in range(len(name)): + for j in name[i]: + temp_sum += ord(j) - ord("A") + 1 + total_sum += (i + 1) * temp_sum + temp_sum = 0 + return total_sum + + +if __name__ == "__main__": + print(solution()) diff --git a/project_euler/problem_234/__init__.py b/project_euler/problem_234/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/project_euler/problem_234/sol1.py b/project_euler/problem_234/sol1.py index c7a6bd97d66b..8298a7f8cce3 100644 --- a/project_euler/problem_234/sol1.py +++ b/project_euler/problem_234/sol1.py @@ -1,4 +1,22 @@ -# https://projecteuler.net/problem=234 +""" +https://projecteuler.net/problem=234 + +For an integer n ≥ 4, we define the lower prime square root of n, denoted by +lps(n), as the largest prime ≤ √n and the upper prime square root of n, ups(n), +as the smallest prime ≥ √n. + +So, for example, lps(4) = 2 = ups(4), lps(1000) = 31, ups(1000) = 37. Let us +call an integer n ≥ 4 semidivisible, if one of lps(n) and ups(n) divides n, +but not both. + +The sum of the semidivisible numbers not exceeding 15 is 30, the numbers are 8, +10 and 12. 15 is not semidivisible because it is a multiple of both lps(15) = 3 +and ups(15) = 5. As a further example, the sum of the 92 semidivisible numbers +up to 1000 is 34825. + +What is the sum of all semidivisible numbers not exceeding 999966663333 ? +""" + def fib(a, b, n): if n==1: @@ -17,16 +35,22 @@ def fib(a, b, n): return c -q=int(input()) -for x in range(q): - l=[i for i in input().split()] - c1=0 - c2=1 - while(1): - - if len(fib(l[0],l[1],c2))>> solution() + '2783915460' + """ + result = list(map("".join, permutations("0123456789"))) + return result[999999] + + +if __name__ == "__main__": + print(solution()) diff --git a/project_euler/problem_25/__init__.py b/project_euler/problem_25/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/project_euler/problem_25/sol1.py b/project_euler/problem_25/sol1.py index f8cea3093dcf..be3b4d9b2d7d 100644 --- a/project_euler/problem_25/sol1.py +++ b/project_euler/problem_25/sol1.py @@ -1,31 +1,75 @@ -from __future__ import print_function +# -*- coding: utf-8 -*- +""" +The Fibonacci sequence is defined by the recurrence relation: + + Fn = Fn−1 + Fn−2, where F1 = 1 and F2 = 1. + +Hence the first 12 terms will be: + + F1 = 1 + F2 = 1 + F3 = 2 + F4 = 3 + F5 = 5 + F6 = 8 + F7 = 13 + F8 = 21 + F9 = 34 + F10 = 55 + F11 = 89 + F12 = 144 + +The 12th term, F12, is the first term to contain three digits. + +What is the index of the first term in the Fibonacci sequence to contain 1000 +digits? +""" try: - xrange #Python 2 + xrange # Python 2 except NameError: - xrange = range #Python 3 + xrange = range # Python 3 + def fibonacci(n): - if n == 1 or type(n) is not int: - return 0 - elif n == 2: - return 1 - else: - sequence = [0, 1] - for i in xrange(2, n+1): - sequence.append(sequence[i-1] + sequence[i-2]) + if n == 1 or type(n) is not int: + return 0 + elif n == 2: + return 1 + else: + sequence = [0, 1] + for i in xrange(2, n + 1): + sequence.append(sequence[i - 1] + sequence[i - 2]) + + return sequence[n] - return sequence[n] def fibonacci_digits_index(n): - digits = 0 - index = 2 + digits = 0 + index = 2 + + while digits < n: + index += 1 + digits = len(str(fibonacci(index))) + + return index + + +def solution(n): + """Returns the index of the first term in the Fibonacci sequence to contain + n digits. - while digits < n: - index += 1 - digits = len(str(fibonacci(index))) + >>> solution(1000) + 4782 + >>> solution(100) + 476 + >>> solution(50) + 237 + >>> solution(3) + 12 + """ + return fibonacci_digits_index(n) - return index -if __name__ == '__main__': - print(fibonacci_digits_index(1000)) \ No newline at end of file +if __name__ == "__main__": + print(solution(int(str(input()).strip()))) diff --git a/project_euler/problem_25/sol2.py b/project_euler/problem_25/sol2.py index 35147a9bfb14..d754e2ddd722 100644 --- a/project_euler/problem_25/sol2.py +++ b/project_euler/problem_25/sol2.py @@ -1,10 +1,57 @@ -def fibonacci_genrator(): - a, b = 0,1 - while True: - a,b = b,a+b - yield b -answer = 1 -gen = fibonacci_genrator() -while len(str(next(gen))) < 1000: - answer += 1 -assert answer+1 == 4782 +# -*- coding: utf-8 -*- +""" +The Fibonacci sequence is defined by the recurrence relation: + + Fn = Fn−1 + Fn−2, where F1 = 1 and F2 = 1. + +Hence the first 12 terms will be: + + F1 = 1 + F2 = 1 + F3 = 2 + F4 = 3 + F5 = 5 + F6 = 8 + F7 = 13 + F8 = 21 + F9 = 34 + F10 = 55 + F11 = 89 + F12 = 144 + +The 12th term, F12, is the first term to contain three digits. + +What is the index of the first term in the Fibonacci sequence to contain 1000 +digits? +""" + + +def fibonacci_generator(): + a, b = 0, 1 + while True: + a, b = b, a + b + yield b + + +def solution(n): + """Returns the index of the first term in the Fibonacci sequence to contain + n digits. + + >>> solution(1000) + 4782 + >>> solution(100) + 476 + >>> solution(50) + 237 + >>> solution(3) + 12 + """ + answer = 1 + gen = fibonacci_generator() + while len(str(next(gen))) < n: + answer += 1 + return answer + 1 + + +if __name__ == "__main__": + print(solution(int(str(input()).strip()))) diff --git a/project_euler/problem_28/__init__.py b/project_euler/problem_28/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/project_euler/problem_28/sol1.py b/project_euler/problem_28/sol1.py index 4942115ce537..63386ce3058c 100644 --- a/project_euler/problem_28/sol1.py +++ b/project_euler/problem_28/sol1.py @@ -1,29 +1,60 @@ -from __future__ import print_function +""" +Starting with the number 1 and moving to the right in a clockwise direction a 5 +by 5 spiral is formed as follows: + + 21 22 23 24 25 + 20 7 8 9 10 + 19 6 1 2 11 + 18 5 4 3 12 + 17 16 15 14 13 + +It can be verified that the sum of the numbers on the diagonals is 101. + +What is the sum of the numbers on the diagonals in a 1001 by 1001 spiral formed +in the same way? +""" + from math import ceil try: - xrange #Python 2 + xrange # Python 2 except NameError: - xrange = range #Python 3 + xrange = range # Python 3 + def diagonal_sum(n): - total = 1 - - for i in xrange(1, int(ceil(n/2.0))): - odd = 2*i+1 - even = 2*i - total = total + 4*odd**2 - 6*even - - return total - -if __name__ == '__main__': - import sys - - if len(sys.argv) == 1: - print(diagonal_sum(1001)) - else: - try: - n = int(sys.argv[1]) - diagonal_sum(n) - except ValueError: - print('Invalid entry - please enter a number') \ No newline at end of file + """Returns the sum of the numbers on the diagonals in a n by n spiral + formed in the same way. + + >>> diagonal_sum(1001) + 669171001 + >>> diagonal_sum(500) + 82959497 + >>> diagonal_sum(100) + 651897 + >>> diagonal_sum(50) + 79697 + >>> diagonal_sum(10) + 537 + """ + total = 1 + + for i in xrange(1, int(ceil(n / 2.0))): + odd = 2 * i + 1 + even = 2 * i + total = total + 4 * odd ** 2 - 6 * even + + return total + + +if __name__ == "__main__": + import sys + + if len(sys.argv) == 1: + print(diagonal_sum(1001)) + else: + try: + n = int(sys.argv[1]) + print(diagonal_sum(n)) + except ValueError: + print("Invalid entry - please enter a number") diff --git a/project_euler/problem_29/__init__.py b/project_euler/problem_29/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/project_euler/problem_29/solution.py b/project_euler/problem_29/solution.py index 64d35c84d9ca..e67dafe4639d 100644 --- a/project_euler/problem_29/solution.py +++ b/project_euler/problem_29/solution.py @@ -1,33 +1,51 @@ -def main(): +""" +Consider all integer combinations of ab for 2 <= a <= 5 and 2 <= b <= 5: + +2^2=4, 2^3=8, 2^4=16, 2^5=32 +3^2=9, 3^3=27, 3^4=81, 3^5=243 +4^2=16, 4^3=64, 4^4=256, 4^5=1024 +5^2=25, 5^3=125, 5^4=625, 5^5=3125 + +If they are then placed in numerical order, with any repeats removed, we get +the following sequence of 15 distinct terms: + +4, 8, 9, 16, 25, 27, 32, 64, 81, 125, 243, 256, 625, 1024, 3125 + +How many distinct terms are in the sequence generated by ab +for 2 <= a <= 100 and 2 <= b <= 100? +""" +from __future__ import print_function + + +def solution(n): + """Returns the number of distinct terms in the sequence generated by a^b + for 2 <= a <= 100 and 2 <= b <= 100. + + >>> solution(100) + 9183 + >>> solution(50) + 2184 + >>> solution(20) + 324 + >>> solution(5) + 15 + >>> solution(2) + 1 + >>> solution(1) + 0 """ - Consider all integer combinations of ab for 2 <= a <= 5 and 2 <= b <= 5: - - 22=4, 23=8, 24=16, 25=32 - 32=9, 33=27, 34=81, 35=243 - 42=16, 43=64, 44=256, 45=1024 - 52=25, 53=125, 54=625, 55=3125 - If they are then placed in numerical order, with any repeats removed, - we get the following sequence of 15 distinct terms: - - 4, 8, 9, 16, 25, 27, 32, 64, 81, 125, 243, 256, 625, 1024, 3125 - - How many distinct terms are in the sequence generated by ab - for 2 <= a <= 100 and 2 <= b <= 100? - """ - collectPowers = set() currentPow = 0 - N = 101 # maximum limit + N = n + 1 # maximum limit for a in range(2, N): for b in range(2, N): - currentPow = a**b # calculates the current power - collectPowers.add(currentPow) # adds the result to the set - - print("Number of terms ", len(collectPowers)) + currentPow = a ** b # calculates the current power + collectPowers.add(currentPow) # adds the result to the set + return len(collectPowers) -if __name__ == '__main__': - main() +if __name__ == "__main__": + print("Number of terms ", solution(int(str(input()).strip()))) diff --git a/project_euler/problem_31/__init__.py b/project_euler/problem_31/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/project_euler/problem_31/sol1.py b/project_euler/problem_31/sol1.py index 33653722f890..e2a209e5df5a 100644 --- a/project_euler/problem_31/sol1.py +++ b/project_euler/problem_31/sol1.py @@ -1,10 +1,5 @@ # -*- coding: utf-8 -*- -from __future__ import print_function -try: - raw_input # Python 2 -except NameError: - raw_input = input # Python 3 -''' +""" Coin sums Problem 31 In England the currency is made up of pound, £, and pence, p, and there are @@ -15,7 +10,13 @@ 1×£1 + 1×50p + 2×20p + 1×5p + 1×2p + 3×1p How many different ways can £2 be made using any number of coins? -''' +""" +from __future__ import print_function + +try: + raw_input # Python 2 +except NameError: + raw_input = input # Python 3 def one_pence(): @@ -50,4 +51,21 @@ def two_pound(x): return 0 if x < 0 else two_pound(x - 200) + one_pound(x) -print(two_pound(200)) +def solution(n): + """Returns the number of different ways can £n be made using any number of + coins? + + >>> solution(500) + 6295434 + >>> solution(200) + 73682 + >>> solution(50) + 451 + >>> solution(10) + 11 + """ + return two_pound(n) + + +if __name__ == "__main__": + print(solution(int(str(input()).strip()))) diff --git a/project_euler/problem_36/__init__.py b/project_euler/problem_36/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/project_euler/problem_36/sol1.py b/project_euler/problem_36/sol1.py index d78e7e59f210..38b60420992b 100644 --- a/project_euler/problem_36/sol1.py +++ b/project_euler/problem_36/sol1.py @@ -1,30 +1,57 @@ -from __future__ import print_function -''' +""" Double-base palindromes Problem 36 The decimal number, 585 = 10010010012 (binary), is palindromic in both bases. -Find the sum of all numbers, less than one million, which are palindromic in base 10 and base 2. +Find the sum of all numbers, less than one million, which are palindromic in +base 10 and base 2. -(Please note that the palindromic number, in either base, may not include leading zeros.) -''' +(Please note that the palindromic number, in either base, may not include +leading zeros.) +""" try: - xrange #Python 2 + xrange # Python 2 except NameError: - xrange = range #Python 3 + xrange = range # Python 3 + def is_palindrome(n): - n = str(n) + n = str(n) + + if n == n[::-1]: + return True + else: + return False + + +def solution(n): + """Return the sum of all numbers, less than n , which are palindromic in + base 10 and base 2. - if n == n[::-1]: - return True - else: - return False + >>> solution(1000000) + 872187 + >>> solution(500000) + 286602 + >>> solution(100000) + 286602 + >>> solution(1000) + 1772 + >>> solution(100) + 157 + >>> solution(10) + 25 + >>> solution(2) + 1 + >>> solution(1) + 0 + """ + total = 0 -total = 0 + for i in xrange(1, n): + if is_palindrome(i) and is_palindrome(bin(i).split("b")[1]): + total += i + return total -for i in xrange(1, 1000000): - if is_palindrome(i) and is_palindrome(bin(i).split('b')[1]): - total += i -print(total) \ No newline at end of file +if __name__ == "__main__": + print(solution(int(str(input().strip())))) diff --git a/project_euler/problem_40/__init__.py b/project_euler/problem_40/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/project_euler/problem_40/sol1.py b/project_euler/problem_40/sol1.py index ab4017512a1a..accd7125354c 100644 --- a/project_euler/problem_40/sol1.py +++ b/project_euler/problem_40/sol1.py @@ -1,26 +1,47 @@ -#-.- coding: latin-1 -.- -from __future__ import print_function -''' +# -.- coding: latin-1 -.- +""" Champernowne's constant Problem 40 -An irrational decimal fraction is created by concatenating the positive integers: +An irrational decimal fraction is created by concatenating the positive +integers: 0.123456789101112131415161718192021... It can be seen that the 12th digit of the fractional part is 1. -If dn represents the nth digit of the fractional part, find the value of the following expression. +If dn represents the nth digit of the fractional part, find the value of the +following expression. d1 × d10 × d100 × d1000 × d10000 × d100000 × d1000000 -''' +""" +from __future__ import print_function + + +def solution(): + """Returns + + >>> solution() + 210 + """ + constant = [] + i = 1 + + while len(constant) < 1e6: + constant.append(str(i)) + i += 1 -constant = [] -i = 1 + constant = "".join(constant) -while len(constant) < 1e6: - constant.append(str(i)) - i += 1 + return ( + int(constant[0]) + * int(constant[9]) + * int(constant[99]) + * int(constant[999]) + * int(constant[9999]) + * int(constant[99999]) + * int(constant[999999]) + ) -constant = ''.join(constant) -print(int(constant[0])*int(constant[9])*int(constant[99])*int(constant[999])*int(constant[9999])*int(constant[99999])*int(constant[999999])) \ No newline at end of file +if __name__ == "__main__": + print(solution()) diff --git a/project_euler/problem_48/__init__.py b/project_euler/problem_48/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/project_euler/problem_48/sol1.py b/project_euler/problem_48/sol1.py index 5c4bdb0f6384..95af951c0e8a 100644 --- a/project_euler/problem_48/sol1.py +++ b/project_euler/problem_48/sol1.py @@ -1,21 +1,29 @@ -from __future__ import print_function -''' +""" Self Powers Problem 48 The series, 11 + 22 + 33 + ... + 1010 = 10405071317. Find the last ten digits of the series, 11 + 22 + 33 + ... + 10001000. -''' +""" try: - xrange + xrange except NameError: - xrange = range + xrange = range -total = 0 -for i in xrange(1, 1001): - total += i**i +def solution(): + """Returns the last 10 digits of the series, 11 + 22 + 33 + ... + 10001000. -print(str(total)[-10:]) \ No newline at end of file + >>> solution() + '9110846700' + """ + total = 0 + for i in xrange(1, 1001): + total += i ** i + return str(total)[-10:] + + +if __name__ == "__main__": + print(solution()) diff --git a/project_euler/problem_52/__init__.py b/project_euler/problem_52/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/project_euler/problem_52/sol1.py b/project_euler/problem_52/sol1.py index 376b4cfa1d63..df5c46ae05d1 100644 --- a/project_euler/problem_52/sol1.py +++ b/project_euler/problem_52/sol1.py @@ -1,23 +1,37 @@ -from __future__ import print_function -''' +""" Permuted multiples Problem 52 -It can be seen that the number, 125874, and its double, 251748, contain exactly the same digits, but in a different order. +It can be seen that the number, 125874, and its double, 251748, contain exactly +the same digits, but in a different order. -Find the smallest positive integer, x, such that 2x, 3x, 4x, 5x, and 6x, contain the same digits. -''' -i = 1 +Find the smallest positive integer, x, such that 2x, 3x, 4x, 5x, and 6x, +contain the same digits. +""" -while True: - if sorted(list(str(i))) == \ - sorted(list(str(2*i))) == \ - sorted(list(str(3*i))) == \ - sorted(list(str(4*i))) == \ - sorted(list(str(5*i))) == \ - sorted(list(str(6*i))): - break - i += 1 +def solution(): + """Returns the smallest positive integer, x, such that 2x, 3x, 4x, 5x, and + 6x, contain the same digits. -print(i) \ No newline at end of file + >>> solution() + 142857 + """ + i = 1 + + while True: + if ( + sorted(list(str(i))) + == sorted(list(str(2 * i))) + == sorted(list(str(3 * i))) + == sorted(list(str(4 * i))) + == sorted(list(str(5 * i))) + == sorted(list(str(6 * i))) + ): + return i + + i += 1 + + +if __name__ == "__main__": + print(solution()) diff --git a/project_euler/problem_53/__init__.py b/project_euler/problem_53/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/project_euler/problem_53/sol1.py b/project_euler/problem_53/sol1.py index ed6d5329eb4e..c72e0b993a34 100644 --- a/project_euler/problem_53/sol1.py +++ b/project_euler/problem_53/sol1.py @@ -1,13 +1,11 @@ -#-.- coding: latin-1 -.- -from __future__ import print_function -from math import factorial -''' +# -.- coding: latin-1 -.- +""" Combinatoric selections Problem 53 There are exactly ten ways of selecting three from five, 12345: -123, 124, 125, 134, 135, 145, 234, 235, 245, and 345 + 123, 124, 125, 134, 135, 145, 234, 235, 245, and 345 In combinatorics, we use the notation, 5C3 = 10. @@ -16,21 +14,37 @@ nCr = n!/(r!(n−r)!),where r ≤ n, n! = n×(n−1)×...×3×2×1, and 0! = 1. It is not until n = 23, that a value exceeds one-million: 23C10 = 1144066. -How many, not necessarily distinct, values of nCr, for 1 ≤ n ≤ 100, are greater than one-million? -''' +How many, not necessarily distinct, values of nCr, for 1 ≤ n ≤ 100, are greater +than one-million? +""" +from __future__ import print_function +from math import factorial + try: - xrange #Python 2 + xrange # Python 2 except NameError: - xrange = range #Python 3 + xrange = range # Python 3 + def combinations(n, r): - return factorial(n)/(factorial(r)*factorial(n-r)) + return factorial(n) / (factorial(r) * factorial(n - r)) + + +def solution(): + """Returns the number of values of nCr, for 1 ≤ n ≤ 100, are greater than + one-million + + >>> solution() + 4075 + """ + total = 0 -total = 0 + for i in xrange(1, 101): + for j in xrange(1, i + 1): + if combinations(i, j) > 1e6: + total += 1 + return total -for i in xrange(1, 101): - for j in xrange(1, i+1): - if combinations(i, j) > 1e6: - total += 1 -print(total) \ No newline at end of file +if __name__ == "__main__": + print(solution()) diff --git a/project_euler/problem_76/__init__.py b/project_euler/problem_76/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/project_euler/problem_76/sol1.py b/project_euler/problem_76/sol1.py index 2832f6d7afb6..c9e3c452fbc4 100644 --- a/project_euler/problem_76/sol1.py +++ b/project_euler/problem_76/sol1.py @@ -1,5 +1,4 @@ -from __future__ import print_function -''' +""" Counting Summations Problem 76 @@ -12,24 +11,50 @@ 2 + 1 + 1 + 1 1 + 1 + 1 + 1 + 1 -How many different ways can one hundred be written as a sum of at least two positive integers? -''' +How many different ways can one hundred be written as a sum of at least two +positive integers? +""" +from __future__ import print_function + try: - xrange #Python 2 + xrange # Python 2 except NameError: - xrange = range #Python 3 + xrange = range # Python 3 + def partition(m): - memo = [[0 for _ in xrange(m)] for _ in xrange(m+1)] - for i in xrange(m+1): - memo[i][0] = 1 + """Returns the number of different ways one hundred can be written as a sum + of at least two positive integers. + + >>> partition(100) + 190569291 + >>> partition(50) + 204225 + >>> partition(30) + 5603 + >>> partition(10) + 41 + >>> partition(5) + 6 + >>> partition(3) + 2 + >>> partition(2) + 1 + >>> partition(1) + 0 + """ + memo = [[0 for _ in xrange(m)] for _ in xrange(m + 1)] + for i in xrange(m + 1): + memo[i][0] = 1 + + for n in xrange(m + 1): + for k in xrange(1, m): + memo[n][k] += memo[n][k - 1] + if n > k: + memo[n][k] += memo[n - k - 1][k] - for n in xrange(m+1): - for k in xrange(1, m): - memo[n][k] += memo[n][k-1] - if n > k: - memo[n][k] += memo[n-k-1][k] + return memo[m][m - 1] - 1 - return (memo[m][m-1] - 1) -print(partition(100)) \ No newline at end of file +if __name__ == "__main__": + print(partition(int(str(input()).strip()))) From 7cdda931fd6e272529f25a4cd2dbd7c6785d467c Mon Sep 17 00:00:00 2001 From: cclauss Date: Wed, 17 Jul 2019 06:07:25 +0200 Subject: [PATCH 0119/1071] Travis CI: Add pytest --doctest-modules graphs (#1018) --- .travis.yml | 1 + graphs/basic_graphs.py | 75 ++++++++++--------- graphs/bellman_ford.py | 40 +++++----- graphs/dijkstra_2.py | 40 +++++----- ...d_warshall.py => graphs_floyd_warshall.py} | 0 graphs/minimum_spanning_tree_kruskal.py | 48 ++++++------ graphs/minimum_spanning_tree_prims.py | 19 ++--- graphs/multi_hueristic_astar.py | 25 ++++--- graphs/scc_kosaraju.py | 35 +++++---- 9 files changed, 148 insertions(+), 135 deletions(-) rename graphs/{floyd_warshall.py => graphs_floyd_warshall.py} (100%) diff --git a/.travis.yml b/.travis.yml index 3b55045ac33f..55ea2c7ddc24 100644 --- a/.travis.yml +++ b/.travis.yml @@ -21,6 +21,7 @@ script: digital_image_processing divide_and_conquer dynamic_programming + graphs hashes linear_algebra_python matrix diff --git a/graphs/basic_graphs.py b/graphs/basic_graphs.py index 3b3abeb1720d..ee63ca995de6 100644 --- a/graphs/basic_graphs.py +++ b/graphs/basic_graphs.py @@ -10,42 +10,44 @@ except NameError: xrange = range # Python 3 -# Accept No. of Nodes and edges -n, m = map(int, raw_input().split(" ")) -# Initialising Dictionary of edges -g = {} -for i in xrange(n): - g[i + 1] = [] - -""" --------------------------------------------------------------------------------- - Accepting edges of Unweighted Directed Graphs --------------------------------------------------------------------------------- -""" -for _ in xrange(m): - x, y = map(int, raw_input().split(" ")) - g[x].append(y) - -""" --------------------------------------------------------------------------------- - Accepting edges of Unweighted Undirected Graphs --------------------------------------------------------------------------------- -""" -for _ in xrange(m): - x, y = map(int, raw_input().split(" ")) - g[x].append(y) - g[y].append(x) +if __name__ == "__main__": + # Accept No. of Nodes and edges + n, m = map(int, raw_input().split(" ")) -""" --------------------------------------------------------------------------------- - Accepting edges of Weighted Undirected Graphs --------------------------------------------------------------------------------- -""" -for _ in xrange(m): - x, y, r = map(int, raw_input().split(" ")) - g[x].append([y, r]) - g[y].append([x, r]) + # Initialising Dictionary of edges + g = {} + for i in xrange(n): + g[i + 1] = [] + + """ + ---------------------------------------------------------------------------- + Accepting edges of Unweighted Directed Graphs + ---------------------------------------------------------------------------- + """ + for _ in xrange(m): + x, y = map(int, raw_input().strip().split(" ")) + g[x].append(y) + + """ + ---------------------------------------------------------------------------- + Accepting edges of Unweighted Undirected Graphs + ---------------------------------------------------------------------------- + """ + for _ in xrange(m): + x, y = map(int, raw_input().strip().split(" ")) + g[x].append(y) + g[y].append(x) + + """ + ---------------------------------------------------------------------------- + Accepting edges of Weighted Undirected Graphs + ---------------------------------------------------------------------------- + """ + for _ in xrange(m): + x, y, r = map(int, raw_input().strip().split(" ")) + g[x].append([y, r]) + g[y].append([x, r]) """ -------------------------------------------------------------------------------- @@ -168,9 +170,10 @@ def topo(G, ind=None, Q=[1]): def adjm(): - n, a = raw_input(), [] + n = raw_input().strip() + a = [] for i in xrange(n): - a.append(map(int, raw_input().split())) + a.append(map(int, raw_input().strip().split())) return a, n diff --git a/graphs/bellman_ford.py b/graphs/bellman_ford.py index 82db80546b94..f49157230054 100644 --- a/graphs/bellman_ford.py +++ b/graphs/bellman_ford.py @@ -12,7 +12,7 @@ def printDist(dist, V): def BellmanFord(graph, V, E, src): mdist=[float('inf') for i in range(V)] mdist[src] = 0.0 - + for i in range(V-1): for j in range(V): u = graph[j]["src"] @@ -20,7 +20,7 @@ def BellmanFord(graph, V, E, src): w = graph[j]["weight"] if mdist[u] != float('inf') and mdist[u] + w < mdist[v]: - mdist[v] = mdist[u] + w + mdist[v] = mdist[u] + w for j in range(V): u = graph[j]["src"] v = graph[j]["dst"] @@ -29,26 +29,26 @@ def BellmanFord(graph, V, E, src): if mdist[u] != float('inf') and mdist[u] + w < mdist[v]: print("Negative cycle found. Solution not possible.") return - - printDist(mdist, V) - + printDist(mdist, V) + + + +if __name__ == "__main__": + V = int(input("Enter number of vertices: ").strip()) + E = int(input("Enter number of edges: ").strip()) -#MAIN -V = int(input("Enter number of vertices: ")) -E = int(input("Enter number of edges: ")) + graph = [dict() for j in range(E)] -graph = [dict() for j in range(E)] + for i in range(V): + graph[i][i] = 0.0 -for i in range(V): - graph[i][i] = 0.0 + for i in range(E): + print("\nEdge ",i+1) + src = int(input("Enter source:").strip()) + dst = int(input("Enter destination:").strip()) + weight = float(input("Enter weight:").strip()) + graph[i] = {"src": src,"dst": dst, "weight": weight} -for i in range(E): - print("\nEdge ",i+1) - src = int(input("Enter source:")) - dst = int(input("Enter destination:")) - weight = float(input("Enter weight:")) - graph[i] = {"src": src,"dst": dst, "weight": weight} - -gsrc = int(input("\nEnter shortest path source:")) -BellmanFord(graph, V, E, gsrc) + gsrc = int(input("\nEnter shortest path source:").strip()) + BellmanFord(graph, V, E, gsrc) diff --git a/graphs/dijkstra_2.py b/graphs/dijkstra_2.py index a6c340e8a68d..8f39aec41906 100644 --- a/graphs/dijkstra_2.py +++ b/graphs/dijkstra_2.py @@ -22,36 +22,36 @@ def Dijkstra(graph, V, src): mdist=[float('inf') for i in range(V)] vset = [False for i in range(V)] mdist[src] = 0.0 - + for i in range(V-1): u = minDist(mdist, vset, V) vset[u] = True - + for v in range(V): if (not vset[v]) and graph[u][v]!=float('inf') and mdist[u] + graph[u][v] < mdist[v]: - mdist[v] = mdist[u] + graph[u][v] + mdist[v] = mdist[u] + graph[u][v] + + - + printDist(mdist, V) - printDist(mdist, V) - -#MAIN -V = int(input("Enter number of vertices: ")) -E = int(input("Enter number of edges: ")) +if __name__ == "__main__": + V = int(input("Enter number of vertices: ").strip()) + E = int(input("Enter number of edges: ").strip()) -graph = [[float('inf') for i in range(V)] for j in range(V)] + graph = [[float('inf') for i in range(V)] for j in range(V)] -for i in range(V): - graph[i][i] = 0.0 + for i in range(V): + graph[i][i] = 0.0 -for i in range(E): - print("\nEdge ",i+1) - src = int(input("Enter source:")) - dst = int(input("Enter destination:")) - weight = float(input("Enter weight:")) - graph[src][dst] = weight + for i in range(E): + print("\nEdge ",i+1) + src = int(input("Enter source:").strip()) + dst = int(input("Enter destination:").strip()) + weight = float(input("Enter weight:").strip()) + graph[src][dst] = weight -gsrc = int(input("\nEnter shortest path source:")) -Dijkstra(graph, V, gsrc) + gsrc = int(input("\nEnter shortest path source:").strip()) + Dijkstra(graph, V, gsrc) diff --git a/graphs/floyd_warshall.py b/graphs/graphs_floyd_warshall.py similarity index 100% rename from graphs/floyd_warshall.py rename to graphs/graphs_floyd_warshall.py diff --git a/graphs/minimum_spanning_tree_kruskal.py b/graphs/minimum_spanning_tree_kruskal.py index 81d64f421a31..975151c90ede 100644 --- a/graphs/minimum_spanning_tree_kruskal.py +++ b/graphs/minimum_spanning_tree_kruskal.py @@ -1,32 +1,34 @@ from __future__ import print_function -num_nodes, num_edges = list(map(int,input().split())) -edges = [] +if __name__ == "__main__": + num_nodes, num_edges = list(map(int, input().strip().split())) -for i in range(num_edges): - node1, node2, cost = list(map(int,input().split())) - edges.append((i,node1,node2,cost)) + edges = [] -edges = sorted(edges, key=lambda edge: edge[3]) + for i in range(num_edges): + node1, node2, cost = list(map(int, input().strip().split())) + edges.append((i,node1,node2,cost)) -parent = [i for i in range(num_nodes)] + edges = sorted(edges, key=lambda edge: edge[3]) -def find_parent(i): - if(i != parent[i]): - parent[i] = find_parent(parent[i]) - return parent[i] + parent = list(range(num_nodes)) -minimum_spanning_tree_cost = 0 -minimum_spanning_tree = [] + def find_parent(i): + if i != parent[i]: + parent[i] = find_parent(parent[i]) + return parent[i] -for edge in edges: - parent_a = find_parent(edge[1]) - parent_b = find_parent(edge[2]) - if(parent_a != parent_b): - minimum_spanning_tree_cost += edge[3] - minimum_spanning_tree.append(edge) - parent[parent_a] = parent_b + minimum_spanning_tree_cost = 0 + minimum_spanning_tree = [] -print(minimum_spanning_tree_cost) -for edge in minimum_spanning_tree: - print(edge) + for edge in edges: + parent_a = find_parent(edge[1]) + parent_b = find_parent(edge[2]) + if parent_a != parent_b: + minimum_spanning_tree_cost += edge[3] + minimum_spanning_tree.append(edge) + parent[parent_a] = parent_b + + print(minimum_spanning_tree_cost) + for edge in minimum_spanning_tree: + print(edge) diff --git a/graphs/minimum_spanning_tree_prims.py b/graphs/minimum_spanning_tree_prims.py index 7b1ad0e743f7..0f21b8f494e4 100644 --- a/graphs/minimum_spanning_tree_prims.py +++ b/graphs/minimum_spanning_tree_prims.py @@ -100,12 +100,13 @@ def deleteMinimum(heap, positions): Nbr_TV[ v[0] ] = vertex return TreeEdges -# < --------- Prims Algorithm --------- > -n = int(input("Enter number of vertices: ")) -e = int(input("Enter number of edges: ")) -adjlist = defaultdict(list) -for x in range(e): - l = [int(x) for x in input().split()] - adjlist[l[0]].append([ l[1], l[2] ]) - adjlist[l[1]].append([ l[0], l[2] ]) -print(PrimsAlgorithm(adjlist)) +if __name__ == "__main__": + # < --------- Prims Algorithm --------- > + n = int(input("Enter number of vertices: ").strip()) + e = int(input("Enter number of edges: ").strip()) + adjlist = defaultdict(list) + for x in range(e): + l = [int(x) for x in input().strip().split()] + adjlist[l[0]].append([ l[1], l[2] ]) + adjlist[l[1]].append([ l[0], l[2] ]) + print(PrimsAlgorithm(adjlist)) diff --git a/graphs/multi_hueristic_astar.py b/graphs/multi_hueristic_astar.py index 1acd098f327d..1c01fe9aa6d3 100644 --- a/graphs/multi_hueristic_astar.py +++ b/graphs/multi_hueristic_astar.py @@ -18,7 +18,7 @@ def minkey(self): return self.elements[0][0] else: return float('inf') - + def empty(self): return len(self.elements) == 0 @@ -48,10 +48,10 @@ def remove_element(self, item): (pro, x) = heapq.heappop(self.elements) for (prito, yyy) in temp: heapq.heappush(self.elements, (prito, yyy)) - + def top_show(self): return self.elements[0][1] - + def get(self): (priority, item) = heapq.heappop(self.elements) self.set.remove(item) @@ -65,7 +65,7 @@ def consistent_hueristic(P, goal): def hueristic_2(P, goal): # integer division by time variable - return consistent_hueristic(P, goal) // t + return consistent_hueristic(P, goal) // t def hueristic_1(P, goal): # manhattan distance @@ -74,13 +74,13 @@ def hueristic_1(P, goal): def key(start, i, goal, g_function): ans = g_function[start] + W1 * hueristics[i](start, goal) return ans - + def do_something(back_pointer, goal, start): grid = np.chararray((n, n)) for i in range(n): for j in range(n): grid[i][j] = '*' - + for i in range(n): for j in range(n): if (j, (n-1)-i) in blocks: @@ -94,7 +94,7 @@ def do_something(back_pointer, goal, start): grid[(n-1)-y_c][x_c] = "-" x = back_pointer[x] grid[(n-1)][0] = "-" - + for i in xrange(n): for j in range(n): @@ -112,7 +112,7 @@ def do_something(back_pointer, goal, start): print("PATH TAKEN BY THE ALGORITHM IS:-") x = back_pointer[goal] while x != start: - print(x, end=' ') + print(x, end=' ') x = back_pointer[x] print(x) quit() @@ -153,7 +153,7 @@ def expand_state(s, j, visited, g_function, close_list_anchor, close_list_inad, if key(neighbours, var, goal, g_function) <= W2 * key(neighbours, 0, goal, g_function): # print("why not plssssssssss") open_list[j].put(neighbours, key(neighbours, var, goal, g_function)) - + # print @@ -212,7 +212,7 @@ def multi_a_star(start, goal, n_hueristic): for i in range(n_hueristic): open_list.append(PriorityQueue()) open_list[i].put(start, key(start, i, goal, g_function)) - + close_list_anchor = [] close_list_inad = [] while open_list[0].minkey() < float('inf'): @@ -263,4 +263,7 @@ def multi_a_star(start, goal, n_hueristic): print() print("# is an obstacle") print("- is the path taken by algorithm") -multi_a_star(start, goal, n_hueristic) + + +if __name__ == "__main__": + multi_a_star(start, goal, n_hueristic) diff --git a/graphs/scc_kosaraju.py b/graphs/scc_kosaraju.py index 1f13ebaba36b..0d0375203b6d 100644 --- a/graphs/scc_kosaraju.py +++ b/graphs/scc_kosaraju.py @@ -1,19 +1,5 @@ from __future__ import print_function -# n - no of nodes, m - no of edges -n, m = list(map(int,input().split())) - -g = [[] for i in range(n)] #graph -r = [[] for i in range(n)] #reversed graph -# input graph data (edges) -for i in range(m): - u, v = list(map(int,input().split())) - g[u].append(v) - r[v].append(u) - -stack = [] -visit = [False]*n -scc = [] -component = [] + def dfs(u): global g, r, scc, component, visit, stack @@ -43,4 +29,21 @@ def kosaraju(): scc.append(component) return scc -print(kosaraju()) + +if __name__ == "__main__": + # n - no of nodes, m - no of edges + n, m = list(map(int,input().strip().split())) + + g = [[] for i in range(n)] #graph + r = [[] for i in range(n)] #reversed graph + # input graph data (edges) + for i in range(m): + u, v = list(map(int,input().strip().split())) + g[u].append(v) + r[v].append(u) + + stack = [] + visit = [False]*n + scc = [] + component = [] + print(kosaraju()) From f195d9251cc64adb64549f79f4e774e220a668ab Mon Sep 17 00:00:00 2001 From: Jigyasa G <33327397+jpg-130@users.noreply.github.com> Date: Wed, 17 Jul 2019 11:52:09 +0530 Subject: [PATCH 0120/1071] adding factorial (#930) * adding factorial * adding doctest * Update factorial.py --- dynamic_programming/factorial.py | 34 ++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 dynamic_programming/factorial.py diff --git a/dynamic_programming/factorial.py b/dynamic_programming/factorial.py new file mode 100644 index 000000000000..7c6541ee2a74 --- /dev/null +++ b/dynamic_programming/factorial.py @@ -0,0 +1,34 @@ +#Factorial of a number using memoization +result=[-1]*10 +result[0]=result[1]=1 +def factorial(num): + """ + >>> factorial(7) + 5040 + >>> factorial(-1) + 'Number should not be negative.' + >>> [factorial(i) for i in range(5)] + [1, 1, 2, 6, 24] + """ + + if num<0: + return "Number should not be negative." + if result[num]!=-1: + return result[num] + else: + result[num]=num*factorial(num-1) + #uncomment the following to see how recalculations are avoided + #print(result) + return result[num] + +#factorial of num +#uncomment the following to see how recalculations are avoided +##result=[-1]*10 +##result[0]=result[1]=1 +##print(factorial(5)) +# print(factorial(3)) +# print(factorial(7)) + +if __name__ == "__main__": + import doctest + doctest.testmod() From f64b6029389019a2f2599b88078b940054e8be6c Mon Sep 17 00:00:00 2001 From: Sanders Lin <45224617+SandersLin@users.noreply.github.com> Date: Thu, 18 Jul 2019 00:12:24 +0800 Subject: [PATCH 0121/1071] Update max_sub_array.py (#1000) * Update max_sub_array.py added another method of computing maximum sum subarray * Update max_sub_array.py * Update max_sub_array.py --- dynamic_programming/max_sub_array.py | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/dynamic_programming/max_sub_array.py b/dynamic_programming/max_sub_array.py index 5d48882427c0..56983b7d22c2 100644 --- a/dynamic_programming/max_sub_array.py +++ b/dynamic_programming/max_sub_array.py @@ -2,7 +2,7 @@ author : Mayank Kumar Jha (mk9440) """ from __future__ import print_function - +from typing import List import time import matplotlib.pyplot as plt from random import randint @@ -37,7 +37,27 @@ def find_max_cross_sum(A,low,mid,high): right_sum=summ max_right=i return max_left,max_right,(left_sum+right_sum) - + +def max_sub_array(nums: List[int]) -> int: + """ + Finds the contiguous subarray (can be empty array) + which has the largest sum and return its sum. + + >>> max_sub_array([-2,1,-3,4,-1,2,1,-5,4]) + 6 + >>> max_sub_array([]) + 0 + >>> max_sub_array([-1,-2,-3]) + 0 + """ + best = 0 + current = 0 + for i in nums: + current += i + if current < 0: + current = 0 + best = max(best, current) + return best if __name__=='__main__': inputs=[10,100,1000,10000,50000,100000,200000,300000,400000,500000] From e662a5aaefb6f236021c89e9d410519c2013efa0 Mon Sep 17 00:00:00 2001 From: Bruno Simas Hadlich Date: Wed, 17 Jul 2019 15:32:04 -0300 Subject: [PATCH 0122/1071] Added Burrows-Wheeler transform algorithm. (#1029) * Added doctest and more explanation about Dijkstra execution. * tests were not passing with python2 due to missing __init__.py file at number_theory folder * Removed the dot at the beginning of the imported modules names because 'python3 -m doctest -v data_structures/hashing/*.py' and 'python3 -m doctest -v data_structures/stacks/*.py' were failing not finding hash_table.py and stack.py modules. * Moved global code to main scope and added doctest for project euler problems 1 to 14. * Added test case for negative input. * Changed N variable to do not use end of line scape because in case there is a space after it the script will break making it much more error prone. * Added problems description and doctests to the ones that were missing. Limited line length to 79 and executed python black over all scripts. * Changed the way files are loaded to support pytest call. * Added __init__.py to problems to make them modules and allow pytest execution. * Added project_euler folder to test units execution * Changed 'os.path.split(os.path.realpath(__file__))' to 'os.path.dirname()' * Added Burrows-Wheeler transform algorithm. * Added changes suggested by cclauss --- compression/burrows_wheeler.py | 176 +++++++++++++++++++++++++++++++++ 1 file changed, 176 insertions(+) create mode 100644 compression/burrows_wheeler.py diff --git a/compression/burrows_wheeler.py b/compression/burrows_wheeler.py new file mode 100644 index 000000000000..fabeab39adf8 --- /dev/null +++ b/compression/burrows_wheeler.py @@ -0,0 +1,176 @@ +""" +https://en.wikipedia.org/wiki/Burrows%E2%80%93Wheeler_transform + +The Burrows–Wheeler transform (BWT, also called block-sorting compression) +rearranges a character string into runs of similar characters. This is useful +for compression, since it tends to be easy to compress a string that has runs +of repeated characters by techniques such as move-to-front transform and +run-length encoding. More importantly, the transformation is reversible, +without needing to store any additional data except the position of the first +original character. The BWT is thus a "free" method of improving the efficiency +of text compression algorithms, costing only some extra computation. +""" +from typing import List, Dict + + +def all_rotations(s: str) -> List[str]: + """ + :param s: The string that will be rotated len(s) times. + :return: A list with the rotations. + :raises TypeError: If s is not an instance of str. + Examples: + + >>> all_rotations("^BANANA|") # doctest: +NORMALIZE_WHITESPACE + ['^BANANA|', 'BANANA|^', 'ANANA|^B', 'NANA|^BA', 'ANA|^BAN', 'NA|^BANA', + 'A|^BANAN', '|^BANANA'] + >>> all_rotations("a_asa_da_casa") # doctest: +NORMALIZE_WHITESPACE + ['a_asa_da_casa', '_asa_da_casaa', 'asa_da_casaa_', 'sa_da_casaa_a', + 'a_da_casaa_as', '_da_casaa_asa', 'da_casaa_asa_', 'a_casaa_asa_d', + '_casaa_asa_da', 'casaa_asa_da_', 'asaa_asa_da_c', 'saa_asa_da_ca', + 'aa_asa_da_cas'] + >>> all_rotations("panamabanana") # doctest: +NORMALIZE_WHITESPACE + ['panamabanana', 'anamabananap', 'namabananapa', 'amabananapan', + 'mabananapana', 'abananapanam', 'bananapanama', 'ananapanamab', + 'nanapanamaba', 'anapanamaban', 'napanamabana', 'apanamabanan'] + >>> all_rotations(5) + Traceback (most recent call last): + ... + TypeError: The parameter s type must be str. + """ + if not isinstance(s, str): + raise TypeError("The parameter s type must be str.") + + return [s[i:] + s[:i] for i in range(len(s))] + + +def bwt_transform(s: str) -> Dict: + """ + :param s: The string that will be used at bwt algorithm + :return: the string composed of the last char of each row of the ordered + rotations and the index of the original string at ordered rotations list + :raises TypeError: If the s parameter type is not str + :raises ValueError: If the s parameter is empty + Examples: + + >>> bwt_transform("^BANANA") + {'bwt_string': 'BNN^AAA', 'idx_original_string': 6} + >>> bwt_transform("a_asa_da_casa") + {'bwt_string': 'aaaadss_c__aa', 'idx_original_string': 3} + >>> bwt_transform("panamabanana") + {'bwt_string': 'mnpbnnaaaaaa', 'idx_original_string': 11} + >>> bwt_transform(4) + Traceback (most recent call last): + ... + TypeError: The parameter s type must be str. + >>> bwt_transform('') + Traceback (most recent call last): + ... + ValueError: The parameter s must not be empty. + """ + if not isinstance(s, str): + raise TypeError("The parameter s type must be str.") + if not s: + raise ValueError("The parameter s must not be empty.") + + rotations = all_rotations(s) + rotations.sort() # sort the list of rotations in alphabetically order + # make a string composed of the last char of each rotation + return { + "bwt_string": "".join([word[-1] for word in rotations]), + "idx_original_string": rotations.index(s), + } + + +def reverse_bwt(bwt_string: str, idx_original_string: int) -> str: + """ + :param bwt_string: The string returned from bwt algorithm execution + :param idx_original_string: A 0-based index of the string that was used to + generate bwt_string at ordered rotations list + :return: The string used to generate bwt_string when bwt was executed + :raises TypeError: If the bwt_string parameter type is not str + :raises ValueError: If the bwt_string parameter is empty + :raises TypeError: If the idx_original_string type is not int or if not + possible to cast it to int + :raises ValueError: If the idx_original_string value is lower than 0 or + greater than len(bwt_string) - 1 + + >>> reverse_bwt("BNN^AAA", 6) + '^BANANA' + >>> reverse_bwt("aaaadss_c__aa", 3) + 'a_asa_da_casa' + >>> reverse_bwt("mnpbnnaaaaaa", 11) + 'panamabanana' + >>> reverse_bwt(4, 11) + Traceback (most recent call last): + ... + TypeError: The parameter bwt_string type must be str. + >>> reverse_bwt("", 11) + Traceback (most recent call last): + ... + ValueError: The parameter bwt_string must not be empty. + >>> reverse_bwt("mnpbnnaaaaaa", "asd") # doctest: +NORMALIZE_WHITESPACE + Traceback (most recent call last): + ... + TypeError: The parameter idx_original_string type must be int or passive + of cast to int. + >>> reverse_bwt("mnpbnnaaaaaa", -1) + Traceback (most recent call last): + ... + ValueError: The parameter idx_original_string must not be lower than 0. + >>> reverse_bwt("mnpbnnaaaaaa", 12) # doctest: +NORMALIZE_WHITESPACE + Traceback (most recent call last): + ... + ValueError: The parameter idx_original_string must be lower than + len(bwt_string). + >>> reverse_bwt("mnpbnnaaaaaa", 11.0) + 'panamabanana' + >>> reverse_bwt("mnpbnnaaaaaa", 11.4) + 'panamabanana' + """ + if not isinstance(bwt_string, str): + raise TypeError("The parameter bwt_string type must be str.") + if not bwt_string: + raise ValueError("The parameter bwt_string must not be empty.") + try: + idx_original_string = int(idx_original_string) + except ValueError: + raise TypeError( + ( + "The parameter idx_original_string type must be int or passive" + " of cast to int." + ) + ) + if idx_original_string < 0: + raise ValueError( + "The parameter idx_original_string must not be lower than 0." + ) + if idx_original_string >= len(bwt_string): + raise ValueError( + ( + "The parameter idx_original_string must be lower than" + " len(bwt_string)." + ) + ) + + ordered_rotations = [""] * len(bwt_string) + for x in range(len(bwt_string)): + for i in range(len(bwt_string)): + ordered_rotations[i] = bwt_string[i] + ordered_rotations[i] + ordered_rotations.sort() + return ordered_rotations[idx_original_string] + + +if __name__ == "__main__": + entry_msg = "Provide a string that I will generate its BWT transform: " + s = input(entry_msg).strip() + result = bwt_transform(s) + bwt_output_msg = "Burrows Wheeler tranform for string '{}' results in '{}'" + print(bwt_output_msg.format(s, result["bwt_string"])) + original_string = reverse_bwt( + result["bwt_string"], result["idx_original_string"] + ) + fmt = ( + "Reversing Burrows Wheeler tranform for entry '{}' we get original" + " string '{}'" + ) + print(fmt.format(result["bwt_string"], original_string)) From 4658f4a49e2bf9b13d806bd7dfc3df522056ad1c Mon Sep 17 00:00:00 2001 From: Jigyasa G <33327397+jpg-130@users.noreply.github.com> Date: Thu, 18 Jul 2019 16:17:15 +0530 Subject: [PATCH 0123/1071] lgtm fixes (#1032) * adding sum of subsets * lgtm fixes --- project_euler/problem_07/sol2.py | 1 - 1 file changed, 1 deletion(-) diff --git a/project_euler/problem_07/sol2.py b/project_euler/problem_07/sol2.py index 630e5196796d..3dc0b1343eb7 100644 --- a/project_euler/problem_07/sol2.py +++ b/project_euler/problem_07/sol2.py @@ -7,7 +7,6 @@ We can see that the 6th prime is 13. What is the Nth prime number? """ from __future__ import print_function -from math import sqrt try: raw_input # Python 2 From c2e8582abdf64c837c61c08cecdd5cbea222e290 Mon Sep 17 00:00:00 2001 From: cclauss Date: Thu, 18 Jul 2019 13:10:52 +0200 Subject: [PATCH 0124/1071] Travis CI: Add pytest --doctest-modules neural_network (#1028) * neural_network/perceptron.py: Add if __name__ == '__main__': * Remove tab indentation * Add neural_network to the pytests --- .travis.yml | 1 + neural_network/perceptron.py | 27 ++++++++++++++------------- 2 files changed, 15 insertions(+), 13 deletions(-) diff --git a/.travis.yml b/.travis.yml index 55ea2c7ddc24..6ac3010c5396 100644 --- a/.travis.yml +++ b/.travis.yml @@ -26,6 +26,7 @@ script: linear_algebra_python matrix networking_flow + neural_network other project_euler searches diff --git a/neural_network/perceptron.py b/neural_network/perceptron.py index eb8b04e855d3..787ea8f73bf1 100644 --- a/neural_network/perceptron.py +++ b/neural_network/perceptron.py @@ -1,12 +1,12 @@ ''' - Perceptron - w = w + N * (d(k) - y) * x(k) + Perceptron + w = w + N * (d(k) - y) * x(k) - Using perceptron network for oil analysis, - with Measuring of 3 parameters that represent chemical characteristics we can classify the oil, in p1 or p2 - p1 = -1 - p2 = 1 + Using perceptron network for oil analysis, + with Measuring of 3 parameters that represent chemical characteristics we can classify the oil, in p1 or p2 + p1 = -1 + p2 = 1 ''' from __future__ import print_function @@ -113,12 +113,13 @@ def sign(self, u): exit = [-1, -1, -1, 1, 1, -1, 1, -1, 1, 1, -1, 1, -1, -1, -1, -1, 1, 1, 1, 1, -1, 1, 1, 1, 1, -1, -1, 1, -1, 1] -network = Perceptron(sample=samples, exit = exit, learn_rate=0.01, epoch_number=1000, bias=-1) +if __name__ == '__main__': + network = Perceptron(sample=samples, exit = exit, learn_rate=0.01, epoch_number=1000, bias=-1) -network.training() + network.training() -while True: - sample = [] - for i in range(3): - sample.insert(i, float(input('value: '))) - network.sort(sample) + while True: + sample = [] + for i in range(3): + sample.insert(i, float(input('value: ').strip())) + network.sort(sample) From 9a55f2b36a569f27a0893a79f0ba9cee23819557 Mon Sep 17 00:00:00 2001 From: cclauss Date: Thu, 18 Jul 2019 18:15:54 +0200 Subject: [PATCH 0125/1071] Remove the space: lucas series.py --> lucas_series.py (#1036) --- maths/{lucas series.py => lucas_series.py} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename maths/{lucas series.py => lucas_series.py} (100%) diff --git a/maths/lucas series.py b/maths/lucas_series.py similarity index 100% rename from maths/lucas series.py rename to maths/lucas_series.py From f438440ac54bbaad1557d5d8d2caa3331231f99b Mon Sep 17 00:00:00 2001 From: Bruno Simas Hadlich Date: Thu, 18 Jul 2019 14:05:14 -0300 Subject: [PATCH 0126/1071] Fixes for issue "Fix the LGTM issues #1024" (#1034) * Added doctest and more explanation about Dijkstra execution. * tests were not passing with python2 due to missing __init__.py file at number_theory folder * Removed the dot at the beginning of the imported modules names because 'python3 -m doctest -v data_structures/hashing/*.py' and 'python3 -m doctest -v data_structures/stacks/*.py' were failing not finding hash_table.py and stack.py modules. * Moved global code to main scope and added doctest for project euler problems 1 to 14. * Added test case for negative input. * Changed N variable to do not use end of line scape because in case there is a space after it the script will break making it much more error prone. * Added problems description and doctests to the ones that were missing. Limited line length to 79 and executed python black over all scripts. * Changed the way files are loaded to support pytest call. * Added __init__.py to problems to make them modules and allow pytest execution. * Added project_euler folder to test units execution * Changed 'os.path.split(os.path.realpath(__file__))' to 'os.path.dirname()' * Added Burrows-Wheeler transform algorithm. * Added changes suggested by cclauss * Fixes for issue 'Fix the LGTM issues #1024'. * Added doctest for different parameter types and negative values. * Fixed doctest issue added at last commit. --- project_euler/problem_02/sol4.py | 26 +++++++++++++++++++++++++- project_euler/problem_03/sol1.py | 27 +++++++++++++++++++++++++-- project_euler/problem_03/sol2.py | 27 +++++++++++++++++++++++++-- project_euler/problem_05/sol1.py | 27 +++++++++++++++++++++++++-- project_euler/problem_07/sol2.py | 24 ++++++++++++++++++++++++ project_euler/problem_09/sol1.py | 1 - project_euler/problem_19/sol1.py | 2 +- project_euler/problem_234/sol1.py | 1 - 8 files changed, 125 insertions(+), 10 deletions(-) diff --git a/project_euler/problem_02/sol4.py b/project_euler/problem_02/sol4.py index ba13b12a15e9..5e8c04899f3d 100644 --- a/project_euler/problem_02/sol4.py +++ b/project_euler/problem_02/sol4.py @@ -11,7 +11,7 @@ """ from __future__ import print_function import math -from decimal import * +from decimal import Decimal, getcontext try: raw_input # Python 2 @@ -33,7 +33,31 @@ def solution(n): 0 >>> solution(34) 44 + >>> solution(3.4) + 2 + >>> solution(0) + Traceback (most recent call last): + ... + ValueError: Parameter n must be greater or equal to one. + >>> solution(-17) + Traceback (most recent call last): + ... + ValueError: Parameter n must be greater or equal to one. + >>> solution([]) + Traceback (most recent call last): + ... + TypeError: Parameter n must be int or passive of cast to int. + >>> solution("asd") + Traceback (most recent call last): + ... + TypeError: Parameter n must be int or passive of cast to int. """ + try: + n = int(n) + except (TypeError, ValueError) as e: + raise TypeError("Parameter n must be int or passive of cast to int.") + if n <= 0: + raise ValueError("Parameter n must be greater or equal to one.") getcontext().prec = 100 phi = (Decimal(5) ** Decimal(0.5) + 1) / Decimal(2) diff --git a/project_euler/problem_03/sol1.py b/project_euler/problem_03/sol1.py index c2e601bd0040..ab19d8b30457 100644 --- a/project_euler/problem_03/sol1.py +++ b/project_euler/problem_03/sol1.py @@ -28,14 +28,38 @@ def isprime(no): def solution(n): """Returns the largest prime factor of a given number n. - + >>> solution(13195) 29 >>> solution(10) 5 >>> solution(17) 17 + >>> solution(3.4) + 3 + >>> solution(0) + Traceback (most recent call last): + ... + ValueError: Parameter n must be greater or equal to one. + >>> solution(-17) + Traceback (most recent call last): + ... + ValueError: Parameter n must be greater or equal to one. + >>> solution([]) + Traceback (most recent call last): + ... + TypeError: Parameter n must be int or passive of cast to int. + >>> solution("asd") + Traceback (most recent call last): + ... + TypeError: Parameter n must be int or passive of cast to int. """ + try: + n = int(n) + except (TypeError, ValueError) as e: + raise TypeError("Parameter n must be int or passive of cast to int.") + if n <= 0: + raise ValueError("Parameter n must be greater or equal to one.") maxNumber = 0 if isprime(n): return n @@ -54,7 +78,6 @@ def solution(n): elif isprime(i): maxNumber = i return maxNumber - return int(sum) if __name__ == "__main__": diff --git a/project_euler/problem_03/sol2.py b/project_euler/problem_03/sol2.py index 497db3965cc3..f93a0b75f4e0 100644 --- a/project_euler/problem_03/sol2.py +++ b/project_euler/problem_03/sol2.py @@ -6,7 +6,6 @@ e.g. for 10, largest prime factor = 5. For 17, largest prime factor = 17. """ from __future__ import print_function, division -import math try: raw_input # Python 2 @@ -16,14 +15,38 @@ def solution(n): """Returns the largest prime factor of a given number n. - + >>> solution(13195) 29 >>> solution(10) 5 >>> solution(17) 17 + >>> solution(3.4) + 3 + >>> solution(0) + Traceback (most recent call last): + ... + ValueError: Parameter n must be greater or equal to one. + >>> solution(-17) + Traceback (most recent call last): + ... + ValueError: Parameter n must be greater or equal to one. + >>> solution([]) + Traceback (most recent call last): + ... + TypeError: Parameter n must be int or passive of cast to int. + >>> solution("asd") + Traceback (most recent call last): + ... + TypeError: Parameter n must be int or passive of cast to int. """ + try: + n = int(n) + except (TypeError, ValueError) as e: + raise TypeError("Parameter n must be int or passive of cast to int.") + if n <= 0: + raise ValueError("Parameter n must be greater or equal to one.") prime = 1 i = 2 while i * i <= n: diff --git a/project_euler/problem_05/sol1.py b/project_euler/problem_05/sol1.py index 609f02102a08..e2deb91fb6aa 100644 --- a/project_euler/problem_05/sol1.py +++ b/project_euler/problem_05/sol1.py @@ -17,7 +17,7 @@ def solution(n): """Returns the smallest positive number that is evenly divisible(divisible with no remainder) by all of the numbers from 1 to n. - + >>> solution(10) 2520 >>> solution(15) @@ -26,7 +26,31 @@ def solution(n): 232792560 >>> solution(22) 232792560 + >>> solution(3.4) + 6 + >>> solution(0) + Traceback (most recent call last): + ... + ValueError: Parameter n must be greater or equal to one. + >>> solution(-17) + Traceback (most recent call last): + ... + ValueError: Parameter n must be greater or equal to one. + >>> solution([]) + Traceback (most recent call last): + ... + TypeError: Parameter n must be int or passive of cast to int. + >>> solution("asd") + Traceback (most recent call last): + ... + TypeError: Parameter n must be int or passive of cast to int. """ + try: + n = int(n) + except (TypeError, ValueError) as e: + raise TypeError("Parameter n must be int or passive of cast to int.") + if n <= 0: + raise ValueError("Parameter n must be greater or equal to one.") i = 0 while 1: i += n * (n - 1) @@ -39,7 +63,6 @@ def solution(n): if i == 0: i = 1 return i - break if __name__ == "__main__": diff --git a/project_euler/problem_07/sol2.py b/project_euler/problem_07/sol2.py index 3dc0b1343eb7..67336f7c1c96 100644 --- a/project_euler/problem_07/sol2.py +++ b/project_euler/problem_07/sol2.py @@ -36,7 +36,31 @@ def solution(n): 229 >>> solution(100) 541 + >>> solution(3.4) + 5 + >>> solution(0) + Traceback (most recent call last): + ... + ValueError: Parameter n must be greater or equal to one. + >>> solution(-17) + Traceback (most recent call last): + ... + ValueError: Parameter n must be greater or equal to one. + >>> solution([]) + Traceback (most recent call last): + ... + TypeError: Parameter n must be int or passive of cast to int. + >>> solution("asd") + Traceback (most recent call last): + ... + TypeError: Parameter n must be int or passive of cast to int. """ + try: + n = int(n) + except (TypeError, ValueError) as e: + raise TypeError("Parameter n must be int or passive of cast to int.") + if n <= 0: + raise ValueError("Parameter n must be greater or equal to one.") primes = [] num = 2 while len(primes) < n: diff --git a/project_euler/problem_09/sol1.py b/project_euler/problem_09/sol1.py index 0f368e48d2e3..20dedb84bc0e 100644 --- a/project_euler/problem_09/sol1.py +++ b/project_euler/problem_09/sol1.py @@ -28,7 +28,6 @@ def solution(): if (a ** 2) + (b ** 2) == (c ** 2): if (a + b + c) == 1000: return a * b * c - break if __name__ == "__main__": diff --git a/project_euler/problem_19/sol1.py b/project_euler/problem_19/sol1.py index 6e4e29ec19c6..ab59365843b2 100644 --- a/project_euler/problem_19/sol1.py +++ b/project_euler/problem_19/sol1.py @@ -61,4 +61,4 @@ def solution(): if __name__ == "__main__": - print(solution(171)) + print(solution()) diff --git a/project_euler/problem_234/sol1.py b/project_euler/problem_234/sol1.py index 8298a7f8cce3..c0d2949285e9 100644 --- a/project_euler/problem_234/sol1.py +++ b/project_euler/problem_234/sol1.py @@ -40,7 +40,6 @@ def solution(n): semidivisible = [] for x in range(n): l=[i for i in input().split()] - c1=0 c2=1 while(1): if len(fib(l[0],l[1],c2)) Date: Fri, 19 Jul 2019 03:10:51 +0530 Subject: [PATCH 0127/1071] Update find_lcm.py (#1019) * Update find_lcm.py Improved code quality and added comments. * Make the doctests work --- maths/find_lcm.py | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/maths/find_lcm.py b/maths/find_lcm.py index 9062d462b8b3..f7ac958070b5 100644 --- a/maths/find_lcm.py +++ b/maths/find_lcm.py @@ -4,8 +4,17 @@ def find_lcm(num_1, num_2): - """Find the LCM of two numbers.""" - max_num = num_1 if num_1 > num_2 else num_2 + """Find the least common multiple of two numbers. + >>> find_lcm(5,2) + 10 + >>> find_lcm(12,76) + 228 + """ + if num_1>=num_2: + max_num=num_1 + else: + max_num=num_2 + lcm = max_num while True: if ((lcm % num_1 == 0) and (lcm % num_2 == 0)): @@ -16,8 +25,8 @@ def find_lcm(num_1, num_2): def main(): """Use test numbers to run the find_lcm algorithm.""" - num_1 = 12 - num_2 = 76 + num_1 = int(input().strip()) + num_2 = int(input().strip()) print(find_lcm(num_1, num_2)) From f7ac8b5ed054198bdb254635e8f06c6f219c2f75 Mon Sep 17 00:00:00 2001 From: Bruno Simas Hadlich Date: Thu, 18 Jul 2019 19:34:29 -0300 Subject: [PATCH 0128/1071] Commented doctests that were causing slowness at Travis. (#1039) * Added doctest and more explanation about Dijkstra execution. * tests were not passing with python2 due to missing __init__.py file at number_theory folder * Removed the dot at the beginning of the imported modules names because 'python3 -m doctest -v data_structures/hashing/*.py' and 'python3 -m doctest -v data_structures/stacks/*.py' were failing not finding hash_table.py and stack.py modules. * Moved global code to main scope and added doctest for project euler problems 1 to 14. * Added test case for negative input. * Changed N variable to do not use end of line scape because in case there is a space after it the script will break making it much more error prone. * Added problems description and doctests to the ones that were missing. Limited line length to 79 and executed python black over all scripts. * Changed the way files are loaded to support pytest call. * Added __init__.py to problems to make them modules and allow pytest execution. * Added project_euler folder to test units execution * Changed 'os.path.split(os.path.realpath(__file__))' to 'os.path.dirname()' * Added Burrows-Wheeler transform algorithm. * Added changes suggested by cclauss * Fixes for issue 'Fix the LGTM issues #1024'. * Added doctest for different parameter types and negative values. * Fixed doctest issue added at last commit. * Commented doctest that were causing slowness at Travis. * Added comment with the reason for some doctest commented. * pytest --ignore --- .travis.yml | 38 +++++++++++--------------------- project_euler/problem_09/sol1.py | 5 +++-- project_euler/problem_09/sol3.py | 4 ++-- project_euler/problem_10/sol1.py | 5 +++-- project_euler/problem_10/sol2.py | 5 +++-- project_euler/problem_12/sol1.py | 5 +++-- project_euler/problem_12/sol2.py | 5 +++-- project_euler/problem_14/sol1.py | 5 +++-- project_euler/problem_14/sol2.py | 5 +++-- 9 files changed, 36 insertions(+), 41 deletions(-) diff --git a/.travis.yml b/.travis.yml index 6ac3010c5396..a3ff22fb09b7 100644 --- a/.travis.yml +++ b/.travis.yml @@ -9,31 +9,19 @@ before_script: - flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics script: - mypy --ignore-missing-imports . - #- IGNORE="data_structures,file_transfer_protocol,graphs,machine_learning,maths,neural_network,project_euler" - #- pytest . --doctest-modules --ignore=${IGNORE} - - pytest --doctest-modules - arithmetic_analysis - backtracking - boolean_algebra - ciphers - compression - conversions - digital_image_processing - divide_and_conquer - dynamic_programming - graphs - hashes - linear_algebra_python - matrix - networking_flow - neural_network - other - project_euler - searches - sorts - strings - traversals - + - pytest . --doctest-modules + --ignore=data_structures/stacks/balanced_parentheses.py + --ignore=data_structures/stacks/infix_to_postfix_conversion.py + --ignore=file_transfer_protocol/ftp_send_receive.py + --ignore=file_transfer_protocol/ftp_client_server.py + --ignore=machine_learning/linear_regression.py + --ignore=machine_learning/perceptron.py + --ignore=machine_learning/random_forest_classification/random_forest_classification.py + --ignore=machine_learning/random_forest_regression/random_forest_regression.py + --ignore=maths/abs_min.py + --ignore=maths/binary_exponentiation.py + --ignore=maths/lucas_series.py + --ignore=maths/sieve_of_eratosthenes.py after_success: - python scripts/build_directory_md.py - cat DIRECTORY.md diff --git a/project_euler/problem_09/sol1.py b/project_euler/problem_09/sol1.py index 20dedb84bc0e..d9ebe8760861 100644 --- a/project_euler/problem_09/sol1.py +++ b/project_euler/problem_09/sol1.py @@ -18,8 +18,9 @@ def solution(): 2. a**2 + b**2 = c**2 3. a + b + c = 1000 - >>> solution() - 31875000 + # The code below has been commented due to slow execution affecting Travis. + # >>> solution() + # 31875000 """ for a in range(300): for b in range(400): diff --git a/project_euler/problem_09/sol3.py b/project_euler/problem_09/sol3.py index f749b8a61f11..829ba84c4a77 100644 --- a/project_euler/problem_09/sol3.py +++ b/project_euler/problem_09/sol3.py @@ -21,8 +21,8 @@ def solution(): 1. a**2 + b**2 = c**2 2. a + b + c = 1000 - >>> solution() - 31875000 + #>>> solution() + #31875000 """ return [ a * b * c diff --git a/project_euler/problem_10/sol1.py b/project_euler/problem_10/sol1.py index 038da96e6352..49384d7c78f0 100644 --- a/project_euler/problem_10/sol1.py +++ b/project_euler/problem_10/sol1.py @@ -42,8 +42,9 @@ def sum_of_primes(n): def solution(n): """Returns the sum of all the primes below n. - >>> solution(2000000) - 142913828922 + # The code below has been commented due to slow execution affecting Travis. + # >>> solution(2000000) + # 142913828922 >>> solution(1000) 76127 >>> solution(5000) diff --git a/project_euler/problem_10/sol2.py b/project_euler/problem_10/sol2.py index 9e51d61b8749..451a4ae5e8f3 100644 --- a/project_euler/problem_10/sol2.py +++ b/project_euler/problem_10/sol2.py @@ -31,8 +31,9 @@ def prime_generator(): def solution(n): """Returns the sum of all the primes below n. - >>> solution(2000000) - 142913828922 + # The code below has been commented due to slow execution affecting Travis. + # >>> solution(2000000) + # 142913828922 >>> solution(1000) 76127 >>> solution(5000) diff --git a/project_euler/problem_12/sol1.py b/project_euler/problem_12/sol1.py index baf9babab686..54476110b503 100644 --- a/project_euler/problem_12/sol1.py +++ b/project_euler/problem_12/sol1.py @@ -45,8 +45,9 @@ def solution(): """Returns the value of the first triangle number to have over five hundred divisors. - >>> solution() - 76576500 + # The code below has been commented due to slow execution affecting Travis. + # >>> solution() + # 76576500 """ tNum = 1 i = 1 diff --git a/project_euler/problem_12/sol2.py b/project_euler/problem_12/sol2.py index 071d7516ac0f..0d1502830bee 100644 --- a/project_euler/problem_12/sol2.py +++ b/project_euler/problem_12/sol2.py @@ -39,8 +39,9 @@ def solution(): """Returns the value of the first triangle number to have over five hundred divisors. - >>> solution() - 76576500 + # The code below has been commented due to slow execution affecting Travis. + # >>> solution() + # 76576500 """ return next( i for i in triangle_number_generator() if count_divisors(i) > 500 diff --git a/project_euler/problem_14/sol1.py b/project_euler/problem_14/sol1.py index 6b80cd7cb24b..8d3efbc59eb5 100644 --- a/project_euler/problem_14/sol1.py +++ b/project_euler/problem_14/sol1.py @@ -30,8 +30,9 @@ def solution(n): n → n/2 (n is even) n → 3n + 1 (n is odd) - >>> solution(1000000) - {'counter': 525, 'largest_number': 837799} + # The code below has been commented due to slow execution affecting Travis. + # >>> solution(1000000) + # {'counter': 525, 'largest_number': 837799} >>> solution(200) {'counter': 125, 'largest_number': 171} >>> solution(5000) diff --git a/project_euler/problem_14/sol2.py b/project_euler/problem_14/sol2.py index 59fa79515148..0ec80e221f09 100644 --- a/project_euler/problem_14/sol2.py +++ b/project_euler/problem_14/sol2.py @@ -47,8 +47,9 @@ def collatz_sequence(n): def solution(n): """Returns the number under n that generates the longest Collatz sequence. - >>> solution(1000000) - {'counter': 525, 'largest_number': 837799} + # The code below has been commented due to slow execution affecting Travis. + # >>> solution(1000000) + # {'counter': 525, 'largest_number': 837799} >>> solution(200) {'counter': 125, 'largest_number': 171} >>> solution(5000) From 9fcfe6a02bca94664189a656cf76bd425bbf3417 Mon Sep 17 00:00:00 2001 From: Bruno Simas Hadlich Date: Fri, 19 Jul 2019 01:33:28 -0300 Subject: [PATCH 0129/1071] Commented doctests that were causing slowness at Travis. #2 (#1041) * Added doctest and more explanation about Dijkstra execution. * tests were not passing with python2 due to missing __init__.py file at number_theory folder * Removed the dot at the beginning of the imported modules names because 'python3 -m doctest -v data_structures/hashing/*.py' and 'python3 -m doctest -v data_structures/stacks/*.py' were failing not finding hash_table.py and stack.py modules. * Moved global code to main scope and added doctest for project euler problems 1 to 14. * Added test case for negative input. * Changed N variable to do not use end of line scape because in case there is a space after it the script will break making it much more error prone. * Added problems description and doctests to the ones that were missing. Limited line length to 79 and executed python black over all scripts. * Changed the way files are loaded to support pytest call. * Added __init__.py to problems to make them modules and allow pytest execution. * Added project_euler folder to test units execution * Changed 'os.path.split(os.path.realpath(__file__))' to 'os.path.dirname()' * Added Burrows-Wheeler transform algorithm. * Added changes suggested by cclauss * Fixes for issue 'Fix the LGTM issues #1024'. * Added doctest for different parameter types and negative values. * Fixed doctest issue added at last commit. * Commented doctest that were causing slowness at Travis. * Added comment with the reason for some doctest commented. * pytest --ignore * Added tests execution again. * Had forgotten to add comment to file project_euler/problem_09/sol3.py --- project_euler/problem_09/sol3.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/project_euler/problem_09/sol3.py b/project_euler/problem_09/sol3.py index 829ba84c4a77..006029c8a30d 100644 --- a/project_euler/problem_09/sol3.py +++ b/project_euler/problem_09/sol3.py @@ -21,8 +21,9 @@ def solution(): 1. a**2 + b**2 = c**2 2. a + b + c = 1000 - #>>> solution() - #31875000 + # The code below has been commented due to slow execution affecting Travis. + # >>> solution() + # 31875000 """ return [ a * b * c From 60c608d85a41f85f795233b5ff913ca35f8a2f92 Mon Sep 17 00:00:00 2001 From: "Md. Mahbubur Rahman" Date: Fri, 19 Jul 2019 16:41:37 +0900 Subject: [PATCH 0130/1071] Added matrix exponentiation approach for finding fibonacci number. (#1042) * Added matrix exponentiation approach for finding fibonacci number. * Implemented the way of finding nth fibonacci. * Complexity is about O(log(n)*8) * Updated the matrix exponentiation approach of finding nth fibonacci. - Removed some extra spaces - Added the complexity of bruteforce algorithm - Removed unused function called zerro() - Added some docktest based on request * Updated the matrix exponentiation approach of finding nth fibonacci. - Removed some extra spaces - Added the complexity of bruteforce algorithm - Removed unused function called zerro() - Added some docktest based on request * Tighten up main() and add comments on performance --- ...h_fibonacci_using_matrix_exponentiation.py | 88 +++++++++++++++++++ 1 file changed, 88 insertions(+) create mode 100644 matrix/nth_fibonacci_using_matrix_exponentiation.py diff --git a/matrix/nth_fibonacci_using_matrix_exponentiation.py b/matrix/nth_fibonacci_using_matrix_exponentiation.py new file mode 100644 index 000000000000..cee6b21c81eb --- /dev/null +++ b/matrix/nth_fibonacci_using_matrix_exponentiation.py @@ -0,0 +1,88 @@ +""" +Implementation of finding nth fibonacci number using matrix exponentiation. +Time Complexity is about O(log(n)*8), where 8 is the complexity of matrix multiplication of size 2 by 2. +And on the other hand complexity of bruteforce solution is O(n). +As we know + f[n] = f[n-1] + f[n-1] +Converting to matrix, + [f(n),f(n-1)] = [[1,1],[1,0]] * [f(n-1),f(n-2)] +-> [f(n),f(n-1)] = [[1,1],[1,0]]^2 * [f(n-2),f(n-3)] + ... + ... +-> [f(n),f(n-1)] = [[1,1],[1,0]]^(n-1) * [f(1),f(0)] +So we just need the n times multiplication of the matrix [1,1],[1,0]]. +We can decrease the n times multiplication by following the divide and conquer approach. +""" +from __future__ import print_function + + +def multiply(matrix_a, matrix_b): + matrix_c = [] + n = len(matrix_a) + for i in range(n): + list_1 = [] + for j in range(n): + val = 0 + for k in range(n): + val = val + matrix_a[i][k] * matrix_b[k][j] + list_1.append(val) + matrix_c.append(list_1) + return matrix_c + + +def identity(n): + return [[int(row == column) for column in range(n)] for row in range(n)] + + +def nth_fibonacci_matrix(n): + """ + >>> nth_fibonacci_matrix(100) + 354224848179261915075 + >>> nth_fibonacci_matrix(-100) + -100 + """ + if n <= 1: + return n + res_matrix = identity(2) + fibonacci_matrix = [[1, 1], [1, 0]] + n = n - 1 + while n > 0: + if n % 2 == 1: + res_matrix = multiply(res_matrix, fibonacci_matrix) + fibonacci_matrix = multiply(fibonacci_matrix, fibonacci_matrix) + n = int(n / 2) + return res_matrix[0][0] + + +def nth_fibonacci_bruteforce(n): + """ + >>> nth_fibonacci_bruteforce(100) + 354224848179261915075 + >>> nth_fibonacci_bruteforce(-100) + -100 + """ + if n <= 1: + return n + fib0 = 0 + fib1 = 1 + for i in range(2, n + 1): + fib0, fib1 = fib1, fib0 + fib1 + return fib1 + + +def main(): + fmt = "{} fibonacci number using matrix exponentiation is {} and using bruteforce is {}\n" + for ordinal in "0th 1st 2nd 3rd 10th 100th 1000th".split(): + n = int("".join(c for c in ordinal if c in "0123456789")) # 1000th --> 1000 + print(fmt.format(ordinal, nth_fibonacci(n), nth_fibonacci_test(n))) + # from timeit import timeit + # print(timeit("nth_fibonacci_matrix(1000000)", + # "from main import nth_fibonacci_matrix", number=5)) + # print(timeit("nth_fibonacci_bruteforce(1000000)", + # "from main import nth_fibonacci_bruteforce", number=5)) + # 2.3342058970001744 + # 57.256506615000035 + + +if __name__ == "__main__": + main() From dc1de946eabe20053b811b50e0e1d50697bd46fa Mon Sep 17 00:00:00 2001 From: cclauss Date: Fri, 19 Jul 2019 10:55:45 +0200 Subject: [PATCH 0131/1071] Use correct function names in nth_fibonacci_using_matrix_exponentiation.py (#1045) @AnupKumarPanwar @ParthS007 @poyea Could I please get a quick review on this one because I made a mistake here that breaks the build for new pull requests. --- matrix/nth_fibonacci_using_matrix_exponentiation.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/matrix/nth_fibonacci_using_matrix_exponentiation.py b/matrix/nth_fibonacci_using_matrix_exponentiation.py index cee6b21c81eb..7491abcae031 100644 --- a/matrix/nth_fibonacci_using_matrix_exponentiation.py +++ b/matrix/nth_fibonacci_using_matrix_exponentiation.py @@ -74,7 +74,7 @@ def main(): fmt = "{} fibonacci number using matrix exponentiation is {} and using bruteforce is {}\n" for ordinal in "0th 1st 2nd 3rd 10th 100th 1000th".split(): n = int("".join(c for c in ordinal if c in "0123456789")) # 1000th --> 1000 - print(fmt.format(ordinal, nth_fibonacci(n), nth_fibonacci_test(n))) + print(fmt.format(ordinal, nth_fibonacci_matrix(n), nth_fibonacci_bruteforce(n))) # from timeit import timeit # print(timeit("nth_fibonacci_matrix(1000000)", # "from main import nth_fibonacci_matrix", number=5)) From 4e0717c3cfb336aa86f6720f2f49adf58f0e95d7 Mon Sep 17 00:00:00 2001 From: Stephen Gemin <45926479+StephenGemin@users.noreply.github.com> Date: Fri, 19 Jul 2019 23:06:29 -0400 Subject: [PATCH 0132/1071] Add error & test checks for matrix_operations.py (#925) * Update matrix_operation.py 1. Adding error checks for integer inputs 2. Adding error checks for matrix operations where size requirements do not match up 3. Added matrix subtraction function 4. included error check so only integer is passed into identity function * Create test_matrix_operation.py * Update matrix_ops and Add Test Cases 1. Included error checks in matrix operation. There were some cases where the functions would not work correctly. 2. PEP8 changes to matrix_operations.py 3. added test cases for matrix operations using pytest. * Update pytest.ini Add carriage return to end of file --- matrix/matrix_operation.py | 136 +++++++++++++++++++------- matrix/tests/pytest.ini | 3 + matrix/tests/test_matrix_operation.py | 112 +++++++++++++++++++++ 3 files changed, 217 insertions(+), 34 deletions(-) create mode 100644 matrix/tests/pytest.ini create mode 100644 matrix/tests/test_matrix_operation.py diff --git a/matrix/matrix_operation.py b/matrix/matrix_operation.py index dd7c01582681..b32a4dcf7af3 100644 --- a/matrix/matrix_operation.py +++ b/matrix/matrix_operation.py @@ -1,64 +1,131 @@ -from __future__ import print_function +""" +function based version of matrix operations, which are just 2D arrays +""" + def add(matrix_a, matrix_b): - rows = len(matrix_a) - columns = len(matrix_a[0]) - matrix_c = [] - for i in range(rows): - list_1 = [] - for j in range(columns): - val = matrix_a[i][j] + matrix_b[i][j] - list_1.append(val) - matrix_c.append(list_1) - return matrix_c - -def scalarMultiply(matrix , n): + if _check_not_integer(matrix_a) and _check_not_integer(matrix_b): + rows, cols = _verify_matrix_sizes(matrix_a, matrix_b) + matrix_c = [] + for i in range(rows[0]): + list_1 = [] + for j in range(cols[0]): + val = matrix_a[i][j] + matrix_b[i][j] + list_1.append(val) + matrix_c.append(list_1) + return matrix_c + + +def subtract(matrix_a, matrix_b): + if _check_not_integer(matrix_a) and _check_not_integer(matrix_b): + rows, cols = _verify_matrix_sizes(matrix_a, matrix_b) + matrix_c = [] + for i in range(rows[0]): + list_1 = [] + for j in range(cols[0]): + val = matrix_a[i][j] - matrix_b[i][j] + list_1.append(val) + matrix_c.append(list_1) + return matrix_c + + +def scalar_multiply(matrix, n): return [[x * n for x in row] for row in matrix] + def multiply(matrix_a, matrix_b): - matrix_c = [] - n = len(matrix_a) - for i in range(n): - list_1 = [] - for j in range(n): - val = 0 - for k in range(n): - val = val + matrix_a[i][k] * matrix_b[k][j] - list_1.append(val) - matrix_c.append(list_1) - return matrix_c + if _check_not_integer(matrix_a) and _check_not_integer(matrix_b): + matrix_c = [] + rows, cols = _verify_matrix_sizes(matrix_a, matrix_b) + + if cols[0] != rows[1]: + raise ValueError(f'Cannot multiply matrix of dimensions ({rows[0]},{cols[0]}) ' + f'and ({rows[1]},{cols[1]})') + for i in range(rows[0]): + list_1 = [] + for j in range(cols[1]): + val = 0 + for k in range(cols[1]): + val = val + matrix_a[i][k] * matrix_b[k][j] + list_1.append(val) + matrix_c.append(list_1) + return matrix_c + def identity(n): + """ + :param n: dimension for nxn matrix + :type n: int + :return: Identity matrix of shape [n, n] + """ + n = int(n) return [[int(row == column) for column in range(n)] for row in range(n)] -def transpose(matrix): - return map(list , zip(*matrix)) + +def transpose(matrix, return_map=True): + if _check_not_integer(matrix): + if return_map: + return map(list, zip(*matrix)) + else: + # mt = [] + # for i in range(len(matrix[0])): + # mt.append([row[i] for row in matrix]) + # return mt + return [[row[i] for row in matrix] for i in range(len(matrix[0]))] + def minor(matrix, row, column): minor = matrix[:row] + matrix[row + 1:] minor = [row[:column] + row[column + 1:] for row in minor] return minor + def determinant(matrix): - if len(matrix) == 1: return matrix[0][0] + if len(matrix) == 1: + return matrix[0][0] res = 0 for x in range(len(matrix)): - res += matrix[0][x] * determinant(minor(matrix , 0 , x)) * (-1) ** x + res += matrix[0][x] * determinant(minor(matrix, 0, x)) * (-1) ** x return res + def inverse(matrix): det = determinant(matrix) - if det == 0: return None + if det == 0: + return None - matrixMinor = [[] for _ in range(len(matrix))] + matrix_minor = [[] for _ in range(len(matrix))] for i in range(len(matrix)): for j in range(len(matrix)): - matrixMinor[i].append(determinant(minor(matrix , i , j))) + matrix_minor[i].append(determinant(minor(matrix, i, j))) - cofactors = [[x * (-1) ** (row + col) for col, x in enumerate(matrixMinor[row])] for row in range(len(matrix))] + cofactors = [[x * (-1) ** (row + col) for col, x in enumerate(matrix_minor[row])] for row in range(len(matrix))] adjugate = transpose(cofactors) - return scalarMultiply(adjugate , 1/det) + return scalar_multiply(adjugate, 1/det) + + +def _check_not_integer(matrix): + try: + rows = len(matrix) + cols = len(matrix[0]) + return True + except TypeError: + raise TypeError("Cannot input an integer value, it must be a matrix") + + +def _shape(matrix): + return list((len(matrix), len(matrix[0]))) + + +def _verify_matrix_sizes(matrix_a, matrix_b): + shape = _shape(matrix_a) + shape += _shape(matrix_b) + if shape[0] != shape[2] or shape[1] != shape[3]: + raise ValueError(f"operands could not be broadcast together with shape " + f"({shape[0], shape[1]}), ({shape[2], shape[3]})") + return [shape[0], shape[2]], [shape[1], shape[3]] + def main(): matrix_a = [[12, 10], [3, 9]] @@ -68,9 +135,10 @@ def main(): print('Add Operation, %s + %s = %s \n' %(matrix_a, matrix_b, (add(matrix_a, matrix_b)))) print('Multiply Operation, %s * %s = %s \n' %(matrix_a, matrix_b, multiply(matrix_a, matrix_b))) print('Identity: %s \n' %identity(5)) - print('Minor of %s = %s \n' %(matrix_c, minor(matrix_c , 1 , 2))) + print('Minor of %s = %s \n' %(matrix_c, minor(matrix_c, 1, 2))) print('Determinant of %s = %s \n' %(matrix_b, determinant(matrix_b))) print('Inverse of %s = %s\n'%(matrix_d, inverse(matrix_d))) + if __name__ == '__main__': main() diff --git a/matrix/tests/pytest.ini b/matrix/tests/pytest.ini new file mode 100644 index 000000000000..8a978b56ef8b --- /dev/null +++ b/matrix/tests/pytest.ini @@ -0,0 +1,3 @@ +[pytest] +markers = + mat_ops: tests for matrix operations diff --git a/matrix/tests/test_matrix_operation.py b/matrix/tests/test_matrix_operation.py new file mode 100644 index 000000000000..8b81b65d0fc8 --- /dev/null +++ b/matrix/tests/test_matrix_operation.py @@ -0,0 +1,112 @@ +""" +Testing here assumes that numpy and linalg is ALWAYS correct!!!! + +If running from PyCharm you can place the following line in "Additional Arguments" for the pytest run configuration +-vv -m mat_ops -p no:cacheprovider +""" + +# standard libraries +import sys +import numpy as np +import pytest +import logging + +# Custom/local libraries +from matrix import matrix_operation as matop + +mat_a = [[12, 10], [3, 9]] +mat_b = [[3, 4], [7, 4]] +mat_c = [[3, 0, 2], [2, 0, -2], [0, 1, 1]] +mat_d = [[3, 0, -2], [2, 0, 2], [0, 1, 1]] +mat_e = [[3, 0, 2], [2, 0, -2], [0, 1, 1], [2, 0, -2]] +mat_f = [1] +mat_h = [2] + +logger = logging.getLogger() +logger.level = logging.DEBUG +stream_handler = logging.StreamHandler(sys.stdout) +logger.addHandler(stream_handler) + + +@pytest.mark.mat_ops +@pytest.mark.parametrize(('mat1', 'mat2'), [(mat_a, mat_b), (mat_c, mat_d), (mat_d, mat_e), + (mat_f, mat_h)]) +def test_addition(mat1, mat2): + if (np.array(mat1)).shape < (2, 2) or (np.array(mat2)).shape < (2, 2): + with pytest.raises(TypeError): + logger.info(f"\n\t{test_addition.__name__} returned integer") + matop.add(mat1, mat2) + elif (np.array(mat1)).shape == (np.array(mat2)).shape: + logger.info(f"\n\t{test_addition.__name__} with same matrix dims") + act = (np.array(mat1) + np.array(mat2)).tolist() + theo = matop.add(mat1, mat2) + assert theo == act + else: + with pytest.raises(ValueError): + logger.info(f"\n\t{test_addition.__name__} with different matrix dims") + matop.add(mat1, mat2) + + +@pytest.mark.mat_ops +@pytest.mark.parametrize(('mat1', 'mat2'), [(mat_a, mat_b), (mat_c, mat_d), (mat_d, mat_e), + (mat_f, mat_h)]) +def test_subtraction(mat1, mat2): + if (np.array(mat1)).shape < (2, 2) or (np.array(mat2)).shape < (2, 2): + with pytest.raises(TypeError): + logger.info(f"\n\t{test_subtraction.__name__} returned integer") + matop.subtract(mat1, mat2) + elif (np.array(mat1)).shape == (np.array(mat2)).shape: + logger.info(f"\n\t{test_subtraction.__name__} with same matrix dims") + act = (np.array(mat1) - np.array(mat2)).tolist() + theo = matop.subtract(mat1, mat2) + assert theo == act + else: + with pytest.raises(ValueError): + logger.info(f"\n\t{test_subtraction.__name__} with different matrix dims") + assert matop.subtract(mat1, mat2) + + +@pytest.mark.mat_ops +@pytest.mark.parametrize(('mat1', 'mat2'), [(mat_a, mat_b), (mat_c, mat_d), (mat_d, mat_e), + (mat_f, mat_h)]) +def test_multiplication(mat1, mat2): + if (np.array(mat1)).shape < (2, 2) or (np.array(mat2)).shape < (2, 2): + logger.info(f"\n\t{test_multiplication.__name__} returned integer") + with pytest.raises(TypeError): + matop.add(mat1, mat2) + elif (np.array(mat1)).shape == (np.array(mat2)).shape: + logger.info(f"\n\t{test_multiplication.__name__} meets dim requirements") + act = (np.matmul(mat1, mat2)).tolist() + theo = matop.multiply(mat1, mat2) + assert theo == act + else: + with pytest.raises(ValueError): + logger.info(f"\n\t{test_multiplication.__name__} does not meet dim requirements") + assert matop.subtract(mat1, mat2) + + +@pytest.mark.mat_ops +def test_scalar_multiply(): + act = (3.5 * np.array(mat_a)).tolist() + theo = matop.scalar_multiply(mat_a, 3.5) + assert theo == act + + +@pytest.mark.mat_ops +def test_identity(): + act = (np.identity(5)).tolist() + theo = matop.identity(5) + assert theo == act + + +@pytest.mark.mat_ops +@pytest.mark.parametrize('mat', [mat_a, mat_b, mat_c, mat_d, mat_e, mat_f]) +def test_transpose(mat): + if (np.array(mat)).shape < (2, 2): + with pytest.raises(TypeError): + logger.info(f"\n\t{test_transpose.__name__} returned integer") + matop.transpose(mat) + else: + act = (np.transpose(mat)).tolist() + theo = matop.transpose(mat, return_map=False) + assert theo == act From f5e6d4e8cddb920439ed9a94b79f2af9a4fa2ac6 Mon Sep 17 00:00:00 2001 From: Christian Clauss Date: Sat, 20 Jul 2019 09:36:55 +0200 Subject: [PATCH 0133/1071] Update DIRECTORY.md (#1046) * Update DIRECTORY.md * Remove blank lines --- DIRECTORY.md | 27 ++++++++++++++------------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/DIRECTORY.md b/DIRECTORY.md index 66128228abc3..fc06a8cd1548 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -5,6 +5,7 @@ * [newton method](https://github.com/TheAlgorithms/Python/blob/master/arithmetic_analysis/newton_method.py) * [newton raphson method](https://github.com/TheAlgorithms/Python/blob/master/arithmetic_analysis/newton_raphson_method.py) ## Backtracking + * [all combinations](https://github.com/TheAlgorithms/Python/blob/master/backtracking/all_combinations.py) * [all permutations](https://github.com/TheAlgorithms/Python/blob/master/backtracking/all_permutations.py) * [all subsequences](https://github.com/TheAlgorithms/Python/blob/master/backtracking/all_subsequences.py) * [minimax](https://github.com/TheAlgorithms/Python/blob/master/backtracking/minimax.py) @@ -27,7 +28,6 @@ * [morse code implementation](https://github.com/TheAlgorithms/Python/blob/master/ciphers/morse_code_implementation.py) * [onepad cipher](https://github.com/TheAlgorithms/Python/blob/master/ciphers/onepad_cipher.py) * [playfair cipher](https://github.com/TheAlgorithms/Python/blob/master/ciphers/playfair_cipher.py) - * [prehistoric men](https://github.com/TheAlgorithms/Python/blob/master/ciphers/prehistoric_men.txt) * [rabin miller](https://github.com/TheAlgorithms/Python/blob/master/ciphers/rabin_miller.py) * [rot13](https://github.com/TheAlgorithms/Python/blob/master/ciphers/rot13.py) * [rsa cipher](https://github.com/TheAlgorithms/Python/blob/master/ciphers/rsa_cipher.py) @@ -39,6 +39,7 @@ * [vigenere cipher](https://github.com/TheAlgorithms/Python/blob/master/ciphers/vigenere_cipher.py) * [xor cipher](https://github.com/TheAlgorithms/Python/blob/master/ciphers/xor_cipher.py) ## Compression + * [burrows wheeler](https://github.com/TheAlgorithms/Python/blob/master/compression/burrows_wheeler.py) * [huffman](https://github.com/TheAlgorithms/Python/blob/master/compression/huffman.py) * [peak signal to noise ratio](https://github.com/TheAlgorithms/Python/blob/master/compression/peak_signal_to_noise_ratio.py) * Image Data @@ -100,8 +101,10 @@ ## Dynamic Programming * [abbreviation](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/abbreviation.py) * [bitmask](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/bitmask.py) + * [climbing stairs](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/climbing_stairs.py) * [coin change](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/coin_change.py) * [edit distance](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/edit_distance.py) + * [factorial](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/factorial.py) * [fast fibonacci](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/fast_fibonacci.py) * [fibonacci](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/fibonacci.py) * [floyd warshall](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/floyd_warshall.py) @@ -142,9 +145,9 @@ * [eulerian path and circuit for undirected graph](https://github.com/TheAlgorithms/Python/blob/master/graphs/eulerian_path_and_circuit_for_undirected_graph.py) * [even tree](https://github.com/TheAlgorithms/Python/blob/master/graphs/even_tree.py) * [finding bridges](https://github.com/TheAlgorithms/Python/blob/master/graphs/finding_bridges.py) - * [floyd warshall](https://github.com/TheAlgorithms/Python/blob/master/graphs/floyd_warshall.py) * [graph list](https://github.com/TheAlgorithms/Python/blob/master/graphs/graph_list.py) * [graph matrix](https://github.com/TheAlgorithms/Python/blob/master/graphs/graph_matrix.py) + * [graphs floyd warshall](https://github.com/TheAlgorithms/Python/blob/master/graphs/graphs_floyd_warshall.py) * [kahns algorithm long](https://github.com/TheAlgorithms/Python/blob/master/graphs/kahns_algorithm_long.py) * [kahns algorithm topo](https://github.com/TheAlgorithms/Python/blob/master/graphs/kahns_algorithm_topo.py) * [minimum spanning tree kruskal](https://github.com/TheAlgorithms/Python/blob/master/graphs/minimum_spanning_tree_kruskal.py) @@ -170,13 +173,17 @@ * [knn sklearn](https://github.com/TheAlgorithms/Python/blob/master/machine_learning/knn_sklearn.py) * [linear regression](https://github.com/TheAlgorithms/Python/blob/master/machine_learning/linear_regression.py) * [logistic regression](https://github.com/TheAlgorithms/Python/blob/master/machine_learning/logistic_regression.py) + * [NaiveBayes](https://github.com/TheAlgorithms/Python/blob/master/machine_learning/NaiveBayes.ipynb) * [perceptron](https://github.com/TheAlgorithms/Python/blob/master/machine_learning/perceptron.py) + * [reuters one vs rest classifier](https://github.com/TheAlgorithms/Python/blob/master/machine_learning/reuters_one_vs_rest_classifier.ipynb) * [scoring functions](https://github.com/TheAlgorithms/Python/blob/master/machine_learning/scoring_functions.py) * Random Forest Classification * [random forest classification](https://github.com/TheAlgorithms/Python/blob/master/machine_learning/random_forest_classification/random_forest_classification.py) + * [random forest classifier](https://github.com/TheAlgorithms/Python/blob/master/machine_learning/random_forest_classification/random_forest_classifier.ipynb) * [Social Network Ads](https://github.com/TheAlgorithms/Python/blob/master/machine_learning/random_forest_classification/Social_Network_Ads.csv) * Random Forest Regression * [Position Salaries](https://github.com/TheAlgorithms/Python/blob/master/machine_learning/random_forest_regression/Position_Salaries.csv) + * [random forest regression](https://github.com/TheAlgorithms/Python/blob/master/machine_learning/random_forest_regression/random_forest_regression.ipynb) * [random forest regression](https://github.com/TheAlgorithms/Python/blob/master/machine_learning/random_forest_regression/random_forest_regression.py) ## Maths * [3n+1](https://github.com/TheAlgorithms/Python/blob/master/maths/3n+1.py) @@ -197,7 +204,7 @@ * [find max](https://github.com/TheAlgorithms/Python/blob/master/maths/find_max.py) * [find min](https://github.com/TheAlgorithms/Python/blob/master/maths/find_min.py) * [greater common divisor](https://github.com/TheAlgorithms/Python/blob/master/maths/greater_common_divisor.py) - * [lucas series](https://github.com/TheAlgorithms/Python/blob/master/maths/lucas%20series.py) + * [lucas series](https://github.com/TheAlgorithms/Python/blob/master/maths/lucas_series.py) * [modular exponential](https://github.com/TheAlgorithms/Python/blob/master/maths/modular_exponential.py) * [newton raphson](https://github.com/TheAlgorithms/Python/blob/master/maths/newton_raphson.py) * [prime check](https://github.com/TheAlgorithms/Python/blob/master/maths/prime_check.py) @@ -208,6 +215,8 @@ * [volume](https://github.com/TheAlgorithms/Python/blob/master/maths/volume.py) ## Matrix * [matrix operation](https://github.com/TheAlgorithms/Python/blob/master/matrix/matrix_operation.py) + * [nth fibonacci using matrix exponentiation](https://github.com/TheAlgorithms/Python/blob/master/matrix/nth_fibonacci_using_matrix_exponentiation.py) + * [rotate matrix](https://github.com/TheAlgorithms/Python/blob/master/matrix/rotate_matrix.py) * [searching in sorted matrix](https://github.com/TheAlgorithms/Python/blob/master/matrix/searching_in_sorted_matrix.py) * [spiral print](https://github.com/TheAlgorithms/Python/blob/master/matrix/spiral_print.py) ## Networking Flow @@ -216,16 +225,17 @@ ## Neural Network * [back propagation neural network](https://github.com/TheAlgorithms/Python/blob/master/neural_network/back_propagation_neural_network.py) * [convolution neural network](https://github.com/TheAlgorithms/Python/blob/master/neural_network/convolution_neural_network.py) + * [fully connected neural network](https://github.com/TheAlgorithms/Python/blob/master/neural_network/fully_connected_neural_network.ipynb) * [perceptron](https://github.com/TheAlgorithms/Python/blob/master/neural_network/perceptron.py) ## Other * [anagrams](https://github.com/TheAlgorithms/Python/blob/master/other/anagrams.py) * [binary exponentiation](https://github.com/TheAlgorithms/Python/blob/master/other/binary_exponentiation.py) * [binary exponentiation 2](https://github.com/TheAlgorithms/Python/blob/master/other/binary_exponentiation_2.py) * [detecting english programmatically](https://github.com/TheAlgorithms/Python/blob/master/other/detecting_english_programmatically.py) - * [dictionary](https://github.com/TheAlgorithms/Python/blob/master/other/dictionary.txt) * [euclidean gcd](https://github.com/TheAlgorithms/Python/blob/master/other/euclidean_gcd.py) * [finding primes](https://github.com/TheAlgorithms/Python/blob/master/other/finding_primes.py) * [fischer yates shuffle](https://github.com/TheAlgorithms/Python/blob/master/other/fischer_yates_shuffle.py) + * [Food wastage analysis from 1961-2013 (FAO)](https://github.com/TheAlgorithms/Python/blob/master/other/Food%20wastage%20analysis%20from%201961-2013%20(FAO).ipynb) * [frequency finder](https://github.com/TheAlgorithms/Python/blob/master/other/frequency_finder.py) * [game of life](https://github.com/TheAlgorithms/Python/blob/master/other/game_of_life.py) * [linear congruential generator](https://github.com/TheAlgorithms/Python/blob/master/other/linear_congruential_generator.py) @@ -238,8 +248,6 @@ * [two sum](https://github.com/TheAlgorithms/Python/blob/master/other/two_sum.py) * [word patterns](https://github.com/TheAlgorithms/Python/blob/master/other/word_patterns.py) * [words](https://github.com/TheAlgorithms/Python/blob/master/other/words) - * Pycache - * [password generator.cpython-37](https://github.com/TheAlgorithms/Python/blob/master/other/__pycache__/password_generator.cpython-37.pyc) ## Project Euler * Problem 01 * [sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_01/sol1.py) @@ -281,16 +289,13 @@ * [sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_10/sol1.py) * [sol2](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_10/sol2.py) * Problem 11 - * [grid](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_11/grid.txt) * [sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_11/sol1.py) * [sol2](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_11/sol2.py) * Problem 12 * [sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_12/sol1.py) * [sol2](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_12/sol2.py) * Problem 13 - * [num](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_13/num.txt) * [sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_13/sol1.py) - * [sol2](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_13/sol2.py) * Problem 14 * [sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_14/sol1.py) * [sol2](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_14/sol2.py) @@ -309,7 +314,6 @@ * Problem 21 * [sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_21/sol1.py) * Problem 22 - * [p022 names](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_22/p022_names.txt) * [sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_22/sol1.py) * [sol2](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_22/sol2.py) * Problem 234 @@ -337,8 +341,6 @@ * [sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_53/sol1.py) * Problem 76 * [sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_76/sol1.py) -## Scripts - * [build directory md](https://github.com/TheAlgorithms/Python/blob/master/scripts/build_directory_md.py) ## Searches * [binary search](https://github.com/TheAlgorithms/Python/blob/master/searches/binary_search.py) * [interpolation search](https://github.com/TheAlgorithms/Python/blob/master/searches/interpolation_search.py) @@ -347,7 +349,6 @@ * [quick select](https://github.com/TheAlgorithms/Python/blob/master/searches/quick_select.py) * [sentinel linear search](https://github.com/TheAlgorithms/Python/blob/master/searches/sentinel_linear_search.py) * [tabu search](https://github.com/TheAlgorithms/Python/blob/master/searches/tabu_search.py) - * [tabu test data](https://github.com/TheAlgorithms/Python/blob/master/searches/tabu_test_data.txt) * [ternary search](https://github.com/TheAlgorithms/Python/blob/master/searches/ternary_search.py) ## Sorts * [bitonic sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/bitonic_sort.py) From 61fec83242bb00766141ffa2571414bbba68ab6e Mon Sep 17 00:00:00 2001 From: QuantumNovice <43876848+QuantumNovice@users.noreply.github.com> Date: Sat, 20 Jul 2019 17:32:40 +0500 Subject: [PATCH 0134/1071] Adds Gaussian Function in maths section (#1054) * Create gaussian.py * Update gaussian.py * Update gaussian.py * Create gaussian.png * Add files via upload * Create prime_factors.py * Update prime_factors.py * Update prime_factors.py --- maths/gaussian.py | 61 ++++++++++++++++++++++++++++++++++++++ maths/images/gaussian.png | Bin 0 -> 53511 bytes maths/prime_factors.py | 52 ++++++++++++++++++++++++++++++++ 3 files changed, 113 insertions(+) create mode 100644 maths/gaussian.py create mode 100644 maths/images/gaussian.png create mode 100644 maths/prime_factors.py diff --git a/maths/gaussian.py b/maths/gaussian.py new file mode 100644 index 000000000000..f3a47a3f6a1b --- /dev/null +++ b/maths/gaussian.py @@ -0,0 +1,61 @@ + +""" +Reference: https://en.wikipedia.org/wiki/Gaussian_function + +python/black : True +python : 3.7.3 + +""" +from numpy import pi, sqrt, exp + + + +def gaussian(x, mu: float = 0.0, sigma: float = 1.0) -> int: + """ + >>> gaussian(1) + 0.24197072451914337 + + >>> gaussian(24) + 3.342714441794458e-126 + + Supports NumPy Arrays + Use numpy.meshgrid with this to generate gaussian blur on images. + >>> import numpy as np + >>> x = np.arange(15) + >>> gaussian(x) + array([3.98942280e-01, 2.41970725e-01, 5.39909665e-02, 4.43184841e-03, + 1.33830226e-04, 1.48671951e-06, 6.07588285e-09, 9.13472041e-12, + 5.05227108e-15, 1.02797736e-18, 7.69459863e-23, 2.11881925e-27, + 2.14638374e-32, 7.99882776e-38, 1.09660656e-43]) + + >>> gaussian(15) + 5.530709549844416e-50 + + >>> gaussian([1,2, 'string']) + Traceback (most recent call last): + ... + TypeError: unsupported operand type(s) for -: 'list' and 'float' + + >>> gaussian('hello world') + Traceback (most recent call last): + ... + TypeError: unsupported operand type(s) for -: 'str' and 'float' + + >>> gaussian(10**234) # doctest: +IGNORE_EXCEPTION_DETAIL + Traceback (most recent call last): + ... + OverflowError: (34, 'Result too large') + + >>> gaussian(10**-326) + 0.3989422804014327 + + >>> gaussian(2523, mu=234234, sigma=3425) + 0.0 + """ + return 1 / sqrt(2 * pi * sigma ** 2) * exp(-(x - mu) ** 2 / 2 * sigma ** 2) + + +if __name__ == "__main__": + import doctest + + doctest.testmod() diff --git a/maths/images/gaussian.png b/maths/images/gaussian.png new file mode 100644 index 0000000000000000000000000000000000000000..eb007c7e21b2296c17a11ebf4dee30cfa2e2f6b1 GIT binary patch literal 53511 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%0AAvI%JD6*Pf82X=-HgWCAtm5l0EcOP$} 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 *fc}kRb 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 literal 0 HcmV?d00001 diff --git a/maths/prime_factors.py b/maths/prime_factors.py new file mode 100644 index 000000000000..eb3de00de6a7 --- /dev/null +++ b/maths/prime_factors.py @@ -0,0 +1,52 @@ +""" +python/black : True +""" +from typing import List + + +def prime_factors(n: int) -> List[int]: + """ + Returns prime factors of n as a list. + + >>> prime_factors(0) + [] + >>> prime_factors(100) + [2, 2, 5, 5] + >>> prime_factors(2560) + [2, 2, 2, 2, 2, 2, 2, 2, 2, 5] + >>> prime_factors(10**-2) + [] + >>> prime_factors(0.02) + [] + >>> x = prime_factors(10**241) # doctest: +NORMALIZE_WHITESPACE + >>> x == [2]*241 + [5]*241 + True + >>> prime_factors(10**-354) + [] + >>> prime_factors('hello') + Traceback (most recent call last): + ... + TypeError: '<=' not supported between instances of 'int' and 'str' + >>> prime_factors([1,2,'hello']) + Traceback (most recent call last): + ... + TypeError: '<=' not supported between instances of 'int' and 'list' + + """ + i = 2 + factors = [] + while i * i <= n: + if n % i: + i += 1 + else: + n //= i + factors.append(i) + if n > 1: + factors.append(n) + return factors + + +if __name__ == "__main__": + import doctest + + doctest.testmod() From 5b5beb61d63b97562ba0b5d161b9a2e23fb8bcf5 Mon Sep 17 00:00:00 2001 From: QuantumNovice <43876848+QuantumNovice@users.noreply.github.com> Date: Sat, 20 Jul 2019 20:33:04 +0500 Subject: [PATCH 0135/1071] Update newton_raphson_method.py (#1057) --- arithmetic_analysis/newton_raphson_method.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/arithmetic_analysis/newton_raphson_method.py b/arithmetic_analysis/newton_raphson_method.py index 5e7e2f930abc..569f96476afc 100644 --- a/arithmetic_analysis/newton_raphson_method.py +++ b/arithmetic_analysis/newton_raphson_method.py @@ -1,5 +1,5 @@ # Implementing Newton Raphson method in Python -# Author: Haseeb +# Author: Syed Haseeb Shah (github.com/QuantumNovice) from sympy import diff from decimal import Decimal @@ -30,7 +30,3 @@ def NewtonRaphson(func, a): # Exponential Roots print ('exp(x) - 1 = 0', NewtonRaphson('exp(x) - 1', 0)) - - - - From 0f0953070750f9f9813ea599e74d04a9c34e5dd2 Mon Sep 17 00:00:00 2001 From: Christian Clauss Date: Sat, 20 Jul 2019 18:31:08 +0200 Subject: [PATCH 0136/1071] dijkstra.py: Use r"strings" to fix two pylint warnings (#1052) ``` =============================== warnings summary =============================== graphs/dijkstra.py:81 /home/travis/build/TheAlgorithms/Python/graphs/dijkstra.py:81: DeprecationWarning: invalid escape sequence \ """ graphs/dijkstra.py:97 /home/travis/build/TheAlgorithms/Python/graphs/dijkstra.py:97: DeprecationWarning: invalid escape sequence \ """ -- Docs: https://docs.pytest.org/en/latest/warnings.html =================== 126 passed, 7 warnings in 19.35 seconds ==================== ``` --- graphs/dijkstra.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/graphs/dijkstra.py b/graphs/dijkstra.py index 52354b5c916b..5f09a45cf2c4 100644 --- a/graphs/dijkstra.py +++ b/graphs/dijkstra.py @@ -71,7 +71,7 @@ def dijkstra(graph, start, end): "F": [["C", 3], ["E", 3]], } -""" +r""" Layout of G2: E -- 1 --> B -- 1 --> C -- 1 --> D -- 1 --> F @@ -87,7 +87,7 @@ def dijkstra(graph, start, end): "F": [], } -""" +r""" Layout of G3: E -- 1 --> B -- 1 --> C -- 1 --> D -- 1 --> F From b35f5d971b45ed0740dc6f7a4a8b3a05f516a258 Mon Sep 17 00:00:00 2001 From: John Law Date: Sun, 21 Jul 2019 00:40:00 +0800 Subject: [PATCH 0137/1071] Update CONTRIBUTING.md (#1059) --- CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 02235ee89973..3202b817f1c5 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -69,7 +69,7 @@ We want your work to be readable by others; therefore, we encourage you to note """ This function sums two integers a and b Return: a + b - """ + """ return a + b ``` From 93fdc9f2a1d64c08e6f1ef5ebbb1470e4ee5c4e2 Mon Sep 17 00:00:00 2001 From: Christian Clauss Date: Sun, 21 Jul 2019 08:16:28 +0200 Subject: [PATCH 0138/1071] Travis CI: Add pytest --doctest-modules maths (#1020) * Travis CI: Add pytest --doctest-modules maths * Update lucas_series.py * Update lucas_series.py --- .travis.yml | 4 ---- maths/abs_min.py | 9 ++++++--- maths/binary_exponentiation.py | 17 +++++++++-------- maths/lucas_series.py | 28 ++++++++++++++++++---------- maths/sieve_of_eratosthenes.py | 6 ++---- 5 files changed, 35 insertions(+), 29 deletions(-) diff --git a/.travis.yml b/.travis.yml index a3ff22fb09b7..bea512264c19 100644 --- a/.travis.yml +++ b/.travis.yml @@ -18,10 +18,6 @@ script: --ignore=machine_learning/perceptron.py --ignore=machine_learning/random_forest_classification/random_forest_classification.py --ignore=machine_learning/random_forest_regression/random_forest_regression.py - --ignore=maths/abs_min.py - --ignore=maths/binary_exponentiation.py - --ignore=maths/lucas_series.py - --ignore=maths/sieve_of_eratosthenes.py after_success: - python scripts/build_directory_md.py - cat DIRECTORY.md diff --git a/maths/abs_min.py b/maths/abs_min.py index d546196aa1b5..abb0c9051b7d 100644 --- a/maths/abs_min.py +++ b/maths/abs_min.py @@ -1,10 +1,11 @@ -from abs import abs_val +from .abs import abs_val + def absMin(x): """ - # >>>absMin([0,5,1,11]) + >>> absMin([0,5,1,11]) 0 - # >>absMin([3,-10,-2]) + >>> absMin([3,-10,-2]) -2 """ j = x[0] @@ -13,9 +14,11 @@ def absMin(x): j = i return j + def main(): a = [-3,-1,2,-11] print(absMin(a)) # = -1 + if __name__ == '__main__': main() \ No newline at end of file diff --git a/maths/binary_exponentiation.py b/maths/binary_exponentiation.py index cf789afc6f22..a8d736adfea0 100644 --- a/maths/binary_exponentiation.py +++ b/maths/binary_exponentiation.py @@ -17,11 +17,12 @@ def binary_exponentiation(a, n): return b * b -try: - BASE = int(input('Enter Base : ')) - POWER = int(input("Enter Power : ")) -except ValueError: - print("Invalid literal for integer") - -RESULT = binary_exponentiation(BASE, POWER) -print("{}^({}) : {}".format(BASE, POWER, RESULT)) +if __name__ == "__main__": + try: + BASE = int(input("Enter Base : ").strip()) + POWER = int(input("Enter Power : ").strip()) + except ValueError: + print("Invalid literal for integer") + + RESULT = binary_exponentiation(BASE, POWER) + print("{}^({}) : {}".format(BASE, POWER, RESULT)) diff --git a/maths/lucas_series.py b/maths/lucas_series.py index 91ea1ba72a56..9ae437dc9f54 100644 --- a/maths/lucas_series.py +++ b/maths/lucas_series.py @@ -1,13 +1,21 @@ # Lucas Sequence Using Recursion def recur_luc(n): - if n == 1: - return n - if n == 0: - return 2 - return (recur_luc(n-1) + recur_luc(n-2)) - -limit = int(input("How many terms to include in Lucas series:")) -print("Lucas series:") -for i in range(limit): - print(recur_luc(i)) + """ + >>> recur_luc(1) + 1 + >>> recur_luc(0) + 2 + """ + if n == 1: + return n + if n == 0: + return 2 + return recur_luc(n - 1) + recur_luc(n - 2) + + +if __name__ == "__main__": + limit = int(input("How many terms to include in Lucas series:")) + print("Lucas series:") + for i in range(limit): + print(recur_luc(i)) diff --git a/maths/sieve_of_eratosthenes.py b/maths/sieve_of_eratosthenes.py index 11c123693694..cedd04f92aa0 100644 --- a/maths/sieve_of_eratosthenes.py +++ b/maths/sieve_of_eratosthenes.py @@ -2,9 +2,6 @@ import math -N = int(input("Enter n: ")) - - def sieve(n): """Sieve of Eratosthones.""" l = [True] * (n + 1) @@ -26,4 +23,5 @@ def sieve(n): return prime -print(sieve(N)) +if __name__ == "__main__": + print(sieve(int(input("Enter n: ").strip()))) From c964d743b6275e31548f605d5b2d583656960de0 Mon Sep 17 00:00:00 2001 From: QuantumNovice <43876848+QuantumNovice@users.noreply.github.com> Date: Sun, 21 Jul 2019 13:35:42 +0500 Subject: [PATCH 0139/1071] Added Mobius Function (#1058) * Add files via upload * Update mobius_function.py * Add files via upload * Add files via upload * Add files via upload * Add files via upload * Add files via upload * Add files via upload * Update mobius_function.py * Delete mobius_function.py * Add files via upload --- maths/is_square_free.py | 39 ++++++++++++++++++++++++++++++++++++ maths/mobius_function.py | 43 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 82 insertions(+) create mode 100644 maths/is_square_free.py create mode 100644 maths/mobius_function.py diff --git a/maths/is_square_free.py b/maths/is_square_free.py new file mode 100644 index 000000000000..acc13fa5f833 --- /dev/null +++ b/maths/is_square_free.py @@ -0,0 +1,39 @@ +""" +References: wikipedia:square free number +python/black : True +flake8 : True +""" +from typing import List + + +def is_square_free(factors: List[int]) -> bool: + """ + # doctest: +NORMALIZE_WHITESPACE + This functions takes a list of prime factors as input. + returns True if the factors are square free. + >>> is_square_free([1, 1, 2, 3, 4]) + False + + These are wrong but should return some value + it simply checks for repition in the numbers. + >>> is_square_free([1, 3, 4, 'sd', 0.0]) + True + + >>> is_square_free([1, 0.5, 2, 0.0]) + True + >>> is_square_free([1, 2, 2, 5]) + False + >>> is_square_free('asd') + True + >>> is_square_free(24) + Traceback (most recent call last): + ... + TypeError: 'int' object is not iterable + """ + return len(set(factors)) == len(factors) + + +if __name__ == "__main__": + import doctest + + doctest.testmod() diff --git a/maths/mobius_function.py b/maths/mobius_function.py new file mode 100644 index 000000000000..15fb3d4380f4 --- /dev/null +++ b/maths/mobius_function.py @@ -0,0 +1,43 @@ +""" +Refrences: https://en.wikipedia.org/wiki/M%C3%B6bius_function +References: wikipedia:square free number +python/black : True +flake8 : True +""" + +from maths.prime_factors import prime_factors +from maths.is_square_free import is_square_free + + +def mobius(n: int) -> int: + """ + Mobius function + >>> mobius(24) + 0 + >>> mobius(-1) + 1 + >>> mobius('asd') + Traceback (most recent call last): + ... + TypeError: '<=' not supported between instances of 'int' and 'str' + >>> mobius(10**400) + 0 + >>> mobius(10**-400) + 1 + >>> mobius(-1424) + 1 + >>> mobius([1, '2', 2.0]) + Traceback (most recent call last): + ... + TypeError: '<=' not supported between instances of 'int' and 'list' + """ + factors = prime_factors(n) + if is_square_free(factors): + return -1 if len(factors) % 2 else 1 + return 0 + + +if __name__ == "__main__": + import doctest + + doctest.testmod() From 05e567c2f92dc746969fe859cd806d5928067f52 Mon Sep 17 00:00:00 2001 From: QuantumNovice <43876848+QuantumNovice@users.noreply.github.com> Date: Sun, 21 Jul 2019 16:03:39 +0500 Subject: [PATCH 0140/1071] Code to change contrast (#1060) * Add files via upload * Update requirements.txt * Add files via upload * Add files via upload * Add files via upload * Add files via upload --- digital_image_processing/change_contrast.py | 35 +++++++++++++++++++++ requirements.txt | 1 + 2 files changed, 36 insertions(+) create mode 100644 digital_image_processing/change_contrast.py diff --git a/digital_image_processing/change_contrast.py b/digital_image_processing/change_contrast.py new file mode 100644 index 000000000000..76f1a3e1fcd8 --- /dev/null +++ b/digital_image_processing/change_contrast.py @@ -0,0 +1,35 @@ +""" +Changing contrast with PIL + +This algorithm is used in +https://noivce.pythonanywhere.com/ python web app. + +python/black: True +flake8 : True +""" + +from PIL import Image + + +def change_contrast(img: Image, level: float) -> Image: + """ + Function to change contrast + """ + factor = (259 * (level + 255)) / (255 * (259 - level)) + + def contrast(c: int) -> float: + """ + Fundamental Transformation/Operation that'll be performed on + every bit. + """ + return 128 + factor * (c - 128) + + return img.point(contrast) + + +if __name__ == "__main__": + # Load image + with Image.open("image_data/lena.jpg") as img: + # Change contrast to 170 + cont_img = change_contrast(img, 170) + cont_img.save("image_data/lena_high_contrast.png", format="png") diff --git a/requirements.txt b/requirements.txt index 91d3df33323d..a3e62cf968f7 100644 --- a/requirements.txt +++ b/requirements.txt @@ -5,6 +5,7 @@ mypy numpy opencv-python pandas +pillow pytest sklearn sympy From b2ed8d443c03bd9fa8cc523bcefcca4eeff04c2e Mon Sep 17 00:00:00 2001 From: Christian Clauss Date: Tue, 23 Jul 2019 00:07:09 +0200 Subject: [PATCH 0141/1071] rotate_matrix.py: Add type hints for return values (#1023) * rotate_matrix.py: Add type hints for return values @obelisk0114 Your review please? * Fix typo * Run the code thru python/black https://github.com/python/black * Fix 270 comment * Simplify with get_data() and test the alternatives * ) * 3 * Update rotate_matrix.py * Update rotate_matrix.py --- matrix/rotate_matrix.py | 115 ++++++++++++++++++++-------------------- 1 file changed, 58 insertions(+), 57 deletions(-) diff --git a/matrix/rotate_matrix.py b/matrix/rotate_matrix.py index e3495e647954..822851826121 100644 --- a/matrix/rotate_matrix.py +++ b/matrix/rotate_matrix.py @@ -1,99 +1,100 @@ # -*- coding: utf-8 -*- """ - In this problem, we want to rotate the matrix elements by 90, 180, 270 (counterclockwise) - Discussion in stackoverflow: - https://stackoverflow.com/questions/42519/how-do-you-rotate-a-two-dimensional-array +In this problem, we want to rotate the matrix elements by 90, 180, 270 (counterclockwise) +Discussion in stackoverflow: +https://stackoverflow.com/questions/42519/how-do-you-rotate-a-two-dimensional-array """ -def rotate_90(matrix: [[]]): +def make_matrix(row_size: int = 4) -> [[int]]: """ - >>> rotate_90([[1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12], [13, 14, 15, 16]]) - [[4, 8, 12, 16], [3, 7, 11, 15], [2, 6, 10, 14], [1, 5, 9, 13]] + >>> make_matrix() + [[1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12], [13, 14, 15, 16]] + >>> make_matrix(1) + [[1]] + >>> make_matrix(-2) + [[1, 2], [3, 4]] + >>> make_matrix(3) + [[1, 2, 3], [4, 5, 6], [7, 8, 9]] + >>> make_matrix() == make_matrix(4) + True """ - - transpose(matrix) - reverse_row(matrix) - return matrix - + row_size = abs(row_size) or 4 + return [[1 + x + y * row_size for x in range(row_size)] for y in range(row_size)] + -def rotate_180(matrix: [[]]): +def rotate_90(matrix: [[]]) -> [[]]: """ - >>> rotate_180([[1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12], [13, 14, 15, 16]]) - [[16, 15, 14, 13], [12, 11, 10, 9], [8, 7, 6, 5], [4, 3, 2, 1]] + >>> rotate_90(make_matrix()) + [[4, 8, 12, 16], [3, 7, 11, 15], [2, 6, 10, 14], [1, 5, 9, 13]] + >>> rotate_90(make_matrix()) == transpose(reverse_column(make_matrix())) + True """ - - reverse_column(matrix) - reverse_row(matrix) - + + return reverse_row(transpose(matrix)) + # OR.. transpose(reverse_column(matrix)) + + +def rotate_180(matrix: [[]]) -> [[]]: """ - OR - - reverse_row(matrix) - reverse_column(matrix) + >>> rotate_180(make_matrix()) + [[16, 15, 14, 13], [12, 11, 10, 9], [8, 7, 6, 5], [4, 3, 2, 1]] + >>> rotate_180(make_matrix()) == reverse_column(reverse_row(make_matrix())) + True """ - - return matrix - -def rotate_270(matrix: [[]]): + return reverse_row(reverse_column(matrix)) + # OR.. reverse_column(reverse_row(matrix)) + + +def rotate_270(matrix: [[]]) -> [[]]: """ - >>> rotate_270([[1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12], [13, 14, 15, 16]]) + >>> rotate_270(make_matrix()) [[13, 9, 5, 1], [14, 10, 6, 2], [15, 11, 7, 3], [16, 12, 8, 4]] + >>> rotate_270(make_matrix()) == transpose(reverse_row(make_matrix())) + True """ - - transpose(matrix) - reverse_column(matrix) - - """ - OR - - reverse_row(matrix) - transpose(matrix) - """ - - return matrix + return reverse_column(transpose(matrix)) + # OR.. transpose(reverse_row(matrix)) -def transpose(matrix: [[]]): + +def transpose(matrix: [[]]) -> [[]]: matrix[:] = [list(x) for x in zip(*matrix)] return matrix - - -def reverse_row(matrix: [[]]): + + +def reverse_row(matrix: [[]]) -> [[]]: matrix[:] = matrix[::-1] return matrix -def reverse_column(matrix: [[]]): +def reverse_column(matrix: [[]]) -> [[]]: matrix[:] = [x[::-1] for x in matrix] return matrix - - -def print_matrix(matrix: [[]]): + + +def print_matrix(matrix: [[]]) -> [[]]: for i in matrix: print(*i) -if __name__ == '__main__': - matrix = [[1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12], [13, 14, 15, 16]] +if __name__ == "__main__": + matrix = make_matrix() print("\norigin:\n") print_matrix(matrix) - rotate_90(matrix) print("\nrotate 90 counterclockwise:\n") - print_matrix(matrix) + print_matrix(rotate_90(matrix)) - matrix = [[1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12], [13, 14, 15, 16]] + matrix = make_matrix() print("\norigin:\n") print_matrix(matrix) - rotate_180(matrix) print("\nrotate 180:\n") - print_matrix(matrix) + print_matrix(rotate_180(matrix)) - matrix = [[1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12], [13, 14, 15, 16]] + matrix = make_matrix() print("\norigin:\n") print_matrix(matrix) - rotate_270(matrix) print("\nrotate 270 counterclockwise:\n") - print_matrix(matrix) + print_matrix(rotate_270(matrix)) From 7c3ef9885393681c83c68e9733fa2a5fd9651203 Mon Sep 17 00:00:00 2001 From: "Md. Mahbubur Rahman" Date: Wed, 24 Jul 2019 18:32:05 +0900 Subject: [PATCH 0142/1071] Implement ruling hash to appropriate complexity of Rabin Karp (#1066) * Added matrix exponentiation approach for finding fibonacci number. * Implemented the way of finding nth fibonacci. * Complexity is about O(log(n)*8) * Updated the matrix exponentiation approach of finding nth fibonacci. - Removed some extra spaces - Added the complexity of bruteforce algorithm - Removed unused function called zerro() - Added some docktest based on request * Updated the matrix exponentiation approach of finding nth fibonacci. - Removed some extra spaces - Added the complexity of bruteforce algorithm - Removed unused function called zerro() - Added some docktest based on request * Updated Rabin Karp algorithm. - Previous solution is based on the hash function of python. - Implemented ruling hash to get the appropriate complexity of rabin karp. * Updated Rabin Karp algorithm. - Previous solution is based on the hash function of python. - Implemented ruling hash to get the appropriate complexity of rabin karp. * Implemented ruling hash to appropriate complexity of Rabin Karp Added unit pattern testing --- strings/rabin_karp.py | 48 +++++++++++++++++++++++++++++++++++-------- 1 file changed, 39 insertions(+), 9 deletions(-) diff --git a/strings/rabin_karp.py b/strings/rabin_karp.py index 04a849266ead..7c36f7659e24 100644 --- a/strings/rabin_karp.py +++ b/strings/rabin_karp.py @@ -1,6 +1,11 @@ +# Numbers of alphabet which we call base +alphabet_size = 256 +# Modulus to hash a string +modulus = 1000003 + + def rabin_karp(pattern, text): """ - The Rabin-Karp Algorithm for finding a pattern within a piece of text with complexity O(nm), most efficient when it is used with multiple patterns as it is able to check if any of a set of patterns match a section of text in o(1) given the precomputed hashes. @@ -12,22 +17,42 @@ def rabin_karp(pattern, text): 2) Step through the text one character at a time passing a window with the same length as the pattern calculating the hash of the text within the window compare it with the hash of the pattern. Only testing equality if the hashes match - """ p_len = len(pattern) - p_hash = hash(pattern) + t_len = len(text) + if p_len > t_len: + return False + + p_hash = 0 + text_hash = 0 + modulus_power = 1 - for i in range(0, len(text) - (p_len - 1)): + # Calculating the hash of pattern and substring of text + for i in range(p_len): + p_hash = (ord(pattern[i]) + p_hash * alphabet_size) % modulus + text_hash = (ord(text[i]) + text_hash * alphabet_size) % modulus + if i == p_len - 1: + continue + modulus_power = (modulus_power * alphabet_size) % modulus - # written like this t - text_hash = hash(text[i:i + p_len]) - if text_hash == p_hash and \ - text[i:i + p_len] == pattern: + for i in range(0, t_len - p_len + 1): + if text_hash == p_hash and text[i : i + p_len] == pattern: return True + if i == t_len - p_len: + continue + # Calculating the ruling hash + text_hash = ( + (text_hash - ord(text[i]) * modulus_power) * alphabet_size + + ord(text[i + p_len]) + ) % modulus return False -if __name__ == '__main__': +def test_rabin_karp(): + """ + >>> test_rabin_karp() + Success. + """ # Test 1) pattern = "abc1abc12" text1 = "alskfjaldsabc1abc1abc12k23adsfabcabc" @@ -48,3 +73,8 @@ def rabin_karp(pattern, text): pattern = "abcdabcy" text = "abcxabcdabxabcdabcdabcy" assert rabin_karp(pattern, text) + print("Success.") + + +if __name__ == "__main__": + test_rabin_karp() From 46bcee0978fe507891a8e3b8892437fafed28c08 Mon Sep 17 00:00:00 2001 From: QuantumNovice <43876848+QuantumNovice@users.noreply.github.com> Date: Wed, 24 Jul 2019 17:34:22 +0500 Subject: [PATCH 0143/1071] Add badges to the top of README.md (#1064) * Update README.md * Update README.md * Update README.md * Update README.md * Update README.md * Update README.md * Update README.md * Update README.md * Update README.md * Update README.md * Update README.md * Update README.md * Update README.md * Update README.md * Update README.md * Update README.md * Update README.md * Update README.md * Update README.md * Update README.md * Update README.md * Update README.md * Update README.md * Update README.md * Update README.md * Update README.md * Update README.md * Update README.md * Update README.md * Update README.md * Update README.md * Update README.md * Update README.md * Update README.md * Update README.md * Update README.md * Update README.md * Update README.md * Update README.md * Update README.md * Update README.md * Update README.md * Update README.md * Update README.md * Update README.md * Update README.md * Update README.md * Update README.md * Update README.md --- README.md | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 30eccd361673..d4f4acbadb6d 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,11 @@ # The Algorithms - Python - -[![Donate](https://img.shields.io/badge/Donate-PayPal-green.svg)](https://www.paypal.me/TheAlgorithms/100)   -[![Gitter chat](https://badges.gitter.im/gitterHQ/gitter.png)](https://gitter.im/TheAlgorithms)   -[![Open in Gitpod](https://gitpod.io/button/open-in-gitpod.svg)](https://gitpod.io/#https://github.com/TheAlgorithms/Python) - +[![Donate](https://img.shields.io/badge/Donate-PayPal-green.svg?logo=paypal&style=flat-square)](https://www.paypal.me/TheAlgorithms/100)  +[![Build Status](https://img.shields.io/travis/TheAlgorithms/Python.svg?label=Travis%20CI&logo=travis&style=flat-square)](https://travis-ci.org/TheAlgorithms/Python)  +[![LGTM](https://img.shields.io/lgtm/alerts/github/TheAlgorithms/Python.svg?label=LGTM&logo=LGTM&style=flat-square)](https://lgtm.com/projects/g/TheAlgorithms/Python/alerts)  +[![Gitter chat](https://img.shields.io/badge/Chat-Gitter-ff69b4.svg?label=Chat&logo=gitter&style=flat-square)](https://gitter.im/TheAlgorithms)  +[![contributions welcome](https://img.shields.io/static/v1.svg?label=Contributions&message=Welcome&color=0059b3&style=flat-square)](https://github.com/TheAlgorithms/Python/blob/master/CONTRIBUTING.md)  +![](https://img.shields.io/github/repo-size/TheAlgorithms/Python.svg?label=Repo%20size&style=flat-square)  + ### All algorithms implemented in Python (for education) These implementations are for learning purposes. They may be less efficient than the implementations in the Python standard library. @@ -24,6 +26,8 @@ Chetan Kaushik Read our [Contribution Guidelines](CONTRIBUTING.md) before you contribute. +[![Open in Gitpod](https://gitpod.io/button/open-in-gitpod.svg?style=flat-square)](https://gitpod.io/#https://github.com/TheAlgorithms/Python) + ## Community Channel We're on [Gitter](https://gitter.im/TheAlgorithms)! Please join us. From 3c8e9314b6b36b3721d9df50ffd7dc20bfb2fc7d Mon Sep 17 00:00:00 2001 From: Christian Clauss Date: Thu, 25 Jul 2019 09:49:00 +0200 Subject: [PATCH 0144/1071] Travis CI: Add a flake8 test for unused imports (#1038) --- .travis.yml | 2 +- graphs/bfs.py | 21 +- maths/volume.py | 10 +- other/primelib.py | 390 +++++++++++++++---------------- project_euler/problem_13/sol1.py | 10 +- 5 files changed, 212 insertions(+), 221 deletions(-) diff --git a/.travis.yml b/.travis.yml index bea512264c19..6d432c660ddd 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,7 +6,7 @@ before_install: pip install --upgrade pip setuptools install: pip install -r requirements.txt before_script: - black --check . || true - - flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics + - flake8 . --count --select=E9,F401,F63,F7,F82 --show-source --statistics script: - mypy --ignore-missing-imports . - pytest . --doctest-modules diff --git a/graphs/bfs.py b/graphs/bfs.py index 6bbdd9e25435..ebbde0c82ce6 100644 --- a/graphs/bfs.py +++ b/graphs/bfs.py @@ -16,10 +16,19 @@ """ -import collections +G = {'A': ['B', 'C'], + 'B': ['A', 'D', 'E'], + 'C': ['A', 'F'], + 'D': ['B'], + 'E': ['B', 'F'], + 'F': ['C', 'E']} def bfs(graph, start): + """ + >>> ''.join(sorted(bfs(G, 'A'))) + 'ABCDEF' + """ explored, queue = set(), [start] # collections.deque([start]) explored.add(start) while queue: @@ -31,11 +40,5 @@ def bfs(graph, start): return explored -G = {'A': ['B', 'C'], - 'B': ['A', 'D', 'E'], - 'C': ['A', 'F'], - 'D': ['B'], - 'E': ['B', 'F'], - 'F': ['C', 'E']} - -print(bfs(G, 'A')) +if __name__ == '__main__': + print(bfs(G, 'A')) diff --git a/maths/volume.py b/maths/volume.py index 171bc538f5a4..38de7516d9b2 100644 --- a/maths/volume.py +++ b/maths/volume.py @@ -6,8 +6,6 @@ from math import pi -PI = pi - def vol_cube(side_length): """Calculate the Volume of a Cube.""" @@ -39,9 +37,7 @@ def vol_right_circ_cone(radius, height): volume = (1/3) * pi * radius^2 * height """ - import math - - return (float(1) / 3) * PI * (radius ** 2) * height + return (float(1) / 3) * pi * (radius ** 2) * height def vol_prism(area_of_base, height): @@ -71,7 +67,7 @@ def vol_sphere(radius): V = (4/3) * pi * r^3 Wikipedia reference: https://en.wikipedia.org/wiki/Sphere """ - return (float(4) / 3) * PI * radius ** 3 + return (float(4) / 3) * pi * radius ** 3 def vol_circular_cylinder(radius, height): @@ -80,7 +76,7 @@ def vol_circular_cylinder(radius, height): Wikipedia reference: https://en.wikipedia.org/wiki/Cylinder volume = pi * radius^2 * height """ - return PI * radius ** 2 * height + return pi * radius ** 2 * height def main(): diff --git a/other/primelib.py b/other/primelib.py index c371bc1b9861..c000213a7a42 100644 --- a/other/primelib.py +++ b/other/primelib.py @@ -16,7 +16,7 @@ greatestPrimeFactor(number) smallestPrimeFactor(number) getPrime(n) -getPrimesBetween(pNumber1, pNumber2) +getPrimesBetween(pNumber1, pNumber2) ---- @@ -39,34 +39,36 @@ """ +from math import sqrt + + def isPrime(number): """ input: positive integer 'number' returns true if 'number' is prime otherwise false. """ - import math # for function sqrt - + # precondition assert isinstance(number,int) and (number >= 0) , \ "'number' must been an int and positive" - + status = True - - # 0 and 1 are none primes. + + # 0 and 1 are none primes. if number <= 1: status = False - - for divisor in range(2,int(round(math.sqrt(number)))+1): - + + for divisor in range(2,int(round(sqrt(number)))+1): + # if 'number' divisible by 'divisor' then sets 'status' - # of false and break up the loop. + # of false and break up the loop. if number % divisor == 0: status = False break - + # precondition - assert isinstance(status,bool), "'status' must been from type bool" - + assert isinstance(status,bool), "'status' must been from type bool" + return status # ------------------------------------------ @@ -75,37 +77,37 @@ def sieveEr(N): """ input: positive integer 'N' > 2 returns a list of prime numbers from 2 up to N. - + This function implements the algorithm called - sieve of erathostenes. - + sieve of erathostenes. + """ - + # precondition assert isinstance(N,int) and (N > 2), "'N' must been an int and > 2" - + # beginList: conatins all natural numbers from 2 upt to N beginList = [x for x in range(2,N+1)] - ans = [] # this list will be returns. - + ans = [] # this list will be returns. + # actual sieve of erathostenes for i in range(len(beginList)): - + for j in range(i+1,len(beginList)): - + if (beginList[i] != 0) and \ (beginList[j] % beginList[i] == 0): beginList[j] = 0 - - # filters actual prime numbers. + + # filters actual prime numbers. ans = [x for x in beginList if x != 0] - + # precondition - assert isinstance(ans,list), "'ans' must been from type list" - + assert isinstance(ans,list), "'ans' must been from type list" + return ans - + # -------------------------------- @@ -114,203 +116,201 @@ def getPrimeNumbers(N): input: positive integer 'N' > 2 returns a list of prime numbers from 2 up to N (inclusive) This function is more efficient as function 'sieveEr(...)' - """ - + """ + # precondition assert isinstance(N,int) and (N > 2), "'N' must been an int and > 2" - - ans = [] - - # iterates over all numbers between 2 up to N+1 + + ans = [] + + # iterates over all numbers between 2 up to N+1 # if a number is prime then appends to list 'ans' for number in range(2,N+1): - + if isPrime(number): - + ans.append(number) - + # precondition assert isinstance(ans,list), "'ans' must been from type list" - + return ans # ----------------------------------------- - + def primeFactorization(number): """ - input: positive integer 'number' + input: positive integer 'number' returns a list of the prime number factors of 'number' """ - import math # for function sqrt - # precondition assert isinstance(number,int) and number >= 0, \ "'number' must been an int and >= 0" - + ans = [] # this list will be returns of the function. # potential prime number factors. - factor = 2 + factor = 2 quotient = number - - + + if number == 0 or number == 1: - + ans.append(number) - - # if 'number' not prime then builds the prime factorization of 'number' + + # if 'number' not prime then builds the prime factorization of 'number' elif not isPrime(number): - + while (quotient != 1): - + if isPrime(factor) and (quotient % factor == 0): ans.append(factor) quotient /= factor else: factor += 1 - + else: ans.append(number) - + # precondition - assert isinstance(ans,list), "'ans' must been from type list" - + assert isinstance(ans,list), "'ans' must been from type list" + return ans - + # ----------------------------------------- - + def greatestPrimeFactor(number): """ input: positive integer 'number' >= 0 returns the greatest prime number factor of 'number' """ - + # precondition assert isinstance(number,int) and (number >= 0), \ "'number' bust been an int and >= 0" - - ans = 0 - + + ans = 0 + # prime factorization of 'number' primeFactors = primeFactorization(number) - ans = max(primeFactors) - + ans = max(primeFactors) + # precondition - assert isinstance(ans,int), "'ans' must been from type int" - + assert isinstance(ans,int), "'ans' must been from type int" + return ans - + # ---------------------------------------------- - - + + def smallestPrimeFactor(number): """ input: integer 'number' >= 0 returns the smallest prime number factor of 'number' """ - + # precondition assert isinstance(number,int) and (number >= 0), \ "'number' bust been an int and >= 0" - - ans = 0 - + + ans = 0 + # prime factorization of 'number' primeFactors = primeFactorization(number) - + ans = min(primeFactors) # precondition - assert isinstance(ans,int), "'ans' must been from type int" - + assert isinstance(ans,int), "'ans' must been from type int" + return ans - - + + # ---------------------- - + def isEven(number): """ input: integer 'number' returns true if 'number' is even, otherwise false. - """ + """ # precondition - assert isinstance(number, int), "'number' must been an int" + assert isinstance(number, int), "'number' must been an int" assert isinstance(number % 2 == 0, bool), "compare bust been from type bool" - + return number % 2 == 0 - + # ------------------------ - + def isOdd(number): """ input: integer 'number' returns true if 'number' is odd, otherwise false. - """ + """ # precondition - assert isinstance(number, int), "'number' must been an int" + assert isinstance(number, int), "'number' must been an int" assert isinstance(number % 2 != 0, bool), "compare bust been from type bool" - + return number % 2 != 0 - + # ------------------------ - - + + def goldbach(number): """ Goldbach's assumption input: a even positive integer 'number' > 2 returns a list of two prime numbers whose sum is equal to 'number' """ - + # precondition assert isinstance(number,int) and (number > 2) and isEven(number), \ "'number' must been an int, even and > 2" - + ans = [] # this list will returned - + # creates a list of prime numbers between 2 up to 'number' primeNumbers = getPrimeNumbers(number) - lenPN = len(primeNumbers) + lenPN = len(primeNumbers) # run variable for while-loops. i = 0 j = None - + # exit variable. for break up the loops loop = True - + while (i < lenPN and loop): - + j = i+1 - - + + while (j < lenPN and loop): - + if primeNumbers[i] + primeNumbers[j] == number: loop = False ans.append(primeNumbers[i]) ans.append(primeNumbers[j]) - + j += 1 i += 1 - + # precondition assert isinstance(ans,list) and (len(ans) == 2) and \ (ans[0] + ans[1] == number) and isPrime(ans[0]) and isPrime(ans[1]), \ "'ans' must contains two primes. And sum of elements must been eq 'number'" - + return ans - + # ---------------------------------------------- def gcd(number1,number2): @@ -319,173 +319,173 @@ def gcd(number1,number2): input: two positive integer 'number1' and 'number2' returns the greatest common divisor of 'number1' and 'number2' """ - + # precondition assert isinstance(number1,int) and isinstance(number2,int) \ and (number1 >= 0) and (number2 >= 0), \ "'number1' and 'number2' must been positive integer." - rest = 0 - + rest = 0 + while number2 != 0: - + rest = number1 % number2 number1 = number2 number2 = rest # precondition assert isinstance(number1,int) and (number1 >= 0), \ - "'number' must been from type int and positive" - + "'number' must been from type int and positive" + return number1 - + # ---------------------------------------------------- - + def kgV(number1, number2): """ Least common multiple input: two positive integer 'number1' and 'number2' returns the least common multiple of 'number1' and 'number2' """ - + # precondition assert isinstance(number1,int) and isinstance(number2,int) \ and (number1 >= 1) and (number2 >= 1), \ "'number1' and 'number2' must been positive integer." - + ans = 1 # actual answer that will be return. - + # for kgV (x,1) if number1 > 1 and number2 > 1: - + # builds the prime factorization of 'number1' and 'number2' primeFac1 = primeFactorization(number1) primeFac2 = primeFactorization(number2) - + elif number1 == 1 or number2 == 1: - + primeFac1 = [] primeFac2 = [] ans = max(number1,number2) - + count1 = 0 count2 = 0 - + done = [] # captured numbers int both 'primeFac1' and 'primeFac2' - + # iterates through primeFac1 for n in primeFac1: - + if n not in done: - + if n in primeFac2: - + count1 = primeFac1.count(n) count2 = primeFac2.count(n) - + for i in range(max(count1,count2)): ans *= n - + else: - + count1 = primeFac1.count(n) - + for i in range(count1): ans *= n - + done.append(n) - + # iterates through primeFac2 for n in primeFac2: - + if n not in done: - + count2 = primeFac2.count(n) - + for i in range(count2): ans *= n - + done.append(n) - + # precondition assert isinstance(ans,int) and (ans >= 0), \ - "'ans' must been from type int and positive" - + "'ans' must been from type int and positive" + return ans - + # ---------------------------------- - + def getPrime(n): """ Gets the n-th prime number. input: positive integer 'n' >= 0 returns the n-th prime number, beginning at index 0 """ - + # precondition assert isinstance(n,int) and (n >= 0), "'number' must been a positive int" - + index = 0 ans = 2 # this variable holds the answer - + while index < n: - + index += 1 - - ans += 1 # counts to the next number - + + ans += 1 # counts to the next number + # if ans not prime then - # runs to the next prime number. + # runs to the next prime number. while not isPrime(ans): ans += 1 - + # precondition assert isinstance(ans,int) and isPrime(ans), \ - "'ans' must been a prime number and from type int" - + "'ans' must been a prime number and from type int" + return ans - + # --------------------------------------------------- - + def getPrimesBetween(pNumber1, pNumber2): """ input: prime numbers 'pNumber1' and 'pNumber2' pNumber1 < pNumber2 returns a list of all prime numbers between 'pNumber1' (exclusiv) - and 'pNumber2' (exclusiv) + and 'pNumber2' (exclusiv) """ - + # precondition assert isPrime(pNumber1) and isPrime(pNumber2) and (pNumber1 < pNumber2), \ "The arguments must been prime numbers and 'pNumber1' < 'pNumber2'" - + number = pNumber1 + 1 # jump to the next number - + ans = [] # this list will be returns. - + # if number is not prime then - # fetch the next prime number. + # fetch the next prime number. while not isPrime(number): number += 1 - + while number < pNumber2: - + ans.append(number) - + number += 1 - - # fetch the next prime number. + + # fetch the next prime number. while not isPrime(number): number += 1 - + # precondition assert isinstance(ans,list) and ans[0] != pNumber1 \ and ans[len(ans)-1] != pNumber2, \ "'ans' must been a list without the arguments" - + # 'ans' contains not 'pNumber1' and 'pNumber2' ! return ans - + # ---------------------------------------------------- def getDivisors(n): @@ -493,25 +493,23 @@ def getDivisors(n): input: positive integer 'n' >= 1 returns all divisors of n (inclusive 1 and 'n') """ - + # precondition assert isinstance(n,int) and (n >= 1), "'n' must been int and >= 1" - from math import sqrt - ans = [] # will be returned. - + for divisor in range(1,n+1): - + if n % divisor == 0: ans.append(divisor) - - + + #precondition assert ans[0] == 1 and ans[len(ans)-1] == n, \ "Error in function getDivisiors(...)" - - + + return ans @@ -523,18 +521,18 @@ def isPerfectNumber(number): input: positive integer 'number' > 1 returns true if 'number' is a perfect number otherwise false. """ - + # precondition assert isinstance(number,int) and (number > 1), \ "'number' must been an int and >= 1" - + divisors = getDivisors(number) - + # precondition assert isinstance(divisors,list) and(divisors[0] == 1) and \ (divisors[len(divisors)-1] == number), \ "Error in help-function getDivisiors(...)" - + # summed all divisors up to 'number' (exclusive), hence [:-1] return sum(divisors[:-1]) == number @@ -545,13 +543,13 @@ def simplifyFraction(numerator, denominator): input: two integer 'numerator' and 'denominator' assumes: 'denominator' != 0 returns: a tuple with simplify numerator and denominator. - """ - + """ + # precondition assert isinstance(numerator, int) and isinstance(denominator,int) \ and (denominator != 0), \ "The arguments must been from type int and 'denominator' != 0" - + # build the greatest common divisor of numerator and denominator. gcdOfFraction = gcd(abs(numerator), abs(denominator)) @@ -559,46 +557,46 @@ def simplifyFraction(numerator, denominator): assert isinstance(gcdOfFraction, int) and (numerator % gcdOfFraction == 0) \ and (denominator % gcdOfFraction == 0), \ "Error in function gcd(...,...)" - + return (numerator // gcdOfFraction, denominator // gcdOfFraction) - + # ----------------------------------------------------------------- - + def factorial(n): """ input: positive integer 'n' returns the factorial of 'n' (n!) """ - + # precondition assert isinstance(n,int) and (n >= 0), "'n' must been a int and >= 0" - + ans = 1 # this will be return. - + for factor in range(1,n+1): ans *= factor - + return ans - + # ------------------------------------------------------------------- - + def fib(n): """ input: positive integer 'n' returns the n-th fibonacci term , indexing by 0 - """ - + """ + # precondition assert isinstance(n, int) and (n >= 0), "'n' must been an int and >= 0" - + tmp = 0 fib1 = 1 ans = 1 # this will be return - + for i in range(n-1): - + tmp = ans ans += fib1 fib1 = tmp - + return ans diff --git a/project_euler/problem_13/sol1.py b/project_euler/problem_13/sol1.py index 983347675b3f..e36065ec8e11 100644 --- a/project_euler/problem_13/sol1.py +++ b/project_euler/problem_13/sol1.py @@ -3,18 +3,12 @@ Work out the first ten digits of the sum of the following one-hundred 50-digit numbers. """ -from __future__ import print_function -import os - -try: - raw_input # Python 2 -except NameError: - raw_input = input # Python 3 def solution(array): """Returns the first ten digits of the sum of the array elements. - + + >>> import os >>> sum = 0 >>> array = [] >>> with open(os.path.dirname(__file__) + "/num.txt","r") as f: From c27bd5144fd48c1a30d10f0d230fabb4fd0b3925 Mon Sep 17 00:00:00 2001 From: QuantumNovice <43876848+QuantumNovice@users.noreply.github.com> Date: Thu, 25 Jul 2019 23:38:24 +0500 Subject: [PATCH 0145/1071] in_static_equilibrium checks if a 2D static system is in equilibrium (#1062) * Add files via upload * Add files via upload * Create .a * Add files via upload * Add files via upload * Rename static_solver.py to in_static_equilibrium.py * Delete .a * Update in_static_equilibrium.py * Add files via upload * Add files via upload * Update in_static_equilibrium.py * Add files via upload * Add files via upload * Add files via upload * Add files via upload * pyTests added * Add files via upload * Delete red_black_tree.py * Add files via upload --- .../image_data/2D_problems.JPG | Bin 0 -> 58752 bytes .../image_data/2D_problems_1.JPG | Bin 0 -> 41392 bytes arithmetic_analysis/in_static_equilibrium.py | 89 ++ data_structures/binary_tree/red_black_tree.py | 1376 +++++++++-------- 4 files changed, 800 insertions(+), 665 deletions(-) create mode 100644 arithmetic_analysis/image_data/2D_problems.JPG create mode 100644 arithmetic_analysis/image_data/2D_problems_1.JPG create mode 100644 arithmetic_analysis/in_static_equilibrium.py diff --git a/arithmetic_analysis/image_data/2D_problems.JPG b/arithmetic_analysis/image_data/2D_problems.JPG new file mode 100644 index 0000000000000000000000000000000000000000..8887cf6416856496c3bb725e0e8ca3b517370cc0 GIT binary patch literal 58752 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 literal 0 HcmV?d00001 diff --git a/arithmetic_analysis/image_data/2D_problems_1.JPG b/arithmetic_analysis/image_data/2D_problems_1.JPG new file mode 100644 index 0000000000000000000000000000000000000000..aa9f4536201498e50d1d3d50f88143f700ed32b9 GIT binary patch literal 41392 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 Dx^_5` literal 0 HcmV?d00001 diff --git a/arithmetic_analysis/in_static_equilibrium.py b/arithmetic_analysis/in_static_equilibrium.py new file mode 100644 index 000000000000..48eb6135eba7 --- /dev/null +++ b/arithmetic_analysis/in_static_equilibrium.py @@ -0,0 +1,89 @@ +""" +Checks if a system of forces is in static equilibrium. + +python/black : true +flake8 : passed +mypy : passed +""" + +from numpy import array, cos, sin, radians, cross # type: ignore +from typing import List + + +def polar_force( + magnitude: float, angle: float, radian_mode: bool = False +) -> List[float]: + """ + Resolves force along rectangular components. + (force, angle) => (force_x, force_y) + >>> polar_force(10, 45) + [7.0710678118654755, 7.071067811865475] + >>> polar_force(10, 3.14, radian_mode=True) + [-9.999987317275394, 0.01592652916486828] + """ + if radian_mode: + return [magnitude * cos(angle), magnitude * sin(angle)] + return [magnitude * cos(radians(angle)), magnitude * sin(radians(angle))] + + +def in_static_equilibrium( + forces: array, location: array, eps: float = 10 ** -1 +) -> bool: + """ + Check if a system is in equilibrium. + It takes two numpy.array objects. + forces ==> [ + [force1_x, force1_y], + [force2_x, force2_y], + ....] + location ==> [ + [x1, y1], + [x2, y2], + ....] + >>> force = array([[1, 1], [-1, 2]]) + >>> location = array([[1, 0], [10, 0]]) + >>> in_static_equilibrium(force, location) + False + """ + # summation of moments is zero + moments: array = cross(location, forces) + sum_moments: float = sum(moments) + return abs(sum_moments) < eps + + +if __name__ == "__main__": + # Test to check if it works + forces = array( + [ + polar_force(718.4, 180 - 30), + polar_force(879.54, 45), + polar_force(100, -90) + ]) + + location = array([[0, 0], [0, 0], [0, 0]]) + + assert in_static_equilibrium(forces, location) + + # Problem 1 in image_data/2D_problems.jpg + forces = array( + [ + polar_force(30 * 9.81, 15), + polar_force(215, 180 - 45), + polar_force(264, 90 - 30), + ] + ) + + location = array([[0, 0], [0, 0], [0, 0]]) + + assert in_static_equilibrium(forces, location) + + # Problem in image_data/2D_problems_1.jpg + forces = array([[0, -2000], [0, -1200], [0, 15600], [0, -12400]]) + + location = array([[0, 0], [6, 0], [10, 0], [12, 0]]) + + assert in_static_equilibrium(forces, location) + + import doctest + + doctest.testmod() diff --git a/data_structures/binary_tree/red_black_tree.py b/data_structures/binary_tree/red_black_tree.py index 4ca1301dd8fe..526f5ec27987 100644 --- a/data_structures/binary_tree/red_black_tree.py +++ b/data_structures/binary_tree/red_black_tree.py @@ -1,665 +1,711 @@ -class RedBlackTree: - """ - A Red-Black tree, which is a self-balancing BST (binary search - tree). - - This tree has similar performance to AVL trees, but the balancing is - less strict, so it will perform faster for writing/deleting nodes - and slower for reading in the average case, though, because they're - both balanced binary search trees, both will get the same asymptotic - perfomance. - - To read more about them, https://en.wikipedia.org/wiki/Red–black_tree - - Unless otherwise specified, all asymptotic runtimes are specified in - terms of the size of the tree. - """ - def __init__(self, label=None, color=0, parent=None, left=None, right=None): - """Initialize a new Red-Black Tree node with the given values: - label: The value associated with this node - color: 0 if black, 1 if red - parent: The parent to this node - left: This node's left child - right: This node's right child - """ - self.label = label - self.parent = parent - self.left = left - self.right = right - self.color = color - - # Here are functions which are specific to red-black trees - - def rotate_left(self): - """Rotate the subtree rooted at this node to the left and - returns the new root to this subtree. - - Perfoming one rotation can be done in O(1). - """ - parent = self.parent - right = self.right - self.right = right.left - if self.right: - self.right.parent = self - self.parent = right - right.left = self - if parent is not None: - if parent.left is self: - parent.left = right - else: - parent.right = right - right.parent = parent - return right - - def rotate_right(self): - """Rotate the subtree rooted at this node to the right and - returns the new root to this subtree. - - Performing one rotation can be done in O(1). - """ - parent = self.parent - left = self.left - self.left = left.right - if self.left: - self.left.parent = self - self.parent = left - left.right = self - if parent is not None: - if parent.right is self: - parent.right = left - else: - parent.left = left - left.parent = parent - return left - - def insert(self, label): - """Inserts label into the subtree rooted at self, performs any - rotations necessary to maintain balance, and then returns the - new root to this subtree (likely self). - - This is guaranteed to run in O(log(n)) time. - """ - if self.label is None: - # Only possible with an empty tree - self.label = label - return self - if self.label == label: - return self - elif self.label > label: - if self.left: - self.left.insert(label) - else: - self.left = RedBlackTree(label, 1, self) - self.left._insert_repair() - else: - if self.right: - self.right.insert(label) - else: - self.right = RedBlackTree(label, 1, self) - self.right._insert_repair() - return self.parent or self - - def _insert_repair(self): - """Repair the coloring from inserting into a tree.""" - if self.parent is None: - # This node is the root, so it just needs to be black - self.color = 0 - elif color(self.parent) == 0: - # If the parent is black, then it just needs to be red - self.color = 1 - else: - uncle = self.parent.sibling - if color(uncle) == 0: - if self.is_left() and self.parent.is_right(): - self.parent.rotate_right() - self.right._insert_repair() - elif self.is_right() and self.parent.is_left(): - self.parent.rotate_left() - self.left._insert_repair() - elif self.is_left(): - self.grandparent.rotate_right() - self.parent.color = 0 - self.parent.right.color = 1 - else: - self.grandparent.rotate_left() - self.parent.color = 0 - self.parent.left.color = 1 - else: - self.parent.color = 0 - uncle.color = 0 - self.grandparent.color = 1 - self.grandparent._insert_repair() - - def remove(self, label): - """Remove label from this tree.""" - if self.label == label: - if self.left and self.right: - # It's easier to balance a node with at most one child, - # so we replace this node with the greatest one less than - # it and remove that. - value = self.left.get_max() - self.label = value - self.left.remove(value) - else: - # This node has at most one non-None child, so we don't - # need to replace - child = self.left or self.right - if self.color == 1: - # This node is red, and its child is black - # The only way this happens to a node with one child - # is if both children are None leaves. - # We can just remove this node and call it a day. - if self.is_left(): - self.parent.left = None - else: - self.parent.right = None - else: - # The node is black - if child is None: - # This node and its child are black - if self.parent is None: - # The tree is now empty - return RedBlackTree(None) - else: - self._remove_repair() - if self.is_left(): - self.parent.left = None - else: - self.parent.right = None - self.parent = None - else: - # This node is black and its child is red - # Move the child node here and make it black - self.label = child.label - self.left = child.left - self.right = child.right - if self.left: - self.left.parent = self - if self.right: - self.right.parent = self - elif self.label > label: - if self.left: - self.left.remove(label) - else: - if self.right: - self.right.remove(label) - return self.parent or self - - def _remove_repair(self): - """Repair the coloring of the tree that may have been messed up.""" - if color(self.sibling) == 1: - self.sibling.color = 0 - self.parent.color = 1 - if self.is_left(): - self.parent.rotate_left() - else: - self.parent.rotate_right() - if color(self.parent) == 0 and color(self.sibling) == 0 \ - and color(self.sibling.left) == 0 \ - and color(self.sibling.right) == 0: - self.sibling.color = 1 - self.parent._remove_repair() - return - if color(self.parent) == 1 and color(self.sibling) == 0 \ - and color(self.sibling.left) == 0 \ - and color(self.sibling.right) == 0: - self.sibling.color = 1 - self.parent.color = 0 - return - if (self.is_left() - and color(self.sibling) == 0 - and color(self.sibling.right) == 0 - and color(self.sibling.left) == 1): - self.sibling.rotate_right() - self.sibling.color = 0 - self.sibling.right.color = 1 - if (self.is_right() - and color(self.sibling) == 0 - and color(self.sibling.right) == 1 - and color(self.sibling.left) == 0): - self.sibling.rotate_left() - self.sibling.color = 0 - self.sibling.left.color = 1 - if (self.is_left() - and color(self.sibling) == 0 - and color(self.sibling.right) == 1): - self.parent.rotate_left() - self.grandparent.color = self.parent.color - self.parent.color = 0 - self.parent.sibling.color = 0 - if (self.is_right() - and color(self.sibling) == 0 - and color(self.sibling.left) == 1): - self.parent.rotate_right() - self.grandparent.color = self.parent.color - self.parent.color = 0 - self.parent.sibling.color = 0 - - def check_color_properties(self): - """Check the coloring of the tree, and return True iff the tree - is colored in a way which matches these five properties: - (wording stolen from wikipedia article) - 1. Each node is either red or black. - 2. The root node is black. - 3. All leaves are black. - 4. If a node is red, then both its children are black. - 5. Every path from any node to all of its descendent NIL nodes - has the same number of black nodes. - - This function runs in O(n) time, because properties 4 and 5 take - that long to check. - """ - # I assume property 1 to hold because there is nothing that can - # make the color be anything other than 0 or 1. - - # Property 2 - if self.color: - # The root was red - print('Property 2') - return False; - - # Property 3 does not need to be checked, because None is assumed - # to be black and is all the leaves. - - # Property 4 - if not self.check_coloring(): - print('Property 4') - return False - - # Property 5 - if self.black_height() is None: - print('Property 5') - return False - # All properties were met - return True - - def check_coloring(self): - """A helper function to recursively check Property 4 of a - Red-Black Tree. See check_color_properties for more info. - """ - if self.color == 1: - if color(self.left) == 1 or color(self.right) == 1: - return False - if self.left and not self.left.check_coloring(): - return False - if self.right and not self.right.check_coloring(): - return False - return True - - def black_height(self): - """Returns the number of black nodes from this node to the - leaves of the tree, or None if there isn't one such value (the - tree is color incorrectly). - """ - if self is None: - # If we're already at a leaf, there is no path - return 1 - left = RedBlackTree.black_height(self.left) - right = RedBlackTree.black_height(self.right) - if left is None or right is None: - # There are issues with coloring below children nodes - return None - if left != right: - # The two children have unequal depths - return None - # Return the black depth of children, plus one if this node is - # black - return left + (1-self.color) - - # Here are functions which are general to all binary search trees - - def __contains__(self, label): - """Search through the tree for label, returning True iff it is - found somewhere in the tree. - - Guaranteed to run in O(log(n)) time. - """ - return self.search(label) is not None - - def search(self, label): - """Search through the tree for label, returning its node if - it's found, and None otherwise. - - This method is guaranteed to run in O(log(n)) time. - """ - if self.label == label: - return self - elif label > self.label: - if self.right is None: - return None - else: - return self.right.search(label) - else: - if self.left is None: - return None - else: - return self.left.search(label) - - def floor(self, label): - """Returns the largest element in this tree which is at most label. - - This method is guaranteed to run in O(log(n)) time.""" - if self.label == label: - return self.label - elif self.label > label: - if self.left: - return self.left.floor(label) - else: - return None - else: - if self.right: - attempt = self.right.floor(label) - if attempt is not None: - return attempt - return self.label - - def ceil(self, label): - """Returns the smallest element in this tree which is at least label. - - This method is guaranteed to run in O(log(n)) time. - """ - if self.label == label: - return self.label - elif self.label < label: - if self.right: - return self.right.ceil(label) - else: - return None - else: - if self.left: - attempt = self.left.ceil(label) - if attempt is not None: - return attempt - return self.label - - def get_max(self): - """Returns the largest element in this tree. - - This method is guaranteed to run in O(log(n)) time. - """ - if self.right: - # Go as far right as possible - return self.right.get_max() - else: - return self.label - - def get_min(self): - """Returns the smallest element in this tree. - - This method is guaranteed to run in O(log(n)) time. - """ - if self.left: - # Go as far left as possible - return self.left.get_min() - else: - return self.label - - @property - def grandparent(self): - """Get the current node's grandparent, or None if it doesn't exist.""" - if self.parent is None: - return None - else: - return self.parent.parent - - @property - def sibling(self): - """Get the current node's sibling, or None if it doesn't exist.""" - if self.parent is None: - return None - elif self.parent.left is self: - return self.parent.right - else: - return self.parent.left - - def is_left(self): - """Returns true iff this node is the left child of its parent.""" - return self.parent and self.parent.left is self - - def is_right(self): - """Returns true iff this node is the right child of its parent.""" - return self.parent and self.parent.right is self - - def __bool__(self): - return True - - def __len__(self): - """ - Return the number of nodes in this tree. - """ - ln = 1 - if self.left: - ln += len(self.left) - if self.right: - ln += len(self.right) - return ln - - def preorder_traverse(self): - yield self.label - if self.left: - yield from self.left.preorder_traverse() - if self.right: - yield from self.right.preorder_traverse() - - def inorder_traverse(self): - if self.left: - yield from self.left.inorder_traverse() - yield self.label - if self.right: - yield from self.right.inorder_traverse() - - - def postorder_traverse(self): - if self.left: - yield from self.left.postorder_traverse() - if self.right: - yield from self.right.postorder_traverse() - yield self.label - - def __repr__(self): - from pprint import pformat - if self.left is None and self.right is None: - return "'%s %s'" % (self.label, (self.color and 'red') or 'blk') - return pformat({'%s %s' % (self.label, (self.color and 'red') or 'blk'): - (self.left, self.right)}, - indent=1) - - def __eq__(self, other): - """Test if two trees are equal.""" - if self.label == other.label: - return self.left == other.left and self.right == other.right - else: - return False - -def color(node): - """Returns the color of a node, allowing for None leaves.""" - if node is None: - return 0 - else: - return node.color - -""" -Code for testing the various functions of the red-black tree. -""" - -def test_rotations(): - """Test that the rotate_left and rotate_right functions work.""" - # Make a tree to test on - tree = RedBlackTree(0) - tree.left = RedBlackTree(-10, parent=tree) - tree.right = RedBlackTree(10, parent=tree) - tree.left.left = RedBlackTree(-20, parent=tree.left) - tree.left.right = RedBlackTree(-5, parent=tree.left) - tree.right.left = RedBlackTree(5, parent=tree.right) - tree.right.right = RedBlackTree(20, parent=tree.right) - # Make the right rotation - left_rot = RedBlackTree(10) - left_rot.left = RedBlackTree(0, parent=left_rot) - left_rot.left.left = RedBlackTree(-10, parent=left_rot.left) - left_rot.left.right = RedBlackTree(5, parent=left_rot.left) - left_rot.left.left.left = RedBlackTree(-20, parent=left_rot.left.left) - left_rot.left.left.right = RedBlackTree(-5, parent=left_rot.left.left) - left_rot.right = RedBlackTree(20, parent=left_rot) - tree = tree.rotate_left() - if tree != left_rot: - return False - tree = tree.rotate_right() - tree = tree.rotate_right() - # Make the left rotation - right_rot = RedBlackTree(-10) - right_rot.left = RedBlackTree(-20, parent=right_rot) - right_rot.right = RedBlackTree(0, parent=right_rot) - right_rot.right.left = RedBlackTree(-5, parent=right_rot.right) - right_rot.right.right = RedBlackTree(10, parent=right_rot.right) - right_rot.right.right.left = RedBlackTree(5, parent=right_rot.right.right) - right_rot.right.right.right = RedBlackTree(20, parent=right_rot.right.right) - if tree != right_rot: - return False - return True - -def test_insertion_speed(): - """Test that the tree balances inserts to O(log(n)) by doing a lot - of them. - """ - tree = RedBlackTree(-1) - for i in range(300000): - tree = tree.insert(i) - return True - -def test_insert(): - """Test the insert() method of the tree correctly balances, colors, - and inserts. - """ - tree = RedBlackTree(0) - tree.insert(8) - tree.insert(-8) - tree.insert(4) - tree.insert(12) - tree.insert(10) - tree.insert(11) - ans = RedBlackTree(0, 0) - ans.left = RedBlackTree(-8, 0, ans) - ans.right = RedBlackTree(8, 1, ans) - ans.right.left = RedBlackTree(4, 0, ans.right) - ans.right.right = RedBlackTree(11, 0, ans.right) - ans.right.right.left = RedBlackTree(10, 1, ans.right.right) - ans.right.right.right = RedBlackTree(12, 1, ans.right.right) - return tree == ans - -def test_insert_and_search(): - """Tests searching through the tree for values.""" - tree = RedBlackTree(0) - tree.insert(8) - tree.insert(-8) - tree.insert(4) - tree.insert(12) - tree.insert(10) - tree.insert(11) - if 5 in tree or -6 in tree or -10 in tree or 13 in tree: - # Found something not in there - return False - if not (11 in tree and 12 in tree and -8 in tree and 0 in tree): - # Didn't find something in there - return False - return True - -def test_insert_delete(): - """Test the insert() and delete() method of the tree, verifying the - insertion and removal of elements, and the balancing of the tree. - """ - tree = RedBlackTree(0) - tree = tree.insert(-12) - tree = tree.insert(8) - tree = tree.insert(-8) - tree = tree.insert(15) - tree = tree.insert(4) - tree = tree.insert(12) - tree = tree.insert(10) - tree = tree.insert(9) - tree = tree.insert(11) - tree = tree.remove(15) - tree = tree.remove(-12) - tree = tree.remove(9) - if not tree.check_color_properties(): - return False - if list(tree.inorder_traverse()) != [-8, 0, 4, 8, 10, 11, 12]: - return False - return True - -def test_floor_ceil(): - """Tests the floor and ceiling functions in the tree.""" - tree = RedBlackTree(0) - tree.insert(-16) - tree.insert(16) - tree.insert(8) - tree.insert(24) - tree.insert(20) - tree.insert(22) - tuples = [(-20, None, -16), (-10, -16, 0), (8, 8, 8), (50, 24, None)] - for val, floor, ceil in tuples: - if tree.floor(val) != floor or tree.ceil(val) != ceil: - return False - return True - -def test_min_max(): - """Tests the min and max functions in the tree.""" - tree = RedBlackTree(0) - tree.insert(-16) - tree.insert(16) - tree.insert(8) - tree.insert(24) - tree.insert(20) - tree.insert(22) - if tree.get_max() != 22 or tree.get_min() != -16: - return False - return True - -def test_tree_traversal(): - """Tests the three different tree traversal functions.""" - tree = RedBlackTree(0) - tree.insert(-16) - tree.insert(16) - tree.insert(8) - tree.insert(24) - tree.insert(20) - tree.insert(22) - if list(tree.inorder_traverse()) != [-16, 0, 8, 16, 20, 22, 24]: - return False - if list(tree.preorder_traverse()) != [0, -16, 16, 8, 22, 20, 24]: - return False - if list(tree.postorder_traverse()) != [-16, 8, 20, 24, 22, 16, 0]: - return False - return True - -def main(): - if test_rotations(): - print('Rotating right and left works!') - else: - print('Rotating right and left doesn\'t work. :(') - if test_insert(): - print('Inserting works!') - else: - print('Inserting doesn\'t work :(') - if test_insert_and_search(): - print('Searching works!') - else: - print('Searching doesn\'t work :(') - if test_insert_delete(): - print('Deleting works!') - else: - print('Deleting doesn\'t work :(') - if test_floor_ceil(): - print('Floor and ceil work!') - else: - print('Floor and ceil don\'t work :(') - if test_tree_traversal(): - print('Tree traversal works!') - else: - print('Tree traversal doesn\'t work :(') - print('Testing tree balancing...') - print('This should only be a few seconds.') - test_insertion_speed() - print('Done!') - -if __name__ == '__main__': - main() +""" +python/black : true +flake8 : passed +""" + + +class RedBlackTree: + """ + A Red-Black tree, which is a self-balancing BST (binary search + tree). + This tree has similar performance to AVL trees, but the balancing is + less strict, so it will perform faster for writing/deleting nodes + and slower for reading in the average case, though, because they're + both balanced binary search trees, both will get the same asymptotic + perfomance. + To read more about them, https://en.wikipedia.org/wiki/Red–black_tree + Unless otherwise specified, all asymptotic runtimes are specified in + terms of the size of the tree. + """ + + def __init__(self, label=None, color=0, parent=None, left=None, right=None): + """Initialize a new Red-Black Tree node with the given values: + label: The value associated with this node + color: 0 if black, 1 if red + parent: The parent to this node + left: This node's left child + right: This node's right child + """ + self.label = label + self.parent = parent + self.left = left + self.right = right + self.color = color + + # Here are functions which are specific to red-black trees + + def rotate_left(self): + """Rotate the subtree rooted at this node to the left and + returns the new root to this subtree. + Perfoming one rotation can be done in O(1). + """ + parent = self.parent + right = self.right + self.right = right.left + if self.right: + self.right.parent = self + self.parent = right + right.left = self + if parent is not None: + if parent.left == self: + parent.left = right + else: + parent.right = right + right.parent = parent + return right + + def rotate_right(self): + """Rotate the subtree rooted at this node to the right and + returns the new root to this subtree. + Performing one rotation can be done in O(1). + """ + parent = self.parent + left = self.left + self.left = left.right + if self.left: + self.left.parent = self + self.parent = left + left.right = self + if parent is not None: + if parent.right is self: + parent.right = left + else: + parent.left = left + left.parent = parent + return left + + def insert(self, label): + """Inserts label into the subtree rooted at self, performs any + rotations necessary to maintain balance, and then returns the + new root to this subtree (likely self). + This is guaranteed to run in O(log(n)) time. + """ + if self.label is None: + # Only possible with an empty tree + self.label = label + return self + if self.label == label: + return self + elif self.label > label: + if self.left: + self.left.insert(label) + else: + self.left = RedBlackTree(label, 1, self) + self.left._insert_repair() + else: + if self.right: + self.right.insert(label) + else: + self.right = RedBlackTree(label, 1, self) + self.right._insert_repair() + return self.parent or self + + def _insert_repair(self): + """Repair the coloring from inserting into a tree.""" + if self.parent is None: + # This node is the root, so it just needs to be black + self.color = 0 + elif color(self.parent) == 0: + # If the parent is black, then it just needs to be red + self.color = 1 + else: + uncle = self.parent.sibling + if color(uncle) == 0: + if self.is_left() and self.parent.is_right(): + self.parent.rotate_right() + self.right._insert_repair() + elif self.is_right() and self.parent.is_left(): + self.parent.rotate_left() + self.left._insert_repair() + elif self.is_left(): + self.grandparent.rotate_right() + self.parent.color = 0 + self.parent.right.color = 1 + else: + self.grandparent.rotate_left() + self.parent.color = 0 + self.parent.left.color = 1 + else: + self.parent.color = 0 + uncle.color = 0 + self.grandparent.color = 1 + self.grandparent._insert_repair() + + def remove(self, label): + """Remove label from this tree.""" + if self.label == label: + if self.left and self.right: + # It's easier to balance a node with at most one child, + # so we replace this node with the greatest one less than + # it and remove that. + value = self.left.get_max() + self.label = value + self.left.remove(value) + else: + # This node has at most one non-None child, so we don't + # need to replace + child = self.left or self.right + if self.color == 1: + # This node is red, and its child is black + # The only way this happens to a node with one child + # is if both children are None leaves. + # We can just remove this node and call it a day. + if self.is_left(): + self.parent.left = None + else: + self.parent.right = None + else: + # The node is black + if child is None: + # This node and its child are black + if self.parent is None: + # The tree is now empty + return RedBlackTree(None) + else: + self._remove_repair() + if self.is_left(): + self.parent.left = None + else: + self.parent.right = None + self.parent = None + else: + # This node is black and its child is red + # Move the child node here and make it black + self.label = child.label + self.left = child.left + self.right = child.right + if self.left: + self.left.parent = self + if self.right: + self.right.parent = self + elif self.label > label: + if self.left: + self.left.remove(label) + else: + if self.right: + self.right.remove(label) + return self.parent or self + + def _remove_repair(self): + """Repair the coloring of the tree that may have been messed up.""" + if color(self.sibling) == 1: + self.sibling.color = 0 + self.parent.color = 1 + if self.is_left(): + self.parent.rotate_left() + else: + self.parent.rotate_right() + if ( + color(self.parent) == 0 + and color(self.sibling) == 0 + and color(self.sibling.left) == 0 + and color(self.sibling.right) == 0 + ): + self.sibling.color = 1 + self.parent._remove_repair() + return + if ( + color(self.parent) == 1 + and color(self.sibling) == 0 + and color(self.sibling.left) == 0 + and color(self.sibling.right) == 0 + ): + self.sibling.color = 1 + self.parent.color = 0 + return + if ( + self.is_left() + and color(self.sibling) == 0 + and color(self.sibling.right) == 0 + and color(self.sibling.left) == 1 + ): + self.sibling.rotate_right() + self.sibling.color = 0 + self.sibling.right.color = 1 + if ( + self.is_right() + and color(self.sibling) == 0 + and color(self.sibling.right) == 1 + and color(self.sibling.left) == 0 + ): + self.sibling.rotate_left() + self.sibling.color = 0 + self.sibling.left.color = 1 + if ( + self.is_left() + and color(self.sibling) == 0 + and color(self.sibling.right) == 1 + ): + self.parent.rotate_left() + self.grandparent.color = self.parent.color + self.parent.color = 0 + self.parent.sibling.color = 0 + if ( + self.is_right() + and color(self.sibling) == 0 + and color(self.sibling.left) == 1 + ): + self.parent.rotate_right() + self.grandparent.color = self.parent.color + self.parent.color = 0 + self.parent.sibling.color = 0 + + def check_color_properties(self): + """Check the coloring of the tree, and return True iff the tree + is colored in a way which matches these five properties: + (wording stolen from wikipedia article) + 1. Each node is either red or black. + 2. The root node is black. + 3. All leaves are black. + 4. If a node is red, then both its children are black. + 5. Every path from any node to all of its descendent NIL nodes + has the same number of black nodes. + This function runs in O(n) time, because properties 4 and 5 take + that long to check. + """ + # I assume property 1 to hold because there is nothing that can + # make the color be anything other than 0 or 1. + + # Property 2 + if self.color: + # The root was red + print("Property 2") + return False + + # Property 3 does not need to be checked, because None is assumed + # to be black and is all the leaves. + + # Property 4 + if not self.check_coloring(): + print("Property 4") + return False + + # Property 5 + if self.black_height() is None: + print("Property 5") + return False + # All properties were met + return True + + def check_coloring(self): + """A helper function to recursively check Property 4 of a + Red-Black Tree. See check_color_properties for more info. + """ + if self.color == 1: + if color(self.left) == 1 or color(self.right) == 1: + return False + if self.left and not self.left.check_coloring(): + return False + if self.right and not self.right.check_coloring(): + return False + return True + + def black_height(self): + """Returns the number of black nodes from this node to the + leaves of the tree, or None if there isn't one such value (the + tree is color incorrectly). + """ + if self is None: + # If we're already at a leaf, there is no path + return 1 + left = RedBlackTree.black_height(self.left) + right = RedBlackTree.black_height(self.right) + if left is None or right is None: + # There are issues with coloring below children nodes + return None + if left != right: + # The two children have unequal depths + return None + # Return the black depth of children, plus one if this node is + # black + return left + (1 - self.color) + + # Here are functions which are general to all binary search trees + + def __contains__(self, label): + """Search through the tree for label, returning True iff it is + found somewhere in the tree. + Guaranteed to run in O(log(n)) time. + """ + return self.search(label) is not None + + def search(self, label): + """Search through the tree for label, returning its node if + it's found, and None otherwise. + This method is guaranteed to run in O(log(n)) time. + """ + if self.label == label: + return self + elif label > self.label: + if self.right is None: + return None + else: + return self.right.search(label) + else: + if self.left is None: + return None + else: + return self.left.search(label) + + def floor(self, label): + """Returns the largest element in this tree which is at most label. + This method is guaranteed to run in O(log(n)) time.""" + if self.label == label: + return self.label + elif self.label > label: + if self.left: + return self.left.floor(label) + else: + return None + else: + if self.right: + attempt = self.right.floor(label) + if attempt is not None: + return attempt + return self.label + + def ceil(self, label): + """Returns the smallest element in this tree which is at least label. + This method is guaranteed to run in O(log(n)) time. + """ + if self.label == label: + return self.label + elif self.label < label: + if self.right: + return self.right.ceil(label) + else: + return None + else: + if self.left: + attempt = self.left.ceil(label) + if attempt is not None: + return attempt + return self.label + + def get_max(self): + """Returns the largest element in this tree. + This method is guaranteed to run in O(log(n)) time. + """ + if self.right: + # Go as far right as possible + return self.right.get_max() + else: + return self.label + + def get_min(self): + """Returns the smallest element in this tree. + This method is guaranteed to run in O(log(n)) time. + """ + if self.left: + # Go as far left as possible + return self.left.get_min() + else: + return self.label + + @property + def grandparent(self): + """Get the current node's grandparent, or None if it doesn't exist.""" + if self.parent is None: + return None + else: + return self.parent.parent + + @property + def sibling(self): + """Get the current node's sibling, or None if it doesn't exist.""" + if self.parent is None: + return None + elif self.parent.left is self: + return self.parent.right + else: + return self.parent.left + + def is_left(self): + """Returns true iff this node is the left child of its parent.""" + return self.parent and self.parent.left is self + + def is_right(self): + """Returns true iff this node is the right child of its parent.""" + return self.parent and self.parent.right is self + + def __bool__(self): + return True + + def __len__(self): + """ + Return the number of nodes in this tree. + """ + ln = 1 + if self.left: + ln += len(self.left) + if self.right: + ln += len(self.right) + return ln + + def preorder_traverse(self): + yield self.label + if self.left: + yield from self.left.preorder_traverse() + if self.right: + yield from self.right.preorder_traverse() + + def inorder_traverse(self): + if self.left: + yield from self.left.inorder_traverse() + yield self.label + if self.right: + yield from self.right.inorder_traverse() + + def postorder_traverse(self): + if self.left: + yield from self.left.postorder_traverse() + if self.right: + yield from self.right.postorder_traverse() + yield self.label + + def __repr__(self): + from pprint import pformat + + if self.left is None and self.right is None: + return "'%s %s'" % (self.label, (self.color and "red") or "blk") + return pformat( + { + "%s %s" + % (self.label, (self.color and "red") or "blk"): (self.left, self.right) + }, + indent=1, + ) + + def __eq__(self, other): + """Test if two trees are equal.""" + if self.label == other.label: + return self.left == other.left and self.right == other.right + else: + return False + + +def color(node): + """Returns the color of a node, allowing for None leaves.""" + if node is None: + return 0 + else: + return node.color + + +""" +Code for testing the various +functions of the red-black tree. +""" + + +def test_rotations(): + """Test that the rotate_left and rotate_right functions work.""" + # Make a tree to test on + tree = RedBlackTree(0) + tree.left = RedBlackTree(-10, parent=tree) + tree.right = RedBlackTree(10, parent=tree) + tree.left.left = RedBlackTree(-20, parent=tree.left) + tree.left.right = RedBlackTree(-5, parent=tree.left) + tree.right.left = RedBlackTree(5, parent=tree.right) + tree.right.right = RedBlackTree(20, parent=tree.right) + # Make the right rotation + left_rot = RedBlackTree(10) + left_rot.left = RedBlackTree(0, parent=left_rot) + left_rot.left.left = RedBlackTree(-10, parent=left_rot.left) + left_rot.left.right = RedBlackTree(5, parent=left_rot.left) + left_rot.left.left.left = RedBlackTree(-20, parent=left_rot.left.left) + left_rot.left.left.right = RedBlackTree(-5, parent=left_rot.left.left) + left_rot.right = RedBlackTree(20, parent=left_rot) + tree = tree.rotate_left() + if tree != left_rot: + return False + tree = tree.rotate_right() + tree = tree.rotate_right() + # Make the left rotation + right_rot = RedBlackTree(-10) + right_rot.left = RedBlackTree(-20, parent=right_rot) + right_rot.right = RedBlackTree(0, parent=right_rot) + right_rot.right.left = RedBlackTree(-5, parent=right_rot.right) + right_rot.right.right = RedBlackTree(10, parent=right_rot.right) + right_rot.right.right.left = RedBlackTree(5, parent=right_rot.right.right) + right_rot.right.right.right = RedBlackTree(20, parent=right_rot.right.right) + if tree != right_rot: + return False + return True + + +def test_insertion_speed(): + """Test that the tree balances inserts to O(log(n)) by doing a lot + of them. + """ + tree = RedBlackTree(-1) + for i in range(300000): + tree = tree.insert(i) + return True + + +def test_insert(): + """Test the insert() method of the tree correctly balances, colors, + and inserts. + """ + tree = RedBlackTree(0) + tree.insert(8) + tree.insert(-8) + tree.insert(4) + tree.insert(12) + tree.insert(10) + tree.insert(11) + ans = RedBlackTree(0, 0) + ans.left = RedBlackTree(-8, 0, ans) + ans.right = RedBlackTree(8, 1, ans) + ans.right.left = RedBlackTree(4, 0, ans.right) + ans.right.right = RedBlackTree(11, 0, ans.right) + ans.right.right.left = RedBlackTree(10, 1, ans.right.right) + ans.right.right.right = RedBlackTree(12, 1, ans.right.right) + return tree == ans + + +def test_insert_and_search(): + """Tests searching through the tree for values.""" + tree = RedBlackTree(0) + tree.insert(8) + tree.insert(-8) + tree.insert(4) + tree.insert(12) + tree.insert(10) + tree.insert(11) + if 5 in tree or -6 in tree or -10 in tree or 13 in tree: + # Found something not in there + return False + if not (11 in tree and 12 in tree and -8 in tree and 0 in tree): + # Didn't find something in there + return False + return True + + +def test_insert_delete(): + """Test the insert() and delete() method of the tree, verifying the + insertion and removal of elements, and the balancing of the tree. + """ + tree = RedBlackTree(0) + tree = tree.insert(-12) + tree = tree.insert(8) + tree = tree.insert(-8) + tree = tree.insert(15) + tree = tree.insert(4) + tree = tree.insert(12) + tree = tree.insert(10) + tree = tree.insert(9) + tree = tree.insert(11) + tree = tree.remove(15) + tree = tree.remove(-12) + tree = tree.remove(9) + if not tree.check_color_properties(): + return False + if list(tree.inorder_traverse()) != [-8, 0, 4, 8, 10, 11, 12]: + return False + return True + + +def test_floor_ceil(): + """Tests the floor and ceiling functions in the tree.""" + tree = RedBlackTree(0) + tree.insert(-16) + tree.insert(16) + tree.insert(8) + tree.insert(24) + tree.insert(20) + tree.insert(22) + tuples = [(-20, None, -16), (-10, -16, 0), (8, 8, 8), (50, 24, None)] + for val, floor, ceil in tuples: + if tree.floor(val) != floor or tree.ceil(val) != ceil: + return False + return True + + +def test_min_max(): + """Tests the min and max functions in the tree.""" + tree = RedBlackTree(0) + tree.insert(-16) + tree.insert(16) + tree.insert(8) + tree.insert(24) + tree.insert(20) + tree.insert(22) + if tree.get_max() != 22 or tree.get_min() != -16: + return False + return True + + +def test_tree_traversal(): + """Tests the three different tree traversal functions.""" + tree = RedBlackTree(0) + tree = tree.insert(-16) + tree.insert(16) + tree.insert(8) + tree.insert(24) + tree.insert(20) + tree.insert(22) + if list(tree.inorder_traverse()) != [-16, 0, 8, 16, 20, 22, 24]: + return False + if list(tree.preorder_traverse()) != [0, -16, 16, 8, 22, 20, 24]: + return False + if list(tree.postorder_traverse()) != [-16, 8, 20, 24, 22, 16, 0]: + return False + return True + + +def test_tree_chaining(): + """Tests the three different tree chaning functions.""" + tree = RedBlackTree(0) + tree = tree.insert(-16).insert(16).insert(8).insert(24).insert(20).insert(22) + if list(tree.inorder_traverse()) != [-16, 0, 8, 16, 20, 22, 24]: + return False + if list(tree.preorder_traverse()) != [0, -16, 16, 8, 22, 20, 24]: + return False + if list(tree.postorder_traverse()) != [-16, 8, 20, 24, 22, 16, 0]: + return False + return True + + +def print_results(msg: str, passes: bool) -> None: + print(str(msg), "works!" if passes else "doesn't work :(") + + +def pytests(): + assert test_rotations() + assert test_insert() + assert test_insert_and_search() + assert test_insert_delete() + assert test_floor_ceil() + assert test_tree_traversal() + assert test_tree_chaining() + + +def main(): + """ + >>> pytests() + """ + print_results("Rotating right and left", test_rotations()) + + print_results("Inserting", test_insert()) + + print_results("Searching", test_insert_and_search()) + + print_results("Deleting", test_insert_delete()) + + print_results("Floor and ceil", test_floor_ceil()) + + print_results("Tree traversal", test_tree_traversal()) + + print_results("Tree traversal", test_tree_chaining()) + + + print("Testing tree balancing...") + print("This should only be a few seconds.") + test_insertion_speed() + print("Done!") + + +if __name__ == "__main__": + main() From 46bc6738d78de3a05901881c919e8d91a28b8ef4 Mon Sep 17 00:00:00 2001 From: obelisk0114 Date: Fri, 26 Jul 2019 03:25:38 -0700 Subject: [PATCH 0146/1071] Add doctest to maths/sieve_of_eratosthenes.py and remove other/finding_primes.py (#1078) Both of the two files implemented sieve of eratosthenes. However, there was a bug in other/finding_primes.py, and the time complexity was larger than the other. Therefore, remove other/finding_primes.py and add doctest tomaths/sieve_of_eratosthenes.py. --- maths/sieve_of_eratosthenes.py | 38 ++++++++++++++++++++++++++++++++-- other/finding_primes.py | 21 ------------------- 2 files changed, 36 insertions(+), 23 deletions(-) delete mode 100644 other/finding_primes.py diff --git a/maths/sieve_of_eratosthenes.py b/maths/sieve_of_eratosthenes.py index cedd04f92aa0..44c7f8a02682 100644 --- a/maths/sieve_of_eratosthenes.py +++ b/maths/sieve_of_eratosthenes.py @@ -1,19 +1,53 @@ -"""Sieve of Eratosthones.""" +# -*- coding: utf-8 -*- + +""" +Sieve of Eratosthones + +The sieve of Eratosthenes is an algorithm used to find prime numbers, less than or equal to a given value. +Illustration: https://upload.wikimedia.org/wikipedia/commons/b/b9/Sieve_of_Eratosthenes_animation.gif +Reference: https://en.wikipedia.org/wiki/Sieve_of_Eratosthenes + +doctest provider: Bruno Simas Hadlich (https://github.com/brunohadlich) +Also thanks Dmitry (https://github.com/LizardWizzard) for finding the problem +""" + import math + def sieve(n): - """Sieve of Eratosthones.""" + """ + Returns a list with all prime numbers up to n. + + >>> sieve(50) + [2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47] + >>> sieve(25) + [2, 3, 5, 7, 11, 13, 17, 19, 23] + >>> sieve(10) + [2, 3, 5, 7] + >>> sieve(9) + [2, 3, 5, 7] + >>> sieve(2) + [2] + >>> sieve(1) + [] + """ + l = [True] * (n + 1) prime = [] start = 2 end = int(math.sqrt(n)) + while start <= end: + # If start is a prime if l[start] is True: prime.append(start) + + # Set multiples of start be False for i in range(start * start, n + 1, start): if l[i] is True: l[i] = False + start += 1 for j in range(end + 1, n + 1): diff --git a/other/finding_primes.py b/other/finding_primes.py deleted file mode 100644 index 035a14f4a335..000000000000 --- a/other/finding_primes.py +++ /dev/null @@ -1,21 +0,0 @@ -''' --The sieve of Eratosthenes is an algorithm used to find prime numbers, less than or equal to a given value. --Illustration: https://upload.wikimedia.org/wikipedia/commons/b/b9/Sieve_of_Eratosthenes_animation.gif -''' -from __future__ import print_function - - -from math import sqrt -def SOE(n): - check = round(sqrt(n)) #Need not check for multiples past the square root of n - - sieve = [False if i <2 else True for i in range(n+1)] #Set every index to False except for index 0 and 1 - - for i in range(2, check): - if(sieve[i] == True): #If i is a prime - for j in range(i+i, n+1, i): #Step through the list in increments of i(the multiples of the prime) - sieve[j] = False #Sets every multiple of i to False - - for i in range(n+1): - if(sieve[i] == True): - print(i, end=" ") From 3b63857b657ef9e552368d66fe536a0454c6307f Mon Sep 17 00:00:00 2001 From: Jasper <46252815+jasper256@users.noreply.github.com> Date: Fri, 26 Jul 2019 12:28:32 -0400 Subject: [PATCH 0147/1071] added automated doctest to decimal_to_hexadecimal.py in conversions (#1071) * added automated doctest to decimal_to_hexadecimal.py in conversions * improved error handling and added more test cases in decimal_to_hexadecimal.py * implemented 0x notation and simplified AssertionError * fixed negative notation and added comparison test against Python hex function --- conversions/decimal_to_hexadecimal.py | 59 ++++++++++++++++++++------- 1 file changed, 45 insertions(+), 14 deletions(-) diff --git a/conversions/decimal_to_hexadecimal.py b/conversions/decimal_to_hexadecimal.py index f91fac063adc..e6435f1ef570 100644 --- a/conversions/decimal_to_hexadecimal.py +++ b/conversions/decimal_to_hexadecimal.py @@ -21,23 +21,54 @@ } def decimal_to_hexadecimal(decimal): - """ take decimal value, return hexadecimal representation as str """ + """ + take integer decimal value, return hexadecimal representation as str beginning with 0x + >>> decimal_to_hexadecimal(5) + '0x5' + >>> decimal_to_hexadecimal(15) + '0xf' + >>> decimal_to_hexadecimal(37) + '0x25' + >>> decimal_to_hexadecimal(255) + '0xff' + >>> decimal_to_hexadecimal(4096) + '0x1000' + >>> decimal_to_hexadecimal(999098) + '0xf3eba' + >>> # negatives work too + >>> decimal_to_hexadecimal(-256) + '-0x100' + >>> # floats are acceptable if equivalent to an int + >>> decimal_to_hexadecimal(17.0) + '0x11' + >>> # other floats will error + >>> decimal_to_hexadecimal(16.16) # doctest: +ELLIPSIS + Traceback (most recent call last): + ... + AssertionError + >>> # strings will error as well + >>> decimal_to_hexadecimal('0xfffff') # doctest: +ELLIPSIS + Traceback (most recent call last): + ... + AssertionError + >>> # results are the same when compared to Python's default hex function + >>> decimal_to_hexadecimal(-256) == hex(-256) + True + """ + assert type(decimal) in (int, float) and decimal == int(decimal) hexadecimal = '' + negative = False + if decimal < 0: + negative = True + decimal *= -1 while decimal > 0: - remainder = decimal % 16 - decimal -= remainder + decimal, remainder = divmod(decimal, 16) hexadecimal = values[remainder] + hexadecimal - decimal /= 16 + hexadecimal = '0x' + hexadecimal + if negative: + hexadecimal = '-' + hexadecimal return hexadecimal -def main(): - """ print test cases """ - print("5 in hexadecimal is", decimal_to_hexadecimal(5)) - print("15 in hexadecimal is", decimal_to_hexadecimal(15)) - print("37 in hexadecimal is", decimal_to_hexadecimal(37)) - print("255 in hexadecimal is", decimal_to_hexadecimal(255)) - print("4096 in hexadecimal is", decimal_to_hexadecimal(4096)) - print("999098 in hexadecimal is", decimal_to_hexadecimal(999098)) - if __name__ == '__main__': - main() \ No newline at end of file + import doctest + doctest.testmod() From a0817bdcf0c5ce17a7798fc0ede1821cd1bc983f Mon Sep 17 00:00:00 2001 From: Christian Clauss Date: Sun, 28 Jul 2019 17:27:23 +0200 Subject: [PATCH 0148/1071] Rewrite build_directory_md.py (#1076) * Rewrite build_directory_md.py * Regenerate DIRECTORY.md --- .travis.yml | 2 +- DIRECTORY.md | 23 +++++---- scripts/build_directory_md.py | 94 +++++++++++++---------------------- 3 files changed, 48 insertions(+), 71 deletions(-) mode change 100644 => 100755 scripts/build_directory_md.py diff --git a/.travis.yml b/.travis.yml index 6d432c660ddd..d09ef9de262d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -19,5 +19,5 @@ script: --ignore=machine_learning/random_forest_classification/random_forest_classification.py --ignore=machine_learning/random_forest_regression/random_forest_regression.py after_success: - - python scripts/build_directory_md.py + - scripts/build_directory_md.py > DIRECTORY.md - cat DIRECTORY.md diff --git a/DIRECTORY.md b/DIRECTORY.md index fc06a8cd1548..d97791bb5dd3 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -1,5 +1,6 @@ ## Arithmetic Analysis * [bisection](https://github.com/TheAlgorithms/Python/blob/master/arithmetic_analysis/bisection.py) + * [in static equilibrium](https://github.com/TheAlgorithms/Python/blob/master/arithmetic_analysis/in_static_equilibrium.py) * [intersection](https://github.com/TheAlgorithms/Python/blob/master/arithmetic_analysis/intersection.py) * [lu decomposition](https://github.com/TheAlgorithms/Python/blob/master/arithmetic_analysis/lu_decomposition.py) * [newton method](https://github.com/TheAlgorithms/Python/blob/master/arithmetic_analysis/newton_method.py) @@ -42,7 +43,6 @@ * [burrows wheeler](https://github.com/TheAlgorithms/Python/blob/master/compression/burrows_wheeler.py) * [huffman](https://github.com/TheAlgorithms/Python/blob/master/compression/huffman.py) * [peak signal to noise ratio](https://github.com/TheAlgorithms/Python/blob/master/compression/peak_signal_to_noise_ratio.py) - * Image Data ## Conversions * [decimal to binary](https://github.com/TheAlgorithms/Python/blob/master/conversions/decimal_to_binary.py) * [decimal to hexadecimal](https://github.com/TheAlgorithms/Python/blob/master/conversions/decimal_to_hexadecimal.py) @@ -62,9 +62,9 @@ * [double hash](https://github.com/TheAlgorithms/Python/blob/master/data_structures/hashing/double_hash.py) * [hash table](https://github.com/TheAlgorithms/Python/blob/master/data_structures/hashing/hash_table.py) * [hash table with linked list](https://github.com/TheAlgorithms/Python/blob/master/data_structures/hashing/hash_table_with_linked_list.py) - * [quadratic probing](https://github.com/TheAlgorithms/Python/blob/master/data_structures/hashing/quadratic_probing.py) * Number Theory * [prime numbers](https://github.com/TheAlgorithms/Python/blob/master/data_structures/hashing/number_theory/prime_numbers.py) + * [quadratic probing](https://github.com/TheAlgorithms/Python/blob/master/data_structures/hashing/quadratic_probing.py) * Heap * [heap](https://github.com/TheAlgorithms/Python/blob/master/data_structures/heap/heap.py) * Linked List @@ -87,6 +87,7 @@ * Trie * [trie](https://github.com/TheAlgorithms/Python/blob/master/data_structures/trie/trie.py) ## Digital Image Processing + * [change contrast](https://github.com/TheAlgorithms/Python/blob/master/digital_image_processing/change_contrast.py) * Edge Detection * [canny](https://github.com/TheAlgorithms/Python/blob/master/digital_image_processing/edge_detection/canny.py) * Filters @@ -94,7 +95,6 @@ * [gaussian filter](https://github.com/TheAlgorithms/Python/blob/master/digital_image_processing/filters/gaussian_filter.py) * [median filter](https://github.com/TheAlgorithms/Python/blob/master/digital_image_processing/filters/median_filter.py) * [sobel filter](https://github.com/TheAlgorithms/Python/blob/master/digital_image_processing/filters/sobel_filter.py) - * Image Data ## Divide And Conquer * [closest pair of points](https://github.com/TheAlgorithms/Python/blob/master/divide_and_conquer/closest_pair_of_points.py) * [max subarray sum](https://github.com/TheAlgorithms/Python/blob/master/divide_and_conquer/max_subarray_sum.py) @@ -167,24 +167,22 @@ * [lib](https://github.com/TheAlgorithms/Python/blob/master/linear_algebra_python/src/lib.py) * [tests](https://github.com/TheAlgorithms/Python/blob/master/linear_algebra_python/src/tests.py) ## Machine Learning + * [NaiveBayes](https://github.com/TheAlgorithms/Python/blob/master/machine_learning/NaiveBayes.ipynb) * [decision tree](https://github.com/TheAlgorithms/Python/blob/master/machine_learning/decision_tree.py) * [gradient descent](https://github.com/TheAlgorithms/Python/blob/master/machine_learning/gradient_descent.py) * [k means clust](https://github.com/TheAlgorithms/Python/blob/master/machine_learning/k_means_clust.py) * [knn sklearn](https://github.com/TheAlgorithms/Python/blob/master/machine_learning/knn_sklearn.py) * [linear regression](https://github.com/TheAlgorithms/Python/blob/master/machine_learning/linear_regression.py) * [logistic regression](https://github.com/TheAlgorithms/Python/blob/master/machine_learning/logistic_regression.py) - * [NaiveBayes](https://github.com/TheAlgorithms/Python/blob/master/machine_learning/NaiveBayes.ipynb) * [perceptron](https://github.com/TheAlgorithms/Python/blob/master/machine_learning/perceptron.py) - * [reuters one vs rest classifier](https://github.com/TheAlgorithms/Python/blob/master/machine_learning/reuters_one_vs_rest_classifier.ipynb) - * [scoring functions](https://github.com/TheAlgorithms/Python/blob/master/machine_learning/scoring_functions.py) * Random Forest Classification * [random forest classification](https://github.com/TheAlgorithms/Python/blob/master/machine_learning/random_forest_classification/random_forest_classification.py) * [random forest classifier](https://github.com/TheAlgorithms/Python/blob/master/machine_learning/random_forest_classification/random_forest_classifier.ipynb) - * [Social Network Ads](https://github.com/TheAlgorithms/Python/blob/master/machine_learning/random_forest_classification/Social_Network_Ads.csv) * Random Forest Regression - * [Position Salaries](https://github.com/TheAlgorithms/Python/blob/master/machine_learning/random_forest_regression/Position_Salaries.csv) * [random forest regression](https://github.com/TheAlgorithms/Python/blob/master/machine_learning/random_forest_regression/random_forest_regression.ipynb) * [random forest regression](https://github.com/TheAlgorithms/Python/blob/master/machine_learning/random_forest_regression/random_forest_regression.py) + * [reuters one vs rest classifier](https://github.com/TheAlgorithms/Python/blob/master/machine_learning/reuters_one_vs_rest_classifier.ipynb) + * [scoring functions](https://github.com/TheAlgorithms/Python/blob/master/machine_learning/scoring_functions.py) ## Maths * [3n+1](https://github.com/TheAlgorithms/Python/blob/master/maths/3n+1.py) * [abs](https://github.com/TheAlgorithms/Python/blob/master/maths/abs.py) @@ -203,11 +201,15 @@ * [find lcm](https://github.com/TheAlgorithms/Python/blob/master/maths/find_lcm.py) * [find max](https://github.com/TheAlgorithms/Python/blob/master/maths/find_max.py) * [find min](https://github.com/TheAlgorithms/Python/blob/master/maths/find_min.py) + * [gaussian](https://github.com/TheAlgorithms/Python/blob/master/maths/gaussian.py) * [greater common divisor](https://github.com/TheAlgorithms/Python/blob/master/maths/greater_common_divisor.py) + * [is square free](https://github.com/TheAlgorithms/Python/blob/master/maths/is_square_free.py) * [lucas series](https://github.com/TheAlgorithms/Python/blob/master/maths/lucas_series.py) + * [mobius function](https://github.com/TheAlgorithms/Python/blob/master/maths/mobius_function.py) * [modular exponential](https://github.com/TheAlgorithms/Python/blob/master/maths/modular_exponential.py) * [newton raphson](https://github.com/TheAlgorithms/Python/blob/master/maths/newton_raphson.py) * [prime check](https://github.com/TheAlgorithms/Python/blob/master/maths/prime_check.py) + * [prime factors](https://github.com/TheAlgorithms/Python/blob/master/maths/prime_factors.py) * [segmented sieve](https://github.com/TheAlgorithms/Python/blob/master/maths/segmented_sieve.py) * [sieve of eratosthenes](https://github.com/TheAlgorithms/Python/blob/master/maths/sieve_of_eratosthenes.py) * [simpson rule](https://github.com/TheAlgorithms/Python/blob/master/maths/simpson_rule.py) @@ -219,6 +221,8 @@ * [rotate matrix](https://github.com/TheAlgorithms/Python/blob/master/matrix/rotate_matrix.py) * [searching in sorted matrix](https://github.com/TheAlgorithms/Python/blob/master/matrix/searching_in_sorted_matrix.py) * [spiral print](https://github.com/TheAlgorithms/Python/blob/master/matrix/spiral_print.py) + * Tests + * [test matrix operation](https://github.com/TheAlgorithms/Python/blob/master/matrix/tests/test_matrix_operation.py) ## Networking Flow * [ford fulkerson](https://github.com/TheAlgorithms/Python/blob/master/networking_flow/ford_fulkerson.py) * [minimum cut](https://github.com/TheAlgorithms/Python/blob/master/networking_flow/minimum_cut.py) @@ -228,6 +232,7 @@ * [fully connected neural network](https://github.com/TheAlgorithms/Python/blob/master/neural_network/fully_connected_neural_network.ipynb) * [perceptron](https://github.com/TheAlgorithms/Python/blob/master/neural_network/perceptron.py) ## Other + * [Food wastage analysis from 1961-2013 (FAO)](https://github.com/TheAlgorithms/Python/blob/master/other/Food%20wastage%20analysis%20from%201961-2013%20(FAO).ipynb) * [anagrams](https://github.com/TheAlgorithms/Python/blob/master/other/anagrams.py) * [binary exponentiation](https://github.com/TheAlgorithms/Python/blob/master/other/binary_exponentiation.py) * [binary exponentiation 2](https://github.com/TheAlgorithms/Python/blob/master/other/binary_exponentiation_2.py) @@ -235,7 +240,6 @@ * [euclidean gcd](https://github.com/TheAlgorithms/Python/blob/master/other/euclidean_gcd.py) * [finding primes](https://github.com/TheAlgorithms/Python/blob/master/other/finding_primes.py) * [fischer yates shuffle](https://github.com/TheAlgorithms/Python/blob/master/other/fischer_yates_shuffle.py) - * [Food wastage analysis from 1961-2013 (FAO)](https://github.com/TheAlgorithms/Python/blob/master/other/Food%20wastage%20analysis%20from%201961-2013%20(FAO).ipynb) * [frequency finder](https://github.com/TheAlgorithms/Python/blob/master/other/frequency_finder.py) * [game of life](https://github.com/TheAlgorithms/Python/blob/master/other/game_of_life.py) * [linear congruential generator](https://github.com/TheAlgorithms/Python/blob/master/other/linear_congruential_generator.py) @@ -247,7 +251,6 @@ * [tower of hanoi](https://github.com/TheAlgorithms/Python/blob/master/other/tower_of_hanoi.py) * [two sum](https://github.com/TheAlgorithms/Python/blob/master/other/two_sum.py) * [word patterns](https://github.com/TheAlgorithms/Python/blob/master/other/word_patterns.py) - * [words](https://github.com/TheAlgorithms/Python/blob/master/other/words) ## Project Euler * Problem 01 * [sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_01/sol1.py) diff --git a/scripts/build_directory_md.py b/scripts/build_directory_md.py old mode 100644 new mode 100755 index 47192701880d..2ebd445b3667 --- a/scripts/build_directory_md.py +++ b/scripts/build_directory_md.py @@ -1,71 +1,45 @@ -""" -This is a simple script that will scan through the current directory -and generate the corresponding DIRECTORY.md file, can also specify -files or folders to be ignored. -""" +#!/usr/bin/env python3 + import os +from typing import Iterator + +URL_BASE = "https://github.com/TheAlgorithms/Python/blob/master" + +def good_filepaths(top_dir: str = ".") -> Iterator[str]: + for dirpath, dirnames, filenames in os.walk(top_dir): + dirnames[:] = [d for d in dirnames if d != "scripts" and d[0] not in "._"] + for filename in filenames: + if filename == "__init__.py": + continue + if os.path.splitext(filename)[1] in (".py", ".ipynb"): + yield os.path.join(dirpath, filename).lstrip("./") -# Target URL (master) -URL = "https://github.com/TheAlgorithms/Python/blob/master/" +def md_prefix(i): + return f"{i * ' '}*" if i else "##" -def tree(d, ignores, ignores_ext): - return _markdown(d, ignores, ignores_ext, 0) - -def _markdown(parent, ignores, ignores_ext, depth): - out = "" - dirs, files = [], [] - for i in os.listdir(parent): - full = os.path.join(parent, i) - name, ext = os.path.splitext(i) - if i not in ignores and ext not in ignores_ext: - if os.path.isfile(full): - # generate list - pre = parent.replace("./", "").replace(" ", "%20") - # replace all spaces to safe URL - child = i.replace(" ", "%20") - files.append((pre, child, name)) - else: - dirs.append(i) - # Sort files - files.sort(key=lambda e: e[2].lower()) - for f in files: - pre, child, name = f - out += " " * depth + "* [" + name.replace("_", " ") + "](" + URL + pre + "/" + child + ")\n" - # Sort directories - dirs.sort() - for i in dirs: - full = os.path.join(parent, i) - i = i.replace("_", " ").title() - if depth == 0: - out += "## " + i + "\n" - else: - out += " " * depth + "* " + i + "\n" - out += _markdown(full, ignores, ignores_ext, depth+1) - return out +def print_path(old_path: str, new_path: str) -> str: + old_parts = old_path.split(os.sep) + for i, new_part in enumerate(new_path.split(os.sep)): + if i + 1 > len(old_parts) or old_parts[i] != new_part: + if new_part: + print(f"{md_prefix(i)} {new_part.replace('_', ' ').title()}") + return new_path -# Specific files or folders with the given names will be ignored -ignores = [".vs", - ".gitignore", - ".git", - "scripts", - "__init__.py", - "requirements.txt", - ".github" -] -# Files with given entensions will be ignored -ignores_ext = [ - ".md", - ".ipynb", - ".png", - ".jpg", - ".yml" -] +def print_directory_md(top_dir: str = ".") -> None: + old_path = "" + for filepath in sorted(good_filepaths()): + filepath, filename = os.path.split(filepath) + if filepath != old_path: + old_path = print_path(old_path, filepath) + indent = (filepath.count(os.sep) + 1) if filepath else 0 + url = "/".join((URL_BASE, filepath, filename)).replace(" ", "%20") + filename = os.path.splitext(filename.replace("_", " "))[0] + print(f"{md_prefix(indent)} [{filename}]({url})") if __name__ == "__main__": - with open("DIRECTORY.md", "w+") as f: - f.write(tree(".", ignores, ignores_ext)) + print_directory_md(".") From 7b2c9541691a8a1d6ac523287225bfc368146403 Mon Sep 17 00:00:00 2001 From: Abhijeeth S Date: Tue, 30 Jul 2019 12:17:54 +0530 Subject: [PATCH 0149/1071] LargestOfVeryLargeNumbers (#818) * LargestOfVeryLargeNumbers Finds the largest among two very large numbers of the form x^y. Numbers like 512^513 etc * Rename LargestOfVeryLargeNumbers to LargestOfVeryLargeNumbers.py * Input() statements have been indented. input() statements are indented under if __name__ == "__main__": * largest_of_very_large_numbers.py --- maths/largest_of_very_large_numbers.py | 35 ++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 maths/largest_of_very_large_numbers.py diff --git a/maths/largest_of_very_large_numbers.py b/maths/largest_of_very_large_numbers.py new file mode 100644 index 000000000000..d2dc0af18126 --- /dev/null +++ b/maths/largest_of_very_large_numbers.py @@ -0,0 +1,35 @@ +# Author: Abhijeeth S + +import math + + +def res(x, y): + if 0 not in (x, y): + # We use the relation x^y = y*log10(x), where 10 is the base. + return y * math.log10(x) + else: + if x == 0: # 0 raised to any number is 0 + return 0 + elif y == 0: + return 1 # any number raised to 0 is 1 + + +if __name__ == "__main__": # Main function + # Read two numbers from input and typecast them to int using map function. + # Here x is the base and y is the power. + prompt = "Enter the base and the power separated by a comma: " + x1, y1 = map(int, input(prompt).split(",")) + x2, y2 = map(int, input(prompt).split(",")) + + # We find the log of each number, using the function res(), which takes two + # arguments. + res1 = res(x1, y1) + res2 = res(x2, y2) + + # We check for the largest number + if res1 > res2: + print("Largest number is", x1, "^", y1) + elif res2 > res1: + print("Largest number is", x2, "^", y2) + else: + print("Both are equal") From a9ecdb33ca04d44c979d8d9c1c99df312f4dd50c Mon Sep 17 00:00:00 2001 From: Christian Clauss Date: Tue, 30 Jul 2019 12:02:13 +0200 Subject: [PATCH 0150/1071] Validate Python filenames (#1086) --- .travis.yml | 1 + .../{NaiveBayes.ipynb => naive_bayes.ipynb} | 0 ...wastage_analysis_from_1961-2013_fao.ipynb} | 0 scripts/validate_filenames.py | 28 +++++++++++++++++++ 4 files changed, 29 insertions(+) rename machine_learning/{NaiveBayes.ipynb => naive_bayes.ipynb} (100%) rename other/{Food wastage analysis from 1961-2013 (FAO).ipynb => food_wastage_analysis_from_1961-2013_fao.ipynb} (100%) create mode 100755 scripts/validate_filenames.py diff --git a/.travis.yml b/.travis.yml index d09ef9de262d..c46d0d1d653a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -8,6 +8,7 @@ before_script: - black --check . || true - flake8 . --count --select=E9,F401,F63,F7,F82 --show-source --statistics script: + - scripts/validate_filenames.py # no uppercase and no spaces - mypy --ignore-missing-imports . - pytest . --doctest-modules --ignore=data_structures/stacks/balanced_parentheses.py diff --git a/machine_learning/NaiveBayes.ipynb b/machine_learning/naive_bayes.ipynb similarity index 100% rename from machine_learning/NaiveBayes.ipynb rename to machine_learning/naive_bayes.ipynb diff --git a/other/Food wastage analysis from 1961-2013 (FAO).ipynb b/other/food_wastage_analysis_from_1961-2013_fao.ipynb similarity index 100% rename from other/Food wastage analysis from 1961-2013 (FAO).ipynb rename to other/food_wastage_analysis_from_1961-2013_fao.ipynb diff --git a/scripts/validate_filenames.py b/scripts/validate_filenames.py new file mode 100755 index 000000000000..9e1f1503321b --- /dev/null +++ b/scripts/validate_filenames.py @@ -0,0 +1,28 @@ +#!/usr/bin/env python3 + +import os +from build_directory_md import good_filepaths + +filepaths = list(good_filepaths()) +assert filepaths, "good_filepaths() failed!" + + +upper_files = [file for file in filepaths if file != file.lower()] +if upper_files: + print(f"{len(upper_files)} files contain uppercase characters:") + print("\n".join(upper_files) + "\n") + +space_files = [file for file in filepaths if " " in file] +if space_files: + print(f"{len(space_files)} files contain space characters:") + print("\n".join(space_files) + "\n") + +nodir_files = [file for file in filepaths if os.sep not in file] +if nodir_files: + print(f"{len(nodir_files)} files are not in a directory:") + print("\n".join(nodir_files) + "\n") + +bad_files = len(upper_files + space_files + nodir_files) +if bad_files: + import sys + sys.exit(bad_files) From 861a8c36316a0bb10ee93f5560ce3313ef991399 Mon Sep 17 00:00:00 2001 From: obelisk0114 Date: Tue, 30 Jul 2019 09:00:24 -0700 Subject: [PATCH 0151/1071] Add Lucas_Lehmer_primality_test (#1050) * Add Lucas_Lehmer_primality_test * Add explanation for Lucas_Lehmer_primality_test * Update and rename Lucas_Lehmer_primality_test.py to lucas_lehmer_primality_test.py --- maths/lucas_lehmer_primality_test.py | 42 ++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 maths/lucas_lehmer_primality_test.py diff --git a/maths/lucas_lehmer_primality_test.py b/maths/lucas_lehmer_primality_test.py new file mode 100644 index 000000000000..44e41ba58d93 --- /dev/null +++ b/maths/lucas_lehmer_primality_test.py @@ -0,0 +1,42 @@ +# -*- coding: utf-8 -*- +""" + In mathematics, the Lucas–Lehmer test (LLT) is a primality test for Mersenne numbers. + https://en.wikipedia.org/wiki/Lucas%E2%80%93Lehmer_primality_test + + A Mersenne number is a number that is one less than a power of two. + That is M_p = 2^p - 1 + https://en.wikipedia.org/wiki/Mersenne_prime + + The Lucas–Lehmer test is the primality test used by the + Great Internet Mersenne Prime Search (GIMPS) to locate large primes. +""" + + +# Primality test 2^p - 1 +# Return true if 2^p - 1 is prime +def lucas_lehmer_test(p: int) -> bool: + """ + >>> lucas_lehmer_test(p=7) + True + + >>> lucas_lehmer_test(p=11) + False + + # M_11 = 2^11 - 1 = 2047 = 23 * 89 + """ + + if p < 2: + raise ValueError("p should not be less than 2!") + elif p == 2: + return True + + s = 4 + M = (1 << p) - 1 + for i in range(p - 2): + s = ((s * s) - 2) % M + return s == 0 + + +if __name__ == "__main__": + print(lucas_lehmer_test(7)) + print(lucas_lehmer_test(11)) From e58a5e68424df74a4c7b30df04162c775044405c Mon Sep 17 00:00:00 2001 From: FrogBattle <44649323+FrogBattle@users.noreply.github.com> Date: Tue, 30 Jul 2019 17:06:48 +0100 Subject: [PATCH 0152/1071] Update tim_sort.py (#972) * Update tim_sort.py Update tim_sort.py The previous algorithm was skipping numbers, according to issue #959, and my own tests. The version I am applying uses a while loop, which works correctly and is easier to compute, as there is no break statement. * Update tim_sort.py --- sorts/tim_sort.py | 47 ++++++++++++++++++++++++----------------------- 1 file changed, 24 insertions(+), 23 deletions(-) diff --git a/sorts/tim_sort.py b/sorts/tim_sort.py index b4032b91aec1..b95ff34cf384 100644 --- a/sorts/tim_sort.py +++ b/sorts/tim_sort.py @@ -1,10 +1,6 @@ -from __future__ import print_function def binary_search(lst, item, start, end): if start == end: - if lst[start] > item: - return start - else: - return start + 1 + return start if lst[start] > item else start + 1 if start > end: return start @@ -23,7 +19,7 @@ def insertion_sort(lst): for index in range(1, length): value = lst[index] pos = binary_search(lst, value, 0, index - 1) - lst = lst[:pos] + [value] + lst[pos:index] + lst[index+1:] + lst = lst[:pos] + [value] + lst[pos:index] + lst[index + 1 :] return lst @@ -42,30 +38,34 @@ def merge(left, right): def tim_sort(lst): - runs, sorted_runs = [], [] + """ + >>> tim_sort("Python") + ['P', 'h', 'n', 'o', 't', 'y'] + >>> tim_sort((1.1, 1, 0, -1, -1.1)) + [-1.1, -1, 0, 1, 1.1] + >>> tim_sort(list(reversed(list(range(7))))) + [0, 1, 2, 3, 4, 5, 6] + >>> tim_sort([3, 2, 1]) == insertion_sort([3, 2, 1]) + True + >>> tim_sort([3, 2, 1]) == sorted([3, 2, 1]) + True + """ length = len(lst) + runs, sorted_runs = [], [] new_run = [lst[0]] sorted_array = [] - - for i in range(1, length): - if i == length - 1: - new_run.append(lst[i]) - runs.append(new_run) - break - + i = 1 + while i < length: if lst[i] < lst[i - 1]: - if not new_run: - runs.append([lst[i - 1]]) - new_run.append(lst[i]) - else: - runs.append(new_run) - new_run = [] + runs.append(new_run) + new_run = [lst[i]] else: new_run.append(lst[i]) + i += 1 + runs.append(new_run) for run in runs: sorted_runs.append(insertion_sort(run)) - for run in sorted_runs: sorted_array = merge(sorted_array, run) @@ -74,9 +74,10 @@ def tim_sort(lst): def main(): - lst = [5,9,10,3,-4,5,178,92,46,-18,0,7] + lst = [5, 9, 10, 3, -4, 5, 178, 92, 46, -18, 0, 7] sorted_lst = tim_sort(lst) print(sorted_lst) -if __name__ == '__main__': + +if __name__ == "__main__": main() From 4a5589f4fcb71fc6101132bad44981693ef65d2c Mon Sep 17 00:00:00 2001 From: vinayak Date: Wed, 31 Jul 2019 16:50:32 +0530 Subject: [PATCH 0153/1071] project_euler/problem_10 (#1089) * project_euler/problem_10 * update project_euler/problem_10 * update project_euler/problem_10 * Negative user tests added. --- project_euler/problem_10/sol3.py | 58 ++++++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) create mode 100644 project_euler/problem_10/sol3.py diff --git a/project_euler/problem_10/sol3.py b/project_euler/problem_10/sol3.py new file mode 100644 index 000000000000..e5bc0731d8ab --- /dev/null +++ b/project_euler/problem_10/sol3.py @@ -0,0 +1,58 @@ +""" +https://projecteuler.net/problem=10 + +Problem Statement: +The sum of the primes below 10 is 2 + 3 + 5 + 7 = 17. + +Find the sum of all the primes below two million using Sieve_of_Eratosthenes: + +The sieve of Eratosthenes is one of the most efficient ways to find all primes +smaller than n when n is smaller than 10 million. Only for positive numbers. +""" + + +def prime_sum(n: int) -> int: + """ Returns the sum of all the primes below n. + + >>> prime_sum(2_000_000) + 142913828922 + >>> prime_sum(1_000) + 76127 + >>> prime_sum(5_000) + 1548136 + >>> prime_sum(10_000) + 5736396 + >>> prime_sum(7) + 10 + >>> prime_sum(7.1) # doctest: +ELLIPSIS + Traceback (most recent call last): + ... + TypeError: 'float' object cannot be interpreted as an integer + >>> prime_sum(-7) # doctest: +ELLIPSIS + Traceback (most recent call last): + ... + IndexError: list assignment index out of range + >>> prime_sum("seven") # doctest: +ELLIPSIS + Traceback (most recent call last): + ... + TypeError: can only concatenate str (not "int") to str + """ + list_ = [0 for i in range(n + 1)] + list_[0] = 1 + list_[1] = 1 + + for i in range(2, int(n ** 0.5) + 1): + if list_[i] == 0: + for j in range(i * i, n + 1, i): + list_[j] = 1 + s = 0 + for i in range(n): + if list_[i] == 0: + s += i + return s + + +if __name__ == "__main__": + # import doctest + # doctest.testmod() + print(prime_sum(int(input().strip()))) From 7b267e5e4f8ccb72dd58fcf0057642fd62a36bdf Mon Sep 17 00:00:00 2001 From: Christian Clauss Date: Wed, 31 Jul 2019 17:14:35 +0200 Subject: [PATCH 0154/1071] Fix data_structures to pass our Travis CI pytests (#1088) * Fix data_structures to pass pytests * Restore data_structures/stacks/__init__.py --- .travis.yml | 2 -- data_structures/stacks/balanced_parentheses.py | 3 ++- data_structures/stacks/infix_to_postfix_conversion.py | 2 +- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/.travis.yml b/.travis.yml index c46d0d1d653a..eab55af63492 100644 --- a/.travis.yml +++ b/.travis.yml @@ -11,8 +11,6 @@ script: - scripts/validate_filenames.py # no uppercase and no spaces - mypy --ignore-missing-imports . - pytest . --doctest-modules - --ignore=data_structures/stacks/balanced_parentheses.py - --ignore=data_structures/stacks/infix_to_postfix_conversion.py --ignore=file_transfer_protocol/ftp_send_receive.py --ignore=file_transfer_protocol/ftp_client_server.py --ignore=machine_learning/linear_regression.py diff --git a/data_structures/stacks/balanced_parentheses.py b/data_structures/stacks/balanced_parentheses.py index 3229d19c8621..36a4e07a97a3 100644 --- a/data_structures/stacks/balanced_parentheses.py +++ b/data_structures/stacks/balanced_parentheses.py @@ -1,6 +1,7 @@ from __future__ import print_function from __future__ import absolute_import -from stack import Stack + +from .stack import Stack __author__ = 'Omkar Pathak' diff --git a/data_structures/stacks/infix_to_postfix_conversion.py b/data_structures/stacks/infix_to_postfix_conversion.py index e71dccf1f45c..9376b55b8b23 100644 --- a/data_structures/stacks/infix_to_postfix_conversion.py +++ b/data_structures/stacks/infix_to_postfix_conversion.py @@ -2,7 +2,7 @@ from __future__ import absolute_import import string -from stack import Stack +from .stack import Stack __author__ = 'Omkar Pathak' From 9c0cbe33076a570a3c02825b7c6d9866a760e777 Mon Sep 17 00:00:00 2001 From: Sanders Lin <45224617+SandersLin@users.noreply.github.com> Date: Thu, 1 Aug 2019 23:54:03 +0800 Subject: [PATCH 0155/1071] Create collatz_sequence.py (#639) * Create collatz_sequence.py * Update and rename collatz_sequence.py to maths/collatz_sequence.py * doctest --- maths/collatz_sequence.py | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 maths/collatz_sequence.py diff --git a/maths/collatz_sequence.py b/maths/collatz_sequence.py new file mode 100644 index 000000000000..9f88453d518b --- /dev/null +++ b/maths/collatz_sequence.py @@ -0,0 +1,28 @@ +def collatz_sequence(n): + """ + Collatz conjecture: start with any positive integer n.Next termis obtained from the previous term as follows: + if the previous term is even, the next term is one half the previous term. + If the previous term is odd, the next term is 3 times the previous term plus 1. + The conjecture states the sequence will always reach 1 regaardess of starting n. + Example: + >>> collatz_sequence(43) + [43, 130, 65, 196, 98, 49, 148, 74, 37, 112, 56, 28, 14, 7, 22, 11, 34, 17, 52, 26, 13, 40, 20, 10, 5, 16, 8, 4, 2, 1] + """ + sequence = [n] + while n != 1: + if n % 2 == 0:# even + n //= 2 + else: + n = 3*n +1 + sequence.append(n) + return sequence + + +def main(): + n = 43 + sequence = collatz_sequence(n) + print(sequence) + print("collatz sequence from %d took %d steps."%(n,len(sequence))) + +if __name__ == '__main__': + main() From e3131419048010d9d67441387da4e73755364cf2 Mon Sep 17 00:00:00 2001 From: Syed Waleed Hyder Date: Sat, 3 Aug 2019 20:00:10 +0200 Subject: [PATCH 0156/1071] bin(num). convert ZERO and negative decimal numbers to binary. (#1093) * bin(num) can convert ZERO and negative decimal numbers to binary. Consistent with built-in python bin(x) function. * bin(num) can convert ZERO and negative decimal numbers to binary. Consistent with built-in python bin(x) function. * Added doctests. bin(num) can convert ZERO and negative decimal numbers to binary. Consistent with built-in python bin(x) function. * Added doctests. bin(num) can convert ZERO and negative decimal numbers to binary. Consistent with built-in python bin(x) function. * Added doctests. bin(num) can convert ZERO and negative decimal numbers to binary. Consistent with built-in python bin(x) function. * doctests still failing. * Doctests added. --- conversions/decimal_to_binary.py | 59 +++++++++++++++++++++++++------- 1 file changed, 46 insertions(+), 13 deletions(-) diff --git a/conversions/decimal_to_binary.py b/conversions/decimal_to_binary.py index 43ceee61a388..934cf0dfb363 100644 --- a/conversions/decimal_to_binary.py +++ b/conversions/decimal_to_binary.py @@ -2,24 +2,57 @@ def decimal_to_binary(num): - """Convert a Decimal Number to a Binary Number.""" + + """ + Convert a Integer Decimal Number to a Binary Number as str. + >>> decimal_to_binary(0) + '0b0' + >>> decimal_to_binary(2) + '0b10' + >>> decimal_to_binary(7) + '0b111' + >>> decimal_to_binary(35) + '0b100011' + >>> # negatives work too + >>> decimal_to_binary(-2) + '-0b10' + >>> # other floats will error + >>> decimal_to_binary(16.16) # doctest: +ELLIPSIS + Traceback (most recent call last): + ... + TypeError: 'float' object cannot be interpreted as an integer + >>> # strings will error as well + >>> decimal_to_binary('0xfffff') # doctest: +ELLIPSIS + Traceback (most recent call last): + ... + TypeError: 'str' object cannot be interpreted as an integer + """ + + if type(num) == float: + raise TypeError("'float' object cannot be interpreted as an integer") + if type(num) == str: + raise TypeError("'str' object cannot be interpreted as an integer") + + if num == 0: + return "0b0" + + negative = False + + if num < 0: + negative = True + num = -num + binary = [] while num > 0: binary.insert(0, num % 2) num >>= 1 - return "".join(str(e) for e in binary) + if negative: + return "-0b" + "".join(str(e) for e in binary) -def main(): - """Print binary equivelents of decimal numbers.""" - print("\n2 in binary is:") - print(decimal_to_binary(2)) # = 10 - print("\n7 in binary is:") - print(decimal_to_binary(7)) # = 111 - print("\n35 in binary is:") - print(decimal_to_binary(35)) # = 100011 - print("\n") + return "0b" + "".join(str(e) for e in binary) -if __name__ == '__main__': - main() +if __name__ == "__main__": + import doctest + doctest.testmod() From bdbe6825684d61131e0caee3a7361bd581c2442b Mon Sep 17 00:00:00 2001 From: "Marvin M. Michum" Date: Sun, 4 Aug 2019 23:22:28 -0400 Subject: [PATCH 0157/1071] Zeller's Congruence Algorithm (#1095) * doctest updates * remove unused math import * cleanup (suggestions) * cleanup - Dict fix (TravisCI error) --- maths/zellers_congruence.py | 157 ++++++++++++++++++++++++++++++++++++ 1 file changed, 157 insertions(+) create mode 100644 maths/zellers_congruence.py diff --git a/maths/zellers_congruence.py b/maths/zellers_congruence.py new file mode 100644 index 000000000000..e04425eec903 --- /dev/null +++ b/maths/zellers_congruence.py @@ -0,0 +1,157 @@ +from __future__ import annotations +import datetime +import argparse + + +def zeller(date_input: str) -> str: + + """ + Zellers Congruence Algorithm + Find the day of the week for nearly any Gregorian or Julian calendar date + + >>> zeller('01-31-2010') + 'Your date 01-31-2010, is a Sunday!' + + Validate out of range month + >>> zeller('13-31-2010') + Traceback (most recent call last): + ... + ValueError: Month must be between 1 - 12 + >>> zeller('.2-31-2010') + Traceback (most recent call last): + ... + ValueError: invalid literal for int() with base 10: '.2' + + Validate out of range date: + >>> zeller('01-33-2010') + Traceback (most recent call last): + ... + ValueError: Date must be between 1 - 31 + >>> zeller('01-.4-2010') + Traceback (most recent call last): + ... + ValueError: invalid literal for int() with base 10: '.4' + + Validate second seperator: + >>> zeller('01-31*2010') + Traceback (most recent call last): + ... + ValueError: Date seperator must be '-' or '/' + + Validate first seperator: + >>> zeller('01^31-2010') + Traceback (most recent call last): + ... + ValueError: Date seperator must be '-' or '/' + + Validate out of range year: + >>> zeller('01-31-8999') + Traceback (most recent call last): + ... + ValueError: Year out of range. There has to be some sort of limit...right? + + Test null input: + >>> zeller() + Traceback (most recent call last): + ... + TypeError: zeller() missing 1 required positional argument: 'date_input' + + Test length fo date_input: + >>> zeller('') + Traceback (most recent call last): + ... + ValueError: Must be 10 characters long + >>> zeller('01-31-19082939') + Traceback (most recent call last): + ... + ValueError: Must be 10 characters long +""" + + # Days of the week for response + days = { + '0': 'Sunday', + '1': 'Monday', + '2': 'Tuesday', + '3': 'Wednesday', + '4': 'Thursday', + '5': 'Friday', + '6': 'Saturday' + } + + convert_datetime_days = { + 0:1, + 1:2, + 2:3, + 3:4, + 4:5, + 5:6, + 6:0 + } + + # Validate + if not 0 < len(date_input) < 11: + raise ValueError("Must be 10 characters long") + + # Get month + m: int = int(date_input[0] + date_input[1]) + # Validate + if not 0 < m < 13: + raise ValueError("Month must be between 1 - 12") + + sep_1:str = date_input[2] + # Validate + if sep_1 not in ["-","/"]: + raise ValueError("Date seperator must be '-' or '/'") + + # Get day + d: int = int(date_input[3] + date_input[4]) + # Validate + if not 0 < d < 32: + raise ValueError("Date must be between 1 - 31") + + # Get second seperator + sep_2: str = date_input[5] + # Validate + if sep_2 not in ["-","/"]: + raise ValueError("Date seperator must be '-' or '/'") + + # Get year + y: int = int(date_input[6] + date_input[7] + date_input[8] + date_input[9]) + # Arbitrary year range + if not 45 < y < 8500: + raise ValueError("Year out of range. There has to be some sort of limit...right?") + + # Get datetime obj for validation + dt_ck = datetime.date(int(y), int(m), int(d)) + + # Start math + if m <= 2: + y = y - 1 + m = m + 12 + # maths var + c: int = int(str(y)[:2]) + k: int = int(str(y)[2:]) + t: int = int(2.6*m - 5.39) + u: int = int(c / 4) + v: int = int(k / 4) + x: int = int(d + k) + z: int = int(t + u + v + x) + w: int = int(z - (2 * c)) + f: int = round(w%7) + # End math + + # Validate math + if f != convert_datetime_days[dt_ck.weekday()]: + raise AssertionError("The date was evaluated incorrectly. Contact developer.") + + # Response + response: str = f"Your date {date_input}, is a {days[str(f)]}!" + return response + +if __name__ == '__main__': + import doctest + doctest.testmod() + parser = argparse.ArgumentParser(description='Find out what day of the week nearly any date is or was. Enter date as a string in the mm-dd-yyyy or mm/dd/yyyy format') + parser.add_argument('date_input', type=str, help='Date as a string (mm-dd-yyyy or mm/dd/yyyy)') + args = parser.parse_args() + zeller(args.date_input) From 87a789af515333c35aed1060c7a2b5c7d287123c Mon Sep 17 00:00:00 2001 From: "Marvin M. Michum" Date: Mon, 5 Aug 2019 01:05:36 -0400 Subject: [PATCH 0158/1071] Boolean algebra pytests (#1097) * Added Zeller's congruence algorithm * Update args help * add a few doctests * remove old file --- boolean_algebra/quine_mc_cluskey.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/boolean_algebra/quine_mc_cluskey.py b/boolean_algebra/quine_mc_cluskey.py index db4d153cbfd7..94319ca45482 100644 --- a/boolean_algebra/quine_mc_cluskey.py +++ b/boolean_algebra/quine_mc_cluskey.py @@ -1,3 +1,18 @@ +""" + doctests + + >>> decimal_to_binary(3,[1.5]) + ['0.00.01.5'] + + >>> check(['0.00.01.5']) + ['0.00.01.5'] + + >>> prime_implicant_chart(['0.00.01.5'],['0.00.01.5']) + [[1]] + + >>> selection([[1]],['0.00.01.5']) + ['0.00.01.5'] +""" def compare_string(string1, string2): l1 = list(string1); l2 = list(string2) count = 0 @@ -113,4 +128,6 @@ def main(): print(essential_prime_implicants) if __name__ == '__main__': + import doctest + doctest.testmod() main() From 4437439363c8aa3347dfcd817afe7c69b3d7f59e Mon Sep 17 00:00:00 2001 From: Hector S Date: Mon, 5 Aug 2019 01:07:52 -0400 Subject: [PATCH 0159/1071] Added Unicode test to strings/rabin_karp.py (#1096) * Added print function into matrix_multiplication_addition.py and removed blank space in data_structures/binary tree directory * Removed .vs/ folder per #893 * Rename matrix_multiplication_addition.py to matrix_operation.py * Unicode test on strings/rabin_karp.py per #1067 --- strings/rabin_karp.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/strings/rabin_karp.py b/strings/rabin_karp.py index 7c36f7659e24..1fb145ec97fa 100644 --- a/strings/rabin_karp.py +++ b/strings/rabin_karp.py @@ -73,6 +73,13 @@ def test_rabin_karp(): pattern = "abcdabcy" text = "abcxabcdabxabcdabcdabcy" assert rabin_karp(pattern, text) + + # Test 5) + pattern = "Lü" + text = "Lüsai" + assert rabin_karp(pattern, text) + pattern = "Lue" + assert not rabin_karp(pattern, text) print("Success.") From 47bc34ac268d1b0102c72d9cf5f2b7cb52db6a5e Mon Sep 17 00:00:00 2001 From: QuantumNovice <43876848+QuantumNovice@users.noreply.github.com> Date: Tue, 6 Aug 2019 05:06:15 +0500 Subject: [PATCH 0160/1071] Added pytests to sha1.py (#1098) --- 16L' | 0 Q' | 0 hashes/sha1.py | 10 ++++++++-- 3 files changed, 8 insertions(+), 2 deletions(-) create mode 100644 16L' create mode 100644 Q' diff --git a/16L' b/16L' new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/Q' b/Q' new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/hashes/sha1.py b/hashes/sha1.py index 4c78ad3a89e5..511ea6363733 100644 --- a/hashes/sha1.py +++ b/hashes/sha1.py @@ -2,7 +2,7 @@ Demonstrates implementation of SHA1 Hash function in a Python class and gives utilities to find hash of string or hash of text from a file. Usage: python sha1.py --string "Hello World!!" - pyhton sha1.py --file "hello_world.txt" + python sha1.py --file "hello_world.txt" When run without any arguments, it prints the hash of the string "Hello World!! Welcome to Cryptography" Also contains a Test class to verify that the generated Hash is same as that returned by the hashlib library @@ -32,6 +32,8 @@ class SHA1Hash: """ Class to contain the entire pipeline for SHA1 Hashing Algorithm + >>> SHA1Hash(bytes('Allan', 'utf-8')).final_hash() + '872af2d8ac3d8695387e7c804bf0e02c18df9e6e' """ def __init__(self, data): """ @@ -47,6 +49,8 @@ def __init__(self, data): def rotate(n, b): """ Static method to be used inside other methods. Left rotates n by b. + >>> SHA1Hash('').rotate(12,2) + 48 """ return ((n << b) | (n >> (32 - b))) & 0xffffffff @@ -68,7 +72,7 @@ def split_blocks(self): def expand_block(self, block): """ Takes a bytestring-block of length 64, unpacks it to a list of integers and returns a - list of 80 integers pafter some bit operations + list of 80 integers after some bit operations """ w = list(struct.unpack('>16L', block)) + [0] * 64 for i in range(16, 80): @@ -146,3 +150,5 @@ def main(): if __name__ == '__main__': main() + import doctest + doctest.testmod() \ No newline at end of file From 22d2453773522b677052d074ca0b8f2315309a01 Mon Sep 17 00:00:00 2001 From: AugustofCravo <49079453+AugustofCravo@users.noreply.github.com> Date: Mon, 5 Aug 2019 21:22:34 -0300 Subject: [PATCH 0161/1071] Create Quadratic Equations(Complexes Numbers) (#941) * Create Quadratic Equations(Complexes Numbers) Created function that solves quadratic equations treating the cases with complexes numbers. Giving an answer with the imaginary unit "i". * Update Quadratic Equations(Complexes Numbers) Since there was no response from the owner of this PR, I made this little change which I hope will solve the issue! --- maths/Quadratic Equations(Complexes Numbers) | 40 ++++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 maths/Quadratic Equations(Complexes Numbers) diff --git a/maths/Quadratic Equations(Complexes Numbers) b/maths/Quadratic Equations(Complexes Numbers) new file mode 100644 index 000000000000..8e8e78fec68f --- /dev/null +++ b/maths/Quadratic Equations(Complexes Numbers) @@ -0,0 +1,40 @@ +from __future__ import print_function +import math + +def QuadraticEquation(a,b,c): + """ + Prints the solutions for a quadratic equation, given the numerical coefficients a, b and c, + for a*x*x + b*x + c. + Ex.: a = 1, b = 3, c = -4 + Solution1 = 1 and Solution2 = -4 + """ + Delta = b*b - 4*a*c + if a != 0: + if Delta >= 0: + Solution1 = (-b + math.sqrt(Delta))/(2*a) + Solution2 = (-b - math.sqrt(Delta))/(2*a) + print ("The equation solutions are: ", Solution1," and ", Solution2) + else: + """ + Treats cases of Complexes Solutions(i = imaginary unit) + Ex.: a = 5, b = 2, c = 1 + Solution1 = (- 2 + 4.0 *i)/2 and Solution2 = (- 2 + 4.0 *i)/ 10 + """ + if b > 0: + print("The equation solutions are: (-",b,"+",math.sqrt(-Delta),"*i)/2 and (-",b,"+",math.sqrt(-Delta),"*i)/", 2*a) + if b < 0: + print("The equation solutions are: (",b,"+",math.sqrt(-Delta),"*i)/2 and (",b,"+",math.sqrt(-Delta),"*i/",2*a) + if b == 0: + print("The equation solutions are: (",math.sqrt(-Delta),"*i)/2 and ",math.sqrt(-Delta),"*i)/", 2*a) + else: + print("Error. Please, coeficient 'a' must not be zero for quadratic equations.") +def main(): + a = 5 + b = 6 + c = 1 + + QuadraticEquation(a,b,c) # The equation solutions are: -0.2 and -1.0 + + +if __name__ == '__main__': + main() From 58126406fd345c4f0dda6b7d8d5beaa1b9360810 Mon Sep 17 00:00:00 2001 From: rsun0013 <50036197+rsun0013@users.noreply.github.com> Date: Tue, 6 Aug 2019 19:17:17 +1000 Subject: [PATCH 0162/1071] pytests for closest_pair_of_points.py (#1099) added some tests to the file --- divide_and_conquer/closest_pair_of_points.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/divide_and_conquer/closest_pair_of_points.py b/divide_and_conquer/closest_pair_of_points.py index ee06d27063df..b6f63396410c 100644 --- a/divide_and_conquer/closest_pair_of_points.py +++ b/divide_and_conquer/closest_pair_of_points.py @@ -19,6 +19,19 @@ Time complexity: O(n * log n) """ +""" + doctests + >>> euclidean_distance_sqr([1,2],[2,4]) + 5 + >>> dis_between_closest_pair([[1,2],[2,4],[5,7],[8,9],[11,0]],5) + 5 + >>> dis_between_closest_in_strip([[1,2],[2,4],[5,7],[8,9],[11,0]],5) + 85 + >>> points = [(2, 3), (12, 30), (40, 50), (5, 1), (12, 10), (3, 4)] + >>> print("Distance:", closest_pair_of_points(points, len(points))) + "Distance: 1.4142135623730951" +""" + def euclidean_distance_sqr(point1, point2): return (point1[0] - point2[0]) ** 2 + (point1[1] - point2[1]) ** 2 From 6654e1ec7de8c4ca6ee604799645b4b210856190 Mon Sep 17 00:00:00 2001 From: Harshil Date: Tue, 6 Aug 2019 11:41:23 +0200 Subject: [PATCH 0163/1071] remove from __future__, propre filename (#1102) --- ...Complexes Numbers) => quadratic_equations_complex_numbers.py} | 1 - 1 file changed, 1 deletion(-) rename maths/{Quadratic Equations(Complexes Numbers) => quadratic_equations_complex_numbers.py} (97%) diff --git a/maths/Quadratic Equations(Complexes Numbers) b/maths/quadratic_equations_complex_numbers.py similarity index 97% rename from maths/Quadratic Equations(Complexes Numbers) rename to maths/quadratic_equations_complex_numbers.py index 8e8e78fec68f..f05b938fefe9 100644 --- a/maths/Quadratic Equations(Complexes Numbers) +++ b/maths/quadratic_equations_complex_numbers.py @@ -1,4 +1,3 @@ -from __future__ import print_function import math def QuadraticEquation(a,b,c): From 89acf5d01733754b1403df2313a0a6ef17b4b051 Mon Sep 17 00:00:00 2001 From: Christian Clauss Date: Tue, 6 Aug 2019 12:14:23 +0200 Subject: [PATCH 0164/1071] print() is a function just like every other function (#1101) * print() is a function just like every other function --- arithmetic_analysis/newton_raphson_method.py | 16 ++-- ciphers/caesar_cipher.py | 6 +- ciphers/morse_code_implementation.py | 4 +- ciphers/trafid_cipher.py | 14 +-- ciphers/xor_cipher.py | 18 ++-- data_structures/binary_tree/fenwick_tree.py | 12 +-- .../binary_tree/lazy_segment_tree.py | 18 ++-- data_structures/binary_tree/segment_tree.py | 20 ++-- data_structures/queue/double_ended_queue.py | 42 ++++----- data_structures/stacks/stock_span_problem.py | 92 +++++++++---------- machine_learning/logistic_regression.py | 10 +- maths/quadratic_equations_complex_numbers.py | 8 +- other/fischer_yates_shuffle.py | 6 +- 13 files changed, 133 insertions(+), 133 deletions(-) diff --git a/arithmetic_analysis/newton_raphson_method.py b/arithmetic_analysis/newton_raphson_method.py index 569f96476afc..bb6fdd2193ec 100644 --- a/arithmetic_analysis/newton_raphson_method.py +++ b/arithmetic_analysis/newton_raphson_method.py @@ -8,25 +8,25 @@ def NewtonRaphson(func, a): ''' Finds root from the point 'a' onwards by Newton-Raphson method ''' while True: c = Decimal(a) - ( Decimal(eval(func)) / Decimal(eval(str(diff(func)))) ) - + a = c # This number dictates the accuracy of the answer if abs(eval(func)) < 10**-15: return c - + # Let's Execute if __name__ == '__main__': # Find root of trigonometric function # Find value of pi - print ('sin(x) = 0', NewtonRaphson('sin(x)', 2)) - + print('sin(x) = 0', NewtonRaphson('sin(x)', 2)) + # Find root of polynomial - print ('x**2 - 5*x +2 = 0', NewtonRaphson('x**2 - 5*x +2', 0.4)) - + print('x**2 - 5*x +2 = 0', NewtonRaphson('x**2 - 5*x +2', 0.4)) + # Find Square Root of 5 - print ('x**2 - 5 = 0', NewtonRaphson('x**2 - 5', 0.1)) + print('x**2 - 5 = 0', NewtonRaphson('x**2 - 5', 0.1)) # Exponential Roots - print ('exp(x) - 1 = 0', NewtonRaphson('exp(x) - 1', 0)) + print('exp(x) - 1 = 0', NewtonRaphson('exp(x) - 1', 0)) diff --git a/ciphers/caesar_cipher.py b/ciphers/caesar_cipher.py index 872b5d8195c1..95d65d404266 100644 --- a/ciphers/caesar_cipher.py +++ b/ciphers/caesar_cipher.py @@ -41,12 +41,12 @@ def main(): print("4.Quit") choice = input("What would you like to do?: ") if choice not in ['1', '2', '3', '4']: - print ("Invalid choice, please enter a valid choice") + print("Invalid choice, please enter a valid choice") elif choice == '1': strng = input("Please enter the string to be encrypted: ") key = int(input("Please enter off-set between 1-94: ")) if key in range(1, 95): - print (encrypt(strng.lower(), key)) + print(encrypt(strng.lower(), key)) elif choice == '2': strng = input("Please enter the string to be decrypted: ") key = int(input("Please enter off-set between 1-94: ")) @@ -57,7 +57,7 @@ def main(): brute_force(strng) main() elif choice == '4': - print ("Goodbye.") + print("Goodbye.") break diff --git a/ciphers/morse_code_implementation.py b/ciphers/morse_code_implementation.py index 7b2d0a94b24b..5d0e7b2779b1 100644 --- a/ciphers/morse_code_implementation.py +++ b/ciphers/morse_code_implementation.py @@ -71,11 +71,11 @@ def decrypt(message): def main(): message = "Morse code here" result = encrypt(message.upper()) - print (result) + print(result) message = result result = decrypt(message) - print (result) + print(result) if __name__ == '__main__': diff --git a/ciphers/trafid_cipher.py b/ciphers/trafid_cipher.py index 0453272f26a0..53f4d288bfe2 100644 --- a/ciphers/trafid_cipher.py +++ b/ciphers/trafid_cipher.py @@ -3,7 +3,7 @@ def __encryptPart(messagePart, character2Number): one, two, three = "", "", "" tmp = [] - + for character in messagePart: tmp.append(character2Number[character]) @@ -11,7 +11,7 @@ def __encryptPart(messagePart, character2Number): one += each[0] two += each[1] three += each[2] - + return one+two+three def __decryptPart(messagePart, character2Number): @@ -25,7 +25,7 @@ def __decryptPart(messagePart, character2Number): tmp += digit if len(tmp) == len(messagePart): result.append(tmp) - tmp = "" + tmp = "" return result[0], result[1], result[2] @@ -48,7 +48,7 @@ def __prepare(message, alphabet): for letter, number in zip(alphabet, numbers): character2Number[letter] = number number2Character[number] = letter - + return message, alphabet, character2Number, number2Character def encryptMessage(message, alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ.", period=5): @@ -57,7 +57,7 @@ def encryptMessage(message, alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ.", period=5): for i in range(0, len(message)+1, period): encrypted_numeric += __encryptPart(message[i:i+period], character2Number) - + for i in range(0, len(encrypted_numeric), 3): encrypted += number2Character[encrypted_numeric[i:i+3]] @@ -70,7 +70,7 @@ def decryptMessage(message, alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ.", period=5): for i in range(0, len(message)+1, period): a,b,c = __decryptPart(message[i:i+period], character2Number) - + for j in range(0, len(a)): decrypted_numeric.append(a[j]+b[j]+c[j]) @@ -83,4 +83,4 @@ def decryptMessage(message, alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ.", period=5): msg = "DEFEND THE EAST WALL OF THE CASTLE." encrypted = encryptMessage(msg,"EPSDUCVWYM.ZLKXNBTFGORIJHAQ") decrypted = decryptMessage(encrypted, "EPSDUCVWYM.ZLKXNBTFGORIJHAQ") - print ("Encrypted: {}\nDecrypted: {}".format(encrypted, decrypted)) \ No newline at end of file + print("Encrypted: {}\nDecrypted: {}".format(encrypted, decrypted)) diff --git a/ciphers/xor_cipher.py b/ciphers/xor_cipher.py index 727fac3b0703..8bb94212c15a 100644 --- a/ciphers/xor_cipher.py +++ b/ciphers/xor_cipher.py @@ -122,7 +122,7 @@ def decrypt_string(self,content,key = 0): # This will be returned ans = "" - + for ch in content: ans += chr(ord(ch) ^ key) @@ -188,22 +188,22 @@ def decrypt_file(self,file, key): # key = 67 # # test enrcypt -# print crypt.encrypt("hallo welt",key) +# print(crypt.encrypt("hallo welt",key)) # # test decrypt -# print crypt.decrypt(crypt.encrypt("hallo welt",key), key) +# print(crypt.decrypt(crypt.encrypt("hallo welt",key), key)) # # test encrypt_string -# print crypt.encrypt_string("hallo welt",key) +# print(crypt.encrypt_string("hallo welt",key)) # # test decrypt_string -# print crypt.decrypt_string(crypt.encrypt_string("hallo welt",key),key) +# print(crypt.decrypt_string(crypt.encrypt_string("hallo welt",key),key)) # if (crypt.encrypt_file("test.txt",key)): -# print "encrypt successful" +# print("encrypt successful") # else: -# print "encrypt unsuccessful" +# print("encrypt unsuccessful") # if (crypt.decrypt_file("encrypt.out",key)): -# print "decrypt successful" +# print("decrypt successful") # else: -# print "decrypt unsuccessful" \ No newline at end of file +# print("decrypt unsuccessful") diff --git a/data_structures/binary_tree/fenwick_tree.py b/data_structures/binary_tree/fenwick_tree.py index f429161c8c36..ef984082d9e8 100644 --- a/data_structures/binary_tree/fenwick_tree.py +++ b/data_structures/binary_tree/fenwick_tree.py @@ -16,14 +16,14 @@ def query(self, i): # query cumulative data from index 0 to i in O(lg N) ret += self.ft[i] i -= i & (-i) return ret - + if __name__ == '__main__': f = FenwickTree(100) f.update(1,20) f.update(4,4) - print (f.query(1)) - print (f.query(3)) - print (f.query(4)) + print(f.query(1)) + print(f.query(3)) + print(f.query(4)) f.update(2,-5) - print (f.query(1)) - print (f.query(3)) + print(f.query(1)) + print(f.query(3)) diff --git a/data_structures/binary_tree/lazy_segment_tree.py b/data_structures/binary_tree/lazy_segment_tree.py index 9b14b24e81fa..215399976dd3 100644 --- a/data_structures/binary_tree/lazy_segment_tree.py +++ b/data_structures/binary_tree/lazy_segment_tree.py @@ -2,13 +2,13 @@ import math class SegmentTree: - + def __init__(self, N): self.N = N self.st = [0 for i in range(0,4*N)] # approximate the overall size of segment tree with array N self.lazy = [0 for i in range(0,4*N)] # create array to store lazy update self.flag = [0 for i in range(0,4*N)] # flag for lazy update - + def left(self, idx): return idx*2 @@ -34,7 +34,7 @@ def update(self, idx, l, r, a, b, val): # update(1, 1, N, a, b, v) for update va self.lazy[self.right(idx)] = self.lazy[idx] self.flag[self.left(idx)] = True self.flag[self.right(idx)] = True - + if r < a or l > b: return True if l >= a and r <= b : @@ -74,18 +74,18 @@ def showData(self): showList = [] for i in range(1,N+1): showList += [self.query(1, 1, self.N, i, i)] - print (showList) - + print(showList) + if __name__ == '__main__': A = [1,2,-4,7,3,-5,6,11,-20,9,14,15,5,2,-8] N = 15 segt = SegmentTree(N) segt.build(1,1,N,A) - print (segt.query(1,1,N,4,6)) - print (segt.query(1,1,N,7,11)) - print (segt.query(1,1,N,7,12)) + print(segt.query(1,1,N,4,6)) + print(segt.query(1,1,N,7,11)) + print(segt.query(1,1,N,7,12)) segt.update(1,1,N,1,3,111) - print (segt.query(1,1,N,1,15)) + print(segt.query(1,1,N,1,15)) segt.update(1,1,N,7,8,235) segt.showData() diff --git a/data_structures/binary_tree/segment_tree.py b/data_structures/binary_tree/segment_tree.py index 001bf999f391..7e61198ca59c 100644 --- a/data_structures/binary_tree/segment_tree.py +++ b/data_structures/binary_tree/segment_tree.py @@ -2,12 +2,12 @@ import math class SegmentTree: - + def __init__(self, A): self.N = len(A) self.st = [0] * (4 * self.N) # approximate the overall size of segment tree with array N self.build(1, 0, self.N - 1) - + def left(self, idx): return idx * 2 @@ -22,10 +22,10 @@ def build(self, idx, l, r): self.build(self.left(idx), l, mid) self.build(self.right(idx), mid + 1, r) self.st[idx] = max(self.st[self.left(idx)] , self.st[self.right(idx)]) - + def update(self, a, b, val): return self.update_recursive(1, 0, self.N - 1, a - 1, b - 1, val) - + def update_recursive(self, idx, l, r, a, b, val): # update(1, 1, N, a, b, v) for update val v to [a,b] if r < a or l > b: return True @@ -55,17 +55,17 @@ def showData(self): showList = [] for i in range(1,N+1): showList += [self.query(i, i)] - print (showList) - + print(showList) + if __name__ == '__main__': A = [1,2,-4,7,3,-5,6,11,-20,9,14,15,5,2,-8] N = 15 segt = SegmentTree(A) - print (segt.query(4, 6)) - print (segt.query(7, 11)) - print (segt.query(7, 12)) + print(segt.query(4, 6)) + print(segt.query(7, 11)) + print(segt.query(7, 12)) segt.update(1,3,111) - print (segt.query(1, 15)) + print(segt.query(1, 15)) segt.update(7,8,235) segt.showData() diff --git a/data_structures/queue/double_ended_queue.py b/data_structures/queue/double_ended_queue.py index fdee64eb6ae0..838bf2f4bc36 100644 --- a/data_structures/queue/double_ended_queue.py +++ b/data_structures/queue/double_ended_queue.py @@ -1,40 +1,40 @@ from __future__ import print_function -# Python code to demonstrate working of +# Python code to demonstrate working of # extend(), extendleft(), rotate(), reverse() - + # importing "collections" for deque operations import collections - + # initializing deque de = collections.deque([1, 2, 3,]) - -# using extend() to add numbers to right end + +# using extend() to add numbers to right end # adds 4,5,6 to right end de.extend([4,5,6]) - + # printing modified deque -print ("The deque after extending deque at end is : ") -print (de) - -# using extendleft() to add numbers to left end +print("The deque after extending deque at end is : ") +print(de) + +# using extendleft() to add numbers to left end # adds 7,8,9 to right end de.extendleft([7,8,9]) - + # printing modified deque -print ("The deque after extending deque at beginning is : ") -print (de) - +print("The deque after extending deque at beginning is : ") +print(de) + # using rotate() to rotate the deque # rotates by 3 to left de.rotate(-3) - + # printing modified deque -print ("The deque after rotating deque is : ") -print (de) - +print("The deque after rotating deque is : ") +print(de) + # using reverse() to reverse the deque de.reverse() - + # printing modified deque -print ("The deque after reversing deque is : ") -print (de) +print("The deque after reversing deque is : ") +print(de) diff --git a/data_structures/stacks/stock_span_problem.py b/data_structures/stacks/stock_span_problem.py index 9628864edd10..e9afebc193b6 100644 --- a/data_structures/stacks/stock_span_problem.py +++ b/data_structures/stacks/stock_span_problem.py @@ -1,52 +1,52 @@ ''' -The stock span problem is a financial problem where we have a series of n daily +The stock span problem is a financial problem where we have a series of n daily price quotes for a stock and we need to calculate span of stock's price for all n days. -The span Si of the stock's price on a given day i is defined as the maximum -number of consecutive days just before the given day, for which the price of the stock +The span Si of the stock's price on a given day i is defined as the maximum +number of consecutive days just before the given day, for which the price of the stock on the current day is less than or equal to its price on the given day. ''' from __future__ import print_function -def calculateSpan(price, S): - - n = len(price) - # Create a stack and push index of fist element to it - st = [] - st.append(0) - - # Span value of first element is always 1 - S[0] = 1 - - # Calculate span values for rest of the elements - for i in range(1, n): - - # Pop elements from stack whlie stack is not - # empty and top of stack is smaller than price[i] - while( len(st) > 0 and price[st[0]] <= price[i]): - st.pop() - - # If stack becomes empty, then price[i] is greater - # than all elements on left of it, i.e. price[0], - # price[1], ..price[i-1]. Else the price[i] is - # greater than elements after top of stack - S[i] = i+1 if len(st) <= 0 else (i - st[0]) - - # Push this element to stack - st.append(i) - - -# A utility function to print elements of array -def printArray(arr, n): - for i in range(0,n): - print (arr[i],end =" ") - - -# Driver program to test above function -price = [10, 4, 5, 90, 120, 80] -S = [0 for i in range(len(price)+1)] - -# Fill the span values in array S[] -calculateSpan(price, S) - -# Print the calculated span values -printArray(S, len(price)) +def calculateSpan(price, S): + + n = len(price) + # Create a stack and push index of fist element to it + st = [] + st.append(0) + + # Span value of first element is always 1 + S[0] = 1 + + # Calculate span values for rest of the elements + for i in range(1, n): + + # Pop elements from stack whlie stack is not + # empty and top of stack is smaller than price[i] + while( len(st) > 0 and price[st[0]] <= price[i]): + st.pop() + + # If stack becomes empty, then price[i] is greater + # than all elements on left of it, i.e. price[0], + # price[1], ..price[i-1]. Else the price[i] is + # greater than elements after top of stack + S[i] = i+1 if len(st) <= 0 else (i - st[0]) + + # Push this element to stack + st.append(i) + + +# A utility function to print elements of array +def printArray(arr, n): + for i in range(0,n): + print(arr[i],end =" ") + + +# Driver program to test above function +price = [10, 4, 5, 90, 120, 80] +S = [0 for i in range(len(price)+1)] + +# Fill the span values in array S[] +calculateSpan(price, S) + +# Print the calculated span values +printArray(S, len(price)) diff --git a/machine_learning/logistic_regression.py b/machine_learning/logistic_regression.py index 9a60831862da..853de7896af1 100644 --- a/machine_learning/logistic_regression.py +++ b/machine_learning/logistic_regression.py @@ -9,7 +9,7 @@ # importing all the required libraries -''' Implementing logistic regression for classification problem +''' Implementing logistic regression for classification problem Helpful resources : 1.Coursera ML course 2.https://medium.com/@martinpella/logistic-regression-from-scratch-in-python-124c5636b8ac''' import numpy as np @@ -63,10 +63,10 @@ def logistic_reg( if step % 10000 == 0: print(log_likelihood(X,y,weights)) # Print log-likelihood every so often return weights - + if iterations == max_iterations: - print ('Maximum iterations exceeded!') - print ('Minimal cost function J=', J) + print('Maximum iterations exceeded!') + print('Minimal cost function J=', J) converged = True return theta @@ -79,7 +79,7 @@ def logistic_reg( alpha = 0.1 theta = logistic_reg(alpha,X,y,max_iterations=70000,num_steps=30000) - print (theta) + print(theta) def predict_prob(X): diff --git a/maths/quadratic_equations_complex_numbers.py b/maths/quadratic_equations_complex_numbers.py index f05b938fefe9..c3842fee5f96 100644 --- a/maths/quadratic_equations_complex_numbers.py +++ b/maths/quadratic_equations_complex_numbers.py @@ -12,7 +12,7 @@ def QuadraticEquation(a,b,c): if Delta >= 0: Solution1 = (-b + math.sqrt(Delta))/(2*a) Solution2 = (-b - math.sqrt(Delta))/(2*a) - print ("The equation solutions are: ", Solution1," and ", Solution2) + print("The equation solutions are: ", Solution1," and ", Solution2) else: """ Treats cases of Complexes Solutions(i = imaginary unit) @@ -25,7 +25,7 @@ def QuadraticEquation(a,b,c): print("The equation solutions are: (",b,"+",math.sqrt(-Delta),"*i)/2 and (",b,"+",math.sqrt(-Delta),"*i/",2*a) if b == 0: print("The equation solutions are: (",math.sqrt(-Delta),"*i)/2 and ",math.sqrt(-Delta),"*i)/", 2*a) - else: + else: print("Error. Please, coeficient 'a' must not be zero for quadratic equations.") def main(): a = 5 @@ -33,7 +33,7 @@ def main(): c = 1 QuadraticEquation(a,b,c) # The equation solutions are: -0.2 and -1.0 - - + + if __name__ == '__main__': main() diff --git a/other/fischer_yates_shuffle.py b/other/fischer_yates_shuffle.py index d87792f45558..bc2b136344c7 100644 --- a/other/fischer_yates_shuffle.py +++ b/other/fischer_yates_shuffle.py @@ -17,6 +17,6 @@ def FYshuffle(LIST): if __name__ == '__main__': integers = [0,1,2,3,4,5,6,7] strings = ['python', 'says', 'hello', '!'] - print ('Fisher-Yates Shuffle:') - print ('List',integers, strings) - print ('FY Shuffle',FYshuffle(integers), FYshuffle(strings)) + print('Fisher-Yates Shuffle:') + print('List',integers, strings) + print('FY Shuffle',FYshuffle(integers), FYshuffle(strings)) From d21b4cfb4839833b2302da72a646f5a4ecd1bf3b Mon Sep 17 00:00:00 2001 From: QuantumNovice <43876848+QuantumNovice@users.noreply.github.com> Date: Tue, 6 Aug 2019 16:16:30 +0500 Subject: [PATCH 0165/1071] Added pytests to hashes/md5.py (#1100) * Added pytests to sha1.py * tweaking md5 * Added Pytests to hashes/md5.py --- hashes/md5.py | 287 +++++++++++++++++++++++++++----------------------- 1 file changed, 154 insertions(+), 133 deletions(-) diff --git a/hashes/md5.py b/hashes/md5.py index d3f15510874e..7891f2077986 100644 --- a/hashes/md5.py +++ b/hashes/md5.py @@ -1,155 +1,176 @@ from __future__ import print_function import math + def rearrange(bitString32): - """[summary] - Regroups the given binary string. - - Arguments: - bitString32 {[string]} -- [32 bit binary] - - Raises: - ValueError -- [if the given string not are 32 bit binary string] - - Returns: - [string] -- [32 bit binary string] - """ - - if len(bitString32) != 32: - raise ValueError("Need length 32") - newString = "" - for i in [3,2,1,0]: - newString += bitString32[8*i:8*i+8] - return newString + """[summary] + Regroups the given binary string. + + Arguments: + bitString32 {[string]} -- [32 bit binary] + + Raises: + ValueError -- [if the given string not are 32 bit binary string] + + Returns: + [string] -- [32 bit binary string] + >>> rearrange('1234567890abcdfghijklmnopqrstuvw') + 'pqrstuvwhijklmno90abcdfg12345678' + """ + + if len(bitString32) != 32: + raise ValueError("Need length 32") + newString = "" + for i in [3, 2,1,0]: + newString += bitString32[8*i:8*i+8] + return newString + def reformatHex(i): - """[summary] - Converts the given integer into 8-digit hex number. + """[summary] + Converts the given integer into 8-digit hex number. - Arguments: - i {[int]} -- [integer] - """ + Arguments: + i {[int]} -- [integer] + >>> reformatHex(666) + '9a020000' + """ + + hexrep = format(i, '08x') + thing = "" + for i in [3, 2,1,0]: + thing += hexrep[2*i:2*i+2] + return thing - hexrep = format(i,'08x') - thing = "" - for i in [3,2,1,0]: - thing += hexrep[2*i:2*i+2] - return thing def pad(bitString): - """[summary] - Fills up the binary string to a 512 bit binary string - - Arguments: - bitString {[string]} -- [binary string] - - Returns: - [string] -- [binary string] - """ - - startLength = len(bitString) - bitString += '1' - while len(bitString) % 512 != 448: - bitString += '0' - lastPart = format(startLength,'064b') - bitString += rearrange(lastPart[32:]) + rearrange(lastPart[:32]) - return bitString + """[summary] + Fills up the binary string to a 512 bit binary string + + Arguments: + bitString {[string]} -- [binary string] + + Returns: + [string] -- [binary string] + """ + startLength = len(bitString) + bitString += '1' + while len(bitString) % 512 != 448: + bitString += '0' + lastPart = format(startLength, '064b') + bitString += rearrange(lastPart[32:]) + rearrange(lastPart[:32]) + return bitString + def getBlock(bitString): - """[summary] - Iterator: - Returns by each call a list of length 16 with the 32 bit - integer blocks. - - Arguments: - bitString {[string]} -- [binary string >= 512] - """ - - currPos = 0 - while currPos < len(bitString): - currPart = bitString[currPos:currPos+512] - mySplits = [] - for i in range(16): - mySplits.append(int(rearrange(currPart[32*i:32*i+32]),2)) - yield mySplits - currPos += 512 + """[summary] + Iterator: + Returns by each call a list of length 16 with the 32 bit + integer blocks. -def not32(i): - i_str = format(i,'032b') - new_str = '' - for c in i_str: - new_str += '1' if c=='0' else '0' - return int(new_str,2) + Arguments: + bitString {[string]} -- [binary string >= 512] + """ -def sum32(a,b): - return (a + b) % 2**32 + currPos = 0 + while currPos < len(bitString): + currPart = bitString[currPos:currPos+512] + mySplits = [] + for i in range(16): + mySplits.append(int(rearrange(currPart[32*i:32*i+32]), 2)) + yield mySplits + currPos += 512 + + +def not32(i): + ''' + >>> not32(34) + 4294967261 + ''' + i_str = format(i, '032b') + new_str = '' + for c in i_str: + new_str += '1' if c == '0' else '0' + return int(new_str, 2) + +def sum32(a, b): + ''' + + ''' + return (a + b) % 2**32 + +def leftrot32(i, s): + return (i << s) ^ (i >> (32-s)) -def leftrot32(i,s): - return (i << s) ^ (i >> (32-s)) def md5me(testString): - """[summary] - Returns a 32-bit hash code of the string 'testString' - - Arguments: - testString {[string]} -- [message] - """ - - bs ='' - for i in testString: - bs += format(ord(i),'08b') - bs = pad(bs) - - tvals = [int(2**32 * abs(math.sin(i+1))) for i in range(64)] - - a0 = 0x67452301 - b0 = 0xefcdab89 - c0 = 0x98badcfe - d0 = 0x10325476 - - s = [7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, \ - 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20, \ - 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23, \ - 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21 ] - - for m in getBlock(bs): - A = a0 - B = b0 - C = c0 - D = d0 - for i in range(64): - if i <= 15: - #f = (B & C) | (not32(B) & D) - f = D ^ (B & (C ^ D)) - g = i - elif i<= 31: - #f = (D & B) | (not32(D) & C) - f = C ^ (D & (B ^ C)) - g = (5*i+1) % 16 - elif i <= 47: - f = B ^ C ^ D - g = (3*i+5) % 16 - else: - f = C ^ (B | not32(D)) - g = (7*i) % 16 - dtemp = D - D = C - C = B - B = sum32(B,leftrot32((A + f + tvals[i] + m[g]) % 2**32, s[i])) - A = dtemp - a0 = sum32(a0, A) - b0 = sum32(b0, B) - c0 = sum32(c0, C) - d0 = sum32(d0, D) - - digest = reformatHex(a0) + reformatHex(b0) + reformatHex(c0) + reformatHex(d0) - return digest + """[summary] + Returns a 32-bit hash code of the string 'testString' + + Arguments: + testString {[string]} -- [message] + """ + + bs = '' + for i in testString: + bs += format(ord(i), '08b') + bs = pad(bs) + + tvals = [int(2**32 * abs(math.sin(i+1))) for i in range(64)] + + a0 = 0x67452301 + b0 = 0xefcdab89 + c0 = 0x98badcfe + d0 = 0x10325476 + + s = [7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, + 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20, \ + 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23, \ + 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21 ] + + for m in getBlock(bs): + A = a0 + B = b0 + C = c0 + D = d0 + for i in range(64): + if i <= 15: + #f = (B & C) | (not32(B) & D) + f = D ^ (B & (C ^ D)) + g = i + elif i <= 31: + #f = (D & B) | (not32(D) & C) + f = C ^ (D & (B ^ C)) + g = (5*i+1) % 16 + elif i <= 47: + f = B ^ C ^ D + g = (3*i+5) % 16 + else: + f = C ^ (B | not32(D)) + g = (7*i) % 16 + dtemp = D + D = C + C = B + B = sum32(B, leftrot32((A + f + tvals[i] + m[g]) % 2**32, s[i])) + A = dtemp + a0 = sum32(a0, A) + b0 = sum32(b0, B) + c0 = sum32(c0, C) + d0 = sum32(d0, D) + + digest = reformatHex(a0) + reformatHex(b0) + \ + reformatHex(c0) + reformatHex(d0) + return digest + def test(): - assert md5me("") == "d41d8cd98f00b204e9800998ecf8427e" - assert md5me("The quick brown fox jumps over the lazy dog") == "9e107d9d372bb6826bd81d3542a419d6" - print("Success.") + assert md5me("") == "d41d8cd98f00b204e9800998ecf8427e" + assert md5me( + "The quick brown fox jumps over the lazy dog") == "9e107d9d372bb6826bd81d3542a419d6" + print("Success.") if __name__ == "__main__": - test() + test() + import doctest + doctest.testmod() From 762482dc40bdd067e2ab01d94fd2c35857e7de9b Mon Sep 17 00:00:00 2001 From: Harshil Date: Tue, 6 Aug 2019 21:31:03 +0200 Subject: [PATCH 0166/1071] Update closest_pair_of_points.py (#1109) --- divide_and_conquer/closest_pair_of_points.py | 107 ++++++++++--------- 1 file changed, 57 insertions(+), 50 deletions(-) diff --git a/divide_and_conquer/closest_pair_of_points.py b/divide_and_conquer/closest_pair_of_points.py index b6f63396410c..11dac7e0ab2a 100644 --- a/divide_and_conquer/closest_pair_of_points.py +++ b/divide_and_conquer/closest_pair_of_points.py @@ -1,55 +1,54 @@ """ -The algorithm finds distance between closest pair of points +The algorithm finds distance between closest pair of points in the given n points. -Approach used -> Divide and conquer -The points are sorted based on Xco-ords and +Approach used -> Divide and conquer +The points are sorted based on Xco-ords and then based on Yco-ords separately. -And by applying divide and conquer approach, +And by applying divide and conquer approach, minimum distance is obtained recursively. >> Closest points can lie on different sides of partition. -This case handled by forming a strip of points +This case handled by forming a strip of points whose Xco-ords distance is less than closest_pair_dis -from mid-point's Xco-ords. Points sorted based on Yco-ords +from mid-point's Xco-ords. Points sorted based on Yco-ords are used in this step to reduce sorting time. Closest pair distance is found in the strip of points. (closest_in_strip) min(closest_pair_dis, closest_in_strip) would be the final answer. - -Time complexity: O(n * log n) -""" -""" - doctests - >>> euclidean_distance_sqr([1,2],[2,4]) - 5 - >>> dis_between_closest_pair([[1,2],[2,4],[5,7],[8,9],[11,0]],5) - 5 - >>> dis_between_closest_in_strip([[1,2],[2,4],[5,7],[8,9],[11,0]],5) - 85 - >>> points = [(2, 3), (12, 30), (40, 50), (5, 1), (12, 10), (3, 4)] - >>> print("Distance:", closest_pair_of_points(points, len(points))) - "Distance: 1.4142135623730951" +Time complexity: O(n * log n) """ def euclidean_distance_sqr(point1, point2): + """ + >>> euclidean_distance_sqr([1,2],[2,4]) + 5 + """ return (point1[0] - point2[0]) ** 2 + (point1[1] - point2[1]) ** 2 def column_based_sort(array, column = 0): + """ + >>> column_based_sort([(5, 1), (4, 2), (3, 0)], 1) + [(3, 0), (5, 1), (4, 2)] + """ return sorted(array, key = lambda x: x[column]) - + def dis_between_closest_pair(points, points_counts, min_dis = float("inf")): - """ brute force approach to find distance between closest pair points + """ + brute force approach to find distance between closest pair points + + Parameters : + points, points_count, min_dis (list(tuple(int, int)), int, int) - Parameters : - points, points_count, min_dis (list(tuple(int, int)), int, int) - - Returns : + Returns : min_dis (float): distance between closest pair of points + >>> dis_between_closest_pair([[1,2],[2,4],[5,7],[8,9],[11,0]],5) + 5 + """ for i in range(points_counts - 1): @@ -61,14 +60,17 @@ def dis_between_closest_pair(points, points_counts, min_dis = float("inf")): def dis_between_closest_in_strip(points, points_counts, min_dis = float("inf")): - """ closest pair of points in strip + """ + closest pair of points in strip + + Parameters : + points, points_count, min_dis (list(tuple(int, int)), int, int) - Parameters : - points, points_count, min_dis (list(tuple(int, int)), int, int) - - Returns : + Returns : min_dis (float): distance btw closest pair of points in the strip (< min_dis) + >>> dis_between_closest_in_strip([[1,2],[2,4],[5,7],[8,9],[11,0]],5) + 85 """ for i in range(min(6, points_counts - 1), points_counts): @@ -82,29 +84,32 @@ def dis_between_closest_in_strip(points, points_counts, min_dis = float("inf")): def closest_pair_of_points_sqr(points_sorted_on_x, points_sorted_on_y, points_counts): """ divide and conquer approach - Parameters : - points, points_count (list(tuple(int, int)), int) - - Returns : - (float): distance btw closest pair of points + Parameters : + points, points_count (list(tuple(int, int)), int) + + Returns : + (float): distance btw closest pair of points + >>> closest_pair_of_points_sqr([(1, 2), (3, 4)], [(5, 6), (7, 8)], 2) + 8 """ # base case if points_counts <= 3: return dis_between_closest_pair(points_sorted_on_x, points_counts) - + # recursion mid = points_counts//2 - closest_in_left = closest_pair_of_points_sqr(points_sorted_on_x, - points_sorted_on_y[:mid], + closest_in_left = closest_pair_of_points_sqr(points_sorted_on_x, + points_sorted_on_y[:mid], mid) - closest_in_right = closest_pair_of_points_sqr(points_sorted_on_y, - points_sorted_on_y[mid:], + closest_in_right = closest_pair_of_points_sqr(points_sorted_on_y, + points_sorted_on_y[mid:], points_counts - mid) closest_pair_dis = min(closest_in_left, closest_in_right) - - """ cross_strip contains the points, whose Xcoords are at a + + """ + cross_strip contains the points, whose Xcoords are at a distance(< closest_pair_dis) from mid's Xcoord """ @@ -113,21 +118,23 @@ def closest_pair_of_points_sqr(points_sorted_on_x, points_sorted_on_y, points_co if abs(point[0] - points_sorted_on_x[mid][0]) < closest_pair_dis: cross_strip.append(point) - closest_in_strip = dis_between_closest_in_strip(cross_strip, + closest_in_strip = dis_between_closest_in_strip(cross_strip, len(cross_strip), closest_pair_dis) return min(closest_pair_dis, closest_in_strip) - + def closest_pair_of_points(points, points_counts): + """ + >>> closest_pair_of_points([(2, 3), (12, 30)], len([(2, 3), (12, 30)])) + 28.792360097775937 + """ points_sorted_on_x = column_based_sort(points, column = 0) points_sorted_on_y = column_based_sort(points, column = 1) - return (closest_pair_of_points_sqr(points_sorted_on_x, - points_sorted_on_y, + return (closest_pair_of_points_sqr(points_sorted_on_x, + points_sorted_on_y, points_counts)) ** 0.5 if __name__ == "__main__": - points = [(2, 3), (12, 30), (40, 50), (5, 1), (12, 10), (3, 4)] + points = [(2, 3), (12, 30), (40, 50), (5, 1), (12, 10), (3, 4)] print("Distance:", closest_pair_of_points(points, len(points))) - - From 7b5a18453b0abe64350930d675cdfe9bef19c57a Mon Sep 17 00:00:00 2001 From: Christian Clauss Date: Tue, 6 Aug 2019 21:31:45 +0200 Subject: [PATCH 0167/1071] print() is a function just like every other function (#1104) From 7cf3db184320a454e545882408b8c2f561ef0cdb Mon Sep 17 00:00:00 2001 From: Christian Clauss Date: Tue, 6 Aug 2019 21:32:27 +0200 Subject: [PATCH 0168/1071] Add test for QuadraticEquation() (#1107) --- maths/quadratic_equations_complex_numbers.py | 64 ++++++++++---------- 1 file changed, 32 insertions(+), 32 deletions(-) diff --git a/maths/quadratic_equations_complex_numbers.py b/maths/quadratic_equations_complex_numbers.py index c3842fee5f96..8f97508609bf 100644 --- a/maths/quadratic_equations_complex_numbers.py +++ b/maths/quadratic_equations_complex_numbers.py @@ -1,39 +1,39 @@ -import math +from math import sqrt +from typing import Tuple -def QuadraticEquation(a,b,c): + +def QuadraticEquation(a: int, b: int, c: int) -> Tuple[str, str]: + """ + Given the numerical coefficients a, b and c, + prints the solutions for a quadratic equation, for a*x*x + b*x + c. + + >>> QuadraticEquation(a=1, b=3, c=-4) + ('1.0', '-4.0') + >>> QuadraticEquation(5, 6, 1) + ('-0.2', '-1.0') """ - Prints the solutions for a quadratic equation, given the numerical coefficients a, b and c, - for a*x*x + b*x + c. - Ex.: a = 1, b = 3, c = -4 - Solution1 = 1 and Solution2 = -4 + if a == 0: + raise ValueError("Coefficient 'a' must not be zero for quadratic equations.") + delta = b * b - 4 * a * c + if delta >= 0: + return str((-b + sqrt(delta)) / (2 * a)), str((-b - sqrt(delta)) / (2 * a)) """ - Delta = b*b - 4*a*c - if a != 0: - if Delta >= 0: - Solution1 = (-b + math.sqrt(Delta))/(2*a) - Solution2 = (-b - math.sqrt(Delta))/(2*a) - print("The equation solutions are: ", Solution1," and ", Solution2) - else: - """ - Treats cases of Complexes Solutions(i = imaginary unit) - Ex.: a = 5, b = 2, c = 1 - Solution1 = (- 2 + 4.0 *i)/2 and Solution2 = (- 2 + 4.0 *i)/ 10 - """ - if b > 0: - print("The equation solutions are: (-",b,"+",math.sqrt(-Delta),"*i)/2 and (-",b,"+",math.sqrt(-Delta),"*i)/", 2*a) - if b < 0: - print("The equation solutions are: (",b,"+",math.sqrt(-Delta),"*i)/2 and (",b,"+",math.sqrt(-Delta),"*i/",2*a) - if b == 0: - print("The equation solutions are: (",math.sqrt(-Delta),"*i)/2 and ",math.sqrt(-Delta),"*i)/", 2*a) - else: - print("Error. Please, coeficient 'a' must not be zero for quadratic equations.") -def main(): - a = 5 - b = 6 - c = 1 + Treats cases of Complexes Solutions(i = imaginary unit) + Ex.: a = 5, b = 2, c = 1 + Solution1 = (- 2 + 4.0 *i)/2 and Solution2 = (- 2 + 4.0 *i)/ 10 + """ + snd = sqrt(-delta) + if b == 0: + return f"({snd} * i) / 2", f"({snd} * i) / {2 * a}" + b = -abs(b) + return f"({b}+{snd} * i) / 2", f"({b}+{snd} * i) / {2 * a}" + - QuadraticEquation(a,b,c) # The equation solutions are: -0.2 and -1.0 +def main(): + solutions = QuadraticEquation(a=5, b=6, c=1) + print("The equation solutions are: {} and {}".format(*solutions)) + # The equation solutions are: -0.2 and -1.0 -if __name__ == '__main__': +if __name__ == "__main__": main() From 561a41464f6dca6c15656ac2257370410b1e9efa Mon Sep 17 00:00:00 2001 From: Christian Clauss Date: Tue, 6 Aug 2019 21:53:12 +0200 Subject: [PATCH 0169/1071] Travis CI: Run each failing pytest in allow_failures mode (#1087) * Travis CI: Run failing pytest in allow_failures mode * Sync with master * Sync with master --- .travis.yml | 28 +++++++++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index eab55af63492..9abbb0365bc6 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,11 +4,37 @@ python: 3.7 cache: pip before_install: pip install --upgrade pip setuptools install: pip install -r requirements.txt +matrix: + include: + - name: "Main tests" + # The following files currently fail pytests. See issues: #1016, #1044, #1080 + # Here they are run allow_failures mode and when each passes pytest, it can be + # removed BOTH lists below. Complex now but simple once all files pass pytest. + # - env: FILE=pytest file_transfer_protocol/ftp_client_server.py + # before_script: true + # script: pytest ${FILE} --doctest-modules + - env: FILE=pytest file_transfer_protocol/ftp_send_receive.py + before_script: true + script: pytest ${FILE} --doctest-modules + - env: FILE=pytest machine_learning/linear_regression.py + before_script: true + script: pytest ${FILE} --doctest-modules + - env: FILE=pytest machine_learning/perceptron.py + before_script: true + script: pytest ${FILE} --doctest-modules + - env: FILE=pytest machine_learning/random_forest_classification/random_forest_classification.py + before_script: true + script: pytest ${FILE} --doctest-modules + - env: FILE=pytest machine_learning/random_forest_regression/random_forest_regression.py + before_script: true + script: pytest ${FILE} --doctest-modules + allow_failures: + - before_script: true before_script: - black --check . || true - flake8 . --count --select=E9,F401,F63,F7,F82 --show-source --statistics script: - - scripts/validate_filenames.py # no uppercase and no spaces + - scripts/validate_filenames.py # no uppercase, no spaces, in a directory - mypy --ignore-missing-imports . - pytest . --doctest-modules --ignore=file_transfer_protocol/ftp_send_receive.py From 9456e81437bbab7b1781750fed07121c6cd6e301 Mon Sep 17 00:00:00 2001 From: AlexDvorak Date: Wed, 7 Aug 2019 09:44:48 -0400 Subject: [PATCH 0170/1071] Seperate client and server of FTP (#1106) * added sample file to transfer * split client and server into separate files * client and server now work in python2 * server works on python3 * client works on python3 * allow configurable ONE_CONNECTION_ONLY for testing server * allow testing of ftp server + client * use f-strings * removed single letter vars * fixed bad quote marks * clearer file handler names * 'with open() as' syntax * unicode and emojis in the test data * s -> sock * consistent comment spacing * remove closing formalities * swap in and out_file * f-string * if __name__ == '__main__': --- .travis.yml | 1 - file_transfer_protocol/client.py | 23 +++++++++ file_transfer_protocol/ftp_client_server.py | 57 --------------------- file_transfer_protocol/mytext.txt | 6 +++ file_transfer_protocol/server.py | 34 ++++++++++++ 5 files changed, 63 insertions(+), 58 deletions(-) create mode 100644 file_transfer_protocol/client.py delete mode 100644 file_transfer_protocol/ftp_client_server.py create mode 100644 file_transfer_protocol/mytext.txt create mode 100644 file_transfer_protocol/server.py diff --git a/.travis.yml b/.travis.yml index 9abbb0365bc6..2536e72fadff 100644 --- a/.travis.yml +++ b/.travis.yml @@ -38,7 +38,6 @@ script: - mypy --ignore-missing-imports . - pytest . --doctest-modules --ignore=file_transfer_protocol/ftp_send_receive.py - --ignore=file_transfer_protocol/ftp_client_server.py --ignore=machine_learning/linear_regression.py --ignore=machine_learning/perceptron.py --ignore=machine_learning/random_forest_classification/random_forest_classification.py diff --git a/file_transfer_protocol/client.py b/file_transfer_protocol/client.py new file mode 100644 index 000000000000..f404546d7765 --- /dev/null +++ b/file_transfer_protocol/client.py @@ -0,0 +1,23 @@ +if __name__ == '__main__': + import socket # Import socket module + + sock = socket.socket() # Create a socket object + host = socket.gethostname() # Get local machine name + port = 12312 + + sock.connect((host, port)) + sock.send(b'Hello server!') + + with open('Received_file', 'wb') as out_file: + print('File opened') + print('Receiving data...') + while True: + data = sock.recv(1024) + print(f"data={data}") + if not data: + break + out_file.write(data) # Write data to a file + + print('Successfully got the file') + sock.close() + print('Connection closed') diff --git a/file_transfer_protocol/ftp_client_server.py b/file_transfer_protocol/ftp_client_server.py deleted file mode 100644 index 414c336dee9f..000000000000 --- a/file_transfer_protocol/ftp_client_server.py +++ /dev/null @@ -1,57 +0,0 @@ -# server - -import socket # Import socket module - -port = 60000 # Reserve a port for your service. -s = socket.socket() # Create a socket object -host = socket.gethostname() # Get local machine name -s.bind((host, port)) # Bind to the port -s.listen(5) # Now wait for client connection. - -print('Server listening....') - -while True: - conn, addr = s.accept() # Establish connection with client. - print('Got connection from', addr) - data = conn.recv(1024) - print('Server received', repr(data)) - - filename = 'mytext.txt' - with open(filename, 'rb') as f: - in_data = f.read(1024) - while in_data: - conn.send(in_data) - print('Sent ', repr(in_data)) - in_data = f.read(1024) - - print('Done sending') - conn.send('Thank you for connecting') - conn.close() - - -# client side server - -import socket # Import socket module - -s = socket.socket() # Create a socket object -host = socket.gethostname() # Get local machine name -port = 60000 # Reserve a port for your service. - -s.connect((host, port)) -s.send("Hello server!") - -with open('received_file', 'wb') as f: - print('file opened') - while True: - print('receiving data...') - data = s.recv(1024) - print('data=%s', (data)) - if not data: - break - # write data to a file - f.write(data) - -f.close() -print('Successfully get the file') -s.close() -print('connection closed') diff --git a/file_transfer_protocol/mytext.txt b/file_transfer_protocol/mytext.txt new file mode 100644 index 000000000000..54cfa7f766c7 --- /dev/null +++ b/file_transfer_protocol/mytext.txt @@ -0,0 +1,6 @@ +Hello +This is sample data +«küßî» +“ЌύБЇ” +😀😉 +😋 diff --git a/file_transfer_protocol/server.py b/file_transfer_protocol/server.py new file mode 100644 index 000000000000..92fab206c1a1 --- /dev/null +++ b/file_transfer_protocol/server.py @@ -0,0 +1,34 @@ +if __name__ == '__main__': + import socket # Import socket module + + ONE_CONNECTION_ONLY = True # Set this to False if you wish to continuously accept connections + + filename='mytext.txt' + port = 12312 # Reserve a port for your service. + sock = socket.socket() # Create a socket object + host = socket.gethostname() # Get local machine name + sock.bind((host, port)) # Bind to the port + sock.listen(5) # Now wait for client connection. + + print('Server listening....') + + while True: + conn, addr = sock.accept() # Establish connection with client. + print(f"Got connection from {addr}") + data = conn.recv(1024) + print(f"Server received {data}") + + with open(filename,'rb') as in_file: + data = in_file.read(1024) + while (data): + conn.send(data) + print(f"Sent {data!r}") + data = in_file.read(1024) + + print('Done sending') + conn.close() + if ONE_CONNECTION_ONLY: # This is to make sure that the program doesn't hang while testing + break + + sock.shutdown(1) + sock.close() From c92d06bf1f82f21ecb74650b63fd72f07b0a1a70 Mon Sep 17 00:00:00 2001 From: John Law Date: Thu, 8 Aug 2019 01:35:36 +0800 Subject: [PATCH 0171/1071] Delete redundant files (#1115) * Delete 16L' * Delete Q' --- 16L' | 0 Q' | 0 2 files changed, 0 insertions(+), 0 deletions(-) delete mode 100644 16L' delete mode 100644 Q' diff --git a/16L' b/16L' deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/Q' b/Q' deleted file mode 100644 index e69de29bb2d1..000000000000 From 32c0418f635824a14665c58c71bc7c220c509a78 Mon Sep 17 00:00:00 2001 From: Amrit Khera <31596604+AmritK10@users.noreply.github.com> Date: Thu, 8 Aug 2019 01:09:44 +0530 Subject: [PATCH 0172/1071] Infinite loop was fixed. (#1105) * Infinite loop was fixed. Removed issue of unused variables. * Update logistic_regression.py * Update logistic_regression.py * correct spacing according to PEP8 --- machine_learning/logistic_regression.py | 27 +++++++------------------ 1 file changed, 7 insertions(+), 20 deletions(-) diff --git a/machine_learning/logistic_regression.py b/machine_learning/logistic_regression.py index 853de7896af1..b2749f1be260 100644 --- a/machine_learning/logistic_regression.py +++ b/machine_learning/logistic_regression.py @@ -40,34 +40,20 @@ def logistic_reg( alpha, X, y, - num_steps, max_iterations=70000, ): - converged = False - iterations = 0 theta = np.zeros(X.shape[1]) - while not converged: + for iterations in range(max_iterations): z = np.dot(X, theta) h = sigmoid_function(z) gradient = np.dot(X.T, h - y) / y.size - theta = theta - alpha * gradient + theta = theta - alpha * gradient # updating the weights z = np.dot(X, theta) h = sigmoid_function(z) J = cost_function(h, y) - iterations += 1 # update iterations - weights = np.zeros(X.shape[1]) - for step in range(num_steps): - scores = np.dot(X, weights) - predictions = sigmoid_function(scores) - if step % 10000 == 0: - print(log_likelihood(X,y,weights)) # Print log-likelihood every so often - return weights - - if iterations == max_iterations: - print('Maximum iterations exceeded!') - print('Minimal cost function J=', J) - converged = True + if iterations % 100 == 0: + print(f'loss: {J} \t') # printing the loss after every 100 iterations return theta # In[68]: @@ -78,8 +64,8 @@ def logistic_reg( y = (iris.target != 0) * 1 alpha = 0.1 - theta = logistic_reg(alpha,X,y,max_iterations=70000,num_steps=30000) - print(theta) + theta = logistic_reg(alpha,X,y,max_iterations=70000) + print("theta: ",theta) # printing the theta i.e our weights vector def predict_prob(X): @@ -105,3 +91,4 @@ def predict_prob(X): ) plt.legend() + plt.show() From 3ba67c7d2d72b166d7fd0a8a549de46951002f4f Mon Sep 17 00:00:00 2001 From: AlexDvorak Date: Wed, 7 Aug 2019 16:02:31 -0400 Subject: [PATCH 0173/1071] rename non-ftp files (#1116) --- file_transfer_protocol/{client.py => recieve_file.py} | 0 file_transfer_protocol/{server.py => send_file.py} | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename file_transfer_protocol/{client.py => recieve_file.py} (100%) rename file_transfer_protocol/{server.py => send_file.py} (100%) diff --git a/file_transfer_protocol/client.py b/file_transfer_protocol/recieve_file.py similarity index 100% rename from file_transfer_protocol/client.py rename to file_transfer_protocol/recieve_file.py diff --git a/file_transfer_protocol/server.py b/file_transfer_protocol/send_file.py similarity index 100% rename from file_transfer_protocol/server.py rename to file_transfer_protocol/send_file.py From c686cc5863c2ac2530ed1f34bd9ce758bb17945a Mon Sep 17 00:00:00 2001 From: "Marvin M. Michum" Date: Thu, 8 Aug 2019 11:59:15 -0400 Subject: [PATCH 0174/1071] fix outdated fork error (#1117) --- .../filters/median_filter.py | 2 +- .../test_digital_image_processing.py | 62 +++++++++++++++++++ 2 files changed, 63 insertions(+), 1 deletion(-) create mode 100644 digital_image_processing/test_digital_image_processing.py diff --git a/digital_image_processing/filters/median_filter.py b/digital_image_processing/filters/median_filter.py index ed20b1ab7f78..4b21b96b080b 100644 --- a/digital_image_processing/filters/median_filter.py +++ b/digital_image_processing/filters/median_filter.py @@ -15,7 +15,7 @@ def median_filter(gray_img, mask=3): # set image borders bd = int(mask / 2) # copy image size - median_img = zeros_like(gray) + median_img = zeros_like(gray_img) for i in range(bd, gray_img.shape[0] - bd): for j in range(bd, gray_img.shape[1] - bd): # get mask according with mask diff --git a/digital_image_processing/test_digital_image_processing.py b/digital_image_processing/test_digital_image_processing.py new file mode 100644 index 000000000000..0ff9e3333ca8 --- /dev/null +++ b/digital_image_processing/test_digital_image_processing.py @@ -0,0 +1,62 @@ +""" +PyTest's for Digital Image Processing +""" + +import digital_image_processing.edge_detection.canny as canny +import digital_image_processing.filters.gaussian_filter as gg +import digital_image_processing.filters.median_filter as med +import digital_image_processing.filters.sobel_filter as sob +import digital_image_processing.filters.convolve as conv +import digital_image_processing.change_contrast as cc +from cv2 import imread, cvtColor, COLOR_BGR2GRAY +from numpy import array, uint8 +from PIL import Image + +img = imread(r"digital_image_processing/image_data/lena.jpg") +gray = cvtColor(img, COLOR_BGR2GRAY) + +# Test: change_contrast() +def test_change_contrast(): + with Image.open("digital_image_processing/image_data/lena.jpg") as img: + # Work around assertion for response + assert str(cc.change_contrast(img, 110)).startswith( + " Date: Fri, 9 Aug 2019 21:37:16 +0200 Subject: [PATCH 0175/1071] Rename file_transfer and linear_algebra (#1118) * Rename file_transfer and linear_algebra * Rename file_transfer and linear_algebra --- .travis.yml | 4 ++-- {file_transfer_protocol => file_transfer}/ftp_send_receive.py | 0 {file_transfer_protocol => file_transfer}/mytext.txt | 0 {file_transfer_protocol => file_transfer}/recieve_file.py | 0 {file_transfer_protocol => file_transfer}/send_file.py | 0 {linear_algebra_python => linear_algebra}/README.md | 0 {linear_algebra_python => linear_algebra}/src/lib.py | 0 {linear_algebra_python => linear_algebra}/src/tests.py | 0 8 files changed, 2 insertions(+), 2 deletions(-) rename {file_transfer_protocol => file_transfer}/ftp_send_receive.py (100%) rename {file_transfer_protocol => file_transfer}/mytext.txt (100%) rename {file_transfer_protocol => file_transfer}/recieve_file.py (100%) rename {file_transfer_protocol => file_transfer}/send_file.py (100%) rename {linear_algebra_python => linear_algebra}/README.md (100%) rename {linear_algebra_python => linear_algebra}/src/lib.py (100%) rename {linear_algebra_python => linear_algebra}/src/tests.py (100%) diff --git a/.travis.yml b/.travis.yml index 2536e72fadff..532f73f5e895 100644 --- a/.travis.yml +++ b/.travis.yml @@ -13,7 +13,7 @@ matrix: # - env: FILE=pytest file_transfer_protocol/ftp_client_server.py # before_script: true # script: pytest ${FILE} --doctest-modules - - env: FILE=pytest file_transfer_protocol/ftp_send_receive.py + - env: FILE=pytest file_transfer/ftp_send_receive.py before_script: true script: pytest ${FILE} --doctest-modules - env: FILE=pytest machine_learning/linear_regression.py @@ -37,7 +37,7 @@ script: - scripts/validate_filenames.py # no uppercase, no spaces, in a directory - mypy --ignore-missing-imports . - pytest . --doctest-modules - --ignore=file_transfer_protocol/ftp_send_receive.py + --ignore=file_transfer/ftp_send_receive.py --ignore=machine_learning/linear_regression.py --ignore=machine_learning/perceptron.py --ignore=machine_learning/random_forest_classification/random_forest_classification.py diff --git a/file_transfer_protocol/ftp_send_receive.py b/file_transfer/ftp_send_receive.py similarity index 100% rename from file_transfer_protocol/ftp_send_receive.py rename to file_transfer/ftp_send_receive.py diff --git a/file_transfer_protocol/mytext.txt b/file_transfer/mytext.txt similarity index 100% rename from file_transfer_protocol/mytext.txt rename to file_transfer/mytext.txt diff --git a/file_transfer_protocol/recieve_file.py b/file_transfer/recieve_file.py similarity index 100% rename from file_transfer_protocol/recieve_file.py rename to file_transfer/recieve_file.py diff --git a/file_transfer_protocol/send_file.py b/file_transfer/send_file.py similarity index 100% rename from file_transfer_protocol/send_file.py rename to file_transfer/send_file.py diff --git a/linear_algebra_python/README.md b/linear_algebra/README.md similarity index 100% rename from linear_algebra_python/README.md rename to linear_algebra/README.md diff --git a/linear_algebra_python/src/lib.py b/linear_algebra/src/lib.py similarity index 100% rename from linear_algebra_python/src/lib.py rename to linear_algebra/src/lib.py diff --git a/linear_algebra_python/src/tests.py b/linear_algebra/src/tests.py similarity index 100% rename from linear_algebra_python/src/tests.py rename to linear_algebra/src/tests.py From 36684db2780d695add9bd0a4523d73496cb35664 Mon Sep 17 00:00:00 2001 From: Christian Clauss Date: Sat, 10 Aug 2019 22:48:00 +0200 Subject: [PATCH 0176/1071] Travis CI: Add pytest --doctest-modules machine_learning (#1016) * Travis CI: Add pytest --doctest-modules neural_network Fixes #987 ``` neural_network/perceptron.py:123: in sample.insert(i, float(input('value: '))) ../lib/python3.7/site-packages/_pytest/capture.py:693: in read raise IOError("reading from stdin while output is captured") E OSError: reading from stdin while output is captured -------------------------------------------------------------------------------- Captured stdout -------------------------------------------------------------------------------- ('\nEpoch:\n', 399) ------------------------ value: ``` * Adding fix from #1056 -- thanks @QuantumNovice * if __name__ == '__main__': * pytest --ignore=virtualenv # do not test our dependencies --- machine_learning/perceptron.py | 124 ------------------ .../random_forest_classification.py | 8 +- .../random_forest_regression.py | 8 +- neural_network/perceptron.py | 8 +- requirements.txt | 1 + 5 files changed, 15 insertions(+), 134 deletions(-) delete mode 100644 machine_learning/perceptron.py diff --git a/machine_learning/perceptron.py b/machine_learning/perceptron.py deleted file mode 100644 index fe1032aff4af..000000000000 --- a/machine_learning/perceptron.py +++ /dev/null @@ -1,124 +0,0 @@ -''' - - Perceptron - w = w + N * (d(k) - y) * x(k) - - Using perceptron network for oil analysis, - with Measuring of 3 parameters that represent chemical characteristics we can classify the oil, in p1 or p2 - p1 = -1 - p2 = 1 - -''' -from __future__ import print_function - -import random - - -class Perceptron: - def __init__(self, sample, exit, learn_rate=0.01, epoch_number=1000, bias=-1): - self.sample = sample - self.exit = exit - self.learn_rate = learn_rate - self.epoch_number = epoch_number - self.bias = bias - self.number_sample = len(sample) - self.col_sample = len(sample[0]) - self.weight = [] - - def trannig(self): - for sample in self.sample: - sample.insert(0, self.bias) - - for i in range(self.col_sample): - self.weight.append(random.random()) - - self.weight.insert(0, self.bias) - - epoch_count = 0 - - while True: - erro = False - for i in range(self.number_sample): - u = 0 - for j in range(self.col_sample + 1): - u = u + self.weight[j] * self.sample[i][j] - y = self.sign(u) - if y != self.exit[i]: - - for j in range(self.col_sample + 1): - - self.weight[j] = self.weight[j] + self.learn_rate * (self.exit[i] - y) * self.sample[i][j] - erro = True - #print('Epoch: \n',epoch_count) - epoch_count = epoch_count + 1 - # if you want controle the epoch or just by erro - if erro == False: - print(('\nEpoch:\n',epoch_count)) - print('------------------------\n') - #if epoch_count > self.epoch_number or not erro: - break - - def sort(self, sample): - sample.insert(0, self.bias) - u = 0 - for i in range(self.col_sample + 1): - u = u + self.weight[i] * sample[i] - - y = self.sign(u) - - if y == -1: - print(('Sample: ', sample)) - print('classification: P1') - else: - print(('Sample: ', sample)) - print('classification: P2') - - def sign(self, u): - return 1 if u >= 0 else -1 - - -samples = [ - [-0.6508, 0.1097, 4.0009], - [-1.4492, 0.8896, 4.4005], - [2.0850, 0.6876, 12.0710], - [0.2626, 1.1476, 7.7985], - [0.6418, 1.0234, 7.0427], - [0.2569, 0.6730, 8.3265], - [1.1155, 0.6043, 7.4446], - [0.0914, 0.3399, 7.0677], - [0.0121, 0.5256, 4.6316], - [-0.0429, 0.4660, 5.4323], - [0.4340, 0.6870, 8.2287], - [0.2735, 1.0287, 7.1934], - [0.4839, 0.4851, 7.4850], - [0.4089, -0.1267, 5.5019], - [1.4391, 0.1614, 8.5843], - [-0.9115, -0.1973, 2.1962], - [0.3654, 1.0475, 7.4858], - [0.2144, 0.7515, 7.1699], - [0.2013, 1.0014, 6.5489], - [0.6483, 0.2183, 5.8991], - [-0.1147, 0.2242, 7.2435], - [-0.7970, 0.8795, 3.8762], - [-1.0625, 0.6366, 2.4707], - [0.5307, 0.1285, 5.6883], - [-1.2200, 0.7777, 1.7252], - [0.3957, 0.1076, 5.6623], - [-0.1013, 0.5989, 7.1812], - [2.4482, 0.9455, 11.2095], - [2.0149, 0.6192, 10.9263], - [0.2012, 0.2611, 5.4631] - -] - -exit = [-1, -1, -1, 1, 1, -1, 1, -1, 1, 1, -1, 1, -1, -1, -1, -1, 1, 1, 1, 1, -1, 1, 1, 1, 1, -1, -1, 1, -1, 1] - -network = Perceptron(sample=samples, exit = exit, learn_rate=0.01, epoch_number=1000, bias=-1) - -network.trannig() - -while True: - sample = [] - for i in range(3): - sample.insert(i, float(input('value: '))) - network.sort(sample) diff --git a/machine_learning/random_forest_classification/random_forest_classification.py b/machine_learning/random_forest_classification/random_forest_classification.py index d5dde4b13822..81016387ecc7 100644 --- a/machine_learning/random_forest_classification/random_forest_classification.py +++ b/machine_learning/random_forest_classification/random_forest_classification.py @@ -1,17 +1,19 @@ # Random Forest Classification # Importing the libraries +import os import numpy as np import matplotlib.pyplot as plt import pandas as pd # Importing the dataset -dataset = pd.read_csv('Social_Network_Ads.csv') +script_dir = os.path.dirname(os.path.realpath(__file__)) +dataset = pd.read_csv(os.path.join(script_dir, 'Social_Network_Ads.csv')) X = dataset.iloc[:, [2, 3]].values y = dataset.iloc[:, 4].values # Splitting the dataset into the Training set and Test set -from sklearn.cross_validation import train_test_split +from sklearn.model_selection import train_test_split X_train, X_test, y_train, y_test = train_test_split(X, y, test_size = 0.25, random_state = 0) # Feature Scaling @@ -66,4 +68,4 @@ plt.xlabel('Age') plt.ylabel('Estimated Salary') plt.legend() -plt.show() \ No newline at end of file +plt.show() diff --git a/machine_learning/random_forest_regression/random_forest_regression.py b/machine_learning/random_forest_regression/random_forest_regression.py index fce58b1fe283..85ce0676b598 100644 --- a/machine_learning/random_forest_regression/random_forest_regression.py +++ b/machine_learning/random_forest_regression/random_forest_regression.py @@ -1,12 +1,14 @@ # Random Forest Regression # Importing the libraries +import os import numpy as np import matplotlib.pyplot as plt import pandas as pd # Importing the dataset -dataset = pd.read_csv('Position_Salaries.csv') +script_dir = os.path.dirname(os.path.realpath(__file__)) +dataset = pd.read_csv(os.path.join(script_dir, 'Position_Salaries.csv')) X = dataset.iloc[:, 1:2].values y = dataset.iloc[:, 2].values @@ -28,7 +30,7 @@ regressor.fit(X, y) # Predicting a new result -y_pred = regressor.predict(6.5) +y_pred = regressor.predict([[6.5]]) # Visualising the Random Forest Regression results (higher resolution) X_grid = np.arange(min(X), max(X), 0.01) @@ -38,4 +40,4 @@ plt.title('Truth or Bluff (Random Forest Regression)') plt.xlabel('Position level') plt.ylabel('Salary') -plt.show() \ No newline at end of file +plt.show() diff --git a/neural_network/perceptron.py b/neural_network/perceptron.py index 787ea8f73bf1..871eca20273b 100644 --- a/neural_network/perceptron.py +++ b/neural_network/perceptron.py @@ -113,13 +113,13 @@ def sign(self, u): exit = [-1, -1, -1, 1, 1, -1, 1, -1, 1, 1, -1, 1, -1, -1, -1, -1, 1, 1, 1, 1, -1, 1, 1, 1, 1, -1, -1, 1, -1, 1] -if __name__ == '__main__': - network = Perceptron(sample=samples, exit = exit, learn_rate=0.01, epoch_number=1000, bias=-1) +network = Perceptron(sample=samples, exit = exit, learn_rate=0.01, epoch_number=1000, bias=-1) - network.training() +network.training() +if __name__ == '__main__': while True: sample = [] for i in range(3): - sample.insert(i, float(input('value: ').strip())) + sample.insert(i, float(input('value: '))) network.sort(sample) diff --git a/requirements.txt b/requirements.txt index a3e62cf968f7..f5790ad53c30 100644 --- a/requirements.txt +++ b/requirements.txt @@ -7,6 +7,7 @@ opencv-python pandas pillow pytest +requests sklearn sympy tensorflow From 55cea57ffa45ad4ef062363c8ec214e81f8c2448 Mon Sep 17 00:00:00 2001 From: Christian Clauss Date: Sun, 11 Aug 2019 13:00:58 +0200 Subject: [PATCH 0177/1071] Fix tests for file_transfer and perceptron.py (#1121) --- .travis.yml | 31 -------------------------- file_transfer/ftp_send_receive.py | 36 ------------------------------- 2 files changed, 67 deletions(-) delete mode 100644 file_transfer/ftp_send_receive.py diff --git a/.travis.yml b/.travis.yml index 532f73f5e895..f7a9264803f8 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,32 +4,6 @@ python: 3.7 cache: pip before_install: pip install --upgrade pip setuptools install: pip install -r requirements.txt -matrix: - include: - - name: "Main tests" - # The following files currently fail pytests. See issues: #1016, #1044, #1080 - # Here they are run allow_failures mode and when each passes pytest, it can be - # removed BOTH lists below. Complex now but simple once all files pass pytest. - # - env: FILE=pytest file_transfer_protocol/ftp_client_server.py - # before_script: true - # script: pytest ${FILE} --doctest-modules - - env: FILE=pytest file_transfer/ftp_send_receive.py - before_script: true - script: pytest ${FILE} --doctest-modules - - env: FILE=pytest machine_learning/linear_regression.py - before_script: true - script: pytest ${FILE} --doctest-modules - - env: FILE=pytest machine_learning/perceptron.py - before_script: true - script: pytest ${FILE} --doctest-modules - - env: FILE=pytest machine_learning/random_forest_classification/random_forest_classification.py - before_script: true - script: pytest ${FILE} --doctest-modules - - env: FILE=pytest machine_learning/random_forest_regression/random_forest_regression.py - before_script: true - script: pytest ${FILE} --doctest-modules - allow_failures: - - before_script: true before_script: - black --check . || true - flake8 . --count --select=E9,F401,F63,F7,F82 --show-source --statistics @@ -37,11 +11,6 @@ script: - scripts/validate_filenames.py # no uppercase, no spaces, in a directory - mypy --ignore-missing-imports . - pytest . --doctest-modules - --ignore=file_transfer/ftp_send_receive.py - --ignore=machine_learning/linear_regression.py - --ignore=machine_learning/perceptron.py - --ignore=machine_learning/random_forest_classification/random_forest_classification.py - --ignore=machine_learning/random_forest_regression/random_forest_regression.py after_success: - scripts/build_directory_md.py > DIRECTORY.md - cat DIRECTORY.md diff --git a/file_transfer/ftp_send_receive.py b/file_transfer/ftp_send_receive.py deleted file mode 100644 index 6a9819ef3f21..000000000000 --- a/file_transfer/ftp_send_receive.py +++ /dev/null @@ -1,36 +0,0 @@ -""" -File transfer protocol used to send and receive files using FTP server. -Use credentials to provide access to the FTP client - -Note: Do not use root username & password for security reasons -Create a seperate user and provide access to a home directory of the user -Use login id and password of the user created -cwd here stands for current working directory -""" - -from ftplib import FTP -ftp = FTP('xxx.xxx.x.x') # Enter the ip address or the domain name here -ftp.login(user='username', passwd='password') -ftp.cwd('/Enter the directory here/') - -""" -The file which will be received via the FTP server -Enter the location of the file where the file is received -""" - -def ReceiveFile(): - FileName = 'example.txt' """ Enter the location of the file """ - with open(FileName, 'wb') as LocalFile: - ftp.retrbinary('RETR ' + FileName, LocalFile.write, 1024) - ftp.quit() - -""" -The file which will be sent via the FTP server -The file send will be send to the current working directory -""" - -def SendFile(): - FileName = 'example.txt' """ Enter the name of the file """ - with open(FileName, 'rb') as LocalFile: - ftp.storbinary('STOR ' + FileName, LocalFile) - ftp.quit() From 158b319d22e6ffa5399ce42bcfe1a7c968ebaa66 Mon Sep 17 00:00:00 2001 From: Niclas Dern <52120196+nic-dern@users.noreply.github.com> Date: Mon, 12 Aug 2019 09:13:57 +0200 Subject: [PATCH 0178/1071] New linear algebra algorithm (#1122) * Added new algorithm which takes points as an input and outputs a polynom connecting them * Rename Python-Polynom-for-points.py to python-polynom-for-points.py * Update python-polynom-for-points.py * Update python-polynom-for-points.py * Update python-polynom-for-points.py * Update python-polynom-for-points.py * Update python-polynom-for-points.py * Update python-polynom-for-points.py * Update python-polynom-for-points.py * Add doctests and run thru psf/black --- .../src/python-polynom-for-points.py | 130 ++++++++++++++++++ 1 file changed, 130 insertions(+) create mode 100644 linear_algebra/src/python-polynom-for-points.py diff --git a/linear_algebra/src/python-polynom-for-points.py b/linear_algebra/src/python-polynom-for-points.py new file mode 100644 index 000000000000..c884416b6dad --- /dev/null +++ b/linear_algebra/src/python-polynom-for-points.py @@ -0,0 +1,130 @@ +def points_to_polynomial(coordinates): + """ + coordinates is a two dimensional matrix: [[x, y], [x, y], ...] + number of points you want to use + + >>> print(points_to_polynomial([])) + The program cannot work out a fitting polynomial. + >>> print(points_to_polynomial([[]])) + The program cannot work out a fitting polynomial. + >>> print(points_to_polynomial([[1, 0], [2, 0], [3, 0]])) + f(x)=x^2*0.0+x^1*-0.0+x^0*0.0 + >>> print(points_to_polynomial([[1, 1], [2, 1], [3, 1]])) + f(x)=x^2*0.0+x^1*-0.0+x^0*1.0 + >>> print(points_to_polynomial([[1, 3], [2, 3], [3, 3]])) + f(x)=x^2*0.0+x^1*-0.0+x^0*3.0 + >>> print(points_to_polynomial([[1, 1], [2, 2], [3, 3]])) + f(x)=x^2*0.0+x^1*1.0+x^0*0.0 + >>> print(points_to_polynomial([[1, 1], [2, 4], [3, 9]])) + f(x)=x^2*1.0+x^1*-0.0+x^0*0.0 + >>> print(points_to_polynomial([[1, 3], [2, 6], [3, 11]])) + f(x)=x^2*1.0+x^1*-0.0+x^0*2.0 + >>> print(points_to_polynomial([[1, -3], [2, -6], [3, -11]])) + f(x)=x^2*-1.0+x^1*-0.0+x^0*-2.0 + >>> print(points_to_polynomial([[1, 5], [2, 2], [3, 9]])) + f(x)=x^2*5.0+x^1*-18.0+x^0*18.0 + """ + try: + check = 1 + more_check = 0 + d = coordinates[0][0] + for j in range(len(coordinates)): + if j == 0: + continue + if d == coordinates[j][0]: + more_check += 1 + solved = "x=" + str(coordinates[j][0]) + if more_check == len(coordinates) - 1: + check = 2 + break + elif more_check > 0 and more_check != len(coordinates) - 1: + check = 3 + else: + check = 1 + + if len(coordinates) == 1 and coordinates[0][0] == 0: + check = 2 + solved = "x=0" + except Exception: + check = 3 + + x = len(coordinates) + + if check == 1: + count_of_line = 0 + matrix = [] + # put the x and x to the power values in a matrix + while count_of_line < x: + count_in_line = 0 + a = coordinates[count_of_line][0] + count_line = [] + while count_in_line < x: + count_line.append(a ** (x - (count_in_line + 1))) + count_in_line += 1 + matrix.append(count_line) + count_of_line += 1 + + count_of_line = 0 + # put the y values into a vector + vector = [] + while count_of_line < x: + count_in_line = 0 + vector.append(coordinates[count_of_line][1]) + count_of_line += 1 + + count = 0 + + while count < x: + zahlen = 0 + while zahlen < x: + if count == zahlen: + zahlen += 1 + if zahlen == x: + break + bruch = (matrix[zahlen][count]) / (matrix[count][count]) + for counting_columns, item in enumerate(matrix[count]): + # manipulating all the values in the matrix + matrix[zahlen][counting_columns] -= item * bruch + # manipulating the values in the vector + vector[zahlen] -= vector[count] * bruch + zahlen += 1 + count += 1 + + count = 0 + # make solutions + solution = [] + while count < x: + solution.append(vector[count] / matrix[count][count]) + count += 1 + + count = 0 + solved = "f(x)=" + + while count < x: + remove_e = str(solution[count]).split("E") + if len(remove_e) > 1: + solution[count] = remove_e[0] + "*10^" + remove_e[1] + solved += "x^" + str(x - (count + 1)) + "*" + str(solution[count]) + if count + 1 != x: + solved += "+" + count += 1 + + return solved + + elif check == 2: + return solved + else: + return "The program cannot work out a fitting polynomial." + + +if __name__ == "__main__": + print(points_to_polynomial([])) + print(points_to_polynomial([[]])) + print(points_to_polynomial([[1, 0], [2, 0], [3, 0]])) + print(points_to_polynomial([[1, 1], [2, 1], [3, 1]])) + print(points_to_polynomial([[1, 3], [2, 3], [3, 3]])) + print(points_to_polynomial([[1, 1], [2, 2], [3, 3]])) + print(points_to_polynomial([[1, 1], [2, 4], [3, 9]])) + print(points_to_polynomial([[1, 3], [2, 6], [3, 11]])) + print(points_to_polynomial([[1, -3], [2, -6], [3, -11]])) + print(points_to_polynomial([[1, 5], [2, 2], [3, 9]])) From 4fea48072ae88c19f5133a2303e6707eddf96700 Mon Sep 17 00:00:00 2001 From: Christian Clauss Date: Mon, 12 Aug 2019 17:59:59 +0200 Subject: [PATCH 0179/1071] Add type hints to binary_tree_traversals.py (#1123) --- traversals/binary_tree_traversals.py | 103 +++++++++++++-------------- 1 file changed, 50 insertions(+), 53 deletions(-) diff --git a/traversals/binary_tree_traversals.py b/traversals/binary_tree_traversals.py index 393664579146..7fd9f7111844 100644 --- a/traversals/binary_tree_traversals.py +++ b/traversals/binary_tree_traversals.py @@ -1,14 +1,8 @@ """ This is pure python implementation of tree traversal algorithms """ -from __future__ import print_function - import queue - -try: - raw_input # Python 2 -except NameError: - raw_input = input # Python 3 +from typing import List class TreeNode: @@ -20,35 +14,31 @@ def __init__(self, data): def build_tree(): print("\n********Press N to stop entering at any point of time********\n") - print("Enter the value of the root node: ", end="") - check = raw_input().strip().lower() - if check == 'n': + check = input("Enter the value of the root node: ").strip().lower() or "n" + if check == "n": return None - data = int(check) - q = queue.Queue() - tree_node = TreeNode(data) + q: queue.Queue = queue.Queue() + tree_node = TreeNode(int(check)) q.put(tree_node) while not q.empty(): node_found = q.get() - print("Enter the left node of %s: " % node_found.data, end="") - check = raw_input().strip().lower() - if check == 'n': + msg = "Enter the left node of %s: " % node_found.data + check = input(msg).strip().lower() or "n" + if check == "n": return tree_node - left_data = int(check) - left_node = TreeNode(left_data) + left_node = TreeNode(int(check)) node_found.left = left_node q.put(left_node) - print("Enter the right node of %s: " % node_found.data, end="") - check = raw_input().strip().lower() - if check == 'n': + msg = "Enter the right node of %s: " % node_found.data + check = input(msg).strip().lower() or "n" + if check == "n": return tree_node - right_data = int(check) - right_node = TreeNode(right_data) + right_node = TreeNode(int(check)) node_found.right = right_node q.put(right_node) -def pre_order(node): +def pre_order(node: TreeNode) -> None: if not isinstance(node, TreeNode) or not node: return print(node.data, end=" ") @@ -56,7 +46,7 @@ def pre_order(node): pre_order(node.right) -def in_order(node): +def in_order(node: TreeNode) -> None: if not isinstance(node, TreeNode) or not node: return in_order(node.left) @@ -64,7 +54,7 @@ def in_order(node): in_order(node.right) -def post_order(node): +def post_order(node: TreeNode) -> None: if not isinstance(node, TreeNode) or not node: return post_order(node.left) @@ -72,10 +62,10 @@ def post_order(node): print(node.data, end=" ") -def level_order(node): +def level_order(node: TreeNode) -> None: if not isinstance(node, TreeNode) or not node: return - q = queue.Queue() + q: queue.Queue = queue.Queue() q.put(node) while not q.empty(): node_dequeued = q.get() @@ -86,10 +76,10 @@ def level_order(node): q.put(node_dequeued.right) -def level_order_actual(node): +def level_order_actual(node: TreeNode) -> None: if not isinstance(node, TreeNode) or not node: return - q = queue.Queue() + q: queue.Queue = queue.Queue() q.put(node) while not q.empty(): list = [] @@ -106,10 +96,10 @@ def level_order_actual(node): # iteration version -def pre_order_iter(node): +def pre_order_iter(node: TreeNode) -> None: if not isinstance(node, TreeNode) or not node: return - stack = [] + stack: List[TreeNode] = [] n = node while n or stack: while n: # start from root node, find its left child @@ -122,10 +112,10 @@ def pre_order_iter(node): n = n.right -def in_order_iter(node): +def in_order_iter(node: TreeNode) -> None: if not isinstance(node, TreeNode) or not node: return - stack = [] + stack: List[TreeNode] = [] n = node while n or stack: while n: @@ -136,7 +126,7 @@ def in_order_iter(node): n = n.right -def post_order_iter(node): +def post_order_iter(node: TreeNode) -> None: if not isinstance(node, TreeNode) or not node: return stack1, stack2 = [], [] @@ -153,38 +143,45 @@ def post_order_iter(node): print(stack2.pop().data, end=" ") -if __name__ == '__main__': - print("\n********* Binary Tree Traversals ************\n") +def prompt(s: str = "", width=50, char="*") -> str: + if not s: + return "\n" + width * char + left, extra = divmod(width - len(s) - 2, 2) + return f"{left * char} {s} {(left + extra) * char}" + + +if __name__ == "__main__": + print(prompt("Binary Tree Traversals")) node = build_tree() - print("\n********* Pre Order Traversal ************") + print(prompt("Pre Order Traversal")) pre_order(node) - print("\n******************************************\n") + print(prompt() + "\n") - print("\n********* In Order Traversal ************") + print(prompt("In Order Traversal")) in_order(node) - print("\n******************************************\n") + print(prompt() + "\n") - print("\n********* Post Order Traversal ************") + print(prompt("Post Order Traversal")) post_order(node) - print("\n******************************************\n") + print(prompt() + "\n") - print("\n********* Level Order Traversal ************") + print(prompt("Level Order Traversal")) level_order(node) - print("\n******************************************\n") + print(prompt() + "\n") - print("\n********* Actual Level Order Traversal ************") + print(prompt("Actual Level Order Traversal")) level_order_actual(node) - print("\n******************************************\n") + print("*" * 50 + "\n") - print("\n********* Pre Order Traversal - Iteration Version ************") + print(prompt("Pre Order Traversal - Iteration Version")) pre_order_iter(node) - print("\n******************************************\n") + print(prompt() + "\n") - print("\n********* In Order Traversal - Iteration Version ************") + print(prompt("In Order Traversal - Iteration Version")) in_order_iter(node) - print("\n******************************************\n") + print(prompt() + "\n") - print("\n********* Post Order Traversal - Iteration Version ************") + print(prompt("Post Order Traversal - Iteration Version")) post_order_iter(node) - print("\n******************************************\n") + print(prompt()) From c74fd0c9bf984613d9087a793f832d25d19e855f Mon Sep 17 00:00:00 2001 From: Christian Clauss Date: Tue, 13 Aug 2019 11:50:13 +0200 Subject: [PATCH 0180/1071] Add maths/test_prime_check.py (#1125) * Add maths/test_prime_check.py * Add comments on why this file is required --- maths/test_prime_check.py | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 maths/test_prime_check.py diff --git a/maths/test_prime_check.py b/maths/test_prime_check.py new file mode 100644 index 000000000000..b6389684af9e --- /dev/null +++ b/maths/test_prime_check.py @@ -0,0 +1,8 @@ +""" +Minimalist file that allows pytest to find and run the Test unittest. For details, see: +http://doc.pytest.org/en/latest/goodpractices.html#conventions-for-python-test-discovery +""" + +from .prime_check import Test + +Test() From dc2b575274ad4920a0e3f2303a80796daafce84f Mon Sep 17 00:00:00 2001 From: Christian Clauss Date: Tue, 13 Aug 2019 11:59:49 +0200 Subject: [PATCH 0181/1071] Add doctests to networking_flow/minimum_cut.py (#1126) --- networking_flow/minimum_cut.py | 57 +++++++++++++++++++--------------- 1 file changed, 32 insertions(+), 25 deletions(-) diff --git a/networking_flow/minimum_cut.py b/networking_flow/minimum_cut.py index 8ad6e03b00c6..7773df72f8f0 100644 --- a/networking_flow/minimum_cut.py +++ b/networking_flow/minimum_cut.py @@ -1,12 +1,21 @@ # Minimum cut on Ford_Fulkerson algorithm. - + +test_graph = [ + [0, 16, 13, 0, 0, 0], + [0, 0, 10, 12, 0, 0], + [0, 4, 0, 0, 14, 0], + [0, 0, 9, 0, 0, 20], + [0, 0, 0, 7, 0, 4], + [0, 0, 0, 0, 0, 0], +] + + def BFS(graph, s, t, parent): # Return True if there is node that has not iterated. - visited = [False]*len(graph) - queue=[] - queue.append(s) + visited = [False] * len(graph) + queue = [s] visited[s] = True - + while queue: u = queue.pop(0) for ind in range(len(graph[u])): @@ -16,26 +25,30 @@ def BFS(graph, s, t, parent): parent[ind] = u return True if visited[t] else False - + + def mincut(graph, source, sink): - # This array is filled by BFS and to store path - parent = [-1]*(len(graph)) - max_flow = 0 + """This array is filled by BFS and to store path + >>> mincut(test_graph, source=0, sink=5) + [(1, 3), (4, 3), (4, 5)] + """ + parent = [-1] * (len(graph)) + max_flow = 0 res = [] - temp = [i[:] for i in graph] # Record orignial cut, copy. - while BFS(graph, source, sink, parent) : + temp = [i[:] for i in graph] # Record orignial cut, copy. + while BFS(graph, source, sink, parent): path_flow = float("Inf") s = sink - while(s != source): + while s != source: # Find the minimum value in select path - path_flow = min (path_flow, graph[parent[s]][s]) + path_flow = min(path_flow, graph[parent[s]][s]) s = parent[s] - max_flow += path_flow + max_flow += path_flow v = sink - - while(v != source): + + while v != source: u = parent[v] graph[u][v] -= path_flow graph[v][u] += path_flow @@ -44,16 +57,10 @@ def mincut(graph, source, sink): for i in range(len(graph)): for j in range(len(graph[0])): if graph[i][j] == 0 and temp[i][j] > 0: - res.append((i,j)) + res.append((i, j)) return res -graph = [[0, 16, 13, 0, 0, 0], - [0, 0, 10 ,12, 0, 0], - [0, 4, 0, 0, 14, 0], - [0, 0, 9, 0, 0, 20], - [0, 0, 0, 7, 0, 4], - [0, 0, 0, 0, 0, 0]] -source, sink = 0, 5 -print(mincut(graph, source, sink)) \ No newline at end of file +if __name__ == "__main__": + print(mincut(test_graph, source=0, sink=5)) From f3c0b132bcfcf2773734e9418153af01f37475a2 Mon Sep 17 00:00:00 2001 From: adith bharadwaj Date: Tue, 13 Aug 2019 19:21:06 +0530 Subject: [PATCH 0182/1071] Added sudoku solving program in backtracking algorithms (#1128) * Added sudoku solver in backtracking * Added sudoku solver program * Added sudoku solver * Added sudoku solver * Format with black, add doctests, cleanup main --- backtracking/sudoku.py | 151 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 151 insertions(+) create mode 100644 backtracking/sudoku.py diff --git a/backtracking/sudoku.py b/backtracking/sudoku.py new file mode 100644 index 000000000000..b33351fd4911 --- /dev/null +++ b/backtracking/sudoku.py @@ -0,0 +1,151 @@ +""" + + Given a partially filled 9×9 2D array, the objective is to fill a 9×9 + square grid with digits numbered 1 to 9, so that every row, column, and + and each of the nine 3×3 sub-grids contains all of the digits. + + This can be solved using Backtracking and is similar to n-queens. + We check to see if a cell is safe or not and recursively call the + function on the next column to see if it returns True. if yes, we + have solved the puzzle. else, we backtrack and place another number + in that cell and repeat this process. + +""" + +# assigning initial values to the grid +initial_grid = [ + [3, 0, 6, 5, 0, 8, 4, 0, 0], + [5, 2, 0, 0, 0, 0, 0, 0, 0], + [0, 8, 7, 0, 0, 0, 0, 3, 1], + [0, 0, 3, 0, 1, 0, 0, 8, 0], + [9, 0, 0, 8, 6, 3, 0, 0, 5], + [0, 5, 0, 0, 9, 0, 6, 0, 0], + [1, 3, 0, 0, 0, 0, 2, 5, 0], + [0, 0, 0, 0, 0, 0, 0, 7, 4], + [0, 0, 5, 2, 0, 6, 3, 0, 0], +] +# a grid with no solution +no_solution = [ + [5, 0, 6, 5, 0, 8, 4, 0, 3], + [5, 2, 0, 0, 0, 0, 0, 0, 2], + [1, 8, 7, 0, 0, 0, 0, 3, 1], + [0, 0, 3, 0, 1, 0, 0, 8, 0], + [9, 0, 0, 8, 6, 3, 0, 0, 5], + [0, 5, 0, 0, 9, 0, 6, 0, 0], + [1, 3, 0, 0, 0, 0, 2, 5, 0], + [0, 0, 0, 0, 0, 0, 0, 7, 4], + [0, 0, 5, 2, 0, 6, 3, 0, 0], +] + + +def is_safe(grid, row, column, n): + """ + This function checks the grid to see if each row, + column, and the 3x3 subgrids contain the digit 'n'. + It returns False if it is not 'safe' (a duplicate digit + is found) else returns True if it is 'safe' + + """ + + for i in range(9): + if grid[row][i] == n or grid[i][column] == n: + return False + + for i in range(3): + for j in range(3): + if grid[(row - row % 3) + i][(column - column % 3) + j] == n: + return False + + return True + + +def is_completed(grid): + """ + This function checks if the puzzle is completed or not. + it is completed when all the cells are assigned with a number(not zero) + and There is no repeating number in any column, row or 3x3 subgrid. + + """ + + for row in grid: + for cell in row: + if cell == 0: + return False + + return True + + +def find_empty_location(grid): + """ + This function finds an empty location so that we can assign a number + for that particular row and column. + + """ + + for i in range(9): + for j in range(9): + if grid[i][j] == 0: + return i, j + + +def sudoku(grid): + """ + Takes a partially filled-in grid and attempts to assign values to + all unassigned locations in such a way to meet the requirements + for Sudoku solution (non-duplication across rows, columns, and boxes) + + >>> sudoku(initial_grid) # doctest: +NORMALIZE_WHITESPACE + [[3, 1, 6, 5, 7, 8, 4, 9, 2], + [5, 2, 9, 1, 3, 4, 7, 6, 8], + [4, 8, 7, 6, 2, 9, 5, 3, 1], + [2, 6, 3, 4, 1, 5, 9, 8, 7], + [9, 7, 4, 8, 6, 3, 1, 2, 5], + [8, 5, 1, 7, 9, 2, 6, 4, 3], + [1, 3, 8, 9, 4, 7, 2, 5, 6], + [6, 9, 2, 3, 5, 1, 8, 7, 4], + [7, 4, 5, 2, 8, 6, 3, 1, 9]] + >>> sudoku(no_solution) + False + """ + + if is_completed(grid): + return grid + + row, column = find_empty_location(grid) + + for digit in range(1, 10): + if is_safe(grid, row, column, digit): + grid[row][column] = digit + + if sudoku(grid): + return grid + + grid[row][column] = 0 + + return False + + +def print_solution(grid): + """ + A function to print the solution in the form + of a 9x9 grid + + """ + + for row in grid: + for cell in row: + print(cell, end=" ") + print() + + +if __name__ == "__main__": + + # make a copy of grid so that you can compare with the unmodified grid + for grid in (initial_grid, no_solution): + grid = list(map(list, grid)) + solution = sudoku(grid) + if solution: + print("grid after solving:") + print_solution(solution) + else: + print("Cannot find a solution.") From 8eab2f17f4e3378995b8ccdc8f547784e046fc31 Mon Sep 17 00:00:00 2001 From: Alok Shukla Date: Tue, 13 Aug 2019 22:46:11 +0530 Subject: [PATCH 0183/1071] Solution for Problem Euler 56 (#1131) * Solution for Euler 56 * Adding Type and Doctest as per guideline * removing unused import * correcting the way type check works --- project_euler/problem_56/__init__.py | 0 project_euler/problem_56/sol1.py | 26 ++++++++++++++++++++++++++ 2 files changed, 26 insertions(+) create mode 100644 project_euler/problem_56/__init__.py create mode 100644 project_euler/problem_56/sol1.py diff --git a/project_euler/problem_56/__init__.py b/project_euler/problem_56/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/project_euler/problem_56/sol1.py b/project_euler/problem_56/sol1.py new file mode 100644 index 000000000000..194a7a37af43 --- /dev/null +++ b/project_euler/problem_56/sol1.py @@ -0,0 +1,26 @@ + + +def maximum_digital_sum(a: int, b: int) -> int: + """ + Considering natural numbers of the form, a**b, where a, b < 100, + what is the maximum digital sum? + :param a: + :param b: + :return: + >>> maximum_digital_sum(10,10) + 45 + + >>> maximum_digital_sum(100,100) + 972 + + >>> maximum_digital_sum(100,200) + 1872 + """ + + # RETURN the MAXIMUM from the list of SUMs of the list of INT converted from STR of BASE raised to the POWER + return max([sum([int(x) for x in str(base**power)]) for base in range(a) for power in range(b)]) + +#Tests +if __name__ == "__main__": + import doctest + doctest.testmod() From 27205d454877e76fc753feb3dfd528d5d29b93f2 Mon Sep 17 00:00:00 2001 From: Christian Clauss Date: Wed, 14 Aug 2019 23:24:58 +0200 Subject: [PATCH 0184/1071] Update DIRECTORY.md (#1129) --- DIRECTORY.md | 430 +++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 420 insertions(+), 10 deletions(-) diff --git a/DIRECTORY.md b/DIRECTORY.md index d97791bb5dd3..80bf64ef4c30 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -1,395 +1,805 @@ ## Arithmetic Analysis + * [bisection](https://github.com/TheAlgorithms/Python/blob/master/arithmetic_analysis/bisection.py) + * [in static equilibrium](https://github.com/TheAlgorithms/Python/blob/master/arithmetic_analysis/in_static_equilibrium.py) + * [intersection](https://github.com/TheAlgorithms/Python/blob/master/arithmetic_analysis/intersection.py) + * [lu decomposition](https://github.com/TheAlgorithms/Python/blob/master/arithmetic_analysis/lu_decomposition.py) + * [newton method](https://github.com/TheAlgorithms/Python/blob/master/arithmetic_analysis/newton_method.py) + * [newton raphson method](https://github.com/TheAlgorithms/Python/blob/master/arithmetic_analysis/newton_raphson_method.py) + ## Backtracking + * [all combinations](https://github.com/TheAlgorithms/Python/blob/master/backtracking/all_combinations.py) + * [all permutations](https://github.com/TheAlgorithms/Python/blob/master/backtracking/all_permutations.py) + * [all subsequences](https://github.com/TheAlgorithms/Python/blob/master/backtracking/all_subsequences.py) + * [minimax](https://github.com/TheAlgorithms/Python/blob/master/backtracking/minimax.py) + * [n queens](https://github.com/TheAlgorithms/Python/blob/master/backtracking/n_queens.py) + + * [sudoku](https://github.com/TheAlgorithms/Python/blob/master/backtracking/sudoku.py) + * [sum of subsets](https://github.com/TheAlgorithms/Python/blob/master/backtracking/sum_of_subsets.py) + ## Boolean Algebra + * [quine mc cluskey](https://github.com/TheAlgorithms/Python/blob/master/boolean_algebra/quine_mc_cluskey.py) + ## Ciphers + * [affine cipher](https://github.com/TheAlgorithms/Python/blob/master/ciphers/affine_cipher.py) + * [atbash](https://github.com/TheAlgorithms/Python/blob/master/ciphers/atbash.py) + * [base16](https://github.com/TheAlgorithms/Python/blob/master/ciphers/base16.py) + * [base32](https://github.com/TheAlgorithms/Python/blob/master/ciphers/base32.py) + * [base64 cipher](https://github.com/TheAlgorithms/Python/blob/master/ciphers/base64_cipher.py) + * [base85](https://github.com/TheAlgorithms/Python/blob/master/ciphers/base85.py) + * [brute force caesar cipher](https://github.com/TheAlgorithms/Python/blob/master/ciphers/brute_force_caesar_cipher.py) + * [caesar cipher](https://github.com/TheAlgorithms/Python/blob/master/ciphers/caesar_cipher.py) + * [cryptomath module](https://github.com/TheAlgorithms/Python/blob/master/ciphers/cryptomath_module.py) + * [elgamal key generator](https://github.com/TheAlgorithms/Python/blob/master/ciphers/elgamal_key_generator.py) + * [hill cipher](https://github.com/TheAlgorithms/Python/blob/master/ciphers/hill_cipher.py) + * [morse code implementation](https://github.com/TheAlgorithms/Python/blob/master/ciphers/morse_code_implementation.py) + * [onepad cipher](https://github.com/TheAlgorithms/Python/blob/master/ciphers/onepad_cipher.py) + * [playfair cipher](https://github.com/TheAlgorithms/Python/blob/master/ciphers/playfair_cipher.py) + * [rabin miller](https://github.com/TheAlgorithms/Python/blob/master/ciphers/rabin_miller.py) + * [rot13](https://github.com/TheAlgorithms/Python/blob/master/ciphers/rot13.py) + * [rsa cipher](https://github.com/TheAlgorithms/Python/blob/master/ciphers/rsa_cipher.py) + * [rsa key generator](https://github.com/TheAlgorithms/Python/blob/master/ciphers/rsa_key_generator.py) + * [simple substitution cipher](https://github.com/TheAlgorithms/Python/blob/master/ciphers/simple_substitution_cipher.py) + * [trafid cipher](https://github.com/TheAlgorithms/Python/blob/master/ciphers/trafid_cipher.py) + * [transposition cipher](https://github.com/TheAlgorithms/Python/blob/master/ciphers/transposition_cipher.py) + * [transposition cipher encrypt decrypt file](https://github.com/TheAlgorithms/Python/blob/master/ciphers/transposition_cipher_encrypt_decrypt_file.py) + * [vigenere cipher](https://github.com/TheAlgorithms/Python/blob/master/ciphers/vigenere_cipher.py) + * [xor cipher](https://github.com/TheAlgorithms/Python/blob/master/ciphers/xor_cipher.py) + ## Compression + * [burrows wheeler](https://github.com/TheAlgorithms/Python/blob/master/compression/burrows_wheeler.py) + * [huffman](https://github.com/TheAlgorithms/Python/blob/master/compression/huffman.py) + * [peak signal to noise ratio](https://github.com/TheAlgorithms/Python/blob/master/compression/peak_signal_to_noise_ratio.py) + ## Conversions + * [decimal to binary](https://github.com/TheAlgorithms/Python/blob/master/conversions/decimal_to_binary.py) + * [decimal to hexadecimal](https://github.com/TheAlgorithms/Python/blob/master/conversions/decimal_to_hexadecimal.py) + * [decimal to octal](https://github.com/TheAlgorithms/Python/blob/master/conversions/decimal_to_octal.py) + ## Data Structures + * Binary Tree + * [avl tree](https://github.com/TheAlgorithms/Python/blob/master/data_structures/binary_tree/avl_tree.py) + * [basic binary tree](https://github.com/TheAlgorithms/Python/blob/master/data_structures/binary_tree/basic_binary_tree.py) + * [binary search tree](https://github.com/TheAlgorithms/Python/blob/master/data_structures/binary_tree/binary_search_tree.py) + * [fenwick tree](https://github.com/TheAlgorithms/Python/blob/master/data_structures/binary_tree/fenwick_tree.py) + * [lazy segment tree](https://github.com/TheAlgorithms/Python/blob/master/data_structures/binary_tree/lazy_segment_tree.py) + * [lca](https://github.com/TheAlgorithms/Python/blob/master/data_structures/binary_tree/lca.py) + * [red black tree](https://github.com/TheAlgorithms/Python/blob/master/data_structures/binary_tree/red_black_tree.py) + * [segment tree](https://github.com/TheAlgorithms/Python/blob/master/data_structures/binary_tree/segment_tree.py) + * [treap](https://github.com/TheAlgorithms/Python/blob/master/data_structures/binary_tree/treap.py) + * Hashing + * [double hash](https://github.com/TheAlgorithms/Python/blob/master/data_structures/hashing/double_hash.py) + * [hash table](https://github.com/TheAlgorithms/Python/blob/master/data_structures/hashing/hash_table.py) + * [hash table with linked list](https://github.com/TheAlgorithms/Python/blob/master/data_structures/hashing/hash_table_with_linked_list.py) + * Number Theory + * [prime numbers](https://github.com/TheAlgorithms/Python/blob/master/data_structures/hashing/number_theory/prime_numbers.py) + * [quadratic probing](https://github.com/TheAlgorithms/Python/blob/master/data_structures/hashing/quadratic_probing.py) + * Heap + * [heap](https://github.com/TheAlgorithms/Python/blob/master/data_structures/heap/heap.py) + * Linked List + * [doubly linked list](https://github.com/TheAlgorithms/Python/blob/master/data_structures/linked_list/doubly_linked_list.py) + * [is palindrome](https://github.com/TheAlgorithms/Python/blob/master/data_structures/linked_list/is_palindrome.py) + * [singly linked list](https://github.com/TheAlgorithms/Python/blob/master/data_structures/linked_list/singly_linked_list.py) + * [swap nodes](https://github.com/TheAlgorithms/Python/blob/master/data_structures/linked_list/swap_nodes.py) + * Queue + * [double ended queue](https://github.com/TheAlgorithms/Python/blob/master/data_structures/queue/double_ended_queue.py) + * [queue on list](https://github.com/TheAlgorithms/Python/blob/master/data_structures/queue/queue_on_list.py) + * [queue on pseudo stack](https://github.com/TheAlgorithms/Python/blob/master/data_structures/queue/queue_on_pseudo_stack.py) + * Stacks + * [balanced parentheses](https://github.com/TheAlgorithms/Python/blob/master/data_structures/stacks/balanced_parentheses.py) + * [infix to postfix conversion](https://github.com/TheAlgorithms/Python/blob/master/data_structures/stacks/infix_to_postfix_conversion.py) + * [infix to prefix conversion](https://github.com/TheAlgorithms/Python/blob/master/data_structures/stacks/infix_to_prefix_conversion.py) + * [next greater element](https://github.com/TheAlgorithms/Python/blob/master/data_structures/stacks/next_greater_element.py) + * [postfix evaluation](https://github.com/TheAlgorithms/Python/blob/master/data_structures/stacks/postfix_evaluation.py) + * [stack](https://github.com/TheAlgorithms/Python/blob/master/data_structures/stacks/stack.py) + * [stock span problem](https://github.com/TheAlgorithms/Python/blob/master/data_structures/stacks/stock_span_problem.py) + * Trie + * [trie](https://github.com/TheAlgorithms/Python/blob/master/data_structures/trie/trie.py) + ## Digital Image Processing + * [change contrast](https://github.com/TheAlgorithms/Python/blob/master/digital_image_processing/change_contrast.py) + * Edge Detection + * [canny](https://github.com/TheAlgorithms/Python/blob/master/digital_image_processing/edge_detection/canny.py) + * Filters + * [convolve](https://github.com/TheAlgorithms/Python/blob/master/digital_image_processing/filters/convolve.py) + * [gaussian filter](https://github.com/TheAlgorithms/Python/blob/master/digital_image_processing/filters/gaussian_filter.py) + * [median filter](https://github.com/TheAlgorithms/Python/blob/master/digital_image_processing/filters/median_filter.py) + * [sobel filter](https://github.com/TheAlgorithms/Python/blob/master/digital_image_processing/filters/sobel_filter.py) + + * [test digital image processing](https://github.com/TheAlgorithms/Python/blob/master/digital_image_processing/test_digital_image_processing.py) + ## Divide And Conquer + * [closest pair of points](https://github.com/TheAlgorithms/Python/blob/master/divide_and_conquer/closest_pair_of_points.py) + * [max subarray sum](https://github.com/TheAlgorithms/Python/blob/master/divide_and_conquer/max_subarray_sum.py) + ## Dynamic Programming + * [abbreviation](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/abbreviation.py) + * [bitmask](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/bitmask.py) + * [climbing stairs](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/climbing_stairs.py) + * [coin change](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/coin_change.py) + * [edit distance](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/edit_distance.py) + * [factorial](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/factorial.py) + * [fast fibonacci](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/fast_fibonacci.py) + * [fibonacci](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/fibonacci.py) + * [floyd warshall](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/floyd_warshall.py) + * [fractional knapsack](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/fractional_knapsack.py) + * [integer partition](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/integer_partition.py) + * [k means clustering tensorflow](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/k_means_clustering_tensorflow.py) + * [knapsack](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/knapsack.py) + * [longest common subsequence](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/longest_common_subsequence.py) + * [longest increasing subsequence](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/longest_increasing_subsequence.py) + * [longest increasing subsequence o(nlogn)](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/longest_increasing_subsequence_o(nlogn).py) + * [longest sub array](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/longest_sub_array.py) + * [matrix chain order](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/matrix_chain_order.py) + * [max sub array](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/max_sub_array.py) + * [minimum partition](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/minimum_partition.py) + * [rod cutting](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/rod_cutting.py) + * [subset generation](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/subset_generation.py) + * [sum of subset](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/sum_of_subset.py) -## File Transfer Protocol - * [ftp client server](https://github.com/TheAlgorithms/Python/blob/master/file_transfer_protocol/ftp_client_server.py) - * [ftp send receive](https://github.com/TheAlgorithms/Python/blob/master/file_transfer_protocol/ftp_send_receive.py) + +## File Transfer + + * [recieve file](https://github.com/TheAlgorithms/Python/blob/master/file_transfer/recieve_file.py) + + * [send file](https://github.com/TheAlgorithms/Python/blob/master/file_transfer/send_file.py) + ## Graphs + * [a star](https://github.com/TheAlgorithms/Python/blob/master/graphs/a_star.py) + * [articulation points](https://github.com/TheAlgorithms/Python/blob/master/graphs/articulation_points.py) + * [basic graphs](https://github.com/TheAlgorithms/Python/blob/master/graphs/basic_graphs.py) + * [bellman ford](https://github.com/TheAlgorithms/Python/blob/master/graphs/bellman_ford.py) + * [bfs](https://github.com/TheAlgorithms/Python/blob/master/graphs/bfs.py) + * [bfs shortest path](https://github.com/TheAlgorithms/Python/blob/master/graphs/bfs_shortest_path.py) + * [breadth first search](https://github.com/TheAlgorithms/Python/blob/master/graphs/breadth_first_search.py) + * [check bipartite graph bfs](https://github.com/TheAlgorithms/Python/blob/master/graphs/check_bipartite_graph_bfs.py) + * [check bipartite graph dfs](https://github.com/TheAlgorithms/Python/blob/master/graphs/check_bipartite_graph_dfs.py) + * [depth first search](https://github.com/TheAlgorithms/Python/blob/master/graphs/depth_first_search.py) + * [dfs](https://github.com/TheAlgorithms/Python/blob/master/graphs/dfs.py) + * [dijkstra](https://github.com/TheAlgorithms/Python/blob/master/graphs/dijkstra.py) + * [dijkstra 2](https://github.com/TheAlgorithms/Python/blob/master/graphs/dijkstra_2.py) + * [dijkstra algorithm](https://github.com/TheAlgorithms/Python/blob/master/graphs/dijkstra_algorithm.py) + * [directed and undirected (weighted) graph](https://github.com/TheAlgorithms/Python/blob/master/graphs/directed_and_undirected_(weighted)_graph.py) + * [edmonds karp multiple source and sink](https://github.com/TheAlgorithms/Python/blob/master/graphs/edmonds_karp_multiple_source_and_sink.py) + * [eulerian path and circuit for undirected graph](https://github.com/TheAlgorithms/Python/blob/master/graphs/eulerian_path_and_circuit_for_undirected_graph.py) + * [even tree](https://github.com/TheAlgorithms/Python/blob/master/graphs/even_tree.py) + * [finding bridges](https://github.com/TheAlgorithms/Python/blob/master/graphs/finding_bridges.py) + * [graph list](https://github.com/TheAlgorithms/Python/blob/master/graphs/graph_list.py) + * [graph matrix](https://github.com/TheAlgorithms/Python/blob/master/graphs/graph_matrix.py) + * [graphs floyd warshall](https://github.com/TheAlgorithms/Python/blob/master/graphs/graphs_floyd_warshall.py) + * [kahns algorithm long](https://github.com/TheAlgorithms/Python/blob/master/graphs/kahns_algorithm_long.py) + * [kahns algorithm topo](https://github.com/TheAlgorithms/Python/blob/master/graphs/kahns_algorithm_topo.py) + * [minimum spanning tree kruskal](https://github.com/TheAlgorithms/Python/blob/master/graphs/minimum_spanning_tree_kruskal.py) + * [minimum spanning tree prims](https://github.com/TheAlgorithms/Python/blob/master/graphs/minimum_spanning_tree_prims.py) + * [multi hueristic astar](https://github.com/TheAlgorithms/Python/blob/master/graphs/multi_hueristic_astar.py) + * [page rank](https://github.com/TheAlgorithms/Python/blob/master/graphs/page_rank.py) + * [prim](https://github.com/TheAlgorithms/Python/blob/master/graphs/prim.py) + * [scc kosaraju](https://github.com/TheAlgorithms/Python/blob/master/graphs/scc_kosaraju.py) + * [tarjans scc](https://github.com/TheAlgorithms/Python/blob/master/graphs/tarjans_scc.py) + ## Hashes + * [chaos machine](https://github.com/TheAlgorithms/Python/blob/master/hashes/chaos_machine.py) + * [enigma machine](https://github.com/TheAlgorithms/Python/blob/master/hashes/enigma_machine.py) + * [md5](https://github.com/TheAlgorithms/Python/blob/master/hashes/md5.py) + * [sha1](https://github.com/TheAlgorithms/Python/blob/master/hashes/sha1.py) -## Linear Algebra Python + +## Linear Algebra + * Src - * [lib](https://github.com/TheAlgorithms/Python/blob/master/linear_algebra_python/src/lib.py) - * [tests](https://github.com/TheAlgorithms/Python/blob/master/linear_algebra_python/src/tests.py) + + * [lib](https://github.com/TheAlgorithms/Python/blob/master/linear_algebra/src/lib.py) + + * [python-polynom-for-points](https://github.com/TheAlgorithms/Python/blob/master/linear_algebra/src/python-polynom-for-points.py) + + * [tests](https://github.com/TheAlgorithms/Python/blob/master/linear_algebra/src/tests.py) + ## Machine Learning - * [NaiveBayes](https://github.com/TheAlgorithms/Python/blob/master/machine_learning/NaiveBayes.ipynb) + * [decision tree](https://github.com/TheAlgorithms/Python/blob/master/machine_learning/decision_tree.py) + * [gradient descent](https://github.com/TheAlgorithms/Python/blob/master/machine_learning/gradient_descent.py) + * [k means clust](https://github.com/TheAlgorithms/Python/blob/master/machine_learning/k_means_clust.py) + * [knn sklearn](https://github.com/TheAlgorithms/Python/blob/master/machine_learning/knn_sklearn.py) + * [linear regression](https://github.com/TheAlgorithms/Python/blob/master/machine_learning/linear_regression.py) + * [logistic regression](https://github.com/TheAlgorithms/Python/blob/master/machine_learning/logistic_regression.py) - * [perceptron](https://github.com/TheAlgorithms/Python/blob/master/machine_learning/perceptron.py) + + * [naive bayes](https://github.com/TheAlgorithms/Python/blob/master/machine_learning/naive_bayes.ipynb) + * Random Forest Classification + * [random forest classification](https://github.com/TheAlgorithms/Python/blob/master/machine_learning/random_forest_classification/random_forest_classification.py) + * [random forest classifier](https://github.com/TheAlgorithms/Python/blob/master/machine_learning/random_forest_classification/random_forest_classifier.ipynb) + * Random Forest Regression + * [random forest regression](https://github.com/TheAlgorithms/Python/blob/master/machine_learning/random_forest_regression/random_forest_regression.ipynb) + * [random forest regression](https://github.com/TheAlgorithms/Python/blob/master/machine_learning/random_forest_regression/random_forest_regression.py) + * [reuters one vs rest classifier](https://github.com/TheAlgorithms/Python/blob/master/machine_learning/reuters_one_vs_rest_classifier.ipynb) + * [scoring functions](https://github.com/TheAlgorithms/Python/blob/master/machine_learning/scoring_functions.py) + ## Maths + * [3n+1](https://github.com/TheAlgorithms/Python/blob/master/maths/3n+1.py) + * [abs](https://github.com/TheAlgorithms/Python/blob/master/maths/abs.py) + * [abs max](https://github.com/TheAlgorithms/Python/blob/master/maths/abs_max.py) + * [abs min](https://github.com/TheAlgorithms/Python/blob/master/maths/abs_min.py) + * [average mean](https://github.com/TheAlgorithms/Python/blob/master/maths/average_mean.py) + * [average median](https://github.com/TheAlgorithms/Python/blob/master/maths/average_median.py) + * [basic maths](https://github.com/TheAlgorithms/Python/blob/master/maths/basic_maths.py) + * [binary exponentiation](https://github.com/TheAlgorithms/Python/blob/master/maths/binary_exponentiation.py) + + * [collatz sequence](https://github.com/TheAlgorithms/Python/blob/master/maths/collatz_sequence.py) + * [extended euclidean algorithm](https://github.com/TheAlgorithms/Python/blob/master/maths/extended_euclidean_algorithm.py) + * [factorial python](https://github.com/TheAlgorithms/Python/blob/master/maths/factorial_python.py) + * [factorial recursive](https://github.com/TheAlgorithms/Python/blob/master/maths/factorial_recursive.py) + * [fermat little theorem](https://github.com/TheAlgorithms/Python/blob/master/maths/fermat_little_theorem.py) + * [fibonacci](https://github.com/TheAlgorithms/Python/blob/master/maths/fibonacci.py) + * [fibonacci sequence recursion](https://github.com/TheAlgorithms/Python/blob/master/maths/fibonacci_sequence_recursion.py) + * [find lcm](https://github.com/TheAlgorithms/Python/blob/master/maths/find_lcm.py) + * [find max](https://github.com/TheAlgorithms/Python/blob/master/maths/find_max.py) + * [find min](https://github.com/TheAlgorithms/Python/blob/master/maths/find_min.py) + * [gaussian](https://github.com/TheAlgorithms/Python/blob/master/maths/gaussian.py) + * [greater common divisor](https://github.com/TheAlgorithms/Python/blob/master/maths/greater_common_divisor.py) + * [is square free](https://github.com/TheAlgorithms/Python/blob/master/maths/is_square_free.py) + + * [largest of very large numbers](https://github.com/TheAlgorithms/Python/blob/master/maths/largest_of_very_large_numbers.py) + + * [lucas lehmer primality test](https://github.com/TheAlgorithms/Python/blob/master/maths/lucas_lehmer_primality_test.py) + * [lucas series](https://github.com/TheAlgorithms/Python/blob/master/maths/lucas_series.py) + * [mobius function](https://github.com/TheAlgorithms/Python/blob/master/maths/mobius_function.py) + * [modular exponential](https://github.com/TheAlgorithms/Python/blob/master/maths/modular_exponential.py) + * [newton raphson](https://github.com/TheAlgorithms/Python/blob/master/maths/newton_raphson.py) + * [prime check](https://github.com/TheAlgorithms/Python/blob/master/maths/prime_check.py) + * [prime factors](https://github.com/TheAlgorithms/Python/blob/master/maths/prime_factors.py) + + * [quadratic equations complex numbers](https://github.com/TheAlgorithms/Python/blob/master/maths/quadratic_equations_complex_numbers.py) + * [segmented sieve](https://github.com/TheAlgorithms/Python/blob/master/maths/segmented_sieve.py) + * [sieve of eratosthenes](https://github.com/TheAlgorithms/Python/blob/master/maths/sieve_of_eratosthenes.py) + * [simpson rule](https://github.com/TheAlgorithms/Python/blob/master/maths/simpson_rule.py) + + * [test prime check](https://github.com/TheAlgorithms/Python/blob/master/maths/test_prime_check.py) + * [trapezoidal rule](https://github.com/TheAlgorithms/Python/blob/master/maths/trapezoidal_rule.py) + * [volume](https://github.com/TheAlgorithms/Python/blob/master/maths/volume.py) + + * [zellers congruence](https://github.com/TheAlgorithms/Python/blob/master/maths/zellers_congruence.py) + ## Matrix + * [matrix operation](https://github.com/TheAlgorithms/Python/blob/master/matrix/matrix_operation.py) + * [nth fibonacci using matrix exponentiation](https://github.com/TheAlgorithms/Python/blob/master/matrix/nth_fibonacci_using_matrix_exponentiation.py) + * [rotate matrix](https://github.com/TheAlgorithms/Python/blob/master/matrix/rotate_matrix.py) + * [searching in sorted matrix](https://github.com/TheAlgorithms/Python/blob/master/matrix/searching_in_sorted_matrix.py) + * [spiral print](https://github.com/TheAlgorithms/Python/blob/master/matrix/spiral_print.py) + * Tests + * [test matrix operation](https://github.com/TheAlgorithms/Python/blob/master/matrix/tests/test_matrix_operation.py) + ## Networking Flow + * [ford fulkerson](https://github.com/TheAlgorithms/Python/blob/master/networking_flow/ford_fulkerson.py) + * [minimum cut](https://github.com/TheAlgorithms/Python/blob/master/networking_flow/minimum_cut.py) + ## Neural Network + * [back propagation neural network](https://github.com/TheAlgorithms/Python/blob/master/neural_network/back_propagation_neural_network.py) + * [convolution neural network](https://github.com/TheAlgorithms/Python/blob/master/neural_network/convolution_neural_network.py) + * [fully connected neural network](https://github.com/TheAlgorithms/Python/blob/master/neural_network/fully_connected_neural_network.ipynb) + * [perceptron](https://github.com/TheAlgorithms/Python/blob/master/neural_network/perceptron.py) + ## Other - * [Food wastage analysis from 1961-2013 (FAO)](https://github.com/TheAlgorithms/Python/blob/master/other/Food%20wastage%20analysis%20from%201961-2013%20(FAO).ipynb) + * [anagrams](https://github.com/TheAlgorithms/Python/blob/master/other/anagrams.py) + * [binary exponentiation](https://github.com/TheAlgorithms/Python/blob/master/other/binary_exponentiation.py) + * [binary exponentiation 2](https://github.com/TheAlgorithms/Python/blob/master/other/binary_exponentiation_2.py) + * [detecting english programmatically](https://github.com/TheAlgorithms/Python/blob/master/other/detecting_english_programmatically.py) + * [euclidean gcd](https://github.com/TheAlgorithms/Python/blob/master/other/euclidean_gcd.py) - * [finding primes](https://github.com/TheAlgorithms/Python/blob/master/other/finding_primes.py) + * [fischer yates shuffle](https://github.com/TheAlgorithms/Python/blob/master/other/fischer_yates_shuffle.py) + + * [food wastage analysis from 1961-2013 fao](https://github.com/TheAlgorithms/Python/blob/master/other/food_wastage_analysis_from_1961-2013_fao.ipynb) + * [frequency finder](https://github.com/TheAlgorithms/Python/blob/master/other/frequency_finder.py) + * [game of life](https://github.com/TheAlgorithms/Python/blob/master/other/game_of_life.py) + * [linear congruential generator](https://github.com/TheAlgorithms/Python/blob/master/other/linear_congruential_generator.py) + * [nested brackets](https://github.com/TheAlgorithms/Python/blob/master/other/nested_brackets.py) + * [palindrome](https://github.com/TheAlgorithms/Python/blob/master/other/palindrome.py) + * [password generator](https://github.com/TheAlgorithms/Python/blob/master/other/password_generator.py) + * [primelib](https://github.com/TheAlgorithms/Python/blob/master/other/primelib.py) + * [sierpinski triangle](https://github.com/TheAlgorithms/Python/blob/master/other/sierpinski_triangle.py) + * [tower of hanoi](https://github.com/TheAlgorithms/Python/blob/master/other/tower_of_hanoi.py) + * [two sum](https://github.com/TheAlgorithms/Python/blob/master/other/two_sum.py) + * [word patterns](https://github.com/TheAlgorithms/Python/blob/master/other/word_patterns.py) + ## Project Euler + * Problem 01 + * [sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_01/sol1.py) + * [sol2](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_01/sol2.py) + * [sol3](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_01/sol3.py) + * [sol4](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_01/sol4.py) + * [sol5](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_01/sol5.py) + * [sol6](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_01/sol6.py) + * Problem 02 + * [sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_02/sol1.py) + * [sol2](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_02/sol2.py) + * [sol3](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_02/sol3.py) + * [sol4](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_02/sol4.py) + * Problem 03 + * [sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_03/sol1.py) + * [sol2](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_03/sol2.py) + * Problem 04 + * [sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_04/sol1.py) + * [sol2](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_04/sol2.py) + * Problem 05 + * [sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_05/sol1.py) + * [sol2](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_05/sol2.py) + * Problem 06 + * [sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_06/sol1.py) + * [sol2](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_06/sol2.py) + * [sol3](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_06/sol3.py) + * Problem 07 + * [sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_07/sol1.py) + * [sol2](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_07/sol2.py) + * [sol3](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_07/sol3.py) + * Problem 08 + * [sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_08/sol1.py) + * [sol2](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_08/sol2.py) + * Problem 09 + * [sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_09/sol1.py) + * [sol2](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_09/sol2.py) + * [sol3](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_09/sol3.py) + * Problem 10 + * [sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_10/sol1.py) + * [sol2](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_10/sol2.py) + + * [sol3](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_10/sol3.py) + * Problem 11 + * [sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_11/sol1.py) + * [sol2](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_11/sol2.py) + * Problem 12 + * [sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_12/sol1.py) + * [sol2](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_12/sol2.py) + * Problem 13 + * [sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_13/sol1.py) + * Problem 14 + * [sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_14/sol1.py) + * [sol2](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_14/sol2.py) + * Problem 15 + * [sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_15/sol1.py) + * Problem 16 + * [sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_16/sol1.py) + * [sol2](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_16/sol2.py) + * Problem 17 + * [sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_17/sol1.py) + * Problem 19 + * [sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_19/sol1.py) + * Problem 20 + * [sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_20/sol1.py) + * [sol2](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_20/sol2.py) + * Problem 21 + * [sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_21/sol1.py) + * Problem 22 + * [sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_22/sol1.py) + * [sol2](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_22/sol2.py) + * Problem 234 + * [sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_234/sol1.py) + * Problem 24 + * [sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_24/sol1.py) + * Problem 25 + * [sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_25/sol1.py) + * [sol2](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_25/sol2.py) + * Problem 28 + * [sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_28/sol1.py) + * Problem 29 + * [solution](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_29/solution.py) + * Problem 31 + * [sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_31/sol1.py) + * Problem 36 + * [sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_36/sol1.py) + * Problem 40 + * [sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_40/sol1.py) + * Problem 48 + * [sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_48/sol1.py) + * Problem 52 + * [sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_52/sol1.py) + * Problem 53 + * [sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_53/sol1.py) + * Problem 76 + * [sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_76/sol1.py) + ## Searches + * [binary search](https://github.com/TheAlgorithms/Python/blob/master/searches/binary_search.py) + * [interpolation search](https://github.com/TheAlgorithms/Python/blob/master/searches/interpolation_search.py) + * [jump search](https://github.com/TheAlgorithms/Python/blob/master/searches/jump_search.py) + * [linear search](https://github.com/TheAlgorithms/Python/blob/master/searches/linear_search.py) + * [quick select](https://github.com/TheAlgorithms/Python/blob/master/searches/quick_select.py) + * [sentinel linear search](https://github.com/TheAlgorithms/Python/blob/master/searches/sentinel_linear_search.py) + * [tabu search](https://github.com/TheAlgorithms/Python/blob/master/searches/tabu_search.py) + * [ternary search](https://github.com/TheAlgorithms/Python/blob/master/searches/ternary_search.py) + ## Sorts + * [bitonic sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/bitonic_sort.py) + * [bogo sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/bogo_sort.py) + * [bubble sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/bubble_sort.py) + * [bucket sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/bucket_sort.py) + * [cocktail shaker sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/cocktail_shaker_sort.py) + * [comb sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/comb_sort.py) + * [counting sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/counting_sort.py) + * [cycle sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/cycle_sort.py) + * [external sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/external_sort.py) + * [gnome sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/gnome_sort.py) + * [heap sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/heap_sort.py) + * [insertion sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/insertion_sort.py) + * [merge sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/merge_sort.py) + * [merge sort fastest](https://github.com/TheAlgorithms/Python/blob/master/sorts/merge_sort_fastest.py) + * [odd even transposition parallel](https://github.com/TheAlgorithms/Python/blob/master/sorts/odd_even_transposition_parallel.py) + * [odd even transposition single threaded](https://github.com/TheAlgorithms/Python/blob/master/sorts/odd_even_transposition_single_threaded.py) + * [pancake sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/pancake_sort.py) + * [pigeon sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/pigeon_sort.py) + * [quick sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/quick_sort.py) + * [quick sort 3 partition](https://github.com/TheAlgorithms/Python/blob/master/sorts/quick_sort_3_partition.py) + * [radix sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/radix_sort.py) + * [random normal distribution quicksort](https://github.com/TheAlgorithms/Python/blob/master/sorts/random_normal_distribution_quicksort.py) + * [random pivot quick sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/random_pivot_quick_sort.py) + * [selection sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/selection_sort.py) + * [shell sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/shell_sort.py) + * [tim sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/tim_sort.py) + * [topological sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/topological_sort.py) + * [tree sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/tree_sort.py) + * [wiggle sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/wiggle_sort.py) + ## Strings + * [boyer moore search](https://github.com/TheAlgorithms/Python/blob/master/strings/boyer_moore_search.py) + * [knuth morris pratt](https://github.com/TheAlgorithms/Python/blob/master/strings/knuth_morris_pratt.py) + * [levenshtein distance](https://github.com/TheAlgorithms/Python/blob/master/strings/levenshtein_distance.py) + * [manacher](https://github.com/TheAlgorithms/Python/blob/master/strings/manacher.py) + * [min cost string conversion](https://github.com/TheAlgorithms/Python/blob/master/strings/min_cost_string_conversion.py) + * [naive string search](https://github.com/TheAlgorithms/Python/blob/master/strings/naive_string_search.py) + * [rabin karp](https://github.com/TheAlgorithms/Python/blob/master/strings/rabin_karp.py) + ## Traversals + * [binary tree traversals](https://github.com/TheAlgorithms/Python/blob/master/traversals/binary_tree_traversals.py) From 3e69733e44d2789647b434a1a6d112062b143e00 Mon Sep 17 00:00:00 2001 From: Christian Clauss Date: Thu, 15 Aug 2019 13:19:38 +0200 Subject: [PATCH 0185/1071] Remove 'python' from the filename (#1130) --- .../src/{python-polynom-for-points.py => polynom-for-points.py} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename linear_algebra/src/{python-polynom-for-points.py => polynom-for-points.py} (100%) diff --git a/linear_algebra/src/python-polynom-for-points.py b/linear_algebra/src/polynom-for-points.py similarity index 100% rename from linear_algebra/src/python-polynom-for-points.py rename to linear_algebra/src/polynom-for-points.py From 5bdcd4836c1a47fb14f7bf89339b3c99bf67fe51 Mon Sep 17 00:00:00 2001 From: Maxwell Aladago Date: Thu, 15 Aug 2019 14:07:43 -0400 Subject: [PATCH 0186/1071] =?UTF-8?q?EHN:=20A=20divide-and-conquer,=20and?= =?UTF-8?q?=20brute-force=20algorithms=20for=20array=20inversions=20co?= =?UTF-8?q?=E2=80=A6=20(#1133)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * divide and conquer and brute force algorithms for array-inversions counting * divide and conquer and brute force algorithms for array-inversions counting * divide and conquer and brute force algorithms for array-inversions counting --- divide_and_conquer/inversions.py | 173 +++++++++++++++++++++++++++++++ 1 file changed, 173 insertions(+) create mode 100644 divide_and_conquer/inversions.py diff --git a/divide_and_conquer/inversions.py b/divide_and_conquer/inversions.py new file mode 100644 index 000000000000..527741cad3b7 --- /dev/null +++ b/divide_and_conquer/inversions.py @@ -0,0 +1,173 @@ +from __future__ import print_function, absolute_import, division + +""" +Given an array-like data structure A[1..n], how many pairs +(i, j) for all 1 <= i < j <= n such that A[i] > A[j]? These pairs are +called inversions. Counting the number of such inversions in an array-like +object is the important. Among other things, counting inversions can help +us determine how close a given array is to being sorted + +In this implementation, I provide two algorithms, a divide-and-conquer +algorithm which runs in nlogn and the brute-force n^2 algorithm. + +""" + + +def count_inversions_bf(arr): + """ + Counts the number of inversions using a a naive brute-force algorithm + + Parameters + ---------- + arr: arr: array-like, the list containing the items for which the number + of inversions is desired. The elements of `arr` must be comparable. + + Returns + ------- + num_inversions: The total number of inversions in `arr` + + Examples + --------- + + >>> count_inversions_bf([1, 4, 2, 4, 1]) + 4 + >>> count_inversions_bf([1, 1, 2, 4, 4]) + 0 + >>> count_inversions_bf([]) + 0 + """ + + num_inversions = 0 + n = len(arr) + + for i in range(n-1): + for j in range(i + 1, n): + if arr[i] > arr[j]: + num_inversions += 1 + + return num_inversions + + +def count_inversions_recursive(arr): + """ + Counts the number of inversions using a divide-and-conquer algorithm + + Parameters + ----------- + arr: array-like, the list containing the items for which the number + of inversions is desired. The elements of `arr` must be comparable. + + Returns + ------- + C: a sorted copy of `arr`. + num_inversions: int, the total number of inversions in 'arr' + + Examples + -------- + + >>> count_inversions_recursive([1, 4, 2, 4, 1]) + ([1, 1, 2, 4, 4], 4) + >>> count_inversions_recursive([1, 1, 2, 4, 4]) + ([1, 1, 2, 4, 4], 0) + >>> count_inversions_recursive([]) + ([], 0) + """ + if len(arr) <= 1: + return arr, 0 + else: + mid = len(arr)//2 + P = arr[0:mid] + Q = arr[mid:] + + A, inversion_p = count_inversions_recursive(P) + B, inversions_q = count_inversions_recursive(Q) + C, cross_inversions = _count_cross_inversions(A, B) + + num_inversions = inversion_p + inversions_q + cross_inversions + return C, num_inversions + + +def _count_cross_inversions(P, Q): + """ + Counts the inversions across two sorted arrays. + And combine the two arrays into one sorted array + + For all 1<= i<=len(P) and for all 1 <= j <= len(Q), + if P[i] > Q[j], then (i, j) is a cross inversion + + Parameters + ---------- + P: array-like, sorted in non-decreasing order + Q: array-like, sorted in non-decreasing order + + Returns + ------ + R: array-like, a sorted array of the elements of `P` and `Q` + num_inversion: int, the number of inversions across `P` and `Q` + + Examples + -------- + + >>> _count_cross_inversions([1, 2, 3], [0, 2, 5]) + ([0, 1, 2, 2, 3, 5], 4) + >>> _count_cross_inversions([1, 2, 3], [3, 4, 5]) + ([1, 2, 3, 3, 4, 5], 0) + """ + + R = [] + i = j = num_inversion = 0 + while i < len(P) and j < len(Q): + if P[i] > Q[j]: + # if P[1] > Q[j], then P[k] > Q[k] for all i < k <= len(P) + # These are all inversions. The claim emerges from the + # property that P is sorted. + num_inversion += (len(P) - i) + R.append(Q[j]) + j += 1 + else: + R.append(P[i]) + i += 1 + + if i < len(P): + R.extend(P[i:]) + else: + R.extend(Q[j:]) + + return R, num_inversion + + +def main(): + arr_1 = [10, 2, 1, 5, 5, 2, 11] + + # this arr has 8 inversions: + # (10, 2), (10, 1), (10, 5), (10, 5), (10, 2), (2, 1), (5, 2), (5, 2) + + num_inversions_bf = count_inversions_bf(arr_1) + _, num_inversions_recursive = count_inversions_recursive(arr_1) + + assert num_inversions_bf == num_inversions_recursive == 8 + + print("number of inversions = ", num_inversions_bf) + + # testing an array with zero inversion (a sorted arr_1) + + arr_1.sort() + num_inversions_bf = count_inversions_bf(arr_1) + _, num_inversions_recursive = count_inversions_recursive(arr_1) + + assert num_inversions_bf == num_inversions_recursive == 0 + print("number of inversions = ", num_inversions_bf) + + # an empty list should also have zero inversions + arr_1 = [] + num_inversions_bf = count_inversions_bf(arr_1) + _, num_inversions_recursive = count_inversions_recursive(arr_1) + + assert num_inversions_bf == num_inversions_recursive == 0 + print("number of inversions = ", num_inversions_bf) + + +if __name__ == "__main__": + main() + + From a18a8fe2b9e8cfa659d59e14b9a2077f37d86719 Mon Sep 17 00:00:00 2001 From: Christian Clauss Date: Sat, 17 Aug 2019 00:46:33 +0200 Subject: [PATCH 0187/1071] Update .gitignore to remove __pycache__/ (#1127) --- .gitignore | 34 +++++++++++++++++++++++++--------- 1 file changed, 25 insertions(+), 9 deletions(-) diff --git a/.gitignore b/.gitignore index 0c3f33058614..b840d4ed0490 100644 --- a/.gitignore +++ b/.gitignore @@ -7,9 +7,7 @@ __pycache__/ *.so # Distribution / packaging -.vscode/ .Python -env/ build/ develop-eggs/ dist/ @@ -21,9 +19,11 @@ lib64/ parts/ sdist/ var/ +wheels/ *.egg-info/ .installed.cfg *.egg +MANIFEST # PyInstaller # Usually these files are written by a python script from a template @@ -43,8 +43,9 @@ htmlcov/ .cache nosetests.xml coverage.xml -*,cover +*.cover .hypothesis/ +.pytest_cache/ # Translations *.mo @@ -53,6 +54,7 @@ coverage.xml # Django stuff: *.log local_settings.py +db.sqlite3 # Flask stuff: instance/ @@ -67,7 +69,7 @@ docs/_build/ # PyBuilder target/ -# IPython Notebook +# Jupyter Notebook .ipynb_checkpoints # pyenv @@ -76,18 +78,32 @@ target/ # celery beat schedule file celerybeat-schedule -# dotenv -.env +# SageMath parsed files +*.sage.py -# virtualenv +# Environments +.env +.venv +env/ venv/ ENV/ +env.bak/ +venv.bak/ # Spyder project settings .spyderproject +.spyproject # Rope project settings .ropeproject -.idea + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ + .DS_Store -.try \ No newline at end of file +.idea +.try +.vscode/ From 05c9a05f3663df7a93c0e50f9035c8b2c5f2754b Mon Sep 17 00:00:00 2001 From: Maxwell Aladago Date: Sat, 17 Aug 2019 11:36:31 -0400 Subject: [PATCH 0188/1071] ENH: two algorithms for the convex hull problem of a set of 2d points on a plain (#1135) * divide and conquer and brute force algorithms for array-inversions counting * divide and conquer and brute force algorithms for array-inversions counting * divide and conquer and brute force algorithms for array-inversions counting * a naive and divide-and-conquer algorithms for the convex-hull problem * two convex-hull algorithms, a divide-and-conquer and a naive algorithm * two convex-hull algorithms, a divide-and-conquer and a naive algorithm * two convex-hull algorithms, a divide-and-conquer and a naive algorithm --- divide_and_conquer/convex_hull.py | 431 ++++++++++++++++++++++++++++++ 1 file changed, 431 insertions(+) create mode 100644 divide_and_conquer/convex_hull.py diff --git a/divide_and_conquer/convex_hull.py b/divide_and_conquer/convex_hull.py new file mode 100644 index 000000000000..f15d74ddea68 --- /dev/null +++ b/divide_and_conquer/convex_hull.py @@ -0,0 +1,431 @@ +from __future__ import print_function, absolute_import, division + +from numbers import Number +""" +The convex hull problem is problem of finding all the vertices of convex polygon, P of +a set of points in a plane such that all the points are either on the vertices of P or +inside P. TH convex hull problem has several applications in geometrical problems, +computer graphics and game development. + +Two algorithms have been implemented for the convex hull problem here. +1. A brute-force algorithm which runs in O(n^3) +2. A divide-and-conquer algorithm which runs in O(n^3) + +There are other several other algorithms for the convex hull problem +which have not been implemented here, yet. + +""" + + +class Point: + """ + Defines a 2-d point for use by all convex-hull algorithms. + + Parameters + ---------- + x: an int or a float, the x-coordinate of the 2-d point + y: an int or a float, the y-coordinate of the 2-d point + + Examples + -------- + >>> Point(1, 2) + (1, 2) + >>> Point("1", "2") + (1.0, 2.0) + >>> Point(1, 2) > Point(0, 1) + True + >>> Point(1, 1) == Point(1, 1) + True + >>> Point(-0.5, 1) == Point(0.5, 1) + False + >>> Point("pi", "e") + Traceback (most recent call last): + ... + ValueError: x and y must be both numeric types but got , instead + """ + + def __init__(self, x, y): + if not (isinstance(x, Number) and isinstance(y, Number)): + try: + x, y = float(x), float(y) + except ValueError as e: + e.args = ("x and y must be both numeric types " + "but got {}, {} instead".format(type(x), type(y)), ) + raise + + self.x = x + self.y = y + + def __eq__(self, other): + return self.x == other.x and self.y == other.y + + def __ne__(self, other): + return not self == other + + def __gt__(self, other): + if self.x > other.x: + return True + elif self.x == other.x: + return self.y > other.y + return False + + def __lt__(self, other): + return not self > other + + def __ge__(self, other): + if self.x > other.x: + return True + elif self.x == other.x: + return self.y >= other.y + return False + + def __le__(self, other): + if self.x < other.x: + return True + elif self.x == other.x: + return self.y <= other.y + return False + + def __repr__(self): + return "({}, {})".format(self.x, self.y) + + def __hash__(self): + return hash(self.x) + + +def _construct_points(list_of_tuples): + """ + constructs a list of points from an array-like object of numbers + + Arguments + --------- + + list_of_tuples: array-like object of type numbers. Acceptable types so far + are lists, tuples and sets. + + Returns + -------- + points: a list where each item is of type Point. This contains only objects + which can be converted into a Point. + + Examples + ------- + >>> _construct_points([[1, 1], [2, -1], [0.3, 4]]) + [(1, 1), (2, -1), (0.3, 4)] + >>> _construct_points(([1, 1], [2, -1], [0.3, 4])) + [(1, 1), (2, -1), (0.3, 4)] + >>> _construct_points([(1, 1), (2, -1), (0.3, 4)]) + [(1, 1), (2, -1), (0.3, 4)] + >>> _construct_points([[1, 1], (2, -1), [0.3, 4]]) + [(1, 1), (2, -1), (0.3, 4)] + >>> _construct_points([1, 2]) + Ignoring deformed point 1. All points must have at least 2 coordinates. + Ignoring deformed point 2. All points must have at least 2 coordinates. + [] + >>> _construct_points([]) + [] + >>> _construct_points(None) + [] + """ + + points = [] + if list_of_tuples: + for p in list_of_tuples: + try: + points.append(Point(p[0], p[1])) + except (IndexError, TypeError): + print("Ignoring deformed point {}. All points" + " must have at least 2 coordinates.".format(p)) + return points + + +def _validate_input(points): + """ + validates an input instance before a convex-hull algorithms uses it + + Parameters + --------- + points: array-like, the 2d points to validate before using with + a convex-hull algorithm. The elements of points must be either lists, tuples or + Points. + + Returns + ------- + points: array_like, an iterable of all well-defined Points constructed passed in. + + + Exception + --------- + ValueError: if points is empty or None, or if a wrong data structure like a scalar is passed + + TypeError: if an iterable but non-indexable object (eg. dictionary) is passed. + The exception to this a set which we'll convert to a list before using + + + Examples + ------- + >>> _validate_input([[1, 2]]) + [(1, 2)] + >>> _validate_input([(1, 2)]) + [(1, 2)] + >>> _validate_input([Point(2, 1), Point(-1, 2)]) + [(2, 1), (-1, 2)] + >>> _validate_input([]) + Traceback (most recent call last): + ... + ValueError: Expecting a list of points but got [] + >>> _validate_input(1) + Traceback (most recent call last): + ... + ValueError: Expecting an iterable object but got an non-iterable type 1 + """ + + if not points: + raise ValueError("Expecting a list of points but got {}".format(points)) + + if isinstance(points, set): + points = list(points) + + try: + if hasattr(points, "__iter__") and not isinstance(points[0], Point): + if isinstance(points[0], (list, tuple)): + points = _construct_points(points) + else: + raise ValueError("Expecting an iterable of type Point, list or tuple. " + "Found objects of type {} instead" + .format(["point", "list", "tuple"], type(points[0]))) + elif not hasattr(points, "__iter__"): + raise ValueError("Expecting an iterable object " + "but got an non-iterable type {}".format(points)) + except TypeError as e: + print("Expecting an iterable of type Point, list or tuple.") + raise + + return points + + +def _det(a, b, c): + """ + Computes the sign perpendicular distance of a 2d point c from a line segment + ab. The sign indicates the direction of c relative to ab. + A Positive value means c is above ab (to the left), while a negative value + means c is below ab (to the right). 0 means all three points are on a straight line. + + As a side note, 0.5 * abs|det| is the area of triangle abc + + Parameters + ---------- + a: point, the point on the left end of line segment ab + b: point, the point on the right end of line segment ab + c: point, the point for which the direction and location is desired. + + Returns + -------- + det: float, abs(det) is the distance of c from ab. The sign + indicates which side of line segment ab c is. det is computed as + (a_xb_y + c_xa_y + b_xc_y) - (a_yb_x + c_ya_x + b_yc_x) + + Examples + ---------- + >>> _det(Point(1, 1), Point(1, 2), Point(1, 5)) + 0 + >>> _det(Point(0, 0), Point(10, 0), Point(0, 10)) + 100 + >>> _det(Point(0, 0), Point(10, 0), Point(0, -10)) + -100 + """ + + det = (a.x * b.y + b.x * c.y + c.x * a.y) - (a.y * b.x + b.y * c.x + c.y * a.x) + return det + + +def convex_hull_bf(points): + """ + Constructs the convex hull of a set of 2D points using a brute force algorithm. + The algorithm basically considers all combinations of points (i, j) and uses the + definition of convexity to determine whether (i, j) is part of the convex hull or not. + (i, j) is part of the convex hull if and only iff there are no points on both sides + of the line segment connecting the ij, and there is no point k such that k is on either end + of the ij. + + Runtime: O(n^3) - definitely horrible + + Parameters + --------- + points: array-like of object of Points, lists or tuples. + The set of 2d points for which the convex-hull is needed + + Returns + ------ + convex_set: list, the convex-hull of points sorted in non-decreasing order. + + See Also + -------- + convex_hull_recursive, + + Examples + --------- + >>> convex_hull_bf([[0, 0], [1, 0], [10, 1]]) + [(0, 0), (1, 0), (10, 1)] + >>> convex_hull_bf([[0, 0], [1, 0], [10, 0]]) + [(0, 0), (10, 0)] + >>> convex_hull_bf([[-1, 1],[-1, -1], [0, 0], [0.5, 0.5], [1, -1], [1, 1], [-0.75, 1]]) + [(-1, -1), (-1, 1), (1, -1), (1, 1)] + >>> convex_hull_bf([(0, 3), (2, 2), (1, 1), (2, 1), (3, 0), (0, 0), (3, 3), (2, -1), (2, -4), (1, -3)]) + [(0, 0), (0, 3), (1, -3), (2, -4), (3, 0), (3, 3)] + """ + + points = sorted(_validate_input(points)) + n = len(points) + convex_set = set() + + for i in range(n-1): + for j in range(i + 1, n): + points_left_of_ij = points_right_of_ij = False + ij_part_of_convex_hull = True + for k in range(n): + if k != i and k != j: + det_k = _det(points[i], points[j], points[k]) + + if det_k > 0: + points_left_of_ij = True + elif det_k < 0: + points_right_of_ij = True + else: + # point[i], point[j], point[k] all lie on a straight line + # if point[k] is to the left of point[i] or it's to the + # right of point[j], then point[i], point[j] cannot be + # part of the convex hull of A + if points[k] < points[i] or points[k] > points[j]: + ij_part_of_convex_hull = False + break + + if points_left_of_ij and points_right_of_ij: + ij_part_of_convex_hull = False + break + + if ij_part_of_convex_hull: + convex_set.update([points[i], points[j]]) + + return sorted(convex_set) + + +def convex_hull_recursive(points): + """ + Constructs the convex hull of a set of 2D points using a divide-and-conquer strategy + The algorithm exploits the geometric properties of the problem by repeatedly partitioning + the set of points into smaller hulls, and finding the convex hull of these smaller hulls. + The union of the convex hull from smaller hulls is the solution to the convex hull of the larger problem. + + Parameter + --------- + points: array-like of object of Points, lists or tuples. + The set of 2d points for which the convex-hull is needed + + Runtime: O(n log n) + + Returns + ------- + convex_set: list, the convex-hull of points sorted in non-decreasing order. + + Examples + --------- + >>> convex_hull_recursive([[0, 0], [1, 0], [10, 1]]) + [(0, 0), (1, 0), (10, 1)] + >>> convex_hull_recursive([[0, 0], [1, 0], [10, 0]]) + [(0, 0), (10, 0)] + >>> convex_hull_recursive([[-1, 1],[-1, -1], [0, 0], [0.5, 0.5], [1, -1], [1, 1], [-0.75, 1]]) + [(-1, -1), (-1, 1), (1, -1), (1, 1)] + >>> convex_hull_recursive([(0, 3), (2, 2), (1, 1), (2, 1), (3, 0), (0, 0), (3, 3), (2, -1), (2, -4), (1, -3)]) + [(0, 0), (0, 3), (1, -3), (2, -4), (3, 0), (3, 3)] + + """ + points = sorted(_validate_input(points)) + n = len(points) + + # divide all the points into an upper hull and a lower hull + # the left most point and the right most point are definitely + # members of the convex hull by definition. + # use these two anchors to divide all the points into two hulls, + # an upper hull and a lower hull. + + # all points to the left (above) the line joining the extreme points belong to the upper hull + # all points to the right (below) the line joining the extreme points below to the lower hull + # ignore all points on the line joining the extreme points since they cannot be part of the + # convex hull + + left_most_point = points[0] + right_most_point = points[n-1] + + convex_set = {left_most_point, right_most_point} + upperhull = [] + lowerhull = [] + + for i in range(1, n-1): + det = _det(left_most_point, right_most_point, points[i]) + + if det > 0: + upperhull.append(points[i]) + elif det < 0: + lowerhull.append(points[i]) + + _construct_hull(upperhull, left_most_point, right_most_point, convex_set) + _construct_hull(lowerhull, right_most_point, left_most_point, convex_set) + + return sorted(convex_set) + + +def _construct_hull(points, left, right, convex_set): + """ + + Parameters + --------- + points: list or None, the hull of points from which to choose the next convex-hull point + left: Point, the point to the left of line segment joining left and right + right: The point to the right of the line segment joining left and right + convex_set: set, the current convex-hull. The state of convex-set gets updated by this function + + Note + ---- + For the line segment 'ab', 'a' is on the left and 'b' on the right. + but the reverse is true for the line segment 'ba'. + + Returns + ------- + Nothing, only updates the state of convex-set + """ + if points: + extreme_point = None + extreme_point_distance = float('-inf') + candidate_points = [] + + for p in points: + det = _det(left, right, p) + + if det > 0: + candidate_points.append(p) + + if det > extreme_point_distance: + extreme_point_distance = det + extreme_point = p + + if extreme_point: + _construct_hull(candidate_points, left, extreme_point, convex_set) + convex_set.add(extreme_point) + _construct_hull(candidate_points, extreme_point, right, convex_set) + + +def main(): + points = [(0, 3), (2, 2), (1, 1), (2, 1), (3, 0), + (0, 0), (3, 3), (2, -1), (2, -4), (1, -3)] + # the convex set of points is + # [(0, 0), (0, 3), (1, -3), (2, -4), (3, 0), (3, 3)] + results_recursive = convex_hull_recursive(points) + results_bf = convex_hull_bf(points) + assert results_bf == results_recursive + + print(results_bf) + + +if __name__ == '__main__': + main() From 5d46a4dd7beeeeb27f393b972effe785feab7306 Mon Sep 17 00:00:00 2001 From: Maxwell Aladago Date: Mon, 19 Aug 2019 01:39:39 -0400 Subject: [PATCH 0189/1071] ENH: Added a functionality to make it possible to reconstruct an optimal subset for the dynamic programming problem (#1139) * function for the knapsack problem which returns one of the optimal subsets * function for the knapsack problem which returns one of the optimal subsets * function for the knapsack problem which returns one of the optimal subsets * function for the knapsack problem which returns one of the optimal subsets * function for the knapsack problem which returns one of the optimal subsets * some pep8 cleanup too --- dynamic_programming/knapsack.py | 131 ++++++++++++++++++++++++++++---- 1 file changed, 116 insertions(+), 15 deletions(-) diff --git a/dynamic_programming/knapsack.py b/dynamic_programming/knapsack.py index 27d1cfed799b..488059d6244d 100644 --- a/dynamic_programming/knapsack.py +++ b/dynamic_programming/knapsack.py @@ -1,7 +1,13 @@ """ -Given weights and values of n items, put these items in a knapsack of capacity W to get the maximum total value in the knapsack. +Given weights and values of n items, put these items in a knapsack of + capacity W to get the maximum total value in the knapsack. + +Note that only the integer weights 0-1 knapsack problem is solvable + using dynamic programming. """ -def MF_knapsack(i,wt,val,j): + + +def MF_knapsack(i, wt, val, j): ''' This code involves the concept of memory functions. Here we solve the subproblems which are needed unlike the below example @@ -9,34 +15,129 @@ def MF_knapsack(i,wt,val,j): ''' global F # a global dp table for knapsack if F[i][j] < 0: - if j < wt[i - 1]: - val = MF_knapsack(i - 1,wt,val,j) + if j < wt[i-1]: + val = MF_knapsack(i-1, wt, val, j) else: - val = max(MF_knapsack(i - 1,wt,val,j),MF_knapsack(i - 1,wt,val,j - wt[i - 1]) + val[i - 1]) + val = max(MF_knapsack(i-1, wt, val, j), + MF_knapsack(i-1, wt, val, j - wt[i-1]) + val[i-1]) F[i][j] = val return F[i][j] + def knapsack(W, wt, val, n): dp = [[0 for i in range(W+1)]for j in range(n+1)] for i in range(1,n+1): - for w in range(1,W+1): - if(wt[i-1]<=w): - dp[i][w] = max(val[i-1]+dp[i-1][w-wt[i-1]],dp[i-1][w]) + for w in range(1, W+1): + if wt[i-1] <= w: + dp[i][w] = max(val[i-1] + dp[i-1][w-wt[i-1]], dp[i-1][w]) else: dp[i][w] = dp[i-1][w] - return dp[n][w] + return dp[n][W], dp + + +def knapsack_with_example_solution(W: int, wt: list, val:list): + """ + Solves the integer weights knapsack problem returns one of + the several possible optimal subsets. + + Parameters + --------- + + W: int, the total maximum weight for the given knapsack problem. + wt: list, the vector of weights for all items where wt[i] is the weight + of the ith item. + val: list, the vector of values for all items where val[i] is the value + of te ith item + + Returns + ------- + optimal_val: float, the optimal value for the given knapsack problem + example_optional_set: set, the indices of one of the optimal subsets + which gave rise to the optimal value. + + Examples + ------- + >>> knapsack_with_example_solution(10, [1, 3, 5, 2], [10, 20, 100, 22]) + (142, {2, 3, 4}) + >>> knapsack_with_example_solution(6, [4, 3, 2, 3], [3, 2, 4, 4]) + (8, {3, 4}) + >>> knapsack_with_example_solution(6, [4, 3, 2, 3], [3, 2, 4]) + Traceback (most recent call last): + ... + ValueError: The number of weights must be the same as the number of values. + But got 4 weights and 3 values + """ + if not (isinstance(wt, (list, tuple)) and isinstance(val, (list, tuple))): + raise ValueError("Both the weights and values vectors must be either lists or tuples") + + num_items = len(wt) + if num_items != len(val): + raise ValueError("The number of weights must be the " + "same as the number of values.\nBut " + "got {} weights and {} values".format(num_items, len(val))) + for i in range(num_items): + if not isinstance(wt[i], int): + raise TypeError("All weights must be integers but " + "got weight of type {} at index {}".format(type(wt[i]), i)) + + optimal_val, dp_table = knapsack(W, wt, val, num_items) + example_optional_set = set() + _construct_solution(dp_table, wt, num_items, W, example_optional_set) + + return optimal_val, example_optional_set + + +def _construct_solution(dp:list, wt:list, i:int, j:int, optimal_set:set): + """ + Recursively reconstructs one of the optimal subsets given + a filled DP table and the vector of weights + + Parameters + --------- + + dp: list of list, the table of a solved integer weight dynamic programming problem + + wt: list or tuple, the vector of weights of the items + i: int, the index of the item under consideration + j: int, the current possible maximum weight + optimal_set: set, the optimal subset so far. This gets modified by the function. + + Returns + ------- + None + + """ + # for the current item i at a maximum weight j to be part of an optimal subset, + # the optimal value at (i, j) must be greater than the optimal value at (i-1, j). + # where i - 1 means considering only the previous items at the given maximum weight + if i > 0 and j > 0: + if dp[i - 1][j] == dp[i][j]: + _construct_solution(dp, wt, i - 1, j, optimal_set) + else: + optimal_set.add(i) + _construct_solution(dp, wt, i - 1, j - wt[i-1], optimal_set) + if __name__ == '__main__': ''' Adding test case for knapsack ''' - val = [3,2,4,4] - wt = [4,3,2,3] + val = [3, 2, 4, 4] + wt = [4, 3, 2, 3] n = 4 w = 6 - F = [[0]*(w + 1)] + [[0] + [-1 for i in range(w + 1)] for j in range(n + 1)] - print(knapsack(w,wt,val,n)) - print(MF_knapsack(n,wt,val,w)) # switched the n and w - + F = [[0] * (w + 1)] + [[0] + [-1 for i in range(w + 1)] for j in range(n + 1)] + optimal_solution, _ = knapsack(w,wt,val, n) + print(optimal_solution) + print(MF_knapsack(n,wt,val,w)) # switched the n and w + + # testing the dynamic programming problem with example + # the optimal subset for the above example are items 3 and 4 + optimal_solution, optimal_subset = knapsack_with_example_solution(w, wt, val) + assert optimal_solution == 8 + assert optimal_subset == {3, 4} + print("optimal_value = ", optimal_solution) + print("An optimal subset corresponding to the optimal value", optimal_subset) + From 32aa7ff0819dba3d2166b8a66317999a7790e510 Mon Sep 17 00:00:00 2001 From: Maxwell Aladago Date: Mon, 19 Aug 2019 03:40:36 -0400 Subject: [PATCH 0190/1071] ENH: refactored longest common subsequence, also fixed a bug with the sequence returned (#1142) * function for the knapsack problem which returns one of the optimal subsets * function for the knapsack problem which returns one of the optimal subsets * function for the knapsack problem which returns one of the optimal subsets * function for the knapsack problem which returns one of the optimal subsets * function for the knapsack problem which returns one of the optimal subsets * some pep8 cleanup too * ENH: refactored longest common subsequence, also fixed a bug with the sequence returned * renamed function --- .../longest_common_subsequence.py | 90 ++++++++++++++----- 1 file changed, 68 insertions(+), 22 deletions(-) diff --git a/dynamic_programming/longest_common_subsequence.py b/dynamic_programming/longest_common_subsequence.py index 0a4771cb2efd..7836fe303688 100644 --- a/dynamic_programming/longest_common_subsequence.py +++ b/dynamic_programming/longest_common_subsequence.py @@ -1,37 +1,83 @@ """ LCS Problem Statement: Given two sequences, find the length of longest subsequence present in both of them. -A subsequence is a sequence that appears in the same relative order, but not necessarily continious. +A subsequence is a sequence that appears in the same relative order, but not necessarily continuous. Example:"abc", "abg" are subsequences of "abcdefgh". """ from __future__ import print_function -try: - xrange # Python 2 -except NameError: - xrange = range # Python 3 -def lcs_dp(x, y): +def longest_common_subsequence(x: str, y: str): + """ + Finds the longest common subsequence between two strings. Also returns the + The subsequence found + + Parameters + ---------- + + x: str, one of the strings + y: str, the other string + + Returns + ------- + L[m][n]: int, the length of the longest subsequence. Also equal to len(seq) + Seq: str, the subsequence found + + >>> longest_common_subsequence("programming", "gaming") + (6, 'gaming') + >>> longest_common_subsequence("physics", "smartphone") + (2, 'ph') + >>> longest_common_subsequence("computer", "food") + (1, 'o') + """ # find the length of strings + + assert x is not None + assert y is not None + m = len(x) n = len(y) # declaring the array for storing the dp values - L = [[None] * (n + 1) for i in xrange(m + 1)] - seq = [] - - for i in range(m + 1): - for j in range(n + 1): - if i == 0 or j == 0: - L[i][j] = 0 - elif x[i - 1] == y[ j - 1]: - L[i][j] = L[i - 1][j - 1] + 1 - seq.append(x[i -1]) + L = [[0] * (n + 1) for _ in range(m + 1)] + + for i in range(1, m + 1): + for j in range(1, n + 1): + if x[i-1] == y[j-1]: + match = 1 else: - L[i][j] = max(L[i - 1][j], L[i][j - 1]) - # L[m][n] contains the length of LCS of X[0..n-1] & Y[0..m-1] + match = 0 + + L[i][j] = max(L[i-1][j], L[i][j-1], L[i-1][j-1] + match) + + seq = "" + i, j = m, n + while i > 0 and i > 0: + if x[i - 1] == y[j - 1]: + match = 1 + else: + match = 0 + + if L[i][j] == L[i - 1][j - 1] + match: + if match == 1: + seq = x[i - 1] + seq + i -= 1 + j -= 1 + elif L[i][j] == L[i - 1][j]: + i -= 1 + else: + j -= 1 + return L[m][n], seq -if __name__=='__main__': - x = 'AGGTAB' - y = 'GXTXAYB' - print(lcs_dp(x, y)) + +if __name__ == '__main__': + a = 'AGGTAB' + b = 'GXTXAYB' + expected_ln = 4 + expected_subseq = "GTAB" + + ln, subseq = longest_common_subsequence(a, b) + assert expected_ln == ln + assert expected_subseq == subseq + print("len =", ln, ", sub-sequence =", subseq) + From 47a9ea2b0b4eaef3e748d4d61763a77abc3e48cb Mon Sep 17 00:00:00 2001 From: Christian Clauss Date: Mon, 19 Aug 2019 15:37:49 +0200 Subject: [PATCH 0191/1071] Simplify code by dropping support for legacy Python (#1143) * Simplify code by dropping support for legacy Python * sort() --> sorted() --- CONTRIBUTING.md | 20 +++--- ciphers/affine_cipher.py | 1 - ciphers/atbash.py | 20 ++---- ciphers/brute_force_caesar_cipher.py | 1 - ciphers/onepad_cipher.py | 4 +- ciphers/rabin_miller.py | 1 - ciphers/rot13.py | 1 - ciphers/rsa_cipher.py | 3 +- ciphers/rsa_key_generator.py | 1 - ciphers/simple_substitution_cipher.py | 5 +- ciphers/transposition_cipher.py | 1 - ...ansposition_cipher_encrypt_decrypt_file.py | 7 +- ciphers/vigenere_cipher.py | 1 - .../binary_tree/binary_search_tree.py | 15 ++-- data_structures/binary_tree/fenwick_tree.py | 1 - .../binary_tree/lazy_segment_tree.py | 1 - data_structures/binary_tree/segment_tree.py | 1 - data_structures/heap/heap.py | 13 +--- .../linked_list/doubly_linked_list.py | 27 ++++--- .../linked_list/singly_linked_list.py | 13 ++-- data_structures/queue/double_ended_queue.py | 1 - .../stacks/balanced_parentheses.py | 3 - .../stacks/infix_to_postfix_conversion.py | 2 - .../stacks/next_greater_element.py | 9 ++- data_structures/stacks/stack.py | 1 - data_structures/stacks/stock_span_problem.py | 1 - divide_and_conquer/convex_hull.py | 12 ++-- divide_and_conquer/inversions.py | 14 ++-- dynamic_programming/bitmask.py | 35 +++++---- dynamic_programming/coin_change.py | 3 - dynamic_programming/edit_distance.py | 23 ++---- dynamic_programming/fast_fibonacci.py | 1 - dynamic_programming/fibonacci.py | 14 ++-- dynamic_programming/integer_partition.py | 22 ++---- .../longest_common_subsequence.py | 2 - .../longest_increasing_subsequence.py | 4 +- ...longest_increasing_subsequence_o(nlogn).py | 21 +++--- dynamic_programming/longest_sub_array.py | 1 - dynamic_programming/matrix_chain_order.py | 2 - dynamic_programming/max_sub_array.py | 23 +++--- graphs/a_star.py | 16 ++--- graphs/basic_graphs.py | 53 ++++++-------- graphs/bellman_ford.py | 2 - graphs/breadth_first_search.py | 2 - graphs/depth_first_search.py | 1 - graphs/dijkstra_2.py | 2 - graphs/dijkstra_algorithm.py | 1 - graphs/even_tree.py | 1 - graphs/graph_list.py | 1 - graphs/graph_matrix.py | 3 - graphs/graphs_floyd_warshall.py | 8 +-- graphs/minimum_spanning_tree_kruskal.py | 2 - graphs/multi_hueristic_astar.py | 8 +-- graphs/scc_kosaraju.py | 3 - hashes/chaos_machine.py | 8 +-- hashes/enigma_machine.py | 2 - hashes/md5.py | 5 +- machine_learning/decision_tree.py | 10 ++- machine_learning/gradient_descent.py | 1 - machine_learning/k_means_clust.py | 71 +++++++++---------- machine_learning/linear_regression.py | 2 - maths/simpson_rule.py | 3 - maths/trapezoidal_rule.py | 4 +- maths/zellers_congruence.py | 5 +- ...h_fibonacci_using_matrix_exponentiation.py | 3 - neural_network/convolution_neural_network.py | 2 - neural_network/perceptron.py | 2 - other/anagrams.py | 1 - other/euclidean_gcd.py | 1 - other/linear_congruential_generator.py | 7 +- other/nested_brackets.py | 3 - other/password_generator.py | 1 - other/tower_of_hanoi.py | 3 +- other/two_sum.py | 4 +- other/word_patterns.py | 1 - project_euler/problem_01/sol1.py | 12 +--- project_euler/problem_01/sol2.py | 10 +-- project_euler/problem_01/sol3.py | 12 +--- project_euler/problem_01/sol4.py | 12 +--- project_euler/problem_01/sol5.py | 10 +-- project_euler/problem_01/sol6.py | 12 +--- project_euler/problem_02/sol1.py | 12 +--- project_euler/problem_02/sol2.py | 12 +--- project_euler/problem_02/sol3.py | 12 +--- project_euler/problem_02/sol4.py | 10 +-- project_euler/problem_03/sol1.py | 8 +-- project_euler/problem_03/sol2.py | 8 +-- project_euler/problem_04/sol1.py | 12 +--- project_euler/problem_04/sol2.py | 12 +--- project_euler/problem_05/sol1.py | 10 +-- project_euler/problem_05/sol2.py | 11 +-- project_euler/problem_06/sol1.py | 12 +--- project_euler/problem_06/sol2.py | 12 +--- project_euler/problem_06/sol3.py | 10 +-- project_euler/problem_07/sol1.py | 10 +-- project_euler/problem_07/sol2.py | 12 +--- project_euler/problem_07/sol3.py | 10 +-- project_euler/problem_09/sol1.py | 3 +- project_euler/problem_09/sol2.py | 12 +--- project_euler/problem_09/sol3.py | 3 - project_euler/problem_10/sol1.py | 19 ++--- project_euler/problem_10/sol2.py | 10 +-- project_euler/problem_11/sol1.py | 14 ++-- project_euler/problem_11/sol2.py | 26 +++---- project_euler/problem_12/sol1.py | 10 +-- project_euler/problem_12/sol2.py | 5 +- project_euler/problem_14/sol1.py | 12 +--- project_euler/problem_14/sol2.py | 10 +-- project_euler/problem_21/sol1.py | 8 +-- project_euler/problem_22/sol1.py | 6 -- project_euler/problem_25/sol1.py | 8 +-- project_euler/problem_28/sol1.py | 7 +- project_euler/problem_29/solution.py | 5 +- project_euler/problem_31/sol1.py | 8 --- project_euler/problem_36/sol1.py | 8 +-- project_euler/problem_40/sol1.py | 5 +- project_euler/problem_48/sol1.py | 7 +- project_euler/problem_53/sol1.py | 10 +-- project_euler/problem_76/sol1.py | 14 ++-- searches/binary_search.py | 14 ++-- searches/interpolation_search.py | 24 +++---- searches/jump_search.py | 1 - searches/linear_search.py | 9 +-- searches/sentinel_linear_search.py | 9 +-- searches/ternary_search.py | 29 +++----- sorts/bogo_sort.py | 8 +-- sorts/bubble_sort.py | 19 +++-- sorts/cocktail_shaker_sort.py | 15 ++-- sorts/comb_sort.py | 7 +- sorts/counting_sort.py | 13 ++-- sorts/cycle_sort.py | 10 +-- sorts/gnome_sort.py | 9 +-- sorts/heap_sort.py | 10 +-- sorts/insertion_sort.py | 10 +-- sorts/merge_sort.py | 10 +-- sorts/merge_sort_fastest.py | 10 +-- sorts/pigeon_sort.py | 10 +-- sorts/quick_sort.py | 10 +-- sorts/quick_sort_3_partition.py | 9 +-- sorts/random_normal_distribution_quicksort.py | 22 +++--- sorts/selection_sort.py | 10 +-- sorts/shell_sort.py | 10 +-- sorts/topological_sort.py | 1 - strings/levenshtein_distance.py | 9 +-- strings/min_cost_string_conversion.py | 31 ++++---- 145 files changed, 367 insertions(+), 976 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 3202b817f1c5..8c0f54ad528d 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -56,7 +56,7 @@ We want your work to be readable by others; therefore, we encourage you to note ```python """ - This function sums a and b + This function sums a and b """ def sum(a, b): return a + b @@ -82,13 +82,13 @@ We want your work to be readable by others; therefore, we encourage you to note The following "testing" approaches are **not** encouraged: ```python - input('Enter your input:') + input('Enter your input:') # Or even worse... - input = eval(raw_input("Enter your input: ")) + input = eval(input("Enter your input: ")) ``` - + However, if your code uses __input()__ then we encourage you to gracefully deal with leading and trailing whitespace in user input by adding __.strip()__ to the end as in: - + ```python starting_value = int(input("Please enter a starting value: ").strip()) ``` @@ -99,13 +99,13 @@ We want your work to be readable by others; therefore, we encourage you to note def sumab(a, b): return a + b # Write tests this way: - print(sumab(1,2)) # 1+2 = 3 - print(sumab(6,4)) # 6+4 = 10 + print(sumab(1, 2)) # 1+2 = 3 + print(sumab(6, 4)) # 6+4 = 10 # Or this way: - print("1 + 2 = ", sumab(1,2)) # 1+2 = 3 - print("6 + 4 = ", sumab(6,4)) # 6+4 = 10 + print("1 + 2 = ", sumab(1, 2)) # 1+2 = 3 + print("6 + 4 = ", sumab(6, 4)) # 6+4 = 10 ``` - + Better yet, if you know how to write [__doctests__](https://docs.python.org/3/library/doctest.html), please consider adding them. - Avoid importing external libraries for basic algorithms. Only use those libraries for complicated algorithms. diff --git a/ciphers/affine_cipher.py b/ciphers/affine_cipher.py index af5f4e0ff4c6..a5d94f087dbf 100644 --- a/ciphers/affine_cipher.py +++ b/ciphers/affine_cipher.py @@ -1,4 +1,3 @@ -from __future__ import print_function import sys, random, cryptomath_module as cryptoMath SYMBOLS = r""" !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~""" diff --git a/ciphers/atbash.py b/ciphers/atbash.py index 5653f0213745..9ed47e0874f8 100644 --- a/ciphers/atbash.py +++ b/ciphers/atbash.py @@ -1,23 +1,15 @@ -try: # Python 2 - raw_input - unichr -except NameError: # Python 3 - raw_input = input - unichr = chr - - -def Atbash(): +def atbash(): output="" - for i in raw_input("Enter the sentence to be encrypted ").strip(): + for i in input("Enter the sentence to be encrypted ").strip(): extract = ord(i) if 65 <= extract <= 90: - output += unichr(155-extract) + output += chr(155-extract) elif 97 <= extract <= 122: - output += unichr(219-extract) + output += chr(219-extract) else: - output+=i + output += i print(output) if __name__ == '__main__': - Atbash() + atbash() diff --git a/ciphers/brute_force_caesar_cipher.py b/ciphers/brute_force_caesar_cipher.py index 3b0716442fc5..3e6e975c8297 100644 --- a/ciphers/brute_force_caesar_cipher.py +++ b/ciphers/brute_force_caesar_cipher.py @@ -1,4 +1,3 @@ -from __future__ import print_function def decrypt(message): """ >>> decrypt('TMDETUX PMDVU') diff --git a/ciphers/onepad_cipher.py b/ciphers/onepad_cipher.py index 6afbd45249ec..1dac270bda1f 100644 --- a/ciphers/onepad_cipher.py +++ b/ciphers/onepad_cipher.py @@ -1,5 +1,3 @@ -from __future__ import print_function - import random @@ -15,7 +13,7 @@ def encrypt(self, text): cipher.append(c) key.append(k) return cipher, key - + def decrypt(self, cipher, key): '''Function to decrypt text using psedo-random numbers.''' plain = [] diff --git a/ciphers/rabin_miller.py b/ciphers/rabin_miller.py index f71fb03c0051..21378cff6885 100644 --- a/ciphers/rabin_miller.py +++ b/ciphers/rabin_miller.py @@ -1,4 +1,3 @@ -from __future__ import print_function # Primality Testing with the Rabin-Miller Algorithm import random diff --git a/ciphers/rot13.py b/ciphers/rot13.py index 2abf981e9d7d..208de4890e67 100644 --- a/ciphers/rot13.py +++ b/ciphers/rot13.py @@ -1,4 +1,3 @@ -from __future__ import print_function def dencrypt(s, n): out = '' for c in s: diff --git a/ciphers/rsa_cipher.py b/ciphers/rsa_cipher.py index d81f1ffc1a1e..02e5d95d1e95 100644 --- a/ciphers/rsa_cipher.py +++ b/ciphers/rsa_cipher.py @@ -1,4 +1,3 @@ -from __future__ import print_function import sys, rsa_key_generator as rkg, os DEFAULT_BLOCK_SIZE = 128 @@ -16,7 +15,7 @@ def main(): if mode == 'encrypt': if not os.path.exists('rsa_pubkey.txt'): rkg.makeKeyFiles('rsa', 1024) - + message = input('\nEnter message: ') pubKeyFilename = 'rsa_pubkey.txt' print('Encrypting and writing to %s...' % (filename)) diff --git a/ciphers/rsa_key_generator.py b/ciphers/rsa_key_generator.py index 541e90d6e884..7cd7163b68d5 100644 --- a/ciphers/rsa_key_generator.py +++ b/ciphers/rsa_key_generator.py @@ -1,4 +1,3 @@ -from __future__ import print_function import random, sys, os import rabin_miller as rabinMiller, cryptomath_module as cryptoMath diff --git a/ciphers/simple_substitution_cipher.py b/ciphers/simple_substitution_cipher.py index 1bdd7dc04a57..5da07f8526b9 100644 --- a/ciphers/simple_substitution_cipher.py +++ b/ciphers/simple_substitution_cipher.py @@ -1,4 +1,3 @@ -from __future__ import print_function import sys, random LETTERS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' @@ -18,7 +17,7 @@ def main(): translated = decryptMessage(key, message) print('\n%sion: \n%s' % (mode.title(), translated)) - + def checkValidKey(key): keyList = list(key) lettersList = list(LETTERS) @@ -49,7 +48,7 @@ def translateMessage(key, message, mode): if mode == 'decrypt': charsA, charsB = charsB, charsA - + for symbol in message: if symbol.upper() in charsA: symIndex = charsA.find(symbol.upper()) diff --git a/ciphers/transposition_cipher.py b/ciphers/transposition_cipher.py index dbb358315d22..1c2ed0aa0452 100644 --- a/ciphers/transposition_cipher.py +++ b/ciphers/transposition_cipher.py @@ -1,4 +1,3 @@ -from __future__ import print_function import math def main(): diff --git a/ciphers/transposition_cipher_encrypt_decrypt_file.py b/ciphers/transposition_cipher_encrypt_decrypt_file.py index a186cf81cde7..8ebfc1ea7e0c 100644 --- a/ciphers/transposition_cipher_encrypt_decrypt_file.py +++ b/ciphers/transposition_cipher_encrypt_decrypt_file.py @@ -1,4 +1,3 @@ -from __future__ import print_function import time, os, sys import transposition_cipher as transCipher @@ -16,7 +15,7 @@ def main(): response = input('> ') if not response.lower().startswith('y'): sys.exit() - + startTime = time.time() if mode.lower().startswith('e'): with open(inputFile) as f: @@ -29,9 +28,9 @@ def main(): with open(outputFile, 'w') as outputObj: outputObj.write(translated) - + totalTime = round(time.time() - startTime, 2) print(('Done (', totalTime, 'seconds )')) - + if __name__ == '__main__': main() diff --git a/ciphers/vigenere_cipher.py b/ciphers/vigenere_cipher.py index 5d5be0792835..95eeb431109f 100644 --- a/ciphers/vigenere_cipher.py +++ b/ciphers/vigenere_cipher.py @@ -1,4 +1,3 @@ -from __future__ import print_function LETTERS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' def main(): diff --git a/data_structures/binary_tree/binary_search_tree.py b/data_structures/binary_tree/binary_search_tree.py index cef5b55f245d..634b6cbcc231 100644 --- a/data_structures/binary_tree/binary_search_tree.py +++ b/data_structures/binary_tree/binary_search_tree.py @@ -1,7 +1,6 @@ ''' A binary search Tree ''' -from __future__ import print_function class Node: def __init__(self, label, parent): @@ -66,8 +65,8 @@ def insert(self, label): else: parent_node.setRight(new_node) #Set parent to the new node - new_node.setParent(parent_node) - + new_node.setParent(parent_node) + def delete(self, label): if (not self.empty()): #Look for the node with that label @@ -92,7 +91,7 @@ def delete(self, label): self.delete(tmpNode.getLabel()) #Assigns the value to the node to delete and keesp tree structure node.setLabel(tmpNode.getLabel()) - + def getNode(self, label): curr_node = None #If the tree is not empty @@ -177,7 +176,7 @@ def traversalTree(self, traversalFunction = None, root = None): #Returns a list of nodes in the order that the users wants to return traversalFunction(self.root) - #Returns an string of all the nodes labels in the list + #Returns an string of all the nodes labels in the list #In Order Traversal def __str__(self): list = self.__InOrderTraversal(self.root) @@ -203,7 +202,7 @@ def testBinarySearchTree(): / \ \ 1 6 14 / \ / - 4 7 13 + 4 7 13 ''' r''' @@ -236,11 +235,11 @@ def testBinarySearchTree(): print("The label -1 exists") else: print("The label -1 doesn't exist") - + if(not t.empty()): print(("Max Value: ", t.getMax().getLabel())) print(("Min Value: ", t.getMin().getLabel())) - + t.delete(13) t.delete(10) t.delete(8) diff --git a/data_structures/binary_tree/fenwick_tree.py b/data_structures/binary_tree/fenwick_tree.py index ef984082d9e8..30a87fbd7fcf 100644 --- a/data_structures/binary_tree/fenwick_tree.py +++ b/data_structures/binary_tree/fenwick_tree.py @@ -1,4 +1,3 @@ -from __future__ import print_function class FenwickTree: def __init__(self, SIZE): # create fenwick tree with size SIZE diff --git a/data_structures/binary_tree/lazy_segment_tree.py b/data_structures/binary_tree/lazy_segment_tree.py index 215399976dd3..bbe37a6eb97f 100644 --- a/data_structures/binary_tree/lazy_segment_tree.py +++ b/data_structures/binary_tree/lazy_segment_tree.py @@ -1,4 +1,3 @@ -from __future__ import print_function import math class SegmentTree: diff --git a/data_structures/binary_tree/segment_tree.py b/data_structures/binary_tree/segment_tree.py index 7e61198ca59c..da3d15f26b6a 100644 --- a/data_structures/binary_tree/segment_tree.py +++ b/data_structures/binary_tree/segment_tree.py @@ -1,4 +1,3 @@ -from __future__ import print_function import math class SegmentTree: diff --git a/data_structures/heap/heap.py b/data_structures/heap/heap.py index 39778f725c3a..2373d71bb897 100644 --- a/data_structures/heap/heap.py +++ b/data_structures/heap/heap.py @@ -1,15 +1,8 @@ #!/usr/bin/python -from __future__ import print_function, division - -try: - raw_input # Python 2 -except NameError: - raw_input = input # Python 3 - -#This heap class start from here. +# This heap class start from here. class Heap: - def __init__(self): #Default constructor of heap class. + def __init__(self): # Default constructor of heap class. self.h = [] self.currsize = 0 @@ -79,7 +72,7 @@ def display(self): #This function is used to print the heap. print(self.h) def main(): - l = list(map(int, raw_input().split())) + l = list(map(int, input().split())) h = Heap() h.buildHeap(l) h.heapSort() diff --git a/data_structures/linked_list/doubly_linked_list.py b/data_structures/linked_list/doubly_linked_list.py index 75b1f889dfc2..23d91383fa0e 100644 --- a/data_structures/linked_list/doubly_linked_list.py +++ b/data_structures/linked_list/doubly_linked_list.py @@ -4,14 +4,13 @@ - Each link references the next link and the previous one. - A Doubly Linked List (DLL) contains an extra pointer, typically called previous pointer, together with next pointer and data which are there in singly linked list. - Advantages over SLL - IT can be traversed in both forward and backward direction.,Delete operation is more efficent''' -from __future__ import print_function class LinkedList: #making main class named linked list def __init__(self): self.head = None self.tail = None - + def insertHead(self, x): newLink = Link(x) #Create a new link with a value attached to it if(self.isEmpty() == True): #Set the first element added to be the tail @@ -20,52 +19,52 @@ def insertHead(self, x): self.head.previous = newLink # newLink <-- currenthead(head) newLink.next = self.head # newLink <--> currenthead(head) self.head = newLink # newLink(head) <--> oldhead - + def deleteHead(self): temp = self.head - self.head = self.head.next # oldHead <--> 2ndElement(head) + self.head = self.head.next # oldHead <--> 2ndElement(head) self.head.previous = None # oldHead --> 2ndElement(head) nothing pointing at it so the old head will be removed if(self.head is None): self.tail = None #if empty linked list return temp - + def insertTail(self, x): newLink = Link(x) newLink.next = None # currentTail(tail) newLink --> self.tail.next = newLink # currentTail(tail) --> newLink --> newLink.previous = self.tail #currentTail(tail) <--> newLink --> self.tail = newLink # oldTail <--> newLink(tail) --> - + def deleteTail(self): temp = self.tail self.tail = self.tail.previous # 2ndLast(tail) <--> oldTail --> None self.tail.next = None # 2ndlast(tail) --> None return temp - + def delete(self, x): current = self.head - + while(current.value != x): # Find the position to delete current = current.next - + if(current == self.head): self.deleteHead() - + elif(current == self.tail): self.deleteTail() - + else: #Before: 1 <--> 2(current) <--> 3 current.previous.next = current.next # 1 --> 3 current.next.previous = current.previous # 1 <--> 3 - + def isEmpty(self): #Will return True if the list is empty return(self.head is None) - + def display(self): #Prints contents of the list current = self.head while(current != None): current.displayLink() - current = current.next + current = current.next print() class Link: diff --git a/data_structures/linked_list/singly_linked_list.py b/data_structures/linked_list/singly_linked_list.py index 5ae97523b9a1..5943b88d5964 100644 --- a/data_structures/linked_list/singly_linked_list.py +++ b/data_structures/linked_list/singly_linked_list.py @@ -1,6 +1,3 @@ -from __future__ import print_function - - class Node: # create a Node def __init__(self, data): self.data = data # given data @@ -10,7 +7,7 @@ def __init__(self, data): class Linked_List: def __init__(self): self.Head = None # Initialize Head to None - + def insert_tail(self, data): if(self.Head is None): self.insert_head(data) #If this is first node, call insert_head else: @@ -37,7 +34,7 @@ def delete_head(self): # delete from head self.Head = self.Head.next temp.next = None return temp - + def delete_tail(self): # delete from tail tamp = self.Head if self.Head != None: @@ -46,7 +43,7 @@ def delete_tail(self): # delete from tail else: while tamp.next.next is not None: # find the 2nd last element tamp = tamp.next - tamp.next, tamp = None, tamp.next #(2nd last element).next = None and tamp = last element + tamp.next, tamp = None, tamp.next #(2nd last element).next = None and tamp = last element return tamp def isEmpty(self): @@ -79,7 +76,7 @@ def main(): print("\nPrint List : ") A.printList() print("\nInserting 1st at Tail") - a3=input() + a3=input() A.insert_tail(a3) print("Inserting 2nd at Tail") a4=input() @@ -96,6 +93,6 @@ def main(): A.reverse() print("\nPrint List : ") A.printList() - + if __name__ == '__main__': main() diff --git a/data_structures/queue/double_ended_queue.py b/data_structures/queue/double_ended_queue.py index 838bf2f4bc36..a2fc8f66ec22 100644 --- a/data_structures/queue/double_ended_queue.py +++ b/data_structures/queue/double_ended_queue.py @@ -1,4 +1,3 @@ -from __future__ import print_function # Python code to demonstrate working of # extend(), extendleft(), rotate(), reverse() diff --git a/data_structures/stacks/balanced_parentheses.py b/data_structures/stacks/balanced_parentheses.py index 36a4e07a97a3..3f43ccbf5760 100644 --- a/data_structures/stacks/balanced_parentheses.py +++ b/data_structures/stacks/balanced_parentheses.py @@ -1,6 +1,3 @@ -from __future__ import print_function -from __future__ import absolute_import - from .stack import Stack __author__ = 'Omkar Pathak' diff --git a/data_structures/stacks/infix_to_postfix_conversion.py b/data_structures/stacks/infix_to_postfix_conversion.py index 9376b55b8b23..84a5d1480a24 100644 --- a/data_structures/stacks/infix_to_postfix_conversion.py +++ b/data_structures/stacks/infix_to_postfix_conversion.py @@ -1,5 +1,3 @@ -from __future__ import print_function -from __future__ import absolute_import import string from .stack import Stack diff --git a/data_structures/stacks/next_greater_element.py b/data_structures/stacks/next_greater_element.py index bca83339592c..2e67f1764a5a 100644 --- a/data_structures/stacks/next_greater_element.py +++ b/data_structures/stacks/next_greater_element.py @@ -1,17 +1,16 @@ -from __future__ import print_function # Function to print element and NGE pair for all elements of list def printNGE(arr): - + for i in range(0, len(arr), 1): - + next = -1 for j in range(i+1, len(arr), 1): if arr[i] < arr[j]: next = arr[j] break - + print(str(arr[i]) + " -- " + str(next)) - + # Driver program to test above function arr = [11,13,21,3] printNGE(arr) diff --git a/data_structures/stacks/stack.py b/data_structures/stacks/stack.py index 7f979d927d08..387367db2fcc 100644 --- a/data_structures/stacks/stack.py +++ b/data_structures/stacks/stack.py @@ -1,4 +1,3 @@ -from __future__ import print_function __author__ = 'Omkar Pathak' diff --git a/data_structures/stacks/stock_span_problem.py b/data_structures/stacks/stock_span_problem.py index e9afebc193b6..47d916fde9ed 100644 --- a/data_structures/stacks/stock_span_problem.py +++ b/data_structures/stacks/stock_span_problem.py @@ -6,7 +6,6 @@ number of consecutive days just before the given day, for which the price of the stock on the current day is less than or equal to its price on the given day. ''' -from __future__ import print_function def calculateSpan(price, S): n = len(price) diff --git a/divide_and_conquer/convex_hull.py b/divide_and_conquer/convex_hull.py index f15d74ddea68..42219794aed1 100644 --- a/divide_and_conquer/convex_hull.py +++ b/divide_and_conquer/convex_hull.py @@ -1,18 +1,16 @@ -from __future__ import print_function, absolute_import, division - from numbers import Number """ -The convex hull problem is problem of finding all the vertices of convex polygon, P of +The convex hull problem is problem of finding all the vertices of convex polygon, P of a set of points in a plane such that all the points are either on the vertices of P or -inside P. TH convex hull problem has several applications in geometrical problems, -computer graphics and game development. +inside P. TH convex hull problem has several applications in geometrical problems, +computer graphics and game development. -Two algorithms have been implemented for the convex hull problem here. +Two algorithms have been implemented for the convex hull problem here. 1. A brute-force algorithm which runs in O(n^3) 2. A divide-and-conquer algorithm which runs in O(n^3) There are other several other algorithms for the convex hull problem -which have not been implemented here, yet. +which have not been implemented here, yet. """ diff --git a/divide_and_conquer/inversions.py b/divide_and_conquer/inversions.py index 527741cad3b7..e4d50b7d4729 100644 --- a/divide_and_conquer/inversions.py +++ b/divide_and_conquer/inversions.py @@ -1,15 +1,13 @@ -from __future__ import print_function, absolute_import, division - """ Given an array-like data structure A[1..n], how many pairs -(i, j) for all 1 <= i < j <= n such that A[i] > A[j]? These pairs are -called inversions. Counting the number of such inversions in an array-like -object is the important. Among other things, counting inversions can help +(i, j) for all 1 <= i < j <= n such that A[i] > A[j]? These pairs are +called inversions. Counting the number of such inversions in an array-like +object is the important. Among other things, counting inversions can help us determine how close a given array is to being sorted - + In this implementation, I provide two algorithms, a divide-and-conquer -algorithm which runs in nlogn and the brute-force n^2 algorithm. - +algorithm which runs in nlogn and the brute-force n^2 algorithm. + """ diff --git a/dynamic_programming/bitmask.py b/dynamic_programming/bitmask.py index 213b22fe9051..6685e1c68ee6 100644 --- a/dynamic_programming/bitmask.py +++ b/dynamic_programming/bitmask.py @@ -9,27 +9,26 @@ """ -from __future__ import print_function from collections import defaultdict class AssignmentUsingBitmask: def __init__(self,task_performed,total): - + self.total_tasks = total #total no of tasks (N) - + # DP table will have a dimension of (2^M)*N # initially all values are set to -1 self.dp = [[-1 for i in range(total+1)] for j in range(2**len(task_performed))] - + self.task = defaultdict(list) #stores the list of persons for each task - + #finalmask is used to check if all persons are included by setting all bits to 1 self.finalmask = (1< int: dp = [[0 for _ in range(n+1) ] for _ in range(m+1)] for i in range(m+1): for j in range(n+1): - + if i == 0: #first string is empty dp[i][j] = j - elif j == 0: #second string is empty - dp[i][j] = i + elif j == 0: #second string is empty + dp[i][j] = i elif word1[i-1] == word2[j-1]: #last character of both substing is equal dp[i][j] = dp[i-1][j-1] - else: + else: insert = dp[i][j-1] delete = dp[i-1][j] replace = dp[i-1][j-1] @@ -82,21 +81,13 @@ def min_distance_bottom_up(word1: str, word2: str) -> int: return dp[m][n] if __name__ == '__main__': - try: - raw_input # Python 2 - except NameError: - raw_input = input # Python 3 - solver = EditDistance() print("****************** Testing Edit Distance DP Algorithm ******************") print() - print("Enter the first string: ", end="") - S1 = raw_input().strip() - - print("Enter the second string: ", end="") - S2 = raw_input().strip() + S1 = input("Enter the first string: ").strip() + S2 = input("Enter the second string: ").strip() print() print("The minimum Edit Distance is: %d" % (solver.solve(S1, S2))) @@ -106,4 +97,4 @@ def min_distance_bottom_up(word1: str, word2: str) -> int: - + diff --git a/dynamic_programming/fast_fibonacci.py b/dynamic_programming/fast_fibonacci.py index cbc118467b3c..47248078bd81 100644 --- a/dynamic_programming/fast_fibonacci.py +++ b/dynamic_programming/fast_fibonacci.py @@ -5,7 +5,6 @@ This program calculates the nth Fibonacci number in O(log(n)). It's possible to calculate F(1000000) in less than a second. """ -from __future__ import print_function import sys diff --git a/dynamic_programming/fibonacci.py b/dynamic_programming/fibonacci.py index b453ce255853..90fe6386044a 100644 --- a/dynamic_programming/fibonacci.py +++ b/dynamic_programming/fibonacci.py @@ -1,7 +1,6 @@ """ This is a pure Python implementation of Dynamic Programming solution to the fibonacci sequence problem. """ -from __future__ import print_function class Fibonacci: @@ -29,21 +28,16 @@ def get(self, sequence_no=None): if __name__ == '__main__': print("\n********* Fibonacci Series Using Dynamic Programming ************\n") - try: - raw_input # Python 2 - except NameError: - raw_input = input # Python 3 - print("\n Enter the upper limit for the fibonacci sequence: ", end="") try: - N = eval(raw_input().strip()) + N = int(input().strip()) fib = Fibonacci(N) print( - "\n********* Enter different values to get the corresponding fibonacci sequence, enter any negative number to exit. ************\n") + "\n********* Enter different values to get the corresponding fibonacci " + "sequence, enter any negative number to exit. ************\n") while True: - print("Enter value: ", end=" ") try: - i = eval(raw_input().strip()) + i = int(input("Enter value: ").strip()) if i < 0: print("\n********* Good Bye!! ************\n") break diff --git a/dynamic_programming/integer_partition.py b/dynamic_programming/integer_partition.py index 7b27afebaa6c..f17561fc135b 100644 --- a/dynamic_programming/integer_partition.py +++ b/dynamic_programming/integer_partition.py @@ -1,27 +1,15 @@ -from __future__ import print_function - -try: - xrange #Python 2 -except NameError: - xrange = range #Python 3 - -try: - raw_input #Python 2 -except NameError: - raw_input = input #Python 3 - ''' The number of partitions of a number n into at least k parts equals the number of partitions into exactly k parts plus the number of partitions into at least k-1 parts. Subtracting 1 from each part of a partition of n into k parts gives a partition of n-k into k parts. These two facts together are used for this algorithm. ''' def partition(m): - memo = [[0 for _ in xrange(m)] for _ in xrange(m+1)] - for i in xrange(m+1): + memo = [[0 for _ in range(m)] for _ in range(m+1)] + for i in range(m+1): memo[i][0] = 1 - for n in xrange(m+1): - for k in xrange(1, m): + for n in range(m+1): + for k in range(1, m): memo[n][k] += memo[n][k-1] if n-k > 0: memo[n][k] += memo[n-k-1][k] @@ -33,7 +21,7 @@ def partition(m): if len(sys.argv) == 1: try: - n = int(raw_input('Enter a number: ')) + n = int(input('Enter a number: ').strip()) print(partition(n)) except ValueError: print('Please enter a number.') diff --git a/dynamic_programming/longest_common_subsequence.py b/dynamic_programming/longest_common_subsequence.py index 7836fe303688..7447a0cc7810 100644 --- a/dynamic_programming/longest_common_subsequence.py +++ b/dynamic_programming/longest_common_subsequence.py @@ -3,7 +3,6 @@ A subsequence is a sequence that appears in the same relative order, but not necessarily continuous. Example:"abc", "abg" are subsequences of "abcdefgh". """ -from __future__ import print_function def longest_common_subsequence(x: str, y: str): @@ -80,4 +79,3 @@ def longest_common_subsequence(x: str, y: str): assert expected_ln == ln assert expected_subseq == subseq print("len =", ln, ", sub-sequence =", subseq) - diff --git a/dynamic_programming/longest_increasing_subsequence.py b/dynamic_programming/longest_increasing_subsequence.py index b6d165909e70..151a5e0b7c80 100644 --- a/dynamic_programming/longest_increasing_subsequence.py +++ b/dynamic_programming/longest_increasing_subsequence.py @@ -7,10 +7,8 @@ Given an ARRAY, to find the longest and increasing sub ARRAY in that given ARRAY and return it. Example: [10, 22, 9, 33, 21, 50, 41, 60, 80] as input will return [10, 22, 33, 41, 60, 80] as output ''' -from __future__ import print_function - def longestSub(ARRAY): #This function is recursive - + ARRAY_LENGTH = len(ARRAY) if(ARRAY_LENGTH <= 1): #If the array contains only one element, we return it (it's the stop condition of recursion) return ARRAY diff --git a/dynamic_programming/longest_increasing_subsequence_o(nlogn).py b/dynamic_programming/longest_increasing_subsequence_o(nlogn).py index 86bec089adc7..9b27ed6be303 100644 --- a/dynamic_programming/longest_increasing_subsequence_o(nlogn).py +++ b/dynamic_programming/longest_increasing_subsequence_o(nlogn).py @@ -1,9 +1,8 @@ -from __future__ import print_function ############################# # Author: Aravind Kashyap # File: lis.py # comments: This programme outputs the Longest Strictly Increasing Subsequence in O(NLogN) -# Where N is the Number of elements in the list +# Where N is the Number of elements in the list ############################# def CeilIndex(v,l,r,key): while r-l > 1: @@ -12,30 +11,30 @@ def CeilIndex(v,l,r,key): r = m else: l = m - + return r - + def LongestIncreasingSubsequenceLength(v): if(len(v) == 0): - return 0 - + return 0 + tail = [0]*len(v) length = 1 - + tail[0] = v[0] - + for i in range(1,len(v)): if v[i] < tail[0]: tail[0] = v[i] elif v[i] > tail[length-1]: tail[length] = v[i] - length += 1 + length += 1 else: tail[CeilIndex(tail,-1,length-1,v[i])] = v[i] - + return length - + if __name__ == "__main__": v = [2, 5, 3, 7, 11, 8, 10, 13, 6] diff --git a/dynamic_programming/longest_sub_array.py b/dynamic_programming/longest_sub_array.py index de2c88a8b525..856b31f03982 100644 --- a/dynamic_programming/longest_sub_array.py +++ b/dynamic_programming/longest_sub_array.py @@ -6,7 +6,6 @@ The problem is : Given an array, to find the longest and continuous sub array and get the max sum of the sub array in the given array. ''' -from __future__ import print_function class SubArray: diff --git a/dynamic_programming/matrix_chain_order.py b/dynamic_programming/matrix_chain_order.py index b8234a65acbe..cb4aec345437 100644 --- a/dynamic_programming/matrix_chain_order.py +++ b/dynamic_programming/matrix_chain_order.py @@ -1,5 +1,3 @@ -from __future__ import print_function - import sys ''' Dynamic Programming diff --git a/dynamic_programming/max_sub_array.py b/dynamic_programming/max_sub_array.py index 56983b7d22c2..d6084ecfd6d9 100644 --- a/dynamic_programming/max_sub_array.py +++ b/dynamic_programming/max_sub_array.py @@ -1,7 +1,6 @@ """ author : Mayank Kumar Jha (mk9440) """ -from __future__ import print_function from typing import List import time import matplotlib.pyplot as plt @@ -10,7 +9,7 @@ def find_max_sub_array(A,low,high): if low==high: return low,high,A[low] else : - mid=(low+high)//2 + mid=(low+high)//2 left_low,left_high,left_sum=find_max_sub_array(A,low,mid) right_low,right_high,right_sum=find_max_sub_array(A,mid+1,high) cross_left,cross_right,cross_sum=find_max_cross_sum(A,low,mid,high) @@ -30,7 +29,7 @@ def find_max_cross_sum(A,low,mid,high): if summ > left_sum: left_sum=summ max_left=i - summ=0 + summ=0 for i in range(mid+1,high+1): summ+=A[i] if summ > right_sum: @@ -40,7 +39,7 @@ def find_max_cross_sum(A,low,mid,high): def max_sub_array(nums: List[int]) -> int: """ - Finds the contiguous subarray (can be empty array) + Finds the contiguous subarray (can be empty array) which has the largest sum and return its sum. >>> max_sub_array([-2,1,-3,4,-1,2,1,-5,4]) @@ -50,14 +49,14 @@ def max_sub_array(nums: List[int]) -> int: >>> max_sub_array([-1,-2,-3]) 0 """ - best = 0 - current = 0 - for i in nums: - current += i + best = 0 + current = 0 + for i in nums: + current += i if current < 0: current = 0 best = max(best, current) - return best + return best if __name__=='__main__': inputs=[10,100,1000,10000,50000,100000,200000,300000,400000,500000] @@ -68,8 +67,8 @@ def max_sub_array(nums: List[int]) -> int: (find_max_sub_array(li,0,len(li)-1)) end=time.time() tim.append(end-strt) - print("No of Inputs Time Taken") - for i in range(len(inputs)): + print("No of Inputs Time Taken") + for i in range(len(inputs)): print(inputs[i],'\t\t',tim[i]) plt.plot(inputs,tim) plt.xlabel("Number of Inputs");plt.ylabel("Time taken in seconds ") @@ -77,4 +76,4 @@ def max_sub_array(nums: List[int]) -> int: - + diff --git a/graphs/a_star.py b/graphs/a_star.py index 584222e6f62b..09a7a0e579d8 100644 --- a/graphs/a_star.py +++ b/graphs/a_star.py @@ -1,5 +1,3 @@ -from __future__ import print_function - grid = [[0, 1, 0, 0, 0, 0], [0, 1, 0, 0, 0, 0],#0 are free path whereas 1's are obstacles [0, 1, 0, 0, 0, 0], @@ -14,13 +12,13 @@ [5, 4, 3, 2, 1, 0]]''' init = [0, 0] -goal = [len(grid)-1, len(grid[0])-1] #all coordinates are given in format [y,x] +goal = [len(grid)-1, len(grid[0])-1] #all coordinates are given in format [y,x] cost = 1 #the cost map which pushes the path closer to the goal heuristic = [[0 for row in range(len(grid[0]))] for col in range(len(grid))] -for i in range(len(grid)): - for j in range(len(grid[0])): +for i in range(len(grid)): + for j in range(len(grid[0])): heuristic[i][j] = abs(i - goal[0]) + abs(j - goal[1]) if grid[i][j] == 1: heuristic[i][j] = 99 #added extra penalty in the heuristic map @@ -62,7 +60,7 @@ def search(grid,init,goal,cost,heuristic): g = next[1] f = next[0] - + if x == goal[0] and y == goal[1]: found = True else: @@ -93,10 +91,10 @@ def search(grid,init,goal,cost,heuristic): print("ACTION MAP") for i in range(len(action)): print(action[i]) - + return path - + a = search(grid,init,goal,cost,heuristic) for i in range(len(a)): - print(a[i]) + print(a[i]) diff --git a/graphs/basic_graphs.py b/graphs/basic_graphs.py index ee63ca995de6..64c51e139cca 100644 --- a/graphs/basic_graphs.py +++ b/graphs/basic_graphs.py @@ -1,23 +1,10 @@ -from __future__ import print_function - -try: - raw_input # Python 2 -except NameError: - raw_input = input # Python 3 - -try: - xrange # Python 2 -except NameError: - xrange = range # Python 3 - - if __name__ == "__main__": # Accept No. of Nodes and edges - n, m = map(int, raw_input().split(" ")) + n, m = map(int, input().split(" ")) # Initialising Dictionary of edges g = {} - for i in xrange(n): + for i in range(n): g[i + 1] = [] """ @@ -25,8 +12,8 @@ Accepting edges of Unweighted Directed Graphs ---------------------------------------------------------------------------- """ - for _ in xrange(m): - x, y = map(int, raw_input().strip().split(" ")) + for _ in range(m): + x, y = map(int, input().strip().split(" ")) g[x].append(y) """ @@ -34,8 +21,8 @@ Accepting edges of Unweighted Undirected Graphs ---------------------------------------------------------------------------- """ - for _ in xrange(m): - x, y = map(int, raw_input().strip().split(" ")) + for _ in range(m): + x, y = map(int, input().strip().split(" ")) g[x].append(y) g[y].append(x) @@ -44,8 +31,8 @@ Accepting edges of Weighted Undirected Graphs ---------------------------------------------------------------------------- """ - for _ in xrange(m): - x, y, r = map(int, raw_input().strip().split(" ")) + for _ in range(m): + x, y, r = map(int, input().strip().split(" ")) g[x].append([y, r]) g[y].append([x, r]) @@ -170,10 +157,10 @@ def topo(G, ind=None, Q=[1]): def adjm(): - n = raw_input().strip() + n = input().strip() a = [] - for i in xrange(n): - a.append(map(int, raw_input().strip().split())) + for i in range(n): + a.append(map(int, input().strip().split())) return a, n @@ -193,10 +180,10 @@ def adjm(): def floy(A_and_n): (A, n) = A_and_n dist = list(A) - path = [[0] * n for i in xrange(n)] - for k in xrange(n): - for i in xrange(n): - for j in xrange(n): + path = [[0] * n for i in range(n)] + for k in range(n): + for i in range(n): + for j in range(n): if dist[i][j] > dist[i][k] + dist[k][j]: dist[i][j] = dist[i][k] + dist[k][j] path[i][k] = k @@ -245,10 +232,10 @@ def prim(G, s): def edglist(): - n, m = map(int, raw_input().split(" ")) + n, m = map(int, input().split(" ")) l = [] - for i in xrange(m): - l.append(map(int, raw_input().split(' '))) + for i in range(m): + l.append(map(int, input().split(' '))) return l, n @@ -272,10 +259,10 @@ def krusk(E_and_n): break print(s) x = E.pop() - for i in xrange(len(s)): + for i in range(len(s)): if x[0] in s[i]: break - for j in xrange(len(s)): + for j in range(len(s)): if x[1] in s[j]: if i == j: break diff --git a/graphs/bellman_ford.py b/graphs/bellman_ford.py index f49157230054..bebe8f354b26 100644 --- a/graphs/bellman_ford.py +++ b/graphs/bellman_ford.py @@ -1,5 +1,3 @@ -from __future__ import print_function - def printDist(dist, V): print("\nVertex Distance") for i in range(V): diff --git a/graphs/breadth_first_search.py b/graphs/breadth_first_search.py index 3992e2d4d892..205f49a6172b 100644 --- a/graphs/breadth_first_search.py +++ b/graphs/breadth_first_search.py @@ -3,8 +3,6 @@ """ Author: OMKAR PATHAK """ -from __future__ import print_function - class Graph(): def __init__(self): diff --git a/graphs/depth_first_search.py b/graphs/depth_first_search.py index 98faf61354f9..2b03683c0047 100644 --- a/graphs/depth_first_search.py +++ b/graphs/depth_first_search.py @@ -2,7 +2,6 @@ # encoding=utf8 """ Author: OMKAR PATHAK """ -from __future__ import print_function class Graph(): diff --git a/graphs/dijkstra_2.py b/graphs/dijkstra_2.py index 8f39aec41906..f6118830c9c0 100644 --- a/graphs/dijkstra_2.py +++ b/graphs/dijkstra_2.py @@ -1,5 +1,3 @@ -from __future__ import print_function - def printDist(dist, V): print("\nVertex Distance") for i in range(V): diff --git a/graphs/dijkstra_algorithm.py b/graphs/dijkstra_algorithm.py index 985c7f6c1301..c43ff37f5336 100644 --- a/graphs/dijkstra_algorithm.py +++ b/graphs/dijkstra_algorithm.py @@ -2,7 +2,6 @@ # Author: Shubham Malik # References: https://en.wikipedia.org/wiki/Dijkstra%27s_algorithm -from __future__ import print_function import math import sys # For storing the vertex set to retreive node with the lowest distance diff --git a/graphs/even_tree.py b/graphs/even_tree.py index 9383ea9a13c1..45d55eecff8a 100644 --- a/graphs/even_tree.py +++ b/graphs/even_tree.py @@ -12,7 +12,6 @@ Note: The tree input will be such that it can always be decomposed into components containing an even number of nodes. """ -from __future__ import print_function # pylint: disable=invalid-name from collections import defaultdict diff --git a/graphs/graph_list.py b/graphs/graph_list.py index 0c981c39d320..2ca363b1d746 100644 --- a/graphs/graph_list.py +++ b/graphs/graph_list.py @@ -1,7 +1,6 @@ #!/usr/bin/python # encoding=utf8 -from __future__ import print_function # Author: OMKAR PATHAK # We can use Python's dictionary for constructing the graph. diff --git a/graphs/graph_matrix.py b/graphs/graph_matrix.py index de25301d6dd1..1998fec8d6fe 100644 --- a/graphs/graph_matrix.py +++ b/graphs/graph_matrix.py @@ -1,6 +1,3 @@ -from __future__ import print_function - - class Graph: def __init__(self, vertex): diff --git a/graphs/graphs_floyd_warshall.py b/graphs/graphs_floyd_warshall.py index a1d12aac02b4..5f159683733f 100644 --- a/graphs/graphs_floyd_warshall.py +++ b/graphs/graphs_floyd_warshall.py @@ -4,8 +4,6 @@ have negative edge weights. """ -from __future__ import print_function - def _print_dist(dist, v): print("\nThe shortest path matrix using Floyd Warshall algorithm\n") @@ -34,9 +32,9 @@ def floyd_warshall(graph, v): 4. The above is repeated for each vertex k in the graph. 5. Whenever distance[i][j] is given a new minimum value, next vertex[i][j] is updated to the next vertex[i][k]. """ - + dist=[[float('inf') for _ in range(v)] for _ in range(v)] - + for i in range(v): for j in range(v): dist[i][j] = graph[i][j] @@ -53,7 +51,7 @@ def floyd_warshall(graph, v): _print_dist(dist, v) return dist, v - + if __name__== '__main__': v = int(input("Enter number of vertices: ")) diff --git a/graphs/minimum_spanning_tree_kruskal.py b/graphs/minimum_spanning_tree_kruskal.py index 975151c90ede..a2211582ec40 100644 --- a/graphs/minimum_spanning_tree_kruskal.py +++ b/graphs/minimum_spanning_tree_kruskal.py @@ -1,5 +1,3 @@ -from __future__ import print_function - if __name__ == "__main__": num_nodes, num_edges = list(map(int, input().strip().split())) diff --git a/graphs/multi_hueristic_astar.py b/graphs/multi_hueristic_astar.py index 1c01fe9aa6d3..3021c4162b8e 100644 --- a/graphs/multi_hueristic_astar.py +++ b/graphs/multi_hueristic_astar.py @@ -1,12 +1,6 @@ -from __future__ import print_function import heapq import numpy as np -try: - xrange # Python 2 -except NameError: - xrange = range # Python 3 - class PriorityQueue: def __init__(self): @@ -96,7 +90,7 @@ def do_something(back_pointer, goal, start): grid[(n-1)][0] = "-" - for i in xrange(n): + for i in range(n): for j in range(n): if (i, j) == (0, n-1): print(grid[i][j], end=' ') diff --git a/graphs/scc_kosaraju.py b/graphs/scc_kosaraju.py index 0d0375203b6d..99564a7cfa35 100644 --- a/graphs/scc_kosaraju.py +++ b/graphs/scc_kosaraju.py @@ -1,6 +1,3 @@ -from __future__ import print_function - - def dfs(u): global g, r, scc, component, visit, stack if visit[u]: return diff --git a/hashes/chaos_machine.py b/hashes/chaos_machine.py index f0a305bfeade..3a7c3950bb29 100644 --- a/hashes/chaos_machine.py +++ b/hashes/chaos_machine.py @@ -1,10 +1,4 @@ """example of simple chaos machine""" -from __future__ import print_function - -try: - input = raw_input # Python 2 -except NameError: - pass # Python 3 # Chaos Machine (K, t, m) K = [0.33, 0.44, 0.55, 0.44, 0.33]; t = 3; m = 5 @@ -96,7 +90,7 @@ def reset(): for chunk in message: push(chunk) -# for controlling +# for controlling inp = "" # Pulling Data (Output) diff --git a/hashes/enigma_machine.py b/hashes/enigma_machine.py index bd410c5cb21d..06215785765f 100644 --- a/hashes/enigma_machine.py +++ b/hashes/enigma_machine.py @@ -1,5 +1,3 @@ -from __future__ import print_function - alphabets = [chr(i) for i in range(32, 126)] gear_one = [i for i in range(len(alphabets))] gear_two = [i for i in range(len(alphabets))] diff --git a/hashes/md5.py b/hashes/md5.py index 7891f2077986..1ad43013363f 100644 --- a/hashes/md5.py +++ b/hashes/md5.py @@ -1,4 +1,3 @@ -from __future__ import print_function import math @@ -66,7 +65,7 @@ def getBlock(bitString): """[summary] Iterator: Returns by each call a list of length 16 with the 32 bit - integer blocks. + integer blocks. Arguments: bitString {[string]} -- [binary string >= 512] @@ -95,7 +94,7 @@ def not32(i): def sum32(a, b): ''' - + ''' return (a + b) % 2**32 diff --git a/machine_learning/decision_tree.py b/machine_learning/decision_tree.py index 71849904ccf2..acdf646875ac 100644 --- a/machine_learning/decision_tree.py +++ b/machine_learning/decision_tree.py @@ -1,10 +1,8 @@ """ Implementation of a basic regression decision tree. Input data set: The input data set must be 1-dimensional with continuous labels. -Output: The decision tree maps a real number input to a real number output. +Output: The decision tree maps a real number input to a real number output. """ -from __future__ import print_function - import numpy as np class Decision_Tree: @@ -19,7 +17,7 @@ def __init__(self, depth = 5, min_leaf_size = 5): def mean_squared_error(self, labels, prediction): """ mean_squared_error: - @param labels: a one dimensional numpy array + @param labels: a one dimensional numpy array @param prediction: a floating point value return value: mean_squared_error calculates the error if prediction is used to estimate the labels """ @@ -32,7 +30,7 @@ def train(self, X, y): """ train: @param X: a one dimensional numpy array - @param y: a one dimensional numpy array. + @param y: a one dimensional numpy array. The contents of y are the labels for the corresponding X values train does not have a return value @@ -135,6 +133,6 @@ def main(): print("Predictions: " + str(predictions)) print("Average error: " + str(avg_error)) - + if __name__ == '__main__': main() \ No newline at end of file diff --git a/machine_learning/gradient_descent.py b/machine_learning/gradient_descent.py index 6387d4939205..9a17113b7ddb 100644 --- a/machine_learning/gradient_descent.py +++ b/machine_learning/gradient_descent.py @@ -1,7 +1,6 @@ """ Implementation of gradient descent algorithm for minimizing cost of a linear hypothesis function. """ -from __future__ import print_function, division import numpy # List of input, output pairs diff --git a/machine_learning/k_means_clust.py b/machine_learning/k_means_clust.py index 368739a45fe9..d0ce0f2599e0 100644 --- a/machine_learning/k_means_clust.py +++ b/machine_learning/k_means_clust.py @@ -17,36 +17,35 @@ Usage: 1. define 'k' value, 'X' features array and 'hetrogeneity' empty list - + 2. create initial_centroids, initial_centroids = get_initial_centroids( - X, - k, + X, + k, seed=0 # seed value for initial centroid generation, None for randomness(default=None) ) 3. find centroids and clusters using kmeans function. - + centroids, cluster_assignment = kmeans( - X, - k, - initial_centroids, + X, + k, + initial_centroids, maxiter=400, - record_heterogeneity=heterogeneity, + record_heterogeneity=heterogeneity, verbose=True # whether to print logs in console or not.(default=False) ) - - + + 4. Plot the loss function, hetrogeneity values for every iteration saved in hetrogeneity list. plot_heterogeneity( - heterogeneity, + heterogeneity, k ) - + 5. Have fun.. - + ''' -from __future__ import print_function from sklearn.metrics import pairwise_distances import numpy as np @@ -57,30 +56,30 @@ def get_initial_centroids(data, k, seed=None): if seed is not None: # useful for obtaining consistent results np.random.seed(seed) n = data.shape[0] # number of data points - + # Pick K indices from range [0, N). rand_indices = np.random.randint(0, n, k) - + # Keep centroids as dense format, as many entries will be nonzero due to averaging. # As long as at least one document in a cluster contains a word, # it will carry a nonzero weight in the TF-IDF vector of the centroid. centroids = data[rand_indices,:] - + return centroids def centroid_pairwise_dist(X,centroids): return pairwise_distances(X,centroids,metric='euclidean') def assign_clusters(data, centroids): - + # Compute distances between each data point and the set of centroids: # Fill in the blank (RHS only) distances_from_centroids = centroid_pairwise_dist(data,centroids) - + # Compute cluster assignments for each data point: # Fill in the blank (RHS only) cluster_assignment = np.argmin(distances_from_centroids,axis=1) - + return cluster_assignment def revise_centroids(data, k, cluster_assignment): @@ -92,23 +91,23 @@ def revise_centroids(data, k, cluster_assignment): centroid = member_data_points.mean(axis=0) new_centroids.append(centroid) new_centroids = np.array(new_centroids) - + return new_centroids def compute_heterogeneity(data, k, centroids, cluster_assignment): - + heterogeneity = 0.0 for i in range(k): - + # Select all data points that belong to cluster i. Fill in the blank (RHS only) member_data_points = data[cluster_assignment==i, :] - + if member_data_points.shape[0] > 0: # check if i-th cluster is non-empty # Compute distances from centroid to data points (RHS only) distances = pairwise_distances(member_data_points, [centroids[i]], metric='euclidean') squared_distances = distances**2 heterogeneity += np.sum(squared_distances) - + return heterogeneity from matplotlib import pyplot as plt @@ -129,36 +128,36 @@ def kmeans(data, k, initial_centroids, maxiter=500, record_heterogeneity=None, v verbose: if True, print how many data points changed their cluster labels in each iteration''' centroids = initial_centroids[:] prev_cluster_assignment = None - - for itr in range(maxiter): + + for itr in range(maxiter): if verbose: print(itr, end='') - + # 1. Make cluster assignments using nearest centroids cluster_assignment = assign_clusters(data,centroids) - + # 2. Compute a new centroid for each of the k clusters, averaging all data points assigned to that cluster. centroids = revise_centroids(data,k, cluster_assignment) - + # Check for convergence: if none of the assignments changed, stop if prev_cluster_assignment is not None and \ (prev_cluster_assignment==cluster_assignment).all(): break - - # Print number of new assignments + + # Print number of new assignments if prev_cluster_assignment is not None: num_changed = np.sum(prev_cluster_assignment!=cluster_assignment) if verbose: - print(' {0:5d} elements changed their cluster assignment.'.format(num_changed)) - + print(' {0:5d} elements changed their cluster assignment.'.format(num_changed)) + # Record heterogeneity convergence metric if record_heterogeneity is not None: # YOUR CODE HERE score = compute_heterogeneity(data,k,centroids,cluster_assignment) record_heterogeneity.append(score) - + prev_cluster_assignment = cluster_assignment[:] - + return centroids, cluster_assignment # Mock test below diff --git a/machine_learning/linear_regression.py b/machine_learning/linear_regression.py index 03f16629e451..9d9738fced8d 100644 --- a/machine_learning/linear_regression.py +++ b/machine_learning/linear_regression.py @@ -7,8 +7,6 @@ fits our dataset. In this particular code, i had used a CSGO dataset (ADR vs Rating). We try to best fit a line through dataset and estimate the parameters. """ -from __future__ import print_function - import requests import numpy as np diff --git a/maths/simpson_rule.py b/maths/simpson_rule.py index 2b237d2e1a4e..5cf9c14b07ee 100644 --- a/maths/simpson_rule.py +++ b/maths/simpson_rule.py @@ -8,9 +8,6 @@ "Simpson Rule" """ -from __future__ import print_function - - def method_2(boundary, steps): # "Simpson Rule" # int(f) = delta_x/2 * (b-a)/3*(f1 + 4f2 + 2f_3 + ... + fn) diff --git a/maths/trapezoidal_rule.py b/maths/trapezoidal_rule.py index 789f263c6991..f5e5fbbc2662 100644 --- a/maths/trapezoidal_rule.py +++ b/maths/trapezoidal_rule.py @@ -7,8 +7,6 @@ "extended trapezoidal rule" """ -from __future__ import print_function - def method_1(boundary, steps): # "extended trapezoidal rule" # int(f) = dx/2 * (f1 + 2f2 + ... + fn) @@ -19,7 +17,7 @@ def method_1(boundary, steps): y = 0.0 y += (h/2.0)*f(a) for i in x_i: - #print(i) + #print(i) y += h*f(i) y += (h/2.0)*f(b) return y diff --git a/maths/zellers_congruence.py b/maths/zellers_congruence.py index e04425eec903..67c5550802ea 100644 --- a/maths/zellers_congruence.py +++ b/maths/zellers_congruence.py @@ -1,4 +1,3 @@ -from __future__ import annotations import datetime import argparse @@ -7,7 +6,7 @@ def zeller(date_input: str) -> str: """ Zellers Congruence Algorithm - Find the day of the week for nearly any Gregorian or Julian calendar date + Find the day of the week for nearly any Gregorian or Julian calendar date >>> zeller('01-31-2010') 'Your date 01-31-2010, is a Sunday!' @@ -108,7 +107,7 @@ def zeller(date_input: str) -> str: # Validate if not 0 < d < 32: raise ValueError("Date must be between 1 - 31") - + # Get second seperator sep_2: str = date_input[5] # Validate diff --git a/matrix/nth_fibonacci_using_matrix_exponentiation.py b/matrix/nth_fibonacci_using_matrix_exponentiation.py index 7491abcae031..57cdfacd47dd 100644 --- a/matrix/nth_fibonacci_using_matrix_exponentiation.py +++ b/matrix/nth_fibonacci_using_matrix_exponentiation.py @@ -13,9 +13,6 @@ So we just need the n times multiplication of the matrix [1,1],[1,0]]. We can decrease the n times multiplication by following the divide and conquer approach. """ -from __future__ import print_function - - def multiply(matrix_a, matrix_b): matrix_c = [] n = len(matrix_a) diff --git a/neural_network/convolution_neural_network.py b/neural_network/convolution_neural_network.py index 0e72f0c0dca2..e4dd0a11db9d 100644 --- a/neural_network/convolution_neural_network.py +++ b/neural_network/convolution_neural_network.py @@ -15,8 +15,6 @@ Date: 2017.9.20 - - - - - -- - - - - - - - - - - - - - - - - - - - - - - ''' -from __future__ import print_function - import pickle import numpy as np import matplotlib.pyplot as plt diff --git a/neural_network/perceptron.py b/neural_network/perceptron.py index 871eca20273b..fdc710597241 100644 --- a/neural_network/perceptron.py +++ b/neural_network/perceptron.py @@ -9,8 +9,6 @@ p2 = 1 ''' -from __future__ import print_function - import random diff --git a/other/anagrams.py b/other/anagrams.py index 29b34fbdc5d3..1e6e38dee139 100644 --- a/other/anagrams.py +++ b/other/anagrams.py @@ -1,4 +1,3 @@ -from __future__ import print_function import collections, pprint, time, os start_time = time.time() diff --git a/other/euclidean_gcd.py b/other/euclidean_gcd.py index 30853e172076..13378379f286 100644 --- a/other/euclidean_gcd.py +++ b/other/euclidean_gcd.py @@ -1,4 +1,3 @@ -from __future__ import print_function # https://en.wikipedia.org/wiki/Euclidean_algorithm def euclidean_gcd(a, b): diff --git a/other/linear_congruential_generator.py b/other/linear_congruential_generator.py index 34abdf34eaf3..7c592a6400b5 100644 --- a/other/linear_congruential_generator.py +++ b/other/linear_congruential_generator.py @@ -1,4 +1,3 @@ -from __future__ import print_function __author__ = "Tobias Carryer" from time import time @@ -7,11 +6,11 @@ class LinearCongruentialGenerator(object): """ A pseudorandom number generator. """ - + def __init__( self, multiplier, increment, modulo, seed=int(time()) ): """ These parameters are saved and used when nextNumber() is called. - + modulo is the largest number that can be generated (exclusive). The most efficent values are powers of 2. 2^32 is a common value. """ @@ -19,7 +18,7 @@ def __init__( self, multiplier, increment, modulo, seed=int(time()) ): self.increment = increment self.modulo = modulo self.seed = seed - + def next_number( self ): """ The smallest number that can be generated is zero. diff --git a/other/nested_brackets.py b/other/nested_brackets.py index 76677d56439a..14147eaa6456 100644 --- a/other/nested_brackets.py +++ b/other/nested_brackets.py @@ -13,9 +13,6 @@ returns true if S is nested and false otherwise. ''' -from __future__ import print_function - - def is_balanced(S): stack = [] diff --git a/other/password_generator.py b/other/password_generator.py index fd0701041240..16b7e16b22a1 100644 --- a/other/password_generator.py +++ b/other/password_generator.py @@ -1,5 +1,4 @@ """Password generator allows you to generate a random password of length N.""" -from __future__ import print_function from random import choice from string import ascii_letters, digits, punctuation diff --git a/other/tower_of_hanoi.py b/other/tower_of_hanoi.py index 9cc5b9e40543..cd6fbf4d88ac 100644 --- a/other/tower_of_hanoi.py +++ b/other/tower_of_hanoi.py @@ -1,5 +1,4 @@ -from __future__ import print_function -def moveTower(height, fromPole, toPole, withPole): +def moveTower(height, fromPole, toPole, withPole): ''' >>> moveTower(3, 'A', 'B', 'C') moving disk from A to B diff --git a/other/two_sum.py b/other/two_sum.py index d4484aa85505..b784da82767a 100644 --- a/other/two_sum.py +++ b/other/two_sum.py @@ -9,8 +9,6 @@ Because nums[0] + nums[1] = 2 + 7 = 9, return [0, 1]. """ -from __future__ import print_function - def twoSum(nums, target): """ :type nums: List[int] @@ -20,7 +18,7 @@ def twoSum(nums, target): chk_map = {} for index, val in enumerate(nums): compl = target - val - if compl in chk_map: + if compl in chk_map: indices = [chk_map[compl], index] print(indices) return [indices] diff --git a/other/word_patterns.py b/other/word_patterns.py index c33d520087f7..1364d1277255 100644 --- a/other/word_patterns.py +++ b/other/word_patterns.py @@ -1,4 +1,3 @@ -from __future__ import print_function import pprint, time def getWordPattern(word): diff --git a/project_euler/problem_01/sol1.py b/project_euler/problem_01/sol1.py index 1433129af303..76b13b852c87 100644 --- a/project_euler/problem_01/sol1.py +++ b/project_euler/problem_01/sol1.py @@ -4,17 +4,9 @@ we get 3,5,6 and 9. The sum of these multiples is 23. Find the sum of all the multiples of 3 or 5 below N. """ -from __future__ import print_function - -try: - raw_input # Python 2 -except NameError: - raw_input = input # Python 3 - - def solution(n): """Returns the sum of all the multiples of 3 or 5 below n. - + >>> solution(3) 0 >>> solution(4) @@ -31,4 +23,4 @@ def solution(n): if __name__ == "__main__": - print(solution(int(raw_input().strip()))) + print(solution(int(input().strip()))) diff --git a/project_euler/problem_01/sol2.py b/project_euler/problem_01/sol2.py index e58fb03a8fb0..8041c7ffa589 100644 --- a/project_euler/problem_01/sol2.py +++ b/project_euler/problem_01/sol2.py @@ -4,17 +4,11 @@ we get 3,5,6 and 9. The sum of these multiples is 23. Find the sum of all the multiples of 3 or 5 below N. """ -from __future__ import print_function - -try: - raw_input # Python 2 -except NameError: - raw_input = input # Python 3 def solution(n): """Returns the sum of all the multiples of 3 or 5 below n. - + >>> solution(3) 0 >>> solution(4) @@ -36,4 +30,4 @@ def solution(n): if __name__ == "__main__": - print(solution(int(raw_input().strip()))) + print(solution(int(input().strip()))) diff --git a/project_euler/problem_01/sol3.py b/project_euler/problem_01/sol3.py index 013ce5e54fdf..532203ddd95d 100644 --- a/project_euler/problem_01/sol3.py +++ b/project_euler/problem_01/sol3.py @@ -4,20 +4,12 @@ we get 3,5,6 and 9. The sum of these multiples is 23. Find the sum of all the multiples of 3 or 5 below N. """ -from __future__ import print_function - -try: - raw_input # Python 2 -except NameError: - raw_input = input # Python 3 - - def solution(n): """ This solution is based on the pattern that the successive numbers in the series follow: 0+3,+2,+1,+3,+1,+2,+3. Returns the sum of all the multiples of 3 or 5 below n. - + >>> solution(3) 0 >>> solution(4) @@ -63,4 +55,4 @@ def solution(n): if __name__ == "__main__": - print(solution(int(raw_input().strip()))) + print(solution(int(input().strip()))) diff --git a/project_euler/problem_01/sol4.py b/project_euler/problem_01/sol4.py index 90403c3bd6a3..3e6712618870 100644 --- a/project_euler/problem_01/sol4.py +++ b/project_euler/problem_01/sol4.py @@ -4,17 +4,9 @@ we get 3,5,6 and 9. The sum of these multiples is 23. Find the sum of all the multiples of 3 or 5 below N. """ -from __future__ import print_function - -try: - raw_input # Python 2 -except NameError: - raw_input = input # Python 3 - - def solution(n): """Returns the sum of all the multiples of 3 or 5 below n. - + >>> solution(3) 0 >>> solution(4) @@ -50,4 +42,4 @@ def solution(n): if __name__ == "__main__": - print(solution(int(raw_input().strip()))) + print(solution(int(input().strip()))) diff --git a/project_euler/problem_01/sol5.py b/project_euler/problem_01/sol5.py index 302fe44f8bfa..bd96d965f92d 100644 --- a/project_euler/problem_01/sol5.py +++ b/project_euler/problem_01/sol5.py @@ -4,19 +4,13 @@ we get 3,5,6 and 9. The sum of these multiples is 23. Find the sum of all the multiples of 3 or 5 below N. """ -from __future__ import print_function - -try: - raw_input # Python 2 -except NameError: - raw_input = input # Python 3 """A straightforward pythonic solution using list comprehension""" def solution(n): """Returns the sum of all the multiples of 3 or 5 below n. - + >>> solution(3) 0 >>> solution(4) @@ -31,4 +25,4 @@ def solution(n): if __name__ == "__main__": - print(solution(int(raw_input().strip()))) + print(solution(int(input().strip()))) diff --git a/project_euler/problem_01/sol6.py b/project_euler/problem_01/sol6.py index cf6e751d4c05..b9c3db4f8550 100644 --- a/project_euler/problem_01/sol6.py +++ b/project_euler/problem_01/sol6.py @@ -4,17 +4,9 @@ we get 3,5,6 and 9. The sum of these multiples is 23. Find the sum of all the multiples of 3 or 5 below N. """ -from __future__ import print_function - -try: - raw_input # Python 2 -except NameError: - raw_input = input # Python 3 - - def solution(n): """Returns the sum of all the multiples of 3 or 5 below n. - + >>> solution(3) 0 >>> solution(4) @@ -37,4 +29,4 @@ def solution(n): if __name__ == "__main__": - print(solution(int(raw_input().strip()))) + print(solution(int(input().strip()))) diff --git a/project_euler/problem_02/sol1.py b/project_euler/problem_02/sol1.py index f61d04e3dfce..d2ad67e2f424 100644 --- a/project_euler/problem_02/sol1.py +++ b/project_euler/problem_02/sol1.py @@ -9,18 +9,10 @@ n, find the sum of the even-valued terms. e.g. for n=10, we have {2,8}, sum is 10. """ -from __future__ import print_function - -try: - raw_input # Python 2 -except NameError: - raw_input = input # Python 3 - - def solution(n): """Returns the sum of all fibonacci sequence even elements that are lower or equals to n. - + >>> solution(10) 10 >>> solution(15) @@ -44,4 +36,4 @@ def solution(n): if __name__ == "__main__": - print(solution(int(raw_input().strip()))) + print(solution(int(input().strip()))) diff --git a/project_euler/problem_02/sol2.py b/project_euler/problem_02/sol2.py index 3e103a6a4373..71f51b695e84 100644 --- a/project_euler/problem_02/sol2.py +++ b/project_euler/problem_02/sol2.py @@ -9,18 +9,10 @@ n, find the sum of the even-valued terms. e.g. for n=10, we have {2,8}, sum is 10. """ -from __future__ import print_function - -try: - raw_input # Python 2 -except NameError: - raw_input = input # Python 3 - - def solution(n): """Returns the sum of all fibonacci sequence even elements that are lower or equals to n. - + >>> solution(10) [2, 8] >>> solution(15) @@ -42,4 +34,4 @@ def solution(n): if __name__ == "__main__": - print(solution(int(raw_input().strip()))) + print(solution(int(input().strip()))) diff --git a/project_euler/problem_02/sol3.py b/project_euler/problem_02/sol3.py index abd9d6c753b8..c698b8e38ab2 100644 --- a/project_euler/problem_02/sol3.py +++ b/project_euler/problem_02/sol3.py @@ -9,18 +9,10 @@ n, find the sum of the even-valued terms. e.g. for n=10, we have {2,8}, sum is 10. """ -from __future__ import print_function - -try: - raw_input # Python 2 -except NameError: - raw_input = input # Python 3 - - def solution(n): """Returns the sum of all fibonacci sequence even elements that are lower or equals to n. - + >>> solution(10) 10 >>> solution(15) @@ -44,4 +36,4 @@ def solution(n): if __name__ == "__main__": - print(solution(int(raw_input().strip()))) + print(solution(int(input().strip()))) diff --git a/project_euler/problem_02/sol4.py b/project_euler/problem_02/sol4.py index 5e8c04899f3d..92ea0a51e026 100644 --- a/project_euler/problem_02/sol4.py +++ b/project_euler/problem_02/sol4.py @@ -9,20 +9,14 @@ n, find the sum of the even-valued terms. e.g. for n=10, we have {2,8}, sum is 10. """ -from __future__ import print_function import math from decimal import Decimal, getcontext -try: - raw_input # Python 2 -except NameError: - raw_input = input # Python 3 - def solution(n): """Returns the sum of all fibonacci sequence even elements that are lower or equals to n. - + >>> solution(10) 10 >>> solution(15) @@ -68,4 +62,4 @@ def solution(n): if __name__ == "__main__": - print(solution(int(raw_input().strip()))) + print(solution(int(input().strip()))) diff --git a/project_euler/problem_03/sol1.py b/project_euler/problem_03/sol1.py index ab19d8b30457..9f8ecc5e6565 100644 --- a/project_euler/problem_03/sol1.py +++ b/project_euler/problem_03/sol1.py @@ -5,14 +5,8 @@ e.g. for 10, largest prime factor = 5. For 17, largest prime factor = 17. """ -from __future__ import print_function, division import math -try: - raw_input # Python 2 -except NameError: - raw_input = input # Python 3 - def isprime(no): if no == 2: @@ -81,4 +75,4 @@ def solution(n): if __name__ == "__main__": - print(solution(int(raw_input().strip()))) + print(solution(int(input().strip()))) diff --git a/project_euler/problem_03/sol2.py b/project_euler/problem_03/sol2.py index f93a0b75f4e0..b6fad079fa31 100644 --- a/project_euler/problem_03/sol2.py +++ b/project_euler/problem_03/sol2.py @@ -5,12 +5,6 @@ e.g. for 10, largest prime factor = 5. For 17, largest prime factor = 17. """ -from __future__ import print_function, division - -try: - raw_input # Python 2 -except NameError: - raw_input = input # Python 3 def solution(n): @@ -60,4 +54,4 @@ def solution(n): if __name__ == "__main__": - print(solution(int(raw_input().strip()))) + print(solution(int(input().strip()))) diff --git a/project_euler/problem_04/sol1.py b/project_euler/problem_04/sol1.py index 7a255f7308e6..51417b146bbf 100644 --- a/project_euler/problem_04/sol1.py +++ b/project_euler/problem_04/sol1.py @@ -6,18 +6,10 @@ Find the largest palindrome made from the product of two 3-digit numbers which is less than N. """ -from __future__ import print_function - -try: - raw_input # Python 2 -except NameError: - raw_input = input # Python 3 - - def solution(n): """Returns the largest palindrome made from the product of two 3-digit numbers which is less than n. - + >>> solution(20000) 19591 >>> solution(30000) @@ -47,4 +39,4 @@ def solution(n): if __name__ == "__main__": - print(solution(int(raw_input().strip()))) + print(solution(int(input().strip()))) diff --git a/project_euler/problem_04/sol2.py b/project_euler/problem_04/sol2.py index 45c6b256daf8..8740ee44a4b4 100644 --- a/project_euler/problem_04/sol2.py +++ b/project_euler/problem_04/sol2.py @@ -6,18 +6,10 @@ Find the largest palindrome made from the product of two 3-digit numbers which is less than N. """ -from __future__ import print_function - -try: - raw_input # Python 2 -except NameError: - raw_input = input # Python 3 - - def solution(n): """Returns the largest palindrome made from the product of two 3-digit numbers which is less than n. - + >>> solution(20000) 19591 >>> solution(30000) @@ -35,4 +27,4 @@ def solution(n): if __name__ == "__main__": - print(solution(int(raw_input().strip()))) + print(solution(int(input().strip()))) diff --git a/project_euler/problem_05/sol1.py b/project_euler/problem_05/sol1.py index e2deb91fb6aa..83c387e4ae6e 100644 --- a/project_euler/problem_05/sol1.py +++ b/project_euler/problem_05/sol1.py @@ -6,14 +6,6 @@ What is the smallest positive number that is evenly divisible(divisible with no remainder) by all of the numbers from 1 to N? """ -from __future__ import print_function - -try: - raw_input # Python 2 -except NameError: - raw_input = input # Python 3 - - def solution(n): """Returns the smallest positive number that is evenly divisible(divisible with no remainder) by all of the numbers from 1 to n. @@ -66,4 +58,4 @@ def solution(n): if __name__ == "__main__": - print(solution(int(raw_input().strip()))) + print(solution(int(input().strip()))) diff --git a/project_euler/problem_05/sol2.py b/project_euler/problem_05/sol2.py index 293dd96f2294..5aa84d21c8e8 100644 --- a/project_euler/problem_05/sol2.py +++ b/project_euler/problem_05/sol2.py @@ -6,13 +6,6 @@ What is the smallest positive number that is evenly divisible(divisible with no remainder) by all of the numbers from 1 to N? """ -from __future__ import print_function - -try: - raw_input # Python 2 -except NameError: - raw_input = input # Python 3 - """ Euclidean GCD Algorithm """ @@ -30,7 +23,7 @@ def lcm(x, y): def solution(n): """Returns the smallest positive number that is evenly divisible(divisible with no remainder) by all of the numbers from 1 to n. - + >>> solution(10) 2520 >>> solution(15) @@ -47,4 +40,4 @@ def solution(n): if __name__ == "__main__": - print(solution(int(raw_input().strip()))) + print(solution(int(input().strip()))) diff --git a/project_euler/problem_06/sol1.py b/project_euler/problem_06/sol1.py index 728701e167c3..0a964272e7e8 100644 --- a/project_euler/problem_06/sol1.py +++ b/project_euler/problem_06/sol1.py @@ -14,18 +14,10 @@ Find the difference between the sum of the squares of the first N natural numbers and the square of the sum. """ -from __future__ import print_function - -try: - raw_input # Python 2 -except NameError: - raw_input = input # Python 3 - - def solution(n): """Returns the difference between the sum of the squares of the first n natural numbers and the square of the sum. - + >>> solution(10) 2640 >>> solution(15) @@ -45,4 +37,4 @@ def solution(n): if __name__ == "__main__": - print(solution(int(raw_input().strip()))) + print(solution(int(input().strip()))) diff --git a/project_euler/problem_06/sol2.py b/project_euler/problem_06/sol2.py index 2c64812d56f8..45d08d244647 100644 --- a/project_euler/problem_06/sol2.py +++ b/project_euler/problem_06/sol2.py @@ -14,18 +14,10 @@ Find the difference between the sum of the squares of the first N natural numbers and the square of the sum. """ -from __future__ import print_function - -try: - raw_input # Python 2 -except NameError: - raw_input = input # Python 3 - - def solution(n): """Returns the difference between the sum of the squares of the first n natural numbers and the square of the sum. - + >>> solution(10) 2640 >>> solution(15) @@ -42,4 +34,4 @@ def solution(n): if __name__ == "__main__": - print(solution(int(raw_input().strip()))) + print(solution(int(input().strip()))) diff --git a/project_euler/problem_06/sol3.py b/project_euler/problem_06/sol3.py index 7d94b1e2254f..f9c5dacb3777 100644 --- a/project_euler/problem_06/sol3.py +++ b/project_euler/problem_06/sol3.py @@ -14,19 +14,13 @@ Find the difference between the sum of the squares of the first N natural numbers and the square of the sum. """ -from __future__ import print_function import math -try: - raw_input # Python 2 -except NameError: - raw_input = input # Python 3 - def solution(n): """Returns the difference between the sum of the squares of the first n natural numbers and the square of the sum. - + >>> solution(10) 2640 >>> solution(15) @@ -42,4 +36,4 @@ def solution(n): if __name__ == "__main__": - print(solution(int(raw_input().strip()))) + print(solution(int(input().strip()))) diff --git a/project_euler/problem_07/sol1.py b/project_euler/problem_07/sol1.py index 403ded568dda..d8d67e157860 100644 --- a/project_euler/problem_07/sol1.py +++ b/project_euler/problem_07/sol1.py @@ -6,14 +6,8 @@ We can see that the 6th prime is 13. What is the Nth prime number? """ -from __future__ import print_function from math import sqrt -try: - raw_input # Python 2 -except NameError: - raw_input = input # Python 3 - def isprime(n): if n == 2: @@ -30,7 +24,7 @@ def isprime(n): def solution(n): """Returns the n-th prime number. - + >>> solution(6) 13 >>> solution(1) @@ -58,4 +52,4 @@ def solution(n): if __name__ == "__main__": - print(solution(int(raw_input().strip()))) + print(solution(int(input().strip()))) diff --git a/project_euler/problem_07/sol2.py b/project_euler/problem_07/sol2.py index 67336f7c1c96..7d078af32176 100644 --- a/project_euler/problem_07/sol2.py +++ b/project_euler/problem_07/sol2.py @@ -6,14 +6,6 @@ We can see that the 6th prime is 13. What is the Nth prime number? """ -from __future__ import print_function - -try: - raw_input # Python 2 -except NameError: - raw_input = input # Python 3 - - def isprime(number): for i in range(2, int(number ** 0.5) + 1): if number % i == 0: @@ -23,7 +15,7 @@ def isprime(number): def solution(n): """Returns the n-th prime number. - + >>> solution(6) 13 >>> solution(1) @@ -73,4 +65,4 @@ def solution(n): if __name__ == "__main__": - print(solution(int(raw_input().strip()))) + print(solution(int(input().strip()))) diff --git a/project_euler/problem_07/sol3.py b/project_euler/problem_07/sol3.py index bc94762604b3..3c28ecf7fb34 100644 --- a/project_euler/problem_07/sol3.py +++ b/project_euler/problem_07/sol3.py @@ -6,15 +6,9 @@ We can see that the 6th prime is 13. What is the Nth prime number? """ -from __future__ import print_function import math import itertools -try: - raw_input # Python 2 -except NameError: - raw_input = input # Python 3 - def primeCheck(number): if number % 2 == 0 and number > 2: @@ -32,7 +26,7 @@ def prime_generator(): def solution(n): """Returns the n-th prime number. - + >>> solution(6) 13 >>> solution(1) @@ -50,4 +44,4 @@ def solution(n): if __name__ == "__main__": - print(solution(int(raw_input().strip()))) + print(solution(int(input().strip()))) diff --git a/project_euler/problem_09/sol1.py b/project_euler/problem_09/sol1.py index d9ebe8760861..3bb5c968115d 100644 --- a/project_euler/problem_09/sol1.py +++ b/project_euler/problem_09/sol1.py @@ -7,7 +7,6 @@ There exists exactly one Pythagorean triplet for which a + b + c = 1000. Find the product abc. """ -from __future__ import print_function def solution(): @@ -17,7 +16,7 @@ def solution(): 1. a < b < c 2. a**2 + b**2 = c**2 3. a + b + c = 1000 - + # The code below has been commented due to slow execution affecting Travis. # >>> solution() # 31875000 diff --git a/project_euler/problem_09/sol2.py b/project_euler/problem_09/sol2.py index 674daae9ec8e..502f334417c8 100644 --- a/project_euler/problem_09/sol2.py +++ b/project_euler/problem_09/sol2.py @@ -7,14 +7,6 @@ There exists exactly one Pythagorean triplet for which a + b + c = 1000. Find the product abc. """ -from __future__ import print_function - -try: - raw_input # Python 2 -except NameError: - raw_input = input # Python 3 - - def solution(n): """ Return the product of a,b,c which are Pythagorean Triplet that satisfies @@ -22,7 +14,7 @@ def solution(n): 1. a < b < c 2. a**2 + b**2 = c**2 3. a + b + c = 1000 - + >>> solution(1000) 31875000 """ @@ -41,4 +33,4 @@ def solution(n): if __name__ == "__main__": - print(solution(int(raw_input().strip()))) + print(solution(int(input().strip()))) diff --git a/project_euler/problem_09/sol3.py b/project_euler/problem_09/sol3.py index 006029c8a30d..bbe7dcf743e7 100644 --- a/project_euler/problem_09/sol3.py +++ b/project_euler/problem_09/sol3.py @@ -10,9 +10,6 @@ There exists exactly one Pythagorean triplet for which a + b + c = 1000. Find the product abc. """ -from __future__ import print_function - - def solution(): """ Returns the product of a,b,c which are Pythagorean Triplet that satisfies diff --git a/project_euler/problem_10/sol1.py b/project_euler/problem_10/sol1.py index 49384d7c78f0..c81085951ecf 100644 --- a/project_euler/problem_10/sol1.py +++ b/project_euler/problem_10/sol1.py @@ -4,22 +4,11 @@ Find the sum of all the primes below two million. """ -from __future__ import print_function from math import sqrt -try: - raw_input # Python 2 -except NameError: - raw_input = input # Python 3 - -try: - xrange # Python 2 -except NameError: - xrange = range # Python 3 - def is_prime(n): - for i in xrange(2, int(sqrt(n)) + 1): + for i in range(2, int(sqrt(n)) + 1): if n % i == 0: return False @@ -32,7 +21,7 @@ def sum_of_primes(n): else: return 0 - for i in xrange(3, n, 2): + for i in range(3, n, 2): if is_prime(i): sumOfPrimes += i @@ -41,7 +30,7 @@ def sum_of_primes(n): def solution(n): """Returns the sum of all the primes below n. - + # The code below has been commented due to slow execution affecting Travis. # >>> solution(2000000) # 142913828922 @@ -58,4 +47,4 @@ def solution(n): if __name__ == "__main__": - print(solution(int(raw_input().strip()))) + print(solution(int(input().strip()))) diff --git a/project_euler/problem_10/sol2.py b/project_euler/problem_10/sol2.py index 451a4ae5e8f3..b2e2b6e1adf3 100644 --- a/project_euler/problem_10/sol2.py +++ b/project_euler/problem_10/sol2.py @@ -4,15 +4,9 @@ Find the sum of all the primes below two million. """ -from __future__ import print_function import math from itertools import takewhile -try: - raw_input # Python 2 -except NameError: - raw_input = input # Python 3 - def primeCheck(number): if number % 2 == 0 and number > 2: @@ -30,7 +24,7 @@ def prime_generator(): def solution(n): """Returns the sum of all the primes below n. - + # The code below has been commented due to slow execution affecting Travis. # >>> solution(2000000) # 142913828922 @@ -47,4 +41,4 @@ def solution(n): if __name__ == "__main__": - print(solution(int(raw_input().strip()))) + print(solution(int(input().strip()))) diff --git a/project_euler/problem_11/sol1.py b/project_euler/problem_11/sol1.py index 3bdddc89d917..1473439ae00d 100644 --- a/project_euler/problem_11/sol1.py +++ b/project_euler/problem_11/sol1.py @@ -24,14 +24,8 @@ 01 70 54 71 83 51 54 69 16 92 33 48 61 43 52 01 89 19 67 48 """ -from __future__ import print_function import os -try: - xrange # Python 2 -except NameError: - xrange = range # Python 2 - def largest_product(grid): nColumns = len(grid[0]) @@ -43,8 +37,8 @@ def largest_product(grid): # Check vertically, horizontally, diagonally at the same time (only works # for nxn grid) - for i in xrange(nColumns): - for j in xrange(nRows - 3): + for i in range(nColumns): + for j in range(nRows - 3): vertProduct = ( grid[j][i] * grid[j + 1][i] * grid[j + 2][i] * grid[j + 3][i] ) @@ -81,7 +75,7 @@ def largest_product(grid): def solution(): """Returns the sum of all the multiples of 3 or 5 below n. - + >>> solution() 70600674 """ @@ -90,7 +84,7 @@ def solution(): for line in file: grid.append(line.strip("\n").split(" ")) - grid = [[int(i) for i in grid[j]] for j in xrange(len(grid))] + grid = [[int(i) for i in grid[j]] for j in range(len(grid))] return largest_product(grid) diff --git a/project_euler/problem_11/sol2.py b/project_euler/problem_11/sol2.py index 0a5785b42b2c..be6c11a378ad 100644 --- a/project_euler/problem_11/sol2.py +++ b/project_euler/problem_11/sol2.py @@ -24,45 +24,39 @@ 01 70 54 71 83 51 54 69 16 92 33 48 61 43 52 01 89 19 67 48 """ -from __future__ import print_function import os -try: - xrange # Python 2 -except NameError: - xrange = range # Python 2 - def solution(): """Returns the sum of all the multiples of 3 or 5 below n. - + >>> solution() 70600674 """ with open(os.path.dirname(__file__) + "/grid.txt") as f: l = [] - for i in xrange(20): + for i in range(20): l.append([int(x) for x in f.readline().split()]) maximum = 0 # right - for i in xrange(20): - for j in xrange(17): + for i in range(20): + for j in range(17): temp = l[i][j] * l[i][j + 1] * l[i][j + 2] * l[i][j + 3] if temp > maximum: maximum = temp # down - for i in xrange(17): - for j in xrange(20): + for i in range(17): + for j in range(20): temp = l[i][j] * l[i + 1][j] * l[i + 2][j] * l[i + 3][j] if temp > maximum: maximum = temp # diagonal 1 - for i in xrange(17): - for j in xrange(17): + for i in range(17): + for j in range(17): temp = ( l[i][j] * l[i + 1][j + 1] @@ -73,8 +67,8 @@ def solution(): maximum = temp # diagonal 2 - for i in xrange(17): - for j in xrange(3, 20): + for i in range(17): + for j in range(3, 20): temp = ( l[i][j] * l[i + 1][j - 1] diff --git a/project_euler/problem_12/sol1.py b/project_euler/problem_12/sol1.py index 54476110b503..7e080c4e45a1 100644 --- a/project_euler/problem_12/sol1.py +++ b/project_euler/problem_12/sol1.py @@ -21,18 +21,12 @@ What is the value of the first triangle number to have over five hundred divisors? """ -from __future__ import print_function from math import sqrt -try: - xrange # Python 2 -except NameError: - xrange = range # Python 3 - def count_divisors(n): nDivisors = 0 - for i in xrange(1, int(sqrt(n)) + 1): + for i in range(1, int(sqrt(n)) + 1): if n % i == 0: nDivisors += 2 # check if n is perfect square @@ -44,7 +38,7 @@ def count_divisors(n): def solution(): """Returns the value of the first triangle number to have over five hundred divisors. - + # The code below has been commented due to slow execution affecting Travis. # >>> solution() # 76576500 diff --git a/project_euler/problem_12/sol2.py b/project_euler/problem_12/sol2.py index 0d1502830bee..97a4910723ac 100644 --- a/project_euler/problem_12/sol2.py +++ b/project_euler/problem_12/sol2.py @@ -21,9 +21,6 @@ What is the value of the first triangle number to have over five hundred divisors? """ -from __future__ import print_function - - def triangle_number_generator(): for n in range(1, 1000000): yield n * (n + 1) // 2 @@ -38,7 +35,7 @@ def count_divisors(n): def solution(): """Returns the value of the first triangle number to have over five hundred divisors. - + # The code below has been commented due to slow execution affecting Travis. # >>> solution() # 76576500 diff --git a/project_euler/problem_14/sol1.py b/project_euler/problem_14/sol1.py index 8d3efbc59eb5..156322b7d507 100644 --- a/project_euler/problem_14/sol1.py +++ b/project_euler/problem_14/sol1.py @@ -16,20 +16,12 @@ Which starting number, under one million, produces the longest chain? """ -from __future__ import print_function - -try: - raw_input # Python 2 -except NameError: - raw_input = input # Python 3 - - def solution(n): """Returns the number under n that generates the longest sequence using the formula: n → n/2 (n is even) n → 3n + 1 (n is odd) - + # The code below has been commented due to slow execution affecting Travis. # >>> solution(1000000) # {'counter': 525, 'largest_number': 837799} @@ -62,7 +54,7 @@ def solution(n): if __name__ == "__main__": - result = solution(int(raw_input().strip())) + result = solution(int(input().strip())) print( ( "Largest Number:", diff --git a/project_euler/problem_14/sol2.py b/project_euler/problem_14/sol2.py index 0ec80e221f09..25ebd41571c2 100644 --- a/project_euler/problem_14/sol2.py +++ b/project_euler/problem_14/sol2.py @@ -24,14 +24,6 @@ Which starting number, under one million, produces the longest chain? """ -from __future__ import print_function - -try: - raw_input # Python 2 -except NameError: - raw_input = input # Python 3 - - def collatz_sequence(n): """Returns the Collatz sequence for n.""" sequence = [n] @@ -63,7 +55,7 @@ def solution(n): if __name__ == "__main__": - result = solution(int(raw_input().strip())) + result = solution(int(input().strip())) print( "Longest Collatz sequence under one million is %d with length %d" % (result["largest_number"], result["counter"]) diff --git a/project_euler/problem_21/sol1.py b/project_euler/problem_21/sol1.py index 9cf2a64cf2a9..a890e6a98611 100644 --- a/project_euler/problem_21/sol1.py +++ b/project_euler/problem_21/sol1.py @@ -16,15 +16,9 @@ Evaluate the sum of all the amicable numbers under 10000. """ -try: - xrange # Python 2 -except NameError: - xrange = range # Python 3 - - def sum_of_divisors(n): total = 0 - for i in xrange(1, int(sqrt(n) + 1)): + for i in range(1, int(sqrt(n) + 1)): if n % i == 0 and i != sqrt(n): total += i + n // i elif i == sqrt(n): diff --git a/project_euler/problem_22/sol1.py b/project_euler/problem_22/sol1.py index aa779f222eaa..f6275e2138bb 100644 --- a/project_euler/problem_22/sol1.py +++ b/project_euler/problem_22/sol1.py @@ -18,12 +18,6 @@ import os -try: - xrange # Python 2 -except NameError: - xrange = range # Python 3 - - def solution(): """Returns the total of all the name scores in the file. diff --git a/project_euler/problem_25/sol1.py b/project_euler/problem_25/sol1.py index be3b4d9b2d7d..4371c533ce16 100644 --- a/project_euler/problem_25/sol1.py +++ b/project_euler/problem_25/sol1.py @@ -25,12 +25,6 @@ digits? """ -try: - xrange # Python 2 -except NameError: - xrange = range # Python 3 - - def fibonacci(n): if n == 1 or type(n) is not int: return 0 @@ -38,7 +32,7 @@ def fibonacci(n): return 1 else: sequence = [0, 1] - for i in xrange(2, n + 1): + for i in range(2, n + 1): sequence.append(sequence[i - 1] + sequence[i - 2]) return sequence[n] diff --git a/project_euler/problem_28/sol1.py b/project_euler/problem_28/sol1.py index 63386ce3058c..11b48fea9adf 100644 --- a/project_euler/problem_28/sol1.py +++ b/project_euler/problem_28/sol1.py @@ -16,11 +16,6 @@ from math import ceil -try: - xrange # Python 2 -except NameError: - xrange = range # Python 3 - def diagonal_sum(n): """Returns the sum of the numbers on the diagonals in a n by n spiral @@ -39,7 +34,7 @@ def diagonal_sum(n): """ total = 1 - for i in xrange(1, int(ceil(n / 2.0))): + for i in range(1, int(ceil(n / 2.0))): odd = 2 * i + 1 even = 2 * i total = total + 4 * odd ** 2 - 6 * even diff --git a/project_euler/problem_29/solution.py b/project_euler/problem_29/solution.py index e67dafe4639d..24d3e20d94fe 100644 --- a/project_euler/problem_29/solution.py +++ b/project_euler/problem_29/solution.py @@ -14,13 +14,10 @@ How many distinct terms are in the sequence generated by ab for 2 <= a <= 100 and 2 <= b <= 100? """ -from __future__ import print_function - - def solution(n): """Returns the number of distinct terms in the sequence generated by a^b for 2 <= a <= 100 and 2 <= b <= 100. - + >>> solution(100) 9183 >>> solution(50) diff --git a/project_euler/problem_31/sol1.py b/project_euler/problem_31/sol1.py index e2a209e5df5a..f7439d346130 100644 --- a/project_euler/problem_31/sol1.py +++ b/project_euler/problem_31/sol1.py @@ -11,14 +11,6 @@ 1×£1 + 1×50p + 2×20p + 1×5p + 1×2p + 3×1p How many different ways can £2 be made using any number of coins? """ -from __future__ import print_function - -try: - raw_input # Python 2 -except NameError: - raw_input = input # Python 3 - - def one_pence(): return 1 diff --git a/project_euler/problem_36/sol1.py b/project_euler/problem_36/sol1.py index 38b60420992b..7ed74af8fd63 100644 --- a/project_euler/problem_36/sol1.py +++ b/project_euler/problem_36/sol1.py @@ -9,12 +9,6 @@ (Please note that the palindromic number, in either base, may not include leading zeros.) """ -try: - xrange # Python 2 -except NameError: - xrange = range # Python 3 - - def is_palindrome(n): n = str(n) @@ -47,7 +41,7 @@ def solution(n): """ total = 0 - for i in xrange(1, n): + for i in range(1, n): if is_palindrome(i) and is_palindrome(bin(i).split("b")[1]): total += i return total diff --git a/project_euler/problem_40/sol1.py b/project_euler/problem_40/sol1.py index accd7125354c..d15376b739db 100644 --- a/project_euler/problem_40/sol1.py +++ b/project_euler/problem_40/sol1.py @@ -14,11 +14,8 @@ d1 × d10 × d100 × d1000 × d10000 × d100000 × d1000000 """ -from __future__ import print_function - - def solution(): - """Returns + """Returns >>> solution() 210 diff --git a/project_euler/problem_48/sol1.py b/project_euler/problem_48/sol1.py index 95af951c0e8a..06ad1408dcef 100644 --- a/project_euler/problem_48/sol1.py +++ b/project_euler/problem_48/sol1.py @@ -7,11 +7,6 @@ Find the last ten digits of the series, 11 + 22 + 33 + ... + 10001000. """ -try: - xrange -except NameError: - xrange = range - def solution(): """Returns the last 10 digits of the series, 11 + 22 + 33 + ... + 10001000. @@ -20,7 +15,7 @@ def solution(): '9110846700' """ total = 0 - for i in xrange(1, 1001): + for i in range(1, 1001): total += i ** i return str(total)[-10:] diff --git a/project_euler/problem_53/sol1.py b/project_euler/problem_53/sol1.py index c72e0b993a34..f17508b005d1 100644 --- a/project_euler/problem_53/sol1.py +++ b/project_euler/problem_53/sol1.py @@ -17,14 +17,8 @@ How many, not necessarily distinct, values of nCr, for 1 ≤ n ≤ 100, are greater than one-million? """ -from __future__ import print_function from math import factorial -try: - xrange # Python 2 -except NameError: - xrange = range # Python 3 - def combinations(n, r): return factorial(n) / (factorial(r) * factorial(n - r)) @@ -39,8 +33,8 @@ def solution(): """ total = 0 - for i in xrange(1, 101): - for j in xrange(1, i + 1): + for i in range(1, 101): + for j in range(1, i + 1): if combinations(i, j) > 1e6: total += 1 return total diff --git a/project_euler/problem_76/sol1.py b/project_euler/problem_76/sol1.py index c9e3c452fbc4..ed0ee6b507e9 100644 --- a/project_euler/problem_76/sol1.py +++ b/project_euler/problem_76/sol1.py @@ -14,12 +14,6 @@ How many different ways can one hundred be written as a sum of at least two positive integers? """ -from __future__ import print_function - -try: - xrange # Python 2 -except NameError: - xrange = range # Python 3 def partition(m): @@ -43,12 +37,12 @@ def partition(m): >>> partition(1) 0 """ - memo = [[0 for _ in xrange(m)] for _ in xrange(m + 1)] - for i in xrange(m + 1): + memo = [[0 for _ in range(m)] for _ in range(m + 1)] + for i in range(m + 1): memo[i][0] = 1 - for n in xrange(m + 1): - for k in xrange(1, m): + for n in range(m + 1): + for k in range(1, m): memo[n][k] += memo[n][k - 1] if n > k: memo[n][k] += memo[n - k - 1][k] diff --git a/searches/binary_search.py b/searches/binary_search.py index e658dac2a3ef..77abf90239ab 100644 --- a/searches/binary_search.py +++ b/searches/binary_search.py @@ -9,14 +9,8 @@ For manual testing run: python binary_search.py """ -from __future__ import print_function import bisect -try: - raw_input # Python 2 -except NameError: - raw_input = input # Python 3 - def binary_search(sorted_collection, item): """Pure implementation of binary search algorithm in Python @@ -112,7 +106,7 @@ def binary_search_by_recursion(sorted_collection, item, left, right): """ if (right < left): return None - + midpoint = left + (right - left) // 2 if sorted_collection[midpoint] == item: @@ -121,7 +115,7 @@ def binary_search_by_recursion(sorted_collection, item, left, right): return binary_search_by_recursion(sorted_collection, item, left, midpoint-1) else: return binary_search_by_recursion(sorted_collection, item, midpoint+1, right) - + def __assert_sorted(collection): """Check if collection is ascending sorted, if not - raises :py:class:`ValueError` @@ -145,14 +139,14 @@ def __assert_sorted(collection): if __name__ == '__main__': import sys - user_input = raw_input('Enter numbers separated by comma:\n').strip() + user_input = input('Enter numbers separated by comma:\n').strip() collection = [int(item) for item in user_input.split(',')] try: __assert_sorted(collection) except ValueError: sys.exit('Sequence must be ascending sorted to apply binary search') - target_input = raw_input('Enter a single number to be found in the list:\n') + target_input = input('Enter a single number to be found in the list:\n') target = int(target_input) result = binary_search(collection, target) if result is not None: diff --git a/searches/interpolation_search.py b/searches/interpolation_search.py index 329596d340a5..27ee979bb649 100644 --- a/searches/interpolation_search.py +++ b/searches/interpolation_search.py @@ -1,12 +1,6 @@ """ This is pure python implementation of interpolation search algorithm """ -from __future__ import print_function - -try: - raw_input # Python 2 -except NameError: - raw_input = input # Python 3 def interpolation_search(sorted_collection, item): @@ -29,7 +23,7 @@ def interpolation_search(sorted_collection, item): return None point = left + ((item - sorted_collection[left]) * (right - left)) // (sorted_collection[right] - sorted_collection[left]) - + #out of range check if point<0 or point>=len(sorted_collection): return None @@ -42,9 +36,9 @@ def interpolation_search(sorted_collection, item): right = left left = point elif point>right: - left = right + left = right right = point - else: + else: if item < current_item: right = point - 1 else: @@ -70,7 +64,7 @@ def interpolation_search_by_recursion(sorted_collection, item, left, right): return None point = left + ((item - sorted_collection[left]) * (right - left)) // (sorted_collection[right] - sorted_collection[left]) - + #out of range check if point<0 or point>=len(sorted_collection): return None @@ -86,7 +80,7 @@ def interpolation_search_by_recursion(sorted_collection, item, left, right): return interpolation_search_by_recursion(sorted_collection, item, left, point-1) else: return interpolation_search_by_recursion(sorted_collection, item, point+1, right) - + def __assert_sorted(collection): """Check if collection is ascending sorted, if not - raises :py:class:`ValueError` :param collection: collection @@ -107,16 +101,16 @@ def __assert_sorted(collection): if __name__ == '__main__': import sys - + """ - user_input = raw_input('Enter numbers separated by comma:\n').strip() + user_input = input('Enter numbers separated by comma:\n').strip() collection = [int(item) for item in user_input.split(',')] try: __assert_sorted(collection) except ValueError: sys.exit('Sequence must be ascending sorted to apply interpolation search') - target_input = raw_input('Enter a single number to be found in the list:\n') + target_input = input('Enter a single number to be found in the list:\n') target = int(target_input) """ @@ -128,7 +122,7 @@ def __assert_sorted(collection): except ValueError: sys.exit('Sequence must be ascending sorted to apply interpolation search') target = 67 - + result = interpolation_search(collection, target) if result is not None: print('{} found at positions: {}'.format(target, result)) diff --git a/searches/jump_search.py b/searches/jump_search.py index 10cb933f2f35..78d9f79dc6a8 100644 --- a/searches/jump_search.py +++ b/searches/jump_search.py @@ -1,4 +1,3 @@ -from __future__ import print_function import math def jump_search(arr, x): n = len(arr) diff --git a/searches/linear_search.py b/searches/linear_search.py index 058322f21d09..fb784924132e 100644 --- a/searches/linear_search.py +++ b/searches/linear_search.py @@ -9,12 +9,7 @@ For manual testing run: python linear_search.py """ -from __future__ import print_function -try: - raw_input # Python 2 -except NameError: - raw_input = input # Python 3 def linear_search(sequence, target): """Pure implementation of linear search algorithm in Python @@ -43,10 +38,10 @@ def linear_search(sequence, target): if __name__ == '__main__': - user_input = raw_input('Enter numbers separated by comma:\n').strip() + user_input = input('Enter numbers separated by comma:\n').strip() sequence = [int(item) for item in user_input.split(',')] - target_input = raw_input('Enter a single number to be found in the list:\n') + target_input = input('Enter a single number to be found in the list:\n') target = int(target_input) result = linear_search(sequence, target) if result is not None: diff --git a/searches/sentinel_linear_search.py b/searches/sentinel_linear_search.py index 336cc5ab3b74..eb9d32e5f503 100644 --- a/searches/sentinel_linear_search.py +++ b/searches/sentinel_linear_search.py @@ -45,15 +45,10 @@ def sentinel_linear_search(sequence, target): if __name__ == '__main__': - try: - raw_input # Python 2 - except NameError: - raw_input = input # Python 3 - - user_input = raw_input('Enter numbers separated by comma:\n').strip() + user_input = input('Enter numbers separated by comma:\n').strip() sequence = [int(item) for item in user_input.split(',')] - target_input = raw_input('Enter a single number to be found in the list:\n') + target_input = input('Enter a single number to be found in the list:\n') target = int(target_input) result = sentinel_linear_search(sequence, target) if result is not None: diff --git a/searches/ternary_search.py b/searches/ternary_search.py index c610f9b3c6da..41033f33cec6 100644 --- a/searches/ternary_search.py +++ b/searches/ternary_search.py @@ -1,20 +1,13 @@ ''' This is a type of divide and conquer algorithm which divides the search space into -3 parts and finds the target value based on the property of the array or list +3 parts and finds the target value based on the property of the array or list (usually monotonic property). Time Complexity : O(log3 N) Space Complexity : O(1) ''' -from __future__ import print_function - import sys -try: - raw_input # Python 2 -except NameError: - raw_input = input # Python 3 - # This is the precision for this function which can be altered. # It is recommended for users to keep this number greater than or equal to 10. precision = 10 @@ -31,23 +24,23 @@ def ite_ternary_search(A, target): right = len(A) - 1; while(True): if(left>> bubble_sort([-2, -5, -45]) [-45, -5, -2] - - >>> bubble_sort([-23,0,6,-4,34]) + + >>> bubble_sort([-23, 0, 6, -4, 34]) [-23, -4, 0, 6, 34] + + >>> bubble_sort([-23, 0, 6, -4, 34]) == sorted([-23, 0, 6, -4, 34]) + True """ length = len(collection) for i in range(length-1): @@ -28,15 +28,12 @@ def bubble_sort(collection): if collection[j] > collection[j+1]: swapped = True collection[j], collection[j+1] = collection[j+1], collection[j] - if not swapped: break # Stop iteration if the collection is sorted. + if not swapped: + break # Stop iteration if the collection is sorted. return collection if __name__ == '__main__': - try: - raw_input # Python 2 - except NameError: - raw_input = input # Python 3 - user_input = raw_input('Enter numbers separated by a comma:').strip() + user_input = input('Enter numbers separated by a comma:').strip() unsorted = [int(item) for item in user_input.split(',')] print(*bubble_sort(unsorted), sep=',') diff --git a/sorts/cocktail_shaker_sort.py b/sorts/cocktail_shaker_sort.py index 8ad3383bbe9f..d486e6a11dfa 100644 --- a/sorts/cocktail_shaker_sort.py +++ b/sorts/cocktail_shaker_sort.py @@ -1,12 +1,10 @@ -from __future__ import print_function - def cocktail_shaker_sort(unsorted): """ Pure implementation of the cocktail shaker sort algorithm in Python. """ for i in range(len(unsorted)-1, 0, -1): swapped = False - + for j in range(i, 0, -1): if unsorted[j] < unsorted[j-1]: unsorted[j], unsorted[j-1] = unsorted[j-1], unsorted[j] @@ -16,17 +14,12 @@ def cocktail_shaker_sort(unsorted): if unsorted[j] > unsorted[j+1]: unsorted[j], unsorted[j+1] = unsorted[j+1], unsorted[j] swapped = True - + if not swapped: return unsorted - + if __name__ == '__main__': - try: - raw_input # Python 2 - except NameError: - raw_input = input # Python 3 - - user_input = raw_input('Enter numbers separated by a comma:\n').strip() + user_input = input('Enter numbers separated by a comma:\n').strip() unsorted = [int(item) for item in user_input.split(',')] cocktail_shaker_sort(unsorted) print(unsorted) diff --git a/sorts/comb_sort.py b/sorts/comb_sort.py index 22b6f66f04cc..6ce6c1c094f9 100644 --- a/sorts/comb_sort.py +++ b/sorts/comb_sort.py @@ -48,11 +48,6 @@ def comb_sort(data): if __name__ == '__main__': - try: - raw_input # Python 2 - except NameError: - raw_input = input # Python 3 - - user_input = raw_input('Enter numbers separated by a comma:\n').strip() + user_input = input('Enter numbers separated by a comma:\n').strip() unsorted = [int(item) for item in user_input.split(',')] print(comb_sort(unsorted)) diff --git a/sorts/counting_sort.py b/sorts/counting_sort.py index ad98f1a0da4c..a3de1811849e 100644 --- a/sorts/counting_sort.py +++ b/sorts/counting_sort.py @@ -8,8 +8,6 @@ python counting_sort.py """ -from __future__ import print_function - def counting_sort(collection): """Pure implementation of counting sort algorithm in Python @@ -58,6 +56,10 @@ def counting_sort(collection): return ordered def counting_sort_string(string): + """ + >>> counting_sort_string("thisisthestring") + 'eghhiiinrsssttt' + """ return ''.join([chr(i) for i in counting_sort([ord(c) for c in string])]) @@ -65,11 +67,6 @@ def counting_sort_string(string): # Test string sort assert "eghhiiinrsssttt" == counting_sort_string("thisisthestring") - try: - raw_input # Python 2 - except NameError: - raw_input = input # Python 3 - - user_input = raw_input('Enter numbers separated by a comma:\n').strip() + user_input = input('Enter numbers separated by a comma:\n').strip() unsorted = [int(item) for item in user_input.split(',')] print(counting_sort(unsorted)) diff --git a/sorts/cycle_sort.py b/sorts/cycle_sort.py index 492022164427..06a377cbd906 100644 --- a/sorts/cycle_sort.py +++ b/sorts/cycle_sort.py @@ -1,7 +1,4 @@ # Code contributed by Honey Sharma -from __future__ import print_function - - def cycle_sort(array): ans = 0 @@ -45,12 +42,7 @@ def cycle_sort(array): # Main Code starts here if __name__ == '__main__': - try: - raw_input # Python 2 - except NameError: - raw_input = input # Python 3 - - user_input = raw_input('Enter numbers separated by a comma:\n') + user_input = input('Enter numbers separated by a comma:\n') unsorted = [int(item) for item in user_input.split(',')] n = len(unsorted) cycle_sort(unsorted) diff --git a/sorts/gnome_sort.py b/sorts/gnome_sort.py index 075749e37663..fed70eb6bc1b 100644 --- a/sorts/gnome_sort.py +++ b/sorts/gnome_sort.py @@ -1,7 +1,5 @@ """Gnome Sort Algorithm.""" -from __future__ import print_function - def gnome_sort(unsorted): """Pure implementation of the gnome sort algorithm in Python.""" @@ -21,12 +19,7 @@ def gnome_sort(unsorted): if __name__ == '__main__': - try: - raw_input # Python 2 - except NameError: - raw_input = input # Python 3 - - user_input = raw_input('Enter numbers separated by a comma:\n').strip() + user_input = input('Enter numbers separated by a comma:\n').strip() unsorted = [int(item) for item in user_input.split(',')] gnome_sort(unsorted) print(unsorted) diff --git a/sorts/heap_sort.py b/sorts/heap_sort.py index 3c72abca8059..ca4a061afbb7 100644 --- a/sorts/heap_sort.py +++ b/sorts/heap_sort.py @@ -10,9 +10,6 @@ python heap_sort.py ''' -from __future__ import print_function - - def heapify(unsorted, index, heap_size): largest = index left_index = 2 * index + 1 @@ -54,11 +51,6 @@ def heap_sort(unsorted): return unsorted if __name__ == '__main__': - try: - raw_input # Python 2 - except NameError: - raw_input = input # Python 3 - - user_input = raw_input('Enter numbers separated by a comma:\n').strip() + user_input = input('Enter numbers separated by a comma:\n').strip() unsorted = [int(item) for item in user_input.split(',')] print(heap_sort(unsorted)) diff --git a/sorts/insertion_sort.py b/sorts/insertion_sort.py index e088705947d4..e10497b0e282 100644 --- a/sorts/insertion_sort.py +++ b/sorts/insertion_sort.py @@ -9,9 +9,6 @@ For manual testing run: python insertion_sort.py """ -from __future__ import print_function - - def insertion_sort(collection): """Pure implementation of the insertion sort algorithm in Python @@ -40,11 +37,6 @@ def insertion_sort(collection): if __name__ == '__main__': - try: - raw_input # Python 2 - except NameError: - raw_input = input # Python 3 - - user_input = raw_input('Enter numbers separated by a comma:\n').strip() + user_input = input('Enter numbers separated by a comma:\n').strip() unsorted = [int(item) for item in user_input.split(',')] print(insertion_sort(unsorted)) diff --git a/sorts/merge_sort.py b/sorts/merge_sort.py index 714861e72642..e64e90785a32 100644 --- a/sorts/merge_sort.py +++ b/sorts/merge_sort.py @@ -9,9 +9,6 @@ For manual testing run: python merge_sort.py """ -from __future__ import print_function - - def merge_sort(collection): """Pure implementation of the merge sort algorithm in Python @@ -46,11 +43,6 @@ def merge(left, right): if __name__ == '__main__': - try: - raw_input # Python 2 - except NameError: - raw_input = input # Python 3 - - user_input = raw_input('Enter numbers separated by a comma:\n').strip() + user_input = input('Enter numbers separated by a comma:\n').strip() unsorted = [int(item) for item in user_input.split(',')] print(*merge_sort(unsorted), sep=',') diff --git a/sorts/merge_sort_fastest.py b/sorts/merge_sort_fastest.py index bd356c935ca0..3c9ed3e9e8ee 100644 --- a/sorts/merge_sort_fastest.py +++ b/sorts/merge_sort_fastest.py @@ -4,9 +4,6 @@ Best Case Scenario : O(n) Worst Case Scenario : O(n^2) because native python functions:min, max and remove are already O(n) ''' -from __future__ import print_function - - def merge_sort(collection): """Pure implementation of the fastest merge sort algorithm in Python @@ -36,11 +33,6 @@ def merge_sort(collection): if __name__ == '__main__': - try: - raw_input # Python 2 - except NameError: - raw_input = input # Python 3 - - user_input = raw_input('Enter numbers separated by a comma:\n').strip() + user_input = input('Enter numbers separated by a comma:\n').strip() unsorted = [int(item) for item in user_input.split(',')] print(*merge_sort(unsorted), sep=',') diff --git a/sorts/pigeon_sort.py b/sorts/pigeon_sort.py index 65eb8896ea9c..5e5afa137685 100644 --- a/sorts/pigeon_sort.py +++ b/sorts/pigeon_sort.py @@ -1,9 +1,6 @@ ''' This is an implementation of Pigeon Hole Sort. ''' - -from __future__ import print_function - def pigeon_sort(array): # Manually finds the minimum and maximum of the array. min = array[0] @@ -38,12 +35,7 @@ def pigeon_sort(array): return array if __name__ == '__main__': - try: - raw_input # Python2 - except NameError: - raw_input = input # Python 3 - - user_input = raw_input('Enter numbers separated by comma:\n') + user_input = input('Enter numbers separated by comma:\n') unsorted = [int(x) for x in user_input.split(',')] sorted = pigeon_sort(unsorted) diff --git a/sorts/quick_sort.py b/sorts/quick_sort.py index 7e8c868ebb06..60f8803cb79c 100644 --- a/sorts/quick_sort.py +++ b/sorts/quick_sort.py @@ -9,9 +9,6 @@ For manual testing run: python quick_sort.py """ -from __future__ import print_function - - def quick_sort(collection): """Pure implementation of quick sort algorithm in Python @@ -47,11 +44,6 @@ def quick_sort(collection): if __name__ == '__main__': - try: - raw_input # Python 2 - except NameError: - raw_input = input # Python 3 - - user_input = raw_input('Enter numbers separated by a comma:\n').strip() + user_input = input('Enter numbers separated by a comma:\n').strip() unsorted = [ int(item) for item in user_input.split(',') ] print( quick_sort(unsorted) ) diff --git a/sorts/quick_sort_3_partition.py b/sorts/quick_sort_3_partition.py index def646cdbc50..9056b204740a 100644 --- a/sorts/quick_sort_3_partition.py +++ b/sorts/quick_sort_3_partition.py @@ -1,5 +1,3 @@ -from __future__ import print_function - def quick_sort_3partition(sorting, left, right): if right <= left: return @@ -20,12 +18,7 @@ def quick_sort_3partition(sorting, left, right): quick_sort_3partition(sorting, b + 1, right) if __name__ == '__main__': - try: - raw_input # Python 2 - except NameError: - raw_input = input # Python 3 - - user_input = raw_input('Enter numbers separated by a comma:\n').strip() + user_input = input('Enter numbers separated by a comma:\n').strip() unsorted = [ int(item) for item in user_input.split(',') ] quick_sort_3partition(unsorted,0,len(unsorted)-1) print(unsorted) diff --git a/sorts/random_normal_distribution_quicksort.py b/sorts/random_normal_distribution_quicksort.py index dfa37da61e26..39c54c46e263 100644 --- a/sorts/random_normal_distribution_quicksort.py +++ b/sorts/random_normal_distribution_quicksort.py @@ -1,25 +1,23 @@ -from __future__ import print_function from random import randint from tempfile import TemporaryFile import numpy as np - -def _inPlaceQuickSort(A,start,end): +def _inPlaceQuickSort(A,start,end): count = 0 if start Date: Tue, 20 Aug 2019 01:02:43 -0400 Subject: [PATCH 0192/1071] Fixing lgtm issue in basic graphs (#1141) * Added print function into matrix_multiplication_addition.py and removed blank space in data_structures/binary tree directory * Removed .vs/ folder per #893 * Rename matrix_multiplication_addition.py to matrix_operation.py * Fixing lgtm issue in basic_graphs per ##1024 * Fixed lgtm issue per @cclauss recommendation in #1024 --- graphs/basic_graphs.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/graphs/basic_graphs.py b/graphs/basic_graphs.py index 64c51e139cca..308abc0839fa 100644 --- a/graphs/basic_graphs.py +++ b/graphs/basic_graphs.py @@ -128,7 +128,9 @@ def dijk(G, s): from collections import deque -def topo(G, ind=None, Q=[1]): +def topo(G, ind=None, Q=None): + if Q is None: + Q = [1] if ind is None: ind = [0] * (len(G) + 1) # SInce oth Index is ignored for u in G: From 47cb394b5c9a42682803f09d41b3cbe9d1b09304 Mon Sep 17 00:00:00 2001 From: pathak-deep15 <44609019+pathak-deep15@users.noreply.github.com> Date: Thu, 22 Aug 2019 22:25:41 +0530 Subject: [PATCH 0193/1071] added doctests for compare_string and is_for_table (#1138) * added doctests for compare_string and is_for_table >>>compare_string('0010','0110') '0_10' >>> is_for_table('__1','011',2) True The above doctests were added * Update quine_mc_cluskey.py * Update quine_mc_cluskey.py * Update quine_mc_cluskey.py --- boolean_algebra/quine_mc_cluskey.py | 48 ++++++++++++++++++++--------- 1 file changed, 33 insertions(+), 15 deletions(-) diff --git a/boolean_algebra/quine_mc_cluskey.py b/boolean_algebra/quine_mc_cluskey.py index 94319ca45482..b7ca8da437a3 100644 --- a/boolean_algebra/quine_mc_cluskey.py +++ b/boolean_algebra/quine_mc_cluskey.py @@ -1,19 +1,11 @@ -""" - doctests - - >>> decimal_to_binary(3,[1.5]) - ['0.00.01.5'] - - >>> check(['0.00.01.5']) - ['0.00.01.5'] - - >>> prime_implicant_chart(['0.00.01.5'],['0.00.01.5']) - [[1]] - - >>> selection([[1]],['0.00.01.5']) - ['0.00.01.5'] -""" def compare_string(string1, string2): + """ + >>> compare_string('0010','0110') + '0_10' + + >>> compare_string('0110','1101') + -1 + """ l1 = list(string1); l2 = list(string2) count = 0 for i in range(len(l1)): @@ -26,6 +18,10 @@ def compare_string(string1, string2): return("".join(l1)) def check(binary): + """ + >>> check(['0.00.01.5']) + ['0.00.01.5'] + """ pi = [] while 1: check1 = ['$']*len(binary) @@ -45,6 +41,10 @@ def check(binary): binary = list(set(temp)) def decimal_to_binary(no_of_variable, minterms): + """ + >>> decimal_to_binary(3,[1.5]) + ['0.00.01.5'] + """ temp = [] s = '' for m in minterms: @@ -56,6 +56,13 @@ def decimal_to_binary(no_of_variable, minterms): return temp def is_for_table(string1, string2, count): + """ + >>> is_for_table('__1','011',2) + True + + >>> is_for_table('01_','001',1) + False + """ l1 = list(string1);l2=list(string2) count_n = 0 for i in range(len(l1)): @@ -67,6 +74,13 @@ def is_for_table(string1, string2, count): return False def selection(chart, prime_implicants): + """ + >>> selection([[1]],['0.00.01.5']) + ['0.00.01.5'] + + >>> selection([[1]],['0.00.01.5']) + ['0.00.01.5'] + """ temp = [] select = [0]*len(chart) for i in range(len(chart[0])): @@ -104,6 +118,10 @@ def selection(chart, prime_implicants): chart[j][i] = 0 def prime_implicant_chart(prime_implicants, binary): + """ + >>> prime_implicant_chart(['0.00.01.5'],['0.00.01.5']) + [[1]] + """ chart = [[0 for x in range(len(binary))] for x in range(len(prime_implicants))] for i in range(len(prime_implicants)): count = prime_implicants[i].count('_') From e694e596a3fc42ae5f28f0e267c44f74948e25ee Mon Sep 17 00:00:00 2001 From: Nishant Aklecha <31594715+Naklecha@users.noreply.github.com> Date: Sun, 25 Aug 2019 17:44:17 +0530 Subject: [PATCH 0194/1071] Added a few doctests for traversals (#1149) --- traversals/binary_tree_traversals.py | 116 +++++++++++++++++++++++++++ 1 file changed, 116 insertions(+) diff --git a/traversals/binary_tree_traversals.py b/traversals/binary_tree_traversals.py index 7fd9f7111844..389311a7cfde 100644 --- a/traversals/binary_tree_traversals.py +++ b/traversals/binary_tree_traversals.py @@ -39,6 +39,20 @@ def build_tree(): def pre_order(node: TreeNode) -> None: + """ + >>> root = TreeNode(1) + >>> tree_node2 = TreeNode(2) + >>> tree_node3 = TreeNode(3) + >>> tree_node4 = TreeNode(4) + >>> tree_node5 = TreeNode(5) + >>> tree_node6 = TreeNode(6) + >>> tree_node7 = TreeNode(7) + >>> root.left, root.right = tree_node2, tree_node3 + >>> tree_node2.left, tree_node2.right = tree_node4 , tree_node5 + >>> tree_node3.left, tree_node3.right = tree_node6 , tree_node7 + >>> pre_order(root) + 1 2 4 5 3 6 7 + """ if not isinstance(node, TreeNode) or not node: return print(node.data, end=" ") @@ -47,6 +61,20 @@ def pre_order(node: TreeNode) -> None: def in_order(node: TreeNode) -> None: + """ + >>> root = TreeNode(1) + >>> tree_node2 = TreeNode(2) + >>> tree_node3 = TreeNode(3) + >>> tree_node4 = TreeNode(4) + >>> tree_node5 = TreeNode(5) + >>> tree_node6 = TreeNode(6) + >>> tree_node7 = TreeNode(7) + >>> root.left, root.right = tree_node2, tree_node3 + >>> tree_node2.left, tree_node2.right = tree_node4 , tree_node5 + >>> tree_node3.left, tree_node3.right = tree_node6 , tree_node7 + >>> in_order(root) + 4 2 5 1 6 3 7 + """ if not isinstance(node, TreeNode) or not node: return in_order(node.left) @@ -55,6 +83,20 @@ def in_order(node: TreeNode) -> None: def post_order(node: TreeNode) -> None: + """ + >>> root = TreeNode(1) + >>> tree_node2 = TreeNode(2) + >>> tree_node3 = TreeNode(3) + >>> tree_node4 = TreeNode(4) + >>> tree_node5 = TreeNode(5) + >>> tree_node6 = TreeNode(6) + >>> tree_node7 = TreeNode(7) + >>> root.left, root.right = tree_node2, tree_node3 + >>> tree_node2.left, tree_node2.right = tree_node4 , tree_node5 + >>> tree_node3.left, tree_node3.right = tree_node6 , tree_node7 + >>> post_order(root) + 4 5 2 6 7 3 1 + """ if not isinstance(node, TreeNode) or not node: return post_order(node.left) @@ -63,6 +105,20 @@ def post_order(node: TreeNode) -> None: def level_order(node: TreeNode) -> None: + """ + >>> root = TreeNode(1) + >>> tree_node2 = TreeNode(2) + >>> tree_node3 = TreeNode(3) + >>> tree_node4 = TreeNode(4) + >>> tree_node5 = TreeNode(5) + >>> tree_node6 = TreeNode(6) + >>> tree_node7 = TreeNode(7) + >>> root.left, root.right = tree_node2, tree_node3 + >>> tree_node2.left, tree_node2.right = tree_node4 , tree_node5 + >>> tree_node3.left, tree_node3.right = tree_node6 , tree_node7 + >>> level_order(root) + 1 2 3 4 5 6 7 + """ if not isinstance(node, TreeNode) or not node: return q: queue.Queue = queue.Queue() @@ -77,6 +133,22 @@ def level_order(node: TreeNode) -> None: def level_order_actual(node: TreeNode) -> None: + """ + >>> root = TreeNode(1) + >>> tree_node2 = TreeNode(2) + >>> tree_node3 = TreeNode(3) + >>> tree_node4 = TreeNode(4) + >>> tree_node5 = TreeNode(5) + >>> tree_node6 = TreeNode(6) + >>> tree_node7 = TreeNode(7) + >>> root.left, root.right = tree_node2, tree_node3 + >>> tree_node2.left, tree_node2.right = tree_node4 , tree_node5 + >>> tree_node3.left, tree_node3.right = tree_node6 , tree_node7 + >>> level_order_actual(root) + 1 + 2 3 + 4 5 6 7 + """ if not isinstance(node, TreeNode) or not node: return q: queue.Queue = queue.Queue() @@ -97,6 +169,20 @@ def level_order_actual(node: TreeNode) -> None: # iteration version def pre_order_iter(node: TreeNode) -> None: + """ + >>> root = TreeNode(1) + >>> tree_node2 = TreeNode(2) + >>> tree_node3 = TreeNode(3) + >>> tree_node4 = TreeNode(4) + >>> tree_node5 = TreeNode(5) + >>> tree_node6 = TreeNode(6) + >>> tree_node7 = TreeNode(7) + >>> root.left, root.right = tree_node2, tree_node3 + >>> tree_node2.left, tree_node2.right = tree_node4 , tree_node5 + >>> tree_node3.left, tree_node3.right = tree_node6 , tree_node7 + >>> pre_order_iter(root) + 1 2 4 5 3 6 7 + """ if not isinstance(node, TreeNode) or not node: return stack: List[TreeNode] = [] @@ -113,6 +199,20 @@ def pre_order_iter(node: TreeNode) -> None: def in_order_iter(node: TreeNode) -> None: + """ + >>> root = TreeNode(1) + >>> tree_node2 = TreeNode(2) + >>> tree_node3 = TreeNode(3) + >>> tree_node4 = TreeNode(4) + >>> tree_node5 = TreeNode(5) + >>> tree_node6 = TreeNode(6) + >>> tree_node7 = TreeNode(7) + >>> root.left, root.right = tree_node2, tree_node3 + >>> tree_node2.left, tree_node2.right = tree_node4 , tree_node5 + >>> tree_node3.left, tree_node3.right = tree_node6 , tree_node7 + >>> in_order_iter(root) + 4 2 5 1 6 3 7 + """ if not isinstance(node, TreeNode) or not node: return stack: List[TreeNode] = [] @@ -127,6 +227,20 @@ def in_order_iter(node: TreeNode) -> None: def post_order_iter(node: TreeNode) -> None: + """ + >>> root = TreeNode(1) + >>> tree_node2 = TreeNode(2) + >>> tree_node3 = TreeNode(3) + >>> tree_node4 = TreeNode(4) + >>> tree_node5 = TreeNode(5) + >>> tree_node6 = TreeNode(6) + >>> tree_node7 = TreeNode(7) + >>> root.left, root.right = tree_node2, tree_node3 + >>> tree_node2.left, tree_node2.right = tree_node4 , tree_node5 + >>> tree_node3.left, tree_node3.right = tree_node6 , tree_node7 + >>> post_order_iter(root) + 4 5 2 6 7 3 1 + """ if not isinstance(node, TreeNode) or not node: return stack1, stack2 = [], [] @@ -151,6 +265,8 @@ def prompt(s: str = "", width=50, char="*") -> str: if __name__ == "__main__": + import doctest + doctest.testmod() print(prompt("Binary Tree Traversals")) node = build_tree() From 2f8516e561dc07571e48044f75a942db05068fa9 Mon Sep 17 00:00:00 2001 From: Riemann <40825655+anand372@users.noreply.github.com> Date: Wed, 28 Aug 2019 16:26:43 +0530 Subject: [PATCH 0195/1071] implementation of sorted vector machines (#1156) * svm.py for issue #840 I would like to add the Support Vector Machine algorithm implemented in Python 3.6.7 Requirements: - sklearn * update svm.py * update svm.py * Update and renamed to sorted_vector_machines.py * Updated sorted_vector_machines.py --- machine_learning/sorted_vector_machines.py | 54 ++++++++++++++++++++++ 1 file changed, 54 insertions(+) create mode 100644 machine_learning/sorted_vector_machines.py diff --git a/machine_learning/sorted_vector_machines.py b/machine_learning/sorted_vector_machines.py new file mode 100644 index 000000000000..92fa814c998f --- /dev/null +++ b/machine_learning/sorted_vector_machines.py @@ -0,0 +1,54 @@ +from sklearn.datasets import load_iris +from sklearn import svm +from sklearn.model_selection import train_test_split +import doctest + +# different functions implementing different types of SVM's +def NuSVC(train_x, train_y): + svc_NuSVC = svm.NuSVC() + svc_NuSVC.fit(train_x, train_y) + return svc_NuSVC + + +def Linearsvc(train_x, train_y): + svc_linear = svm.LinearSVC() + svc_linear.fit(train_x, train_y) + return svc_linear + + +def SVC(train_x, train_y): + # svm.SVC(C=1.0, kernel='rbf', degree=3, gamma=0.0, coef0=0.0, shrinking=True, probability=False,tol=0.001, cache_size=200, class_weight=None, verbose=False, max_iter=-1, random_state=None) + # various parameters like "kernal","gamma","C" can effectively tuned for a given machine learning model. + SVC = svm.SVC(gamma="auto") + SVC.fit(train_x, train_y) + return SVC + + +def test(X_new): + """ + 3 test cases to be passed + an array containing the sepal length (cm), sepal width (cm),petal length (cm),petal width (cm) + based on which the target name will be predicted + >>> test([1,2,1,4]) + 'virginica' + >>> test([5, 2, 4, 1]) + 'versicolor' + >>> test([6,3,4,1]) + 'versicolor' + + """ + iris = load_iris() + # splitting the dataset to test and train + train_x, test_x, train_y, test_y = train_test_split( + iris["data"], iris["target"], random_state=4 + ) + # any of the 3 types of SVM can be used + # current_model=SVC(train_x, train_y) + # current_model=NuSVC(train_x, train_y) + current_model = Linearsvc(train_x, train_y) + prediction = current_model.predict([X_new]) + return iris["target_names"][prediction][0] + + +if __name__ == "__main__": + doctest.testmod() From 82a079c209bf7836baade1aa4e7a6e3a85b740a2 Mon Sep 17 00:00:00 2001 From: Harshil Date: Wed, 28 Aug 2019 17:16:12 +0200 Subject: [PATCH 0196/1071] add .github/stale.yml (#1158) --- .github/stale.yml | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 .github/stale.yml diff --git a/.github/stale.yml b/.github/stale.yml new file mode 100644 index 000000000000..6af2a10216b8 --- /dev/null +++ b/.github/stale.yml @@ -0,0 +1,18 @@ +# Number of days of inactivity before an issue becomes stale +daysUntilStale: 14 +# Number of days of inactivity before a stale issue is closed +daysUntilClose: 7 +# Issues with these labels will never be considered stale +exemptLabels: + - bug + - help wanted + - OK to merge +# Label to use when marking an issue as stale +staleLabel: wontfix +# Comment to post when marking an issue as stale. Set to `false` to disable +markComment: > + This issue has been automatically marked as stale because it has not had + recent activity. It will be closed if no further activity occurs. Thank you + for your contributions. +# Comment to post when closing a stale issue. Set to `false` to disable +closeComment: false \ No newline at end of file From d327f107022a51f8ae75436d6dae20fda34622ee Mon Sep 17 00:00:00 2001 From: Rohit Gupta Date: Thu, 29 Aug 2019 00:53:42 +0530 Subject: [PATCH 0197/1071] Update stale.yml --- .github/stale.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/stale.yml b/.github/stale.yml index 6af2a10216b8..70032115fc2c 100644 --- a/.github/stale.yml +++ b/.github/stale.yml @@ -1,5 +1,5 @@ # Number of days of inactivity before an issue becomes stale -daysUntilStale: 14 +daysUntilStale: 30 # Number of days of inactivity before a stale issue is closed daysUntilClose: 7 # Issues with these labels will never be considered stale @@ -15,4 +15,4 @@ markComment: > recent activity. It will be closed if no further activity occurs. Thank you for your contributions. # Comment to post when closing a stale issue. Set to `false` to disable -closeComment: false \ No newline at end of file +closeComment: true From d4151bd5164d99a8184a4d842fe5e36305716dbc Mon Sep 17 00:00:00 2001 From: Rwithik Manoj Date: Sat, 31 Aug 2019 17:10:50 +0530 Subject: [PATCH 0198/1071] Fix possible error in longest_common_subsequence.py (#1163) The comparison at line 53 was not checking if (j > 0). --- dynamic_programming/longest_common_subsequence.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dynamic_programming/longest_common_subsequence.py b/dynamic_programming/longest_common_subsequence.py index 7447a0cc7810..d39485408988 100644 --- a/dynamic_programming/longest_common_subsequence.py +++ b/dynamic_programming/longest_common_subsequence.py @@ -50,7 +50,7 @@ def longest_common_subsequence(x: str, y: str): seq = "" i, j = m, n - while i > 0 and i > 0: + while i > 0 and j > 0: if x[i - 1] == y[j - 1]: match = 1 else: From d567a9eb8c1bf00e7bc5a007f21e3c977d7f83ec Mon Sep 17 00:00:00 2001 From: b63 <52578583+b63@users.noreply.github.com> Date: Sun, 1 Sep 2019 01:07:31 -0500 Subject: [PATCH 0199/1071] solution to problem 551 from project euler (#1164) * solution to problem 551 from project euler * renamed variables, and added more comments to improve readabilty --- project_euler/problem_551/__init__.py | 0 project_euler/problem_551/sol1.py | 204 ++++++++++++++++++++++++++ 2 files changed, 204 insertions(+) create mode 100644 project_euler/problem_551/__init__.py create mode 100644 project_euler/problem_551/sol1.py diff --git a/project_euler/problem_551/__init__.py b/project_euler/problem_551/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/project_euler/problem_551/sol1.py b/project_euler/problem_551/sol1.py new file mode 100644 index 000000000000..238d7b772190 --- /dev/null +++ b/project_euler/problem_551/sol1.py @@ -0,0 +1,204 @@ +""" +Sum of digits sequence +Problem 551 + +Let a(0), a(1),... be an interger sequence defined by: + a(0) = 1 + for n >= 1, a(n) is the sum of the digits of all preceding terms + +The sequence starts with 1, 1, 2, 4, 8, ... +You are given a(10^6) = 31054319. + +Find a(10^15) +""" + +ks = [k for k in range(2, 20+1)] +base = [10 ** k for k in range(ks[-1] + 1)] +memo = {} + + +def next_term(a_i, k, i, n): + """ + Calculates and updates a_i in-place to either the n-th term or the + smallest term for which c > 10^k when the terms are written in the form: + a(i) = b * 10^k + c + + For any a(i), if digitsum(b) and c have the same value, the difference + between subsequent terms will be the same until c >= 10^k. This difference + is cached to greatly speed up the computation. + + Arguments: + a_i -- array of digits starting from the one's place that represent + the i-th term in the sequence + k -- k when terms are written in the from a(i) = b*10^k + c. + Term are calulcated until c > 10^k or the n-th term is reached. + i -- position along the sequence + n -- term to caluclate up to if k is large enough + + Return: a tuple of difference between ending term and starting term, and + the number of terms calculated. ex. if starting term is a_0=1, and + ending term is a_10=62, then (61, 9) is returned. + """ + # ds_b - digitsum(b) + ds_b = 0 + for j in range(k, len(a_i)): + ds_b += a_i[j] + c = 0 + for j in range(min(len(a_i), k)): + c += a_i[j] * base[j] + + diff, dn = 0, 0 + max_dn = n - i + + sub_memo = memo.get(ds_b) + + if sub_memo != None: + jumps = sub_memo.get(c) + + if jumps != None and len(jumps) > 0: + # find and make the largest jump without going over + max_jump = -1 + for _k in range(len(jumps) - 1, -1, -1): + if jumps[_k][2] <= k and jumps[_k][1] <= max_dn: + max_jump = _k + break + + if max_jump >= 0: + diff, dn, _kk = jumps[max_jump] + # since the difference between jumps is cached, add c + new_c = diff + c + for j in range(min(k, len(a_i))): + new_c, a_i[j] = divmod(new_c, 10) + if new_c > 0: + add(a_i, k, new_c) + + else: + sub_memo[c] = [] + else: + sub_memo = {c: []} + memo[ds_b] = sub_memo + + if dn >= max_dn or c + diff >= base[k]: + return diff, dn + + if k > ks[0]: + while True: + # keep doing smaller jumps + _diff, terms_jumped = next_term(a_i, k - 1, i + dn, n) + diff += _diff + dn += terms_jumped + + if dn >= max_dn or c + diff >= base[k]: + break + else: + # would be too small a jump, just compute sequential terms instead + _diff, terms_jumped = compute(a_i, k, i + dn, n) + diff += _diff + dn += terms_jumped + + jumps = sub_memo[c] + + # keep jumps sorted by # of terms skipped + j = 0 + while j < len(jumps): + if jumps[j][1] > dn: + break + j += 1 + + # cache the jump for this value digitsum(b) and c + sub_memo[c].insert(j, (diff, dn, k)) + return (diff, dn) + + +def compute(a_i, k, i, n): + """ + same as next_term(a_i, k, i, n) but computes terms without memoizing results. + """ + if i >= n: + return 0, i + if k > len(a_i): + a_i.extend([0 for _ in range(k - len(a_i))]) + + # note: a_i -> b * 10^k + c + # ds_b -> digitsum(b) + # ds_c -> digitsum(c) + start_i = i + ds_b, ds_c, diff = 0, 0, 0 + for j in range(len(a_i)): + if j >= k: + ds_b += a_i[j] + else: + ds_c += a_i[j] + + while i < n: + i += 1 + addend = ds_c + ds_b + diff += addend + ds_c = 0 + for j in range(k): + s = a_i[j] + addend + addend, a_i[j] = divmod(s, 10) + + ds_c += a_i[j] + + if addend > 0: + break + + if addend > 0: + add(a_i, k, addend) + return diff, i - start_i + + +def add(digits, k, addend): + """ + adds addend to digit array given in digits + starting at index k + """ + for j in range(k, len(digits)): + s = digits[j] + addend + if s >= 10: + quotient, digits[j] = divmod(s, 10) + addend = addend // 10 + quotient + else: + digits[j] = s + addend = addend // 10 + + if addend == 0: + break + + while addend > 0: + addend, digit = divmod(addend, 10) + digits.append(digit) + + +def solution(n): + """ + returns n-th term of sequence + + >>> solution(10) + 62 + + >>> solution(10**6) + 31054319 + + >>> solution(10**15) + 73597483551591773 + """ + + digits = [1] + i = 1 + dn = 0 + while True: + diff, terms_jumped = next_term(digits, 20, i + dn, n) + dn += terms_jumped + if dn == n - i: + break + + a_n = 0 + for j in range(len(digits)): + a_n += digits[j] * 10 ** j + return a_n + + +if __name__ == "__main__": + print(solution(10 ** 15)) From 9492e7af7cb24bb2fbe9e814c2e96dde96e95909 Mon Sep 17 00:00:00 2001 From: McDic Date: Tue, 3 Sep 2019 16:02:53 +0900 Subject: [PATCH 0200/1071] Created Sherman Morrison method (#1162) * Created Sherman Morrison * Added docstring for class * Updated Sherman morrison 1. Added docstring tests 2. Tweaked __str__() using join 3. Added __repr__() 4. Changed index validation to be independent method * Applied cclauss's point 1. Reduced line length for __str__() 2. Removed parens for assert --- matrix/sherman_morrison.py | 255 +++++++++++++++++++++++++++++++++++++ 1 file changed, 255 insertions(+) create mode 100644 matrix/sherman_morrison.py diff --git a/matrix/sherman_morrison.py b/matrix/sherman_morrison.py new file mode 100644 index 000000000000..0d49d78509be --- /dev/null +++ b/matrix/sherman_morrison.py @@ -0,0 +1,255 @@ +class Matrix: + """ + + Matrix structure. + """ + + def __init__(self, row: int, column: int, default_value: float = 0): + """ + + Initialize matrix with given size and default value. + + Example: + >>> a = Matrix(2, 3, 1) + >>> a + Matrix consist of 2 rows and 3 columns + [1, 1, 1] + [1, 1, 1] + """ + + self.row, self.column = row, column + self.array = [[default_value for c in range(column)] for r in range(row)] + + def __str__(self): + """ + + Return string representation of this matrix. + """ + + # Prefix + s = "Matrix consist of %d rows and %d columns\n" % (self.row, self.column) + + # Make string identifier + max_element_length = 0 + for row_vector in self.array: + for obj in row_vector: + max_element_length = max(max_element_length, len(str(obj))) + string_format_identifier = "%%%ds" % (max_element_length,) + + # Make string and return + def single_line(row_vector): + nonlocal string_format_identifier + line = "[" + line += ", ".join(string_format_identifier % (obj,) for obj in row_vector) + line += "]" + return line + s += "\n".join(single_line(row_vector) for row_vector in self.array) + return s + + def __repr__(self): return str(self) + + def validateIndices(self, loc: tuple): + """ + + Check if given indices are valid to pick element from matrix. + + Example: + >>> a = Matrix(2, 6, 0) + >>> a.validateIndices((2, 7)) + False + >>> a.validateIndices((0, 0)) + True + """ + if not(isinstance(loc, (list, tuple)) and len(loc) == 2): return False + elif not(0 <= loc[0] < self.row and 0 <= loc[1] < self.column): return False + else: return True + + def __getitem__(self, loc: tuple): + """ + + Return array[row][column] where loc = (row, column). + + Example: + >>> a = Matrix(3, 2, 7) + >>> a[1, 0] + 7 + """ + assert self.validateIndices(loc) + return self.array[loc[0]][loc[1]] + + def __setitem__(self, loc: tuple, value: float): + """ + + Set array[row][column] = value where loc = (row, column). + + Example: + >>> a = Matrix(2, 3, 1) + >>> a[1, 2] = 51 + >>> a + Matrix consist of 2 rows and 3 columns + [ 1, 1, 1] + [ 1, 1, 51] + """ + assert self.validateIndices(loc) + self.array[loc[0]][loc[1]] = value + + def __add__(self, another): + """ + + Return self + another. + + Example: + >>> a = Matrix(2, 1, -4) + >>> b = Matrix(2, 1, 3) + >>> a+b + Matrix consist of 2 rows and 1 columns + [-1] + [-1] + """ + + # Validation + assert isinstance(another, Matrix) + assert self.row == another.row and self.column == another.column + + # Add + result = Matrix(self.row, self.column) + for r in range(self.row): + for c in range(self.column): + result[r,c] = self[r,c] + another[r,c] + return result + + def __neg__(self): + """ + + Return -self. + + Example: + >>> a = Matrix(2, 2, 3) + >>> a[0, 1] = a[1, 0] = -2 + >>> -a + Matrix consist of 2 rows and 2 columns + [-3, 2] + [ 2, -3] + """ + + result = Matrix(self.row, self.column) + for r in range(self.row): + for c in range(self.column): + result[r,c] = -self[r,c] + return result + + def __sub__(self, another): return self + (-another) + + def __mul__(self, another): + """ + + Return self * another. + + Example: + >>> a = Matrix(2, 3, 1) + >>> a[0,2] = a[1,2] = 3 + >>> a * -2 + Matrix consist of 2 rows and 3 columns + [-2, -2, -6] + [-2, -2, -6] + """ + + if isinstance(another, (int, float)): # Scalar multiplication + result = Matrix(self.row, self.column) + for r in range(self.row): + for c in range(self.column): + result[r,c] = self[r,c] * another + return result + elif isinstance(another, Matrix): # Matrix multiplication + assert(self.column == another.row) + result = Matrix(self.row, another.column) + for r in range(self.row): + for c in range(another.column): + for i in range(self.column): + result[r,c] += self[r,i] * another[i,c] + return result + else: raise TypeError("Unsupported type given for another (%s)" % (type(another),)) + + def transpose(self): + """ + + Return self^T. + + Example: + >>> a = Matrix(2, 3) + >>> for r in range(2): + ... for c in range(3): + ... a[r,c] = r*c + ... + >>> a.transpose() + Matrix consist of 3 rows and 2 columns + [0, 0] + [0, 1] + [0, 2] + """ + + result = Matrix(self.column, self.row) + for r in range(self.row): + for c in range(self.column): + result[c,r] = self[r,c] + return result + + def ShermanMorrison(self, u, v): + """ + + Apply Sherman-Morrison formula in O(n^2). + To learn this formula, please look this: https://en.wikipedia.org/wiki/Sherman%E2%80%93Morrison_formula + This method returns (A + uv^T)^(-1) where A^(-1) is self. Returns None if it's impossible to calculate. + Warning: This method doesn't check if self is invertible. + Make sure self is invertible before execute this method. + + Example: + >>> ainv = Matrix(3, 3, 0) + >>> for i in range(3): ainv[i,i] = 1 + ... + >>> u = Matrix(3, 1, 0) + >>> u[0,0], u[1,0], u[2,0] = 1, 2, -3 + >>> v = Matrix(3, 1, 0) + >>> v[0,0], v[1,0], v[2,0] = 4, -2, 5 + >>> ainv.ShermanMorrison(u, v) + Matrix consist of 3 rows and 3 columns + [ 1.2857142857142856, -0.14285714285714285, 0.3571428571428571] + [ 0.5714285714285714, 0.7142857142857143, 0.7142857142857142] + [ -0.8571428571428571, 0.42857142857142855, -0.0714285714285714] + """ + + # Size validation + assert isinstance(u, Matrix) and isinstance(v, Matrix) + assert self.row == self.column == u.row == v.row # u, v should be column vector + assert u.column == v.column == 1 # u, v should be column vector + + # Calculate + vT = v.transpose() + numerator_factor = (vT * self * u)[0, 0] + 1 + if numerator_factor == 0: return None # It's not invertable + return self - ((self * u) * (vT * self) * (1.0 / numerator_factor)) + +# Testing +if __name__ == "__main__": + + def test1(): + # a^(-1) + ainv = Matrix(3, 3, 0) + for i in range(3): ainv[i,i] = 1 + print("a^(-1) is %s" % (ainv,)) + # u, v + u = Matrix(3, 1, 0) + u[0,0], u[1,0], u[2,0] = 1, 2, -3 + v = Matrix(3, 1, 0) + v[0,0], v[1,0], v[2,0] = 4, -2, 5 + print("u is %s" % (u,)) + print("v is %s" % (v,)) + print("uv^T is %s" % (u * v.transpose())) + # Sherman Morrison + print("(a + uv^T)^(-1) is %s" % (ainv.ShermanMorrison(u, v),)) + + def test2(): + import doctest + doctest.testmod() + + test2() \ No newline at end of file From a4ed40be86e79375ea54421fda9d860a3e0526e2 Mon Sep 17 00:00:00 2001 From: Maxwell Aladago Date: Wed, 4 Sep 2019 16:06:44 -0400 Subject: [PATCH 0201/1071] changing typo (#1168) --- divide_and_conquer/convex_hull.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/divide_and_conquer/convex_hull.py b/divide_and_conquer/convex_hull.py index 42219794aed1..a0c319e766da 100644 --- a/divide_and_conquer/convex_hull.py +++ b/divide_and_conquer/convex_hull.py @@ -7,7 +7,7 @@ Two algorithms have been implemented for the convex hull problem here. 1. A brute-force algorithm which runs in O(n^3) -2. A divide-and-conquer algorithm which runs in O(n^3) +2. A divide-and-conquer algorithm which runs in O(n log(n)) There are other several other algorithms for the convex hull problem which have not been implemented here, yet. From f31a812c468e41c3f5f7f170ae1dd5fa13bae6dd Mon Sep 17 00:00:00 2001 From: KirilBangachev <51961981+KirilBangachev@users.noreply.github.com> Date: Thu, 5 Sep 2019 08:58:38 +0300 Subject: [PATCH 0202/1071] Add Binomial Heap (#1146) * Binomial Heap Implementation of Binomial Heap. Reference: Advanced Data Structures, Peter Brass * Update binomial_heap.py * Update binomial_heap.py * Update binomial_heap.py - Fuller documentation of binomial heap - Update unit tests - Replace printing method by overwriting __str__() * Update binomial_heap.py - Added more tests - Added to the documentation - Stylistic editing - mergeHeaps now also returns a reference to the merged heap - added a preOrder function that returns a list with the preorder of the heap * Update binomial_heap.py Changed the unit tests structure * Turned the tests into doctests --- data_structures/heap/binomial_heap.py | 442 ++++++++++++++++++++++++++ 1 file changed, 442 insertions(+) create mode 100644 data_structures/heap/binomial_heap.py diff --git a/data_structures/heap/binomial_heap.py b/data_structures/heap/binomial_heap.py new file mode 100644 index 000000000000..bc9cb5145f2e --- /dev/null +++ b/data_structures/heap/binomial_heap.py @@ -0,0 +1,442 @@ +""" + Binomial Heap + + Reference: Advanced Data Structures, Peter Brass +""" + + +class Node: + """ + Node in a doubly-linked binomial tree, containing: + - value + - size of left subtree + - link to left, right and parent nodes + """ + + def __init__(self, val): + self.val = val + # Number of nodes in left subtree + self.left_tree_size = 0 + self.left = None + self.right = None + self.parent = None + + def mergeTrees(self, other): + """ + In-place merge of two binomial trees of equal size. + Returns the root of the resulting tree + """ + assert ( + self.left_tree_size == other.left_tree_size + ), "Unequal Sizes of Blocks" + + if self.val < other.val: + other.left = self.right + other.parent = None + if self.right: + self.right.parent = other + self.right = other + self.left_tree_size = ( + self.left_tree_size * 2 + 1 + ) + return self + else: + self.left = other.right + self.parent = None + if other.right: + other.right.parent = self + other.right = self + other.left_tree_size = ( + other.left_tree_size * 2 + 1 + ) + return other + + +class BinomialHeap: + """ + Min-oriented priority queue implemented with the Binomial Heap data + structure implemented with the BinomialHeap class. It supports: + + - Insert element in a heap with n elemnts: Guaranteed logn, amoratized 1 + - Merge (meld) heaps of size m and n: O(logn + logm) + - Delete Min: O(logn) + - Peek (return min without deleting it): O(1) + + Example: + + Create a random permutation of 30 integers to be inserted and + 19 of them deleted + >>> import numpy as np + >>> permutation = np.random.permutation(list(range(30))) + + Create a Heap and insert the 30 integers + + __init__() test + >>> first_heap = BinomialHeap() + + 30 inserts - insert() test + >>> for number in permutation: + ... first_heap.insert(number) + + Size test + >>> print(first_heap.size) + 30 + + Deleting - delete() test + >>> for i in range(25): + ... print(first_heap.deleteMin(), end=" ") + 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 + + Create a new Heap + >>> second_heap = BinomialHeap() + >>> vals = [17, 20, 31, 34] + >>> for value in vals: + ... second_heap.insert(value) + + + The heap should have the following structure: + + 17 + / \ + # 31 + / \ + 20 34 + / \ / \ + # # # # + + preOrder() test + >>> print(second_heap.preOrder()) + [(17, 0), ('#', 1), (31, 1), (20, 2), ('#', 3), ('#', 3), (34, 2), ('#', 3), ('#', 3)] + + printing Heap - __str__() test + >>> print(second_heap) + 17 + -# + -31 + --20 + ---# + ---# + --34 + ---# + ---# + + mergeHeaps() test + >>> merged = second_heap.mergeHeaps(first_heap) + >>> merged.peek() + 17 + + values in merged heap; (merge is inplace) + >>> while not first_heap.isEmpty(): + ... print(first_heap.deleteMin(), end=" ") + 17 20 25 26 27 28 29 31 34 + + """ + + def __init__( + self, bottom_root=None, min_node=None, heap_size=0 + ): + self.size = heap_size + self.bottom_root = bottom_root + self.min_node = min_node + + def mergeHeaps(self, other): + """ + In-place merge of two binomial heaps. + Both of them become the resulting merged heap + """ + + # Empty heaps corner cases + if other.size == 0: + return + if self.size == 0: + self.size = other.size + self.bottom_root = other.bottom_root + self.min_node = other.min_node + return + # Update size + self.size = self.size + other.size + + # Update min.node + if self.min_node.val > other.min_node.val: + self.min_node = other.min_node + # Merge + + # Order roots by left_subtree_size + combined_roots_list = [] + i, j = self.bottom_root, other.bottom_root + while i or j: + if i and ( + (not j) + or i.left_tree_size < j.left_tree_size + ): + combined_roots_list.append((i, True)) + i = i.parent + else: + combined_roots_list.append((j, False)) + j = j.parent + # Insert links between them + for i in range(len(combined_roots_list) - 1): + if ( + combined_roots_list[i][1] + != combined_roots_list[i + 1][1] + ): + combined_roots_list[i][ + 0 + ].parent = combined_roots_list[i + 1][0] + combined_roots_list[i + 1][ + 0 + ].left = combined_roots_list[i][0] + # Consecutively merge roots with same left_tree_size + i = combined_roots_list[0][0] + while i.parent: + if ( + ( + i.left_tree_size + == i.parent.left_tree_size + ) + and (not i.parent.parent) + ) or ( + i.left_tree_size == i.parent.left_tree_size + and i.left_tree_size + != i.parent.parent.left_tree_size + ): + + # Neighbouring Nodes + previous_node = i.left + next_node = i.parent.parent + + # Merging trees + i = i.mergeTrees(i.parent) + + # Updating links + i.left = previous_node + i.parent = next_node + if previous_node: + previous_node.parent = i + if next_node: + next_node.left = i + else: + i = i.parent + # Updating self.bottom_root + while i.left: + i = i.left + self.bottom_root = i + + # Update other + other.size = self.size + other.bottom_root = self.bottom_root + other.min_node = self.min_node + + # Return the merged heap + return self + + def insert(self, val): + """ + insert a value in the heap + """ + if self.size == 0: + self.bottom_root = Node(val) + self.size = 1 + self.min_node = self.bottom_root + else: + # Create new node + new_node = Node(val) + + # Update size + self.size += 1 + + # update min_node + if val < self.min_node.val: + self.min_node = new_node + # Put new_node as a bottom_root in heap + self.bottom_root.left = new_node + new_node.parent = self.bottom_root + self.bottom_root = new_node + + # Consecutively merge roots with same left_tree_size + while ( + self.bottom_root.parent + and self.bottom_root.left_tree_size + == self.bottom_root.parent.left_tree_size + ): + + # Next node + next_node = self.bottom_root.parent.parent + + # Merge + self.bottom_root = self.bottom_root.mergeTrees( + self.bottom_root.parent + ) + + # Update Links + self.bottom_root.parent = next_node + self.bottom_root.left = None + if next_node: + next_node.left = self.bottom_root + + def peek(self): + """ + return min element without deleting it + """ + return self.min_node.val + + def isEmpty(self): + return self.size == 0 + + def deleteMin(self): + """ + delete min element and return it + """ + # assert not self.isEmpty(), "Empty Heap" + + # Save minimal value + min_value = self.min_node.val + + # Last element in heap corner case + if self.size == 1: + # Update size + self.size = 0 + + # Update bottom root + self.bottom_root = None + + # Update min_node + self.min_node = None + + return min_value + # No right subtree corner case + # The structure of the tree implies that this should be the bottom root + # and there is at least one other root + if self.min_node.right == None: + # Update size + self.size -= 1 + + # Update bottom root + self.bottom_root = self.bottom_root.parent + self.bottom_root.left = None + + # Update min_node + self.min_node = self.bottom_root + i = self.bottom_root.parent + while i: + if i.val < self.min_node.val: + self.min_node = i + i = i.parent + return min_value + # General case + # Find the BinomialHeap of the right subtree of min_node + bottom_of_new = self.min_node.right + bottom_of_new.parent = None + min_of_new = bottom_of_new + size_of_new = 1 + + # Size, min_node and bottom_root + while bottom_of_new.left: + size_of_new = size_of_new * 2 + 1 + bottom_of_new = bottom_of_new.left + if bottom_of_new.val < min_of_new.val: + min_of_new = bottom_of_new + # Corner case of single root on top left path + if (not self.min_node.left) and ( + not self.min_node.parent + ): + self.size = size_of_new + self.bottom_root = bottom_of_new + self.min_node = min_of_new + # print("Single root, multiple nodes case") + return min_value + # Remaining cases + # Construct heap of right subtree + newHeap = BinomialHeap( + bottom_root=bottom_of_new, + min_node=min_of_new, + heap_size=size_of_new, + ) + + # Update size + self.size = self.size - 1 - size_of_new + + # Neighbour nodes + previous_node = self.min_node.left + next_node = self.min_node.parent + + # Initialize new bottom_root and min_node + self.min_node = previous_node or next_node + self.bottom_root = next_node + + # Update links of previous_node and search below for new min_node and + # bottom_root + if previous_node: + previous_node.parent = next_node + + # Update bottom_root and search for min_node below + self.bottom_root = previous_node + self.min_node = previous_node + while self.bottom_root.left: + self.bottom_root = self.bottom_root.left + if self.bottom_root.val < self.min_node.val: + self.min_node = self.bottom_root + if next_node: + next_node.left = previous_node + + # Search for new min_node above min_node + i = next_node + while i: + if i.val < self.min_node.val: + self.min_node = i + i = i.parent + # Merge heaps + self.mergeHeaps(newHeap) + + return min_value + + def preOrder(self): + """ + Returns the Pre-order representation of the heap including + values of nodes plus their level distance from the root; + Empty nodes appear as # + """ + # Find top root + top_root = self.bottom_root + while top_root.parent: + top_root = top_root.parent + # preorder + heap_preOrder = [] + self.__traversal(top_root, heap_preOrder) + return heap_preOrder + + def __traversal(self, curr_node, preorder, level=0): + """ + Pre-order traversal of nodes + """ + if curr_node: + preorder.append((curr_node.val, level)) + self.__traversal( + curr_node.left, preorder, level + 1 + ) + self.__traversal( + curr_node.right, preorder, level + 1 + ) + else: + preorder.append(("#", level)) + + def __str__(self): + """ + Overwriting str for a pre-order print of nodes in heap; + Performance is poor, so use only for small examples + """ + if self.isEmpty(): + return "" + preorder_heap = self.preOrder() + + return "\n".join( + ("-" * level + str(value)) + for value, level in preorder_heap + ) + + +# Unit Tests +if __name__ == "__main__": + import doctest + + doctest.testmod() From 2dfe01e4d8e4e84fbe3b04da08e5dfd18e17410a Mon Sep 17 00:00:00 2001 From: Maxwell Aladago Date: Thu, 5 Sep 2019 02:22:06 -0400 Subject: [PATCH 0203/1071] Fully refactored the rod cutting module. (#1169) * changing typo * fully refactored the rod-cutting module * more documentations * rewording --- dynamic_programming/rod_cutting.py | 230 +++++++++++++++++++++++------ 1 file changed, 183 insertions(+), 47 deletions(-) diff --git a/dynamic_programming/rod_cutting.py b/dynamic_programming/rod_cutting.py index c3111dcfc8a1..5b52eaca7c89 100644 --- a/dynamic_programming/rod_cutting.py +++ b/dynamic_programming/rod_cutting.py @@ -1,57 +1,193 @@ -from typing import List +""" +This module provides two implementations for the rod-cutting problem: +1. A naive recursive implementation which has an exponential runtime +2. Two dynamic programming implementations which have quadratic runtime -def rod_cutting(prices: List[int],length: int) -> int: +The rod-cutting problem is the problem of finding the maximum possible revenue +obtainable from a rod of length ``n`` given a list of prices for each integral piece +of the rod. The maximum revenue can thus be obtained by cutting the rod and selling the +pieces separately or not cutting it at all if the price of it is the maximum obtainable. + +""" + + +def naive_cut_rod_recursive(n: int, prices: list): + """ + Solves the rod-cutting problem via naively without using the benefit of dynamic programming. + The results is the same sub-problems are solved several times leading to an exponential runtime + + Runtime: O(2^n) + + Arguments + ------- + n: int, the length of the rod + prices: list, the prices for each piece of rod. ``p[i-i]`` is the + price for a rod of length ``i`` + + Returns + ------- + The maximum revenue obtainable for a rod of length n given the list of prices for each piece. + + Examples + -------- + >>> naive_cut_rod_recursive(4, [1, 5, 8, 9]) + 10 + >>> naive_cut_rod_recursive(10, [1, 5, 8, 9, 10, 17, 17, 20, 24, 30]) + 30 + """ + + _enforce_args(n, prices) + if n == 0: + return 0 + max_revue = float("-inf") + for i in range(1, n + 1): + max_revue = max(max_revue, prices[i - 1] + naive_cut_rod_recursive(n - i, prices)) + + return max_revue + + +def top_down_cut_rod(n: int, prices: list): """ - Given a rod of length n and array of prices that indicate price at each length. - Determine the maximum value obtainable by cutting up the rod and selling the pieces - - >>> rod_cutting([1,5,8,9],4) + Constructs a top-down dynamic programming solution for the rod-cutting problem + via memoization. This function serves as a wrapper for _top_down_cut_rod_recursive + + Runtime: O(n^2) + + Arguments + -------- + n: int, the length of the rod + prices: list, the prices for each piece of rod. ``p[i-i]`` is the + price for a rod of length ``i`` + + Note + ---- + For convenience and because Python's lists using 0-indexing, length(max_rev) = n + 1, + to accommodate for the revenue obtainable from a rod of length 0. + + Returns + ------- + The maximum revenue obtainable for a rod of length n given the list of prices for each piece. + + Examples + ------- + >>> top_down_cut_rod(4, [1, 5, 8, 9]) 10 - >>> rod_cutting([1,1,1],3) - 3 - >>> rod_cutting([1,2,3], -1) - Traceback (most recent call last): - ValueError: Given integer must be greater than 1, not -1 - >>> rod_cutting([1,2,3], 3.2) - Traceback (most recent call last): - TypeError: Must be int, not float - >>> rod_cutting([], 3) - Traceback (most recent call last): - AssertionError: prices list is shorted than length: 3 - - - - Args: - prices: list indicating price at each length, where prices[0] = 0 indicating rod of zero length has no value - length: length of rod - - Returns: - Maximum revenue attainable by cutting up the rod in any way. - """ - - prices.insert(0, 0) - if not isinstance(length, int): - raise TypeError('Must be int, not {0}'.format(type(length).__name__)) - if length < 0: - raise ValueError('Given integer must be greater than 1, not {0}'.format(length)) - assert len(prices) - 1 >= length, "prices list is shorted than length: {0}".format(length) - - return rod_cutting_recursive(prices, length) - -def rod_cutting_recursive(prices: List[int],length: int) -> int: - #base case - if length == 0: + >>> top_down_cut_rod(10, [1, 5, 8, 9, 10, 17, 17, 20, 24, 30]) + 30 + """ + _enforce_args(n, prices) + max_rev = [float("-inf") for _ in range(n + 1)] + return _top_down_cut_rod_recursive(n, prices, max_rev) + + +def _top_down_cut_rod_recursive(n: int, prices: list, max_rev: list): + """ + Constructs a top-down dynamic programming solution for the rod-cutting problem + via memoization. + + Runtime: O(n^2) + + Arguments + -------- + n: int, the length of the rod + prices: list, the prices for each piece of rod. ``p[i-i]`` is the + price for a rod of length ``i`` + max_rev: list, the computed maximum revenue for a piece of rod. + ``max_rev[i]`` is the maximum revenue obtainable for a rod of length ``i`` + + Returns + ------- + The maximum revenue obtainable for a rod of length n given the list of prices for each piece. + """ + if max_rev[n] >= 0: + return max_rev[n] + elif n == 0: return 0 - value = float('-inf') - for firstCutLocation in range(1,length+1): - value = max(value, prices[firstCutLocation]+rod_cutting_recursive(prices,length - firstCutLocation)) - return value + else: + max_revenue = float("-inf") + for i in range(1, n + 1): + max_revenue = max(max_revenue, prices[i - 1] + _top_down_cut_rod_recursive(n - i, prices, max_rev)) + + max_rev[n] = max_revenue + + return max_rev[n] + + +def bottom_up_cut_rod(n: int, prices: list): + """ + Constructs a bottom-up dynamic programming solution for the rod-cutting problem + + Runtime: O(n^2) + + Arguments + ---------- + n: int, the maximum length of the rod. + prices: list, the prices for each piece of rod. ``p[i-i]`` is the + price for a rod of length ``i`` + + Returns + ------- + The maximum revenue obtainable from cutting a rod of length n given + the prices for each piece of rod p. + + Examples + ------- + >>> bottom_up_cut_rod(4, [1, 5, 8, 9]) + 10 + >>> bottom_up_cut_rod(10, [1, 5, 8, 9, 10, 17, 17, 20, 24, 30]) + 30 + """ + _enforce_args(n, prices) + + # length(max_rev) = n + 1, to accommodate for the revenue obtainable from a rod of length 0. + max_rev = [float("-inf") for _ in range(n + 1)] + max_rev[0] = 0 + + for i in range(1, n + 1): + max_revenue_i = max_rev[i] + for j in range(1, i + 1): + max_revenue_i = max(max_revenue_i, prices[j - 1] + max_rev[i - j]) + + max_rev[i] = max_revenue_i + + return max_rev[n] + + +def _enforce_args(n: int, prices: list): + """ + Basic checks on the arguments to the rod-cutting algorithms + + n: int, the length of the rod + prices: list, the price list for each piece of rod. + + Throws ValueError: + + if n is negative or there are fewer items in the price list than the length of the rod + """ + if n < 0: + raise ValueError(f"n must be greater than or equal to 0. Got n = {n}") + + if n > len(prices): + raise ValueError(f"Each integral piece of rod must have a corresponding " + f"price. Got n = {n} but length of prices = {len(prices)}") def main(): - assert rod_cutting([1,5,8,9,10,17,17,20,24,30],10) == 30 - # print(rod_cutting([],0)) + prices = [6, 10, 12, 15, 20, 23] + n = len(prices) + + # the best revenue comes from cutting the rod into 6 pieces, each + # of length 1 resulting in a revenue of 6 * 6 = 36. + expected_max_revenue = 36 + + max_rev_top_down = top_down_cut_rod(n, prices) + max_rev_bottom_up = bottom_up_cut_rod(n, prices) + max_rev_naive = naive_cut_rod_recursive(n, prices) + + assert expected_max_revenue == max_rev_top_down + assert max_rev_top_down == max_rev_bottom_up + assert max_rev_bottom_up == max_rev_naive + if __name__ == '__main__': main() - From ab25079e168aa6de61bfd082af5a056b8ee50b43 Mon Sep 17 00:00:00 2001 From: Jai Kumar Dewani Date: Fri, 6 Sep 2019 14:32:37 +0530 Subject: [PATCH 0204/1071] Update DIRECTORY (#1161) * Update DIRECTORY * Updated DIRECTORY * Fixed bug in directory build and re-build the directory.md * fixed url issue * fixed indentation in Directory.md --- DIRECTORY.md | 622 +++++++--------------------------- scripts/build_directory_md.py | 6 +- 2 files changed, 117 insertions(+), 511 deletions(-) diff --git a/DIRECTORY.md b/DIRECTORY.md index 80bf64ef4c30..248fe7b9eaa6 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -1,805 +1,411 @@ ## Arithmetic Analysis - * [bisection](https://github.com/TheAlgorithms/Python/blob/master/arithmetic_analysis/bisection.py) - * [in static equilibrium](https://github.com/TheAlgorithms/Python/blob/master/arithmetic_analysis/in_static_equilibrium.py) - * [intersection](https://github.com/TheAlgorithms/Python/blob/master/arithmetic_analysis/intersection.py) - * [lu decomposition](https://github.com/TheAlgorithms/Python/blob/master/arithmetic_analysis/lu_decomposition.py) - * [newton method](https://github.com/TheAlgorithms/Python/blob/master/arithmetic_analysis/newton_method.py) - * [newton raphson method](https://github.com/TheAlgorithms/Python/blob/master/arithmetic_analysis/newton_raphson_method.py) - ## Backtracking - * [all combinations](https://github.com/TheAlgorithms/Python/blob/master/backtracking/all_combinations.py) - * [all permutations](https://github.com/TheAlgorithms/Python/blob/master/backtracking/all_permutations.py) - * [all subsequences](https://github.com/TheAlgorithms/Python/blob/master/backtracking/all_subsequences.py) - * [minimax](https://github.com/TheAlgorithms/Python/blob/master/backtracking/minimax.py) - * [n queens](https://github.com/TheAlgorithms/Python/blob/master/backtracking/n_queens.py) - * [sudoku](https://github.com/TheAlgorithms/Python/blob/master/backtracking/sudoku.py) - * [sum of subsets](https://github.com/TheAlgorithms/Python/blob/master/backtracking/sum_of_subsets.py) - ## Boolean Algebra - * [quine mc cluskey](https://github.com/TheAlgorithms/Python/blob/master/boolean_algebra/quine_mc_cluskey.py) - ## Ciphers - * [affine cipher](https://github.com/TheAlgorithms/Python/blob/master/ciphers/affine_cipher.py) - * [atbash](https://github.com/TheAlgorithms/Python/blob/master/ciphers/atbash.py) - * [base16](https://github.com/TheAlgorithms/Python/blob/master/ciphers/base16.py) - * [base32](https://github.com/TheAlgorithms/Python/blob/master/ciphers/base32.py) - * [base64 cipher](https://github.com/TheAlgorithms/Python/blob/master/ciphers/base64_cipher.py) - * [base85](https://github.com/TheAlgorithms/Python/blob/master/ciphers/base85.py) - * [brute force caesar cipher](https://github.com/TheAlgorithms/Python/blob/master/ciphers/brute_force_caesar_cipher.py) - * [caesar cipher](https://github.com/TheAlgorithms/Python/blob/master/ciphers/caesar_cipher.py) - * [cryptomath module](https://github.com/TheAlgorithms/Python/blob/master/ciphers/cryptomath_module.py) - * [elgamal key generator](https://github.com/TheAlgorithms/Python/blob/master/ciphers/elgamal_key_generator.py) - * [hill cipher](https://github.com/TheAlgorithms/Python/blob/master/ciphers/hill_cipher.py) - * [morse code implementation](https://github.com/TheAlgorithms/Python/blob/master/ciphers/morse_code_implementation.py) - * [onepad cipher](https://github.com/TheAlgorithms/Python/blob/master/ciphers/onepad_cipher.py) - * [playfair cipher](https://github.com/TheAlgorithms/Python/blob/master/ciphers/playfair_cipher.py) - * [rabin miller](https://github.com/TheAlgorithms/Python/blob/master/ciphers/rabin_miller.py) - * [rot13](https://github.com/TheAlgorithms/Python/blob/master/ciphers/rot13.py) - * [rsa cipher](https://github.com/TheAlgorithms/Python/blob/master/ciphers/rsa_cipher.py) - * [rsa key generator](https://github.com/TheAlgorithms/Python/blob/master/ciphers/rsa_key_generator.py) - * [simple substitution cipher](https://github.com/TheAlgorithms/Python/blob/master/ciphers/simple_substitution_cipher.py) - * [trafid cipher](https://github.com/TheAlgorithms/Python/blob/master/ciphers/trafid_cipher.py) - * [transposition cipher](https://github.com/TheAlgorithms/Python/blob/master/ciphers/transposition_cipher.py) - * [transposition cipher encrypt decrypt file](https://github.com/TheAlgorithms/Python/blob/master/ciphers/transposition_cipher_encrypt_decrypt_file.py) - * [vigenere cipher](https://github.com/TheAlgorithms/Python/blob/master/ciphers/vigenere_cipher.py) - * [xor cipher](https://github.com/TheAlgorithms/Python/blob/master/ciphers/xor_cipher.py) - ## Compression - * [burrows wheeler](https://github.com/TheAlgorithms/Python/blob/master/compression/burrows_wheeler.py) - * [huffman](https://github.com/TheAlgorithms/Python/blob/master/compression/huffman.py) - * [peak signal to noise ratio](https://github.com/TheAlgorithms/Python/blob/master/compression/peak_signal_to_noise_ratio.py) - ## Conversions - * [decimal to binary](https://github.com/TheAlgorithms/Python/blob/master/conversions/decimal_to_binary.py) - * [decimal to hexadecimal](https://github.com/TheAlgorithms/Python/blob/master/conversions/decimal_to_hexadecimal.py) - * [decimal to octal](https://github.com/TheAlgorithms/Python/blob/master/conversions/decimal_to_octal.py) - ## Data Structures - * Binary Tree - - * [avl tree](https://github.com/TheAlgorithms/Python/blob/master/data_structures/binary_tree/avl_tree.py) - - * [basic binary tree](https://github.com/TheAlgorithms/Python/blob/master/data_structures/binary_tree/basic_binary_tree.py) - - * [binary search tree](https://github.com/TheAlgorithms/Python/blob/master/data_structures/binary_tree/binary_search_tree.py) - - * [fenwick tree](https://github.com/TheAlgorithms/Python/blob/master/data_structures/binary_tree/fenwick_tree.py) - - * [lazy segment tree](https://github.com/TheAlgorithms/Python/blob/master/data_structures/binary_tree/lazy_segment_tree.py) - - * [lca](https://github.com/TheAlgorithms/Python/blob/master/data_structures/binary_tree/lca.py) - - * [red black tree](https://github.com/TheAlgorithms/Python/blob/master/data_structures/binary_tree/red_black_tree.py) - - * [segment tree](https://github.com/TheAlgorithms/Python/blob/master/data_structures/binary_tree/segment_tree.py) - - * [treap](https://github.com/TheAlgorithms/Python/blob/master/data_structures/binary_tree/treap.py) - + * [avl tree](https://github.com/TheAlgorithms/Python/blob/master/data_structures/avl_tree.py) + * [basic binary tree](https://github.com/TheAlgorithms/Python/blob/master/data_structures/basic_binary_tree.py) + * [binary search tree](https://github.com/TheAlgorithms/Python/blob/master/data_structures/binary_search_tree.py) + * [fenwick tree](https://github.com/TheAlgorithms/Python/blob/master/data_structures/fenwick_tree.py) + * [lazy segment tree](https://github.com/TheAlgorithms/Python/blob/master/data_structures/lazy_segment_tree.py) + * [lca](https://github.com/TheAlgorithms/Python/blob/master/data_structures/lca.py) + * [red black tree](https://github.com/TheAlgorithms/Python/blob/master/data_structures/red_black_tree.py) + * [segment tree](https://github.com/TheAlgorithms/Python/blob/master/data_structures/segment_tree.py) + * [treap](https://github.com/TheAlgorithms/Python/blob/master/data_structures/treap.py) * Hashing - - * [double hash](https://github.com/TheAlgorithms/Python/blob/master/data_structures/hashing/double_hash.py) - - * [hash table](https://github.com/TheAlgorithms/Python/blob/master/data_structures/hashing/hash_table.py) - - * [hash table with linked list](https://github.com/TheAlgorithms/Python/blob/master/data_structures/hashing/hash_table_with_linked_list.py) - - * Number Theory - - * [prime numbers](https://github.com/TheAlgorithms/Python/blob/master/data_structures/hashing/number_theory/prime_numbers.py) - - * [quadratic probing](https://github.com/TheAlgorithms/Python/blob/master/data_structures/hashing/quadratic_probing.py) - + * [double hash](https://github.com/TheAlgorithms/Python/blob/master/data_structures/double_hash.py) + * [hash table](https://github.com/TheAlgorithms/Python/blob/master/data_structures/hash_table.py) + * [hash table with linked list](https://github.com/TheAlgorithms/Python/blob/master/data_structures/hash_table_with_linked_list.py) + * Number Theory + * [prime numbers](https://github.com/TheAlgorithms/Python/blob/master/data_structures/prime_numbers.py) + * [quadratic probing](https://github.com/TheAlgorithms/Python/blob/master/data_structures/quadratic_probing.py) * Heap - - * [heap](https://github.com/TheAlgorithms/Python/blob/master/data_structures/heap/heap.py) - + * [heap](https://github.com/TheAlgorithms/Python/blob/master/data_structures/heap.py) * Linked List - - * [doubly linked list](https://github.com/TheAlgorithms/Python/blob/master/data_structures/linked_list/doubly_linked_list.py) - - * [is palindrome](https://github.com/TheAlgorithms/Python/blob/master/data_structures/linked_list/is_palindrome.py) - - * [singly linked list](https://github.com/TheAlgorithms/Python/blob/master/data_structures/linked_list/singly_linked_list.py) - - * [swap nodes](https://github.com/TheAlgorithms/Python/blob/master/data_structures/linked_list/swap_nodes.py) - + * [doubly linked list](https://github.com/TheAlgorithms/Python/blob/master/data_structures/doubly_linked_list.py) + * [is palindrome](https://github.com/TheAlgorithms/Python/blob/master/data_structures/is_palindrome.py) + * [singly linked list](https://github.com/TheAlgorithms/Python/blob/master/data_structures/singly_linked_list.py) + * [swap nodes](https://github.com/TheAlgorithms/Python/blob/master/data_structures/swap_nodes.py) * Queue - - * [double ended queue](https://github.com/TheAlgorithms/Python/blob/master/data_structures/queue/double_ended_queue.py) - - * [queue on list](https://github.com/TheAlgorithms/Python/blob/master/data_structures/queue/queue_on_list.py) - - * [queue on pseudo stack](https://github.com/TheAlgorithms/Python/blob/master/data_structures/queue/queue_on_pseudo_stack.py) - + * [double ended queue](https://github.com/TheAlgorithms/Python/blob/master/data_structures/double_ended_queue.py) + * [queue on list](https://github.com/TheAlgorithms/Python/blob/master/data_structures/queue_on_list.py) + * [queue on pseudo stack](https://github.com/TheAlgorithms/Python/blob/master/data_structures/queue_on_pseudo_stack.py) * Stacks - - * [balanced parentheses](https://github.com/TheAlgorithms/Python/blob/master/data_structures/stacks/balanced_parentheses.py) - - * [infix to postfix conversion](https://github.com/TheAlgorithms/Python/blob/master/data_structures/stacks/infix_to_postfix_conversion.py) - - * [infix to prefix conversion](https://github.com/TheAlgorithms/Python/blob/master/data_structures/stacks/infix_to_prefix_conversion.py) - - * [next greater element](https://github.com/TheAlgorithms/Python/blob/master/data_structures/stacks/next_greater_element.py) - - * [postfix evaluation](https://github.com/TheAlgorithms/Python/blob/master/data_structures/stacks/postfix_evaluation.py) - - * [stack](https://github.com/TheAlgorithms/Python/blob/master/data_structures/stacks/stack.py) - - * [stock span problem](https://github.com/TheAlgorithms/Python/blob/master/data_structures/stacks/stock_span_problem.py) - + * [balanced parentheses](https://github.com/TheAlgorithms/Python/blob/master/data_structures/balanced_parentheses.py) + * [infix to postfix conversion](https://github.com/TheAlgorithms/Python/blob/master/data_structures/infix_to_postfix_conversion.py) + * [infix to prefix conversion](https://github.com/TheAlgorithms/Python/blob/master/data_structures/infix_to_prefix_conversion.py) + * [next greater element](https://github.com/TheAlgorithms/Python/blob/master/data_structures/next_greater_element.py) + * [postfix evaluation](https://github.com/TheAlgorithms/Python/blob/master/data_structures/postfix_evaluation.py) + * [stack](https://github.com/TheAlgorithms/Python/blob/master/data_structures/stack.py) + * [stock span problem](https://github.com/TheAlgorithms/Python/blob/master/data_structures/stock_span_problem.py) * Trie - - * [trie](https://github.com/TheAlgorithms/Python/blob/master/data_structures/trie/trie.py) - + * [trie](https://github.com/TheAlgorithms/Python/blob/master/data_structures/trie.py) ## Digital Image Processing - - * [change contrast](https://github.com/TheAlgorithms/Python/blob/master/digital_image_processing/change_contrast.py) - + * [change contrast](https://github.com/TheAlgorithms/Python/blob/master/digital_image_processing/change_contrast.py) * Edge Detection - - * [canny](https://github.com/TheAlgorithms/Python/blob/master/digital_image_processing/edge_detection/canny.py) - + * [canny](https://github.com/TheAlgorithms/Python/blob/master/digital_image_processing/canny.py) * Filters - - * [convolve](https://github.com/TheAlgorithms/Python/blob/master/digital_image_processing/filters/convolve.py) - - * [gaussian filter](https://github.com/TheAlgorithms/Python/blob/master/digital_image_processing/filters/gaussian_filter.py) - - * [median filter](https://github.com/TheAlgorithms/Python/blob/master/digital_image_processing/filters/median_filter.py) - - * [sobel filter](https://github.com/TheAlgorithms/Python/blob/master/digital_image_processing/filters/sobel_filter.py) - - * [test digital image processing](https://github.com/TheAlgorithms/Python/blob/master/digital_image_processing/test_digital_image_processing.py) - + * [convolve](https://github.com/TheAlgorithms/Python/blob/master/digital_image_processing/convolve.py) + * [gaussian filter](https://github.com/TheAlgorithms/Python/blob/master/digital_image_processing/gaussian_filter.py) + * [median filter](https://github.com/TheAlgorithms/Python/blob/master/digital_image_processing/median_filter.py) + * [sobel filter](https://github.com/TheAlgorithms/Python/blob/master/digital_image_processing/sobel_filter.py) + * [test digital image processing](https://github.com/TheAlgorithms/Python/blob/master/digital_image_processing/test_digital_image_processing.py) ## Divide And Conquer - * [closest pair of points](https://github.com/TheAlgorithms/Python/blob/master/divide_and_conquer/closest_pair_of_points.py) - + * [convex hull](https://github.com/TheAlgorithms/Python/blob/master/divide_and_conquer/convex_hull.py) + * [inversions](https://github.com/TheAlgorithms/Python/blob/master/divide_and_conquer/inversions.py) * [max subarray sum](https://github.com/TheAlgorithms/Python/blob/master/divide_and_conquer/max_subarray_sum.py) - ## Dynamic Programming - * [abbreviation](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/abbreviation.py) - * [bitmask](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/bitmask.py) - * [climbing stairs](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/climbing_stairs.py) - * [coin change](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/coin_change.py) - * [edit distance](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/edit_distance.py) - * [factorial](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/factorial.py) - * [fast fibonacci](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/fast_fibonacci.py) - * [fibonacci](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/fibonacci.py) - * [floyd warshall](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/floyd_warshall.py) - * [fractional knapsack](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/fractional_knapsack.py) - * [integer partition](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/integer_partition.py) - * [k means clustering tensorflow](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/k_means_clustering_tensorflow.py) - * [knapsack](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/knapsack.py) - * [longest common subsequence](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/longest_common_subsequence.py) - * [longest increasing subsequence](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/longest_increasing_subsequence.py) - * [longest increasing subsequence o(nlogn)](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/longest_increasing_subsequence_o(nlogn).py) - * [longest sub array](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/longest_sub_array.py) - * [matrix chain order](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/matrix_chain_order.py) - * [max sub array](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/max_sub_array.py) - * [minimum partition](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/minimum_partition.py) - * [rod cutting](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/rod_cutting.py) - * [subset generation](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/subset_generation.py) - * [sum of subset](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/sum_of_subset.py) - ## File Transfer - * [recieve file](https://github.com/TheAlgorithms/Python/blob/master/file_transfer/recieve_file.py) - * [send file](https://github.com/TheAlgorithms/Python/blob/master/file_transfer/send_file.py) - ## Graphs - * [a star](https://github.com/TheAlgorithms/Python/blob/master/graphs/a_star.py) - * [articulation points](https://github.com/TheAlgorithms/Python/blob/master/graphs/articulation_points.py) - * [basic graphs](https://github.com/TheAlgorithms/Python/blob/master/graphs/basic_graphs.py) - * [bellman ford](https://github.com/TheAlgorithms/Python/blob/master/graphs/bellman_ford.py) - * [bfs](https://github.com/TheAlgorithms/Python/blob/master/graphs/bfs.py) - * [bfs shortest path](https://github.com/TheAlgorithms/Python/blob/master/graphs/bfs_shortest_path.py) - * [breadth first search](https://github.com/TheAlgorithms/Python/blob/master/graphs/breadth_first_search.py) - * [check bipartite graph bfs](https://github.com/TheAlgorithms/Python/blob/master/graphs/check_bipartite_graph_bfs.py) - * [check bipartite graph dfs](https://github.com/TheAlgorithms/Python/blob/master/graphs/check_bipartite_graph_dfs.py) - * [depth first search](https://github.com/TheAlgorithms/Python/blob/master/graphs/depth_first_search.py) - * [dfs](https://github.com/TheAlgorithms/Python/blob/master/graphs/dfs.py) - * [dijkstra](https://github.com/TheAlgorithms/Python/blob/master/graphs/dijkstra.py) - * [dijkstra 2](https://github.com/TheAlgorithms/Python/blob/master/graphs/dijkstra_2.py) - * [dijkstra algorithm](https://github.com/TheAlgorithms/Python/blob/master/graphs/dijkstra_algorithm.py) - * [directed and undirected (weighted) graph](https://github.com/TheAlgorithms/Python/blob/master/graphs/directed_and_undirected_(weighted)_graph.py) - * [edmonds karp multiple source and sink](https://github.com/TheAlgorithms/Python/blob/master/graphs/edmonds_karp_multiple_source_and_sink.py) - * [eulerian path and circuit for undirected graph](https://github.com/TheAlgorithms/Python/blob/master/graphs/eulerian_path_and_circuit_for_undirected_graph.py) - * [even tree](https://github.com/TheAlgorithms/Python/blob/master/graphs/even_tree.py) - * [finding bridges](https://github.com/TheAlgorithms/Python/blob/master/graphs/finding_bridges.py) - * [graph list](https://github.com/TheAlgorithms/Python/blob/master/graphs/graph_list.py) - * [graph matrix](https://github.com/TheAlgorithms/Python/blob/master/graphs/graph_matrix.py) - * [graphs floyd warshall](https://github.com/TheAlgorithms/Python/blob/master/graphs/graphs_floyd_warshall.py) - * [kahns algorithm long](https://github.com/TheAlgorithms/Python/blob/master/graphs/kahns_algorithm_long.py) - * [kahns algorithm topo](https://github.com/TheAlgorithms/Python/blob/master/graphs/kahns_algorithm_topo.py) - * [minimum spanning tree kruskal](https://github.com/TheAlgorithms/Python/blob/master/graphs/minimum_spanning_tree_kruskal.py) - * [minimum spanning tree prims](https://github.com/TheAlgorithms/Python/blob/master/graphs/minimum_spanning_tree_prims.py) - * [multi hueristic astar](https://github.com/TheAlgorithms/Python/blob/master/graphs/multi_hueristic_astar.py) - * [page rank](https://github.com/TheAlgorithms/Python/blob/master/graphs/page_rank.py) - * [prim](https://github.com/TheAlgorithms/Python/blob/master/graphs/prim.py) - * [scc kosaraju](https://github.com/TheAlgorithms/Python/blob/master/graphs/scc_kosaraju.py) - * [tarjans scc](https://github.com/TheAlgorithms/Python/blob/master/graphs/tarjans_scc.py) - ## Hashes - * [chaos machine](https://github.com/TheAlgorithms/Python/blob/master/hashes/chaos_machine.py) - * [enigma machine](https://github.com/TheAlgorithms/Python/blob/master/hashes/enigma_machine.py) - * [md5](https://github.com/TheAlgorithms/Python/blob/master/hashes/md5.py) - * [sha1](https://github.com/TheAlgorithms/Python/blob/master/hashes/sha1.py) - ## Linear Algebra - * Src - - * [lib](https://github.com/TheAlgorithms/Python/blob/master/linear_algebra/src/lib.py) - - * [python-polynom-for-points](https://github.com/TheAlgorithms/Python/blob/master/linear_algebra/src/python-polynom-for-points.py) - - * [tests](https://github.com/TheAlgorithms/Python/blob/master/linear_algebra/src/tests.py) - + * [lib](https://github.com/TheAlgorithms/Python/blob/master/linear_algebra/lib.py) + * [polynom-for-points](https://github.com/TheAlgorithms/Python/blob/master/linear_algebra/polynom-for-points.py) + * [tests](https://github.com/TheAlgorithms/Python/blob/master/linear_algebra/tests.py) ## Machine Learning - * [decision tree](https://github.com/TheAlgorithms/Python/blob/master/machine_learning/decision_tree.py) - * [gradient descent](https://github.com/TheAlgorithms/Python/blob/master/machine_learning/gradient_descent.py) - * [k means clust](https://github.com/TheAlgorithms/Python/blob/master/machine_learning/k_means_clust.py) - * [knn sklearn](https://github.com/TheAlgorithms/Python/blob/master/machine_learning/knn_sklearn.py) - * [linear regression](https://github.com/TheAlgorithms/Python/blob/master/machine_learning/linear_regression.py) - * [logistic regression](https://github.com/TheAlgorithms/Python/blob/master/machine_learning/logistic_regression.py) - * [naive bayes](https://github.com/TheAlgorithms/Python/blob/master/machine_learning/naive_bayes.ipynb) - * Random Forest Classification - - * [random forest classification](https://github.com/TheAlgorithms/Python/blob/master/machine_learning/random_forest_classification/random_forest_classification.py) - - * [random forest classifier](https://github.com/TheAlgorithms/Python/blob/master/machine_learning/random_forest_classification/random_forest_classifier.ipynb) - + * [random forest classification](https://github.com/TheAlgorithms/Python/blob/master/machine_learning/random_forest_classification.py) + * [random forest classifier](https://github.com/TheAlgorithms/Python/blob/master/machine_learning/random_forest_classifier.ipynb) * Random Forest Regression - - * [random forest regression](https://github.com/TheAlgorithms/Python/blob/master/machine_learning/random_forest_regression/random_forest_regression.ipynb) - - * [random forest regression](https://github.com/TheAlgorithms/Python/blob/master/machine_learning/random_forest_regression/random_forest_regression.py) - + * [random forest regression](https://github.com/TheAlgorithms/Python/blob/master/machine_learning/random_forest_regression.ipynb) + * [random forest regression](https://github.com/TheAlgorithms/Python/blob/master/machine_learning/random_forest_regression.py) * [reuters one vs rest classifier](https://github.com/TheAlgorithms/Python/blob/master/machine_learning/reuters_one_vs_rest_classifier.ipynb) - * [scoring functions](https://github.com/TheAlgorithms/Python/blob/master/machine_learning/scoring_functions.py) - + * [sorted vector machines](https://github.com/TheAlgorithms/Python/blob/master/machine_learning/sorted_vector_machines.py) ## Maths - * [3n+1](https://github.com/TheAlgorithms/Python/blob/master/maths/3n+1.py) - * [abs](https://github.com/TheAlgorithms/Python/blob/master/maths/abs.py) - * [abs max](https://github.com/TheAlgorithms/Python/blob/master/maths/abs_max.py) - * [abs min](https://github.com/TheAlgorithms/Python/blob/master/maths/abs_min.py) - * [average mean](https://github.com/TheAlgorithms/Python/blob/master/maths/average_mean.py) - * [average median](https://github.com/TheAlgorithms/Python/blob/master/maths/average_median.py) - * [basic maths](https://github.com/TheAlgorithms/Python/blob/master/maths/basic_maths.py) - * [binary exponentiation](https://github.com/TheAlgorithms/Python/blob/master/maths/binary_exponentiation.py) - * [collatz sequence](https://github.com/TheAlgorithms/Python/blob/master/maths/collatz_sequence.py) - * [extended euclidean algorithm](https://github.com/TheAlgorithms/Python/blob/master/maths/extended_euclidean_algorithm.py) - * [factorial python](https://github.com/TheAlgorithms/Python/blob/master/maths/factorial_python.py) - * [factorial recursive](https://github.com/TheAlgorithms/Python/blob/master/maths/factorial_recursive.py) - * [fermat little theorem](https://github.com/TheAlgorithms/Python/blob/master/maths/fermat_little_theorem.py) - * [fibonacci](https://github.com/TheAlgorithms/Python/blob/master/maths/fibonacci.py) - * [fibonacci sequence recursion](https://github.com/TheAlgorithms/Python/blob/master/maths/fibonacci_sequence_recursion.py) - * [find lcm](https://github.com/TheAlgorithms/Python/blob/master/maths/find_lcm.py) - * [find max](https://github.com/TheAlgorithms/Python/blob/master/maths/find_max.py) - * [find min](https://github.com/TheAlgorithms/Python/blob/master/maths/find_min.py) - * [gaussian](https://github.com/TheAlgorithms/Python/blob/master/maths/gaussian.py) - * [greater common divisor](https://github.com/TheAlgorithms/Python/blob/master/maths/greater_common_divisor.py) - * [is square free](https://github.com/TheAlgorithms/Python/blob/master/maths/is_square_free.py) - * [largest of very large numbers](https://github.com/TheAlgorithms/Python/blob/master/maths/largest_of_very_large_numbers.py) - * [lucas lehmer primality test](https://github.com/TheAlgorithms/Python/blob/master/maths/lucas_lehmer_primality_test.py) - * [lucas series](https://github.com/TheAlgorithms/Python/blob/master/maths/lucas_series.py) - * [mobius function](https://github.com/TheAlgorithms/Python/blob/master/maths/mobius_function.py) - * [modular exponential](https://github.com/TheAlgorithms/Python/blob/master/maths/modular_exponential.py) - * [newton raphson](https://github.com/TheAlgorithms/Python/blob/master/maths/newton_raphson.py) - * [prime check](https://github.com/TheAlgorithms/Python/blob/master/maths/prime_check.py) - * [prime factors](https://github.com/TheAlgorithms/Python/blob/master/maths/prime_factors.py) - * [quadratic equations complex numbers](https://github.com/TheAlgorithms/Python/blob/master/maths/quadratic_equations_complex_numbers.py) - * [segmented sieve](https://github.com/TheAlgorithms/Python/blob/master/maths/segmented_sieve.py) - * [sieve of eratosthenes](https://github.com/TheAlgorithms/Python/blob/master/maths/sieve_of_eratosthenes.py) - * [simpson rule](https://github.com/TheAlgorithms/Python/blob/master/maths/simpson_rule.py) - * [test prime check](https://github.com/TheAlgorithms/Python/blob/master/maths/test_prime_check.py) - * [trapezoidal rule](https://github.com/TheAlgorithms/Python/blob/master/maths/trapezoidal_rule.py) - * [volume](https://github.com/TheAlgorithms/Python/blob/master/maths/volume.py) - * [zellers congruence](https://github.com/TheAlgorithms/Python/blob/master/maths/zellers_congruence.py) - ## Matrix - * [matrix operation](https://github.com/TheAlgorithms/Python/blob/master/matrix/matrix_operation.py) - * [nth fibonacci using matrix exponentiation](https://github.com/TheAlgorithms/Python/blob/master/matrix/nth_fibonacci_using_matrix_exponentiation.py) - * [rotate matrix](https://github.com/TheAlgorithms/Python/blob/master/matrix/rotate_matrix.py) - * [searching in sorted matrix](https://github.com/TheAlgorithms/Python/blob/master/matrix/searching_in_sorted_matrix.py) - * [spiral print](https://github.com/TheAlgorithms/Python/blob/master/matrix/spiral_print.py) - * Tests - - * [test matrix operation](https://github.com/TheAlgorithms/Python/blob/master/matrix/tests/test_matrix_operation.py) - + * [test matrix operation](https://github.com/TheAlgorithms/Python/blob/master/matrix/test_matrix_operation.py) ## Networking Flow - * [ford fulkerson](https://github.com/TheAlgorithms/Python/blob/master/networking_flow/ford_fulkerson.py) - * [minimum cut](https://github.com/TheAlgorithms/Python/blob/master/networking_flow/minimum_cut.py) - ## Neural Network - * [back propagation neural network](https://github.com/TheAlgorithms/Python/blob/master/neural_network/back_propagation_neural_network.py) - * [convolution neural network](https://github.com/TheAlgorithms/Python/blob/master/neural_network/convolution_neural_network.py) - * [fully connected neural network](https://github.com/TheAlgorithms/Python/blob/master/neural_network/fully_connected_neural_network.ipynb) - * [perceptron](https://github.com/TheAlgorithms/Python/blob/master/neural_network/perceptron.py) - ## Other - * [anagrams](https://github.com/TheAlgorithms/Python/blob/master/other/anagrams.py) - * [binary exponentiation](https://github.com/TheAlgorithms/Python/blob/master/other/binary_exponentiation.py) - * [binary exponentiation 2](https://github.com/TheAlgorithms/Python/blob/master/other/binary_exponentiation_2.py) - * [detecting english programmatically](https://github.com/TheAlgorithms/Python/blob/master/other/detecting_english_programmatically.py) - * [euclidean gcd](https://github.com/TheAlgorithms/Python/blob/master/other/euclidean_gcd.py) - * [fischer yates shuffle](https://github.com/TheAlgorithms/Python/blob/master/other/fischer_yates_shuffle.py) - * [food wastage analysis from 1961-2013 fao](https://github.com/TheAlgorithms/Python/blob/master/other/food_wastage_analysis_from_1961-2013_fao.ipynb) - * [frequency finder](https://github.com/TheAlgorithms/Python/blob/master/other/frequency_finder.py) - * [game of life](https://github.com/TheAlgorithms/Python/blob/master/other/game_of_life.py) - * [linear congruential generator](https://github.com/TheAlgorithms/Python/blob/master/other/linear_congruential_generator.py) - * [nested brackets](https://github.com/TheAlgorithms/Python/blob/master/other/nested_brackets.py) - * [palindrome](https://github.com/TheAlgorithms/Python/blob/master/other/palindrome.py) - * [password generator](https://github.com/TheAlgorithms/Python/blob/master/other/password_generator.py) - * [primelib](https://github.com/TheAlgorithms/Python/blob/master/other/primelib.py) - * [sierpinski triangle](https://github.com/TheAlgorithms/Python/blob/master/other/sierpinski_triangle.py) - * [tower of hanoi](https://github.com/TheAlgorithms/Python/blob/master/other/tower_of_hanoi.py) - * [two sum](https://github.com/TheAlgorithms/Python/blob/master/other/two_sum.py) - * [word patterns](https://github.com/TheAlgorithms/Python/blob/master/other/word_patterns.py) - ## Project Euler - * Problem 01 - - * [sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_01/sol1.py) - - * [sol2](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_01/sol2.py) - - * [sol3](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_01/sol3.py) - - * [sol4](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_01/sol4.py) - - * [sol5](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_01/sol5.py) - - * [sol6](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_01/sol6.py) - + * [sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/sol1.py) + * [sol2](https://github.com/TheAlgorithms/Python/blob/master/project_euler/sol2.py) + * [sol3](https://github.com/TheAlgorithms/Python/blob/master/project_euler/sol3.py) + * [sol4](https://github.com/TheAlgorithms/Python/blob/master/project_euler/sol4.py) + * [sol5](https://github.com/TheAlgorithms/Python/blob/master/project_euler/sol5.py) + * [sol6](https://github.com/TheAlgorithms/Python/blob/master/project_euler/sol6.py) * Problem 02 - - * [sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_02/sol1.py) - - * [sol2](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_02/sol2.py) - - * [sol3](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_02/sol3.py) - - * [sol4](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_02/sol4.py) - + * [sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/sol1.py) + * [sol2](https://github.com/TheAlgorithms/Python/blob/master/project_euler/sol2.py) + * [sol3](https://github.com/TheAlgorithms/Python/blob/master/project_euler/sol3.py) + * [sol4](https://github.com/TheAlgorithms/Python/blob/master/project_euler/sol4.py) * Problem 03 - - * [sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_03/sol1.py) - - * [sol2](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_03/sol2.py) - + * [sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/sol1.py) + * [sol2](https://github.com/TheAlgorithms/Python/blob/master/project_euler/sol2.py) * Problem 04 - - * [sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_04/sol1.py) - - * [sol2](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_04/sol2.py) - + * [sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/sol1.py) + * [sol2](https://github.com/TheAlgorithms/Python/blob/master/project_euler/sol2.py) * Problem 05 - - * [sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_05/sol1.py) - - * [sol2](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_05/sol2.py) - + * [sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/sol1.py) + * [sol2](https://github.com/TheAlgorithms/Python/blob/master/project_euler/sol2.py) * Problem 06 - - * [sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_06/sol1.py) - - * [sol2](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_06/sol2.py) - - * [sol3](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_06/sol3.py) - + * [sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/sol1.py) + * [sol2](https://github.com/TheAlgorithms/Python/blob/master/project_euler/sol2.py) + * [sol3](https://github.com/TheAlgorithms/Python/blob/master/project_euler/sol3.py) * Problem 07 - - * [sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_07/sol1.py) - - * [sol2](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_07/sol2.py) - - * [sol3](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_07/sol3.py) - + * [sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/sol1.py) + * [sol2](https://github.com/TheAlgorithms/Python/blob/master/project_euler/sol2.py) + * [sol3](https://github.com/TheAlgorithms/Python/blob/master/project_euler/sol3.py) * Problem 08 - - * [sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_08/sol1.py) - - * [sol2](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_08/sol2.py) - + * [sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/sol1.py) + * [sol2](https://github.com/TheAlgorithms/Python/blob/master/project_euler/sol2.py) * Problem 09 - - * [sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_09/sol1.py) - - * [sol2](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_09/sol2.py) - - * [sol3](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_09/sol3.py) - + * [sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/sol1.py) + * [sol2](https://github.com/TheAlgorithms/Python/blob/master/project_euler/sol2.py) + * [sol3](https://github.com/TheAlgorithms/Python/blob/master/project_euler/sol3.py) * Problem 10 - - * [sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_10/sol1.py) - - * [sol2](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_10/sol2.py) - - * [sol3](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_10/sol3.py) - + * [sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/sol1.py) + * [sol2](https://github.com/TheAlgorithms/Python/blob/master/project_euler/sol2.py) + * [sol3](https://github.com/TheAlgorithms/Python/blob/master/project_euler/sol3.py) * Problem 11 - - * [sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_11/sol1.py) - - * [sol2](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_11/sol2.py) - + * [sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/sol1.py) + * [sol2](https://github.com/TheAlgorithms/Python/blob/master/project_euler/sol2.py) * Problem 12 - - * [sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_12/sol1.py) - - * [sol2](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_12/sol2.py) - + * [sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/sol1.py) + * [sol2](https://github.com/TheAlgorithms/Python/blob/master/project_euler/sol2.py) * Problem 13 - - * [sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_13/sol1.py) - + * [sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/sol1.py) * Problem 14 - - * [sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_14/sol1.py) - - * [sol2](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_14/sol2.py) - + * [sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/sol1.py) + * [sol2](https://github.com/TheAlgorithms/Python/blob/master/project_euler/sol2.py) * Problem 15 - - * [sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_15/sol1.py) - + * [sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/sol1.py) * Problem 16 - - * [sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_16/sol1.py) - - * [sol2](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_16/sol2.py) - + * [sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/sol1.py) + * [sol2](https://github.com/TheAlgorithms/Python/blob/master/project_euler/sol2.py) * Problem 17 - - * [sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_17/sol1.py) - + * [sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/sol1.py) + * Problem 18 + * [sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/sol1.py) * Problem 19 - - * [sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_19/sol1.py) - + * [sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/sol1.py) * Problem 20 - - * [sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_20/sol1.py) - - * [sol2](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_20/sol2.py) - + * [sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/sol1.py) + * [sol2](https://github.com/TheAlgorithms/Python/blob/master/project_euler/sol2.py) * Problem 21 - - * [sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_21/sol1.py) - + * [sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/sol1.py) * Problem 22 - - * [sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_22/sol1.py) - - * [sol2](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_22/sol2.py) - + * [sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/sol1.py) + * [sol2](https://github.com/TheAlgorithms/Python/blob/master/project_euler/sol2.py) * Problem 234 - - * [sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_234/sol1.py) - + * [sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/sol1.py) * Problem 24 - - * [sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_24/sol1.py) - + * [sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/sol1.py) * Problem 25 - - * [sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_25/sol1.py) - - * [sol2](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_25/sol2.py) - + * [sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/sol1.py) + * [sol2](https://github.com/TheAlgorithms/Python/blob/master/project_euler/sol2.py) * Problem 28 - - * [sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_28/sol1.py) - + * [sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/sol1.py) * Problem 29 - - * [solution](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_29/solution.py) - + * [solution](https://github.com/TheAlgorithms/Python/blob/master/project_euler/solution.py) * Problem 31 - - * [sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_31/sol1.py) - + * [sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/sol1.py) * Problem 36 - - * [sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_36/sol1.py) - + * [sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/sol1.py) * Problem 40 - - * [sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_40/sol1.py) - + * [sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/sol1.py) * Problem 48 - - * [sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_48/sol1.py) - + * [sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/sol1.py) * Problem 52 - - * [sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_52/sol1.py) - + * [sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/sol1.py) * Problem 53 - - * [sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_53/sol1.py) - + * [sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/sol1.py) + * Problem 56 + * [sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/sol1.py) * Problem 76 - - * [sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_76/sol1.py) - + * [sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/sol1.py) ## Searches - * [binary search](https://github.com/TheAlgorithms/Python/blob/master/searches/binary_search.py) - * [interpolation search](https://github.com/TheAlgorithms/Python/blob/master/searches/interpolation_search.py) - * [jump search](https://github.com/TheAlgorithms/Python/blob/master/searches/jump_search.py) - * [linear search](https://github.com/TheAlgorithms/Python/blob/master/searches/linear_search.py) - * [quick select](https://github.com/TheAlgorithms/Python/blob/master/searches/quick_select.py) - * [sentinel linear search](https://github.com/TheAlgorithms/Python/blob/master/searches/sentinel_linear_search.py) - * [tabu search](https://github.com/TheAlgorithms/Python/blob/master/searches/tabu_search.py) - * [ternary search](https://github.com/TheAlgorithms/Python/blob/master/searches/ternary_search.py) - ## Sorts - * [bitonic sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/bitonic_sort.py) - * [bogo sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/bogo_sort.py) - * [bubble sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/bubble_sort.py) - * [bucket sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/bucket_sort.py) - * [cocktail shaker sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/cocktail_shaker_sort.py) - * [comb sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/comb_sort.py) - * [counting sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/counting_sort.py) - * [cycle sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/cycle_sort.py) - * [external sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/external_sort.py) - * [gnome sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/gnome_sort.py) - * [heap sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/heap_sort.py) - * [insertion sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/insertion_sort.py) - * [merge sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/merge_sort.py) - * [merge sort fastest](https://github.com/TheAlgorithms/Python/blob/master/sorts/merge_sort_fastest.py) - * [odd even transposition parallel](https://github.com/TheAlgorithms/Python/blob/master/sorts/odd_even_transposition_parallel.py) - * [odd even transposition single threaded](https://github.com/TheAlgorithms/Python/blob/master/sorts/odd_even_transposition_single_threaded.py) - * [pancake sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/pancake_sort.py) - * [pigeon sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/pigeon_sort.py) - * [quick sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/quick_sort.py) - * [quick sort 3 partition](https://github.com/TheAlgorithms/Python/blob/master/sorts/quick_sort_3_partition.py) - * [radix sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/radix_sort.py) - * [random normal distribution quicksort](https://github.com/TheAlgorithms/Python/blob/master/sorts/random_normal_distribution_quicksort.py) - * [random pivot quick sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/random_pivot_quick_sort.py) - * [selection sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/selection_sort.py) - * [shell sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/shell_sort.py) - * [tim sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/tim_sort.py) - * [topological sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/topological_sort.py) - * [tree sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/tree_sort.py) - * [wiggle sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/wiggle_sort.py) - ## Strings - * [boyer moore search](https://github.com/TheAlgorithms/Python/blob/master/strings/boyer_moore_search.py) - * [knuth morris pratt](https://github.com/TheAlgorithms/Python/blob/master/strings/knuth_morris_pratt.py) - * [levenshtein distance](https://github.com/TheAlgorithms/Python/blob/master/strings/levenshtein_distance.py) - * [manacher](https://github.com/TheAlgorithms/Python/blob/master/strings/manacher.py) - * [min cost string conversion](https://github.com/TheAlgorithms/Python/blob/master/strings/min_cost_string_conversion.py) - * [naive string search](https://github.com/TheAlgorithms/Python/blob/master/strings/naive_string_search.py) - * [rabin karp](https://github.com/TheAlgorithms/Python/blob/master/strings/rabin_karp.py) - ## Traversals - * [binary tree traversals](https://github.com/TheAlgorithms/Python/blob/master/traversals/binary_tree_traversals.py) + diff --git a/scripts/build_directory_md.py b/scripts/build_directory_md.py index 2ebd445b3667..b39edca6c933 100755 --- a/scripts/build_directory_md.py +++ b/scripts/build_directory_md.py @@ -14,7 +14,7 @@ def good_filepaths(top_dir: str = ".") -> Iterator[str]: continue if os.path.splitext(filename)[1] in (".py", ".ipynb"): yield os.path.join(dirpath, filename).lstrip("./") - + def md_prefix(i): return f"{i * ' '}*" if i else "##" @@ -25,7 +25,7 @@ def print_path(old_path: str, new_path: str) -> str: for i, new_part in enumerate(new_path.split(os.sep)): if i + 1 > len(old_parts) or old_parts[i] != new_part: if new_part: - print(f"{md_prefix(i)} {new_part.replace('_', ' ').title()}") + print(f"{md_prefix(i-1)} {new_part.replace('_', ' ').title()}") return new_path @@ -36,7 +36,7 @@ def print_directory_md(top_dir: str = ".") -> None: if filepath != old_path: old_path = print_path(old_path, filepath) indent = (filepath.count(os.sep) + 1) if filepath else 0 - url = "/".join((URL_BASE, filepath, filename)).replace(" ", "%20") + url = "/".join((URL_BASE, filepath.split(os.sep)[1], filename)).replace(" ", "%20") filename = os.path.splitext(filename.replace("_", " "))[0] print(f"{md_prefix(indent)} [{filename}]({url})") From a41a14f9d89b665178f08535f128ba14652ce449 Mon Sep 17 00:00:00 2001 From: KirilBangachev <51961981+KirilBangachev@users.noreply.github.com> Date: Fri, 6 Sep 2019 12:06:56 +0300 Subject: [PATCH 0205/1071] Add radix2 FFT (#1166) * Add radix2 FFT Created a dynamic implementation of the radix - 2 Fast Fourier Transform for fast polynomial multiplication. Reference: https://en.wikipedia.org/wiki/Cooley%E2%80%93Tukey_FFT_algorithm#The_radix-2_DIT_case * Rename radix2_FFT.py to radix2_fft.py * Update radix2_fft printing Improved the printing method with f.prefix and String.join() * __str__ method update * Turned the tests into doctests --- maths/radix2_fft.py | 222 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 222 insertions(+) create mode 100644 maths/radix2_fft.py diff --git a/maths/radix2_fft.py b/maths/radix2_fft.py new file mode 100644 index 000000000000..c7ffe96528b4 --- /dev/null +++ b/maths/radix2_fft.py @@ -0,0 +1,222 @@ +""" +Fast Polynomial Multiplication using radix-2 fast Fourier Transform. +""" + +import mpmath # for roots of unity +import numpy as np + + +class FFT: + """ + Fast Polynomial Multiplication using radix-2 fast Fourier Transform. + + Reference: + https://en.wikipedia.org/wiki/Cooley%E2%80%93Tukey_FFT_algorithm#The_radix-2_DIT_case + + For polynomials of degree m and n the algorithms has complexity + O(n*logn + m*logm) + + The main part of the algorithm is split in two parts: + 1) __DFT: We compute the discrete fourier transform (DFT) of A and B using a + bottom-up dynamic approach - + 2) __multiply: Once we obtain the DFT of A*B, we can similarly + invert it to obtain A*B + + The class FFT takes two polynomials A and B with complex coefficients as arguments; + The two polynomials should be represented as a sequence of coefficients starting + from the free term. Thus, for instance x + 2*x^3 could be represented as + [0,1,0,2] or (0,1,0,2). The constructor adds some zeros at the end so that the + polynomials have the same length which is a power of 2 at least the length of + their product. + + Example: + + Create two polynomials as sequences + >>> A = [0, 1, 0, 2] # x+2x^3 + >>> B = (2, 3, 4, 0) # 2+3x+4x^2 + + Create an FFT object with them + >>> x = FFT(A, B) + + Print product + >>> print(x.product) # 2x + 3x^2 + 8x^3 + 4x^4 + 6x^5 + [(-0+0j), (2+0j), (3+0j), (8+0j), (6+0j), (8+0j)] + + __str__ test + >>> print(x) + A = 0*x^0 + 1*x^1 + 2*x^0 + 3*x^2 + B = 0*x^2 + 1*x^3 + 2*x^4 + A*B = 0*x^(-0+0j) + 1*x^(2+0j) + 2*x^(3+0j) + 3*x^(8+0j) + 4*x^(6+0j) + 5*x^(8+0j) + """ + + def __init__(self, polyA=[0], polyB=[0]): + # Input as list + self.polyA = list(polyA)[:] + self.polyB = list(polyB)[:] + + # Remove leading zero coefficients + while self.polyA[-1] == 0: + self.polyA.pop() + self.len_A = len(self.polyA) + + while self.polyB[-1] == 0: + self.polyB.pop() + self.len_B = len(self.polyB) + + # Add 0 to make lengths equal a power of 2 + self.C_max_length = int( + 2 + ** np.ceil( + np.log2( + len(self.polyA) + len(self.polyB) - 1 + ) + ) + ) + + while len(self.polyA) < self.C_max_length: + self.polyA.append(0) + while len(self.polyB) < self.C_max_length: + self.polyB.append(0) + # A complex root used for the fourier transform + self.root = complex( + mpmath.root(x=1, n=self.C_max_length, k=1) + ) + + # The product + self.product = self.__multiply() + + # Discrete fourier transform of A and B + def __DFT(self, which): + if which == "A": + dft = [[x] for x in self.polyA] + else: + dft = [[x] for x in self.polyB] + # Corner case + if len(dft) <= 1: + return dft[0] + # + next_ncol = self.C_max_length // 2 + while next_ncol > 0: + new_dft = [[] for i in range(next_ncol)] + root = self.root ** next_ncol + + # First half of next step + current_root = 1 + for j in range( + self.C_max_length // (next_ncol * 2) + ): + for i in range(next_ncol): + new_dft[i].append( + dft[i][j] + + current_root + * dft[i + next_ncol][j] + ) + current_root *= root + # Second half of next step + current_root = 1 + for j in range( + self.C_max_length // (next_ncol * 2) + ): + for i in range(next_ncol): + new_dft[i].append( + dft[i][j] + - current_root + * dft[i + next_ncol][j] + ) + current_root *= root + # Update + dft = new_dft + next_ncol = next_ncol // 2 + return dft[0] + + # multiply the DFTs of A and B and find A*B + def __multiply(self): + dftA = self.__DFT("A") + dftB = self.__DFT("B") + inverseC = [ + [ + dftA[i] * dftB[i] + for i in range(self.C_max_length) + ] + ] + del dftA + del dftB + + # Corner Case + if len(inverseC[0]) <= 1: + return inverseC[0] + # Inverse DFT + next_ncol = 2 + while next_ncol <= self.C_max_length: + new_inverseC = [[] for i in range(next_ncol)] + root = self.root ** (next_ncol // 2) + current_root = 1 + # First half of next step + for j in range(self.C_max_length // next_ncol): + for i in range(next_ncol // 2): + # Even positions + new_inverseC[i].append( + ( + inverseC[i][j] + + inverseC[i][ + j + + self.C_max_length + // next_ncol + ] + ) + / 2 + ) + # Odd positions + new_inverseC[i + next_ncol // 2].append( + ( + inverseC[i][j] + - inverseC[i][ + j + + self.C_max_length + // next_ncol + ] + ) + / (2 * current_root) + ) + current_root *= root + # Update + inverseC = new_inverseC + next_ncol *= 2 + # Unpack + inverseC = [ + round(x[0].real, 8) + round(x[0].imag, 8) * 1j + for x in inverseC + ] + + # Remove leading 0's + while inverseC[-1] == 0: + inverseC.pop() + return inverseC + + # Overwrite __str__ for print(); Shows A, B and A*B + def __str__(self): + A = "A = " + " + ".join( + f"{coef}*x^{i}" + for coef, i in enumerate( + self.polyA[: self.len_A] + ) + ) + B = "B = " + " + ".join( + f"{coef}*x^{i}" + for coef, i in enumerate( + self.polyB[: self.len_B] + ) + ) + C = "A*B = " + " + ".join( + f"{coef}*x^{i}" + for coef, i in enumerate(self.product) + ) + + return "\n".join((A, B, C)) + + +# Unit tests +if __name__ == "__main__": + import doctest + + doctest.testmod() From 5b483be73b95b9e5a39c122734ca4eff0da593cd Mon Sep 17 00:00:00 2001 From: Jasper <46252815+jasper256@users.noreply.github.com> Date: Sun, 8 Sep 2019 01:07:14 -0400 Subject: [PATCH 0206/1071] Added OOP approach to matrices (#1165) * Added OOP aproach to matrices * created methods for minors, cofactors, and determinants and added corresponding doctests * Added methods for adjugate, inverse, and identity (along with corresponding doctests) to matrix_class.py A small bug persists that causes the doctest to fail. After a couple Matrix objects are printed, the next one is printed in a different format. * formatted matrix_class.py with python/black * implemented negation and exponentiation as well as corresponding doctests in matrix_class.py. Also implemented eq and ne comparison operations * changed __str__ method in matrix_class.py to align with numpy standard and fixed bug in cofactors method * removed property decorators from several methods in matrix_class.py --- matrix/matrix_class.py | 364 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 364 insertions(+) create mode 100644 matrix/matrix_class.py diff --git a/matrix/matrix_class.py b/matrix/matrix_class.py new file mode 100644 index 000000000000..2cd43fc9ca8e --- /dev/null +++ b/matrix/matrix_class.py @@ -0,0 +1,364 @@ +# An OOP aproach to representing and manipulating matrices + + +class Matrix: + """ + Matrix object generated from a 2D array where each element is an array representing a row. + Rows can contain type int or float. + Common operations and information available. + >>> rows = [ + ... [1, 2, 3], + ... [4, 5, 6], + ... [7, 8, 9] + ... ] + >>> matrix = Matrix(rows) + >>> print(matrix) + [[1. 2. 3.] + [4. 5. 6.] + [7. 8. 9.]] + + Matrix rows and columns are available as 2D arrays + >>> print(matrix.rows) + [[1, 2, 3], [4, 5, 6], [7, 8, 9]] + >>> print(matrix.columns()) + [[1, 4, 7], [2, 5, 8], [3, 6, 9]] + + Order is returned as a tuple + >>> matrix.order + (3, 3) + + Squareness and invertability are represented as bool + >>> matrix.is_square + True + >>> matrix.is_invertable() + False + + Identity, Minors, Cofactors and Adjugate are returned as Matrices. Inverse can be a Matrix or Nonetype + >>> print(matrix.identity()) + [[1. 0. 0.] + [0. 1. 0.] + [0. 0. 1.]] + >>> print(matrix.minors()) + [[-3. -6. -3.] + [-6. -12. -6.] + [-3. -6. -3.]] + >>> print(matrix.cofactors()) + [[-3. 6. -3.] + [6. -12. 6.] + [-3. 6. -3.]] + >>> print(matrix.adjugate()) # won't be apparent due to the nature of the cofactor matrix + [[-3. 6. -3.] + [6. -12. 6.] + [-3. 6. -3.]] + >>> print(matrix.inverse()) + None + + Determinant is an int, float, or Nonetype + >>> matrix.determinant() + 0 + + Negation, scalar multiplication, addition, subtraction, multiplication and exponentiation are available and all return a Matrix + >>> print(-matrix) + [[-1. -2. -3.] + [-4. -5. -6.] + [-7. -8. -9.]] + >>> matrix2 = matrix * 3 + >>> print(matrix2) + [[3. 6. 9.] + [12. 15. 18.] + [21. 24. 27.]] + >>> print(matrix + matrix2) + [[4. 8. 12.] + [16. 20. 24.] + [28. 32. 36.]] + >>> print(matrix - matrix2) + [[-2. -4. -6.] + [-8. -10. -12.] + [-14. -16. -18.]] + >>> print(matrix ** 3) + [[468. 576. 684.] + [1062. 1305. 1548.] + [1656. 2034. 2412.]] + + Matrices can also be modified + >>> matrix.add_row([10, 11, 12]) + >>> print(matrix) + [[1. 2. 3.] + [4. 5. 6.] + [7. 8. 9.] + [10. 11. 12.]] + >>> matrix2.add_column([8, 16, 32]) + >>> print(matrix2) + [[3. 6. 9. 8.] + [12. 15. 18. 16.] + [21. 24. 27. 32.]] + >>> print(matrix * matrix2) + [[90. 108. 126. 136.] + [198. 243. 288. 304.] + [306. 378. 450. 472.] + [414. 513. 612. 640.]] + + """ + + def __init__(self, rows): + error = TypeError( + "Matrices must be formed from a list of zero or more lists containing at least one and the same number of values, \ + each of which must be of type int or float" + ) + if len(rows) != 0: + cols = len(rows[0]) + if cols == 0: + raise error + for row in rows: + if not len(row) == cols: + raise error + for value in row: + if not isinstance(value, (int, float)): + raise error + self.rows = rows + else: + self.rows = [] + + # MATRIX INFORMATION + def columns(self): + return [[row[i] for row in self.rows] for i in range(len(self.rows[0]))] + + @property + def num_rows(self): + return len(self.rows) + + @property + def num_columns(self): + return len(self.rows[0]) + + @property + def order(self): + return (self.num_rows, self.num_columns) + + @property + def is_square(self): + if self.order[0] == self.order[1]: + return True + return False + + def identity(self): + values = [ + [0 if column_num != row_num else 1 for column_num in range(self.num_rows)] + for row_num in range(self.num_rows) + ] + return Matrix(values) + + def determinant(self): + if not self.is_square: + return None + if self.order == (0, 0): + return 1 + if self.order == (1, 1): + return self.rows[0][0] + if self.order == (2, 2): + return (self.rows[0][0] * self.rows[1][1]) - ( + self.rows[0][1] * self.rows[1][0] + ) + else: + return sum( + [ + self.rows[0][column] * self.cofactors().rows[0][column] + for column in range(self.num_columns) + ] + ) + + def is_invertable(self): + if self.determinant(): + return True + return False + + def get_minor(self, row, column): + values = [ + [ + self.rows[other_row][other_column] + for other_column in range(self.num_columns) + if other_column != column + ] + for other_row in range(self.num_rows) + if other_row != row + ] + return Matrix(values).determinant() + + def get_cofactor(self, row, column): + if (row + column) % 2 == 0: + return self.get_minor(row, column) + return -1 * self.get_minor(row, column) + + def minors(self): + return Matrix( + [ + [self.get_minor(row, column) for column in range(self.num_columns)] + for row in range(self.num_rows) + ] + ) + + def cofactors(self): + return Matrix( + [ + [ + self.minors().rows[row][column] + if (row + column) % 2 == 0 + else self.minors().rows[row][column] * -1 + for column in range(self.minors().num_columns) + ] + for row in range(self.minors().num_rows) + ] + ) + + def adjugate(self): + values = [ + [self.cofactors().rows[column][row] for column in range(self.num_columns)] + for row in range(self.num_rows) + ] + return Matrix(values) + + def inverse(self): + if not self.is_invertable(): + return None + return self.adjugate() * (1 / self.determinant()) + + def __repr__(self): + return str(self.rows) + + def __str__(self): + if self.num_rows == 0: + return "[]" + if self.num_rows == 1: + return "[[" + ". ".join(self.rows[0]) + "]]" + return ( + "[" + + "\n ".join( + [ + "[" + ". ".join([str(value) for value in row]) + ".]" + for row in self.rows + ] + ) + + "]" + ) + + # MATRIX MANIPULATION + def add_row(self, row, position=None): + type_error = TypeError("Row must be a list containing all ints and/or floats") + if not isinstance(row, list): + raise type_error + for value in row: + if not isinstance(value, (int, float)): + raise type_error + if len(row) != self.num_columns: + raise ValueError( + "Row must be equal in length to the other rows in the matrix" + ) + if position is None: + self.rows.append(row) + else: + self.rows = self.rows[0:position] + [row] + self.rows[position:] + + def add_column(self, column, position=None): + type_error = TypeError( + "Column must be a list containing all ints and/or floats" + ) + if not isinstance(column, list): + raise type_error + for value in column: + if not isinstance(value, (int, float)): + raise type_error + if len(column) != self.num_rows: + raise ValueError( + "Column must be equal in length to the other columns in the matrix" + ) + if position is None: + self.rows = [self.rows[i] + [column[i]] for i in range(self.num_rows)] + else: + self.rows = [ + self.rows[i][0:position] + [column[i]] + self.rows[i][position:] + for i in range(self.num_rows) + ] + + # MATRIX OPERATIONS + def __eq__(self, other): + if not isinstance(other, Matrix): + raise TypeError("A Matrix can only be compared with another Matrix") + if self.rows == other.rows: + return True + return False + + def __ne__(self, other): + if self == other: + return False + return True + + def __neg__(self): + return self * -1 + + def __add__(self, other): + if self.order != other.order: + raise ValueError("Addition requires matrices of the same order") + return Matrix( + [ + [self.rows[i][j] + other.rows[i][j] for j in range(self.num_columns)] + for i in range(self.num_rows) + ] + ) + + def __sub__(self, other): + if self.order != other.order: + raise ValueError("Subtraction requires matrices of the same order") + return Matrix( + [ + [self.rows[i][j] - other.rows[i][j] for j in range(self.num_columns)] + for i in range(self.num_rows) + ] + ) + + def __mul__(self, other): + if not isinstance(other, (int, float, Matrix)): + raise TypeError( + "A Matrix can only be multiplied by an int, float, or another matrix" + ) + if type(other) in (int, float): + return Matrix([[element * other for element in row] for row in self.rows]) + if type(other) is Matrix: + if self.num_columns != other.num_rows: + raise ValueError( + "The number of columns in the first matrix must be equal to the number of rows in the second" + ) + return Matrix( + [ + [Matrix.dot_product(row, column) for column in other.columns()] + for row in self.rows + ] + ) + + def __pow__(self, other): + if not isinstance(other, int): + raise TypeError("A Matrix can only be raised to the power of an int") + if not self.is_square: + raise ValueError("Only square matrices can be raised to a power") + if other == 0: + return self.identity() + if other < 0: + if self.is_invertable: + return self.inverse() ** (-other) + raise ValueError( + "Only invertable matrices can be raised to a negative power" + ) + result = self + for i in range(other - 1): + result *= self + return result + + @classmethod + def dot_product(cls, row, column): + return sum([row[i] * column[i] for i in range(len(row))]) + + +if __name__ == "__main__": + import doctest + + test = doctest.testmod() + print(test) From 3c3f92db530e3ae0e594cd1d5c4e034cc23defc9 Mon Sep 17 00:00:00 2001 From: Simon Landry Date: Sun, 8 Sep 2019 04:40:07 -0400 Subject: [PATCH 0207/1071] Add problem 67 solution (#1170) --- project_euler/problem_67/__init__.py | 0 project_euler/problem_67/sol1.py | 49 +++++++++++++ project_euler/problem_67/triangle.txt | 100 ++++++++++++++++++++++++++ 3 files changed, 149 insertions(+) create mode 100644 project_euler/problem_67/__init__.py create mode 100644 project_euler/problem_67/sol1.py create mode 100644 project_euler/problem_67/triangle.txt diff --git a/project_euler/problem_67/__init__.py b/project_euler/problem_67/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/project_euler/problem_67/sol1.py b/project_euler/problem_67/sol1.py new file mode 100644 index 000000000000..2da757e303aa --- /dev/null +++ b/project_euler/problem_67/sol1.py @@ -0,0 +1,49 @@ +""" +Problem Statement: +By starting at the top of the triangle below and moving to adjacent numbers on +the row below, the maximum total from top to bottom is 23. +3 +7 4 +2 4 6 +8 5 9 3 +That is, 3 + 7 + 4 + 9 = 23. +Find the maximum total from top to bottom in triangle.txt (right click and +'Save Link/Target As...'), a 15K text file containing a triangle with +one-hundred rows. +""" +import os + + +def solution(): + """ + Finds the maximum total in a triangle as described by the problem statement + above. + + >>> solution() + 7273 + """ + script_dir = os.path.dirname(os.path.realpath(__file__)) + triangle = os.path.join(script_dir, 'triangle.txt') + + with open(triangle, 'r') as f: + triangle = f.readlines() + + a = map(lambda x: x.rstrip('\r\n').split(' '), triangle) + a = list(map(lambda x: list(map(lambda y: int(y), x)), a)) + + for i in range(1, len(a)): + for j in range(len(a[i])): + if j != len(a[i - 1]): + number1 = a[i - 1][j] + else: + number1 = 0 + if j > 0: + number2 = a[i - 1][j - 1] + else: + number2 = 0 + a[i][j] += max(number1, number2) + return max(a[-1]) + + +if __name__ == "__main__": + print(solution()) diff --git a/project_euler/problem_67/triangle.txt b/project_euler/problem_67/triangle.txt new file mode 100644 index 000000000000..00aa2bc6382d --- /dev/null +++ b/project_euler/problem_67/triangle.txt @@ -0,0 +1,100 @@ +59 +73 41 +52 40 09 +26 53 06 34 +10 51 87 86 81 +61 95 66 57 25 68 +90 81 80 38 92 67 73 +30 28 51 76 81 18 75 44 +84 14 95 87 62 81 17 78 58 +21 46 71 58 02 79 62 39 31 09 +56 34 35 53 78 31 81 18 90 93 15 +78 53 04 21 84 93 32 13 97 11 37 51 +45 03 81 79 05 18 78 86 13 30 63 99 95 +39 87 96 28 03 38 42 17 82 87 58 07 22 57 +06 17 51 17 07 93 09 07 75 97 95 78 87 08 53 +67 66 59 60 88 99 94 65 55 77 55 34 27 53 78 28 +76 40 41 04 87 16 09 42 75 69 23 97 30 60 10 79 87 +12 10 44 26 21 36 32 84 98 60 13 12 36 16 63 31 91 35 +70 39 06 05 55 27 38 48 28 22 34 35 62 62 15 14 94 89 86 +66 56 68 84 96 21 34 34 34 81 62 40 65 54 62 05 98 03 02 60 +38 89 46 37 99 54 34 53 36 14 70 26 02 90 45 13 31 61 83 73 47 +36 10 63 96 60 49 41 05 37 42 14 58 84 93 96 17 09 43 05 43 06 59 +66 57 87 57 61 28 37 51 84 73 79 15 39 95 88 87 43 39 11 86 77 74 18 +54 42 05 79 30 49 99 73 46 37 50 02 45 09 54 52 27 95 27 65 19 45 26 45 +71 39 17 78 76 29 52 90 18 99 78 19 35 62 71 19 23 65 93 85 49 33 75 09 02 +33 24 47 61 60 55 32 88 57 55 91 54 46 57 07 77 98 52 80 99 24 25 46 78 79 05 +92 09 13 55 10 67 26 78 76 82 63 49 51 31 24 68 05 57 07 54 69 21 67 43 17 63 12 +24 59 06 08 98 74 66 26 61 60 13 03 09 09 24 30 71 08 88 70 72 70 29 90 11 82 41 34 +66 82 67 04 36 60 92 77 91 85 62 49 59 61 30 90 29 94 26 41 89 04 53 22 83 41 09 74 90 +48 28 26 37 28 52 77 26 51 32 18 98 79 36 62 13 17 08 19 54 89 29 73 68 42 14 08 16 70 37 +37 60 69 70 72 71 09 59 13 60 38 13 57 36 09 30 43 89 30 39 15 02 44 73 05 73 26 63 56 86 12 +55 55 85 50 62 99 84 77 28 85 03 21 27 22 19 26 82 69 54 04 13 07 85 14 01 15 70 59 89 95 10 19 +04 09 31 92 91 38 92 86 98 75 21 05 64 42 62 84 36 20 73 42 21 23 22 51 51 79 25 45 85 53 03 43 22 +75 63 02 49 14 12 89 14 60 78 92 16 44 82 38 30 72 11 46 52 90 27 08 65 78 03 85 41 57 79 39 52 33 48 +78 27 56 56 39 13 19 43 86 72 58 95 39 07 04 34 21 98 39 15 39 84 89 69 84 46 37 57 59 35 59 50 26 15 93 +42 89 36 27 78 91 24 11 17 41 05 94 07 69 51 96 03 96 47 90 90 45 91 20 50 56 10 32 36 49 04 53 85 92 25 65 +52 09 61 30 61 97 66 21 96 92 98 90 06 34 96 60 32 69 68 33 75 84 18 31 71 50 84 63 03 03 19 11 28 42 75 45 45 +61 31 61 68 96 34 49 39 05 71 76 59 62 67 06 47 96 99 34 21 32 47 52 07 71 60 42 72 94 56 82 83 84 40 94 87 82 46 +01 20 60 14 17 38 26 78 66 81 45 95 18 51 98 81 48 16 53 88 37 52 69 95 72 93 22 34 98 20 54 27 73 61 56 63 60 34 63 +93 42 94 83 47 61 27 51 79 79 45 01 44 73 31 70 83 42 88 25 53 51 30 15 65 94 80 44 61 84 12 77 02 62 02 65 94 42 14 94 +32 73 09 67 68 29 74 98 10 19 85 48 38 31 85 67 53 93 93 77 47 67 39 72 94 53 18 43 77 40 78 32 29 59 24 06 02 83 50 60 66 +32 01 44 30 16 51 15 81 98 15 10 62 86 79 50 62 45 60 70 38 31 85 65 61 64 06 69 84 14 22 56 43 09 48 66 69 83 91 60 40 36 61 +92 48 22 99 15 95 64 43 01 16 94 02 99 19 17 69 11 58 97 56 89 31 77 45 67 96 12 73 08 20 36 47 81 44 50 64 68 85 40 81 85 52 09 +91 35 92 45 32 84 62 15 19 64 21 66 06 01 52 80 62 59 12 25 88 28 91 50 40 16 22 99 92 79 87 51 21 77 74 77 07 42 38 42 74 83 02 05 +46 19 77 66 24 18 05 32 02 84 31 99 92 58 96 72 91 36 62 99 55 29 53 42 12 37 26 58 89 50 66 19 82 75 12 48 24 87 91 85 02 07 03 76 86 +99 98 84 93 07 17 33 61 92 20 66 60 24 66 40 30 67 05 37 29 24 96 03 27 70 62 13 04 45 47 59 88 43 20 66 15 46 92 30 04 71 66 78 70 53 99 +67 60 38 06 88 04 17 72 10 99 71 07 42 25 54 05 26 64 91 50 45 71 06 30 67 48 69 82 08 56 80 67 18 46 66 63 01 20 08 80 47 07 91 16 03 79 87 +18 54 78 49 80 48 77 40 68 23 60 88 58 80 33 57 11 69 55 53 64 02 94 49 60 92 16 35 81 21 82 96 25 24 96 18 02 05 49 03 50 77 06 32 84 27 18 38 +68 01 50 04 03 21 42 94 53 24 89 05 92 26 52 36 68 11 85 01 04 42 02 45 15 06 50 04 53 73 25 74 81 88 98 21 67 84 79 97 99 20 95 04 40 46 02 58 87 +94 10 02 78 88 52 21 03 88 60 06 53 49 71 20 91 12 65 07 49 21 22 11 41 58 99 36 16 09 48 17 24 52 36 23 15 72 16 84 56 02 99 43 76 81 71 29 39 49 17 +64 39 59 84 86 16 17 66 03 09 43 06 64 18 63 29 68 06 23 07 87 14 26 35 17 12 98 41 53 64 78 18 98 27 28 84 80 67 75 62 10 11 76 90 54 10 05 54 41 39 66 +43 83 18 37 32 31 52 29 95 47 08 76 35 11 04 53 35 43 34 10 52 57 12 36 20 39 40 55 78 44 07 31 38 26 08 15 56 88 86 01 52 62 10 24 32 05 60 65 53 28 57 99 +03 50 03 52 07 73 49 92 66 80 01 46 08 67 25 36 73 93 07 42 25 53 13 96 76 83 87 90 54 89 78 22 78 91 73 51 69 09 79 94 83 53 09 40 69 62 10 79 49 47 03 81 30 +71 54 73 33 51 76 59 54 79 37 56 45 84 17 62 21 98 69 41 95 65 24 39 37 62 03 24 48 54 64 46 82 71 78 33 67 09 16 96 68 52 74 79 68 32 21 13 78 96 60 09 69 20 36 +73 26 21 44 46 38 17 83 65 98 07 23 52 46 61 97 33 13 60 31 70 15 36 77 31 58 56 93 75 68 21 36 69 53 90 75 25 82 39 50 65 94 29 30 11 33 11 13 96 02 56 47 07 49 02 +76 46 73 30 10 20 60 70 14 56 34 26 37 39 48 24 55 76 84 91 39 86 95 61 50 14 53 93 64 67 37 31 10 84 42 70 48 20 10 72 60 61 84 79 69 65 99 73 89 25 85 48 92 56 97 16 +03 14 80 27 22 30 44 27 67 75 79 32 51 54 81 29 65 14 19 04 13 82 04 91 43 40 12 52 29 99 07 76 60 25 01 07 61 71 37 92 40 47 99 66 57 01 43 44 22 40 53 53 09 69 26 81 07 +49 80 56 90 93 87 47 13 75 28 87 23 72 79 32 18 27 20 28 10 37 59 21 18 70 04 79 96 03 31 45 71 81 06 14 18 17 05 31 50 92 79 23 47 09 39 47 91 43 54 69 47 42 95 62 46 32 85 +37 18 62 85 87 28 64 05 77 51 47 26 30 65 05 70 65 75 59 80 42 52 25 20 44 10 92 17 71 95 52 14 77 13 24 55 11 65 26 91 01 30 63 15 49 48 41 17 67 47 03 68 20 90 98 32 04 40 68 +90 51 58 60 06 55 23 68 05 19 76 94 82 36 96 43 38 90 87 28 33 83 05 17 70 83 96 93 06 04 78 47 80 06 23 84 75 23 87 72 99 14 50 98 92 38 90 64 61 58 76 94 36 66 87 80 51 35 61 38 +57 95 64 06 53 36 82 51 40 33 47 14 07 98 78 65 39 58 53 06 50 53 04 69 40 68 36 69 75 78 75 60 03 32 39 24 74 47 26 90 13 40 44 71 90 76 51 24 36 50 25 45 70 80 61 80 61 43 90 64 11 +18 29 86 56 68 42 79 10 42 44 30 12 96 18 23 18 52 59 02 99 67 46 60 86 43 38 55 17 44 93 42 21 55 14 47 34 55 16 49 24 23 29 96 51 55 10 46 53 27 92 27 46 63 57 30 65 43 27 21 20 24 83 +81 72 93 19 69 52 48 01 13 83 92 69 20 48 69 59 20 62 05 42 28 89 90 99 32 72 84 17 08 87 36 03 60 31 36 36 81 26 97 36 48 54 56 56 27 16 91 08 23 11 87 99 33 47 02 14 44 73 70 99 43 35 33 +90 56 61 86 56 12 70 59 63 32 01 15 81 47 71 76 95 32 65 80 54 70 34 51 40 45 33 04 64 55 78 68 88 47 31 47 68 87 03 84 23 44 89 72 35 08 31 76 63 26 90 85 96 67 65 91 19 14 17 86 04 71 32 95 +37 13 04 22 64 37 37 28 56 62 86 33 07 37 10 44 52 82 52 06 19 52 57 75 90 26 91 24 06 21 14 67 76 30 46 14 35 89 89 41 03 64 56 97 87 63 22 34 03 79 17 45 11 53 25 56 96 61 23 18 63 31 37 37 47 +77 23 26 70 72 76 77 04 28 64 71 69 14 85 96 54 95 48 06 62 99 83 86 77 97 75 71 66 30 19 57 90 33 01 60 61 14 12 90 99 32 77 56 41 18 14 87 49 10 14 90 64 18 50 21 74 14 16 88 05 45 73 82 47 74 44 +22 97 41 13 34 31 54 61 56 94 03 24 59 27 98 77 04 09 37 40 12 26 87 09 71 70 07 18 64 57 80 21 12 71 83 94 60 39 73 79 73 19 97 32 64 29 41 07 48 84 85 67 12 74 95 20 24 52 41 67 56 61 29 93 35 72 69 +72 23 63 66 01 11 07 30 52 56 95 16 65 26 83 90 50 74 60 18 16 48 43 77 37 11 99 98 30 94 91 26 62 73 45 12 87 73 47 27 01 88 66 99 21 41 95 80 02 53 23 32 61 48 32 43 43 83 14 66 95 91 19 81 80 67 25 88 +08 62 32 18 92 14 83 71 37 96 11 83 39 99 05 16 23 27 10 67 02 25 44 11 55 31 46 64 41 56 44 74 26 81 51 31 45 85 87 09 81 95 22 28 76 69 46 48 64 87 67 76 27 89 31 11 74 16 62 03 60 94 42 47 09 34 94 93 72 +56 18 90 18 42 17 42 32 14 86 06 53 33 95 99 35 29 15 44 20 49 59 25 54 34 59 84 21 23 54 35 90 78 16 93 13 37 88 54 19 86 67 68 55 66 84 65 42 98 37 87 56 33 28 58 38 28 38 66 27 52 21 81 15 08 22 97 32 85 27 +91 53 40 28 13 34 91 25 01 63 50 37 22 49 71 58 32 28 30 18 68 94 23 83 63 62 94 76 80 41 90 22 82 52 29 12 18 56 10 08 35 14 37 57 23 65 67 40 72 39 93 39 70 89 40 34 07 46 94 22 20 05 53 64 56 30 05 56 61 88 27 +23 95 11 12 37 69 68 24 66 10 87 70 43 50 75 07 62 41 83 58 95 93 89 79 45 39 02 22 05 22 95 43 62 11 68 29 17 40 26 44 25 71 87 16 70 85 19 25 59 94 90 41 41 80 61 70 55 60 84 33 95 76 42 63 15 09 03 40 38 12 03 32 +09 84 56 80 61 55 85 97 16 94 82 94 98 57 84 30 84 48 93 90 71 05 95 90 73 17 30 98 40 64 65 89 07 79 09 19 56 36 42 30 23 69 73 72 07 05 27 61 24 31 43 48 71 84 21 28 26 65 65 59 65 74 77 20 10 81 61 84 95 08 52 23 70 +47 81 28 09 98 51 67 64 35 51 59 36 92 82 77 65 80 24 72 53 22 07 27 10 21 28 30 22 48 82 80 48 56 20 14 43 18 25 50 95 90 31 77 08 09 48 44 80 90 22 93 45 82 17 13 96 25 26 08 73 34 99 06 49 24 06 83 51 40 14 15 10 25 01 +54 25 10 81 30 64 24 74 75 80 36 75 82 60 22 69 72 91 45 67 03 62 79 54 89 74 44 83 64 96 66 73 44 30 74 50 37 05 09 97 70 01 60 46 37 91 39 75 75 18 58 52 72 78 51 81 86 52 08 97 01 46 43 66 98 62 81 18 70 93 73 08 32 46 34 +96 80 82 07 59 71 92 53 19 20 88 66 03 26 26 10 24 27 50 82 94 73 63 08 51 33 22 45 19 13 58 33 90 15 22 50 36 13 55 06 35 47 82 52 33 61 36 27 28 46 98 14 73 20 73 32 16 26 80 53 47 66 76 38 94 45 02 01 22 52 47 96 64 58 52 39 +88 46 23 39 74 63 81 64 20 90 33 33 76 55 58 26 10 46 42 26 74 74 12 83 32 43 09 02 73 55 86 54 85 34 28 23 29 79 91 62 47 41 82 87 99 22 48 90 20 05 96 75 95 04 43 28 81 39 81 01 28 42 78 25 39 77 90 57 58 98 17 36 73 22 63 74 51 +29 39 74 94 95 78 64 24 38 86 63 87 93 06 70 92 22 16 80 64 29 52 20 27 23 50 14 13 87 15 72 96 81 22 08 49 72 30 70 24 79 31 16 64 59 21 89 34 96 91 48 76 43 53 88 01 57 80 23 81 90 79 58 01 80 87 17 99 86 90 72 63 32 69 14 28 88 69 +37 17 71 95 56 93 71 35 43 45 04 98 92 94 84 96 11 30 31 27 31 60 92 03 48 05 98 91 86 94 35 90 90 08 48 19 33 28 68 37 59 26 65 96 50 68 22 07 09 49 34 31 77 49 43 06 75 17 81 87 61 79 52 26 27 72 29 50 07 98 86 01 17 10 46 64 24 18 56 +51 30 25 94 88 85 79 91 40 33 63 84 49 67 98 92 15 26 75 19 82 05 18 78 65 93 61 48 91 43 59 41 70 51 22 15 92 81 67 91 46 98 11 11 65 31 66 10 98 65 83 21 05 56 05 98 73 67 46 74 69 34 08 30 05 52 07 98 32 95 30 94 65 50 24 63 28 81 99 57 +19 23 61 36 09 89 71 98 65 17 30 29 89 26 79 74 94 11 44 48 97 54 81 55 39 66 69 45 28 47 13 86 15 76 74 70 84 32 36 33 79 20 78 14 41 47 89 28 81 05 99 66 81 86 38 26 06 25 13 60 54 55 23 53 27 05 89 25 23 11 13 54 59 54 56 34 16 24 53 44 06 +13 40 57 72 21 15 60 08 04 19 11 98 34 45 09 97 86 71 03 15 56 19 15 44 97 31 90 04 87 87 76 08 12 30 24 62 84 28 12 85 82 53 99 52 13 94 06 65 97 86 09 50 94 68 69 74 30 67 87 94 63 07 78 27 80 36 69 41 06 92 32 78 37 82 30 05 18 87 99 72 19 99 +44 20 55 77 69 91 27 31 28 81 80 27 02 07 97 23 95 98 12 25 75 29 47 71 07 47 78 39 41 59 27 76 13 15 66 61 68 35 69 86 16 53 67 63 99 85 41 56 08 28 33 40 94 76 90 85 31 70 24 65 84 65 99 82 19 25 54 37 21 46 33 02 52 99 51 33 26 04 87 02 08 18 96 +54 42 61 45 91 06 64 79 80 82 32 16 83 63 42 49 19 78 65 97 40 42 14 61 49 34 04 18 25 98 59 30 82 72 26 88 54 36 21 75 03 88 99 53 46 51 55 78 22 94 34 40 68 87 84 25 30 76 25 08 92 84 42 61 40 38 09 99 40 23 29 39 46 55 10 90 35 84 56 70 63 23 91 39 +52 92 03 71 89 07 09 37 68 66 58 20 44 92 51 56 13 71 79 99 26 37 02 06 16 67 36 52 58 16 79 73 56 60 59 27 44 77 94 82 20 50 98 33 09 87 94 37 40 83 64 83 58 85 17 76 53 02 83 52 22 27 39 20 48 92 45 21 09 42 24 23 12 37 52 28 50 78 79 20 86 62 73 20 59 +54 96 80 15 91 90 99 70 10 09 58 90 93 50 81 99 54 38 36 10 30 11 35 84 16 45 82 18 11 97 36 43 96 79 97 65 40 48 23 19 17 31 64 52 65 65 37 32 65 76 99 79 34 65 79 27 55 33 03 01 33 27 61 28 66 08 04 70 49 46 48 83 01 45 19 96 13 81 14 21 31 79 93 85 50 05 +92 92 48 84 59 98 31 53 23 27 15 22 79 95 24 76 05 79 16 93 97 89 38 89 42 83 02 88 94 95 82 21 01 97 48 39 31 78 09 65 50 56 97 61 01 07 65 27 21 23 14 15 80 97 44 78 49 35 33 45 81 74 34 05 31 57 09 38 94 07 69 54 69 32 65 68 46 68 78 90 24 28 49 51 45 86 35 +41 63 89 76 87 31 86 09 46 14 87 82 22 29 47 16 13 10 70 72 82 95 48 64 58 43 13 75 42 69 21 12 67 13 64 85 58 23 98 09 37 76 05 22 31 12 66 50 29 99 86 72 45 25 10 28 19 06 90 43 29 31 67 79 46 25 74 14 97 35 76 37 65 46 23 82 06 22 30 76 93 66 94 17 96 13 20 72 +63 40 78 08 52 09 90 41 70 28 36 14 46 44 85 96 24 52 58 15 87 37 05 98 99 39 13 61 76 38 44 99 83 74 90 22 53 80 56 98 30 51 63 39 44 30 91 91 04 22 27 73 17 35 53 18 35 45 54 56 27 78 48 13 69 36 44 38 71 25 30 56 15 22 73 43 32 69 59 25 93 83 45 11 34 94 44 39 92 +12 36 56 88 13 96 16 12 55 54 11 47 19 78 17 17 68 81 77 51 42 55 99 85 66 27 81 79 93 42 65 61 69 74 14 01 18 56 12 01 58 37 91 22 42 66 83 25 19 04 96 41 25 45 18 69 96 88 36 93 10 12 98 32 44 83 83 04 72 91 04 27 73 07 34 37 71 60 59 31 01 54 54 44 96 93 83 36 04 45 +30 18 22 20 42 96 65 79 17 41 55 69 94 81 29 80 91 31 85 25 47 26 43 49 02 99 34 67 99 76 16 14 15 93 08 32 99 44 61 77 67 50 43 55 87 55 53 72 17 46 62 25 50 99 73 05 93 48 17 31 70 80 59 09 44 59 45 13 74 66 58 94 87 73 16 14 85 38 74 99 64 23 79 28 71 42 20 37 82 31 23 +51 96 39 65 46 71 56 13 29 68 53 86 45 33 51 49 12 91 21 21 76 85 02 17 98 15 46 12 60 21 88 30 92 83 44 59 42 50 27 88 46 86 94 73 45 54 23 24 14 10 94 21 20 34 23 51 04 83 99 75 90 63 60 16 22 33 83 70 11 32 10 50 29 30 83 46 11 05 31 17 86 42 49 01 44 63 28 60 07 78 95 40 +44 61 89 59 04 49 51 27 69 71 46 76 44 04 09 34 56 39 15 06 94 91 75 90 65 27 56 23 74 06 23 33 36 69 14 39 05 34 35 57 33 22 76 46 56 10 61 65 98 09 16 69 04 62 65 18 99 76 49 18 72 66 73 83 82 40 76 31 89 91 27 88 17 35 41 35 32 51 32 67 52 68 74 85 80 57 07 11 62 66 47 22 67 +65 37 19 97 26 17 16 24 24 17 50 37 64 82 24 36 32 11 68 34 69 31 32 89 79 93 96 68 49 90 14 23 04 04 67 99 81 74 70 74 36 96 68 09 64 39 88 35 54 89 96 58 66 27 88 97 32 14 06 35 78 20 71 06 85 66 57 02 58 91 72 05 29 56 73 48 86 52 09 93 22 57 79 42 12 01 31 68 17 59 63 76 07 77 +73 81 14 13 17 20 11 09 01 83 08 85 91 70 84 63 62 77 37 07 47 01 59 95 39 69 39 21 99 09 87 02 97 16 92 36 74 71 90 66 33 73 73 75 52 91 11 12 26 53 05 26 26 48 61 50 90 65 01 87 42 47 74 35 22 73 24 26 56 70 52 05 48 41 31 18 83 27 21 39 80 85 26 08 44 02 71 07 63 22 05 52 19 08 20 +17 25 21 11 72 93 33 49 64 23 53 82 03 13 91 65 85 02 40 05 42 31 77 42 05 36 06 54 04 58 07 76 87 83 25 57 66 12 74 33 85 37 74 32 20 69 03 97 91 68 82 44 19 14 89 28 85 85 80 53 34 87 58 98 88 78 48 65 98 40 11 57 10 67 70 81 60 79 74 72 97 59 79 47 30 20 54 80 89 91 14 05 33 36 79 39 +60 85 59 39 60 07 57 76 77 92 06 35 15 72 23 41 45 52 95 18 64 79 86 53 56 31 69 11 91 31 84 50 44 82 22 81 41 40 30 42 30 91 48 94 74 76 64 58 74 25 96 57 14 19 03 99 28 83 15 75 99 01 89 85 79 50 03 95 32 67 44 08 07 41 62 64 29 20 14 76 26 55 48 71 69 66 19 72 44 25 14 01 48 74 12 98 07 +64 66 84 24 18 16 27 48 20 14 47 69 30 86 48 40 23 16 61 21 51 50 26 47 35 33 91 28 78 64 43 68 04 79 51 08 19 60 52 95 06 68 46 86 35 97 27 58 04 65 30 58 99 12 12 75 91 39 50 31 42 64 70 04 46 07 98 73 98 93 37 89 77 91 64 71 64 65 66 21 78 62 81 74 42 20 83 70 73 95 78 45 92 27 34 53 71 15 +30 11 85 31 34 71 13 48 05 14 44 03 19 67 23 73 19 57 06 90 94 72 57 69 81 62 59 68 88 57 55 69 49 13 07 87 97 80 89 05 71 05 05 26 38 40 16 62 45 99 18 38 98 24 21 26 62 74 69 04 85 57 77 35 58 67 91 79 79 57 86 28 66 34 72 51 76 78 36 95 63 90 08 78 47 63 45 31 22 70 52 48 79 94 15 77 61 67 68 +23 33 44 81 80 92 93 75 94 88 23 61 39 76 22 03 28 94 32 06 49 65 41 34 18 23 08 47 62 60 03 63 33 13 80 52 31 54 73 43 70 26 16 69 57 87 83 31 03 93 70 81 47 95 77 44 29 68 39 51 56 59 63 07 25 70 07 77 43 53 64 03 94 42 95 39 18 01 66 21 16 97 20 50 90 16 70 10 95 69 29 06 25 61 41 26 15 59 63 35 From 030600f9b3c4f9265fcaed97ae81b41ad2d7f7f4 Mon Sep 17 00:00:00 2001 From: Christian Clauss Date: Tue, 10 Sep 2019 07:49:07 +0200 Subject: [PATCH 0208/1071] Update matrix_class.py (#1175) --- matrix/matrix_class.py | 45 +++++++++++++++--------------------------- 1 file changed, 16 insertions(+), 29 deletions(-) diff --git a/matrix/matrix_class.py b/matrix/matrix_class.py index 2cd43fc9ca8e..c82fb2cf6464 100644 --- a/matrix/matrix_class.py +++ b/matrix/matrix_class.py @@ -102,15 +102,15 @@ class Matrix: def __init__(self, rows): error = TypeError( - "Matrices must be formed from a list of zero or more lists containing at least one and the same number of values, \ - each of which must be of type int or float" + "Matrices must be formed from a list of zero or more lists containing at least " + "one and the same number of values, each of which must be of type int or float." ) if len(rows) != 0: cols = len(rows[0]) if cols == 0: raise error for row in rows: - if not len(row) == cols: + if len(row) != cols: raise error for value in row: if not isinstance(value, (int, float)): @@ -137,9 +137,7 @@ def order(self): @property def is_square(self): - if self.order[0] == self.order[1]: - return True - return False + return self.order[0] == self.order[1] def identity(self): values = [ @@ -168,9 +166,7 @@ def determinant(self): ) def is_invertable(self): - if self.determinant(): - return True - return False + return bool(self.determinant()) def get_minor(self, row, column): values = [ @@ -218,9 +214,8 @@ def adjugate(self): return Matrix(values) def inverse(self): - if not self.is_invertable(): - return None - return self.adjugate() * (1 / self.determinant()) + determinant = self.determinant() + return None if not determinant else self.adjugate() * (1 / determinant) def __repr__(self): return str(self.rows) @@ -283,14 +278,10 @@ def add_column(self, column, position=None): def __eq__(self, other): if not isinstance(other, Matrix): raise TypeError("A Matrix can only be compared with another Matrix") - if self.rows == other.rows: - return True - return False + return self.rows == other.rows def __ne__(self, other): - if self == other: - return False - return True + return not self == other def __neg__(self): return self * -1 @@ -316,23 +307,20 @@ def __sub__(self, other): ) def __mul__(self, other): - if not isinstance(other, (int, float, Matrix)): - raise TypeError( - "A Matrix can only be multiplied by an int, float, or another matrix" - ) - if type(other) in (int, float): + if isinstance(other, (int, float)): return Matrix([[element * other for element in row] for row in self.rows]) - if type(other) is Matrix: + elif isinstance(other, Matrix): if self.num_columns != other.num_rows: - raise ValueError( - "The number of columns in the first matrix must be equal to the number of rows in the second" - ) + raise ValueError("The number of columns in the first matrix must " + "be equal to the number of rows in the second") return Matrix( [ [Matrix.dot_product(row, column) for column in other.columns()] for row in self.rows ] ) + else: + raise TypeError("A Matrix can only be multiplied by an int, float, or another matrix") def __pow__(self, other): if not isinstance(other, int): @@ -360,5 +348,4 @@ def dot_product(cls, row, column): if __name__ == "__main__": import doctest - test = doctest.testmod() - print(test) + doctest.testmod() From 47d17951b830f23fe7cddffa9c24431c29cfea74 Mon Sep 17 00:00:00 2001 From: Kiril Bangachev <51961981+KirilBangachev@users.noreply.github.com> Date: Fri, 13 Sep 2019 07:13:55 -0400 Subject: [PATCH 0209/1071] Add Kth lexicographic permutation (#1179) * Add Kth lexicographic permutation Function that computes the kth lexicographic permtation of 0,1,2,...,n-1 in O(n^2) time * Update kth_lexicographic_permutation.py Addressed requested changes --- maths/kth_lexicographic_permutation.py | 40 ++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 maths/kth_lexicographic_permutation.py diff --git a/maths/kth_lexicographic_permutation.py b/maths/kth_lexicographic_permutation.py new file mode 100644 index 000000000000..1820be7274e3 --- /dev/null +++ b/maths/kth_lexicographic_permutation.py @@ -0,0 +1,40 @@ +def kthPermutation(k, n): + """ + Finds k'th lexicographic permutation (in increasing order) of + 0,1,2,...n-1 in O(n^2) time. + + Examples: + First permutation is always 0,1,2,...n + >>> kthPermutation(0,5) + [0, 1, 2, 3, 4] + + The order of permutation of 0,1,2,3 is [0,1,2,3], [0,1,3,2], [0,2,1,3], + [0,2,3,1], [0,3,1,2], [0,3,2,1], [1,0,2,3], [1,0,3,2], [1,2,0,3], + [1,2,3,0], [1,3,0,2] + >>> kthPermutation(10,4) + [1, 3, 0, 2] + """ + # Factorails from 1! to (n-1)! + factorials = [1] + for i in range(2, n): + factorials.append(factorials[-1] * i) + assert 0 <= k < factorials[-1] * n, "k out of bounds" + + permutation = [] + elements = list(range(n)) + + # Find permutation + while factorials: + factorial = factorials.pop() + number, k = divmod(k, factorial) + permutation.append(elements[number]) + elements.remove(elements[number]) + permutation.append(elements[0]) + + return permutation + + +if __name__ == "__main__": + import doctest + + doctest.testmod() From f8e30cfab1863b4f76415956ee1f10d606c50674 Mon Sep 17 00:00:00 2001 From: "Marvin M. Michum" Date: Fri, 13 Sep 2019 07:40:14 -0400 Subject: [PATCH 0210/1071] Digital Image Processing Tests (#1178) * add version of smaller image * swap image in tests * edits for image src --- .../image_data/lena_small.jpg | Bin 0 -> 6971 bytes .../test_digital_image_processing.py | 8 ++++---- 2 files changed, 4 insertions(+), 4 deletions(-) create mode 100644 digital_image_processing/image_data/lena_small.jpg diff --git a/digital_image_processing/image_data/lena_small.jpg b/digital_image_processing/image_data/lena_small.jpg new file mode 100644 index 0000000000000000000000000000000000000000..b85144e9f65ca0c115c41e79a6c29c24aa9a322d GIT binary patch literal 6971 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 Hxt{+YSh(6Q literal 0 HcmV?d00001 diff --git a/digital_image_processing/test_digital_image_processing.py b/digital_image_processing/test_digital_image_processing.py index 0ff9e3333ca8..02c1a2d3a663 100644 --- a/digital_image_processing/test_digital_image_processing.py +++ b/digital_image_processing/test_digital_image_processing.py @@ -12,15 +12,15 @@ from numpy import array, uint8 from PIL import Image -img = imread(r"digital_image_processing/image_data/lena.jpg") +img = imread(r"digital_image_processing/image_data/lena_small.jpg") gray = cvtColor(img, COLOR_BGR2GRAY) # Test: change_contrast() def test_change_contrast(): - with Image.open("digital_image_processing/image_data/lena.jpg") as img: + with Image.open("digital_image_processing/image_data/lena_small.jpg") as img: # Work around assertion for response assert str(cc.change_contrast(img, 110)).startswith( - " Date: Fri, 13 Sep 2019 12:07:24 +0000 Subject: [PATCH 0211/1071] Add Flake8 F4 Tests to .travis.yml (#974) * Add Flake8 F4 Tests to .travis.yml F4 tests check for import errors. Implements issue #973 * Remove wildcard imports --- .travis.yml | 2 +- linear_algebra/src/tests.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index f7a9264803f8..be227df1fdbd 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,7 +6,7 @@ before_install: pip install --upgrade pip setuptools install: pip install -r requirements.txt before_script: - black --check . || true - - flake8 . --count --select=E9,F401,F63,F7,F82 --show-source --statistics + - flake8 . --count --select=E9,F4,F63,F7,F82 --show-source --statistics script: - scripts/validate_filenames.py # no uppercase, no spaces, in a directory - mypy --ignore-missing-imports . diff --git a/linear_algebra/src/tests.py b/linear_algebra/src/tests.py index a26eb92653e2..afca4ce87117 100644 --- a/linear_algebra/src/tests.py +++ b/linear_algebra/src/tests.py @@ -9,7 +9,7 @@ """ import unittest -from lib import * +from lib import Matrix, Vector, axpy, squareZeroMatrix, unitBasisVector, zeroVector class Test(unittest.TestCase): def test_component(self): From 768700b91f85b21fbb422d9deba6bb69d320e18d Mon Sep 17 00:00:00 2001 From: Sangeet K Date: Fri, 13 Sep 2019 13:24:25 -0700 Subject: [PATCH 0212/1071] Add delete to trie.py + tests (#1177) * Add delete to trie.py + tests * Minor fixes + tests * Remove noqa comments + modify tests for Travis CI to detect * Minor improvement --- data_structures/trie/trie.py | 84 +++++++++++++++++++++++++++++------- 1 file changed, 68 insertions(+), 16 deletions(-) diff --git a/data_structures/trie/trie.py b/data_structures/trie/trie.py index b6234c6704c6..5a560b97c293 100644 --- a/data_structures/trie/trie.py +++ b/data_structures/trie/trie.py @@ -1,9 +1,8 @@ """ A Trie/Prefix Tree is a kind of search tree used to provide quick lookup of words/patterns in a set of words. A basic Trie however has O(n^2) space complexity -making it impractical in practice. It however provides O(max(search_string, length of longest word)) lookup -time making it an optimal approach when space is not an issue. - +making it impractical in practice. It however provides O(max(search_string, length of longest word)) +lookup time making it an optimal approach when space is not an issue. """ @@ -12,7 +11,7 @@ def __init__(self): self.nodes = dict() # Mapping from char to TrieNode self.is_leaf = False - def insert_many(self, words: [str]): # noqa: E999 This syntax is Python 3 only + def insert_many(self, words: [str]): """ Inserts a list of words into the Trie :param words: list of string words @@ -21,7 +20,7 @@ def insert_many(self, words: [str]): # noqa: E999 This syntax is Python 3 only for word in words: self.insert(word) - def insert(self, word: str): # noqa: E999 This syntax is Python 3 only + def insert(self, word: str): """ Inserts a word into the Trie :param word: word to be inserted @@ -34,7 +33,7 @@ def insert(self, word: str): # noqa: E999 This syntax is Python 3 only curr = curr.nodes[char] curr.is_leaf = True - def find(self, word: str) -> bool: # noqa: E999 This syntax is Python 3 only + def find(self, word: str) -> bool: """ Tries to find word in a Trie :param word: word to look for @@ -47,8 +46,36 @@ def find(self, word: str) -> bool: # noqa: E999 This syntax is Python 3 only curr = curr.nodes[char] return curr.is_leaf + def delete(self, word: str): + """ + Deletes a word in a Trie + :param word: word to delete + :return: None + """ -def print_words(node: TrieNode, word: str): # noqa: E999 This syntax is Python 3 only + def _delete(curr: TrieNode, word: str, index: int): + if index == len(word): + # If word does not exist + if not curr.is_leaf: + return False + curr.is_leaf = False + return len(curr.nodes) == 0 + char = word[index] + char_node = curr.nodes.get(char) + # If char not in current trie node + if not char_node: + return False + # Flag to check if node can be deleted + delete_curr = _delete(char_node, word, index + 1) + if delete_curr: + del curr.nodes[char] + return len(curr.nodes) == 0 + return delete_curr + + _delete(self, word, 0) + + +def print_words(node: TrieNode, word: str): """ Prints all the words in a Trie :param node: root node of Trie @@ -56,20 +83,45 @@ def print_words(node: TrieNode, word: str): # noqa: E999 This syntax is Python :return: None """ if node.is_leaf: - print(word, end=' ') + print(word, end=" ") for key, value in node.nodes.items(): print_words(value, word + key) -def test(): - words = ['banana', 'bananas', 'bandana', 'band', 'apple', 'all', 'beast'] +def test_trie(): + words = "banana bananas bandana band apple all beast".split() root = TrieNode() root.insert_many(words) - # print_words(root, '') - assert root.find('banana') - assert not root.find('bandanas') - assert not root.find('apps') - assert root.find('apple') + # print_words(root, "") + assert all(root.find(word) for word in words) + assert root.find("banana") + assert not root.find("bandanas") + assert not root.find("apps") + assert root.find("apple") + assert root.find("all") + root.delete("all") + assert not root.find("all") + root.delete("banana") + assert not root.find("banana") + assert root.find("bananas") + return True + + +def print_results(msg: str, passes: bool) -> None: + print(str(msg), "works!" if passes else "doesn't work :(") + + +def pytests(): + assert test_trie() + + +def main(): + """ + >>> pytests() + """ + print_results("Testing trie functionality", test_trie()) + -test() +if __name__ == "__main__": + main() From a2b5a90c11ad07f82432fe4b96f6f17bed40e6c8 Mon Sep 17 00:00:00 2001 From: BAKEZQ Date: Wed, 18 Sep 2019 22:01:05 +0800 Subject: [PATCH 0213/1071] Added sequential minimum optimization algorithm for SVM (#508) * Implementation of sequential minimal optimization algorithm * Update smo.py * Add demonstration of svm partition boundary 1:Use matplotlib show svm's partition boundary 2:Automatically download test dataset * Update smo.py * Update smo.py * Rename smo.py to sequential_minimum_optimization.py * Update doc and simplify the code. Fix filename typo error in doc. Use ternary conditional operator in predict() * Update doc. --- .../sequential_minimum_optimization.py | 526 ++++++++++++++++++ 1 file changed, 526 insertions(+) create mode 100644 machine_learning/sequential_minimum_optimization.py diff --git a/machine_learning/sequential_minimum_optimization.py b/machine_learning/sequential_minimum_optimization.py new file mode 100644 index 000000000000..0b5d788e92e1 --- /dev/null +++ b/machine_learning/sequential_minimum_optimization.py @@ -0,0 +1,526 @@ +# coding: utf-8 +""" + Implementation of sequential minimal optimization(SMO) for support vector machines(SVM). + + Sequential minimal optimization (SMO) is an algorithm for solving the quadratic programming (QP) problem + that arises during the training of support vector machines. + It was invented by John Platt in 1998. + +Input: + 0: type: numpy.ndarray. + 1: first column of ndarray must be tags of samples, must be 1 or -1. + 2: rows of ndarray represent samples. + +Usage: + Command: + python3 sequential_minimum_optimization.py + Code: + from sequential_minimum_optimization import SmoSVM, Kernel + + kernel = Kernel(kernel='poly', degree=3., coef0=1., gamma=0.5) + init_alphas = np.zeros(train.shape[0]) + SVM = SmoSVM(train=train, alpha_list=init_alphas, kernel_func=kernel, cost=0.4, b=0.0, tolerance=0.001) + SVM.fit() + predict = SVM.predict(test_samples) + +Reference: + https://www.microsoft.com/en-us/research/wp-content/uploads/2016/02/smo-book.pdf + https://www.microsoft.com/en-us/research/wp-content/uploads/2016/02/tr-98-14.pdf + http://web.cs.iastate.edu/~honavar/smo-svm.pdf +""" + +from __future__ import division + +import os +import sys +import urllib.request + +import matplotlib.pyplot as plt +import numpy as np +import pandas as pd +from sklearn.datasets import make_blobs, make_circles +from sklearn.preprocessing import StandardScaler + +CANCER_DATASET_URL = 'http://archive.ics.uci.edu/ml/machine-learning-databases/breast-cancer-wisconsin/wdbc.data' + + +class SmoSVM(object): + def __init__(self, train, kernel_func, alpha_list=None, cost=0.4, b=0.0, tolerance=0.001, auto_norm=True): + self._init = True + self._auto_norm = auto_norm + self._c = np.float64(cost) + self._b = np.float64(b) + self._tol = np.float64(tolerance) if tolerance > 0.0001 else np.float64(0.001) + + self.tags = train[:, 0] + self.samples = self._norm(train[:, 1:]) if self._auto_norm else train[:, 1:] + self.alphas = alpha_list if alpha_list is not None else np.zeros(train.shape[0]) + self.Kernel = kernel_func + + self._eps = 0.001 + self._all_samples = list(range(self.length)) + self._K_matrix = self._calculate_k_matrix() + self._error = np.zeros(self.length) + self._unbound = [] + + self.choose_alpha = self._choose_alphas() + + # Calculate alphas using SMO algorithsm + def fit(self): + K = self._k + state = None + while True: + + # 1: Find alpha1, alpha2 + try: + i1, i2 = self.choose_alpha.send(state) + state = None + except StopIteration: + print("Optimization done!\r\nEvery sample satisfy the KKT condition!") + break + + # 2: calculate new alpha2 and new alpha1 + y1, y2 = self.tags[i1], self.tags[i2] + a1, a2 = self.alphas[i1].copy(), self.alphas[i2].copy() + e1, e2 = self._e(i1), self._e(i2) + args = (i1, i2, a1, a2, e1, e2, y1, y2) + a1_new, a2_new = self._get_new_alpha(*args) + if not a1_new and not a2_new: + state = False + continue + self.alphas[i1], self.alphas[i2] = a1_new, a2_new + + # 3: update threshold(b) + b1_new = np.float64(-e1 - y1 * K(i1, i1) * (a1_new - a1) - y2 * K(i2, i1) * (a2_new - a2) + self._b) + b2_new = np.float64(-e2 - y2 * K(i2, i2) * (a2_new - a2) - y1 * K(i1, i2) * (a1_new - a1) + self._b) + if 0.0 < a1_new < self._c: + b = b1_new + if 0.0 < a2_new < self._c: + b = b2_new + if not (np.float64(0) < a2_new < self._c) and not (np.float64(0) < a1_new < self._c): + b = (b1_new + b2_new) / 2.0 + b_old = self._b + self._b = b + + # 4: update error value,here we only calculate those non-bound samples' error + self._unbound = [i for i in self._all_samples if self._is_unbound(i)] + for s in self.unbound: + if s == i1 or s == i2: + continue + self._error[s] += y1 * (a1_new - a1) * K(i1, s) + y2 * (a2_new - a2) * K(i2, s) + (self._b - b_old) + + # if i1 or i2 is non-bound,update there error value to zero + if self._is_unbound(i1): + self._error[i1] = 0 + if self._is_unbound(i2): + self._error[i2] = 0 + + # Predict test samles + def predict(self, test_samples, classify=True): + + if test_samples.shape[1] > self.samples.shape[1]: + raise ValueError("Test samples' feature length does not equal to that of train samples") + + if self._auto_norm: + test_samples = self._norm(test_samples) + + results = [] + for test_sample in test_samples: + result = self._predict(test_sample) + if classify: + results.append(1 if result > 0 else -1) + else: + results.append(result) + return np.array(results) + + # Check if alpha violate KKT condition + def _check_obey_kkt(self, index): + alphas = self.alphas + tol = self._tol + r = self._e(index) * self.tags[index] + c = self._c + + return (r < -tol and alphas[index] < c) or (r > tol and alphas[index] > 0.0) + + # Get value calculated from kernel function + def _k(self, i1, i2): + # for test samples,use Kernel function + if isinstance(i2, np.ndarray): + return self.Kernel(self.samples[i1], i2) + # for train samples,Kernel values have been saved in matrix + else: + return self._K_matrix[i1, i2] + + # Get sample's error + def _e(self, index): + """ + Two cases: + 1:Sample[index] is non-bound,Fetch error from list: _error + 2:sample[index] is bound,Use predicted value deduct true value: g(xi) - yi + + """ + # get from error data + if self._is_unbound(index): + return self._error[index] + # get by g(xi) - yi + else: + gx = np.dot(self.alphas * self.tags, self._K_matrix[:, index]) + self._b + yi = self.tags[index] + return gx - yi + + # Calculate Kernel matrix of all possible i1,i2 ,saving time + def _calculate_k_matrix(self): + k_matrix = np.zeros([self.length, self.length]) + for i in self._all_samples: + for j in self._all_samples: + k_matrix[i, j] = np.float64(self.Kernel(self.samples[i, :], self.samples[j, :])) + return k_matrix + + # Predict test sample's tag + def _predict(self, sample): + k = self._k + predicted_value = np.sum( + [self.alphas[i1] * self.tags[i1] * k(i1, sample) for i1 in self._all_samples]) + self._b + return predicted_value + + # Choose alpha1 and alpha2 + def _choose_alphas(self): + locis = yield from self._choose_a1() + if not locis: + return + return locis + + def _choose_a1(self): + """ + Choose first alpha ;steps: + 1:Fisrt loop over all sample + 2:Second loop over all non-bound samples till all non-bound samples does not voilate kkt condition. + 3:Repeat this two process endlessly,till all samples does not voilate kkt condition samples after first loop. + """ + while True: + all_not_obey = True + # all sample + print('scanning all sample!') + for i1 in [i for i in self._all_samples if self._check_obey_kkt(i)]: + all_not_obey = False + yield from self._choose_a2(i1) + + # non-bound sample + print('scanning non-bound sample!') + while True: + not_obey = True + for i1 in [i for i in self._all_samples if self._check_obey_kkt(i) and self._is_unbound(i)]: + not_obey = False + yield from self._choose_a2(i1) + if not_obey: + print('all non-bound samples fit the KKT condition!') + break + if all_not_obey: + print('all samples fit the KKT condition! Optimization done!') + break + return False + + def _choose_a2(self, i1): + """ + Choose the second alpha by using heuristic algorithm ;steps: + 1:Choosed alpha2 which get the maximum step size (|E1 - E2|). + 2:Start in a random point,loop over all non-bound samples till alpha1 and alpha2 are optimized. + 3:Start in a random point,loop over all samples till alpha1 and alpha2 are optimized. + """ + self._unbound = [i for i in self._all_samples if self._is_unbound(i)] + + if len(self.unbound) > 0: + tmp_error = self._error.copy().tolist() + tmp_error_dict = {index: value for index, value in enumerate(tmp_error) if self._is_unbound(index)} + if self._e(i1) >= 0: + i2 = min(tmp_error_dict, key=lambda index: tmp_error_dict[index]) + else: + i2 = max(tmp_error_dict, key=lambda index: tmp_error_dict[index]) + cmd = yield i1, i2 + if cmd is None: + return + + for i2 in np.roll(self.unbound, np.random.choice(self.length)): + cmd = yield i1, i2 + if cmd is None: + return + + for i2 in np.roll(self._all_samples, np.random.choice(self.length)): + cmd = yield i1, i2 + if cmd is None: + return + + # Get the new alpha2 and new alpha1 + def _get_new_alpha(self, i1, i2, a1, a2, e1, e2, y1, y2): + K = self._k + if i1 == i2: + return None, None + + # calculate L and H which bound the new alpha2 + s = y1 * y2 + if s == -1: + L, H = max(0.0, a2 - a1), min(self._c, self._c + a2 - a1) + else: + L, H = max(0.0, a2 + a1 - self._c), min(self._c, a2 + a1) + if L == H: + return None, None + + # calculate eta + k11 = K(i1, i1) + k22 = K(i2, i2) + k12 = K(i1, i2) + eta = k11 + k22 - 2.0 * k12 + + # select the new alpha2 which could get the minimal objectives + if eta > 0.0: + a2_new_unc = a2 + (y2 * (e1 - e2)) / eta + # a2_new has a boundry + if a2_new_unc >= H: + a2_new = H + elif a2_new_unc <= L: + a2_new = L + else: + a2_new = a2_new_unc + else: + b = self._b + l1 = a1 + s * (a2 - L) + h1 = a1 + s * (a2 - H) + + # way 1 + f1 = y1 * (e1 + b) - a1 * K(i1, i1) - s * a2 * K(i1, i2) + f2 = y2 * (e2 + b) - a2 * K(i2, i2) - s * a1 * K(i1, i2) + ol = l1 * f1 + L * f2 + 1 / 2 * l1 ** 2 * K(i1, i1) + 1 / 2 * L ** 2 * K(i2, i2) + s * L * l1 * K(i1, i2) + oh = h1 * f1 + H * f2 + 1 / 2 * h1 ** 2 * K(i1, i1) + 1 / 2 * H ** 2 * K(i2, i2) + s * H * h1 * K(i1, i2) + """ + # way 2 + Use objective function check which alpha2 new could get the minimal objectives + + """ + if ol < (oh - self._eps): + a2_new = L + elif ol > oh + self._eps: + a2_new = H + else: + a2_new = a2 + + # a1_new has a boundry too + a1_new = a1 + s * (a2 - a2_new) + if a1_new < 0: + a2_new += s * a1_new + a1_new = 0 + if a1_new > self._c: + a2_new += s * (a1_new - self._c) + a1_new = self._c + + return a1_new, a2_new + + # Normalise data using min_max way + def _norm(self, data): + if self._init: + self._min = np.min(data, axis=0) + self._max = np.max(data, axis=0) + self._init = False + return (data - self._min) / (self._max - self._min) + else: + return (data - self._min) / (self._max - self._min) + + def _is_unbound(self, index): + if 0.0 < self.alphas[index] < self._c: + return True + else: + return False + + def _is_support(self, index): + if self.alphas[index] > 0: + return True + else: + return False + + @property + def unbound(self): + return self._unbound + + @property + def support(self): + return [i for i in range(self.length) if self._is_support(i)] + + @property + def length(self): + return self.samples.shape[0] + + +class Kernel(object): + def __init__(self, kernel, degree=1.0, coef0=0.0, gamma=1.0): + self.degree = np.float64(degree) + self.coef0 = np.float64(coef0) + self.gamma = np.float64(gamma) + self._kernel_name = kernel + self._kernel = self._get_kernel(kernel_name=kernel) + self._check() + + def _polynomial(self, v1, v2): + return (self.gamma * np.inner(v1, v2) + self.coef0) ** self.degree + + def _linear(self, v1, v2): + return np.inner(v1, v2) + self.coef0 + + def _rbf(self, v1, v2): + return np.exp(-1 * (self.gamma * np.linalg.norm(v1 - v2) ** 2)) + + def _check(self): + if self._kernel == self._rbf: + if self.gamma < 0: + raise ValueError('gamma value must greater than 0') + + def _get_kernel(self, kernel_name): + maps = { + 'linear': self._linear, + 'poly': self._polynomial, + 'rbf': self._rbf + } + return maps[kernel_name] + + def __call__(self, v1, v2): + return self._kernel(v1, v2) + + def __repr__(self): + return self._kernel_name + + +def count_time(func): + def call_func(*args, **kwargs): + import time + start_time = time.time() + func(*args, **kwargs) + end_time = time.time() + print('smo algorithm cost {} seconds'.format(end_time - start_time)) + + return call_func + + +@count_time +def test_cancel_data(): + print('Hello!\r\nStart test svm by smo algorithm!') + # 0: download dataset and load into pandas' dataframe + if not os.path.exists(r'cancel_data.csv'): + request = urllib.request.Request( + CANCER_DATASET_URL, + headers={'User-Agent': 'Mozilla/4.0 (compatible; MSIE 5.5; Windows NT)'} + ) + response = urllib.request.urlopen(request) + content = response.read().decode('utf-8') + with open(r'cancel_data.csv', 'w') as f: + f.write(content) + + data = pd.read_csv(r'cancel_data.csv', header=None) + + # 1: pre-processing data + del data[data.columns.tolist()[0]] + data = data.dropna(axis=0) + data = data.replace({'M': np.float64(1), 'B': np.float64(-1)}) + samples = np.array(data)[:, :] + + # 2: deviding data into train_data data and test_data data + train_data, test_data = samples[:328, :], samples[328:, :] + test_tags, test_samples = test_data[:, 0], test_data[:, 1:] + + # 3: choose kernel function,and set initial alphas to zero(optional) + mykernel = Kernel(kernel='rbf', degree=5, coef0=1, gamma=0.5) + al = np.zeros(train_data.shape[0]) + + # 4: calculating best alphas using SMO algorithm and predict test_data samples + mysvm = SmoSVM(train=train_data, kernel_func=mykernel, alpha_list=al, cost=0.4, b=0.0, tolerance=0.001) + mysvm.fit() + predict = mysvm.predict(test_samples) + + # 5: check accuracy + score = 0 + test_num = test_tags.shape[0] + for i in range(test_tags.shape[0]): + if test_tags[i] == predict[i]: + score += 1 + print('\r\nall: {}\r\nright: {}\r\nfalse: {}'.format(test_num, score, test_num - score)) + print("Rough Accuracy: {}".format(score / test_tags.shape[0])) + + +def test_demonstration(): + # change stdout + print('\r\nStart plot,please wait!!!') + sys.stdout = open(os.devnull, 'w') + + ax1 = plt.subplot2grid((2, 2), (0, 0)) + ax2 = plt.subplot2grid((2, 2), (0, 1)) + ax3 = plt.subplot2grid((2, 2), (1, 0)) + ax4 = plt.subplot2grid((2, 2), (1, 1)) + ax1.set_title("linear svm,cost:0.1") + test_linear_kernel(ax1, cost=0.1) + ax2.set_title("linear svm,cost:500") + test_linear_kernel(ax2, cost=500) + ax3.set_title("rbf kernel svm,cost:0.1") + test_rbf_kernel(ax3, cost=0.1) + ax4.set_title("rbf kernel svm,cost:500") + test_rbf_kernel(ax4, cost=500) + + sys.stdout = sys.__stdout__ + print("Plot done!!!") + +def test_linear_kernel(ax, cost): + train_x, train_y = make_blobs(n_samples=500, centers=2, + n_features=2, random_state=1) + train_y[train_y == 0] = -1 + scaler = StandardScaler() + train_x_scaled = scaler.fit_transform(train_x, train_y) + train_data = np.hstack((train_y.reshape(500, 1), train_x_scaled)) + mykernel = Kernel(kernel='linear', degree=5, coef0=1, gamma=0.5) + mysvm = SmoSVM(train=train_data, kernel_func=mykernel, cost=cost, tolerance=0.001, auto_norm=False) + mysvm.fit() + plot_partition_boundary(mysvm, train_data, ax=ax) + + +def test_rbf_kernel(ax, cost): + train_x, train_y = make_circles(n_samples=500, noise=0.1, factor=0.1, random_state=1) + train_y[train_y == 0] = -1 + scaler = StandardScaler() + train_x_scaled = scaler.fit_transform(train_x, train_y) + train_data = np.hstack((train_y.reshape(500, 1), train_x_scaled)) + mykernel = Kernel(kernel='rbf', degree=5, coef0=1, gamma=0.5) + mysvm = SmoSVM(train=train_data, kernel_func=mykernel, cost=cost, tolerance=0.001, auto_norm=False) + mysvm.fit() + plot_partition_boundary(mysvm, train_data, ax=ax) + + +def plot_partition_boundary(model, train_data, ax, resolution=100, colors=('b', 'k', 'r')): + """ + We can not get the optimum w of our kernel svm model which is different from linear svm. + For this reason, we generate randomly destributed points with high desity and prediced values of these points are + calculated by using our tained model. Then we could use this prediced values to draw contour map. + And this contour map can represent svm's partition boundary. + + """ + train_data_x = train_data[:, 1] + train_data_y = train_data[:, 2] + train_data_tags = train_data[:, 0] + xrange = np.linspace(train_data_x.min(), train_data_x.max(), resolution) + yrange = np.linspace(train_data_y.min(), train_data_y.max(), resolution) + test_samples = np.array([(x, y) for x in xrange for y in yrange]).reshape(resolution * resolution, 2) + + test_tags = model.predict(test_samples, classify=False) + grid = test_tags.reshape((len(xrange), len(yrange))) + + # Plot contour map which represents the partition boundary + ax.contour(xrange, yrange, np.mat(grid).T, levels=(-1, 0, 1), linestyles=('--', '-', '--'), + linewidths=(1, 1, 1), + colors=colors) + # Plot all train samples + ax.scatter(train_data_x, train_data_y, c=train_data_tags, cmap=plt.cm.Dark2, lw=0, alpha=0.5) + + # Plot support vectors + support = model.support + ax.scatter(train_data_x[support], train_data_y[support], c=train_data_tags[support], cmap=plt.cm.Dark2) + + +if __name__ == '__main__': + test_cancel_data() + test_demonstration() + plt.show() + From 04962c0d17b82f349e0453b06b76d9f594e2b024 Mon Sep 17 00:00:00 2001 From: Denis Trofimov Date: Sat, 21 Sep 2019 17:23:34 +0300 Subject: [PATCH 0214/1071] Fix lgtm error display #1024 (#1190) * fix: Syntax Error lgtm display in matrix/matrix_operation.py. * Testing for None should use the 'is' operator. * fix: Too many arguments for string format. * fix: supress lgtm alert as false positive. * style: Unnecessary 'pass' statement. * Revert "fix: Syntax Error lgtm display in matrix/matrix_operation.py." This reverts commit 4c629b4ce16a32b99a8127f5553cdb9015bf5e80. --- data_structures/heap/binomial_heap.py | 2 +- divide_and_conquer/convex_hull.py | 2 +- neural_network/convolution_neural_network.py | 3 +-- strings/boyer_moore_search.py | 2 +- 4 files changed, 4 insertions(+), 5 deletions(-) diff --git a/data_structures/heap/binomial_heap.py b/data_structures/heap/binomial_heap.py index bc9cb5145f2e..0154390d7707 100644 --- a/data_structures/heap/binomial_heap.py +++ b/data_structures/heap/binomial_heap.py @@ -307,7 +307,7 @@ def deleteMin(self): # No right subtree corner case # The structure of the tree implies that this should be the bottom root # and there is at least one other root - if self.min_node.right == None: + if self.min_node.right is None: # Update size self.size -= 1 diff --git a/divide_and_conquer/convex_hull.py b/divide_and_conquer/convex_hull.py index a0c319e766da..534ebda2c780 100644 --- a/divide_and_conquer/convex_hull.py +++ b/divide_and_conquer/convex_hull.py @@ -191,7 +191,7 @@ def _validate_input(points): else: raise ValueError("Expecting an iterable of type Point, list or tuple. " "Found objects of type {} instead" - .format(["point", "list", "tuple"], type(points[0]))) + .format(type(points[0]))) elif not hasattr(points, "__iter__"): raise ValueError("Expecting an iterable object " "but got an non-iterable type {}".format(points)) diff --git a/neural_network/convolution_neural_network.py b/neural_network/convolution_neural_network.py index e4dd0a11db9d..786992c054a0 100644 --- a/neural_network/convolution_neural_network.py +++ b/neural_network/convolution_neural_network.py @@ -297,7 +297,6 @@ def convolution(self, data): if __name__ == '__main__': - pass ''' I will put the example on other file -''' + ''' diff --git a/strings/boyer_moore_search.py b/strings/boyer_moore_search.py index 781ff0ca6106..2d67043dc028 100644 --- a/strings/boyer_moore_search.py +++ b/strings/boyer_moore_search.py @@ -70,7 +70,7 @@ def bad_character_heuristic(self): positions.append(i) else: match_index = self.match_in_pattern(self.text[mismatch_index]) - i = mismatch_index - match_index #shifting index + i = mismatch_index - match_index #shifting index lgtm [py/multiple-definition] return positions From 837bfffd991cddb3113139a3d13292b58529f05f Mon Sep 17 00:00:00 2001 From: Holden-Lin Date: Sun, 22 Sep 2019 22:56:32 +0800 Subject: [PATCH 0215/1071] Rename sorted_vector_machines.py to support_vector_machines.py (#1195) SVM stands for support vector machines. Intuitively, a support vector is the vector right near the decision boundary. --- .../{sorted_vector_machines.py => support_vector_machines.py} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename machine_learning/{sorted_vector_machines.py => support_vector_machines.py} (100%) diff --git a/machine_learning/sorted_vector_machines.py b/machine_learning/support_vector_machines.py similarity index 100% rename from machine_learning/sorted_vector_machines.py rename to machine_learning/support_vector_machines.py From 01601e6382e92b5a3806fb3a44357460dff97ee7 Mon Sep 17 00:00:00 2001 From: luoheng <1301089462@qq.com> Date: Mon, 23 Sep 2019 11:08:20 +0800 Subject: [PATCH 0216/1071] Add disjoint set (#1194) * Add disjoint set * disjoint set: add doctest, make code more Pythonic * disjoint set: replace x.p with x.parent * disjoint set: add test and refercence --- data_structures/disjoint_set/disjoint_set.py | 79 ++++++++++++++++++++ 1 file changed, 79 insertions(+) create mode 100644 data_structures/disjoint_set/disjoint_set.py diff --git a/data_structures/disjoint_set/disjoint_set.py b/data_structures/disjoint_set/disjoint_set.py new file mode 100644 index 000000000000..a93b89621c4a --- /dev/null +++ b/data_structures/disjoint_set/disjoint_set.py @@ -0,0 +1,79 @@ +""" + disjoint set + Reference: https://en.wikipedia.org/wiki/Disjoint-set_data_structure +""" + + +class Node: + def __init__(self, data): + self.data = data + + +def make_set(x): + """ + make x as a set. + """ + # rank is the distance from x to its' parent + # root's rank is 0 + x.rank = 0 + x.parent = x + + +def union_set(x, y): + """ + union two sets. + set with bigger rank should be parent, so that the + disjoint set tree will be more flat. + """ + x, y = find_set(x), find_set(y) + if x.rank > y.rank: + y.parent = x + else: + x.parent = y + if x.rank == y.rank: + y.rank += 1 + + +def find_set(x): + """ + return the parent of x + """ + if x != x.parent: + x.parent = find_set(x.parent) + return x.parent + + +def find_python_set(node: Node) -> set: + """ + Return a Python Standard Library set that contains i. + """ + sets = ({0, 1, 2}, {3, 4, 5}) + for s in sets: + if node.data in s: + return s + raise ValueError(f"{node.data} is not in {sets}") + + +def test_disjoint_set(): + """ + >>> test_disjoint_set() + """ + vertex = [Node(i) for i in range(6)] + for v in vertex: + make_set(v) + + union_set(vertex[0], vertex[1]) + union_set(vertex[1], vertex[2]) + union_set(vertex[3], vertex[4]) + union_set(vertex[3], vertex[5]) + + for node0 in vertex: + for node1 in vertex: + if find_python_set(node0).isdisjoint(find_python_set(node1)): + assert find_set(node0) != find_set(node1) + else: + assert find_set(node0) == find_set(node1) + + +if __name__ == "__main__": + test_disjoint_set() From e40d4a25f9b7a58d08fdc5d247a25cb226a46d21 Mon Sep 17 00:00:00 2001 From: Aniruddha Bhattacharjee Date: Wed, 25 Sep 2019 23:38:45 +0530 Subject: [PATCH 0217/1071] Added Matrix Exponentiation (#1203) * Added the matrix_exponentiation.py file in maths directory * Implemented the requested changes * Update matrix_exponentiation.py --- maths/matrix_exponentiation.py | 99 ++++++++++++++++++++++++++++++++++ 1 file changed, 99 insertions(+) create mode 100644 maths/matrix_exponentiation.py diff --git a/maths/matrix_exponentiation.py b/maths/matrix_exponentiation.py new file mode 100644 index 000000000000..ee9e1757687c --- /dev/null +++ b/maths/matrix_exponentiation.py @@ -0,0 +1,99 @@ +"""Matrix Exponentiation""" + +import timeit + +""" +Matrix Exponentiation is a technique to solve linear recurrences in logarithmic time. +You read more about it here: +http://zobayer.blogspot.com/2010/11/matrix-exponentiation.html +https://www.hackerearth.com/practice/notes/matrix-exponentiation-1/ +""" + + +class Matrix(object): + def __init__(self, arg): + if isinstance(arg, list): # Initialzes a matrix identical to the one provided. + self.t = arg + self.n = len(arg) + else: # Initializes a square matrix of the given size and set the values to zero. + self.n = arg + self.t = [[0 for _ in range(self.n)] for _ in range(self.n)] + + def __mul__(self, b): + matrix = Matrix(self.n) + for i in range(self.n): + for j in range(self.n): + for k in range(self.n): + matrix.t[i][j] += self.t[i][k] * b.t[k][j] + return matrix + + +def modular_exponentiation(a, b): + matrix = Matrix([[1, 0], [0, 1]]) + while b > 0: + if b & 1: + matrix *= a + a *= a + b >>= 1 + return matrix + + +def fibonacci_with_matrix_exponentiation(n, f1, f2): + # Trivial Cases + if n == 1: + return f1 + elif n == 2: + return f2 + matrix = Matrix([[1, 1], [1, 0]]) + matrix = modular_exponentiation(matrix, n - 2) + return f2 * matrix.t[0][0] + f1 * matrix.t[0][1] + + +def simple_fibonacci(n, f1, f2): + # Trival Cases + if n == 1: + return f1 + elif n == 2: + return f2 + + fn_1 = f1 + fn_2 = f2 + n -= 2 + + while n > 0: + fn_1, fn_2 = fn_1 + fn_2, fn_1 + n -= 1 + + return fn + + +def matrix_exponentiation_time(): + setup = """ +from random import randint +from __main__ import fibonacci_with_matrix_exponentiation +""" + code = "fibonacci_with_matrix_exponentiation(randint(1,70000), 1, 1)" + exec_time = timeit.timeit(setup=setup, stmt=code, number=100) + print("With matrix exponentiation the average execution time is ", exec_time / 100) + return exec_time + + +def simple_fibonacci_time(): + setup = """ +from random import randint +from __main__ import simple_fibonacci +""" + code = "simple_fibonacci(randint(1,70000), 1, 1)" + exec_time = timeit.timeit(setup=setup, stmt=code, number=100) + print("Without matrix exponentiation the average execution time is ", + exec_time / 100) + return exec_time + + +def main(): + matrix_exponentiation_time() + simple_fibonacci_time() + + +if __name__ == "__main__": + main() From 6ac7b1387f9b2c2fa428bbe2bd26d3e087e41b23 Mon Sep 17 00:00:00 2001 From: Raj Date: Wed, 25 Sep 2019 15:03:31 -0700 Subject: [PATCH 0218/1071] Min head with decrease key functionality (#1202) * Min head with decrease key functionality * doctest added * __str__ changed as per Python convention * edits in doctest * get_value by key added * __getitem__ added --- data_structures/heap/min_heap.py | 169 +++++++++++++++++++++++++++++++ 1 file changed, 169 insertions(+) create mode 100644 data_structures/heap/min_heap.py diff --git a/data_structures/heap/min_heap.py b/data_structures/heap/min_heap.py new file mode 100644 index 000000000000..6184d83be774 --- /dev/null +++ b/data_structures/heap/min_heap.py @@ -0,0 +1,169 @@ +# Min head data structure +# with decrease key functionality - in O(log(n)) time + + +class Node: + def __init__(self, name, val): + self.name = name + self.val = val + + def __str__(self): + return f"{self.__class__.__name__}({self.name}, {self.val})" + + def __lt__(self, other): + return self.val < other.val + + +class MinHeap: + """ + >>> r = Node("R", -1) + >>> b = Node("B", 6) + >>> a = Node("A", 3) + >>> x = Node("X", 1) + >>> e = Node("E", 4) + >>> print(b) + Node(B, 6) + >>> myMinHeap = MinHeap([r, b, a, x, e]) + >>> myMinHeap.decrease_key(b, -17) + >>> print(b) + Node(B, -17) + >>> print(myMinHeap["B"]) + -17 + """ + + def __init__(self, array): + self.idx_of_element = {} + self.heap_dict = {} + self.heap = self.build_heap(array) + + def __getitem__(self, key): + return self.get_value(key) + + def get_parent_idx(self, idx): + return (idx - 1) // 2 + + def get_left_child_idx(self, idx): + return idx * 2 + 1 + + def get_right_child_idx(self, idx): + return idx * 2 + 2 + + def get_value(self, key): + return self.heap_dict[key] + + def build_heap(self, array): + lastIdx = len(array) - 1 + startFrom = self.get_parent_idx(lastIdx) + + for idx, i in enumerate(array): + self.idx_of_element[i] = idx + self.heap_dict[i.name] = i.val + + for i in range(startFrom, -1, -1): + self.sift_down(i, array) + return array + + # this is min-heapify method + def sift_down(self, idx, array): + while True: + l = self.get_left_child_idx(idx) + r = self.get_right_child_idx(idx) + + smallest = idx + if l < len(array) and array[l] < array[idx]: + smallest = l + if r < len(array) and array[r] < array[smallest]: + smallest = r + + if smallest != idx: + array[idx], array[smallest] = array[smallest], array[idx] + self.idx_of_element[array[idx]], self.idx_of_element[ + array[smallest] + ] = ( + self.idx_of_element[array[smallest]], + self.idx_of_element[array[idx]], + ) + idx = smallest + else: + break + + def sift_up(self, idx): + p = self.get_parent_idx(idx) + while p >= 0 and self.heap[p] > self.heap[idx]: + self.heap[p], self.heap[idx] = self.heap[idx], self.heap[p] + self.idx_of_element[self.heap[p]], self.idx_of_element[self.heap[idx]] = ( + self.idx_of_element[self.heap[idx]], + self.idx_of_element[self.heap[p]], + ) + idx = p + p = self.get_parent_idx(idx) + + def peek(self): + return self.heap[0] + + def remove(self): + self.heap[0], self.heap[-1] = self.heap[-1], self.heap[0] + self.idx_of_element[self.heap[0]], self.idx_of_element[self.heap[-1]] = ( + self.idx_of_element[self.heap[-1]], + self.idx_of_element[self.heap[0]], + ) + + x = self.heap.pop() + del self.idx_of_element[x] + self.sift_down(0, self.heap) + return x + + def insert(self, node): + self.heap.append(node) + self.idx_of_element[node] = len(self.heap) - 1 + self.heap_dict[node.name] = node.val + self.sift_up(len(self.heap) - 1) + + def is_empty(self): + return True if len(self.heap) == 0 else False + + def decrease_key(self, node, newValue): + assert ( + self.heap[self.idx_of_element[node]].val > newValue + ), "newValue must be less that current value" + node.val = newValue + self.heap_dict[node.name] = newValue + self.sift_up(self.idx_of_element[node]) + + +## USAGE + +r = Node("R", -1) +b = Node("B", 6) +a = Node("A", 3) +x = Node("X", 1) +e = Node("E", 4) + +# Use one of these two ways to generate Min-Heap + +# Generating Min-Heap from array +myMinHeap = MinHeap([r, b, a, x, e]) + +# Generating Min-Heap by Insert method +# myMinHeap.insert(a) +# myMinHeap.insert(b) +# myMinHeap.insert(x) +# myMinHeap.insert(r) +# myMinHeap.insert(e) + +# Before +print("Min Heap - before decrease key") +for i in myMinHeap.heap: + print(i) + +print("Min Heap - After decrease key of node [B -> -17]") +myMinHeap.decrease_key(b, -17) + +# After +for i in myMinHeap.heap: + print(i) + +if __name__ == "__main__": + import doctest + + doctest.testmod() From 2375bfbee59f091435818d850e5057fdd50ffe19 Mon Sep 17 00:00:00 2001 From: Charitoc <37042130+Charitoc@users.noreply.github.com> Date: Thu, 26 Sep 2019 18:19:01 +0300 Subject: [PATCH 0219/1071] Adding stooge sort (#1206) * Adding stooge sort * Updated doctest * Just added underscore in the name --- sorts/stooge_sort.py | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 sorts/stooge_sort.py diff --git a/sorts/stooge_sort.py b/sorts/stooge_sort.py new file mode 100644 index 000000000000..d2325abc9b38 --- /dev/null +++ b/sorts/stooge_sort.py @@ -0,0 +1,34 @@ +def stooge_sort(arr): + """ + >>> arr = [2, 4, 5, 3, 1] + >>> stooge_sort(arr) + >>> print(arr) + [1, 2, 3, 4, 5] + """ + stooge(arr,0,len(arr)-1) + + +def stooge(arr, i, h): + + + if i >= h: + return + + # If first element is smaller than the last then swap them + if arr[i]>arr[h]: + arr[i], arr[h] = arr[h], arr[i] + + # If there are more than 2 elements in the array + if h-i+1 > 2: + t = (int)((h-i+1)/3) + + # Recursively sort first 2/3 elements + stooge(arr, i, (h-t)) + + # Recursively sort last 2/3 elements + stooge(arr, i+t, (h)) + + # Recursively sort first 2/3 elements + stooge(arr, i, (h-t)) + + From a79fc2b92ad2c36e77d1c1696a2a0bc746ef9fea Mon Sep 17 00:00:00 2001 From: Christian Clauss Date: Thu, 26 Sep 2019 17:32:04 +0200 Subject: [PATCH 0220/1071] Fix the build typo: fn --> fn1 (#1205) --- maths/matrix_exponentiation.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/maths/matrix_exponentiation.py b/maths/matrix_exponentiation.py index ee9e1757687c..f80f6c3cad5e 100644 --- a/maths/matrix_exponentiation.py +++ b/maths/matrix_exponentiation.py @@ -64,7 +64,7 @@ def simple_fibonacci(n, f1, f2): fn_1, fn_2 = fn_1 + fn_2, fn_1 n -= 1 - return fn + return fn_1 def matrix_exponentiation_time(): From 4617aa78b2a61fcfebc136f7fc217ea980205067 Mon Sep 17 00:00:00 2001 From: Kaushik Amar Das Date: Sun, 29 Sep 2019 14:14:41 +0530 Subject: [PATCH 0221/1071] DBSCAN algorithm (#1207) * Added dbscan in two formats. A jupyter notebook file for the storytelling and a .py file for people that just want to look at the code. The code in both is essentially the same. With a few things different in the .py file for plotting the clusters. * fixed LGTM problems * Some requested changes implemented. Still need to do docstring * implememted all changes as requested --- machine_learning/dbscan/dbscan.ipynb | 376 +++++++++++++++++++++++++++ machine_learning/dbscan/dbscan.py | 271 +++++++++++++++++++ 2 files changed, 647 insertions(+) create mode 100644 machine_learning/dbscan/dbscan.ipynb create mode 100644 machine_learning/dbscan/dbscan.py diff --git a/machine_learning/dbscan/dbscan.ipynb b/machine_learning/dbscan/dbscan.ipynb new file mode 100644 index 000000000000..603a4cd405b9 --- /dev/null +++ b/machine_learning/dbscan/dbscan.ipynb @@ -0,0 +1,376 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## DBSCAN\n", + "This implementation and notebook is inspired from the original DBSCAN algorithm and article as given in \n", + "[DBSCAN Wikipedia](https://en.wikipedia.org/wiki/DBSCAN).\n", + "\n", + "Stands for __Density-based spatial clustering of applications with noise__ . \n", + "\n", + "DBSCAN is clustering algorithm that tries to captures the intuition that if two points belong to the same cluster they should be close to one another. It does so by finding regions that are densely packed together, i.e, the points that have many close neighbours.\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### When to use ?\n", + "\n", + "1. You need a robust clustering algorithm.\n", + "2. You don't know how many clusters there are in the dataset\n", + "3. You find it difficult to guess the number of clusters there are just by eyeballing the dataset.\n", + "4. The clusters are of arbitrary shapes.\n", + "5. You want to detect outliers/noise." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Why DBSCAN ? \n", + "\n", + "This algorithm is way better than other clustering algorithms such as [k-means](https://en.wikipedia.org/wiki/K-means_clustering) whose only job is to find circular blobs. It is smart enough to figure out the number of clusters in the dataset on its own, unlike k-means where you need to specify 'k'. It can also find clusters of arbitrary shapes, not just circular blobs. Its too robust to be affected by outliers (the noise points) and isn't fooled by them, unlike k-means where the entire centroid get pulled thanks to pesky outliers. Plus, you can fine-tune its parameters depending on what you are clustering.\n", + "\n", + "#### Have a look at these [neat animations](https://www.naftaliharris.com/blog/visualizing-dbscan-clustering/) of DBSCAN to see for yourself." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "import matplotlib.pyplot as plt\n", + "import numpy as np\n", + "\n", + "%matplotlib inline" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## First lets grab a dataset\n", + "We will take the moons dataset which is pretty good at showing the power of DBSCAN. \n", + "\n", + "Lets generate 200 random points in the shape of two moons" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [], + "source": [ + "from sklearn.datasets import make_moons\n", + "\n", + "x, label = make_moons(n_samples=200, noise=0.1, random_state=19)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Visualize the dataset using matplotlib\n", + "You will observe that the points are in the shape of two crescent moons. \n", + "\n", + "The challenge here is to cluster the two moons. " + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": { + "scrolled": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "[]" + ] + }, + "execution_count": 3, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "plt.plot(x[:,0], x[:,1],'ro')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Abstract of the Algorithm\n", + "The DBSCAN algorithm can be abstracted into the following steps:\n", + "\n", + "- Find the points in the $ε$ (eps) neighborhood of every point, and identify the core points with more than min_pts neighbors.\n", + "- Find the connected components of core points on the neighbor graph, ignoring all non-core points.\n", + "- Assign each non-core point to a nearby cluster if the cluster is an $ε$ (eps) neighbor, otherwise assign it to noise.\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Preparing the points\n", + "Initially we label all the points in the dataset as __undefined__ .\n", + "\n", + "__points__ is our database of all points in the dataset." + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [], + "source": [ + "points = { (point[0],point[1]):{'label':'undefined'} for point in x }" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Helper functions" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [], + "source": [ + "def euclidean_distance(q, p):\n", + " \"\"\"\n", + " Calculates the Euclidean distance\n", + " between points P and Q\n", + " \"\"\"\n", + " a = pow((q[0] - p[0]), 2)\n", + " b = pow((q[1] - p[1]), 2)\n", + " return pow((a + b), 0.5)" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [], + "source": [ + "def find_neighbors(db, q, eps):\n", + " \"\"\"\n", + " Finds all points in the DB that\n", + " are within a distance of eps from Q\n", + " \"\"\"\n", + " return [p for p in db if euclidean_distance(q, p) <= eps]" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [], + "source": [ + "def plot_cluster(db, clusters):\n", + " \"\"\"\n", + " Extracts all the points in the DB and puts them together\n", + " as seperate clusters and finally plots them\n", + " \"\"\"\n", + " temp = []\n", + " noise = []\n", + " for i in clusters:\n", + " stack = []\n", + " for k, v in db.items():\n", + " if v[\"label\"] == i:\n", + " stack.append(k)\n", + " elif v[\"label\"] == \"noise\":\n", + " noise.append(k)\n", + " temp.append(stack)\n", + "\n", + " color = iter(plt.cm.rainbow(np.linspace(0, 1, len(clusters))))\n", + " for i in range(0, len(temp)):\n", + " c = next(color)\n", + " x = [l[0] for l in temp[i]]\n", + " y = [l[1] for l in temp[i]]\n", + " plt.plot(x, y, \"ro\", c=c)\n", + "\n", + " x = [l[0] for l in noise]\n", + " y = [l[1] for l in noise]\n", + " plt.plot(x, y, \"ro\", c=\"0\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Implementation of DBSCAN\n", + "\n", + "Initialize an empty list, clusters = $[ ]$ and cluster identifier, c = 0\n", + "\n", + "1. For each point p in our database/dict db :\n", + "\n", + " 1.1 Check if p is already labelled. If it's already labelled (means it already been associated to a cluster), continue to the next point,i.e, go to step 1\n", + " \n", + " 1.2. Find the list of neighbors of p , i.e, points that are within a distance of eps from p\n", + " \n", + " 1.3. If p does not have atleast min_pts neighbours, we label it as noise and go back to step 1\n", + " \n", + " 1.4. Initialize the cluster, by incrementing c by 1\n", + " \n", + " 1.5. Append the cluster identifier c to clusters\n", + " \n", + " 1.6. Label p with the cluster identifier c\n", + " \n", + " 1.7 Remove p from the list of neighbors (p will be detected as its own neighbor because it is within eps of itself)\n", + " \n", + " 1.8. Initialize the seed_set as a copy of neighbors\n", + " \n", + " 1.9. While the seed_set is not empty:\n", + " 1.9.1. Removing the 1st point from seed_set and initialise it as q\n", + " 1.9.2. If it's label is noise, label it with c\n", + " 1.9.3. If it's not unlabelled, go back to step 1.9\n", + " 1.9.4. Label q with c\n", + " 1.9.5. Find the neighbours of q \n", + " 1.9.6. If there are atleast min_pts neighbors, append them to the seed_set" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": {}, + "outputs": [], + "source": [ + "def dbscan(db,eps,min_pts):\n", + " '''\n", + " Implementation of the DBSCAN algorithm\n", + " '''\n", + " clusters = []\n", + " c = 0\n", + " for p in db:\n", + " if db[p][\"label\"] != \"undefined\":\n", + " continue\n", + " neighbors = find_neighbors(db, p, eps)\n", + " if len(neighbors) < min_pts:\n", + " db[p][\"label\"] = \"noise\"\n", + " continue\n", + " c += 1\n", + " clusters.append(c)\n", + " db[p][\"label\"] = c\n", + " neighbors.remove(p)\n", + " seed_set = neighbors.copy()\n", + " while seed_set != []:\n", + " q = seed_set.pop(0)\n", + " if db[q][\"label\"] == \"noise\":\n", + " db[q][\"label\"] = c\n", + " if db[q][\"label\"] != \"undefined\":\n", + " continue\n", + " db[q][\"label\"] = c\n", + " neighbors_n = find_neighbors(db, q, eps)\n", + " if len(neighbors_n) >= min_pts:\n", + " seed_set = seed_set + neighbors_n\n", + " return db, clusters\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Lets run it!" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "eps = 0.25\n", + "min_pts = 12\n", + "\n", + "db,clusters = dbscan(points,eps,min_pts)\n", + "\n", + "plot_cluster(db,clusters)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "I encourage you to try with different datasets and playing with the values of eps and min_pts.\n", + "\n", + "Also, try kmeans on this dataset and see how it compares to dbscan. " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "I hope by now you are convinced about about how cool dbscan is. But it has its pitfalls.\n", + "### When NOT to use ?\n", + "\n", + "1. You have a high dimentional dataset. Euclidean distance will fail thanks to '[curse of dimentionality](https://en.wikipedia.org/wiki/Curse_of_dimensionality#Distance_functions)'.\n", + "2. We have used a dict to store the points. So we can't do anything about the order in which the points will be processed. So it's not entirely deterministic.\n", + "3. Won't work well if there are large differences in density. Finding the min_pts and $ε$ combination will be difficult.\n", + "4. Choosing the $ε$ without understanding the data and its scale, might result is poor clustering performance." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.7.0" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/machine_learning/dbscan/dbscan.py b/machine_learning/dbscan/dbscan.py new file mode 100644 index 000000000000..04fb5f0186e1 --- /dev/null +++ b/machine_learning/dbscan/dbscan.py @@ -0,0 +1,271 @@ +import matplotlib.pyplot as plt +import numpy as np +from sklearn.datasets import make_moons +import warnings + + +def euclidean_distance(q, p): + """ + Calculates the Euclidean distance + between points q and p + + Distance can only be calculated between numeric values + >>> euclidean_distance([1,'a'],[1,2]) + Traceback (most recent call last): + ... + ValueError: Non-numeric input detected + + The dimentions of both the points must be the same + >>> euclidean_distance([1,1,1],[1,2]) + Traceback (most recent call last): + ... + ValueError: expected dimensions to be 2-d, instead got p:3 and q:2 + + Supports only two dimentional points + >>> euclidean_distance([1,1,1],[1,2]) + Traceback (most recent call last): + ... + ValueError: expected dimensions to be 2-d, instead got p:3 and q:2 + + Input should be in the format [x,y] or (x,y) + >>> euclidean_distance(1,2) + Traceback (most recent call last): + ... + TypeError: inputs must be iterable, either list [x,y] or tuple (x,y) + """ + if not hasattr(q, "__iter__") or not hasattr(p, "__iter__"): + raise TypeError("inputs must be iterable, either list [x,y] or tuple (x,y)") + + if isinstance(q, str) or isinstance(p, str): + raise TypeError("inputs cannot be str") + + if len(q) != 2 or len(p) != 2: + raise ValueError( + "expected dimensions to be 2-d, instead got p:{} and q:{}".format( + len(q), len(p) + ) + ) + + for num in q + p: + try: + num = int(num) + except: + raise ValueError("Non-numeric input detected") + + a = pow((q[0] - p[0]), 2) + b = pow((q[1] - p[1]), 2) + return pow((a + b), 0.5) + + +def find_neighbors(db, q, eps): + """ + Finds all points in the db that + are within a distance of eps from Q + + eps value should be a number + >>> find_neighbors({ (1,2):{'label':'undefined'}, (2,3):{'label':'undefined'}}, (2,5),'a') + Traceback (most recent call last): + ... + ValueError: eps should be either int or float + + Q must be a 2-d point as list or tuple + >>> find_neighbors({ (1,2):{'label':'undefined'}, (2,3):{'label':'undefined'}}, 2, 0.5) + Traceback (most recent call last): + ... + TypeError: Q must a 2-dimentional point in the format (x,y) or [x,y] + + Points must be in correct format + >>> find_neighbors([], (2,2) ,0.4) + Traceback (most recent call last): + ... + TypeError: db must be a dict of points in the format {(x,y):{'label':'boolean/undefined'}} + """ + + if not isinstance(eps, (int, float)): + raise ValueError("eps should be either int or float") + + if not hasattr(q, "__iter__"): + raise TypeError("Q must a 2-dimentional point in the format (x,y) or [x,y]") + + if not isinstance(db, dict): + raise TypeError( + "db must be a dict of points in the format {(x,y):{'label':'boolean/undefined'}}" + ) + + return [p for p in db if euclidean_distance(q, p) <= eps] + + +def plot_cluster(db, clusters, ax): + """ + Extracts all the points in the db and puts them together + as seperate clusters and finally plots them + + db cannot be empty + >>> fig, axes = plt.subplots(nrows=1, ncols=2, figsize=(7, 5)) + >>> plot_cluster({},[1,2], axes[1] ) + Traceback (most recent call last): + ... + Exception: db is empty. No points to cluster + + clusters cannot be empty + >>> fig, axes = plt.subplots(nrows=1, ncols=2, figsize=(7, 5)) + >>> plot_cluster({ (1,2):{'label':'undefined'}, (2,3):{'label':'undefined'}},[],axes[1] ) + Traceback (most recent call last): + ... + Exception: nothing to cluster. Empty clusters + + clusters cannot be empty + >>> fig, axes = plt.subplots(nrows=1, ncols=2, figsize=(7, 5)) + >>> plot_cluster({ (1,2):{'label':'undefined'}, (2,3):{'label':'undefined'}},[],axes[1] ) + Traceback (most recent call last): + ... + Exception: nothing to cluster. Empty clusters + + ax must be a plotable + >>> plot_cluster({ (1,2):{'label':'1'}, (2,3):{'label':'2'}},[1,2], [] ) + Traceback (most recent call last): + ... + TypeError: ax must be an slot in a matplotlib figure + """ + if len(db) == 0: + raise Exception("db is empty. No points to cluster") + + if len(clusters) == 0: + raise Exception("nothing to cluster. Empty clusters") + + if not hasattr(ax, "plot"): + raise TypeError("ax must be an slot in a matplotlib figure") + + temp = [] + noise = [] + for i in clusters: + stack = [] + for k, v in db.items(): + if v["label"] == i: + stack.append(k) + elif v["label"] == "noise": + noise.append(k) + temp.append(stack) + + color = iter(plt.cm.rainbow(np.linspace(0, 1, len(clusters)))) + for i in range(0, len(temp)): + c = next(color) + x = [l[0] for l in temp[i]] + y = [l[1] for l in temp[i]] + ax.plot(x, y, "ro", c=c) + + x = [l[0] for l in noise] + y = [l[1] for l in noise] + ax.plot(x, y, "ro", c="0") + + +def dbscan(db, eps, min_pts): + """ + Implementation of the DBSCAN algorithm + + Points must be in correct format + >>> dbscan([], (2,2) ,0.4) + Traceback (most recent call last): + ... + TypeError: db must be a dict of points in the format {(x,y):{'label':'boolean/undefined'}} + + eps value should be a number + >>> dbscan({ (1,2):{'label':'undefined'}, (2,3):{'label':'undefined'}},'a',20 ) + Traceback (most recent call last): + ... + ValueError: eps should be either int or float + + min_pts value should be an integer + >>> dbscan({ (1,2):{'label':'undefined'}, (2,3):{'label':'undefined'}},0.4,20.0 ) + Traceback (most recent call last): + ... + ValueError: min_pts should be int + + db cannot be empty + >>> dbscan({},0.4,20.0 ) + Traceback (most recent call last): + ... + Exception: db is empty, nothing to cluster + + min_pts cannot be negative + >>> dbscan({ (1,2):{'label':'undefined'}, (2,3):{'label':'undefined'}}, 0.4, -20) + Traceback (most recent call last): + ... + ValueError: min_pts or eps cannot be negative + + eps cannot be negative + >>> dbscan({ (1,2):{'label':'undefined'}, (2,3):{'label':'undefined'}},-0.4, 20) + Traceback (most recent call last): + ... + ValueError: min_pts or eps cannot be negative + + """ + if not isinstance(db, dict): + raise TypeError( + "db must be a dict of points in the format {(x,y):{'label':'boolean/undefined'}}" + ) + + if len(db) == 0: + raise Exception("db is empty, nothing to cluster") + + if not isinstance(eps, (int, float)): + raise ValueError("eps should be either int or float") + + if not isinstance(min_pts, int): + raise ValueError("min_pts should be int") + + if min_pts < 0 or eps < 0: + raise ValueError("min_pts or eps cannot be negative") + + if min_pts == 0: + warnings.warn("min_pts is 0. Are you sure you want this ?") + + if eps == 0: + warnings.warn("eps is 0. Are you sure you want this ?") + + clusters = [] + c = 0 + for p in db: + if db[p]["label"] != "undefined": + continue + neighbors = find_neighbors(db, p, eps) + if len(neighbors) < min_pts: + db[p]["label"] = "noise" + continue + c += 1 + clusters.append(c) + db[p]["label"] = c + neighbors.remove(p) + seed_set = neighbors.copy() + while seed_set != []: + q = seed_set.pop(0) + if db[q]["label"] == "noise": + db[q]["label"] = c + if db[q]["label"] != "undefined": + continue + db[q]["label"] = c + neighbors_n = find_neighbors(db, q, eps) + if len(neighbors_n) >= min_pts: + seed_set = seed_set + neighbors_n + return db, clusters + + +if __name__ == "__main__": + + fig, axes = plt.subplots(nrows=1, ncols=2, figsize=(7, 5)) + + x, label = make_moons(n_samples=200, noise=0.1, random_state=19) + + axes[0].plot(x[:, 0], x[:, 1], "ro") + + points = {(point[0], point[1]): {"label": "undefined"} for point in x} + + eps = 0.25 + + min_pts = 12 + + db, clusters = dbscan(points, eps, min_pts) + + plot_cluster(db, clusters, axes[1]) + + plt.show() From 189b35031224e78e10b668e19fc4b2a1966d19c1 Mon Sep 17 00:00:00 2001 From: yijoonsu <44707391+paulo9428@users.noreply.github.com> Date: Mon, 30 Sep 2019 23:27:41 +0900 Subject: [PATCH 0222/1071] Deque (#1200) * deque add pop * deque add remove --- data_structures/queue/double_ended_queue.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/data_structures/queue/double_ended_queue.py b/data_structures/queue/double_ended_queue.py index a2fc8f66ec22..a3cfa7230710 100644 --- a/data_structures/queue/double_ended_queue.py +++ b/data_structures/queue/double_ended_queue.py @@ -37,3 +37,21 @@ # printing modified deque print("The deque after reversing deque is : ") print(de) + +# get right-end value and eliminate +startValue = de.pop() + +print("The deque after popping value at end is : ") +print(de) + +# get left-end value and eliminate +endValue = de.popleft() + +print("The deque after popping value at start is : ") +print(de) + +# eliminate element searched by value +de.remove(5) + +print("The deque after eliminating element searched by value : ") +print(de) From b738281f2b0cf44257e50b4f21ad74c8cbb1714e Mon Sep 17 00:00:00 2001 From: Shoaib Asgar Date: Tue, 1 Oct 2019 12:28:00 +0530 Subject: [PATCH 0223/1071] maths-polynomial_evalutation (#1214) * maths-polynomial_evalutation * added doctest and removed redundancy --- maths/polynomial_evaluation.py | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 maths/polynomial_evaluation.py diff --git a/maths/polynomial_evaluation.py b/maths/polynomial_evaluation.py new file mode 100644 index 000000000000..b4f18b9fa106 --- /dev/null +++ b/maths/polynomial_evaluation.py @@ -0,0 +1,25 @@ +def evaluate_poly(poly, x): + """ + Objective: Computes the polynomial function for a given value x. + Returns that value. + Input Prams: + poly: tuple of numbers - value of cofficients + x: value for x in f(x) + Return: value of f(x) + + >>> evaluate_poly((0.0, 0.0, 5.0, 9.3, 7.0), 10) + 79800.0 + """ + + return sum(c*(x**i) for i, c in enumerate(poly)) + + +if __name__ == "__main__": + """ + Example: poly = (0.0, 0.0, 5.0, 9.3, 7.0) # f(x) = 7.0x^4 + 9.3x^3 + 5.0x^2 + x = -13 + print (evaluate_poly(poly, x)) # f(-13) = 7.0(-13)^4 + 9.3(-13)^3 + 5.0(-13)^2 = 180339.9 + """ + poly = (0.0, 0.0, 5.0, 9.3, 7.0) + x = 10 + print(evaluate_poly(poly, x)) From df44d1b703d756f965f0085ac05e91810b8e9c21 Mon Sep 17 00:00:00 2001 From: Christian Clauss Date: Wed, 2 Oct 2019 18:19:00 +0200 Subject: [PATCH 0224/1071] Update CONTRIBUTING.md (#1250) * Update CONTRIBUTING.md * Update CONTRIBUTING.md * Add Python type hints and mypy --- CONTRIBUTING.md | 88 ++++++++++++++++++++++++++++--------------------- 1 file changed, 51 insertions(+), 37 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 8c0f54ad528d..8cd03217d51f 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -23,26 +23,38 @@ We are very happy that you consider implementing algorithms and data structure f We appreciate any contribution, from fixing a grammar mistake in a comment to implementing complex algorithms. Please read this section if you are contributing your work. +Your contribution will be tested by our [automated testing on Travis CI](https://travis-ci.org/TheAlgorithms/Python/pull_requests) to save time and mental energy. After you have submitted your pull request, you should see the Travis tests start to run at the bottom of your submission page. If those tests fail, then click on the ___details___ button try to read through the Travis output to understand the failure. If you do not understand, please leave a comment on your submission page and a community member will try to help. + #### Coding Style We want your work to be readable by others; therefore, we encourage you to note the following: -- Please write in Python 3.x. -- Please consider running [__python/black__](https://github.com/python/black) on your Python file(s) before submitting your pull request. This is not a requirement but it does make your code more readable. There are other code formatters (autopep8, yapf) but the __black__ style is now the recommendation of the Python core team. To use it, +- Please write in Python 3.7+. __print()__ is a function in Python 3 so __print "Hello"__ will _not_ work but __print("Hello")__ will. + +- Please focus hard on naming of functions, classes, and variables. Help your reader by using __descriptive names__ that can help you to remove redundant comments. + - Single letter variable names are _old school_ so please avoid them unless their life only spans a few lines. + - Expand acronyms because __gcd()__ is hard to understand but __greatest_common_divisor()__ is not. + - Please follow the [Python Naming Conventions](https://pep8.org/#prescriptive-naming-conventions) so variable_names and function_names should be lower_case, CONSTANTS in UPPERCASE, ClassNames should be CamelCase, etc. + +- We encourage the use of Python [f-strings](https://realpython.com/python-f-strings/#f-strings-a-new-and-improved-way-to-format-strings-in-python) where the make the code easier to read. + +- Please consider running [__psf/black__](https://github.com/python/black) on your Python file(s) before submitting your pull request. This is not yet a requirement but it does make your code more readable and automatically aligns it with much of [PEP 8](https://www.python.org/dev/peps/pep-0008/). There are other code formatters (autopep8, yapf) but the __black__ style is now the recommendation of the Python Core Team. To use it, ```bash pip3 install black # only required the first time - black my-submission.py + black . ``` - All submissions will need to pass the test __flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics__ before they will be accepted so if possible, try this test locally on your Python file(s) before submitting your pull request. + ```bash + pip3 install flake8 # only required the first time + flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics + ``` -- If you know [PEP 8](https://www.python.org/dev/peps/pep-0008/) already, you will have no problem in coding style, though we do not follow it strictly. Read the remaining section and have fun coding! - -- Always use 4 spaces to indent. +- Original code submission require docstrings or comments to describe your work. -- Original code submission requires comments to describe your work. +- More on docstrings and comments: -- More on comments and docstrings: + If you are using a Wikipedia article or some other source material to create your algorithm, please add the URL in a docstring or comment to help your reader. The following are considered to be bad and may be requested to be improved: @@ -52,34 +64,40 @@ We want your work to be readable by others; therefore, we encourage you to note This is too trivial. Comments are expected to be explanatory. For comments, you can write them above, on or below a line of code, as long as you are consistent within the same piece of code. - *Sometimes, docstrings are avoided.* This will happen if you are using some editors and not careful with indentation: + We encourage you to put docstrings inside your functions but please pay attention to indentation of docstrings. The following is acceptable in this case: ```python + def sumab(a, b): + """ + This function returns the sum of two integers a and b + Return: a + b """ - This function sums a and b - """ - def sum(a, b): return a + b ``` - However, if you insist to use docstrings, we encourage you to put docstrings inside functions. Also, please pay attention to indentation to docstrings. The following is acceptable in this case: +- Write tests (especially [__doctests__](https://docs.python.org/3/library/doctest.html)) to illustrate and verify your work. We highly encourage the use of _doctests on all functions_. ```python def sumab(a, b): """ - This function sums two integers a and b - Return: a + b + This function returns the sum of two integers a and b + Return: a + b + >>> sum(2, 2) + 4 + >>> sum(-2, 3) + 1 + >>> sum(4.9, 6.1) + 10.0 """ return a + b ``` -- `lambda`, `map`, `filter`, `reduce` and complicated list comprehension are welcome and acceptable to demonstrate the power of Python, as long as they are simple enough to read. - - - This is arguable: **write comments** and assign appropriate variable names, so that the code is easy to read! - -- Write tests to illustrate your work. + These doctests will be run by pytest as part of our automated testing so please try to run your doctests locally and make sure that they are found and pass: + ```bash + python3 -m doctest -v my_submission.py + ``` - The following "testing" approaches are **not** encouraged: + The use of the Python builtin __input()__ function is **not** encouraged: ```python input('Enter your input:') @@ -87,34 +105,31 @@ We want your work to be readable by others; therefore, we encourage you to note input = eval(input("Enter your input: ")) ``` - However, if your code uses __input()__ then we encourage you to gracefully deal with leading and trailing whitespace in user input by adding __.strip()__ to the end as in: + However, if your code uses __input()__ then we encourage you to gracefully deal with leading and trailing whitespace in user input by adding __.strip()__ as in: ```python starting_value = int(input("Please enter a starting value: ").strip()) ``` - - Please write down your test case, like the following: - - ```python - def sumab(a, b): - return a + b - # Write tests this way: - print(sumab(1, 2)) # 1+2 = 3 - print(sumab(6, 4)) # 6+4 = 10 - # Or this way: - print("1 + 2 = ", sumab(1, 2)) # 1+2 = 3 - print("6 + 4 = ", sumab(6, 4)) # 6+4 = 10 + + The use of [Python type hints](https://docs.python.org/3/library/typing.html) is encouraged for function parameters and return values. Our automated testing will run [mypy](http://mypy-lang.org) so run that locally before making your submission. +```python +def sumab(a: int, b: int) --> int: + pass ``` - Better yet, if you know how to write [__doctests__](https://docs.python.org/3/library/doctest.html), please consider adding them. +- [__list comprehensions and generators__](https://docs.python.org/3/tutorial/datastructures.html#list-comprehensions) are preferred over the use of `lambda`, `map`, `filter`, `reduce` but the important thing is to demonstrate the power of Python in code that is easy to read and maintain. - Avoid importing external libraries for basic algorithms. Only use those libraries for complicated algorithms. +- If you need a third party module that is not in the file __requirements.txt__, please add it to that file as part of your submission. + #### Other Standard While Submitting Your Work - File extension for code should be `.py`. Jupiter notebook files are acceptable in machine learning algorithms. -- Strictly use snake case (underscore separated) in your file name, as it will be easy to parse in future using scripts. +- Please avoid creating new directories if at all possible. Try to fit your work into the existing directory structue. + +- Strictly use snake_case (underscore_separated) in your file_name, as it will be easy to parse in future using scripts. If possible, follow the standard *within* the folder you are submitting to. @@ -135,5 +150,4 @@ We want your work to be readable by others; therefore, we encourage you to note - Happy coding! - Writer [@poyea](https://github.com/poyea), Jun 2019. From b8490ed097d8baab3234c54f835b8a7a55454e52 Mon Sep 17 00:00:00 2001 From: Anup Kumar Panwar <1anuppanwar@gmail.com> Date: Thu, 3 Oct 2019 12:47:22 +0530 Subject: [PATCH 0225/1071] Removed owners from README (#1254) --- README.md | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/README.md b/README.md index d4f4acbadb6d..a5af46ad8505 100644 --- a/README.md +++ b/README.md @@ -10,18 +10,6 @@ These implementations are for learning purposes. They may be less efficient than the implementations in the Python standard library. -## Owners - -Anup Kumar Panwar -  [[Gmail](mailto:1anuppanwar@gmail.com?Subject=The%20Algorithms%20-%20Python) -  [GitHub](https://github.com/anupkumarpanwar) -  [LinkedIn](https://www.linkedin.com/in/anupkumarpanwar/)] - -Chetan Kaushik -  [[Gmail](mailto:dynamitechetan@gmail.com?Subject=The%20Algorithms%20-%20Python) -  [GitHub](https://github.com/dynamitechetan) -  [LinkedIn](https://www.linkedin.com/in/chetankaushik/)] - ## Contribution Guidelines Read our [Contribution Guidelines](CONTRIBUTING.md) before you contribute. From 390feb0b23b4845a7ddfec7afd703aa053d1b224 Mon Sep 17 00:00:00 2001 From: Parth Paradkar Date: Thu, 3 Oct 2019 13:49:11 +0530 Subject: [PATCH 0226/1071] Add doctests for sorting algorithms (#1263) * doctests and intro docstring added * doctests, docstrings and check for empty collection added * Intro docstring added * python versions reversed --- sorts/pancake_sort.py | 28 +++++++++++++++++++++++----- sorts/pigeon_sort.py | 26 +++++++++++++++++++++++--- 2 files changed, 46 insertions(+), 8 deletions(-) diff --git a/sorts/pancake_sort.py b/sorts/pancake_sort.py index 3b48bc6e46d9..873c14a0a174 100644 --- a/sorts/pancake_sort.py +++ b/sorts/pancake_sort.py @@ -1,9 +1,25 @@ -"""Pancake Sort Algorithm.""" -# Only can reverse array from 0 to i - +""" +This is a pure python implementation of the pancake sort algorithm +For doctests run following command: +python3 -m doctest -v pancake_sort.py +or +python -m doctest -v pancake_sort.py +For manual testing run: +python pancake_sort.py +""" def pancake_sort(arr): - """Sort Array with Pancake Sort.""" + """Sort Array with Pancake Sort. + :param arr: Collection containing comparable items + :return: Collection ordered in ascending order of items + Examples: + >>> pancake_sort([0, 5, 3, 2, 2]) + [0, 2, 2, 3, 5] + >>> pancake_sort([]) + [] + >>> pancake_sort([-2, -5, -45]) + [-45, -5, -2] + """ cur = len(arr) while cur > 1: # Find the maximum number in arr @@ -17,4 +33,6 @@ def pancake_sort(arr): if __name__ == '__main__': - print(pancake_sort([0, 10, 15, 3, 2, 9, 14, 13])) + user_input = input('Enter numbers separated by a comma:\n').strip() + unsorted = [int(item) for item in user_input.split(',')] + print(pancake_sort(unsorted)) diff --git a/sorts/pigeon_sort.py b/sorts/pigeon_sort.py index 5e5afa137685..5417234d331b 100644 --- a/sorts/pigeon_sort.py +++ b/sorts/pigeon_sort.py @@ -1,7 +1,29 @@ ''' This is an implementation of Pigeon Hole Sort. + For doctests run following command: + + python3 -m doctest -v pigeon_sort.py + or + python -m doctest -v pigeon_sort.py + + For manual testing run: + python pigeon_sort.py ''' def pigeon_sort(array): + """ + Implementation of pigeon hole sort algorithm + :param array: Collection of comparable items + :return: Collection sorted in ascending order + >>> pigeon_sort([0, 5, 3, 2, 2]) + [0, 2, 2, 3, 5] + >>> pigeon_sort([]) + [] + >>> pigeon_sort([-2, -5, -45]) + [-45, -5, -2] + """ + if(len(array) == 0): + return array + # Manually finds the minimum and maximum of the array. min = array[0] max = array[0] @@ -37,6 +59,4 @@ def pigeon_sort(array): if __name__ == '__main__': user_input = input('Enter numbers separated by comma:\n') unsorted = [int(x) for x in user_input.split(',')] - sorted = pigeon_sort(unsorted) - - print(sorted) + print(pigeon_sort(unsorted)) From 0e333ae02193fca1046fcd65ff5202eb054cc04e Mon Sep 17 00:00:00 2001 From: William Zhang <39932068+WilliamHYZhang@users.noreply.github.com> Date: Thu, 3 Oct 2019 05:17:30 -0400 Subject: [PATCH 0227/1071] added bogobogosort (#1258) * added bogobogosort * fix indentation error --- sorts/bogo_bogo_sort.py | 54 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) create mode 100644 sorts/bogo_bogo_sort.py diff --git a/sorts/bogo_bogo_sort.py b/sorts/bogo_bogo_sort.py new file mode 100644 index 000000000000..f26a46e78645 --- /dev/null +++ b/sorts/bogo_bogo_sort.py @@ -0,0 +1,54 @@ +""" +Python implementation of bogobogosort, a "sorting algorithm +designed not to succeed before the heat death of the universe +on any sizable list" - https://en.wikipedia.org/wiki/Bogosort. + +Author: WilliamHYZhang +""" + +import random + + +def bogo_bogo_sort(collection): + """ + returns the collection sorted in ascending order + :param collection: list of comparable items + :return: the list sorted in ascending order + + Examples: + >>> bogo_bogo_sort([0, 5, 3, 2, 2]) + [0, 2, 2, 3, 5] + >>> bogo_bogo_sort([-2, -5, -45]) + [-45, -5, -2] + >>> bogo_bogo_sort([420, 69]) + [69, 420] + """ + + def is_sorted(collection): + if len(collection) == 1: + return True + + clone = collection.copy() + while True: + random.shuffle(clone) + ordered = bogo_bogo_sort(clone[:-1]) + if clone[len(clone) - 1] >= max(ordered): + break + + for i in range(len(ordered)): + clone[i] = ordered[i] + + for i in range(len(collection)): + if clone[i] != collection[i]: + return False + return True + + while not is_sorted(collection): + random.shuffle(collection) + return collection + + +if __name__ == "__main__": + user_input = input("Enter numbers separated by a comma:\n").strip() + unsorted = [int(item) for item in user_input.split(",")] + print(bogo_bogo_sort(unsorted)) From 0e2d6b2963deff0a47c97c2b41deb69aed254350 Mon Sep 17 00:00:00 2001 From: Kaushik Amar Das Date: Thu, 3 Oct 2019 20:00:36 +0530 Subject: [PATCH 0228/1071] adding softmax function (#1267) * adding softmax function * wraped lines as asked --- maths/softmax.py | 56 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) create mode 100644 maths/softmax.py diff --git a/maths/softmax.py b/maths/softmax.py new file mode 100644 index 000000000000..92ff4ca27b88 --- /dev/null +++ b/maths/softmax.py @@ -0,0 +1,56 @@ +""" +This script demonstrates the implementation of the Softmax function. + +Its a function that takes as input a vector of K real numbers, and normalizes +it into a probability distribution consisting of K probabilities proportional +to the exponentials of the input numbers. After softmax, the elements of the +vector always sum up to 1. + +Script inspired from its corresponding Wikipedia article +https://en.wikipedia.org/wiki/Softmax_function +""" + +import numpy as np + + +def softmax(vector): + """ + Implements the softmax function + + Parameters: + vector (np.array,list,tuple): A numpy array of shape (1,n) + consisting of real values or a similar list,tuple + + + Returns: + softmax_vec (np.array): The input numpy array after applying + softmax. + + The softmax vector adds up to one. We need to ceil to mitigate for + precision + >>> np.ceil(np.sum(softmax([1,2,3,4]))) + 1.0 + + >>> vec = np.array([5,5]) + >>> softmax(vec) + array([0.5, 0.5]) + + >>> softmax([0]) + array([1.]) + """ + + # Calculate e^x for each x in your vector where e is Euler's + # number (approximately 2.718) + exponentVector = np.exp(vector) + + # Add up the all the exponentials + sumOfExponents = np.sum(exponentVector) + + # Divide every exponent by the sum of all exponents + softmax_vector = exponentVector / sumOfExponents + + return softmax_vector + + +if __name__ == "__main__": + print(softmax((0,))) From 03aba96c0a28cf69e827b85dac1a463c74930530 Mon Sep 17 00:00:00 2001 From: Shubham garg <42842217+shubhamgarg2000@users.noreply.github.com> Date: Fri, 4 Oct 2019 01:01:11 +0530 Subject: [PATCH 0229/1071] added defination (#1244) --- arithmetic_analysis/newton_raphson_method.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/arithmetic_analysis/newton_raphson_method.py b/arithmetic_analysis/newton_raphson_method.py index bb6fdd2193ec..d17b57a2e670 100644 --- a/arithmetic_analysis/newton_raphson_method.py +++ b/arithmetic_analysis/newton_raphson_method.py @@ -1,6 +1,7 @@ # Implementing Newton Raphson method in Python # Author: Syed Haseeb Shah (github.com/QuantumNovice) - +#The Newton-Raphson method (also known as Newton's method) is a way to +#quickly find a good approximation for the root of a real-valued function from sympy import diff from decimal import Decimal From f970c730611ea1c4679b5db1708c2f2f2cb8dabc Mon Sep 17 00:00:00 2001 From: Simon Landry Date: Thu, 3 Oct 2019 16:08:25 -0400 Subject: [PATCH 0230/1071] Add problem 23 solution (#1261) --- project_euler/problem_23/sol1.py | 51 ++++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) create mode 100644 project_euler/problem_23/sol1.py diff --git a/project_euler/problem_23/sol1.py b/project_euler/problem_23/sol1.py new file mode 100644 index 000000000000..e76be053040f --- /dev/null +++ b/project_euler/problem_23/sol1.py @@ -0,0 +1,51 @@ +""" +A perfect number is a number for which the sum of its proper divisors is exactly +equal to the number. For example, the sum of the proper divisors of 28 would be +1 + 2 + 4 + 7 + 14 = 28, which means that 28 is a perfect number. + +A number n is called deficient if the sum of its proper divisors is less than n +and it is called abundant if this sum exceeds n. + +As 12 is the smallest abundant number, 1 + 2 + 3 + 4 + 6 = 16, the smallest +number that can be written as the sum of two abundant numbers is 24. By +mathematical analysis, it can be shown that all integers greater than 28123 +can be written as the sum of two abundant numbers. However, this upper limit +cannot be reduced any further by analysis even though it is known that the +greatest number that cannot be expressed as the sum of two abundant numbers +is less than this limit. + +Find the sum of all the positive integers which cannot be written as the sum +of two abundant numbers. +""" + +def solution(limit = 28123): + """ + Finds the sum of all the positive integers which cannot be written as + the sum of two abundant numbers + as described by the statement above. + + >>> solution() + 4179871 + """ + sumDivs = [1] * (limit + 1) + + for i in range(2, int(limit ** 0.5) + 1): + sumDivs[i * i] += i + for k in range(i + 1, limit // i + 1): + sumDivs[k * i] += k + i + + abundants = set() + res = 0 + + for n in range(1, limit + 1): + if sumDivs[n] > n: + abundants.add(n) + + if not any((n - a in abundants) for a in abundants): + res+=n + + return res + + +if __name__ == "__main__": + print(solution()) From d28fc7120281d726741e3001d5750bd737a38c59 Mon Sep 17 00:00:00 2001 From: Simon Landry Date: Thu, 3 Oct 2019 16:47:08 -0400 Subject: [PATCH 0231/1071] Add problem18 solution (#1260) --- project_euler/problem_18/solution.py | 64 +++++++++++++++++++++++++++ project_euler/problem_18/triangle.txt | 15 +++++++ 2 files changed, 79 insertions(+) create mode 100644 project_euler/problem_18/solution.py create mode 100644 project_euler/problem_18/triangle.txt diff --git a/project_euler/problem_18/solution.py b/project_euler/problem_18/solution.py new file mode 100644 index 000000000000..f9762e8b0176 --- /dev/null +++ b/project_euler/problem_18/solution.py @@ -0,0 +1,64 @@ +""" +By starting at the top of the triangle below and moving to adjacent numbers on +the row below, the maximum total from top to bottom is 23. + +3 +7 4 +2 4 6 +8 5 9 3 + +That is, 3 + 7 + 4 + 9 = 23. + +Find the maximum total from top to bottom of the triangle below: + +75 +95 64 +17 47 82 +18 35 87 10 +20 04 82 47 65 +19 01 23 75 03 34 +88 02 77 73 07 63 67 +99 65 04 28 06 16 70 92 +41 41 26 56 83 40 80 70 33 +41 48 72 33 47 32 37 16 94 29 +53 71 44 65 25 43 91 52 97 51 14 +70 11 33 28 77 73 17 78 39 68 17 57 +91 71 52 38 17 14 91 43 58 50 27 29 48 +63 66 04 68 89 53 67 30 73 16 69 87 40 31 +04 62 98 27 23 09 70 98 73 93 38 53 60 04 23 +""" +import os + + +def solution(): + """ + Finds the maximum total in a triangle as described by the problem statement + above. + + >>> solution() + 1074 + """ + script_dir = os.path.dirname(os.path.realpath(__file__)) + triangle = os.path.join(script_dir, 'triangle.txt') + + with open(triangle, 'r') as f: + triangle = f.readlines() + + a = [[int(y) for y in x.rstrip('\r\n').split(' ')] for x in triangle] + + for i in range(1, len(a)): + for j in range(len(a[i])): + if j != len(a[i - 1]): + number1 = a[i - 1][j] + else: + number1 = 0 + if j > 0: + number2 = a[i - 1][j - 1] + else: + number2 = 0 + a[i][j] += max(number1, number2) + return max(a[-1]) + + +if __name__ == "__main__": + print(solution()) diff --git a/project_euler/problem_18/triangle.txt b/project_euler/problem_18/triangle.txt new file mode 100644 index 000000000000..e236c2ff7ee2 --- /dev/null +++ b/project_euler/problem_18/triangle.txt @@ -0,0 +1,15 @@ +75 +95 64 +17 47 82 +18 35 87 10 +20 04 82 47 65 +19 01 23 75 03 34 +88 02 77 73 07 63 67 +99 65 04 28 06 16 70 92 +41 41 26 56 83 40 80 70 33 +41 48 72 33 47 32 37 16 94 29 +53 71 44 65 25 43 91 52 97 51 14 +70 11 33 28 77 73 17 78 39 68 17 57 +91 71 52 38 17 14 91 43 58 50 27 29 48 +63 66 04 68 89 53 67 30 73 16 69 87 40 31 +04 62 98 27 23 09 70 98 73 93 38 53 60 04 23 From 6e6920866662f314a9cbb125667c64e421046a4b Mon Sep 17 00:00:00 2001 From: Simon Landry Date: Thu, 3 Oct 2019 16:47:39 -0400 Subject: [PATCH 0232/1071] Add problem 32 solution (#1257) --- project_euler/problem_32/solution.py | 62 ++++++++++++++++++++++++++++ 1 file changed, 62 insertions(+) create mode 100644 project_euler/problem_32/solution.py diff --git a/project_euler/problem_32/solution.py b/project_euler/problem_32/solution.py new file mode 100644 index 000000000000..fd5178303de3 --- /dev/null +++ b/project_euler/problem_32/solution.py @@ -0,0 +1,62 @@ +""" +We shall say that an n-digit number is pandigital if it makes use of all the +digits 1 to n exactly once; for example, the 5-digit number, 15234, is 1 through +5 pandigital. + +The product 7254 is unusual, as the identity, 39 × 186 = 7254, containing +multiplicand, multiplier, and product is 1 through 9 pandigital. + +Find the sum of all products whose multiplicand/multiplier/product identity can +be written as a 1 through 9 pandigital. + +HINT: Some products can be obtained in more than one way so be sure to only +include it once in your sum. +""" +import itertools + + +def isCombinationValid(combination): + """ + Checks if a combination (a tuple of 9 digits) + is a valid product equation. + + >>> isCombinationValid(('3', '9', '1', '8', '6', '7', '2', '5', '4')) + True + + >>> isCombinationValid(('1', '2', '3', '4', '5', '6', '7', '8', '9')) + False + + """ + return ( + int(''.join(combination[0:2])) * + int(''.join(combination[2:5])) == + int(''.join(combination[5:9])) + ) or ( + int(''.join(combination[0])) * + int(''.join(combination[1:5])) == + int(''.join(combination[5:9])) + ) + + +def solution(): + """ + Finds the sum of all products whose multiplicand/multiplier/product identity + can be written as a 1 through 9 pandigital + + >>> solution() + 45228 + """ + + return sum( + set( + [ + int(''.join(pandigital[5:9])) + for pandigital + in itertools.permutations('123456789') + if isCombinationValid(pandigital) + ] + ) + ) + +if __name__ == "__main__": + print(solution()) From 309204a5813cae19e2397f5aeb27fefb7dfac514 Mon Sep 17 00:00:00 2001 From: Simon Landry Date: Thu, 3 Oct 2019 16:48:53 -0400 Subject: [PATCH 0233/1071] Add problem 42 solution (#1259) --- project_euler/problem_42/solution.py | 50 ++++++++++++++++++++++++++++ project_euler/problem_42/words.txt | 1 + 2 files changed, 51 insertions(+) create mode 100644 project_euler/problem_42/solution.py create mode 100644 project_euler/problem_42/words.txt diff --git a/project_euler/problem_42/solution.py b/project_euler/problem_42/solution.py new file mode 100644 index 000000000000..ff976545055d --- /dev/null +++ b/project_euler/problem_42/solution.py @@ -0,0 +1,50 @@ +""" +The nth term of the sequence of triangle numbers is given by, tn = ½n(n+1); so +the first ten triangle numbers are: + +1, 3, 6, 10, 15, 21, 28, 36, 45, 55, ... + +By converting each letter in a word to a number corresponding to its +alphabetical position and adding these values we form a word value. For example, +the word value for SKY is 19 + 11 + 25 = 55 = t10. If the word value is a +triangle number then we shall call the word a triangle word. + +Using words.txt (right click and 'Save Link/Target As...'), a 16K text file +containing nearly two-thousand common English words, how many are triangle +words? +""" +import os + + +# Precomputes a list of the 100 first triangular numbers +TRIANGULAR_NUMBERS = [int(0.5 * n * (n + 1)) for n in range(1, 101)] + + +def solution(): + """ + Finds the amount of triangular words in the words file. + + >>> solution() + 162 + """ + script_dir = os.path.dirname(os.path.realpath(__file__)) + wordsFilePath = os.path.join(script_dir, 'words.txt') + + words = '' + with open(wordsFilePath, 'r') as f: + words = f.readline() + + words = list(map(lambda word: word.strip('"'), words.strip('\r\n').split(','))) + words = list( + filter( + lambda word: word in TRIANGULAR_NUMBERS, + map( + lambda word: sum(map(lambda x: ord(x) - 64, word)), + words + ) + ) + ) + return len(words) + +if __name__ == '__main__': + print(solution()) diff --git a/project_euler/problem_42/words.txt b/project_euler/problem_42/words.txt new file mode 100644 index 000000000000..af3aeb42f151 --- /dev/null +++ b/project_euler/problem_42/words.txt @@ -0,0 +1 @@ +"A","ABILITY","ABLE","ABOUT","ABOVE","ABSENCE","ABSOLUTELY","ACADEMIC","ACCEPT","ACCESS","ACCIDENT","ACCOMPANY","ACCORDING","ACCOUNT","ACHIEVE","ACHIEVEMENT","ACID","ACQUIRE","ACROSS","ACT","ACTION","ACTIVE","ACTIVITY","ACTUAL","ACTUALLY","ADD","ADDITION","ADDITIONAL","ADDRESS","ADMINISTRATION","ADMIT","ADOPT","ADULT","ADVANCE","ADVANTAGE","ADVICE","ADVISE","AFFAIR","AFFECT","AFFORD","AFRAID","AFTER","AFTERNOON","AFTERWARDS","AGAIN","AGAINST","AGE","AGENCY","AGENT","AGO","AGREE","AGREEMENT","AHEAD","AID","AIM","AIR","AIRCRAFT","ALL","ALLOW","ALMOST","ALONE","ALONG","ALREADY","ALRIGHT","ALSO","ALTERNATIVE","ALTHOUGH","ALWAYS","AMONG","AMONGST","AMOUNT","AN","ANALYSIS","ANCIENT","AND","ANIMAL","ANNOUNCE","ANNUAL","ANOTHER","ANSWER","ANY","ANYBODY","ANYONE","ANYTHING","ANYWAY","APART","APPARENT","APPARENTLY","APPEAL","APPEAR","APPEARANCE","APPLICATION","APPLY","APPOINT","APPOINTMENT","APPROACH","APPROPRIATE","APPROVE","AREA","ARGUE","ARGUMENT","ARISE","ARM","ARMY","AROUND","ARRANGE","ARRANGEMENT","ARRIVE","ART","ARTICLE","ARTIST","AS","ASK","ASPECT","ASSEMBLY","ASSESS","ASSESSMENT","ASSET","ASSOCIATE","ASSOCIATION","ASSUME","ASSUMPTION","AT","ATMOSPHERE","ATTACH","ATTACK","ATTEMPT","ATTEND","ATTENTION","ATTITUDE","ATTRACT","ATTRACTIVE","AUDIENCE","AUTHOR","AUTHORITY","AVAILABLE","AVERAGE","AVOID","AWARD","AWARE","AWAY","AYE","BABY","BACK","BACKGROUND","BAD","BAG","BALANCE","BALL","BAND","BANK","BAR","BASE","BASIC","BASIS","BATTLE","BE","BEAR","BEAT","BEAUTIFUL","BECAUSE","BECOME","BED","BEDROOM","BEFORE","BEGIN","BEGINNING","BEHAVIOUR","BEHIND","BELIEF","BELIEVE","BELONG","BELOW","BENEATH","BENEFIT","BESIDE","BEST","BETTER","BETWEEN","BEYOND","BIG","BILL","BIND","BIRD","BIRTH","BIT","BLACK","BLOCK","BLOOD","BLOODY","BLOW","BLUE","BOARD","BOAT","BODY","BONE","BOOK","BORDER","BOTH","BOTTLE","BOTTOM","BOX","BOY","BRAIN","BRANCH","BREAK","BREATH","BRIDGE","BRIEF","BRIGHT","BRING","BROAD","BROTHER","BUDGET","BUILD","BUILDING","BURN","BUS","BUSINESS","BUSY","BUT","BUY","BY","CABINET","CALL","CAMPAIGN","CAN","CANDIDATE","CAPABLE","CAPACITY","CAPITAL","CAR","CARD","CARE","CAREER","CAREFUL","CAREFULLY","CARRY","CASE","CASH","CAT","CATCH","CATEGORY","CAUSE","CELL","CENTRAL","CENTRE","CENTURY","CERTAIN","CERTAINLY","CHAIN","CHAIR","CHAIRMAN","CHALLENGE","CHANCE","CHANGE","CHANNEL","CHAPTER","CHARACTER","CHARACTERISTIC","CHARGE","CHEAP","CHECK","CHEMICAL","CHIEF","CHILD","CHOICE","CHOOSE","CHURCH","CIRCLE","CIRCUMSTANCE","CITIZEN","CITY","CIVIL","CLAIM","CLASS","CLEAN","CLEAR","CLEARLY","CLIENT","CLIMB","CLOSE","CLOSELY","CLOTHES","CLUB","COAL","CODE","COFFEE","COLD","COLLEAGUE","COLLECT","COLLECTION","COLLEGE","COLOUR","COMBINATION","COMBINE","COME","COMMENT","COMMERCIAL","COMMISSION","COMMIT","COMMITMENT","COMMITTEE","COMMON","COMMUNICATION","COMMUNITY","COMPANY","COMPARE","COMPARISON","COMPETITION","COMPLETE","COMPLETELY","COMPLEX","COMPONENT","COMPUTER","CONCENTRATE","CONCENTRATION","CONCEPT","CONCERN","CONCERNED","CONCLUDE","CONCLUSION","CONDITION","CONDUCT","CONFERENCE","CONFIDENCE","CONFIRM","CONFLICT","CONGRESS","CONNECT","CONNECTION","CONSEQUENCE","CONSERVATIVE","CONSIDER","CONSIDERABLE","CONSIDERATION","CONSIST","CONSTANT","CONSTRUCTION","CONSUMER","CONTACT","CONTAIN","CONTENT","CONTEXT","CONTINUE","CONTRACT","CONTRAST","CONTRIBUTE","CONTRIBUTION","CONTROL","CONVENTION","CONVERSATION","COPY","CORNER","CORPORATE","CORRECT","COS","COST","COULD","COUNCIL","COUNT","COUNTRY","COUNTY","COUPLE","COURSE","COURT","COVER","CREATE","CREATION","CREDIT","CRIME","CRIMINAL","CRISIS","CRITERION","CRITICAL","CRITICISM","CROSS","CROWD","CRY","CULTURAL","CULTURE","CUP","CURRENT","CURRENTLY","CURRICULUM","CUSTOMER","CUT","DAMAGE","DANGER","DANGEROUS","DARK","DATA","DATE","DAUGHTER","DAY","DEAD","DEAL","DEATH","DEBATE","DEBT","DECADE","DECIDE","DECISION","DECLARE","DEEP","DEFENCE","DEFENDANT","DEFINE","DEFINITION","DEGREE","DELIVER","DEMAND","DEMOCRATIC","DEMONSTRATE","DENY","DEPARTMENT","DEPEND","DEPUTY","DERIVE","DESCRIBE","DESCRIPTION","DESIGN","DESIRE","DESK","DESPITE","DESTROY","DETAIL","DETAILED","DETERMINE","DEVELOP","DEVELOPMENT","DEVICE","DIE","DIFFERENCE","DIFFERENT","DIFFICULT","DIFFICULTY","DINNER","DIRECT","DIRECTION","DIRECTLY","DIRECTOR","DISAPPEAR","DISCIPLINE","DISCOVER","DISCUSS","DISCUSSION","DISEASE","DISPLAY","DISTANCE","DISTINCTION","DISTRIBUTION","DISTRICT","DIVIDE","DIVISION","DO","DOCTOR","DOCUMENT","DOG","DOMESTIC","DOOR","DOUBLE","DOUBT","DOWN","DRAW","DRAWING","DREAM","DRESS","DRINK","DRIVE","DRIVER","DROP","DRUG","DRY","DUE","DURING","DUTY","EACH","EAR","EARLY","EARN","EARTH","EASILY","EAST","EASY","EAT","ECONOMIC","ECONOMY","EDGE","EDITOR","EDUCATION","EDUCATIONAL","EFFECT","EFFECTIVE","EFFECTIVELY","EFFORT","EGG","EITHER","ELDERLY","ELECTION","ELEMENT","ELSE","ELSEWHERE","EMERGE","EMPHASIS","EMPLOY","EMPLOYEE","EMPLOYER","EMPLOYMENT","EMPTY","ENABLE","ENCOURAGE","END","ENEMY","ENERGY","ENGINE","ENGINEERING","ENJOY","ENOUGH","ENSURE","ENTER","ENTERPRISE","ENTIRE","ENTIRELY","ENTITLE","ENTRY","ENVIRONMENT","ENVIRONMENTAL","EQUAL","EQUALLY","EQUIPMENT","ERROR","ESCAPE","ESPECIALLY","ESSENTIAL","ESTABLISH","ESTABLISHMENT","ESTATE","ESTIMATE","EVEN","EVENING","EVENT","EVENTUALLY","EVER","EVERY","EVERYBODY","EVERYONE","EVERYTHING","EVIDENCE","EXACTLY","EXAMINATION","EXAMINE","EXAMPLE","EXCELLENT","EXCEPT","EXCHANGE","EXECUTIVE","EXERCISE","EXHIBITION","EXIST","EXISTENCE","EXISTING","EXPECT","EXPECTATION","EXPENDITURE","EXPENSE","EXPENSIVE","EXPERIENCE","EXPERIMENT","EXPERT","EXPLAIN","EXPLANATION","EXPLORE","EXPRESS","EXPRESSION","EXTEND","EXTENT","EXTERNAL","EXTRA","EXTREMELY","EYE","FACE","FACILITY","FACT","FACTOR","FACTORY","FAIL","FAILURE","FAIR","FAIRLY","FAITH","FALL","FAMILIAR","FAMILY","FAMOUS","FAR","FARM","FARMER","FASHION","FAST","FATHER","FAVOUR","FEAR","FEATURE","FEE","FEEL","FEELING","FEMALE","FEW","FIELD","FIGHT","FIGURE","FILE","FILL","FILM","FINAL","FINALLY","FINANCE","FINANCIAL","FIND","FINDING","FINE","FINGER","FINISH","FIRE","FIRM","FIRST","FISH","FIT","FIX","FLAT","FLIGHT","FLOOR","FLOW","FLOWER","FLY","FOCUS","FOLLOW","FOLLOWING","FOOD","FOOT","FOOTBALL","FOR","FORCE","FOREIGN","FOREST","FORGET","FORM","FORMAL","FORMER","FORWARD","FOUNDATION","FREE","FREEDOM","FREQUENTLY","FRESH","FRIEND","FROM","FRONT","FRUIT","FUEL","FULL","FULLY","FUNCTION","FUND","FUNNY","FURTHER","FUTURE","GAIN","GAME","GARDEN","GAS","GATE","GATHER","GENERAL","GENERALLY","GENERATE","GENERATION","GENTLEMAN","GET","GIRL","GIVE","GLASS","GO","GOAL","GOD","GOLD","GOOD","GOVERNMENT","GRANT","GREAT","GREEN","GREY","GROUND","GROUP","GROW","GROWING","GROWTH","GUEST","GUIDE","GUN","HAIR","HALF","HALL","HAND","HANDLE","HANG","HAPPEN","HAPPY","HARD","HARDLY","HATE","HAVE","HE","HEAD","HEALTH","HEAR","HEART","HEAT","HEAVY","HELL","HELP","HENCE","HER","HERE","HERSELF","HIDE","HIGH","HIGHLY","HILL","HIM","HIMSELF","HIS","HISTORICAL","HISTORY","HIT","HOLD","HOLE","HOLIDAY","HOME","HOPE","HORSE","HOSPITAL","HOT","HOTEL","HOUR","HOUSE","HOUSEHOLD","HOUSING","HOW","HOWEVER","HUGE","HUMAN","HURT","HUSBAND","I","IDEA","IDENTIFY","IF","IGNORE","ILLUSTRATE","IMAGE","IMAGINE","IMMEDIATE","IMMEDIATELY","IMPACT","IMPLICATION","IMPLY","IMPORTANCE","IMPORTANT","IMPOSE","IMPOSSIBLE","IMPRESSION","IMPROVE","IMPROVEMENT","IN","INCIDENT","INCLUDE","INCLUDING","INCOME","INCREASE","INCREASED","INCREASINGLY","INDEED","INDEPENDENT","INDEX","INDICATE","INDIVIDUAL","INDUSTRIAL","INDUSTRY","INFLUENCE","INFORM","INFORMATION","INITIAL","INITIATIVE","INJURY","INSIDE","INSIST","INSTANCE","INSTEAD","INSTITUTE","INSTITUTION","INSTRUCTION","INSTRUMENT","INSURANCE","INTEND","INTENTION","INTEREST","INTERESTED","INTERESTING","INTERNAL","INTERNATIONAL","INTERPRETATION","INTERVIEW","INTO","INTRODUCE","INTRODUCTION","INVESTIGATE","INVESTIGATION","INVESTMENT","INVITE","INVOLVE","IRON","IS","ISLAND","ISSUE","IT","ITEM","ITS","ITSELF","JOB","JOIN","JOINT","JOURNEY","JUDGE","JUMP","JUST","JUSTICE","KEEP","KEY","KID","KILL","KIND","KING","KITCHEN","KNEE","KNOW","KNOWLEDGE","LABOUR","LACK","LADY","LAND","LANGUAGE","LARGE","LARGELY","LAST","LATE","LATER","LATTER","LAUGH","LAUNCH","LAW","LAWYER","LAY","LEAD","LEADER","LEADERSHIP","LEADING","LEAF","LEAGUE","LEAN","LEARN","LEAST","LEAVE","LEFT","LEG","LEGAL","LEGISLATION","LENGTH","LESS","LET","LETTER","LEVEL","LIABILITY","LIBERAL","LIBRARY","LIE","LIFE","LIFT","LIGHT","LIKE","LIKELY","LIMIT","LIMITED","LINE","LINK","LIP","LIST","LISTEN","LITERATURE","LITTLE","LIVE","LIVING","LOAN","LOCAL","LOCATION","LONG","LOOK","LORD","LOSE","LOSS","LOT","LOVE","LOVELY","LOW","LUNCH","MACHINE","MAGAZINE","MAIN","MAINLY","MAINTAIN","MAJOR","MAJORITY","MAKE","MALE","MAN","MANAGE","MANAGEMENT","MANAGER","MANNER","MANY","MAP","MARK","MARKET","MARRIAGE","MARRIED","MARRY","MASS","MASTER","MATCH","MATERIAL","MATTER","MAY","MAYBE","ME","MEAL","MEAN","MEANING","MEANS","MEANWHILE","MEASURE","MECHANISM","MEDIA","MEDICAL","MEET","MEETING","MEMBER","MEMBERSHIP","MEMORY","MENTAL","MENTION","MERELY","MESSAGE","METAL","METHOD","MIDDLE","MIGHT","MILE","MILITARY","MILK","MIND","MINE","MINISTER","MINISTRY","MINUTE","MISS","MISTAKE","MODEL","MODERN","MODULE","MOMENT","MONEY","MONTH","MORE","MORNING","MOST","MOTHER","MOTION","MOTOR","MOUNTAIN","MOUTH","MOVE","MOVEMENT","MUCH","MURDER","MUSEUM","MUSIC","MUST","MY","MYSELF","NAME","NARROW","NATION","NATIONAL","NATURAL","NATURE","NEAR","NEARLY","NECESSARILY","NECESSARY","NECK","NEED","NEGOTIATION","NEIGHBOUR","NEITHER","NETWORK","NEVER","NEVERTHELESS","NEW","NEWS","NEWSPAPER","NEXT","NICE","NIGHT","NO","NOBODY","NOD","NOISE","NONE","NOR","NORMAL","NORMALLY","NORTH","NORTHERN","NOSE","NOT","NOTE","NOTHING","NOTICE","NOTION","NOW","NUCLEAR","NUMBER","NURSE","OBJECT","OBJECTIVE","OBSERVATION","OBSERVE","OBTAIN","OBVIOUS","OBVIOUSLY","OCCASION","OCCUR","ODD","OF","OFF","OFFENCE","OFFER","OFFICE","OFFICER","OFFICIAL","OFTEN","OIL","OKAY","OLD","ON","ONCE","ONE","ONLY","ONTO","OPEN","OPERATE","OPERATION","OPINION","OPPORTUNITY","OPPOSITION","OPTION","OR","ORDER","ORDINARY","ORGANISATION","ORGANISE","ORGANIZATION","ORIGIN","ORIGINAL","OTHER","OTHERWISE","OUGHT","OUR","OURSELVES","OUT","OUTCOME","OUTPUT","OUTSIDE","OVER","OVERALL","OWN","OWNER","PACKAGE","PAGE","PAIN","PAINT","PAINTING","PAIR","PANEL","PAPER","PARENT","PARK","PARLIAMENT","PART","PARTICULAR","PARTICULARLY","PARTLY","PARTNER","PARTY","PASS","PASSAGE","PAST","PATH","PATIENT","PATTERN","PAY","PAYMENT","PEACE","PENSION","PEOPLE","PER","PERCENT","PERFECT","PERFORM","PERFORMANCE","PERHAPS","PERIOD","PERMANENT","PERSON","PERSONAL","PERSUADE","PHASE","PHONE","PHOTOGRAPH","PHYSICAL","PICK","PICTURE","PIECE","PLACE","PLAN","PLANNING","PLANT","PLASTIC","PLATE","PLAY","PLAYER","PLEASE","PLEASURE","PLENTY","PLUS","POCKET","POINT","POLICE","POLICY","POLITICAL","POLITICS","POOL","POOR","POPULAR","POPULATION","POSITION","POSITIVE","POSSIBILITY","POSSIBLE","POSSIBLY","POST","POTENTIAL","POUND","POWER","POWERFUL","PRACTICAL","PRACTICE","PREFER","PREPARE","PRESENCE","PRESENT","PRESIDENT","PRESS","PRESSURE","PRETTY","PREVENT","PREVIOUS","PREVIOUSLY","PRICE","PRIMARY","PRIME","PRINCIPLE","PRIORITY","PRISON","PRISONER","PRIVATE","PROBABLY","PROBLEM","PROCEDURE","PROCESS","PRODUCE","PRODUCT","PRODUCTION","PROFESSIONAL","PROFIT","PROGRAM","PROGRAMME","PROGRESS","PROJECT","PROMISE","PROMOTE","PROPER","PROPERLY","PROPERTY","PROPORTION","PROPOSE","PROPOSAL","PROSPECT","PROTECT","PROTECTION","PROVE","PROVIDE","PROVIDED","PROVISION","PUB","PUBLIC","PUBLICATION","PUBLISH","PULL","PUPIL","PURPOSE","PUSH","PUT","QUALITY","QUARTER","QUESTION","QUICK","QUICKLY","QUIET","QUITE","RACE","RADIO","RAILWAY","RAIN","RAISE","RANGE","RAPIDLY","RARE","RATE","RATHER","REACH","REACTION","READ","READER","READING","READY","REAL","REALISE","REALITY","REALIZE","REALLY","REASON","REASONABLE","RECALL","RECEIVE","RECENT","RECENTLY","RECOGNISE","RECOGNITION","RECOGNIZE","RECOMMEND","RECORD","RECOVER","RED","REDUCE","REDUCTION","REFER","REFERENCE","REFLECT","REFORM","REFUSE","REGARD","REGION","REGIONAL","REGULAR","REGULATION","REJECT","RELATE","RELATION","RELATIONSHIP","RELATIVE","RELATIVELY","RELEASE","RELEVANT","RELIEF","RELIGION","RELIGIOUS","RELY","REMAIN","REMEMBER","REMIND","REMOVE","REPEAT","REPLACE","REPLY","REPORT","REPRESENT","REPRESENTATION","REPRESENTATIVE","REQUEST","REQUIRE","REQUIREMENT","RESEARCH","RESOURCE","RESPECT","RESPOND","RESPONSE","RESPONSIBILITY","RESPONSIBLE","REST","RESTAURANT","RESULT","RETAIN","RETURN","REVEAL","REVENUE","REVIEW","REVOLUTION","RICH","RIDE","RIGHT","RING","RISE","RISK","RIVER","ROAD","ROCK","ROLE","ROLL","ROOF","ROOM","ROUND","ROUTE","ROW","ROYAL","RULE","RUN","RURAL","SAFE","SAFETY","SALE","SAME","SAMPLE","SATISFY","SAVE","SAY","SCALE","SCENE","SCHEME","SCHOOL","SCIENCE","SCIENTIFIC","SCIENTIST","SCORE","SCREEN","SEA","SEARCH","SEASON","SEAT","SECOND","SECONDARY","SECRETARY","SECTION","SECTOR","SECURE","SECURITY","SEE","SEEK","SEEM","SELECT","SELECTION","SELL","SEND","SENIOR","SENSE","SENTENCE","SEPARATE","SEQUENCE","SERIES","SERIOUS","SERIOUSLY","SERVANT","SERVE","SERVICE","SESSION","SET","SETTLE","SETTLEMENT","SEVERAL","SEVERE","SEX","SEXUAL","SHAKE","SHALL","SHAPE","SHARE","SHE","SHEET","SHIP","SHOE","SHOOT","SHOP","SHORT","SHOT","SHOULD","SHOULDER","SHOUT","SHOW","SHUT","SIDE","SIGHT","SIGN","SIGNAL","SIGNIFICANCE","SIGNIFICANT","SILENCE","SIMILAR","SIMPLE","SIMPLY","SINCE","SING","SINGLE","SIR","SISTER","SIT","SITE","SITUATION","SIZE","SKILL","SKIN","SKY","SLEEP","SLIGHTLY","SLIP","SLOW","SLOWLY","SMALL","SMILE","SO","SOCIAL","SOCIETY","SOFT","SOFTWARE","SOIL","SOLDIER","SOLICITOR","SOLUTION","SOME","SOMEBODY","SOMEONE","SOMETHING","SOMETIMES","SOMEWHAT","SOMEWHERE","SON","SONG","SOON","SORRY","SORT","SOUND","SOURCE","SOUTH","SOUTHERN","SPACE","SPEAK","SPEAKER","SPECIAL","SPECIES","SPECIFIC","SPEECH","SPEED","SPEND","SPIRIT","SPORT","SPOT","SPREAD","SPRING","STAFF","STAGE","STAND","STANDARD","STAR","START","STATE","STATEMENT","STATION","STATUS","STAY","STEAL","STEP","STICK","STILL","STOCK","STONE","STOP","STORE","STORY","STRAIGHT","STRANGE","STRATEGY","STREET","STRENGTH","STRIKE","STRONG","STRONGLY","STRUCTURE","STUDENT","STUDIO","STUDY","STUFF","STYLE","SUBJECT","SUBSTANTIAL","SUCCEED","SUCCESS","SUCCESSFUL","SUCH","SUDDENLY","SUFFER","SUFFICIENT","SUGGEST","SUGGESTION","SUITABLE","SUM","SUMMER","SUN","SUPPLY","SUPPORT","SUPPOSE","SURE","SURELY","SURFACE","SURPRISE","SURROUND","SURVEY","SURVIVE","SWITCH","SYSTEM","TABLE","TAKE","TALK","TALL","TAPE","TARGET","TASK","TAX","TEA","TEACH","TEACHER","TEACHING","TEAM","TEAR","TECHNICAL","TECHNIQUE","TECHNOLOGY","TELEPHONE","TELEVISION","TELL","TEMPERATURE","TEND","TERM","TERMS","TERRIBLE","TEST","TEXT","THAN","THANK","THANKS","THAT","THE","THEATRE","THEIR","THEM","THEME","THEMSELVES","THEN","THEORY","THERE","THEREFORE","THESE","THEY","THIN","THING","THINK","THIS","THOSE","THOUGH","THOUGHT","THREAT","THREATEN","THROUGH","THROUGHOUT","THROW","THUS","TICKET","TIME","TINY","TITLE","TO","TODAY","TOGETHER","TOMORROW","TONE","TONIGHT","TOO","TOOL","TOOTH","TOP","TOTAL","TOTALLY","TOUCH","TOUR","TOWARDS","TOWN","TRACK","TRADE","TRADITION","TRADITIONAL","TRAFFIC","TRAIN","TRAINING","TRANSFER","TRANSPORT","TRAVEL","TREAT","TREATMENT","TREATY","TREE","TREND","TRIAL","TRIP","TROOP","TROUBLE","TRUE","TRUST","TRUTH","TRY","TURN","TWICE","TYPE","TYPICAL","UNABLE","UNDER","UNDERSTAND","UNDERSTANDING","UNDERTAKE","UNEMPLOYMENT","UNFORTUNATELY","UNION","UNIT","UNITED","UNIVERSITY","UNLESS","UNLIKELY","UNTIL","UP","UPON","UPPER","URBAN","US","USE","USED","USEFUL","USER","USUAL","USUALLY","VALUE","VARIATION","VARIETY","VARIOUS","VARY","VAST","VEHICLE","VERSION","VERY","VIA","VICTIM","VICTORY","VIDEO","VIEW","VILLAGE","VIOLENCE","VISION","VISIT","VISITOR","VITAL","VOICE","VOLUME","VOTE","WAGE","WAIT","WALK","WALL","WANT","WAR","WARM","WARN","WASH","WATCH","WATER","WAVE","WAY","WE","WEAK","WEAPON","WEAR","WEATHER","WEEK","WEEKEND","WEIGHT","WELCOME","WELFARE","WELL","WEST","WESTERN","WHAT","WHATEVER","WHEN","WHERE","WHEREAS","WHETHER","WHICH","WHILE","WHILST","WHITE","WHO","WHOLE","WHOM","WHOSE","WHY","WIDE","WIDELY","WIFE","WILD","WILL","WIN","WIND","WINDOW","WINE","WING","WINNER","WINTER","WISH","WITH","WITHDRAW","WITHIN","WITHOUT","WOMAN","WONDER","WONDERFUL","WOOD","WORD","WORK","WORKER","WORKING","WORKS","WORLD","WORRY","WORTH","WOULD","WRITE","WRITER","WRITING","WRONG","YARD","YEAH","YEAR","YES","YESTERDAY","YET","YOU","YOUNG","YOUR","YOURSELF","YOUTH" From 07f04a2e5523f91954d7c468e94b395457ea3b7d Mon Sep 17 00:00:00 2001 From: Kaushik Amar Das Date: Fri, 4 Oct 2019 13:29:45 +0530 Subject: [PATCH 0234/1071] adding jaccard similarity (#1270) * adding jaccard similarity * renaming files. zeebus! what an headache --- maths/jaccard_similarity.py | 80 +++++++++++++++++++ .../problem_32/{solution.py => sol32.py} | 0 .../problem_42/{solution.py => solution42.py} | 0 3 files changed, 80 insertions(+) create mode 100644 maths/jaccard_similarity.py rename project_euler/problem_32/{solution.py => sol32.py} (100%) rename project_euler/problem_42/{solution.py => solution42.py} (100%) diff --git a/maths/jaccard_similarity.py b/maths/jaccard_similarity.py new file mode 100644 index 000000000000..4f24d308f340 --- /dev/null +++ b/maths/jaccard_similarity.py @@ -0,0 +1,80 @@ +""" +The Jaccard similarity coefficient is a commonly used indicator of the +similarity between two sets. Let U be a set and A and B be subsets of U, +then the Jaccard index/similarity is defined to be the ratio of the number +of elements of their intersection and the number of elements of their union. + +Inspired from Wikipedia and +the book Mining of Massive Datasets [MMDS 2nd Edition, Chapter 3] + +https://en.wikipedia.org/wiki/Jaccard_index +https://mmds.org + +Jaccard similarity is widely used with MinHashing. +""" + + +def jaccard_similariy(setA, setB, alternativeUnion=False): + """ + Finds the jaccard similarity between two sets. + Essentially, its intersection over union. + + The alternative way to calculate this is to take union as sum of the + number of items in the two sets. This will lead to jaccard similarity + of a set with itself be 1/2 instead of 1. [MMDS 2nd Edition, Page 77] + + Parameters: + :setA (set,list,tuple): A non-empty set/list + :setB (set,list,tuple): A non-empty set/list + :alternativeUnion (boolean): If True, use sum of number of + items as union + + Output: + (float) The jaccard similarity between the two sets. + + Examples: + >>> setA = {'a', 'b', 'c', 'd', 'e'} + >>> setB = {'c', 'd', 'e', 'f', 'h', 'i'} + >>> jaccard_similariy(setA,setB) + 0.375 + + >>> jaccard_similariy(setA,setA) + 1.0 + + >>> jaccard_similariy(setA,setA,True) + 0.5 + + >>> setA = ['a', 'b', 'c', 'd', 'e'] + >>> setB = ('c', 'd', 'e', 'f', 'h', 'i') + >>> jaccard_similariy(setA,setB) + 0.375 + """ + + if isinstance(setA, set) and isinstance(setB, set): + + intersection = len(setA.intersection(setB)) + + if alternativeUnion: + union = len(setA) + len(setB) + else: + union = len(setA.union(setB)) + + return intersection / union + + if isinstance(setA, (list, tuple)) and isinstance(setB, (list, tuple)): + + intersection = [element for element in setA if element in setB] + + if alternativeUnion: + union = len(setA) + len(setB) + else: + union = setA + [element for element in setB if element not in setA] + + return len(intersection) / len(union) + + +if __name__ == "__main__": + + setA = {"a", "b", "c", "d", "e"} + setB = {"c", "d", "e", "f", "h", "i"} + print(jaccard_similariy(setA, setB)) diff --git a/project_euler/problem_32/solution.py b/project_euler/problem_32/sol32.py similarity index 100% rename from project_euler/problem_32/solution.py rename to project_euler/problem_32/sol32.py diff --git a/project_euler/problem_42/solution.py b/project_euler/problem_42/solution42.py similarity index 100% rename from project_euler/problem_42/solution.py rename to project_euler/problem_42/solution42.py From 9eac17a4083ad08c4bb0520cb0b8e5ce385f9ce0 Mon Sep 17 00:00:00 2001 From: William Zhang <39932068+WilliamHYZhang@users.noreply.github.com> Date: Sat, 5 Oct 2019 01:14:13 -0400 Subject: [PATCH 0235/1071] psf/black code formatting (#1277) --- arithmetic_analysis/bisection.py | 13 +- arithmetic_analysis/in_static_equilibrium.py | 7 +- arithmetic_analysis/intersection.py | 21 +- arithmetic_analysis/lu_decomposition.py | 4 +- arithmetic_analysis/newton_method.py | 6 +- arithmetic_analysis/newton_raphson_method.py | 23 +- backtracking/all_combinations.py | 4 +- backtracking/all_permutations.py | 34 +- backtracking/all_subsequences.py | 28 +- backtracking/minimax.py | 40 +- backtracking/n_queens.py | 48 +- backtracking/sum_of_subsets.py | 56 +- boolean_algebra/quine_mc_cluskey.py | 241 ++--- ciphers/affine_cipher.py | 70 +- ciphers/atbash.py | 8 +- ciphers/base16.py | 12 +- ciphers/base32.py | 12 +- ciphers/base64_cipher.py | 63 +- ciphers/base85.py | 12 +- ciphers/brute_force_caesar_cipher.py | 5 +- ciphers/caesar_cipher.py | 25 +- ciphers/cryptomath_module.py | 5 +- ciphers/elgamal_key_generator.py | 41 +- ciphers/hill_cipher.py | 62 +- ciphers/morse_code_implementation.py | 95 +- ciphers/onepad_cipher.py | 14 +- ciphers/playfair_cipher.py | 60 +- ciphers/rabin_miller.py | 198 +++- ciphers/rot13.py | 14 +- ciphers/rsa_cipher.py | 72 +- ciphers/rsa_key_generator.py | 43 +- ciphers/simple_substitution_cipher.py | 39 +- ciphers/trafid_cipher.py | 66 +- ciphers/transposition_cipher.py | 32 +- ...ansposition_cipher_encrypt_decrypt_file.py | 30 +- ciphers/vigenere_cipher.py | 43 +- ciphers/xor_cipher.py | 172 ++-- compression/burrows_wheeler.py | 13 +- compression/huffman.py | 9 +- compression/peak_signal_to_noise_ratio.py | 13 +- conversions/decimal_to_binary.py | 1 + conversions/decimal_to_hexadecimal.py | 43 +- conversions/decimal_to_octal.py | 4 +- data_structures/binary_tree/avl_tree.py | 142 ++- .../binary_tree/basic_binary_tree.py | 22 +- .../binary_tree/binary_search_tree.py | 144 +-- data_structures/binary_tree/fenwick_tree.py | 22 +- .../binary_tree/lazy_segment_tree.py | 76 +- data_structures/binary_tree/lca.py | 2 +- data_structures/binary_tree/red_black_tree.py | 1 - data_structures/binary_tree/segment_tree.py | 36 +- data_structures/binary_tree/treap.py | 1 + data_structures/hashing/double_hash.py | 21 +- data_structures/hashing/hash_table.py | 12 +- .../hashing/hash_table_with_linked_list.py | 18 +- .../hashing/number_theory/prime_numbers.py | 20 +- data_structures/hashing/quadratic_probing.py | 13 +- data_structures/heap/binomial_heap.py | 68 +- data_structures/heap/heap.py | 138 +-- data_structures/linked_list/__init__.py | 1 + .../linked_list/doubly_linked_list.py | 65 +- .../linked_list/singly_linked_list.py | 36 +- data_structures/linked_list/swap_nodes.py | 13 +- data_structures/queue/double_ended_queue.py | 6 +- data_structures/queue/queue_on_list.py | 18 +- .../queue/queue_on_pseudo_stack.py | 19 +- data_structures/stacks/__init__.py | 35 +- .../stacks/balanced_parentheses.py | 14 +- .../stacks/infix_to_postfix_conversion.py | 33 +- .../stacks/infix_to_prefix_conversion.py | 86 +- .../stacks/next_greater_element.py | 5 +- data_structures/stacks/postfix_evaluation.py | 48 +- data_structures/stacks/stack.py | 22 +- data_structures/stacks/stock_span_problem.py | 16 +- .../edge_detection/canny.py | 46 +- digital_image_processing/filters/convolve.py | 12 +- .../filters/gaussian_filter.py | 20 +- .../filters/median_filter.py | 10 +- .../filters/sobel_filter.py | 14 +- divide_and_conquer/closest_pair_of_points.py | 43 +- divide_and_conquer/convex_hull.py | 52 +- divide_and_conquer/inversions.py | 14 +- divide_and_conquer/max_subarray_sum.py | 11 +- dynamic_programming/bitmask.py | 53 +- dynamic_programming/coin_change.py | 4 +- dynamic_programming/edit_distance.py | 81 +- dynamic_programming/factorial.py | 26 +- dynamic_programming/fibonacci.py | 8 +- dynamic_programming/floyd_warshall.py | 49 +- dynamic_programming/fractional_knapsack.py | 20 +- dynamic_programming/integer_partition.py | 53 +- .../k_means_clustering_tensorflow.py | 110 +- dynamic_programming/knapsack.py | 61 +- .../longest_common_subsequence.py | 10 +- .../longest_increasing_subsequence.py | 71 +- ...longest_increasing_subsequence_o(nlogn).py | 48 +- dynamic_programming/longest_sub_array.py | 22 +- dynamic_programming/matrix_chain_order.py | 62 +- dynamic_programming/max_sub_array.py | 86 +- dynamic_programming/minimum_partition.py | 24 +- dynamic_programming/rod_cutting.py | 113 ++- dynamic_programming/subset_generation.py | 74 +- dynamic_programming/sum_of_subset.py | 23 +- file_transfer/recieve_file.py | 14 +- file_transfer/send_file.py | 26 +- graphs/a_star.py | 62 +- graphs/articulation_points.py | 15 +- graphs/basic_graphs.py | 2 +- graphs/bellman_ford.py | 62 +- graphs/bfs.py | 18 +- graphs/bfs_shortest_path.py | 30 +- graphs/breadth_first_search.py | 11 +- graphs/check_bipartite_graph_bfs.py | 5 +- graphs/check_bipartite_graph_dfs.py | 4 +- graphs/depth_first_search.py | 13 +- graphs/dfs.py | 22 +- graphs/dijkstra.py | 1 + graphs/dijkstra_2.py | 71 +- graphs/dijkstra_algorithm.py | 26 +- ...irected_and_undirected_(weighted)_graph.py | 949 +++++++++--------- .../edmonds_karp_multiple_source_and_sink.py | 35 +- ...n_path_and_circuit_for_undirected_graph.py | 30 +- graphs/even_tree.py | 14 +- graphs/finding_bridges.py | 19 +- graphs/graph_list.py | 8 +- graphs/graph_matrix.py | 20 +- graphs/graphs_floyd_warshall.py | 151 +-- graphs/kahns_algorithm_long.py | 7 +- graphs/kahns_algorithm_topo.py | 5 +- graphs/minimum_spanning_tree_kruskal.py | 24 +- graphs/minimum_spanning_tree_prims.py | 27 +- graphs/multi_hueristic_astar.py | 503 ++++++---- graphs/page_rank.py | 38 +- graphs/prim.py | 6 +- graphs/scc_kosaraju.py | 23 +- graphs/tarjans_scc.py | 10 +- hashes/chaos_machine.py | 112 ++- hashes/enigma_machine.py | 5 +- hashes/md5.py | 142 ++- hashes/sha1.py | 67 +- linear_algebra/src/lib.py | 158 +-- linear_algebra/src/tests.py | 129 ++- machine_learning/decision_tree.py | 25 +- machine_learning/gradient_descent.py | 46 +- machine_learning/k_means_clust.py | 99 +- machine_learning/knn_sklearn.py | 27 +- machine_learning/linear_regression.py | 24 +- machine_learning/logistic_regression.py | 47 +- .../random_forest_classification.py | 74 +- .../random_forest_regression.py | 15 +- machine_learning/scoring_functions.py | 22 +- .../sequential_minimum_optimization.py | 207 +++- maths/3n+1.py | 25 +- maths/abs.py | 2 +- maths/abs_max.py | 15 +- maths/abs_min.py | 6 +- maths/average_mean.py | 2 +- maths/average_median.py | 4 +- maths/basic_maths.py | 6 +- maths/binary_exponentiation.py | 4 +- maths/collatz_sequence.py | 11 +- maths/extended_euclidean_algorithm.py | 4 +- maths/factorial_recursive.py | 2 +- maths/fermat_little_theorem.py | 4 +- maths/fibonacci.py | 23 +- maths/fibonacci_sequence_recursion.py | 8 +- maths/find_lcm.py | 16 +- maths/find_max.py | 13 +- maths/find_min.py | 3 +- maths/gaussian.py | 2 - maths/greater_common_divisor.py | 4 +- maths/lucas_series.py | 1 + maths/matrix_exponentiation.py | 5 +- maths/modular_exponential.py | 2 +- maths/newton_raphson.py | 36 +- maths/polynomial_evaluation.py | 2 +- maths/prime_check.py | 15 +- maths/radix2_fft.py | 68 +- maths/segmented_sieve.py | 2 +- maths/simpson_rule.py | 41 +- maths/trapezoidal_rule.py | 70 +- maths/zellers_congruence.py | 50 +- matrix/matrix_class.py | 10 +- matrix/matrix_operation.py | 53 +- ...h_fibonacci_using_matrix_exponentiation.py | 2 + matrix/searching_in_sorted_matrix.py | 13 +- matrix/sherman_morrison.py | 59 +- matrix/spiral_print.py | 6 +- matrix/tests/test_matrix_operation.py | 21 +- networking_flow/ford_fulkerson.py | 43 +- .../back_propagation_neural_network.py | 113 ++- neural_network/convolution_neural_network.py | 312 +++--- neural_network/perceptron.py | 73 +- other/anagrams.py | 23 +- other/binary_exponentiation.py | 7 +- other/binary_exponentiation_2.py | 6 +- other/detecting_english_programmatically.py | 18 +- other/euclidean_gcd.py | 5 +- other/fischer_yates_shuffle.py | 18 +- other/frequency_finder.py | 90 +- other/game_of_life.py | 87 +- other/linear_congruential_generator.py | 12 +- other/nested_brackets.py | 16 +- other/palindrome.py | 4 +- other/password_generator.py | 11 +- other/primelib.py | 223 ++-- other/sierpinski_triangle.py | 71 +- other/tower_of_hanoi.py | 21 +- other/two_sum.py | 16 +- other/word_patterns.py | 15 +- project_euler/problem_01/sol1.py | 2 + project_euler/problem_01/sol3.py | 2 + project_euler/problem_01/sol4.py | 2 + project_euler/problem_01/sol6.py | 2 + project_euler/problem_02/sol1.py | 2 + project_euler/problem_02/sol2.py | 2 + project_euler/problem_02/sol3.py | 2 + project_euler/problem_04/sol1.py | 6 +- project_euler/problem_04/sol2.py | 2 + project_euler/problem_05/sol1.py | 2 + project_euler/problem_06/sol1.py | 2 + project_euler/problem_06/sol2.py | 2 + project_euler/problem_07/sol2.py | 2 + project_euler/problem_09/sol2.py | 2 + project_euler/problem_09/sol3.py | 2 + project_euler/problem_11/sol1.py | 12 +- project_euler/problem_11/sol2.py | 14 +- project_euler/problem_12/sol2.py | 10 +- project_euler/problem_14/sol1.py | 10 +- project_euler/problem_14/sol2.py | 2 + project_euler/problem_15/sol1.py | 4 +- project_euler/problem_18/solution.py | 6 +- project_euler/problem_21/sol1.py | 5 +- project_euler/problem_23/sol1.py | 5 +- project_euler/problem_234/sol1.py | 29 +- project_euler/problem_25/sol1.py | 1 + project_euler/problem_29/solution.py | 2 + project_euler/problem_31/sol1.py | 2 + project_euler/problem_32/sol32.py | 16 +- project_euler/problem_36/sol1.py | 2 + project_euler/problem_40/sol1.py | 2 + project_euler/problem_42/solution42.py | 16 +- project_euler/problem_551/sol1.py | 2 +- project_euler/problem_56/sol1.py | 14 +- project_euler/problem_67/sol1.py | 6 +- scripts/build_directory_md.py | 6 +- scripts/validate_filenames.py | 1 + searches/binary_search.py | 25 +- searches/interpolation_search.py | 58 +- searches/jump_search.py | 9 +- searches/linear_search.py | 12 +- searches/quick_select.py | 23 +- searches/sentinel_linear_search.py | 13 +- searches/tabu_search.py | 55 +- searches/ternary_search.py | 85 +- sorts/bitonic_sort.py | 16 +- sorts/bogo_sort.py | 7 +- sorts/bubble_sort.py | 16 +- sorts/bucket_sort.py | 18 +- sorts/cocktail_shaker_sort.py | 17 +- sorts/comb_sort.py | 11 +- sorts/counting_sort.py | 13 +- sorts/cycle_sort.py | 8 +- sorts/external_sort.py | 43 +- sorts/gnome_sort.py | 8 +- sorts/heap_sort.py | 16 +- sorts/insertion_sort.py | 18 +- sorts/merge_sort.py | 16 +- sorts/merge_sort_fastest.py | 14 +- sorts/odd_even_transposition_parallel.py | 79 +- .../odd_even_transposition_single_threaded.py | 5 +- sorts/pancake_sort.py | 11 +- sorts/pigeon_sort.py | 30 +- sorts/quick_sort.py | 10 +- sorts/quick_sort_3_partition.py | 9 +- sorts/radix_sort.py | 30 +- sorts/random_normal_distribution_quicksort.py | 74 +- sorts/random_pivot_quick_sort.py | 23 +- sorts/selection_sort.py | 12 +- sorts/shell_sort.py | 9 +- sorts/stooge_sort.py | 29 +- sorts/topological_sort.py | 8 +- sorts/tree_sort.py | 4 +- sorts/wiggle_sort.py | 2 +- strings/boyer_moore_search.py | 23 +- strings/knuth_morris_pratt.py | 4 +- strings/levenshtein_distance.py | 13 +- strings/manacher.py | 34 +- strings/min_cost_string_conversion.py | 200 ++-- strings/naive_string_search.py | 27 +- traversals/binary_tree_traversals.py | 1 + 291 files changed, 6100 insertions(+), 4657 deletions(-) diff --git a/arithmetic_analysis/bisection.py b/arithmetic_analysis/bisection.py index 8bf3f09782a3..78582b025880 100644 --- a/arithmetic_analysis/bisection.py +++ b/arithmetic_analysis/bisection.py @@ -1,7 +1,9 @@ import math -def bisection(function, a, b): # finds where the function becomes 0 in [a,b] using bolzano +def bisection( + function, a, b +): # finds where the function becomes 0 in [a,b] using bolzano start = a end = b @@ -9,13 +11,15 @@ def bisection(function, a, b): # finds where the function becomes 0 in [a,b] us return a elif function(b) == 0: return b - elif function(a) * function(b) > 0: # if none of these are root and they are both positive or negative, + elif ( + function(a) * function(b) > 0 + ): # if none of these are root and they are both positive or negative, # then his algorithm can't find the root print("couldn't find root in [a,b]") return else: mid = start + (end - start) / 2.0 - while abs(start - mid) > 10**-7: # until we achieve precise equals to 10^-7 + while abs(start - mid) > 10 ** -7: # until we achieve precise equals to 10^-7 if function(mid) == 0: return mid elif function(mid) * function(start) < 0: @@ -27,7 +31,8 @@ def bisection(function, a, b): # finds where the function becomes 0 in [a,b] us def f(x): - return math.pow(x, 3) - 2*x - 5 + return math.pow(x, 3) - 2 * x - 5 + if __name__ == "__main__": print(bisection(f, 1, 1000)) diff --git a/arithmetic_analysis/in_static_equilibrium.py b/arithmetic_analysis/in_static_equilibrium.py index 48eb6135eba7..addaff888f7f 100644 --- a/arithmetic_analysis/in_static_equilibrium.py +++ b/arithmetic_analysis/in_static_equilibrium.py @@ -54,11 +54,8 @@ def in_static_equilibrium( if __name__ == "__main__": # Test to check if it works forces = array( - [ - polar_force(718.4, 180 - 30), - polar_force(879.54, 45), - polar_force(100, -90) - ]) + [polar_force(718.4, 180 - 30), polar_force(879.54, 45), polar_force(100, -90)] + ) location = array([[0, 0], [0, 0], [0, 0]]) diff --git a/arithmetic_analysis/intersection.py b/arithmetic_analysis/intersection.py index 2f25f76ebd96..0fdcfbf1943e 100644 --- a/arithmetic_analysis/intersection.py +++ b/arithmetic_analysis/intersection.py @@ -1,17 +1,24 @@ import math -def intersection(function,x0,x1): #function is the f we want to find its root and x0 and x1 are two random starting points + +def intersection( + function, x0, x1 +): # function is the f we want to find its root and x0 and x1 are two random starting points x_n = x0 x_n1 = x1 while True: - x_n2 = x_n1-(function(x_n1)/((function(x_n1)-function(x_n))/(x_n1-x_n))) - if abs(x_n2 - x_n1) < 10**-5: + x_n2 = x_n1 - ( + function(x_n1) / ((function(x_n1) - function(x_n)) / (x_n1 - x_n)) + ) + if abs(x_n2 - x_n1) < 10 ** -5: return x_n2 - x_n=x_n1 - x_n1=x_n2 + x_n = x_n1 + x_n1 = x_n2 + def f(x): - return math.pow(x , 3) - (2 * x) -5 + return math.pow(x, 3) - (2 * x) - 5 + if __name__ == "__main__": - print(intersection(f,3,3.5)) + print(intersection(f, 3, 3.5)) diff --git a/arithmetic_analysis/lu_decomposition.py b/arithmetic_analysis/lu_decomposition.py index 19e259afb826..4372621d74cb 100644 --- a/arithmetic_analysis/lu_decomposition.py +++ b/arithmetic_analysis/lu_decomposition.py @@ -28,9 +28,7 @@ def LUDecompose(table): if __name__ == "__main__": - matrix = numpy.array([[2, -2, 1], - [0, 1, 2], - [5, 3, 1]]) + matrix = numpy.array([[2, -2, 1], [0, 1, 2], [5, 3, 1]]) L, U = LUDecompose(matrix) print(L) print(U) diff --git a/arithmetic_analysis/newton_method.py b/arithmetic_analysis/newton_method.py index cf5649ee3f3b..1408a983041d 100644 --- a/arithmetic_analysis/newton_method.py +++ b/arithmetic_analysis/newton_method.py @@ -8,17 +8,17 @@ def newton(function, function1, startingInt): x_n = startingInt while True: x_n1 = x_n - function(x_n) / function1(x_n) - if abs(x_n - x_n1) < 10**-5: + if abs(x_n - x_n1) < 10 ** -5: return x_n1 x_n = x_n1 def f(x): - return (x**3) - (2 * x) - 5 + return (x ** 3) - (2 * x) - 5 def f1(x): - return 3 * (x**2) - 2 + return 3 * (x ** 2) - 2 if __name__ == "__main__": diff --git a/arithmetic_analysis/newton_raphson_method.py b/arithmetic_analysis/newton_raphson_method.py index d17b57a2e670..646b352a923c 100644 --- a/arithmetic_analysis/newton_raphson_method.py +++ b/arithmetic_analysis/newton_raphson_method.py @@ -1,33 +1,34 @@ # Implementing Newton Raphson method in Python # Author: Syed Haseeb Shah (github.com/QuantumNovice) -#The Newton-Raphson method (also known as Newton's method) is a way to -#quickly find a good approximation for the root of a real-valued function +# The Newton-Raphson method (also known as Newton's method) is a way to +# quickly find a good approximation for the root of a real-valued function from sympy import diff from decimal import Decimal + def NewtonRaphson(func, a): - ''' Finds root from the point 'a' onwards by Newton-Raphson method ''' + """ Finds root from the point 'a' onwards by Newton-Raphson method """ while True: - c = Decimal(a) - ( Decimal(eval(func)) / Decimal(eval(str(diff(func)))) ) + c = Decimal(a) - (Decimal(eval(func)) / Decimal(eval(str(diff(func))))) a = c # This number dictates the accuracy of the answer - if abs(eval(func)) < 10**-15: - return c + if abs(eval(func)) < 10 ** -15: + return c # Let's Execute -if __name__ == '__main__': +if __name__ == "__main__": # Find root of trigonometric function # Find value of pi - print('sin(x) = 0', NewtonRaphson('sin(x)', 2)) + print("sin(x) = 0", NewtonRaphson("sin(x)", 2)) # Find root of polynomial - print('x**2 - 5*x +2 = 0', NewtonRaphson('x**2 - 5*x +2', 0.4)) + print("x**2 - 5*x +2 = 0", NewtonRaphson("x**2 - 5*x +2", 0.4)) # Find Square Root of 5 - print('x**2 - 5 = 0', NewtonRaphson('x**2 - 5', 0.1)) + print("x**2 - 5 = 0", NewtonRaphson("x**2 - 5", 0.1)) # Exponential Roots - print('exp(x) - 1 = 0', NewtonRaphson('exp(x) - 1', 0)) + print("exp(x) - 1 = 0", NewtonRaphson("exp(x) - 1", 0)) diff --git a/backtracking/all_combinations.py b/backtracking/all_combinations.py index 63425aeabbd1..23fe378f5462 100644 --- a/backtracking/all_combinations.py +++ b/backtracking/all_combinations.py @@ -12,7 +12,7 @@ def generate_all_combinations(n: int, k: int) -> [[int]]: >>> generate_all_combinations(n=4, k=2) [[1, 2], [1, 3], [1, 4], [2, 3], [2, 4], [3, 4]] """ - + result = [] create_all_state(1, n, k, [], result) return result @@ -34,7 +34,7 @@ def print_all_state(total_list): print(*i) -if __name__ == '__main__': +if __name__ == "__main__": n = 4 k = 2 total_list = generate_all_combinations(n, k) diff --git a/backtracking/all_permutations.py b/backtracking/all_permutations.py index 299b708fef4e..b0955bf53a31 100644 --- a/backtracking/all_permutations.py +++ b/backtracking/all_permutations.py @@ -1,42 +1,42 @@ -''' +""" In this problem, we want to determine all possible permutations of the given sequence. We use backtracking to solve this problem. Time complexity: O(n! * n), where n denotes the length of the given sequence. -''' +""" def generate_all_permutations(sequence): - create_state_space_tree(sequence, [], 0, [0 for i in range(len(sequence))]) + create_state_space_tree(sequence, [], 0, [0 for i in range(len(sequence))]) def create_state_space_tree(sequence, current_sequence, index, index_used): - ''' + """ Creates a state space tree to iterate through each branch using DFS. We know that each state has exactly len(sequence) - index children. It terminates when it reaches the end of the given sequence. - ''' + """ - if index == len(sequence): - print(current_sequence) - return + if index == len(sequence): + print(current_sequence) + return - for i in range(len(sequence)): - if not index_used[i]: - current_sequence.append(sequence[i]) - index_used[i] = True - create_state_space_tree(sequence, current_sequence, index + 1, index_used) - current_sequence.pop() - index_used[i] = False + for i in range(len(sequence)): + if not index_used[i]: + current_sequence.append(sequence[i]) + index_used[i] = True + create_state_space_tree(sequence, current_sequence, index + 1, index_used) + current_sequence.pop() + index_used[i] = False -''' +""" remove the comment to take an input from the user print("Enter the elements") sequence = list(map(int, input().split())) -''' +""" sequence = [3, 1, 2, 4] generate_all_permutations(sequence) diff --git a/backtracking/all_subsequences.py b/backtracking/all_subsequences.py index d868377234a8..4a22c05d29a8 100644 --- a/backtracking/all_subsequences.py +++ b/backtracking/all_subsequences.py @@ -1,39 +1,39 @@ -''' +""" In this problem, we want to determine all possible subsequences of the given sequence. We use backtracking to solve this problem. Time complexity: O(2^n), where n denotes the length of the given sequence. -''' +""" def generate_all_subsequences(sequence): - create_state_space_tree(sequence, [], 0) + create_state_space_tree(sequence, [], 0) def create_state_space_tree(sequence, current_subsequence, index): - ''' + """ Creates a state space tree to iterate through each branch using DFS. We know that each state has exactly two children. It terminates when it reaches the end of the given sequence. - ''' + """ - if index == len(sequence): - print(current_subsequence) - return + if index == len(sequence): + print(current_subsequence) + return - create_state_space_tree(sequence, current_subsequence, index + 1) - current_subsequence.append(sequence[index]) - create_state_space_tree(sequence, current_subsequence, index + 1) - current_subsequence.pop() + create_state_space_tree(sequence, current_subsequence, index + 1) + current_subsequence.append(sequence[index]) + create_state_space_tree(sequence, current_subsequence, index + 1) + current_subsequence.pop() -''' +""" remove the comment to take an input from the user print("Enter the elements") sequence = list(map(int, input().split())) -''' +""" sequence = [3, 1, 2, 4] generate_all_subsequences(sequence) diff --git a/backtracking/minimax.py b/backtracking/minimax.py index 5168306e71fc..af07b8d8171a 100644 --- a/backtracking/minimax.py +++ b/backtracking/minimax.py @@ -1,28 +1,34 @@ -import math +import math -''' Minimax helps to achieve maximum score in a game by checking all possible moves +""" Minimax helps to achieve maximum score in a game by checking all possible moves depth is current depth in game tree. nodeIndex is index of current node in scores[]. if move is of maximizer return true else false leaves of game tree is stored in scores[] height is maximum height of Game tree -''' +""" -def minimax (Depth, nodeIndex, isMax, scores, height): - if Depth == height: - return scores[nodeIndex] +def minimax(Depth, nodeIndex, isMax, scores, height): - if isMax: - return (max(minimax(Depth + 1, nodeIndex * 2, False, scores, height), - minimax(Depth + 1, nodeIndex * 2 + 1, False, scores, height))) - return (min(minimax(Depth + 1, nodeIndex * 2, True, scores, height), - minimax(Depth + 1, nodeIndex * 2 + 1, True, scores, height))) + if Depth == height: + return scores[nodeIndex] -if __name__ == "__main__": - - scores = [90, 23, 6, 33, 21, 65, 123, 34423] - height = math.log(len(scores), 2) + if isMax: + return max( + minimax(Depth + 1, nodeIndex * 2, False, scores, height), + minimax(Depth + 1, nodeIndex * 2 + 1, False, scores, height), + ) + return min( + minimax(Depth + 1, nodeIndex * 2, True, scores, height), + minimax(Depth + 1, nodeIndex * 2 + 1, True, scores, height), + ) - print("Optimal value : ", end = "") - print(minimax(0, 0, True, scores, height)) + +if __name__ == "__main__": + + scores = [90, 23, 6, 33, 21, 65, 123, 34423] + height = math.log(len(scores), 2) + + print("Optimal value : ", end="") + print(minimax(0, 0, True, scores, height)) diff --git a/backtracking/n_queens.py b/backtracking/n_queens.py index dfd4498b166b..f95357c82e21 100644 --- a/backtracking/n_queens.py +++ b/backtracking/n_queens.py @@ -1,4 +1,4 @@ -''' +""" The nqueens problem is of placing N queens on a N * N chess board such that no queen can attack any other queens placed @@ -6,11 +6,12 @@ This means that one queen cannot have any other queen on its horizontal, vertical and diagonal lines. -''' +""" solution = [] + def isSafe(board, row, column): - ''' + """ This function returns a boolean value True if it is safe to place a queen there considering the current state of the board. @@ -21,64 +22,67 @@ def isSafe(board, row, column): Returns : Boolean Value - ''' + """ for i in range(len(board)): if board[row][i] == 1: return False for i in range(len(board)): if board[i][column] == 1: return False - for i,j in zip(range(row,-1,-1),range(column,-1,-1)): + for i, j in zip(range(row, -1, -1), range(column, -1, -1)): if board[i][j] == 1: return False - for i,j in zip(range(row,-1,-1),range(column,len(board))): + for i, j in zip(range(row, -1, -1), range(column, len(board))): if board[i][j] == 1: return False return True + def solve(board, row): - ''' + """ It creates a state space tree and calls the safe function untill it receives a False Boolean and terminates that brach and backtracks to the next poosible solution branch. - ''' + """ if row >= len(board): - ''' + """ If the row number exceeds N we have board with a successful combination and that combination is appended to the solution list and the board is printed. - ''' + """ solution.append(board) printboard(board) print() - return + return for i in range(len(board)): - ''' + """ For every row it iterates through each column to check if it is feesible to place a queen there. If all the combinations for that particaular branch are successfull the board is reinitialized for the next possible combination. - ''' - if isSafe(board,row,i): + """ + if isSafe(board, row, i): board[row][i] = 1 - solve(board,row+1) + solve(board, row + 1) board[row][i] = 0 return False + def printboard(board): - ''' + """ Prints the boards that have a successfull combination. - ''' + """ for i in range(len(board)): for j in range(len(board)): if board[i][j] == 1: - print("Q", end = " ") - else : - print(".", end = " ") + print("Q", end=" ") + else: + print(".", end=" ") print() -#n=int(input("The no. of queens")) + +# n=int(input("The no. of queens")) n = 8 -board = [[0 for i in range(n)]for j in range(n)] +board = [[0 for i in range(n)] for j in range(n)] solve(board, 0) print("The total no. of solutions are :", len(solution)) diff --git a/backtracking/sum_of_subsets.py b/backtracking/sum_of_subsets.py index b01bffbb651d..d96552d39997 100644 --- a/backtracking/sum_of_subsets.py +++ b/backtracking/sum_of_subsets.py @@ -1,36 +1,46 @@ -''' +""" The sum-of-subsetsproblem states that a set of non-negative integers, and a value M, determine all possible subsets of the given set whose summation sum equal to given M. Summation of the chosen numbers must be equal to given number M and one number can be used only once. -''' +""" + def generate_sum_of_subsets_soln(nums, max_sum): - result = [] - path = [] - num_index = 0 - remaining_nums_sum = sum(nums) - create_state_space_tree(nums, max_sum, num_index, path,result, remaining_nums_sum) - return result - -def create_state_space_tree(nums,max_sum,num_index,path,result, remaining_nums_sum): - ''' + result = [] + path = [] + num_index = 0 + remaining_nums_sum = sum(nums) + create_state_space_tree(nums, max_sum, num_index, path, result, remaining_nums_sum) + return result + + +def create_state_space_tree(nums, max_sum, num_index, path, result, remaining_nums_sum): + """ Creates a state space tree to iterate through each branch using DFS. It terminates the branching of a node when any of the two conditions given below satisfy. This algorithm follows depth-fist-search and backtracks when the node is not branchable. - ''' - if sum(path) > max_sum or (remaining_nums_sum + sum(path)) < max_sum: - return - if sum(path) == max_sum: - result.append(path) - return - for num_index in range(num_index,len(nums)): - create_state_space_tree(nums, max_sum, num_index + 1, path + [nums[num_index]], result, remaining_nums_sum - nums[num_index]) + """ + if sum(path) > max_sum or (remaining_nums_sum + sum(path)) < max_sum: + return + if sum(path) == max_sum: + result.append(path) + return + for num_index in range(num_index, len(nums)): + create_state_space_tree( + nums, + max_sum, + num_index + 1, + path + [nums[num_index]], + result, + remaining_nums_sum - nums[num_index], + ) + -''' +""" remove the comment to take an input from the user print("Enter the elements") @@ -38,8 +48,8 @@ def create_state_space_tree(nums,max_sum,num_index,path,result, remaining_nums_s print("Enter max_sum sum") max_sum = int(input()) -''' +""" nums = [3, 34, 4, 12, 5, 2] max_sum = 9 -result = generate_sum_of_subsets_soln(nums,max_sum) -print(*result) \ No newline at end of file +result = generate_sum_of_subsets_soln(nums, max_sum) +print(*result) diff --git a/boolean_algebra/quine_mc_cluskey.py b/boolean_algebra/quine_mc_cluskey.py index b7ca8da437a3..7762d712a01a 100644 --- a/boolean_algebra/quine_mc_cluskey.py +++ b/boolean_algebra/quine_mc_cluskey.py @@ -1,151 +1,168 @@ def compare_string(string1, string2): - """ + """ >>> compare_string('0010','0110') '0_10' >>> compare_string('0110','1101') -1 """ - l1 = list(string1); l2 = list(string2) - count = 0 - for i in range(len(l1)): - if l1[i] != l2[i]: - count += 1 - l1[i] = '_' - if count > 1: - return -1 - else: - return("".join(l1)) + l1 = list(string1) + l2 = list(string2) + count = 0 + for i in range(len(l1)): + if l1[i] != l2[i]: + count += 1 + l1[i] = "_" + if count > 1: + return -1 + else: + return "".join(l1) + def check(binary): - """ + """ >>> check(['0.00.01.5']) ['0.00.01.5'] """ - pi = [] - while 1: - check1 = ['$']*len(binary) - temp = [] - for i in range(len(binary)): - for j in range(i+1, len(binary)): - k=compare_string(binary[i], binary[j]) - if k != -1: - check1[i] = '*' - check1[j] = '*' - temp.append(k) - for i in range(len(binary)): - if check1[i] == '$': - pi.append(binary[i]) - if len(temp) == 0: - return pi - binary = list(set(temp)) + pi = [] + while 1: + check1 = ["$"] * len(binary) + temp = [] + for i in range(len(binary)): + for j in range(i + 1, len(binary)): + k = compare_string(binary[i], binary[j]) + if k != -1: + check1[i] = "*" + check1[j] = "*" + temp.append(k) + for i in range(len(binary)): + if check1[i] == "$": + pi.append(binary[i]) + if len(temp) == 0: + return pi + binary = list(set(temp)) + def decimal_to_binary(no_of_variable, minterms): - """ + """ >>> decimal_to_binary(3,[1.5]) ['0.00.01.5'] """ - temp = [] - s = '' - for m in minterms: - for i in range(no_of_variable): - s = str(m%2) + s - m //= 2 - temp.append(s) - s = '' - return temp + temp = [] + s = "" + for m in minterms: + for i in range(no_of_variable): + s = str(m % 2) + s + m //= 2 + temp.append(s) + s = "" + return temp + def is_for_table(string1, string2, count): - """ + """ >>> is_for_table('__1','011',2) True >>> is_for_table('01_','001',1) False """ - l1 = list(string1);l2=list(string2) - count_n = 0 - for i in range(len(l1)): - if l1[i] != l2[i]: - count_n += 1 - if count_n == count: - return True - else: - return False + l1 = list(string1) + l2 = list(string2) + count_n = 0 + for i in range(len(l1)): + if l1[i] != l2[i]: + count_n += 1 + if count_n == count: + return True + else: + return False + def selection(chart, prime_implicants): - """ + """ >>> selection([[1]],['0.00.01.5']) ['0.00.01.5'] >>> selection([[1]],['0.00.01.5']) ['0.00.01.5'] """ - temp = [] - select = [0]*len(chart) - for i in range(len(chart[0])): - count = 0 - rem = -1 - for j in range(len(chart)): - if chart[j][i] == 1: - count += 1 - rem = j - if count == 1: - select[rem] = 1 - for i in range(len(select)): - if select[i] == 1: - for j in range(len(chart[0])): - if chart[i][j] == 1: - for k in range(len(chart)): - chart[k][j] = 0 - temp.append(prime_implicants[i]) - while 1: - max_n = 0; rem = -1; count_n = 0 - for i in range(len(chart)): - count_n = chart[i].count(1) - if count_n > max_n: - max_n = count_n - rem = i - - if max_n == 0: - return temp - - temp.append(prime_implicants[rem]) - - for i in range(len(chart[0])): - if chart[rem][i] == 1: - for j in range(len(chart)): - chart[j][i] = 0 - + temp = [] + select = [0] * len(chart) + for i in range(len(chart[0])): + count = 0 + rem = -1 + for j in range(len(chart)): + if chart[j][i] == 1: + count += 1 + rem = j + if count == 1: + select[rem] = 1 + for i in range(len(select)): + if select[i] == 1: + for j in range(len(chart[0])): + if chart[i][j] == 1: + for k in range(len(chart)): + chart[k][j] = 0 + temp.append(prime_implicants[i]) + while 1: + max_n = 0 + rem = -1 + count_n = 0 + for i in range(len(chart)): + count_n = chart[i].count(1) + if count_n > max_n: + max_n = count_n + rem = i + + if max_n == 0: + return temp + + temp.append(prime_implicants[rem]) + + for i in range(len(chart[0])): + if chart[rem][i] == 1: + for j in range(len(chart)): + chart[j][i] = 0 + + def prime_implicant_chart(prime_implicants, binary): - """ + """ >>> prime_implicant_chart(['0.00.01.5'],['0.00.01.5']) [[1]] """ - chart = [[0 for x in range(len(binary))] for x in range(len(prime_implicants))] - for i in range(len(prime_implicants)): - count = prime_implicants[i].count('_') - for j in range(len(binary)): - if(is_for_table(prime_implicants[i], binary[j], count)): - chart[i][j] = 1 - - return chart + chart = [[0 for x in range(len(binary))] for x in range(len(prime_implicants))] + for i in range(len(prime_implicants)): + count = prime_implicants[i].count("_") + for j in range(len(binary)): + if is_for_table(prime_implicants[i], binary[j], count): + chart[i][j] = 1 + + return chart + def main(): - no_of_variable = int(input("Enter the no. of variables\n")) - minterms = [int(x) for x in input("Enter the decimal representation of Minterms 'Spaces Seprated'\n").split()] - binary = decimal_to_binary(no_of_variable, minterms) - - prime_implicants = check(binary) - print("Prime Implicants are:") - print(prime_implicants) - chart = prime_implicant_chart(prime_implicants, binary) - - essential_prime_implicants = selection(chart,prime_implicants) - print("Essential Prime Implicants are:") - print(essential_prime_implicants) - -if __name__ == '__main__': - import doctest - doctest.testmod() - main() + no_of_variable = int(input("Enter the no. of variables\n")) + minterms = [ + int(x) + for x in input( + "Enter the decimal representation of Minterms 'Spaces Seprated'\n" + ).split() + ] + binary = decimal_to_binary(no_of_variable, minterms) + + prime_implicants = check(binary) + print("Prime Implicants are:") + print(prime_implicants) + chart = prime_implicant_chart(prime_implicants, binary) + + essential_prime_implicants = selection(chart, prime_implicants) + print("Essential Prime Implicants are:") + print(essential_prime_implicants) + + +if __name__ == "__main__": + import doctest + + doctest.testmod() + main() diff --git a/ciphers/affine_cipher.py b/ciphers/affine_cipher.py index a5d94f087dbf..eb50acf8fc20 100644 --- a/ciphers/affine_cipher.py +++ b/ciphers/affine_cipher.py @@ -2,42 +2,56 @@ SYMBOLS = r""" !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~""" + def main(): - message = input('Enter message: ') - key = int(input('Enter key [2000 - 9000]: ')) - mode = input('Encrypt/Decrypt [E/D]: ') - - if mode.lower().startswith('e'): - mode = 'encrypt' - translated = encryptMessage(key, message) - elif mode.lower().startswith('d'): - mode = 'decrypt' - translated = decryptMessage(key, message) - print('\n%sed text: \n%s' % (mode.title(), translated)) + message = input("Enter message: ") + key = int(input("Enter key [2000 - 9000]: ")) + mode = input("Encrypt/Decrypt [E/D]: ") + + if mode.lower().startswith("e"): + mode = "encrypt" + translated = encryptMessage(key, message) + elif mode.lower().startswith("d"): + mode = "decrypt" + translated = decryptMessage(key, message) + print("\n%sed text: \n%s" % (mode.title(), translated)) + def getKeyParts(key): keyA = key // len(SYMBOLS) keyB = key % len(SYMBOLS) return (keyA, keyB) + def checkKeys(keyA, keyB, mode): - if keyA == 1 and mode == 'encrypt': - sys.exit('The affine cipher becomes weak when key A is set to 1. Choose different key') - if keyB == 0 and mode == 'encrypt': - sys.exit('The affine cipher becomes weak when key A is set to 1. Choose different key') + if keyA == 1 and mode == "encrypt": + sys.exit( + "The affine cipher becomes weak when key A is set to 1. Choose different key" + ) + if keyB == 0 and mode == "encrypt": + sys.exit( + "The affine cipher becomes weak when key A is set to 1. Choose different key" + ) if keyA < 0 or keyB < 0 or keyB > len(SYMBOLS) - 1: - sys.exit('Key A must be greater than 0 and key B must be between 0 and %s.' % (len(SYMBOLS) - 1)) + sys.exit( + "Key A must be greater than 0 and key B must be between 0 and %s." + % (len(SYMBOLS) - 1) + ) if cryptoMath.gcd(keyA, len(SYMBOLS)) != 1: - sys.exit('Key A %s and the symbol set size %s are not relatively prime. Choose a different key.' % (keyA, len(SYMBOLS))) + sys.exit( + "Key A %s and the symbol set size %s are not relatively prime. Choose a different key." + % (keyA, len(SYMBOLS)) + ) + def encryptMessage(key, message): - ''' + """ >>> encryptMessage(4545, 'The affine cipher is a type of monoalphabetic substitution cipher.') 'VL}p MM{I}p~{HL}Gp{vp pFsH}pxMpyxIx JHL O}F{~pvuOvF{FuF{xIp~{HL}Gi' - ''' + """ keyA, keyB = getKeyParts(key) - checkKeys(keyA, keyB, 'encrypt') - cipherText = '' + checkKeys(keyA, keyB, "encrypt") + cipherText = "" for symbol in message: if symbol in SYMBOLS: symIndex = SYMBOLS.find(symbol) @@ -46,14 +60,15 @@ def encryptMessage(key, message): cipherText += symbol return cipherText + def decryptMessage(key, message): - ''' + """ >>> decryptMessage(4545, 'VL}p MM{I}p~{HL}Gp{vp pFsH}pxMpyxIx JHL O}F{~pvuOvF{FuF{xIp~{HL}Gi') 'The affine cipher is a type of monoalphabetic substitution cipher.' - ''' + """ keyA, keyB = getKeyParts(key) - checkKeys(keyA, keyB, 'decrypt') - plainText = '' + checkKeys(keyA, keyB, "decrypt") + plainText = "" modInverseOfkeyA = cryptoMath.findModInverse(keyA, len(SYMBOLS)) for symbol in message: if symbol in SYMBOLS: @@ -63,6 +78,7 @@ def decryptMessage(key, message): plainText += symbol return plainText + def getRandomKey(): while True: keyA = random.randint(2, len(SYMBOLS)) @@ -70,7 +86,9 @@ def getRandomKey(): if cryptoMath.gcd(keyA, len(SYMBOLS)) == 1: return keyA * len(SYMBOLS) + keyB -if __name__ == '__main__': + +if __name__ == "__main__": import doctest + doctest.testmod() main() diff --git a/ciphers/atbash.py b/ciphers/atbash.py index 9ed47e0874f8..4cf003859856 100644 --- a/ciphers/atbash.py +++ b/ciphers/atbash.py @@ -1,15 +1,15 @@ def atbash(): - output="" + output = "" for i in input("Enter the sentence to be encrypted ").strip(): extract = ord(i) if 65 <= extract <= 90: - output += chr(155-extract) + output += chr(155 - extract) elif 97 <= extract <= 122: - output += chr(219-extract) + output += chr(219 - extract) else: output += i print(output) -if __name__ == '__main__': +if __name__ == "__main__": atbash() diff --git a/ciphers/base16.py b/ciphers/base16.py index 9bc0e5d8337a..0210315d54e6 100644 --- a/ciphers/base16.py +++ b/ciphers/base16.py @@ -1,11 +1,13 @@ import base64 + def main(): - inp = input('->') - encoded = inp.encode('utf-8') #encoded the input (we need a bytes like object) - b16encoded = base64.b16encode(encoded) #b16encoded the encoded string + inp = input("->") + encoded = inp.encode("utf-8") # encoded the input (we need a bytes like object) + b16encoded = base64.b16encode(encoded) # b16encoded the encoded string print(b16encoded) - print(base64.b16decode(b16encoded).decode('utf-8'))#decoded it + print(base64.b16decode(b16encoded).decode("utf-8")) # decoded it + -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/ciphers/base32.py b/ciphers/base32.py index 2ac29f441e94..5bba8c4dd685 100644 --- a/ciphers/base32.py +++ b/ciphers/base32.py @@ -1,11 +1,13 @@ import base64 + def main(): - inp = input('->') - encoded = inp.encode('utf-8') #encoded the input (we need a bytes like object) - b32encoded = base64.b32encode(encoded) #b32encoded the encoded string + inp = input("->") + encoded = inp.encode("utf-8") # encoded the input (we need a bytes like object) + b32encoded = base64.b32encode(encoded) # b32encoded the encoded string print(b32encoded) - print(base64.b32decode(b32encoded).decode('utf-8'))#decoded it + print(base64.b32decode(b32encoded).decode("utf-8")) # decoded it + -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/ciphers/base64_cipher.py b/ciphers/base64_cipher.py index fa3451c0cbae..9fca5b02679f 100644 --- a/ciphers/base64_cipher.py +++ b/ciphers/base64_cipher.py @@ -1,64 +1,71 @@ def encodeBase64(text): base64chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/" - - r = "" #the result - c = 3 - len(text) % 3 #the length of padding - p = "=" * c #the padding - s = text + "\0" * c #the text to encode - - i = 0 + + r = "" # the result + c = 3 - len(text) % 3 # the length of padding + p = "=" * c # the padding + s = text + "\0" * c # the text to encode + + i = 0 while i < len(s): if i > 0 and ((i / 3 * 4) % 76) == 0: r = r + "\r\n" - - n = (ord(s[i]) << 16) + (ord(s[i+1]) << 8 ) + ord(s[i+2]) - + + n = (ord(s[i]) << 16) + (ord(s[i + 1]) << 8) + ord(s[i + 2]) + n1 = (n >> 18) & 63 n2 = (n >> 12) & 63 - n3 = (n >> 6) & 63 + n3 = (n >> 6) & 63 n4 = n & 63 - + r += base64chars[n1] + base64chars[n2] + base64chars[n3] + base64chars[n4] i += 3 - return r[0: len(r)-len(p)] + p - + return r[0 : len(r) - len(p)] + p + + def decodeBase64(text): base64chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/" s = "" - + for i in text: if i in base64chars: s += i c = "" else: - if i == '=': - c += '=' - + if i == "=": + c += "=" + p = "" if c == "=": - p = 'A' + p = "A" else: if c == "==": p = "AA" - + r = "" s = s + p - + i = 0 while i < len(s): - n = (base64chars.index(s[i]) << 18) + (base64chars.index(s[i+1]) << 12) + (base64chars.index(s[i+2]) << 6) +base64chars.index(s[i+3]) - + n = ( + (base64chars.index(s[i]) << 18) + + (base64chars.index(s[i + 1]) << 12) + + (base64chars.index(s[i + 2]) << 6) + + base64chars.index(s[i + 3]) + ) + r += chr((n >> 16) & 255) + chr((n >> 8) & 255) + chr(n & 255) - + i += 4 - - return r[0: len(r) - len(p)] + + return r[0 : len(r) - len(p)] + def main(): print(encodeBase64("WELCOME to base64 encoding")) print(decodeBase64(encodeBase64("WELCOME to base64 encoding"))) - -if __name__ == '__main__': + +if __name__ == "__main__": main() diff --git a/ciphers/base85.py b/ciphers/base85.py index 5fd13837f662..ebfd0480f794 100644 --- a/ciphers/base85.py +++ b/ciphers/base85.py @@ -1,11 +1,13 @@ import base64 + def main(): - inp = input('->') - encoded = inp.encode('utf-8') #encoded the input (we need a bytes like object) - a85encoded = base64.a85encode(encoded) #a85encoded the encoded string + inp = input("->") + encoded = inp.encode("utf-8") # encoded the input (we need a bytes like object) + a85encoded = base64.a85encode(encoded) # a85encoded the encoded string print(a85encoded) - print(base64.a85decode(a85encoded).decode('utf-8'))#decoded it + print(base64.a85decode(a85encoded).decode("utf-8")) # decoded it + -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/ciphers/brute_force_caesar_cipher.py b/ciphers/brute_force_caesar_cipher.py index 3e6e975c8297..2586803ba5ff 100644 --- a/ciphers/brute_force_caesar_cipher.py +++ b/ciphers/brute_force_caesar_cipher.py @@ -42,12 +42,15 @@ def decrypt(message): translated = translated + symbol print("Decryption using Key #%s: %s" % (key, translated)) + def main(): message = input("Encrypted message: ") message = message.upper() decrypt(message) -if __name__ == '__main__': + +if __name__ == "__main__": import doctest + doctest.testmod() main() diff --git a/ciphers/caesar_cipher.py b/ciphers/caesar_cipher.py index 95d65d404266..3f24e049afb0 100644 --- a/ciphers/caesar_cipher.py +++ b/ciphers/caesar_cipher.py @@ -1,5 +1,5 @@ def encrypt(strng, key): - encrypted = '' + encrypted = "" for x in strng: indx = (ord(x) + key) % 256 if indx > 126: @@ -9,7 +9,7 @@ def encrypt(strng, key): def decrypt(strng, key): - decrypted = '' + decrypted = "" for x in strng: indx = (ord(x) - key) % 256 if indx < 32: @@ -17,9 +17,10 @@ def decrypt(strng, key): decrypted = decrypted + chr(indx) return decrypted + def brute_force(strng): key = 1 - decrypted = '' + decrypted = "" while key <= 94: for x in strng: indx = (ord(x) - key) % 256 @@ -27,39 +28,39 @@ def brute_force(strng): indx = indx + 95 decrypted = decrypted + chr(indx) print("Key: {}\t| Message: {}".format(key, decrypted)) - decrypted = '' + decrypted = "" key += 1 return None def main(): while True: - print('-' * 10 + "\n**Menu**\n" + '-' * 10) + print("-" * 10 + "\n**Menu**\n" + "-" * 10) print("1.Encrpyt") print("2.Decrypt") print("3.BruteForce") print("4.Quit") choice = input("What would you like to do?: ") - if choice not in ['1', '2', '3', '4']: + if choice not in ["1", "2", "3", "4"]: print("Invalid choice, please enter a valid choice") - elif choice == '1': + elif choice == "1": strng = input("Please enter the string to be encrypted: ") key = int(input("Please enter off-set between 1-94: ")) if key in range(1, 95): print(encrypt(strng.lower(), key)) - elif choice == '2': + elif choice == "2": strng = input("Please enter the string to be decrypted: ") key = int(input("Please enter off-set between 1-94: ")) - if key in range(1,95): + if key in range(1, 95): print(decrypt(strng, key)) - elif choice == '3': + elif choice == "3": strng = input("Please enter the string to be decrypted: ") brute_force(strng) main() - elif choice == '4': + elif choice == "4": print("Goodbye.") break -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/ciphers/cryptomath_module.py b/ciphers/cryptomath_module.py index 3e8e71b117ed..fc38e4bd2a22 100644 --- a/ciphers/cryptomath_module.py +++ b/ciphers/cryptomath_module.py @@ -3,6 +3,7 @@ def gcd(a, b): a, b = b % a, a return b + def findModInverse(a, m): if gcd(a, m) != 1: return None @@ -10,5 +11,5 @@ def findModInverse(a, m): v1, v2, v3 = 0, 1, m while v3 != 0: q = u3 // v3 - v1, v2, v3, u1, u2, u3 = (u1 - q * v1), (u2 - q * v2), (u3 - q *v3), v1, v2, v3 - return u1 % m + v1, v2, v3, u1, u2, u3 = (u1 - q * v1), (u2 - q * v2), (u3 - q * v3), v1, v2, v3 + return u1 % m diff --git a/ciphers/elgamal_key_generator.py b/ciphers/elgamal_key_generator.py index 6a8751f69524..cc6b297f2daf 100644 --- a/ciphers/elgamal_key_generator.py +++ b/ciphers/elgamal_key_generator.py @@ -7,9 +7,9 @@ def main(): - print('Making key files...') - makeKeyFiles('elgamal', 2048) - print('Key files generation successful') + print("Making key files...") + makeKeyFiles("elgamal", 2048) + print("Key files generation successful") # I have written my code naively same as definition of primitive root @@ -19,7 +19,7 @@ def main(): def primitiveRoot(p_val): print("Generating primitive root of p") while True: - g = random.randrange(3,p_val) + g = random.randrange(3, p_val) if pow(g, 2, p_val) == 1: continue if pow(g, p_val, p_val) == 1: @@ -28,7 +28,7 @@ def primitiveRoot(p_val): def generateKey(keySize): - print('Generating prime p...') + print("Generating prime p...") p = rabinMiller.generateLargePrime(keySize) # select large prime number. e_1 = primitiveRoot(p) # one primitive root on modulo p. d = random.randrange(3, p) # private_key -> have to be greater than 2 for safety. @@ -41,23 +41,28 @@ def generateKey(keySize): def makeKeyFiles(name, keySize): - if os.path.exists('%s_pubkey.txt' % name) or os.path.exists('%s_privkey.txt' % name): - print('\nWARNING:') - print('"%s_pubkey.txt" or "%s_privkey.txt" already exists. \n' - 'Use a different name or delete these files and re-run this program.' % - (name, name)) + if os.path.exists("%s_pubkey.txt" % name) or os.path.exists( + "%s_privkey.txt" % name + ): + print("\nWARNING:") + print( + '"%s_pubkey.txt" or "%s_privkey.txt" already exists. \n' + "Use a different name or delete these files and re-run this program." + % (name, name) + ) sys.exit() publicKey, privateKey = generateKey(keySize) - print('\nWriting public key to file %s_pubkey.txt...' % name) - with open('%s_pubkey.txt' % name, 'w') as fo: - fo.write('%d,%d,%d,%d' % (publicKey[0], publicKey[1], publicKey[2], publicKey[3])) + print("\nWriting public key to file %s_pubkey.txt..." % name) + with open("%s_pubkey.txt" % name, "w") as fo: + fo.write( + "%d,%d,%d,%d" % (publicKey[0], publicKey[1], publicKey[2], publicKey[3]) + ) - print('Writing private key to file %s_privkey.txt...' % name) - with open('%s_privkey.txt' % name, 'w') as fo: - fo.write('%d,%d' % (privateKey[0], privateKey[1])) + print("Writing private key to file %s_privkey.txt..." % name) + with open("%s_privkey.txt" % name, "w") as fo: + fo.write("%d,%d" % (privateKey[0], privateKey[1])) -if __name__ == '__main__': +if __name__ == "__main__": main() - \ No newline at end of file diff --git a/ciphers/hill_cipher.py b/ciphers/hill_cipher.py index 89b88beed17e..e01b6a3f48a8 100644 --- a/ciphers/hill_cipher.py +++ b/ciphers/hill_cipher.py @@ -44,7 +44,7 @@ def gcd(a, b): if a == 0: return b - return gcd(b%a, a) + return gcd(b % a, a) class HillCipher: @@ -59,25 +59,29 @@ class HillCipher: modulus = numpy.vectorize(lambda x: x % 36) toInt = numpy.vectorize(lambda x: round(x)) - + def __init__(self, encrypt_key): """ encrypt_key is an NxN numpy matrix """ - self.encrypt_key = self.modulus(encrypt_key) # mod36 calc's on the encrypt key - self.checkDeterminant() # validate the determinant of the encryption key + self.encrypt_key = self.modulus(encrypt_key) # mod36 calc's on the encrypt key + self.checkDeterminant() # validate the determinant of the encryption key self.decrypt_key = None self.break_key = encrypt_key.shape[0] def checkDeterminant(self): det = round(numpy.linalg.det(self.encrypt_key)) - + if det < 0: det = det % len(self.key_string) req_l = len(self.key_string) if gcd(det, len(self.key_string)) != 1: - raise ValueError("discriminant modular {0} of encryption key({1}) is not co prime w.r.t {2}.\nTry another key.".format(req_l, det, req_l)) + raise ValueError( + "discriminant modular {0} of encryption key({1}) is not co prime w.r.t {2}.\nTry another key.".format( + req_l, det, req_l + ) + ) def processText(self, text): text = list(text.upper()) @@ -87,25 +91,27 @@ def processText(self, text): while len(text) % self.break_key != 0: text.append(last) - return ''.join(text) - + return "".join(text) + def encrypt(self, text): text = self.processText(text.upper()) - encrypted = '' + encrypted = "" for i in range(0, len(text) - self.break_key + 1, self.break_key): - batch = text[i:i+self.break_key] + batch = text[i : i + self.break_key] batch_vec = list(map(self.replaceLetters, batch)) batch_vec = numpy.matrix([batch_vec]).T - batch_encrypted = self.modulus(self.encrypt_key.dot(batch_vec)).T.tolist()[0] - encrypted_batch = ''.join(list(map(self.replaceNumbers, batch_encrypted))) + batch_encrypted = self.modulus(self.encrypt_key.dot(batch_vec)).T.tolist()[ + 0 + ] + encrypted_batch = "".join(list(map(self.replaceNumbers, batch_encrypted))) encrypted += encrypted_batch return encrypted def makeDecryptKey(self): det = round(numpy.linalg.det(self.encrypt_key)) - + if det < 0: det = det % len(self.key_string) det_inv = None @@ -114,22 +120,27 @@ def makeDecryptKey(self): det_inv = i break - inv_key = det_inv * numpy.linalg.det(self.encrypt_key) *\ - numpy.linalg.inv(self.encrypt_key) + inv_key = ( + det_inv + * numpy.linalg.det(self.encrypt_key) + * numpy.linalg.inv(self.encrypt_key) + ) return self.toInt(self.modulus(inv_key)) - + def decrypt(self, text): self.decrypt_key = self.makeDecryptKey() text = self.processText(text.upper()) - decrypted = '' + decrypted = "" for i in range(0, len(text) - self.break_key + 1, self.break_key): - batch = text[i:i+self.break_key] + batch = text[i : i + self.break_key] batch_vec = list(map(self.replaceLetters, batch)) batch_vec = numpy.matrix([batch_vec]).T - batch_decrypted = self.modulus(self.decrypt_key.dot(batch_vec)).T.tolist()[0] - decrypted_batch = ''.join(list(map(self.replaceNumbers, batch_decrypted))) + batch_decrypted = self.modulus(self.decrypt_key.dot(batch_vec)).T.tolist()[ + 0 + ] + decrypted_batch = "".join(list(map(self.replaceNumbers, batch_decrypted))) decrypted += decrypted_batch return decrypted @@ -147,21 +158,22 @@ def main(): hc = HillCipher(numpy.matrix(hill_matrix)) print("Would you like to encrypt or decrypt some text? (1 or 2)") - option = input(""" + option = input( + """ 1. Encrypt 2. Decrypt """ - ) + ) - if option == '1': + if option == "1": text_e = input("What text would you like to encrypt?: ") print("Your encrypted text is:") print(hc.encrypt(text_e)) - elif option == '2': + elif option == "2": text_d = input("What text would you like to decrypt?: ") print("Your decrypted text is:") print(hc.decrypt(text_d)) - + if __name__ == "__main__": main() diff --git a/ciphers/morse_code_implementation.py b/ciphers/morse_code_implementation.py index 5d0e7b2779b1..6df4632af4cb 100644 --- a/ciphers/morse_code_implementation.py +++ b/ciphers/morse_code_implementation.py @@ -2,68 +2,93 @@ # Dictionary representing the morse code chart -MORSE_CODE_DICT = { 'A':'.-', 'B':'-...', - 'C':'-.-.', 'D':'-..', 'E':'.', - 'F':'..-.', 'G':'--.', 'H':'....', - 'I':'..', 'J':'.---', 'K':'-.-', - 'L':'.-..', 'M':'--', 'N':'-.', - 'O':'---', 'P':'.--.', 'Q':'--.-', - 'R':'.-.', 'S':'...', 'T':'-', - 'U':'..-', 'V':'...-', 'W':'.--', - 'X':'-..-', 'Y':'-.--', 'Z':'--..', - '1':'.----', '2':'..---', '3':'...--', - '4':'....-', '5':'.....', '6':'-....', - '7':'--...', '8':'---..', '9':'----.', - '0':'-----', ', ':'--..--', '.':'.-.-.-', - '?':'..--..', '/':'-..-.', '-':'-....-', - '(':'-.--.', ')':'-.--.-'} +MORSE_CODE_DICT = { + "A": ".-", + "B": "-...", + "C": "-.-.", + "D": "-..", + "E": ".", + "F": "..-.", + "G": "--.", + "H": "....", + "I": "..", + "J": ".---", + "K": "-.-", + "L": ".-..", + "M": "--", + "N": "-.", + "O": "---", + "P": ".--.", + "Q": "--.-", + "R": ".-.", + "S": "...", + "T": "-", + "U": "..-", + "V": "...-", + "W": ".--", + "X": "-..-", + "Y": "-.--", + "Z": "--..", + "1": ".----", + "2": "..---", + "3": "...--", + "4": "....-", + "5": ".....", + "6": "-....", + "7": "--...", + "8": "---..", + "9": "----.", + "0": "-----", + ", ": "--..--", + ".": ".-.-.-", + "?": "..--..", + "/": "-..-.", + "-": "-....-", + "(": "-.--.", + ")": "-.--.-", +} def encrypt(message): - cipher = '' + cipher = "" for letter in message: - if letter != ' ': + if letter != " ": - - cipher += MORSE_CODE_DICT[letter] + ' ' + cipher += MORSE_CODE_DICT[letter] + " " else: - cipher += ' ' + cipher += " " return cipher def decrypt(message): - message += ' ' + message += " " - decipher = '' - citext = '' + decipher = "" + citext = "" for letter in message: - if (letter != ' '): - + if letter != " ": i = 0 - citext += letter else: i += 1 + if i == 2: - if i == 2 : - - - decipher += ' ' + decipher += " " else: - - decipher += list(MORSE_CODE_DICT.keys())[list(MORSE_CODE_DICT - .values()).index(citext)] - citext = '' + decipher += list(MORSE_CODE_DICT.keys())[ + list(MORSE_CODE_DICT.values()).index(citext) + ] + citext = "" return decipher @@ -78,5 +103,5 @@ def main(): print(result) -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/ciphers/onepad_cipher.py b/ciphers/onepad_cipher.py index 1dac270bda1f..5a410bfa638a 100644 --- a/ciphers/onepad_cipher.py +++ b/ciphers/onepad_cipher.py @@ -3,28 +3,28 @@ class Onepad: def encrypt(self, text): - '''Function to encrypt text using psedo-random numbers''' + """Function to encrypt text using psedo-random numbers""" plain = [ord(i) for i in text] key = [] cipher = [] for i in plain: k = random.randint(1, 300) - c = (i+k)*k + c = (i + k) * k cipher.append(c) key.append(k) return cipher, key def decrypt(self, cipher, key): - '''Function to decrypt text using psedo-random numbers.''' + """Function to decrypt text using psedo-random numbers.""" plain = [] for i in range(len(key)): - p = int((cipher[i]-(key[i])**2)/key[i]) + p = int((cipher[i] - (key[i]) ** 2) / key[i]) plain.append(chr(p)) - plain = ''.join([i for i in plain]) + plain = "".join([i for i in plain]) return plain -if __name__ == '__main__': - c, k = Onepad().encrypt('Hello') +if __name__ == "__main__": + c, k = Onepad().encrypt("Hello") print(c, k) print(Onepad().decrypt(c, k)) diff --git a/ciphers/playfair_cipher.py b/ciphers/playfair_cipher.py index 20449b161963..030fe8155a69 100644 --- a/ciphers/playfair_cipher.py +++ b/ciphers/playfair_cipher.py @@ -1,14 +1,14 @@ import string import itertools + def chunker(seq, size): it = iter(seq) while True: - chunk = tuple(itertools.islice(it, size)) - if not chunk: - return - yield chunk - + chunk = tuple(itertools.islice(it, size)) + if not chunk: + return + yield chunk def prepare_input(dirty): @@ -16,32 +16,33 @@ def prepare_input(dirty): Prepare the plaintext by up-casing it and separating repeated letters with X's """ - - dirty = ''.join([c.upper() for c in dirty if c in string.ascii_letters]) + + dirty = "".join([c.upper() for c in dirty if c in string.ascii_letters]) clean = "" - + if len(dirty) < 2: return dirty - for i in range(len(dirty)-1): + for i in range(len(dirty) - 1): clean += dirty[i] - - if dirty[i] == dirty[i+1]: - clean += 'X' - + + if dirty[i] == dirty[i + 1]: + clean += "X" + clean += dirty[-1] if len(clean) & 1: - clean += 'X' + clean += "X" return clean + def generate_table(key): # I and J are used interchangeably to allow # us to use a 5x5 table (25 letters) alphabet = "ABCDEFGHIKLMNOPQRSTUVWXYZ" - # we're using a list instead of a '2d' array because it makes the math + # we're using a list instead of a '2d' array because it makes the math # for setting up the table and doing the actual encoding/decoding simpler table = [] @@ -57,6 +58,7 @@ def generate_table(key): return table + def encode(plaintext, key): table = generate_table(key) plaintext = prepare_input(plaintext) @@ -68,14 +70,14 @@ def encode(plaintext, key): row2, col2 = divmod(table.index(char2), 5) if row1 == row2: - ciphertext += table[row1*5+(col1+1)%5] - ciphertext += table[row2*5+(col2+1)%5] + ciphertext += table[row1 * 5 + (col1 + 1) % 5] + ciphertext += table[row2 * 5 + (col2 + 1) % 5] elif col1 == col2: - ciphertext += table[((row1+1)%5)*5+col1] - ciphertext += table[((row2+1)%5)*5+col2] - else: # rectangle - ciphertext += table[row1*5+col2] - ciphertext += table[row2*5+col1] + ciphertext += table[((row1 + 1) % 5) * 5 + col1] + ciphertext += table[((row2 + 1) % 5) * 5 + col2] + else: # rectangle + ciphertext += table[row1 * 5 + col2] + ciphertext += table[row2 * 5 + col1] return ciphertext @@ -90,13 +92,13 @@ def decode(ciphertext, key): row2, col2 = divmod(table.index(char2), 5) if row1 == row2: - plaintext += table[row1*5+(col1-1)%5] - plaintext += table[row2*5+(col2-1)%5] + plaintext += table[row1 * 5 + (col1 - 1) % 5] + plaintext += table[row2 * 5 + (col2 - 1) % 5] elif col1 == col2: - plaintext += table[((row1-1)%5)*5+col1] - plaintext += table[((row2-1)%5)*5+col2] - else: # rectangle - plaintext += table[row1*5+col2] - plaintext += table[row2*5+col1] + plaintext += table[((row1 - 1) % 5) * 5 + col1] + plaintext += table[((row2 - 1) % 5) * 5 + col2] + else: # rectangle + plaintext += table[row1 * 5 + col2] + plaintext += table[row2 * 5 + col1] return plaintext diff --git a/ciphers/rabin_miller.py b/ciphers/rabin_miller.py index 21378cff6885..c544abdf9acc 100644 --- a/ciphers/rabin_miller.py +++ b/ciphers/rabin_miller.py @@ -2,6 +2,7 @@ import random + def rabinMiller(num): s = num - 1 t = 0 @@ -23,24 +24,181 @@ def rabinMiller(num): v = (v ** 2) % num return True + def isPrime(num): - if (num < 2): + if num < 2: return False - lowPrimes = [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] + lowPrimes = [ + 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, + ] if num in lowPrimes: return True @@ -51,13 +209,15 @@ def isPrime(num): return rabinMiller(num) -def generateLargePrime(keysize = 1024): + +def generateLargePrime(keysize=1024): while True: num = random.randrange(2 ** (keysize - 1), 2 ** (keysize)) if isPrime(num): return num -if __name__ == '__main__': + +if __name__ == "__main__": num = generateLargePrime() - print(('Prime number:', num)) - print(('isPrime:', isPrime(num))) + print(("Prime number:", num)) + print(("isPrime:", isPrime(num))) diff --git a/ciphers/rot13.py b/ciphers/rot13.py index 208de4890e67..a7b546511967 100644 --- a/ciphers/rot13.py +++ b/ciphers/rot13.py @@ -1,17 +1,17 @@ def dencrypt(s, n): - out = '' + out = "" for c in s: - if c >= 'A' and c <= 'Z': - out += chr(ord('A') + (ord(c) - ord('A') + n) % 26) - elif c >= 'a' and c <= 'z': - out += chr(ord('a') + (ord(c) - ord('a') + n) % 26) + if c >= "A" and c <= "Z": + out += chr(ord("A") + (ord(c) - ord("A") + n) % 26) + elif c >= "a" and c <= "z": + out += chr(ord("a") + (ord(c) - ord("a") + n) % 26) else: out += c return out def main(): - s0 = 'HELLO' + s0 = "HELLO" s1 = dencrypt(s0, 13) print(s1) # URYYB @@ -20,5 +20,5 @@ def main(): print(s2) # HELLO -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/ciphers/rsa_cipher.py b/ciphers/rsa_cipher.py index 02e5d95d1e95..a9b2dcc55daa 100644 --- a/ciphers/rsa_cipher.py +++ b/ciphers/rsa_cipher.py @@ -3,41 +3,42 @@ DEFAULT_BLOCK_SIZE = 128 BYTE_SIZE = 256 + def main(): - filename = 'encrypted_file.txt' - response = input(r'Encrypte\Decrypt [e\d]: ') + filename = "encrypted_file.txt" + response = input(r"Encrypte\Decrypt [e\d]: ") - if response.lower().startswith('e'): - mode = 'encrypt' - elif response.lower().startswith('d'): - mode = 'decrypt' + if response.lower().startswith("e"): + mode = "encrypt" + elif response.lower().startswith("d"): + mode = "decrypt" - if mode == 'encrypt': - if not os.path.exists('rsa_pubkey.txt'): - rkg.makeKeyFiles('rsa', 1024) + if mode == "encrypt": + if not os.path.exists("rsa_pubkey.txt"): + rkg.makeKeyFiles("rsa", 1024) - message = input('\nEnter message: ') - pubKeyFilename = 'rsa_pubkey.txt' - print('Encrypting and writing to %s...' % (filename)) + message = input("\nEnter message: ") + pubKeyFilename = "rsa_pubkey.txt" + print("Encrypting and writing to %s..." % (filename)) encryptedText = encryptAndWriteToFile(filename, pubKeyFilename, message) - print('\nEncrypted text:') + print("\nEncrypted text:") print(encryptedText) - elif mode == 'decrypt': - privKeyFilename = 'rsa_privkey.txt' - print('Reading from %s and decrypting...' % (filename)) + elif mode == "decrypt": + privKeyFilename = "rsa_privkey.txt" + print("Reading from %s and decrypting..." % (filename)) decryptedText = readFromFileAndDecrypt(filename, privKeyFilename) - print('writing decryption to rsa_decryption.txt...') - with open('rsa_decryption.txt', 'w') as dec: + print("writing decryption to rsa_decryption.txt...") + with open("rsa_decryption.txt", "w") as dec: dec.write(decryptedText) - print('\nDecryption:') + print("\nDecryption:") print(decryptedText) def getBlocksFromText(message, blockSize=DEFAULT_BLOCK_SIZE): - messageBytes = message.encode('ascii') + messageBytes = message.encode("ascii") blockInts = [] for blockStart in range(0, len(messageBytes), blockSize): @@ -58,7 +59,7 @@ def getTextFromBlocks(blockInts, messageLength, blockSize=DEFAULT_BLOCK_SIZE): blockInt = blockInt % (BYTE_SIZE ** i) blockMessage.insert(0, chr(asciiNumber)) message.extend(blockMessage) - return ''.join(message) + return "".join(message) def encryptMessage(message, key, blockSize=DEFAULT_BLOCK_SIZE): @@ -81,22 +82,27 @@ def decryptMessage(encryptedBlocks, messageLength, key, blockSize=DEFAULT_BLOCK_ def readKeyFile(keyFilename): with open(keyFilename) as fo: content = fo.read() - keySize, n, EorD = content.split(',') + keySize, n, EorD = content.split(",") return (int(keySize), int(n), int(EorD)) -def encryptAndWriteToFile(messageFilename, keyFilename, message, blockSize=DEFAULT_BLOCK_SIZE): +def encryptAndWriteToFile( + messageFilename, keyFilename, message, blockSize=DEFAULT_BLOCK_SIZE +): keySize, n, e = readKeyFile(keyFilename) if keySize < blockSize * 8: - sys.exit('ERROR: Block size is %s bits and key size is %s bits. The RSA cipher requires the block size to be equal to or greater than the key size. Either decrease the block size or use different keys.' % (blockSize * 8, keySize)) + sys.exit( + "ERROR: Block size is %s bits and key size is %s bits. The RSA cipher requires the block size to be equal to or greater than the key size. Either decrease the block size or use different keys." + % (blockSize * 8, keySize) + ) encryptedBlocks = encryptMessage(message, (n, e), blockSize) for i in range(len(encryptedBlocks)): encryptedBlocks[i] = str(encryptedBlocks[i]) - encryptedContent = ','.join(encryptedBlocks) - encryptedContent = '%s_%s_%s' % (len(message), blockSize, encryptedContent) - with open(messageFilename, 'w') as fo: + encryptedContent = ",".join(encryptedBlocks) + encryptedContent = "%s_%s_%s" % (len(message), blockSize, encryptedContent) + with open(messageFilename, "w") as fo: fo.write(encryptedContent) return encryptedContent @@ -105,18 +111,22 @@ def readFromFileAndDecrypt(messageFilename, keyFilename): keySize, n, d = readKeyFile(keyFilename) with open(messageFilename) as fo: content = fo.read() - messageLength, blockSize, encryptedMessage = content.split('_') + messageLength, blockSize, encryptedMessage = content.split("_") messageLength = int(messageLength) blockSize = int(blockSize) if keySize < blockSize * 8: - sys.exit('ERROR: Block size is %s bits and key size is %s bits. The RSA cipher requires the block size to be equal to or greater than the key size. Did you specify the correct key file and encrypted file?' % (blockSize * 8, keySize)) + sys.exit( + "ERROR: Block size is %s bits and key size is %s bits. The RSA cipher requires the block size to be equal to or greater than the key size. Did you specify the correct key file and encrypted file?" + % (blockSize * 8, keySize) + ) encryptedBlocks = [] - for block in encryptedMessage.split(','): + for block in encryptedMessage.split(","): encryptedBlocks.append(int(block)) return decryptMessage(encryptedBlocks, messageLength, (n, d), blockSize) -if __name__ == '__main__': + +if __name__ == "__main__": main() diff --git a/ciphers/rsa_key_generator.py b/ciphers/rsa_key_generator.py index 7cd7163b68d5..ce7c1f3dd12b 100644 --- a/ciphers/rsa_key_generator.py +++ b/ciphers/rsa_key_generator.py @@ -1,45 +1,54 @@ import random, sys, os import rabin_miller as rabinMiller, cryptomath_module as cryptoMath + def main(): - print('Making key files...') - makeKeyFiles('rsa', 1024) - print('Key files generation successful.') + print("Making key files...") + makeKeyFiles("rsa", 1024) + print("Key files generation successful.") + def generateKey(keySize): - print('Generating prime p...') + print("Generating prime p...") p = rabinMiller.generateLargePrime(keySize) - print('Generating prime q...') + print("Generating prime q...") q = rabinMiller.generateLargePrime(keySize) n = p * q - print('Generating e that is relatively prime to (p - 1) * (q - 1)...') + print("Generating e that is relatively prime to (p - 1) * (q - 1)...") while True: e = random.randrange(2 ** (keySize - 1), 2 ** (keySize)) if cryptoMath.gcd(e, (p - 1) * (q - 1)) == 1: break - print('Calculating d that is mod inverse of e...') + print("Calculating d that is mod inverse of e...") d = cryptoMath.findModInverse(e, (p - 1) * (q - 1)) publicKey = (n, e) privateKey = (n, d) return (publicKey, privateKey) + def makeKeyFiles(name, keySize): - if os.path.exists('%s_pubkey.txt' % (name)) or os.path.exists('%s_privkey.txt' % (name)): - print('\nWARNING:') - print('"%s_pubkey.txt" or "%s_privkey.txt" already exists. \nUse a different name or delete these files and re-run this program.' % (name, name)) + if os.path.exists("%s_pubkey.txt" % (name)) or os.path.exists( + "%s_privkey.txt" % (name) + ): + print("\nWARNING:") + print( + '"%s_pubkey.txt" or "%s_privkey.txt" already exists. \nUse a different name or delete these files and re-run this program.' + % (name, name) + ) sys.exit() publicKey, privateKey = generateKey(keySize) - print('\nWriting public key to file %s_pubkey.txt...' % name) - with open('%s_pubkey.txt' % name, 'w') as fo: - fo.write('%s,%s,%s' % (keySize, publicKey[0], publicKey[1])) + print("\nWriting public key to file %s_pubkey.txt..." % name) + with open("%s_pubkey.txt" % name, "w") as fo: + fo.write("%s,%s,%s" % (keySize, publicKey[0], publicKey[1])) + + print("Writing private key to file %s_privkey.txt..." % name) + with open("%s_privkey.txt" % name, "w") as fo: + fo.write("%s,%s,%s" % (keySize, privateKey[0], privateKey[1])) - print('Writing private key to file %s_privkey.txt...' % name) - with open('%s_privkey.txt' % name, 'w') as fo: - fo.write('%s,%s,%s' % (keySize, privateKey[0], privateKey[1])) -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/ciphers/simple_substitution_cipher.py b/ciphers/simple_substitution_cipher.py index 5da07f8526b9..12511cc39bbc 100644 --- a/ciphers/simple_substitution_cipher.py +++ b/ciphers/simple_substitution_cipher.py @@ -1,22 +1,24 @@ import sys, random -LETTERS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' +LETTERS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + def main(): - message = input('Enter message: ') - key = 'LFWOAYUISVKMNXPBDCRJTQEGHZ' - resp = input('Encrypt/Decrypt [e/d]: ') + message = input("Enter message: ") + key = "LFWOAYUISVKMNXPBDCRJTQEGHZ" + resp = input("Encrypt/Decrypt [e/d]: ") checkValidKey(key) - if resp.lower().startswith('e'): - mode = 'encrypt' + if resp.lower().startswith("e"): + mode = "encrypt" translated = encryptMessage(key, message) - elif resp.lower().startswith('d'): - mode = 'decrypt' + elif resp.lower().startswith("d"): + mode = "decrypt" translated = decryptMessage(key, message) - print('\n%sion: \n%s' % (mode.title(), translated)) + print("\n%sion: \n%s" % (mode.title(), translated)) + def checkValidKey(key): keyList = list(key) @@ -25,28 +27,31 @@ def checkValidKey(key): lettersList.sort() if keyList != lettersList: - sys.exit('Error in the key or symbol set.') + sys.exit("Error in the key or symbol set.") + def encryptMessage(key, message): """ >>> encryptMessage('LFWOAYUISVKMNXPBDCRJTQEGHZ', 'Harshil Darji') 'Ilcrism Olcvs' """ - return translateMessage(key, message, 'encrypt') + return translateMessage(key, message, "encrypt") + def decryptMessage(key, message): """ >>> decryptMessage('LFWOAYUISVKMNXPBDCRJTQEGHZ', 'Ilcrism Olcvs') 'Harshil Darji' """ - return translateMessage(key, message, 'decrypt') + return translateMessage(key, message, "decrypt") + def translateMessage(key, message, mode): - translated = '' + translated = "" charsA = LETTERS charsB = key - if mode == 'decrypt': + if mode == "decrypt": charsA, charsB = charsB, charsA for symbol in message: @@ -61,10 +66,12 @@ def translateMessage(key, message, mode): return translated + def getRandomKey(): key = list(LETTERS) random.shuffle(key) - return ''.join(key) + return "".join(key) + -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/ciphers/trafid_cipher.py b/ciphers/trafid_cipher.py index 53f4d288bfe2..0add9ee74beb 100644 --- a/ciphers/trafid_cipher.py +++ b/ciphers/trafid_cipher.py @@ -1,4 +1,5 @@ -#https://en.wikipedia.org/wiki/Trifid_cipher +# https://en.wikipedia.org/wiki/Trifid_cipher + def __encryptPart(messagePart, character2Number): one, two, three = "", "", "" @@ -12,7 +13,8 @@ def __encryptPart(messagePart, character2Number): two += each[1] three += each[2] - return one+two+three + return one + two + three + def __decryptPart(messagePart, character2Number): tmp, thisPart = "", "" @@ -29,20 +31,49 @@ def __decryptPart(messagePart, character2Number): return result[0], result[1], result[2] + def __prepare(message, alphabet): - #Validate message and alphabet, set to upper and remove spaces + # Validate message and alphabet, set to upper and remove spaces alphabet = alphabet.replace(" ", "").upper() message = message.replace(" ", "").upper() - #Check length and characters + # Check length and characters if len(alphabet) != 27: raise KeyError("Length of alphabet has to be 27.") for each in message: if each not in alphabet: raise ValueError("Each message character has to be included in alphabet!") - #Generate dictionares - numbers = ("111","112","113","121","122","123","131","132","133","211","212","213","221","222","223","231","232","233","311","312","313","321","322","323","331","332","333") + # Generate dictionares + numbers = ( + "111", + "112", + "113", + "121", + "122", + "123", + "131", + "132", + "133", + "211", + "212", + "213", + "221", + "222", + "223", + "231", + "232", + "233", + "311", + "312", + "313", + "321", + "322", + "323", + "331", + "332", + "333", + ) character2Number = {} number2Character = {} for letter, number in zip(alphabet, numbers): @@ -51,36 +82,39 @@ def __prepare(message, alphabet): return message, alphabet, character2Number, number2Character -def encryptMessage(message, alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ.", period=5): + +def encryptMessage(message, alphabet="ABCDEFGHIJKLMNOPQRSTUVWXYZ.", period=5): message, alphabet, character2Number, number2Character = __prepare(message, alphabet) encrypted, encrypted_numeric = "", "" - for i in range(0, len(message)+1, period): - encrypted_numeric += __encryptPart(message[i:i+period], character2Number) + for i in range(0, len(message) + 1, period): + encrypted_numeric += __encryptPart(message[i : i + period], character2Number) for i in range(0, len(encrypted_numeric), 3): - encrypted += number2Character[encrypted_numeric[i:i+3]] + encrypted += number2Character[encrypted_numeric[i : i + 3]] return encrypted -def decryptMessage(message, alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ.", period=5): + +def decryptMessage(message, alphabet="ABCDEFGHIJKLMNOPQRSTUVWXYZ.", period=5): message, alphabet, character2Number, number2Character = __prepare(message, alphabet) decrypted_numeric = [] decrypted = "" - for i in range(0, len(message)+1, period): - a,b,c = __decryptPart(message[i:i+period], character2Number) + for i in range(0, len(message) + 1, period): + a, b, c = __decryptPart(message[i : i + period], character2Number) for j in range(0, len(a)): - decrypted_numeric.append(a[j]+b[j]+c[j]) + decrypted_numeric.append(a[j] + b[j] + c[j]) for each in decrypted_numeric: decrypted += number2Character[each] return decrypted -if __name__ == '__main__': + +if __name__ == "__main__": msg = "DEFEND THE EAST WALL OF THE CASTLE." - encrypted = encryptMessage(msg,"EPSDUCVWYM.ZLKXNBTFGORIJHAQ") + encrypted = encryptMessage(msg, "EPSDUCVWYM.ZLKXNBTFGORIJHAQ") decrypted = decryptMessage(encrypted, "EPSDUCVWYM.ZLKXNBTFGORIJHAQ") print("Encrypted: {}\nDecrypted: {}".format(encrypted, decrypted)) diff --git a/ciphers/transposition_cipher.py b/ciphers/transposition_cipher.py index 1c2ed0aa0452..b6c9195b5dee 100644 --- a/ciphers/transposition_cipher.py +++ b/ciphers/transposition_cipher.py @@ -1,30 +1,33 @@ import math + def main(): - message = input('Enter message: ') - key = int(input('Enter key [2-%s]: ' % (len(message) - 1))) - mode = input('Encryption/Decryption [e/d]: ') + message = input("Enter message: ") + key = int(input("Enter key [2-%s]: " % (len(message) - 1))) + mode = input("Encryption/Decryption [e/d]: ") - if mode.lower().startswith('e'): + if mode.lower().startswith("e"): text = encryptMessage(key, message) - elif mode.lower().startswith('d'): + elif mode.lower().startswith("d"): text = decryptMessage(key, message) # Append pipe symbol (vertical bar) to identify spaces at the end. - print('Output:\n%s' %(text + '|')) + print("Output:\n%s" % (text + "|")) + def encryptMessage(key, message): """ >>> encryptMessage(6, 'Harshil Darji') 'Hlia rDsahrij' """ - cipherText = [''] * key + cipherText = [""] * key for col in range(key): pointer = col while pointer < len(message): cipherText[col] += message[pointer] pointer += key - return ''.join(cipherText) + return "".join(cipherText) + def decryptMessage(key, message): """ @@ -35,19 +38,26 @@ def decryptMessage(key, message): numRows = key numShadedBoxes = (numCols * numRows) - len(message) plainText = [""] * numCols - col = 0; row = 0; + col = 0 + row = 0 for symbol in message: plainText[col] += symbol col += 1 - if (col == numCols) or (col == numCols - 1) and (row >= numRows - numShadedBoxes): + if ( + (col == numCols) + or (col == numCols - 1) + and (row >= numRows - numShadedBoxes) + ): col = 0 row += 1 return "".join(plainText) -if __name__ == '__main__': + +if __name__ == "__main__": import doctest + doctest.testmod() main() diff --git a/ciphers/transposition_cipher_encrypt_decrypt_file.py b/ciphers/transposition_cipher_encrypt_decrypt_file.py index 8ebfc1ea7e0c..775df354e117 100644 --- a/ciphers/transposition_cipher_encrypt_decrypt_file.py +++ b/ciphers/transposition_cipher_encrypt_decrypt_file.py @@ -1,36 +1,38 @@ import time, os, sys import transposition_cipher as transCipher + def main(): - inputFile = 'Prehistoric Men.txt' - outputFile = 'Output.txt' - key = int(input('Enter key: ')) - mode = input('Encrypt/Decrypt [e/d]: ') + inputFile = "Prehistoric Men.txt" + outputFile = "Output.txt" + key = int(input("Enter key: ")) + mode = input("Encrypt/Decrypt [e/d]: ") if not os.path.exists(inputFile): - print('File %s does not exist. Quitting...' % inputFile) + print("File %s does not exist. Quitting..." % inputFile) sys.exit() if os.path.exists(outputFile): - print('Overwrite %s? [y/n]' % outputFile) - response = input('> ') - if not response.lower().startswith('y'): + print("Overwrite %s? [y/n]" % outputFile) + response = input("> ") + if not response.lower().startswith("y"): sys.exit() startTime = time.time() - if mode.lower().startswith('e'): + if mode.lower().startswith("e"): with open(inputFile) as f: content = f.read() translated = transCipher.encryptMessage(key, content) - elif mode.lower().startswith('d'): + elif mode.lower().startswith("d"): with open(outputFile) as f: content = f.read() - translated =transCipher .decryptMessage(key, content) + translated = transCipher.decryptMessage(key, content) - with open(outputFile, 'w') as outputObj: + with open(outputFile, "w") as outputObj: outputObj.write(translated) totalTime = round(time.time() - startTime, 2) - print(('Done (', totalTime, 'seconds )')) + print(("Done (", totalTime, "seconds )")) + -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/ciphers/vigenere_cipher.py b/ciphers/vigenere_cipher.py index 95eeb431109f..6c10e7d773f2 100644 --- a/ciphers/vigenere_cipher.py +++ b/ciphers/vigenere_cipher.py @@ -1,33 +1,37 @@ -LETTERS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' +LETTERS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + def main(): - message = input('Enter message: ') - key = input('Enter key [alphanumeric]: ') - mode = input('Encrypt/Decrypt [e/d]: ') + message = input("Enter message: ") + key = input("Enter key [alphanumeric]: ") + mode = input("Encrypt/Decrypt [e/d]: ") - if mode.lower().startswith('e'): - mode = 'encrypt' + if mode.lower().startswith("e"): + mode = "encrypt" translated = encryptMessage(key, message) - elif mode.lower().startswith('d'): - mode = 'decrypt' + elif mode.lower().startswith("d"): + mode = "decrypt" translated = decryptMessage(key, message) - print('\n%sed message:' % mode.title()) + print("\n%sed message:" % mode.title()) print(translated) + def encryptMessage(key, message): - ''' + """ >>> encryptMessage('HDarji', 'This is Harshil Darji from Dharmaj.') 'Akij ra Odrjqqs Gaisq muod Mphumrs.' - ''' - return translateMessage(key, message, 'encrypt') + """ + return translateMessage(key, message, "encrypt") + def decryptMessage(key, message): - ''' + """ >>> decryptMessage('HDarji', 'Akij ra Odrjqqs Gaisq muod Mphumrs.') 'This is Harshil Darji from Dharmaj.' - ''' - return translateMessage(key, message, 'decrypt') + """ + return translateMessage(key, message, "decrypt") + def translateMessage(key, message, mode): translated = [] @@ -37,9 +41,9 @@ def translateMessage(key, message, mode): for symbol in message: num = LETTERS.find(symbol.upper()) if num != -1: - if mode == 'encrypt': + if mode == "encrypt": num += LETTERS.find(key[keyIndex]) - elif mode == 'decrypt': + elif mode == "decrypt": num -= LETTERS.find(key[keyIndex]) num %= len(LETTERS) @@ -54,7 +58,8 @@ def translateMessage(key, message, mode): keyIndex = 0 else: translated.append(symbol) - return ''.join(translated) + return "".join(translated) + -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/ciphers/xor_cipher.py b/ciphers/xor_cipher.py index 8bb94212c15a..7d8dbe41fdea 100644 --- a/ciphers/xor_cipher.py +++ b/ciphers/xor_cipher.py @@ -16,121 +16,120 @@ - encrypt_file : boolean - decrypt_file : boolean """ -class XORCipher(object): - def __init__(self, key = 0): - """ + +class XORCipher(object): + def __init__(self, key=0): + """ simple constructor that receives a key or uses default key = 0 """ - #private field - self.__key = key + # private field + self.__key = key - def encrypt(self, content, key): - """ + def encrypt(self, content, key): + """ input: 'content' of type string and 'key' of type int output: encrypted string 'content' as a list of chars if key not passed the method uses the key by the constructor. otherwise key = 1 """ - # precondition - assert (isinstance(key,int) and isinstance(content,str)) + # precondition + assert isinstance(key, int) and isinstance(content, str) - key = key or self.__key or 1 + key = key or self.__key or 1 - # make sure key can be any size - while (key > 255): - key -= 255 + # make sure key can be any size + while key > 255: + key -= 255 - # This will be returned - ans = [] + # This will be returned + ans = [] - for ch in content: - ans.append(chr(ord(ch) ^ key)) + for ch in content: + ans.append(chr(ord(ch) ^ key)) - return ans + return ans - def decrypt(self,content,key): - """ + def decrypt(self, content, key): + """ input: 'content' of type list and 'key' of type int output: decrypted string 'content' as a list of chars if key not passed the method uses the key by the constructor. otherwise key = 1 """ - # precondition - assert (isinstance(key,int) and isinstance(content,list)) + # precondition + assert isinstance(key, int) and isinstance(content, list) - key = key or self.__key or 1 + key = key or self.__key or 1 - # make sure key can be any size - while (key > 255): - key -= 255 + # make sure key can be any size + while key > 255: + key -= 255 - # This will be returned - ans = [] + # This will be returned + ans = [] - for ch in content: - ans.append(chr(ord(ch) ^ key)) + for ch in content: + ans.append(chr(ord(ch) ^ key)) - return ans + return ans - - def encrypt_string(self,content, key = 0): - """ + def encrypt_string(self, content, key=0): + """ input: 'content' of type string and 'key' of type int output: encrypted string 'content' if key not passed the method uses the key by the constructor. otherwise key = 1 """ - # precondition - assert (isinstance(key,int) and isinstance(content,str)) + # precondition + assert isinstance(key, int) and isinstance(content, str) - key = key or self.__key or 1 + key = key or self.__key or 1 - # make sure key can be any size - while (key > 255): - key -= 255 + # make sure key can be any size + while key > 255: + key -= 255 - # This will be returned - ans = "" + # This will be returned + ans = "" - for ch in content: - ans += chr(ord(ch) ^ key) + for ch in content: + ans += chr(ord(ch) ^ key) - return ans + return ans - def decrypt_string(self,content,key = 0): - """ + def decrypt_string(self, content, key=0): + """ input: 'content' of type string and 'key' of type int output: decrypted string 'content' if key not passed the method uses the key by the constructor. otherwise key = 1 """ - # precondition - assert (isinstance(key,int) and isinstance(content,str)) - - key = key or self.__key or 1 + # precondition + assert isinstance(key, int) and isinstance(content, str) - # make sure key can be any size - while (key > 255): - key -= 255 + key = key or self.__key or 1 - # This will be returned - ans = "" + # make sure key can be any size + while key > 255: + key -= 255 - for ch in content: - ans += chr(ord(ch) ^ key) + # This will be returned + ans = "" - return ans + for ch in content: + ans += chr(ord(ch) ^ key) + return ans - def encrypt_file(self, file, key = 0): - """ + def encrypt_file(self, file, key=0): + """ input: filename (str) and a key (int) output: returns true if encrypt process was successful otherwise false @@ -138,25 +137,24 @@ def encrypt_file(self, file, key = 0): otherwise key = 1 """ - #precondition - assert (isinstance(file,str) and isinstance(key,int)) - - try: - with open(file,"r") as fin: - with open("encrypt.out","w+") as fout: + # precondition + assert isinstance(file, str) and isinstance(key, int) - # actual encrypt-process - for line in fin: - fout.write(self.encrypt_string(line,key)) + try: + with open(file, "r") as fin: + with open("encrypt.out", "w+") as fout: - except: - return False + # actual encrypt-process + for line in fin: + fout.write(self.encrypt_string(line, key)) - return True + except: + return False + return True - def decrypt_file(self,file, key): - """ + def decrypt_file(self, file, key): + """ input: filename (str) and a key (int) output: returns true if decrypt process was successful otherwise false @@ -164,23 +162,21 @@ def decrypt_file(self,file, key): otherwise key = 1 """ - #precondition - assert (isinstance(file,str) and isinstance(key,int)) - - try: - with open(file,"r") as fin: - with open("decrypt.out","w+") as fout: - - # actual encrypt-process - for line in fin: - fout.write(self.decrypt_string(line,key)) + # precondition + assert isinstance(file, str) and isinstance(key, int) - except: - return False + try: + with open(file, "r") as fin: + with open("decrypt.out", "w+") as fout: - return True + # actual encrypt-process + for line in fin: + fout.write(self.decrypt_string(line, key)) + except: + return False + return True # Tests diff --git a/compression/burrows_wheeler.py b/compression/burrows_wheeler.py index fabeab39adf8..50ee62aa0cb3 100644 --- a/compression/burrows_wheeler.py +++ b/compression/burrows_wheeler.py @@ -141,15 +141,10 @@ def reverse_bwt(bwt_string: str, idx_original_string: int) -> str: ) ) if idx_original_string < 0: - raise ValueError( - "The parameter idx_original_string must not be lower than 0." - ) + raise ValueError("The parameter idx_original_string must not be lower than 0.") if idx_original_string >= len(bwt_string): raise ValueError( - ( - "The parameter idx_original_string must be lower than" - " len(bwt_string)." - ) + ("The parameter idx_original_string must be lower than" " len(bwt_string).") ) ordered_rotations = [""] * len(bwt_string) @@ -166,9 +161,7 @@ def reverse_bwt(bwt_string: str, idx_original_string: int) -> str: result = bwt_transform(s) bwt_output_msg = "Burrows Wheeler tranform for string '{}' results in '{}'" print(bwt_output_msg.format(s, result["bwt_string"])) - original_string = reverse_bwt( - result["bwt_string"], result["idx_original_string"] - ) + original_string = reverse_bwt(result["bwt_string"], result["idx_original_string"]) fmt = ( "Reversing Burrows Wheeler tranform for entry '{}' we get original" " string '{}'" diff --git a/compression/huffman.py b/compression/huffman.py index 7417551ba209..73c084351c85 100644 --- a/compression/huffman.py +++ b/compression/huffman.py @@ -1,5 +1,6 @@ import sys + class Letter: def __init__(self, letter, freq): self.letter = letter @@ -7,7 +8,7 @@ def __init__(self, letter, freq): self.bitstring = "" def __repr__(self): - return f'{self.letter}:{self.freq}' + return f"{self.letter}:{self.freq}" class TreeNode: @@ -31,6 +32,7 @@ def parse_file(file_path): chars[c] = chars[c] + 1 if c in chars.keys() else 1 return sorted([Letter(c, f) for c, f in chars.items()], key=lambda l: l.freq) + def build_tree(letters): """ Run through the list of Letters and build the min heap @@ -45,6 +47,7 @@ def build_tree(letters): letters.sort(key=lambda l: l.freq) return letters[0] + def traverse_tree(root, bitstring): """ Recursively traverse the Huffman Tree to set each @@ -58,6 +61,7 @@ def traverse_tree(root, bitstring): letters += traverse_tree(root.right, bitstring + "1") return letters + def huffman(file_path): """ Parse the file, build the tree, then run through the file @@ -67,7 +71,7 @@ def huffman(file_path): letters_list = parse_file(file_path) root = build_tree(letters_list) letters = traverse_tree(root, "") - print(f'Huffman Coding of {file_path}: ') + print(f"Huffman Coding of {file_path}: ") with open(file_path) as f: while True: c = f.read(1) @@ -77,6 +81,7 @@ def huffman(file_path): print(le.bitstring, end=" ") print() + if __name__ == "__main__": # pass the file path to the huffman function huffman(sys.argv[1]) diff --git a/compression/peak_signal_to_noise_ratio.py b/compression/peak_signal_to_noise_ratio.py index b0efb1462dcc..418832a8127c 100644 --- a/compression/peak_signal_to_noise_ratio.py +++ b/compression/peak_signal_to_noise_ratio.py @@ -9,6 +9,7 @@ import cv2 import numpy as np + def psnr(original, contrast): mse = np.mean((original - contrast) ** 2) if mse == 0: @@ -21,11 +22,13 @@ def psnr(original, contrast): def main(): dir_path = os.path.dirname(os.path.realpath(__file__)) # Loading images (original image and compressed image) - original = cv2.imread(os.path.join(dir_path, 'image_data/original_image.png')) - contrast = cv2.imread(os.path.join(dir_path, 'image_data/compressed_image.png'), 1) + original = cv2.imread(os.path.join(dir_path, "image_data/original_image.png")) + contrast = cv2.imread(os.path.join(dir_path, "image_data/compressed_image.png"), 1) - original2 = cv2.imread(os.path.join(dir_path, 'image_data/PSNR-example-base.png')) - contrast2 = cv2.imread(os.path.join(dir_path, 'image_data/PSNR-example-comp-10.jpg'), 1) + original2 = cv2.imread(os.path.join(dir_path, "image_data/PSNR-example-base.png")) + contrast2 = cv2.imread( + os.path.join(dir_path, "image_data/PSNR-example-comp-10.jpg"), 1 + ) # Value expected: 29.73dB print("-- First Test --") @@ -36,5 +39,5 @@ def main(): print(f"PSNR value is {psnr(original2, contrast2)} dB") -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/conversions/decimal_to_binary.py b/conversions/decimal_to_binary.py index 934cf0dfb363..ad4ba166745d 100644 --- a/conversions/decimal_to_binary.py +++ b/conversions/decimal_to_binary.py @@ -55,4 +55,5 @@ def decimal_to_binary(num): if __name__ == "__main__": import doctest + doctest.testmod() diff --git a/conversions/decimal_to_hexadecimal.py b/conversions/decimal_to_hexadecimal.py index e6435f1ef570..a70e3c7b97bf 100644 --- a/conversions/decimal_to_hexadecimal.py +++ b/conversions/decimal_to_hexadecimal.py @@ -2,24 +2,25 @@ # set decimal value for each hexadecimal digit values = { - 0:'0', - 1:'1', - 2:'2', - 3:'3', - 4:'4', - 5:'5', - 6:'6', - 7:'7', - 8:'8', - 9:'9', - 10:'a', - 11:'b', - 12:'c', - 13:'d', - 14:'e', - 15:'f' + 0: "0", + 1: "1", + 2: "2", + 3: "3", + 4: "4", + 5: "5", + 6: "6", + 7: "7", + 8: "8", + 9: "9", + 10: "a", + 11: "b", + 12: "c", + 13: "d", + 14: "e", + 15: "f", } + def decimal_to_hexadecimal(decimal): """ take integer decimal value, return hexadecimal representation as str beginning with 0x @@ -56,7 +57,7 @@ def decimal_to_hexadecimal(decimal): True """ assert type(decimal) in (int, float) and decimal == int(decimal) - hexadecimal = '' + hexadecimal = "" negative = False if decimal < 0: negative = True @@ -64,11 +65,13 @@ def decimal_to_hexadecimal(decimal): while decimal > 0: decimal, remainder = divmod(decimal, 16) hexadecimal = values[remainder] + hexadecimal - hexadecimal = '0x' + hexadecimal + hexadecimal = "0x" + hexadecimal if negative: - hexadecimal = '-' + hexadecimal + hexadecimal = "-" + hexadecimal return hexadecimal -if __name__ == '__main__': + +if __name__ == "__main__": import doctest + doctest.testmod() diff --git a/conversions/decimal_to_octal.py b/conversions/decimal_to_octal.py index 187a0300e33a..0b005429d9d7 100644 --- a/conversions/decimal_to_octal.py +++ b/conversions/decimal_to_octal.py @@ -16,7 +16,7 @@ def decimal_to_octal(num): counter += 1 num = math.floor(num / 8) # basically /= 8 without remainder if any # This formatting removes trailing '.0' from `octal`. - return'{0:g}'.format(float(octal)) + return "{0:g}".format(float(octal)) def main(): @@ -34,5 +34,5 @@ def main(): print("\n") -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/data_structures/binary_tree/avl_tree.py b/data_structures/binary_tree/avl_tree.py index ff44963d1690..31d12c811105 100644 --- a/data_structures/binary_tree/avl_tree.py +++ b/data_structures/binary_tree/avl_tree.py @@ -1,71 +1,88 @@ # -*- coding: utf-8 -*- -''' +""" An auto-balanced binary tree! -''' +""" import math import random + + class my_queue: def __init__(self): self.data = [] self.head = 0 self.tail = 0 + def isEmpty(self): return self.head == self.tail - def push(self,data): + + def push(self, data): self.data.append(data) self.tail = self.tail + 1 + def pop(self): ret = self.data[self.head] self.head = self.head + 1 return ret + def count(self): return self.tail - self.head + def print(self): print(self.data) print("**************") - print(self.data[self.head:self.tail]) - + print(self.data[self.head : self.tail]) + + class my_node: - def __init__(self,data): + def __init__(self, data): self.data = data self.left = None self.right = None self.height = 1 + def getdata(self): return self.data + def getleft(self): return self.left + def getright(self): return self.right + def getheight(self): return self.height - def setdata(self,data): + + def setdata(self, data): self.data = data return - def setleft(self,node): + + def setleft(self, node): self.left = node return - def setright(self,node): + + def setright(self, node): self.right = node return - def setheight(self,height): + + def setheight(self, height): self.height = height return + def getheight(node): if node is None: return 0 return node.getheight() -def my_max(a,b): + +def my_max(a, b): if a > b: return a return b - def leftrotation(node): - r''' + r""" A B / \ / \ B C Bl A @@ -75,33 +92,35 @@ def leftrotation(node): UB UB = unbalanced node - ''' - print("left rotation node:",node.getdata()) + """ + print("left rotation node:", node.getdata()) ret = node.getleft() node.setleft(ret.getright()) ret.setright(node) - h1 = my_max(getheight(node.getright()),getheight(node.getleft())) + 1 + h1 = my_max(getheight(node.getright()), getheight(node.getleft())) + 1 node.setheight(h1) - h2 = my_max(getheight(ret.getright()),getheight(ret.getleft())) + 1 + h2 = my_max(getheight(ret.getright()), getheight(ret.getleft())) + 1 ret.setheight(h2) return ret + def rightrotation(node): - ''' + """ a mirror symmetry rotation of the leftrotation - ''' - print("right rotation node:",node.getdata()) + """ + print("right rotation node:", node.getdata()) ret = node.getright() node.setright(ret.getleft()) ret.setleft(node) - h1 = my_max(getheight(node.getright()),getheight(node.getleft())) + 1 + h1 = my_max(getheight(node.getright()), getheight(node.getleft())) + 1 node.setheight(h1) - h2 = my_max(getheight(ret.getright()),getheight(ret.getleft())) + 1 + h2 = my_max(getheight(ret.getright()), getheight(ret.getleft())) + 1 ret.setheight(h2) return ret + def rlrotation(node): - r''' + r""" A A Br / \ / \ / \ B C RR Br C LR B A @@ -110,51 +129,60 @@ def rlrotation(node): \ / UB Bl RR = rightrotation LR = leftrotation - ''' + """ node.setleft(rightrotation(node.getleft())) return leftrotation(node) + def lrrotation(node): node.setright(leftrotation(node.getright())) return rightrotation(node) -def insert_node(node,data): +def insert_node(node, data): if node is None: return my_node(data) if data < node.getdata(): - node.setleft(insert_node(node.getleft(),data)) - if getheight(node.getleft()) - getheight(node.getright()) == 2: #an unbalance detected - if data < node.getleft().getdata(): #new node is the left child of the left child + node.setleft(insert_node(node.getleft(), data)) + if ( + getheight(node.getleft()) - getheight(node.getright()) == 2 + ): # an unbalance detected + if ( + data < node.getleft().getdata() + ): # new node is the left child of the left child node = leftrotation(node) else: - node = rlrotation(node) #new node is the right child of the left child + node = rlrotation(node) # new node is the right child of the left child else: - node.setright(insert_node(node.getright(),data)) + node.setright(insert_node(node.getright(), data)) if getheight(node.getright()) - getheight(node.getleft()) == 2: if data < node.getright().getdata(): node = lrrotation(node) else: node = rightrotation(node) - h1 = my_max(getheight(node.getright()),getheight(node.getleft())) + 1 + h1 = my_max(getheight(node.getright()), getheight(node.getleft())) + 1 node.setheight(h1) return node + def getRightMost(root): while root.getright() is not None: root = root.getright() return root.getdata() + + def getLeftMost(root): while root.getleft() is not None: root = root.getleft() return root.getdata() -def del_node(root,data): + +def del_node(root, data): if root.getdata() == data: if root.getleft() is not None and root.getright() is not None: temp_data = getLeftMost(root.getright()) root.setdata(temp_data) - root.setright(del_node(root.getright(),temp_data)) + root.setright(del_node(root.getright(), temp_data)) elif root.getleft() is not None: root = root.getleft() else: @@ -164,12 +192,12 @@ def del_node(root,data): print("No such data") return root else: - root.setleft(del_node(root.getleft(),data)) + root.setleft(del_node(root.getleft(), data)) elif root.getdata() < data: if root.getright() is None: return root else: - root.setright(del_node(root.getright(),data)) + root.setright(del_node(root.getright(), data)) if root is None: return root if getheight(root.getright()) - getheight(root.getleft()) == 2: @@ -182,27 +210,31 @@ def del_node(root,data): root = leftrotation(root) else: root = rlrotation(root) - height = my_max(getheight(root.getright()),getheight(root.getleft())) + 1 + height = my_max(getheight(root.getright()), getheight(root.getleft())) + 1 root.setheight(height) return root + class AVLtree: def __init__(self): self.root = None + def getheight(self): -# print("yyy") + # print("yyy") return getheight(self.root) - def insert(self,data): - print("insert:"+str(data)) - self.root = insert_node(self.root,data) - - def del_node(self,data): - print("delete:"+str(data)) + + def insert(self, data): + print("insert:" + str(data)) + self.root = insert_node(self.root, data) + + def del_node(self, data): + print("delete:" + str(data)) if self.root is None: print("Tree is empty!") return - self.root = del_node(self.root,data) - def traversale(self): #a level traversale, gives a more intuitive look on the tree + self.root = del_node(self.root, data) + + def traversale(self): # a level traversale, gives a more intuitive look on the tree q = my_queue() q.push(self.root) layer = self.getheight() @@ -211,21 +243,21 @@ def traversale(self): #a level traversale, gives a more intuitive look on the tr cnt = 0 while not q.isEmpty(): node = q.pop() - space = " "*int(math.pow(2,layer-1)) - print(space,end = "") + space = " " * int(math.pow(2, layer - 1)) + print(space, end="") if node is None: - print("*",end = "") + print("*", end="") q.push(None) q.push(None) else: - print(node.getdata(),end = "") + print(node.getdata(), end="") q.push(node.getleft()) q.push(node.getright()) - print(space,end = "") + print(space, end="") cnt = cnt + 1 for i in range(100): - if cnt == math.pow(2,i) - 1: - layer = layer -1 + if cnt == math.pow(2, i) - 1: + layer = layer - 1 if layer == 0: print() print("*************************************") @@ -235,11 +267,13 @@ def traversale(self): #a level traversale, gives a more intuitive look on the tr print() print("*************************************") return - + def test(self): getheight(None) print("****") self.getheight() + + if __name__ == "__main__": t = AVLtree() t.traversale() @@ -248,7 +282,7 @@ def test(self): for i in l: t.insert(i) t.traversale() - + random.shuffle(l) for i in l: t.del_node(i) diff --git a/data_structures/binary_tree/basic_binary_tree.py b/data_structures/binary_tree/basic_binary_tree.py index 7c6240fb4dd4..6b7de7803704 100644 --- a/data_structures/binary_tree/basic_binary_tree.py +++ b/data_structures/binary_tree/basic_binary_tree.py @@ -1,12 +1,13 @@ -class Node: # This is the Class Node with constructor that contains data variable to type data and left,right pointers. +class Node: # This is the Class Node with constructor that contains data variable to type data and left,right pointers. def __init__(self, data): self.data = data self.left = None self.right = None -def display(tree): #In Order traversal of the tree - if tree is None: +def display(tree): # In Order traversal of the tree + + if tree is None: return if tree.left is not None: @@ -19,7 +20,10 @@ def display(tree): #In Order traversal of the tree return -def depth_of_tree(tree): #This is the recursive function to find the depth of binary tree. + +def depth_of_tree( + tree +): # This is the recursive function to find the depth of binary tree. if tree is None: return 0 else: @@ -31,18 +35,20 @@ def depth_of_tree(tree): #This is the recursive function to find the depth of bi return 1 + depth_r_tree -def is_full_binary_tree(tree): # This functions returns that is it full binary tree or not? +def is_full_binary_tree( + tree +): # This functions returns that is it full binary tree or not? if tree is None: return True if (tree.left is None) and (tree.right is None): return True if (tree.left is not None) and (tree.right is not None): - return (is_full_binary_tree(tree.left) and is_full_binary_tree(tree.right)) + return is_full_binary_tree(tree.left) and is_full_binary_tree(tree.right) else: return False -def main(): # Main func for testing. +def main(): # Main func for testing. tree = Node(1) tree.left = Node(2) tree.right = Node(3) @@ -59,5 +65,5 @@ def main(): # Main func for testing. display(tree) -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/data_structures/binary_tree/binary_search_tree.py b/data_structures/binary_tree/binary_search_tree.py index 634b6cbcc231..c6e037880bb6 100644 --- a/data_structures/binary_tree/binary_search_tree.py +++ b/data_structures/binary_tree/binary_search_tree.py @@ -1,13 +1,14 @@ -''' +""" A binary search Tree -''' -class Node: +""" + +class Node: def __init__(self, label, parent): self.label = label self.left = None self.right = None - #Added in order to delete a node easier + # Added in order to delete a node easier self.parent = parent def getLabel(self): @@ -34,8 +35,8 @@ def getParent(self): def setParent(self, parent): self.parent = parent -class BinarySearchTree: +class BinarySearchTree: def __init__(self): self.root = None @@ -46,90 +47,90 @@ def insert(self, label): if self.empty(): self.root = new_node else: - #If Tree is not empty + # If Tree is not empty curr_node = self.root - #While we don't get to a leaf + # While we don't get to a leaf while curr_node is not None: - #We keep reference of the parent node + # We keep reference of the parent node parent_node = curr_node - #If node label is less than current node + # If node label is less than current node if new_node.getLabel() < curr_node.getLabel(): - #We go left + # We go left curr_node = curr_node.getLeft() else: - #Else we go right + # Else we go right curr_node = curr_node.getRight() - #We insert the new node in a leaf + # We insert the new node in a leaf if new_node.getLabel() < parent_node.getLabel(): parent_node.setLeft(new_node) else: parent_node.setRight(new_node) - #Set parent to the new node + # Set parent to the new node new_node.setParent(parent_node) def delete(self, label): - if (not self.empty()): - #Look for the node with that label + if not self.empty(): + # Look for the node with that label node = self.getNode(label) - #If the node exists - if(node is not None): - #If it has no children - if(node.getLeft() is None and node.getRight() is None): + # If the node exists + if node is not None: + # If it has no children + if node.getLeft() is None and node.getRight() is None: self.__reassignNodes(node, None) node = None - #Has only right children - elif(node.getLeft() is None and node.getRight() is not None): + # Has only right children + elif node.getLeft() is None and node.getRight() is not None: self.__reassignNodes(node, node.getRight()) - #Has only left children - elif(node.getLeft() is not None and node.getRight() is None): + # Has only left children + elif node.getLeft() is not None and node.getRight() is None: self.__reassignNodes(node, node.getLeft()) - #Has two children + # Has two children else: - #Gets the max value of the left branch + # Gets the max value of the left branch tmpNode = self.getMax(node.getLeft()) - #Deletes the tmpNode + # Deletes the tmpNode self.delete(tmpNode.getLabel()) - #Assigns the value to the node to delete and keesp tree structure + # Assigns the value to the node to delete and keesp tree structure node.setLabel(tmpNode.getLabel()) def getNode(self, label): curr_node = None - #If the tree is not empty - if(not self.empty()): - #Get tree root + # If the tree is not empty + if not self.empty(): + # Get tree root curr_node = self.getRoot() - #While we don't find the node we look for - #I am using lazy evaluation here to avoid NoneType Attribute error + # While we don't find the node we look for + # I am using lazy evaluation here to avoid NoneType Attribute error while curr_node is not None and curr_node.getLabel() is not label: - #If node label is less than current node + # If node label is less than current node if label < curr_node.getLabel(): - #We go left + # We go left curr_node = curr_node.getLeft() else: - #Else we go right + # Else we go right curr_node = curr_node.getRight() return curr_node - def getMax(self, root = None): - if(root is not None): + def getMax(self, root=None): + if root is not None: curr_node = root else: - #We go deep on the right branch + # We go deep on the right branch curr_node = self.getRoot() - if(not self.empty()): - while(curr_node.getRight() is not None): + if not self.empty(): + while curr_node.getRight() is not None: curr_node = curr_node.getRight() return curr_node - def getMin(self, root = None): - if(root is not None): + def getMin(self, root=None): + if root is not None: curr_node = root else: - #We go deep on the left branch + # We go deep on the left branch curr_node = self.getRoot() - if(not self.empty()): + if not self.empty(): curr_node = self.getRoot() - while(curr_node.getLeft() is not None): + while curr_node.getLeft() is not None: curr_node = curr_node.getLeft() return curr_node @@ -150,34 +151,34 @@ def getRoot(self): return self.root def __isRightChildren(self, node): - if(node == node.getParent().getRight()): + if node == node.getParent().getRight(): return True return False def __reassignNodes(self, node, newChildren): - if(newChildren is not None): + if newChildren is not None: newChildren.setParent(node.getParent()) - if(node.getParent() is not None): - #If it is the Right Children - if(self.__isRightChildren(node)): + if node.getParent() is not None: + # If it is the Right Children + if self.__isRightChildren(node): node.getParent().setRight(newChildren) else: - #Else it is the left children + # Else it is the left children node.getParent().setLeft(newChildren) - #This function traversal the tree. By default it returns an - #In order traversal list. You can pass a function to traversal - #The tree as needed by client code - def traversalTree(self, traversalFunction = None, root = None): - if(traversalFunction is None): - #Returns a list of nodes in preOrder by default + # This function traversal the tree. By default it returns an + # In order traversal list. You can pass a function to traversal + # The tree as needed by client code + def traversalTree(self, traversalFunction=None, root=None): + if traversalFunction is None: + # Returns a list of nodes in preOrder by default return self.__InOrderTraversal(self.root) else: - #Returns a list of nodes in the order that the users wants to + # Returns a list of nodes in the order that the users wants to return traversalFunction(self.root) - #Returns an string of all the nodes labels in the list - #In Order Traversal + # Returns an string of all the nodes labels in the list + # In Order Traversal def __str__(self): list = self.__InOrderTraversal(self.root) str = "" @@ -185,6 +186,7 @@ def __str__(self): str = str + " " + x.getLabel().__str__() return str + def InPreOrder(curr_node): nodeList = [] if curr_node is not None: @@ -193,8 +195,9 @@ def InPreOrder(curr_node): nodeList = nodeList + InPreOrder(curr_node.getRight()) return nodeList + def testBinarySearchTree(): - r''' + r""" Example 8 / \ @@ -203,15 +206,15 @@ def testBinarySearchTree(): 1 6 14 / \ / 4 7 13 - ''' + """ - r''' + r""" Example After Deletion 7 / \ 1 4 - ''' + """ t = BinarySearchTree() t.insert(8) t.insert(3) @@ -223,20 +226,20 @@ def testBinarySearchTree(): t.insert(4) t.insert(7) - #Prints all the elements of the list in order traversal + # Prints all the elements of the list in order traversal print(t.__str__()) - if(t.getNode(6) is not None): + if t.getNode(6) is not None: print("The label 6 exists") else: print("The label 6 doesn't exist") - if(t.getNode(-1) is not None): + if t.getNode(-1) is not None: print("The label -1 exists") else: print("The label -1 doesn't exist") - if(not t.empty()): + if not t.empty(): print(("Max Value: ", t.getMax().getLabel())) print(("Min Value: ", t.getMin().getLabel())) @@ -247,11 +250,12 @@ def testBinarySearchTree(): t.delete(6) t.delete(14) - #Gets all the elements of the tree In pre order - #And it prints them + # Gets all the elements of the tree In pre order + # And it prints them list = t.traversalTree(InPreOrder, t.root) for x in list: print(x) + if __name__ == "__main__": testBinarySearchTree() diff --git a/data_structures/binary_tree/fenwick_tree.py b/data_structures/binary_tree/fenwick_tree.py index 30a87fbd7fcf..54f0f07ac68d 100644 --- a/data_structures/binary_tree/fenwick_tree.py +++ b/data_structures/binary_tree/fenwick_tree.py @@ -1,28 +1,28 @@ class FenwickTree: - - def __init__(self, SIZE): # create fenwick tree with size SIZE + def __init__(self, SIZE): # create fenwick tree with size SIZE self.Size = SIZE - self.ft = [0 for i in range (0,SIZE)] + self.ft = [0 for i in range(0, SIZE)] - def update(self, i, val): # update data (adding) in index i in O(lg N) - while (i < self.Size): + def update(self, i, val): # update data (adding) in index i in O(lg N) + while i < self.Size: self.ft[i] += val i += i & (-i) - def query(self, i): # query cumulative data from index 0 to i in O(lg N) + def query(self, i): # query cumulative data from index 0 to i in O(lg N) ret = 0 - while (i > 0): + while i > 0: ret += self.ft[i] i -= i & (-i) return ret -if __name__ == '__main__': + +if __name__ == "__main__": f = FenwickTree(100) - f.update(1,20) - f.update(4,4) + f.update(1, 20) + f.update(4, 4) print(f.query(1)) print(f.query(3)) print(f.query(4)) - f.update(2,-5) + f.update(2, -5) print(f.query(1)) print(f.query(3)) diff --git a/data_structures/binary_tree/lazy_segment_tree.py b/data_structures/binary_tree/lazy_segment_tree.py index bbe37a6eb97f..acd551b41b96 100644 --- a/data_structures/binary_tree/lazy_segment_tree.py +++ b/data_structures/binary_tree/lazy_segment_tree.py @@ -1,34 +1,38 @@ import math -class SegmentTree: +class SegmentTree: def __init__(self, N): self.N = N - self.st = [0 for i in range(0,4*N)] # approximate the overall size of segment tree with array N - self.lazy = [0 for i in range(0,4*N)] # create array to store lazy update - self.flag = [0 for i in range(0,4*N)] # flag for lazy update + self.st = [ + 0 for i in range(0, 4 * N) + ] # approximate the overall size of segment tree with array N + self.lazy = [0 for i in range(0, 4 * N)] # create array to store lazy update + self.flag = [0 for i in range(0, 4 * N)] # flag for lazy update def left(self, idx): - return idx*2 + return idx * 2 def right(self, idx): - return idx*2 + 1 + return idx * 2 + 1 def build(self, idx, l, r, A): - if l==r: - self.st[idx] = A[l-1] - else : - mid = (l+r)//2 - self.build(self.left(idx),l,mid, A) - self.build(self.right(idx),mid+1,r, A) - self.st[idx] = max(self.st[self.left(idx)] , self.st[self.right(idx)]) + if l == r: + self.st[idx] = A[l - 1] + else: + mid = (l + r) // 2 + self.build(self.left(idx), l, mid, A) + self.build(self.right(idx), mid + 1, r, A) + self.st[idx] = max(self.st[self.left(idx)], self.st[self.right(idx)]) # update with O(lg N) (Normal segment tree without lazy update will take O(Nlg N) for each update) - def update(self, idx, l, r, a, b, val): # update(1, 1, N, a, b, v) for update val v to [a,b] + def update( + self, idx, l, r, a, b, val + ): # update(1, 1, N, a, b, v) for update val v to [a,b] if self.flag[idx] == True: self.st[idx] = self.lazy[idx] self.flag[idx] = False - if l!=r: + if l != r: self.lazy[self.left(idx)] = self.lazy[idx] self.lazy[self.right(idx)] = self.lazy[idx] self.flag[self.left(idx)] = True @@ -36,22 +40,22 @@ def update(self, idx, l, r, a, b, val): # update(1, 1, N, a, b, v) for update va if r < a or l > b: return True - if l >= a and r <= b : + if l >= a and r <= b: self.st[idx] = val - if l!=r: + if l != r: self.lazy[self.left(idx)] = val self.lazy[self.right(idx)] = val self.flag[self.left(idx)] = True self.flag[self.right(idx)] = True return True - mid = (l+r)//2 - self.update(self.left(idx),l,mid,a,b,val) - self.update(self.right(idx),mid+1,r,a,b,val) - self.st[idx] = max(self.st[self.left(idx)] , self.st[self.right(idx)]) + mid = (l + r) // 2 + self.update(self.left(idx), l, mid, a, b, val) + self.update(self.right(idx), mid + 1, r, a, b, val) + self.st[idx] = max(self.st[self.left(idx)], self.st[self.right(idx)]) return True # query with O(lg N) - def query(self, idx, l, r, a, b): #query(1, 1, N, a, b) for query max of [a,b] + def query(self, idx, l, r, a, b): # query(1, 1, N, a, b) for query max of [a,b] if self.flag[idx] == True: self.st[idx] = self.lazy[idx] self.flag[idx] = False @@ -64,27 +68,27 @@ def query(self, idx, l, r, a, b): #query(1, 1, N, a, b) for query max of [a,b] return -math.inf if l >= a and r <= b: return self.st[idx] - mid = (l+r)//2 - q1 = self.query(self.left(idx),l,mid,a,b) - q2 = self.query(self.right(idx),mid+1,r,a,b) - return max(q1,q2) + mid = (l + r) // 2 + q1 = self.query(self.left(idx), l, mid, a, b) + q2 = self.query(self.right(idx), mid + 1, r, a, b) + return max(q1, q2) def showData(self): showList = [] - for i in range(1,N+1): + for i in range(1, N + 1): showList += [self.query(1, 1, self.N, i, i)] print(showList) -if __name__ == '__main__': - A = [1,2,-4,7,3,-5,6,11,-20,9,14,15,5,2,-8] +if __name__ == "__main__": + A = [1, 2, -4, 7, 3, -5, 6, 11, -20, 9, 14, 15, 5, 2, -8] N = 15 segt = SegmentTree(N) - segt.build(1,1,N,A) - print(segt.query(1,1,N,4,6)) - print(segt.query(1,1,N,7,11)) - print(segt.query(1,1,N,7,12)) - segt.update(1,1,N,1,3,111) - print(segt.query(1,1,N,1,15)) - segt.update(1,1,N,7,8,235) + segt.build(1, 1, N, A) + print(segt.query(1, 1, N, 4, 6)) + print(segt.query(1, 1, N, 7, 11)) + print(segt.query(1, 1, N, 7, 12)) + segt.update(1, 1, N, 1, 3, 111) + print(segt.query(1, 1, N, 1, 15)) + segt.update(1, 1, N, 7, 8, 235) segt.showData() diff --git a/data_structures/binary_tree/lca.py b/data_structures/binary_tree/lca.py index 9c9d8ca629c7..c18f1e944bab 100644 --- a/data_structures/binary_tree/lca.py +++ b/data_structures/binary_tree/lca.py @@ -75,7 +75,7 @@ def main(): 10: [], 11: [], 12: [], - 13: [] + 13: [], } level, parent = bfs(level, parent, max_node, graph, 1) parent = creatSparse(max_node, parent) diff --git a/data_structures/binary_tree/red_black_tree.py b/data_structures/binary_tree/red_black_tree.py index 526f5ec27987..908f13cd581e 100644 --- a/data_structures/binary_tree/red_black_tree.py +++ b/data_structures/binary_tree/red_black_tree.py @@ -700,7 +700,6 @@ def main(): print_results("Tree traversal", test_tree_chaining()) - print("Testing tree balancing...") print("This should only be a few seconds.") test_insertion_speed() diff --git a/data_structures/binary_tree/segment_tree.py b/data_structures/binary_tree/segment_tree.py index da3d15f26b6a..ad9476b4514b 100644 --- a/data_structures/binary_tree/segment_tree.py +++ b/data_structures/binary_tree/segment_tree.py @@ -1,10 +1,12 @@ import math -class SegmentTree: +class SegmentTree: def __init__(self, A): self.N = len(A) - self.st = [0] * (4 * self.N) # approximate the overall size of segment tree with array N + self.st = [0] * ( + 4 * self.N + ) # approximate the overall size of segment tree with array N self.build(1, 0, self.N - 1) def left(self, idx): @@ -20,51 +22,55 @@ def build(self, idx, l, r): mid = (l + r) // 2 self.build(self.left(idx), l, mid) self.build(self.right(idx), mid + 1, r) - self.st[idx] = max(self.st[self.left(idx)] , self.st[self.right(idx)]) + self.st[idx] = max(self.st[self.left(idx)], self.st[self.right(idx)]) def update(self, a, b, val): return self.update_recursive(1, 0, self.N - 1, a - 1, b - 1, val) - def update_recursive(self, idx, l, r, a, b, val): # update(1, 1, N, a, b, v) for update val v to [a,b] + def update_recursive( + self, idx, l, r, a, b, val + ): # update(1, 1, N, a, b, v) for update val v to [a,b] if r < a or l > b: return True - if l == r : + if l == r: self.st[idx] = val return True - mid = (l+r)//2 + mid = (l + r) // 2 self.update_recursive(self.left(idx), l, mid, a, b, val) - self.update_recursive(self.right(idx), mid+1, r, a, b, val) - self.st[idx] = max(self.st[self.left(idx)] , self.st[self.right(idx)]) + self.update_recursive(self.right(idx), mid + 1, r, a, b, val) + self.st[idx] = max(self.st[self.left(idx)], self.st[self.right(idx)]) return True def query(self, a, b): return self.query_recursive(1, 0, self.N - 1, a - 1, b - 1) - def query_recursive(self, idx, l, r, a, b): #query(1, 1, N, a, b) for query max of [a,b] + def query_recursive( + self, idx, l, r, a, b + ): # query(1, 1, N, a, b) for query max of [a,b] if r < a or l > b: return -math.inf if l >= a and r <= b: return self.st[idx] - mid = (l+r)//2 + mid = (l + r) // 2 q1 = self.query_recursive(self.left(idx), l, mid, a, b) q2 = self.query_recursive(self.right(idx), mid + 1, r, a, b) return max(q1, q2) def showData(self): showList = [] - for i in range(1,N+1): + for i in range(1, N + 1): showList += [self.query(i, i)] print(showList) -if __name__ == '__main__': - A = [1,2,-4,7,3,-5,6,11,-20,9,14,15,5,2,-8] +if __name__ == "__main__": + A = [1, 2, -4, 7, 3, -5, 6, 11, -20, 9, 14, 15, 5, 2, -8] N = 15 segt = SegmentTree(A) print(segt.query(4, 6)) print(segt.query(7, 11)) print(segt.query(7, 12)) - segt.update(1,3,111) + segt.update(1, 3, 111) print(segt.query(1, 15)) - segt.update(7,8,235) + segt.update(7, 8, 235) segt.showData() diff --git a/data_structures/binary_tree/treap.py b/data_structures/binary_tree/treap.py index 0399ff67030a..5d34abc3c931 100644 --- a/data_structures/binary_tree/treap.py +++ b/data_structures/binary_tree/treap.py @@ -7,6 +7,7 @@ class Node: Treap's node Treap is a binary tree by key and heap by priority """ + def __init__(self, key: int): self.key = key self.prior = random() diff --git a/data_structures/hashing/double_hash.py b/data_structures/hashing/double_hash.py index 7a0ce0b3a67b..6c3699cc9950 100644 --- a/data_structures/hashing/double_hash.py +++ b/data_structures/hashing/double_hash.py @@ -8,13 +8,17 @@ class DoubleHash(HashTable): """ Hash Table example with open addressing and Double Hash """ + def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) def __hash_function_2(self, value, data): - next_prime_gt = next_prime(value % self.size_table) \ - if not check_prime(value % self.size_table) else value % self.size_table #gt = bigger than + next_prime_gt = ( + next_prime(value % self.size_table) + if not check_prime(value % self.size_table) + else value % self.size_table + ) # gt = bigger than return next_prime_gt - (data % next_prime_gt) def __hash_double_function(self, key, data, increment): @@ -25,9 +29,14 @@ def _colision_resolution(self, key, data=None): new_key = self.hash_function(data) while self.values[new_key] is not None and self.values[new_key] != key: - new_key = self.__hash_double_function(key, data, i) if \ - self.balanced_factor() >= self.lim_charge else None - if new_key is None: break - else: i += 1 + new_key = ( + self.__hash_double_function(key, data, i) + if self.balanced_factor() >= self.lim_charge + else None + ) + if new_key is None: + break + else: + i += 1 return new_key diff --git a/data_structures/hashing/hash_table.py b/data_structures/hashing/hash_table.py index f0de128d1ad1..ab473dc52324 100644 --- a/data_structures/hashing/hash_table.py +++ b/data_structures/hashing/hash_table.py @@ -19,8 +19,9 @@ def keys(self): return self._keys def balanced_factor(self): - return sum([1 for slot in self.values - if slot is not None]) / (self.size_table * self.charge_factor) + return sum([1 for slot in self.values if slot is not None]) / ( + self.size_table * self.charge_factor + ) def hash_function(self, key): return key % self.size_table @@ -46,8 +47,7 @@ def _set_value(self, key, data): def _colision_resolution(self, key, data=None): new_key = self.hash_function(key + 1) - while self.values[new_key] is not None \ - and self.values[new_key] != key: + while self.values[new_key] is not None and self.values[new_key] != key: if self.values.count(None) > 0: new_key = self.hash_function(new_key + 1) @@ -61,7 +61,7 @@ def rehashing(self): survivor_values = [value for value in self.values if value is not None] self.size_table = next_prime(self.size_table, factor=2) self._keys.clear() - self.values = [None] * self.size_table #hell's pointers D: don't DRY ;/ + self.values = [None] * self.size_table # hell's pointers D: don't DRY ;/ map(self.insert_data, survivor_values) def insert_data(self, data): @@ -80,5 +80,3 @@ def insert_data(self, data): else: self.rehashing() self.insert_data(data) - - diff --git a/data_structures/hashing/hash_table_with_linked_list.py b/data_structures/hashing/hash_table_with_linked_list.py index a45876df49bd..236985b69ac6 100644 --- a/data_structures/hashing/hash_table_with_linked_list.py +++ b/data_structures/hashing/hash_table_with_linked_list.py @@ -7,18 +7,20 @@ def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) def _set_value(self, key, data): - self.values[key] = deque([]) if self.values[key] is None else self.values[key] + self.values[key] = deque([]) if self.values[key] is None else self.values[key] self.values[key].appendleft(data) self._keys[key] = self.values[key] def balanced_factor(self): - return sum([self.charge_factor - len(slot) for slot in self.values])\ - / self.size_table * self.charge_factor - + return ( + sum([self.charge_factor - len(slot) for slot in self.values]) + / self.size_table + * self.charge_factor + ) + def _colision_resolution(self, key, data=None): - if not (len(self.values[key]) == self.charge_factor - and self.values.count(None) == 0): + if not ( + len(self.values[key]) == self.charge_factor and self.values.count(None) == 0 + ): return key return super()._colision_resolution(key, data) - - diff --git a/data_structures/hashing/number_theory/prime_numbers.py b/data_structures/hashing/number_theory/prime_numbers.py index 8a521bc45758..2a966e0da7f2 100644 --- a/data_structures/hashing/number_theory/prime_numbers.py +++ b/data_structures/hashing/number_theory/prime_numbers.py @@ -5,25 +5,25 @@ def check_prime(number): - """ + """ it's not the best solution """ - special_non_primes = [0,1,2] - if number in special_non_primes[:2]: - return 2 - elif number == special_non_primes[-1]: - return 3 - - return all([number % i for i in range(2, number)]) + special_non_primes = [0, 1, 2] + if number in special_non_primes[:2]: + return 2 + elif number == special_non_primes[-1]: + return 3 + + return all([number % i for i in range(2, number)]) def next_prime(value, factor=1, **kwargs): value = factor * value first_value_val = value - + while not check_prime(value): value += 1 if not ("desc" in kwargs.keys() and kwargs["desc"] is True) else -1 - + if value == first_value_val: return next_prime(value + 1, **kwargs) return value diff --git a/data_structures/hashing/quadratic_probing.py b/data_structures/hashing/quadratic_probing.py index 1e61100a81fa..ac966e1cd67e 100644 --- a/data_structures/hashing/quadratic_probing.py +++ b/data_structures/hashing/quadratic_probing.py @@ -7,18 +7,21 @@ class QuadraticProbing(HashTable): """ Basic Hash Table example with open addressing using Quadratic Probing """ + def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) def _colision_resolution(self, key, data=None): i = 1 - new_key = self.hash_function(key + i*i) + new_key = self.hash_function(key + i * i) - while self.values[new_key] is not None \ - and self.values[new_key] != key: + while self.values[new_key] is not None and self.values[new_key] != key: i += 1 - new_key = self.hash_function(key + i*i) if not \ - self.balanced_factor() >= self.lim_charge else None + new_key = ( + self.hash_function(key + i * i) + if not self.balanced_factor() >= self.lim_charge + else None + ) if new_key is None: break diff --git a/data_structures/heap/binomial_heap.py b/data_structures/heap/binomial_heap.py index 0154390d7707..e1a005487e34 100644 --- a/data_structures/heap/binomial_heap.py +++ b/data_structures/heap/binomial_heap.py @@ -26,9 +26,7 @@ def mergeTrees(self, other): In-place merge of two binomial trees of equal size. Returns the root of the resulting tree """ - assert ( - self.left_tree_size == other.left_tree_size - ), "Unequal Sizes of Blocks" + assert self.left_tree_size == other.left_tree_size, "Unequal Sizes of Blocks" if self.val < other.val: other.left = self.right @@ -36,9 +34,7 @@ def mergeTrees(self, other): if self.right: self.right.parent = other self.right = other - self.left_tree_size = ( - self.left_tree_size * 2 + 1 - ) + self.left_tree_size = self.left_tree_size * 2 + 1 return self else: self.left = other.right @@ -46,9 +42,7 @@ def mergeTrees(self, other): if other.right: other.right.parent = self other.right = self - other.left_tree_size = ( - other.left_tree_size * 2 + 1 - ) + other.left_tree_size = other.left_tree_size * 2 + 1 return other @@ -132,9 +126,7 @@ class BinomialHeap: """ - def __init__( - self, bottom_root=None, min_node=None, heap_size=0 - ): + def __init__(self, bottom_root=None, min_node=None, heap_size=0): self.size = heap_size self.bottom_root = bottom_root self.min_node = min_node @@ -165,10 +157,7 @@ def mergeHeaps(self, other): combined_roots_list = [] i, j = self.bottom_root, other.bottom_root while i or j: - if i and ( - (not j) - or i.left_tree_size < j.left_tree_size - ): + if i and ((not j) or i.left_tree_size < j.left_tree_size): combined_roots_list.append((i, True)) i = i.parent else: @@ -176,29 +165,17 @@ def mergeHeaps(self, other): j = j.parent # Insert links between them for i in range(len(combined_roots_list) - 1): - if ( - combined_roots_list[i][1] - != combined_roots_list[i + 1][1] - ): - combined_roots_list[i][ - 0 - ].parent = combined_roots_list[i + 1][0] - combined_roots_list[i + 1][ - 0 - ].left = combined_roots_list[i][0] + if combined_roots_list[i][1] != combined_roots_list[i + 1][1]: + combined_roots_list[i][0].parent = combined_roots_list[i + 1][0] + combined_roots_list[i + 1][0].left = combined_roots_list[i][0] # Consecutively merge roots with same left_tree_size i = combined_roots_list[0][0] while i.parent: if ( - ( - i.left_tree_size - == i.parent.left_tree_size - ) - and (not i.parent.parent) + (i.left_tree_size == i.parent.left_tree_size) and (not i.parent.parent) ) or ( i.left_tree_size == i.parent.left_tree_size - and i.left_tree_size - != i.parent.parent.left_tree_size + and i.left_tree_size != i.parent.parent.left_tree_size ): # Neighbouring Nodes @@ -264,9 +241,7 @@ def insert(self, val): next_node = self.bottom_root.parent.parent # Merge - self.bottom_root = self.bottom_root.mergeTrees( - self.bottom_root.parent - ) + self.bottom_root = self.bottom_root.mergeTrees(self.bottom_root.parent) # Update Links self.bottom_root.parent = next_node @@ -337,9 +312,7 @@ def deleteMin(self): if bottom_of_new.val < min_of_new.val: min_of_new = bottom_of_new # Corner case of single root on top left path - if (not self.min_node.left) and ( - not self.min_node.parent - ): + if (not self.min_node.left) and (not self.min_node.parent): self.size = size_of_new self.bottom_root = bottom_of_new self.min_node = min_of_new @@ -348,9 +321,7 @@ def deleteMin(self): # Remaining cases # Construct heap of right subtree newHeap = BinomialHeap( - bottom_root=bottom_of_new, - min_node=min_of_new, - heap_size=size_of_new, + bottom_root=bottom_of_new, min_node=min_of_new, heap_size=size_of_new ) # Update size @@ -411,12 +382,8 @@ def __traversal(self, curr_node, preorder, level=0): """ if curr_node: preorder.append((curr_node.val, level)) - self.__traversal( - curr_node.left, preorder, level + 1 - ) - self.__traversal( - curr_node.right, preorder, level + 1 - ) + self.__traversal(curr_node.left, preorder, level + 1) + self.__traversal(curr_node.right, preorder, level + 1) else: preorder.append(("#", level)) @@ -429,10 +396,7 @@ def __str__(self): return "" preorder_heap = self.preOrder() - return "\n".join( - ("-" * level + str(value)) - for value, level in preorder_heap - ) + return "\n".join(("-" * level + str(value)) for value, level in preorder_heap) # Unit Tests diff --git a/data_structures/heap/heap.py b/data_structures/heap/heap.py index 2373d71bb897..b020ab067cc8 100644 --- a/data_structures/heap/heap.py +++ b/data_structures/heap/heap.py @@ -2,83 +2,85 @@ # This heap class start from here. class Heap: - def __init__(self): # Default constructor of heap class. - self.h = [] - self.currsize = 0 + def __init__(self): # Default constructor of heap class. + self.h = [] + self.currsize = 0 - def leftChild(self,i): - if 2*i+1 < self.currsize: - return 2*i+1 - return None + def leftChild(self, i): + if 2 * i + 1 < self.currsize: + return 2 * i + 1 + return None - def rightChild(self,i): - if 2*i+2 < self.currsize: - return 2*i+2 - return None + def rightChild(self, i): + if 2 * i + 2 < self.currsize: + return 2 * i + 2 + return None - def maxHeapify(self,node): - if node < self.currsize: - m = node - lc = self.leftChild(node) - rc = self.rightChild(node) - if lc is not None and self.h[lc] > self.h[m]: - m = lc - if rc is not None and self.h[rc] > self.h[m]: - m = rc - if m!=node: - temp = self.h[node] - self.h[node] = self.h[m] - self.h[m] = temp - self.maxHeapify(m) + def maxHeapify(self, node): + if node < self.currsize: + m = node + lc = self.leftChild(node) + rc = self.rightChild(node) + if lc is not None and self.h[lc] > self.h[m]: + m = lc + if rc is not None and self.h[rc] > self.h[m]: + m = rc + if m != node: + temp = self.h[node] + self.h[node] = self.h[m] + self.h[m] = temp + self.maxHeapify(m) - def buildHeap(self,a): #This function is used to build the heap from the data container 'a'. - self.currsize = len(a) - self.h = list(a) - for i in range(self.currsize//2,-1,-1): - self.maxHeapify(i) + def buildHeap( + self, a + ): # This function is used to build the heap from the data container 'a'. + self.currsize = len(a) + self.h = list(a) + for i in range(self.currsize // 2, -1, -1): + self.maxHeapify(i) - def getMax(self): #This function is used to get maximum value from the heap. - if self.currsize >= 1: - me = self.h[0] - temp = self.h[0] - self.h[0] = self.h[self.currsize-1] - self.h[self.currsize-1] = temp - self.currsize -= 1 - self.maxHeapify(0) - return me - return None + def getMax(self): # This function is used to get maximum value from the heap. + if self.currsize >= 1: + me = self.h[0] + temp = self.h[0] + self.h[0] = self.h[self.currsize - 1] + self.h[self.currsize - 1] = temp + self.currsize -= 1 + self.maxHeapify(0) + return me + return None - def heapSort(self): #This function is used to sort the heap. - size = self.currsize - while self.currsize-1 >= 0: - temp = self.h[0] - self.h[0] = self.h[self.currsize-1] - self.h[self.currsize-1] = temp - self.currsize -= 1 - self.maxHeapify(0) - self.currsize = size + def heapSort(self): # This function is used to sort the heap. + size = self.currsize + while self.currsize - 1 >= 0: + temp = self.h[0] + self.h[0] = self.h[self.currsize - 1] + self.h[self.currsize - 1] = temp + self.currsize -= 1 + self.maxHeapify(0) + self.currsize = size - def insert(self,data): #This function is used to insert data in the heap. - self.h.append(data) - curr = self.currsize - self.currsize+=1 - while self.h[curr] > self.h[curr/2]: - temp = self.h[curr/2] - self.h[curr/2] = self.h[curr] - self.h[curr] = temp - curr = curr/2 + def insert(self, data): # This function is used to insert data in the heap. + self.h.append(data) + curr = self.currsize + self.currsize += 1 + while self.h[curr] > self.h[curr / 2]: + temp = self.h[curr / 2] + self.h[curr / 2] = self.h[curr] + self.h[curr] = temp + curr = curr / 2 - def display(self): #This function is used to print the heap. - print(self.h) + def display(self): # This function is used to print the heap. + print(self.h) -def main(): - l = list(map(int, input().split())) - h = Heap() - h.buildHeap(l) - h.heapSort() - h.display() -if __name__=='__main__': - main() +def main(): + l = list(map(int, input().split())) + h = Heap() + h.buildHeap(l) + h.heapSort() + h.display() +if __name__ == "__main__": + main() diff --git a/data_structures/linked_list/__init__.py b/data_structures/linked_list/__init__.py index 6d50f23c1f1a..a050adba42b2 100644 --- a/data_structures/linked_list/__init__.py +++ b/data_structures/linked_list/__init__.py @@ -3,6 +3,7 @@ def __init__(self, item, next): self.item = item self.next = next + class LinkedList: def __init__(self): self.head = None diff --git a/data_structures/linked_list/doubly_linked_list.py b/data_structures/linked_list/doubly_linked_list.py index 23d91383fa0e..38fff867b416 100644 --- a/data_structures/linked_list/doubly_linked_list.py +++ b/data_structures/linked_list/doubly_linked_list.py @@ -1,76 +1,81 @@ -''' +""" - A linked list is similar to an array, it holds values. However, links in a linked list do not have indexes. - This is an example of a double ended, doubly linked list. - Each link references the next link and the previous one. - A Doubly Linked List (DLL) contains an extra pointer, typically called previous pointer, together with next pointer and data which are there in singly linked list. - - Advantages over SLL - IT can be traversed in both forward and backward direction.,Delete operation is more efficent''' + - Advantages over SLL - IT can be traversed in both forward and backward direction.,Delete operation is more efficent""" -class LinkedList: #making main class named linked list +class LinkedList: # making main class named linked list def __init__(self): self.head = None self.tail = None def insertHead(self, x): - newLink = Link(x) #Create a new link with a value attached to it - if(self.isEmpty() == True): #Set the first element added to be the tail + newLink = Link(x) # Create a new link with a value attached to it + if self.isEmpty() == True: # Set the first element added to be the tail self.tail = newLink else: - self.head.previous = newLink # newLink <-- currenthead(head) - newLink.next = self.head # newLink <--> currenthead(head) - self.head = newLink # newLink(head) <--> oldhead + self.head.previous = newLink # newLink <-- currenthead(head) + newLink.next = self.head # newLink <--> currenthead(head) + self.head = newLink # newLink(head) <--> oldhead def deleteHead(self): temp = self.head - self.head = self.head.next # oldHead <--> 2ndElement(head) - self.head.previous = None # oldHead --> 2ndElement(head) nothing pointing at it so the old head will be removed - if(self.head is None): - self.tail = None #if empty linked list + self.head = self.head.next # oldHead <--> 2ndElement(head) + self.head.previous = ( + None + ) # oldHead --> 2ndElement(head) nothing pointing at it so the old head will be removed + if self.head is None: + self.tail = None # if empty linked list return temp def insertTail(self, x): newLink = Link(x) - newLink.next = None # currentTail(tail) newLink --> - self.tail.next = newLink # currentTail(tail) --> newLink --> - newLink.previous = self.tail #currentTail(tail) <--> newLink --> - self.tail = newLink # oldTail <--> newLink(tail) --> + newLink.next = None # currentTail(tail) newLink --> + self.tail.next = newLink # currentTail(tail) --> newLink --> + newLink.previous = self.tail # currentTail(tail) <--> newLink --> + self.tail = newLink # oldTail <--> newLink(tail) --> def deleteTail(self): temp = self.tail - self.tail = self.tail.previous # 2ndLast(tail) <--> oldTail --> None - self.tail.next = None # 2ndlast(tail) --> None + self.tail = self.tail.previous # 2ndLast(tail) <--> oldTail --> None + self.tail.next = None # 2ndlast(tail) --> None return temp def delete(self, x): current = self.head - while(current.value != x): # Find the position to delete + while current.value != x: # Find the position to delete current = current.next - if(current == self.head): + if current == self.head: self.deleteHead() - elif(current == self.tail): + elif current == self.tail: self.deleteTail() - else: #Before: 1 <--> 2(current) <--> 3 - current.previous.next = current.next # 1 --> 3 - current.next.previous = current.previous # 1 <--> 3 + else: # Before: 1 <--> 2(current) <--> 3 + current.previous.next = current.next # 1 --> 3 + current.next.previous = current.previous # 1 <--> 3 - def isEmpty(self): #Will return True if the list is empty - return(self.head is None) + def isEmpty(self): # Will return True if the list is empty + return self.head is None - def display(self): #Prints contents of the list + def display(self): # Prints contents of the list current = self.head - while(current != None): + while current != None: current.displayLink() current = current.next print() + class Link: - next = None #This points to the link in front of the new link - previous = None #This points to the link behind the new link + next = None # This points to the link in front of the new link + previous = None # This points to the link behind the new link + def __init__(self, x): self.value = x + def displayLink(self): print("{}".format(self.value), end=" ") diff --git a/data_structures/linked_list/singly_linked_list.py b/data_structures/linked_list/singly_linked_list.py index 5943b88d5964..16436ff90274 100644 --- a/data_structures/linked_list/singly_linked_list.py +++ b/data_structures/linked_list/singly_linked_list.py @@ -6,21 +6,22 @@ def __init__(self, data): class Linked_List: def __init__(self): - self.Head = None # Initialize Head to None + self.Head = None # Initialize Head to None def insert_tail(self, data): - if(self.Head is None): self.insert_head(data) #If this is first node, call insert_head + if self.Head is None: + self.insert_head(data) # If this is first node, call insert_head else: temp = self.Head - while(temp.next != None): #traverse to last node + while temp.next != None: # traverse to last node temp = temp.next - temp.next = Node(data) #create node & link to tail + temp.next = Node(data) # create node & link to tail def insert_head(self, data): - newNod = Node(data) # create a new node + newNod = Node(data) # create a new node if self.Head != None: - newNod.next = self.Head # link newNode to head - self.Head = newNod # make NewNode as Head + newNod.next = self.Head # link newNode to head + self.Head = newNod # make NewNode as Head def printList(self): # print every node data tamp = self.Head @@ -38,12 +39,15 @@ def delete_head(self): # delete from head def delete_tail(self): # delete from tail tamp = self.Head if self.Head != None: - if(self.Head.next is None): # if Head is the only Node in the Linked List + if self.Head.next is None: # if Head is the only Node in the Linked List self.Head = None else: while tamp.next.next is not None: # find the 2nd last element tamp = tamp.next - tamp.next, tamp = None, tamp.next #(2nd last element).next = None and tamp = last element + tamp.next, tamp = ( + None, + tamp.next, + ) # (2nd last element).next = None and tamp = last element return tamp def isEmpty(self): @@ -65,21 +69,22 @@ def reverse(self): # Return prev in order to put the head at the end self.Head = prev + def main(): A = Linked_List() print("Inserting 1st at Head") - a1=input() + a1 = input() A.insert_head(a1) print("Inserting 2nd at Head") - a2=input() + a2 = input() A.insert_head(a2) print("\nPrint List : ") A.printList() print("\nInserting 1st at Tail") - a3=input() + a3 = input() A.insert_tail(a3) print("Inserting 2nd at Tail") - a4=input() + a4 = input() A.insert_tail(a4) print("\nPrint List : ") A.printList() @@ -94,5 +99,6 @@ def main(): print("\nPrint List : ") A.printList() -if __name__ == '__main__': - main() + +if __name__ == "__main__": + main() diff --git a/data_structures/linked_list/swap_nodes.py b/data_structures/linked_list/swap_nodes.py index ce2543bc46d8..a6a50091e3e0 100644 --- a/data_structures/linked_list/swap_nodes.py +++ b/data_structures/linked_list/swap_nodes.py @@ -1,6 +1,6 @@ class Node: def __init__(self, data): - self.data = data; + self.data = data self.next = None @@ -14,13 +14,13 @@ def print_list(self): print(temp.data) temp = temp.next -# adding nodes + # adding nodes def push(self, new_data): new_node = Node(new_data) new_node.next = self.head self.head = new_node -# swapping nodes + # swapping nodes def swapNodes(self, d1, d2): prevD1 = None prevD2 = None @@ -53,11 +53,11 @@ def swapNodes(self, d1, d2): D1.next = D2.next D2.next = temp -# swapping code ends here +# swapping code ends here -if __name__ == '__main__': +if __name__ == "__main__": list = Linkedlist() list.push(5) list.push(4) @@ -70,6 +70,3 @@ def swapNodes(self, d1, d2): list.swapNodes(1, 4) print("After swapping") list.print_list() - - - diff --git a/data_structures/queue/double_ended_queue.py b/data_structures/queue/double_ended_queue.py index a3cfa7230710..dd003b7c98ac 100644 --- a/data_structures/queue/double_ended_queue.py +++ b/data_structures/queue/double_ended_queue.py @@ -5,11 +5,11 @@ import collections # initializing deque -de = collections.deque([1, 2, 3,]) +de = collections.deque([1, 2, 3]) # using extend() to add numbers to right end # adds 4,5,6 to right end -de.extend([4,5,6]) +de.extend([4, 5, 6]) # printing modified deque print("The deque after extending deque at end is : ") @@ -17,7 +17,7 @@ # using extendleft() to add numbers to left end # adds 7,8,9 to right end -de.extendleft([7,8,9]) +de.extendleft([7, 8, 9]) # printing modified deque print("The deque after extending deque at beginning is : ") diff --git a/data_structures/queue/queue_on_list.py b/data_structures/queue/queue_on_list.py index 2ec9bac8398a..bb44e08ad6c5 100644 --- a/data_structures/queue/queue_on_list.py +++ b/data_structures/queue/queue_on_list.py @@ -1,46 +1,52 @@ """Queue represented by a python list""" -class Queue(): + + +class Queue: def __init__(self): self.entries = [] self.length = 0 - self.front=0 + self.front = 0 def __str__(self): - printed = '<' + str(self.entries)[1:-1] + '>' + printed = "<" + str(self.entries)[1:-1] + ">" return printed """Enqueues {@code item} @param item item to enqueue""" + def put(self, item): self.entries.append(item) self.length = self.length + 1 - """Dequeues {@code item} @requirement: |self.length| > 0 @return dequeued item that was dequeued""" + def get(self): self.length = self.length - 1 dequeued = self.entries[self.front] - #self.front-=1 - #self.entries = self.entries[self.front:] + # self.front-=1 + # self.entries = self.entries[self.front:] self.entries = self.entries[1:] return dequeued """Rotates the queue {@code rotation} times @param rotation number of times to rotate queue""" + def rotate(self, rotation): for i in range(rotation): self.put(self.get()) """Enqueues {@code item} @return item at front of self.entries""" + def front(self): return self.entries[0] """Returns the length of this.entries""" + def size(self): return self.length diff --git a/data_structures/queue/queue_on_pseudo_stack.py b/data_structures/queue/queue_on_pseudo_stack.py index b69fbcc988f7..7fa2fb2566af 100644 --- a/data_structures/queue/queue_on_pseudo_stack.py +++ b/data_structures/queue/queue_on_pseudo_stack.py @@ -1,16 +1,19 @@ """Queue represented by a pseudo stack (represented by a list with pop and append)""" -class Queue(): + + +class Queue: def __init__(self): self.stack = [] self.length = 0 def __str__(self): - printed = '<' + str(self.stack)[1:-1] + '>' + printed = "<" + str(self.stack)[1:-1] + ">" return printed """Enqueues {@code item} @param item item to enqueue""" + def put(self, item): self.stack.append(item) self.length = self.length + 1 @@ -19,17 +22,19 @@ def put(self, item): @requirement: |self.length| > 0 @return dequeued item that was dequeued""" + def get(self): self.rotate(1) - dequeued = self.stack[self.length-1] + dequeued = self.stack[self.length - 1] self.stack = self.stack[:-1] - self.rotate(self.length-1) - self.length = self.length -1 + self.rotate(self.length - 1) + self.length = self.length - 1 return dequeued """Rotates the queue {@code rotation} times @param rotation number of times to rotate queue""" + def rotate(self, rotation): for i in range(rotation): temp = self.stack[0] @@ -39,12 +44,14 @@ def rotate(self, rotation): """Reports item at the front of self @return item at front of self.stack""" + def front(self): front = self.get() self.put(front) - self.rotate(self.length-1) + self.rotate(self.length - 1) return front """Returns the length of this.stack""" + def size(self): return self.length diff --git a/data_structures/stacks/__init__.py b/data_structures/stacks/__init__.py index f7e92ae2d269..f6995cf98977 100644 --- a/data_structures/stacks/__init__.py +++ b/data_structures/stacks/__init__.py @@ -1,23 +1,22 @@ class Stack: + def __init__(self): + self.stack = [] + self.top = 0 - def __init__(self): - self.stack = [] - self.top = 0 + def is_empty(self): + return self.top == 0 - def is_empty(self): - return (self.top == 0) + def push(self, item): + if self.top < len(self.stack): + self.stack[self.top] = item + else: + self.stack.append(item) - def push(self, item): - if self.top < len(self.stack): - self.stack[self.top] = item - else: - self.stack.append(item) + self.top += 1 - self.top += 1 - - def pop(self): - if self.is_empty(): - return None - else: - self.top -= 1 - return self.stack[self.top] + def pop(self): + if self.is_empty(): + return None + else: + self.top -= 1 + return self.stack[self.top] diff --git a/data_structures/stacks/balanced_parentheses.py b/data_structures/stacks/balanced_parentheses.py index 3f43ccbf5760..7aacd5969277 100644 --- a/data_structures/stacks/balanced_parentheses.py +++ b/data_structures/stacks/balanced_parentheses.py @@ -1,23 +1,23 @@ from .stack import Stack -__author__ = 'Omkar Pathak' +__author__ = "Omkar Pathak" def balanced_parentheses(parentheses): """ Use a stack to check if a string of parentheses is balanced.""" stack = Stack(len(parentheses)) for parenthesis in parentheses: - if parenthesis == '(': + if parenthesis == "(": stack.push(parenthesis) - elif parenthesis == ')': + elif parenthesis == ")": if stack.is_empty(): return False stack.pop() return stack.is_empty() -if __name__ == '__main__': - examples = ['((()))', '((())', '(()))'] - print('Balanced parentheses demonstration:\n') +if __name__ == "__main__": + examples = ["((()))", "((())", "(()))"] + print("Balanced parentheses demonstration:\n") for example in examples: - print(example + ': ' + str(balanced_parentheses(example))) + print(example + ": " + str(balanced_parentheses(example))) diff --git a/data_structures/stacks/infix_to_postfix_conversion.py b/data_structures/stacks/infix_to_postfix_conversion.py index 84a5d1480a24..61114402377a 100644 --- a/data_structures/stacks/infix_to_postfix_conversion.py +++ b/data_structures/stacks/infix_to_postfix_conversion.py @@ -2,7 +2,7 @@ from .stack import Stack -__author__ = 'Omkar Pathak' +__author__ = "Omkar Pathak" def is_operand(char): @@ -15,9 +15,7 @@ def precedence(char): https://en.wikipedia.org/wiki/Order_of_operations """ - dictionary = {'+': 1, '-': 1, - '*': 2, '/': 2, - '^': 3} + dictionary = {"+": 1, "-": 1, "*": 2, "/": 2, "^": 3} return dictionary.get(char, -1) @@ -34,29 +32,28 @@ def infix_to_postfix(expression): for char in expression: if is_operand(char): postfix.append(char) - elif char not in {'(', ')'}: - while (not stack.is_empty() - and precedence(char) <= precedence(stack.peek())): + elif char not in {"(", ")"}: + while not stack.is_empty() and precedence(char) <= precedence(stack.peek()): postfix.append(stack.pop()) stack.push(char) - elif char == '(': + elif char == "(": stack.push(char) - elif char == ')': - while not stack.is_empty() and stack.peek() != '(': + elif char == ")": + while not stack.is_empty() and stack.peek() != "(": postfix.append(stack.pop()) # Pop '(' from stack. If there is no '(', there is a mismatched # parentheses. - if stack.peek() != '(': - raise ValueError('Mismatched parentheses') + if stack.peek() != "(": + raise ValueError("Mismatched parentheses") stack.pop() while not stack.is_empty(): postfix.append(stack.pop()) - return ' '.join(postfix) + return " ".join(postfix) -if __name__ == '__main__': - expression = 'a+b*(c^d-e)^(f+g*h)-i' +if __name__ == "__main__": + expression = "a+b*(c^d-e)^(f+g*h)-i" - print('Infix to Postfix Notation demonstration:\n') - print('Infix notation: ' + expression) - print('Postfix notation: ' + infix_to_postfix(expression)) + print("Infix to Postfix Notation demonstration:\n") + print("Infix notation: " + expression) + print("Postfix notation: " + infix_to_postfix(expression)) diff --git a/data_structures/stacks/infix_to_prefix_conversion.py b/data_structures/stacks/infix_to_prefix_conversion.py index da5fc261fb9f..4f0e1ab8adfa 100644 --- a/data_structures/stacks/infix_to_prefix_conversion.py +++ b/data_structures/stacks/infix_to_prefix_conversion.py @@ -14,48 +14,82 @@ a+b^c (Infix) -> +a^bc (Prefix) """ + def infix_2_postfix(Infix): Stack = [] Postfix = [] - priority = {'^':3, '*':2, '/':2, '%':2, '+':1, '-':1} # Priority of each operator - print_width = len(Infix) if(len(Infix)>7) else 7 + priority = { + "^": 3, + "*": 2, + "/": 2, + "%": 2, + "+": 1, + "-": 1, + } # Priority of each operator + print_width = len(Infix) if (len(Infix) > 7) else 7 # Print table header for output - print('Symbol'.center(8), 'Stack'.center(print_width), 'Postfix'.center(print_width), sep = " | ") - print('-'*(print_width*3+7)) + print( + "Symbol".center(8), + "Stack".center(print_width), + "Postfix".center(print_width), + sep=" | ", + ) + print("-" * (print_width * 3 + 7)) for x in Infix: - if(x.isalpha() or x.isdigit()): Postfix.append(x) # if x is Alphabet / Digit, add it to Postfix - elif(x == '('): Stack.append(x) # if x is "(" push to Stack - elif(x == ')'): # if x is ")" pop stack until "(" is encountered - while(Stack[-1] != '('): - Postfix.append( Stack.pop() ) #Pop stack & add the content to Postfix + if x.isalpha() or x.isdigit(): + Postfix.append(x) # if x is Alphabet / Digit, add it to Postfix + elif x == "(": + Stack.append(x) # if x is "(" push to Stack + elif x == ")": # if x is ")" pop stack until "(" is encountered + while Stack[-1] != "(": + Postfix.append(Stack.pop()) # Pop stack & add the content to Postfix Stack.pop() else: - if(len(Stack)==0): Stack.append(x) #If stack is empty, push x to stack + if len(Stack) == 0: + Stack.append(x) # If stack is empty, push x to stack else: - while( len(Stack) > 0 and priority[x] <= priority[Stack[-1]]): # while priority of x is not greater than priority of element in the stack - Postfix.append( Stack.pop() ) # pop stack & add to Postfix - Stack.append(x) # push x to stack + while ( + len(Stack) > 0 and priority[x] <= priority[Stack[-1]] + ): # while priority of x is not greater than priority of element in the stack + Postfix.append(Stack.pop()) # pop stack & add to Postfix + Stack.append(x) # push x to stack + + print( + x.center(8), + ("".join(Stack)).ljust(print_width), + ("".join(Postfix)).ljust(print_width), + sep=" | ", + ) # Output in tabular format - print(x.center(8), (''.join(Stack)).ljust(print_width), (''.join(Postfix)).ljust(print_width), sep = " | ") # Output in tabular format + while len(Stack) > 0: # while stack is not empty + Postfix.append(Stack.pop()) # pop stack & add to Postfix + print( + " ".center(8), + ("".join(Stack)).ljust(print_width), + ("".join(Postfix)).ljust(print_width), + sep=" | ", + ) # Output in tabular format - while(len(Stack) > 0): # while stack is not empty - Postfix.append( Stack.pop() ) # pop stack & add to Postfix - print(' '.center(8), (''.join(Stack)).ljust(print_width), (''.join(Postfix)).ljust(print_width), sep = " | ") # Output in tabular format + return "".join(Postfix) # return Postfix as str - return "".join(Postfix) # return Postfix as str def infix_2_prefix(Infix): - Infix = list(Infix[::-1]) # reverse the infix equation - + Infix = list(Infix[::-1]) # reverse the infix equation + for i in range(len(Infix)): - if(Infix[i] == '('): Infix[i] = ')' # change "(" to ")" - elif(Infix[i] == ')'): Infix[i] = '(' # change ")" to "(" - - return (infix_2_postfix("".join(Infix)))[::-1] # call infix_2_postfix on Infix, return reverse of Postfix + if Infix[i] == "(": + Infix[i] = ")" # change "(" to ")" + elif Infix[i] == ")": + Infix[i] = "(" # change ")" to "(" + + return (infix_2_postfix("".join(Infix)))[ + ::-1 + ] # call infix_2_postfix on Infix, return reverse of Postfix + if __name__ == "__main__": - Infix = input("\nEnter an Infix Equation = ") #Input an Infix equation - Infix = "".join(Infix.split()) #Remove spaces from the input + Infix = input("\nEnter an Infix Equation = ") # Input an Infix equation + Infix = "".join(Infix.split()) # Remove spaces from the input print("\n\t", Infix, "(Infix) -> ", infix_2_prefix(Infix), "(Prefix)") diff --git a/data_structures/stacks/next_greater_element.py b/data_structures/stacks/next_greater_element.py index 2e67f1764a5a..02a86196f5bf 100644 --- a/data_structures/stacks/next_greater_element.py +++ b/data_structures/stacks/next_greater_element.py @@ -4,13 +4,14 @@ def printNGE(arr): for i in range(0, len(arr), 1): next = -1 - for j in range(i+1, len(arr), 1): + for j in range(i + 1, len(arr), 1): if arr[i] < arr[j]: next = arr[j] break print(str(arr[i]) + " -- " + str(next)) + # Driver program to test above function -arr = [11,13,21,3] +arr = [11, 13, 21, 3] printNGE(arr) diff --git a/data_structures/stacks/postfix_evaluation.py b/data_structures/stacks/postfix_evaluation.py index 1786e71dd383..0f3d5c76d6a3 100644 --- a/data_structures/stacks/postfix_evaluation.py +++ b/data_structures/stacks/postfix_evaluation.py @@ -19,32 +19,52 @@ import operator as op + def Solve(Postfix): Stack = [] - Div = lambda x, y: int(x/y) # integer division operation - Opr = {'^':op.pow, '*':op.mul, '/':Div, '+':op.add, '-':op.sub} # operators & their respective operation + Div = lambda x, y: int(x / y) # integer division operation + Opr = { + "^": op.pow, + "*": op.mul, + "/": Div, + "+": op.add, + "-": op.sub, + } # operators & their respective operation # print table header - print('Symbol'.center(8), 'Action'.center(12), 'Stack', sep = " | ") - print('-'*(30+len(Postfix))) + print("Symbol".center(8), "Action".center(12), "Stack", sep=" | ") + print("-" * (30 + len(Postfix))) for x in Postfix: - if( x.isdigit() ): # if x in digit - Stack.append(x) # append x to stack - print(x.rjust(8), ('push('+x+')').ljust(12), ','.join(Stack), sep = " | ") # output in tabular format + if x.isdigit(): # if x in digit + Stack.append(x) # append x to stack + print( + x.rjust(8), ("push(" + x + ")").ljust(12), ",".join(Stack), sep=" | " + ) # output in tabular format else: - B = Stack.pop() # pop stack - print("".rjust(8), ('pop('+B+')').ljust(12), ','.join(Stack), sep = " | ") # output in tabular format + B = Stack.pop() # pop stack + print( + "".rjust(8), ("pop(" + B + ")").ljust(12), ",".join(Stack), sep=" | " + ) # output in tabular format - A = Stack.pop() # pop stack - print("".rjust(8), ('pop('+A+')').ljust(12), ','.join(Stack), sep = " | ") # output in tabular format + A = Stack.pop() # pop stack + print( + "".rjust(8), ("pop(" + A + ")").ljust(12), ",".join(Stack), sep=" | " + ) # output in tabular format - Stack.append( str(Opr[x](int(A), int(B))) ) # evaluate the 2 values poped from stack & push result to stack - print(x.rjust(8), ('push('+A+x+B+')').ljust(12), ','.join(Stack), sep = " | ") # output in tabular format + Stack.append( + str(Opr[x](int(A), int(B))) + ) # evaluate the 2 values poped from stack & push result to stack + print( + x.rjust(8), + ("push(" + A + x + B + ")").ljust(12), + ",".join(Stack), + sep=" | ", + ) # output in tabular format return int(Stack[0]) if __name__ == "__main__": - Postfix = input("\n\nEnter a Postfix Equation (space separated) = ").split(' ') + Postfix = input("\n\nEnter a Postfix Equation (space separated) = ").split(" ") print("\n\tResult = ", Solve(Postfix)) diff --git a/data_structures/stacks/stack.py b/data_structures/stacks/stack.py index 387367db2fcc..9f5b279710c6 100644 --- a/data_structures/stacks/stack.py +++ b/data_structures/stacks/stack.py @@ -1,4 +1,4 @@ -__author__ = 'Omkar Pathak' +__author__ = "Omkar Pathak" class Stack(object): @@ -32,7 +32,7 @@ def pop(self): if self.stack: return self.stack.pop() else: - raise IndexError('pop from an empty stack') + raise IndexError("pop from an empty stack") def peek(self): """ Peek at the top-most element of the stack.""" @@ -52,17 +52,17 @@ class StackOverflowError(BaseException): pass -if __name__ == '__main__': +if __name__ == "__main__": stack = Stack() for i in range(10): stack.push(i) - print('Stack demonstration:\n') - print('Initial stack: ' + str(stack)) - print('pop(): ' + str(stack.pop())) - print('After pop(), the stack is now: ' + str(stack)) - print('peek(): ' + str(stack.peek())) + print("Stack demonstration:\n") + print("Initial stack: " + str(stack)) + print("pop(): " + str(stack.pop())) + print("After pop(), the stack is now: " + str(stack)) + print("peek(): " + str(stack.peek())) stack.push(100) - print('After push(100), the stack is now: ' + str(stack)) - print('is_empty(): ' + str(stack.is_empty())) - print('size(): ' + str(stack.size())) + print("After push(100), the stack is now: " + str(stack)) + print("is_empty(): " + str(stack.is_empty())) + print("size(): " + str(stack.size())) diff --git a/data_structures/stacks/stock_span_problem.py b/data_structures/stacks/stock_span_problem.py index 47d916fde9ed..45cd6bae1282 100644 --- a/data_structures/stacks/stock_span_problem.py +++ b/data_structures/stacks/stock_span_problem.py @@ -1,11 +1,13 @@ -''' +""" The stock span problem is a financial problem where we have a series of n daily price quotes for a stock and we need to calculate span of stock's price for all n days. The span Si of the stock's price on a given day i is defined as the maximum number of consecutive days just before the given day, for which the price of the stock on the current day is less than or equal to its price on the given day. -''' +""" + + def calculateSpan(price, S): n = len(price) @@ -21,14 +23,14 @@ def calculateSpan(price, S): # Pop elements from stack whlie stack is not # empty and top of stack is smaller than price[i] - while( len(st) > 0 and price[st[0]] <= price[i]): + while len(st) > 0 and price[st[0]] <= price[i]: st.pop() # If stack becomes empty, then price[i] is greater # than all elements on left of it, i.e. price[0], # price[1], ..price[i-1]. Else the price[i] is # greater than elements after top of stack - S[i] = i+1 if len(st) <= 0 else (i - st[0]) + S[i] = i + 1 if len(st) <= 0 else (i - st[0]) # Push this element to stack st.append(i) @@ -36,13 +38,13 @@ def calculateSpan(price, S): # A utility function to print elements of array def printArray(arr, n): - for i in range(0,n): - print(arr[i],end =" ") + for i in range(0, n): + print(arr[i], end=" ") # Driver program to test above function price = [10, 4, 5, 90, 120, 80] -S = [0 for i in range(len(price)+1)] +S = [0 for i in range(len(price) + 1)] # Fill the span values in array S[] calculateSpan(price, S) diff --git a/digital_image_processing/edge_detection/canny.py b/digital_image_processing/edge_detection/canny.py index 7fde75a90a48..6f98fee6308e 100644 --- a/digital_image_processing/edge_detection/canny.py +++ b/digital_image_processing/edge_detection/canny.py @@ -8,8 +8,12 @@ def gen_gaussian_kernel(k_size, sigma): center = k_size // 2 - x, y = np.mgrid[0 - center:k_size - center, 0 - center:k_size - center] - g = 1 / (2 * np.pi * sigma) * np.exp(-(np.square(x) + np.square(y)) / (2 * np.square(sigma))) + x, y = np.mgrid[0 - center : k_size - center, 0 - center : k_size - center] + g = ( + 1 + / (2 * np.pi * sigma) + * np.exp(-(np.square(x) + np.square(y)) / (2 * np.square(sigma))) + ) return g @@ -34,27 +38,33 @@ def canny(image, threshold_low=15, threshold_high=30, weak=128, strong=255): if ( 0 <= direction < 22.5 - or 15 * PI / 8 <= direction <= 2 * PI - or 7 * PI / 8 <= direction <= 9 * PI / 8 + or 15 * PI / 8 <= direction <= 2 * PI + or 7 * PI / 8 <= direction <= 9 * PI / 8 ): W = sobel_grad[row, col - 1] E = sobel_grad[row, col + 1] if sobel_grad[row, col] >= W and sobel_grad[row, col] >= E: dst[row, col] = sobel_grad[row, col] - elif (PI / 8 <= direction < 3 * PI / 8) or (9 * PI / 8 <= direction < 11 * PI / 8): + elif (PI / 8 <= direction < 3 * PI / 8) or ( + 9 * PI / 8 <= direction < 11 * PI / 8 + ): SW = sobel_grad[row + 1, col - 1] NE = sobel_grad[row - 1, col + 1] if sobel_grad[row, col] >= SW and sobel_grad[row, col] >= NE: dst[row, col] = sobel_grad[row, col] - elif (3 * PI / 8 <= direction < 5 * PI / 8) or (11 * PI / 8 <= direction < 13 * PI / 8): + elif (3 * PI / 8 <= direction < 5 * PI / 8) or ( + 11 * PI / 8 <= direction < 13 * PI / 8 + ): N = sobel_grad[row - 1, col] S = sobel_grad[row + 1, col] if sobel_grad[row, col] >= N and sobel_grad[row, col] >= S: dst[row, col] = sobel_grad[row, col] - elif (5 * PI / 8 <= direction < 7 * PI / 8) or (13 * PI / 8 <= direction < 15 * PI / 8): + elif (5 * PI / 8 <= direction < 7 * PI / 8) or ( + 13 * PI / 8 <= direction < 15 * PI / 8 + ): NW = sobel_grad[row - 1, col - 1] SE = sobel_grad[row + 1, col + 1] if sobel_grad[row, col] >= NW and sobel_grad[row, col] >= SE: @@ -82,14 +92,14 @@ def canny(image, threshold_low=15, threshold_high=30, weak=128, strong=255): for col in range(1, image_col): if dst[row, col] == weak: if 255 in ( - dst[row, col + 1], - dst[row, col - 1], - dst[row - 1, col], - dst[row + 1, col], - dst[row - 1, col - 1], - dst[row + 1, col - 1], - dst[row - 1, col + 1], - dst[row + 1, col + 1], + dst[row, col + 1], + dst[row, col - 1], + dst[row - 1, col], + dst[row + 1, col], + dst[row - 1, col - 1], + dst[row + 1, col - 1], + dst[row - 1, col + 1], + dst[row + 1, col + 1], ): dst[row, col] = strong else: @@ -98,10 +108,10 @@ def canny(image, threshold_low=15, threshold_high=30, weak=128, strong=255): return dst -if __name__ == '__main__': +if __name__ == "__main__": # read original image in gray mode - lena = cv2.imread(r'../image_data/lena.jpg', 0) + lena = cv2.imread(r"../image_data/lena.jpg", 0) # canny edge detection canny_dst = canny(lena) - cv2.imshow('canny', canny_dst) + cv2.imshow("canny", canny_dst) cv2.waitKey(0) diff --git a/digital_image_processing/filters/convolve.py b/digital_image_processing/filters/convolve.py index b7600d74c294..ec500d940366 100644 --- a/digital_image_processing/filters/convolve.py +++ b/digital_image_processing/filters/convolve.py @@ -13,7 +13,7 @@ def im2col(image, block_size): row = 0 for i in range(0, dst_height): for j in range(0, dst_width): - window = ravel(image[i:i + block_size[0], j:j + block_size[1]]) + window = ravel(image[i : i + block_size[0], j : j + block_size[1]]) image_array[row, :] = window row += 1 @@ -23,9 +23,9 @@ def im2col(image, block_size): def img_convolve(image, filter_kernel): height, width = image.shape[0], image.shape[1] k_size = filter_kernel.shape[0] - pad_size = k_size//2 + pad_size = k_size // 2 # Pads image with the edge values of array. - image_tmp = pad(image, pad_size, mode='edge') + image_tmp = pad(image, pad_size, mode="edge") # im2col, turn the k_size*k_size pixels into a row and np.vstack all rows image_array = im2col(image_tmp, (k_size, k_size)) @@ -37,13 +37,13 @@ def img_convolve(image, filter_kernel): return dst -if __name__ == '__main__': +if __name__ == "__main__": # read original image - img = imread(r'../image_data/lena.jpg') + img = imread(r"../image_data/lena.jpg") # turn image in gray scale value gray = cvtColor(img, COLOR_BGR2GRAY) # Laplace operator Laplace_kernel = array([[0, 1, 0], [1, -4, 1], [0, 1, 0]]) out = img_convolve(gray, Laplace_kernel).astype(uint8) - imshow('Laplacian', out) + imshow("Laplacian", out) waitKey(0) diff --git a/digital_image_processing/filters/gaussian_filter.py b/digital_image_processing/filters/gaussian_filter.py index ff85ce047220..b800f0a7edc8 100644 --- a/digital_image_processing/filters/gaussian_filter.py +++ b/digital_image_processing/filters/gaussian_filter.py @@ -7,23 +7,23 @@ def gen_gaussian_kernel(k_size, sigma): center = k_size // 2 - x, y = mgrid[0-center:k_size-center, 0-center:k_size-center] - g = 1/(2*pi*sigma) * exp(-(square(x) + square(y))/(2*square(sigma))) + x, y = mgrid[0 - center : k_size - center, 0 - center : k_size - center] + g = 1 / (2 * pi * sigma) * exp(-(square(x) + square(y)) / (2 * square(sigma))) return g def gaussian_filter(image, k_size, sigma): height, width = image.shape[0], image.shape[1] # dst image height and width - dst_height = height-k_size+1 - dst_width = width-k_size+1 + dst_height = height - k_size + 1 + dst_width = width - k_size + 1 # im2col, turn the k_size*k_size pixels into a row and np.vstack all rows - image_array = zeros((dst_height*dst_width, k_size*k_size)) + image_array = zeros((dst_height * dst_width, k_size * k_size)) row = 0 for i in range(0, dst_height): for j in range(0, dst_width): - window = ravel(image[i:i + k_size, j:j + k_size]) + window = ravel(image[i : i + k_size, j : j + k_size]) image_array[row, :] = window row += 1 @@ -37,9 +37,9 @@ def gaussian_filter(image, k_size, sigma): return dst -if __name__ == '__main__': +if __name__ == "__main__": # read original image - img = imread(r'../image_data/lena.jpg') + img = imread(r"../image_data/lena.jpg") # turn image in gray scale value gray = cvtColor(img, COLOR_BGR2GRAY) @@ -48,6 +48,6 @@ def gaussian_filter(image, k_size, sigma): gaussian5x5 = gaussian_filter(gray, 5, sigma=0.8) # show result images - imshow('gaussian filter with 3x3 mask', gaussian3x3) - imshow('gaussian filter with 5x5 mask', gaussian5x5) + imshow("gaussian filter with 3x3 mask", gaussian3x3) + imshow("gaussian filter with 5x5 mask", gaussian5x5) waitKey() diff --git a/digital_image_processing/filters/median_filter.py b/digital_image_processing/filters/median_filter.py index 4b21b96b080b..151ef8a55df1 100644 --- a/digital_image_processing/filters/median_filter.py +++ b/digital_image_processing/filters/median_filter.py @@ -19,16 +19,16 @@ def median_filter(gray_img, mask=3): for i in range(bd, gray_img.shape[0] - bd): for j in range(bd, gray_img.shape[1] - bd): # get mask according with mask - kernel = ravel(gray_img[i - bd:i + bd + 1, j - bd:j + bd + 1]) + kernel = ravel(gray_img[i - bd : i + bd + 1, j - bd : j + bd + 1]) # calculate mask median median = sort(kernel)[int8(divide((multiply(mask, mask)), 2) + 1)] median_img[i, j] = median return median_img -if __name__ == '__main__': +if __name__ == "__main__": # read original image - img = imread('../image_data/lena.jpg') + img = imread("../image_data/lena.jpg") # turn image in gray scale value gray = cvtColor(img, COLOR_BGR2GRAY) @@ -37,6 +37,6 @@ def median_filter(gray_img, mask=3): median5x5 = median_filter(gray, 5) # show result images - imshow('median filter with 3x3 mask', median3x3) - imshow('median filter with 5x5 mask', median5x5) + imshow("median filter with 3x3 mask", median3x3) + imshow("median filter with 5x5 mask", median5x5) waitKey(0) diff --git a/digital_image_processing/filters/sobel_filter.py b/digital_image_processing/filters/sobel_filter.py index f3ef407d49e5..822d49fe38a1 100644 --- a/digital_image_processing/filters/sobel_filter.py +++ b/digital_image_processing/filters/sobel_filter.py @@ -13,26 +13,26 @@ def sobel_filter(image): dst_x = np.abs(img_convolve(image, kernel_x)) dst_y = np.abs(img_convolve(image, kernel_y)) # modify the pix within [0, 255] - dst_x = dst_x * 255/np.max(dst_x) - dst_y = dst_y * 255/np.max(dst_y) + dst_x = dst_x * 255 / np.max(dst_x) + dst_y = dst_y * 255 / np.max(dst_y) dst_xy = np.sqrt((np.square(dst_x)) + (np.square(dst_y))) - dst_xy = dst_xy * 255/np.max(dst_xy) + dst_xy = dst_xy * 255 / np.max(dst_xy) dst = dst_xy.astype(np.uint8) theta = np.arctan2(dst_y, dst_x) return dst, theta -if __name__ == '__main__': +if __name__ == "__main__": # read original image - img = imread('../image_data/lena.jpg') + img = imread("../image_data/lena.jpg") # turn image in gray scale value gray = cvtColor(img, COLOR_BGR2GRAY) sobel_grad, sobel_theta = sobel_filter(gray) # show result images - imshow('sobel filter', sobel_grad) - imshow('sobel theta', sobel_theta) + imshow("sobel filter", sobel_grad) + imshow("sobel theta", sobel_theta) waitKey(0) diff --git a/divide_and_conquer/closest_pair_of_points.py b/divide_and_conquer/closest_pair_of_points.py index 11dac7e0ab2a..eecf53a7450e 100644 --- a/divide_and_conquer/closest_pair_of_points.py +++ b/divide_and_conquer/closest_pair_of_points.py @@ -28,15 +28,15 @@ def euclidean_distance_sqr(point1, point2): return (point1[0] - point2[0]) ** 2 + (point1[1] - point2[1]) ** 2 -def column_based_sort(array, column = 0): +def column_based_sort(array, column=0): """ >>> column_based_sort([(5, 1), (4, 2), (3, 0)], 1) [(3, 0), (5, 1), (4, 2)] """ - return sorted(array, key = lambda x: x[column]) + return sorted(array, key=lambda x: x[column]) -def dis_between_closest_pair(points, points_counts, min_dis = float("inf")): +def dis_between_closest_pair(points, points_counts, min_dis=float("inf")): """ brute force approach to find distance between closest pair points @@ -52,14 +52,14 @@ def dis_between_closest_pair(points, points_counts, min_dis = float("inf")): """ for i in range(points_counts - 1): - for j in range(i+1, points_counts): + for j in range(i + 1, points_counts): current_dis = euclidean_distance_sqr(points[i], points[j]) if current_dis < min_dis: min_dis = current_dis return min_dis -def dis_between_closest_in_strip(points, points_counts, min_dis = float("inf")): +def dis_between_closest_in_strip(points, points_counts, min_dis=float("inf")): """ closest pair of points in strip @@ -74,7 +74,7 @@ def dis_between_closest_in_strip(points, points_counts, min_dis = float("inf")): """ for i in range(min(6, points_counts - 1), points_counts): - for j in range(max(0, i-6), i): + for j in range(max(0, i - 6), i): current_dis = euclidean_distance_sqr(points[i], points[j]) if current_dis < min_dis: min_dis = current_dis @@ -99,13 +99,13 @@ def closest_pair_of_points_sqr(points_sorted_on_x, points_sorted_on_y, points_co return dis_between_closest_pair(points_sorted_on_x, points_counts) # recursion - mid = points_counts//2 - closest_in_left = closest_pair_of_points_sqr(points_sorted_on_x, - points_sorted_on_y[:mid], - mid) - closest_in_right = closest_pair_of_points_sqr(points_sorted_on_y, - points_sorted_on_y[mid:], - points_counts - mid) + mid = points_counts // 2 + closest_in_left = closest_pair_of_points_sqr( + points_sorted_on_x, points_sorted_on_y[:mid], mid + ) + closest_in_right = closest_pair_of_points_sqr( + points_sorted_on_y, points_sorted_on_y[mid:], points_counts - mid + ) closest_pair_dis = min(closest_in_left, closest_in_right) """ @@ -118,8 +118,9 @@ def closest_pair_of_points_sqr(points_sorted_on_x, points_sorted_on_y, points_co if abs(point[0] - points_sorted_on_x[mid][0]) < closest_pair_dis: cross_strip.append(point) - closest_in_strip = dis_between_closest_in_strip(cross_strip, - len(cross_strip), closest_pair_dis) + closest_in_strip = dis_between_closest_in_strip( + cross_strip, len(cross_strip), closest_pair_dis + ) return min(closest_pair_dis, closest_in_strip) @@ -128,11 +129,13 @@ def closest_pair_of_points(points, points_counts): >>> closest_pair_of_points([(2, 3), (12, 30)], len([(2, 3), (12, 30)])) 28.792360097775937 """ - points_sorted_on_x = column_based_sort(points, column = 0) - points_sorted_on_y = column_based_sort(points, column = 1) - return (closest_pair_of_points_sqr(points_sorted_on_x, - points_sorted_on_y, - points_counts)) ** 0.5 + points_sorted_on_x = column_based_sort(points, column=0) + points_sorted_on_y = column_based_sort(points, column=1) + return ( + closest_pair_of_points_sqr( + points_sorted_on_x, points_sorted_on_y, points_counts + ) + ) ** 0.5 if __name__ == "__main__": diff --git a/divide_and_conquer/convex_hull.py b/divide_and_conquer/convex_hull.py index 534ebda2c780..bd88256ab01c 100644 --- a/divide_and_conquer/convex_hull.py +++ b/divide_and_conquer/convex_hull.py @@ -1,4 +1,5 @@ from numbers import Number + """ The convex hull problem is problem of finding all the vertices of convex polygon, P of a set of points in a plane such that all the points are either on the vertices of P or @@ -47,8 +48,10 @@ def __init__(self, x, y): try: x, y = float(x), float(y) except ValueError as e: - e.args = ("x and y must be both numeric types " - "but got {}, {} instead".format(type(x), type(y)), ) + e.args = ( + "x and y must be both numeric types " + "but got {}, {} instead".format(type(x), type(y)), + ) raise self.x = x @@ -85,7 +88,7 @@ def __le__(self, other): return False def __repr__(self): - return "({}, {})".format(self.x, self.y) + return "({}, {})".format(self.x, self.y) def __hash__(self): return hash(self.x) @@ -132,8 +135,10 @@ def _construct_points(list_of_tuples): try: points.append(Point(p[0], p[1])) except (IndexError, TypeError): - print("Ignoring deformed point {}. All points" - " must have at least 2 coordinates.".format(p)) + print( + "Ignoring deformed point {}. All points" + " must have at least 2 coordinates.".format(p) + ) return points @@ -189,12 +194,15 @@ def _validate_input(points): if isinstance(points[0], (list, tuple)): points = _construct_points(points) else: - raise ValueError("Expecting an iterable of type Point, list or tuple. " - "Found objects of type {} instead" - .format(type(points[0]))) + raise ValueError( + "Expecting an iterable of type Point, list or tuple. " + "Found objects of type {} instead".format(type(points[0])) + ) elif not hasattr(points, "__iter__"): - raise ValueError("Expecting an iterable object " - "but got an non-iterable type {}".format(points)) + raise ValueError( + "Expecting an iterable object " + "but got an non-iterable type {}".format(points) + ) except TypeError as e: print("Expecting an iterable of type Point, list or tuple.") raise @@ -277,7 +285,7 @@ def convex_hull_bf(points): n = len(points) convex_set = set() - for i in range(n-1): + for i in range(n - 1): for j in range(i + 1, n): points_left_of_ij = points_right_of_ij = False ij_part_of_convex_hull = True @@ -353,13 +361,13 @@ def convex_hull_recursive(points): # convex hull left_most_point = points[0] - right_most_point = points[n-1] + right_most_point = points[n - 1] convex_set = {left_most_point, right_most_point} upperhull = [] lowerhull = [] - for i in range(1, n-1): + for i in range(1, n - 1): det = _det(left_most_point, right_most_point, points[i]) if det > 0: @@ -394,7 +402,7 @@ def _construct_hull(points, left, right, convex_set): """ if points: extreme_point = None - extreme_point_distance = float('-inf') + extreme_point_distance = float("-inf") candidate_points = [] for p in points: @@ -414,8 +422,18 @@ def _construct_hull(points, left, right, convex_set): def main(): - points = [(0, 3), (2, 2), (1, 1), (2, 1), (3, 0), - (0, 0), (3, 3), (2, -1), (2, -4), (1, -3)] + points = [ + (0, 3), + (2, 2), + (1, 1), + (2, 1), + (3, 0), + (0, 0), + (3, 3), + (2, -1), + (2, -4), + (1, -3), + ] # the convex set of points is # [(0, 0), (0, 3), (1, -3), (2, -4), (3, 0), (3, 3)] results_recursive = convex_hull_recursive(points) @@ -425,5 +443,5 @@ def main(): print(results_bf) -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/divide_and_conquer/inversions.py b/divide_and_conquer/inversions.py index e4d50b7d4729..9bb656229321 100644 --- a/divide_and_conquer/inversions.py +++ b/divide_and_conquer/inversions.py @@ -38,7 +38,7 @@ def count_inversions_bf(arr): num_inversions = 0 n = len(arr) - for i in range(n-1): + for i in range(n - 1): for j in range(i + 1, n): if arr[i] > arr[j]: num_inversions += 1 @@ -73,7 +73,7 @@ def count_inversions_recursive(arr): if len(arr) <= 1: return arr, 0 else: - mid = len(arr)//2 + mid = len(arr) // 2 P = arr[0:mid] Q = arr[mid:] @@ -119,7 +119,7 @@ def _count_cross_inversions(P, Q): # if P[1] > Q[j], then P[k] > Q[k] for all i < k <= len(P) # These are all inversions. The claim emerges from the # property that P is sorted. - num_inversion += (len(P) - i) + num_inversion += len(P) - i R.append(Q[j]) j += 1 else: @@ -127,9 +127,9 @@ def _count_cross_inversions(P, Q): i += 1 if i < len(P): - R.extend(P[i:]) + R.extend(P[i:]) else: - R.extend(Q[j:]) + R.extend(Q[j:]) return R, num_inversion @@ -166,6 +166,4 @@ def main(): if __name__ == "__main__": - main() - - + main() diff --git a/divide_and_conquer/max_subarray_sum.py b/divide_and_conquer/max_subarray_sum.py index 0428f4e13768..9e81c83649a6 100644 --- a/divide_and_conquer/max_subarray_sum.py +++ b/divide_and_conquer/max_subarray_sum.py @@ -40,8 +40,8 @@ def max_cross_array_sum(array, left, mid, right): """ - max_sum_of_left = max_sum_from_start(array[left:mid+1][::-1]) - max_sum_of_right = max_sum_from_start(array[mid+1: right+1]) + max_sum_of_left = max_sum_from_start(array[left : mid + 1][::-1]) + max_sum_of_right = max_sum_from_start(array[mid + 1 : right + 1]) return max_sum_of_left + max_sum_of_right @@ -60,7 +60,7 @@ def max_subarray_sum(array, left, right): # base case: array has only one element if left == right: return array[right] - + # Recursion mid = (left + right) // 2 left_half_sum = max_subarray_sum(array, left, mid) @@ -71,5 +71,6 @@ def max_subarray_sum(array, left, right): array = [-2, -5, 6, -2, -3, 1, 5, -6] array_length = len(array) -print("Maximum sum of contiguous subarray:", max_subarray_sum(array, 0, array_length - 1)) - +print( + "Maximum sum of contiguous subarray:", max_subarray_sum(array, 0, array_length - 1) +) diff --git a/dynamic_programming/bitmask.py b/dynamic_programming/bitmask.py index 6685e1c68ee6..5c1ed36cb42a 100644 --- a/dynamic_programming/bitmask.py +++ b/dynamic_programming/bitmask.py @@ -13,54 +13,55 @@ class AssignmentUsingBitmask: - def __init__(self,task_performed,total): + def __init__(self, task_performed, total): - self.total_tasks = total #total no of tasks (N) + self.total_tasks = total # total no of tasks (N) # DP table will have a dimension of (2^M)*N # initially all values are set to -1 - self.dp = [[-1 for i in range(total+1)] for j in range(2**len(task_performed))] + self.dp = [ + [-1 for i in range(total + 1)] for j in range(2 ** len(task_performed)) + ] - self.task = defaultdict(list) #stores the list of persons for each task + self.task = defaultdict(list) # stores the list of persons for each task - #finalmask is used to check if all persons are included by setting all bits to 1 - self.finalmask = (1< self.total_tasks: return 0 - #if case already considered - if self.dp[mask][taskno]!=-1: + # if case already considered + if self.dp[mask][taskno] != -1: return self.dp[mask][taskno] # Number of ways when we dont this task in the arrangement - total_ways_util = self.CountWaysUtil(mask,taskno+1) + total_ways_util = self.CountWaysUtil(mask, taskno + 1) # now assign the tasks one by one to all possible persons and recursively assign for the remaining tasks. if taskno in self.task: for p in self.task[taskno]: # if p is already given a task - if mask & (1<-1): + if x == -1: + return y + 1 + elif y == -1: + return x + 1 + elif self.dp[x][y] > -1: return self.dp[x][y] else: - if (self.A[x]==self.B[y]): - self.dp[x][y] = self.__solveDP(x-1,y-1) + if self.A[x] == self.B[y]: + self.dp[x][y] = self.__solveDP(x - 1, y - 1) else: - self.dp[x][y] = 1+min(self.__solveDP(x,y-1), self.__solveDP(x-1,y), self.__solveDP(x-1,y-1)) + self.dp[x][y] = 1 + min( + self.__solveDP(x, y - 1), + self.__solveDP(x - 1, y), + self.__solveDP(x - 1, y - 1), + ) return self.dp[x][y] def solve(self, A, B): - if isinstance(A,bytes): - A = A.decode('ascii') + if isinstance(A, bytes): + A = A.decode("ascii") - if isinstance(B,bytes): - B = B.decode('ascii') + if isinstance(B, bytes): + B = B.decode("ascii") self.A = str(A) self.B = str(B) self.__prepare__(len(A), len(B)) - return self.__solveDP(len(A)-1, len(B)-1) + return self.__solveDP(len(A) - 1, len(B) - 1) def min_distance_bottom_up(word1: str, word2: str) -> int: @@ -63,38 +67,37 @@ def min_distance_bottom_up(word1: str, word2: str) -> int: """ m = len(word1) n = len(word2) - dp = [[0 for _ in range(n+1) ] for _ in range(m+1)] - for i in range(m+1): - for j in range(n+1): + dp = [[0 for _ in range(n + 1)] for _ in range(m + 1)] + for i in range(m + 1): + for j in range(n + 1): - if i == 0: #first string is empty + if i == 0: # first string is empty dp[i][j] = j - elif j == 0: #second string is empty + elif j == 0: # second string is empty dp[i][j] = i - elif word1[i-1] == word2[j-1]: #last character of both substing is equal - dp[i][j] = dp[i-1][j-1] + elif ( + word1[i - 1] == word2[j - 1] + ): # last character of both substing is equal + dp[i][j] = dp[i - 1][j - 1] else: - insert = dp[i][j-1] - delete = dp[i-1][j] - replace = dp[i-1][j-1] + insert = dp[i][j - 1] + delete = dp[i - 1][j] + replace = dp[i - 1][j - 1] dp[i][j] = 1 + min(insert, delete, replace) return dp[m][n] -if __name__ == '__main__': - solver = EditDistance() - - print("****************** Testing Edit Distance DP Algorithm ******************") - print() - - S1 = input("Enter the first string: ").strip() - S2 = input("Enter the second string: ").strip() - - print() - print("The minimum Edit Distance is: %d" % (solver.solve(S1, S2))) - print("The minimum Edit Distance is: %d" % (min_distance_bottom_up(S1, S2))) - print() - print("*************** End of Testing Edit Distance DP Algorithm ***************") +if __name__ == "__main__": + solver = EditDistance() + print("****************** Testing Edit Distance DP Algorithm ******************") + print() + S1 = input("Enter the first string: ").strip() + S2 = input("Enter the second string: ").strip() + print() + print("The minimum Edit Distance is: %d" % (solver.solve(S1, S2))) + print("The minimum Edit Distance is: %d" % (min_distance_bottom_up(S1, S2))) + print() + print("*************** End of Testing Edit Distance DP Algorithm ***************") diff --git a/dynamic_programming/factorial.py b/dynamic_programming/factorial.py index 7c6541ee2a74..0269014e7a18 100644 --- a/dynamic_programming/factorial.py +++ b/dynamic_programming/factorial.py @@ -1,6 +1,8 @@ -#Factorial of a number using memoization -result=[-1]*10 -result[0]=result[1]=1 +# Factorial of a number using memoization +result = [-1] * 10 +result[0] = result[1] = 1 + + def factorial(num): """ >>> factorial(7) @@ -10,19 +12,20 @@ def factorial(num): >>> [factorial(i) for i in range(5)] [1, 1, 2, 6, 24] """ - - if num<0: + + if num < 0: return "Number should not be negative." - if result[num]!=-1: + if result[num] != -1: return result[num] else: - result[num]=num*factorial(num-1) - #uncomment the following to see how recalculations are avoided - #print(result) + result[num] = num * factorial(num - 1) + # uncomment the following to see how recalculations are avoided + # print(result) return result[num] -#factorial of num -#uncomment the following to see how recalculations are avoided + +# factorial of num +# uncomment the following to see how recalculations are avoided ##result=[-1]*10 ##result[0]=result[1]=1 ##print(factorial(5)) @@ -31,4 +34,5 @@ def factorial(num): if __name__ == "__main__": import doctest + doctest.testmod() diff --git a/dynamic_programming/fibonacci.py b/dynamic_programming/fibonacci.py index 90fe6386044a..2dd1c2555f3e 100644 --- a/dynamic_programming/fibonacci.py +++ b/dynamic_programming/fibonacci.py @@ -4,7 +4,6 @@ class Fibonacci: - def __init__(self, N=None): self.fib_array = [] if N: @@ -19,14 +18,14 @@ def __init__(self, N=None): def get(self, sequence_no=None): if sequence_no != None: if sequence_no < len(self.fib_array): - return print(self.fib_array[:sequence_no + 1]) + return print(self.fib_array[: sequence_no + 1]) else: print("Out of bound.") else: print("Please specify a value") -if __name__ == '__main__': +if __name__ == "__main__": print("\n********* Fibonacci Series Using Dynamic Programming ************\n") print("\n Enter the upper limit for the fibonacci sequence: ", end="") try: @@ -34,7 +33,8 @@ def get(self, sequence_no=None): fib = Fibonacci(N) print( "\n********* Enter different values to get the corresponding fibonacci " - "sequence, enter any negative number to exit. ************\n") + "sequence, enter any negative number to exit. ************\n" + ) while True: try: i = int(input("Enter value: ").strip()) diff --git a/dynamic_programming/floyd_warshall.py b/dynamic_programming/floyd_warshall.py index 038499ca03b6..a4b6c6a82568 100644 --- a/dynamic_programming/floyd_warshall.py +++ b/dynamic_programming/floyd_warshall.py @@ -1,37 +1,42 @@ import math + class Graph: - - def __init__(self, N = 0): # a graph with Node 0,1,...,N-1 + def __init__(self, N=0): # a graph with Node 0,1,...,N-1 self.N = N - self.W = [[math.inf for j in range(0,N)] for i in range(0,N)] # adjacency matrix for weight - self.dp = [[math.inf for j in range(0,N)] for i in range(0,N)] # dp[i][j] stores minimum distance from i to j + self.W = [ + [math.inf for j in range(0, N)] for i in range(0, N) + ] # adjacency matrix for weight + self.dp = [ + [math.inf for j in range(0, N)] for i in range(0, N) + ] # dp[i][j] stores minimum distance from i to j def addEdge(self, u, v, w): self.dp[u][v] = w def floyd_warshall(self): - for k in range(0,self.N): - for i in range(0,self.N): - for j in range(0,self.N): + for k in range(0, self.N): + for i in range(0, self.N): + for j in range(0, self.N): self.dp[i][j] = min(self.dp[i][j], self.dp[i][k] + self.dp[k][j]) def showMin(self, u, v): return self.dp[u][v] - -if __name__ == '__main__': + + +if __name__ == "__main__": graph = Graph(5) - graph.addEdge(0,2,9) - graph.addEdge(0,4,10) - graph.addEdge(1,3,5) - graph.addEdge(2,3,7) - graph.addEdge(3,0,10) - graph.addEdge(3,1,2) - graph.addEdge(3,2,1) - graph.addEdge(3,4,6) - graph.addEdge(4,1,3) - graph.addEdge(4,2,4) - graph.addEdge(4,3,9) + graph.addEdge(0, 2, 9) + graph.addEdge(0, 4, 10) + graph.addEdge(1, 3, 5) + graph.addEdge(2, 3, 7) + graph.addEdge(3, 0, 10) + graph.addEdge(3, 1, 2) + graph.addEdge(3, 2, 1) + graph.addEdge(3, 4, 6) + graph.addEdge(4, 1, 3) + graph.addEdge(4, 2, 4) + graph.addEdge(4, 3, 9) graph.floyd_warshall() - graph.showMin(1,4) - graph.showMin(0,3) + graph.showMin(1, 4) + graph.showMin(0, 3) diff --git a/dynamic_programming/fractional_knapsack.py b/dynamic_programming/fractional_knapsack.py index 74e85b4b4708..881b6a3969d0 100644 --- a/dynamic_programming/fractional_knapsack.py +++ b/dynamic_programming/fractional_knapsack.py @@ -1,12 +1,20 @@ from itertools import accumulate from bisect import bisect + def fracKnapsack(vl, wt, W, n): - r = list(sorted(zip(vl,wt), key=lambda x:x[0]/x[1],reverse=True)) - vl , wt = [i[0] for i in r],[i[1] for i in r] - acc=list(accumulate(wt)) - k = bisect(acc,W) - return 0 if k == 0 else sum(vl[:k])+(W-acc[k-1])*(vl[k])/(wt[k]) if k!=n else sum(vl[:k]) + r = list(sorted(zip(vl, wt), key=lambda x: x[0] / x[1], reverse=True)) + vl, wt = [i[0] for i in r], [i[1] for i in r] + acc = list(accumulate(wt)) + k = bisect(acc, W) + return ( + 0 + if k == 0 + else sum(vl[:k]) + (W - acc[k - 1]) * (vl[k]) / (wt[k]) + if k != n + else sum(vl[:k]) + ) + -print("%.0f"%fracKnapsack([60, 100, 120],[10, 20, 30],50,3)) +print("%.0f" % fracKnapsack([60, 100, 120], [10, 20, 30], 50, 3)) diff --git a/dynamic_programming/integer_partition.py b/dynamic_programming/integer_partition.py index f17561fc135b..ec8c5bf62d7d 100644 --- a/dynamic_programming/integer_partition.py +++ b/dynamic_programming/integer_partition.py @@ -1,33 +1,36 @@ -''' +""" The number of partitions of a number n into at least k parts equals the number of partitions into exactly k parts plus the number of partitions into at least k-1 parts. Subtracting 1 from each part of a partition of n into k parts gives a partition of n-k into k parts. These two facts together are used for this algorithm. -''' +""" + + def partition(m): - memo = [[0 for _ in range(m)] for _ in range(m+1)] - for i in range(m+1): - memo[i][0] = 1 + memo = [[0 for _ in range(m)] for _ in range(m + 1)] + for i in range(m + 1): + memo[i][0] = 1 + + for n in range(m + 1): + for k in range(1, m): + memo[n][k] += memo[n][k - 1] + if n - k > 0: + memo[n][k] += memo[n - k - 1][k] - for n in range(m+1): - for k in range(1, m): - memo[n][k] += memo[n][k-1] - if n-k > 0: - memo[n][k] += memo[n-k-1][k] + return memo[m][m - 1] - return memo[m][m-1] -if __name__ == '__main__': - import sys +if __name__ == "__main__": + import sys - if len(sys.argv) == 1: - try: - n = int(input('Enter a number: ').strip()) - print(partition(n)) - except ValueError: - print('Please enter a number.') - else: - try: - n = int(sys.argv[1]) - print(partition(n)) - except ValueError: - print('Please pass a number.') \ No newline at end of file + if len(sys.argv) == 1: + try: + n = int(input("Enter a number: ").strip()) + print(partition(n)) + except ValueError: + print("Please enter a number.") + else: + try: + n = int(sys.argv[1]) + print(partition(n)) + except ValueError: + print("Please pass a number.") diff --git a/dynamic_programming/k_means_clustering_tensorflow.py b/dynamic_programming/k_means_clustering_tensorflow.py index b6813c6a22b3..6b1eb628e5c3 100644 --- a/dynamic_programming/k_means_clustering_tensorflow.py +++ b/dynamic_programming/k_means_clustering_tensorflow.py @@ -14,24 +14,24 @@ def TFKMeansCluster(vectors, noofclusters): noofclusters = int(noofclusters) assert noofclusters < len(vectors) - #Find out the dimensionality + # Find out the dimensionality dim = len(vectors[0]) - #Will help select random centroids from among the available vectors + # Will help select random centroids from among the available vectors vector_indices = list(range(len(vectors))) shuffle(vector_indices) - #GRAPH OF COMPUTATION - #We initialize a new graph and set it as the default during each run - #of this algorithm. This ensures that as this function is called - #multiple times, the default graph doesn't keep getting crowded with - #unused ops and Variables from previous function calls. + # GRAPH OF COMPUTATION + # We initialize a new graph and set it as the default during each run + # of this algorithm. This ensures that as this function is called + # multiple times, the default graph doesn't keep getting crowded with + # unused ops and Variables from previous function calls. graph = tf.Graph() with graph.as_default(): - #SESSION OF COMPUTATION + # SESSION OF COMPUTATION sess = tf.Session() @@ -39,8 +39,9 @@ def TFKMeansCluster(vectors, noofclusters): ##First lets ensure we have a Variable vector for each centroid, ##initialized to one of the vectors from the available data points - centroids = [tf.Variable((vectors[vector_indices[i]])) - for i in range(noofclusters)] + centroids = [ + tf.Variable((vectors[vector_indices[i]])) for i in range(noofclusters) + ] ##These nodes will assign the centroid Variables the appropriate ##values centroid_value = tf.placeholder("float64", [dim]) @@ -56,26 +57,24 @@ def TFKMeansCluster(vectors, noofclusters): assignment_value = tf.placeholder("int32") cluster_assigns = [] for assignment in assignments: - cluster_assigns.append(tf.assign(assignment, - assignment_value)) + cluster_assigns.append(tf.assign(assignment, assignment_value)) ##Now lets construct the node that will compute the mean - #The placeholder for the input + # The placeholder for the input mean_input = tf.placeholder("float", [None, dim]) - #The Node/op takes the input and computes a mean along the 0th - #dimension, i.e. the list of input vectors + # The Node/op takes the input and computes a mean along the 0th + # dimension, i.e. the list of input vectors mean_op = tf.reduce_mean(mean_input, 0) ##Node for computing Euclidean distances - #Placeholders for input + # Placeholders for input v1 = tf.placeholder("float", [dim]) v2 = tf.placeholder("float", [dim]) - euclid_dist = tf.sqrt(tf.reduce_sum(tf.pow(tf.sub( - v1, v2), 2))) + euclid_dist = tf.sqrt(tf.reduce_sum(tf.pow(tf.sub(v1, v2), 2))) ##This node will figure out which cluster to assign a vector to, ##based on Euclidean distances of the vector from the centroids. - #Placeholder for input + # Placeholder for input centroid_distances = tf.placeholder("float", [noofclusters]) cluster_assignment = tf.argmin(centroid_distances, 0) @@ -87,55 +86,62 @@ def TFKMeansCluster(vectors, noofclusters): ##will be included in the initialization. init_op = tf.initialize_all_variables() - #Initialize all variables + # Initialize all variables sess.run(init_op) ##CLUSTERING ITERATIONS - #Now perform the Expectation-Maximization steps of K-Means clustering - #iterations. To keep things simple, we will only do a set number of - #iterations, instead of using a Stopping Criterion. + # Now perform the Expectation-Maximization steps of K-Means clustering + # iterations. To keep things simple, we will only do a set number of + # iterations, instead of using a Stopping Criterion. noofiterations = 100 for iteration_n in range(noofiterations): ##EXPECTATION STEP ##Based on the centroid locations till last iteration, compute ##the _expected_ centroid assignments. - #Iterate over each vector + # Iterate over each vector for vector_n in range(len(vectors)): vect = vectors[vector_n] - #Compute Euclidean distance between this vector and each - #centroid. Remember that this list cannot be named + # Compute Euclidean distance between this vector and each + # centroid. Remember that this list cannot be named #'centroid_distances', since that is the input to the - #cluster assignment node. - distances = [sess.run(euclid_dist, feed_dict={ - v1: vect, v2: sess.run(centroid)}) - for centroid in centroids] - #Now use the cluster assignment node, with the distances - #as the input - assignment = sess.run(cluster_assignment, feed_dict = { - centroid_distances: distances}) - #Now assign the value to the appropriate state variable - sess.run(cluster_assigns[vector_n], feed_dict={ - assignment_value: assignment}) + # cluster assignment node. + distances = [ + sess.run(euclid_dist, feed_dict={v1: vect, v2: sess.run(centroid)}) + for centroid in centroids + ] + # Now use the cluster assignment node, with the distances + # as the input + assignment = sess.run( + cluster_assignment, feed_dict={centroid_distances: distances} + ) + # Now assign the value to the appropriate state variable + sess.run( + cluster_assigns[vector_n], feed_dict={assignment_value: assignment} + ) ##MAXIMIZATION STEP - #Based on the expected state computed from the Expectation Step, - #compute the locations of the centroids so as to maximize the - #overall objective of minimizing within-cluster Sum-of-Squares + # Based on the expected state computed from the Expectation Step, + # compute the locations of the centroids so as to maximize the + # overall objective of minimizing within-cluster Sum-of-Squares for cluster_n in range(noofclusters): - #Collect all the vectors assigned to this cluster - assigned_vects = [vectors[i] for i in range(len(vectors)) - if sess.run(assignments[i]) == cluster_n] - #Compute new centroid location - new_location = sess.run(mean_op, feed_dict={ - mean_input: array(assigned_vects)}) - #Assign value to appropriate variable - sess.run(cent_assigns[cluster_n], feed_dict={ - centroid_value: new_location}) - - #Return centroids and assignments + # Collect all the vectors assigned to this cluster + assigned_vects = [ + vectors[i] + for i in range(len(vectors)) + if sess.run(assignments[i]) == cluster_n + ] + # Compute new centroid location + new_location = sess.run( + mean_op, feed_dict={mean_input: array(assigned_vects)} + ) + # Assign value to appropriate variable + sess.run( + cent_assigns[cluster_n], feed_dict={centroid_value: new_location} + ) + + # Return centroids and assignments centroids = sess.run(centroids) assignments = sess.run(assignments) return centroids, assignments - diff --git a/dynamic_programming/knapsack.py b/dynamic_programming/knapsack.py index 488059d6244d..e71e3892e8cc 100644 --- a/dynamic_programming/knapsack.py +++ b/dynamic_programming/knapsack.py @@ -8,36 +8,38 @@ def MF_knapsack(i, wt, val, j): - ''' + """ This code involves the concept of memory functions. Here we solve the subproblems which are needed unlike the below example F is a 2D array with -1s filled up - ''' + """ global F # a global dp table for knapsack if F[i][j] < 0: - if j < wt[i-1]: - val = MF_knapsack(i-1, wt, val, j) + if j < wt[i - 1]: + val = MF_knapsack(i - 1, wt, val, j) else: - val = max(MF_knapsack(i-1, wt, val, j), - MF_knapsack(i-1, wt, val, j - wt[i-1]) + val[i-1]) + val = max( + MF_knapsack(i - 1, wt, val, j), + MF_knapsack(i - 1, wt, val, j - wt[i - 1]) + val[i - 1], + ) F[i][j] = val return F[i][j] def knapsack(W, wt, val, n): - dp = [[0 for i in range(W+1)]for j in range(n+1)] + dp = [[0 for i in range(W + 1)] for j in range(n + 1)] - for i in range(1,n+1): - for w in range(1, W+1): - if wt[i-1] <= w: - dp[i][w] = max(val[i-1] + dp[i-1][w-wt[i-1]], dp[i-1][w]) + for i in range(1, n + 1): + for w in range(1, W + 1): + if wt[i - 1] <= w: + dp[i][w] = max(val[i - 1] + dp[i - 1][w - wt[i - 1]], dp[i - 1][w]) else: - dp[i][w] = dp[i-1][w] + dp[i][w] = dp[i - 1][w] return dp[n][W], dp -def knapsack_with_example_solution(W: int, wt: list, val:list): +def knapsack_with_example_solution(W: int, wt: list, val: list): """ Solves the integer weights knapsack problem returns one of the several possible optimal subsets. @@ -70,17 +72,23 @@ def knapsack_with_example_solution(W: int, wt: list, val:list): But got 4 weights and 3 values """ if not (isinstance(wt, (list, tuple)) and isinstance(val, (list, tuple))): - raise ValueError("Both the weights and values vectors must be either lists or tuples") + raise ValueError( + "Both the weights and values vectors must be either lists or tuples" + ) num_items = len(wt) if num_items != len(val): - raise ValueError("The number of weights must be the " - "same as the number of values.\nBut " - "got {} weights and {} values".format(num_items, len(val))) + raise ValueError( + "The number of weights must be the " + "same as the number of values.\nBut " + "got {} weights and {} values".format(num_items, len(val)) + ) for i in range(num_items): if not isinstance(wt[i], int): - raise TypeError("All weights must be integers but " - "got weight of type {} at index {}".format(type(wt[i]), i)) + raise TypeError( + "All weights must be integers but " + "got weight of type {} at index {}".format(type(wt[i]), i) + ) optimal_val, dp_table = knapsack(W, wt, val, num_items) example_optional_set = set() @@ -89,7 +97,7 @@ def knapsack_with_example_solution(W: int, wt: list, val:list): return optimal_val, example_optional_set -def _construct_solution(dp:list, wt:list, i:int, j:int, optimal_set:set): +def _construct_solution(dp: list, wt: list, i: int, j: int, optimal_set: set): """ Recursively reconstructs one of the optimal subsets given a filled DP table and the vector of weights @@ -117,21 +125,21 @@ def _construct_solution(dp:list, wt:list, i:int, j:int, optimal_set:set): _construct_solution(dp, wt, i - 1, j, optimal_set) else: optimal_set.add(i) - _construct_solution(dp, wt, i - 1, j - wt[i-1], optimal_set) + _construct_solution(dp, wt, i - 1, j - wt[i - 1], optimal_set) -if __name__ == '__main__': - ''' +if __name__ == "__main__": + """ Adding test case for knapsack - ''' + """ val = [3, 2, 4, 4] wt = [4, 3, 2, 3] n = 4 w = 6 F = [[0] * (w + 1)] + [[0] + [-1 for i in range(w + 1)] for j in range(n + 1)] - optimal_solution, _ = knapsack(w,wt,val, n) + optimal_solution, _ = knapsack(w, wt, val, n) print(optimal_solution) - print(MF_knapsack(n,wt,val,w)) # switched the n and w + print(MF_knapsack(n, wt, val, w)) # switched the n and w # testing the dynamic programming problem with example # the optimal subset for the above example are items 3 and 4 @@ -140,4 +148,3 @@ def _construct_solution(dp:list, wt:list, i:int, j:int, optimal_set:set): assert optimal_subset == {3, 4} print("optimal_value = ", optimal_solution) print("An optimal subset corresponding to the optimal value", optimal_subset) - diff --git a/dynamic_programming/longest_common_subsequence.py b/dynamic_programming/longest_common_subsequence.py index d39485408988..12fcae684051 100644 --- a/dynamic_programming/longest_common_subsequence.py +++ b/dynamic_programming/longest_common_subsequence.py @@ -41,12 +41,12 @@ def longest_common_subsequence(x: str, y: str): for i in range(1, m + 1): for j in range(1, n + 1): - if x[i-1] == y[j-1]: + if x[i - 1] == y[j - 1]: match = 1 else: match = 0 - L[i][j] = max(L[i-1][j], L[i][j-1], L[i-1][j-1] + match) + L[i][j] = max(L[i - 1][j], L[i][j - 1], L[i - 1][j - 1] + match) seq = "" i, j = m, n @@ -69,9 +69,9 @@ def longest_common_subsequence(x: str, y: str): return L[m][n], seq -if __name__ == '__main__': - a = 'AGGTAB' - b = 'GXTXAYB' +if __name__ == "__main__": + a = "AGGTAB" + b = "GXTXAYB" expected_ln = 4 expected_subseq = "GTAB" diff --git a/dynamic_programming/longest_increasing_subsequence.py b/dynamic_programming/longest_increasing_subsequence.py index 151a5e0b7c80..3cb57806e06f 100644 --- a/dynamic_programming/longest_increasing_subsequence.py +++ b/dynamic_programming/longest_increasing_subsequence.py @@ -1,4 +1,4 @@ -''' +""" Author : Mehdi ALAOUI This is a pure Python implementation of Dynamic Programming solution to the longest increasing subsequence of a given sequence. @@ -6,35 +6,40 @@ The problem is : Given an ARRAY, to find the longest and increasing sub ARRAY in that given ARRAY and return it. Example: [10, 22, 9, 33, 21, 50, 41, 60, 80] as input will return [10, 22, 33, 41, 60, 80] as output -''' -def longestSub(ARRAY): #This function is recursive - - ARRAY_LENGTH = len(ARRAY) - if(ARRAY_LENGTH <= 1): #If the array contains only one element, we return it (it's the stop condition of recursion) - return ARRAY - #Else - PIVOT=ARRAY[0] - isFound=False - i=1 - LONGEST_SUB=[] - while(not isFound and i= ARRAY[i] ] - TEMPORARY_ARRAY = longestSub(TEMPORARY_ARRAY) - if ( len(TEMPORARY_ARRAY) > len(LONGEST_SUB) ): - LONGEST_SUB = TEMPORARY_ARRAY - else: - i+=1 - - TEMPORARY_ARRAY = [ element for element in ARRAY[1:] if element >= PIVOT ] - TEMPORARY_ARRAY = [PIVOT] + longestSub(TEMPORARY_ARRAY) - if ( len(TEMPORARY_ARRAY) > len(LONGEST_SUB) ): - return TEMPORARY_ARRAY - else: - return LONGEST_SUB - -#Some examples - -print(longestSub([4,8,7,5,1,12,2,3,9])) -print(longestSub([9,8,7,6,5,7])) \ No newline at end of file +""" + + +def longestSub(ARRAY): # This function is recursive + + ARRAY_LENGTH = len(ARRAY) + if ( + ARRAY_LENGTH <= 1 + ): # If the array contains only one element, we return it (it's the stop condition of recursion) + return ARRAY + # Else + PIVOT = ARRAY[0] + isFound = False + i = 1 + LONGEST_SUB = [] + while not isFound and i < ARRAY_LENGTH: + if ARRAY[i] < PIVOT: + isFound = True + TEMPORARY_ARRAY = [element for element in ARRAY[i:] if element >= ARRAY[i]] + TEMPORARY_ARRAY = longestSub(TEMPORARY_ARRAY) + if len(TEMPORARY_ARRAY) > len(LONGEST_SUB): + LONGEST_SUB = TEMPORARY_ARRAY + else: + i += 1 + + TEMPORARY_ARRAY = [element for element in ARRAY[1:] if element >= PIVOT] + TEMPORARY_ARRAY = [PIVOT] + longestSub(TEMPORARY_ARRAY) + if len(TEMPORARY_ARRAY) > len(LONGEST_SUB): + return TEMPORARY_ARRAY + else: + return LONGEST_SUB + + +# Some examples + +print(longestSub([4, 8, 7, 5, 1, 12, 2, 3, 9])) +print(longestSub([9, 8, 7, 6, 5, 7])) diff --git a/dynamic_programming/longest_increasing_subsequence_o(nlogn).py b/dynamic_programming/longest_increasing_subsequence_o(nlogn).py index 9b27ed6be303..f7b2c6915bcb 100644 --- a/dynamic_programming/longest_increasing_subsequence_o(nlogn).py +++ b/dynamic_programming/longest_increasing_subsequence_o(nlogn).py @@ -4,38 +4,38 @@ # comments: This programme outputs the Longest Strictly Increasing Subsequence in O(NLogN) # Where N is the Number of elements in the list ############################# -def CeilIndex(v,l,r,key): - while r-l > 1: - m = (l + r)/2 - if v[m] >= key: - r = m - else: - l = m +def CeilIndex(v, l, r, key): + while r - l > 1: + m = (l + r) / 2 + if v[m] >= key: + r = m + else: + l = m - return r + return r def LongestIncreasingSubsequenceLength(v): - if(len(v) == 0): - return 0 + if len(v) == 0: + return 0 - tail = [0]*len(v) - length = 1 + tail = [0] * len(v) + length = 1 - tail[0] = v[0] + tail[0] = v[0] - for i in range(1,len(v)): - if v[i] < tail[0]: - tail[0] = v[i] - elif v[i] > tail[length-1]: - tail[length] = v[i] - length += 1 - else: - tail[CeilIndex(tail,-1,length-1,v[i])] = v[i] + for i in range(1, len(v)): + if v[i] < tail[0]: + tail[0] = v[i] + elif v[i] > tail[length - 1]: + tail[length] = v[i] + length += 1 + else: + tail[CeilIndex(tail, -1, length - 1, v[i])] = v[i] - return length + return length if __name__ == "__main__": - v = [2, 5, 3, 7, 11, 8, 10, 13, 6] - print(LongestIncreasingSubsequenceLength(v)) + v = [2, 5, 3, 7, 11, 8, 10, 13, 6] + print(LongestIncreasingSubsequenceLength(v)) diff --git a/dynamic_programming/longest_sub_array.py b/dynamic_programming/longest_sub_array.py index 856b31f03982..65ce151c33d6 100644 --- a/dynamic_programming/longest_sub_array.py +++ b/dynamic_programming/longest_sub_array.py @@ -1,32 +1,32 @@ -''' +""" Auther : Yvonne This is a pure Python implementation of Dynamic Programming solution to the longest_sub_array problem. The problem is : Given an array, to find the longest and continuous sub array and get the max sum of the sub array in the given array. -''' +""" class SubArray: - def __init__(self, arr): # we need a list not a string, so do something to change the type - self.array = arr.split(',') + self.array = arr.split(",") print(("the input array is:", self.array)) def solve_sub_array(self): - rear = [int(self.array[0])]*len(self.array) - sum_value = [int(self.array[0])]*len(self.array) + rear = [int(self.array[0])] * len(self.array) + sum_value = [int(self.array[0])] * len(self.array) for i in range(1, len(self.array)): - sum_value[i] = max(int(self.array[i]) + sum_value[i-1], int(self.array[i])) - rear[i] = max(sum_value[i], rear[i-1]) - return rear[len(self.array)-1] + sum_value[i] = max( + int(self.array[i]) + sum_value[i - 1], int(self.array[i]) + ) + rear[i] = max(sum_value[i], rear[i - 1]) + return rear[len(self.array) - 1] -if __name__ == '__main__': +if __name__ == "__main__": whole_array = input("please input some numbers:") array = SubArray(whole_array) re = array.solve_sub_array() print(("the results is:", re)) - diff --git a/dynamic_programming/matrix_chain_order.py b/dynamic_programming/matrix_chain_order.py index cb4aec345437..f88a9be8ac95 100644 --- a/dynamic_programming/matrix_chain_order.py +++ b/dynamic_programming/matrix_chain_order.py @@ -1,44 +1,54 @@ import sys -''' + +""" Dynamic Programming Implementation of Matrix Chain Multiplication Time Complexity: O(n^3) Space Complexity: O(n^2) -''' +""" + + def MatrixChainOrder(array): - N=len(array) - Matrix=[[0 for x in range(N)] for x in range(N)] - Sol=[[0 for x in range(N)] for x in range(N)] + N = len(array) + Matrix = [[0 for x in range(N)] for x in range(N)] + Sol = [[0 for x in range(N)] for x in range(N)] - for ChainLength in range(2,N): - for a in range(1,N-ChainLength+1): - b = a+ChainLength-1 + for ChainLength in range(2, N): + for a in range(1, N - ChainLength + 1): + b = a + ChainLength - 1 Matrix[a][b] = sys.maxsize - for c in range(a , b): - cost = Matrix[a][c] + Matrix[c+1][b] + array[a-1]*array[c]*array[b] + for c in range(a, b): + cost = ( + Matrix[a][c] + Matrix[c + 1][b] + array[a - 1] * array[c] * array[b] + ) if cost < Matrix[a][b]: Matrix[a][b] = cost Sol[a][b] = c - return Matrix , Sol -#Print order of matrix with Ai as Matrix -def PrintOptimalSolution(OptimalSolution,i,j): - if i==j: - print("A" + str(i),end = " ") + return Matrix, Sol + + +# Print order of matrix with Ai as Matrix +def PrintOptimalSolution(OptimalSolution, i, j): + if i == j: + print("A" + str(i), end=" ") else: - print("(",end = " ") - PrintOptimalSolution(OptimalSolution,i,OptimalSolution[i][j]) - PrintOptimalSolution(OptimalSolution,OptimalSolution[i][j]+1,j) - print(")",end = " ") + print("(", end=" ") + PrintOptimalSolution(OptimalSolution, i, OptimalSolution[i][j]) + PrintOptimalSolution(OptimalSolution, OptimalSolution[i][j] + 1, j) + print(")", end=" ") + def main(): - array=[30,35,15,5,10,20,25] - n=len(array) - #Size of matrix created from above array will be + array = [30, 35, 15, 5, 10, 20, 25] + n = len(array) + # Size of matrix created from above array will be # 30*35 35*15 15*5 5*10 10*20 20*25 - Matrix , OptimalSolution = MatrixChainOrder(array) + Matrix, OptimalSolution = MatrixChainOrder(array) + + print("No. of Operation required: " + str((Matrix[1][n - 1]))) + PrintOptimalSolution(OptimalSolution, 1, n - 1) + - print("No. of Operation required: "+str((Matrix[1][n-1]))) - PrintOptimalSolution(OptimalSolution,1,n-1) -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/dynamic_programming/max_sub_array.py b/dynamic_programming/max_sub_array.py index d6084ecfd6d9..eb6ab41bf52d 100644 --- a/dynamic_programming/max_sub_array.py +++ b/dynamic_programming/max_sub_array.py @@ -5,37 +5,41 @@ import time import matplotlib.pyplot as plt from random import randint -def find_max_sub_array(A,low,high): - if low==high: - return low,high,A[low] - else : - mid=(low+high)//2 - left_low,left_high,left_sum=find_max_sub_array(A,low,mid) - right_low,right_high,right_sum=find_max_sub_array(A,mid+1,high) - cross_left,cross_right,cross_sum=find_max_cross_sum(A,low,mid,high) - if left_sum>=right_sum and left_sum>=cross_sum: - return left_low,left_high,left_sum - elif right_sum>=left_sum and right_sum>=cross_sum : - return right_low,right_high,right_sum + + +def find_max_sub_array(A, low, high): + if low == high: + return low, high, A[low] + else: + mid = (low + high) // 2 + left_low, left_high, left_sum = find_max_sub_array(A, low, mid) + right_low, right_high, right_sum = find_max_sub_array(A, mid + 1, high) + cross_left, cross_right, cross_sum = find_max_cross_sum(A, low, mid, high) + if left_sum >= right_sum and left_sum >= cross_sum: + return left_low, left_high, left_sum + elif right_sum >= left_sum and right_sum >= cross_sum: + return right_low, right_high, right_sum else: - return cross_left,cross_right,cross_sum + return cross_left, cross_right, cross_sum + -def find_max_cross_sum(A,low,mid,high): - left_sum,max_left=-999999999,-1 - right_sum,max_right=-999999999,-1 - summ=0 - for i in range(mid,low-1,-1): - summ+=A[i] +def find_max_cross_sum(A, low, mid, high): + left_sum, max_left = -999999999, -1 + right_sum, max_right = -999999999, -1 + summ = 0 + for i in range(mid, low - 1, -1): + summ += A[i] if summ > left_sum: - left_sum=summ - max_left=i - summ=0 - for i in range(mid+1,high+1): - summ+=A[i] + left_sum = summ + max_left = i + summ = 0 + for i in range(mid + 1, high + 1): + summ += A[i] if summ > right_sum: - right_sum=summ - max_right=i - return max_left,max_right,(left_sum+right_sum) + right_sum = summ + max_right = i + return max_left, max_right, (left_sum + right_sum) + def max_sub_array(nums: List[int]) -> int: """ @@ -58,22 +62,20 @@ def max_sub_array(nums: List[int]) -> int: best = max(best, current) return best -if __name__=='__main__': - inputs=[10,100,1000,10000,50000,100000,200000,300000,400000,500000] - tim=[] + +if __name__ == "__main__": + inputs = [10, 100, 1000, 10000, 50000, 100000, 200000, 300000, 400000, 500000] + tim = [] for i in inputs: - li=[randint(1,i) for j in range(i)] - strt=time.time() - (find_max_sub_array(li,0,len(li)-1)) - end=time.time() - tim.append(end-strt) + li = [randint(1, i) for j in range(i)] + strt = time.time() + (find_max_sub_array(li, 0, len(li) - 1)) + end = time.time() + tim.append(end - strt) print("No of Inputs Time Taken") for i in range(len(inputs)): - print(inputs[i],'\t\t',tim[i]) - plt.plot(inputs,tim) - plt.xlabel("Number of Inputs");plt.ylabel("Time taken in seconds ") + print(inputs[i], "\t\t", tim[i]) + plt.plot(inputs, tim) + plt.xlabel("Number of Inputs") + plt.ylabel("Time taken in seconds ") plt.show() - - - - diff --git a/dynamic_programming/minimum_partition.py b/dynamic_programming/minimum_partition.py index 18aa1faa2fa6..d5750326fea4 100644 --- a/dynamic_programming/minimum_partition.py +++ b/dynamic_programming/minimum_partition.py @@ -1,28 +1,30 @@ """ Partition a set into two subsets such that the difference of subset sums is minimum """ + + def findMin(arr): n = len(arr) s = sum(arr) - dp = [[False for x in range(s+1)]for y in range(n+1)] + dp = [[False for x in range(s + 1)] for y in range(n + 1)] - for i in range(1, n+1): + for i in range(1, n + 1): dp[i][0] = True - for i in range(1, s+1): + for i in range(1, s + 1): dp[0][i] = False - for i in range(1, n+1): - for j in range(1, s+1): - dp[i][j]= dp[i][j-1] + for i in range(1, n + 1): + for j in range(1, s + 1): + dp[i][j] = dp[i][j - 1] - if (arr[i-1] <= j): - dp[i][j] = dp[i][j] or dp[i-1][j-arr[i-1]] + if arr[i - 1] <= j: + dp[i][j] = dp[i][j] or dp[i - 1][j - arr[i - 1]] - for j in range(int(s/2), -1, -1): + for j in range(int(s / 2), -1, -1): if dp[n][j] == True: - diff = s-2*j - break; + diff = s - 2 * j + break return diff diff --git a/dynamic_programming/rod_cutting.py b/dynamic_programming/rod_cutting.py index 5b52eaca7c89..3a1d55320d7b 100644 --- a/dynamic_programming/rod_cutting.py +++ b/dynamic_programming/rod_cutting.py @@ -12,7 +12,7 @@ def naive_cut_rod_recursive(n: int, prices: list): - """ + """ Solves the rod-cutting problem via naively without using the benefit of dynamic programming. The results is the same sub-problems are solved several times leading to an exponential runtime @@ -36,18 +36,20 @@ def naive_cut_rod_recursive(n: int, prices: list): 30 """ - _enforce_args(n, prices) - if n == 0: - return 0 - max_revue = float("-inf") - for i in range(1, n + 1): - max_revue = max(max_revue, prices[i - 1] + naive_cut_rod_recursive(n - i, prices)) + _enforce_args(n, prices) + if n == 0: + return 0 + max_revue = float("-inf") + for i in range(1, n + 1): + max_revue = max( + max_revue, prices[i - 1] + naive_cut_rod_recursive(n - i, prices) + ) - return max_revue + return max_revue def top_down_cut_rod(n: int, prices: list): - """ + """ Constructs a top-down dynamic programming solution for the rod-cutting problem via memoization. This function serves as a wrapper for _top_down_cut_rod_recursive @@ -75,13 +77,13 @@ def top_down_cut_rod(n: int, prices: list): >>> top_down_cut_rod(10, [1, 5, 8, 9, 10, 17, 17, 20, 24, 30]) 30 """ - _enforce_args(n, prices) - max_rev = [float("-inf") for _ in range(n + 1)] - return _top_down_cut_rod_recursive(n, prices, max_rev) + _enforce_args(n, prices) + max_rev = [float("-inf") for _ in range(n + 1)] + return _top_down_cut_rod_recursive(n, prices, max_rev) def _top_down_cut_rod_recursive(n: int, prices: list, max_rev: list): - """ + """ Constructs a top-down dynamic programming solution for the rod-cutting problem via memoization. @@ -99,22 +101,25 @@ def _top_down_cut_rod_recursive(n: int, prices: list, max_rev: list): ------- The maximum revenue obtainable for a rod of length n given the list of prices for each piece. """ - if max_rev[n] >= 0: - return max_rev[n] - elif n == 0: - return 0 - else: - max_revenue = float("-inf") - for i in range(1, n + 1): - max_revenue = max(max_revenue, prices[i - 1] + _top_down_cut_rod_recursive(n - i, prices, max_rev)) + if max_rev[n] >= 0: + return max_rev[n] + elif n == 0: + return 0 + else: + max_revenue = float("-inf") + for i in range(1, n + 1): + max_revenue = max( + max_revenue, + prices[i - 1] + _top_down_cut_rod_recursive(n - i, prices, max_rev), + ) - max_rev[n] = max_revenue + max_rev[n] = max_revenue - return max_rev[n] + return max_rev[n] def bottom_up_cut_rod(n: int, prices: list): - """ + """ Constructs a bottom-up dynamic programming solution for the rod-cutting problem Runtime: O(n^2) @@ -137,24 +142,24 @@ def bottom_up_cut_rod(n: int, prices: list): >>> bottom_up_cut_rod(10, [1, 5, 8, 9, 10, 17, 17, 20, 24, 30]) 30 """ - _enforce_args(n, prices) + _enforce_args(n, prices) - # length(max_rev) = n + 1, to accommodate for the revenue obtainable from a rod of length 0. - max_rev = [float("-inf") for _ in range(n + 1)] - max_rev[0] = 0 + # length(max_rev) = n + 1, to accommodate for the revenue obtainable from a rod of length 0. + max_rev = [float("-inf") for _ in range(n + 1)] + max_rev[0] = 0 - for i in range(1, n + 1): - max_revenue_i = max_rev[i] - for j in range(1, i + 1): - max_revenue_i = max(max_revenue_i, prices[j - 1] + max_rev[i - j]) + for i in range(1, n + 1): + max_revenue_i = max_rev[i] + for j in range(1, i + 1): + max_revenue_i = max(max_revenue_i, prices[j - 1] + max_rev[i - j]) - max_rev[i] = max_revenue_i + max_rev[i] = max_revenue_i - return max_rev[n] + return max_rev[n] def _enforce_args(n: int, prices: list): - """ + """ Basic checks on the arguments to the rod-cutting algorithms n: int, the length of the rod @@ -164,30 +169,32 @@ def _enforce_args(n: int, prices: list): if n is negative or there are fewer items in the price list than the length of the rod """ - if n < 0: - raise ValueError(f"n must be greater than or equal to 0. Got n = {n}") + if n < 0: + raise ValueError(f"n must be greater than or equal to 0. Got n = {n}") - if n > len(prices): - raise ValueError(f"Each integral piece of rod must have a corresponding " - f"price. Got n = {n} but length of prices = {len(prices)}") + if n > len(prices): + raise ValueError( + f"Each integral piece of rod must have a corresponding " + f"price. Got n = {n} but length of prices = {len(prices)}" + ) def main(): - prices = [6, 10, 12, 15, 20, 23] - n = len(prices) + prices = [6, 10, 12, 15, 20, 23] + n = len(prices) - # the best revenue comes from cutting the rod into 6 pieces, each - # of length 1 resulting in a revenue of 6 * 6 = 36. - expected_max_revenue = 36 + # the best revenue comes from cutting the rod into 6 pieces, each + # of length 1 resulting in a revenue of 6 * 6 = 36. + expected_max_revenue = 36 - max_rev_top_down = top_down_cut_rod(n, prices) - max_rev_bottom_up = bottom_up_cut_rod(n, prices) - max_rev_naive = naive_cut_rod_recursive(n, prices) + max_rev_top_down = top_down_cut_rod(n, prices) + max_rev_bottom_up = bottom_up_cut_rod(n, prices) + max_rev_naive = naive_cut_rod_recursive(n, prices) - assert expected_max_revenue == max_rev_top_down - assert max_rev_top_down == max_rev_bottom_up - assert max_rev_bottom_up == max_rev_naive + assert expected_max_revenue == max_rev_top_down + assert max_rev_top_down == max_rev_bottom_up + assert max_rev_bottom_up == max_rev_naive -if __name__ == '__main__': - main() +if __name__ == "__main__": + main() diff --git a/dynamic_programming/subset_generation.py b/dynamic_programming/subset_generation.py index 4b7a2bf87fd5..2cca97fc3cbc 100644 --- a/dynamic_programming/subset_generation.py +++ b/dynamic_programming/subset_generation.py @@ -1,39 +1,43 @@ # python program to print all subset combination of n element in given set of r element . -#arr[] ---> Input Array -#data[] ---> Temporary array to store current combination +# arr[] ---> Input Array +# data[] ---> Temporary array to store current combination # start & end ---> Staring and Ending indexes in arr[] # index ---> Current index in data[] -#r ---> Size of a combination to be printed -def combinationUtil(arr,n,r,index,data,i): -#Current combination is ready to be printed, -# print it - if(index == r): - for j in range(r): - print(data[j],end =" ") - print(" ") - return -# When no more elements are there to put in data[] - if(i >= n): - return -#current is included, put next at next -# location - data[index] = arr[i] - combinationUtil(arr,n,r,index+1,data,i+1) - # current is excluded, replace it with - # next (Note that i+1 is passed, but - # index is not changed) - combinationUtil(arr,n,r,index,data,i+1) - # The main function that prints all combinations - #of size r in arr[] of size n. This function - #mainly uses combinationUtil() -def printcombination(arr,n,r): -# A temporary array to store all combination -# one by one - data = [0]*r -#Print all combination using temprary -#array 'data[]' - combinationUtil(arr,n,r,0,data,0) +# r ---> Size of a combination to be printed +def combinationUtil(arr, n, r, index, data, i): + # Current combination is ready to be printed, + # print it + if index == r: + for j in range(r): + print(data[j], end=" ") + print(" ") + return + # When no more elements are there to put in data[] + if i >= n: + return + # current is included, put next at next + # location + data[index] = arr[i] + combinationUtil(arr, n, r, index + 1, data, i + 1) + # current is excluded, replace it with + # next (Note that i+1 is passed, but + # index is not changed) + combinationUtil(arr, n, r, index, data, i + 1) + # The main function that prints all combinations + # of size r in arr[] of size n. This function + # mainly uses combinationUtil() + + +def printcombination(arr, n, r): + # A temporary array to store all combination + # one by one + data = [0] * r + # Print all combination using temprary + # array 'data[]' + combinationUtil(arr, n, r, 0, data, 0) + + # Driver function to check for above function -arr = [10,20,30,40,50] -printcombination(arr,len(arr),3) -#This code is contributed by Ambuj sahu +arr = [10, 20, 30, 40, 50] +printcombination(arr, len(arr), 3) +# This code is contributed by Ambuj sahu diff --git a/dynamic_programming/sum_of_subset.py b/dynamic_programming/sum_of_subset.py index f6509a259c5d..581039080101 100644 --- a/dynamic_programming/sum_of_subset.py +++ b/dynamic_programming/sum_of_subset.py @@ -1,34 +1,35 @@ def isSumSubset(arr, arrLen, requiredSum): # a subset value says 1 if that subset sum can be formed else 0 - #initially no subsets can be formed hence False/0 - subset = ([[False for i in range(requiredSum + 1)] for i in range(arrLen + 1)]) + # initially no subsets can be formed hence False/0 + subset = [[False for i in range(requiredSum + 1)] for i in range(arrLen + 1)] - #for each arr value, a sum of zero(0) can be formed by not taking any element hence True/1 + # for each arr value, a sum of zero(0) can be formed by not taking any element hence True/1 for i in range(arrLen + 1): subset[i][0] = True - #sum is not zero and set is empty then false + # sum is not zero and set is empty then false for i in range(1, requiredSum + 1): subset[0][i] = False for i in range(1, arrLen + 1): for j in range(1, requiredSum + 1): - if arr[i-1]>j: - subset[i][j] = subset[i-1][j] - if arr[i-1]<=j: - subset[i][j] = (subset[i-1][j] or subset[i-1][j-arr[i-1]]) + if arr[i - 1] > j: + subset[i][j] = subset[i - 1][j] + if arr[i - 1] <= j: + subset[i][j] = subset[i - 1][j] or subset[i - 1][j - arr[i - 1]] - #uncomment to print the subset + # uncomment to print the subset # for i in range(arrLen+1): # print(subset[i]) return subset[arrLen][requiredSum] + arr = [2, 4, 6, 8] -requiredSum = 5 +requiredSum = 5 arrLen = len(arr) if isSumSubset(arr, arrLen, requiredSum): print("Found a subset with required sum") else: - print("No subset with required sum") \ No newline at end of file + print("No subset with required sum") diff --git a/file_transfer/recieve_file.py b/file_transfer/recieve_file.py index f404546d7765..cfba6ed88484 100644 --- a/file_transfer/recieve_file.py +++ b/file_transfer/recieve_file.py @@ -1,4 +1,4 @@ -if __name__ == '__main__': +if __name__ == "__main__": import socket # Import socket module sock = socket.socket() # Create a socket object @@ -6,11 +6,11 @@ port = 12312 sock.connect((host, port)) - sock.send(b'Hello server!') + sock.send(b"Hello server!") - with open('Received_file', 'wb') as out_file: - print('File opened') - print('Receiving data...') + with open("Received_file", "wb") as out_file: + print("File opened") + print("Receiving data...") while True: data = sock.recv(1024) print(f"data={data}") @@ -18,6 +18,6 @@ break out_file.write(data) # Write data to a file - print('Successfully got the file') + print("Successfully got the file") sock.close() - print('Connection closed') + print("Connection closed") diff --git a/file_transfer/send_file.py b/file_transfer/send_file.py index 92fab206c1a1..ebc075a30ad4 100644 --- a/file_transfer/send_file.py +++ b/file_transfer/send_file.py @@ -1,16 +1,18 @@ -if __name__ == '__main__': +if __name__ == "__main__": import socket # Import socket module - ONE_CONNECTION_ONLY = True # Set this to False if you wish to continuously accept connections + ONE_CONNECTION_ONLY = ( + True + ) # Set this to False if you wish to continuously accept connections - filename='mytext.txt' + filename = "mytext.txt" port = 12312 # Reserve a port for your service. sock = socket.socket() # Create a socket object host = socket.gethostname() # Get local machine name sock.bind((host, port)) # Bind to the port sock.listen(5) # Now wait for client connection. - print('Server listening....') + print("Server listening....") while True: conn, addr = sock.accept() # Establish connection with client. @@ -18,16 +20,18 @@ data = conn.recv(1024) print(f"Server received {data}") - with open(filename,'rb') as in_file: + with open(filename, "rb") as in_file: data = in_file.read(1024) - while (data): - conn.send(data) - print(f"Sent {data!r}") - data = in_file.read(1024) + while data: + conn.send(data) + print(f"Sent {data!r}") + data = in_file.read(1024) - print('Done sending') + print("Done sending") conn.close() - if ONE_CONNECTION_ONLY: # This is to make sure that the program doesn't hang while testing + if ( + ONE_CONNECTION_ONLY + ): # This is to make sure that the program doesn't hang while testing break sock.shutdown(1) diff --git a/graphs/a_star.py b/graphs/a_star.py index 09a7a0e579d8..e1d17fc55434 100644 --- a/graphs/a_star.py +++ b/graphs/a_star.py @@ -1,42 +1,45 @@ -grid = [[0, 1, 0, 0, 0, 0], - [0, 1, 0, 0, 0, 0],#0 are free path whereas 1's are obstacles - [0, 1, 0, 0, 0, 0], - [0, 1, 0, 0, 1, 0], - [0, 0, 0, 0, 1, 0]] - -''' +grid = [ + [0, 1, 0, 0, 0, 0], + [0, 1, 0, 0, 0, 0], # 0 are free path whereas 1's are obstacles + [0, 1, 0, 0, 0, 0], + [0, 1, 0, 0, 1, 0], + [0, 0, 0, 0, 1, 0], +] + +""" heuristic = [[9, 8, 7, 6, 5, 4], [8, 7, 6, 5, 4, 3], [7, 6, 5, 4, 3, 2], [6, 5, 4, 3, 2, 1], - [5, 4, 3, 2, 1, 0]]''' + [5, 4, 3, 2, 1, 0]]""" init = [0, 0] -goal = [len(grid)-1, len(grid[0])-1] #all coordinates are given in format [y,x] +goal = [len(grid) - 1, len(grid[0]) - 1] # all coordinates are given in format [y,x] cost = 1 -#the cost map which pushes the path closer to the goal +# the cost map which pushes the path closer to the goal heuristic = [[0 for row in range(len(grid[0]))] for col in range(len(grid))] for i in range(len(grid)): for j in range(len(grid[0])): heuristic[i][j] = abs(i - goal[0]) + abs(j - goal[1]) if grid[i][j] == 1: - heuristic[i][j] = 99 #added extra penalty in the heuristic map + heuristic[i][j] = 99 # added extra penalty in the heuristic map -#the actions we can take -delta = [[-1, 0 ], # go up - [ 0, -1], # go left - [ 1, 0 ], # go down - [ 0, 1 ]] # go right +# the actions we can take +delta = [[-1, 0], [0, -1], [1, 0], [0, 1]] # go up # go left # go down # go right -#function to search the path -def search(grid,init,goal,cost,heuristic): +# function to search the path +def search(grid, init, goal, cost, heuristic): - closed = [[0 for col in range(len(grid[0]))] for row in range(len(grid))]# the referrence grid + closed = [ + [0 for col in range(len(grid[0]))] for row in range(len(grid)) + ] # the referrence grid closed[init[0]][init[1]] = 1 - action = [[0 for col in range(len(grid[0]))] for row in range(len(grid))]#the action grid + action = [ + [0 for col in range(len(grid[0]))] for row in range(len(grid)) + ] # the action grid x = init[0] y = init[1] @@ -45,14 +48,14 @@ def search(grid,init,goal,cost,heuristic): cell = [[f, g, x, y]] found = False # flag that is set when search is complete - resign = False # flag set if we can't find expand + resign = False # flag set if we can't find expand while not found and not resign: if len(cell) == 0: resign = True return "FAIL" else: - cell.sort()#to choose the least costliest action so as to move closer to the goal + cell.sort() # to choose the least costliest action so as to move closer to the goal cell.reverse() next = cell.pop() x = next[2] @@ -60,14 +63,13 @@ def search(grid,init,goal,cost,heuristic): g = next[1] f = next[0] - if x == goal[0] and y == goal[1]: found = True else: - for i in range(len(delta)):#to try out different valid actions + for i in range(len(delta)): # to try out different valid actions x2 = x + delta[i][0] y2 = y + delta[i][1] - if x2 >= 0 and x2 < len(grid) and y2 >=0 and y2 < len(grid[0]): + if x2 >= 0 and x2 < len(grid) and y2 >= 0 and y2 < len(grid[0]): if closed[x2][y2] == 0 and grid[x2][y2] == 0: g2 = g + cost f2 = g2 + heuristic[x2][y2] @@ -77,7 +79,7 @@ def search(grid,init,goal,cost,heuristic): invpath = [] x = goal[0] y = goal[1] - invpath.append([x, y])#we get the reverse path from here + invpath.append([x, y]) # we get the reverse path from here while x != init[0] or y != init[1]: x2 = x - delta[action[x][y]][0] y2 = y - delta[action[x][y]][1] @@ -87,14 +89,14 @@ def search(grid,init,goal,cost,heuristic): path = [] for i in range(len(invpath)): - path.append(invpath[len(invpath) - 1 - i]) + path.append(invpath[len(invpath) - 1 - i]) print("ACTION MAP") for i in range(len(action)): print(action[i]) return path -a = search(grid,init,goal,cost,heuristic) -for i in range(len(a)): - print(a[i]) +a = search(grid, init, goal, cost, heuristic) +for i in range(len(a)): + print(a[i]) diff --git a/graphs/articulation_points.py b/graphs/articulation_points.py index 1173c4ea373c..3ecc829946e8 100644 --- a/graphs/articulation_points.py +++ b/graphs/articulation_points.py @@ -33,12 +33,23 @@ def dfs(root, at, parent, outEdgeCount): if not visited[i]: outEdgeCount = 0 outEdgeCount = dfs(i, i, -1, outEdgeCount) - isArt[i] = (outEdgeCount > 1) + isArt[i] = outEdgeCount > 1 for x in range(len(isArt)): if isArt[x] == True: print(x) + # Adjacency list of graph -l = {0:[1,2], 1:[0,2], 2:[0,1,3,5], 3:[2,4], 4:[3], 5:[2,6,8], 6:[5,7], 7:[6,8], 8:[5,7]} +l = { + 0: [1, 2], + 1: [0, 2], + 2: [0, 1, 3, 5], + 3: [2, 4], + 4: [3], + 5: [2, 6, 8], + 6: [5, 7], + 7: [6, 8], + 8: [5, 7], +} computeAP(l) diff --git a/graphs/basic_graphs.py b/graphs/basic_graphs.py index 308abc0839fa..161bc0c09d3b 100644 --- a/graphs/basic_graphs.py +++ b/graphs/basic_graphs.py @@ -237,7 +237,7 @@ def edglist(): n, m = map(int, input().split(" ")) l = [] for i in range(m): - l.append(map(int, input().split(' '))) + l.append(map(int, input().split(" "))) return l, n diff --git a/graphs/bellman_ford.py b/graphs/bellman_ford.py index bebe8f354b26..b782a899fda9 100644 --- a/graphs/bellman_ford.py +++ b/graphs/bellman_ford.py @@ -1,35 +1,35 @@ def printDist(dist, V): - print("\nVertex Distance") - for i in range(V): - if dist[i] != float('inf') : - print(i,"\t",int(dist[i]),end = "\t") - else: - print(i,"\t","INF",end="\t") - print() + print("\nVertex Distance") + for i in range(V): + if dist[i] != float("inf"): + print(i, "\t", int(dist[i]), end="\t") + else: + print(i, "\t", "INF", end="\t") + print() -def BellmanFord(graph, V, E, src): - mdist=[float('inf') for i in range(V)] - mdist[src] = 0.0 - for i in range(V-1): - for j in range(V): - u = graph[j]["src"] - v = graph[j]["dst"] - w = graph[j]["weight"] +def BellmanFord(graph, V, E, src): + mdist = [float("inf") for i in range(V)] + mdist[src] = 0.0 - if mdist[u] != float('inf') and mdist[u] + w < mdist[v]: - mdist[v] = mdist[u] + w - for j in range(V): - u = graph[j]["src"] - v = graph[j]["dst"] - w = graph[j]["weight"] + for i in range(V - 1): + for j in range(V): + u = graph[j]["src"] + v = graph[j]["dst"] + w = graph[j]["weight"] - if mdist[u] != float('inf') and mdist[u] + w < mdist[v]: - print("Negative cycle found. Solution not possible.") - return + if mdist[u] != float("inf") and mdist[u] + w < mdist[v]: + mdist[v] = mdist[u] + w + for j in range(V): + u = graph[j]["src"] + v = graph[j]["dst"] + w = graph[j]["weight"] - printDist(mdist, V) + if mdist[u] != float("inf") and mdist[u] + w < mdist[v]: + print("Negative cycle found. Solution not possible.") + return + printDist(mdist, V) if __name__ == "__main__": @@ -39,14 +39,14 @@ def BellmanFord(graph, V, E, src): graph = [dict() for j in range(E)] for i in range(V): - graph[i][i] = 0.0 + graph[i][i] = 0.0 for i in range(E): - print("\nEdge ",i+1) - src = int(input("Enter source:").strip()) - dst = int(input("Enter destination:").strip()) - weight = float(input("Enter weight:").strip()) - graph[i] = {"src": src,"dst": dst, "weight": weight} + print("\nEdge ", i + 1) + src = int(input("Enter source:").strip()) + dst = int(input("Enter destination:").strip()) + weight = float(input("Enter weight:").strip()) + graph[i] = {"src": src, "dst": dst, "weight": weight} gsrc = int(input("\nEnter shortest path source:").strip()) BellmanFord(graph, V, E, gsrc) diff --git a/graphs/bfs.py b/graphs/bfs.py index ebbde0c82ce6..9d9b1ac037d9 100644 --- a/graphs/bfs.py +++ b/graphs/bfs.py @@ -16,12 +16,14 @@ """ -G = {'A': ['B', 'C'], - 'B': ['A', 'D', 'E'], - 'C': ['A', 'F'], - 'D': ['B'], - 'E': ['B', 'F'], - 'F': ['C', 'E']} +G = { + "A": ["B", "C"], + "B": ["A", "D", "E"], + "C": ["A", "F"], + "D": ["B"], + "E": ["B", "F"], + "F": ["C", "E"], +} def bfs(graph, start): @@ -40,5 +42,5 @@ def bfs(graph, start): return explored -if __name__ == '__main__': - print(bfs(G, 'A')) +if __name__ == "__main__": + print(bfs(G, "A")) diff --git a/graphs/bfs_shortest_path.py b/graphs/bfs_shortest_path.py index 5853351a53a3..ec82c13997e2 100644 --- a/graphs/bfs_shortest_path.py +++ b/graphs/bfs_shortest_path.py @@ -1,21 +1,24 @@ -graph = {'A': ['B', 'C', 'E'], - 'B': ['A','D', 'E'], - 'C': ['A', 'F', 'G'], - 'D': ['B'], - 'E': ['A', 'B','D'], - 'F': ['C'], - 'G': ['C']} +graph = { + "A": ["B", "C", "E"], + "B": ["A", "D", "E"], + "C": ["A", "F", "G"], + "D": ["B"], + "E": ["A", "B", "D"], + "F": ["C"], + "G": ["C"], +} + def bfs_shortest_path(graph, start, goal): # keep track of explored nodes explored = [] # keep track of all the paths to be checked queue = [[start]] - + # return path if start is goal if start == goal: return "That was easy! Start = goal" - + # keeps looping until all possible paths have been checked while queue: # pop the first path from the queue @@ -33,11 +36,12 @@ def bfs_shortest_path(graph, start, goal): # return path if neighbour is goal if neighbour == goal: return new_path - + # mark node as explored explored.append(node) - + # in case there's no path between the 2 nodes return "So sorry, but a connecting path doesn't exist :(" - -bfs_shortest_path(graph, 'G', 'D') # returns ['G', 'C', 'A', 'B', 'D'] + + +bfs_shortest_path(graph, "G", "D") # returns ['G', 'C', 'A', 'B', 'D'] diff --git a/graphs/breadth_first_search.py b/graphs/breadth_first_search.py index 205f49a6172b..8516e60a59c4 100644 --- a/graphs/breadth_first_search.py +++ b/graphs/breadth_first_search.py @@ -4,14 +4,14 @@ """ Author: OMKAR PATHAK """ -class Graph(): +class Graph: def __init__(self): self.vertex = {} # for printing the Graph vertexes def printGraph(self): for i in self.vertex.keys(): - print(i,' -> ', ' -> '.join([str(j) for j in self.vertex[i]])) + print(i, " -> ", " -> ".join([str(j) for j in self.vertex[i]])) # for adding the edge beween two vertexes def addEdge(self, fromVertex, toVertex): @@ -35,7 +35,7 @@ def BFS(self, startVertex): while queue: startVertex = queue.pop(0) - print(startVertex, end = ' ') + print(startVertex, end=" ") # mark all adjacent nodes as visited and print them for i in self.vertex[startVertex]: @@ -43,7 +43,8 @@ def BFS(self, startVertex): queue.append(i) visited[i] = True -if __name__ == '__main__': + +if __name__ == "__main__": g = Graph() g.addEdge(0, 1) g.addEdge(0, 2) @@ -53,7 +54,7 @@ def BFS(self, startVertex): g.addEdge(3, 3) g.printGraph() - print('BFS:') + print("BFS:") g.BFS(2) # OUTPUT: diff --git a/graphs/check_bipartite_graph_bfs.py b/graphs/check_bipartite_graph_bfs.py index 1b9c32c6ccc4..1ec3e3d1d45f 100644 --- a/graphs/check_bipartite_graph_bfs.py +++ b/graphs/check_bipartite_graph_bfs.py @@ -11,7 +11,7 @@ def checkBipartite(l): color = [-1] * len(l) def bfs(): - while(queue): + while queue: u = queue.pop(0) visited[u] = True @@ -38,6 +38,7 @@ def bfs(): return True + # Adjacency List of graph -l = {0:[1,3], 1:[0,2], 2:[1,3], 3:[0,2]} +l = {0: [1, 3], 1: [0, 2], 2: [1, 3], 3: [0, 2]} print(checkBipartite(l)) diff --git a/graphs/check_bipartite_graph_dfs.py b/graphs/check_bipartite_graph_dfs.py index eeb3a84b7a15..6fe54a6723c5 100644 --- a/graphs/check_bipartite_graph_dfs.py +++ b/graphs/check_bipartite_graph_dfs.py @@ -26,8 +26,8 @@ def dfs(v, c): return False return True - + # Adjacency list of graph -l = {0:[1,3], 1:[0,2], 2:[1,3], 3:[0,2], 4: []} +l = {0: [1, 3], 1: [0, 2], 2: [1, 3], 3: [0, 2], 4: []} print(check_bipartite_dfs(l)) diff --git a/graphs/depth_first_search.py b/graphs/depth_first_search.py index 2b03683c0047..5347c2fbcfa3 100644 --- a/graphs/depth_first_search.py +++ b/graphs/depth_first_search.py @@ -4,7 +4,7 @@ """ Author: OMKAR PATHAK """ -class Graph(): +class Graph: def __init__(self): self.vertex = {} @@ -12,7 +12,7 @@ def __init__(self): def printGraph(self): print(self.vertex) for i in self.vertex.keys(): - print(i,' -> ', ' -> '.join([str(j) for j in self.vertex[i]])) + print(i, " -> ", " -> ".join([str(j) for j in self.vertex[i]])) # for adding the edge beween two vertexes def addEdge(self, fromVertex, toVertex): @@ -36,14 +36,15 @@ def DFSRec(self, startVertex, visited): # mark start vertex as visited visited[startVertex] = True - print(startVertex, end = ' ') + print(startVertex, end=" ") # Recur for all the vertexes that are adjacent to this node for i in self.vertex.keys(): if visited[i] == False: self.DFSRec(i, visited) -if __name__ == '__main__': + +if __name__ == "__main__": g = Graph() g.addEdge(0, 1) g.addEdge(0, 2) @@ -53,7 +54,7 @@ def DFSRec(self, startVertex, visited): g.addEdge(3, 3) g.printGraph() - print('DFS:') + print("DFS:") g.DFS() # OUTPUT: @@ -62,4 +63,4 @@ def DFSRec(self, startVertex, visited): # 2  ->  0 -> 3 # 3  ->  3 # DFS: - # 0 1 2 3 + #  0 1 2 3 diff --git a/graphs/dfs.py b/graphs/dfs.py index 68bf60e3c298..f183eae73fef 100644 --- a/graphs/dfs.py +++ b/graphs/dfs.py @@ -17,8 +17,10 @@ def dfs(graph, start): it off the stack.""" explored, stack = set(), [start] while stack: - v = stack.pop() # one difference from BFS is to pop last element here instead of first one - + v = ( + stack.pop() + ) # one difference from BFS is to pop last element here instead of first one + if v in explored: continue @@ -30,11 +32,13 @@ def dfs(graph, start): return explored -G = {'A': ['B', 'C'], - 'B': ['A', 'D', 'E'], - 'C': ['A', 'F'], - 'D': ['B'], - 'E': ['B', 'F'], - 'F': ['C', 'E']} +G = { + "A": ["B", "C"], + "B": ["A", "D", "E"], + "C": ["A", "F"], + "D": ["B"], + "E": ["B", "F"], + "F": ["C", "E"], +} -print(dfs(G, 'A')) +print(dfs(G, "A")) diff --git a/graphs/dijkstra.py b/graphs/dijkstra.py index 5f09a45cf2c4..195f4e02d409 100644 --- a/graphs/dijkstra.py +++ b/graphs/dijkstra.py @@ -115,4 +115,5 @@ def dijkstra(graph, start, end): if __name__ == "__main__": import doctest + doctest.testmod() diff --git a/graphs/dijkstra_2.py b/graphs/dijkstra_2.py index f6118830c9c0..762884136e4a 100644 --- a/graphs/dijkstra_2.py +++ b/graphs/dijkstra_2.py @@ -1,55 +1,58 @@ def printDist(dist, V): - print("\nVertex Distance") - for i in range(V): - if dist[i] != float('inf') : - print(i,"\t",int(dist[i]),end = "\t") - else: - print(i,"\t","INF",end="\t") - print() - -def minDist(mdist, vset, V): - minVal = float('inf') - minInd = -1 - for i in range(V): - if (not vset[i]) and mdist[i] < minVal : - minInd = i - minVal = mdist[i] - return minInd + print("\nVertex Distance") + for i in range(V): + if dist[i] != float("inf"): + print(i, "\t", int(dist[i]), end="\t") + else: + print(i, "\t", "INF", end="\t") + print() -def Dijkstra(graph, V, src): - mdist=[float('inf') for i in range(V)] - vset = [False for i in range(V)] - mdist[src] = 0.0 - for i in range(V-1): - u = minDist(mdist, vset, V) - vset[u] = True +def minDist(mdist, vset, V): + minVal = float("inf") + minInd = -1 + for i in range(V): + if (not vset[i]) and mdist[i] < minVal: + minInd = i + minVal = mdist[i] + return minInd - for v in range(V): - if (not vset[v]) and graph[u][v]!=float('inf') and mdist[u] + graph[u][v] < mdist[v]: - mdist[v] = mdist[u] + graph[u][v] +def Dijkstra(graph, V, src): + mdist = [float("inf") for i in range(V)] + vset = [False for i in range(V)] + mdist[src] = 0.0 + for i in range(V - 1): + u = minDist(mdist, vset, V) + vset[u] = True - printDist(mdist, V) + for v in range(V): + if ( + (not vset[v]) + and graph[u][v] != float("inf") + and mdist[u] + graph[u][v] < mdist[v] + ): + mdist[v] = mdist[u] + graph[u][v] + printDist(mdist, V) if __name__ == "__main__": V = int(input("Enter number of vertices: ").strip()) E = int(input("Enter number of edges: ").strip()) - graph = [[float('inf') for i in range(V)] for j in range(V)] + graph = [[float("inf") for i in range(V)] for j in range(V)] for i in range(V): - graph[i][i] = 0.0 + graph[i][i] = 0.0 for i in range(E): - print("\nEdge ",i+1) - src = int(input("Enter source:").strip()) - dst = int(input("Enter destination:").strip()) - weight = float(input("Enter weight:").strip()) - graph[src][dst] = weight + print("\nEdge ", i + 1) + src = int(input("Enter source:").strip()) + dst = int(input("Enter destination:").strip()) + weight = float(input("Enter weight:").strip()) + graph[src][dst] = weight gsrc = int(input("\nEnter shortest path source:").strip()) Dijkstra(graph, V, gsrc) diff --git a/graphs/dijkstra_algorithm.py b/graphs/dijkstra_algorithm.py index c43ff37f5336..9304a83148f3 100644 --- a/graphs/dijkstra_algorithm.py +++ b/graphs/dijkstra_algorithm.py @@ -4,6 +4,7 @@ import math import sys + # For storing the vertex set to retreive node with the lowest distance @@ -12,7 +13,7 @@ class PriorityQueue: def __init__(self): self.cur_size = 0 self.array = [] - self.pos = {} # To store the pos of node in array + self.pos = {} # To store the pos of node in array def isEmpty(self): return self.cur_size == 0 @@ -78,8 +79,8 @@ def decrease_key(self, tup, new_d): class Graph: def __init__(self, num): - self.adjList = {} # To store graph: u -> (v,w) - self.num_nodes = num # Number of nodes in graph + self.adjList = {} # To store graph: u -> (v,w) + self.num_nodes = num # Number of nodes in graph # To store the distance from source vertex self.dist = [0] * self.num_nodes self.par = [-1] * self.num_nodes # To store the path @@ -102,8 +103,11 @@ def add_edge(self, u, v, w): def show_graph(self): # u -> v(w) for u in self.adjList: - print(u, '->', ' -> '.join(str("{}({})".format(v, w)) - for v, w in self.adjList[u])) + print( + u, + "->", + " -> ".join(str("{}({})".format(v, w)) for v, w in self.adjList[u]), + ) def dijkstra(self, src): # Flush old junk values in par[] @@ -137,7 +141,7 @@ def dijkstra(self, src): def show_distances(self, src): print("Distance from node: {}".format(src)) for u in range(self.num_nodes): - print('Node {} has distance: {}'.format(u, self.dist[u])) + print("Node {} has distance: {}".format(u, self.dist[u])) def show_path(self, src, dest): # To show the shortest path from src to dest @@ -157,16 +161,16 @@ def show_path(self, src, dest): path.append(src) path.reverse() - print('----Path to reach {} from {}----'.format(dest, src)) + print("----Path to reach {} from {}----".format(dest, src)) for u in path: - print('{}'.format(u), end=' ') + print("{}".format(u), end=" ") if u != dest: - print('-> ', end='') + print("-> ", end="") - print('\nTotal cost of path: ', cost) + print("\nTotal cost of path: ", cost) -if __name__ == '__main__': +if __name__ == "__main__": graph = Graph(9) graph.add_edge(0, 1, 4) graph.add_edge(0, 7, 8) diff --git a/graphs/directed_and_undirected_(weighted)_graph.py b/graphs/directed_and_undirected_(weighted)_graph.py index a31a4a96d6d0..883a8a00c6b1 100644 --- a/graphs/directed_and_undirected_(weighted)_graph.py +++ b/graphs/directed_and_undirected_(weighted)_graph.py @@ -5,468 +5,493 @@ # the dfault weight is 1 if not assigend but all the implementation is weighted + class DirectedGraph: - def __init__(self): - self.graph = {} - - # adding vertices and edges - # adding the weight is optional - # handels repetition - def add_pair(self, u, v, w = 1): - if self.graph.get(u): - if self.graph[u].count([w,v]) == 0: - self.graph[u].append([w, v]) - else: - self.graph[u] = [[w, v]] - if not self.graph.get(v): - self.graph[v] = [] - - def all_nodes(self): - return list(self.graph) - - # handels if the input does not exist - def remove_pair(self, u, v): - if self.graph.get(u): - for _ in self.graph[u]: - if _[1] == v: - self.graph[u].remove(_) - - # if no destination is meant the defaut value is -1 - def dfs(self, s = -2, d = -1): - if s == d: - return [] - stack = [] - visited = [] - if s == -2: - s = list(self.graph.keys())[0] - stack.append(s) - visited.append(s) - ss = s - - while True: - # check if there is any non isolated nodes - if len(self.graph[s]) != 0: - ss = s - for __ in self.graph[s]: - if visited.count(__[1]) < 1: - if __[1] == d: - visited.append(d) - return visited - else: - stack.append(__[1]) - visited.append(__[1]) - ss =__[1] - break - - # check if all the children are visited - if s == ss : - stack.pop() - if len(stack) != 0: - s = stack[len(stack) - 1] - else: - s = ss - - # check if se have reached the starting point - if len(stack) == 0: - return visited - - # c is the count of nodes you want and if you leave it or pass -1 to the funtion the count - # will be random from 10 to 10000 - def fill_graph_randomly(self, c = -1): - if c == -1: - c = (math.floor(rand.random() * 10000)) + 10 - for _ in range(c): - # every vertex has max 100 edges - e = math.floor(rand.random() * 102) + 1 - for __ in range(e): - n = math.floor(rand.random() * (c)) + 1 - if n == _: - continue - self.add_pair(_, n, 1) - - def bfs(self, s = -2): - d = deque() - visited = [] - if s == -2: - s = list(self.graph.keys())[0] - d.append(s) - visited.append(s) - while d: - s = d.popleft() - if len(self.graph[s]) != 0: - for __ in self.graph[s]: - if visited.count(__[1]) < 1: - d.append(__[1]) - visited.append(__[1]) - return visited - def in_degree(self, u): - count = 0 - for _ in self.graph: - for __ in self.graph[_]: - if __[1] == u: - count += 1 - return count - - def out_degree(self, u): - return len(self.graph[u]) - - def topological_sort(self, s = -2): - stack = [] - visited = [] - if s == -2: - s = list(self.graph.keys())[0] - stack.append(s) - visited.append(s) - ss = s - sorted_nodes = [] - - while True: - # check if there is any non isolated nodes - if len(self.graph[s]) != 0: - ss = s - for __ in self.graph[s]: - if visited.count(__[1]) < 1: - stack.append(__[1]) - visited.append(__[1]) - ss =__[1] - break - - # check if all the children are visited - if s == ss : - sorted_nodes.append(stack.pop()) - if len(stack) != 0: - s = stack[len(stack) - 1] - else: - s = ss - - # check if se have reached the starting point - if len(stack) == 0: - return sorted_nodes - - def cycle_nodes(self): - stack = [] - visited = [] - s = list(self.graph.keys())[0] - stack.append(s) - visited.append(s) - parent = -2 - indirect_parents = [] - ss = s - on_the_way_back = False - anticipating_nodes = set() - - while True: - # check if there is any non isolated nodes - if len(self.graph[s]) != 0: - ss = s - for __ in self.graph[s]: - if visited.count(__[1]) > 0 and __[1] != parent and indirect_parents.count(__[1]) > 0 and not on_the_way_back: - l = len(stack) - 1 - while True and l >= 0: - if stack[l] == __[1]: - anticipating_nodes.add(__[1]) - break - else: - anticipating_nodes.add(stack[l]) - l -= 1 - if visited.count(__[1]) < 1: - stack.append(__[1]) - visited.append(__[1]) - ss =__[1] - break - - # check if all the children are visited - if s == ss : - stack.pop() - on_the_way_back = True - if len(stack) != 0: - s = stack[len(stack) - 1] - else: - on_the_way_back = False - indirect_parents.append(parent) - parent = s - s = ss - - # check if se have reached the starting point - if len(stack) == 0: - return list(anticipating_nodes) - - def has_cycle(self): - stack = [] - visited = [] - s = list(self.graph.keys())[0] - stack.append(s) - visited.append(s) - parent = -2 - indirect_parents = [] - ss = s - on_the_way_back = False - anticipating_nodes = set() - - while True: - # check if there is any non isolated nodes - if len(self.graph[s]) != 0: - ss = s - for __ in self.graph[s]: - if visited.count(__[1]) > 0 and __[1] != parent and indirect_parents.count(__[1]) > 0 and not on_the_way_back: - l = len(stack) - 1 - while True and l >= 0: - if stack[l] == __[1]: - anticipating_nodes.add(__[1]) - break - else: - return True - anticipating_nodes.add(stack[l]) - l -= 1 - if visited.count(__[1]) < 1: - stack.append(__[1]) - visited.append(__[1]) - ss =__[1] - break - - # check if all the children are visited - if s == ss : - stack.pop() - on_the_way_back = True - if len(stack) != 0: - s = stack[len(stack) - 1] - else: - on_the_way_back = False - indirect_parents.append(parent) - parent = s - s = ss - - # check if se have reached the starting point - if len(stack) == 0: - return False - - def dfs_time(self, s = -2, e = -1): - begin = time.time() - self.dfs(s,e) - end = time.time() - return end - begin - - def bfs_time(self, s = -2): - begin = time.time() - self.bfs(s) - end = time.time() - return end - begin + def __init__(self): + self.graph = {} + + # adding vertices and edges + # adding the weight is optional + # handels repetition + def add_pair(self, u, v, w=1): + if self.graph.get(u): + if self.graph[u].count([w, v]) == 0: + self.graph[u].append([w, v]) + else: + self.graph[u] = [[w, v]] + if not self.graph.get(v): + self.graph[v] = [] + + def all_nodes(self): + return list(self.graph) + + # handels if the input does not exist + def remove_pair(self, u, v): + if self.graph.get(u): + for _ in self.graph[u]: + if _[1] == v: + self.graph[u].remove(_) + + # if no destination is meant the defaut value is -1 + def dfs(self, s=-2, d=-1): + if s == d: + return [] + stack = [] + visited = [] + if s == -2: + s = list(self.graph.keys())[0] + stack.append(s) + visited.append(s) + ss = s + + while True: + # check if there is any non isolated nodes + if len(self.graph[s]) != 0: + ss = s + for __ in self.graph[s]: + if visited.count(__[1]) < 1: + if __[1] == d: + visited.append(d) + return visited + else: + stack.append(__[1]) + visited.append(__[1]) + ss = __[1] + break + + # check if all the children are visited + if s == ss: + stack.pop() + if len(stack) != 0: + s = stack[len(stack) - 1] + else: + s = ss + + # check if se have reached the starting point + if len(stack) == 0: + return visited + + # c is the count of nodes you want and if you leave it or pass -1 to the funtion the count + # will be random from 10 to 10000 + def fill_graph_randomly(self, c=-1): + if c == -1: + c = (math.floor(rand.random() * 10000)) + 10 + for _ in range(c): + # every vertex has max 100 edges + e = math.floor(rand.random() * 102) + 1 + for __ in range(e): + n = math.floor(rand.random() * (c)) + 1 + if n == _: + continue + self.add_pair(_, n, 1) + + def bfs(self, s=-2): + d = deque() + visited = [] + if s == -2: + s = list(self.graph.keys())[0] + d.append(s) + visited.append(s) + while d: + s = d.popleft() + if len(self.graph[s]) != 0: + for __ in self.graph[s]: + if visited.count(__[1]) < 1: + d.append(__[1]) + visited.append(__[1]) + return visited + + def in_degree(self, u): + count = 0 + for _ in self.graph: + for __ in self.graph[_]: + if __[1] == u: + count += 1 + return count + + def out_degree(self, u): + return len(self.graph[u]) + + def topological_sort(self, s=-2): + stack = [] + visited = [] + if s == -2: + s = list(self.graph.keys())[0] + stack.append(s) + visited.append(s) + ss = s + sorted_nodes = [] + + while True: + # check if there is any non isolated nodes + if len(self.graph[s]) != 0: + ss = s + for __ in self.graph[s]: + if visited.count(__[1]) < 1: + stack.append(__[1]) + visited.append(__[1]) + ss = __[1] + break + + # check if all the children are visited + if s == ss: + sorted_nodes.append(stack.pop()) + if len(stack) != 0: + s = stack[len(stack) - 1] + else: + s = ss + + # check if se have reached the starting point + if len(stack) == 0: + return sorted_nodes + + def cycle_nodes(self): + stack = [] + visited = [] + s = list(self.graph.keys())[0] + stack.append(s) + visited.append(s) + parent = -2 + indirect_parents = [] + ss = s + on_the_way_back = False + anticipating_nodes = set() + + while True: + # check if there is any non isolated nodes + if len(self.graph[s]) != 0: + ss = s + for __ in self.graph[s]: + if ( + visited.count(__[1]) > 0 + and __[1] != parent + and indirect_parents.count(__[1]) > 0 + and not on_the_way_back + ): + l = len(stack) - 1 + while True and l >= 0: + if stack[l] == __[1]: + anticipating_nodes.add(__[1]) + break + else: + anticipating_nodes.add(stack[l]) + l -= 1 + if visited.count(__[1]) < 1: + stack.append(__[1]) + visited.append(__[1]) + ss = __[1] + break + + # check if all the children are visited + if s == ss: + stack.pop() + on_the_way_back = True + if len(stack) != 0: + s = stack[len(stack) - 1] + else: + on_the_way_back = False + indirect_parents.append(parent) + parent = s + s = ss + + # check if se have reached the starting point + if len(stack) == 0: + return list(anticipating_nodes) + + def has_cycle(self): + stack = [] + visited = [] + s = list(self.graph.keys())[0] + stack.append(s) + visited.append(s) + parent = -2 + indirect_parents = [] + ss = s + on_the_way_back = False + anticipating_nodes = set() + + while True: + # check if there is any non isolated nodes + if len(self.graph[s]) != 0: + ss = s + for __ in self.graph[s]: + if ( + visited.count(__[1]) > 0 + and __[1] != parent + and indirect_parents.count(__[1]) > 0 + and not on_the_way_back + ): + l = len(stack) - 1 + while True and l >= 0: + if stack[l] == __[1]: + anticipating_nodes.add(__[1]) + break + else: + return True + anticipating_nodes.add(stack[l]) + l -= 1 + if visited.count(__[1]) < 1: + stack.append(__[1]) + visited.append(__[1]) + ss = __[1] + break + + # check if all the children are visited + if s == ss: + stack.pop() + on_the_way_back = True + if len(stack) != 0: + s = stack[len(stack) - 1] + else: + on_the_way_back = False + indirect_parents.append(parent) + parent = s + s = ss + + # check if se have reached the starting point + if len(stack) == 0: + return False + + def dfs_time(self, s=-2, e=-1): + begin = time.time() + self.dfs(s, e) + end = time.time() + return end - begin + + def bfs_time(self, s=-2): + begin = time.time() + self.bfs(s) + end = time.time() + return end - begin + class Graph: - def __init__(self): - self.graph = {} - - # adding vertices and edges - # adding the weight is optional - # handels repetition - def add_pair(self, u, v, w = 1): - # check if the u exists - if self.graph.get(u): - # if there already is a edge - if self.graph[u].count([w,v]) == 0: - self.graph[u].append([w, v]) - else: - # if u does not exist - self.graph[u] = [[w, v]] - # add the other way - if self.graph.get(v): - # if there already is a edge - if self.graph[v].count([w,u]) == 0: - self.graph[v].append([w, u]) - else: - # if u does not exist - self.graph[v] = [[w, u]] - - # handels if the input does not exist - def remove_pair(self, u, v): - if self.graph.get(u): - for _ in self.graph[u]: - if _[1] == v: - self.graph[u].remove(_) - # the other way round - if self.graph.get(v): - for _ in self.graph[v]: - if _[1] == u: - self.graph[v].remove(_) - - # if no destination is meant the defaut value is -1 - def dfs(self, s = -2, d = -1): - if s == d: - return [] - stack = [] - visited = [] - if s == -2: - s = list(self.graph.keys())[0] - stack.append(s) - visited.append(s) - ss = s - - while True: - # check if there is any non isolated nodes - if len(self.graph[s]) != 0: - ss = s - for __ in self.graph[s]: - if visited.count(__[1]) < 1: - if __[1] == d: - visited.append(d) - return visited - else: - stack.append(__[1]) - visited.append(__[1]) - ss =__[1] - break - - # check if all the children are visited - if s == ss : - stack.pop() - if len(stack) != 0: - s = stack[len(stack) - 1] - else: - s = ss - - # check if se have reached the starting point - if len(stack) == 0: - return visited - - # c is the count of nodes you want and if you leave it or pass -1 to the funtion the count - # will be random from 10 to 10000 - def fill_graph_randomly(self, c = -1): - if c == -1: - c = (math.floor(rand.random() * 10000)) + 10 - for _ in range(c): - # every vertex has max 100 edges - e = math.floor(rand.random() * 102) + 1 - for __ in range(e): - n = math.floor(rand.random() * (c)) + 1 - if n == _: - continue - self.add_pair(_, n, 1) - - def bfs(self, s = -2): - d = deque() - visited = [] - if s == -2: - s = list(self.graph.keys())[0] - d.append(s) - visited.append(s) - while d: - s = d.popleft() - if len(self.graph[s]) != 0: - for __ in self.graph[s]: - if visited.count(__[1]) < 1: - d.append(__[1]) - visited.append(__[1]) - return visited - def degree(self, u): - return len(self.graph[u]) - - def cycle_nodes(self): - stack = [] - visited = [] - s = list(self.graph.keys())[0] - stack.append(s) - visited.append(s) - parent = -2 - indirect_parents = [] - ss = s - on_the_way_back = False - anticipating_nodes = set() - - while True: - # check if there is any non isolated nodes - if len(self.graph[s]) != 0: - ss = s - for __ in self.graph[s]: - if visited.count(__[1]) > 0 and __[1] != parent and indirect_parents.count(__[1]) > 0 and not on_the_way_back: - l = len(stack) - 1 - while True and l >= 0: - if stack[l] == __[1]: - anticipating_nodes.add(__[1]) - break - else: - anticipating_nodes.add(stack[l]) - l -= 1 - if visited.count(__[1]) < 1: - stack.append(__[1]) - visited.append(__[1]) - ss =__[1] - break - - # check if all the children are visited - if s == ss : - stack.pop() - on_the_way_back = True - if len(stack) != 0: - s = stack[len(stack) - 1] - else: - on_the_way_back = False - indirect_parents.append(parent) - parent = s - s = ss - - # check if se have reached the starting point - if len(stack) == 0: - return list(anticipating_nodes) - - def has_cycle(self): - stack = [] - visited = [] - s = list(self.graph.keys())[0] - stack.append(s) - visited.append(s) - parent = -2 - indirect_parents = [] - ss = s - on_the_way_back = False - anticipating_nodes = set() - - while True: - # check if there is any non isolated nodes - if len(self.graph[s]) != 0: - ss = s - for __ in self.graph[s]: - if visited.count(__[1]) > 0 and __[1] != parent and indirect_parents.count(__[1]) > 0 and not on_the_way_back: - l = len(stack) - 1 - while True and l >= 0: - if stack[l] == __[1]: - anticipating_nodes.add(__[1]) - break - else: - return True - anticipating_nodes.add(stack[l]) - l -= 1 - if visited.count(__[1]) < 1: - stack.append(__[1]) - visited.append(__[1]) - ss =__[1] - break - - # check if all the children are visited - if s == ss : - stack.pop() - on_the_way_back = True - if len(stack) != 0: - s = stack[len(stack) - 1] - else: - on_the_way_back = False - indirect_parents.append(parent) - parent = s - s = ss - - # check if se have reached the starting point - if len(stack) == 0: - return False - def all_nodes(self): - return list(self.graph) - - def dfs_time(self, s = -2, e = -1): - begin = time.time() - self.dfs(s,e) - end = time.time() - return end - begin - - def bfs_time(self, s = -2): - begin = time.time() - self.bfs(s) - end = time.time() - return end - begin + def __init__(self): + self.graph = {} + + # adding vertices and edges + # adding the weight is optional + # handels repetition + def add_pair(self, u, v, w=1): + # check if the u exists + if self.graph.get(u): + # if there already is a edge + if self.graph[u].count([w, v]) == 0: + self.graph[u].append([w, v]) + else: + # if u does not exist + self.graph[u] = [[w, v]] + # add the other way + if self.graph.get(v): + # if there already is a edge + if self.graph[v].count([w, u]) == 0: + self.graph[v].append([w, u]) + else: + # if u does not exist + self.graph[v] = [[w, u]] + + # handels if the input does not exist + def remove_pair(self, u, v): + if self.graph.get(u): + for _ in self.graph[u]: + if _[1] == v: + self.graph[u].remove(_) + # the other way round + if self.graph.get(v): + for _ in self.graph[v]: + if _[1] == u: + self.graph[v].remove(_) + + # if no destination is meant the defaut value is -1 + def dfs(self, s=-2, d=-1): + if s == d: + return [] + stack = [] + visited = [] + if s == -2: + s = list(self.graph.keys())[0] + stack.append(s) + visited.append(s) + ss = s + + while True: + # check if there is any non isolated nodes + if len(self.graph[s]) != 0: + ss = s + for __ in self.graph[s]: + if visited.count(__[1]) < 1: + if __[1] == d: + visited.append(d) + return visited + else: + stack.append(__[1]) + visited.append(__[1]) + ss = __[1] + break + + # check if all the children are visited + if s == ss: + stack.pop() + if len(stack) != 0: + s = stack[len(stack) - 1] + else: + s = ss + + # check if se have reached the starting point + if len(stack) == 0: + return visited + + # c is the count of nodes you want and if you leave it or pass -1 to the funtion the count + # will be random from 10 to 10000 + def fill_graph_randomly(self, c=-1): + if c == -1: + c = (math.floor(rand.random() * 10000)) + 10 + for _ in range(c): + # every vertex has max 100 edges + e = math.floor(rand.random() * 102) + 1 + for __ in range(e): + n = math.floor(rand.random() * (c)) + 1 + if n == _: + continue + self.add_pair(_, n, 1) + + def bfs(self, s=-2): + d = deque() + visited = [] + if s == -2: + s = list(self.graph.keys())[0] + d.append(s) + visited.append(s) + while d: + s = d.popleft() + if len(self.graph[s]) != 0: + for __ in self.graph[s]: + if visited.count(__[1]) < 1: + d.append(__[1]) + visited.append(__[1]) + return visited + + def degree(self, u): + return len(self.graph[u]) + + def cycle_nodes(self): + stack = [] + visited = [] + s = list(self.graph.keys())[0] + stack.append(s) + visited.append(s) + parent = -2 + indirect_parents = [] + ss = s + on_the_way_back = False + anticipating_nodes = set() + + while True: + # check if there is any non isolated nodes + if len(self.graph[s]) != 0: + ss = s + for __ in self.graph[s]: + if ( + visited.count(__[1]) > 0 + and __[1] != parent + and indirect_parents.count(__[1]) > 0 + and not on_the_way_back + ): + l = len(stack) - 1 + while True and l >= 0: + if stack[l] == __[1]: + anticipating_nodes.add(__[1]) + break + else: + anticipating_nodes.add(stack[l]) + l -= 1 + if visited.count(__[1]) < 1: + stack.append(__[1]) + visited.append(__[1]) + ss = __[1] + break + + # check if all the children are visited + if s == ss: + stack.pop() + on_the_way_back = True + if len(stack) != 0: + s = stack[len(stack) - 1] + else: + on_the_way_back = False + indirect_parents.append(parent) + parent = s + s = ss + + # check if se have reached the starting point + if len(stack) == 0: + return list(anticipating_nodes) + + def has_cycle(self): + stack = [] + visited = [] + s = list(self.graph.keys())[0] + stack.append(s) + visited.append(s) + parent = -2 + indirect_parents = [] + ss = s + on_the_way_back = False + anticipating_nodes = set() + + while True: + # check if there is any non isolated nodes + if len(self.graph[s]) != 0: + ss = s + for __ in self.graph[s]: + if ( + visited.count(__[1]) > 0 + and __[1] != parent + and indirect_parents.count(__[1]) > 0 + and not on_the_way_back + ): + l = len(stack) - 1 + while True and l >= 0: + if stack[l] == __[1]: + anticipating_nodes.add(__[1]) + break + else: + return True + anticipating_nodes.add(stack[l]) + l -= 1 + if visited.count(__[1]) < 1: + stack.append(__[1]) + visited.append(__[1]) + ss = __[1] + break + + # check if all the children are visited + if s == ss: + stack.pop() + on_the_way_back = True + if len(stack) != 0: + s = stack[len(stack) - 1] + else: + on_the_way_back = False + indirect_parents.append(parent) + parent = s + s = ss + + # check if se have reached the starting point + if len(stack) == 0: + return False + + def all_nodes(self): + return list(self.graph) + + def dfs_time(self, s=-2, e=-1): + begin = time.time() + self.dfs(s, e) + end = time.time() + return end - begin + + def bfs_time(self, s=-2): + begin = time.time() + self.bfs(s) + end = time.time() + return end - begin diff --git a/graphs/edmonds_karp_multiple_source_and_sink.py b/graphs/edmonds_karp_multiple_source_and_sink.py index d231ac2c4cc3..6334f05c50bd 100644 --- a/graphs/edmonds_karp_multiple_source_and_sink.py +++ b/graphs/edmonds_karp_multiple_source_and_sink.py @@ -28,14 +28,13 @@ def _normalizeGraph(self, sources, sinks): for i in sources: maxInputFlow += sum(self.graph[i]) - size = len(self.graph) + 1 for room in self.graph: room.insert(0, 0) self.graph.insert(0, [0] * size) for i in sources: self.graph[0][i + 1] = maxInputFlow - self.sourceIndex = 0 + self.sourceIndex = 0 size = len(self.graph) + 1 for room in self.graph: @@ -45,7 +44,6 @@ def _normalizeGraph(self, sources, sinks): self.graph[i + 1][size - 1] = maxInputFlow self.sinkIndex = size - 1 - def findMaximumFlow(self): if self.maximumFlowAlgorithm is None: raise Exception("You need to set maximum flow algorithm before.") @@ -80,7 +78,6 @@ def _algorithm(self): pass - class MaximumFlowAlgorithmExecutor(FlowNetworkAlgorithmExecutor): def __init__(self, flowNetwork): super(MaximumFlowAlgorithmExecutor, self).__init__(flowNetwork) @@ -93,6 +90,7 @@ def getMaximumFlow(self): return self.maximumFlow + class PushRelabelExecutor(MaximumFlowAlgorithmExecutor): def __init__(self, flowNetwork): super(PushRelabelExecutor, self).__init__(flowNetwork) @@ -112,8 +110,11 @@ def _algorithm(self): self.excesses[nextVertexIndex] += bandwidth # Relabel-to-front selection rule - verticesList = [i for i in range(self.verticesCount) - if i != self.sourceIndex and i != self.sinkIndex] + verticesList = [ + i + for i in range(self.verticesCount) + if i != self.sourceIndex and i != self.sinkIndex + ] # move through list i = 0 @@ -135,15 +136,21 @@ def processVertex(self, vertexIndex): while self.excesses[vertexIndex] > 0: for neighbourIndex in range(self.verticesCount): # if it's neighbour and current vertex is higher - if self.graph[vertexIndex][neighbourIndex] - self.preflow[vertexIndex][neighbourIndex] > 0\ - and self.heights[vertexIndex] > self.heights[neighbourIndex]: + if ( + self.graph[vertexIndex][neighbourIndex] + - self.preflow[vertexIndex][neighbourIndex] + > 0 + and self.heights[vertexIndex] > self.heights[neighbourIndex] + ): self.push(vertexIndex, neighbourIndex) self.relabel(vertexIndex) def push(self, fromIndex, toIndex): - preflowDelta = min(self.excesses[fromIndex], - self.graph[fromIndex][toIndex] - self.preflow[fromIndex][toIndex]) + preflowDelta = min( + self.excesses[fromIndex], + self.graph[fromIndex][toIndex] - self.preflow[fromIndex][toIndex], + ) self.preflow[fromIndex][toIndex] += preflowDelta self.preflow[toIndex][fromIndex] -= preflowDelta self.excesses[fromIndex] -= preflowDelta @@ -152,14 +159,18 @@ def push(self, fromIndex, toIndex): def relabel(self, vertexIndex): minHeight = None for toIndex in range(self.verticesCount): - if self.graph[vertexIndex][toIndex] - self.preflow[vertexIndex][toIndex] > 0: + if ( + self.graph[vertexIndex][toIndex] - self.preflow[vertexIndex][toIndex] + > 0 + ): if minHeight is None or self.heights[toIndex] < minHeight: minHeight = self.heights[toIndex] if minHeight is not None: self.heights[vertexIndex] = minHeight + 1 -if __name__ == '__main__': + +if __name__ == "__main__": entrances = [0] exits = [3] # graph = [ diff --git a/graphs/eulerian_path_and_circuit_for_undirected_graph.py b/graphs/eulerian_path_and_circuit_for_undirected_graph.py index c6c6a1a25f03..a2e5cf4da26a 100644 --- a/graphs/eulerian_path_and_circuit_for_undirected_graph.py +++ b/graphs/eulerian_path_and_circuit_for_undirected_graph.py @@ -50,32 +50,10 @@ def check_euler(graph, max_node): def main(): - G1 = { - 1: [2, 3, 4], - 2: [1, 3], - 3: [1, 2], - 4: [1, 5], - 5: [4] - } - G2 = { - 1: [2, 3, 4, 5], - 2: [1, 3], - 3: [1, 2], - 4: [1, 5], - 5: [1, 4] - } - G3 = { - 1: [2, 3, 4], - 2: [1, 3, 4], - 3: [1, 2], - 4: [1, 2, 5], - 5: [4] - } - G4 = { - 1: [2, 3], - 2: [1, 3], - 3: [1, 2], - } + G1 = {1: [2, 3, 4], 2: [1, 3], 3: [1, 2], 4: [1, 5], 5: [4]} + G2 = {1: [2, 3, 4, 5], 2: [1, 3], 3: [1, 2], 4: [1, 5], 5: [1, 4]} + G3 = {1: [2, 3, 4], 2: [1, 3, 4], 3: [1, 2], 4: [1, 2, 5], 5: [4]} + G4 = {1: [2, 3], 2: [1, 3], 3: [1, 2]} G5 = { 1: [], 2: [] diff --git a/graphs/even_tree.py b/graphs/even_tree.py index 45d55eecff8a..c9aef6e7861f 100644 --- a/graphs/even_tree.py +++ b/graphs/even_tree.py @@ -45,23 +45,13 @@ def even_tree(): dfs(1) -if __name__ == '__main__': +if __name__ == "__main__": n, m = 10, 9 tree = defaultdict(list) visited = {} cuts = [] count = 0 - edges = [ - (2, 1), - (3, 1), - (4, 3), - (5, 2), - (6, 1), - (7, 2), - (8, 6), - (9, 8), - (10, 8), - ] + edges = [(2, 1), (3, 1), (4, 3), (5, 2), (6, 1), (7, 2), (8, 6), (9, 8), (10, 8)] for u, v in edges: tree[u].append(v) tree[v].append(u) diff --git a/graphs/finding_bridges.py b/graphs/finding_bridges.py index 56533dd48bde..e18a3bafa9c0 100644 --- a/graphs/finding_bridges.py +++ b/graphs/finding_bridges.py @@ -1,7 +1,7 @@ # Finding Bridges in Undirected Graph def computeBridges(l): id = 0 - n = len(l) # No of vertices in graph + n = len(l) # No of vertices in graph low = [0] * n visited = [False] * n @@ -23,9 +23,20 @@ def dfs(at, parent, bridges, id): bridges = [] for i in range(n): - if (not visited[i]): + if not visited[i]: dfs(i, -1, bridges, id) print(bridges) - -l = {0:[1,2], 1:[0,2], 2:[0,1,3,5], 3:[2,4], 4:[3], 5:[2,6,8], 6:[5,7], 7:[6,8], 8:[5,7]} + + +l = { + 0: [1, 2], + 1: [0, 2], + 2: [0, 1, 3, 5], + 3: [2, 4], + 4: [3], + 5: [2, 6, 8], + 6: [5, 7], + 7: [6, 8], + 8: [5, 7], +} computeBridges(l) diff --git a/graphs/graph_list.py b/graphs/graph_list.py index 2ca363b1d746..4f0cbf15c033 100644 --- a/graphs/graph_list.py +++ b/graphs/graph_list.py @@ -5,6 +5,7 @@ # We can use Python's dictionary for constructing the graph. + class AdjacencyList(object): def __init__(self): self.List = {} @@ -17,10 +18,11 @@ def addEdge(self, fromVertex, toVertex): self.List[fromVertex] = [toVertex] def printList(self): - for i in self.List: - print((i,'->',' -> '.join([str(j) for j in self.List[i]]))) + for i in self.List: + print((i, "->", " -> ".join([str(j) for j in self.List[i]]))) + -if __name__ == '__main__': +if __name__ == "__main__": al = AdjacencyList() al.addEdge(0, 1) al.addEdge(0, 4) diff --git a/graphs/graph_matrix.py b/graphs/graph_matrix.py index 1998fec8d6fe..987168426ba5 100644 --- a/graphs/graph_matrix.py +++ b/graphs/graph_matrix.py @@ -1,8 +1,7 @@ class Graph: - def __init__(self, vertex): self.vertex = vertex - self.graph = [[0] * vertex for i in range(vertex) ] + self.graph = [[0] * vertex for i in range(vertex)] def add_edge(self, u, v): self.graph[u - 1][v - 1] = 1 @@ -12,18 +11,15 @@ def show(self): for i in self.graph: for j in i: - print(j, end=' ') - print(' ') - - + print(j, end=" ") + print(" ") g = Graph(100) -g.add_edge(1,4) -g.add_edge(4,2) -g.add_edge(4,5) -g.add_edge(2,5) -g.add_edge(5,3) +g.add_edge(1, 4) +g.add_edge(4, 2) +g.add_edge(4, 5) +g.add_edge(2, 5) +g.add_edge(5, 3) g.show() - diff --git a/graphs/graphs_floyd_warshall.py b/graphs/graphs_floyd_warshall.py index 5f159683733f..5727a2f21d89 100644 --- a/graphs/graphs_floyd_warshall.py +++ b/graphs/graphs_floyd_warshall.py @@ -6,19 +6,18 @@ def _print_dist(dist, v): - print("\nThe shortest path matrix using Floyd Warshall algorithm\n") - for i in range(v): - for j in range(v): - if dist[i][j] != float('inf') : - print(int(dist[i][j]),end = "\t") - else: - print("INF",end="\t") - print() - + print("\nThe shortest path matrix using Floyd Warshall algorithm\n") + for i in range(v): + for j in range(v): + if dist[i][j] != float("inf"): + print(int(dist[i][j]), end="\t") + else: + print("INF", end="\t") + print() def floyd_warshall(graph, v): - """ + """ :param graph: 2D array calculated from weight[edge[i, j]] :type graph: List[List[float]] :param v: number of vertices @@ -33,68 +32,70 @@ def floyd_warshall(graph, v): 5. Whenever distance[i][j] is given a new minimum value, next vertex[i][j] is updated to the next vertex[i][k]. """ - dist=[[float('inf') for _ in range(v)] for _ in range(v)] - - for i in range(v): - for j in range(v): - dist[i][j] = graph[i][j] - - # check vertex k against all other vertices (i, j) - for k in range(v): - # looping through rows of graph array - for i in range(v): - # looping through columns of graph array - for j in range(v): - if dist[i][k]!=float('inf') and dist[k][j]!=float('inf') and dist[i][k]+dist[k][j] < dist[i][j]: - dist[i][j] = dist[i][k] + dist[k][j] - - _print_dist(dist, v) - return dist, v - - - -if __name__== '__main__': - v = int(input("Enter number of vertices: ")) - e = int(input("Enter number of edges: ")) - - graph = [[float('inf') for i in range(v)] for j in range(v)] - - for i in range(v): - graph[i][i] = 0.0 - - # src and dst are indices that must be within the array size graph[e][v] - # failure to follow this will result in an error - for i in range(e): - print("\nEdge ",i+1) - src = int(input("Enter source:")) - dst = int(input("Enter destination:")) - weight = float(input("Enter weight:")) - graph[src][dst] = weight - - floyd_warshall(graph, v) - - - # Example Input - # Enter number of vertices: 3 - # Enter number of edges: 2 - - # # generated graph from vertex and edge inputs - # [[inf, inf, inf], [inf, inf, inf], [inf, inf, inf]] - # [[0.0, inf, inf], [inf, 0.0, inf], [inf, inf, 0.0]] - - # specify source, destination and weight for edge #1 - # Edge 1 - # Enter source:1 - # Enter destination:2 - # Enter weight:2 - - # specify source, destination and weight for edge #2 - # Edge 2 - # Enter source:2 - # Enter destination:1 - # Enter weight:1 - - # # Expected Output from the vertice, edge and src, dst, weight inputs!! - # 0 INF INF - # INF 0 2 - # INF 1 0 + dist = [[float("inf") for _ in range(v)] for _ in range(v)] + + for i in range(v): + for j in range(v): + dist[i][j] = graph[i][j] + + # check vertex k against all other vertices (i, j) + for k in range(v): + # looping through rows of graph array + for i in range(v): + # looping through columns of graph array + for j in range(v): + if ( + dist[i][k] != float("inf") + and dist[k][j] != float("inf") + and dist[i][k] + dist[k][j] < dist[i][j] + ): + dist[i][j] = dist[i][k] + dist[k][j] + + _print_dist(dist, v) + return dist, v + + +if __name__ == "__main__": + v = int(input("Enter number of vertices: ")) + e = int(input("Enter number of edges: ")) + + graph = [[float("inf") for i in range(v)] for j in range(v)] + + for i in range(v): + graph[i][i] = 0.0 + + # src and dst are indices that must be within the array size graph[e][v] + # failure to follow this will result in an error + for i in range(e): + print("\nEdge ", i + 1) + src = int(input("Enter source:")) + dst = int(input("Enter destination:")) + weight = float(input("Enter weight:")) + graph[src][dst] = weight + + floyd_warshall(graph, v) + + # Example Input + # Enter number of vertices: 3 + # Enter number of edges: 2 + + # # generated graph from vertex and edge inputs + # [[inf, inf, inf], [inf, inf, inf], [inf, inf, inf]] + # [[0.0, inf, inf], [inf, 0.0, inf], [inf, inf, 0.0]] + + # specify source, destination and weight for edge #1 + # Edge 1 + # Enter source:1 + # Enter destination:2 + # Enter weight:2 + + # specify source, destination and weight for edge #2 + # Edge 2 + # Enter source:2 + # Enter destination:1 + # Enter weight:1 + + # # Expected Output from the vertice, edge and src, dst, weight inputs!! + # 0 INF INF + # INF 0 2 + # INF 1 0 diff --git a/graphs/kahns_algorithm_long.py b/graphs/kahns_algorithm_long.py index 453b5706f6da..0651040365d0 100644 --- a/graphs/kahns_algorithm_long.py +++ b/graphs/kahns_algorithm_long.py @@ -12,19 +12,20 @@ def longestDistance(l): if indegree[i] == 0: queue.append(i) - while(queue): + while queue: vertex = queue.pop(0) for x in l[vertex]: indegree[x] -= 1 if longDist[vertex] + 1 > longDist[x]: - longDist[x] = longDist[vertex] + 1 + longDist[x] = longDist[vertex] + 1 if indegree[x] == 0: queue.append(x) print(max(longDist)) + # Adjacency list of Graph -l = {0:[2,3,4], 1:[2,7], 2:[5], 3:[5,7], 4:[7], 5:[6], 6:[7], 7:[]} +l = {0: [2, 3, 4], 1: [2, 7], 2: [5], 3: [5, 7], 4: [7], 5: [6], 6: [7], 7: []} longestDistance(l) diff --git a/graphs/kahns_algorithm_topo.py b/graphs/kahns_algorithm_topo.py index 8c182c4e902c..d50bc9a43d19 100644 --- a/graphs/kahns_algorithm_topo.py +++ b/graphs/kahns_algorithm_topo.py @@ -13,7 +13,7 @@ def topologicalSort(l): if indegree[i] == 0: queue.append(i) - while(queue): + while queue: vertex = queue.pop(0) cnt += 1 topo.append(vertex) @@ -27,6 +27,7 @@ def topologicalSort(l): else: print(topo) + # Adjacency List of Graph -l = {0:[1,2], 1:[3], 2:[3], 3:[4,5], 4:[], 5:[]} +l = {0: [1, 2], 1: [3], 2: [3], 3: [4, 5], 4: [], 5: []} topologicalSort(l) diff --git a/graphs/minimum_spanning_tree_kruskal.py b/graphs/minimum_spanning_tree_kruskal.py index a2211582ec40..91b44f6508e7 100644 --- a/graphs/minimum_spanning_tree_kruskal.py +++ b/graphs/minimum_spanning_tree_kruskal.py @@ -4,29 +4,29 @@ edges = [] for i in range(num_edges): - node1, node2, cost = list(map(int, input().strip().split())) - edges.append((i,node1,node2,cost)) + node1, node2, cost = list(map(int, input().strip().split())) + edges.append((i, node1, node2, cost)) edges = sorted(edges, key=lambda edge: edge[3]) parent = list(range(num_nodes)) def find_parent(i): - if i != parent[i]: - parent[i] = find_parent(parent[i]) - return parent[i] + if i != parent[i]: + parent[i] = find_parent(parent[i]) + return parent[i] minimum_spanning_tree_cost = 0 minimum_spanning_tree = [] for edge in edges: - parent_a = find_parent(edge[1]) - parent_b = find_parent(edge[2]) - if parent_a != parent_b: - minimum_spanning_tree_cost += edge[3] - minimum_spanning_tree.append(edge) - parent[parent_a] = parent_b + parent_a = find_parent(edge[1]) + parent_b = find_parent(edge[2]) + if parent_a != parent_b: + minimum_spanning_tree_cost += edge[3] + minimum_spanning_tree.append(edge) + parent[parent_a] = parent_b print(minimum_spanning_tree_cost) for edge in minimum_spanning_tree: - print(edge) + print(edge) diff --git a/graphs/minimum_spanning_tree_prims.py b/graphs/minimum_spanning_tree_prims.py index 0f21b8f494e4..216d6a3f56de 100644 --- a/graphs/minimum_spanning_tree_prims.py +++ b/graphs/minimum_spanning_tree_prims.py @@ -1,9 +1,11 @@ import sys from collections import defaultdict + def PrimsAlgorithm(l): nodePosition = [] + def getPosition(vertex): return nodePosition[vertex] @@ -36,11 +38,11 @@ def topToBottom(heap, start, size, positions): def bottomToTop(val, index, heap, position): temp = position[index] - while(index != 0): + while index != 0: if index % 2 == 0: - parent = int( (index-2) / 2 ) + parent = int((index - 2) / 2) else: - parent = int( (index-1) / 2 ) + parent = int((index - 1) / 2) if val < heap[parent]: heap[index] = heap[parent] @@ -69,9 +71,9 @@ def deleteMinimum(heap, positions): return temp visited = [0 for i in range(len(l))] - Nbr_TV = [-1 for i in range(len(l))] # Neighboring Tree Vertex of selected vertex + Nbr_TV = [-1 for i in range(len(l))] # Neighboring Tree Vertex of selected vertex # Minimum Distance of explored vertex with neighboring vertex of partial tree formed in graph - Distance_TV = [] # Heap of Distance of vertices from their neighboring vertex + Distance_TV = [] # Heap of Distance of vertices from their neighboring vertex Positions = [] for x in range(len(l)): @@ -84,8 +86,8 @@ def deleteMinimum(heap, positions): visited[0] = 1 Distance_TV[0] = sys.maxsize for x in l[0]: - Nbr_TV[ x[0] ] = 0 - Distance_TV[ x[0] ] = x[1] + Nbr_TV[x[0]] = 0 + Distance_TV[x[0]] = x[1] heapify(Distance_TV, Positions) for i in range(1, len(l)): @@ -94,12 +96,13 @@ def deleteMinimum(heap, positions): TreeEdges.append((Nbr_TV[vertex], vertex)) visited[vertex] = 1 for v in l[vertex]: - if visited[v[0]] == 0 and v[1] < Distance_TV[ getPosition(v[0]) ]: - Distance_TV[ getPosition(v[0]) ] = v[1] + if visited[v[0]] == 0 and v[1] < Distance_TV[getPosition(v[0])]: + Distance_TV[getPosition(v[0])] = v[1] bottomToTop(v[1], getPosition(v[0]), Distance_TV, Positions) - Nbr_TV[ v[0] ] = vertex + Nbr_TV[v[0]] = vertex return TreeEdges + if __name__ == "__main__": # < --------- Prims Algorithm --------- > n = int(input("Enter number of vertices: ").strip()) @@ -107,6 +110,6 @@ def deleteMinimum(heap, positions): adjlist = defaultdict(list) for x in range(e): l = [int(x) for x in input().strip().split()] - adjlist[l[0]].append([ l[1], l[2] ]) - adjlist[l[1]].append([ l[0], l[2] ]) + adjlist[l[0]].append([l[1], l[2]]) + adjlist[l[1]].append([l[0], l[2]]) print(PrimsAlgorithm(adjlist)) diff --git a/graphs/multi_hueristic_astar.py b/graphs/multi_hueristic_astar.py index 3021c4162b8e..56cfc727d338 100644 --- a/graphs/multi_hueristic_astar.py +++ b/graphs/multi_hueristic_astar.py @@ -3,260 +3,319 @@ class PriorityQueue: - def __init__(self): - self.elements = [] - self.set = set() - - def minkey(self): - if not self.empty(): - return self.elements[0][0] - else: - return float('inf') - - def empty(self): - return len(self.elements) == 0 - - def put(self, item, priority): - if item not in self.set: - heapq.heappush(self.elements, (priority, item)) - self.set.add(item) - else: - # update - # print("update", item) - temp = [] - (pri, x) = heapq.heappop(self.elements) - while x != item: - temp.append((pri, x)) - (pri, x) = heapq.heappop(self.elements) - temp.append((priority, item)) - for (pro, xxx) in temp: - heapq.heappush(self.elements, (pro, xxx)) - - def remove_element(self, item): - if item in self.set: - self.set.remove(item) - temp = [] - (pro, x) = heapq.heappop(self.elements) - while x != item: - temp.append((pro, x)) - (pro, x) = heapq.heappop(self.elements) - for (prito, yyy) in temp: - heapq.heappush(self.elements, (prito, yyy)) - - def top_show(self): - return self.elements[0][1] - - def get(self): - (priority, item) = heapq.heappop(self.elements) - self.set.remove(item) - return (priority, item) + def __init__(self): + self.elements = [] + self.set = set() + + def minkey(self): + if not self.empty(): + return self.elements[0][0] + else: + return float("inf") + + def empty(self): + return len(self.elements) == 0 + + def put(self, item, priority): + if item not in self.set: + heapq.heappush(self.elements, (priority, item)) + self.set.add(item) + else: + # update + # print("update", item) + temp = [] + (pri, x) = heapq.heappop(self.elements) + while x != item: + temp.append((pri, x)) + (pri, x) = heapq.heappop(self.elements) + temp.append((priority, item)) + for (pro, xxx) in temp: + heapq.heappush(self.elements, (pro, xxx)) + + def remove_element(self, item): + if item in self.set: + self.set.remove(item) + temp = [] + (pro, x) = heapq.heappop(self.elements) + while x != item: + temp.append((pro, x)) + (pro, x) = heapq.heappop(self.elements) + for (prito, yyy) in temp: + heapq.heappush(self.elements, (prito, yyy)) + + def top_show(self): + return self.elements[0][1] + + def get(self): + (priority, item) = heapq.heappop(self.elements) + self.set.remove(item) + return (priority, item) + def consistent_hueristic(P, goal): - # euclidean distance - a = np.array(P) - b = np.array(goal) - return np.linalg.norm(a - b) + # euclidean distance + a = np.array(P) + b = np.array(goal) + return np.linalg.norm(a - b) + def hueristic_2(P, goal): - # integer division by time variable - return consistent_hueristic(P, goal) // t + # integer division by time variable + return consistent_hueristic(P, goal) // t + def hueristic_1(P, goal): - # manhattan distance - return abs(P[0] - goal[0]) + abs(P[1] - goal[1]) + # manhattan distance + return abs(P[0] - goal[0]) + abs(P[1] - goal[1]) + def key(start, i, goal, g_function): - ans = g_function[start] + W1 * hueristics[i](start, goal) - return ans + ans = g_function[start] + W1 * hueristics[i](start, goal) + return ans + def do_something(back_pointer, goal, start): - grid = np.chararray((n, n)) - for i in range(n): - for j in range(n): - grid[i][j] = '*' - - for i in range(n): - for j in range(n): - if (j, (n-1)-i) in blocks: - grid[i][j] = "#" - - grid[0][(n-1)] = "-" - x = back_pointer[goal] - while x != start: - (x_c, y_c) = x - # print(x) - grid[(n-1)-y_c][x_c] = "-" - x = back_pointer[x] - grid[(n-1)][0] = "-" - - - for i in range(n): - for j in range(n): - if (i, j) == (0, n-1): - print(grid[i][j], end=' ') - print("<-- End position", end=' ') - else: - print(grid[i][j], end=' ') - print() - print("^") - print("Start position") - print() - print("# is an obstacle") - print("- is the path taken by algorithm") - print("PATH TAKEN BY THE ALGORITHM IS:-") - x = back_pointer[goal] - while x != start: - print(x, end=' ') - x = back_pointer[x] - print(x) - quit() + grid = np.chararray((n, n)) + for i in range(n): + for j in range(n): + grid[i][j] = "*" + + for i in range(n): + for j in range(n): + if (j, (n - 1) - i) in blocks: + grid[i][j] = "#" + + grid[0][(n - 1)] = "-" + x = back_pointer[goal] + while x != start: + (x_c, y_c) = x + # print(x) + grid[(n - 1) - y_c][x_c] = "-" + x = back_pointer[x] + grid[(n - 1)][0] = "-" + + for i in range(n): + for j in range(n): + if (i, j) == (0, n - 1): + print(grid[i][j], end=" ") + print("<-- End position", end=" ") + else: + print(grid[i][j], end=" ") + print() + print("^") + print("Start position") + print() + print("# is an obstacle") + print("- is the path taken by algorithm") + print("PATH TAKEN BY THE ALGORITHM IS:-") + x = back_pointer[goal] + while x != start: + print(x, end=" ") + x = back_pointer[x] + print(x) + quit() + def valid(p): - if p[0] < 0 or p[0] > n-1: - return False - if p[1] < 0 or p[1] > n-1: - return False - return True - -def expand_state(s, j, visited, g_function, close_list_anchor, close_list_inad, open_list, back_pointer): - for itera in range(n_hueristic): - open_list[itera].remove_element(s) - # print("s", s) - # print("j", j) - (x, y) = s - left = (x-1, y) - right = (x+1, y) - up = (x, y+1) - down = (x, y-1) - - for neighbours in [left, right, up, down]: - if neighbours not in blocks: - if valid(neighbours) and neighbours not in visited: - # print("neighbour", neighbours) - visited.add(neighbours) - back_pointer[neighbours] = -1 - g_function[neighbours] = float('inf') - - if valid(neighbours) and g_function[neighbours] > g_function[s] + 1: - g_function[neighbours] = g_function[s] + 1 - back_pointer[neighbours] = s - if neighbours not in close_list_anchor: - open_list[0].put(neighbours, key(neighbours, 0, goal, g_function)) - if neighbours not in close_list_inad: - for var in range(1,n_hueristic): - if key(neighbours, var, goal, g_function) <= W2 * key(neighbours, 0, goal, g_function): - # print("why not plssssssssss") - open_list[j].put(neighbours, key(neighbours, var, goal, g_function)) - - - # print + if p[0] < 0 or p[0] > n - 1: + return False + if p[1] < 0 or p[1] > n - 1: + return False + return True + + +def expand_state( + s, + j, + visited, + g_function, + close_list_anchor, + close_list_inad, + open_list, + back_pointer, +): + for itera in range(n_hueristic): + open_list[itera].remove_element(s) + # print("s", s) + # print("j", j) + (x, y) = s + left = (x - 1, y) + right = (x + 1, y) + up = (x, y + 1) + down = (x, y - 1) + + for neighbours in [left, right, up, down]: + if neighbours not in blocks: + if valid(neighbours) and neighbours not in visited: + # print("neighbour", neighbours) + visited.add(neighbours) + back_pointer[neighbours] = -1 + g_function[neighbours] = float("inf") + + if valid(neighbours) and g_function[neighbours] > g_function[s] + 1: + g_function[neighbours] = g_function[s] + 1 + back_pointer[neighbours] = s + if neighbours not in close_list_anchor: + open_list[0].put(neighbours, key(neighbours, 0, goal, g_function)) + if neighbours not in close_list_inad: + for var in range(1, n_hueristic): + if key(neighbours, var, goal, g_function) <= W2 * key( + neighbours, 0, goal, g_function + ): + # print("why not plssssssssss") + open_list[j].put( + neighbours, key(neighbours, var, goal, g_function) + ) + + # print + def make_common_ground(): - some_list = [] - # block 1 - for x in range(1, 5): - for y in range(1, 6): - some_list.append((x, y)) - - # line - for x in range(15, 20): - some_list.append((x, 17)) - - # block 2 big - for x in range(10, 19): - for y in range(1, 15): - some_list.append((x, y)) - - # L block - for x in range(1, 4): - for y in range(12, 19): - some_list.append((x, y)) - for x in range(3, 13): - for y in range(16, 19): - some_list.append((x, y)) - return some_list + some_list = [] + # block 1 + for x in range(1, 5): + for y in range(1, 6): + some_list.append((x, y)) + + # line + for x in range(15, 20): + some_list.append((x, 17)) + + # block 2 big + for x in range(10, 19): + for y in range(1, 15): + some_list.append((x, y)) + + # L block + for x in range(1, 4): + for y in range(12, 19): + some_list.append((x, y)) + for x in range(3, 13): + for y in range(16, 19): + some_list.append((x, y)) + return some_list + hueristics = {0: consistent_hueristic, 1: hueristic_1, 2: hueristic_2} -blocks_blk = [(0, 1),(1, 1),(2, 1),(3, 1),(4, 1),(5, 1),(6, 1),(7, 1),(8, 1),(9, 1),(10, 1),(11, 1),(12, 1),(13, 1),(14, 1),(15, 1),(16, 1),(17, 1),(18, 1), (19, 1)] +blocks_blk = [ + (0, 1), + (1, 1), + (2, 1), + (3, 1), + (4, 1), + (5, 1), + (6, 1), + (7, 1), + (8, 1), + (9, 1), + (10, 1), + (11, 1), + (12, 1), + (13, 1), + (14, 1), + (15, 1), + (16, 1), + (17, 1), + (18, 1), + (19, 1), +] blocks_no = [] blocks_all = make_common_ground() - - blocks = blocks_blk # hyper parameters W1 = 1 W2 = 1 n = 20 -n_hueristic = 3 # one consistent and two other inconsistent +n_hueristic = 3 # one consistent and two other inconsistent # start and end destination start = (0, 0) -goal = (n-1, n-1) +goal = (n - 1, n - 1) t = 1 + + def multi_a_star(start, goal, n_hueristic): - g_function = {start: 0, goal: float('inf')} - back_pointer = {start:-1, goal:-1} - open_list = [] - visited = set() - - for i in range(n_hueristic): - open_list.append(PriorityQueue()) - open_list[i].put(start, key(start, i, goal, g_function)) - - close_list_anchor = [] - close_list_inad = [] - while open_list[0].minkey() < float('inf'): - for i in range(1, n_hueristic): - # print("i", i) - # print(open_list[0].minkey(), open_list[i].minkey()) - if open_list[i].minkey() <= W2 * open_list[0].minkey(): - global t - t += 1 - # print("less prio") - if g_function[goal] <= open_list[i].minkey(): - if g_function[goal] < float('inf'): - do_something(back_pointer, goal, start) - else: - _, get_s = open_list[i].top_show() - visited.add(get_s) - expand_state(get_s, i, visited, g_function, close_list_anchor, close_list_inad, open_list, back_pointer) - close_list_inad.append(get_s) - else: - # print("more prio") - if g_function[goal] <= open_list[0].minkey(): - if g_function[goal] < float('inf'): - do_something(back_pointer, goal, start) - else: - # print("hoolla") - get_s = open_list[0].top_show() - visited.add(get_s) - expand_state(get_s, 0, visited, g_function, close_list_anchor, close_list_inad, open_list, back_pointer) - close_list_anchor.append(get_s) - print("No path found to goal") - print() - for i in range(n-1,-1, -1): - for j in range(n): - if (j, i) in blocks: - print('#', end=' ') - elif (j, i) in back_pointer: - if (j, i) == (n-1, n-1): - print('*', end=' ') - else: - print('-', end=' ') - else: - print('*', end=' ') - if (j, i) == (n-1, n-1): - print('<-- End position', end=' ') - print() - print("^") - print("Start position") - print() - print("# is an obstacle") - print("- is the path taken by algorithm") + g_function = {start: 0, goal: float("inf")} + back_pointer = {start: -1, goal: -1} + open_list = [] + visited = set() + + for i in range(n_hueristic): + open_list.append(PriorityQueue()) + open_list[i].put(start, key(start, i, goal, g_function)) + + close_list_anchor = [] + close_list_inad = [] + while open_list[0].minkey() < float("inf"): + for i in range(1, n_hueristic): + # print("i", i) + # print(open_list[0].minkey(), open_list[i].minkey()) + if open_list[i].minkey() <= W2 * open_list[0].minkey(): + global t + t += 1 + # print("less prio") + if g_function[goal] <= open_list[i].minkey(): + if g_function[goal] < float("inf"): + do_something(back_pointer, goal, start) + else: + _, get_s = open_list[i].top_show() + visited.add(get_s) + expand_state( + get_s, + i, + visited, + g_function, + close_list_anchor, + close_list_inad, + open_list, + back_pointer, + ) + close_list_inad.append(get_s) + else: + # print("more prio") + if g_function[goal] <= open_list[0].minkey(): + if g_function[goal] < float("inf"): + do_something(back_pointer, goal, start) + else: + # print("hoolla") + get_s = open_list[0].top_show() + visited.add(get_s) + expand_state( + get_s, + 0, + visited, + g_function, + close_list_anchor, + close_list_inad, + open_list, + back_pointer, + ) + close_list_anchor.append(get_s) + print("No path found to goal") + print() + for i in range(n - 1, -1, -1): + for j in range(n): + if (j, i) in blocks: + print("#", end=" ") + elif (j, i) in back_pointer: + if (j, i) == (n - 1, n - 1): + print("*", end=" ") + else: + print("-", end=" ") + else: + print("*", end=" ") + if (j, i) == (n - 1, n - 1): + print("<-- End position", end=" ") + print() + print("^") + print("Start position") + print() + print("# is an obstacle") + print("- is the path taken by algorithm") if __name__ == "__main__": diff --git a/graphs/page_rank.py b/graphs/page_rank.py index 59f15a99e6b2..1e2c7d9aeb48 100644 --- a/graphs/page_rank.py +++ b/graphs/page_rank.py @@ -1,7 +1,7 @@ -''' +""" Author: https://github.com/bhushan-borole -''' -''' +""" +""" The input graph for the algorithm is: A B C @@ -9,11 +9,9 @@ B 0 0 1 C 1 0 0 -''' +""" -graph = [[0, 1, 1], - [0, 0, 1], - [1, 0, 0]] +graph = [[0, 1, 1], [0, 0, 1], [1, 0, 0]] class Node: @@ -21,17 +19,17 @@ def __init__(self, name): self.name = name self.inbound = [] self.outbound = [] - + def add_inbound(self, node): self.inbound.append(node) - + def add_outbound(self, node): self.outbound.append(node) - + def __repr__(self): - return 'Node {}: Inbound: {} ; Outbound: {}'.format(self.name, - self.inbound, - self.outbound) + return "Node {}: Inbound: {} ; Outbound: {}".format( + self.name, self.inbound, self.outbound + ) def page_rank(nodes, limit=3, d=0.85): @@ -44,17 +42,19 @@ def page_rank(nodes, limit=3, d=0.85): outbounds[node.name] = len(node.outbound) for i in range(limit): - print("======= Iteration {} =======".format(i+1)) + print("======= Iteration {} =======".format(i + 1)) for j, node in enumerate(nodes): - ranks[node.name] = (1 - d) + d * sum([ ranks[ib]/outbounds[ib] for ib in node.inbound ]) + ranks[node.name] = (1 - d) + d * sum( + [ranks[ib] / outbounds[ib] for ib in node.inbound] + ) print(ranks) def main(): - names = list(input('Enter Names of the Nodes: ').split()) + names = list(input("Enter Names of the Nodes: ").split()) nodes = [Node(name) for name in names] - + for ri, row in enumerate(graph): for ci, col in enumerate(row): if col == 1: @@ -68,5 +68,5 @@ def main(): page_rank(nodes) -if __name__ == '__main__': - main() \ No newline at end of file +if __name__ == "__main__": + main() diff --git a/graphs/prim.py b/graphs/prim.py index f7e08278966d..336424d2c3c1 100644 --- a/graphs/prim.py +++ b/graphs/prim.py @@ -21,7 +21,7 @@ import math -class vertex(): +class vertex: """Class Vertex.""" def __init__(self, id): @@ -40,7 +40,7 @@ def __init__(self, id): def __lt__(self, other): """Comparison rule to < operator.""" - return (self.key < other.key) + return self.key < other.key def __repr__(self): """Return the vertex id.""" @@ -76,4 +76,4 @@ def prim(graph, root): v.key = u.edges[v.id] for i in range(1, len(graph)): A.append([graph[i].id, graph[i].pi.id]) - return(A) + return A diff --git a/graphs/scc_kosaraju.py b/graphs/scc_kosaraju.py index 99564a7cfa35..573c1bf5e363 100644 --- a/graphs/scc_kosaraju.py +++ b/graphs/scc_kosaraju.py @@ -1,26 +1,31 @@ def dfs(u): global g, r, scc, component, visit, stack - if visit[u]: return + if visit[u]: + return visit[u] = True for v in g[u]: dfs(v) stack.append(u) + def dfs2(u): global g, r, scc, component, visit, stack - if visit[u]: return + if visit[u]: + return visit[u] = True component.append(u) for v in r[u]: dfs2(v) + def kosaraju(): global g, r, scc, component, visit, stack for i in range(n): dfs(i) - visit = [False]*n + visit = [False] * n for i in stack[::-1]: - if visit[i]: continue + if visit[i]: + continue component = [] dfs2(i) scc.append(component) @@ -29,18 +34,18 @@ def kosaraju(): if __name__ == "__main__": # n - no of nodes, m - no of edges - n, m = list(map(int,input().strip().split())) + n, m = list(map(int, input().strip().split())) - g = [[] for i in range(n)] #graph - r = [[] for i in range(n)] #reversed graph + g = [[] for i in range(n)] # graph + r = [[] for i in range(n)] # reversed graph # input graph data (edges) for i in range(m): - u, v = list(map(int,input().strip().split())) + u, v = list(map(int, input().strip().split())) g[u].append(v) r[v].append(u) stack = [] - visit = [False]*n + visit = [False] * n scc = [] component = [] print(kosaraju()) diff --git a/graphs/tarjans_scc.py b/graphs/tarjans_scc.py index 89754e593508..4b0a689ea3c0 100644 --- a/graphs/tarjans_scc.py +++ b/graphs/tarjans_scc.py @@ -36,9 +36,13 @@ def strong_connect(v, index, components): for w in g[v]: if index_of[w] == -1: index = strong_connect(w, index, components) - lowlink_of[v] = lowlink_of[w] if lowlink_of[w] < lowlink_of[v] else lowlink_of[v] + lowlink_of[v] = ( + lowlink_of[w] if lowlink_of[w] < lowlink_of[v] else lowlink_of[v] + ) elif on_stack[w]: - lowlink_of[v] = lowlink_of[w] if lowlink_of[w] < lowlink_of[v] else lowlink_of[v] + lowlink_of[v] = ( + lowlink_of[w] if lowlink_of[w] < lowlink_of[v] else lowlink_of[v] + ) if lowlink_of[v] == index_of[v]: component = [] @@ -67,7 +71,7 @@ def create_graph(n, edges): return g -if __name__ == '__main__': +if __name__ == "__main__": # Test n_vertices = 7 source = [0, 0, 1, 2, 3, 3, 4, 4, 6] diff --git a/hashes/chaos_machine.py b/hashes/chaos_machine.py index 3a7c3950bb29..8d3bbd4c0251 100644 --- a/hashes/chaos_machine.py +++ b/hashes/chaos_machine.py @@ -1,7 +1,9 @@ """example of simple chaos machine""" # Chaos Machine (K, t, m) -K = [0.33, 0.44, 0.55, 0.44, 0.33]; t = 3; m = 5 +K = [0.33, 0.44, 0.55, 0.44, 0.33] +t = 3 +m = 5 # Buffer Space (with Parameters Space) buffer_space, params_space = [], [] @@ -9,75 +11,73 @@ # Machine Time machine_time = 0 + def push(seed): - global buffer_space, params_space, machine_time, \ - K, m, t + global buffer_space, params_space, machine_time, K, m, t + + # Choosing Dynamical Systems (All) + for key, value in enumerate(buffer_space): + # Evolution Parameter + e = float(seed / value) - # Choosing Dynamical Systems (All) - for key, value in enumerate(buffer_space): - # Evolution Parameter - e = float(seed / value) + # Control Theory: Orbit Change + value = (buffer_space[(key + 1) % m] + e) % 1 - # Control Theory: Orbit Change - value = (buffer_space[(key + 1) % m] + e) % 1 + # Control Theory: Trajectory Change + r = (params_space[key] + e) % 1 + 3 - # Control Theory: Trajectory Change - r = (params_space[key] + e) % 1 + 3 + # Modification (Transition Function) - Jumps + buffer_space[key] = round(float(r * value * (1 - value)), 10) + params_space[key] = r # Saving to Parameters Space - # Modification (Transition Function) - Jumps - buffer_space[key] = \ - round(float(r * value * (1 - value)), 10) - params_space[key] = \ - r # Saving to Parameters Space + # Logistic Map + assert max(buffer_space) < 1 + assert max(params_space) < 4 - # Logistic Map - assert max(buffer_space) < 1 - assert max(params_space) < 4 + # Machine Time + machine_time += 1 - # Machine Time - machine_time += 1 def pull(): - global buffer_space, params_space, machine_time, \ - K, m, t + global buffer_space, params_space, machine_time, K, m, t + + # PRNG (Xorshift by George Marsaglia) + def xorshift(X, Y): + X ^= Y >> 13 + Y ^= X << 17 + X ^= Y >> 5 + return X - # PRNG (Xorshift by George Marsaglia) - def xorshift(X, Y): - X ^= Y >> 13 - Y ^= X << 17 - X ^= Y >> 5 - return X + # Choosing Dynamical Systems (Increment) + key = machine_time % m - # Choosing Dynamical Systems (Increment) - key = machine_time % m + # Evolution (Time Length) + for i in range(0, t): + # Variables (Position + Parameters) + r = params_space[key] + value = buffer_space[key] - # Evolution (Time Length) - for i in range(0, t): - # Variables (Position + Parameters) - r = params_space[key] - value = buffer_space[key] + # Modification (Transition Function) - Flow + buffer_space[key] = round(float(r * value * (1 - value)), 10) + params_space[key] = (machine_time * 0.01 + r * 1.01) % 1 + 3 - # Modification (Transition Function) - Flow - buffer_space[key] = \ - round(float(r * value * (1 - value)), 10) - params_space[key] = \ - (machine_time * 0.01 + r * 1.01) % 1 + 3 + # Choosing Chaotic Data + X = int(buffer_space[(key + 2) % m] * (10 ** 10)) + Y = int(buffer_space[(key - 2) % m] * (10 ** 10)) - # Choosing Chaotic Data - X = int(buffer_space[(key + 2) % m] * (10 ** 10)) - Y = int(buffer_space[(key - 2) % m] * (10 ** 10)) + # Machine Time + machine_time += 1 - # Machine Time - machine_time += 1 + return xorshift(X, Y) % 0xFFFFFFFF - return xorshift(X, Y) % 0xFFFFFFFF def reset(): - global buffer_space, params_space, machine_time, \ - K, m, t + global buffer_space, params_space, machine_time, K, m, t + + buffer_space = K + params_space = [0] * m + machine_time = 0 - buffer_space = K; params_space = [0] * m - machine_time = 0 ####################################### @@ -86,15 +86,17 @@ def reset(): # Pushing Data (Input) import random + message = random.sample(range(0xFFFFFFFF), 100) for chunk in message: - push(chunk) + push(chunk) # for controlling inp = "" # Pulling Data (Output) while inp in ("e", "E"): - print("%s" % format(pull(), '#04x')) - print(buffer_space); print(params_space) - inp = input("(e)exit? ").strip() + print("%s" % format(pull(), "#04x")) + print(buffer_space) + print(params_space) + inp = input("(e)exit? ").strip() diff --git a/hashes/enigma_machine.py b/hashes/enigma_machine.py index 06215785765f..5420bacc1409 100644 --- a/hashes/enigma_machine.py +++ b/hashes/enigma_machine.py @@ -40,7 +40,7 @@ def engine(input_character): rotator() -if __name__ == '__main__': +if __name__ == "__main__": decode = input("Type your message:\n") decode = list(decode) while True: @@ -56,4 +56,5 @@ def engine(input_character): print("\n" + "".join(code)) print( f"\nYour Token is {token} please write it down.\nIf you want to decode " - f"this message again you should input same digits as token!") + f"this message again you should input same digits as token!" + ) diff --git a/hashes/md5.py b/hashes/md5.py index 1ad43013363f..85565533d175 100644 --- a/hashes/md5.py +++ b/hashes/md5.py @@ -20,8 +20,8 @@ def rearrange(bitString32): if len(bitString32) != 32: raise ValueError("Need length 32") newString = "" - for i in [3, 2,1,0]: - newString += bitString32[8*i:8*i+8] + for i in [3, 2, 1, 0]: + newString += bitString32[8 * i : 8 * i + 8] return newString @@ -35,10 +35,10 @@ def reformatHex(i): '9a020000' """ - hexrep = format(i, '08x') + hexrep = format(i, "08x") thing = "" - for i in [3, 2,1,0]: - thing += hexrep[2*i:2*i+2] + for i in [3, 2, 1, 0]: + thing += hexrep[2 * i : 2 * i + 2] return thing @@ -53,10 +53,10 @@ def pad(bitString): [string] -- [binary string] """ startLength = len(bitString) - bitString += '1' + bitString += "1" while len(bitString) % 512 != 448: - bitString += '0' - lastPart = format(startLength, '064b') + bitString += "0" + lastPart = format(startLength, "064b") bitString += rearrange(lastPart[32:]) + rearrange(lastPart[:32]) return bitString @@ -73,33 +73,35 @@ def getBlock(bitString): currPos = 0 while currPos < len(bitString): - currPart = bitString[currPos:currPos+512] + currPart = bitString[currPos : currPos + 512] mySplits = [] for i in range(16): - mySplits.append(int(rearrange(currPart[32*i:32*i+32]), 2)) + mySplits.append(int(rearrange(currPart[32 * i : 32 * i + 32]), 2)) yield mySplits currPos += 512 def not32(i): - ''' + """ >>> not32(34) 4294967261 - ''' - i_str = format(i, '032b') - new_str = '' + """ + i_str = format(i, "032b") + new_str = "" for c in i_str: - new_str += '1' if c == '0' else '0' + new_str += "1" if c == "0" else "0" return int(new_str, 2) + def sum32(a, b): - ''' + """ + + """ + return (a + b) % 2 ** 32 - ''' - return (a + b) % 2**32 def leftrot32(i, s): - return (i << s) ^ (i >> (32-s)) + return (i << s) ^ (i >> (32 - s)) def md5me(testString): @@ -110,22 +112,84 @@ def md5me(testString): testString {[string]} -- [message] """ - bs = '' + bs = "" for i in testString: - bs += format(ord(i), '08b') + bs += format(ord(i), "08b") bs = pad(bs) - tvals = [int(2**32 * abs(math.sin(i+1))) for i in range(64)] + tvals = [int(2 ** 32 * abs(math.sin(i + 1))) for i in range(64)] a0 = 0x67452301 - b0 = 0xefcdab89 - c0 = 0x98badcfe + b0 = 0xEFCDAB89 + c0 = 0x98BADCFE d0 = 0x10325476 - s = [7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, - 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20, \ - 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23, \ - 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21 ] + s = [ + 7, + 12, + 17, + 22, + 7, + 12, + 17, + 22, + 7, + 12, + 17, + 22, + 7, + 12, + 17, + 22, + 5, + 9, + 14, + 20, + 5, + 9, + 14, + 20, + 5, + 9, + 14, + 20, + 5, + 9, + 14, + 20, + 4, + 11, + 16, + 23, + 4, + 11, + 16, + 23, + 4, + 11, + 16, + 23, + 4, + 11, + 16, + 23, + 6, + 10, + 15, + 21, + 6, + 10, + 15, + 21, + 6, + 10, + 15, + 21, + 6, + 10, + 15, + 21, + ] for m in getBlock(bs): A = a0 @@ -134,42 +198,44 @@ def md5me(testString): D = d0 for i in range(64): if i <= 15: - #f = (B & C) | (not32(B) & D) + # f = (B & C) | (not32(B) & D) f = D ^ (B & (C ^ D)) g = i elif i <= 31: - #f = (D & B) | (not32(D) & C) + # f = (D & B) | (not32(D) & C) f = C ^ (D & (B ^ C)) - g = (5*i+1) % 16 + g = (5 * i + 1) % 16 elif i <= 47: f = B ^ C ^ D - g = (3*i+5) % 16 + g = (3 * i + 5) % 16 else: f = C ^ (B | not32(D)) - g = (7*i) % 16 + g = (7 * i) % 16 dtemp = D D = C C = B - B = sum32(B, leftrot32((A + f + tvals[i] + m[g]) % 2**32, s[i])) + B = sum32(B, leftrot32((A + f + tvals[i] + m[g]) % 2 ** 32, s[i])) A = dtemp a0 = sum32(a0, A) b0 = sum32(b0, B) c0 = sum32(c0, C) d0 = sum32(d0, D) - digest = reformatHex(a0) + reformatHex(b0) + \ - reformatHex(c0) + reformatHex(d0) + digest = reformatHex(a0) + reformatHex(b0) + reformatHex(c0) + reformatHex(d0) return digest def test(): assert md5me("") == "d41d8cd98f00b204e9800998ecf8427e" - assert md5me( - "The quick brown fox jumps over the lazy dog") == "9e107d9d372bb6826bd81d3542a419d6" + assert ( + md5me("The quick brown fox jumps over the lazy dog") + == "9e107d9d372bb6826bd81d3542a419d6" + ) print("Success.") if __name__ == "__main__": test() import doctest + doctest.testmod() diff --git a/hashes/sha1.py b/hashes/sha1.py index 511ea6363733..3bf27af27582 100644 --- a/hashes/sha1.py +++ b/hashes/sha1.py @@ -25,7 +25,7 @@ import argparse import struct -import hashlib #hashlib is only used inside the Test class +import hashlib # hashlib is only used inside the Test class import unittest @@ -35,6 +35,7 @@ class SHA1Hash: >>> SHA1Hash(bytes('Allan', 'utf-8')).final_hash() '872af2d8ac3d8695387e7c804bf0e02c18df9e6e' """ + def __init__(self, data): """ Inititates the variables data and h. h is a list of 5 8-digit Hexadecimal @@ -52,21 +53,23 @@ def rotate(n, b): >>> SHA1Hash('').rotate(12,2) 48 """ - return ((n << b) | (n >> (32 - b))) & 0xffffffff + return ((n << b) | (n >> (32 - b))) & 0xFFFFFFFF def padding(self): """ Pads the input message with zeros so that padded_data has 64 bytes or 512 bits """ - padding = b'\x80' + b'\x00'*(63 - (len(self.data) + 8) % 64) - padded_data = self.data + padding + struct.pack('>Q', 8 * len(self.data)) + padding = b"\x80" + b"\x00" * (63 - (len(self.data) + 8) % 64) + padded_data = self.data + padding + struct.pack(">Q", 8 * len(self.data)) return padded_data def split_blocks(self): """ Returns a list of bytestrings each of length 64 """ - return [self.padded_data[i:i+64] for i in range(0, len(self.padded_data), 64)] + return [ + self.padded_data[i : i + 64] for i in range(0, len(self.padded_data), 64) + ] # @staticmethod def expand_block(self, block): @@ -74,9 +77,9 @@ def expand_block(self, block): Takes a bytestring-block of length 64, unpacks it to a list of integers and returns a list of 80 integers after some bit operations """ - w = list(struct.unpack('>16L', block)) + [0] * 64 + w = list(struct.unpack(">16L", block)) + [0] * 64 for i in range(16, 80): - w[i] = self.rotate((w[i-3] ^ w[i-8] ^ w[i-14] ^ w[i-16]), 1) + w[i] = self.rotate((w[i - 3] ^ w[i - 8] ^ w[i - 14] ^ w[i - 16]), 1) return w def final_hash(self): @@ -106,22 +109,30 @@ def final_hash(self): elif 60 <= i < 80: f = b ^ c ^ d k = 0xCA62C1D6 - a, b, c, d, e = self.rotate(a, 5) + f + e + k + expanded_block[i] & 0xffffffff,\ - a, self.rotate(b, 30), c, d - self.h = self.h[0] + a & 0xffffffff,\ - self.h[1] + b & 0xffffffff,\ - self.h[2] + c & 0xffffffff,\ - self.h[3] + d & 0xffffffff,\ - self.h[4] + e & 0xffffffff - return '%08x%08x%08x%08x%08x' %tuple(self.h) + a, b, c, d, e = ( + self.rotate(a, 5) + f + e + k + expanded_block[i] & 0xFFFFFFFF, + a, + self.rotate(b, 30), + c, + d, + ) + self.h = ( + self.h[0] + a & 0xFFFFFFFF, + self.h[1] + b & 0xFFFFFFFF, + self.h[2] + c & 0xFFFFFFFF, + self.h[3] + d & 0xFFFFFFFF, + self.h[4] + e & 0xFFFFFFFF, + ) + return "%08x%08x%08x%08x%08x" % tuple(self.h) class SHA1HashTest(unittest.TestCase): """ Test class for the SHA1Hash class. Inherits the TestCase class from unittest """ + def testMatchHashes(self): - msg = bytes('Test String', 'utf-8') + msg = bytes("Test String", "utf-8") self.assertEqual(SHA1Hash(msg).final_hash(), hashlib.sha1(msg).hexdigest()) @@ -132,23 +143,27 @@ def main(): the test each time. """ # unittest.main() - parser = argparse.ArgumentParser(description='Process some strings or files') - parser.add_argument('--string', dest='input_string', - default='Hello World!! Welcome to Cryptography', - help='Hash the string') - parser.add_argument('--file', dest='input_file', help='Hash contents of a file') + parser = argparse.ArgumentParser(description="Process some strings or files") + parser.add_argument( + "--string", + dest="input_string", + default="Hello World!! Welcome to Cryptography", + help="Hash the string", + ) + parser.add_argument("--file", dest="input_file", help="Hash contents of a file") args = parser.parse_args() input_string = args.input_string - #In any case hash input should be a bytestring + # In any case hash input should be a bytestring if args.input_file: - with open(args.input_file, 'rb') as f: + with open(args.input_file, "rb") as f: hash_input = f.read() else: - hash_input = bytes(input_string, 'utf-8') + hash_input = bytes(input_string, "utf-8") print(SHA1Hash(hash_input).final_hash()) -if __name__ == '__main__': +if __name__ == "__main__": main() import doctest - doctest.testmod() \ No newline at end of file + + doctest.testmod() diff --git a/linear_algebra/src/lib.py b/linear_algebra/src/lib.py index 281991a93b2d..5ce0f696ad71 100644 --- a/linear_algebra/src/lib.py +++ b/linear_algebra/src/lib.py @@ -45,13 +45,15 @@ class Vector(object): changeComponent(pos,value) : changes the specified component. TODO: compare-operator """ - def __init__(self,components=[]): + + def __init__(self, components=[]): """ input: components or nothing simple constructor for init the vector """ self.__components = list(components) - def set(self,components): + + def set(self, components): """ input: new components changes the components of the vector. @@ -61,34 +63,39 @@ def set(self,components): self.__components = list(components) else: raise Exception("please give any vector") + def __str__(self): """ returns a string representation of the vector """ return "(" + ",".join(map(str, self.__components)) + ")" - def component(self,i): + + def component(self, i): """ input: index (start at 0) output: the i-th component of the vector. """ - if type(i) is int and -len(self.__components) <= i < len(self.__components) : + if type(i) is int and -len(self.__components) <= i < len(self.__components): return self.__components[i] else: raise Exception("index out of range") + def __len__(self): """ returns the size of the vector """ return len(self.__components) + def eulidLength(self): """ returns the eulidean length of the vector """ summe = 0 for c in self.__components: - summe += c**2 + summe += c ** 2 return math.sqrt(summe) - def __add__(self,other): + + def __add__(self, other): """ input: other vector assumes: other vector has the same size @@ -100,7 +107,8 @@ def __add__(self,other): return Vector(result) else: raise Exception("must have the same size") - def __sub__(self,other): + + def __sub__(self, other): """ input: other vector assumes: other vector has the same size @@ -110,73 +118,80 @@ def __sub__(self,other): if size == len(other): result = [self.__components[i] - other.component(i) for i in range(size)] return result - else: # error case + else: # error case raise Exception("must have the same size") - def __mul__(self,other): + + def __mul__(self, other): """ mul implements the scalar multiplication and the dot-product """ - if isinstance(other,float) or isinstance(other,int): - ans = [c*other for c in self.__components] + if isinstance(other, float) or isinstance(other, int): + ans = [c * other for c in self.__components] return ans - elif (isinstance(other,Vector) and (len(self) == len(other))): + elif isinstance(other, Vector) and (len(self) == len(other)): size = len(self) summe = 0 for i in range(size): summe += self.__components[i] * other.component(i) return summe - else: # error case + else: # error case raise Exception("invalide operand!") + def copy(self): """ copies this vector and returns it. """ return Vector(self.__components) - def changeComponent(self,pos,value): + + def changeComponent(self, pos, value): """ input: an index (pos) and a value changes the specified component (pos) with the 'value' """ - #precondition - assert (-len(self.__components) <= pos < len(self.__components)) + # precondition + assert -len(self.__components) <= pos < len(self.__components) self.__components[pos] = value - + + def zeroVector(dimension): """ returns a zero-vector of size 'dimension' - """ - #precondition - assert(isinstance(dimension,int)) - return Vector([0]*dimension) + """ + # precondition + assert isinstance(dimension, int) + return Vector([0] * dimension) -def unitBasisVector(dimension,pos): +def unitBasisVector(dimension, pos): """ returns a unit basis vector with a One at index 'pos' (indexing at 0) """ - #precondition - assert(isinstance(dimension,int) and (isinstance(pos,int))) - ans = [0]*dimension + # precondition + assert isinstance(dimension, int) and (isinstance(pos, int)) + ans = [0] * dimension ans[pos] = 1 return Vector(ans) - -def axpy(scalar,x,y): + +def axpy(scalar, x, y): """ input: a 'scalar' and two vectors 'x' and 'y' output: a vector computes the axpy operation """ # precondition - assert(isinstance(x,Vector) and (isinstance(y,Vector)) \ - and (isinstance(scalar,int) or isinstance(scalar,float))) - return (x*scalar + y) - + assert ( + isinstance(x, Vector) + and (isinstance(y, Vector)) + and (isinstance(scalar, int) or isinstance(scalar, float)) + ) + return x * scalar + y + -def randomVector(N,a,b): +def randomVector(N, a, b): """ input: size (N) of the vector. random range (a,b) @@ -184,7 +199,7 @@ def randomVector(N,a,b): random integer components between 'a' and 'b'. """ random.seed(None) - ans = [random.randint(a,b) for i in range(N)] + ans = [random.randint(a, b) for i in range(N)] return Vector(ans) @@ -205,7 +220,8 @@ class Matrix(object): operator + : implements the matrix-addition. operator - _ implements the matrix-subtraction """ - def __init__(self,matrix,w,h): + + def __init__(self, matrix, w, h): """ simple constructor for initialzes the matrix with components. @@ -213,6 +229,7 @@ def __init__(self,matrix,w,h): self.__matrix = matrix self.__width = w self.__height = h + def __str__(self): """ returns a string representation of this @@ -222,102 +239,113 @@ def __str__(self): for i in range(self.__height): ans += "|" for j in range(self.__width): - if j < self.__width -1: + if j < self.__width - 1: ans += str(self.__matrix[i][j]) + "," else: ans += str(self.__matrix[i][j]) + "|\n" return ans - def changeComponent(self,x,y, value): + + def changeComponent(self, x, y, value): """ changes the x-y component of this matrix """ if x >= 0 and x < self.__height and y >= 0 and y < self.__width: self.__matrix[x][y] = value else: - raise Exception ("changeComponent: indices out of bounds") - def component(self,x,y): + raise Exception("changeComponent: indices out of bounds") + + def component(self, x, y): """ returns the specified (x,y) component """ if x >= 0 and x < self.__height and y >= 0 and y < self.__width: return self.__matrix[x][y] else: - raise Exception ("changeComponent: indices out of bounds") + raise Exception("changeComponent: indices out of bounds") + def width(self): """ getter for the width """ return self.__width + def height(self): """ getter for the height """ return self.__height - def __mul__(self,other): + + def __mul__(self, other): """ implements the matrix-vector multiplication. implements the matrix-scalar multiplication """ - if isinstance(other, Vector): # vector-matrix - if (len(other) == self.__width): + if isinstance(other, Vector): # vector-matrix + if len(other) == self.__width: ans = zeroVector(self.__height) for i in range(self.__height): summe = 0 for j in range(self.__width): summe += other.component(j) * self.__matrix[i][j] - ans.changeComponent(i,summe) + ans.changeComponent(i, summe) summe = 0 return ans else: - raise Exception("vector must have the same size as the " + "number of columns of the matrix!") - elif isinstance(other,int) or isinstance(other,float): # matrix-scalar - matrix = [[self.__matrix[i][j] * other for j in range(self.__width)] for i in range(self.__height)] - return Matrix(matrix,self.__width,self.__height) - def __add__(self,other): + raise Exception( + "vector must have the same size as the " + + "number of columns of the matrix!" + ) + elif isinstance(other, int) or isinstance(other, float): # matrix-scalar + matrix = [ + [self.__matrix[i][j] * other for j in range(self.__width)] + for i in range(self.__height) + ] + return Matrix(matrix, self.__width, self.__height) + + def __add__(self, other): """ implements the matrix-addition. """ - if (self.__width == other.width() and self.__height == other.height()): + if self.__width == other.width() and self.__height == other.height(): matrix = [] for i in range(self.__height): row = [] for j in range(self.__width): - row.append(self.__matrix[i][j] + other.component(i,j)) + row.append(self.__matrix[i][j] + other.component(i, j)) matrix.append(row) - return Matrix(matrix,self.__width,self.__height) + return Matrix(matrix, self.__width, self.__height) else: raise Exception("matrix must have the same dimension!") - def __sub__(self,other): + + def __sub__(self, other): """ implements the matrix-subtraction. """ - if (self.__width == other.width() and self.__height == other.height()): + if self.__width == other.width() and self.__height == other.height(): matrix = [] for i in range(self.__height): row = [] for j in range(self.__width): - row.append(self.__matrix[i][j] - other.component(i,j)) + row.append(self.__matrix[i][j] - other.component(i, j)) matrix.append(row) - return Matrix(matrix,self.__width,self.__height) + return Matrix(matrix, self.__width, self.__height) else: raise Exception("matrix must have the same dimension!") - + def squareZeroMatrix(N): """ returns a square zero-matrix of dimension NxN """ - ans = [[0]*N for i in range(N)] - return Matrix(ans,N,N) - - -def randomMatrix(W,H,a,b): + ans = [[0] * N for i in range(N)] + return Matrix(ans, N, N) + + +def randomMatrix(W, H, a, b): """ returns a random matrix WxH with integer components between 'a' and 'b' """ random.seed(None) - matrix = [[random.randint(a,b) for j in range(W)] for i in range(H)] - return Matrix(matrix,W,H) - - + matrix = [[random.randint(a, b) for j in range(W)] for i in range(H)] + return Matrix(matrix, W, H) diff --git a/linear_algebra/src/tests.py b/linear_algebra/src/tests.py index afca4ce87117..b63f2ae8c2db 100644 --- a/linear_algebra/src/tests.py +++ b/linear_algebra/src/tests.py @@ -11,123 +11,144 @@ import unittest from lib import Matrix, Vector, axpy, squareZeroMatrix, unitBasisVector, zeroVector + class Test(unittest.TestCase): def test_component(self): """ test for method component """ - x = Vector([1,2,3]) - self.assertEqual(x.component(0),1) - self.assertEqual(x.component(2),3) + x = Vector([1, 2, 3]) + self.assertEqual(x.component(0), 1) + self.assertEqual(x.component(2), 3) try: y = Vector() self.assertTrue(False) except: self.assertTrue(True) + def test_str(self): """ test for toString() method """ - x = Vector([0,0,0,0,0,1]) - self.assertEqual(str(x),"(0,0,0,0,0,1)") + x = Vector([0, 0, 0, 0, 0, 1]) + self.assertEqual(str(x), "(0,0,0,0,0,1)") + def test_size(self): """ test for size()-method """ - x = Vector([1,2,3,4]) - self.assertEqual(len(x),4) + x = Vector([1, 2, 3, 4]) + self.assertEqual(len(x), 4) + def test_euclidLength(self): """ test for the eulidean length """ - x = Vector([1,2]) - self.assertAlmostEqual(x.eulidLength(),2.236,3) + x = Vector([1, 2]) + self.assertAlmostEqual(x.eulidLength(), 2.236, 3) + def test_add(self): """ test for + operator """ - x = Vector([1,2,3]) - y = Vector([1,1,1]) - self.assertEqual((x+y).component(0),2) - self.assertEqual((x+y).component(1),3) - self.assertEqual((x+y).component(2),4) + x = Vector([1, 2, 3]) + y = Vector([1, 1, 1]) + self.assertEqual((x + y).component(0), 2) + self.assertEqual((x + y).component(1), 3) + self.assertEqual((x + y).component(2), 4) + def test_sub(self): """ test for - operator """ - x = Vector([1,2,3]) - y = Vector([1,1,1]) - self.assertEqual((x-y).component(0),0) - self.assertEqual((x-y).component(1),1) - self.assertEqual((x-y).component(2),2) + x = Vector([1, 2, 3]) + y = Vector([1, 1, 1]) + self.assertEqual((x - y).component(0), 0) + self.assertEqual((x - y).component(1), 1) + self.assertEqual((x - y).component(2), 2) + def test_mul(self): """ test for * operator """ - x = Vector([1,2,3]) - a = Vector([2,-1,4]) # for test of dot-product - b = Vector([1,-2,-1]) - self.assertEqual(str(x*3.0),"(3.0,6.0,9.0)") - self.assertEqual((a*b),0) + x = Vector([1, 2, 3]) + a = Vector([2, -1, 4]) # for test of dot-product + b = Vector([1, -2, -1]) + self.assertEqual(str(x * 3.0), "(3.0,6.0,9.0)") + self.assertEqual((a * b), 0) + def test_zeroVector(self): """ test for the global function zeroVector(...) """ self.assertTrue(str(zeroVector(10)).count("0") == 10) + def test_unitBasisVector(self): """ test for the global function unitBasisVector(...) """ - self.assertEqual(str(unitBasisVector(3,1)),"(0,1,0)") + self.assertEqual(str(unitBasisVector(3, 1)), "(0,1,0)") + def test_axpy(self): """ test for the global function axpy(...) (operation) """ - x = Vector([1,2,3]) - y = Vector([1,0,1]) - self.assertEqual(str(axpy(2,x,y)),"(3,4,7)") + x = Vector([1, 2, 3]) + y = Vector([1, 0, 1]) + self.assertEqual(str(axpy(2, x, y)), "(3,4,7)") + def test_copy(self): """ test for the copy()-method """ - x = Vector([1,0,0,0,0,0]) + x = Vector([1, 0, 0, 0, 0, 0]) y = x.copy() - self.assertEqual(str(x),str(y)) + self.assertEqual(str(x), str(y)) + def test_changeComponent(self): """ test for the changeComponent(...)-method """ - x = Vector([1,0,0]) - x.changeComponent(0,0) - x.changeComponent(1,1) - self.assertEqual(str(x),"(0,1,0)") + x = Vector([1, 0, 0]) + x.changeComponent(0, 0) + x.changeComponent(1, 1) + self.assertEqual(str(x), "(0,1,0)") + def test_str_matrix(self): - A = Matrix([[1,2,3],[2,4,5],[6,7,8]],3,3) - self.assertEqual("|1,2,3|\n|2,4,5|\n|6,7,8|\n",str(A)) + A = Matrix([[1, 2, 3], [2, 4, 5], [6, 7, 8]], 3, 3) + self.assertEqual("|1,2,3|\n|2,4,5|\n|6,7,8|\n", str(A)) + def test__mul__matrix(self): - A = Matrix([[1,2,3],[4,5,6],[7,8,9]],3,3) - x = Vector([1,2,3]) - self.assertEqual("(14,32,50)",str(A*x)) - self.assertEqual("|2,4,6|\n|8,10,12|\n|14,16,18|\n",str(A*2)) + A = Matrix([[1, 2, 3], [4, 5, 6], [7, 8, 9]], 3, 3) + x = Vector([1, 2, 3]) + self.assertEqual("(14,32,50)", str(A * x)) + self.assertEqual("|2,4,6|\n|8,10,12|\n|14,16,18|\n", str(A * 2)) + def test_changeComponent_matrix(self): - A = Matrix([[1,2,3],[2,4,5],[6,7,8]],3,3) - A.changeComponent(0,2,5) - self.assertEqual("|1,2,5|\n|2,4,5|\n|6,7,8|\n",str(A)) + A = Matrix([[1, 2, 3], [2, 4, 5], [6, 7, 8]], 3, 3) + A.changeComponent(0, 2, 5) + self.assertEqual("|1,2,5|\n|2,4,5|\n|6,7,8|\n", str(A)) + def test_component_matrix(self): - A = Matrix([[1,2,3],[2,4,5],[6,7,8]],3,3) - self.assertEqual(7,A.component(2,1),0.01) + A = Matrix([[1, 2, 3], [2, 4, 5], [6, 7, 8]], 3, 3) + self.assertEqual(7, A.component(2, 1), 0.01) + def test__add__matrix(self): - A = Matrix([[1,2,3],[2,4,5],[6,7,8]],3,3) - B = Matrix([[1,2,7],[2,4,5],[6,7,10]],3,3) - self.assertEqual("|2,4,10|\n|4,8,10|\n|12,14,18|\n",str(A+B)) + A = Matrix([[1, 2, 3], [2, 4, 5], [6, 7, 8]], 3, 3) + B = Matrix([[1, 2, 7], [2, 4, 5], [6, 7, 10]], 3, 3) + self.assertEqual("|2,4,10|\n|4,8,10|\n|12,14,18|\n", str(A + B)) + def test__sub__matrix(self): - A = Matrix([[1,2,3],[2,4,5],[6,7,8]],3,3) - B = Matrix([[1,2,7],[2,4,5],[6,7,10]],3,3) - self.assertEqual("|0,0,-4|\n|0,0,0|\n|0,0,-2|\n",str(A-B)) + A = Matrix([[1, 2, 3], [2, 4, 5], [6, 7, 8]], 3, 3) + B = Matrix([[1, 2, 7], [2, 4, 5], [6, 7, 10]], 3, 3) + self.assertEqual("|0,0,-4|\n|0,0,0|\n|0,0,-2|\n", str(A - B)) + def test_squareZeroMatrix(self): - self.assertEqual('|0,0,0,0,0|\n|0,0,0,0,0|\n|0,0,0,0,0|\n|0,0,0,0,0|' - +'\n|0,0,0,0,0|\n',str(squareZeroMatrix(5))) - + self.assertEqual( + "|0,0,0,0,0|\n|0,0,0,0,0|\n|0,0,0,0,0|\n|0,0,0,0,0|" + "\n|0,0,0,0,0|\n", + str(squareZeroMatrix(5)), + ) + if __name__ == "__main__": unittest.main() diff --git a/machine_learning/decision_tree.py b/machine_learning/decision_tree.py index acdf646875ac..4f7a4d12966e 100644 --- a/machine_learning/decision_tree.py +++ b/machine_learning/decision_tree.py @@ -5,8 +5,9 @@ """ import numpy as np + class Decision_Tree: - def __init__(self, depth = 5, min_leaf_size = 5): + def __init__(self, depth=5, min_leaf_size=5): self.depth = depth self.decision_boundary = 0 self.left = None @@ -58,8 +59,7 @@ def train(self, X, y): return best_split = 0 - min_error = self.mean_squared_error(X,np.mean(y)) * 2 - + min_error = self.mean_squared_error(X, np.mean(y)) * 2 """ loop over all possible splits for the decision tree. find the best split. @@ -86,8 +86,12 @@ def train(self, X, y): right_y = y[best_split:] self.decision_boundary = X[best_split] - self.left = Decision_Tree(depth = self.depth - 1, min_leaf_size = self.min_leaf_size) - self.right = Decision_Tree(depth = self.depth - 1, min_leaf_size = self.min_leaf_size) + self.left = Decision_Tree( + depth=self.depth - 1, min_leaf_size=self.min_leaf_size + ) + self.right = Decision_Tree( + depth=self.depth - 1, min_leaf_size=self.min_leaf_size + ) self.left.train(left_X, left_y) self.right.train(right_X, right_y) else: @@ -113,17 +117,18 @@ def predict(self, x): print("Error: Decision tree not yet trained") return None + def main(): """ In this demonstration we're generating a sample data set from the sin function in numpy. We then train a decision tree on the data set and use the decision tree to predict the label of 10 different test values. Then the mean squared error over this test is displayed. """ - X = np.arange(-1., 1., 0.005) + X = np.arange(-1.0, 1.0, 0.005) y = np.sin(X) - tree = Decision_Tree(depth = 10, min_leaf_size = 10) - tree.train(X,y) + tree = Decision_Tree(depth=10, min_leaf_size=10) + tree.train(X, y) test_cases = (np.random.rand(10) * 2) - 1 predictions = np.array([tree.predict(x) for x in test_cases]) @@ -134,5 +139,5 @@ def main(): print("Average error: " + str(avg_error)) -if __name__ == '__main__': - main() \ No newline at end of file +if __name__ == "__main__": + main() diff --git a/machine_learning/gradient_descent.py b/machine_learning/gradient_descent.py index 9a17113b7ddb..811cc68467f9 100644 --- a/machine_learning/gradient_descent.py +++ b/machine_learning/gradient_descent.py @@ -4,21 +4,28 @@ import numpy # List of input, output pairs -train_data = (((5, 2, 3), 15), ((6, 5, 9), 25), - ((11, 12, 13), 41), ((1, 1, 1), 8), ((11, 12, 13), 41)) +train_data = ( + ((5, 2, 3), 15), + ((6, 5, 9), 25), + ((11, 12, 13), 41), + ((1, 1, 1), 8), + ((11, 12, 13), 41), +) test_data = (((515, 22, 13), 555), ((61, 35, 49), 150)) parameter_vector = [2, 4, 1, 5] m = len(train_data) LEARNING_RATE = 0.009 -def _error(example_no, data_set='train'): +def _error(example_no, data_set="train"): """ :param data_set: train data or test data :param example_no: example number whose error has to be checked :return: error in example pointed by example number. """ - return calculate_hypothesis_value(example_no, data_set) - output(example_no, data_set) + return calculate_hypothesis_value(example_no, data_set) - output( + example_no, data_set + ) def _hypothesis_value(data_input_tuple): @@ -32,7 +39,7 @@ def _hypothesis_value(data_input_tuple): """ hyp_val = 0 for i in range(len(parameter_vector) - 1): - hyp_val += data_input_tuple[i]*parameter_vector[i+1] + hyp_val += data_input_tuple[i] * parameter_vector[i + 1] hyp_val += parameter_vector[0] return hyp_val @@ -43,9 +50,9 @@ def output(example_no, data_set): :param example_no: example whose output is to be fetched :return: output for that example """ - if data_set == 'train': + if data_set == "train": return train_data[example_no][1] - elif data_set == 'test': + elif data_set == "test": return test_data[example_no][1] @@ -75,7 +82,7 @@ def summation_of_cost_derivative(index, end=m): if index == -1: summation_value += _error(i) else: - summation_value += _error(i)*train_data[i][0][index] + summation_value += _error(i) * train_data[i][0][index] return summation_value @@ -85,7 +92,7 @@ def get_cost_derivative(index): :return: derivative wrt to that index Note: If index is -1, this means we are calculating summation wrt to biased parameter. """ - cost_derivative_value = summation_of_cost_derivative(index, m)/m + cost_derivative_value = summation_of_cost_derivative(index, m) / m return cost_derivative_value @@ -99,11 +106,16 @@ def run_gradient_descent(): j += 1 temp_parameter_vector = [0, 0, 0, 0] for i in range(0, len(parameter_vector)): - cost_derivative = get_cost_derivative(i-1) - temp_parameter_vector[i] = parameter_vector[i] - \ - LEARNING_RATE*cost_derivative - if numpy.allclose(parameter_vector, temp_parameter_vector, - atol=absolute_error_limit, rtol=relative_error_limit): + cost_derivative = get_cost_derivative(i - 1) + temp_parameter_vector[i] = ( + parameter_vector[i] - LEARNING_RATE * cost_derivative + ) + if numpy.allclose( + parameter_vector, + temp_parameter_vector, + atol=absolute_error_limit, + rtol=relative_error_limit, + ): break parameter_vector = temp_parameter_vector print(("Number of iterations:", j)) @@ -111,11 +123,11 @@ def run_gradient_descent(): def test_gradient_descent(): for i in range(len(test_data)): - print(("Actual output value:", output(i, 'test'))) - print(("Hypothesis output:", calculate_hypothesis_value(i, 'test'))) + print(("Actual output value:", output(i, "test"))) + print(("Hypothesis output:", calculate_hypothesis_value(i, "test"))) -if __name__ == '__main__': +if __name__ == "__main__": run_gradient_descent() print("\nTesting gradient descent for a linear hypothesis function.\n") test_gradient_descent() diff --git a/machine_learning/k_means_clust.py b/machine_learning/k_means_clust.py index d0ce0f2599e0..4c643226b213 100644 --- a/machine_learning/k_means_clust.py +++ b/machine_learning/k_means_clust.py @@ -1,4 +1,4 @@ -'''README, Author - Anurag Kumar(mailto:anuragkumarak95@gmail.com) +"""README, Author - Anurag Kumar(mailto:anuragkumarak95@gmail.com) Requirements: - sklearn @@ -45,17 +45,18 @@ 5. Have fun.. -''' +""" from sklearn.metrics import pairwise_distances import numpy as np -TAG = 'K-MEANS-CLUST/ ' +TAG = "K-MEANS-CLUST/ " + def get_initial_centroids(data, k, seed=None): - '''Randomly choose k data points as initial centroids''' - if seed is not None: # useful for obtaining consistent results + """Randomly choose k data points as initial centroids""" + if seed is not None: # useful for obtaining consistent results np.random.seed(seed) - n = data.shape[0] # number of data points + n = data.shape[0] # number of data points # Pick K indices from range [0, N). rand_indices = np.random.randint(0, n, k) @@ -63,30 +64,33 @@ def get_initial_centroids(data, k, seed=None): # Keep centroids as dense format, as many entries will be nonzero due to averaging. # As long as at least one document in a cluster contains a word, # it will carry a nonzero weight in the TF-IDF vector of the centroid. - centroids = data[rand_indices,:] + centroids = data[rand_indices, :] return centroids -def centroid_pairwise_dist(X,centroids): - return pairwise_distances(X,centroids,metric='euclidean') + +def centroid_pairwise_dist(X, centroids): + return pairwise_distances(X, centroids, metric="euclidean") + def assign_clusters(data, centroids): # Compute distances between each data point and the set of centroids: # Fill in the blank (RHS only) - distances_from_centroids = centroid_pairwise_dist(data,centroids) + distances_from_centroids = centroid_pairwise_dist(data, centroids) # Compute cluster assignments for each data point: # Fill in the blank (RHS only) - cluster_assignment = np.argmin(distances_from_centroids,axis=1) + cluster_assignment = np.argmin(distances_from_centroids, axis=1) return cluster_assignment + def revise_centroids(data, k, cluster_assignment): new_centroids = [] for i in range(k): # Select all data points that belong to cluster i. Fill in the blank (RHS only) - member_data_points = data[cluster_assignment==i] + member_data_points = data[cluster_assignment == i] # Compute the mean of the data points. Fill in the blank (RHS only) centroid = member_data_points.mean(axis=0) new_centroids.append(centroid) @@ -94,79 +98,102 @@ def revise_centroids(data, k, cluster_assignment): return new_centroids + def compute_heterogeneity(data, k, centroids, cluster_assignment): heterogeneity = 0.0 for i in range(k): # Select all data points that belong to cluster i. Fill in the blank (RHS only) - member_data_points = data[cluster_assignment==i, :] + member_data_points = data[cluster_assignment == i, :] - if member_data_points.shape[0] > 0: # check if i-th cluster is non-empty + if member_data_points.shape[0] > 0: # check if i-th cluster is non-empty # Compute distances from centroid to data points (RHS only) - distances = pairwise_distances(member_data_points, [centroids[i]], metric='euclidean') - squared_distances = distances**2 + distances = pairwise_distances( + member_data_points, [centroids[i]], metric="euclidean" + ) + squared_distances = distances ** 2 heterogeneity += np.sum(squared_distances) return heterogeneity + from matplotlib import pyplot as plt + + def plot_heterogeneity(heterogeneity, k): - plt.figure(figsize=(7,4)) + plt.figure(figsize=(7, 4)) plt.plot(heterogeneity, linewidth=4) - plt.xlabel('# Iterations') - plt.ylabel('Heterogeneity') - plt.title('Heterogeneity of clustering over time, K={0:d}'.format(k)) - plt.rcParams.update({'font.size': 16}) + plt.xlabel("# Iterations") + plt.ylabel("Heterogeneity") + plt.title("Heterogeneity of clustering over time, K={0:d}".format(k)) + plt.rcParams.update({"font.size": 16}) plt.show() -def kmeans(data, k, initial_centroids, maxiter=500, record_heterogeneity=None, verbose=False): - '''This function runs k-means on given data and initial set of centroids. + +def kmeans( + data, k, initial_centroids, maxiter=500, record_heterogeneity=None, verbose=False +): + """This function runs k-means on given data and initial set of centroids. maxiter: maximum number of iterations to run.(default=500) record_heterogeneity: (optional) a list, to store the history of heterogeneity as function of iterations if None, do not store the history. - verbose: if True, print how many data points changed their cluster labels in each iteration''' + verbose: if True, print how many data points changed their cluster labels in each iteration""" centroids = initial_centroids[:] prev_cluster_assignment = None for itr in range(maxiter): if verbose: - print(itr, end='') + print(itr, end="") # 1. Make cluster assignments using nearest centroids - cluster_assignment = assign_clusters(data,centroids) + cluster_assignment = assign_clusters(data, centroids) # 2. Compute a new centroid for each of the k clusters, averaging all data points assigned to that cluster. - centroids = revise_centroids(data,k, cluster_assignment) + centroids = revise_centroids(data, k, cluster_assignment) # Check for convergence: if none of the assignments changed, stop - if prev_cluster_assignment is not None and \ - (prev_cluster_assignment==cluster_assignment).all(): + if ( + prev_cluster_assignment is not None + and (prev_cluster_assignment == cluster_assignment).all() + ): break # Print number of new assignments if prev_cluster_assignment is not None: - num_changed = np.sum(prev_cluster_assignment!=cluster_assignment) + num_changed = np.sum(prev_cluster_assignment != cluster_assignment) if verbose: - print(' {0:5d} elements changed their cluster assignment.'.format(num_changed)) + print( + " {0:5d} elements changed their cluster assignment.".format( + num_changed + ) + ) # Record heterogeneity convergence metric if record_heterogeneity is not None: # YOUR CODE HERE - score = compute_heterogeneity(data,k,centroids,cluster_assignment) + score = compute_heterogeneity(data, k, centroids, cluster_assignment) record_heterogeneity.append(score) prev_cluster_assignment = cluster_assignment[:] return centroids, cluster_assignment + # Mock test below -if False: # change to true to run this test case. +if False: # change to true to run this test case. import sklearn.datasets as ds + dataset = ds.load_iris() k = 3 heterogeneity = [] - initial_centroids = get_initial_centroids(dataset['data'], k, seed=0) - centroids, cluster_assignment = kmeans(dataset['data'], k, initial_centroids, maxiter=400, - record_heterogeneity=heterogeneity, verbose=True) + initial_centroids = get_initial_centroids(dataset["data"], k, seed=0) + centroids, cluster_assignment = kmeans( + dataset["data"], + k, + initial_centroids, + maxiter=400, + record_heterogeneity=heterogeneity, + verbose=True, + ) plot_heterogeneity(heterogeneity, k) diff --git a/machine_learning/knn_sklearn.py b/machine_learning/knn_sklearn.py index 64582564304f..a371e30f5403 100644 --- a/machine_learning/knn_sklearn.py +++ b/machine_learning/knn_sklearn.py @@ -2,27 +2,30 @@ from sklearn.datasets import load_iris from sklearn.neighbors import KNeighborsClassifier -#Load iris file +# Load iris file iris = load_iris() iris.keys() -print('Target names: \n {} '.format(iris.target_names)) -print('\n Features: \n {}'.format(iris.feature_names)) +print("Target names: \n {} ".format(iris.target_names)) +print("\n Features: \n {}".format(iris.feature_names)) -#Train set e Test set -X_train, X_test, y_train, y_test = train_test_split(iris['data'],iris['target'], random_state=4) +# Train set e Test set +X_train, X_test, y_train, y_test = train_test_split( + iris["data"], iris["target"], random_state=4 +) -#KNN +# KNN -knn = KNeighborsClassifier (n_neighbors = 1) +knn = KNeighborsClassifier(n_neighbors=1) knn.fit(X_train, y_train) -#new array to test -X_new = [[1,2,1,4], - [2,3,4,5]] +# new array to test +X_new = [[1, 2, 1, 4], [2, 3, 4, 5]] prediction = knn.predict(X_new) -print('\nNew array: \n {}' - '\n\nTarget Names Prediction: \n {}'.format(X_new, iris['target_names'][prediction])) +print( + "\nNew array: \n {}" + "\n\nTarget Names Prediction: \n {}".format(X_new, iris["target_names"][prediction]) +) diff --git a/machine_learning/linear_regression.py b/machine_learning/linear_regression.py index 9d9738fced8d..b666feddccc7 100644 --- a/machine_learning/linear_regression.py +++ b/machine_learning/linear_regression.py @@ -16,21 +16,22 @@ def collect_dataset(): The dataset contains ADR vs Rating of a Player :return : dataset obtained from the link, as matrix """ - response = requests.get('https://raw.githubusercontent.com/yashLadha/' + - 'The_Math_of_Intelligence/master/Week1/ADRvs' + - 'Rating.csv') + response = requests.get( + "https://raw.githubusercontent.com/yashLadha/" + + "The_Math_of_Intelligence/master/Week1/ADRvs" + + "Rating.csv" + ) lines = response.text.splitlines() data = [] for item in lines: - item = item.split(',') + item = item.split(",") data.append(item) data.pop(0) # This is for removing the labels from the list dataset = np.matrix(data) return dataset -def run_steep_gradient_descent(data_x, data_y, - len_data, alpha, theta): +def run_steep_gradient_descent(data_x, data_y, len_data, alpha, theta): """ Run steep gradient descent and updates the Feature vector accordingly_ :param data_x : contains the dataset :param data_y : contains the output associated with each data-entry @@ -79,10 +80,9 @@ def run_linear_regression(data_x, data_y): theta = np.zeros((1, no_features)) for i in range(0, iterations): - theta = run_steep_gradient_descent(data_x, data_y, - len_data, alpha, theta) + theta = run_steep_gradient_descent(data_x, data_y, len_data, alpha, theta) error = sum_of_square_error(data_x, data_y, len_data, theta) - print('At Iteration %d - Error is %.5f ' % (i + 1, error)) + print("At Iteration %d - Error is %.5f " % (i + 1, error)) return theta @@ -97,10 +97,10 @@ def main(): theta = run_linear_regression(data_x, data_y) len_result = theta.shape[1] - print('Resultant Feature vector : ') + print("Resultant Feature vector : ") for i in range(0, len_result): - print('%.5f' % (theta[0, i])) + print("%.5f" % (theta[0, i])) -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/machine_learning/logistic_regression.py b/machine_learning/logistic_regression.py index b2749f1be260..f23d400ced55 100644 --- a/machine_learning/logistic_regression.py +++ b/machine_learning/logistic_regression.py @@ -9,8 +9,8 @@ # importing all the required libraries -''' Implementing logistic regression for classification problem - Helpful resources : 1.Coursera ML course 2.https://medium.com/@martinpella/logistic-regression-from-scratch-in-python-124c5636b8ac''' +""" Implementing logistic regression for classification problem + Helpful resources : 1.Coursera ML course 2.https://medium.com/@martinpella/logistic-regression-from-scratch-in-python-124c5636b8ac""" import numpy as np import matplotlib.pyplot as plt @@ -24,6 +24,7 @@ # sigmoid function or logistic function is used as a hypothesis function in classification problems + def sigmoid_function(z): return 1 / (1 + np.exp(-z)) @@ -31,17 +32,14 @@ def sigmoid_function(z): def cost_function(h, y): return (-y * np.log(h) - (1 - y) * np.log(1 - h)).mean() + def log_likelihood(X, Y, weights): scores = np.dot(X, weights) - return np.sum(Y*scores - np.log(1 + np.exp(scores)) ) + return np.sum(Y * scores - np.log(1 + np.exp(scores))) + # here alpha is the learning rate, X is the feature matrix,y is the target matrix -def logistic_reg( - alpha, - X, - y, - max_iterations=70000, - ): +def logistic_reg(alpha, X, y, max_iterations=70000): theta = np.zeros(X.shape[1]) for iterations in range(max_iterations): @@ -53,42 +51,35 @@ def logistic_reg( h = sigmoid_function(z) J = cost_function(h, y) if iterations % 100 == 0: - print(f'loss: {J} \t') # printing the loss after every 100 iterations + print(f"loss: {J} \t") # printing the loss after every 100 iterations return theta + # In[68]: -if __name__ == '__main__': +if __name__ == "__main__": iris = datasets.load_iris() X = iris.data[:, :2] y = (iris.target != 0) * 1 alpha = 0.1 - theta = logistic_reg(alpha,X,y,max_iterations=70000) - print("theta: ",theta) # printing the theta i.e our weights vector - + theta = logistic_reg(alpha, X, y, max_iterations=70000) + print("theta: ", theta) # printing the theta i.e our weights vector def predict_prob(X): - return sigmoid_function(np.dot(X, theta)) # predicting the value of probability from the logistic regression algorithm - + return sigmoid_function( + np.dot(X, theta) + ) # predicting the value of probability from the logistic regression algorithm plt.figure(figsize=(10, 6)) - plt.scatter(X[y == 0][:, 0], X[y == 0][:, 1], color='b', label='0') - plt.scatter(X[y == 1][:, 0], X[y == 1][:, 1], color='r', label='1') + plt.scatter(X[y == 0][:, 0], X[y == 0][:, 1], color="b", label="0") + plt.scatter(X[y == 1][:, 0], X[y == 1][:, 1], color="r", label="1") (x1_min, x1_max) = (X[:, 0].min(), X[:, 0].max()) (x2_min, x2_max) = (X[:, 1].min(), X[:, 1].max()) - (xx1, xx2) = np.meshgrid(np.linspace(x1_min, x1_max), - np.linspace(x2_min, x2_max)) + (xx1, xx2) = np.meshgrid(np.linspace(x1_min, x1_max), np.linspace(x2_min, x2_max)) grid = np.c_[xx1.ravel(), xx2.ravel()] probs = predict_prob(grid).reshape(xx1.shape) - plt.contour( - xx1, - xx2, - probs, - [0.5], - linewidths=1, - colors='black', - ) + plt.contour(xx1, xx2, probs, [0.5], linewidths=1, colors="black") plt.legend() plt.show() diff --git a/machine_learning/random_forest_classification/random_forest_classification.py b/machine_learning/random_forest_classification/random_forest_classification.py index 81016387ecc7..6aed4e6e66de 100644 --- a/machine_learning/random_forest_classification/random_forest_classification.py +++ b/machine_learning/random_forest_classification/random_forest_classification.py @@ -8,23 +8,30 @@ # Importing the dataset script_dir = os.path.dirname(os.path.realpath(__file__)) -dataset = pd.read_csv(os.path.join(script_dir, 'Social_Network_Ads.csv')) +dataset = pd.read_csv(os.path.join(script_dir, "Social_Network_Ads.csv")) X = dataset.iloc[:, [2, 3]].values y = dataset.iloc[:, 4].values # Splitting the dataset into the Training set and Test set from sklearn.model_selection import train_test_split -X_train, X_test, y_train, y_test = train_test_split(X, y, test_size = 0.25, random_state = 0) + +X_train, X_test, y_train, y_test = train_test_split( + X, y, test_size=0.25, random_state=0 +) # Feature Scaling from sklearn.preprocessing import StandardScaler + sc = StandardScaler() X_train = sc.fit_transform(X_train) X_test = sc.transform(X_test) # Fitting Random Forest Classification to the Training set from sklearn.ensemble import RandomForestClassifier -classifier = RandomForestClassifier(n_estimators = 10, criterion = 'entropy', random_state = 0) + +classifier = RandomForestClassifier( + n_estimators=10, criterion="entropy", random_state=0 +) classifier.fit(X_train, y_train) # Predicting the Test set results @@ -32,40 +39,65 @@ # Making the Confusion Matrix from sklearn.metrics import confusion_matrix + cm = confusion_matrix(y_test, y_pred) # Visualising the Training set results from matplotlib.colors import ListedColormap + X_set, y_set = X_train, y_train -X1, X2 = np.meshgrid(np.arange(start = X_set[:, 0].min() - 1, stop = X_set[:, 0].max() + 1, step = 0.01), - np.arange(start = X_set[:, 1].min() - 1, stop = X_set[:, 1].max() + 1, step = 0.01)) -plt.contourf(X1, X2, classifier.predict(np.array([X1.ravel(), X2.ravel()]).T).reshape(X1.shape), - alpha = 0.75, cmap = ListedColormap(('red', 'green'))) +X1, X2 = np.meshgrid( + np.arange(start=X_set[:, 0].min() - 1, stop=X_set[:, 0].max() + 1, step=0.01), + np.arange(start=X_set[:, 1].min() - 1, stop=X_set[:, 1].max() + 1, step=0.01), +) +plt.contourf( + X1, + X2, + classifier.predict(np.array([X1.ravel(), X2.ravel()]).T).reshape(X1.shape), + alpha=0.75, + cmap=ListedColormap(("red", "green")), +) plt.xlim(X1.min(), X1.max()) plt.ylim(X2.min(), X2.max()) for i, j in enumerate(np.unique(y_set)): - plt.scatter(X_set[y_set == j, 0], X_set[y_set == j, 1], - c = ListedColormap(('red', 'green'))(i), label = j) -plt.title('Random Forest Classification (Training set)') -plt.xlabel('Age') -plt.ylabel('Estimated Salary') + plt.scatter( + X_set[y_set == j, 0], + X_set[y_set == j, 1], + c=ListedColormap(("red", "green"))(i), + label=j, + ) +plt.title("Random Forest Classification (Training set)") +plt.xlabel("Age") +plt.ylabel("Estimated Salary") plt.legend() plt.show() # Visualising the Test set results from matplotlib.colors import ListedColormap + X_set, y_set = X_test, y_test -X1, X2 = np.meshgrid(np.arange(start = X_set[:, 0].min() - 1, stop = X_set[:, 0].max() + 1, step = 0.01), - np.arange(start = X_set[:, 1].min() - 1, stop = X_set[:, 1].max() + 1, step = 0.01)) -plt.contourf(X1, X2, classifier.predict(np.array([X1.ravel(), X2.ravel()]).T).reshape(X1.shape), - alpha = 0.75, cmap = ListedColormap(('red', 'green'))) +X1, X2 = np.meshgrid( + np.arange(start=X_set[:, 0].min() - 1, stop=X_set[:, 0].max() + 1, step=0.01), + np.arange(start=X_set[:, 1].min() - 1, stop=X_set[:, 1].max() + 1, step=0.01), +) +plt.contourf( + X1, + X2, + classifier.predict(np.array([X1.ravel(), X2.ravel()]).T).reshape(X1.shape), + alpha=0.75, + cmap=ListedColormap(("red", "green")), +) plt.xlim(X1.min(), X1.max()) plt.ylim(X2.min(), X2.max()) for i, j in enumerate(np.unique(y_set)): - plt.scatter(X_set[y_set == j, 0], X_set[y_set == j, 1], - c = ListedColormap(('red', 'green'))(i), label = j) -plt.title('Random Forest Classification (Test set)') -plt.xlabel('Age') -plt.ylabel('Estimated Salary') + plt.scatter( + X_set[y_set == j, 0], + X_set[y_set == j, 1], + c=ListedColormap(("red", "green"))(i), + label=j, + ) +plt.title("Random Forest Classification (Test set)") +plt.xlabel("Age") +plt.ylabel("Estimated Salary") plt.legend() plt.show() diff --git a/machine_learning/random_forest_regression/random_forest_regression.py b/machine_learning/random_forest_regression/random_forest_regression.py index 85ce0676b598..2599e97e957e 100644 --- a/machine_learning/random_forest_regression/random_forest_regression.py +++ b/machine_learning/random_forest_regression/random_forest_regression.py @@ -8,7 +8,7 @@ # Importing the dataset script_dir = os.path.dirname(os.path.realpath(__file__)) -dataset = pd.read_csv(os.path.join(script_dir, 'Position_Salaries.csv')) +dataset = pd.read_csv(os.path.join(script_dir, "Position_Salaries.csv")) X = dataset.iloc[:, 1:2].values y = dataset.iloc[:, 2].values @@ -26,7 +26,8 @@ # Fitting Random Forest Regression to the dataset from sklearn.ensemble import RandomForestRegressor -regressor = RandomForestRegressor(n_estimators = 10, random_state = 0) + +regressor = RandomForestRegressor(n_estimators=10, random_state=0) regressor.fit(X, y) # Predicting a new result @@ -35,9 +36,9 @@ # Visualising the Random Forest Regression results (higher resolution) X_grid = np.arange(min(X), max(X), 0.01) X_grid = X_grid.reshape((len(X_grid), 1)) -plt.scatter(X, y, color = 'red') -plt.plot(X_grid, regressor.predict(X_grid), color = 'blue') -plt.title('Truth or Bluff (Random Forest Regression)') -plt.xlabel('Position level') -plt.ylabel('Salary') +plt.scatter(X, y, color="red") +plt.plot(X_grid, regressor.predict(X_grid), color="blue") +plt.title("Truth or Bluff (Random Forest Regression)") +plt.xlabel("Position level") +plt.ylabel("Salary") plt.show() diff --git a/machine_learning/scoring_functions.py b/machine_learning/scoring_functions.py index a2d97b09ded2..2b24287b3726 100755 --- a/machine_learning/scoring_functions.py +++ b/machine_learning/scoring_functions.py @@ -14,7 +14,7 @@ and types of data """ -#Mean Absolute Error +# Mean Absolute Error def mae(predict, actual): predict = np.array(predict) actual = np.array(actual) @@ -24,7 +24,8 @@ def mae(predict, actual): return score -#Mean Squared Error + +# Mean Squared Error def mse(predict, actual): predict = np.array(predict) actual = np.array(actual) @@ -35,7 +36,8 @@ def mse(predict, actual): score = square_diff.mean() return score -#Root Mean Squared Error + +# Root Mean Squared Error def rmse(predict, actual): predict = np.array(predict) actual = np.array(actual) @@ -46,13 +48,14 @@ def rmse(predict, actual): score = np.sqrt(mean_square_diff) return score -#Root Mean Square Logarithmic Error + +# Root Mean Square Logarithmic Error def rmsle(predict, actual): predict = np.array(predict) actual = np.array(actual) - log_predict = np.log(predict+1) - log_actual = np.log(actual+1) + log_predict = np.log(predict + 1) + log_actual = np.log(actual + 1) difference = log_predict - log_actual square_diff = np.square(difference) @@ -62,14 +65,15 @@ def rmsle(predict, actual): return score -#Mean Bias Deviation + +# Mean Bias Deviation def mbd(predict, actual): predict = np.array(predict) actual = np.array(actual) difference = predict - actual - numerator = np.sum(difference) / len(predict) - denumerator = np.sum(actual) / len(predict) + numerator = np.sum(difference) / len(predict) + denumerator = np.sum(actual) / len(predict) print(numerator) print(denumerator) diff --git a/machine_learning/sequential_minimum_optimization.py b/machine_learning/sequential_minimum_optimization.py index 0b5d788e92e1..1d4e4a276bc1 100644 --- a/machine_learning/sequential_minimum_optimization.py +++ b/machine_learning/sequential_minimum_optimization.py @@ -41,11 +41,20 @@ from sklearn.datasets import make_blobs, make_circles from sklearn.preprocessing import StandardScaler -CANCER_DATASET_URL = 'http://archive.ics.uci.edu/ml/machine-learning-databases/breast-cancer-wisconsin/wdbc.data' +CANCER_DATASET_URL = "http://archive.ics.uci.edu/ml/machine-learning-databases/breast-cancer-wisconsin/wdbc.data" class SmoSVM(object): - def __init__(self, train, kernel_func, alpha_list=None, cost=0.4, b=0.0, tolerance=0.001, auto_norm=True): + def __init__( + self, + train, + kernel_func, + alpha_list=None, + cost=0.4, + b=0.0, + tolerance=0.001, + auto_norm=True, + ): self._init = True self._auto_norm = auto_norm self._c = np.float64(cost) @@ -91,13 +100,25 @@ def fit(self): self.alphas[i1], self.alphas[i2] = a1_new, a2_new # 3: update threshold(b) - b1_new = np.float64(-e1 - y1 * K(i1, i1) * (a1_new - a1) - y2 * K(i2, i1) * (a2_new - a2) + self._b) - b2_new = np.float64(-e2 - y2 * K(i2, i2) * (a2_new - a2) - y1 * K(i1, i2) * (a1_new - a1) + self._b) + b1_new = np.float64( + -e1 + - y1 * K(i1, i1) * (a1_new - a1) + - y2 * K(i2, i1) * (a2_new - a2) + + self._b + ) + b2_new = np.float64( + -e2 + - y2 * K(i2, i2) * (a2_new - a2) + - y1 * K(i1, i2) * (a1_new - a1) + + self._b + ) if 0.0 < a1_new < self._c: b = b1_new if 0.0 < a2_new < self._c: b = b2_new - if not (np.float64(0) < a2_new < self._c) and not (np.float64(0) < a1_new < self._c): + if not (np.float64(0) < a2_new < self._c) and not ( + np.float64(0) < a1_new < self._c + ): b = (b1_new + b2_new) / 2.0 b_old = self._b self._b = b @@ -107,7 +128,11 @@ def fit(self): for s in self.unbound: if s == i1 or s == i2: continue - self._error[s] += y1 * (a1_new - a1) * K(i1, s) + y2 * (a2_new - a2) * K(i2, s) + (self._b - b_old) + self._error[s] += ( + y1 * (a1_new - a1) * K(i1, s) + + y2 * (a2_new - a2) * K(i2, s) + + (self._b - b_old) + ) # if i1 or i2 is non-bound,update there error value to zero if self._is_unbound(i1): @@ -119,7 +144,9 @@ def fit(self): def predict(self, test_samples, classify=True): if test_samples.shape[1] > self.samples.shape[1]: - raise ValueError("Test samples' feature length does not equal to that of train samples") + raise ValueError( + "Test samples' feature length does not equal to that of train samples" + ) if self._auto_norm: test_samples = self._norm(test_samples) @@ -173,14 +200,23 @@ def _calculate_k_matrix(self): k_matrix = np.zeros([self.length, self.length]) for i in self._all_samples: for j in self._all_samples: - k_matrix[i, j] = np.float64(self.Kernel(self.samples[i, :], self.samples[j, :])) + k_matrix[i, j] = np.float64( + self.Kernel(self.samples[i, :], self.samples[j, :]) + ) return k_matrix # Predict test sample's tag def _predict(self, sample): k = self._k - predicted_value = np.sum( - [self.alphas[i1] * self.tags[i1] * k(i1, sample) for i1 in self._all_samples]) + self._b + predicted_value = ( + np.sum( + [ + self.alphas[i1] * self.tags[i1] * k(i1, sample) + for i1 in self._all_samples + ] + ) + + self._b + ) return predicted_value # Choose alpha1 and alpha2 @@ -200,23 +236,27 @@ def _choose_a1(self): while True: all_not_obey = True # all sample - print('scanning all sample!') + print("scanning all sample!") for i1 in [i for i in self._all_samples if self._check_obey_kkt(i)]: all_not_obey = False yield from self._choose_a2(i1) # non-bound sample - print('scanning non-bound sample!') + print("scanning non-bound sample!") while True: not_obey = True - for i1 in [i for i in self._all_samples if self._check_obey_kkt(i) and self._is_unbound(i)]: + for i1 in [ + i + for i in self._all_samples + if self._check_obey_kkt(i) and self._is_unbound(i) + ]: not_obey = False yield from self._choose_a2(i1) if not_obey: - print('all non-bound samples fit the KKT condition!') + print("all non-bound samples fit the KKT condition!") break if all_not_obey: - print('all samples fit the KKT condition! Optimization done!') + print("all samples fit the KKT condition! Optimization done!") break return False @@ -231,7 +271,11 @@ def _choose_a2(self, i1): if len(self.unbound) > 0: tmp_error = self._error.copy().tolist() - tmp_error_dict = {index: value for index, value in enumerate(tmp_error) if self._is_unbound(index)} + tmp_error_dict = { + index: value + for index, value in enumerate(tmp_error) + if self._is_unbound(index) + } if self._e(i1) >= 0: i2 = min(tmp_error_dict, key=lambda index: tmp_error_dict[index]) else: @@ -289,8 +333,20 @@ def _get_new_alpha(self, i1, i2, a1, a2, e1, e2, y1, y2): # way 1 f1 = y1 * (e1 + b) - a1 * K(i1, i1) - s * a2 * K(i1, i2) f2 = y2 * (e2 + b) - a2 * K(i2, i2) - s * a1 * K(i1, i2) - ol = l1 * f1 + L * f2 + 1 / 2 * l1 ** 2 * K(i1, i1) + 1 / 2 * L ** 2 * K(i2, i2) + s * L * l1 * K(i1, i2) - oh = h1 * f1 + H * f2 + 1 / 2 * h1 ** 2 * K(i1, i1) + 1 / 2 * H ** 2 * K(i2, i2) + s * H * h1 * K(i1, i2) + ol = ( + l1 * f1 + + L * f2 + + 1 / 2 * l1 ** 2 * K(i1, i1) + + 1 / 2 * L ** 2 * K(i2, i2) + + s * L * l1 * K(i1, i2) + ) + oh = ( + h1 * f1 + + H * f2 + + 1 / 2 * h1 ** 2 * K(i1, i1) + + 1 / 2 * H ** 2 * K(i2, i2) + + s * H * h1 * K(i1, i2) + ) """ # way 2 Use objective function check which alpha2 new could get the minimal objectives @@ -370,14 +426,10 @@ def _rbf(self, v1, v2): def _check(self): if self._kernel == self._rbf: if self.gamma < 0: - raise ValueError('gamma value must greater than 0') + raise ValueError("gamma value must greater than 0") def _get_kernel(self, kernel_name): - maps = { - 'linear': self._linear, - 'poly': self._polynomial, - 'rbf': self._rbf - } + maps = {"linear": self._linear, "poly": self._polynomial, "rbf": self._rbf} return maps[kernel_name] def __call__(self, v1, v2): @@ -390,34 +442,35 @@ def __repr__(self): def count_time(func): def call_func(*args, **kwargs): import time + start_time = time.time() func(*args, **kwargs) end_time = time.time() - print('smo algorithm cost {} seconds'.format(end_time - start_time)) + print("smo algorithm cost {} seconds".format(end_time - start_time)) return call_func @count_time def test_cancel_data(): - print('Hello!\r\nStart test svm by smo algorithm!') + print("Hello!\r\nStart test svm by smo algorithm!") # 0: download dataset and load into pandas' dataframe - if not os.path.exists(r'cancel_data.csv'): + if not os.path.exists(r"cancel_data.csv"): request = urllib.request.Request( CANCER_DATASET_URL, - headers={'User-Agent': 'Mozilla/4.0 (compatible; MSIE 5.5; Windows NT)'} + headers={"User-Agent": "Mozilla/4.0 (compatible; MSIE 5.5; Windows NT)"}, ) response = urllib.request.urlopen(request) - content = response.read().decode('utf-8') - with open(r'cancel_data.csv', 'w') as f: + content = response.read().decode("utf-8") + with open(r"cancel_data.csv", "w") as f: f.write(content) - data = pd.read_csv(r'cancel_data.csv', header=None) + data = pd.read_csv(r"cancel_data.csv", header=None) # 1: pre-processing data del data[data.columns.tolist()[0]] data = data.dropna(axis=0) - data = data.replace({'M': np.float64(1), 'B': np.float64(-1)}) + data = data.replace({"M": np.float64(1), "B": np.float64(-1)}) samples = np.array(data)[:, :] # 2: deviding data into train_data data and test_data data @@ -425,11 +478,18 @@ def test_cancel_data(): test_tags, test_samples = test_data[:, 0], test_data[:, 1:] # 3: choose kernel function,and set initial alphas to zero(optional) - mykernel = Kernel(kernel='rbf', degree=5, coef0=1, gamma=0.5) + mykernel = Kernel(kernel="rbf", degree=5, coef0=1, gamma=0.5) al = np.zeros(train_data.shape[0]) # 4: calculating best alphas using SMO algorithm and predict test_data samples - mysvm = SmoSVM(train=train_data, kernel_func=mykernel, alpha_list=al, cost=0.4, b=0.0, tolerance=0.001) + mysvm = SmoSVM( + train=train_data, + kernel_func=mykernel, + alpha_list=al, + cost=0.4, + b=0.0, + tolerance=0.001, + ) mysvm.fit() predict = mysvm.predict(test_samples) @@ -439,14 +499,18 @@ def test_cancel_data(): for i in range(test_tags.shape[0]): if test_tags[i] == predict[i]: score += 1 - print('\r\nall: {}\r\nright: {}\r\nfalse: {}'.format(test_num, score, test_num - score)) + print( + "\r\nall: {}\r\nright: {}\r\nfalse: {}".format( + test_num, score, test_num - score + ) + ) print("Rough Accuracy: {}".format(score / test_tags.shape[0])) def test_demonstration(): # change stdout - print('\r\nStart plot,please wait!!!') - sys.stdout = open(os.devnull, 'w') + print("\r\nStart plot,please wait!!!") + sys.stdout = open(os.devnull, "w") ax1 = plt.subplot2grid((2, 2), (0, 0)) ax2 = plt.subplot2grid((2, 2), (0, 1)) @@ -464,32 +528,50 @@ def test_demonstration(): sys.stdout = sys.__stdout__ print("Plot done!!!") + def test_linear_kernel(ax, cost): - train_x, train_y = make_blobs(n_samples=500, centers=2, - n_features=2, random_state=1) + train_x, train_y = make_blobs( + n_samples=500, centers=2, n_features=2, random_state=1 + ) train_y[train_y == 0] = -1 scaler = StandardScaler() train_x_scaled = scaler.fit_transform(train_x, train_y) train_data = np.hstack((train_y.reshape(500, 1), train_x_scaled)) - mykernel = Kernel(kernel='linear', degree=5, coef0=1, gamma=0.5) - mysvm = SmoSVM(train=train_data, kernel_func=mykernel, cost=cost, tolerance=0.001, auto_norm=False) + mykernel = Kernel(kernel="linear", degree=5, coef0=1, gamma=0.5) + mysvm = SmoSVM( + train=train_data, + kernel_func=mykernel, + cost=cost, + tolerance=0.001, + auto_norm=False, + ) mysvm.fit() plot_partition_boundary(mysvm, train_data, ax=ax) def test_rbf_kernel(ax, cost): - train_x, train_y = make_circles(n_samples=500, noise=0.1, factor=0.1, random_state=1) + train_x, train_y = make_circles( + n_samples=500, noise=0.1, factor=0.1, random_state=1 + ) train_y[train_y == 0] = -1 scaler = StandardScaler() train_x_scaled = scaler.fit_transform(train_x, train_y) train_data = np.hstack((train_y.reshape(500, 1), train_x_scaled)) - mykernel = Kernel(kernel='rbf', degree=5, coef0=1, gamma=0.5) - mysvm = SmoSVM(train=train_data, kernel_func=mykernel, cost=cost, tolerance=0.001, auto_norm=False) + mykernel = Kernel(kernel="rbf", degree=5, coef0=1, gamma=0.5) + mysvm = SmoSVM( + train=train_data, + kernel_func=mykernel, + cost=cost, + tolerance=0.001, + auto_norm=False, + ) mysvm.fit() plot_partition_boundary(mysvm, train_data, ax=ax) -def plot_partition_boundary(model, train_data, ax, resolution=100, colors=('b', 'k', 'r')): +def plot_partition_boundary( + model, train_data, ax, resolution=100, colors=("b", "k", "r") +): """ We can not get the optimum w of our kernel svm model which is different from linear svm. For this reason, we generate randomly destributed points with high desity and prediced values of these points are @@ -502,25 +584,44 @@ def plot_partition_boundary(model, train_data, ax, resolution=100, colors=('b', train_data_tags = train_data[:, 0] xrange = np.linspace(train_data_x.min(), train_data_x.max(), resolution) yrange = np.linspace(train_data_y.min(), train_data_y.max(), resolution) - test_samples = np.array([(x, y) for x in xrange for y in yrange]).reshape(resolution * resolution, 2) + test_samples = np.array([(x, y) for x in xrange for y in yrange]).reshape( + resolution * resolution, 2 + ) test_tags = model.predict(test_samples, classify=False) grid = test_tags.reshape((len(xrange), len(yrange))) # Plot contour map which represents the partition boundary - ax.contour(xrange, yrange, np.mat(grid).T, levels=(-1, 0, 1), linestyles=('--', '-', '--'), - linewidths=(1, 1, 1), - colors=colors) + ax.contour( + xrange, + yrange, + np.mat(grid).T, + levels=(-1, 0, 1), + linestyles=("--", "-", "--"), + linewidths=(1, 1, 1), + colors=colors, + ) # Plot all train samples - ax.scatter(train_data_x, train_data_y, c=train_data_tags, cmap=plt.cm.Dark2, lw=0, alpha=0.5) + ax.scatter( + train_data_x, + train_data_y, + c=train_data_tags, + cmap=plt.cm.Dark2, + lw=0, + alpha=0.5, + ) # Plot support vectors support = model.support - ax.scatter(train_data_x[support], train_data_y[support], c=train_data_tags[support], cmap=plt.cm.Dark2) + ax.scatter( + train_data_x[support], + train_data_y[support], + c=train_data_tags[support], + cmap=plt.cm.Dark2, + ) -if __name__ == '__main__': +if __name__ == "__main__": test_cancel_data() test_demonstration() plt.show() - diff --git a/maths/3n+1.py b/maths/3n+1.py index d6c14ff0f47d..6b2dfc785794 100644 --- a/maths/3n+1.py +++ b/maths/3n+1.py @@ -1,6 +1,7 @@ from typing import Tuple, List -def n31(a: int) -> Tuple[List[int], int]: + +def n31(a: int) -> Tuple[List[int], int]: """ Returns the Collatz sequence and its length of any postiver integer. >>> n31(4) @@ -8,23 +9,29 @@ def n31(a: int) -> Tuple[List[int], int]: """ if not isinstance(a, int): - raise TypeError('Must be int, not {0}'.format(type(a).__name__)) - if a < 1: - raise ValueError('Given integer must be greater than 1, not {0}'.format(a)) - + raise TypeError("Must be int, not {0}".format(type(a).__name__)) + if a < 1: + raise ValueError("Given integer must be greater than 1, not {0}".format(a)) + path = [a] while a != 1: if a % 2 == 0: a = a // 2 else: - a = 3*a +1 + a = 3 * a + 1 path += [a] return path, len(path) + def main(): num = 4 - path , length = n31(num) - print("The Collatz sequence of {0} took {1} steps. \nPath: {2}".format(num,length, path)) + path, length = n31(num) + print( + "The Collatz sequence of {0} took {1} steps. \nPath: {2}".format( + num, length, path + ) + ) + -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/maths/abs.py b/maths/abs.py index 2734e58ceee6..4d15ee6e82a8 100644 --- a/maths/abs.py +++ b/maths/abs.py @@ -22,5 +22,5 @@ def main(): print(abs_val(-34)) # = 34 -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/maths/abs_max.py b/maths/abs_max.py index 28f631f0100e..554e27f6ee66 100644 --- a/maths/abs_max.py +++ b/maths/abs_max.py @@ -1,4 +1,5 @@ -from typing import List +from typing import List + def abs_max(x: List[int]) -> int: """ @@ -7,12 +8,13 @@ def abs_max(x: List[int]) -> int: >>> abs_max([3,-10,-2]) -10 """ - j =x[0] + j = x[0] for i in x: if abs(i) > abs(j): j = i return j + def abs_max_sort(x): """ >>> abs_max_sort([0,5,1,11]) @@ -20,13 +22,14 @@ def abs_max_sort(x): >>> abs_max_sort([3,-10,-2]) -10 """ - return sorted(x,key=abs)[-1] + return sorted(x, key=abs)[-1] + def main(): - a = [1,2,-11] + a = [1, 2, -11] assert abs_max(a) == -11 assert abs_max_sort(a) == -11 -if __name__ == '__main__': - main() +if __name__ == "__main__": + main() diff --git a/maths/abs_min.py b/maths/abs_min.py index abb0c9051b7d..eb84de37ce23 100644 --- a/maths/abs_min.py +++ b/maths/abs_min.py @@ -16,9 +16,9 @@ def absMin(x): def main(): - a = [-3,-1,2,-11] + a = [-3, -1, 2, -11] print(absMin(a)) # = -1 -if __name__ == '__main__': - main() \ No newline at end of file +if __name__ == "__main__": + main() diff --git a/maths/average_mean.py b/maths/average_mean.py index 78387111022d..e04b63be0e19 100644 --- a/maths/average_mean.py +++ b/maths/average_mean.py @@ -16,5 +16,5 @@ def main(): average([2, 4, 6, 8, 20, 50, 70]) -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/maths/average_median.py b/maths/average_median.py index eab0107d8da8..ccb250d7718c 100644 --- a/maths/average_median.py +++ b/maths/average_median.py @@ -24,11 +24,13 @@ def median(nums): med = sorted_list[mid_index] return med + def main(): print("Odd number of numbers:") print(median([2, 4, 6, 8, 20, 50, 70])) print("Even number of numbers:") print(median([2, 4, 6, 8, 20, 50])) -if __name__ == '__main__': + +if __name__ == "__main__": main() diff --git a/maths/basic_maths.py b/maths/basic_maths.py index cd7bac0113b8..34ffd1031527 100644 --- a/maths/basic_maths.py +++ b/maths/basic_maths.py @@ -49,7 +49,7 @@ def sum_of_divisors(n): temp += 1 n = int(n / 2) if temp > 1: - s *= (2**temp - 1) / (2 - 1) + s *= (2 ** temp - 1) / (2 - 1) for i in range(3, int(math.sqrt(n)) + 1, 2): temp = 1 @@ -57,7 +57,7 @@ def sum_of_divisors(n): temp += 1 n = int(n / i) if temp > 1: - s *= (i**temp - 1) / (i - 1) + s *= (i ** temp - 1) / (i - 1) return s @@ -80,5 +80,5 @@ def main(): print(euler_phi(100)) -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/maths/binary_exponentiation.py b/maths/binary_exponentiation.py index a8d736adfea0..57c4b8686f5c 100644 --- a/maths/binary_exponentiation.py +++ b/maths/binary_exponentiation.py @@ -6,10 +6,10 @@ def binary_exponentiation(a, n): - if (n == 0): + if n == 0: return 1 - elif (n % 2 == 1): + elif n % 2 == 1: return binary_exponentiation(a, n - 1) * a else: diff --git a/maths/collatz_sequence.py b/maths/collatz_sequence.py index 9f88453d518b..c83da3f0f0e8 100644 --- a/maths/collatz_sequence.py +++ b/maths/collatz_sequence.py @@ -10,10 +10,10 @@ def collatz_sequence(n): """ sequence = [n] while n != 1: - if n % 2 == 0:# even - n //= 2 + if n % 2 == 0: # even + n //= 2 else: - n = 3*n +1 + n = 3 * n + 1 sequence.append(n) return sequence @@ -22,7 +22,8 @@ def main(): n = 43 sequence = collatz_sequence(n) print(sequence) - print("collatz sequence from %d took %d steps."%(n,len(sequence))) + print("collatz sequence from %d took %d steps." % (n, len(sequence))) -if __name__ == '__main__': + +if __name__ == "__main__": main() diff --git a/maths/extended_euclidean_algorithm.py b/maths/extended_euclidean_algorithm.py index fc3798e7e432..fe81bcfaf71d 100644 --- a/maths/extended_euclidean_algorithm.py +++ b/maths/extended_euclidean_algorithm.py @@ -61,12 +61,12 @@ def extended_euclidean_algorithm(m, n): def main(): """Call Extended Euclidean Algorithm.""" if len(sys.argv) < 3: - print('2 integer arguments required') + print("2 integer arguments required") exit(1) m = int(sys.argv[1]) n = int(sys.argv[2]) print(extended_euclidean_algorithm(m, n)) -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/maths/factorial_recursive.py b/maths/factorial_recursive.py index 06173dcbcd7d..f346c65f1962 100644 --- a/maths/factorial_recursive.py +++ b/maths/factorial_recursive.py @@ -11,4 +11,4 @@ def fact(n): where i ranges from 1 to 20. """ for i in range(1, 21): - print(i, ": ", fact(i), sep='') + print(i, ": ", fact(i), sep="") diff --git a/maths/fermat_little_theorem.py b/maths/fermat_little_theorem.py index 8cf60dafe3ca..24d558115795 100644 --- a/maths/fermat_little_theorem.py +++ b/maths/fermat_little_theorem.py @@ -6,10 +6,10 @@ def binary_exponentiation(a, n, mod): - if (n == 0): + if n == 0: return 1 - elif (n % 2 == 1): + elif n % 2 == 1: return (binary_exponentiation(a, n - 1, mod) * a) % mod else: diff --git a/maths/fibonacci.py b/maths/fibonacci.py index 0a0611f21379..5ba9f6636364 100644 --- a/maths/fibonacci.py +++ b/maths/fibonacci.py @@ -21,10 +21,11 @@ def timer_wrapper(*args, **kwargs): func(*args, **kwargs) end = time.time() if int(end - start) > 0: - print(f'Run time for {func.__name__}: {(end - start):0.2f}s') + print(f"Run time for {func.__name__}: {(end - start):0.2f}s") else: - print(f'Run time for {func.__name__}: {(end - start)*1000:0.2f}ms') + print(f"Run time for {func.__name__}: {(end - start)*1000:0.2f}ms") return func(*args, **kwargs) + return timer_wrapper @@ -69,9 +70,13 @@ def _check_number_input(n, min_thresh, max_thresh=None): except ValueLessThanZero: print("Incorrect Input: number must not be less than 0") except ValueTooSmallError: - print(f'Incorrect Input: input number must be > {min_thresh} for the recursive calculation') + print( + f"Incorrect Input: input number must be > {min_thresh} for the recursive calculation" + ) except ValueTooLargeError: - print(f'Incorrect Input: input number must be < {max_thresh} for the recursive calculation') + print( + f"Incorrect Input: input number must be < {max_thresh} for the recursive calculation" + ) return False @@ -86,8 +91,8 @@ def fib_iterative(n): if _check_number_input(n, 2): seq_out = [0, 1] a, b = 0, 1 - for _ in range(n-len(seq_out)): - a, b = b, a+b + for _ in range(n - len(seq_out)): + a, b = b, a + b seq_out.append(b) return seq_out @@ -106,12 +111,14 @@ def fib_formula(n): phi_1 = Decimal(1 + sqrt) / Decimal(2) phi_2 = Decimal(1 - sqrt) / Decimal(2) for i in range(2, n): - temp_out = ((phi_1**Decimal(i)) - (phi_2**Decimal(i))) * (Decimal(sqrt) ** Decimal(-1)) + temp_out = ((phi_1 ** Decimal(i)) - (phi_2 ** Decimal(i))) * ( + Decimal(sqrt) ** Decimal(-1) + ) seq_out.append(int(temp_out)) return seq_out -if __name__ == '__main__': +if __name__ == "__main__": num = 20 # print(f'{fib_recursive(num)}\n') # print(f'{fib_iterative(num)}\n') diff --git a/maths/fibonacci_sequence_recursion.py b/maths/fibonacci_sequence_recursion.py index 9190e7fc7a40..3a565a458631 100644 --- a/maths/fibonacci_sequence_recursion.py +++ b/maths/fibonacci_sequence_recursion.py @@ -1,14 +1,17 @@ # Fibonacci Sequence Using Recursion + def recur_fibo(n): if n <= 1: return n else: - (recur_fibo(n-1) + recur_fibo(n-2)) + (recur_fibo(n - 1) + recur_fibo(n - 2)) + def isPositiveInteger(limit): return limit >= 0 + def main(): limit = int(input("How many terms to include in fibonacci series: ")) if isPositiveInteger(limit): @@ -17,5 +20,6 @@ def main(): else: print("Please enter a positive integer: ") -if __name__ == '__main__': + +if __name__ == "__main__": main() diff --git a/maths/find_lcm.py b/maths/find_lcm.py index f7ac958070b5..dffadd1f3c5b 100644 --- a/maths/find_lcm.py +++ b/maths/find_lcm.py @@ -10,14 +10,14 @@ def find_lcm(num_1, num_2): >>> find_lcm(12,76) 228 """ - if num_1>=num_2: - max_num=num_1 + if num_1 >= num_2: + max_num = num_1 else: - max_num=num_2 - + max_num = num_2 + lcm = max_num while True: - if ((lcm % num_1 == 0) and (lcm % num_2 == 0)): + if (lcm % num_1 == 0) and (lcm % num_2 == 0): break lcm += max_num return lcm @@ -25,10 +25,10 @@ def find_lcm(num_1, num_2): def main(): """Use test numbers to run the find_lcm algorithm.""" - num_1 = int(input().strip()) - num_2 = int(input().strip()) + num_1 = int(input().strip()) + num_2 = int(input().strip()) print(find_lcm(num_1, num_2)) -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/maths/find_max.py b/maths/find_max.py index 0ce49a68c348..7cc82aacfb09 100644 --- a/maths/find_max.py +++ b/maths/find_max.py @@ -1,14 +1,17 @@ # NguyenU + def find_max(nums): max = nums[0] for x in nums: - if x > max: - max = x + if x > max: + max = x print(max) + def main(): - find_max([2, 4, 9, 7, 19, 94, 5]) + find_max([2, 4, 9, 7, 19, 94, 5]) + -if __name__ == '__main__': - main() +if __name__ == "__main__": + main() diff --git a/maths/find_min.py b/maths/find_min.py index c720da268a25..e24982a9369b 100644 --- a/maths/find_min.py +++ b/maths/find_min.py @@ -3,6 +3,7 @@ def main(): """Find Minimum Number in a List.""" + def find_min(x): min_num = x[0] for i in x: @@ -13,5 +14,5 @@ def find_min(x): print(find_min([0, 1, 2, 3, 4, 5, -3, 24, -56])) # = -56 -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/maths/gaussian.py b/maths/gaussian.py index f3a47a3f6a1b..e5f55dfaffd1 100644 --- a/maths/gaussian.py +++ b/maths/gaussian.py @@ -1,4 +1,3 @@ - """ Reference: https://en.wikipedia.org/wiki/Gaussian_function @@ -9,7 +8,6 @@ from numpy import pi, sqrt, exp - def gaussian(x, mu: float = 0.0, sigma: float = 1.0) -> int: """ >>> gaussian(1) diff --git a/maths/greater_common_divisor.py b/maths/greater_common_divisor.py index adc7811e8317..ec608488a61f 100644 --- a/maths/greater_common_divisor.py +++ b/maths/greater_common_divisor.py @@ -13,7 +13,7 @@ def gcd(a, b): def main(): """Call GCD Function.""" try: - nums = input("Enter two Integers separated by comma (,): ").split(',') + nums = input("Enter two Integers separated by comma (,): ").split(",") num_1 = int(nums[0]) num_2 = int(nums[1]) except (IndexError, UnboundLocalError, ValueError): @@ -21,5 +21,5 @@ def main(): print(f"gcd({num_1}, {num_2}) = {gcd(num_1, num_2)}") -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/maths/lucas_series.py b/maths/lucas_series.py index 9ae437dc9f54..22ad893a6567 100644 --- a/maths/lucas_series.py +++ b/maths/lucas_series.py @@ -1,5 +1,6 @@ # Lucas Sequence Using Recursion + def recur_luc(n): """ >>> recur_luc(1) diff --git a/maths/matrix_exponentiation.py b/maths/matrix_exponentiation.py index f80f6c3cad5e..c20292735a92 100644 --- a/maths/matrix_exponentiation.py +++ b/maths/matrix_exponentiation.py @@ -85,8 +85,9 @@ def simple_fibonacci_time(): """ code = "simple_fibonacci(randint(1,70000), 1, 1)" exec_time = timeit.timeit(setup=setup, stmt=code, number=100) - print("Without matrix exponentiation the average execution time is ", - exec_time / 100) + print( + "Without matrix exponentiation the average execution time is ", exec_time / 100 + ) return exec_time diff --git a/maths/modular_exponential.py b/maths/modular_exponential.py index 750de7cba99e..8715e17147ff 100644 --- a/maths/modular_exponential.py +++ b/maths/modular_exponential.py @@ -21,5 +21,5 @@ def main(): print(modular_exponential(3, 200, 13)) -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/maths/newton_raphson.py b/maths/newton_raphson.py index d89f264acdd8..093cc4438416 100644 --- a/maths/newton_raphson.py +++ b/maths/newton_raphson.py @@ -1,4 +1,4 @@ -''' +""" Author: P Shreyas Shetty Implementation of Newton-Raphson method for solving equations of kind f(x) = 0. It is an iterative method where solution is found by the expression @@ -6,27 +6,29 @@ If no solution exists, then either the solution will not be found when iteration limit is reached or the gradient f'(x[n]) approaches zero. In both cases, exception is raised. If iteration limit is reached, try increasing maxiter. - ''' + """ import math as m + def calc_derivative(f, a, h=0.001): - ''' + """ Calculates derivative at point a for function f using finite difference method - ''' - return (f(a+h)-f(a-h))/(2*h) + """ + return (f(a + h) - f(a - h)) / (2 * h) + + +def newton_raphson(f, x0=0, maxiter=100, step=0.0001, maxerror=1e-6, logsteps=False): -def newton_raphson(f, x0=0, maxiter=100, step=0.0001, maxerror=1e-6,logsteps=False): - - a = x0 #set the initial guess + a = x0 # set the initial guess steps = [a] error = abs(f(a)) - f1 = lambda x:calc_derivative(f, x, h=step) #Derivative of f(x) + f1 = lambda x: calc_derivative(f, x, h=step) # Derivative of f(x) for _ in range(maxiter): if f1(a) == 0: raise ValueError("No converging solution found") - a = a - f(a)/f1(a) #Calculate the next estimate + a = a - f(a) / f1(a) # Calculate the next estimate if logsteps: steps.append(a) if error < maxerror: @@ -34,14 +36,18 @@ def newton_raphson(f, x0=0, maxiter=100, step=0.0001, maxerror=1e-6,logsteps=Fal else: raise ValueError("Iteration limit reached, no converging solution found") if logsteps: - #If logstep is true, then log intermediate steps + # If logstep is true, then log intermediate steps return a, error, steps return a, error - -if __name__ == '__main__': + + +if __name__ == "__main__": import matplotlib.pyplot as plt - f = lambda x:m.tanh(x)**2-m.exp(3*x) - solution, error, steps = newton_raphson(f, x0=10, maxiter=1000, step=1e-6, logsteps=True) + + f = lambda x: m.tanh(x) ** 2 - m.exp(3 * x) + solution, error, steps = newton_raphson( + f, x0=10, maxiter=1000, step=1e-6, logsteps=True + ) plt.plot([abs(f(x)) for x in steps]) plt.xlabel("step") plt.ylabel("error") diff --git a/maths/polynomial_evaluation.py b/maths/polynomial_evaluation.py index b4f18b9fa106..3c91ecd93031 100644 --- a/maths/polynomial_evaluation.py +++ b/maths/polynomial_evaluation.py @@ -11,7 +11,7 @@ def evaluate_poly(poly, x): 79800.0 """ - return sum(c*(x**i) for i, c in enumerate(poly)) + return sum(c * (x ** i) for i, c in enumerate(poly)) if __name__ == "__main__": diff --git a/maths/prime_check.py b/maths/prime_check.py index 9249834dc069..e60281228fda 100644 --- a/maths/prime_check.py +++ b/maths/prime_check.py @@ -40,12 +40,13 @@ def test_primes(self): self.assertTrue(prime_check(29)) def test_not_primes(self): - self.assertFalse(prime_check(-19), - "Negative numbers are not prime.") - self.assertFalse(prime_check(0), - "Zero doesn't have any divider, primes must have two") - self.assertFalse(prime_check(1), - "One just have 1 divider, primes must have two.") + self.assertFalse(prime_check(-19), "Negative numbers are not prime.") + self.assertFalse( + prime_check(0), "Zero doesn't have any divider, primes must have two" + ) + self.assertFalse( + prime_check(1), "One just have 1 divider, primes must have two." + ) self.assertFalse(prime_check(2 * 2)) self.assertFalse(prime_check(2 * 3)) self.assertFalse(prime_check(3 * 3)) @@ -53,5 +54,5 @@ def test_not_primes(self): self.assertFalse(prime_check(3 * 5 * 7)) -if __name__ == '__main__': +if __name__ == "__main__": unittest.main() diff --git a/maths/radix2_fft.py b/maths/radix2_fft.py index c7ffe96528b4..3911fea1d04d 100644 --- a/maths/radix2_fft.py +++ b/maths/radix2_fft.py @@ -65,12 +65,7 @@ def __init__(self, polyA=[0], polyB=[0]): # Add 0 to make lengths equal a power of 2 self.C_max_length = int( - 2 - ** np.ceil( - np.log2( - len(self.polyA) + len(self.polyB) - 1 - ) - ) + 2 ** np.ceil(np.log2(len(self.polyA) + len(self.polyB) - 1)) ) while len(self.polyA) < self.C_max_length: @@ -78,9 +73,7 @@ def __init__(self, polyA=[0], polyB=[0]): while len(self.polyB) < self.C_max_length: self.polyB.append(0) # A complex root used for the fourier transform - self.root = complex( - mpmath.root(x=1, n=self.C_max_length, k=1) - ) + self.root = complex(mpmath.root(x=1, n=self.C_max_length, k=1)) # The product self.product = self.__multiply() @@ -102,27 +95,15 @@ def __DFT(self, which): # First half of next step current_root = 1 - for j in range( - self.C_max_length // (next_ncol * 2) - ): + for j in range(self.C_max_length // (next_ncol * 2)): for i in range(next_ncol): - new_dft[i].append( - dft[i][j] - + current_root - * dft[i + next_ncol][j] - ) + new_dft[i].append(dft[i][j] + current_root * dft[i + next_ncol][j]) current_root *= root # Second half of next step current_root = 1 - for j in range( - self.C_max_length // (next_ncol * 2) - ): + for j in range(self.C_max_length // (next_ncol * 2)): for i in range(next_ncol): - new_dft[i].append( - dft[i][j] - - current_root - * dft[i + next_ncol][j] - ) + new_dft[i].append(dft[i][j] - current_root * dft[i + next_ncol][j]) current_root *= root # Update dft = new_dft @@ -133,12 +114,7 @@ def __DFT(self, which): def __multiply(self): dftA = self.__DFT("A") dftB = self.__DFT("B") - inverseC = [ - [ - dftA[i] * dftB[i] - for i in range(self.C_max_length) - ] - ] + inverseC = [[dftA[i] * dftB[i] for i in range(self.C_max_length)]] del dftA del dftB @@ -158,11 +134,7 @@ def __multiply(self): new_inverseC[i].append( ( inverseC[i][j] - + inverseC[i][ - j - + self.C_max_length - // next_ncol - ] + + inverseC[i][j + self.C_max_length // next_ncol] ) / 2 ) @@ -170,11 +142,7 @@ def __multiply(self): new_inverseC[i + next_ncol // 2].append( ( inverseC[i][j] - - inverseC[i][ - j - + self.C_max_length - // next_ncol - ] + - inverseC[i][j + self.C_max_length // next_ncol] ) / (2 * current_root) ) @@ -183,10 +151,7 @@ def __multiply(self): inverseC = new_inverseC next_ncol *= 2 # Unpack - inverseC = [ - round(x[0].real, 8) + round(x[0].imag, 8) * 1j - for x in inverseC - ] + inverseC = [round(x[0].real, 8) + round(x[0].imag, 8) * 1j for x in inverseC] # Remove leading 0's while inverseC[-1] == 0: @@ -196,20 +161,13 @@ def __multiply(self): # Overwrite __str__ for print(); Shows A, B and A*B def __str__(self): A = "A = " + " + ".join( - f"{coef}*x^{i}" - for coef, i in enumerate( - self.polyA[: self.len_A] - ) + f"{coef}*x^{i}" for coef, i in enumerate(self.polyA[: self.len_A]) ) B = "B = " + " + ".join( - f"{coef}*x^{i}" - for coef, i in enumerate( - self.polyB[: self.len_B] - ) + f"{coef}*x^{i}" for coef, i in enumerate(self.polyB[: self.len_B]) ) C = "A*B = " + " + ".join( - f"{coef}*x^{i}" - for coef, i in enumerate(self.product) + f"{coef}*x^{i}" for coef, i in enumerate(self.product) ) return "\n".join((A, B, C)) diff --git a/maths/segmented_sieve.py b/maths/segmented_sieve.py index b15ec2480678..c1cc497ad33e 100644 --- a/maths/segmented_sieve.py +++ b/maths/segmented_sieve.py @@ -48,4 +48,4 @@ def sieve(n): return prime -print(sieve(10**6)) +print(sieve(10 ** 6)) diff --git a/maths/simpson_rule.py b/maths/simpson_rule.py index 5cf9c14b07ee..f4620be8e70f 100644 --- a/maths/simpson_rule.py +++ b/maths/simpson_rule.py @@ -1,4 +1,3 @@ - """ Numerical integration or quadrature for a smooth function f with known values at x_i @@ -8,39 +7,45 @@ "Simpson Rule" """ + + def method_2(boundary, steps): -# "Simpson Rule" -# int(f) = delta_x/2 * (b-a)/3*(f1 + 4f2 + 2f_3 + ... + fn) + # "Simpson Rule" + # int(f) = delta_x/2 * (b-a)/3*(f1 + 4f2 + 2f_3 + ... + fn) h = (boundary[1] - boundary[0]) / steps a = boundary[0] b = boundary[1] - x_i = make_points(a,b,h) + x_i = make_points(a, b, h) y = 0.0 - y += (h/3.0)*f(a) + y += (h / 3.0) * f(a) cnt = 2 for i in x_i: - y += (h/3)*(4-2*(cnt%2))*f(i) + y += (h / 3) * (4 - 2 * (cnt % 2)) * f(i) cnt += 1 - y += (h/3.0)*f(b) + y += (h / 3.0) * f(b) return y -def make_points(a,b,h): + +def make_points(a, b, h): x = a + h - while x < (b-h): + while x < (b - h): yield x x = x + h -def f(x): #enter your function here - y = (x-0)*(x-0) + +def f(x): # enter your function here + y = (x - 0) * (x - 0) return y + def main(): - a = 0.0 #Lower bound of integration - b = 1.0 #Upper bound of integration - steps = 10.0 #define number of steps or resolution - boundary = [a, b] #define boundary of integration + a = 0.0 # Lower bound of integration + b = 1.0 # Upper bound of integration + steps = 10.0 # define number of steps or resolution + boundary = [a, b] # define boundary of integration y = method_2(boundary, steps) - print('y = {0}'.format(y)) + print("y = {0}".format(y)) + -if __name__ == '__main__': - main() +if __name__ == "__main__": + main() diff --git a/maths/trapezoidal_rule.py b/maths/trapezoidal_rule.py index f5e5fbbc2662..0f321317614d 100644 --- a/maths/trapezoidal_rule.py +++ b/maths/trapezoidal_rule.py @@ -7,38 +7,44 @@ "extended trapezoidal rule" """ + + def method_1(boundary, steps): -# "extended trapezoidal rule" -# int(f) = dx/2 * (f1 + 2f2 + ... + fn) - h = (boundary[1] - boundary[0]) / steps - a = boundary[0] - b = boundary[1] - x_i = make_points(a,b,h) - y = 0.0 - y += (h/2.0)*f(a) - for i in x_i: - #print(i) - y += h*f(i) - y += (h/2.0)*f(b) - return y - -def make_points(a,b,h): - x = a + h - while x < (b-h): - yield x - x = x + h - -def f(x): #enter your function here - y = (x-0)*(x-0) - return y + # "extended trapezoidal rule" + # int(f) = dx/2 * (f1 + 2f2 + ... + fn) + h = (boundary[1] - boundary[0]) / steps + a = boundary[0] + b = boundary[1] + x_i = make_points(a, b, h) + y = 0.0 + y += (h / 2.0) * f(a) + for i in x_i: + # print(i) + y += h * f(i) + y += (h / 2.0) * f(b) + return y + + +def make_points(a, b, h): + x = a + h + while x < (b - h): + yield x + x = x + h + + +def f(x): # enter your function here + y = (x - 0) * (x - 0) + return y + def main(): - a = 0.0 #Lower bound of integration - b = 1.0 #Upper bound of integration - steps = 10.0 #define number of steps or resolution - boundary = [a, b] #define boundary of integration - y = method_1(boundary, steps) - print('y = {0}'.format(y)) - -if __name__ == '__main__': - main() + a = 0.0 # Lower bound of integration + b = 1.0 # Upper bound of integration + steps = 10.0 # define number of steps or resolution + boundary = [a, b] # define boundary of integration + y = method_1(boundary, steps) + print("y = {0}".format(y)) + + +if __name__ == "__main__": + main() diff --git a/maths/zellers_congruence.py b/maths/zellers_congruence.py index 67c5550802ea..277ecfaf0da9 100644 --- a/maths/zellers_congruence.py +++ b/maths/zellers_congruence.py @@ -68,24 +68,16 @@ def zeller(date_input: str) -> str: # Days of the week for response days = { - '0': 'Sunday', - '1': 'Monday', - '2': 'Tuesday', - '3': 'Wednesday', - '4': 'Thursday', - '5': 'Friday', - '6': 'Saturday' + "0": "Sunday", + "1": "Monday", + "2": "Tuesday", + "3": "Wednesday", + "4": "Thursday", + "5": "Friday", + "6": "Saturday", } - convert_datetime_days = { - 0:1, - 1:2, - 2:3, - 3:4, - 4:5, - 5:6, - 6:0 - } + convert_datetime_days = {0: 1, 1: 2, 2: 3, 3: 4, 4: 5, 5: 6, 6: 0} # Validate if not 0 < len(date_input) < 11: @@ -97,9 +89,9 @@ def zeller(date_input: str) -> str: if not 0 < m < 13: raise ValueError("Month must be between 1 - 12") - sep_1:str = date_input[2] + sep_1: str = date_input[2] # Validate - if sep_1 not in ["-","/"]: + if sep_1 not in ["-", "/"]: raise ValueError("Date seperator must be '-' or '/'") # Get day @@ -111,14 +103,16 @@ def zeller(date_input: str) -> str: # Get second seperator sep_2: str = date_input[5] # Validate - if sep_2 not in ["-","/"]: + if sep_2 not in ["-", "/"]: raise ValueError("Date seperator must be '-' or '/'") # Get year y: int = int(date_input[6] + date_input[7] + date_input[8] + date_input[9]) # Arbitrary year range if not 45 < y < 8500: - raise ValueError("Year out of range. There has to be some sort of limit...right?") + raise ValueError( + "Year out of range. There has to be some sort of limit...right?" + ) # Get datetime obj for validation dt_ck = datetime.date(int(y), int(m), int(d)) @@ -130,13 +124,13 @@ def zeller(date_input: str) -> str: # maths var c: int = int(str(y)[:2]) k: int = int(str(y)[2:]) - t: int = int(2.6*m - 5.39) + t: int = int(2.6 * m - 5.39) u: int = int(c / 4) v: int = int(k / 4) x: int = int(d + k) z: int = int(t + u + v + x) w: int = int(z - (2 * c)) - f: int = round(w%7) + f: int = round(w % 7) # End math # Validate math @@ -147,10 +141,16 @@ def zeller(date_input: str) -> str: response: str = f"Your date {date_input}, is a {days[str(f)]}!" return response -if __name__ == '__main__': + +if __name__ == "__main__": import doctest + doctest.testmod() - parser = argparse.ArgumentParser(description='Find out what day of the week nearly any date is or was. Enter date as a string in the mm-dd-yyyy or mm/dd/yyyy format') - parser.add_argument('date_input', type=str, help='Date as a string (mm-dd-yyyy or mm/dd/yyyy)') + parser = argparse.ArgumentParser( + description="Find out what day of the week nearly any date is or was. Enter date as a string in the mm-dd-yyyy or mm/dd/yyyy format" + ) + parser.add_argument( + "date_input", type=str, help="Date as a string (mm-dd-yyyy or mm/dd/yyyy)" + ) args = parser.parse_args() zeller(args.date_input) diff --git a/matrix/matrix_class.py b/matrix/matrix_class.py index c82fb2cf6464..a8066e319559 100644 --- a/matrix/matrix_class.py +++ b/matrix/matrix_class.py @@ -311,8 +311,10 @@ def __mul__(self, other): return Matrix([[element * other for element in row] for row in self.rows]) elif isinstance(other, Matrix): if self.num_columns != other.num_rows: - raise ValueError("The number of columns in the first matrix must " - "be equal to the number of rows in the second") + raise ValueError( + "The number of columns in the first matrix must " + "be equal to the number of rows in the second" + ) return Matrix( [ [Matrix.dot_product(row, column) for column in other.columns()] @@ -320,7 +322,9 @@ def __mul__(self, other): ] ) else: - raise TypeError("A Matrix can only be multiplied by an int, float, or another matrix") + raise TypeError( + "A Matrix can only be multiplied by an int, float, or another matrix" + ) def __pow__(self, other): if not isinstance(other, int): diff --git a/matrix/matrix_operation.py b/matrix/matrix_operation.py index b32a4dcf7af3..5ca61b4ed023 100644 --- a/matrix/matrix_operation.py +++ b/matrix/matrix_operation.py @@ -39,8 +39,10 @@ def multiply(matrix_a, matrix_b): rows, cols = _verify_matrix_sizes(matrix_a, matrix_b) if cols[0] != rows[1]: - raise ValueError(f'Cannot multiply matrix of dimensions ({rows[0]},{cols[0]}) ' - f'and ({rows[1]},{cols[1]})') + raise ValueError( + f"Cannot multiply matrix of dimensions ({rows[0]},{cols[0]}) " + f"and ({rows[1]},{cols[1]})" + ) for i in range(rows[0]): list_1 = [] for j in range(cols[1]): @@ -59,7 +61,7 @@ def identity(n): :return: Identity matrix of shape [n, n] """ n = int(n) - return [[int(row == column) for column in range(n)] for row in range(n)] + return [[int(row == column) for column in range(n)] for row in range(n)] def transpose(matrix, return_map=True): @@ -75,15 +77,15 @@ def transpose(matrix, return_map=True): def minor(matrix, row, column): - minor = matrix[:row] + matrix[row + 1:] - minor = [row[:column] + row[column + 1:] for row in minor] + minor = matrix[:row] + matrix[row + 1 :] + minor = [row[:column] + row[column + 1 :] for row in minor] return minor def determinant(matrix): if len(matrix) == 1: return matrix[0][0] - + res = 0 for x in range(len(matrix)): res += matrix[0][x] * determinant(minor(matrix, 0, x)) * (-1) ** x @@ -99,10 +101,13 @@ def inverse(matrix): for i in range(len(matrix)): for j in range(len(matrix)): matrix_minor[i].append(determinant(minor(matrix, i, j))) - - cofactors = [[x * (-1) ** (row + col) for col, x in enumerate(matrix_minor[row])] for row in range(len(matrix))] + + cofactors = [ + [x * (-1) ** (row + col) for col, x in enumerate(matrix_minor[row])] + for row in range(len(matrix)) + ] adjugate = transpose(cofactors) - return scalar_multiply(adjugate, 1/det) + return scalar_multiply(adjugate, 1 / det) def _check_not_integer(matrix): @@ -122,8 +127,10 @@ def _verify_matrix_sizes(matrix_a, matrix_b): shape = _shape(matrix_a) shape += _shape(matrix_b) if shape[0] != shape[2] or shape[1] != shape[3]: - raise ValueError(f"operands could not be broadcast together with shape " - f"({shape[0], shape[1]}), ({shape[2], shape[3]})") + raise ValueError( + f"operands could not be broadcast together with shape " + f"({shape[0], shape[1]}), ({shape[2], shape[3]})" + ) return [shape[0], shape[2]], [shape[1], shape[3]] @@ -132,13 +139,19 @@ def main(): matrix_b = [[3, 4], [7, 4]] matrix_c = [[11, 12, 13, 14], [21, 22, 23, 24], [31, 32, 33, 34], [41, 42, 43, 44]] matrix_d = [[3, 0, 2], [2, 0, -2], [0, 1, 1]] - print('Add Operation, %s + %s = %s \n' %(matrix_a, matrix_b, (add(matrix_a, matrix_b)))) - print('Multiply Operation, %s * %s = %s \n' %(matrix_a, matrix_b, multiply(matrix_a, matrix_b))) - print('Identity: %s \n' %identity(5)) - print('Minor of %s = %s \n' %(matrix_c, minor(matrix_c, 1, 2))) - print('Determinant of %s = %s \n' %(matrix_b, determinant(matrix_b))) - print('Inverse of %s = %s\n'%(matrix_d, inverse(matrix_d))) - - -if __name__ == '__main__': + print( + "Add Operation, %s + %s = %s \n" + % (matrix_a, matrix_b, (add(matrix_a, matrix_b))) + ) + print( + "Multiply Operation, %s * %s = %s \n" + % (matrix_a, matrix_b, multiply(matrix_a, matrix_b)) + ) + print("Identity: %s \n" % identity(5)) + print("Minor of %s = %s \n" % (matrix_c, minor(matrix_c, 1, 2))) + print("Determinant of %s = %s \n" % (matrix_b, determinant(matrix_b))) + print("Inverse of %s = %s\n" % (matrix_d, inverse(matrix_d))) + + +if __name__ == "__main__": main() diff --git a/matrix/nth_fibonacci_using_matrix_exponentiation.py b/matrix/nth_fibonacci_using_matrix_exponentiation.py index 57cdfacd47dd..222779f454f9 100644 --- a/matrix/nth_fibonacci_using_matrix_exponentiation.py +++ b/matrix/nth_fibonacci_using_matrix_exponentiation.py @@ -13,6 +13,8 @@ So we just need the n times multiplication of the matrix [1,1],[1,0]]. We can decrease the n times multiplication by following the divide and conquer approach. """ + + def multiply(matrix_a, matrix_b): matrix_c = [] n = len(matrix_a) diff --git a/matrix/searching_in_sorted_matrix.py b/matrix/searching_in_sorted_matrix.py index 54913b350803..1b3eeedf3110 100644 --- a/matrix/searching_in_sorted_matrix.py +++ b/matrix/searching_in_sorted_matrix.py @@ -2,26 +2,21 @@ def search_in_a_sorted_matrix(mat, m, n, key): i, j = m - 1, 0 while i >= 0 and j < n: if key == mat[i][j]: - print('Key %s found at row- %s column- %s' % (key, i + 1, j + 1)) + print("Key %s found at row- %s column- %s" % (key, i + 1, j + 1)) return if key < mat[i][j]: i -= 1 else: j += 1 - print('Key %s not found' % (key)) + print("Key %s not found" % (key)) def main(): - mat = [ - [2, 5, 7], - [4, 8, 13], - [9, 11, 15], - [12, 17, 20] - ] + mat = [[2, 5, 7], [4, 8, 13], [9, 11, 15], [12, 17, 20]] x = int(input("Enter the element to be searched:")) print(mat) search_in_a_sorted_matrix(mat, len(mat), len(mat[0]), x) -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/matrix/sherman_morrison.py b/matrix/sherman_morrison.py index 0d49d78509be..531b76cdeb94 100644 --- a/matrix/sherman_morrison.py +++ b/matrix/sherman_morrison.py @@ -28,7 +28,7 @@ def __str__(self): # Prefix s = "Matrix consist of %d rows and %d columns\n" % (self.row, self.column) - + # Make string identifier max_element_length = 0 for row_vector in self.array: @@ -37,16 +37,18 @@ def __str__(self): string_format_identifier = "%%%ds" % (max_element_length,) # Make string and return - def single_line(row_vector): + def single_line(row_vector): nonlocal string_format_identifier line = "[" line += ", ".join(string_format_identifier % (obj,) for obj in row_vector) line += "]" return line + s += "\n".join(single_line(row_vector) for row_vector in self.array) return s - def __repr__(self): return str(self) + def __repr__(self): + return str(self) def validateIndices(self, loc: tuple): """ @@ -60,9 +62,12 @@ def validateIndices(self, loc: tuple): >>> a.validateIndices((0, 0)) True """ - if not(isinstance(loc, (list, tuple)) and len(loc) == 2): return False - elif not(0 <= loc[0] < self.row and 0 <= loc[1] < self.column): return False - else: return True + if not (isinstance(loc, (list, tuple)) and len(loc) == 2): + return False + elif not (0 <= loc[0] < self.row and 0 <= loc[1] < self.column): + return False + else: + return True def __getitem__(self, loc: tuple): """ @@ -115,7 +120,7 @@ def __add__(self, another): result = Matrix(self.row, self.column) for r in range(self.row): for c in range(self.column): - result[r,c] = self[r,c] + another[r,c] + result[r, c] = self[r, c] + another[r, c] return result def __neg__(self): @@ -135,10 +140,11 @@ def __neg__(self): result = Matrix(self.row, self.column) for r in range(self.row): for c in range(self.column): - result[r,c] = -self[r,c] + result[r, c] = -self[r, c] return result - def __sub__(self, another): return self + (-another) + def __sub__(self, another): + return self + (-another) def __mul__(self, another): """ @@ -154,21 +160,24 @@ def __mul__(self, another): [-2, -2, -6] """ - if isinstance(another, (int, float)): # Scalar multiplication + if isinstance(another, (int, float)): # Scalar multiplication result = Matrix(self.row, self.column) for r in range(self.row): for c in range(self.column): - result[r,c] = self[r,c] * another + result[r, c] = self[r, c] * another return result - elif isinstance(another, Matrix): # Matrix multiplication - assert(self.column == another.row) + elif isinstance(another, Matrix): # Matrix multiplication + assert self.column == another.row result = Matrix(self.row, another.column) for r in range(self.row): for c in range(another.column): for i in range(self.column): - result[r,c] += self[r,i] * another[i,c] + result[r, c] += self[r, i] * another[i, c] return result - else: raise TypeError("Unsupported type given for another (%s)" % (type(another),)) + else: + raise TypeError( + "Unsupported type given for another (%s)" % (type(another),) + ) def transpose(self): """ @@ -191,7 +200,7 @@ def transpose(self): result = Matrix(self.column, self.row) for r in range(self.row): for c in range(self.column): - result[c,r] = self[r,c] + result[c, r] = self[r, c] return result def ShermanMorrison(self, u, v): @@ -220,28 +229,31 @@ def ShermanMorrison(self, u, v): # Size validation assert isinstance(u, Matrix) and isinstance(v, Matrix) - assert self.row == self.column == u.row == v.row # u, v should be column vector - assert u.column == v.column == 1 # u, v should be column vector + assert self.row == self.column == u.row == v.row # u, v should be column vector + assert u.column == v.column == 1 # u, v should be column vector # Calculate vT = v.transpose() numerator_factor = (vT * self * u)[0, 0] + 1 - if numerator_factor == 0: return None # It's not invertable + if numerator_factor == 0: + return None # It's not invertable return self - ((self * u) * (vT * self) * (1.0 / numerator_factor)) + # Testing if __name__ == "__main__": def test1(): # a^(-1) ainv = Matrix(3, 3, 0) - for i in range(3): ainv[i,i] = 1 + for i in range(3): + ainv[i, i] = 1 print("a^(-1) is %s" % (ainv,)) # u, v u = Matrix(3, 1, 0) - u[0,0], u[1,0], u[2,0] = 1, 2, -3 + u[0, 0], u[1, 0], u[2, 0] = 1, 2, -3 v = Matrix(3, 1, 0) - v[0,0], v[1,0], v[2,0] = 4, -2, 5 + v[0, 0], v[1, 0], v[2, 0] = 4, -2, 5 print("u is %s" % (u,)) print("v is %s" % (v,)) print("uv^T is %s" % (u * v.transpose())) @@ -250,6 +262,7 @@ def test1(): def test2(): import doctest + doctest.testmod() - test2() \ No newline at end of file + test2() diff --git a/matrix/spiral_print.py b/matrix/spiral_print.py index 447881e508e7..31d9fff84bfd 100644 --- a/matrix/spiral_print.py +++ b/matrix/spiral_print.py @@ -6,6 +6,8 @@ i) matrix should be only one or two dimensional ii)column of all the row should be equal """ + + def checkMatrix(a): # must be if type(a) == list and len(a) > 0: @@ -51,7 +53,7 @@ def spiralPrint(a): # vertical printing up for i in range(matRow - 2, 0, -1): print(a[i][0]), - remainMat = [row[1:matCol - 1] for row in a[1:matRow - 1]] + remainMat = [row[1 : matCol - 1] for row in a[1 : matRow - 1]] if len(remainMat) > 0: spiralPrint(remainMat) else: @@ -62,5 +64,5 @@ def spiralPrint(a): # driver code -a = [[1 , 2, 3, 4],[5, 6, 7, 8],[9, 10, 11, 12]] +a = [[1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12]] spiralPrint(a) diff --git a/matrix/tests/test_matrix_operation.py b/matrix/tests/test_matrix_operation.py index 8b81b65d0fc8..f9f72cf59af8 100644 --- a/matrix/tests/test_matrix_operation.py +++ b/matrix/tests/test_matrix_operation.py @@ -29,8 +29,9 @@ @pytest.mark.mat_ops -@pytest.mark.parametrize(('mat1', 'mat2'), [(mat_a, mat_b), (mat_c, mat_d), (mat_d, mat_e), - (mat_f, mat_h)]) +@pytest.mark.parametrize( + ("mat1", "mat2"), [(mat_a, mat_b), (mat_c, mat_d), (mat_d, mat_e), (mat_f, mat_h)] +) def test_addition(mat1, mat2): if (np.array(mat1)).shape < (2, 2) or (np.array(mat2)).shape < (2, 2): with pytest.raises(TypeError): @@ -48,8 +49,9 @@ def test_addition(mat1, mat2): @pytest.mark.mat_ops -@pytest.mark.parametrize(('mat1', 'mat2'), [(mat_a, mat_b), (mat_c, mat_d), (mat_d, mat_e), - (mat_f, mat_h)]) +@pytest.mark.parametrize( + ("mat1", "mat2"), [(mat_a, mat_b), (mat_c, mat_d), (mat_d, mat_e), (mat_f, mat_h)] +) def test_subtraction(mat1, mat2): if (np.array(mat1)).shape < (2, 2) or (np.array(mat2)).shape < (2, 2): with pytest.raises(TypeError): @@ -67,8 +69,9 @@ def test_subtraction(mat1, mat2): @pytest.mark.mat_ops -@pytest.mark.parametrize(('mat1', 'mat2'), [(mat_a, mat_b), (mat_c, mat_d), (mat_d, mat_e), - (mat_f, mat_h)]) +@pytest.mark.parametrize( + ("mat1", "mat2"), [(mat_a, mat_b), (mat_c, mat_d), (mat_d, mat_e), (mat_f, mat_h)] +) def test_multiplication(mat1, mat2): if (np.array(mat1)).shape < (2, 2) or (np.array(mat2)).shape < (2, 2): logger.info(f"\n\t{test_multiplication.__name__} returned integer") @@ -81,7 +84,9 @@ def test_multiplication(mat1, mat2): assert theo == act else: with pytest.raises(ValueError): - logger.info(f"\n\t{test_multiplication.__name__} does not meet dim requirements") + logger.info( + f"\n\t{test_multiplication.__name__} does not meet dim requirements" + ) assert matop.subtract(mat1, mat2) @@ -100,7 +105,7 @@ def test_identity(): @pytest.mark.mat_ops -@pytest.mark.parametrize('mat', [mat_a, mat_b, mat_c, mat_d, mat_e, mat_f]) +@pytest.mark.parametrize("mat", [mat_a, mat_b, mat_c, mat_d, mat_e, mat_f]) def test_transpose(mat): if (np.array(mat)).shape < (2, 2): with pytest.raises(TypeError): diff --git a/networking_flow/ford_fulkerson.py b/networking_flow/ford_fulkerson.py index d51f1f0661b3..0028c7cc577f 100644 --- a/networking_flow/ford_fulkerson.py +++ b/networking_flow/ford_fulkerson.py @@ -4,14 +4,15 @@ (1) Start with initial flow as 0; (2) Choose augmenting path from source to sink and add path to flow; """ - + + def BFS(graph, s, t, parent): # Return True if there is node that has not iterated. - visited = [False]*len(graph) - queue=[] + visited = [False] * len(graph) + queue = [] queue.append(s) visited[s] = True - + while queue: u = queue.pop(0) for ind in range(len(graph[u])): @@ -21,36 +22,40 @@ def BFS(graph, s, t, parent): parent[ind] = u return True if visited[t] else False - + + def FordFulkerson(graph, source, sink): # This array is filled by BFS and to store path - parent = [-1]*(len(graph)) - max_flow = 0 - while BFS(graph, source, sink, parent) : + parent = [-1] * (len(graph)) + max_flow = 0 + while BFS(graph, source, sink, parent): path_flow = float("Inf") s = sink - while(s != source): + while s != source: # Find the minimum value in select path - path_flow = min (path_flow, graph[parent[s]][s]) + path_flow = min(path_flow, graph[parent[s]][s]) s = parent[s] - max_flow += path_flow + max_flow += path_flow v = sink - while(v != source): + while v != source: u = parent[v] graph[u][v] -= path_flow graph[v][u] += path_flow v = parent[v] return max_flow -graph = [[0, 16, 13, 0, 0, 0], - [0, 0, 10 ,12, 0, 0], - [0, 4, 0, 0, 14, 0], - [0, 0, 9, 0, 0, 20], - [0, 0, 0, 7, 0, 4], - [0, 0, 0, 0, 0, 0]] + +graph = [ + [0, 16, 13, 0, 0, 0], + [0, 0, 10, 12, 0, 0], + [0, 4, 0, 0, 14, 0], + [0, 0, 9, 0, 0, 20], + [0, 0, 0, 7, 0, 4], + [0, 0, 0, 0, 0, 0], +] source, sink = 0, 5 -print(FordFulkerson(graph, source, sink)) \ No newline at end of file +print(FordFulkerson(graph, source, sink)) diff --git a/neural_network/back_propagation_neural_network.py b/neural_network/back_propagation_neural_network.py index 92deaee19c6e..86797694bb0a 100644 --- a/neural_network/back_propagation_neural_network.py +++ b/neural_network/back_propagation_neural_network.py @@ -1,7 +1,7 @@ #!/usr/bin/python # encoding=utf8 -''' +""" A Framework of Back Propagation Neural Network(BP) model @@ -17,7 +17,7 @@ Github : https://github.com/RiptideBo Date: 2017.11.23 -''' +""" import numpy as np import matplotlib.pyplot as plt @@ -26,18 +26,22 @@ def sigmoid(x): return 1 / (1 + np.exp(-1 * x)) -class DenseLayer(): - ''' + +class DenseLayer: + """ Layers of BP neural network - ''' - def __init__(self,units,activation=None,learning_rate=None,is_input_layer=False): - ''' + """ + + def __init__( + self, units, activation=None, learning_rate=None, is_input_layer=False + ): + """ common connected layer of bp network :param units: numbers of neural units :param activation: activation function :param learning_rate: learning rate for paras :param is_input_layer: whether it is input layer or not - ''' + """ self.units = units self.weight = None self.bias = None @@ -47,21 +51,21 @@ def __init__(self,units,activation=None,learning_rate=None,is_input_layer=False) self.learn_rate = learning_rate self.is_input_layer = is_input_layer - def initializer(self,back_units): - self.weight = np.asmatrix(np.random.normal(0,0.5,(self.units,back_units))) - self.bias = np.asmatrix(np.random.normal(0,0.5,self.units)).T + def initializer(self, back_units): + self.weight = np.asmatrix(np.random.normal(0, 0.5, (self.units, back_units))) + self.bias = np.asmatrix(np.random.normal(0, 0.5, self.units)).T if self.activation is None: self.activation = sigmoid def cal_gradient(self): if self.activation == sigmoid: - gradient_mat = np.dot(self.output ,(1- self.output).T) + gradient_mat = np.dot(self.output, (1 - self.output).T) gradient_activation = np.diag(np.diag(gradient_mat)) else: gradient_activation = 1 return gradient_activation - def forward_propagation(self,xdata): + def forward_propagation(self, xdata): self.xdata = xdata if self.is_input_layer: # input layer @@ -69,22 +73,22 @@ def forward_propagation(self,xdata): self.output = xdata return xdata else: - self.wx_plus_b = np.dot(self.weight,self.xdata) - self.bias + self.wx_plus_b = np.dot(self.weight, self.xdata) - self.bias self.output = self.activation(self.wx_plus_b) return self.output - def back_propagation(self,gradient): + def back_propagation(self, gradient): - gradient_activation = self.cal_gradient() # i * i 维 - gradient = np.asmatrix(np.dot(gradient.T,gradient_activation)) + gradient_activation = self.cal_gradient() # i * i 维 + gradient = np.asmatrix(np.dot(gradient.T, gradient_activation)) self._gradient_weight = np.asmatrix(self.xdata) self._gradient_bias = -1 self._gradient_x = self.weight - self.gradient_weight = np.dot(gradient.T,self._gradient_weight.T) + self.gradient_weight = np.dot(gradient.T, self._gradient_weight.T) self.gradient_bias = gradient * self._gradient_bias - self.gradient = np.dot(gradient,self._gradient_x).T + self.gradient = np.dot(gradient, self._gradient_x).T # ----------------------upgrade # -----------the Negative gradient direction -------- self.weight = self.weight - self.learn_rate * self.gradient_weight @@ -93,33 +97,34 @@ def back_propagation(self,gradient): return self.gradient -class BPNN(): - ''' +class BPNN: + """ Back Propagation Neural Network model - ''' + """ + def __init__(self): self.layers = [] self.train_mse = [] self.fig_loss = plt.figure() - self.ax_loss = self.fig_loss.add_subplot(1,1,1) + self.ax_loss = self.fig_loss.add_subplot(1, 1, 1) - def add_layer(self,layer): + def add_layer(self, layer): self.layers.append(layer) def build(self): - for i,layer in enumerate(self.layers[:]): + for i, layer in enumerate(self.layers[:]): if i < 1: layer.is_input_layer = True else: - layer.initializer(self.layers[i-1].units) + layer.initializer(self.layers[i - 1].units) def summary(self): - for i,layer in enumerate(self.layers[:]): - print('------- layer %d -------'%i) - print('weight.shape ',np.shape(layer.weight)) - print('bias.shape ',np.shape(layer.bias)) + for i, layer in enumerate(self.layers[:]): + print("------- layer %d -------" % i) + print("weight.shape ", np.shape(layer.weight)) + print("bias.shape ", np.shape(layer.bias)) - def train(self,xdata,ydata,train_round,accuracy): + def train(self, xdata, ydata, train_round, accuracy): self.train_round = train_round self.accuracy = accuracy @@ -129,8 +134,8 @@ def train(self,xdata,ydata,train_round,accuracy): for round_i in range(train_round): all_loss = 0 for row in range(x_shape[0]): - _xdata = np.asmatrix(xdata[row,:]).T - _ydata = np.asmatrix(ydata[row,:]).T + _xdata = np.asmatrix(xdata[row, :]).T + _ydata = np.asmatrix(ydata[row, :]).T # forward propagation for layer in self.layers: @@ -144,40 +149,49 @@ def train(self,xdata,ydata,train_round,accuracy): for layer in self.layers[:0:-1]: gradient = layer.back_propagation(gradient) - mse = all_loss/x_shape[0] + mse = all_loss / x_shape[0] self.train_mse.append(mse) self.plot_loss() if mse < self.accuracy: - print('----达到精度----') + print("----达到精度----") return mse - def cal_loss(self,ydata,ydata_): - self.loss = np.sum(np.power((ydata - ydata_),2)) + def cal_loss(self, ydata, ydata_): + self.loss = np.sum(np.power((ydata - ydata_), 2)) self.loss_gradient = 2 * (ydata_ - ydata) # vector (shape is the same as _ydata.shape) - return self.loss,self.loss_gradient + return self.loss, self.loss_gradient def plot_loss(self): if self.ax_loss.lines: self.ax_loss.lines.remove(self.ax_loss.lines[0]) - self.ax_loss.plot(self.train_mse, 'r-') + self.ax_loss.plot(self.train_mse, "r-") plt.ion() - plt.xlabel('step') - plt.ylabel('loss') + plt.xlabel("step") + plt.ylabel("loss") plt.show() plt.pause(0.1) - - def example(): - x = np.random.randn(10,10) - y = np.asarray([[0.8,0.4],[0.4,0.3],[0.34,0.45],[0.67,0.32], - [0.88,0.67],[0.78,0.77],[0.55,0.66],[0.55,0.43],[0.54,0.1], - [0.1,0.5]]) + x = np.random.randn(10, 10) + y = np.asarray( + [ + [0.8, 0.4], + [0.4, 0.3], + [0.34, 0.45], + [0.67, 0.32], + [0.88, 0.67], + [0.78, 0.77], + [0.55, 0.66], + [0.55, 0.43], + [0.54, 0.1], + [0.1, 0.5], + ] + ) model = BPNN() model.add_layer(DenseLayer(10)) @@ -189,7 +203,8 @@ def example(): model.summary() - model.train(xdata=x,ydata=y,train_round=100,accuracy=0.01) + model.train(xdata=x, ydata=y, train_round=100, accuracy=0.01) + -if __name__ == '__main__': +if __name__ == "__main__": example() diff --git a/neural_network/convolution_neural_network.py b/neural_network/convolution_neural_network.py index 786992c054a0..9448671abace 100644 --- a/neural_network/convolution_neural_network.py +++ b/neural_network/convolution_neural_network.py @@ -1,6 +1,6 @@ -#-*- coding: utf-8 -*- +# -*- coding: utf-8 -*- -''' +""" - - - - - -- - - - - - - - - - - - - - - - - - - - - - - Name - - CNN - Convolution Neural Network For Photo Recognizing Goal - - Recognize Handing Writting Word Photo @@ -14,15 +14,17 @@ Github: 245885195@qq.com Date: 2017.9.20 - - - - - -- - - - - - - - - - - - - - - - - - - - - - - -''' +""" import pickle import numpy as np import matplotlib.pyplot as plt -class CNN(): - def __init__(self, conv1_get, size_p1, bp_num1, bp_num2, bp_num3, rate_w=0.2, rate_t=0.2): - ''' +class CNN: + def __init__( + self, conv1_get, size_p1, bp_num1, bp_num2, bp_num3, rate_w=0.2, rate_t=0.2 + ): + """ :param conv1_get: [a,c,d],size, number, step of convolution kernel :param size_p1: pooling size :param bp_num1: units number of flatten layer @@ -30,7 +32,7 @@ def __init__(self, conv1_get, size_p1, bp_num1, bp_num2, bp_num3, rate_w=0.2, ra :param bp_num3: units number of output layer :param rate_w: rate of weight learning :param rate_t: rate of threshold learning - ''' + """ self.num_bp1 = bp_num1 self.num_bp2 = bp_num2 self.num_bp3 = bp_num3 @@ -39,206 +41,246 @@ def __init__(self, conv1_get, size_p1, bp_num1, bp_num2, bp_num3, rate_w=0.2, ra self.size_pooling1 = size_p1 self.rate_weight = rate_w self.rate_thre = rate_t - self.w_conv1 = [np.mat(-1*np.random.rand(self.conv1[0],self.conv1[0])+0.5) for i in range(self.conv1[1])] + self.w_conv1 = [ + np.mat(-1 * np.random.rand(self.conv1[0], self.conv1[0]) + 0.5) + for i in range(self.conv1[1]) + ] self.wkj = np.mat(-1 * np.random.rand(self.num_bp3, self.num_bp2) + 0.5) - self.vji = np.mat(-1*np.random.rand(self.num_bp2, self.num_bp1)+0.5) - self.thre_conv1 = -2*np.random.rand(self.conv1[1])+1 - self.thre_bp2 = -2*np.random.rand(self.num_bp2)+1 - self.thre_bp3 = -2*np.random.rand(self.num_bp3)+1 - + self.vji = np.mat(-1 * np.random.rand(self.num_bp2, self.num_bp1) + 0.5) + self.thre_conv1 = -2 * np.random.rand(self.conv1[1]) + 1 + self.thre_bp2 = -2 * np.random.rand(self.num_bp2) + 1 + self.thre_bp3 = -2 * np.random.rand(self.num_bp3) + 1 def save_model(self, save_path): - #save model dict with pickle - model_dic = {'num_bp1':self.num_bp1, - 'num_bp2':self.num_bp2, - 'num_bp3':self.num_bp3, - 'conv1':self.conv1, - 'step_conv1':self.step_conv1, - 'size_pooling1':self.size_pooling1, - 'rate_weight':self.rate_weight, - 'rate_thre':self.rate_thre, - 'w_conv1':self.w_conv1, - 'wkj':self.wkj, - 'vji':self.vji, - 'thre_conv1':self.thre_conv1, - 'thre_bp2':self.thre_bp2, - 'thre_bp3':self.thre_bp3} - with open(save_path, 'wb') as f: + # save model dict with pickle + model_dic = { + "num_bp1": self.num_bp1, + "num_bp2": self.num_bp2, + "num_bp3": self.num_bp3, + "conv1": self.conv1, + "step_conv1": self.step_conv1, + "size_pooling1": self.size_pooling1, + "rate_weight": self.rate_weight, + "rate_thre": self.rate_thre, + "w_conv1": self.w_conv1, + "wkj": self.wkj, + "vji": self.vji, + "thre_conv1": self.thre_conv1, + "thre_bp2": self.thre_bp2, + "thre_bp3": self.thre_bp3, + } + with open(save_path, "wb") as f: pickle.dump(model_dic, f) - print('Model saved: %s'% save_path) + print("Model saved: %s" % save_path) @classmethod def ReadModel(cls, model_path): - #read saved model - with open(model_path, 'rb') as f: + # read saved model + with open(model_path, "rb") as f: model_dic = pickle.load(f) - conv_get= model_dic.get('conv1') - conv_get.append(model_dic.get('step_conv1')) - size_p1 = model_dic.get('size_pooling1') - bp1 = model_dic.get('num_bp1') - bp2 = model_dic.get('num_bp2') - bp3 = model_dic.get('num_bp3') - r_w = model_dic.get('rate_weight') - r_t = model_dic.get('rate_thre') - #create model instance - conv_ins = CNN(conv_get,size_p1,bp1,bp2,bp3,r_w,r_t) - #modify model parameter - conv_ins.w_conv1 = model_dic.get('w_conv1') - conv_ins.wkj = model_dic.get('wkj') - conv_ins.vji = model_dic.get('vji') - conv_ins.thre_conv1 = model_dic.get('thre_conv1') - conv_ins.thre_bp2 = model_dic.get('thre_bp2') - conv_ins.thre_bp3 = model_dic.get('thre_bp3') + conv_get = model_dic.get("conv1") + conv_get.append(model_dic.get("step_conv1")) + size_p1 = model_dic.get("size_pooling1") + bp1 = model_dic.get("num_bp1") + bp2 = model_dic.get("num_bp2") + bp3 = model_dic.get("num_bp3") + r_w = model_dic.get("rate_weight") + r_t = model_dic.get("rate_thre") + # create model instance + conv_ins = CNN(conv_get, size_p1, bp1, bp2, bp3, r_w, r_t) + # modify model parameter + conv_ins.w_conv1 = model_dic.get("w_conv1") + conv_ins.wkj = model_dic.get("wkj") + conv_ins.vji = model_dic.get("vji") + conv_ins.thre_conv1 = model_dic.get("thre_conv1") + conv_ins.thre_bp2 = model_dic.get("thre_bp2") + conv_ins.thre_bp3 = model_dic.get("thre_bp3") return conv_ins - def sig(self, x): - return 1 / (1 + np.exp(-1*x)) + return 1 / (1 + np.exp(-1 * x)) def do_round(self, x): return round(x, 3) def convolute(self, data, convs, w_convs, thre_convs, conv_step): - #convolution process + # convolution process size_conv = convs[0] - num_conv =convs[1] + num_conv = convs[1] size_data = np.shape(data)[0] - #get the data slice of original image data, data_focus + # get the data slice of original image data, data_focus data_focus = [] for i_focus in range(0, size_data - size_conv + 1, conv_step): for j_focus in range(0, size_data - size_conv + 1, conv_step): - focus = data[i_focus:i_focus + size_conv, j_focus:j_focus + size_conv] + focus = data[ + i_focus : i_focus + size_conv, j_focus : j_focus + size_conv + ] data_focus.append(focus) - #caculate the feature map of every single kernel, and saved as list of matrix + # caculate the feature map of every single kernel, and saved as list of matrix data_featuremap = [] Size_FeatureMap = int((size_data - size_conv) / conv_step + 1) for i_map in range(num_conv): featuremap = [] for i_focus in range(len(data_focus)): - net_focus = np.sum(np.multiply(data_focus[i_focus], w_convs[i_map])) - thre_convs[i_map] + net_focus = ( + np.sum(np.multiply(data_focus[i_focus], w_convs[i_map])) + - thre_convs[i_map] + ) featuremap.append(self.sig(net_focus)) - featuremap = np.asmatrix(featuremap).reshape(Size_FeatureMap, Size_FeatureMap) + featuremap = np.asmatrix(featuremap).reshape( + Size_FeatureMap, Size_FeatureMap + ) data_featuremap.append(featuremap) - #expanding the data slice to One dimenssion + # expanding the data slice to One dimenssion focus1_list = [] for each_focus in data_focus: focus1_list.extend(self.Expand_Mat(each_focus)) focus_list = np.asarray(focus1_list) - return focus_list,data_featuremap + return focus_list, data_featuremap - def pooling(self, featuremaps, size_pooling, type='average_pool'): - #pooling process + def pooling(self, featuremaps, size_pooling, type="average_pool"): + # pooling process size_map = len(featuremaps[0]) - size_pooled = int(size_map/size_pooling) + size_pooled = int(size_map / size_pooling) featuremap_pooled = [] for i_map in range(len(featuremaps)): map = featuremaps[i_map] map_pooled = [] - for i_focus in range(0,size_map,size_pooling): + for i_focus in range(0, size_map, size_pooling): for j_focus in range(0, size_map, size_pooling): - focus = map[i_focus:i_focus + size_pooling, j_focus:j_focus + size_pooling] - if type == 'average_pool': - #average pooling + focus = map[ + i_focus : i_focus + size_pooling, + j_focus : j_focus + size_pooling, + ] + if type == "average_pool": + # average pooling map_pooled.append(np.average(focus)) - elif type == 'max_pooling': - #max pooling + elif type == "max_pooling": + # max pooling map_pooled.append(np.max(focus)) - map_pooled = np.asmatrix(map_pooled).reshape(size_pooled,size_pooled) + map_pooled = np.asmatrix(map_pooled).reshape(size_pooled, size_pooled) featuremap_pooled.append(map_pooled) return featuremap_pooled def _expand(self, datas): - #expanding three dimension data to one dimension list + # expanding three dimension data to one dimension list data_expanded = [] for i in range(len(datas)): shapes = np.shape(datas[i]) - data_listed = datas[i].reshape(1,shapes[0]*shapes[1]) + data_listed = datas[i].reshape(1, shapes[0] * shapes[1]) data_listed = data_listed.getA().tolist()[0] data_expanded.extend(data_listed) data_expanded = np.asarray(data_expanded) return data_expanded def _expand_mat(self, data_mat): - #expanding matrix to one dimension list + # expanding matrix to one dimension list data_mat = np.asarray(data_mat) shapes = np.shape(data_mat) - data_expanded = data_mat.reshape(1,shapes[0]*shapes[1]) + data_expanded = data_mat.reshape(1, shapes[0] * shapes[1]) return data_expanded - def _calculate_gradient_from_pool(self, out_map, pd_pool,num_map, size_map, size_pooling): - ''' + def _calculate_gradient_from_pool( + self, out_map, pd_pool, num_map, size_map, size_pooling + ): + """ calcluate the gradient from the data slice of pool layer pd_pool: list of matrix out_map: the shape of data slice(size_map*size_map) return: pd_all: list of matrix, [num, size_map, size_map] - ''' + """ pd_all = [] i_pool = 0 for i_map in range(num_map): pd_conv1 = np.ones((size_map, size_map)) for i in range(0, size_map, size_pooling): for j in range(0, size_map, size_pooling): - pd_conv1[i:i + size_pooling, j:j + size_pooling] = pd_pool[i_pool] + pd_conv1[i : i + size_pooling, j : j + size_pooling] = pd_pool[ + i_pool + ] i_pool = i_pool + 1 - pd_conv2 = np.multiply(pd_conv1,np.multiply(out_map[i_map],(1-out_map[i_map]))) + pd_conv2 = np.multiply( + pd_conv1, np.multiply(out_map[i_map], (1 - out_map[i_map])) + ) pd_all.append(pd_conv2) return pd_all - def train(self, patterns, datas_train, datas_teach, n_repeat, error_accuracy, draw_e = bool): - #model traning - print('----------------------Start Training-------------------------') - print((' - - Shape: Train_Data ',np.shape(datas_train))) - print((' - - Shape: Teach_Data ',np.shape(datas_teach))) + def train( + self, patterns, datas_train, datas_teach, n_repeat, error_accuracy, draw_e=bool + ): + # model traning + print("----------------------Start Training-------------------------") + print((" - - Shape: Train_Data ", np.shape(datas_train))) + print((" - - Shape: Teach_Data ", np.shape(datas_teach))) rp = 0 all_mse = [] - mse = 10000 + mse = 10000 while rp < n_repeat and mse >= error_accuracy: alle = 0 - print('-------------Learning Time %d--------------'%rp) + print("-------------Learning Time %d--------------" % rp) for p in range(len(datas_train)): - #print('------------Learning Image: %d--------------'%p) + # print('------------Learning Image: %d--------------'%p) data_train = np.asmatrix(datas_train[p]) data_teach = np.asarray(datas_teach[p]) - data_focus1,data_conved1 = self.convolute(data_train,self.conv1,self.w_conv1, - self.thre_conv1,conv_step=self.step_conv1) - data_pooled1 = self.pooling(data_conved1,self.size_pooling1) + data_focus1, data_conved1 = self.convolute( + data_train, + self.conv1, + self.w_conv1, + self.thre_conv1, + conv_step=self.step_conv1, + ) + data_pooled1 = self.pooling(data_conved1, self.size_pooling1) shape_featuremap1 = np.shape(data_conved1) - ''' + """ print(' -----original shape ', np.shape(data_train)) print(' ---- after convolution ',np.shape(data_conv1)) print(' -----after pooling ',np.shape(data_pooled1)) - ''' + """ data_bp_input = self._expand(data_pooled1) bp_out1 = data_bp_input - bp_net_j = np.dot(bp_out1,self.vji.T) - self.thre_bp2 + bp_net_j = np.dot(bp_out1, self.vji.T) - self.thre_bp2 bp_out2 = self.sig(bp_net_j) - bp_net_k = np.dot(bp_out2 ,self.wkj.T) - self.thre_bp3 + bp_net_k = np.dot(bp_out2, self.wkj.T) - self.thre_bp3 bp_out3 = self.sig(bp_net_k) - #--------------Model Leaning ------------------------ + # --------------Model Leaning ------------------------ # calcluate error and gradient--------------- - pd_k_all = np.multiply((data_teach - bp_out3), np.multiply(bp_out3, (1 - bp_out3))) - pd_j_all = np.multiply(np.dot(pd_k_all,self.wkj), np.multiply(bp_out2, (1 - bp_out2))) - pd_i_all = np.dot(pd_j_all,self.vji) + pd_k_all = np.multiply( + (data_teach - bp_out3), np.multiply(bp_out3, (1 - bp_out3)) + ) + pd_j_all = np.multiply( + np.dot(pd_k_all, self.wkj), np.multiply(bp_out2, (1 - bp_out2)) + ) + pd_i_all = np.dot(pd_j_all, self.vji) - pd_conv1_pooled = pd_i_all / (self.size_pooling1*self.size_pooling1) + pd_conv1_pooled = pd_i_all / (self.size_pooling1 * self.size_pooling1) pd_conv1_pooled = pd_conv1_pooled.T.getA().tolist() - pd_conv1_all = self._calculate_gradient_from_pool(data_conved1,pd_conv1_pooled,shape_featuremap1[0], - shape_featuremap1[1],self.size_pooling1) - #weight and threshold learning process--------- - #convolution layer + pd_conv1_all = self._calculate_gradient_from_pool( + data_conved1, + pd_conv1_pooled, + shape_featuremap1[0], + shape_featuremap1[1], + self.size_pooling1, + ) + # weight and threshold learning process--------- + # convolution layer for k_conv in range(self.conv1[1]): pd_conv_list = self._expand_mat(pd_conv1_all[k_conv]) - delta_w = self.rate_weight * np.dot(pd_conv_list,data_focus1) + delta_w = self.rate_weight * np.dot(pd_conv_list, data_focus1) - self.w_conv1[k_conv] = self.w_conv1[k_conv] + delta_w.reshape((self.conv1[0],self.conv1[0])) + self.w_conv1[k_conv] = self.w_conv1[k_conv] + delta_w.reshape( + (self.conv1[0], self.conv1[0]) + ) - self.thre_conv1[k_conv] = self.thre_conv1[k_conv] - np.sum(pd_conv1_all[k_conv]) * self.rate_thre - #all connected layer + self.thre_conv1[k_conv] = ( + self.thre_conv1[k_conv] + - np.sum(pd_conv1_all[k_conv]) * self.rate_thre + ) + # all connected layer self.wkj = self.wkj + pd_k_all.T * bp_out2 * self.rate_weight self.vji = self.vji + pd_j_all.T * bp_out1 * self.rate_weight self.thre_bp3 = self.thre_bp3 - pd_k_all * self.rate_thre @@ -246,34 +288,41 @@ def train(self, patterns, datas_train, datas_teach, n_repeat, error_accuracy, dr # calculate the sum error of all single image errors = np.sum(abs((data_teach - bp_out3))) alle = alle + errors - #print(' ----Teach ',data_teach) - #print(' ----BP_output ',bp_out3) + # print(' ----Teach ',data_teach) + # print(' ----BP_output ',bp_out3) rp = rp + 1 - mse = alle/patterns + mse = alle / patterns all_mse.append(mse) + def draw_error(): yplot = [error_accuracy for i in range(int(n_repeat * 1.2))] - plt.plot(all_mse, '+-') - plt.plot(yplot, 'r--') - plt.xlabel('Learning Times') - plt.ylabel('All_mse') + plt.plot(all_mse, "+-") + plt.plot(yplot, "r--") + plt.xlabel("Learning Times") + plt.ylabel("All_mse") plt.grid(True, alpha=0.5) plt.show() - print('------------------Training Complished---------------------') - print((' - - Training epoch: ', rp, ' - - Mse: %.6f' % mse)) + + print("------------------Training Complished---------------------") + print((" - - Training epoch: ", rp, " - - Mse: %.6f" % mse)) if draw_e: draw_error() return mse def predict(self, datas_test): - #model predict + # model predict produce_out = [] - print('-------------------Start Testing-------------------------') - print((' - - Shape: Test_Data ',np.shape(datas_test))) + print("-------------------Start Testing-------------------------") + print((" - - Shape: Test_Data ", np.shape(datas_test))) for p in range(len(datas_test)): data_test = np.asmatrix(datas_test[p]) - data_focus1, data_conved1 = self.convolute(data_test, self.conv1, self.w_conv1, - self.thre_conv1, conv_step=self.step_conv1) + data_focus1, data_conved1 = self.convolute( + data_test, + self.conv1, + self.w_conv1, + self.thre_conv1, + conv_step=self.step_conv1, + ) data_pooled1 = self.pooling(data_conved1, self.size_pooling1) data_bp_input = self._expand(data_pooled1) @@ -283,20 +332,25 @@ def predict(self, datas_test): bp_net_k = bp_out2 * self.wkj.T - self.thre_bp3 bp_out3 = self.sig(bp_net_k) produce_out.extend(bp_out3.getA().tolist()) - res = [list(map(self.do_round,each)) for each in produce_out] + res = [list(map(self.do_round, each)) for each in produce_out] return np.asarray(res) def convolution(self, data): - #return the data of image after convoluting process so we can check it out + # return the data of image after convoluting process so we can check it out data_test = np.asmatrix(data) - data_focus1, data_conved1 = self.convolute(data_test, self.conv1, self.w_conv1, - self.thre_conv1, conv_step=self.step_conv1) + data_focus1, data_conved1 = self.convolute( + data_test, + self.conv1, + self.w_conv1, + self.thre_conv1, + conv_step=self.step_conv1, + ) data_pooled1 = self.pooling(data_conved1, self.size_pooling1) - return data_conved1,data_pooled1 + return data_conved1, data_pooled1 -if __name__ == '__main__': - ''' +if __name__ == "__main__": + """ I will put the example on other file - ''' + """ diff --git a/neural_network/perceptron.py b/neural_network/perceptron.py index fdc710597241..5feb8610a911 100644 --- a/neural_network/perceptron.py +++ b/neural_network/perceptron.py @@ -1,4 +1,4 @@ -''' +""" Perceptron w = w + N * (d(k) - y) * x(k) @@ -8,7 +8,7 @@ p1 = -1 p2 = 1 -''' +""" import random @@ -28,7 +28,7 @@ def training(self): sample.insert(0, self.bias) for i in range(self.col_sample): - self.weight.append(random.random()) + self.weight.append(random.random()) self.weight.insert(0, self.bias) @@ -45,15 +45,18 @@ def training(self): for j in range(self.col_sample + 1): - self.weight[j] = self.weight[j] + self.learn_rate * (self.exit[i] - y) * self.sample[i][j] + self.weight[j] = ( + self.weight[j] + + self.learn_rate * (self.exit[i] - y) * self.sample[i][j] + ) erro = True - #print('Epoch: \n',epoch_count) + # print('Epoch: \n',epoch_count) epoch_count = epoch_count + 1 # if you want controle the epoch or just by erro if erro == False: - print(('\nEpoch:\n',epoch_count)) - print('------------------------\n') - #if epoch_count > self.epoch_number or not erro: + print(("\nEpoch:\n", epoch_count)) + print("------------------------\n") + # if epoch_count > self.epoch_number or not erro: break def sort(self, sample): @@ -64,12 +67,12 @@ def sort(self, sample): y = self.sign(u) - if y == -1: - print(('Sample: ', sample)) - print('classification: P1') + if y == -1: + print(("Sample: ", sample)) + print("classification: P1") else: - print(('Sample: ', sample)) - print('classification: P2') + print(("Sample: ", sample)) + print("classification: P2") def sign(self, u): return 1 if u >= 0 else -1 @@ -105,19 +108,51 @@ def sign(self, u): [-0.1013, 0.5989, 7.1812], [2.4482, 0.9455, 11.2095], [2.0149, 0.6192, 10.9263], - [0.2012, 0.2611, 5.4631] - + [0.2012, 0.2611, 5.4631], ] -exit = [-1, -1, -1, 1, 1, -1, 1, -1, 1, 1, -1, 1, -1, -1, -1, -1, 1, 1, 1, 1, -1, 1, 1, 1, 1, -1, -1, 1, -1, 1] +exit = [ + -1, + -1, + -1, + 1, + 1, + -1, + 1, + -1, + 1, + 1, + -1, + 1, + -1, + -1, + -1, + -1, + 1, + 1, + 1, + 1, + -1, + 1, + 1, + 1, + 1, + -1, + -1, + 1, + -1, + 1, +] -network = Perceptron(sample=samples, exit = exit, learn_rate=0.01, epoch_number=1000, bias=-1) +network = Perceptron( + sample=samples, exit=exit, learn_rate=0.01, epoch_number=1000, bias=-1 +) network.training() -if __name__ == '__main__': +if __name__ == "__main__": while True: sample = [] for i in range(3): - sample.insert(i, float(input('value: '))) + sample.insert(i, float(input("value: "))) network.sort(sample) diff --git a/other/anagrams.py b/other/anagrams.py index 1e6e38dee139..9e103296b382 100644 --- a/other/anagrams.py +++ b/other/anagrams.py @@ -1,29 +1,32 @@ import collections, pprint, time, os start_time = time.time() -print('creating word list...') +print("creating word list...") path = os.path.split(os.path.realpath(__file__)) -with open(path[0] + '/words') as f: +with open(path[0] + "/words") as f: word_list = sorted(list(set([word.strip().lower() for word in f]))) + def signature(word): - return ''.join(sorted(word)) + return "".join(sorted(word)) + word_bysig = collections.defaultdict(list) for word in word_list: word_bysig[signature(word)].append(word) + def anagram(myword): return word_bysig[signature(myword)] -print('finding anagrams...') -all_anagrams = {word: anagram(word) - for word in word_list if len(anagram(word)) > 1} -print('writing anagrams to file...') -with open('anagrams.txt', 'w') as file: - file.write('all_anagrams = ') +print("finding anagrams...") +all_anagrams = {word: anagram(word) for word in word_list if len(anagram(word)) > 1} + +print("writing anagrams to file...") +with open("anagrams.txt", "w") as file: + file.write("all_anagrams = ") file.write(pprint.pformat(all_anagrams)) total_time = round(time.time() - start_time, 2) -print(('Done [', total_time, 'seconds ]')) +print(("Done [", total_time, "seconds ]")) diff --git a/other/binary_exponentiation.py b/other/binary_exponentiation.py index 1a30fb8fd266..dd4e70e74129 100644 --- a/other/binary_exponentiation.py +++ b/other/binary_exponentiation.py @@ -14,7 +14,7 @@ def b_expo(a, b): res = 1 while b > 0: - if b&1: + if b & 1: res *= a a *= a @@ -26,14 +26,15 @@ def b_expo(a, b): def b_expo_mod(a, b, c): res = 1 while b > 0: - if b&1: - res = ((res%c) * (a%c)) % c + if b & 1: + res = ((res % c) * (a % c)) % c a *= a b >>= 1 return res + """ * Wondering how this method works ! * It's pretty simple. diff --git a/other/binary_exponentiation_2.py b/other/binary_exponentiation_2.py index 217a616c99fb..51ec4baf2598 100644 --- a/other/binary_exponentiation_2.py +++ b/other/binary_exponentiation_2.py @@ -14,7 +14,7 @@ def b_expo(a, b): res = 0 while b > 0: - if b&1: + if b & 1: res += a a += a @@ -26,8 +26,8 @@ def b_expo(a, b): def b_expo_mod(a, b, c): res = 0 while b > 0: - if b&1: - res = ((res%c) + (a%c)) % c + if b & 1: + res = ((res % c) + (a % c)) % c a += a b >>= 1 diff --git a/other/detecting_english_programmatically.py b/other/detecting_english_programmatically.py index 8b73ff6cf0c3..4b0bb37ce520 100644 --- a/other/detecting_english_programmatically.py +++ b/other/detecting_english_programmatically.py @@ -1,18 +1,21 @@ import os -UPPERLETTERS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' -LETTERS_AND_SPACE = UPPERLETTERS + UPPERLETTERS.lower() + ' \t\n' +UPPERLETTERS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" +LETTERS_AND_SPACE = UPPERLETTERS + UPPERLETTERS.lower() + " \t\n" + def loadDictionary(): path = os.path.split(os.path.realpath(__file__)) englishWords = {} - with open(path[0] + '/dictionary.txt') as dictionaryFile: - for word in dictionaryFile.read().split('\n'): + with open(path[0] + "/dictionary.txt") as dictionaryFile: + for word in dictionaryFile.read().split("\n"): englishWords[word] = None return englishWords + ENGLISH_WORDS = loadDictionary() + def getEnglishCount(message): message = message.upper() message = removeNonLetters(message) @@ -28,14 +31,16 @@ def getEnglishCount(message): return float(matches) / len(possibleWords) + def removeNonLetters(message): lettersOnly = [] for symbol in message: if symbol in LETTERS_AND_SPACE: lettersOnly.append(symbol) - return ''.join(lettersOnly) + return "".join(lettersOnly) + -def isEnglish(message, wordPercentage = 20, letterPercentage = 85): +def isEnglish(message, wordPercentage=20, letterPercentage=85): """ >>> isEnglish('Hello World') True @@ -51,4 +56,5 @@ def isEnglish(message, wordPercentage = 20, letterPercentage = 85): import doctest + doctest.testmod() diff --git a/other/euclidean_gcd.py b/other/euclidean_gcd.py index 13378379f286..c6c11f947a08 100644 --- a/other/euclidean_gcd.py +++ b/other/euclidean_gcd.py @@ -1,5 +1,6 @@ # https://en.wikipedia.org/wiki/Euclidean_algorithm + def euclidean_gcd(a, b): while b: t = b @@ -7,6 +8,7 @@ def euclidean_gcd(a, b): a = t return a + def main(): print("GCD(3, 5) = " + str(euclidean_gcd(3, 5))) print("GCD(5, 3) = " + str(euclidean_gcd(5, 3))) @@ -14,5 +16,6 @@ def main(): print("GCD(3, 6) = " + str(euclidean_gcd(3, 6))) print("GCD(6, 3) = " + str(euclidean_gcd(6, 3))) -if __name__ == '__main__': + +if __name__ == "__main__": main() diff --git a/other/fischer_yates_shuffle.py b/other/fischer_yates_shuffle.py index bc2b136344c7..977e5f131e4f 100644 --- a/other/fischer_yates_shuffle.py +++ b/other/fischer_yates_shuffle.py @@ -7,16 +7,18 @@ """ import random + def FYshuffle(LIST): for i in range(len(LIST)): - a = random.randint(0, len(LIST)-1) - b = random.randint(0, len(LIST)-1) + a = random.randint(0, len(LIST) - 1) + b = random.randint(0, len(LIST) - 1) LIST[a], LIST[b] = LIST[b], LIST[a] return LIST -if __name__ == '__main__': - integers = [0,1,2,3,4,5,6,7] - strings = ['python', 'says', 'hello', '!'] - print('Fisher-Yates Shuffle:') - print('List',integers, strings) - print('FY Shuffle',FYshuffle(integers), FYshuffle(strings)) + +if __name__ == "__main__": + integers = [0, 1, 2, 3, 4, 5, 6, 7] + strings = ["python", "says", "hello", "!"] + print("Fisher-Yates Shuffle:") + print("List", integers, strings) + print("FY Shuffle", FYshuffle(integers), FYshuffle(strings)) diff --git a/other/frequency_finder.py b/other/frequency_finder.py index 6264b25bf303..48760a9deb09 100644 --- a/other/frequency_finder.py +++ b/other/frequency_finder.py @@ -1,29 +1,78 @@ # Frequency Finder # frequency taken from http://en.wikipedia.org/wiki/Letter_frequency -englishLetterFreq = {'E': 12.70, 'T': 9.06, 'A': 8.17, 'O': 7.51, 'I': 6.97, - 'N': 6.75, 'S': 6.33, 'H': 6.09, 'R': 5.99, 'D': 4.25, - 'L': 4.03, 'C': 2.78, 'U': 2.76, 'M': 2.41, 'W': 2.36, - 'F': 2.23, 'G': 2.02, 'Y': 1.97, 'P': 1.93, 'B': 1.29, - 'V': 0.98, 'K': 0.77, 'J': 0.15, 'X': 0.15, 'Q': 0.10, - 'Z': 0.07} -ETAOIN = 'ETAOINSHRDLCUMWFGYPBVKJXQZ' -LETTERS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' +englishLetterFreq = { + "E": 12.70, + "T": 9.06, + "A": 8.17, + "O": 7.51, + "I": 6.97, + "N": 6.75, + "S": 6.33, + "H": 6.09, + "R": 5.99, + "D": 4.25, + "L": 4.03, + "C": 2.78, + "U": 2.76, + "M": 2.41, + "W": 2.36, + "F": 2.23, + "G": 2.02, + "Y": 1.97, + "P": 1.93, + "B": 1.29, + "V": 0.98, + "K": 0.77, + "J": 0.15, + "X": 0.15, + "Q": 0.10, + "Z": 0.07, +} +ETAOIN = "ETAOINSHRDLCUMWFGYPBVKJXQZ" +LETTERS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + def getLetterCount(message): - letterCount = {'A': 0, 'B': 0, 'C': 0, 'D': 0, 'E': 0, 'F': 0, 'G': 0, 'H': 0, - 'I': 0, 'J': 0, 'K': 0, 'L': 0, 'M': 0, 'N': 0, 'O': 0, 'P': 0, - 'Q': 0, 'R': 0, 'S': 0, 'T': 0, 'U': 0, 'V': 0, 'W': 0, 'X': 0, - 'Y': 0, 'Z': 0} + letterCount = { + "A": 0, + "B": 0, + "C": 0, + "D": 0, + "E": 0, + "F": 0, + "G": 0, + "H": 0, + "I": 0, + "J": 0, + "K": 0, + "L": 0, + "M": 0, + "N": 0, + "O": 0, + "P": 0, + "Q": 0, + "R": 0, + "S": 0, + "T": 0, + "U": 0, + "V": 0, + "W": 0, + "X": 0, + "Y": 0, + "Z": 0, + } for letter in message.upper(): if letter in LETTERS: letterCount[letter] += 1 return letterCount + def getItemAtIndexZero(x): return x[0] + def getFrequencyOrder(message): letterToFreq = getLetterCount(message) freqToLetter = {} @@ -34,23 +83,24 @@ def getFrequencyOrder(message): freqToLetter[letterToFreq[letter]].append(letter) for freq in freqToLetter: - freqToLetter[freq].sort(key = ETAOIN.find, reverse = True) - freqToLetter[freq] = ''.join(freqToLetter[freq]) + freqToLetter[freq].sort(key=ETAOIN.find, reverse=True) + freqToLetter[freq] = "".join(freqToLetter[freq]) freqPairs = list(freqToLetter.items()) - freqPairs.sort(key = getItemAtIndexZero, reverse = True) + freqPairs.sort(key=getItemAtIndexZero, reverse=True) freqOrder = [] for freqPair in freqPairs: freqOrder.append(freqPair[1]) - return ''.join(freqOrder) + return "".join(freqOrder) + def englishFreqMatchScore(message): - ''' + """ >>> englishFreqMatchScore('Hello World') 1 - ''' + """ freqOrder = getFrequencyOrder(message) matchScore = 0 for commonLetter in ETAOIN[:6]: @@ -63,6 +113,8 @@ def englishFreqMatchScore(message): return matchScore -if __name__ == '__main__': + +if __name__ == "__main__": import doctest + doctest.testmod() diff --git a/other/game_of_life.py b/other/game_of_life.py index 1fdaa21b4a7b..2b4d1116fa8c 100644 --- a/other/game_of_life.py +++ b/other/game_of_life.py @@ -1,4 +1,4 @@ -'''Conway's Game Of Life, Author Anurag Kumar(mailto:anuragkumarak95@gmail.com) +"""Conway's Game Of Life, Author Anurag Kumar(mailto:anuragkumarak95@gmail.com) Requirements: - numpy @@ -26,28 +26,31 @@ 4. Any dead cell with exactly three live neighbours be- comes a live cell, as if by reproduction. - ''' + """ import numpy as np import random, sys from matplotlib import pyplot as plt from matplotlib.colors import ListedColormap -usage_doc='Usage of script: script_nama ' +usage_doc = "Usage of script: script_nama " -choice = [0]*100 + [1]*10 +choice = [0] * 100 + [1] * 10 random.shuffle(choice) + def create_canvas(size): - canvas = [ [False for i in range(size)] for j in range(size)] + canvas = [[False for i in range(size)] for j in range(size)] return canvas + def seed(canvas): - for i,row in enumerate(canvas): - for j,_ in enumerate(row): - canvas[i][j]=bool(random.getrandbits(1)) + for i, row in enumerate(canvas): + for j, _ in enumerate(row): + canvas[i][j] = bool(random.getrandbits(1)) + def run(canvas): - ''' This function runs the rules of game through all points, and changes their status accordingly.(in the same canvas) + """ This function runs the rules of game through all points, and changes their status accordingly.(in the same canvas) @Args: -- canvas : canvas of population to run the rules on. @@ -55,63 +58,71 @@ def run(canvas): @returns: -- None - ''' + """ canvas = np.array(canvas) next_gen_canvas = np.array(create_canvas(canvas.shape[0])) for r, row in enumerate(canvas): for c, pt in enumerate(row): # print(r-1,r+2,c-1,c+2) - next_gen_canvas[r][c] = __judge_point(pt,canvas[r-1:r+2,c-1:c+2]) - + next_gen_canvas[r][c] = __judge_point( + pt, canvas[r - 1 : r + 2, c - 1 : c + 2] + ) + canvas = next_gen_canvas - del next_gen_canvas # cleaning memory as we move on. - return canvas.tolist() + del next_gen_canvas # cleaning memory as we move on. + return canvas.tolist() + -def __judge_point(pt,neighbours): - dead = 0 +def __judge_point(pt, neighbours): + dead = 0 alive = 0 # finding dead or alive neighbours count. for i in neighbours: for status in i: - if status: alive+=1 - else: dead+=1 + if status: + alive += 1 + else: + dead += 1 # handling duplicate entry for focus pt. - if pt : alive-=1 - else : dead-=1 - + if pt: + alive -= 1 + else: + dead -= 1 + # running the rules of game here. state = pt if pt: - if alive<2: - state=False - elif alive==2 or alive==3: - state=True - elif alive>3: - state=False + if alive < 2: + state = False + elif alive == 2 or alive == 3: + state = True + elif alive > 3: + state = False else: - if alive==3: - state=True + if alive == 3: + state = True return state -if __name__=='__main__': - if len(sys.argv) != 2: raise Exception(usage_doc) - +if __name__ == "__main__": + if len(sys.argv) != 2: + raise Exception(usage_doc) + canvas_size = int(sys.argv[1]) # main working structure of this module. - c=create_canvas(canvas_size) + c = create_canvas(canvas_size) seed(c) fig, ax = plt.subplots() - fig.show() - cmap = ListedColormap(['w','k']) + fig.show() + cmap = ListedColormap(["w", "k"]) try: while True: - c = run(c) - ax.matshow(c,cmap=cmap) + c = run(c) + ax.matshow(c, cmap=cmap) fig.canvas.draw() - ax.cla() + ax.cla() except KeyboardInterrupt: # do nothing. pass diff --git a/other/linear_congruential_generator.py b/other/linear_congruential_generator.py index 7c592a6400b5..3b150f422e4f 100644 --- a/other/linear_congruential_generator.py +++ b/other/linear_congruential_generator.py @@ -2,12 +2,13 @@ from time import time + class LinearCongruentialGenerator(object): """ A pseudorandom number generator. """ - def __init__( self, multiplier, increment, modulo, seed=int(time()) ): + def __init__(self, multiplier, increment, modulo, seed=int(time())): """ These parameters are saved and used when nextNumber() is called. @@ -19,7 +20,7 @@ def __init__( self, multiplier, increment, modulo, seed=int(time()) ): self.modulo = modulo self.seed = seed - def next_number( self ): + def next_number(self): """ The smallest number that can be generated is zero. The largest number that can be generated is modulo-1. modulo is set in the constructor. @@ -27,8 +28,9 @@ def next_number( self ): self.seed = (self.multiplier * self.seed + self.increment) % self.modulo return self.seed + if __name__ == "__main__": # Show the LCG in action. - lcg = LinearCongruentialGenerator(1664525, 1013904223, 2<<31) - while True : - print(lcg.next_number()) \ No newline at end of file + lcg = LinearCongruentialGenerator(1664525, 1013904223, 2 << 31) + while True: + print(lcg.next_number()) diff --git a/other/nested_brackets.py b/other/nested_brackets.py index 14147eaa6456..011e94b92928 100644 --- a/other/nested_brackets.py +++ b/other/nested_brackets.py @@ -1,4 +1,4 @@ -''' +""" The nested brackets problem is a problem that determines if a sequence of brackets are properly nested. A sequence of brackets s is considered properly nested if any of the following conditions are true: @@ -12,13 +12,15 @@ The function called is_balanced takes as input a string S which is a sequence of brackets and returns true if S is nested and false otherwise. -''' +""" + + def is_balanced(S): stack = [] - open_brackets = set({'(', '[', '{'}) - closed_brackets = set({')', ']', '}'}) - open_to_closed = dict({'{':'}', '[':']', '(':')'}) + open_brackets = set({"(", "[", "{"}) + closed_brackets = set({")", "]", "}"}) + open_to_closed = dict({"{": "}", "[": "]", "(": ")"}) for i in range(len(S)): @@ -26,7 +28,9 @@ def is_balanced(S): stack.append(S[i]) elif S[i] in closed_brackets: - if len(stack) == 0 or (len(stack) > 0 and open_to_closed[stack.pop()] != S[i]): + if len(stack) == 0 or ( + len(stack) > 0 and open_to_closed[stack.pop()] != S[i] + ): return False return len(stack) == 0 diff --git a/other/palindrome.py b/other/palindrome.py index 990ec844f9fb..2ca453b64702 100644 --- a/other/palindrome.py +++ b/other/palindrome.py @@ -22,10 +22,10 @@ def recursive_palindrome(str): def main(): - str = 'ama' + str = "ama" print(recursive_palindrome(str.lower())) print(is_palindrome(str.lower())) -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/other/password_generator.py b/other/password_generator.py index 16b7e16b22a1..f72686bfb1c0 100644 --- a/other/password_generator.py +++ b/other/password_generator.py @@ -17,7 +17,7 @@ def password_generator(length=8): 0 """ chars = tuple(ascii_letters) + tuple(digits) + tuple(punctuation) - return ''.join(choice(chars) for x in range(length)) + return "".join(choice(chars) for x in range(length)) # ALTERNATIVE METHODS @@ -42,11 +42,10 @@ def random_characters(ctbi, i): def main(): - length = int( - input('Please indicate the max length of your password: ').strip()) - print('Password generated:', password_generator(length)) - print('[If you are thinking of using this passsword, You better save it.]') + length = int(input("Please indicate the max length of your password: ").strip()) + print("Password generated:", password_generator(length)) + print("[If you are thinking of using this passsword, You better save it.]") -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/other/primelib.py b/other/primelib.py index c000213a7a42..6fc5eddeb257 100644 --- a/other/primelib.py +++ b/other/primelib.py @@ -49,8 +49,9 @@ def isPrime(number): """ # precondition - assert isinstance(number,int) and (number >= 0) , \ - "'number' must been an int and positive" + assert isinstance(number, int) and ( + number >= 0 + ), "'number' must been an int and positive" status = True @@ -58,7 +59,7 @@ def isPrime(number): if number <= 1: status = False - for divisor in range(2,int(round(sqrt(number)))+1): + for divisor in range(2, int(round(sqrt(number))) + 1): # if 'number' divisible by 'divisor' then sets 'status' # of false and break up the loop. @@ -67,12 +68,14 @@ def isPrime(number): break # precondition - assert isinstance(status,bool), "'status' must been from type bool" + assert isinstance(status, bool), "'status' must been from type bool" return status + # ------------------------------------------ + def sieveEr(N): """ input: positive integer 'N' > 2 @@ -84,33 +87,33 @@ def sieveEr(N): """ # precondition - assert isinstance(N,int) and (N > 2), "'N' must been an int and > 2" + assert isinstance(N, int) and (N > 2), "'N' must been an int and > 2" # beginList: conatins all natural numbers from 2 upt to N - beginList = [x for x in range(2,N+1)] + beginList = [x for x in range(2, N + 1)] - ans = [] # this list will be returns. + ans = [] # this list will be returns. # actual sieve of erathostenes for i in range(len(beginList)): - for j in range(i+1,len(beginList)): + for j in range(i + 1, len(beginList)): - if (beginList[i] != 0) and \ - (beginList[j] % beginList[i] == 0): + if (beginList[i] != 0) and (beginList[j] % beginList[i] == 0): beginList[j] = 0 # filters actual prime numbers. ans = [x for x in beginList if x != 0] # precondition - assert isinstance(ans,list), "'ans' must been from type list" + assert isinstance(ans, list), "'ans' must been from type list" return ans # -------------------------------- + def getPrimeNumbers(N): """ input: positive integer 'N' > 2 @@ -119,26 +122,27 @@ def getPrimeNumbers(N): """ # precondition - assert isinstance(N,int) and (N > 2), "'N' must been an int and > 2" + assert isinstance(N, int) and (N > 2), "'N' must been an int and > 2" ans = [] # iterates over all numbers between 2 up to N+1 # if a number is prime then appends to list 'ans' - for number in range(2,N+1): + for number in range(2, N + 1): if isPrime(number): ans.append(number) # precondition - assert isinstance(ans,list), "'ans' must been from type list" + assert isinstance(ans, list), "'ans' must been from type list" return ans # ----------------------------------------- + def primeFactorization(number): """ input: positive integer 'number' @@ -146,10 +150,9 @@ def primeFactorization(number): """ # precondition - assert isinstance(number,int) and number >= 0, \ - "'number' must been an int and >= 0" + assert isinstance(number, int) and number >= 0, "'number' must been an int and >= 0" - ans = [] # this list will be returns of the function. + ans = [] # this list will be returns of the function. # potential prime number factors. @@ -157,7 +160,6 @@ def primeFactorization(number): quotient = number - if number == 0 or number == 1: ans.append(number) @@ -165,25 +167,26 @@ def primeFactorization(number): # if 'number' not prime then builds the prime factorization of 'number' elif not isPrime(number): - while (quotient != 1): + while quotient != 1: if isPrime(factor) and (quotient % factor == 0): - ans.append(factor) - quotient /= factor + ans.append(factor) + quotient /= factor else: - factor += 1 + factor += 1 else: ans.append(number) # precondition - assert isinstance(ans,list), "'ans' must been from type list" + assert isinstance(ans, list), "'ans' must been from type list" return ans # ----------------------------------------- + def greatestPrimeFactor(number): """ input: positive integer 'number' >= 0 @@ -191,8 +194,9 @@ def greatestPrimeFactor(number): """ # precondition - assert isinstance(number,int) and (number >= 0), \ - "'number' bust been an int and >= 0" + assert isinstance(number, int) and ( + number >= 0 + ), "'number' bust been an int and >= 0" ans = 0 @@ -202,7 +206,7 @@ def greatestPrimeFactor(number): ans = max(primeFactors) # precondition - assert isinstance(ans,int), "'ans' must been from type int" + assert isinstance(ans, int), "'ans' must been from type int" return ans @@ -217,8 +221,9 @@ def smallestPrimeFactor(number): """ # precondition - assert isinstance(number,int) and (number >= 0), \ - "'number' bust been an int and >= 0" + assert isinstance(number, int) and ( + number >= 0 + ), "'number' bust been an int and >= 0" ans = 0 @@ -228,13 +233,14 @@ def smallestPrimeFactor(number): ans = min(primeFactors) # precondition - assert isinstance(ans,int), "'ans' must been from type int" + assert isinstance(ans, int), "'ans' must been from type int" return ans # ---------------------- + def isEven(number): """ input: integer 'number' @@ -247,8 +253,10 @@ def isEven(number): return number % 2 == 0 + # ------------------------ + def isOdd(number): """ input: integer 'number' @@ -261,6 +269,7 @@ def isOdd(number): return number % 2 != 0 + # ------------------------ @@ -272,10 +281,11 @@ def goldbach(number): """ # precondition - assert isinstance(number,int) and (number > 2) and isEven(number), \ - "'number' must been an int, even and > 2" + assert ( + isinstance(number, int) and (number > 2) and isEven(number) + ), "'number' must been an int, even and > 2" - ans = [] # this list will returned + ans = [] # this list will returned # creates a list of prime numbers between 2 up to 'number' primeNumbers = getPrimeNumbers(number) @@ -288,12 +298,11 @@ def goldbach(number): # exit variable. for break up the loops loop = True - while (i < lenPN and loop): + while i < lenPN and loop: - j = i+1 + j = i + 1 - - while (j < lenPN and loop): + while j < lenPN and loop: if primeNumbers[i] + primeNumbers[j] == number: loop = False @@ -305,15 +314,21 @@ def goldbach(number): i += 1 # precondition - assert isinstance(ans,list) and (len(ans) == 2) and \ - (ans[0] + ans[1] == number) and isPrime(ans[0]) and isPrime(ans[1]), \ - "'ans' must contains two primes. And sum of elements must been eq 'number'" + assert ( + isinstance(ans, list) + and (len(ans) == 2) + and (ans[0] + ans[1] == number) + and isPrime(ans[0]) + and isPrime(ans[1]) + ), "'ans' must contains two primes. And sum of elements must been eq 'number'" return ans + # ---------------------------------------------- -def gcd(number1,number2): + +def gcd(number1, number2): """ Greatest common divisor input: two positive integer 'number1' and 'number2' @@ -321,9 +336,12 @@ def gcd(number1,number2): """ # precondition - assert isinstance(number1,int) and isinstance(number2,int) \ - and (number1 >= 0) and (number2 >= 0), \ - "'number1' and 'number2' must been positive integer." + assert ( + isinstance(number1, int) + and isinstance(number2, int) + and (number1 >= 0) + and (number2 >= 0) + ), "'number1' and 'number2' must been positive integer." rest = 0 @@ -334,13 +352,16 @@ def gcd(number1,number2): number2 = rest # precondition - assert isinstance(number1,int) and (number1 >= 0), \ - "'number' must been from type int and positive" + assert isinstance(number1, int) and ( + number1 >= 0 + ), "'number' must been from type int and positive" return number1 + # ---------------------------------------------------- + def kgV(number1, number2): """ Least common multiple @@ -349,11 +370,14 @@ def kgV(number1, number2): """ # precondition - assert isinstance(number1,int) and isinstance(number2,int) \ - and (number1 >= 1) and (number2 >= 1), \ - "'number1' and 'number2' must been positive integer." + assert ( + isinstance(number1, int) + and isinstance(number2, int) + and (number1 >= 1) + and (number2 >= 1) + ), "'number1' and 'number2' must been positive integer." - ans = 1 # actual answer that will be return. + ans = 1 # actual answer that will be return. # for kgV (x,1) if number1 > 1 and number2 > 1: @@ -366,12 +390,12 @@ def kgV(number1, number2): primeFac1 = [] primeFac2 = [] - ans = max(number1,number2) + ans = max(number1, number2) count1 = 0 count2 = 0 - done = [] # captured numbers int both 'primeFac1' and 'primeFac2' + done = [] # captured numbers int both 'primeFac1' and 'primeFac2' # iterates through primeFac1 for n in primeFac1: @@ -383,7 +407,7 @@ def kgV(number1, number2): count1 = primeFac1.count(n) count2 = primeFac2.count(n) - for i in range(max(count1,count2)): + for i in range(max(count1, count2)): ans *= n else: @@ -408,13 +432,16 @@ def kgV(number1, number2): done.append(n) # precondition - assert isinstance(ans,int) and (ans >= 0), \ - "'ans' must been from type int and positive" + assert isinstance(ans, int) and ( + ans >= 0 + ), "'ans' must been from type int and positive" return ans + # ---------------------------------- + def getPrime(n): """ Gets the n-th prime number. @@ -423,16 +450,16 @@ def getPrime(n): """ # precondition - assert isinstance(n,int) and (n >= 0), "'number' must been a positive int" + assert isinstance(n, int) and (n >= 0), "'number' must been a positive int" index = 0 - ans = 2 # this variable holds the answer + ans = 2 # this variable holds the answer while index < n: index += 1 - ans += 1 # counts to the next number + ans += 1 # counts to the next number # if ans not prime then # runs to the next prime number. @@ -440,13 +467,16 @@ def getPrime(n): ans += 1 # precondition - assert isinstance(ans,int) and isPrime(ans), \ - "'ans' must been a prime number and from type int" + assert isinstance(ans, int) and isPrime( + ans + ), "'ans' must been a prime number and from type int" return ans + # --------------------------------------------------- + def getPrimesBetween(pNumber1, pNumber2): """ input: prime numbers 'pNumber1' and 'pNumber2' @@ -456,12 +486,13 @@ def getPrimesBetween(pNumber1, pNumber2): """ # precondition - assert isPrime(pNumber1) and isPrime(pNumber2) and (pNumber1 < pNumber2), \ - "The arguments must been prime numbers and 'pNumber1' < 'pNumber2'" + assert ( + isPrime(pNumber1) and isPrime(pNumber2) and (pNumber1 < pNumber2) + ), "The arguments must been prime numbers and 'pNumber1' < 'pNumber2'" - number = pNumber1 + 1 # jump to the next number + number = pNumber1 + 1 # jump to the next number - ans = [] # this list will be returns. + ans = [] # this list will be returns. # if number is not prime then # fetch the next prime number. @@ -479,15 +510,17 @@ def getPrimesBetween(pNumber1, pNumber2): number += 1 # precondition - assert isinstance(ans,list) and ans[0] != pNumber1 \ - and ans[len(ans)-1] != pNumber2, \ - "'ans' must been a list without the arguments" + assert ( + isinstance(ans, list) and ans[0] != pNumber1 and ans[len(ans) - 1] != pNumber2 + ), "'ans' must been a list without the arguments" # 'ans' contains not 'pNumber1' and 'pNumber2' ! return ans + # ---------------------------------------------------- + def getDivisors(n): """ input: positive integer 'n' >= 1 @@ -495,20 +528,17 @@ def getDivisors(n): """ # precondition - assert isinstance(n,int) and (n >= 1), "'n' must been int and >= 1" + assert isinstance(n, int) and (n >= 1), "'n' must been int and >= 1" - ans = [] # will be returned. + ans = [] # will be returned. - for divisor in range(1,n+1): + for divisor in range(1, n + 1): if n % divisor == 0: ans.append(divisor) - - #precondition - assert ans[0] == 1 and ans[len(ans)-1] == n, \ - "Error in function getDivisiors(...)" - + # precondition + assert ans[0] == 1 and ans[len(ans) - 1] == n, "Error in function getDivisiors(...)" return ans @@ -523,21 +553,26 @@ def isPerfectNumber(number): """ # precondition - assert isinstance(number,int) and (number > 1), \ - "'number' must been an int and >= 1" + assert isinstance(number, int) and ( + number > 1 + ), "'number' must been an int and >= 1" divisors = getDivisors(number) # precondition - assert isinstance(divisors,list) and(divisors[0] == 1) and \ - (divisors[len(divisors)-1] == number), \ - "Error in help-function getDivisiors(...)" + assert ( + isinstance(divisors, list) + and (divisors[0] == 1) + and (divisors[len(divisors) - 1] == number) + ), "Error in help-function getDivisiors(...)" # summed all divisors up to 'number' (exclusive), hence [:-1] return sum(divisors[:-1]) == number + # ------------------------------------------------------------ + def simplifyFraction(numerator, denominator): """ input: two integer 'numerator' and 'denominator' @@ -546,22 +581,28 @@ def simplifyFraction(numerator, denominator): """ # precondition - assert isinstance(numerator, int) and isinstance(denominator,int) \ - and (denominator != 0), \ - "The arguments must been from type int and 'denominator' != 0" + assert ( + isinstance(numerator, int) + and isinstance(denominator, int) + and (denominator != 0) + ), "The arguments must been from type int and 'denominator' != 0" # build the greatest common divisor of numerator and denominator. gcdOfFraction = gcd(abs(numerator), abs(denominator)) # precondition - assert isinstance(gcdOfFraction, int) and (numerator % gcdOfFraction == 0) \ - and (denominator % gcdOfFraction == 0), \ - "Error in function gcd(...,...)" + assert ( + isinstance(gcdOfFraction, int) + and (numerator % gcdOfFraction == 0) + and (denominator % gcdOfFraction == 0) + ), "Error in function gcd(...,...)" return (numerator // gcdOfFraction, denominator // gcdOfFraction) + # ----------------------------------------------------------------- + def factorial(n): """ input: positive integer 'n' @@ -569,17 +610,19 @@ def factorial(n): """ # precondition - assert isinstance(n,int) and (n >= 0), "'n' must been a int and >= 0" + assert isinstance(n, int) and (n >= 0), "'n' must been a int and >= 0" - ans = 1 # this will be return. + ans = 1 # this will be return. - for factor in range(1,n+1): + for factor in range(1, n + 1): ans *= factor return ans + # ------------------------------------------------------------------- + def fib(n): """ input: positive integer 'n' @@ -591,9 +634,9 @@ def fib(n): tmp = 0 fib1 = 1 - ans = 1 # this will be return + ans = 1 # this will be return - for i in range(n-1): + for i in range(n - 1): tmp = ans ans += fib1 diff --git a/other/sierpinski_triangle.py b/other/sierpinski_triangle.py index fc22aad96059..0e6ce43e35d3 100644 --- a/other/sierpinski_triangle.py +++ b/other/sierpinski_triangle.py @@ -1,7 +1,7 @@ #!/usr/bin/python # encoding=utf8 -'''Author Anurag Kumar | anuragkumarak95@gmail.com | git/anuragkumarak95 +"""Author Anurag Kumar | anuragkumarak95@gmail.com | git/anuragkumarak95 Simple example of Fractal generation using recursive function. @@ -23,46 +23,51 @@ Credits: This code was written by editing the code from http://www.riannetrujillo.com/blog/python-fractal/ -''' +""" import turtle import sys -PROGNAME = 'Sierpinski Triangle' -points = [[-175,-125],[0,175],[175,-125]] #size of triangle +PROGNAME = "Sierpinski Triangle" -def getMid(p1,p2): - return ( (p1[0]+p2[0]) / 2, (p1[1] + p2[1]) / 2) #find midpoint +points = [[-175, -125], [0, 175], [175, -125]] # size of triangle -def triangle(points,depth): + +def getMid(p1, p2): + return ((p1[0] + p2[0]) / 2, (p1[1] + p2[1]) / 2) # find midpoint + + +def triangle(points, depth): myPen.up() - myPen.goto(points[0][0],points[0][1]) + myPen.goto(points[0][0], points[0][1]) myPen.down() - myPen.goto(points[1][0],points[1][1]) - myPen.goto(points[2][0],points[2][1]) - myPen.goto(points[0][0],points[0][1]) - - if depth>0: - triangle([points[0], - getMid(points[0], points[1]), - getMid(points[0], points[2])], - depth-1) - triangle([points[1], - getMid(points[0], points[1]), - getMid(points[1], points[2])], - depth-1) - triangle([points[2], - getMid(points[2], points[1]), - getMid(points[0], points[2])], - depth-1) - - -if __name__ == '__main__': - if len(sys.argv) !=2: - raise ValueError('right format for using this script: ' - '$python fractals.py ') + myPen.goto(points[1][0], points[1][1]) + myPen.goto(points[2][0], points[2][1]) + myPen.goto(points[0][0], points[0][1]) + + if depth > 0: + triangle( + [points[0], getMid(points[0], points[1]), getMid(points[0], points[2])], + depth - 1, + ) + triangle( + [points[1], getMid(points[0], points[1]), getMid(points[1], points[2])], + depth - 1, + ) + triangle( + [points[2], getMid(points[2], points[1]), getMid(points[0], points[2])], + depth - 1, + ) + + +if __name__ == "__main__": + if len(sys.argv) != 2: + raise ValueError( + "right format for using this script: " + "$python fractals.py " + ) myPen = turtle.Turtle() myPen.ht() myPen.speed(5) - myPen.pencolor('red') - triangle(points,int(sys.argv[1])) + myPen.pencolor("red") + triangle(points, int(sys.argv[1])) diff --git a/other/tower_of_hanoi.py b/other/tower_of_hanoi.py index cd6fbf4d88ac..3cc0e40b369f 100644 --- a/other/tower_of_hanoi.py +++ b/other/tower_of_hanoi.py @@ -1,5 +1,5 @@ def moveTower(height, fromPole, toPole, withPole): - ''' + """ >>> moveTower(3, 'A', 'B', 'C') moving disk from A to B moving disk from A to C @@ -8,18 +8,21 @@ def moveTower(height, fromPole, toPole, withPole): moving disk from C to A moving disk from C to B moving disk from A to B - ''' + """ if height >= 1: - moveTower(height-1, fromPole, withPole, toPole) + moveTower(height - 1, fromPole, withPole, toPole) moveDisk(fromPole, toPole) - moveTower(height-1, withPole, toPole, fromPole) + moveTower(height - 1, withPole, toPole, fromPole) + + +def moveDisk(fp, tp): + print("moving disk from", fp, "to", tp) -def moveDisk(fp,tp): - print('moving disk from', fp, 'to', tp) def main(): - height = int(input('Height of hanoi: ').strip()) - moveTower(height, 'A', 'B', 'C') + height = int(input("Height of hanoi: ").strip()) + moveTower(height, "A", "B", "C") + -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/other/two_sum.py b/other/two_sum.py index b784da82767a..70d5c5375026 100644 --- a/other/two_sum.py +++ b/other/two_sum.py @@ -9,6 +9,8 @@ Because nums[0] + nums[1] = 2 + 7 = 9, return [0, 1]. """ + + def twoSum(nums, target): """ :type nums: List[int] @@ -17,11 +19,11 @@ def twoSum(nums, target): """ chk_map = {} for index, val in enumerate(nums): - compl = target - val - if compl in chk_map: - indices = [chk_map[compl], index] - print(indices) - return [indices] - else: - chk_map[val] = index + compl = target - val + if compl in chk_map: + indices = [chk_map[compl], index] + print(indices) + return [indices] + else: + chk_map[val] = index return False diff --git a/other/word_patterns.py b/other/word_patterns.py index 1364d1277255..16089019704b 100644 --- a/other/word_patterns.py +++ b/other/word_patterns.py @@ -1,5 +1,6 @@ import pprint, time + def getWordPattern(word): word = word.upper() nextNum = 0 @@ -11,14 +12,15 @@ def getWordPattern(word): letterNums[letter] = str(nextNum) nextNum += 1 wordPattern.append(letterNums[letter]) - return '.'.join(wordPattern) + return ".".join(wordPattern) + def main(): startTime = time.time() allPatterns = {} - with open('Dictionary.txt') as fo: - wordList = fo.read().split('\n') + with open("Dictionary.txt") as fo: + wordList = fo.read().split("\n") for word in wordList: pattern = getWordPattern(word) @@ -28,11 +30,12 @@ def main(): else: allPatterns[pattern].append(word) - with open('Word Patterns.txt', 'w') as fo: + with open("Word Patterns.txt", "w") as fo: fo.write(pprint.pformat(allPatterns)) totalTime = round(time.time() - startTime, 2) - print(('Done! [', totalTime, 'seconds ]')) + print(("Done! [", totalTime, "seconds ]")) + -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/project_euler/problem_01/sol1.py b/project_euler/problem_01/sol1.py index 76b13b852c87..e81156edaee4 100644 --- a/project_euler/problem_01/sol1.py +++ b/project_euler/problem_01/sol1.py @@ -4,6 +4,8 @@ we get 3,5,6 and 9. The sum of these multiples is 23. Find the sum of all the multiples of 3 or 5 below N. """ + + def solution(n): """Returns the sum of all the multiples of 3 or 5 below n. diff --git a/project_euler/problem_01/sol3.py b/project_euler/problem_01/sol3.py index 532203ddd95d..c0bcbc06ec83 100644 --- a/project_euler/problem_01/sol3.py +++ b/project_euler/problem_01/sol3.py @@ -4,6 +4,8 @@ we get 3,5,6 and 9. The sum of these multiples is 23. Find the sum of all the multiples of 3 or 5 below N. """ + + def solution(n): """ This solution is based on the pattern that the successive numbers in the diff --git a/project_euler/problem_01/sol4.py b/project_euler/problem_01/sol4.py index 3e6712618870..e01dc977d8cf 100644 --- a/project_euler/problem_01/sol4.py +++ b/project_euler/problem_01/sol4.py @@ -4,6 +4,8 @@ we get 3,5,6 and 9. The sum of these multiples is 23. Find the sum of all the multiples of 3 or 5 below N. """ + + def solution(n): """Returns the sum of all the multiples of 3 or 5 below n. diff --git a/project_euler/problem_01/sol6.py b/project_euler/problem_01/sol6.py index b9c3db4f8550..c9f94b9f77c8 100644 --- a/project_euler/problem_01/sol6.py +++ b/project_euler/problem_01/sol6.py @@ -4,6 +4,8 @@ we get 3,5,6 and 9. The sum of these multiples is 23. Find the sum of all the multiples of 3 or 5 below N. """ + + def solution(n): """Returns the sum of all the multiples of 3 or 5 below n. diff --git a/project_euler/problem_02/sol1.py b/project_euler/problem_02/sol1.py index d2ad67e2f424..ec89ddaeb2b5 100644 --- a/project_euler/problem_02/sol1.py +++ b/project_euler/problem_02/sol1.py @@ -9,6 +9,8 @@ n, find the sum of the even-valued terms. e.g. for n=10, we have {2,8}, sum is 10. """ + + def solution(n): """Returns the sum of all fibonacci sequence even elements that are lower or equals to n. diff --git a/project_euler/problem_02/sol2.py b/project_euler/problem_02/sol2.py index 71f51b695e84..bc5040cc6b3b 100644 --- a/project_euler/problem_02/sol2.py +++ b/project_euler/problem_02/sol2.py @@ -9,6 +9,8 @@ n, find the sum of the even-valued terms. e.g. for n=10, we have {2,8}, sum is 10. """ + + def solution(n): """Returns the sum of all fibonacci sequence even elements that are lower or equals to n. diff --git a/project_euler/problem_02/sol3.py b/project_euler/problem_02/sol3.py index c698b8e38ab2..f29f21c287e5 100644 --- a/project_euler/problem_02/sol3.py +++ b/project_euler/problem_02/sol3.py @@ -9,6 +9,8 @@ n, find the sum of the even-valued terms. e.g. for n=10, we have {2,8}, sum is 10. """ + + def solution(n): """Returns the sum of all fibonacci sequence even elements that are lower or equals to n. diff --git a/project_euler/problem_04/sol1.py b/project_euler/problem_04/sol1.py index 51417b146bbf..53fff8bed4d4 100644 --- a/project_euler/problem_04/sol1.py +++ b/project_euler/problem_04/sol1.py @@ -6,6 +6,8 @@ Find the largest palindrome made from the product of two 3-digit numbers which is less than N. """ + + def solution(n): """Returns the largest palindrome made from the product of two 3-digit numbers which is less than n. @@ -31,9 +33,7 @@ def solution(n): # if 'number' is a product of two 3-digit numbers # then number is the answer otherwise fetch next number. while divisor != 99: - if (number % divisor == 0) and ( - len(str(int(number / divisor))) == 3 - ): + if (number % divisor == 0) and (len(str(int(number / divisor))) == 3): return number divisor -= 1 diff --git a/project_euler/problem_04/sol2.py b/project_euler/problem_04/sol2.py index 8740ee44a4b4..ecc503912c34 100644 --- a/project_euler/problem_04/sol2.py +++ b/project_euler/problem_04/sol2.py @@ -6,6 +6,8 @@ Find the largest palindrome made from the product of two 3-digit numbers which is less than N. """ + + def solution(n): """Returns the largest palindrome made from the product of two 3-digit numbers which is less than n. diff --git a/project_euler/problem_05/sol1.py b/project_euler/problem_05/sol1.py index 83c387e4ae6e..b3a231f4dcf5 100644 --- a/project_euler/problem_05/sol1.py +++ b/project_euler/problem_05/sol1.py @@ -6,6 +6,8 @@ What is the smallest positive number that is evenly divisible(divisible with no remainder) by all of the numbers from 1 to N? """ + + def solution(n): """Returns the smallest positive number that is evenly divisible(divisible with no remainder) by all of the numbers from 1 to n. diff --git a/project_euler/problem_06/sol1.py b/project_euler/problem_06/sol1.py index 0a964272e7e8..c69b6c89e35a 100644 --- a/project_euler/problem_06/sol1.py +++ b/project_euler/problem_06/sol1.py @@ -14,6 +14,8 @@ Find the difference between the sum of the squares of the first N natural numbers and the square of the sum. """ + + def solution(n): """Returns the difference between the sum of the squares of the first n natural numbers and the square of the sum. diff --git a/project_euler/problem_06/sol2.py b/project_euler/problem_06/sol2.py index 45d08d244647..1698a3fb61fd 100644 --- a/project_euler/problem_06/sol2.py +++ b/project_euler/problem_06/sol2.py @@ -14,6 +14,8 @@ Find the difference between the sum of the squares of the first N natural numbers and the square of the sum. """ + + def solution(n): """Returns the difference between the sum of the squares of the first n natural numbers and the square of the sum. diff --git a/project_euler/problem_07/sol2.py b/project_euler/problem_07/sol2.py index 7d078af32176..5d30e540b3e7 100644 --- a/project_euler/problem_07/sol2.py +++ b/project_euler/problem_07/sol2.py @@ -6,6 +6,8 @@ We can see that the 6th prime is 13. What is the Nth prime number? """ + + def isprime(number): for i in range(2, int(number ** 0.5) + 1): if number % i == 0: diff --git a/project_euler/problem_09/sol2.py b/project_euler/problem_09/sol2.py index 502f334417c8..de7b12d40c09 100644 --- a/project_euler/problem_09/sol2.py +++ b/project_euler/problem_09/sol2.py @@ -7,6 +7,8 @@ There exists exactly one Pythagorean triplet for which a + b + c = 1000. Find the product abc. """ + + def solution(n): """ Return the product of a,b,c which are Pythagorean Triplet that satisfies diff --git a/project_euler/problem_09/sol3.py b/project_euler/problem_09/sol3.py index bbe7dcf743e7..a6df46a3a66b 100644 --- a/project_euler/problem_09/sol3.py +++ b/project_euler/problem_09/sol3.py @@ -10,6 +10,8 @@ There exists exactly one Pythagorean triplet for which a + b + c = 1000. Find the product abc. """ + + def solution(): """ Returns the product of a,b,c which are Pythagorean Triplet that satisfies diff --git a/project_euler/problem_11/sol1.py b/project_euler/problem_11/sol1.py index 1473439ae00d..4e49013c8210 100644 --- a/project_euler/problem_11/sol1.py +++ b/project_euler/problem_11/sol1.py @@ -39,12 +39,8 @@ def largest_product(grid): # for nxn grid) for i in range(nColumns): for j in range(nRows - 3): - vertProduct = ( - grid[j][i] * grid[j + 1][i] * grid[j + 2][i] * grid[j + 3][i] - ) - horzProduct = ( - grid[i][j] * grid[i][j + 1] * grid[i][j + 2] * grid[i][j + 3] - ) + vertProduct = grid[j][i] * grid[j + 1][i] * grid[j + 2][i] * grid[j + 3][i] + horzProduct = grid[i][j] * grid[i][j + 1] * grid[i][j + 2] * grid[i][j + 3] # Left-to-right diagonal (\) product if i < nColumns - 3: @@ -64,9 +60,7 @@ def largest_product(grid): * grid[i - 3][j + 3] ) - maxProduct = max( - vertProduct, horzProduct, lrDiagProduct, rlDiagProduct - ) + maxProduct = max(vertProduct, horzProduct, lrDiagProduct, rlDiagProduct) if maxProduct > largest: largest = maxProduct diff --git a/project_euler/problem_11/sol2.py b/project_euler/problem_11/sol2.py index be6c11a378ad..64702e852b0f 100644 --- a/project_euler/problem_11/sol2.py +++ b/project_euler/problem_11/sol2.py @@ -57,24 +57,14 @@ def solution(): # diagonal 1 for i in range(17): for j in range(17): - temp = ( - l[i][j] - * l[i + 1][j + 1] - * l[i + 2][j + 2] - * l[i + 3][j + 3] - ) + temp = l[i][j] * l[i + 1][j + 1] * l[i + 2][j + 2] * l[i + 3][j + 3] if temp > maximum: maximum = temp # diagonal 2 for i in range(17): for j in range(3, 20): - temp = ( - l[i][j] - * l[i + 1][j - 1] - * l[i + 2][j - 2] - * l[i + 3][j - 3] - ) + temp = l[i][j] * l[i + 1][j - 1] * l[i + 2][j - 2] * l[i + 3][j - 3] if temp > maximum: maximum = temp return maximum diff --git a/project_euler/problem_12/sol2.py b/project_euler/problem_12/sol2.py index 97a4910723ac..5ff0d8349b90 100644 --- a/project_euler/problem_12/sol2.py +++ b/project_euler/problem_12/sol2.py @@ -21,15 +21,15 @@ What is the value of the first triangle number to have over five hundred divisors? """ + + def triangle_number_generator(): for n in range(1, 1000000): yield n * (n + 1) // 2 def count_divisors(n): - return sum( - [2 for i in range(1, int(n ** 0.5) + 1) if n % i == 0 and i * i != n] - ) + return sum([2 for i in range(1, int(n ** 0.5) + 1) if n % i == 0 and i * i != n]) def solution(): @@ -40,9 +40,7 @@ def solution(): # >>> solution() # 76576500 """ - return next( - i for i in triangle_number_generator() if count_divisors(i) > 500 - ) + return next(i for i in triangle_number_generator() if count_divisors(i) > 500) if __name__ == "__main__": diff --git a/project_euler/problem_14/sol1.py b/project_euler/problem_14/sol1.py index 156322b7d507..ab09937fb315 100644 --- a/project_euler/problem_14/sol1.py +++ b/project_euler/problem_14/sol1.py @@ -16,6 +16,8 @@ Which starting number, under one million, produces the longest chain? """ + + def solution(n): """Returns the number under n that generates the longest sequence using the formula: @@ -56,11 +58,5 @@ def solution(n): if __name__ == "__main__": result = solution(int(input().strip())) print( - ( - "Largest Number:", - result["largest_number"], - "->", - result["counter"], - "digits", - ) + ("Largest Number:", result["largest_number"], "->", result["counter"], "digits") ) diff --git a/project_euler/problem_14/sol2.py b/project_euler/problem_14/sol2.py index 25ebd41571c2..9b8857e710b4 100644 --- a/project_euler/problem_14/sol2.py +++ b/project_euler/problem_14/sol2.py @@ -24,6 +24,8 @@ Which starting number, under one million, produces the longest chain? """ + + def collatz_sequence(n): """Returns the Collatz sequence for n.""" sequence = [n] diff --git a/project_euler/problem_15/sol1.py b/project_euler/problem_15/sol1.py index de58bb436d68..1be7d10ed674 100644 --- a/project_euler/problem_15/sol1.py +++ b/project_euler/problem_15/sol1.py @@ -35,9 +35,7 @@ def lattice_paths(n): 2 """ - n = ( - 2 * n - ) # middle entry of odd rows starting at row 3 is the solution for n = 1, + n = 2 * n # middle entry of odd rows starting at row 3 is the solution for n = 1, # 2, 3,... k = n / 2 diff --git a/project_euler/problem_18/solution.py b/project_euler/problem_18/solution.py index f9762e8b0176..38593813901e 100644 --- a/project_euler/problem_18/solution.py +++ b/project_euler/problem_18/solution.py @@ -39,12 +39,12 @@ def solution(): 1074 """ script_dir = os.path.dirname(os.path.realpath(__file__)) - triangle = os.path.join(script_dir, 'triangle.txt') + triangle = os.path.join(script_dir, "triangle.txt") - with open(triangle, 'r') as f: + with open(triangle, "r") as f: triangle = f.readlines() - a = [[int(y) for y in x.rstrip('\r\n').split(' ')] for x in triangle] + a = [[int(y) for y in x.rstrip("\r\n").split(" ")] for x in triangle] for i in range(1, len(a)): for j in range(len(a[i])): diff --git a/project_euler/problem_21/sol1.py b/project_euler/problem_21/sol1.py index a890e6a98611..49c2db964316 100644 --- a/project_euler/problem_21/sol1.py +++ b/project_euler/problem_21/sol1.py @@ -16,6 +16,8 @@ Evaluate the sum of all the amicable numbers under 10000. """ + + def sum_of_divisors(n): total = 0 for i in range(1, int(sqrt(n) + 1)): @@ -44,8 +46,7 @@ def solution(n): [ i for i in range(1, n) - if sum_of_divisors(sum_of_divisors(i)) == i - and sum_of_divisors(i) != i + if sum_of_divisors(sum_of_divisors(i)) == i and sum_of_divisors(i) != i ] ) return total diff --git a/project_euler/problem_23/sol1.py b/project_euler/problem_23/sol1.py index e76be053040f..a72b6123e3ee 100644 --- a/project_euler/problem_23/sol1.py +++ b/project_euler/problem_23/sol1.py @@ -18,7 +18,8 @@ of two abundant numbers. """ -def solution(limit = 28123): + +def solution(limit=28123): """ Finds the sum of all the positive integers which cannot be written as the sum of two abundant numbers @@ -42,7 +43,7 @@ def solution(limit = 28123): abundants.add(n) if not any((n - a in abundants) for a in abundants): - res+=n + res += n return res diff --git a/project_euler/problem_234/sol1.py b/project_euler/problem_234/sol1.py index c0d2949285e9..28d82b550c85 100644 --- a/project_euler/problem_234/sol1.py +++ b/project_euler/problem_234/sol1.py @@ -17,18 +17,19 @@ What is the sum of all semidivisible numbers not exceeding 999966663333 ? """ + def fib(a, b, n): - - if n==1: + + if n == 1: return a - elif n==2: + elif n == 2: return b - elif n==3: - return str(a)+str(b) - + elif n == 3: + return str(a) + str(b) + temp = 0 - for x in range(2,n): - c=str(a) + str(b) + for x in range(2, n): + c = str(a) + str(b) temp = b b = c a = temp @@ -39,14 +40,14 @@ def solution(n): """Returns the sum of all semidivisible numbers not exceeding n.""" semidivisible = [] for x in range(n): - l=[i for i in input().split()] - c2=1 - while(1): - if len(fib(l[0],l[1],c2)) int: """ Considering natural numbers of the form, a**b, where a, b < 100, @@ -18,9 +16,17 @@ def maximum_digital_sum(a: int, b: int) -> int: """ # RETURN the MAXIMUM from the list of SUMs of the list of INT converted from STR of BASE raised to the POWER - return max([sum([int(x) for x in str(base**power)]) for base in range(a) for power in range(b)]) + return max( + [ + sum([int(x) for x in str(base ** power)]) + for base in range(a) + for power in range(b) + ] + ) -#Tests + +# Tests if __name__ == "__main__": import doctest + doctest.testmod() diff --git a/project_euler/problem_67/sol1.py b/project_euler/problem_67/sol1.py index 2da757e303aa..9494ff7bbabd 100644 --- a/project_euler/problem_67/sol1.py +++ b/project_euler/problem_67/sol1.py @@ -23,12 +23,12 @@ def solution(): 7273 """ script_dir = os.path.dirname(os.path.realpath(__file__)) - triangle = os.path.join(script_dir, 'triangle.txt') + triangle = os.path.join(script_dir, "triangle.txt") - with open(triangle, 'r') as f: + with open(triangle, "r") as f: triangle = f.readlines() - a = map(lambda x: x.rstrip('\r\n').split(' '), triangle) + a = map(lambda x: x.rstrip("\r\n").split(" "), triangle) a = list(map(lambda x: list(map(lambda y: int(y), x)), a)) for i in range(1, len(a)): diff --git a/scripts/build_directory_md.py b/scripts/build_directory_md.py index b39edca6c933..cae38c13dbf4 100755 --- a/scripts/build_directory_md.py +++ b/scripts/build_directory_md.py @@ -14,7 +14,7 @@ def good_filepaths(top_dir: str = ".") -> Iterator[str]: continue if os.path.splitext(filename)[1] in (".py", ".ipynb"): yield os.path.join(dirpath, filename).lstrip("./") - + def md_prefix(i): return f"{i * ' '}*" if i else "##" @@ -36,7 +36,9 @@ def print_directory_md(top_dir: str = ".") -> None: if filepath != old_path: old_path = print_path(old_path, filepath) indent = (filepath.count(os.sep) + 1) if filepath else 0 - url = "/".join((URL_BASE, filepath.split(os.sep)[1], filename)).replace(" ", "%20") + url = "/".join((URL_BASE, filepath.split(os.sep)[1], filename)).replace( + " ", "%20" + ) filename = os.path.splitext(filename.replace("_", " "))[0] print(f"{md_prefix(indent)} [{filename}]({url})") diff --git a/scripts/validate_filenames.py b/scripts/validate_filenames.py index 9e1f1503321b..51dd6a40cb41 100755 --- a/scripts/validate_filenames.py +++ b/scripts/validate_filenames.py @@ -25,4 +25,5 @@ bad_files = len(upper_files + space_files + nodir_files) if bad_files: import sys + sys.exit(bad_files) diff --git a/searches/binary_search.py b/searches/binary_search.py index 77abf90239ab..9237c0e1f6f5 100644 --- a/searches/binary_search.py +++ b/searches/binary_search.py @@ -79,6 +79,7 @@ def binary_search_std_lib(sorted_collection, item): return index return None + def binary_search_by_recursion(sorted_collection, item, left, right): """Pure implementation of binary search algorithm in Python by recursion @@ -104,7 +105,7 @@ def binary_search_by_recursion(sorted_collection, item, left, right): >>> binary_search_std_lib([0, 5, 7, 10, 15], 6) """ - if (right < left): + if right < left: return None midpoint = left + (right - left) // 2 @@ -112,9 +113,10 @@ def binary_search_by_recursion(sorted_collection, item, left, right): if sorted_collection[midpoint] == item: return midpoint elif sorted_collection[midpoint] > item: - return binary_search_by_recursion(sorted_collection, item, left, midpoint-1) + return binary_search_by_recursion(sorted_collection, item, left, midpoint - 1) else: - return binary_search_by_recursion(sorted_collection, item, midpoint+1, right) + return binary_search_by_recursion(sorted_collection, item, midpoint + 1, right) + def __assert_sorted(collection): """Check if collection is ascending sorted, if not - raises :py:class:`ValueError` @@ -133,23 +135,24 @@ def __assert_sorted(collection): ValueError: Collection must be ascending sorted """ if collection != sorted(collection): - raise ValueError('Collection must be ascending sorted') + raise ValueError("Collection must be ascending sorted") return True -if __name__ == '__main__': +if __name__ == "__main__": import sys - user_input = input('Enter numbers separated by comma:\n').strip() - collection = [int(item) for item in user_input.split(',')] + + user_input = input("Enter numbers separated by comma:\n").strip() + collection = [int(item) for item in user_input.split(",")] try: __assert_sorted(collection) except ValueError: - sys.exit('Sequence must be ascending sorted to apply binary search') + sys.exit("Sequence must be ascending sorted to apply binary search") - target_input = input('Enter a single number to be found in the list:\n') + target_input = input("Enter a single number to be found in the list:\n") target = int(target_input) result = binary_search(collection, target) if result is not None: - print('{} found at positions: {}'.format(target, result)) + print("{} found at positions: {}".format(target, result)) else: - print('Not found') + print("Not found") diff --git a/searches/interpolation_search.py b/searches/interpolation_search.py index 27ee979bb649..d1873083bf8a 100644 --- a/searches/interpolation_search.py +++ b/searches/interpolation_search.py @@ -15,27 +15,29 @@ def interpolation_search(sorted_collection, item): right = len(sorted_collection) - 1 while left <= right: - #avoid devided by 0 during interpolation - if sorted_collection[left]==sorted_collection[right]: - if sorted_collection[left]==item: + # avoid devided by 0 during interpolation + if sorted_collection[left] == sorted_collection[right]: + if sorted_collection[left] == item: return left else: return None - point = left + ((item - sorted_collection[left]) * (right - left)) // (sorted_collection[right] - sorted_collection[left]) + point = left + ((item - sorted_collection[left]) * (right - left)) // ( + sorted_collection[right] - sorted_collection[left] + ) - #out of range check - if point<0 or point>=len(sorted_collection): + # out of range check + if point < 0 or point >= len(sorted_collection): return None current_item = sorted_collection[point] if current_item == item: return point else: - if pointright: + elif point > right: left = right right = point else: @@ -45,6 +47,7 @@ def interpolation_search(sorted_collection, item): left = point + 1 return None + def interpolation_search_by_recursion(sorted_collection, item, left, right): """Pure implementation of interpolation search algorithm in Python by recursion @@ -56,30 +59,37 @@ def interpolation_search_by_recursion(sorted_collection, item, left, right): :return: index of found item or None if item is not found """ - #avoid devided by 0 during interpolation - if sorted_collection[left]==sorted_collection[right]: - if sorted_collection[left]==item: + # avoid devided by 0 during interpolation + if sorted_collection[left] == sorted_collection[right]: + if sorted_collection[left] == item: return left else: return None - point = left + ((item - sorted_collection[left]) * (right - left)) // (sorted_collection[right] - sorted_collection[left]) + point = left + ((item - sorted_collection[left]) * (right - left)) // ( + sorted_collection[right] - sorted_collection[left] + ) - #out of range check - if point<0 or point>=len(sorted_collection): + # out of range check + if point < 0 or point >= len(sorted_collection): return None if sorted_collection[point] == item: return point - elif pointright: + elif point > right: return interpolation_search_by_recursion(sorted_collection, item, right, left) else: if sorted_collection[point] > item: - return interpolation_search_by_recursion(sorted_collection, item, left, point-1) + return interpolation_search_by_recursion( + sorted_collection, item, left, point - 1 + ) else: - return interpolation_search_by_recursion(sorted_collection, item, point+1, right) + return interpolation_search_by_recursion( + sorted_collection, item, point + 1, right + ) + def __assert_sorted(collection): """Check if collection is ascending sorted, if not - raises :py:class:`ValueError` @@ -95,11 +105,11 @@ def __assert_sorted(collection): ValueError: Collection must be ascending sorted """ if collection != sorted(collection): - raise ValueError('Collection must be ascending sorted') + raise ValueError("Collection must be ascending sorted") return True -if __name__ == '__main__': +if __name__ == "__main__": import sys """ @@ -116,15 +126,15 @@ def __assert_sorted(collection): debug = 0 if debug == 1: - collection = [10,30,40,45,50,66,77,93] + collection = [10, 30, 40, 45, 50, 66, 77, 93] try: __assert_sorted(collection) except ValueError: - sys.exit('Sequence must be ascending sorted to apply interpolation search') + sys.exit("Sequence must be ascending sorted to apply interpolation search") target = 67 result = interpolation_search(collection, target) if result is not None: - print('{} found at positions: {}'.format(target, result)) + print("{} found at positions: {}".format(target, result)) else: - print('Not found') + print("Not found") diff --git a/searches/jump_search.py b/searches/jump_search.py index 78d9f79dc6a8..e191cf2d4b27 100644 --- a/searches/jump_search.py +++ b/searches/jump_search.py @@ -1,9 +1,11 @@ import math + + def jump_search(arr, x): n = len(arr) step = int(math.floor(math.sqrt(n))) prev = 0 - while arr[min(step, n)-1] < x: + while arr[min(step, n) - 1] < x: prev = step step += int(math.floor(math.sqrt(n))) if prev >= n: @@ -18,8 +20,7 @@ def jump_search(arr, x): return -1 - -arr = [ 0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610] +arr = [0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610] x = 55 index = jump_search(arr, x) -print("\nNumber " + str(x) +" is at index " + str(index)); +print("\nNumber " + str(x) + " is at index " + str(index)) diff --git a/searches/linear_search.py b/searches/linear_search.py index fb784924132e..ab20f3527bb3 100644 --- a/searches/linear_search.py +++ b/searches/linear_search.py @@ -37,14 +37,14 @@ def linear_search(sequence, target): return None -if __name__ == '__main__': - user_input = input('Enter numbers separated by comma:\n').strip() - sequence = [int(item) for item in user_input.split(',')] +if __name__ == "__main__": + user_input = input("Enter numbers separated by comma:\n").strip() + sequence = [int(item) for item in user_input.split(",")] - target_input = input('Enter a single number to be found in the list:\n') + target_input = input("Enter a single number to be found in the list:\n") target = int(target_input) result = linear_search(sequence, target) if result is not None: - print('{} found at positions: {}'.format(target, result)) + print("{} found at positions: {}".format(target, result)) else: - print('Not found') + print("Not found") diff --git a/searches/quick_select.py b/searches/quick_select.py index 76d09cb97f97..6b70562bd78f 100644 --- a/searches/quick_select.py +++ b/searches/quick_select.py @@ -4,6 +4,8 @@ A python implementation of the quick select algorithm, which is efficient for calculating the value that would appear in the index of a list if it would be sorted, even if it is not already sorted https://en.wikipedia.org/wiki/Quickselect """ + + def _partition(data, pivot): """ Three way partition the data into smaller, equal and greater lists, @@ -21,29 +23,30 @@ def _partition(data, pivot): else: equal.append(element) return less, equal, greater - + + def quickSelect(list, k): - #k = len(list) // 2 when trying to find the median (index that value would be when list is sorted) - - #invalid input - if k>=len(list) or k<0: + # k = len(list) // 2 when trying to find the median (index that value would be when list is sorted) + + # invalid input + if k >= len(list) or k < 0: return None - + smaller = [] larger = [] pivot = random.randint(0, len(list) - 1) pivot = list[pivot] count = 0 - smaller, equal, larger =_partition(list, pivot) + smaller, equal, larger = _partition(list, pivot) count = len(equal) m = len(smaller) - #k is the pivot + # k is the pivot if m <= k < m + count: return pivot # must be in smaller elif m > k: return quickSelect(smaller, k) - #must be in larger + # must be in larger else: - return quickSelect(larger, k - (m + count)) \ No newline at end of file + return quickSelect(larger, k - (m + count)) diff --git a/searches/sentinel_linear_search.py b/searches/sentinel_linear_search.py index eb9d32e5f503..6c4da9b21189 100644 --- a/searches/sentinel_linear_search.py +++ b/searches/sentinel_linear_search.py @@ -10,6 +10,7 @@ python sentinel_linear_search.py """ + def sentinel_linear_search(sequence, target): """Pure implementation of sentinel linear search algorithm in Python @@ -44,14 +45,14 @@ def sentinel_linear_search(sequence, target): return index -if __name__ == '__main__': - user_input = input('Enter numbers separated by comma:\n').strip() - sequence = [int(item) for item in user_input.split(',')] +if __name__ == "__main__": + user_input = input("Enter numbers separated by comma:\n").strip() + sequence = [int(item) for item in user_input.split(",")] - target_input = input('Enter a single number to be found in the list:\n') + target_input = input("Enter a single number to be found in the list:\n") target = int(target_input) result = sentinel_linear_search(sequence, target) if result is not None: - print('{} found at positions: {}'.format(target, result)) + print("{} found at positions: {}".format(target, result)) else: - print('Not found') \ No newline at end of file + print("Not found") diff --git a/searches/tabu_search.py b/searches/tabu_search.py index ffd84f8ac031..9a1478244503 100644 --- a/searches/tabu_search.py +++ b/searches/tabu_search.py @@ -55,13 +55,17 @@ def generate_neighbours(path): _list.append([line.split()[1], line.split()[2]]) dict_of_neighbours[line.split()[0]] = _list else: - dict_of_neighbours[line.split()[0]].append([line.split()[1], line.split()[2]]) + dict_of_neighbours[line.split()[0]].append( + [line.split()[1], line.split()[2]] + ) if line.split()[1] not in dict_of_neighbours: _list = list() _list.append([line.split()[0], line.split()[2]]) dict_of_neighbours[line.split()[1]] = _list else: - dict_of_neighbours[line.split()[1]].append([line.split()[0], line.split()[2]]) + dict_of_neighbours[line.split()[1]].append( + [line.split()[0], line.split()[2]] + ) return dict_of_neighbours @@ -111,8 +115,11 @@ def generate_first_solution(path, dict_of_neighbours): break position += 1 - distance_of_first_solution = distance_of_first_solution + int( - dict_of_neighbours[first_solution[-2]][position][1]) - 10000 + distance_of_first_solution = ( + distance_of_first_solution + + int(dict_of_neighbours[first_solution[-2]][position][1]) + - 10000 + ) return first_solution, distance_of_first_solution @@ -167,7 +174,9 @@ def find_neighborhood(solution, dict_of_neighbours): return neighborhood_of_solution -def tabu_search(first_solution, distance_of_first_solution, dict_of_neighbours, iters, size): +def tabu_search( + first_solution, distance_of_first_solution, dict_of_neighbours, iters, size +): """ Pure implementation of Tabu search algorithm for a Travelling Salesman Problem in Python. @@ -207,8 +216,10 @@ def tabu_search(first_solution, distance_of_first_solution, dict_of_neighbours, break i = i + 1 - if [first_exchange_node, second_exchange_node] not in tabu_list and [second_exchange_node, - first_exchange_node] not in tabu_list: + if [first_exchange_node, second_exchange_node] not in tabu_list and [ + second_exchange_node, + first_exchange_node, + ] not in tabu_list: tabu_list.append([first_exchange_node, second_exchange_node]) found = True solution = best_solution[:-1] @@ -231,10 +242,17 @@ def tabu_search(first_solution, distance_of_first_solution, dict_of_neighbours, def main(args=None): dict_of_neighbours = generate_neighbours(args.File) - first_solution, distance_of_first_solution = generate_first_solution(args.File, dict_of_neighbours) + first_solution, distance_of_first_solution = generate_first_solution( + args.File, dict_of_neighbours + ) - best_sol, best_cost = tabu_search(first_solution, distance_of_first_solution, dict_of_neighbours, args.Iterations, - args.Size) + best_sol, best_cost = tabu_search( + first_solution, + distance_of_first_solution, + dict_of_neighbours, + args.Iterations, + args.Size, + ) print("Best solution: {0}, with total distance: {1}.".format(best_sol, best_cost)) @@ -242,11 +260,22 @@ def main(args=None): if __name__ == "__main__": parser = argparse.ArgumentParser(description="Tabu Search") parser.add_argument( - "-f", "--File", type=str, help="Path to the file containing the data", required=True) + "-f", + "--File", + type=str, + help="Path to the file containing the data", + required=True, + ) parser.add_argument( - "-i", "--Iterations", type=int, help="How many iterations the algorithm should perform", required=True) + "-i", + "--Iterations", + type=int, + help="How many iterations the algorithm should perform", + required=True, + ) parser.add_argument( - "-s", "--Size", type=int, help="Size of the tabu list", required=True) + "-s", "--Size", type=int, help="Size of the tabu list", required=True + ) # Pass the arguments to main method sys.exit(main(parser.parse_args())) diff --git a/searches/ternary_search.py b/searches/ternary_search.py index 41033f33cec6..43407b7e5538 100644 --- a/searches/ternary_search.py +++ b/searches/ternary_search.py @@ -1,11 +1,11 @@ -''' +""" This is a type of divide and conquer algorithm which divides the search space into 3 parts and finds the target value based on the property of the array or list (usually monotonic property). Time Complexity : O(log3 N) Space Complexity : O(1) -''' +""" import sys # This is the precision for this function which can be altered. @@ -14,87 +14,90 @@ # This is the linear search that will occur after the search space has become smaller. def lin_search(left, right, A, target): - for i in range(left, right+1): - if(A[i] == target): + for i in range(left, right + 1): + if A[i] == target: return i + # This is the iterative method of the ternary search algorithm. def ite_ternary_search(A, target): left = 0 - right = len(A) - 1; - while(True): - if(left a[j]) agrees with the direction, -# then a[i] and a[j] are interchanged.*/ +# The parameter dir indicates the sorting direction, ASCENDING +# or DESCENDING; if (a[i] > a[j]) agrees with the direction, +# then a[i] and a[j] are interchanged.*/ def compAndSwap(a, i, j, dire): if (dire == 1 and a[i] > a[j]) or (dire == 0 and a[i] < a[j]): a[i], a[j] = a[j], a[i] @@ -12,8 +12,8 @@ def compAndSwap(a, i, j, dire): # if dir = 1, and in descending order otherwise (means dir=0). -# The sequence to be sorted starts at index position low, -# the parameter cnt is the number of elements to be sorted. +# The sequence to be sorted starts at index position low, +# the parameter cnt is the number of elements to be sorted. def bitonicMerge(a, low, cnt, dire): if cnt > 1: k = int(cnt / 2) @@ -26,7 +26,7 @@ def bitonicMerge(a, low, cnt, dire): # sorting its two halves in opposite sorting orders, and then -# calls bitonicMerge to make them in the same order +# calls bitonicMerge to make them in the same order def bitonicSort(a, low, cnt, dire): if cnt > 1: k = int(cnt / 2) diff --git a/sorts/bogo_sort.py b/sorts/bogo_sort.py index a3b2cbc1aa29..0afa444e5b8e 100644 --- a/sorts/bogo_sort.py +++ b/sorts/bogo_sort.py @@ -37,7 +37,8 @@ def isSorted(collection): random.shuffle(collection) return collection -if __name__ == '__main__': - user_input = input('Enter numbers separated by a comma:\n').strip() - unsorted = [int(item) for item in user_input.split(',')] + +if __name__ == "__main__": + user_input = input("Enter numbers separated by a comma:\n").strip() + unsorted = [int(item) for item in user_input.split(",")] print(bogo_sort(unsorted)) diff --git a/sorts/bubble_sort.py b/sorts/bubble_sort.py index c41a51ea3cbf..ccd8a2e11ee1 100644 --- a/sorts/bubble_sort.py +++ b/sorts/bubble_sort.py @@ -22,18 +22,18 @@ def bubble_sort(collection): True """ length = len(collection) - for i in range(length-1): + for i in range(length - 1): swapped = False - for j in range(length-1-i): - if collection[j] > collection[j+1]: + for j in range(length - 1 - i): + if collection[j] > collection[j + 1]: swapped = True - collection[j], collection[j+1] = collection[j+1], collection[j] + collection[j], collection[j + 1] = collection[j + 1], collection[j] if not swapped: break # Stop iteration if the collection is sorted. return collection -if __name__ == '__main__': - user_input = input('Enter numbers separated by a comma:').strip() - unsorted = [int(item) for item in user_input.split(',')] - print(*bubble_sort(unsorted), sep=',') +if __name__ == "__main__": + user_input = input("Enter numbers separated by a comma:").strip() + unsorted = [int(item) for item in user_input.split(",")] + print(*bubble_sort(unsorted), sep=",") diff --git a/sorts/bucket_sort.py b/sorts/bucket_sort.py index 0678b1194657..217ee5893c4b 100644 --- a/sorts/bucket_sort.py +++ b/sorts/bucket_sort.py @@ -17,8 +17,8 @@ # number of buckets. # Time Complexity of Solution: -# Worst case scenario occurs when all the elements are placed in a single bucket. The overall performance -# would then be dominated by the algorithm used to sort each bucket. In this case, O(n log n), because of TimSort +# Worst case scenario occurs when all the elements are placed in a single bucket. The overall performance +# would then be dominated by the algorithm used to sort each bucket. In this case, O(n log n), because of TimSort # # Average Case O(n + (n^2)/k + k), where k is the number of buckets # @@ -32,18 +32,18 @@ def bucket_sort(my_list, bucket_size=DEFAULT_BUCKET_SIZE): raise Exception("Please add some elements in the array.") min_value, max_value = (min(my_list), max(my_list)) - bucket_count = ((max_value - min_value) // bucket_size + 1) + bucket_count = (max_value - min_value) // bucket_size + 1 buckets = [[] for _ in range(int(bucket_count))] for i in range(len(my_list)): - buckets[int((my_list[i] - min_value) // bucket_size) - ].append(my_list[i]) + buckets[int((my_list[i] - min_value) // bucket_size)].append(my_list[i]) - return sorted([buckets[i][j] for i in range(len(buckets)) - for j in range(len(buckets[i]))]) + return sorted( + [buckets[i][j] for i in range(len(buckets)) for j in range(len(buckets[i]))] + ) if __name__ == "__main__": - user_input = input('Enter numbers separated by a comma:').strip() - unsorted = [float(n) for n in user_input.split(',') if len(user_input) > 0] + user_input = input("Enter numbers separated by a comma:").strip() + unsorted = [float(n) for n in user_input.split(",") if len(user_input) > 0] print(bucket_sort(unsorted)) diff --git a/sorts/cocktail_shaker_sort.py b/sorts/cocktail_shaker_sort.py index d486e6a11dfa..ab624421a3d6 100644 --- a/sorts/cocktail_shaker_sort.py +++ b/sorts/cocktail_shaker_sort.py @@ -2,24 +2,25 @@ def cocktail_shaker_sort(unsorted): """ Pure implementation of the cocktail shaker sort algorithm in Python. """ - for i in range(len(unsorted)-1, 0, -1): + for i in range(len(unsorted) - 1, 0, -1): swapped = False for j in range(i, 0, -1): - if unsorted[j] < unsorted[j-1]: - unsorted[j], unsorted[j-1] = unsorted[j-1], unsorted[j] + if unsorted[j] < unsorted[j - 1]: + unsorted[j], unsorted[j - 1] = unsorted[j - 1], unsorted[j] swapped = True for j in range(i): - if unsorted[j] > unsorted[j+1]: - unsorted[j], unsorted[j+1] = unsorted[j+1], unsorted[j] + if unsorted[j] > unsorted[j + 1]: + unsorted[j], unsorted[j + 1] = unsorted[j + 1], unsorted[j] swapped = True if not swapped: return unsorted -if __name__ == '__main__': - user_input = input('Enter numbers separated by a comma:\n').strip() - unsorted = [int(item) for item in user_input.split(',')] + +if __name__ == "__main__": + user_input = input("Enter numbers separated by a comma:\n").strip() + unsorted = [int(item) for item in user_input.split(",")] cocktail_shaker_sort(unsorted) print(unsorted) diff --git a/sorts/comb_sort.py b/sorts/comb_sort.py index 6ce6c1c094f9..3c4c57483e3f 100644 --- a/sorts/comb_sort.py +++ b/sorts/comb_sort.py @@ -12,6 +12,7 @@ python comb_sort.py """ + def comb_sort(data): """Pure implementation of comb sort algorithm in Python :param collection: some mutable ordered collection with heterogeneous @@ -38,16 +39,16 @@ def comb_sort(data): i = 0 while gap + i < len(data): - if data[i] > data[i+gap]: + if data[i] > data[i + gap]: # Swap values - data[i], data[i+gap] = data[i+gap], data[i] + data[i], data[i + gap] = data[i + gap], data[i] swapped = True i += 1 return data -if __name__ == '__main__': - user_input = input('Enter numbers separated by a comma:\n').strip() - unsorted = [int(item) for item in user_input.split(',')] +if __name__ == "__main__": + user_input = input("Enter numbers separated by a comma:\n").strip() + unsorted = [int(item) for item in user_input.split(",")] print(comb_sort(unsorted)) diff --git a/sorts/counting_sort.py b/sorts/counting_sort.py index a3de1811849e..b672d4af47cb 100644 --- a/sorts/counting_sort.py +++ b/sorts/counting_sort.py @@ -42,7 +42,7 @@ def counting_sort(collection): # sum each position with it's predecessors. now, counting_arr[i] tells # us how many elements <= i has in the collection for i in range(1, counting_arr_length): - counting_arr[i] = counting_arr[i] + counting_arr[i-1] + counting_arr[i] = counting_arr[i] + counting_arr[i - 1] # create the output collection ordered = [0] * coll_len @@ -50,23 +50,24 @@ def counting_sort(collection): # place the elements in the output, respecting the original order (stable # sort) from end to begin, updating counting_arr for i in reversed(range(0, coll_len)): - ordered[counting_arr[collection[i] - coll_min]-1] = collection[i] + ordered[counting_arr[collection[i] - coll_min] - 1] = collection[i] counting_arr[collection[i] - coll_min] -= 1 return ordered + def counting_sort_string(string): """ >>> counting_sort_string("thisisthestring") 'eghhiiinrsssttt' """ - return ''.join([chr(i) for i in counting_sort([ord(c) for c in string])]) + return "".join([chr(i) for i in counting_sort([ord(c) for c in string])]) -if __name__ == '__main__': +if __name__ == "__main__": # Test string sort assert "eghhiiinrsssttt" == counting_sort_string("thisisthestring") - user_input = input('Enter numbers separated by a comma:\n').strip() - unsorted = [int(item) for item in user_input.split(',')] + user_input = input("Enter numbers separated by a comma:\n").strip() + unsorted = [int(item) for item in user_input.split(",")] print(counting_sort(unsorted)) diff --git a/sorts/cycle_sort.py b/sorts/cycle_sort.py index 06a377cbd906..4ce6a2a0e757 100644 --- a/sorts/cycle_sort.py +++ b/sorts/cycle_sort.py @@ -41,12 +41,12 @@ def cycle_sort(array): # Main Code starts here -if __name__ == '__main__': - user_input = input('Enter numbers separated by a comma:\n') - unsorted = [int(item) for item in user_input.split(',')] +if __name__ == "__main__": + user_input = input("Enter numbers separated by a comma:\n") + unsorted = [int(item) for item in user_input.split(",")] n = len(unsorted) cycle_sort(unsorted) print("After sort : ") for i in range(0, n): - print(unsorted[i], end=' ') + print(unsorted[i], end=" ") diff --git a/sorts/external_sort.py b/sorts/external_sort.py index 1638e9efafee..abdcb29f95b2 100644 --- a/sorts/external_sort.py +++ b/sorts/external_sort.py @@ -6,8 +6,9 @@ import os import argparse + class FileSplitter(object): - BLOCK_FILENAME_FORMAT = 'block_{0}.dat' + BLOCK_FILENAME_FORMAT = "block_{0}.dat" def __init__(self, filename): self.filename = filename @@ -15,7 +16,7 @@ def __init__(self, filename): def write_block(self, data, block_number): filename = self.BLOCK_FILENAME_FORMAT.format(block_number) - with open(filename, 'w') as file: + with open(filename, "w") as file: file.write(data) self.block_filenames.append(filename) @@ -36,7 +37,7 @@ def split(self, block_size, sort_key=None): else: lines.sort(key=sort_key) - self.write_block(''.join(lines), i) + self.write_block("".join(lines), i) i += 1 def cleanup(self): @@ -63,14 +64,16 @@ def __init__(self, files): self.buffers = {i: None for i in range(self.num_buffers)} def get_dict(self): - return {i: self.buffers[i] for i in range(self.num_buffers) if i not in self.empty} + return { + i: self.buffers[i] for i in range(self.num_buffers) if i not in self.empty + } def refresh(self): for i in range(self.num_buffers): if self.buffers[i] is None and i not in self.empty: self.buffers[i] = self.files[i].readline() - if self.buffers[i] == '': + if self.buffers[i] == "": self.empty.add(i) self.files[i].close() @@ -92,7 +95,7 @@ def __init__(self, merge_strategy): def merge(self, filenames, outfilename, buffer_size): buffers = FilesArray(self.get_file_handles(filenames, buffer_size)) - with open(outfilename, 'w', buffer_size) as outfile: + with open(outfilename, "w", buffer_size) as outfile: while buffers.refresh(): min_index = self.merge_strategy.select(buffers.get_dict()) outfile.write(buffers.unshift(min_index)) @@ -101,12 +104,11 @@ def get_file_handles(self, filenames, buffer_size): files = {} for i in range(len(filenames)): - files[i] = open(filenames[i], 'r', buffer_size) + files[i] = open(filenames[i], "r", buffer_size) return files - class ExternalSort(object): def __init__(self, block_size): self.block_size = block_size @@ -118,7 +120,7 @@ def sort(self, filename, sort_key=None): merger = FileMerger(NWayMerge()) buffer_size = self.block_size / (num_blocks + 1) - merger.merge(splitter.get_block_filenames(), filename + '.out', buffer_size) + merger.merge(splitter.get_block_filenames(), filename + ".out", buffer_size) splitter.cleanup() @@ -127,32 +129,29 @@ def get_number_blocks(self, filename, block_size): def parse_memory(string): - if string[-1].lower() == 'k': + if string[-1].lower() == "k": return int(string[:-1]) * 1024 - elif string[-1].lower() == 'm': + elif string[-1].lower() == "m": return int(string[:-1]) * 1024 * 1024 - elif string[-1].lower() == 'g': + elif string[-1].lower() == "g": return int(string[:-1]) * 1024 * 1024 * 1024 else: return int(string) - def main(): parser = argparse.ArgumentParser() - parser.add_argument('-m', - '--mem', - help='amount of memory to use for sorting', - default='100M') - parser.add_argument('filename', - metavar='', - nargs=1, - help='name of file to sort') + parser.add_argument( + "-m", "--mem", help="amount of memory to use for sorting", default="100M" + ) + parser.add_argument( + "filename", metavar="", nargs=1, help="name of file to sort" + ) args = parser.parse_args() sorter = ExternalSort(parse_memory(args.mem)) sorter.sort(args.filename[0]) -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/sorts/gnome_sort.py b/sorts/gnome_sort.py index fed70eb6bc1b..58a44c94da43 100644 --- a/sorts/gnome_sort.py +++ b/sorts/gnome_sort.py @@ -14,12 +14,12 @@ def gnome_sort(unsorted): else: unsorted[i - 1], unsorted[i] = unsorted[i], unsorted[i - 1] i -= 1 - if (i == 0): + if i == 0: i = 1 -if __name__ == '__main__': - user_input = input('Enter numbers separated by a comma:\n').strip() - unsorted = [int(item) for item in user_input.split(',')] +if __name__ == "__main__": + user_input = input("Enter numbers separated by a comma:\n").strip() + unsorted = [int(item) for item in user_input.split(",")] gnome_sort(unsorted) print(unsorted) diff --git a/sorts/heap_sort.py b/sorts/heap_sort.py index ca4a061afbb7..a39ae2b88da2 100644 --- a/sorts/heap_sort.py +++ b/sorts/heap_sort.py @@ -1,4 +1,4 @@ -''' +""" This is a pure python implementation of the heap sort algorithm. For doctests run following command: @@ -8,7 +8,8 @@ For manual testing run: python heap_sort.py -''' +""" + def heapify(unsorted, index, heap_size): largest = index @@ -26,7 +27,7 @@ def heapify(unsorted, index, heap_size): def heap_sort(unsorted): - ''' + """ Pure implementation of the heap sort algorithm in Python :param collection: some mutable ordered collection with heterogeneous comparable items inside @@ -41,7 +42,7 @@ def heap_sort(unsorted): >>> heap_sort([-2, -5, -45]) [-45, -5, -2] - ''' + """ n = len(unsorted) for i in range(n // 2 - 1, -1, -1): heapify(unsorted, i, n) @@ -50,7 +51,8 @@ def heap_sort(unsorted): heapify(unsorted, 0, i) return unsorted -if __name__ == '__main__': - user_input = input('Enter numbers separated by a comma:\n').strip() - unsorted = [int(item) for item in user_input.split(',')] + +if __name__ == "__main__": + user_input = input("Enter numbers separated by a comma:\n").strip() + unsorted = [int(item) for item in user_input.split(",")] print(heap_sort(unsorted)) diff --git a/sorts/insertion_sort.py b/sorts/insertion_sort.py index e10497b0e282..b767018c3d57 100644 --- a/sorts/insertion_sort.py +++ b/sorts/insertion_sort.py @@ -9,6 +9,8 @@ For manual testing run: python insertion_sort.py """ + + def insertion_sort(collection): """Pure implementation of the insertion sort algorithm in Python @@ -29,14 +31,20 @@ def insertion_sort(collection): for loop_index in range(1, len(collection)): insertion_index = loop_index - while insertion_index > 0 and collection[insertion_index - 1] > collection[insertion_index]: - collection[insertion_index], collection[insertion_index - 1] = collection[insertion_index - 1], collection[insertion_index] + while ( + insertion_index > 0 + and collection[insertion_index - 1] > collection[insertion_index] + ): + collection[insertion_index], collection[insertion_index - 1] = ( + collection[insertion_index - 1], + collection[insertion_index], + ) insertion_index -= 1 return collection -if __name__ == '__main__': - user_input = input('Enter numbers separated by a comma:\n').strip() - unsorted = [int(item) for item in user_input.split(',')] +if __name__ == "__main__": + user_input = input("Enter numbers separated by a comma:\n").strip() + unsorted = [int(item) for item in user_input.split(",")] print(insertion_sort(unsorted)) diff --git a/sorts/merge_sort.py b/sorts/merge_sort.py index e64e90785a32..13f1144d4ad3 100644 --- a/sorts/merge_sort.py +++ b/sorts/merge_sort.py @@ -9,6 +9,8 @@ For manual testing run: python merge_sort.py """ + + def merge_sort(collection): """Pure implementation of the merge sort algorithm in Python @@ -26,23 +28,25 @@ def merge_sort(collection): >>> merge_sort([-2, -5, -45]) [-45, -5, -2] """ + def merge(left, right): - '''merge left and right + """merge left and right :param left: left collection :param right: right collection :return: merge result - ''' + """ result = [] while left and right: result.append((left if left[0] <= right[0] else right).pop(0)) return result + left + right + if len(collection) <= 1: return collection mid = len(collection) // 2 return merge(merge_sort(collection[:mid]), merge_sort(collection[mid:])) -if __name__ == '__main__': - user_input = input('Enter numbers separated by a comma:\n').strip() - unsorted = [int(item) for item in user_input.split(',')] - print(*merge_sort(unsorted), sep=',') +if __name__ == "__main__": + user_input = input("Enter numbers separated by a comma:\n").strip() + unsorted = [int(item) for item in user_input.split(",")] + print(*merge_sort(unsorted), sep=",") diff --git a/sorts/merge_sort_fastest.py b/sorts/merge_sort_fastest.py index 3c9ed3e9e8ee..f3c067795dd5 100644 --- a/sorts/merge_sort_fastest.py +++ b/sorts/merge_sort_fastest.py @@ -1,9 +1,11 @@ -''' +""" Python implementation of the fastest merge sort algorithm. Takes an average of 0.6 microseconds to sort a list of length 1000 items. Best Case Scenario : O(n) Worst Case Scenario : O(n^2) because native python functions:min, max and remove are already O(n) -''' +""" + + def merge_sort(collection): """Pure implementation of the fastest merge sort algorithm in Python @@ -32,7 +34,7 @@ def merge_sort(collection): return start + collection + end -if __name__ == '__main__': - user_input = input('Enter numbers separated by a comma:\n').strip() - unsorted = [int(item) for item in user_input.split(',')] - print(*merge_sort(unsorted), sep=',') +if __name__ == "__main__": + user_input = input("Enter numbers separated by a comma:\n").strip() + unsorted = [int(item) for item in user_input.split(",")] + print(*merge_sort(unsorted), sep=",") diff --git a/sorts/odd_even_transposition_parallel.py b/sorts/odd_even_transposition_parallel.py index 9bf81a39e27a..4d2f377024d2 100644 --- a/sorts/odd_even_transposition_parallel.py +++ b/sorts/odd_even_transposition_parallel.py @@ -12,7 +12,7 @@ """ from multiprocessing import Process, Pipe, Lock -#lock used to ensure that two processes do not access a pipe at the same time +# lock used to ensure that two processes do not access a pipe at the same time processLock = Lock() """ @@ -25,89 +25,117 @@ LRcv, RRcv = the pipes we use to receive from our left and right neighbors resultPipe = the pipe used to send results back to main """ + + def oeProcess(position, value, LSend, RSend, LRcv, RRcv, resultPipe): global processLock - #we perform n swaps since after n swaps we know we are sorted - #we *could* stop early if we are sorted already, but it takes as long to - #find out we are sorted as it does to sort the list with this algorithm + # we perform n swaps since after n swaps we know we are sorted + # we *could* stop early if we are sorted already, but it takes as long to + # find out we are sorted as it does to sort the list with this algorithm for i in range(0, 10): - if( (i + position) % 2 == 0 and RSend != None): - #send your value to your right neighbor + if (i + position) % 2 == 0 and RSend != None: + # send your value to your right neighbor processLock.acquire() RSend[1].send(value) processLock.release() - #receive your right neighbor's value + # receive your right neighbor's value processLock.acquire() temp = RRcv[0].recv() processLock.release() - #take the lower value since you are on the left + # take the lower value since you are on the left value = min(value, temp) - elif( (i + position) % 2 != 0 and LSend != None): - #send your value to your left neighbor + elif (i + position) % 2 != 0 and LSend != None: + # send your value to your left neighbor processLock.acquire() LSend[1].send(value) processLock.release() - #receive your left neighbor's value + # receive your left neighbor's value processLock.acquire() temp = LRcv[0].recv() processLock.release() - #take the higher value since you are on the right + # take the higher value since you are on the right value = max(value, temp) - #after all swaps are performed, send the values back to main + # after all swaps are performed, send the values back to main resultPipe[1].send(value) + """ the function which creates the processes that perform the parallel swaps arr = the list to be sorted """ + + def OddEvenTransposition(arr): processArray = [] resultPipe = [] - #initialize the list of pipes where the values will be retrieved + # initialize the list of pipes where the values will be retrieved for _ in arr: resultPipe.append(Pipe()) - #creates the processes - #the first and last process only have one neighbor so they are made outside - #of the loop + # creates the processes + # the first and last process only have one neighbor so they are made outside + # of the loop tempRs = Pipe() tempRr = Pipe() - processArray.append(Process(target = oeProcess, args = (0, arr[0], None, tempRs, None, tempRr, resultPipe[0]))) + processArray.append( + Process( + target=oeProcess, + args=(0, arr[0], None, tempRs, None, tempRr, resultPipe[0]), + ) + ) tempLr = tempRs tempLs = tempRr for i in range(1, len(arr) - 1): tempRs = Pipe() tempRr = Pipe() - processArray.append(Process(target = oeProcess, args = (i, arr[i], tempLs, tempRs, tempLr, tempRr, resultPipe[i]))) + processArray.append( + Process( + target=oeProcess, + args=(i, arr[i], tempLs, tempRs, tempLr, tempRr, resultPipe[i]), + ) + ) tempLr = tempRs tempLs = tempRr - processArray.append(Process(target = oeProcess, args = (len(arr) - 1, arr[len(arr) - 1], tempLs, None, tempLr, None, resultPipe[len(arr) - 1]))) - - #start the processes + processArray.append( + Process( + target=oeProcess, + args=( + len(arr) - 1, + arr[len(arr) - 1], + tempLs, + None, + tempLr, + None, + resultPipe[len(arr) - 1], + ), + ) + ) + + # start the processes for p in processArray: p.start() - #wait for the processes to end and write their values to the list + # wait for the processes to end and write their values to the list for p in range(0, len(resultPipe)): arr[p] = resultPipe[p][0].recv() processArray[p].join() - return(arr) + return arr -#creates a reverse sorted list and sorts it +# creates a reverse sorted list and sorts it def main(): arr = [] @@ -121,5 +149,6 @@ def main(): print("Sorted List\n") print(*arr) + if __name__ == "__main__": main() diff --git a/sorts/odd_even_transposition_single_threaded.py b/sorts/odd_even_transposition_single_threaded.py index ec5f3cf14e55..ec045d9dd08d 100644 --- a/sorts/odd_even_transposition_single_threaded.py +++ b/sorts/odd_even_transposition_single_threaded.py @@ -5,6 +5,7 @@ is no better than bubble sort. """ + def OddEvenTransposition(arr): for i in range(0, len(arr)): for i in range(i % 2, len(arr) - 1, 2): @@ -14,7 +15,8 @@ def OddEvenTransposition(arr): return arr -#creates a list and sorts it + +# creates a list and sorts it def main(): list = [] @@ -28,5 +30,6 @@ def main(): print("Sorted List\n") print(*list) + if __name__ == "__main__": main() diff --git a/sorts/pancake_sort.py b/sorts/pancake_sort.py index 873c14a0a174..ee54e57f9e0f 100644 --- a/sorts/pancake_sort.py +++ b/sorts/pancake_sort.py @@ -8,6 +8,7 @@ python pancake_sort.py """ + def pancake_sort(arr): """Sort Array with Pancake Sort. :param arr: Collection containing comparable items @@ -25,14 +26,14 @@ def pancake_sort(arr): # Find the maximum number in arr mi = arr.index(max(arr[0:cur])) # Reverse from 0 to mi - arr = arr[mi::-1] + arr[mi + 1:len(arr)] + arr = arr[mi::-1] + arr[mi + 1 : len(arr)] # Reverse whole list - arr = arr[cur - 1::-1] + arr[cur:len(arr)] + arr = arr[cur - 1 :: -1] + arr[cur : len(arr)] cur -= 1 return arr -if __name__ == '__main__': - user_input = input('Enter numbers separated by a comma:\n').strip() - unsorted = [int(item) for item in user_input.split(',')] +if __name__ == "__main__": + user_input = input("Enter numbers separated by a comma:\n").strip() + unsorted = [int(item) for item in user_input.split(",")] print(pancake_sort(unsorted)) diff --git a/sorts/pigeon_sort.py b/sorts/pigeon_sort.py index 5417234d331b..cf900699bc8d 100644 --- a/sorts/pigeon_sort.py +++ b/sorts/pigeon_sort.py @@ -1,4 +1,4 @@ -''' +""" This is an implementation of Pigeon Hole Sort. For doctests run following command: @@ -8,7 +8,9 @@ For manual testing run: python pigeon_sort.py -''' +""" + + def pigeon_sort(array): """ Implementation of pigeon hole sort algorithm @@ -21,7 +23,7 @@ def pigeon_sort(array): >>> pigeon_sort([-2, -5, -45]) [-45, -5, -2] """ - if(len(array) == 0): + if len(array) == 0: return array # Manually finds the minimum and maximum of the array. @@ -29,26 +31,29 @@ def pigeon_sort(array): max = array[0] for i in range(len(array)): - if(array[i] < min): min = array[i] - elif(array[i] > max): max = array[i] + if array[i] < min: + min = array[i] + elif array[i] > max: + max = array[i] # Compute the variables - holes_range = max-min + 1 + holes_range = max - min + 1 holes = [0 for _ in range(holes_range)] holes_repeat = [0 for _ in range(holes_range)] # Make the sorting. for i in range(len(array)): index = array[i] - min - if(holes[index] != array[i]): + if holes[index] != array[i]: holes[index] = array[i] holes_repeat[index] += 1 - else: holes_repeat[index] += 1 + else: + holes_repeat[index] += 1 # Makes the array back by replacing the numbers. index = 0 for i in range(holes_range): - while(holes_repeat[i] > 0): + while holes_repeat[i] > 0: array[index] = holes[i] index += 1 holes_repeat[i] -= 1 @@ -56,7 +61,8 @@ def pigeon_sort(array): # Returns the sorted array. return array -if __name__ == '__main__': - user_input = input('Enter numbers separated by comma:\n') - unsorted = [int(x) for x in user_input.split(',')] + +if __name__ == "__main__": + user_input = input("Enter numbers separated by comma:\n") + unsorted = [int(x) for x in user_input.split(",")] print(pigeon_sort(unsorted)) diff --git a/sorts/quick_sort.py b/sorts/quick_sort.py index 60f8803cb79c..29e10206f720 100644 --- a/sorts/quick_sort.py +++ b/sorts/quick_sort.py @@ -9,6 +9,8 @@ For manual testing run: python quick_sort.py """ + + def quick_sort(collection): """Pure implementation of quick sort algorithm in Python @@ -43,7 +45,7 @@ def quick_sort(collection): return quick_sort(lesser) + [pivot] + quick_sort(greater) -if __name__ == '__main__': - user_input = input('Enter numbers separated by a comma:\n').strip() - unsorted = [ int(item) for item in user_input.split(',') ] - print( quick_sort(unsorted) ) +if __name__ == "__main__": + user_input = input("Enter numbers separated by a comma:\n").strip() + unsorted = [int(item) for item in user_input.split(",")] + print(quick_sort(unsorted)) diff --git a/sorts/quick_sort_3_partition.py b/sorts/quick_sort_3_partition.py index 9056b204740a..a25ac7def802 100644 --- a/sorts/quick_sort_3_partition.py +++ b/sorts/quick_sort_3_partition.py @@ -17,8 +17,9 @@ def quick_sort_3partition(sorting, left, right): quick_sort_3partition(sorting, left, a - 1) quick_sort_3partition(sorting, b + 1, right) -if __name__ == '__main__': - user_input = input('Enter numbers separated by a comma:\n').strip() - unsorted = [ int(item) for item in user_input.split(',') ] - quick_sort_3partition(unsorted,0,len(unsorted)-1) + +if __name__ == "__main__": + user_input = input("Enter numbers separated by a comma:\n").strip() + unsorted = [int(item) for item in user_input.split(",")] + quick_sort_3partition(unsorted, 0, len(unsorted) - 1) print(unsorted) diff --git a/sorts/radix_sort.py b/sorts/radix_sort.py index 8dfc66b17b23..2990247a0ac0 100644 --- a/sorts/radix_sort.py +++ b/sorts/radix_sort.py @@ -6,21 +6,21 @@ def radix_sort(lst): max_digit = max(lst) while placement < max_digit: - # declare and initialize buckets - buckets = [list() for _ in range( RADIX )] + # declare and initialize buckets + buckets = [list() for _ in range(RADIX)] - # split lst between lists - for i in lst: - tmp = int((i / placement) % RADIX) - buckets[tmp].append(i) + # split lst between lists + for i in lst: + tmp = int((i / placement) % RADIX) + buckets[tmp].append(i) - # empty lists into lst array - a = 0 - for b in range( RADIX ): - buck = buckets[b] - for i in buck: - lst[a] = i - a += 1 + # empty lists into lst array + a = 0 + for b in range(RADIX): + buck = buckets[b] + for i in buck: + lst[a] = i + a += 1 - # move to next - placement *= RADIX + # move to next + placement *= RADIX diff --git a/sorts/random_normal_distribution_quicksort.py b/sorts/random_normal_distribution_quicksort.py index 39c54c46e263..be3b90190407 100644 --- a/sorts/random_normal_distribution_quicksort.py +++ b/sorts/random_normal_distribution_quicksort.py @@ -3,62 +3,60 @@ import numpy as np -def _inPlaceQuickSort(A,start,end): +def _inPlaceQuickSort(A, start, end): count = 0 - if start>> print(arr) [1, 2, 3, 4, 5] """ - stooge(arr,0,len(arr)-1) + stooge(arr, 0, len(arr) - 1) - -def stooge(arr, i, h): +def stooge(arr, i, h): if i >= h: return - + # If first element is smaller than the last then swap them - if arr[i]>arr[h]: + if arr[i] > arr[h]: arr[i], arr[h] = arr[h], arr[i] - + # If there are more than 2 elements in the array - if h-i+1 > 2: - t = (int)((h-i+1)/3) - + if h - i + 1 > 2: + t = (int)((h - i + 1) / 3) + # Recursively sort first 2/3 elements - stooge(arr, i, (h-t)) - + stooge(arr, i, (h - t)) + # Recursively sort last 2/3 elements - stooge(arr, i+t, (h)) - - # Recursively sort first 2/3 elements - stooge(arr, i, (h-t)) + stooge(arr, i + t, (h)) - + # Recursively sort first 2/3 elements + stooge(arr, i, (h - t)) diff --git a/sorts/topological_sort.py b/sorts/topological_sort.py index 74e58899a9a0..e7a52f7c7714 100644 --- a/sorts/topological_sort.py +++ b/sorts/topological_sort.py @@ -5,8 +5,8 @@ # b c # / \ # d e -edges = {'a': ['c', 'b'], 'b': ['d', 'e'], 'c': [], 'd': [], 'e': []} -vertices = ['a', 'b', 'c', 'd', 'e'] +edges = {"a": ["c", "b"], "b": ["d", "e"], "c": [], "d": [], "e": []} +vertices = ["a", "b", "c", "d", "e"] def topological_sort(start, visited, sort): @@ -30,6 +30,6 @@ def topological_sort(start, visited, sort): return sort -if __name__ == '__main__': - sort = topological_sort('a', [], []) +if __name__ == "__main__": + sort = topological_sort("a", [], []) print(sort) diff --git a/sorts/tree_sort.py b/sorts/tree_sort.py index baa4fc1acc20..716170a94fd1 100644 --- a/sorts/tree_sort.py +++ b/sorts/tree_sort.py @@ -5,7 +5,7 @@ """ -class node(): +class node: # BST data structure def __init__(self, val): self.val = val @@ -49,5 +49,5 @@ def tree_sort(arr): return res -if __name__ == '__main__': +if __name__ == "__main__": print(tree_sort([10, 1, 3, 2, 9, 14, 13])) diff --git a/sorts/wiggle_sort.py b/sorts/wiggle_sort.py index 606feb4d3dd1..5e5220ffbf05 100644 --- a/sorts/wiggle_sort.py +++ b/sorts/wiggle_sort.py @@ -16,7 +16,7 @@ def wiggle_sort(nums): nums[i - 1], nums[i] = nums[i], nums[i - 1] -if __name__ == '__main__': +if __name__ == "__main__": print("Enter the array elements:\n") array = list(map(int, input().split())) print("The unsorted array is:\n") diff --git a/strings/boyer_moore_search.py b/strings/boyer_moore_search.py index 2d67043dc028..59ee76b860d3 100644 --- a/strings/boyer_moore_search.py +++ b/strings/boyer_moore_search.py @@ -20,12 +20,9 @@ class BoyerMooreSearch: - - def __init__(self, text, pattern): self.text, self.pattern = text, pattern self.textLen, self.patLen = len(text), len(pattern) - def match_in_pattern(self, char): """ finds the index of char in pattern in reverse order @@ -36,14 +33,13 @@ def match_in_pattern(self, char): Returns : i (int): index of char from last in pattern -1 (int): if char is not found in pattern - """ + """ - for i in range(self.patLen-1, -1, -1): + for i in range(self.patLen - 1, -1, -1): if char == self.pattern[i]: return i return -1 - def mismatch_in_text(self, currentPos): """ finds the index of mis-matched character in text when compared with pattern from last @@ -55,14 +51,13 @@ def mismatch_in_text(self, currentPos): -1 (int): if there is no mis-match between pattern and text block """ - for i in range(self.patLen-1, -1, -1): + for i in range(self.patLen - 1, -1, -1): if self.pattern[i] != self.text[currentPos + i]: return currentPos + i return -1 - def bad_character_heuristic(self): - # searches pattern in text and returns index positions + # searches pattern in text and returns index positions positions = [] for i in range(self.textLen - self.patLen + 1): mismatch_index = self.mismatch_in_text(i) @@ -70,12 +65,14 @@ def bad_character_heuristic(self): positions.append(i) else: match_index = self.match_in_pattern(self.text[mismatch_index]) - i = mismatch_index - match_index #shifting index lgtm [py/multiple-definition] + i = ( + mismatch_index - match_index + ) # shifting index lgtm [py/multiple-definition] return positions - + text = "ABAABA" -pattern = "AB" +pattern = "AB" bms = BoyerMooreSearch(text, pattern) positions = bms.bad_character_heuristic() @@ -84,5 +81,3 @@ def bad_character_heuristic(self): else: print("Pattern found in following positions: ") print(positions) - - diff --git a/strings/knuth_morris_pratt.py b/strings/knuth_morris_pratt.py index 4553944284be..c7e96887c387 100644 --- a/strings/knuth_morris_pratt.py +++ b/strings/knuth_morris_pratt.py @@ -46,14 +46,14 @@ def get_failure_array(pattern): if pattern[i] == pattern[j]: i += 1 elif i > 0: - i = failure[i-1] + i = failure[i - 1] continue j += 1 failure.append(i) return failure -if __name__ == '__main__': +if __name__ == "__main__": # Test 1) pattern = "abc1abc12" text1 = "alskfjaldsabc1abc1abc12k23adsfabcabc" diff --git a/strings/levenshtein_distance.py b/strings/levenshtein_distance.py index 78175576194b..9b8793544a99 100644 --- a/strings/levenshtein_distance.py +++ b/strings/levenshtein_distance.py @@ -64,10 +64,13 @@ def levenshtein_distance(first_word, second_word): return previous_row[-1] -if __name__ == '__main__': - first_word = input('Enter the first word:\n').strip() - second_word = input('Enter the second word:\n').strip() +if __name__ == "__main__": + first_word = input("Enter the first word:\n").strip() + second_word = input("Enter the second word:\n").strip() result = levenshtein_distance(first_word, second_word) - print('Levenshtein distance between {} and {} is {}'.format( - first_word, second_word, result)) + print( + "Levenshtein distance between {} and {} is {}".format( + first_word, second_word, result + ) + ) diff --git a/strings/manacher.py b/strings/manacher.py index e73e173b43e0..ef8a724d027d 100644 --- a/strings/manacher.py +++ b/strings/manacher.py @@ -1,10 +1,15 @@ # calculate palindromic length from center with incrementing difference -def palindromic_length( center, diff, string): - if center-diff == -1 or center+diff == len(string) or string[center-diff] != string[center+diff] : +def palindromic_length(center, diff, string): + if ( + center - diff == -1 + or center + diff == len(string) + or string[center - diff] != string[center + diff] + ): return 0 - return 1 + palindromic_length(center, diff+1, string) + return 1 + palindromic_length(center, diff + 1, string) -def palindromic_string( input_string ): + +def palindromic_string(input_string): """ Manacher’s algorithm which finds Longest Palindromic Substring in linear time. @@ -16,37 +21,36 @@ def palindromic_string( input_string ): 3. return output_string from center - max_length to center + max_length and remove all "|" """ max_length = 0 - + # if input_string is "aba" than new_input_string become "a|b|a" new_input_string = "" output_string = "" # append each character + "|" in new_string for range(0, length-1) - for i in input_string[:len(input_string)-1] : + for i in input_string[: len(input_string) - 1]: new_input_string += i + "|" - #append last character + # append last character new_input_string += input_string[-1] - # for each character in new_string find corresponding palindromic string - for i in range(len(new_input_string)) : + for i in range(len(new_input_string)): # get palindromic length from ith position length = palindromic_length(i, 1, new_input_string) # update max_length and start position - if max_length < length : + if max_length < length: max_length = length start = i - - #create that string - for i in new_input_string[start-max_length:start+max_length+1] : + + # create that string + for i in new_input_string[start - max_length : start + max_length + 1]: if i != "|": output_string += i - + return output_string -if __name__ == '__main__': +if __name__ == "__main__": n = input() print(palindromic_string(n)) diff --git a/strings/min_cost_string_conversion.py b/strings/min_cost_string_conversion.py index 95840c484ba7..abc9d2c65158 100644 --- a/strings/min_cost_string_conversion.py +++ b/strings/min_cost_string_conversion.py @@ -1,114 +1,118 @@ -''' +""" Algorithm for calculating the most cost-efficient sequence for converting one string into another. The only allowed operations are ---Copy character with cost cC ---Replace character with cost cR ---Delete character with cost cD ---Insert character with cost cI -''' +""" + + def compute_transform_tables(X, Y, cC, cR, cD, cI): - X = list(X) - Y = list(Y) - m = len(X) - n = len(Y) + X = list(X) + Y = list(Y) + m = len(X) + n = len(Y) + + costs = [[0 for _ in range(n + 1)] for _ in range(m + 1)] + ops = [[0 for _ in range(n + 1)] for _ in range(m + 1)] - costs = [[0 for _ in range(n+1)] for _ in range(m+1)] - ops = [[0 for _ in range(n+1)] for _ in range(m+1)] + for i in range(1, m + 1): + costs[i][0] = i * cD + ops[i][0] = "D%c" % X[i - 1] - for i in range(1, m+1): - costs[i][0] = i*cD - ops[i][0] = 'D%c' % X[i-1] + for i in range(1, n + 1): + costs[0][i] = i * cI + ops[0][i] = "I%c" % Y[i - 1] - for i in range(1, n+1): - costs[0][i] = i*cI - ops[0][i] = 'I%c' % Y[i-1] + for i in range(1, m + 1): + for j in range(1, n + 1): + if X[i - 1] == Y[j - 1]: + costs[i][j] = costs[i - 1][j - 1] + cC + ops[i][j] = "C%c" % X[i - 1] + else: + costs[i][j] = costs[i - 1][j - 1] + cR + ops[i][j] = "R%c" % X[i - 1] + str(Y[j - 1]) - for i in range(1, m+1): - for j in range(1, n+1): - if X[i-1] == Y[j-1]: - costs[i][j] = costs[i-1][j-1] + cC - ops[i][j] = 'C%c' % X[i-1] - else: - costs[i][j] = costs[i-1][j-1] + cR - ops[i][j] = 'R%c' % X[i-1] + str(Y[j-1]) + if costs[i - 1][j] + cD < costs[i][j]: + costs[i][j] = costs[i - 1][j] + cD + ops[i][j] = "D%c" % X[i - 1] - if costs[i-1][j] + cD < costs[i][j]: - costs[i][j] = costs[i-1][j] + cD - ops[i][j] = 'D%c' % X[i-1] + if costs[i][j - 1] + cI < costs[i][j]: + costs[i][j] = costs[i][j - 1] + cI + ops[i][j] = "I%c" % Y[j - 1] - if costs[i][j-1] + cI < costs[i][j]: - costs[i][j] = costs[i][j-1] + cI - ops[i][j] = 'I%c' % Y[j-1] + return costs, ops - return costs, ops def assemble_transformation(ops, i, j): - if i == 0 and j == 0: - seq = [] - return seq - else: - if ops[i][j][0] == 'C' or ops[i][j][0] == 'R': - seq = assemble_transformation(ops, i-1, j-1) - seq.append(ops[i][j]) - return seq - elif ops[i][j][0] == 'D': - seq = assemble_transformation(ops, i-1, j) - seq.append(ops[i][j]) - return seq - else: - seq = assemble_transformation(ops, i, j-1) - seq.append(ops[i][j]) - return seq - -if __name__ == '__main__': - _, operations = compute_transform_tables('Python', 'Algorithms', -1, 1, 2, 2) - - m = len(operations) - n = len(operations[0]) - sequence = assemble_transformation(operations, m-1, n-1) - - string = list('Python') - i = 0 - cost = 0 - - with open('min_cost.txt', 'w') as file: - for op in sequence: - print(''.join(string)) - - if op[0] == 'C': - file.write('%-16s' % 'Copy %c' % op[1]) - file.write('\t\t\t' + ''.join(string)) - file.write('\r\n') - - cost -= 1 - elif op[0] == 'R': - string[i] = op[2] - - file.write('%-16s' % ('Replace %c' % op[1] + ' with ' + str(op[2]))) - file.write('\t\t' + ''.join(string)) - file.write('\r\n') - - cost += 1 - elif op[0] == 'D': - string.pop(i) - - file.write('%-16s' % 'Delete %c' % op[1]) - file.write('\t\t\t' + ''.join(string)) - file.write('\r\n') - - cost += 2 - else: - string.insert(i, op[1]) - - file.write('%-16s' % 'Insert %c' % op[1]) - file.write('\t\t\t' + ''.join(string)) - file.write('\r\n') - - cost += 2 - - i += 1 - - print(''.join(string)) - print('Cost: ', cost) - - file.write('\r\nMinimum cost: ' + str(cost)) + if i == 0 and j == 0: + seq = [] + return seq + else: + if ops[i][j][0] == "C" or ops[i][j][0] == "R": + seq = assemble_transformation(ops, i - 1, j - 1) + seq.append(ops[i][j]) + return seq + elif ops[i][j][0] == "D": + seq = assemble_transformation(ops, i - 1, j) + seq.append(ops[i][j]) + return seq + else: + seq = assemble_transformation(ops, i, j - 1) + seq.append(ops[i][j]) + return seq + + +if __name__ == "__main__": + _, operations = compute_transform_tables("Python", "Algorithms", -1, 1, 2, 2) + + m = len(operations) + n = len(operations[0]) + sequence = assemble_transformation(operations, m - 1, n - 1) + + string = list("Python") + i = 0 + cost = 0 + + with open("min_cost.txt", "w") as file: + for op in sequence: + print("".join(string)) + + if op[0] == "C": + file.write("%-16s" % "Copy %c" % op[1]) + file.write("\t\t\t" + "".join(string)) + file.write("\r\n") + + cost -= 1 + elif op[0] == "R": + string[i] = op[2] + + file.write("%-16s" % ("Replace %c" % op[1] + " with " + str(op[2]))) + file.write("\t\t" + "".join(string)) + file.write("\r\n") + + cost += 1 + elif op[0] == "D": + string.pop(i) + + file.write("%-16s" % "Delete %c" % op[1]) + file.write("\t\t\t" + "".join(string)) + file.write("\r\n") + + cost += 2 + else: + string.insert(i, op[1]) + + file.write("%-16s" % "Insert %c" % op[1]) + file.write("\t\t\t" + "".join(string)) + file.write("\r\n") + + cost += 2 + + i += 1 + + print("".join(string)) + print("Cost: ", cost) + + file.write("\r\nMinimum cost: " + str(cost)) diff --git a/strings/naive_string_search.py b/strings/naive_string_search.py index 04c0d8157b24..a8c2ea584399 100644 --- a/strings/naive_string_search.py +++ b/strings/naive_string_search.py @@ -7,23 +7,26 @@ n=length of main string m=length of pattern string """ -def naivePatternSearch(mainString,pattern): - patLen=len(pattern) - strLen=len(mainString) - position=[] - for i in range(strLen-patLen+1): - match_found=True + + +def naivePatternSearch(mainString, pattern): + patLen = len(pattern) + strLen = len(mainString) + position = [] + for i in range(strLen - patLen + 1): + match_found = True for j in range(patLen): - if mainString[i+j]!=pattern[j]: - match_found=False + if mainString[i + j] != pattern[j]: + match_found = False break if match_found: position.append(i) return position -mainString="ABAAABCDBBABCDDEBCABC" -pattern="ABC" -position=naivePatternSearch(mainString,pattern) + +mainString = "ABAAABCDBBABCDDEBCABC" +pattern = "ABC" +position = naivePatternSearch(mainString, pattern) print("Pattern found in position ") for x in position: - print(x) \ No newline at end of file + print(x) diff --git a/traversals/binary_tree_traversals.py b/traversals/binary_tree_traversals.py index 389311a7cfde..31a73ae0c6a4 100644 --- a/traversals/binary_tree_traversals.py +++ b/traversals/binary_tree_traversals.py @@ -266,6 +266,7 @@ def prompt(s: str = "", width=50, char="*") -> str: if __name__ == "__main__": import doctest + doctest.testmod() print(prompt("Binary Tree Traversals")) From 6ebd899c0145e8a4655c9a0d476f19f5f4d7ba2f Mon Sep 17 00:00:00 2001 From: Christian Clauss Date: Sat, 5 Oct 2019 20:32:58 +0500 Subject: [PATCH 0236/1071] CONTRIBUTING.md: Fix mistake in doctest ;-) (#1266) * CONTRIBUTING.md: Fix mistake in doctest ;-) * Update CONTRIBUTING.md --- CONTRIBUTING.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 8cd03217d51f..6dd2f6c6ff78 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -82,11 +82,11 @@ We want your work to be readable by others; therefore, we encourage you to note """ This function returns the sum of two integers a and b Return: a + b - >>> sum(2, 2) + >>> sumab(2, 2) 4 - >>> sum(-2, 3) + >>> sumab(-2, 3) 1 - >>> sum(4.9, 6.1) + >>> sumab(4.9, 5.1) 10.0 """ return a + b From c4a97677a57ab5ed5bf1d8238983c8899eb88a6c Mon Sep 17 00:00:00 2001 From: Nikhil Nayak Date: Mon, 7 Oct 2019 00:05:56 +0530 Subject: [PATCH 0237/1071] Update fibonacci_sequence_recursion.py (#1287) - Fixed minor bugs. - Minimized Codes --- maths/fibonacci_sequence_recursion.py | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/maths/fibonacci_sequence_recursion.py b/maths/fibonacci_sequence_recursion.py index 3a565a458631..2e0d835cf15e 100644 --- a/maths/fibonacci_sequence_recursion.py +++ b/maths/fibonacci_sequence_recursion.py @@ -2,20 +2,17 @@ def recur_fibo(n): - if n <= 1: - return n - else: - (recur_fibo(n - 1) + recur_fibo(n - 2)) - - -def isPositiveInteger(limit): - return limit >= 0 + """ + >>> [recur_fibo(i) for i in range(12)] + [0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89] + """ + return n if n <= 1 else recur_fibo(n-1) + recur_fibo(n-2) def main(): limit = int(input("How many terms to include in fibonacci series: ")) - if isPositiveInteger(limit): - print("The first {limit} terms of the fibonacci series are as follows:") + if limit > 0: + print(f"The first {limit} terms of the fibonacci series are as follows:") print([recur_fibo(n) for n in range(limit)]) else: print("Please enter a positive integer: ") From 0a7d387acbde85cda981ebc4cc4266270f77cf0a Mon Sep 17 00:00:00 2001 From: TheRealDarkCoder Date: Mon, 7 Oct 2019 00:47:32 +0600 Subject: [PATCH 0238/1071] Added a python script for finding sum of arithmetic series (#1279) * Added a python script for finding sum of arithmetic series * Added some linting * Resolved comments * Fixed flake8 test --- maths/sum_of_arithmetic_series.py | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100755 maths/sum_of_arithmetic_series.py diff --git a/maths/sum_of_arithmetic_series.py b/maths/sum_of_arithmetic_series.py new file mode 100755 index 000000000000..f7ea5dc84cb8 --- /dev/null +++ b/maths/sum_of_arithmetic_series.py @@ -0,0 +1,22 @@ +# DarkCoder +def sum_of_series(first_term, common_diff, num_of_terms): + """ + Find the sum of n terms in an arithmetic progression. + + >>> sum_of_series(1, 1, 10) + 55.0 + >>> sum_of_series(1, 10, 100) + 49600.0 + """ + sum = ((num_of_terms/2)*(2*first_term+(num_of_terms-1)*common_diff)) + # formula for sum of series + return sum + + +def main(): + print(sum_of_series(1, 1, 10)) + + +if __name__ == "__main__": + import doctest + doctest.testmod() From b1a769cf44df6f1eec740e10e393fab548e3822a Mon Sep 17 00:00:00 2001 From: Parth Paradkar Date: Mon, 7 Oct 2019 00:20:50 +0530 Subject: [PATCH 0239/1071] Add pure implementation of K-Nearest Neighbours (#1278) * Pure implementation of KNN added * Comments and test case added * doctest added --- machine_learning/k_nearest_neighbours.py | 55 ++++++++++++++++++++++++ 1 file changed, 55 insertions(+) create mode 100644 machine_learning/k_nearest_neighbours.py diff --git a/machine_learning/k_nearest_neighbours.py b/machine_learning/k_nearest_neighbours.py new file mode 100644 index 000000000000..83d8399fe9b6 --- /dev/null +++ b/machine_learning/k_nearest_neighbours.py @@ -0,0 +1,55 @@ +import numpy as np +from collections import Counter +from sklearn import datasets +from sklearn.model_selection import train_test_split + +data = datasets.load_iris() + +X = np.array(data['data']) +y = np.array(data['target']) +classes = data['target_names'] + +X_train, X_test, y_train, y_test = train_test_split(X, y) + +def euclidean_distance(a, b): + """ + Gives the euclidean distance between two points + >>> euclidean_distance([0, 0], [3, 4]) + 5.0 + >>> euclidean_distance([1, 2, 3], [1, 8, 11]) + 10.0 + """ + return np.linalg.norm(np.array(a) - np.array(b)) + +def classifier(train_data, train_target, classes, point, k=5): + """ + Classifies the point using the KNN algorithm + k closest points are found (ranked in ascending order of euclidean distance) + Params: + :train_data: Set of points that are classified into two or more classes + :train_target: List of classes in the order of train_data points + :classes: Labels of the classes + :point: The data point that needs to be classifed + + >>> X_train = [[0, 0], [1, 0], [0, 1], [0.5, 0.5], [3, 3], [2, 3], [3, 2]] + >>> y_train = [0, 0, 0, 0, 1, 1, 1] + >>> classes = ['A','B']; point = [1.2,1.2] + >>> classifier(X_train, y_train, classes,point) + 'A' + """ + data = zip(train_data, train_target) + # List of distances of all points from the point to be classified + distances = [] + for data_point in data: + distance = euclidean_distance(data_point[0], point) + distances.append((distance, data_point[1])) + # Choosing 'k' points with the least distances. + votes = [i[1] for i in sorted(distances)[:k]] + # Most commonly occuring class among them + # is the class into which the point is classified + result = Counter(votes).most_common(1)[0][0] + return classes[result] + + +if __name__ == "__main__": + print(classifier(X_train, y_train, classes, [4.4, 3.1, 1.3, 1.4])) \ No newline at end of file From 9cc9f67d646d427eb6b8296767aea50dd139969f Mon Sep 17 00:00:00 2001 From: Sushil Singh <36241112+OddExtension5@users.noreply.github.com> Date: Mon, 7 Oct 2019 00:22:04 +0530 Subject: [PATCH 0240/1071] Chinese Remainder Theorem | Diophantine Equation | Modular Division (#1248) * Update .gitignore to remove __pycache__/ * added chinese_remainder_theorem * Added Diophantine_equation algorithm * Update Diophantine eqn & chinese remainder theorem * Update Diophantine eqn & chinese remainder theorem * added efficient modular division algorithm * added GCD function * update chinese_remainder_theorem | dipohantine eqn | modular_division * update chinese_remainder_theorem | dipohantine eqn | modular_division * added a new directory named blockchain & a files from data_structures/hashing/number_theory * added a new directory named blockchain & a files from data_structures/hashing/number_theory --- blockchain/chinese_remainder_theorem.py | 91 +++++++++++++++ blockchain/diophantine_equation.py | 124 ++++++++++++++++++++ blockchain/modular_division.py | 149 ++++++++++++++++++++++++ 3 files changed, 364 insertions(+) create mode 100644 blockchain/chinese_remainder_theorem.py create mode 100644 blockchain/diophantine_equation.py create mode 100644 blockchain/modular_division.py diff --git a/blockchain/chinese_remainder_theorem.py b/blockchain/chinese_remainder_theorem.py new file mode 100644 index 000000000000..f1409530a70e --- /dev/null +++ b/blockchain/chinese_remainder_theorem.py @@ -0,0 +1,91 @@ +# Chinese Remainder Theorem: +# GCD ( Greatest Common Divisor ) or HCF ( Highest Common Factor ) + +# If GCD(a,b) = 1, then for any remainder ra modulo a and any remainder rb modulo b there exists integer n, +# such that n = ra (mod a) and n = ra(mod b). If n1 and n2 are two such integers, then n1=n2(mod ab) + +# Algorithm : + +# 1. Use extended euclid algorithm to find x,y such that a*x + b*y = 1 +# 2. Take n = ra*by + rb*ax + + +# Extended Euclid +def extended_euclid(a, b): + """ + >>> extended_euclid(10, 6) + (-1, 2) + + >>> extended_euclid(7, 5) + (-2, 3) + + """ + if b == 0: + return (1, 0) + (x, y) = extended_euclid(b, a % b) + k = a // b + return (y, x - k * y) + + +# Uses ExtendedEuclid to find inverses +def chinese_remainder_theorem(n1, r1, n2, r2): + """ + >>> chinese_remainder_theorem(5,1,7,3) + 31 + + Explanation : 31 is the smallest number such that + (i) When we divide it by 5, we get remainder 1 + (ii) When we divide it by 7, we get remainder 3 + + >>> chinese_remainder_theorem(6,1,4,3) + 14 + + """ + (x, y) = extended_euclid(n1, n2) + m = n1 * n2 + n = r2 * x * n1 + r1 * y * n2 + return ((n % m + m) % m) + + +# ----------SAME SOLUTION USING InvertModulo instead ExtendedEuclid---------------- + +# This function find the inverses of a i.e., a^(-1) +def invert_modulo(a, n): + """ + >>> invert_modulo(2, 5) + 3 + + >>> invert_modulo(8,7) + 1 + + """ + (b, x) = extended_euclid(a, n) + if b < 0: + b = (b % n + n) % n + return b + + +# Same a above using InvertingModulo +def chinese_remainder_theorem2(n1, r1, n2, r2): + """ + >>> chinese_remainder_theorem2(5,1,7,3) + 31 + + >>> chinese_remainder_theorem2(6,1,4,3) + 14 + + """ + x, y = invert_modulo(n1, n2), invert_modulo(n2, n1) + m = n1 * n2 + n = r2 * x * n1 + r1 * y * n2 + return (n % m + m) % m + + +# import testmod for testing our function +from doctest import testmod + +if __name__ == '__main__': + testmod(name='chinese_remainder_theorem', verbose=True) + testmod(name='chinese_remainder_theorem2', verbose=True) + testmod(name='invert_modulo', verbose=True) + testmod(name='extended_euclid', verbose=True) diff --git a/blockchain/diophantine_equation.py b/blockchain/diophantine_equation.py new file mode 100644 index 000000000000..3ac7094eed6b --- /dev/null +++ b/blockchain/diophantine_equation.py @@ -0,0 +1,124 @@ +# Diophantine Equation : Given integers a,b,c ( at least one of a and b != 0), the diophantine equation +# a*x + b*y = c has a solution (where x and y are integers) iff gcd(a,b) divides c. + +# GCD ( Greatest Common Divisor ) or HCF ( Highest Common Factor ) + + +def diophantine(a, b, c): + """ + >>> diophantine(10,6,14) + (-7.0, 14.0) + + >>> diophantine(391,299,-69) + (9.0, -12.0) + + But above equation has one more solution i.e., x = -4, y = 5. + That's why we need diophantine all solution function. + + """ + + assert c % greatest_common_divisor(a, b) == 0 # greatest_common_divisor(a,b) function implemented below + (d, x, y) = extended_gcd(a, b) # extended_gcd(a,b) function implemented below + r = c / d + return (r * x, r * y) + + +# Lemma : if n|ab and gcd(a,n) = 1, then n|b. + +# Finding All solutions of Diophantine Equations: + +# Theorem : Let gcd(a,b) = d, a = d*p, b = d*q. If (x0,y0) is a solution of Diophantine Equation a*x + b*y = c. +# a*x0 + b*y0 = c, then all the solutions have the form a(x0 + t*q) + b(y0 - t*p) = c, where t is an arbitrary integer. + +# n is the number of solution you want, n = 2 by default + +def diophantine_all_soln(a, b, c, n=2): + """ + >>> diophantine_all_soln(10, 6, 14) + -7.0 14.0 + -4.0 9.0 + + >>> diophantine_all_soln(10, 6, 14, 4) + -7.0 14.0 + -4.0 9.0 + -1.0 4.0 + 2.0 -1.0 + + >>> diophantine_all_soln(391, 299, -69, n = 4) + 9.0 -12.0 + 22.0 -29.0 + 35.0 -46.0 + 48.0 -63.0 + + """ + (x0, y0) = diophantine(a, b, c) # Initial value + d = greatest_common_divisor(a, b) + p = a // d + q = b // d + + for i in range(n): + x = x0 + i * q + y = y0 - i * p + print(x, y) + + +# Euclid's Lemma : d divides a and b, if and only if d divides a-b and b + +# Euclid's Algorithm + +def greatest_common_divisor(a, b): + """ + >>> greatest_common_divisor(7,5) + 1 + + Note : In number theory, two integers a and b are said to be relatively prime, mutually prime, or co-prime + if the only positive integer (factor) that divides both of them is 1 i.e., gcd(a,b) = 1. + + >>> greatest_common_divisor(121, 11) + 11 + + """ + if a < b: + a, b = b, a + + while a % b != 0: + a, b = b, a % b + + return b + + +# Extended Euclid's Algorithm : If d divides a and b and d = a*x + b*y for integers x and y, then d = gcd(a,b) + + +def extended_gcd(a, b): + """ + >>> extended_gcd(10, 6) + (2, -1, 2) + + >>> extended_gcd(7, 5) + (1, -2, 3) + + """ + assert a >= 0 and b >= 0 + + if b == 0: + d, x, y = a, 1, 0 + else: + (d, p, q) = extended_gcd(b, a % b) + x = q + y = p - q * (a // b) + + assert a % d == 0 and b % d == 0 + assert d == a * x + b * y + + return (d, x, y) + + +# import testmod for testing our function +from doctest import testmod + +if __name__ == '__main__': + testmod(name='diophantine', verbose=True) + testmod(name='diophantine_all_soln', verbose=True) + testmod(name='extended_gcd', verbose=True) + testmod(name='greatest_common_divisor', verbose=True) diff --git a/blockchain/modular_division.py b/blockchain/modular_division.py new file mode 100644 index 000000000000..4e1623fbe923 --- /dev/null +++ b/blockchain/modular_division.py @@ -0,0 +1,149 @@ +# Modular Division : +# An efficient algorithm for dividing b by a modulo n. + +# GCD ( Greatest Common Divisor ) or HCF ( Highest Common Factor ) + +# Given three integers a, b, and n, such that gcd(a,n)=1 and n>1, the algorithm should return an integer x such that +# 0≤x≤n−1, and b/a=x(modn) (that is, b=ax(modn)). + +# Theorem: +# a has a multiplicative inverse modulo n iff gcd(a,n) = 1 + + +# This find x = b*a^(-1) mod n +# Uses ExtendedEuclid to find the inverse of a + + +def modular_division(a, b, n): + """ + >>> modular_division(4,8,5) + 2 + + >>> modular_division(3,8,5) + 1 + + >>> modular_division(4, 11, 5) + 4 + + """ + assert n > 1 and a > 0 and greatest_common_divisor(a, n) == 1 + (d, t, s) = extended_gcd(n, a) # Implemented below + x = (b * s) % n + return x + + +# This function find the inverses of a i.e., a^(-1) +def invert_modulo(a, n): + """ + >>> invert_modulo(2, 5) + 3 + + >>> invert_modulo(8,7) + 1 + + """ + (b, x) = extended_euclid(a, n) # Implemented below + if b < 0: + b = (b % n + n) % n + return b + + +# ------------------ Finding Modular division using invert_modulo ------------------- + +# This function used the above inversion of a to find x = (b*a^(-1))mod n +def modular_division2(a, b, n): + """ + >>> modular_division2(4,8,5) + 2 + + >>> modular_division2(3,8,5) + 1 + + >>> modular_division2(4, 11, 5) + 4 + + """ + s = invert_modulo(a, n) + x = (b * s) % n + return x + + +# Extended Euclid's Algorithm : If d divides a and b and d = a*x + b*y for integers x and y, then d = gcd(a,b) + +def extended_gcd(a, b): + """ + >>> extended_gcd(10, 6) + (2, -1, 2) + + >>> extended_gcd(7, 5) + (1, -2, 3) + + ** extended_gcd function is used when d = gcd(a,b) is required in output + + """ + assert a >= 0 and b >= 0 + + if b == 0: + d, x, y = a, 1, 0 + else: + (d, p, q) = extended_gcd(b, a % b) + x = q + y = p - q * (a // b) + + assert a % d == 0 and b % d == 0 + assert d == a * x + b * y + + return (d, x, y) + + +# Extended Euclid +def extended_euclid(a, b): + """ + >>> extended_euclid(10, 6) + (-1, 2) + + >>> extended_euclid(7, 5) + (-2, 3) + + """ + if b == 0: + return (1, 0) + (x, y) = extended_euclid(b, a % b) + k = a // b + return (y, x - k * y) + + +# Euclid's Lemma : d divides a and b, if and only if d divides a-b and b +# Euclid's Algorithm + +def greatest_common_divisor(a, b): + """ + >>> greatest_common_divisor(7,5) + 1 + + Note : In number theory, two integers a and b are said to be relatively prime, mutually prime, or co-prime + if the only positive integer (factor) that divides both of them is 1 i.e., gcd(a,b) = 1. + + >>> greatest_common_divisor(121, 11) + 11 + + """ + if a < b: + a, b = b, a + + while a % b != 0: + a, b = b, a % b + + return b + + +# Import testmod for testing our function +from doctest import testmod + +if __name__ == '__main__': + testmod(name='modular_division', verbose=True) + testmod(name='modular_division2', verbose=True) + testmod(name='invert_modulo', verbose=True) + testmod(name='extended_gcd', verbose=True) + testmod(name='extended_euclid', verbose=True) + testmod(name='greatest_common_divisor', verbose=True) From 067a9b513628c8aa91fb1c74f44dfa5ee3ef6c1d Mon Sep 17 00:00:00 2001 From: mvhb Date: Sun, 6 Oct 2019 15:55:55 -0300 Subject: [PATCH 0241/1071] adding input option and increasing the number of doctest (#1281) * adding input option and incresing the number of doctest * mixing positive and negative numbers in the same test case --- sorts/stooge_sort.py | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/sorts/stooge_sort.py b/sorts/stooge_sort.py index 8cccd1e8c657..089b01a4def1 100644 --- a/sorts/stooge_sort.py +++ b/sorts/stooge_sort.py @@ -1,11 +1,14 @@ def stooge_sort(arr): """ - >>> arr = [2, 4, 5, 3, 1] - >>> stooge_sort(arr) - >>> print(arr) - [1, 2, 3, 4, 5] + Examples: + >>> stooge_sort([18.1, 0, -7.1, -1, 2, 2]) + [-7.1, -1, 0, 2, 2, 18.1] + + >>> stooge_sort([]) + [] """ stooge(arr, 0, len(arr) - 1) + return arr def stooge(arr, i, h): @@ -29,3 +32,8 @@ def stooge(arr, i, h): # Recursively sort first 2/3 elements stooge(arr, i, (h - t)) + +if __name__ == "__main__": + user_input = input("Enter numbers separated by a comma:\n").strip() + unsorted = [int(item) for item in user_input.split(",")] + print(stooge_sort(unsorted)) From 01bc785e84c0f116898ac482d3ad95d2acf4af51 Mon Sep 17 00:00:00 2001 From: Anup Kumar Panwar <1anuppanwar@gmail.com> Date: Mon, 7 Oct 2019 23:53:46 +0530 Subject: [PATCH 0242/1071] Fixed links in DIRECTORY.md (#1291) --- DIRECTORY.md | 818 +++++++++++++++++++++++++++------------------------ 1 file changed, 433 insertions(+), 385 deletions(-) diff --git a/DIRECTORY.md b/DIRECTORY.md index 248fe7b9eaa6..a4838d24dab7 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -1,411 +1,459 @@ ## Arithmetic Analysis - * [bisection](https://github.com/TheAlgorithms/Python/blob/master/arithmetic_analysis/bisection.py) - * [in static equilibrium](https://github.com/TheAlgorithms/Python/blob/master/arithmetic_analysis/in_static_equilibrium.py) - * [intersection](https://github.com/TheAlgorithms/Python/blob/master/arithmetic_analysis/intersection.py) - * [lu decomposition](https://github.com/TheAlgorithms/Python/blob/master/arithmetic_analysis/lu_decomposition.py) - * [newton method](https://github.com/TheAlgorithms/Python/blob/master/arithmetic_analysis/newton_method.py) - * [newton raphson method](https://github.com/TheAlgorithms/Python/blob/master/arithmetic_analysis/newton_raphson_method.py) + +- [bisection](https://github.com/TheAlgorithms/Python/blob/master/arithmetic_analysis/bisection.py) +- [in static equilibrium](https://github.com/TheAlgorithms/Python/blob/master/arithmetic_analysis/in_static_equilibrium.py) +- [intersection](https://github.com/TheAlgorithms/Python/blob/master/arithmetic_analysis/intersection.py) +- [lu decomposition](https://github.com/TheAlgorithms/Python/blob/master/arithmetic_analysis/lu_decomposition.py) +- [newton method](https://github.com/TheAlgorithms/Python/blob/master/arithmetic_analysis/newton_method.py) +- [newton raphson method](https://github.com/TheAlgorithms/Python/blob/master/arithmetic_analysis/newton_raphson_method.py) + ## Backtracking - * [all combinations](https://github.com/TheAlgorithms/Python/blob/master/backtracking/all_combinations.py) - * [all permutations](https://github.com/TheAlgorithms/Python/blob/master/backtracking/all_permutations.py) - * [all subsequences](https://github.com/TheAlgorithms/Python/blob/master/backtracking/all_subsequences.py) - * [minimax](https://github.com/TheAlgorithms/Python/blob/master/backtracking/minimax.py) - * [n queens](https://github.com/TheAlgorithms/Python/blob/master/backtracking/n_queens.py) - * [sudoku](https://github.com/TheAlgorithms/Python/blob/master/backtracking/sudoku.py) - * [sum of subsets](https://github.com/TheAlgorithms/Python/blob/master/backtracking/sum_of_subsets.py) + +- [all combinations](https://github.com/TheAlgorithms/Python/blob/master/backtracking/all_combinations.py) +- [all permutations](https://github.com/TheAlgorithms/Python/blob/master/backtracking/all_permutations.py) +- [all subsequences](https://github.com/TheAlgorithms/Python/blob/master/backtracking/all_subsequences.py) +- [minimax](https://github.com/TheAlgorithms/Python/blob/master/backtracking/minimax.py) +- [n queens](https://github.com/TheAlgorithms/Python/blob/master/backtracking/n_queens.py) +- [sudoku](https://github.com/TheAlgorithms/Python/blob/master/backtracking/sudoku.py) +- [sum of subsets](https://github.com/TheAlgorithms/Python/blob/master/backtracking/sum_of_subsets.py) + ## Boolean Algebra - * [quine mc cluskey](https://github.com/TheAlgorithms/Python/blob/master/boolean_algebra/quine_mc_cluskey.py) + +- [quine mc cluskey](https://github.com/TheAlgorithms/Python/blob/master/boolean_algebra/quine_mc_cluskey.py) + ## Ciphers - * [affine cipher](https://github.com/TheAlgorithms/Python/blob/master/ciphers/affine_cipher.py) - * [atbash](https://github.com/TheAlgorithms/Python/blob/master/ciphers/atbash.py) - * [base16](https://github.com/TheAlgorithms/Python/blob/master/ciphers/base16.py) - * [base32](https://github.com/TheAlgorithms/Python/blob/master/ciphers/base32.py) - * [base64 cipher](https://github.com/TheAlgorithms/Python/blob/master/ciphers/base64_cipher.py) - * [base85](https://github.com/TheAlgorithms/Python/blob/master/ciphers/base85.py) - * [brute force caesar cipher](https://github.com/TheAlgorithms/Python/blob/master/ciphers/brute_force_caesar_cipher.py) - * [caesar cipher](https://github.com/TheAlgorithms/Python/blob/master/ciphers/caesar_cipher.py) - * [cryptomath module](https://github.com/TheAlgorithms/Python/blob/master/ciphers/cryptomath_module.py) - * [elgamal key generator](https://github.com/TheAlgorithms/Python/blob/master/ciphers/elgamal_key_generator.py) - * [hill cipher](https://github.com/TheAlgorithms/Python/blob/master/ciphers/hill_cipher.py) - * [morse code implementation](https://github.com/TheAlgorithms/Python/blob/master/ciphers/morse_code_implementation.py) - * [onepad cipher](https://github.com/TheAlgorithms/Python/blob/master/ciphers/onepad_cipher.py) - * [playfair cipher](https://github.com/TheAlgorithms/Python/blob/master/ciphers/playfair_cipher.py) - * [rabin miller](https://github.com/TheAlgorithms/Python/blob/master/ciphers/rabin_miller.py) - * [rot13](https://github.com/TheAlgorithms/Python/blob/master/ciphers/rot13.py) - * [rsa cipher](https://github.com/TheAlgorithms/Python/blob/master/ciphers/rsa_cipher.py) - * [rsa key generator](https://github.com/TheAlgorithms/Python/blob/master/ciphers/rsa_key_generator.py) - * [simple substitution cipher](https://github.com/TheAlgorithms/Python/blob/master/ciphers/simple_substitution_cipher.py) - * [trafid cipher](https://github.com/TheAlgorithms/Python/blob/master/ciphers/trafid_cipher.py) - * [transposition cipher](https://github.com/TheAlgorithms/Python/blob/master/ciphers/transposition_cipher.py) - * [transposition cipher encrypt decrypt file](https://github.com/TheAlgorithms/Python/blob/master/ciphers/transposition_cipher_encrypt_decrypt_file.py) - * [vigenere cipher](https://github.com/TheAlgorithms/Python/blob/master/ciphers/vigenere_cipher.py) - * [xor cipher](https://github.com/TheAlgorithms/Python/blob/master/ciphers/xor_cipher.py) + +- [affine cipher](https://github.com/TheAlgorithms/Python/blob/master/ciphers/affine_cipher.py) +- [atbash](https://github.com/TheAlgorithms/Python/blob/master/ciphers/atbash.py) +- [base16](https://github.com/TheAlgorithms/Python/blob/master/ciphers/base16.py) +- [base32](https://github.com/TheAlgorithms/Python/blob/master/ciphers/base32.py) +- [base64 cipher](https://github.com/TheAlgorithms/Python/blob/master/ciphers/base64_cipher.py) +- [base85](https://github.com/TheAlgorithms/Python/blob/master/ciphers/base85.py) +- [brute force caesar cipher](https://github.com/TheAlgorithms/Python/blob/master/ciphers/brute_force_caesar_cipher.py) +- [caesar cipher](https://github.com/TheAlgorithms/Python/blob/master/ciphers/caesar_cipher.py) +- [cryptomath module](https://github.com/TheAlgorithms/Python/blob/master/ciphers/cryptomath_module.py) +- [elgamal key generator](https://github.com/TheAlgorithms/Python/blob/master/ciphers/elgamal_key_generator.py) +- [hill cipher](https://github.com/TheAlgorithms/Python/blob/master/ciphers/hill_cipher.py) +- [morse code implementation](https://github.com/TheAlgorithms/Python/blob/master/ciphers/morse_code_implementation.py) +- [onepad cipher](https://github.com/TheAlgorithms/Python/blob/master/ciphers/onepad_cipher.py) +- [playfair cipher](https://github.com/TheAlgorithms/Python/blob/master/ciphers/playfair_cipher.py) +- [rabin miller](https://github.com/TheAlgorithms/Python/blob/master/ciphers/rabin_miller.py) +- [rot13](https://github.com/TheAlgorithms/Python/blob/master/ciphers/rot13.py) +- [rsa cipher](https://github.com/TheAlgorithms/Python/blob/master/ciphers/rsa_cipher.py) +- [rsa key generator](https://github.com/TheAlgorithms/Python/blob/master/ciphers/rsa_key_generator.py) +- [simple substitution cipher](https://github.com/TheAlgorithms/Python/blob/master/ciphers/simple_substitution_cipher.py) +- [trafid cipher](https://github.com/TheAlgorithms/Python/blob/master/ciphers/trafid_cipher.py) +- [transposition cipher](https://github.com/TheAlgorithms/Python/blob/master/ciphers/transposition_cipher.py) +- [transposition cipher encrypt decrypt file](https://github.com/TheAlgorithms/Python/blob/master/ciphers/transposition_cipher_encrypt_decrypt_file.py) +- [vigenere cipher](https://github.com/TheAlgorithms/Python/blob/master/ciphers/vigenere_cipher.py) +- [xor cipher](https://github.com/TheAlgorithms/Python/blob/master/ciphers/xor_cipher.py) + ## Compression - * [burrows wheeler](https://github.com/TheAlgorithms/Python/blob/master/compression/burrows_wheeler.py) - * [huffman](https://github.com/TheAlgorithms/Python/blob/master/compression/huffman.py) - * [peak signal to noise ratio](https://github.com/TheAlgorithms/Python/blob/master/compression/peak_signal_to_noise_ratio.py) + +- [burrows wheeler](https://github.com/TheAlgorithms/Python/blob/master/compression/burrows_wheeler.py) +- [huffman](https://github.com/TheAlgorithms/Python/blob/master/compression/huffman.py) +- [peak signal to noise ratio](https://github.com/TheAlgorithms/Python/blob/master/compression/peak_signal_to_noise_ratio.py) + ## Conversions - * [decimal to binary](https://github.com/TheAlgorithms/Python/blob/master/conversions/decimal_to_binary.py) - * [decimal to hexadecimal](https://github.com/TheAlgorithms/Python/blob/master/conversions/decimal_to_hexadecimal.py) - * [decimal to octal](https://github.com/TheAlgorithms/Python/blob/master/conversions/decimal_to_octal.py) + +- [decimal to binary](https://github.com/TheAlgorithms/Python/blob/master/conversions/decimal_to_binary.py) +- [decimal to hexadecimal](https://github.com/TheAlgorithms/Python/blob/master/conversions/decimal_to_hexadecimal.py) +- [decimal to octal](https://github.com/TheAlgorithms/Python/blob/master/conversions/decimal_to_octal.py) + ## Data Structures - * Binary Tree - * [avl tree](https://github.com/TheAlgorithms/Python/blob/master/data_structures/avl_tree.py) - * [basic binary tree](https://github.com/TheAlgorithms/Python/blob/master/data_structures/basic_binary_tree.py) - * [binary search tree](https://github.com/TheAlgorithms/Python/blob/master/data_structures/binary_search_tree.py) - * [fenwick tree](https://github.com/TheAlgorithms/Python/blob/master/data_structures/fenwick_tree.py) - * [lazy segment tree](https://github.com/TheAlgorithms/Python/blob/master/data_structures/lazy_segment_tree.py) - * [lca](https://github.com/TheAlgorithms/Python/blob/master/data_structures/lca.py) - * [red black tree](https://github.com/TheAlgorithms/Python/blob/master/data_structures/red_black_tree.py) - * [segment tree](https://github.com/TheAlgorithms/Python/blob/master/data_structures/segment_tree.py) - * [treap](https://github.com/TheAlgorithms/Python/blob/master/data_structures/treap.py) - * Hashing - * [double hash](https://github.com/TheAlgorithms/Python/blob/master/data_structures/double_hash.py) - * [hash table](https://github.com/TheAlgorithms/Python/blob/master/data_structures/hash_table.py) - * [hash table with linked list](https://github.com/TheAlgorithms/Python/blob/master/data_structures/hash_table_with_linked_list.py) - * Number Theory - * [prime numbers](https://github.com/TheAlgorithms/Python/blob/master/data_structures/prime_numbers.py) - * [quadratic probing](https://github.com/TheAlgorithms/Python/blob/master/data_structures/quadratic_probing.py) - * Heap - * [heap](https://github.com/TheAlgorithms/Python/blob/master/data_structures/heap.py) - * Linked List - * [doubly linked list](https://github.com/TheAlgorithms/Python/blob/master/data_structures/doubly_linked_list.py) - * [is palindrome](https://github.com/TheAlgorithms/Python/blob/master/data_structures/is_palindrome.py) - * [singly linked list](https://github.com/TheAlgorithms/Python/blob/master/data_structures/singly_linked_list.py) - * [swap nodes](https://github.com/TheAlgorithms/Python/blob/master/data_structures/swap_nodes.py) - * Queue - * [double ended queue](https://github.com/TheAlgorithms/Python/blob/master/data_structures/double_ended_queue.py) - * [queue on list](https://github.com/TheAlgorithms/Python/blob/master/data_structures/queue_on_list.py) - * [queue on pseudo stack](https://github.com/TheAlgorithms/Python/blob/master/data_structures/queue_on_pseudo_stack.py) - * Stacks - * [balanced parentheses](https://github.com/TheAlgorithms/Python/blob/master/data_structures/balanced_parentheses.py) - * [infix to postfix conversion](https://github.com/TheAlgorithms/Python/blob/master/data_structures/infix_to_postfix_conversion.py) - * [infix to prefix conversion](https://github.com/TheAlgorithms/Python/blob/master/data_structures/infix_to_prefix_conversion.py) - * [next greater element](https://github.com/TheAlgorithms/Python/blob/master/data_structures/next_greater_element.py) - * [postfix evaluation](https://github.com/TheAlgorithms/Python/blob/master/data_structures/postfix_evaluation.py) - * [stack](https://github.com/TheAlgorithms/Python/blob/master/data_structures/stack.py) - * [stock span problem](https://github.com/TheAlgorithms/Python/blob/master/data_structures/stock_span_problem.py) - * Trie - * [trie](https://github.com/TheAlgorithms/Python/blob/master/data_structures/trie.py) + +- Binary Tree + - [avl tree](https://github.com/TheAlgorithms/Python/blob/master/data_structures/binary_tree/avl_tree.py) + - [basic binary tree](https://github.com/TheAlgorithms/Python/blob/master/data_structures/binary_tree/basic_binary_tree.py) + - [binary search tree](https://github.com/TheAlgorithms/Python/blob/master/data_structures/binary_tree/binary_search_tree.py) + - [fenwick tree](https://github.com/TheAlgorithms/Python/blob/master/data_structures/binary_tree/fenwick_tree.py) + - [lazy segment tree](https://github.com/TheAlgorithms/Python/blob/master/data_structures/binary_tree/lazy_segment_tree.py) + - [lca](https://github.com/TheAlgorithms/Python/blob/master/data_structures/binary_tree/lca.py) + - [red black tree](https://github.com/TheAlgorithms/Python/blob/master/data_structures/binary_tree/red_black_tree.py) + - [segment tree](https://github.com/TheAlgorithms/Python/blob/master/data_structures/binary_tree/segment_tree.py) + - [treap](https://github.com/TheAlgorithms/Python/blob/master/data_structures/binary_tree/treap.py) +- Hashing + - [double hash](https://github.com/TheAlgorithms/Python/blob/master/data_structures/hashing/double_hash.py) + - [hash table](https://github.com/TheAlgorithms/Python/blob/master/data_structures/hashing/hash_table.py) + - [hash table with linked list](https://github.com/TheAlgorithms/Python/blob/master/data_structures/hashing/hash_table_with_linked_list.py) +- Number Theory + - [prime numbers](https://github.com/TheAlgorithms/Python/blob/master/data_structures/number_theory/prime_numbers.py) + - [quadratic probing](https://github.com/TheAlgorithms/Python/blob/master/data_structures/number_theory/quadratic_probing.py) +- Heap + - [heap](https://github.com/TheAlgorithms/Python/blob/master/data_structures/heap/heap.py) +- Linked List + - [doubly linked list](https://github.com/TheAlgorithms/Python/blob/master/data_structures/linked_list/doubly_linked_list.py) + - [is palindrome](https://github.com/TheAlgorithms/Python/blob/master/data_structures/linked_list/is_palindrome.py) + - [singly linked list](https://github.com/TheAlgorithms/Python/blob/master/data_structures/linked_list/singly_linked_list.py) + - [swap nodes](https://github.com/TheAlgorithms/Python/blob/master/data_structures/linked_list/swap_nodes.py) +- Queue + - [double ended queue](https://github.com/TheAlgorithms/Python/blob/master/data_structures/queue/double_ended_queue.py) + - [queue on list](https://github.com/TheAlgorithms/Python/blob/master/data_structures/queue/queue_on_list.py) + - [queue on pseudo stack](https://github.com/TheAlgorithms/Python/blob/master/data_structures/queue/queue_on_pseudo_stack.py) +- Stacks + - [balanced parentheses](https://github.com/TheAlgorithms/Python/blob/master/data_structures/stacks/balanced_parentheses.py) + - [infix to postfix conversion](https://github.com/TheAlgorithms/Python/blob/master/data_structures/stacks/infix_to_postfix_conversion.py) + - [infix to prefix conversion](https://github.com/TheAlgorithms/Python/blob/master/data_structures/stacks/infix_to_prefix_conversion.py) + - [next greater element](https://github.com/TheAlgorithms/Python/blob/master/data_structures/stacks/next_greater_element.py) + - [postfix evaluation](https://github.com/TheAlgorithms/Python/blob/master/data_structures/stacks/postfix_evaluation.py) + - [stack](https://github.com/TheAlgorithms/Python/blob/master/data_structures/stacks/stack.py) + - [stock span problem](https://github.com/TheAlgorithms/Python/blob/master/data_structures/stacks/stock_span_problem.py) +- Trie + - [trie](https://github.com/TheAlgorithms/Python/blob/master/data_structures/trie/trie.py) + ## Digital Image Processing - * [change contrast](https://github.com/TheAlgorithms/Python/blob/master/digital_image_processing/change_contrast.py) - * Edge Detection - * [canny](https://github.com/TheAlgorithms/Python/blob/master/digital_image_processing/canny.py) - * Filters - * [convolve](https://github.com/TheAlgorithms/Python/blob/master/digital_image_processing/convolve.py) - * [gaussian filter](https://github.com/TheAlgorithms/Python/blob/master/digital_image_processing/gaussian_filter.py) - * [median filter](https://github.com/TheAlgorithms/Python/blob/master/digital_image_processing/median_filter.py) - * [sobel filter](https://github.com/TheAlgorithms/Python/blob/master/digital_image_processing/sobel_filter.py) - * [test digital image processing](https://github.com/TheAlgorithms/Python/blob/master/digital_image_processing/test_digital_image_processing.py) + +- [change contrast](https://github.com/TheAlgorithms/Python/blob/master/digital_image_processing/change_contrast.py) +- Edge Detection + - [canny](https://github.com/TheAlgorithms/Python/blob/master/digital_image_processing/edge_detection/canny.py) +- Filters + - [convolve](https://github.com/TheAlgorithms/Python/blob/master/digital_image_processing/filters/convolve.py) + - [gaussian filter](https://github.com/TheAlgorithms/Python/blob/master/digital_image_processing/filters/gaussian_filter.py) + - [median filter](https://github.com/TheAlgorithms/Python/blob/master/digital_image_processing/filters/median_filter.py) + - [sobel filter](https://github.com/TheAlgorithms/Python/blob/master/digital_image_processing/filters/sobel_filter.py) +- [test digital image processing](https://github.com/TheAlgorithms/Python/blob/master/digital_image_processing/test_digital_image_processing.py) + ## Divide And Conquer - * [closest pair of points](https://github.com/TheAlgorithms/Python/blob/master/divide_and_conquer/closest_pair_of_points.py) - * [convex hull](https://github.com/TheAlgorithms/Python/blob/master/divide_and_conquer/convex_hull.py) - * [inversions](https://github.com/TheAlgorithms/Python/blob/master/divide_and_conquer/inversions.py) - * [max subarray sum](https://github.com/TheAlgorithms/Python/blob/master/divide_and_conquer/max_subarray_sum.py) + +- [closest pair of points](https://github.com/TheAlgorithms/Python/blob/master/divide_and_conquer/closest_pair_of_points.py) +- [convex hull](https://github.com/TheAlgorithms/Python/blob/master/divide_and_conquer/convex_hull.py) +- [inversions](https://github.com/TheAlgorithms/Python/blob/master/divide_and_conquer/inversions.py) +- [max subarray sum](https://github.com/TheAlgorithms/Python/blob/master/divide_and_conquer/max_subarray_sum.py) + ## Dynamic Programming - * [abbreviation](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/abbreviation.py) - * [bitmask](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/bitmask.py) - * [climbing stairs](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/climbing_stairs.py) - * [coin change](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/coin_change.py) - * [edit distance](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/edit_distance.py) - * [factorial](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/factorial.py) - * [fast fibonacci](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/fast_fibonacci.py) - * [fibonacci](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/fibonacci.py) - * [floyd warshall](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/floyd_warshall.py) - * [fractional knapsack](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/fractional_knapsack.py) - * [integer partition](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/integer_partition.py) - * [k means clustering tensorflow](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/k_means_clustering_tensorflow.py) - * [knapsack](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/knapsack.py) - * [longest common subsequence](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/longest_common_subsequence.py) - * [longest increasing subsequence](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/longest_increasing_subsequence.py) - * [longest increasing subsequence o(nlogn)](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/longest_increasing_subsequence_o(nlogn).py) - * [longest sub array](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/longest_sub_array.py) - * [matrix chain order](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/matrix_chain_order.py) - * [max sub array](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/max_sub_array.py) - * [minimum partition](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/minimum_partition.py) - * [rod cutting](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/rod_cutting.py) - * [subset generation](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/subset_generation.py) - * [sum of subset](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/sum_of_subset.py) + +- [abbreviation](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/abbreviation.py) +- [bitmask](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/bitmask.py) +- [climbing stairs](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/climbing_stairs.py) +- [coin change](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/coin_change.py) +- [edit distance](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/edit_distance.py) +- [factorial](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/factorial.py) +- [fast fibonacci](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/fast_fibonacci.py) +- [fibonacci](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/fibonacci.py) +- [floyd warshall](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/floyd_warshall.py) +- [fractional knapsack](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/fractional_knapsack.py) +- [integer partition](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/integer_partition.py) +- [k means clustering tensorflow](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/k_means_clustering_tensorflow.py) +- [knapsack](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/knapsack.py) +- [longest common subsequence](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/longest_common_subsequence.py) +- [longest increasing subsequence](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/longest_increasing_subsequence.py) +- [longest increasing subsequence o(nlogn)]() +- [longest sub array](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/longest_sub_array.py) +- [matrix chain order](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/matrix_chain_order.py) +- [max sub array](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/max_sub_array.py) +- [minimum partition](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/minimum_partition.py) +- [rod cutting](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/rod_cutting.py) +- [subset generation](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/subset_generation.py) +- [sum of subset](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/sum_of_subset.py) + ## File Transfer - * [recieve file](https://github.com/TheAlgorithms/Python/blob/master/file_transfer/recieve_file.py) - * [send file](https://github.com/TheAlgorithms/Python/blob/master/file_transfer/send_file.py) + +- [recieve file](https://github.com/TheAlgorithms/Python/blob/master/file_transfer/recieve_file.py) +- [send file](https://github.com/TheAlgorithms/Python/blob/master/file_transfer/send_file.py) + ## Graphs - * [a star](https://github.com/TheAlgorithms/Python/blob/master/graphs/a_star.py) - * [articulation points](https://github.com/TheAlgorithms/Python/blob/master/graphs/articulation_points.py) - * [basic graphs](https://github.com/TheAlgorithms/Python/blob/master/graphs/basic_graphs.py) - * [bellman ford](https://github.com/TheAlgorithms/Python/blob/master/graphs/bellman_ford.py) - * [bfs](https://github.com/TheAlgorithms/Python/blob/master/graphs/bfs.py) - * [bfs shortest path](https://github.com/TheAlgorithms/Python/blob/master/graphs/bfs_shortest_path.py) - * [breadth first search](https://github.com/TheAlgorithms/Python/blob/master/graphs/breadth_first_search.py) - * [check bipartite graph bfs](https://github.com/TheAlgorithms/Python/blob/master/graphs/check_bipartite_graph_bfs.py) - * [check bipartite graph dfs](https://github.com/TheAlgorithms/Python/blob/master/graphs/check_bipartite_graph_dfs.py) - * [depth first search](https://github.com/TheAlgorithms/Python/blob/master/graphs/depth_first_search.py) - * [dfs](https://github.com/TheAlgorithms/Python/blob/master/graphs/dfs.py) - * [dijkstra](https://github.com/TheAlgorithms/Python/blob/master/graphs/dijkstra.py) - * [dijkstra 2](https://github.com/TheAlgorithms/Python/blob/master/graphs/dijkstra_2.py) - * [dijkstra algorithm](https://github.com/TheAlgorithms/Python/blob/master/graphs/dijkstra_algorithm.py) - * [directed and undirected (weighted) graph](https://github.com/TheAlgorithms/Python/blob/master/graphs/directed_and_undirected_(weighted)_graph.py) - * [edmonds karp multiple source and sink](https://github.com/TheAlgorithms/Python/blob/master/graphs/edmonds_karp_multiple_source_and_sink.py) - * [eulerian path and circuit for undirected graph](https://github.com/TheAlgorithms/Python/blob/master/graphs/eulerian_path_and_circuit_for_undirected_graph.py) - * [even tree](https://github.com/TheAlgorithms/Python/blob/master/graphs/even_tree.py) - * [finding bridges](https://github.com/TheAlgorithms/Python/blob/master/graphs/finding_bridges.py) - * [graph list](https://github.com/TheAlgorithms/Python/blob/master/graphs/graph_list.py) - * [graph matrix](https://github.com/TheAlgorithms/Python/blob/master/graphs/graph_matrix.py) - * [graphs floyd warshall](https://github.com/TheAlgorithms/Python/blob/master/graphs/graphs_floyd_warshall.py) - * [kahns algorithm long](https://github.com/TheAlgorithms/Python/blob/master/graphs/kahns_algorithm_long.py) - * [kahns algorithm topo](https://github.com/TheAlgorithms/Python/blob/master/graphs/kahns_algorithm_topo.py) - * [minimum spanning tree kruskal](https://github.com/TheAlgorithms/Python/blob/master/graphs/minimum_spanning_tree_kruskal.py) - * [minimum spanning tree prims](https://github.com/TheAlgorithms/Python/blob/master/graphs/minimum_spanning_tree_prims.py) - * [multi hueristic astar](https://github.com/TheAlgorithms/Python/blob/master/graphs/multi_hueristic_astar.py) - * [page rank](https://github.com/TheAlgorithms/Python/blob/master/graphs/page_rank.py) - * [prim](https://github.com/TheAlgorithms/Python/blob/master/graphs/prim.py) - * [scc kosaraju](https://github.com/TheAlgorithms/Python/blob/master/graphs/scc_kosaraju.py) - * [tarjans scc](https://github.com/TheAlgorithms/Python/blob/master/graphs/tarjans_scc.py) + +- [a star](https://github.com/TheAlgorithms/Python/blob/master/graphs/a_star.py) +- [articulation points](https://github.com/TheAlgorithms/Python/blob/master/graphs/articulation_points.py) +- [basic graphs](https://github.com/TheAlgorithms/Python/blob/master/graphs/basic_graphs.py) +- [bellman ford](https://github.com/TheAlgorithms/Python/blob/master/graphs/bellman_ford.py) +- [bfs](https://github.com/TheAlgorithms/Python/blob/master/graphs/bfs.py) +- [bfs shortest path](https://github.com/TheAlgorithms/Python/blob/master/graphs/bfs_shortest_path.py) +- [breadth first search](https://github.com/TheAlgorithms/Python/blob/master/graphs/breadth_first_search.py) +- [check bipartite graph bfs](https://github.com/TheAlgorithms/Python/blob/master/graphs/check_bipartite_graph_bfs.py) +- [check bipartite graph dfs](https://github.com/TheAlgorithms/Python/blob/master/graphs/check_bipartite_graph_dfs.py) +- [depth first search](https://github.com/TheAlgorithms/Python/blob/master/graphs/depth_first_search.py) +- [dfs](https://github.com/TheAlgorithms/Python/blob/master/graphs/dfs.py) +- [dijkstra](https://github.com/TheAlgorithms/Python/blob/master/graphs/dijkstra.py) +- [dijkstra 2](https://github.com/TheAlgorithms/Python/blob/master/graphs/dijkstra_2.py) +- [dijkstra algorithm](https://github.com/TheAlgorithms/Python/blob/master/graphs/dijkstra_algorithm.py) +- [directed and undirected (weighted) graph]() +- [edmonds karp multiple source and sink](https://github.com/TheAlgorithms/Python/blob/master/graphs/edmonds_karp_multiple_source_and_sink.py) +- [eulerian path and circuit for undirected graph](https://github.com/TheAlgorithms/Python/blob/master/graphs/eulerian_path_and_circuit_for_undirected_graph.py) +- [even tree](https://github.com/TheAlgorithms/Python/blob/master/graphs/even_tree.py) +- [finding bridges](https://github.com/TheAlgorithms/Python/blob/master/graphs/finding_bridges.py) +- [graph list](https://github.com/TheAlgorithms/Python/blob/master/graphs/graph_list.py) +- [graph matrix](https://github.com/TheAlgorithms/Python/blob/master/graphs/graph_matrix.py) +- [graphs floyd warshall](https://github.com/TheAlgorithms/Python/blob/master/graphs/graphs_floyd_warshall.py) +- [kahns algorithm long](https://github.com/TheAlgorithms/Python/blob/master/graphs/kahns_algorithm_long.py) +- [kahns algorithm topo](https://github.com/TheAlgorithms/Python/blob/master/graphs/kahns_algorithm_topo.py) +- [minimum spanning tree kruskal](https://github.com/TheAlgorithms/Python/blob/master/graphs/minimum_spanning_tree_kruskal.py) +- [minimum spanning tree prims](https://github.com/TheAlgorithms/Python/blob/master/graphs/minimum_spanning_tree_prims.py) +- [multi hueristic astar](https://github.com/TheAlgorithms/Python/blob/master/graphs/multi_hueristic_astar.py) +- [page rank](https://github.com/TheAlgorithms/Python/blob/master/graphs/page_rank.py) +- [prim](https://github.com/TheAlgorithms/Python/blob/master/graphs/prim.py) +- [scc kosaraju](https://github.com/TheAlgorithms/Python/blob/master/graphs/scc_kosaraju.py) +- [tarjans scc](https://github.com/TheAlgorithms/Python/blob/master/graphs/tarjans_scc.py) + ## Hashes - * [chaos machine](https://github.com/TheAlgorithms/Python/blob/master/hashes/chaos_machine.py) - * [enigma machine](https://github.com/TheAlgorithms/Python/blob/master/hashes/enigma_machine.py) - * [md5](https://github.com/TheAlgorithms/Python/blob/master/hashes/md5.py) - * [sha1](https://github.com/TheAlgorithms/Python/blob/master/hashes/sha1.py) + +- [chaos machine](https://github.com/TheAlgorithms/Python/blob/master/hashes/chaos_machine.py) +- [enigma machine](https://github.com/TheAlgorithms/Python/blob/master/hashes/enigma_machine.py) +- [md5](https://github.com/TheAlgorithms/Python/blob/master/hashes/md5.py) +- [sha1](https://github.com/TheAlgorithms/Python/blob/master/hashes/sha1.py) + ## Linear Algebra - * Src - * [lib](https://github.com/TheAlgorithms/Python/blob/master/linear_algebra/lib.py) - * [polynom-for-points](https://github.com/TheAlgorithms/Python/blob/master/linear_algebra/polynom-for-points.py) - * [tests](https://github.com/TheAlgorithms/Python/blob/master/linear_algebra/tests.py) + +- Src + - [lib](https://github.com/TheAlgorithms/Python/blob/master/linear_algebra/src/lib.py) + - [polynom-for-points](https://github.com/TheAlgorithms/Python/blob/master/linear_algebra/src/polynom-for-points.py) + - [tests](https://github.com/TheAlgorithms/Python/blob/master/linear_algebra/src/tests.py) + ## Machine Learning - * [decision tree](https://github.com/TheAlgorithms/Python/blob/master/machine_learning/decision_tree.py) - * [gradient descent](https://github.com/TheAlgorithms/Python/blob/master/machine_learning/gradient_descent.py) - * [k means clust](https://github.com/TheAlgorithms/Python/blob/master/machine_learning/k_means_clust.py) - * [knn sklearn](https://github.com/TheAlgorithms/Python/blob/master/machine_learning/knn_sklearn.py) - * [linear regression](https://github.com/TheAlgorithms/Python/blob/master/machine_learning/linear_regression.py) - * [logistic regression](https://github.com/TheAlgorithms/Python/blob/master/machine_learning/logistic_regression.py) - * [naive bayes](https://github.com/TheAlgorithms/Python/blob/master/machine_learning/naive_bayes.ipynb) - * Random Forest Classification - * [random forest classification](https://github.com/TheAlgorithms/Python/blob/master/machine_learning/random_forest_classification.py) - * [random forest classifier](https://github.com/TheAlgorithms/Python/blob/master/machine_learning/random_forest_classifier.ipynb) - * Random Forest Regression - * [random forest regression](https://github.com/TheAlgorithms/Python/blob/master/machine_learning/random_forest_regression.ipynb) - * [random forest regression](https://github.com/TheAlgorithms/Python/blob/master/machine_learning/random_forest_regression.py) - * [reuters one vs rest classifier](https://github.com/TheAlgorithms/Python/blob/master/machine_learning/reuters_one_vs_rest_classifier.ipynb) - * [scoring functions](https://github.com/TheAlgorithms/Python/blob/master/machine_learning/scoring_functions.py) - * [sorted vector machines](https://github.com/TheAlgorithms/Python/blob/master/machine_learning/sorted_vector_machines.py) + +- [decision tree](https://github.com/TheAlgorithms/Python/blob/master/machine_learning/decision_tree.py) +- [gradient descent](https://github.com/TheAlgorithms/Python/blob/master/machine_learning/gradient_descent.py) +- [k means clust](https://github.com/TheAlgorithms/Python/blob/master/machine_learning/k_means_clust.py) +- [knn sklearn](https://github.com/TheAlgorithms/Python/blob/master/machine_learning/knn_sklearn.py) +- [linear regression](https://github.com/TheAlgorithms/Python/blob/master/machine_learning/linear_regression.py) +- [logistic regression](https://github.com/TheAlgorithms/Python/blob/master/machine_learning/logistic_regression.py) +- [naive bayes](https://github.com/TheAlgorithms/Python/blob/master/machine_learning/naive_bayes.ipynb) +- Random Forest Classification + - [random forest classification](https://github.com/TheAlgorithms/Python/blob/master/machine_learning/random_forest_classification/random_forest_classification.py) + - [random forest classifier](https://github.com/TheAlgorithms/Python/blob/master/machine_learning/random_forest_classification/random_forest_classifier.ipynb) +- Random Forest Regression + - [random forest regression](https://github.com/TheAlgorithms/Python/blob/master/machine_learning/random_forest_regression/random_forest_regression.ipynb) + - [random forest regression](https://github.com/TheAlgorithms/Python/blob/master/machine_learning/random_forest_regression/random_forest_regression.py) +- [reuters one vs rest classifier](https://github.com/TheAlgorithms/Python/blob/master/machine_learning/reuters_one_vs_rest_classifier.ipynb) +- [scoring functions](https://github.com/TheAlgorithms/Python/blob/master/machine_learning/scoring_functions.py) +- [sorted vector machines](https://github.com/TheAlgorithms/Python/blob/master/machine_learning/sorted_vector_machines.py) + ## Maths - * [3n+1](https://github.com/TheAlgorithms/Python/blob/master/maths/3n+1.py) - * [abs](https://github.com/TheAlgorithms/Python/blob/master/maths/abs.py) - * [abs max](https://github.com/TheAlgorithms/Python/blob/master/maths/abs_max.py) - * [abs min](https://github.com/TheAlgorithms/Python/blob/master/maths/abs_min.py) - * [average mean](https://github.com/TheAlgorithms/Python/blob/master/maths/average_mean.py) - * [average median](https://github.com/TheAlgorithms/Python/blob/master/maths/average_median.py) - * [basic maths](https://github.com/TheAlgorithms/Python/blob/master/maths/basic_maths.py) - * [binary exponentiation](https://github.com/TheAlgorithms/Python/blob/master/maths/binary_exponentiation.py) - * [collatz sequence](https://github.com/TheAlgorithms/Python/blob/master/maths/collatz_sequence.py) - * [extended euclidean algorithm](https://github.com/TheAlgorithms/Python/blob/master/maths/extended_euclidean_algorithm.py) - * [factorial python](https://github.com/TheAlgorithms/Python/blob/master/maths/factorial_python.py) - * [factorial recursive](https://github.com/TheAlgorithms/Python/blob/master/maths/factorial_recursive.py) - * [fermat little theorem](https://github.com/TheAlgorithms/Python/blob/master/maths/fermat_little_theorem.py) - * [fibonacci](https://github.com/TheAlgorithms/Python/blob/master/maths/fibonacci.py) - * [fibonacci sequence recursion](https://github.com/TheAlgorithms/Python/blob/master/maths/fibonacci_sequence_recursion.py) - * [find lcm](https://github.com/TheAlgorithms/Python/blob/master/maths/find_lcm.py) - * [find max](https://github.com/TheAlgorithms/Python/blob/master/maths/find_max.py) - * [find min](https://github.com/TheAlgorithms/Python/blob/master/maths/find_min.py) - * [gaussian](https://github.com/TheAlgorithms/Python/blob/master/maths/gaussian.py) - * [greater common divisor](https://github.com/TheAlgorithms/Python/blob/master/maths/greater_common_divisor.py) - * [is square free](https://github.com/TheAlgorithms/Python/blob/master/maths/is_square_free.py) - * [largest of very large numbers](https://github.com/TheAlgorithms/Python/blob/master/maths/largest_of_very_large_numbers.py) - * [lucas lehmer primality test](https://github.com/TheAlgorithms/Python/blob/master/maths/lucas_lehmer_primality_test.py) - * [lucas series](https://github.com/TheAlgorithms/Python/blob/master/maths/lucas_series.py) - * [mobius function](https://github.com/TheAlgorithms/Python/blob/master/maths/mobius_function.py) - * [modular exponential](https://github.com/TheAlgorithms/Python/blob/master/maths/modular_exponential.py) - * [newton raphson](https://github.com/TheAlgorithms/Python/blob/master/maths/newton_raphson.py) - * [prime check](https://github.com/TheAlgorithms/Python/blob/master/maths/prime_check.py) - * [prime factors](https://github.com/TheAlgorithms/Python/blob/master/maths/prime_factors.py) - * [quadratic equations complex numbers](https://github.com/TheAlgorithms/Python/blob/master/maths/quadratic_equations_complex_numbers.py) - * [segmented sieve](https://github.com/TheAlgorithms/Python/blob/master/maths/segmented_sieve.py) - * [sieve of eratosthenes](https://github.com/TheAlgorithms/Python/blob/master/maths/sieve_of_eratosthenes.py) - * [simpson rule](https://github.com/TheAlgorithms/Python/blob/master/maths/simpson_rule.py) - * [test prime check](https://github.com/TheAlgorithms/Python/blob/master/maths/test_prime_check.py) - * [trapezoidal rule](https://github.com/TheAlgorithms/Python/blob/master/maths/trapezoidal_rule.py) - * [volume](https://github.com/TheAlgorithms/Python/blob/master/maths/volume.py) - * [zellers congruence](https://github.com/TheAlgorithms/Python/blob/master/maths/zellers_congruence.py) + +- [3n+1](https://github.com/TheAlgorithms/Python/blob/master/maths/3n+1.py) +- [abs](https://github.com/TheAlgorithms/Python/blob/master/maths/abs.py) +- [abs max](https://github.com/TheAlgorithms/Python/blob/master/maths/abs_max.py) +- [abs min](https://github.com/TheAlgorithms/Python/blob/master/maths/abs_min.py) +- [average mean](https://github.com/TheAlgorithms/Python/blob/master/maths/average_mean.py) +- [average median](https://github.com/TheAlgorithms/Python/blob/master/maths/average_median.py) +- [basic maths](https://github.com/TheAlgorithms/Python/blob/master/maths/basic_maths.py) +- [binary exponentiation](https://github.com/TheAlgorithms/Python/blob/master/maths/binary_exponentiation.py) +- [collatz sequence](https://github.com/TheAlgorithms/Python/blob/master/maths/collatz_sequence.py) +- [extended euclidean algorithm](https://github.com/TheAlgorithms/Python/blob/master/maths/extended_euclidean_algorithm.py) +- [factorial python](https://github.com/TheAlgorithms/Python/blob/master/maths/factorial_python.py) +- [factorial recursive](https://github.com/TheAlgorithms/Python/blob/master/maths/factorial_recursive.py) +- [fermat little theorem](https://github.com/TheAlgorithms/Python/blob/master/maths/fermat_little_theorem.py) +- [fibonacci](https://github.com/TheAlgorithms/Python/blob/master/maths/fibonacci.py) +- [fibonacci sequence recursion](https://github.com/TheAlgorithms/Python/blob/master/maths/fibonacci_sequence_recursion.py) +- [find lcm](https://github.com/TheAlgorithms/Python/blob/master/maths/find_lcm.py) +- [find max](https://github.com/TheAlgorithms/Python/blob/master/maths/find_max.py) +- [find min](https://github.com/TheAlgorithms/Python/blob/master/maths/find_min.py) +- [gaussian](https://github.com/TheAlgorithms/Python/blob/master/maths/gaussian.py) +- [greater common divisor](https://github.com/TheAlgorithms/Python/blob/master/maths/greater_common_divisor.py) +- [is square free](https://github.com/TheAlgorithms/Python/blob/master/maths/is_square_free.py) +- [largest of very large numbers](https://github.com/TheAlgorithms/Python/blob/master/maths/largest_of_very_large_numbers.py) +- [lucas lehmer primality test](https://github.com/TheAlgorithms/Python/blob/master/maths/lucas_lehmer_primality_test.py) +- [lucas series](https://github.com/TheAlgorithms/Python/blob/master/maths/lucas_series.py) +- [mobius function](https://github.com/TheAlgorithms/Python/blob/master/maths/mobius_function.py) +- [modular exponential](https://github.com/TheAlgorithms/Python/blob/master/maths/modular_exponential.py) +- [newton raphson](https://github.com/TheAlgorithms/Python/blob/master/maths/newton_raphson.py) +- [prime check](https://github.com/TheAlgorithms/Python/blob/master/maths/prime_check.py) +- [prime factors](https://github.com/TheAlgorithms/Python/blob/master/maths/prime_factors.py) +- [quadratic equations complex numbers](https://github.com/TheAlgorithms/Python/blob/master/maths/quadratic_equations_complex_numbers.py) +- [segmented sieve](https://github.com/TheAlgorithms/Python/blob/master/maths/segmented_sieve.py) +- [sieve of eratosthenes](https://github.com/TheAlgorithms/Python/blob/master/maths/sieve_of_eratosthenes.py) +- [simpson rule](https://github.com/TheAlgorithms/Python/blob/master/maths/simpson_rule.py) +- [test prime check](https://github.com/TheAlgorithms/Python/blob/master/maths/test_prime_check.py) +- [trapezoidal rule](https://github.com/TheAlgorithms/Python/blob/master/maths/trapezoidal_rule.py) +- [volume](https://github.com/TheAlgorithms/Python/blob/master/maths/volume.py) +- [zellers congruence](https://github.com/TheAlgorithms/Python/blob/master/maths/zellers_congruence.py) + ## Matrix - * [matrix operation](https://github.com/TheAlgorithms/Python/blob/master/matrix/matrix_operation.py) - * [nth fibonacci using matrix exponentiation](https://github.com/TheAlgorithms/Python/blob/master/matrix/nth_fibonacci_using_matrix_exponentiation.py) - * [rotate matrix](https://github.com/TheAlgorithms/Python/blob/master/matrix/rotate_matrix.py) - * [searching in sorted matrix](https://github.com/TheAlgorithms/Python/blob/master/matrix/searching_in_sorted_matrix.py) - * [spiral print](https://github.com/TheAlgorithms/Python/blob/master/matrix/spiral_print.py) - * Tests - * [test matrix operation](https://github.com/TheAlgorithms/Python/blob/master/matrix/test_matrix_operation.py) + +- [matrix operation](https://github.com/TheAlgorithms/Python/blob/master/matrix/matrix_operation.py) +- [nth fibonacci using matrix exponentiation](https://github.com/TheAlgorithms/Python/blob/master/matrix/nth_fibonacci_using_matrix_exponentiation.py) +- [rotate matrix](https://github.com/TheAlgorithms/Python/blob/master/matrix/rotate_matrix.py) +- [searching in sorted matrix](https://github.com/TheAlgorithms/Python/blob/master/matrix/searching_in_sorted_matrix.py) +- [spiral print](https://github.com/TheAlgorithms/Python/blob/master/matrix/spiral_print.py) +- Tests + - [test matrix operation](https://github.com/TheAlgorithms/Python/blob/master/matrix/tests/test_matrix_operation.py) + ## Networking Flow - * [ford fulkerson](https://github.com/TheAlgorithms/Python/blob/master/networking_flow/ford_fulkerson.py) - * [minimum cut](https://github.com/TheAlgorithms/Python/blob/master/networking_flow/minimum_cut.py) + +- [ford fulkerson](https://github.com/TheAlgorithms/Python/blob/master/networking_flow/ford_fulkerson.py) +- [minimum cut](https://github.com/TheAlgorithms/Python/blob/master/networking_flow/minimum_cut.py) + ## Neural Network - * [back propagation neural network](https://github.com/TheAlgorithms/Python/blob/master/neural_network/back_propagation_neural_network.py) - * [convolution neural network](https://github.com/TheAlgorithms/Python/blob/master/neural_network/convolution_neural_network.py) - * [fully connected neural network](https://github.com/TheAlgorithms/Python/blob/master/neural_network/fully_connected_neural_network.ipynb) - * [perceptron](https://github.com/TheAlgorithms/Python/blob/master/neural_network/perceptron.py) + +- [back propagation neural network](https://github.com/TheAlgorithms/Python/blob/master/neural_network/back_propagation_neural_network.py) +- [convolution neural network](https://github.com/TheAlgorithms/Python/blob/master/neural_network/convolution_neural_network.py) +- [fully connected neural network](https://github.com/TheAlgorithms/Python/blob/master/neural_network/fully_connected_neural_network.ipynb) +- [perceptron](https://github.com/TheAlgorithms/Python/blob/master/neural_network/perceptron.py) + ## Other - * [anagrams](https://github.com/TheAlgorithms/Python/blob/master/other/anagrams.py) - * [binary exponentiation](https://github.com/TheAlgorithms/Python/blob/master/other/binary_exponentiation.py) - * [binary exponentiation 2](https://github.com/TheAlgorithms/Python/blob/master/other/binary_exponentiation_2.py) - * [detecting english programmatically](https://github.com/TheAlgorithms/Python/blob/master/other/detecting_english_programmatically.py) - * [euclidean gcd](https://github.com/TheAlgorithms/Python/blob/master/other/euclidean_gcd.py) - * [fischer yates shuffle](https://github.com/TheAlgorithms/Python/blob/master/other/fischer_yates_shuffle.py) - * [food wastage analysis from 1961-2013 fao](https://github.com/TheAlgorithms/Python/blob/master/other/food_wastage_analysis_from_1961-2013_fao.ipynb) - * [frequency finder](https://github.com/TheAlgorithms/Python/blob/master/other/frequency_finder.py) - * [game of life](https://github.com/TheAlgorithms/Python/blob/master/other/game_of_life.py) - * [linear congruential generator](https://github.com/TheAlgorithms/Python/blob/master/other/linear_congruential_generator.py) - * [nested brackets](https://github.com/TheAlgorithms/Python/blob/master/other/nested_brackets.py) - * [palindrome](https://github.com/TheAlgorithms/Python/blob/master/other/palindrome.py) - * [password generator](https://github.com/TheAlgorithms/Python/blob/master/other/password_generator.py) - * [primelib](https://github.com/TheAlgorithms/Python/blob/master/other/primelib.py) - * [sierpinski triangle](https://github.com/TheAlgorithms/Python/blob/master/other/sierpinski_triangle.py) - * [tower of hanoi](https://github.com/TheAlgorithms/Python/blob/master/other/tower_of_hanoi.py) - * [two sum](https://github.com/TheAlgorithms/Python/blob/master/other/two_sum.py) - * [word patterns](https://github.com/TheAlgorithms/Python/blob/master/other/word_patterns.py) + +- [anagrams](https://github.com/TheAlgorithms/Python/blob/master/other/anagrams.py) +- [binary exponentiation](https://github.com/TheAlgorithms/Python/blob/master/other/binary_exponentiation.py) +- [binary exponentiation 2](https://github.com/TheAlgorithms/Python/blob/master/other/binary_exponentiation_2.py) +- [detecting english programmatically](https://github.com/TheAlgorithms/Python/blob/master/other/detecting_english_programmatically.py) +- [euclidean gcd](https://github.com/TheAlgorithms/Python/blob/master/other/euclidean_gcd.py) +- [fischer yates shuffle](https://github.com/TheAlgorithms/Python/blob/master/other/fischer_yates_shuffle.py) +- [food wastage analysis from 1961-2013 fao](https://github.com/TheAlgorithms/Python/blob/master/other/food_wastage_analysis_from_1961-2013_fao.ipynb) +- [frequency finder](https://github.com/TheAlgorithms/Python/blob/master/other/frequency_finder.py) +- [game of life](https://github.com/TheAlgorithms/Python/blob/master/other/game_of_life.py) +- [linear congruential generator](https://github.com/TheAlgorithms/Python/blob/master/other/linear_congruential_generator.py) +- [nested brackets](https://github.com/TheAlgorithms/Python/blob/master/other/nested_brackets.py) +- [palindrome](https://github.com/TheAlgorithms/Python/blob/master/other/palindrome.py) +- [password generator](https://github.com/TheAlgorithms/Python/blob/master/other/password_generator.py) +- [primelib](https://github.com/TheAlgorithms/Python/blob/master/other/primelib.py) +- [sierpinski triangle](https://github.com/TheAlgorithms/Python/blob/master/other/sierpinski_triangle.py) +- [tower of hanoi](https://github.com/TheAlgorithms/Python/blob/master/other/tower_of_hanoi.py) +- [two sum](https://github.com/TheAlgorithms/Python/blob/master/other/two_sum.py) +- [word patterns](https://github.com/TheAlgorithms/Python/blob/master/other/word_patterns.py) + ## Project Euler - * Problem 01 - * [sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/sol1.py) - * [sol2](https://github.com/TheAlgorithms/Python/blob/master/project_euler/sol2.py) - * [sol3](https://github.com/TheAlgorithms/Python/blob/master/project_euler/sol3.py) - * [sol4](https://github.com/TheAlgorithms/Python/blob/master/project_euler/sol4.py) - * [sol5](https://github.com/TheAlgorithms/Python/blob/master/project_euler/sol5.py) - * [sol6](https://github.com/TheAlgorithms/Python/blob/master/project_euler/sol6.py) - * Problem 02 - * [sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/sol1.py) - * [sol2](https://github.com/TheAlgorithms/Python/blob/master/project_euler/sol2.py) - * [sol3](https://github.com/TheAlgorithms/Python/blob/master/project_euler/sol3.py) - * [sol4](https://github.com/TheAlgorithms/Python/blob/master/project_euler/sol4.py) - * Problem 03 - * [sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/sol1.py) - * [sol2](https://github.com/TheAlgorithms/Python/blob/master/project_euler/sol2.py) - * Problem 04 - * [sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/sol1.py) - * [sol2](https://github.com/TheAlgorithms/Python/blob/master/project_euler/sol2.py) - * Problem 05 - * [sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/sol1.py) - * [sol2](https://github.com/TheAlgorithms/Python/blob/master/project_euler/sol2.py) - * Problem 06 - * [sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/sol1.py) - * [sol2](https://github.com/TheAlgorithms/Python/blob/master/project_euler/sol2.py) - * [sol3](https://github.com/TheAlgorithms/Python/blob/master/project_euler/sol3.py) - * Problem 07 - * [sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/sol1.py) - * [sol2](https://github.com/TheAlgorithms/Python/blob/master/project_euler/sol2.py) - * [sol3](https://github.com/TheAlgorithms/Python/blob/master/project_euler/sol3.py) - * Problem 08 - * [sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/sol1.py) - * [sol2](https://github.com/TheAlgorithms/Python/blob/master/project_euler/sol2.py) - * Problem 09 - * [sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/sol1.py) - * [sol2](https://github.com/TheAlgorithms/Python/blob/master/project_euler/sol2.py) - * [sol3](https://github.com/TheAlgorithms/Python/blob/master/project_euler/sol3.py) - * Problem 10 - * [sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/sol1.py) - * [sol2](https://github.com/TheAlgorithms/Python/blob/master/project_euler/sol2.py) - * [sol3](https://github.com/TheAlgorithms/Python/blob/master/project_euler/sol3.py) - * Problem 11 - * [sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/sol1.py) - * [sol2](https://github.com/TheAlgorithms/Python/blob/master/project_euler/sol2.py) - * Problem 12 - * [sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/sol1.py) - * [sol2](https://github.com/TheAlgorithms/Python/blob/master/project_euler/sol2.py) - * Problem 13 - * [sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/sol1.py) - * Problem 14 - * [sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/sol1.py) - * [sol2](https://github.com/TheAlgorithms/Python/blob/master/project_euler/sol2.py) - * Problem 15 - * [sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/sol1.py) - * Problem 16 - * [sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/sol1.py) - * [sol2](https://github.com/TheAlgorithms/Python/blob/master/project_euler/sol2.py) - * Problem 17 - * [sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/sol1.py) - * Problem 18 - * [sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/sol1.py) - * Problem 19 - * [sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/sol1.py) - * Problem 20 - * [sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/sol1.py) - * [sol2](https://github.com/TheAlgorithms/Python/blob/master/project_euler/sol2.py) - * Problem 21 - * [sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/sol1.py) - * Problem 22 - * [sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/sol1.py) - * [sol2](https://github.com/TheAlgorithms/Python/blob/master/project_euler/sol2.py) - * Problem 234 - * [sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/sol1.py) - * Problem 24 - * [sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/sol1.py) - * Problem 25 - * [sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/sol1.py) - * [sol2](https://github.com/TheAlgorithms/Python/blob/master/project_euler/sol2.py) - * Problem 28 - * [sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/sol1.py) - * Problem 29 - * [solution](https://github.com/TheAlgorithms/Python/blob/master/project_euler/solution.py) - * Problem 31 - * [sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/sol1.py) - * Problem 36 - * [sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/sol1.py) - * Problem 40 - * [sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/sol1.py) - * Problem 48 - * [sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/sol1.py) - * Problem 52 - * [sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/sol1.py) - * Problem 53 - * [sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/sol1.py) - * Problem 56 - * [sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/sol1.py) - * Problem 76 - * [sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/sol1.py) + +- Problem 01 + - [sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_01/sol1.py) + - [sol2](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_01/sol2.py) + - [sol3](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_01/sol3.py) + - [sol4](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_01/sol4.py) + - [sol5](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_01/sol5.py) + - [sol6](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_01/sol6.py) +- Problem 02 + - [sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_02/sol1.py) + - [sol2](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_02/sol2.py) + - [sol3](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_02/sol3.py) + - [sol4](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_02/sol4.py) +- Problem 03 + - [sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_03/sol1.py) + - [sol2](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_03/sol2.py) +- Problem 04 + - [sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_04/sol1.py) + - [sol2](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_04/sol2.py) +- Problem 05 + - [sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_05/sol1.py) + - [sol2](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_05/sol2.py) +- Problem 06 + - [sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_06/sol1.py) + - [sol2](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_06/sol2.py) + - [sol3](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_06/sol3.py) +- Problem 07 + - [sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_07/sol1.py) + - [sol2](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_07/sol2.py) + - [sol3](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_07/sol3.py) +- Problem 08 + - [sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_08/sol1.py) + - [sol2](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_08/sol2.py) +- Problem 09 + - [sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_09/sol1.py) + - [sol2](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_09/sol2.py) + - [sol3](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_09/sol3.py) +- Problem 10 + - [sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_10/sol1.py) + - [sol2](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_10/sol2.py) + - [sol3](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_10/sol3.py) +- Problem 11 + - [sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_11/sol1.py) + - [sol2](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_11/sol2.py) +- Problem 12 + - [sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_12/sol1.py) + - [sol2](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_12/sol2.py) +- Problem 13 + - [sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_13/sol1.py) +- Problem 14 + - [sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_14/sol1.py) + - [sol2](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_14/sol2.py) +- Problem 15 + - [sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_15/sol1.py) +- Problem 16 + - [sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_16/sol1.py) + - [sol2](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_16/sol2.py) +- Problem 17 + - [sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_17/sol1.py) +- Problem 18 + - [sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_18/sol1.py) +- Problem 19 + - [sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_19/sol1.py) +- Problem 20 + - [sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_20/sol1.py) + - [sol2](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_20/sol2.py) +- Problem 21 + - [sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_21/sol1.py) +- Problem 22 + - [sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_22/sol1.py) + - [sol2](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_22/sol2.py) +- Problem 234 + - [sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_234/sol1.py) +- Problem 24 + - [sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_24/sol1.py) +- Problem 25 + - [sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_25/sol1.py) + - [sol2](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_25/sol2.py) +- Problem 28 + - [sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_28/sol1.py) +- Problem 29 + - [solution](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_28/solution.py) +- Problem 31 + - [sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_31/sol1.py) +- Problem 36 + - [sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_36/sol1.py) +- Problem 40 + - [sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_40/sol1.py) +- Problem 48 + - [sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_48/sol1.py) +- Problem 52 + - [sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_52/sol1.py) +- Problem 53 + - [sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_53/sol1.py) +- Problem 56 + - [sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_56/sol1.py) +- Problem 76 + - [sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_76/sol1.py) + ## Searches - * [binary search](https://github.com/TheAlgorithms/Python/blob/master/searches/binary_search.py) - * [interpolation search](https://github.com/TheAlgorithms/Python/blob/master/searches/interpolation_search.py) - * [jump search](https://github.com/TheAlgorithms/Python/blob/master/searches/jump_search.py) - * [linear search](https://github.com/TheAlgorithms/Python/blob/master/searches/linear_search.py) - * [quick select](https://github.com/TheAlgorithms/Python/blob/master/searches/quick_select.py) - * [sentinel linear search](https://github.com/TheAlgorithms/Python/blob/master/searches/sentinel_linear_search.py) - * [tabu search](https://github.com/TheAlgorithms/Python/blob/master/searches/tabu_search.py) - * [ternary search](https://github.com/TheAlgorithms/Python/blob/master/searches/ternary_search.py) + +- [binary search](https://github.com/TheAlgorithms/Python/blob/master/searches/binary_search.py) +- [interpolation search](https://github.com/TheAlgorithms/Python/blob/master/searches/interpolation_search.py) +- [jump search](https://github.com/TheAlgorithms/Python/blob/master/searches/jump_search.py) +- [linear search](https://github.com/TheAlgorithms/Python/blob/master/searches/linear_search.py) +- [quick select](https://github.com/TheAlgorithms/Python/blob/master/searches/quick_select.py) +- [sentinel linear search](https://github.com/TheAlgorithms/Python/blob/master/searches/sentinel_linear_search.py) +- [tabu search](https://github.com/TheAlgorithms/Python/blob/master/searches/tabu_search.py) +- [ternary search](https://github.com/TheAlgorithms/Python/blob/master/searches/ternary_search.py) + ## Sorts - * [bitonic sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/bitonic_sort.py) - * [bogo sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/bogo_sort.py) - * [bubble sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/bubble_sort.py) - * [bucket sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/bucket_sort.py) - * [cocktail shaker sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/cocktail_shaker_sort.py) - * [comb sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/comb_sort.py) - * [counting sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/counting_sort.py) - * [cycle sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/cycle_sort.py) - * [external sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/external_sort.py) - * [gnome sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/gnome_sort.py) - * [heap sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/heap_sort.py) - * [insertion sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/insertion_sort.py) - * [merge sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/merge_sort.py) - * [merge sort fastest](https://github.com/TheAlgorithms/Python/blob/master/sorts/merge_sort_fastest.py) - * [odd even transposition parallel](https://github.com/TheAlgorithms/Python/blob/master/sorts/odd_even_transposition_parallel.py) - * [odd even transposition single threaded](https://github.com/TheAlgorithms/Python/blob/master/sorts/odd_even_transposition_single_threaded.py) - * [pancake sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/pancake_sort.py) - * [pigeon sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/pigeon_sort.py) - * [quick sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/quick_sort.py) - * [quick sort 3 partition](https://github.com/TheAlgorithms/Python/blob/master/sorts/quick_sort_3_partition.py) - * [radix sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/radix_sort.py) - * [random normal distribution quicksort](https://github.com/TheAlgorithms/Python/blob/master/sorts/random_normal_distribution_quicksort.py) - * [random pivot quick sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/random_pivot_quick_sort.py) - * [selection sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/selection_sort.py) - * [shell sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/shell_sort.py) - * [tim sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/tim_sort.py) - * [topological sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/topological_sort.py) - * [tree sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/tree_sort.py) - * [wiggle sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/wiggle_sort.py) + +- [bitonic sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/bitonic_sort.py) +- [bogo sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/bogo_sort.py) +- [bubble sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/bubble_sort.py) +- [bucket sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/bucket_sort.py) +- [cocktail shaker sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/cocktail_shaker_sort.py) +- [comb sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/comb_sort.py) +- [counting sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/counting_sort.py) +- [cycle sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/cycle_sort.py) +- [external sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/external_sort.py) +- [gnome sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/gnome_sort.py) +- [heap sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/heap_sort.py) +- [insertion sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/insertion_sort.py) +- [merge sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/merge_sort.py) +- [merge sort fastest](https://github.com/TheAlgorithms/Python/blob/master/sorts/merge_sort_fastest.py) +- [odd even transposition parallel](https://github.com/TheAlgorithms/Python/blob/master/sorts/odd_even_transposition_parallel.py) +- [odd even transposition single threaded](https://github.com/TheAlgorithms/Python/blob/master/sorts/odd_even_transposition_single_threaded.py) +- [pancake sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/pancake_sort.py) +- [pigeon sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/pigeon_sort.py) +- [quick sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/quick_sort.py) +- [quick sort 3 partition](https://github.com/TheAlgorithms/Python/blob/master/sorts/quick_sort_3_partition.py) +- [radix sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/radix_sort.py) +- [random normal distribution quicksort](https://github.com/TheAlgorithms/Python/blob/master/sorts/random_normal_distribution_quicksort.py) +- [random pivot quick sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/random_pivot_quick_sort.py) +- [selection sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/selection_sort.py) +- [shell sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/shell_sort.py) +- [tim sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/tim_sort.py) +- [topological sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/topological_sort.py) +- [tree sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/tree_sort.py) +- [wiggle sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/wiggle_sort.py) + ## Strings - * [boyer moore search](https://github.com/TheAlgorithms/Python/blob/master/strings/boyer_moore_search.py) - * [knuth morris pratt](https://github.com/TheAlgorithms/Python/blob/master/strings/knuth_morris_pratt.py) - * [levenshtein distance](https://github.com/TheAlgorithms/Python/blob/master/strings/levenshtein_distance.py) - * [manacher](https://github.com/TheAlgorithms/Python/blob/master/strings/manacher.py) - * [min cost string conversion](https://github.com/TheAlgorithms/Python/blob/master/strings/min_cost_string_conversion.py) - * [naive string search](https://github.com/TheAlgorithms/Python/blob/master/strings/naive_string_search.py) - * [rabin karp](https://github.com/TheAlgorithms/Python/blob/master/strings/rabin_karp.py) + +- [boyer moore search](https://github.com/TheAlgorithms/Python/blob/master/strings/boyer_moore_search.py) +- [knuth morris pratt](https://github.com/TheAlgorithms/Python/blob/master/strings/knuth_morris_pratt.py) +- [levenshtein distance](https://github.com/TheAlgorithms/Python/blob/master/strings/levenshtein_distance.py) +- [manacher](https://github.com/TheAlgorithms/Python/blob/master/strings/manacher.py) +- [min cost string conversion](https://github.com/TheAlgorithms/Python/blob/master/strings/min_cost_string_conversion.py) +- [naive string search](https://github.com/TheAlgorithms/Python/blob/master/strings/naive_string_search.py) +- [rabin karp](https://github.com/TheAlgorithms/Python/blob/master/strings/rabin_karp.py) + ## Traversals - * [binary tree traversals](https://github.com/TheAlgorithms/Python/blob/master/traversals/binary_tree_traversals.py) +- [binary tree traversals](https://github.com/TheAlgorithms/Python/blob/master/traversals/binary_tree_traversals.py) From 22bd6ff967fd8a983490e241485f950c0a82380c Mon Sep 17 00:00:00 2001 From: Maram Sumanth Date: Mon, 7 Oct 2019 23:56:40 +0530 Subject: [PATCH 0243/1071] Update average_mean.py (#1293) --- maths/average_mean.py | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/maths/average_mean.py b/maths/average_mean.py index e04b63be0e19..77464ef5d9f7 100644 --- a/maths/average_mean.py +++ b/maths/average_mean.py @@ -3,17 +3,13 @@ def average(nums): """Find mean of a list of numbers.""" - sum = 0 - for x in nums: - sum += x - avg = sum / len(nums) - print(avg) + avg = sum(nums) / len(nums) return avg def main(): """Call average module to find mean of a specific list of numbers.""" - average([2, 4, 6, 8, 20, 50, 70]) + print(average([2, 4, 6, 8, 20, 50, 70])) if __name__ == "__main__": From 06d736199b058ab80d747da242085d48a6803096 Mon Sep 17 00:00:00 2001 From: Nishant-Ingle <30694286+Nishant-Ingle@users.noreply.github.com> Date: Mon, 7 Oct 2019 23:59:14 +0530 Subject: [PATCH 0244/1071] Added comment (#1294) --- data_structures/binary_tree/binary_search_tree.py | 1 + 1 file changed, 1 insertion(+) diff --git a/data_structures/binary_tree/binary_search_tree.py b/data_structures/binary_tree/binary_search_tree.py index c6e037880bb6..1e6c17112e81 100644 --- a/data_structures/binary_tree/binary_search_tree.py +++ b/data_structures/binary_tree/binary_search_tree.py @@ -40,6 +40,7 @@ class BinarySearchTree: def __init__(self): self.root = None + # Insert a new node in Binary Search Tree with value label def insert(self, label): # Create a new Node new_node = Node(label, None) From 3a06aba66a0a4265077979c6ad9aad923deb3023 Mon Sep 17 00:00:00 2001 From: Craigory V Coppola Date: Mon, 7 Oct 2019 14:32:16 -0400 Subject: [PATCH 0245/1071] Update Linear Algebra Readme (#1298) Update formatting to better indicate matrix and vector sections --- linear_algebra/README.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/linear_algebra/README.md b/linear_algebra/README.md index 1e34d0bd7805..f1b554e139de 100644 --- a/linear_algebra/README.md +++ b/linear_algebra/README.md @@ -6,7 +6,8 @@ This module contains some useful classes and functions for dealing with linear a ## Overview -- class Vector +### class Vector +- - This class represents a vector of arbitray size and operations on it. **Overview about the methods:** @@ -32,7 +33,8 @@ This module contains some useful classes and functions for dealing with linear a - function randomVector(N,a,b) - returns a random vector of size N, with random integer components between 'a' and 'b'. -- class Matrix +### class Matrix +- - This class represents a matrix of arbitrary size and operations on it. **Overview about the methods:** From 25701a98773b30eb70e078d9a04f246989d11d64 Mon Sep 17 00:00:00 2001 From: Kaushik Amar Das Date: Tue, 8 Oct 2019 13:42:27 +0530 Subject: [PATCH 0246/1071] added doctests to scoring_functions.py (#1300) * added doctests to scoring_functions.py * dedented lines --- machine_learning/scoring_functions.py | 60 +++++++++++++++++++++++++-- 1 file changed, 57 insertions(+), 3 deletions(-) diff --git a/machine_learning/scoring_functions.py b/machine_learning/scoring_functions.py index 2b24287b3726..5c84f7026e74 100755 --- a/machine_learning/scoring_functions.py +++ b/machine_learning/scoring_functions.py @@ -16,6 +16,16 @@ # Mean Absolute Error def mae(predict, actual): + """ + Examples(rounded for precision): + >>> actual = [1,2,3];predict = [1,4,3] + >>> np.around(mae(predict,actual),decimals = 2) + 0.67 + + >>> actual = [1,1,1];predict = [1,1,1] + >>> mae(predict,actual) + 0.0 + """ predict = np.array(predict) actual = np.array(actual) @@ -27,6 +37,16 @@ def mae(predict, actual): # Mean Squared Error def mse(predict, actual): + """ + Examples(rounded for precision): + >>> actual = [1,2,3];predict = [1,4,3] + >>> np.around(mse(predict,actual),decimals = 2) + 1.33 + + >>> actual = [1,1,1];predict = [1,1,1] + >>> mse(predict,actual) + 0.0 + """ predict = np.array(predict) actual = np.array(actual) @@ -39,6 +59,16 @@ def mse(predict, actual): # Root Mean Squared Error def rmse(predict, actual): + """ + Examples(rounded for precision): + >>> actual = [1,2,3];predict = [1,4,3] + >>> np.around(rmse(predict,actual),decimals = 2) + 1.15 + + >>> actual = [1,1,1];predict = [1,1,1] + >>> rmse(predict,actual) + 0.0 + """ predict = np.array(predict) actual = np.array(actual) @@ -51,6 +81,16 @@ def rmse(predict, actual): # Root Mean Square Logarithmic Error def rmsle(predict, actual): + """ + Examples(rounded for precision): + >>> actual = [10,10,30];predict = [10,2,30] + >>> np.around(rmsle(predict,actual),decimals = 2) + 0.75 + + >>> actual = [1,1,1];predict = [1,1,1] + >>> rmsle(predict,actual) + 0.0 + """ predict = np.array(predict) actual = np.array(actual) @@ -68,15 +108,29 @@ def rmsle(predict, actual): # Mean Bias Deviation def mbd(predict, actual): + """ + This value is Negative, if the model underpredicts, + positive, if it overpredicts. + + Example(rounded for precision): + + Here the model overpredicts + >>> actual = [1,2,3];predict = [2,3,4] + >>> np.around(mbd(predict,actual),decimals = 2) + 50.0 + + Here the model underpredicts + >>> actual = [1,2,3];predict = [0,1,1] + >>> np.around(mbd(predict,actual),decimals = 2) + -66.67 + """ predict = np.array(predict) actual = np.array(actual) difference = predict - actual numerator = np.sum(difference) / len(predict) denumerator = np.sum(actual) / len(predict) - print(numerator) - print(denumerator) - + # print(numerator, denumerator) score = float(numerator) / denumerator * 100 return score From 0da4d0a7f35c825447aa4666bb073a3ad118f319 Mon Sep 17 00:00:00 2001 From: Du YuanChao Date: Tue, 8 Oct 2019 16:22:40 +0800 Subject: [PATCH 0247/1071] make code more readable (#1304) --- searches/binary_search.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/searches/binary_search.py b/searches/binary_search.py index 9237c0e1f6f5..76a50560e943 100644 --- a/searches/binary_search.py +++ b/searches/binary_search.py @@ -43,11 +43,10 @@ def binary_search(sorted_collection, item): current_item = sorted_collection[midpoint] if current_item == item: return midpoint + elif item < current_item: + right = midpoint - 1 else: - if item < current_item: - right = midpoint - 1 - else: - left = midpoint + 1 + left = midpoint + 1 return None From f0568d642ee6bcebbba330452e09e76d3c73e150 Mon Sep 17 00:00:00 2001 From: Du YuanChao Date: Tue, 8 Oct 2019 16:24:01 +0800 Subject: [PATCH 0248/1071] less code (#1292) --- maths/abs.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/maths/abs.py b/maths/abs.py index 4d15ee6e82a8..7509c5c20a22 100644 --- a/maths/abs.py +++ b/maths/abs.py @@ -10,11 +10,7 @@ def abs_val(num): >>abs_val(0) 0 """ - if num < 0: - return -num - - # Returns if number is not < 0 - return num + return -num if num < 0 else num def main(): From e80d248e65d20227d23098d354ca82f111d79967 Mon Sep 17 00:00:00 2001 From: Du YuanChao Date: Tue, 8 Oct 2019 16:25:00 +0800 Subject: [PATCH 0249/1071] optimization (#1303) --- sorts/selection_sort.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/sorts/selection_sort.py b/sorts/selection_sort.py index 43ad26a7bf27..6a9c063d3364 100644 --- a/sorts/selection_sort.py +++ b/sorts/selection_sort.py @@ -35,7 +35,8 @@ def selection_sort(collection): for k in range(i + 1, length): if collection[k] < collection[least]: least = k - collection[least], collection[i] = (collection[i], collection[least]) + if least != i: + collection[least], collection[i] = (collection[i], collection[least]) return collection From 61f7f94fde8dff80740ee5ef19b2998f192da8d2 Mon Sep 17 00:00:00 2001 From: Rishabh Kumar Date: Tue, 8 Oct 2019 17:55:50 +0530 Subject: [PATCH 0250/1071] Create karatsuba.py (#1309) * Create karatsuba.py Added karatsuba algorithm for multiplication of two numbers * Update karatsuba.py Added doctests and divmod * Update karatsuba.py --- maths/karatsuba.py | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 maths/karatsuba.py diff --git a/maths/karatsuba.py b/maths/karatsuba.py new file mode 100644 index 000000000000..be4630184933 --- /dev/null +++ b/maths/karatsuba.py @@ -0,0 +1,31 @@ +""" Multiply two numbers using Karatsuba algorithm """ + +def karatsuba(a, b): + """ + >>> karatsuba(15463, 23489) == 15463 * 23489 + True + >>> karatsuba(3, 9) == 3 * 9 + True + """ + if len(str(a)) == 1 or len(str(b)) == 1: + return (a * b) + else: + m1 = max(len(str(a)), len(str(b))) + m2 = m1 // 2 + + a1, a2 = divmod(a, 10**m2) + b1, b2 = divmod(b, 10**m2) + + x = karatsuba(a2, b2) + y = karatsuba((a1 + a2), (b1 + b2)) + z = karatsuba(a1, b1) + + return ((z * 10**(2*m2)) + ((y - z - x) * 10**(m2)) + (x)) + + +def main(): + print(karatsuba(15463, 23489)) + + +if __name__ == "__main__": + main() From b6cc37d461da2a631fe077d0cf952c5981d2dfdd Mon Sep 17 00:00:00 2001 From: Jigyasa G Date: Thu, 10 Oct 2019 00:42:09 +0530 Subject: [PATCH 0251/1071] mergesort added (#1313) * mergesort added * added doctest --- divide_and_conquer/mergesort.py | 45 +++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100644 divide_and_conquer/mergesort.py diff --git a/divide_and_conquer/mergesort.py b/divide_and_conquer/mergesort.py new file mode 100644 index 000000000000..b2a5a4c321ae --- /dev/null +++ b/divide_and_conquer/mergesort.py @@ -0,0 +1,45 @@ +def merge(a,b,m,e): + l=a[b:m+1] + r=a[m+1:e+1] + k=b + i=0 + j=0 + while i>> mergesort([3,2,1],0,2) + [1, 2, 3] + >>> mergesort([3,2,1,0,1,2,3,5,4],0,8) + [0, 1, 1, 2, 2, 3, 3, 4, 5] + """ + if b Date: Thu, 10 Oct 2019 00:50:19 +0530 Subject: [PATCH 0252/1071] Adding missing Doctests (#1330) * Adding doctests in abbreviation * Adding doctests in fibonacci.py --- dynamic_programming/abbreviation.py | 12 ++++++++++-- dynamic_programming/fibonacci.py | 16 ++++++++++++++++ 2 files changed, 26 insertions(+), 2 deletions(-) diff --git a/dynamic_programming/abbreviation.py b/dynamic_programming/abbreviation.py index f4d07e402925..a2aec35af77b 100644 --- a/dynamic_programming/abbreviation.py +++ b/dynamic_programming/abbreviation.py @@ -11,8 +11,13 @@ daBcd -> capitalize a and c(dABCd) -> remove d (ABC) """ - def abbr(a, b): + """ + >>> abbr("daBcd", "ABC") + True + >>> abbr("dBcd", "ABC") + False + """ n = len(a) m = len(b) dp = [[False for _ in range(m + 1)] for _ in range(n + 1)] @@ -28,4 +33,7 @@ def abbr(a, b): if __name__ == "__main__": - print(abbr("daBcd", "ABC")) # expect True + # print(abbr("daBcd", "ABC")) # expect True + import doctest + + doctest.testmod() diff --git a/dynamic_programming/fibonacci.py b/dynamic_programming/fibonacci.py index 2dd1c2555f3e..125686416603 100644 --- a/dynamic_programming/fibonacci.py +++ b/dynamic_programming/fibonacci.py @@ -14,8 +14,20 @@ def __init__(self, N=None): self.fib_array.append(self.fib_array[i - 1] + self.fib_array[i - 2]) elif N == 0: self.fib_array.append(0) + print(self.fib_array) def get(self, sequence_no=None): + """ + >>> Fibonacci(5).get(3) + [0, 1, 1, 2, 3, 5] + [0, 1, 1, 2] + >>> Fibonacci(5).get(6) + [0, 1, 1, 2, 3, 5] + Out of bound. + >>> Fibonacci(5).get(-1) + [0, 1, 1, 2, 3, 5] + [] + """ if sequence_no != None: if sequence_no < len(self.fib_array): return print(self.fib_array[: sequence_no + 1]) @@ -46,3 +58,7 @@ def get(self, sequence_no=None): print("\nInvalid input, please try again.") except NameError: print("\n********* Invalid input, good bye!! ************\n") + + import doctest + + doctest.testmod() From e67887989232bb64533995cc1044e13d739b02ef Mon Sep 17 00:00:00 2001 From: Jigyasa G Date: Fri, 11 Oct 2019 23:59:50 +0530 Subject: [PATCH 0253/1071] Adding doctests for sum_of_subset.py (#1333) --- dynamic_programming/sum_of_subset.py | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/dynamic_programming/sum_of_subset.py b/dynamic_programming/sum_of_subset.py index 581039080101..5c7944d5090e 100644 --- a/dynamic_programming/sum_of_subset.py +++ b/dynamic_programming/sum_of_subset.py @@ -1,5 +1,10 @@ def isSumSubset(arr, arrLen, requiredSum): - + """ + >>> isSumSubset([2, 4, 6, 8], 4, 5) + False + >>> isSumSubset([2, 4, 6, 8], 4, 14) + True + """ # a subset value says 1 if that subset sum can be formed else 0 # initially no subsets can be formed hence False/0 subset = [[False for i in range(requiredSum + 1)] for i in range(arrLen + 1)] @@ -22,14 +27,9 @@ def isSumSubset(arr, arrLen, requiredSum): # uncomment to print the subset # for i in range(arrLen+1): # print(subset[i]) + print(subset[arrLen][requiredSum]) - return subset[arrLen][requiredSum] - +if __name__ == "__main__": + import doctest -arr = [2, 4, 6, 8] -requiredSum = 5 -arrLen = len(arr) -if isSumSubset(arr, arrLen, requiredSum): - print("Found a subset with required sum") -else: - print("No subset with required sum") + doctest.testmod() From 67291a5bce1f5761ce2d6978193ec4a3b1f6dcc6 Mon Sep 17 00:00:00 2001 From: Jigyasa G Date: Sat, 12 Oct 2019 00:02:41 +0530 Subject: [PATCH 0254/1071] Modified longest_common_ssubsequence.py for successful doctests (#1332) --- dynamic_programming/longest_common_subsequence.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/dynamic_programming/longest_common_subsequence.py b/dynamic_programming/longest_common_subsequence.py index 12fcae684051..4bb1db044d3b 100644 --- a/dynamic_programming/longest_common_subsequence.py +++ b/dynamic_programming/longest_common_subsequence.py @@ -76,6 +76,7 @@ def longest_common_subsequence(x: str, y: str): expected_subseq = "GTAB" ln, subseq = longest_common_subsequence(a, b) - assert expected_ln == ln - assert expected_subseq == subseq - print("len =", ln, ", sub-sequence =", subseq) +## print("len =", ln, ", sub-sequence =", subseq) + import doctest + + doctest.testmod() From b190c8f629d1fb8371fb9ea434d986a843b2ecab Mon Sep 17 00:00:00 2001 From: Aliabbas Merchant Date: Tue, 15 Oct 2019 00:05:51 +0530 Subject: [PATCH 0255/1071] Rename GCD File (#1354) --- ...greater_common_divisor.py => greatest_common_divisor.py} | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) rename maths/{greater_common_divisor.py => greatest_common_divisor.py} (76%) diff --git a/maths/greater_common_divisor.py b/maths/greatest_common_divisor.py similarity index 76% rename from maths/greater_common_divisor.py rename to maths/greatest_common_divisor.py index ec608488a61f..ebc08f37ffa6 100644 --- a/maths/greater_common_divisor.py +++ b/maths/greatest_common_divisor.py @@ -1,12 +1,12 @@ """ -Greater Common Divisor. +Greatest Common Divisor. Wikipedia reference: https://en.wikipedia.org/wiki/Greatest_common_divisor """ def gcd(a, b): - """Calculate Greater Common Divisor (GCD).""" + """Calculate Greatest Common Divisor (GCD).""" return b if a == 0 else gcd(b % a, a) @@ -16,9 +16,9 @@ def main(): nums = input("Enter two Integers separated by comma (,): ").split(",") num_1 = int(nums[0]) num_2 = int(nums[1]) + print(f"gcd({num_1}, {num_2}) = {gcd(num_1, num_2)}") except (IndexError, UnboundLocalError, ValueError): print("Wrong Input") - print(f"gcd({num_1}, {num_2}) = {gcd(num_1, num_2)}") if __name__ == "__main__": From dcee2eac68e0c1b4061c94c69e920ed2d42f42f2 Mon Sep 17 00:00:00 2001 From: Andrii Siriak Date: Thu, 17 Oct 2019 17:02:40 +0300 Subject: [PATCH 0256/1071] Update build badge (#1378) * Update build badge * Update README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index a5af46ad8505..8ccb789be7e9 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ -# The Algorithms - Python +# The Algorithms - Python [![Donate](https://img.shields.io/badge/Donate-PayPal-green.svg?logo=paypal&style=flat-square)](https://www.paypal.me/TheAlgorithms/100)  -[![Build Status](https://img.shields.io/travis/TheAlgorithms/Python.svg?label=Travis%20CI&logo=travis&style=flat-square)](https://travis-ci.org/TheAlgorithms/Python)  +[![Build Status](https://img.shields.io/travis/TheAlgorithms/Python.svg?label=Travis%20CI&logo=travis&style=flat-square)](https://travis-ci.com/TheAlgorithms/Python)  [![LGTM](https://img.shields.io/lgtm/alerts/github/TheAlgorithms/Python.svg?label=LGTM&logo=LGTM&style=flat-square)](https://lgtm.com/projects/g/TheAlgorithms/Python/alerts)  [![Gitter chat](https://img.shields.io/badge/Chat-Gitter-ff69b4.svg?label=Chat&logo=gitter&style=flat-square)](https://gitter.im/TheAlgorithms)  [![contributions welcome](https://img.shields.io/static/v1.svg?label=Contributions&message=Welcome&color=0059b3&style=flat-square)](https://github.com/TheAlgorithms/Python/blob/master/CONTRIBUTING.md)  From 927a8c7722a141b19a86fdb2f6fb3ae7dcb94c9e Mon Sep 17 00:00:00 2001 From: Stephen <24819660+infrontoftheforest@users.noreply.github.com> Date: Thu, 17 Oct 2019 14:50:51 +0000 Subject: [PATCH 0257/1071] added horner's method (#1360) --- maths/polynomial_evaluation.py | 60 +++++++++++++++++++++++++--------- 1 file changed, 44 insertions(+), 16 deletions(-) diff --git a/maths/polynomial_evaluation.py b/maths/polynomial_evaluation.py index 3c91ecd93031..d2394f398c36 100644 --- a/maths/polynomial_evaluation.py +++ b/maths/polynomial_evaluation.py @@ -1,25 +1,53 @@ -def evaluate_poly(poly, x): - """ - Objective: Computes the polynomial function for a given value x. - Returns that value. - Input Prams: - poly: tuple of numbers - value of cofficients - x: value for x in f(x) - Return: value of f(x) - - >>> evaluate_poly((0.0, 0.0, 5.0, 9.3, 7.0), 10) - 79800.0 - """ +from typing import Sequence + + +def evaluate_poly(poly: Sequence[float], x: float) -> float: + """Evaluate a polynomial f(x) at specified point x and return the value. + Arguments: + poly -- the coeffiecients of a polynomial as an iterable in order of + ascending degree + x -- the point at which to evaluate the polynomial + + >>> evaluate_poly((0.0, 0.0, 5.0, 9.3, 7.0), 10.0) + 79800.0 + """ return sum(c * (x ** i) for i, c in enumerate(poly)) +def horner(poly: Sequence[float], x: float) -> float: + """Evaluate a polynomial at specified point using Horner's method. + + In terms of computational complexity, Horner's method is an efficient method + of evaluating a polynomial. It avoids the use of expensive exponentiation, + and instead uses only multiplication and addition to evaluate the polynomial + in O(n), where n is the degree of the polynomial. + + https://en.wikipedia.org/wiki/Horner's_method + + Arguments: + poly -- the coeffiecients of a polynomial as an iterable in order of + ascending degree + x -- the point at which to evaluate the polynomial + + >>> horner((0.0, 0.0, 5.0, 9.3, 7.0), 10.0) + 79800.0 + """ + result = 0.0 + for coeff in reversed(poly): + result = result * x + coeff + return result + + if __name__ == "__main__": """ - Example: poly = (0.0, 0.0, 5.0, 9.3, 7.0) # f(x) = 7.0x^4 + 9.3x^3 + 5.0x^2 - x = -13 - print (evaluate_poly(poly, x)) # f(-13) = 7.0(-13)^4 + 9.3(-13)^3 + 5.0(-13)^2 = 180339.9 + Example: + >>> poly = (0.0, 0.0, 5.0, 9.3, 7.0) # f(x) = 7.0x^4 + 9.3x^3 + 5.0x^2 + >>> x = -13.0 + >>> print(evaluate_poly(poly, x)) # f(-13) = 7.0(-13)^4 + 9.3(-13)^3 + 5.0(-13)^2 = 180339.9 + 180339.9 """ poly = (0.0, 0.0, 5.0, 9.3, 7.0) - x = 10 + x = 10.0 print(evaluate_poly(poly, x)) + print(horner(poly, x)) From 63d8cadc3e5aea15d0eadec970a3639752572d94 Mon Sep 17 00:00:00 2001 From: Milad Sadeghi DM Date: Thu, 17 Oct 2019 21:43:28 +0330 Subject: [PATCH 0258/1071] Fixing Some Minor Issues (#1386) * Replacing mutable default argument in __init__ * Fixing typo mistakes in lib.py * Simplifying chained comparisons * Update lib.py --- linear_algebra/src/lib.py | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/linear_algebra/src/lib.py b/linear_algebra/src/lib.py index 5ce0f696ad71..090427d9a520 100644 --- a/linear_algebra/src/lib.py +++ b/linear_algebra/src/lib.py @@ -27,7 +27,7 @@ class Vector(object): """ - This class represents a vector of arbitray size. + This class represents a vector of arbitrary size. You need to give the vector components. Overview about the methods: @@ -46,11 +46,13 @@ class Vector(object): TODO: compare-operator """ - def __init__(self, components=[]): + def __init__(self, components=None): """ input: components or nothing simple constructor for init the vector """ + if components is None: + components = [] self.__components = list(components) def set(self, components): @@ -112,7 +114,7 @@ def __sub__(self, other): """ input: other vector assumes: other vector has the same size - returns a new vector that represents the differenz. + returns a new vector that represents the difference. """ size = len(self) if size == len(other): @@ -136,7 +138,7 @@ def __mul__(self, other): summe += self.__components[i] * other.component(i) return summe else: # error case - raise Exception("invalide operand!") + raise Exception("invalid operand!") def copy(self): """ @@ -223,7 +225,7 @@ class Matrix(object): def __init__(self, matrix, w, h): """ - simple constructor for initialzes + simple constructor for initializing the matrix with components. """ self.__matrix = matrix @@ -249,7 +251,7 @@ def changeComponent(self, x, y, value): """ changes the x-y component of this matrix """ - if x >= 0 and x < self.__height and y >= 0 and y < self.__width: + if 0 <= x < self.__height and 0 <= y < self.__width: self.__matrix[x][y] = value else: raise Exception("changeComponent: indices out of bounds") @@ -258,7 +260,7 @@ def component(self, x, y): """ returns the specified (x,y) component """ - if x >= 0 and x < self.__height and y >= 0 and y < self.__width: + if 0 <= x < self.__height and 0 <= y < self.__width: return self.__matrix[x][y] else: raise Exception("changeComponent: indices out of bounds") From 83c104e839c158c3e84e567c38f5d30a4224e832 Mon Sep 17 00:00:00 2001 From: Du YuanChao Date: Fri, 18 Oct 2019 12:20:36 +0800 Subject: [PATCH 0259/1071] Divide and Conquer (#1308) Thanks for your persistence! --- maths/find_max_recursion.py | 25 +++++++++++++++++++++++++ maths/find_min_recursion.py | 25 +++++++++++++++++++++++++ 2 files changed, 50 insertions(+) create mode 100644 maths/find_max_recursion.py create mode 100644 maths/find_min_recursion.py diff --git a/maths/find_max_recursion.py b/maths/find_max_recursion.py new file mode 100644 index 000000000000..fc10ecf3757a --- /dev/null +++ b/maths/find_max_recursion.py @@ -0,0 +1,25 @@ +# Divide and Conquer algorithm +def find_max(nums, left, right): + """ + find max value in list + :param nums: contains elements + :param left: index of first element + :param right: index of last element + :return: max in nums + + >>> nums = [1, 3, 5, 7, 9, 2, 4, 6, 8, 10] + >>> find_max(nums, 0, len(nums) - 1) == max(nums) + True + """ + if left == right: + return nums[left] + mid = (left + right) >> 1 # the middle + left_max = find_max(nums, left, mid) # find max in range[left, mid] + right_max = find_max(nums, mid + 1, right) # find max in range[mid + 1, right] + + return left_max if left_max >= right_max else right_max + + +if __name__ == "__main__": + nums = [1, 3, 5, 7, 9, 2, 4, 6, 8, 10] + assert find_max(nums, 0, len(nums) - 1) == 10 diff --git a/maths/find_min_recursion.py b/maths/find_min_recursion.py new file mode 100644 index 000000000000..4488967cc57a --- /dev/null +++ b/maths/find_min_recursion.py @@ -0,0 +1,25 @@ +# Divide and Conquer algorithm +def find_min(nums, left, right): + """ + find min value in list + :param nums: contains elements + :param left: index of first element + :param right: index of last element + :return: min in nums + + >>> nums = [1, 3, 5, 7, 9, 2, 4, 6, 8, 10] + >>> find_min(nums, 0, len(nums) - 1) == min(nums) + True + """ + if left == right: + return nums[left] + mid = (left + right) >> 1 # the middle + left_min = find_min(nums, left, mid) # find min in range[left, mid] + right_min = find_min(nums, mid + 1, right) # find min in range[mid + 1, right] + + return left_min if left_min <= right_min else right_min + + +if __name__ == "__main__": + nums = [1, 3, 5, 7, 9, 2, 4, 6, 8, 10] + assert find_min(nums, 0, len(nums) - 1) == 1 From 28b964c9b56a52465ef69f36246947247ef1c21b Mon Sep 17 00:00:00 2001 From: aritrartira <53134837+aritrartira@users.noreply.github.com> Date: Fri, 18 Oct 2019 10:16:13 +0530 Subject: [PATCH 0260/1071] Added missing problem statements (#1364) --- project_euler/README.md | 56 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) diff --git a/project_euler/README.md b/project_euler/README.md index 9f77f719f0f1..89b6d63b5744 100644 --- a/project_euler/README.md +++ b/project_euler/README.md @@ -37,21 +37,77 @@ PROBLEMS: 7. By listing the first six prime numbers: 2, 3, 5, 7, 11, and 13, we can see that the 6th prime is 13. What is the Nth prime number? + +8. Find the consecutive k digits in a number N that have the largest product. 9. A Pythagorean triplet is a set of three natural numbers, a < b < c, for which, a^2 + b^2 = c^2 There exists exactly one Pythagorean triplet for which a + b + c = 1000. Find the product abc. +10. Find sum of all prime numbers below 2 million. + +11. In the given 20x20 grid, find 4 adjacent numbers (horizontally, vertically or diagonally) that have the largest product. + +12. The sequence of triangle numbers is generated by adding the natural numbers. So the 7th triangle number would be 1 + 2 + 3 + 4 + 5 + 6 + 7 = 28. The first ten terms would be: + + 1, 3, 6, 10, 15, 21, 28, 36, 45, 55, ... + + Let us list the factors of the first seven triangle numbers: + + 1: 1 + 3: 1,3 + 6: 1,2,3,6 + 10: 1,2,5,10 + 15: 1,3,5,15 + 21: 1,3,7,21 + 28: 1,2,4,7,14,28 + We can see that 28 is the first triangle number to have over five divisors. + + What is the value of the first triangle number to have over five hundred divisors? + +13. Work out the first 10 digits of the sum of the given hundred 50 digit numbers. + 14. The following iterative sequence is defined for the set of positive integers: n → n/2 (n is even) n → 3n + 1 (n is odd) Using the rule above and starting with 13, we generate the following sequence: 13 → 40 → 20 → 10 → 5 → 16 → 8 → 4 → 2 → 1 Which starting number, under one million, produces the longest chain? + +15. Starting from top left corner of a 20x20 grid how many routes are there to reach the bottom left corner? 16. 2^15 = 32768 and the sum of its digits is 3 + 2 + 7 + 6 + 8 = 26. What is the sum of the digits of the number 2^1000? + +17. If the numbers 1 through 1000 were written in words, how many total letters would be used? + +18. By starting at the top of the triangle below and moving to adjacent numbers on the row below, the maximum total from top to bottom is 23. + 3 + 7 4 + 2 4 6 +8 5 9 3 + +That is, 3 + 7 + 4 + 9 = 23. + +Find the maximum total from top to bottom of the triangle below: + + 75 + 95 64 + 17 47 82 + 18 35 87 10 + 20 04 82 47 65 + 19 01 23 75 03 34 + 88 02 77 73 07 63 67 + 99 65 04 28 06 16 70 92 + 41 41 26 56 83 40 80 70 33 + 41 48 72 33 47 32 37 16 94 29 + 53 71 44 65 25 43 91 52 97 51 14 + 70 11 33 28 77 73 17 78 39 68 17 57 + 91 71 52 38 17 14 91 43 58 50 27 29 48 + 63 66 04 68 89 53 67 30 73 16 69 87 40 31 +04 62 98 27 23 09 70 98 73 93 38 53 60 04 23 + 20. n! means n × (n − 1) × ... × 3 × 2 × 1 For example, 10! = 10 × 9 × ... × 3 × 2 × 1 = 3628800, and the sum of the digits in the number 10! is 3 + 6 + 2 + 8 + 8 + 0 + 0 = 27. From 14c23bc8475a6d1d0ddbab6a6259bf8f30af41cd Mon Sep 17 00:00:00 2001 From: Stephen <24819660+infrontoftheforest@users.noreply.github.com> Date: Fri, 18 Oct 2019 04:48:16 +0000 Subject: [PATCH 0261/1071] create qr_decomposition (#1363) --- maths/qr_decomposition.py | 71 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 71 insertions(+) create mode 100644 maths/qr_decomposition.py diff --git a/maths/qr_decomposition.py b/maths/qr_decomposition.py new file mode 100644 index 000000000000..197211f1e694 --- /dev/null +++ b/maths/qr_decomposition.py @@ -0,0 +1,71 @@ +import numpy as np + + +def qr_householder(A): + """Return a QR-decomposition of the matrix A using Householder reflection. + + The QR-decomposition decomposes the matrix A of shape (m, n) into an + orthogonal matrix Q of shape (m, m) and an upper triangular matrix R of + shape (m, n). Note that the matrix A does not have to be square. This + method of decomposing A uses the Householder reflection, which is + numerically stable and of complexity O(n^3). + + https://en.wikipedia.org/wiki/QR_decomposition#Using_Householder_reflections + + Arguments: + A -- a numpy.ndarray of shape (m, n) + + Note: several optimizations can be made for numeric efficiency, but this is + intended to demonstrate how it would be represented in a mathematics + textbook. In cases where efficiency is particularly important, an optimized + version from BLAS should be used. + + >>> A = np.array([[12, -51, 4], [6, 167, -68], [-4, 24, -41]], dtype=float) + >>> Q, R = qr_householder(A) + + >>> # check that the decomposition is correct + >>> np.allclose(Q@R, A) + True + + >>> # check that Q is orthogonal + >>> np.allclose(Q@Q.T, np.eye(A.shape[0])) + True + >>> np.allclose(Q.T@Q, np.eye(A.shape[0])) + True + + >>> # check that R is upper triangular + >>> np.allclose(np.triu(R), R) + True + """ + m, n = A.shape + t = min(m, n) + Q = np.eye(m) + R = A.copy() + + for k in range(t - 1): + # select a column of modified matrix A': + x = R[k:, [k]] + # construct first basis vector + e1 = np.zeros_like(x) + e1[0] = 1.0 + # determine scaling factor + alpha = np.linalg.norm(x) + # construct vector v for Householder reflection + v = x + np.sign(x[0])*alpha*e1 + v /= np.linalg.norm(v) + + # construct the Householder matrix + Q_k = np.eye(m - k) - 2.0*v@v.T + # pad with ones and zeros as necessary + Q_k = np.block([[np.eye(k), np.zeros((k, m - k))], + [np.zeros((m - k, k)), Q_k ]]) + + Q = Q@Q_k.T + R = Q_k@R + + return Q, R + + +if __name__ == "__main__": + import doctest + doctest.testmod() From 870eebf349f78047461eb639d60921096179facb Mon Sep 17 00:00:00 2001 From: Yurii <33547678+yuriimchg@users.noreply.github.com> Date: Fri, 18 Oct 2019 08:27:55 +0300 Subject: [PATCH 0262/1071] rewrite the algorithm from scratch (#1351) --- maths/factorial_python.py | 38 ++++++++++++++++++++------------------ 1 file changed, 20 insertions(+), 18 deletions(-) diff --git a/maths/factorial_python.py b/maths/factorial_python.py index 6c1349fd5f4c..10083af0bef2 100644 --- a/maths/factorial_python.py +++ b/maths/factorial_python.py @@ -1,19 +1,21 @@ -"""Python program to find the factorial of a number provided by the user.""" +def factorial(input_number: int) -> int: + """ + Non-recursive algorithm of finding factorial of the + input number. + >>> factorial(1) + 1 + >>> factorial(6) + 720 + >>> factorial(0) + 1 + """ -# change the value for a different result -NUM = 10 - -# uncomment to take input from the user -# num = int(input("Enter a number: ")) - -FACTORIAL = 1 - -# check if the number is negative, positive or zero -if NUM < 0: - print("Sorry, factorial does not exist for negative numbers") -elif NUM == 0: - print("The factorial of 0 is 1") -else: - for i in range(1, NUM + 1): - FACTORIAL = FACTORIAL * i - print("The factorial of", NUM, "is", FACTORIAL) + if input_number < 0: + raise ValueError('Input input_number should be non-negative') + elif input_number == 0: + return 1 + else: + result = 1 + for i in range(input_number): + result = result * (i + 1) + return result From 3cc35310769b5681201e0fb2828ee4e40d947f05 Mon Sep 17 00:00:00 2001 From: Yurii <33547678+yuriimchg@users.noreply.github.com> Date: Fri, 18 Oct 2019 08:35:29 +0300 Subject: [PATCH 0263/1071] Feature/update least common multiple (#1352) * renamed module to extend the acronym * add type hints (will not work with Python less than 3.4) * update docstring * refactor the function * add unittests for the least common squares multiple --- maths/find_lcm.py | 34 -------------------------- maths/least_common_multiple.py | 44 ++++++++++++++++++++++++++++++++++ 2 files changed, 44 insertions(+), 34 deletions(-) delete mode 100644 maths/find_lcm.py create mode 100644 maths/least_common_multiple.py diff --git a/maths/find_lcm.py b/maths/find_lcm.py deleted file mode 100644 index dffadd1f3c5b..000000000000 --- a/maths/find_lcm.py +++ /dev/null @@ -1,34 +0,0 @@ -"""Find Least Common Multiple.""" - -# https://en.wikipedia.org/wiki/Least_common_multiple - - -def find_lcm(num_1, num_2): - """Find the least common multiple of two numbers. - >>> find_lcm(5,2) - 10 - >>> find_lcm(12,76) - 228 - """ - if num_1 >= num_2: - max_num = num_1 - else: - max_num = num_2 - - lcm = max_num - while True: - if (lcm % num_1 == 0) and (lcm % num_2 == 0): - break - lcm += max_num - return lcm - - -def main(): - """Use test numbers to run the find_lcm algorithm.""" - num_1 = int(input().strip()) - num_2 = int(input().strip()) - print(find_lcm(num_1, num_2)) - - -if __name__ == "__main__": - main() diff --git a/maths/least_common_multiple.py b/maths/least_common_multiple.py new file mode 100644 index 000000000000..863744e182b6 --- /dev/null +++ b/maths/least_common_multiple.py @@ -0,0 +1,44 @@ +import unittest + + +def find_lcm(first_num: int, second_num: int) -> int: + """Find the least common multiple of two numbers. + + Learn more: https://en.wikipedia.org/wiki/Least_common_multiple + + >>> find_lcm(5,2) + 10 + >>> find_lcm(12,76) + 228 + """ + max_num = first_num if first_num >= second_num else second_num + common_mult = max_num + while (common_mult % first_num > 0) or (common_mult % second_num > 0): + common_mult += max_num + return common_mult + + +class TestLeastCommonMultiple(unittest.TestCase): + + test_inputs = [ + (10, 20), + (13, 15), + (4, 31), + (10, 42), + (43, 34), + (5, 12), + (12, 25), + (10, 25), + (6, 9), + ] + expected_results = [20, 195, 124, 210, 1462, 60, 300, 50, 18] + + def test_lcm_function(self): + for i, (first_num, second_num) in enumerate(self.test_inputs): + actual_result = find_lcm(first_num, second_num) + with self.subTest(i=i): + self.assertEqual(actual_result, self.expected_results[i]) + + +if __name__ == "__main__": + unittest.main() From b7fb0630f2c466412337d25d2e611eeef7bc381b Mon Sep 17 00:00:00 2001 From: Yurii <33547678+yuriimchg@users.noreply.github.com> Date: Fri, 18 Oct 2019 08:36:52 +0300 Subject: [PATCH 0264/1071] Feature/fix caesar cipher (#1350) * change var names for better reading * rewrite encrypt function to fix ascii char ranges * fix decrypt function * update formatting (add f-strings) * upd fuctions * add f-string formatting (python3.6+) * add type hints (python3.4+) --- ciphers/caesar_cipher.py | 69 ++++++++++++++++++++-------------------- 1 file changed, 35 insertions(+), 34 deletions(-) diff --git a/ciphers/caesar_cipher.py b/ciphers/caesar_cipher.py index 3f24e049afb0..52155bbdc49e 100644 --- a/ciphers/caesar_cipher.py +++ b/ciphers/caesar_cipher.py @@ -1,61 +1,62 @@ -def encrypt(strng, key): - encrypted = "" - for x in strng: - indx = (ord(x) + key) % 256 - if indx > 126: - indx = indx - 95 - encrypted = encrypted + chr(indx) - return encrypted +def encrypt(input_string: str, key: int) -> str: + result = '' + for x in input_string: + if not x.isalpha(): + result += x + elif x.isupper(): + result += chr((ord(x) + key - 65) % 26 + 65) + elif x.islower(): + result += chr((ord(x) + key - 97) % 26 + 97) + return result -def decrypt(strng, key): - decrypted = "" - for x in strng: - indx = (ord(x) - key) % 256 - if indx < 32: - indx = indx + 95 - decrypted = decrypted + chr(indx) - return decrypted +def decrypt(input_string: str, key: int) -> str: + result = '' + for x in input_string: + if not x.isalpha(): + result += x + elif x.isupper(): + result += chr((ord(x) - key - 65) % 26 + 65) + elif x.islower(): + result += chr((ord(x) - key - 97) % 26 + 97) + return result -def brute_force(strng): +def brute_force(input_string: str) -> None: key = 1 - decrypted = "" + result = '' while key <= 94: - for x in strng: + for x in input_string: indx = (ord(x) - key) % 256 if indx < 32: indx = indx + 95 - decrypted = decrypted + chr(indx) - print("Key: {}\t| Message: {}".format(key, decrypted)) - decrypted = "" + result = result + chr(indx) + print(f'Key: {key}\t| Message: {result}') + result = '' key += 1 return None def main(): while True: - print("-" * 10 + "\n**Menu**\n" + "-" * 10) - print("1.Encrpyt") - print("2.Decrypt") - print("3.BruteForce") - print("4.Quit") + print(f'{"-" * 10}\n Menu\n{"-", * 10}') + print(*["1.Encrpyt", "2.Decrypt", "3.BruteForce", "4.Quit"], sep='\n') choice = input("What would you like to do?: ") if choice not in ["1", "2", "3", "4"]: print("Invalid choice, please enter a valid choice") elif choice == "1": - strng = input("Please enter the string to be encrypted: ") - key = int(input("Please enter off-set between 1-94: ")) + input_string = input("Please enter the string to be encrypted: ") + key = int(input("Please enter off-set between 0-25: ")) if key in range(1, 95): - print(encrypt(strng.lower(), key)) + print(encrypt(input_string.lower(), key)) elif choice == "2": - strng = input("Please enter the string to be decrypted: ") + input_string = input("Please enter the string to be decrypted: ") key = int(input("Please enter off-set between 1-94: ")) if key in range(1, 95): - print(decrypt(strng, key)) + print(decrypt(input_string, key)) elif choice == "3": - strng = input("Please enter the string to be decrypted: ") - brute_force(strng) + input_string = input("Please enter the string to be decrypted: ") + brute_force(input_string) main() elif choice == "4": print("Goodbye.") From 9c634735d39a6d51aa94b0c92cf0473af72439f1 Mon Sep 17 00:00:00 2001 From: Laisha Wadhwa Date: Fri, 18 Oct 2019 11:40:08 +0530 Subject: [PATCH 0265/1071] added fibonacci_search.py (#1341) * added fibonacci_search.py * added Fibonacci_search.py after error handling * added doctests --- searches/fibonacci_search.py | 50 ++++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) create mode 100644 searches/fibonacci_search.py diff --git a/searches/fibonacci_search.py b/searches/fibonacci_search.py new file mode 100644 index 000000000000..f76528b9c283 --- /dev/null +++ b/searches/fibonacci_search.py @@ -0,0 +1,50 @@ +#run using python fibonacci_search.py -v + +''' +@params +arr: input array +val: the value to be searched +output: the index of element in the array or -1 if not found +return 0 if input array is empty +''' +def fibonacci_search(arr, val): + + """ + >>> fibonacci_search([1,6,7,0,0,0], 6) + 1 + >>> fibonacci_search([1,-1, 5, 2, 9], 10) + -1 + >>> fibonacci_search([], 9) + 0 + """ + fib_N_2 = 0 + fib_N_1 = 1 + fibNext = fib_N_1 + fib_N_2 + length = len(arr) + if length == 0: + return 0 + while (fibNext < len(arr)): + fib_N_2 = fib_N_1 + fib_N_1 = fibNext + fibNext = fib_N_1 + fib_N_2 + index = -1; + while (fibNext > 1): + i = min(index + fib_N_2, (length-1)) + if (arr[i] < val): + fibNext = fib_N_1 + fib_N_1 = fib_N_2 + fib_N_2 = fibNext - fib_N_1 + index = i + elif (arr[i] > val): + fibNext = fib_N_2 + fib_N_1 = fib_N_1 - fib_N_2 + fib_N_2 = fibNext - fib_N_1 + else : + return i + if (fib_N_1 and index < length-1) and (arr[index+1] == val): + return index+1; + return -1 + +if __name__ == "__main__": + import doctest + doctest.testmod() From ddb094919b8af4b1268e118fa3e33868c30f4386 Mon Sep 17 00:00:00 2001 From: Jigyasa G Date: Fri, 18 Oct 2019 11:43:20 +0530 Subject: [PATCH 0266/1071] Adding doctests for fractional_knapsack.py (#1331) * Adding doctests for fractional_knapsack.py * Update fractional_knapsack.py --- dynamic_programming/fractional_knapsack.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/dynamic_programming/fractional_knapsack.py b/dynamic_programming/fractional_knapsack.py index 881b6a3969d0..728cdeb009ac 100644 --- a/dynamic_programming/fractional_knapsack.py +++ b/dynamic_programming/fractional_knapsack.py @@ -3,6 +3,10 @@ def fracKnapsack(vl, wt, W, n): + """ + >>> fracKnapsack([60, 100, 120], [10, 20, 30], 50, 3) + 240.0 + """ r = list(sorted(zip(vl, wt), key=lambda x: x[0] / x[1], reverse=True)) vl, wt = [i[0] for i in r], [i[1] for i in r] @@ -16,5 +20,7 @@ def fracKnapsack(vl, wt, W, n): else sum(vl[:k]) ) +if __name__ == "__main__": + import doctest -print("%.0f" % fracKnapsack([60, 100, 120], [10, 20, 30], 50, 3)) + doctest.testmod() From 455509acee831cb4a8c3bf9d495fd0e296825c68 Mon Sep 17 00:00:00 2001 From: Phyllipe Bezerra <32442929+pmba@users.noreply.github.com> Date: Fri, 18 Oct 2019 03:13:58 -0300 Subject: [PATCH 0267/1071] Add Topological Sort (#1302) * add topological sort * fix topological sort? * running black * renaming file --- blockchain/chinese_remainder_theorem.py | 12 +++--- blockchain/diophantine_equation.py | 16 +++++--- blockchain/modular_division.py | 16 ++++---- graphs/g_topological_sort.py | 47 ++++++++++++++++++++++++ machine_learning/k_nearest_neighbours.py | 14 ++++--- maths/fibonacci_sequence_recursion.py | 2 +- maths/sum_of_arithmetic_series.py | 3 +- sorts/stooge_sort.py | 1 + 8 files changed, 84 insertions(+), 27 deletions(-) create mode 100644 graphs/g_topological_sort.py diff --git a/blockchain/chinese_remainder_theorem.py b/blockchain/chinese_remainder_theorem.py index f1409530a70e..8c3eb9b4b01e 100644 --- a/blockchain/chinese_remainder_theorem.py +++ b/blockchain/chinese_remainder_theorem.py @@ -44,7 +44,7 @@ def chinese_remainder_theorem(n1, r1, n2, r2): (x, y) = extended_euclid(n1, n2) m = n1 * n2 n = r2 * x * n1 + r1 * y * n2 - return ((n % m + m) % m) + return (n % m + m) % m # ----------SAME SOLUTION USING InvertModulo instead ExtendedEuclid---------------- @@ -84,8 +84,8 @@ def chinese_remainder_theorem2(n1, r1, n2, r2): # import testmod for testing our function from doctest import testmod -if __name__ == '__main__': - testmod(name='chinese_remainder_theorem', verbose=True) - testmod(name='chinese_remainder_theorem2', verbose=True) - testmod(name='invert_modulo', verbose=True) - testmod(name='extended_euclid', verbose=True) +if __name__ == "__main__": + testmod(name="chinese_remainder_theorem", verbose=True) + testmod(name="chinese_remainder_theorem2", verbose=True) + testmod(name="invert_modulo", verbose=True) + testmod(name="extended_euclid", verbose=True) diff --git a/blockchain/diophantine_equation.py b/blockchain/diophantine_equation.py index 3ac7094eed6b..ec2ed26e40ec 100644 --- a/blockchain/diophantine_equation.py +++ b/blockchain/diophantine_equation.py @@ -17,7 +17,9 @@ def diophantine(a, b, c): """ - assert c % greatest_common_divisor(a, b) == 0 # greatest_common_divisor(a,b) function implemented below + assert ( + c % greatest_common_divisor(a, b) == 0 + ) # greatest_common_divisor(a,b) function implemented below (d, x, y) = extended_gcd(a, b) # extended_gcd(a,b) function implemented below r = c / d return (r * x, r * y) @@ -32,6 +34,7 @@ def diophantine(a, b, c): # n is the number of solution you want, n = 2 by default + def diophantine_all_soln(a, b, c, n=2): """ >>> diophantine_all_soln(10, 6, 14) @@ -66,6 +69,7 @@ def diophantine_all_soln(a, b, c, n=2): # Euclid's Algorithm + def greatest_common_divisor(a, b): """ >>> greatest_common_divisor(7,5) @@ -117,8 +121,8 @@ def extended_gcd(a, b): # import testmod for testing our function from doctest import testmod -if __name__ == '__main__': - testmod(name='diophantine', verbose=True) - testmod(name='diophantine_all_soln', verbose=True) - testmod(name='extended_gcd', verbose=True) - testmod(name='greatest_common_divisor', verbose=True) +if __name__ == "__main__": + testmod(name="diophantine", verbose=True) + testmod(name="diophantine_all_soln", verbose=True) + testmod(name="extended_gcd", verbose=True) + testmod(name="greatest_common_divisor", verbose=True) diff --git a/blockchain/modular_division.py b/blockchain/modular_division.py index 4e1623fbe923..1255f04328d5 100644 --- a/blockchain/modular_division.py +++ b/blockchain/modular_division.py @@ -70,6 +70,7 @@ def modular_division2(a, b, n): # Extended Euclid's Algorithm : If d divides a and b and d = a*x + b*y for integers x and y, then d = gcd(a,b) + def extended_gcd(a, b): """ >>> extended_gcd(10, 6) @@ -116,6 +117,7 @@ def extended_euclid(a, b): # Euclid's Lemma : d divides a and b, if and only if d divides a-b and b # Euclid's Algorithm + def greatest_common_divisor(a, b): """ >>> greatest_common_divisor(7,5) @@ -140,10 +142,10 @@ def greatest_common_divisor(a, b): # Import testmod for testing our function from doctest import testmod -if __name__ == '__main__': - testmod(name='modular_division', verbose=True) - testmod(name='modular_division2', verbose=True) - testmod(name='invert_modulo', verbose=True) - testmod(name='extended_gcd', verbose=True) - testmod(name='extended_euclid', verbose=True) - testmod(name='greatest_common_divisor', verbose=True) +if __name__ == "__main__": + testmod(name="modular_division", verbose=True) + testmod(name="modular_division2", verbose=True) + testmod(name="invert_modulo", verbose=True) + testmod(name="extended_gcd", verbose=True) + testmod(name="extended_euclid", verbose=True) + testmod(name="greatest_common_divisor", verbose=True) diff --git a/graphs/g_topological_sort.py b/graphs/g_topological_sort.py new file mode 100644 index 000000000000..1a2f4fa11d88 --- /dev/null +++ b/graphs/g_topological_sort.py @@ -0,0 +1,47 @@ +# Author: Phyllipe Bezerra (https://github.com/pmba) + +clothes = { + 0: "underwear", + 1: "pants", + 2: "belt", + 3: "suit", + 4: "shoe", + 5: "socks", + 6: "shirt", + 7: "tie", + 8: "clock", +} + +graph = [[1, 4], [2, 4], [3], [], [], [4], [2, 7], [3], []] + +visited = [0 for x in range(len(graph))] +stack = [] + + +def print_stack(stack, clothes): + order = 1 + while stack: + cur_clothe = stack.pop() + print(order, clothes[cur_clothe]) + order += 1 + + +def dfs(u, visited, graph): + visited[u] = 1 + for v in graph[u]: + if not visited[v]: + dfs(v, visited, graph) + + stack.append(u) + + +def top_sort(graph, visited): + for v in range(len(graph)): + if not visited[v]: + dfs(v, visited, graph) + + +if __name__ == "__main__": + top_sort(graph, visited) + print(stack) + print_stack(stack, clothes) diff --git a/machine_learning/k_nearest_neighbours.py b/machine_learning/k_nearest_neighbours.py index 83d8399fe9b6..a60b744bc65e 100644 --- a/machine_learning/k_nearest_neighbours.py +++ b/machine_learning/k_nearest_neighbours.py @@ -5,12 +5,13 @@ data = datasets.load_iris() -X = np.array(data['data']) -y = np.array(data['target']) -classes = data['target_names'] +X = np.array(data["data"]) +y = np.array(data["target"]) +classes = data["target_names"] X_train, X_test, y_train, y_test = train_test_split(X, y) + def euclidean_distance(a, b): """ Gives the euclidean distance between two points @@ -21,6 +22,7 @@ def euclidean_distance(a, b): """ return np.linalg.norm(np.array(a) - np.array(b)) + def classifier(train_data, train_target, classes, point, k=5): """ Classifies the point using the KNN algorithm @@ -43,13 +45,13 @@ def classifier(train_data, train_target, classes, point, k=5): for data_point in data: distance = euclidean_distance(data_point[0], point) distances.append((distance, data_point[1])) - # Choosing 'k' points with the least distances. + # Choosing 'k' points with the least distances. votes = [i[1] for i in sorted(distances)[:k]] - # Most commonly occuring class among them + # Most commonly occuring class among them # is the class into which the point is classified result = Counter(votes).most_common(1)[0][0] return classes[result] if __name__ == "__main__": - print(classifier(X_train, y_train, classes, [4.4, 3.1, 1.3, 1.4])) \ No newline at end of file + print(classifier(X_train, y_train, classes, [4.4, 3.1, 1.3, 1.4])) diff --git a/maths/fibonacci_sequence_recursion.py b/maths/fibonacci_sequence_recursion.py index 2e0d835cf15e..91619600d5b4 100644 --- a/maths/fibonacci_sequence_recursion.py +++ b/maths/fibonacci_sequence_recursion.py @@ -6,7 +6,7 @@ def recur_fibo(n): >>> [recur_fibo(i) for i in range(12)] [0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89] """ - return n if n <= 1 else recur_fibo(n-1) + recur_fibo(n-2) + return n if n <= 1 else recur_fibo(n - 1) + recur_fibo(n - 2) def main(): diff --git a/maths/sum_of_arithmetic_series.py b/maths/sum_of_arithmetic_series.py index f7ea5dc84cb8..74eef0f18a12 100755 --- a/maths/sum_of_arithmetic_series.py +++ b/maths/sum_of_arithmetic_series.py @@ -8,7 +8,7 @@ def sum_of_series(first_term, common_diff, num_of_terms): >>> sum_of_series(1, 10, 100) 49600.0 """ - sum = ((num_of_terms/2)*(2*first_term+(num_of_terms-1)*common_diff)) + sum = (num_of_terms / 2) * (2 * first_term + (num_of_terms - 1) * common_diff) # formula for sum of series return sum @@ -19,4 +19,5 @@ def main(): if __name__ == "__main__": import doctest + doctest.testmod() diff --git a/sorts/stooge_sort.py b/sorts/stooge_sort.py index 089b01a4def1..de997a85df12 100644 --- a/sorts/stooge_sort.py +++ b/sorts/stooge_sort.py @@ -33,6 +33,7 @@ def stooge(arr, i, h): # Recursively sort first 2/3 elements stooge(arr, i, (h - t)) + if __name__ == "__main__": user_input = input("Enter numbers separated by a comma:\n").strip() unsorted = [int(item) for item in user_input.split(",")] From 2197bfa029641c1695f8172ecf95f25dad9ddd0d Mon Sep 17 00:00:00 2001 From: archit kaushal <38643326+archu5@users.noreply.github.com> Date: Fri, 18 Oct 2019 11:50:22 +0530 Subject: [PATCH 0268/1071] #840 adds polymonial regression program in python (#1235) * #840 adds polymonial regression program in python * Update polymonial_regression.py * Update polymonial_regression.py --- machine_learning/polymonial_regression.py | 37 +++++++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 machine_learning/polymonial_regression.py diff --git a/machine_learning/polymonial_regression.py b/machine_learning/polymonial_regression.py new file mode 100644 index 000000000000..03f5f0a9713d --- /dev/null +++ b/machine_learning/polymonial_regression.py @@ -0,0 +1,37 @@ +import matplotlib.pyplot as plt +import pandas as pd + +# Importing the dataset +dataset = pd.read_csv('https://s3.us-west-2.amazonaws.com/public.gamelab.fun/dataset/position_salaries.csv') +X = dataset.iloc[:, 1:2].values +y = dataset.iloc[:, 2].values + + +# Splitting the dataset into the Training set and Test set +from sklearn.model_selection import train_test_split +X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=0) + + +# Fitting Polynomial Regression to the dataset +from sklearn.preprocessing import PolynomialFeatures +from sklearn.linear_model import LinearRegression +poly_reg = PolynomialFeatures(degree=4) +X_poly = poly_reg.fit_transform(X) +pol_reg = LinearRegression() +pol_reg.fit(X_poly, y) + + +# Visualizing the Polymonial Regression results +def viz_polymonial(): + plt.scatter(X, y, color='red') + plt.plot(X, pol_reg.predict(poly_reg.fit_transform(X)), color='blue') + plt.title('Truth or Bluff (Linear Regression)') + plt.xlabel('Position level') + plt.ylabel('Salary') + plt.show() + return +viz_polymonial() + +# Predicting a new result with Polymonial Regression +pol_reg.predict(poly_reg.fit_transform([[5.5]])) +#output should be 132148.43750003 From e177198177695d70cd17c351fce7033c9f4f2789 Mon Sep 17 00:00:00 2001 From: Pierrick <43653255+laurent-pck@users.noreply.github.com> Date: Fri, 18 Oct 2019 08:35:13 +0200 Subject: [PATCH 0269/1071] Add unicode support in ciphers/base64_cipher.py script. (#1316) * Add unicode support in ciphers/base64_cipher.py script. * Add doctests and correct the padding length computation in base64_cipher. --- ciphers/base64_cipher.py | 57 ++++++++++++++++++++++++++-------------- 1 file changed, 37 insertions(+), 20 deletions(-) diff --git a/ciphers/base64_cipher.py b/ciphers/base64_cipher.py index 9fca5b02679f..eea065b94ee0 100644 --- a/ciphers/base64_cipher.py +++ b/ciphers/base64_cipher.py @@ -1,35 +1,52 @@ -def encodeBase64(text): - base64chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/" - +def encode_base64(text): + r""" + >>> encode_base64('WELCOME to base64 encoding 😁') + 'V0VMQ09NRSB0byBiYXNlNjQgZW5jb2Rpbmcg8J+YgQ==' + >>> encode_base64('AÅᐃ𐀏🤓') + 'QcOF4ZCD8JCAj/CfpJM=' + >>> encode_base64('A'*60) + 'QUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFB\r\nQUFB' + """ + base64_chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/" + + byte_text = bytes(text, "utf-8") # put text in bytes for unicode support r = "" # the result - c = 3 - len(text) % 3 # the length of padding + c = -len(byte_text) % 3 # the length of padding p = "=" * c # the padding - s = text + "\0" * c # the text to encode + s = byte_text + b"\x00" * c # the text to encode i = 0 while i < len(s): if i > 0 and ((i / 3 * 4) % 76) == 0: - r = r + "\r\n" + r = r + "\r\n" # for unix newline, put "\n" - n = (ord(s[i]) << 16) + (ord(s[i + 1]) << 8) + ord(s[i + 2]) + n = (s[i] << 16) + (s[i + 1] << 8) + s[i + 2] n1 = (n >> 18) & 63 n2 = (n >> 12) & 63 n3 = (n >> 6) & 63 n4 = n & 63 - r += base64chars[n1] + base64chars[n2] + base64chars[n3] + base64chars[n4] + r += base64_chars[n1] + base64_chars[n2] + base64_chars[n3] + base64_chars[n4] i += 3 return r[0 : len(r) - len(p)] + p -def decodeBase64(text): - base64chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/" +def decode_base64(text): + r""" + >>> decode_base64('V0VMQ09NRSB0byBiYXNlNjQgZW5jb2Rpbmcg8J+YgQ==') + 'WELCOME to base64 encoding 😁' + >>> decode_base64('QcOF4ZCD8JCAj/CfpJM=') + 'AÅᐃ𐀏🤓' + >>> decode_base64("QUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFB\r\nQUFB") + 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' + """ + base64_chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/" s = "" for i in text: - if i in base64chars: + if i in base64_chars: s += i c = "" else: @@ -43,28 +60,28 @@ def decodeBase64(text): if c == "==": p = "AA" - r = "" + r = b"" s = s + p i = 0 while i < len(s): n = ( - (base64chars.index(s[i]) << 18) - + (base64chars.index(s[i + 1]) << 12) - + (base64chars.index(s[i + 2]) << 6) - + base64chars.index(s[i + 3]) + (base64_chars.index(s[i]) << 18) + + (base64_chars.index(s[i + 1]) << 12) + + (base64_chars.index(s[i + 2]) << 6) + + base64_chars.index(s[i + 3]) ) - r += chr((n >> 16) & 255) + chr((n >> 8) & 255) + chr(n & 255) + r += bytes([(n >> 16) & 255]) + bytes([(n >> 8) & 255]) + bytes([n & 255]) i += 4 - return r[0 : len(r) - len(p)] + return str(r[0 : len(r) - len(p)], "utf-8") def main(): - print(encodeBase64("WELCOME to base64 encoding")) - print(decodeBase64(encodeBase64("WELCOME to base64 encoding"))) + print(encode_base64("WELCOME to base64 encoding 😁")) + print(decode_base64(encode_base64("WELCOME to base64 encoding 😁"))) if __name__ == "__main__": From 7376addcd5f786a3c46e48e5600cf88d363015c3 Mon Sep 17 00:00:00 2001 From: Mariusz Skoneczko Date: Fri, 18 Oct 2019 17:38:31 +1100 Subject: [PATCH 0270/1071] Implement Linked Queue and Linked Stack data structures (#1324) * Add LinkedQueue * Add LinkedStack --- data_structures/queue/linked_queue.py | 74 ++++++++++++++++++++++++++ data_structures/stacks/linked_stack.py | 67 +++++++++++++++++++++++ 2 files changed, 141 insertions(+) create mode 100644 data_structures/queue/linked_queue.py create mode 100644 data_structures/stacks/linked_stack.py diff --git a/data_structures/queue/linked_queue.py b/data_structures/queue/linked_queue.py new file mode 100644 index 000000000000..614c60cd1ae2 --- /dev/null +++ b/data_structures/queue/linked_queue.py @@ -0,0 +1,74 @@ +""" A Queue using a Linked List like structure """ +from typing import Any, Optional + + +class Node: + def __init__(self, data: Any, next: Optional["Node"] = None): + self.data: Any = data + self.next: Optional["Node"] = next + + +class LinkedQueue: + """ + Linked List Queue implementing put (to end of queue), + get (from front of queue) and is_empty + + >>> queue = LinkedQueue() + >>> queue.is_empty() + True + >>> queue.put(5) + >>> queue.put(9) + >>> queue.put('python') + >>> queue.is_empty(); + False + >>> queue.get() + 5 + >>> queue.put('algorithms') + >>> queue.get() + 9 + >>> queue.get() + 'python' + >>> queue.get() + 'algorithms' + >>> queue.is_empty() + True + >>> queue.get() + Traceback (most recent call last): + ... + IndexError: get from empty queue + """ + + def __init__(self) -> None: + self.front: Optional[Node] = None + self.rear: Optional[Node] = None + + def is_empty(self) -> bool: + """ returns boolean describing if queue is empty """ + return self.front is None + + def put(self, item: Any) -> None: + """ append item to rear of queue """ + node: Node = Node(item) + if self.is_empty(): + # the queue contains just the single element + self.front = node + self.rear = node + else: + # not empty, so we add it to the rear of the queue + assert isinstance(self.rear, Node) + self.rear.next = node + self.rear = node + + def get(self) -> Any: + """ returns and removes item at front of queue """ + if self.is_empty(): + raise IndexError("get from empty queue") + else: + # "remove" element by having front point to the next one + assert isinstance(self.front, Node) + node: Node = self.front + self.front = node.next + if self.front is None: + self.rear = None + + return node.data diff --git a/data_structures/stacks/linked_stack.py b/data_structures/stacks/linked_stack.py new file mode 100644 index 000000000000..18ba87ddc221 --- /dev/null +++ b/data_structures/stacks/linked_stack.py @@ -0,0 +1,67 @@ +""" A Stack using a Linked List like structure """ +from typing import Any, Optional + + +class Node: + def __init__(self, data: Any, next: Optional["Node"] = None): + self.data: Any = data + self.next: Optional["Node"] = next + + +class LinkedStack: + """ + Linked List Stack implementing push (to top), + pop (from top) and is_empty + + >>> stack = LinkedStack() + >>> stack.is_empty() + True + >>> stack.push(5) + >>> stack.push(9) + >>> stack.push('python') + >>> stack.is_empty(); + False + >>> stack.pop() + 'python' + >>> stack.push('algorithms') + >>> stack.pop() + 'algorithms' + >>> stack.pop() + 9 + >>> stack.pop() + 5 + >>> stack.is_empty() + True + >>> stack.pop() + Traceback (most recent call last): + ... + IndexError: pop from empty stack + """ + + def __init__(self) -> None: + self.top: Optional[Node] = None + + def is_empty(self) -> bool: + """ returns boolean describing if stack is empty """ + return self.top is None + + def push(self, item: Any) -> None: + """ append item to top of stack """ + node: Node = Node(item) + if self.is_empty(): + self.top = node + else: + # each node points to the item "lower" in the stack + node.next = self.top + self.top = node + + def pop(self) -> Any: + """ returns and removes item at top of stack """ + if self.is_empty(): + raise IndexError("pop from empty stack") + else: + # "remove" element by having top point to the next one + assert isinstance(self.top, Node) + node: Node = self.top + self.top = node.next + return node.data From 179284a41bf265c59913b72d94bee47d7d6b7414 Mon Sep 17 00:00:00 2001 From: Hocnonsense <48747984+Hocnonsense@users.noreply.github.com> Date: Fri, 18 Oct 2019 15:39:37 +0800 Subject: [PATCH 0271/1071] Update treap.py (#1358) * Update treap.py check merge() * Update treap.py random() is used. its difficult to write doctests l->left r->right key->value add __repr__ and __str__ in preorder --- data_structures/binary_tree/treap.py | 188 +++++++++++++++++---------- 1 file changed, 116 insertions(+), 72 deletions(-) diff --git a/data_structures/binary_tree/treap.py b/data_structures/binary_tree/treap.py index 5d34abc3c931..0b5947f4cc04 100644 --- a/data_structures/binary_tree/treap.py +++ b/data_structures/binary_tree/treap.py @@ -2,129 +2,173 @@ from typing import Tuple -class Node: +class Node(object): """ Treap's node - Treap is a binary tree by key and heap by priority + Treap is a binary tree by value and heap by priority """ - - def __init__(self, key: int): - self.key = key + def __init__(self, value: int = None): + self.value = value self.prior = random() - self.l = None - self.r = None + self.left = None + self.right = None + def __repr__(self): + from pprint import pformat -def split(root: Node, key: int) -> Tuple[Node, Node]: + if self.left is None and self.right is None: + return "'%s: %.5s'" % (self.value, self.prior) + else: + return pformat( + { + "%s: %.5s" + % (self.value, self.prior): (self.left, self.right) + }, + indent=1, + ) + + def __str__(self): + value = str(self.value) + " " + left = str(self.left or "") + right = str(self.right or "") + return value + left + right + +def split(root: Node, value: int) -> Tuple[Node, Node]: """ - We split current tree into 2 trees with key: + We split current tree into 2 trees with value: - Left tree contains all keys less than split key. - Right tree contains all keys greater or equal, than split key + Left tree contains all values less than split value. + Right tree contains all values greater or equal, than split value """ if root is None: # None tree is split into 2 Nones return (None, None) - if root.key >= key: - """ - Right tree's root will be current node. - Now we split(with the same key) current node's left son - Left tree: left part of that split - Right tree's left son: right part of that split - """ - l, root.l = split(root.l, key) - return (l, root) + elif root.value is None: + return (None, None) else: - """ - Just symmetric to previous case - """ - root.r, r = split(root.r, key) - return (root, r) - + if value < root.value: + """ + Right tree's root will be current node. + Now we split(with the same value) current node's left son + Left tree: left part of that split + Right tree's left son: right part of that split + """ + left, root.left = split(root.left, value) + return (left, root) + else: + """ + Just symmetric to previous case + """ + root.right, right = split(root.right, value) + return (root, right) def merge(left: Node, right: Node) -> Node: """ We merge 2 trees into one. - Note: all left tree's keys must be less than all right tree's + Note: all left tree's values must be less than all right tree's """ - if (not left) or (not right): - """ - If one node is None, return the other - """ + if (not left) or (not right): # If one node is None, return the other return left or right - if left.key > right.key: + elif left.prior < right.prior: """ Left will be root because it has more priority Now we need to merge left's right son and right tree """ - left.r = merge(left.r, right) + left.right = merge(left.right, right) return left else: """ Symmetric as well """ - right.l = merge(left, right.l) + right.left = merge(left, right.left) return right - -def insert(root: Node, key: int) -> Node: +def insert(root: Node, value: int) -> Node: """ Insert element - Split current tree with a key into l, r, + Split current tree with a value into left, right, Insert new node into the middle - Merge l, node, r into root + Merge left, node, right into root """ - node = Node(key) - l, r = split(root, key) - root = merge(l, node) - root = merge(root, r) - return root + node = Node(value) + left, right = split(root, value) + return merge(merge(left, node), right) - -def erase(root: Node, key: int) -> Node: +def erase(root: Node, value: int) -> Node: """ Erase element - Split all nodes with keys less into l, - Split all nodes with keys greater into r. - Merge l, r + Split all nodes with values less into left, + Split all nodes with values greater into right. + Merge left, right """ - l, r = split(root, key) - _, r = split(r, key + 1) - return merge(l, r) - + left, right = split(root, value-1) + _, right = split(right, value) + return merge(left, right) -def node_print(root: Node): +def inorder(root: Node): """ Just recursive print of a tree """ - if not root: + if not root: # None return - node_print(root.l) - print(root.key, end=" ") - node_print(root.r) + else: + inorder(root.left) + print(root.value, end=" ") + inorder(root.right) -def interactTreap(): +def interactTreap(root, args): """ Commands: - + key to add key into treap - - key to erase all nodes with key - - After each command, program prints treap + + value to add value into treap + - value to erase all nodes with value + + >>> root = interactTreap(None, "+1") + >>> inorder(root) + 1 + >>> root = interactTreap(root, "+3 +5 +17 +19 +2 +16 +4 +0") + >>> inorder(root) + 0 1 2 3 4 5 16 17 19 + >>> root = interactTreap(root, "+4 +4 +4") + >>> inorder(root) + 0 1 2 3 4 4 4 4 5 16 17 19 + >>> root = interactTreap(root, "-0") + >>> inorder(root) + 1 2 3 4 4 4 4 5 16 17 19 + >>> root = interactTreap(root, "-4") + >>> inorder(root) + 1 2 3 5 16 17 19 + >>> root = interactTreap(root, "=0") + Unknown command """ - root = None - while True: - cmd = input().split() - cmd[1] = int(cmd[1]) - if cmd[0] == "+": - root = insert(root, cmd[1]) - elif cmd[0] == "-": - root = erase(root, cmd[1]) + for arg in args.split(): + if arg[0] == "+": + root = insert(root, int(arg[1:])) + + elif arg[0] == "-": + root = erase(root, int(arg[1:])) + else: print("Unknown command") - node_print(root) + return root + +def main(): + """After each command, program prints treap""" + root = None + print("enter numbers to creat a tree, + value to add value into treap, - value to erase all nodes with value. 'q' to quit. ") + + args = input() + while args != 'q': + root = interactTreap(root, args) + print(root) + args = input() + + print("good by!") + pass if __name__ == "__main__": - interactTreap() + import doctest + doctest.testmod() + main() From 4590363806857d389c489b2e45330e7b9b26f52c Mon Sep 17 00:00:00 2001 From: Hrishikesh Suslade <41867989+hash84@users.noreply.github.com> Date: Fri, 18 Oct 2019 23:53:37 +0530 Subject: [PATCH 0272/1071] Added Pytests for Decission Tree mean_squared_error method (#1374) * Added Pytests for Decission Tree Modified the mean_squared_error to be a static method Created the Test_Decision_Tree class Consists of two methods 1. helper_mean_squared_error_test: This method calculates the mean squared error manually without using numpy. Instead a for loop is used for the same. 2. test_one_mean_squared_error: This method considers a simple test case and compares the results by the helper function and the original mean_squared_error method of Decision_Tree class. This is done using asert keyword. Execution: PyTest installation pip3 install pytest OR pip install pytest Test function execution pytest decision_tree.py * Modified the pytests to be compatible with the doctest Added 2 doctest in the mean_squared_error method For its verification a static method helper_mean_squared_error(labels, prediction) is used It uses a for loop to calculate the error instead of the numpy inbuilt methods Execution ``` pytest .\decision_tree.py --doctest-modules ``` --- machine_learning/decision_tree.py | 32 +++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/machine_learning/decision_tree.py b/machine_learning/decision_tree.py index 4f7a4d12966e..14c02b64df0c 100644 --- a/machine_learning/decision_tree.py +++ b/machine_learning/decision_tree.py @@ -21,6 +21,14 @@ def mean_squared_error(self, labels, prediction): @param labels: a one dimensional numpy array @param prediction: a floating point value return value: mean_squared_error calculates the error if prediction is used to estimate the labels + >>> tester = Decision_Tree() + >>> test_labels = np.array([1,2,3,4,5,6,7,8,9,10]) + >>> test_prediction = np.float(6) + >>> assert tester.mean_squared_error(test_labels, test_prediction) == Test_Decision_Tree.helper_mean_squared_error_test(test_labels, test_prediction) + >>> test_labels = np.array([1,2,3]) + >>> test_prediction = np.float(2) + >>> assert tester.mean_squared_error(test_labels, test_prediction) == Test_Decision_Tree.helper_mean_squared_error_test(test_labels, test_prediction) + """ if labels.ndim != 1: print("Error: Input labels must be one dimensional") @@ -117,6 +125,27 @@ def predict(self, x): print("Error: Decision tree not yet trained") return None +class Test_Decision_Tree: + """Decision Tres test class + """ + + @staticmethod + def helper_mean_squared_error_test(labels, prediction): + """ + helper_mean_squared_error_test: + @param labels: a one dimensional numpy array + @param prediction: a floating point value + return value: helper_mean_squared_error_test calculates the mean squared error + """ + squared_error_sum = np.float(0) + for label in labels: + squared_error_sum += ((label-prediction) ** 2) + + return np.float(squared_error_sum/labels.size) + + + + def main(): """ @@ -141,3 +170,6 @@ def main(): if __name__ == "__main__": main() + import doctest + + doctest.testmod(name="mean_squarred_error", verbose=True) From a7f3851939f83b76252ec79d89cc874e8162754d Mon Sep 17 00:00:00 2001 From: Jigyasa G Date: Fri, 18 Oct 2019 23:56:48 +0530 Subject: [PATCH 0273/1071] fuzzy operations added (#1310) * fuzzy operations added * fuzzy inference system added * unnecessary files removed * requirements added * Modified requirements for travis ci * Modified requirements for travis ci * Add scikit-fuzzy to requirements.txt --- fuzzy_logic/fuzzy_operations.py | 100 ++++++++++++++++++++++++++++++++ requirements.txt | 1 + 2 files changed, 101 insertions(+) create mode 100644 fuzzy_logic/fuzzy_operations.py diff --git a/fuzzy_logic/fuzzy_operations.py b/fuzzy_logic/fuzzy_operations.py new file mode 100644 index 000000000000..e497eabd1690 --- /dev/null +++ b/fuzzy_logic/fuzzy_operations.py @@ -0,0 +1,100 @@ +"""README, Author - Jigyasa Gandhi(mailto:jigsgandhi97@gmail.com) +Requirements: + - scikit-fuzzy + - numpy + - matplotlib +Python: + - 3.5 +""" +# Create universe of discourse in python using linspace () +import numpy as np +X = np.linspace(start=0, stop=75, num=75, endpoint=True, retstep=False) + +# Create two fuzzy sets by defining any membership function (trapmf(), gbellmf(),gaussmf(), etc). +import skfuzzy as fuzz +abc1=[0,25,50] +abc2=[25,50,75] +young = fuzz.membership.trimf(X,abc1) +middle_aged = fuzz.membership.trimf(X,abc2) + +# Compute the different operations using inbuilt functions. +one = np.ones(75) +zero = np.zeros((75,)) +#1. Union = max(µA(x), µB(x)) +union = fuzz.fuzzy_or(X, young, X, middle_aged)[1] +#2. Intersection = min(µA(x), µB(x)) +intersection = fuzz.fuzzy_and(X, young, X, middle_aged)[1] +#3. Complement (A) = (1- min(µA(x)) +complement_a = fuzz.fuzzy_not(young) +#4. Difference (A/B) = min(µA(x),(1- µB(x))) +difference = fuzz.fuzzy_and(X, young, X, fuzz.fuzzy_not(middle_aged)[1])[1] +#5. Algebraic Sum = [µA(x) + µB(x) – (µA(x) * µB(x))] +alg_sum = young + middle_aged - (young*middle_aged) +#6. Algebraic Product = (µA(x) * µB(x)) +alg_product = young*middle_aged +#7. Bounded Sum = min[1,(µA(x), µB(x))] +bdd_sum = fuzz.fuzzy_and(X, one, X, young+middle_aged)[1] +#8. Bounded difference = min[0,(µA(x), µB(x))] +bdd_difference = fuzz.fuzzy_or(X, zero, X, young-middle_aged)[1] + +#max-min composition +#max-product composition + + +# Plot each set A, set B and each operation result using plot() and subplot(). +import matplotlib.pyplot as plt + +plt.figure() + +plt.subplot(4,3,1) +plt.plot(X,young) +plt.title("Young") +plt.grid(True) + +plt.subplot(4,3,2) +plt.plot(X,middle_aged) +plt.title("Middle aged") +plt.grid(True) + +plt.subplot(4,3,3) +plt.plot(X,union) +plt.title("union") +plt.grid(True) + +plt.subplot(4,3,4) +plt.plot(X,intersection) +plt.title("intersection") +plt.grid(True) + +plt.subplot(4,3,5) +plt.plot(X,complement_a) +plt.title("complement_a") +plt.grid(True) + +plt.subplot(4,3,6) +plt.plot(X,difference) +plt.title("difference a/b") +plt.grid(True) + +plt.subplot(4,3,7) +plt.plot(X,alg_sum) +plt.title("alg_sum") +plt.grid(True) + +plt.subplot(4,3,8) +plt.plot(X,alg_product) +plt.title("alg_product") +plt.grid(True) + +plt.subplot(4,3,9) +plt.plot(X,bdd_sum) +plt.title("bdd_sum") +plt.grid(True) + +plt.subplot(4,3,10) +plt.plot(X,bdd_difference) +plt.title("bdd_difference") +plt.grid(True) + +plt.subplots_adjust(hspace = 0.5) +plt.show() diff --git a/requirements.txt b/requirements.txt index f5790ad53c30..4f6ff321c268 100644 --- a/requirements.txt +++ b/requirements.txt @@ -8,6 +8,7 @@ pandas pillow pytest requests +scikit-fuzzy sklearn sympy tensorflow From 5ef5f67a51b658d768c6bf6f2f4e68c45736ca15 Mon Sep 17 00:00:00 2001 From: Jai Kumar Dewani Date: Sat, 19 Oct 2019 00:44:01 +0530 Subject: [PATCH 0274/1071] Added more details about the problem statement (#1367) * Update DIRECTORY * Updated DIRECTORY * Fixed bug in directory build and re-build the directory.md * fixed url issue * fixed indentation in Directory.md * Add problem-18 of project-euler * Delete sol1.py * Delete files * Added more details to question * Added doctest in printNGE() * Made changes to fix Travis CI build * Remove the trailing whitespace --- data_structures/stacks/next_greater_element.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/data_structures/stacks/next_greater_element.py b/data_structures/stacks/next_greater_element.py index 02a86196f5bf..29a039b9698b 100644 --- a/data_structures/stacks/next_greater_element.py +++ b/data_structures/stacks/next_greater_element.py @@ -1,6 +1,13 @@ -# Function to print element and NGE pair for all elements of list def printNGE(arr): - + """ + Function to print element and Next Greatest Element (NGE) pair for all elements of list + NGE - Maximum element present afterwards the current one which is also greater than current one + >>> printNGE([11,13,21,3]) + 11 -- 13 + 13 -- 21 + 21 -- -1 + 3 -- -1 + """ for i in range(0, len(arr), 1): next = -1 From 43f99e56c969b46d3ea65593d0a20e9d9896ed0f Mon Sep 17 00:00:00 2001 From: kunal kumar barman Date: Sat, 19 Oct 2019 03:00:52 +0530 Subject: [PATCH 0275/1071] Python program that surfs 3 site at a time (#1389) * Python program that scrufs 3 site at a time add input in the compiling time like -- python3 project1.py (man) * Update project1.py * noqa: F401 and reformat with black * Rename project1.py to web_programming/crawl_google_results.py * Add beautifulsoup4 to requirements.txt * Add fake_useragent to requirements.txt * Update crawl_google_results.py * headers={"UserAgent": UserAgent().random} * html.parser, not lxml * link, not links --- requirements.txt | 2 ++ web_programming/crawl_google_results.py | 20 ++++++++++++++++++++ 2 files changed, 22 insertions(+) create mode 100644 web_programming/crawl_google_results.py diff --git a/requirements.txt b/requirements.txt index 4f6ff321c268..824f534a245f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,6 @@ +beautifulsoup4 black +fake_useragent flake8 matplotlib mypy diff --git a/web_programming/crawl_google_results.py b/web_programming/crawl_google_results.py new file mode 100644 index 000000000000..c31ec1526d3e --- /dev/null +++ b/web_programming/crawl_google_results.py @@ -0,0 +1,20 @@ +import sys +import webbrowser + +from bs4 import BeautifulSoup +from fake_useragent import UserAgent +import requests + +print("Googling.....") +url = "https://www.google.com/search?q=" + " ".join(sys.argv[1:]) +res = requests.get(url, headers={"UserAgent": UserAgent().random}) +# res.raise_for_status() +with open("project1a.html", "wb") as out_file: # only for knowing the class + for data in res.iter_content(10000): + out_file.write(data) +soup = BeautifulSoup(res.text, "html.parser") +links = list(soup.select(".eZt8xd"))[:5] + +print(len(links)) +for link in links: + webbrowser.open(f"http://google.com{link.get('href')}") From 83667826884fae7aa3dfbcf3b73aedab9922a356 Mon Sep 17 00:00:00 2001 From: Swati Prajapati <42577922+swatiprajapati08@users.noreply.github.com> Date: Sat, 19 Oct 2019 03:13:33 +0530 Subject: [PATCH 0276/1071] Create ActivitySelection (#1384) * Create ActivitySelection * Update and rename ActivitySelection to activity_selection.py * Update activity_selection.py * Update activity_selection.py * Update activity_selection.py * Update activity_selection.py * Update activity_selection.py * Update activity_selection.py * Rename activity_selection.py to other/activity_selection.py * Update activity_selection.py * Update activity_selection.py * Add a doctest * print(j, end=" ") * print(i, end=" ") * colons * Add trailing space --- other/activity_selection.py | 43 +++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 other/activity_selection.py diff --git a/other/activity_selection.py b/other/activity_selection.py new file mode 100644 index 000000000000..5c14df7d6aa7 --- /dev/null +++ b/other/activity_selection.py @@ -0,0 +1,43 @@ +"""The following implementation assumes that the activities +are already sorted according to their finish time""" + +"""Prints a maximum set of activities that can be done by a +single person, one at a time""" +# n --> Total number of activities +# start[]--> An array that contains start time of all activities +# finish[] --> An array that contains finish time of all activities + +def printMaxActivities(start, finish): + """ + >>> start = [1, 3, 0, 5, 8, 5] + >>> finish = [2, 4, 6, 7, 9, 9] + >>> printMaxActivities(start, finish) + The following activities are selected: + 0 1 3 4 + """ + n = len(finish) + print("The following activities are selected:") + + # The first activity is always selected + i = 0 + print(i, end=" ") + + # Consider rest of the activities + for j in range(n): + + # If this activity has start time greater than + # or equal to the finish time of previously + # selected activity, then select it + if start[j] >= finish[i]: + print(j, end=" ") + i = j + +# Driver program to test above function +start = [1, 3, 0, 5, 8, 5] +finish = [2, 4, 6, 7, 9, 9] +printMaxActivities(start, finish) + +""" +The following activities are selected: +0 1 3 4 +""" From 86a2d5fd037e29613171c84576f0eb0ea6e3d1e7 Mon Sep 17 00:00:00 2001 From: Iqrar Agalosi Nureyza Date: Sat, 19 Oct 2019 04:49:30 +0700 Subject: [PATCH 0277/1071] fixed some typos (#1392) missing 'c' in eulidianLength and eulidian. Should be euclidianLength and euclidian. --- linear_algebra/src/lib.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/linear_algebra/src/lib.py b/linear_algebra/src/lib.py index 090427d9a520..bf9e0d302a89 100644 --- a/linear_algebra/src/lib.py +++ b/linear_algebra/src/lib.py @@ -37,7 +37,7 @@ class Vector(object): __str__() : toString method component(i : int): gets the i-th component (start by 0) __len__() : gets the size of the vector (number of components) - euclidLength() : returns the eulidean length of the vector. + euclidLength() : returns the euclidean length of the vector. operator + : vector addition operator - : vector subtraction operator * : scalar multiplication and dot product @@ -88,9 +88,9 @@ def __len__(self): """ return len(self.__components) - def eulidLength(self): + def euclidLength(self): """ - returns the eulidean length of the vector + returns the euclidean length of the vector """ summe = 0 for c in self.__components: From e0158c2c30a8273a6780fd9f83c36be6582e9e91 Mon Sep 17 00:00:00 2001 From: Kumar Shivam <53289673+kshivi99@users.noreply.github.com> Date: Sat, 19 Oct 2019 03:22:32 +0530 Subject: [PATCH 0278/1071] fixed project eular readme (#1391) --- project_euler/README.md | 105 +--------------------------------------- 1 file changed, 1 insertion(+), 104 deletions(-) diff --git a/project_euler/README.md b/project_euler/README.md index 89b6d63b5744..f80d58ea0038 100644 --- a/project_euler/README.md +++ b/project_euler/README.md @@ -8,107 +8,4 @@ insights to solve. Project Euler is ideal for mathematicians who are learning to Here the efficiency of your code is also checked. I've tried to provide all the best possible solutions. -PROBLEMS: - -1. If we list all the natural numbers below 10 that are multiples of 3 or 5, we get 3,5,6 and 9. The sum of these multiples is 23. - Find the sum of all the multiples of 3 or 5 below N. - -2. Each new term in the Fibonacci sequence is generated by adding the previous two terms. By starting with 1 and 2, - the first 10 terms will be: - 1,2,3,5,8,13,21,34,55,89,.. - By considering the terms in the Fibonacci sequence whose values do not exceed n, find the sum of the even-valued terms. - e.g. for n=10, we have {2,8}, sum is 10. - -3. The prime factors of 13195 are 5,7,13 and 29. What is the largest prime factor of a given number N? - e.g. for 10, largest prime factor = 5. For 17, largest prime factor = 17. - -4. A palindromic number reads the same both ways. The largest palindrome made from the product of two 2-digit numbers is 9009 = 91 × 99. - Find the largest palindrome made from the product of two 3-digit numbers which is less than N. - -5. 2520 is the smallest number that can be divided by each of the numbers from 1 to 10 without any remainder. - What is the smallest positive number that is evenly divisible(divisible with no remainder) by all of the numbers from 1 to N? - -6. The sum of the squares of the first ten natural numbers is, - 1^2 + 2^2 + ... + 10^2 = 385 - The square of the sum of the first ten natural numbers is, - (1 + 2 + ... + 10)^2 = 552 = 3025 - Hence the difference between the sum of the squares of the first ten natural numbers and the square of the sum is 3025 − 385 = 2640. - Find the difference between the sum of the squares of the first N natural numbers and the square of the sum. - -7. By listing the first six prime numbers: 2, 3, 5, 7, 11, and 13, we can see that the 6th prime is 13. - What is the Nth prime number? - -8. Find the consecutive k digits in a number N that have the largest product. - -9. A Pythagorean triplet is a set of three natural numbers, a < b < c, for which, - a^2 + b^2 = c^2 - There exists exactly one Pythagorean triplet for which a + b + c = 1000. - Find the product abc. - -10. Find sum of all prime numbers below 2 million. - -11. In the given 20x20 grid, find 4 adjacent numbers (horizontally, vertically or diagonally) that have the largest product. - -12. The sequence of triangle numbers is generated by adding the natural numbers. So the 7th triangle number would be 1 + 2 + 3 + 4 + 5 + 6 + 7 = 28. The first ten terms would be: - - 1, 3, 6, 10, 15, 21, 28, 36, 45, 55, ... - - Let us list the factors of the first seven triangle numbers: - - 1: 1 - 3: 1,3 - 6: 1,2,3,6 - 10: 1,2,5,10 - 15: 1,3,5,15 - 21: 1,3,7,21 - 28: 1,2,4,7,14,28 - We can see that 28 is the first triangle number to have over five divisors. - - What is the value of the first triangle number to have over five hundred divisors? - -13. Work out the first 10 digits of the sum of the given hundred 50 digit numbers. - -14. The following iterative sequence is defined for the set of positive integers: - n → n/2 (n is even) - n → 3n + 1 (n is odd) - Using the rule above and starting with 13, we generate the following sequence: - 13 → 40 → 20 → 10 → 5 → 16 → 8 → 4 → 2 → 1 - Which starting number, under one million, produces the longest chain? - -15. Starting from top left corner of a 20x20 grid how many routes are there to reach the bottom left corner? - -16. 2^15 = 32768 and the sum of its digits is 3 + 2 + 7 + 6 + 8 = 26. - What is the sum of the digits of the number 2^1000? - -17. If the numbers 1 through 1000 were written in words, how many total letters would be used? - -18. By starting at the top of the triangle below and moving to adjacent numbers on the row below, the maximum total from top to bottom is 23. - 3 - 7 4 - 2 4 6 -8 5 9 3 - -That is, 3 + 7 + 4 + 9 = 23. - -Find the maximum total from top to bottom of the triangle below: - - 75 - 95 64 - 17 47 82 - 18 35 87 10 - 20 04 82 47 65 - 19 01 23 75 03 34 - 88 02 77 73 07 63 67 - 99 65 04 28 06 16 70 92 - 41 41 26 56 83 40 80 70 33 - 41 48 72 33 47 32 37 16 94 29 - 53 71 44 65 25 43 91 52 97 51 14 - 70 11 33 28 77 73 17 78 39 68 17 57 - 91 71 52 38 17 14 91 43 58 50 27 29 48 - 63 66 04 68 89 53 67 30 73 16 69 87 40 31 -04 62 98 27 23 09 70 98 73 93 38 53 60 04 23 - -20. n! means n × (n − 1) × ... × 3 × 2 × 1 - For example, 10! = 10 × 9 × ... × 3 × 2 × 1 = 3628800, - and the sum of the digits in the number 10! is 3 + 6 + 2 + 8 + 8 + 0 + 0 = 27. - Find the sum of the digits in the number 100! +For description of the problem statements, kindly visit https://projecteuler.net/show=all From acd962b2b6ddbcd5cee9b70a6feee13cbe12bcfc Mon Sep 17 00:00:00 2001 From: Sourav kumar <33771969+souravs17031999@users.noreply.github.com> Date: Sat, 19 Oct 2019 03:32:32 +0530 Subject: [PATCH 0279/1071] adding program to print diamond pattern (#1338) * adding program to print diamond pattern Written a program to print diamond pattern with stars in python 3.7 * update - changing strings to r strings --- other/magicdiamondpattern.py | 53 ++++++++++++++++++++++++++++++++++++ 1 file changed, 53 insertions(+) create mode 100644 other/magicdiamondpattern.py diff --git a/other/magicdiamondpattern.py b/other/magicdiamondpattern.py new file mode 100644 index 000000000000..024fbd0f569a --- /dev/null +++ b/other/magicdiamondpattern.py @@ -0,0 +1,53 @@ +# Python program for generating diamond pattern in python 3.7+ + +# Function to print upper half of diamond (pyramid) +def floyd(n): + ''' + Parameters: + n : size of pattern + ''' + for i in range(0, n): + for j in range(0, n-i-1): # printing spaces + print(" ", end = "") + for k in range(0, i + 1): # printing stars + print("* ", end = "") + print() + + +# Function to print lower half of diamond (pyramid) +def reverse_floyd(n): + ''' + Parameters: + n : size of pattern + ''' + for i in range(n, 0, -1): + for j in range(i, 0, -1): # printing stars + print("* ", end = "") + print() + for k in range(n-i+1, 0, -1): # printing spaces + print(" ", end = "") + +# Function to print complete diamond pattern of "*" +def pretty_print(n): + ''' + Parameters: + n : size of pattern + ''' + if n <= 0: + print(" ... .... nothing printing :(") + return + floyd(n) # upper half + reverse_floyd(n) # lower half + + +if __name__ == "__main__": + print(r"| /\ | |- | |- |--| |\ /| |-") + print(r"|/ \| |- |_ |_ |__| | \/ | |_") + K = 1 + while(K): + user_number = int(input("enter the number and , and see the magic : ")) + print() + pretty_print(user_number) + K = int(input("press 0 to exit... and 1 to continue...")) + + print("Good Bye...") From 5c351d81bf12e72030aff385275e8aa50846456d Mon Sep 17 00:00:00 2001 From: Alfin_William Date: Sat, 19 Oct 2019 09:32:38 +0530 Subject: [PATCH 0280/1071] Implementation of Hardy Ramanujan Algorithm in /maths (#1355) * Implementation of Hardy Ramanujan Algorithm * added docstrings * added doctests * Run Python black on the code * Travis CI: Upgrade to Python 3.8 * Revert to Python 3.7 --- .travis.yml | 1 - maths/hardy_ramanujanalgo.py | 45 ++++++++++++++++++++++++++++++++++++ 2 files changed, 45 insertions(+), 1 deletion(-) create mode 100644 maths/hardy_ramanujanalgo.py diff --git a/.travis.yml b/.travis.yml index be227df1fdbd..877dbee9ade2 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,5 +1,4 @@ language: python -dist: xenial # required for Python >= 3.7 python: 3.7 cache: pip before_install: pip install --upgrade pip setuptools diff --git a/maths/hardy_ramanujanalgo.py b/maths/hardy_ramanujanalgo.py new file mode 100644 index 000000000000..bb31a1be49fb --- /dev/null +++ b/maths/hardy_ramanujanalgo.py @@ -0,0 +1,45 @@ +# This theorem states that the number of prime factors of n +# will be approximately log(log(n)) for most natural numbers n + +import math + + +def exactPrimeFactorCount(n): + """ + >>> exactPrimeFactorCount(51242183) + 3 + """ + count = 0 + if n % 2 == 0: + count += 1 + while n % 2 == 0: + n = int(n / 2) + # the n input value must be odd so that + # we can skip one element (ie i += 2) + + i = 3 + + while i <= int(math.sqrt(n)): + if n % i == 0: + count += 1 + while n % i == 0: + n = int(n / i) + i = i + 2 + + # this condition checks the prime + # number n is greater than 2 + + if n > 2: + count += 1 + return count + + +if __name__ == "__main__": + n = 51242183 + print(f"The number of distinct prime factors is/are {exactPrimeFactorCount(n)}") + print("The value of log(log(n)) is {0:.4f}".format(math.log(math.log(n)))) + + """ + The number of distinct prime factors is/are 3 + The value of log(log(n)) is 2.8765 + """ From dbf904f4381132dbe8d1de349dc4f4de2a7b4342 Mon Sep 17 00:00:00 2001 From: Stephen <24819660+infrontoftheforest@users.noreply.github.com> Date: Sat, 19 Oct 2019 09:11:05 +0000 Subject: [PATCH 0281/1071] added runge-kutta (#1393) --- maths/runge_kutta.py | 44 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 maths/runge_kutta.py diff --git a/maths/runge_kutta.py b/maths/runge_kutta.py new file mode 100644 index 000000000000..b0ba9025883f --- /dev/null +++ b/maths/runge_kutta.py @@ -0,0 +1,44 @@ +import numpy as np + + +def runge_kutta(f, y0, x0, h, x_end): + """ + Calculate the numeric solution at each step to the ODE f(x, y) using RK4 + + https://en.wikipedia.org/wiki/Runge-Kutta_methods + + Arguments: + f -- The ode as a function of x and y + y0 -- the initial value for y + x0 -- the initial value for x + h -- the stepsize + x_end -- the end value for x + + >>> # the exact solution is math.exp(x) + >>> def f(x, y): + ... return y + >>> y0 = 1 + >>> y = runge_kutta(f, y0, 0.0, 0.01, 5) + >>> y[-1] + 148.41315904125113 + """ + N = int(np.ceil((x_end - x0)/h)) + y = np.zeros((N + 1,)) + y[0] = y0 + x = x0 + + for k in range(N): + k1 = f(x, y[k]) + k2 = f(x + 0.5*h, y[k] + 0.5*h*k1) + k3 = f(x + 0.5*h, y[k] + 0.5*h*k2) + k4 = f(x + h, y[k] + h * k3) + y[k + 1] = y[k] + (1/6)*h*(k1 + 2*k2 + 2*k3 + k4) + x += h + + return y + + +if __name__ == "__main__": + import doctest + + doctest.testmod() From ab65a3915c063bfc31102512bb3c9a755bef7ed0 Mon Sep 17 00:00:00 2001 From: ayush246 <37112252+ayush246@users.noreply.github.com> Date: Sat, 19 Oct 2019 16:08:15 +0530 Subject: [PATCH 0282/1071] Double sort (Added with required updates) (#1399) * Added with required updates * Updated * required updates * Update double_sort.py * Update double_sort.py --- sorts/double_sort.py | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 sorts/double_sort.py diff --git a/sorts/double_sort.py b/sorts/double_sort.py new file mode 100644 index 000000000000..011e17d8f035 --- /dev/null +++ b/sorts/double_sort.py @@ -0,0 +1,34 @@ +def double_sort(lst): + """this sorting algorithm sorts an array using the principle of bubble sort , + but does it both from left to right and right to left , + hence i decided to call it "double sort" + :param collection: mutable ordered sequence of elements + :return: the same collection in ascending order + Examples: + >>> double_sort([-1 ,-2 ,-3 ,-4 ,-5 ,-6 ,-7]) + [-7, -6, -5, -4, -3, -2, -1] + >>> double_sort([]) + [] + >>> double_sort([-1 ,-2 ,-3 ,-4 ,-5 ,-6]) + [-6, -5, -4, -3, -2, -1] + >>> double_sort([-3, 10, 16, -42, 29]) == sorted([-3, 10, 16, -42, 29]) + True + """ + no_of_elements=len(lst) + for i in range(0,int(((no_of_elements-1)/2)+1)): # we dont need to traverse to end of list as + for j in range(0,no_of_elements-1): + if (lst[j+1] Date: Sat, 19 Oct 2019 23:44:37 +0530 Subject: [PATCH 0283/1071] Issue #1397 (#1403) --- .../linked_list/singly_linked_list.py | 58 +++++++++---------- 1 file changed, 29 insertions(+), 29 deletions(-) diff --git a/data_structures/linked_list/singly_linked_list.py b/data_structures/linked_list/singly_linked_list.py index 16436ff90274..73b982316e76 100644 --- a/data_structures/linked_list/singly_linked_list.py +++ b/data_structures/linked_list/singly_linked_list.py @@ -6,56 +6,56 @@ def __init__(self, data): class Linked_List: def __init__(self): - self.Head = None # Initialize Head to None + self.head = None # Initialize head to None def insert_tail(self, data): - if self.Head is None: + if self.head is None: self.insert_head(data) # If this is first node, call insert_head else: - temp = self.Head + temp = self.head while temp.next != None: # traverse to last node temp = temp.next temp.next = Node(data) # create node & link to tail def insert_head(self, data): newNod = Node(data) # create a new node - if self.Head != None: - newNod.next = self.Head # link newNode to head - self.Head = newNod # make NewNode as Head + if self.head != None: + newNod.next = self.head # link newNode to head + self.head = newNod # make NewNode as head def printList(self): # print every node data - tamp = self.Head - while tamp is not None: - print(tamp.data) - tamp = tamp.next + temp = self.head + while temp is not None: + print(temp.data) + temp = temp.next def delete_head(self): # delete from head - temp = self.Head - if self.Head != None: - self.Head = self.Head.next + temp = self.head + if self.head != None: + self.head = self.head.next temp.next = None return temp def delete_tail(self): # delete from tail - tamp = self.Head - if self.Head != None: - if self.Head.next is None: # if Head is the only Node in the Linked List - self.Head = None + temp = self.head + if self.head != None: + if self.head.next is None: # if head is the only Node in the Linked List + self.head = None else: - while tamp.next.next is not None: # find the 2nd last element - tamp = tamp.next - tamp.next, tamp = ( + while temp.next.next is not None: # find the 2nd last element + temp = temp.next + temp.next, temp = ( None, - tamp.next, - ) # (2nd last element).next = None and tamp = last element - return tamp + temp.next, + ) # (2nd last element).next = None and temp = last element + return temp def isEmpty(self): - return self.Head is None # Return if Head is none + return self.head is None # Return if head is none def reverse(self): prev = None - current = self.Head + current = self.head while current: # Store the current node's next node. @@ -67,15 +67,15 @@ def reverse(self): # Make the current node the next node (to progress iteration) current = next_node # Return prev in order to put the head at the end - self.Head = prev + self.head = prev def main(): A = Linked_List() - print("Inserting 1st at Head") + print("Inserting 1st at head") a1 = input() A.insert_head(a1) - print("Inserting 2nd at Head") + print("Inserting 2nd at head") a2 = input() A.insert_head(a2) print("\nPrint List : ") @@ -88,7 +88,7 @@ def main(): A.insert_tail(a4) print("\nPrint List : ") A.printList() - print("\nDelete Head") + print("\nDelete head") A.delete_head() print("Delete Tail") A.delete_tail() From 38d7e7073affe758557636ea201615d17d24fa97 Mon Sep 17 00:00:00 2001 From: Sujitkumar Singh <37760194+SinghSujitkumar@users.noreply.github.com> Date: Sun, 20 Oct 2019 01:42:54 +0530 Subject: [PATCH 0284/1071] The time complexity of every algorithms make its value (#1401) * added timer in bubble sort * Updated time of execution * import time in main only * Update bubble_sort.py * start = time.process_time() --- sorts/bubble_sort.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/sorts/bubble_sort.py b/sorts/bubble_sort.py index ccd8a2e11ee1..4faa40da1d8e 100644 --- a/sorts/bubble_sort.py +++ b/sorts/bubble_sort.py @@ -6,13 +6,13 @@ def bubble_sort(collection): :return: the same collection ordered by ascending Examples: - >>> bubble_sort([0, 5, 3, 2, 2]) + >>> bubble_sort([0, 5, 2, 3, 2]) [0, 2, 2, 3, 5] >>> bubble_sort([]) [] - >>> bubble_sort([-2, -5, -45]) + >>> bubble_sort([-2, -45, -5]) [-45, -5, -2] >>> bubble_sort([-23, 0, 6, -4, 34]) @@ -29,11 +29,14 @@ def bubble_sort(collection): swapped = True collection[j], collection[j + 1] = collection[j + 1], collection[j] if not swapped: - break # Stop iteration if the collection is sorted. + break # Stop iteration if the collection is sorted. return collection if __name__ == "__main__": + import time user_input = input("Enter numbers separated by a comma:").strip() unsorted = [int(item) for item in user_input.split(",")] + start = time.process_time() print(*bubble_sort(unsorted), sep=",") + print(f"Processing time: {time.process_time() - start}") From 313a043107bc4882623ad5524a96fbb099d0d161 Mon Sep 17 00:00:00 2001 From: Archana Prabhu Date: Sun, 20 Oct 2019 14:10:40 +0530 Subject: [PATCH 0285/1071] Create autocomplete_using_trie.py (#1406) * Create autocomplete_using_trie.py The program aims to design a trie implementation for autocomplete which is easy to understand and ready to run. * Removed unused import * Updated the list value * Update autocomplete_using_trie.py * Run the code through Black and add doctest --- other/autocomplete_using_trie.py | 64 ++++++++++++++++++++++++++++++++ 1 file changed, 64 insertions(+) create mode 100644 other/autocomplete_using_trie.py diff --git a/other/autocomplete_using_trie.py b/other/autocomplete_using_trie.py new file mode 100644 index 000000000000..eb906f8efa9a --- /dev/null +++ b/other/autocomplete_using_trie.py @@ -0,0 +1,64 @@ +END = "#" + + +class Trie: + def __init__(self): + self._trie = {} + + def insert_word(self, text): + trie = self._trie + for char in text: + if char not in trie: + trie[char] = {} + trie = trie[char] + trie[END] = True + + def find_word(self, prefix): + trie = self._trie + for char in prefix: + if char in trie: + trie = trie[char] + else: + return [] + return self._elements(trie) + + def _elements(self, d): + result = [] + for c, v in d.items(): + if c == END: + subresult = [" "] + else: + subresult = [c + s for s in self._elements(v)] + result.extend(subresult) + return tuple(result) + + +trie = Trie() +words = ("depart", "detergent", "daring", "dog", "deer", "deal") +for word in words: + trie.insert_word(word) + + +def autocomplete_using_trie(s): + """ + >>> trie = Trie() + >>> for word in words: + ... trie.insert_word(word) + ... + >>> matches = autocomplete_using_trie("de") + + "detergent " in matches + True + "dog " in matches + False + """ + suffixes = trie.find_word(s) + return tuple(s + w for w in suffixes) + + +def main(): + print(autocomplete_using_trie("de")) + + +if __name__ == "__main__": + main() From afeb13bbc8a43bfb427cb6f730e014b1ca1f7a6d Mon Sep 17 00:00:00 2001 From: Stephen <24819660+infrontoftheforest@users.noreply.github.com> Date: Mon, 21 Oct 2019 17:19:43 +0000 Subject: [PATCH 0286/1071] added explicit euler's method (#1394) * added explicit euler's method * update explicit_euler.py variable names --- maths/explicit_euler.py | 41 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 maths/explicit_euler.py diff --git a/maths/explicit_euler.py b/maths/explicit_euler.py new file mode 100644 index 000000000000..9fce4e4185a6 --- /dev/null +++ b/maths/explicit_euler.py @@ -0,0 +1,41 @@ +import numpy as np + + +def explicit_euler(ode_func, y0, x0, stepsize, x_end): + """ + Calculate numeric solution at each step to an ODE using Euler's Method + + https://en.wikipedia.org/wiki/Euler_method + + Arguments: + ode_func -- The ode as a function of x and y + y0 -- the initial value for y + x0 -- the initial value for x + stepsize -- the increment value for x + x_end -- the end value for x + + >>> # the exact solution is math.exp(x) + >>> def f(x, y): + ... return y + >>> y0 = 1 + >>> y = explicit_euler(f, y0, 0.0, 0.01, 5) + >>> y[-1] + 144.77277243257308 + """ + N = int(np.ceil((x_end - x0)/stepsize)) + y = np.zeros((N + 1,)) + y[0] = y0 + x = x0 + + for k in range(N): + y[k + 1] = y[k] + stepsize*ode_func(x, y[k]) + x += stepsize + + return y + + +if __name__ == "__main__": + import doctest + + doctest.testmod() + From a06995a686c0509f50a481e7d7d41bb35ffe8f19 Mon Sep 17 00:00:00 2001 From: Shubham Lad <30789414+ShuLaPy@users.noreply.github.com> Date: Mon, 21 Oct 2019 23:40:19 +0530 Subject: [PATCH 0287/1071] add simple improved Sieve Of Eratosthenes Algorithm (#1412) * add simple improved Sieve Of Eratosthenes Algorithm * added doctests * name changed --- maths/prime_sieve_eratosthenes.py | 41 +++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 maths/prime_sieve_eratosthenes.py diff --git a/maths/prime_sieve_eratosthenes.py b/maths/prime_sieve_eratosthenes.py new file mode 100644 index 000000000000..7d039aaadd7d --- /dev/null +++ b/maths/prime_sieve_eratosthenes.py @@ -0,0 +1,41 @@ +''' +Sieve of Eratosthenes + +Input : n =10 +Output : 2 3 5 7 + +Input : n = 20 +Output: 2 3 5 7 11 13 17 19 + +you can read in detail about this at +https://en.wikipedia.org/wiki/Sieve_of_Eratosthenes +''' + +def prime_sieve_eratosthenes(num): + """ + print the prime numbers upto n + + >>> prime_sieve_eratosthenes(10) + 2 3 5 7 + >>> prime_sieve_eratosthenes(20) + 2 3 5 7 11 13 17 19 + """ + + + primes = [True for i in range(num + 1)] + p = 2 + + while p * p <= num: + if primes[p] == True: + for i in range(p*p, num+1, p): + primes[i] = False + p+=1 + + for prime in range(2, num+1): + if primes[prime]: + print(prime, end=" ") + +if __name__ == "__main__": + num = int(input()) + + prime_sieve_eratosthenes(num) From 67aa3cfb4dd50e1d6feff6a36f630b374e2ab922 Mon Sep 17 00:00:00 2001 From: akankshamahajan99 <47140026+akankshamahajan99@users.noreply.github.com> Date: Tue, 22 Oct 2019 01:35:12 +0530 Subject: [PATCH 0288/1071] Added alternative way to generate password in password_generator.py (#1415) --- other/password_generator.py | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/other/password_generator.py b/other/password_generator.py index f72686bfb1c0..b4c7999ca44a 100644 --- a/other/password_generator.py +++ b/other/password_generator.py @@ -1,5 +1,5 @@ """Password generator allows you to generate a random password of length N.""" -from random import choice +from random import choice, shuffle from string import ascii_letters, digits, punctuation @@ -26,9 +26,22 @@ def password_generator(length=8): def alternative_password_generator(ctbi, i): # Password generator = full boot with random_number, random_letters, and # random_character FUNCTIONS - pass # Put your code here... - + # Put your code here... + i = i - len(ctbi) + quotient = int(i / 3) + remainder = i % 3 + #chars = ctbi + random_letters(ascii_letters, i / 3 + remainder) + random_number(digits, i / 3) + random_characters(punctuation, i / 3) + chars = ctbi + random(ascii_letters, quotient + remainder) + random(digits, quotient) + random(punctuation, quotient) + chars = list(chars) + shuffle(chars) + return "".join(chars) + + #random is a generalised function for letters, characters and numbers +def random(ctbi, i): + return "".join(choice(ctbi) for x in range(i)) + + def random_number(ctbi, i): pass # Put your code here... @@ -43,7 +56,9 @@ def random_characters(ctbi, i): def main(): length = int(input("Please indicate the max length of your password: ").strip()) + ctbi = input("Please indicate the characters that must be in your password: ").strip() print("Password generated:", password_generator(length)) + print("Alternative Password generated:", alternative_password_generator(ctbi, length)) print("[If you are thinking of using this passsword, You better save it.]") From f93cce66a634746839a78624af4d7b8aab205808 Mon Sep 17 00:00:00 2001 From: moita69 Date: Mon, 21 Oct 2019 17:36:33 -0300 Subject: [PATCH 0289/1071] some pytest on math folder (#1405) * some pytest on math folder * Run the test function via a doctest Also format the code with psf/black as discussed in CONTRIBUTING.md * Update abs.py * Update average_mean.py --- maths/3n+1.py | 19 ++++++++++--------- maths/abs.py | 20 +++++++++++++------- maths/average_mean.py | 16 ++++++++++------ 3 files changed, 33 insertions(+), 22 deletions(-) diff --git a/maths/3n+1.py b/maths/3n+1.py index 6b2dfc785794..ff769550297c 100644 --- a/maths/3n+1.py +++ b/maths/3n+1.py @@ -23,15 +23,16 @@ def n31(a: int) -> Tuple[List[int], int]: return path, len(path) -def main(): - num = 4 - path, length = n31(num) - print( - "The Collatz sequence of {0} took {1} steps. \nPath: {2}".format( - num, length, path - ) - ) +def test_n31(): + """ + >>> test_n31() + """ + assert n31(4) == ([4, 2, 1], 3) + assert n31(11) == ([11, 34, 17, 52, 26, 13, 40, 20, 10, 5, 16, 8, 4, 2, 1], 15) + assert n31(31) == ([31, 94, 47, 142, 71, 214, 107, 322, 161, 484, 242, 121, 364, 182, 91, 274, 137, 412, 206, 103, 310, 155, 466, 233, 700, 350, 175, 526, 263, 790, 395, 1186, 593, 1780, 890, 445, 1336, 668, 334, 167, 502, 251, 754, 377, 1132, 566, 283, 850, 425, 1276, 638, 319, 958, 479, 1438, 719, 2158, 1079, 3238, 1619, 4858, 2429, 7288, 3644, 1822, 911, 2734, 1367, 4102, 2051, 6154, 3077, 9232, 4616, 2308, 1154, 577, 1732, 866, 433, 1300, 650, 325, 976, 488, 244, 122, 61, 184, 92, 46, 23, 70, 35, 106, 53, 160, 80, 40, 20, 10, 5, 16, 8, 4, 2, 1], 107) if __name__ == "__main__": - main() + num = 4 + path, length = n31(num) + print(f"The Collatz sequence of {num} took {length} steps. \nPath: {path}") diff --git a/maths/abs.py b/maths/abs.py index 7509c5c20a22..68c99a1d51d8 100644 --- a/maths/abs.py +++ b/maths/abs.py @@ -5,18 +5,24 @@ def abs_val(num): """ Find the absolute value of a number. - >>abs_val(-5) - 5 - >>abs_val(0) + >>> abs_val(-5.1) + 5.1 + >>> abs_val(-5) == abs_val(5) + True + >>> abs_val(0) 0 """ return -num if num < 0 else num -def main(): - """Print absolute value of -34.""" - print(abs_val(-34)) # = 34 +def test_abs_val(): + """ + >>> test_abs_val() + """ + assert 0 == abs_val(0) + assert 34 == abs_val(34) + assert 100000000000 == abs_val(-100000000000) if __name__ == "__main__": - main() + print(abs_val(-34)) # --> 34 diff --git a/maths/average_mean.py b/maths/average_mean.py index 77464ef5d9f7..4beca1f741a0 100644 --- a/maths/average_mean.py +++ b/maths/average_mean.py @@ -3,14 +3,18 @@ def average(nums): """Find mean of a list of numbers.""" - avg = sum(nums) / len(nums) - return avg + return sum(nums) / len(nums) -def main(): - """Call average module to find mean of a specific list of numbers.""" - print(average([2, 4, 6, 8, 20, 50, 70])) +def test_average(): + """ + >>> test_average() + """ + assert 12.0 == average([3, 6, 9, 12, 15, 18, 21]) + assert 20 == average([5, 10, 15, 20, 25, 30, 35]) + assert 4.5 == average([1, 2, 3, 4, 5, 6, 7, 8]) if __name__ == "__main__": - main() + """Call average module to find mean of a specific list of numbers.""" + print(average([2, 4, 6, 8, 20, 50, 70])) From 4531ea425e911a84a4fa507633fc5b566832e4b2 Mon Sep 17 00:00:00 2001 From: Christian Clauss Date: Tue, 22 Oct 2019 08:45:03 +0200 Subject: [PATCH 0290/1071] Transfer .ipynb files to TheAlgorithms/Jupyter (#1414) --- machine_learning/dbscan/dbscan.ipynb | 376 ---- machine_learning/dbscan/dbscan.py | 271 --- machine_learning/naive_bayes.ipynb | 1659 ----------------- .../Social_Network_Ads.csv | 401 ---- .../random_forest_classification.py | 103 - .../random_forest_classifier.ipynb | 196 -- .../Position_Salaries.csv | 11 - .../random_forest_regression.ipynb | 147 -- .../random_forest_regression.py | 44 - .../reuters_one_vs_rest_classifier.ipynb | 405 ---- .../fully_connected_neural_network.ipynb | 327 ---- 11 files changed, 3940 deletions(-) delete mode 100644 machine_learning/dbscan/dbscan.ipynb delete mode 100644 machine_learning/dbscan/dbscan.py delete mode 100644 machine_learning/naive_bayes.ipynb delete mode 100644 machine_learning/random_forest_classification/Social_Network_Ads.csv delete mode 100644 machine_learning/random_forest_classification/random_forest_classification.py delete mode 100644 machine_learning/random_forest_classification/random_forest_classifier.ipynb delete mode 100644 machine_learning/random_forest_regression/Position_Salaries.csv delete mode 100644 machine_learning/random_forest_regression/random_forest_regression.ipynb delete mode 100644 machine_learning/random_forest_regression/random_forest_regression.py delete mode 100644 machine_learning/reuters_one_vs_rest_classifier.ipynb delete mode 100644 neural_network/fully_connected_neural_network.ipynb diff --git a/machine_learning/dbscan/dbscan.ipynb b/machine_learning/dbscan/dbscan.ipynb deleted file mode 100644 index 603a4cd405b9..000000000000 --- a/machine_learning/dbscan/dbscan.ipynb +++ /dev/null @@ -1,376 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## DBSCAN\n", - "This implementation and notebook is inspired from the original DBSCAN algorithm and article as given in \n", - "[DBSCAN Wikipedia](https://en.wikipedia.org/wiki/DBSCAN).\n", - "\n", - "Stands for __Density-based spatial clustering of applications with noise__ . \n", - "\n", - "DBSCAN is clustering algorithm that tries to captures the intuition that if two points belong to the same cluster they should be close to one another. It does so by finding regions that are densely packed together, i.e, the points that have many close neighbours.\n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### When to use ?\n", - "\n", - "1. You need a robust clustering algorithm.\n", - "2. You don't know how many clusters there are in the dataset\n", - "3. You find it difficult to guess the number of clusters there are just by eyeballing the dataset.\n", - "4. The clusters are of arbitrary shapes.\n", - "5. You want to detect outliers/noise." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Why DBSCAN ? \n", - "\n", - "This algorithm is way better than other clustering algorithms such as [k-means](https://en.wikipedia.org/wiki/K-means_clustering) whose only job is to find circular blobs. It is smart enough to figure out the number of clusters in the dataset on its own, unlike k-means where you need to specify 'k'. It can also find clusters of arbitrary shapes, not just circular blobs. Its too robust to be affected by outliers (the noise points) and isn't fooled by them, unlike k-means where the entire centroid get pulled thanks to pesky outliers. Plus, you can fine-tune its parameters depending on what you are clustering.\n", - "\n", - "#### Have a look at these [neat animations](https://www.naftaliharris.com/blog/visualizing-dbscan-clustering/) of DBSCAN to see for yourself." - ] - }, - { - "cell_type": "code", - "execution_count": 1, - "metadata": {}, - "outputs": [], - "source": [ - "import matplotlib.pyplot as plt\n", - "import numpy as np\n", - "\n", - "%matplotlib inline" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## First lets grab a dataset\n", - "We will take the moons dataset which is pretty good at showing the power of DBSCAN. \n", - "\n", - "Lets generate 200 random points in the shape of two moons" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "metadata": {}, - "outputs": [], - "source": [ - "from sklearn.datasets import make_moons\n", - "\n", - "x, label = make_moons(n_samples=200, noise=0.1, random_state=19)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Visualize the dataset using matplotlib\n", - "You will observe that the points are in the shape of two crescent moons. \n", - "\n", - "The challenge here is to cluster the two moons. " - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "metadata": { - "scrolled": false - }, - "outputs": [ - { - "data": { - "text/plain": [ - "[]" - ] - }, - "execution_count": 3, - "metadata": {}, - "output_type": "execute_result" - }, - { - "data": { - "image/png": "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\n", - "text/plain": [ - "
" - ] - }, - "metadata": { - "needs_background": "light" - }, - "output_type": "display_data" - } - ], - "source": [ - "plt.plot(x[:,0], x[:,1],'ro')" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Abstract of the Algorithm\n", - "The DBSCAN algorithm can be abstracted into the following steps:\n", - "\n", - "- Find the points in the $ε$ (eps) neighborhood of every point, and identify the core points with more than min_pts neighbors.\n", - "- Find the connected components of core points on the neighbor graph, ignoring all non-core points.\n", - "- Assign each non-core point to a nearby cluster if the cluster is an $ε$ (eps) neighbor, otherwise assign it to noise.\n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Preparing the points\n", - "Initially we label all the points in the dataset as __undefined__ .\n", - "\n", - "__points__ is our database of all points in the dataset." - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "metadata": {}, - "outputs": [], - "source": [ - "points = { (point[0],point[1]):{'label':'undefined'} for point in x }" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Helper functions" - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "metadata": {}, - "outputs": [], - "source": [ - "def euclidean_distance(q, p):\n", - " \"\"\"\n", - " Calculates the Euclidean distance\n", - " between points P and Q\n", - " \"\"\"\n", - " a = pow((q[0] - p[0]), 2)\n", - " b = pow((q[1] - p[1]), 2)\n", - " return pow((a + b), 0.5)" - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "metadata": {}, - "outputs": [], - "source": [ - "def find_neighbors(db, q, eps):\n", - " \"\"\"\n", - " Finds all points in the DB that\n", - " are within a distance of eps from Q\n", - " \"\"\"\n", - " return [p for p in db if euclidean_distance(q, p) <= eps]" - ] - }, - { - "cell_type": "code", - "execution_count": 7, - "metadata": {}, - "outputs": [], - "source": [ - "def plot_cluster(db, clusters):\n", - " \"\"\"\n", - " Extracts all the points in the DB and puts them together\n", - " as seperate clusters and finally plots them\n", - " \"\"\"\n", - " temp = []\n", - " noise = []\n", - " for i in clusters:\n", - " stack = []\n", - " for k, v in db.items():\n", - " if v[\"label\"] == i:\n", - " stack.append(k)\n", - " elif v[\"label\"] == \"noise\":\n", - " noise.append(k)\n", - " temp.append(stack)\n", - "\n", - " color = iter(plt.cm.rainbow(np.linspace(0, 1, len(clusters))))\n", - " for i in range(0, len(temp)):\n", - " c = next(color)\n", - " x = [l[0] for l in temp[i]]\n", - " y = [l[1] for l in temp[i]]\n", - " plt.plot(x, y, \"ro\", c=c)\n", - "\n", - " x = [l[0] for l in noise]\n", - " y = [l[1] for l in noise]\n", - " plt.plot(x, y, \"ro\", c=\"0\")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Implementation of DBSCAN\n", - "\n", - "Initialize an empty list, clusters = $[ ]$ and cluster identifier, c = 0\n", - "\n", - "1. For each point p in our database/dict db :\n", - "\n", - " 1.1 Check if p is already labelled. If it's already labelled (means it already been associated to a cluster), continue to the next point,i.e, go to step 1\n", - " \n", - " 1.2. Find the list of neighbors of p , i.e, points that are within a distance of eps from p\n", - " \n", - " 1.3. If p does not have atleast min_pts neighbours, we label it as noise and go back to step 1\n", - " \n", - " 1.4. Initialize the cluster, by incrementing c by 1\n", - " \n", - " 1.5. Append the cluster identifier c to clusters\n", - " \n", - " 1.6. Label p with the cluster identifier c\n", - " \n", - " 1.7 Remove p from the list of neighbors (p will be detected as its own neighbor because it is within eps of itself)\n", - " \n", - " 1.8. Initialize the seed_set as a copy of neighbors\n", - " \n", - " 1.9. While the seed_set is not empty:\n", - " 1.9.1. Removing the 1st point from seed_set and initialise it as q\n", - " 1.9.2. If it's label is noise, label it with c\n", - " 1.9.3. If it's not unlabelled, go back to step 1.9\n", - " 1.9.4. Label q with c\n", - " 1.9.5. Find the neighbours of q \n", - " 1.9.6. If there are atleast min_pts neighbors, append them to the seed_set" - ] - }, - { - "cell_type": "code", - "execution_count": 8, - "metadata": {}, - "outputs": [], - "source": [ - "def dbscan(db,eps,min_pts):\n", - " '''\n", - " Implementation of the DBSCAN algorithm\n", - " '''\n", - " clusters = []\n", - " c = 0\n", - " for p in db:\n", - " if db[p][\"label\"] != \"undefined\":\n", - " continue\n", - " neighbors = find_neighbors(db, p, eps)\n", - " if len(neighbors) < min_pts:\n", - " db[p][\"label\"] = \"noise\"\n", - " continue\n", - " c += 1\n", - " clusters.append(c)\n", - " db[p][\"label\"] = c\n", - " neighbors.remove(p)\n", - " seed_set = neighbors.copy()\n", - " while seed_set != []:\n", - " q = seed_set.pop(0)\n", - " if db[q][\"label\"] == \"noise\":\n", - " db[q][\"label\"] = c\n", - " if db[q][\"label\"] != \"undefined\":\n", - " continue\n", - " db[q][\"label\"] = c\n", - " neighbors_n = find_neighbors(db, q, eps)\n", - " if len(neighbors_n) >= min_pts:\n", - " seed_set = seed_set + neighbors_n\n", - " return db, clusters\n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Lets run it!" - ] - }, - { - "cell_type": "code", - "execution_count": 9, - "metadata": {}, - "outputs": [ - { - "data": { - "image/png": "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\n", - "text/plain": [ - "
" - ] - }, - "metadata": { - "needs_background": "light" - }, - "output_type": "display_data" - } - ], - "source": [ - "eps = 0.25\n", - "min_pts = 12\n", - "\n", - "db,clusters = dbscan(points,eps,min_pts)\n", - "\n", - "plot_cluster(db,clusters)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "I encourage you to try with different datasets and playing with the values of eps and min_pts.\n", - "\n", - "Also, try kmeans on this dataset and see how it compares to dbscan. " - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "I hope by now you are convinced about about how cool dbscan is. But it has its pitfalls.\n", - "### When NOT to use ?\n", - "\n", - "1. You have a high dimentional dataset. Euclidean distance will fail thanks to '[curse of dimentionality](https://en.wikipedia.org/wiki/Curse_of_dimensionality#Distance_functions)'.\n", - "2. We have used a dict to store the points. So we can't do anything about the order in which the points will be processed. So it's not entirely deterministic.\n", - "3. Won't work well if there are large differences in density. Finding the min_pts and $ε$ combination will be difficult.\n", - "4. Choosing the $ε$ without understanding the data and its scale, might result is poor clustering performance." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.7.0" - } - }, - "nbformat": 4, - "nbformat_minor": 2 -} diff --git a/machine_learning/dbscan/dbscan.py b/machine_learning/dbscan/dbscan.py deleted file mode 100644 index 04fb5f0186e1..000000000000 --- a/machine_learning/dbscan/dbscan.py +++ /dev/null @@ -1,271 +0,0 @@ -import matplotlib.pyplot as plt -import numpy as np -from sklearn.datasets import make_moons -import warnings - - -def euclidean_distance(q, p): - """ - Calculates the Euclidean distance - between points q and p - - Distance can only be calculated between numeric values - >>> euclidean_distance([1,'a'],[1,2]) - Traceback (most recent call last): - ... - ValueError: Non-numeric input detected - - The dimentions of both the points must be the same - >>> euclidean_distance([1,1,1],[1,2]) - Traceback (most recent call last): - ... - ValueError: expected dimensions to be 2-d, instead got p:3 and q:2 - - Supports only two dimentional points - >>> euclidean_distance([1,1,1],[1,2]) - Traceback (most recent call last): - ... - ValueError: expected dimensions to be 2-d, instead got p:3 and q:2 - - Input should be in the format [x,y] or (x,y) - >>> euclidean_distance(1,2) - Traceback (most recent call last): - ... - TypeError: inputs must be iterable, either list [x,y] or tuple (x,y) - """ - if not hasattr(q, "__iter__") or not hasattr(p, "__iter__"): - raise TypeError("inputs must be iterable, either list [x,y] or tuple (x,y)") - - if isinstance(q, str) or isinstance(p, str): - raise TypeError("inputs cannot be str") - - if len(q) != 2 or len(p) != 2: - raise ValueError( - "expected dimensions to be 2-d, instead got p:{} and q:{}".format( - len(q), len(p) - ) - ) - - for num in q + p: - try: - num = int(num) - except: - raise ValueError("Non-numeric input detected") - - a = pow((q[0] - p[0]), 2) - b = pow((q[1] - p[1]), 2) - return pow((a + b), 0.5) - - -def find_neighbors(db, q, eps): - """ - Finds all points in the db that - are within a distance of eps from Q - - eps value should be a number - >>> find_neighbors({ (1,2):{'label':'undefined'}, (2,3):{'label':'undefined'}}, (2,5),'a') - Traceback (most recent call last): - ... - ValueError: eps should be either int or float - - Q must be a 2-d point as list or tuple - >>> find_neighbors({ (1,2):{'label':'undefined'}, (2,3):{'label':'undefined'}}, 2, 0.5) - Traceback (most recent call last): - ... - TypeError: Q must a 2-dimentional point in the format (x,y) or [x,y] - - Points must be in correct format - >>> find_neighbors([], (2,2) ,0.4) - Traceback (most recent call last): - ... - TypeError: db must be a dict of points in the format {(x,y):{'label':'boolean/undefined'}} - """ - - if not isinstance(eps, (int, float)): - raise ValueError("eps should be either int or float") - - if not hasattr(q, "__iter__"): - raise TypeError("Q must a 2-dimentional point in the format (x,y) or [x,y]") - - if not isinstance(db, dict): - raise TypeError( - "db must be a dict of points in the format {(x,y):{'label':'boolean/undefined'}}" - ) - - return [p for p in db if euclidean_distance(q, p) <= eps] - - -def plot_cluster(db, clusters, ax): - """ - Extracts all the points in the db and puts them together - as seperate clusters and finally plots them - - db cannot be empty - >>> fig, axes = plt.subplots(nrows=1, ncols=2, figsize=(7, 5)) - >>> plot_cluster({},[1,2], axes[1] ) - Traceback (most recent call last): - ... - Exception: db is empty. No points to cluster - - clusters cannot be empty - >>> fig, axes = plt.subplots(nrows=1, ncols=2, figsize=(7, 5)) - >>> plot_cluster({ (1,2):{'label':'undefined'}, (2,3):{'label':'undefined'}},[],axes[1] ) - Traceback (most recent call last): - ... - Exception: nothing to cluster. Empty clusters - - clusters cannot be empty - >>> fig, axes = plt.subplots(nrows=1, ncols=2, figsize=(7, 5)) - >>> plot_cluster({ (1,2):{'label':'undefined'}, (2,3):{'label':'undefined'}},[],axes[1] ) - Traceback (most recent call last): - ... - Exception: nothing to cluster. Empty clusters - - ax must be a plotable - >>> plot_cluster({ (1,2):{'label':'1'}, (2,3):{'label':'2'}},[1,2], [] ) - Traceback (most recent call last): - ... - TypeError: ax must be an slot in a matplotlib figure - """ - if len(db) == 0: - raise Exception("db is empty. No points to cluster") - - if len(clusters) == 0: - raise Exception("nothing to cluster. Empty clusters") - - if not hasattr(ax, "plot"): - raise TypeError("ax must be an slot in a matplotlib figure") - - temp = [] - noise = [] - for i in clusters: - stack = [] - for k, v in db.items(): - if v["label"] == i: - stack.append(k) - elif v["label"] == "noise": - noise.append(k) - temp.append(stack) - - color = iter(plt.cm.rainbow(np.linspace(0, 1, len(clusters)))) - for i in range(0, len(temp)): - c = next(color) - x = [l[0] for l in temp[i]] - y = [l[1] for l in temp[i]] - ax.plot(x, y, "ro", c=c) - - x = [l[0] for l in noise] - y = [l[1] for l in noise] - ax.plot(x, y, "ro", c="0") - - -def dbscan(db, eps, min_pts): - """ - Implementation of the DBSCAN algorithm - - Points must be in correct format - >>> dbscan([], (2,2) ,0.4) - Traceback (most recent call last): - ... - TypeError: db must be a dict of points in the format {(x,y):{'label':'boolean/undefined'}} - - eps value should be a number - >>> dbscan({ (1,2):{'label':'undefined'}, (2,3):{'label':'undefined'}},'a',20 ) - Traceback (most recent call last): - ... - ValueError: eps should be either int or float - - min_pts value should be an integer - >>> dbscan({ (1,2):{'label':'undefined'}, (2,3):{'label':'undefined'}},0.4,20.0 ) - Traceback (most recent call last): - ... - ValueError: min_pts should be int - - db cannot be empty - >>> dbscan({},0.4,20.0 ) - Traceback (most recent call last): - ... - Exception: db is empty, nothing to cluster - - min_pts cannot be negative - >>> dbscan({ (1,2):{'label':'undefined'}, (2,3):{'label':'undefined'}}, 0.4, -20) - Traceback (most recent call last): - ... - ValueError: min_pts or eps cannot be negative - - eps cannot be negative - >>> dbscan({ (1,2):{'label':'undefined'}, (2,3):{'label':'undefined'}},-0.4, 20) - Traceback (most recent call last): - ... - ValueError: min_pts or eps cannot be negative - - """ - if not isinstance(db, dict): - raise TypeError( - "db must be a dict of points in the format {(x,y):{'label':'boolean/undefined'}}" - ) - - if len(db) == 0: - raise Exception("db is empty, nothing to cluster") - - if not isinstance(eps, (int, float)): - raise ValueError("eps should be either int or float") - - if not isinstance(min_pts, int): - raise ValueError("min_pts should be int") - - if min_pts < 0 or eps < 0: - raise ValueError("min_pts or eps cannot be negative") - - if min_pts == 0: - warnings.warn("min_pts is 0. Are you sure you want this ?") - - if eps == 0: - warnings.warn("eps is 0. Are you sure you want this ?") - - clusters = [] - c = 0 - for p in db: - if db[p]["label"] != "undefined": - continue - neighbors = find_neighbors(db, p, eps) - if len(neighbors) < min_pts: - db[p]["label"] = "noise" - continue - c += 1 - clusters.append(c) - db[p]["label"] = c - neighbors.remove(p) - seed_set = neighbors.copy() - while seed_set != []: - q = seed_set.pop(0) - if db[q]["label"] == "noise": - db[q]["label"] = c - if db[q]["label"] != "undefined": - continue - db[q]["label"] = c - neighbors_n = find_neighbors(db, q, eps) - if len(neighbors_n) >= min_pts: - seed_set = seed_set + neighbors_n - return db, clusters - - -if __name__ == "__main__": - - fig, axes = plt.subplots(nrows=1, ncols=2, figsize=(7, 5)) - - x, label = make_moons(n_samples=200, noise=0.1, random_state=19) - - axes[0].plot(x[:, 0], x[:, 1], "ro") - - points = {(point[0], point[1]): {"label": "undefined"} for point in x} - - eps = 0.25 - - min_pts = 12 - - db, clusters = dbscan(points, eps, min_pts) - - plot_cluster(db, clusters, axes[1]) - - plt.show() diff --git a/machine_learning/naive_bayes.ipynb b/machine_learning/naive_bayes.ipynb deleted file mode 100644 index 5a427c5cb965..000000000000 --- a/machine_learning/naive_bayes.ipynb +++ /dev/null @@ -1,1659 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": 1, - "metadata": { - "collapsed": true - }, - "outputs": [], - "source": [ - "from sklearn import datasets\n", - "import pandas as pd" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "metadata": { - "collapsed": true - }, - "outputs": [], - "source": [ - "iris = datasets.load_iris()" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "metadata": { - "collapsed": true - }, - "outputs": [], - "source": [ - "df = pd.DataFrame(iris.data)\n", - "df.columns = [\"sl\", \"sw\", 'pl', 'pw']" - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "metadata": { - "collapsed": true - }, - "outputs": [], - "source": [ - "def abc(k, *val):\n", - " if k < val[0]:\n", - " return 0\n", - " else:\n", - " return 1" - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "0 1\n", - "1 0\n", - "2 0\n", - "3 0\n", - "4 1\n", - "5 1\n", - "6 0\n", - "7 1\n", - "8 0\n", - "9 0\n", - "10 1\n", - "11 0\n", - "12 0\n", - "13 0\n", - "14 1\n", - "15 1\n", - "16 1\n", - "17 1\n", - "18 1\n", - "19 1\n", - "20 1\n", - "21 1\n", - "22 0\n", - "23 1\n", - "24 0\n", - "25 1\n", - "26 1\n", - "27 1\n", - "28 1\n", - "29 0\n", - " ..\n", - "120 1\n", - "121 1\n", - "122 1\n", - "123 1\n", - "124 1\n", - "125 1\n", - "126 1\n", - "127 1\n", - "128 1\n", - "129 1\n", - "130 1\n", - "131 1\n", - "132 1\n", - "133 1\n", - "134 1\n", - "135 1\n", - "136 1\n", - "137 1\n", - "138 1\n", - "139 1\n", - "140 1\n", - "141 1\n", - "142 1\n", - "143 1\n", - "144 1\n", - "145 1\n", - "146 1\n", - "147 1\n", - "148 1\n", - "149 1\n", - "Name: sl, dtype: int64" - ] - }, - "execution_count": 5, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "df.sl.apply(abc, args=(5,))" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": true - }, - "outputs": [], - "source": [] - }, - { - "cell_type": "code", - "execution_count": 6, - "metadata": {}, - "outputs": [], - "source": [ - "def label(val, *boundaries):\n", - " if (val < boundaries[0]):\n", - " return 'a'\n", - " elif (val < boundaries[1]):\n", - " return 'b'\n", - " elif (val < boundaries[2]):\n", - " return 'c'\n", - " else:\n", - " return 'd'\n", - "\n", - "def toLabel(df, old_feature_name):\n", - " second = df[old_feature_name].mean()\n", - " minimum = df[old_feature_name].min()\n", - " first = (minimum + second)/2\n", - " maximum = df[old_feature_name].max()\n", - " third = (maximum + second)/2\n", - " return df[old_feature_name].apply(label, args= (first, second, third))" - ] - }, - { - "cell_type": "code", - "execution_count": 7, - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "
\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
slswplpwsl_labeledsw_labeledpl_labeledpw_labeled
05.13.51.40.2bcaa
14.93.01.40.2abaa
24.73.21.30.2acaa
34.63.11.50.2acaa
45.03.61.40.2acaa
55.43.91.70.4bdaa
64.63.41.40.3acaa
75.03.41.50.2acaa
84.42.91.40.2abaa
94.93.11.50.1acaa
105.43.71.50.2bcaa
114.83.41.60.2acaa
124.83.01.40.1abaa
134.33.01.10.1abaa
145.84.01.20.2bdaa
155.74.41.50.4bdaa
165.43.91.30.4bdaa
175.13.51.40.3bcaa
185.73.81.70.3bdaa
195.13.81.50.3bdaa
205.43.41.70.2bcaa
215.13.71.50.4bcaa
224.63.61.00.2acaa
235.13.31.70.5bcaa
244.83.41.90.2acaa
255.03.01.60.2abaa
265.03.41.60.4acaa
275.23.51.50.2bcaa
285.23.41.40.2bcaa
294.73.21.60.2acaa
...........................
1206.93.25.72.3dcdd
1215.62.84.92.0bbcd
1227.72.86.72.0dbdd
1236.32.74.91.8cbcc
1246.73.35.72.1ccdd
1257.23.26.01.8dcdc
1266.22.84.81.8cbcc
1276.13.04.91.8cbcc
1286.42.85.62.1cbdd
1297.23.05.81.6dbdc
1307.42.86.11.9dbdd
1317.93.86.42.0dddd
1326.42.85.62.2cbdd
1336.32.85.11.5cbcc
1346.12.65.61.4cbdc
1357.73.06.12.3dbdd
1366.33.45.62.4ccdd
1376.43.15.51.8ccdc
1386.03.04.81.8cbcc
1396.93.15.42.1dcdd
1406.73.15.62.4ccdd
1416.93.15.12.3dccd
1425.82.75.11.9bbcd
1436.83.25.92.3ccdd
1446.73.35.72.5ccdd
1456.73.05.22.3cbcd
1466.32.55.01.9cacd
1476.53.05.22.0cbcd
1486.23.45.42.3ccdd
1495.93.05.11.8cbcc
\n", - "

150 rows × 8 columns

\n", - "
" - ], - "text/plain": [ - " sl sw pl pw sl_labeled sw_labeled pl_labeled pw_labeled\n", - "0 5.1 3.5 1.4 0.2 b c a a\n", - "1 4.9 3.0 1.4 0.2 a b a a\n", - "2 4.7 3.2 1.3 0.2 a c a a\n", - "3 4.6 3.1 1.5 0.2 a c a a\n", - "4 5.0 3.6 1.4 0.2 a c a a\n", - "5 5.4 3.9 1.7 0.4 b d a a\n", - "6 4.6 3.4 1.4 0.3 a c a a\n", - "7 5.0 3.4 1.5 0.2 a c a a\n", - "8 4.4 2.9 1.4 0.2 a b a a\n", - "9 4.9 3.1 1.5 0.1 a c a a\n", - "10 5.4 3.7 1.5 0.2 b c a a\n", - "11 4.8 3.4 1.6 0.2 a c a a\n", - "12 4.8 3.0 1.4 0.1 a b a a\n", - "13 4.3 3.0 1.1 0.1 a b a a\n", - "14 5.8 4.0 1.2 0.2 b d a a\n", - "15 5.7 4.4 1.5 0.4 b d a a\n", - "16 5.4 3.9 1.3 0.4 b d a a\n", - "17 5.1 3.5 1.4 0.3 b c a a\n", - "18 5.7 3.8 1.7 0.3 b d a a\n", - "19 5.1 3.8 1.5 0.3 b d a a\n", - "20 5.4 3.4 1.7 0.2 b c a a\n", - "21 5.1 3.7 1.5 0.4 b c a a\n", - "22 4.6 3.6 1.0 0.2 a c a a\n", - "23 5.1 3.3 1.7 0.5 b c a a\n", - "24 4.8 3.4 1.9 0.2 a c a a\n", - "25 5.0 3.0 1.6 0.2 a b a a\n", - "26 5.0 3.4 1.6 0.4 a c a a\n", - "27 5.2 3.5 1.5 0.2 b c a a\n", - "28 5.2 3.4 1.4 0.2 b c a a\n", - "29 4.7 3.2 1.6 0.2 a c a a\n", - ".. ... ... ... ... ... ... ... ...\n", - "120 6.9 3.2 5.7 2.3 d c d d\n", - "121 5.6 2.8 4.9 2.0 b b c d\n", - "122 7.7 2.8 6.7 2.0 d b d d\n", - "123 6.3 2.7 4.9 1.8 c b c c\n", - "124 6.7 3.3 5.7 2.1 c c d d\n", - "125 7.2 3.2 6.0 1.8 d c d c\n", - "126 6.2 2.8 4.8 1.8 c b c c\n", - "127 6.1 3.0 4.9 1.8 c b c c\n", - "128 6.4 2.8 5.6 2.1 c b d d\n", - "129 7.2 3.0 5.8 1.6 d b d c\n", - "130 7.4 2.8 6.1 1.9 d b d d\n", - "131 7.9 3.8 6.4 2.0 d d d d\n", - "132 6.4 2.8 5.6 2.2 c b d d\n", - "133 6.3 2.8 5.1 1.5 c b c c\n", - "134 6.1 2.6 5.6 1.4 c b d c\n", - "135 7.7 3.0 6.1 2.3 d b d d\n", - "136 6.3 3.4 5.6 2.4 c c d d\n", - "137 6.4 3.1 5.5 1.8 c c d c\n", - "138 6.0 3.0 4.8 1.8 c b c c\n", - "139 6.9 3.1 5.4 2.1 d c d d\n", - "140 6.7 3.1 5.6 2.4 c c d d\n", - "141 6.9 3.1 5.1 2.3 d c c d\n", - "142 5.8 2.7 5.1 1.9 b b c d\n", - "143 6.8 3.2 5.9 2.3 c c d d\n", - "144 6.7 3.3 5.7 2.5 c c d d\n", - "145 6.7 3.0 5.2 2.3 c b c d\n", - "146 6.3 2.5 5.0 1.9 c a c d\n", - "147 6.5 3.0 5.2 2.0 c b c d\n", - "148 6.2 3.4 5.4 2.3 c c d d\n", - "149 5.9 3.0 5.1 1.8 c b c c\n", - "\n", - "[150 rows x 8 columns]" - ] - }, - "execution_count": 7, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "df['sl_labeled'] = toLabel(df, 'sl')\n", - "df['sw_labeled'] = toLabel(df, 'sw')\n", - "df['pl_labeled'] = toLabel(df, 'pl')\n", - "df['pw_labeled'] = toLabel(df, 'pw')\n", - "df" - ] - }, - { - "cell_type": "code", - "execution_count": 8, - "metadata": {}, - "outputs": [], - "source": [ - "df.drop(['sl', 'sw', 'pl', 'pw'], axis = 1, inplace = True)" - ] - }, - { - "cell_type": "code", - "execution_count": 9, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "{'a', 'b', 'c', 'd'}" - ] - }, - "execution_count": 9, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "set(df['sl_labeled'])" - ] - }, - { - "cell_type": "code", - "execution_count": 12, - "metadata": {}, - "outputs": [], - "source": [ - "df[\"output\"] = iris.target" - ] - }, - { - "cell_type": "code", - "execution_count": 13, - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "
\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
sl_labeledsw_labeledpl_labeledpw_labeledoutput
0bcaa0
1abaa0
2acaa0
3acaa0
4acaa0
5bdaa0
6acaa0
7acaa0
8abaa0
9acaa0
10bcaa0
11acaa0
12abaa0
13abaa0
14bdaa0
15bdaa0
16bdaa0
17bcaa0
18bdaa0
19bdaa0
20bcaa0
21bcaa0
22acaa0
23bcaa0
24acaa0
25abaa0
26acaa0
27bcaa0
28bcaa0
29acaa0
..................
120dcdd2
121bbcd2
122dbdd2
123cbcc2
124ccdd2
125dcdc2
126cbcc2
127cbcc2
128cbdd2
129dbdc2
130dbdd2
131dddd2
132cbdd2
133cbcc2
134cbdc2
135dbdd2
136ccdd2
137ccdc2
138cbcc2
139dcdd2
140ccdd2
141dccd2
142bbcd2
143ccdd2
144ccdd2
145cbcd2
146cacd2
147cbcd2
148ccdd2
149cbcc2
\n", - "

150 rows × 5 columns

\n", - "
" - ], - "text/plain": [ - " sl_labeled sw_labeled pl_labeled pw_labeled output\n", - "0 b c a a 0\n", - "1 a b a a 0\n", - "2 a c a a 0\n", - "3 a c a a 0\n", - "4 a c a a 0\n", - "5 b d a a 0\n", - "6 a c a a 0\n", - "7 a c a a 0\n", - "8 a b a a 0\n", - "9 a c a a 0\n", - "10 b c a a 0\n", - "11 a c a a 0\n", - "12 a b a a 0\n", - "13 a b a a 0\n", - "14 b d a a 0\n", - "15 b d a a 0\n", - "16 b d a a 0\n", - "17 b c a a 0\n", - "18 b d a a 0\n", - "19 b d a a 0\n", - "20 b c a a 0\n", - "21 b c a a 0\n", - "22 a c a a 0\n", - "23 b c a a 0\n", - "24 a c a a 0\n", - "25 a b a a 0\n", - "26 a c a a 0\n", - "27 b c a a 0\n", - "28 b c a a 0\n", - "29 a c a a 0\n", - ".. ... ... ... ... ...\n", - "120 d c d d 2\n", - "121 b b c d 2\n", - "122 d b d d 2\n", - "123 c b c c 2\n", - "124 c c d d 2\n", - "125 d c d c 2\n", - "126 c b c c 2\n", - "127 c b c c 2\n", - "128 c b d d 2\n", - "129 d b d c 2\n", - "130 d b d d 2\n", - "131 d d d d 2\n", - "132 c b d d 2\n", - "133 c b c c 2\n", - "134 c b d c 2\n", - "135 d b d d 2\n", - "136 c c d d 2\n", - "137 c c d c 2\n", - "138 c b c c 2\n", - "139 d c d d 2\n", - "140 c c d d 2\n", - "141 d c c d 2\n", - "142 b b c d 2\n", - "143 c c d d 2\n", - "144 c c d d 2\n", - "145 c b c d 2\n", - "146 c a c d 2\n", - "147 c b c d 2\n", - "148 c c d d 2\n", - "149 c b c c 2\n", - "\n", - "[150 rows x 5 columns]" - ] - }, - "execution_count": 13, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "df" - ] - }, - { - "cell_type": "code", - "execution_count": 14, - "metadata": { - "collapsed": true - }, - "outputs": [], - "source": [ - "def fit(data):\n", - " output_name = data.columns[-1]\n", - " features = data.columns[0:-1]\n", - " counts = {}\n", - " possible_outputs = set(data[output_name])\n", - " for output in possible_outputs:\n", - " counts[output] = {}\n", - " smallData = data[data[output_name] == output]\n", - " counts[output][\"total_count\"] = len(smallData)\n", - " for f in features:\n", - " counts[output][f] = {}\n", - " possible_values = set(smallData[f])\n", - " for value in possible_values:\n", - " val_count = len(smallData[smallData[f] == value])\n", - " counts[output][f][value] = val_count\n", - " return counts" - ] - }, - { - "cell_type": "code", - "execution_count": 15, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "{0: {'pl_labeled': {'a': 50},\n", - " 'pw_labeled': {'a': 50},\n", - " 'sl_labeled': {'a': 28, 'b': 22},\n", - " 'sw_labeled': {'a': 1, 'b': 7, 'c': 32, 'd': 10},\n", - " 'total_count': 50},\n", - " 1: {'pl_labeled': {'b': 7, 'c': 43},\n", - " 'pw_labeled': {'b': 10, 'c': 40},\n", - " 'sl_labeled': {'a': 3, 'b': 21, 'c': 24, 'd': 2},\n", - " 'sw_labeled': {'a': 13, 'b': 29, 'c': 8},\n", - " 'total_count': 50},\n", - " 2: {'pl_labeled': {'c': 20, 'd': 30},\n", - " 'pw_labeled': {'c': 16, 'd': 34},\n", - " 'sl_labeled': {'a': 1, 'b': 5, 'c': 29, 'd': 15},\n", - " 'sw_labeled': {'a': 5, 'b': 28, 'c': 15, 'd': 2},\n", - " 'total_count': 50}}" - ] - }, - "execution_count": 15, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "fit(df)" - ] - } - ], - "metadata": { - "anaconda-cloud": {}, - "kernelspec": { - "display_name": "Python [default]", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.6.5" - } - }, - "nbformat": 4, - "nbformat_minor": 1 -} diff --git a/machine_learning/random_forest_classification/Social_Network_Ads.csv b/machine_learning/random_forest_classification/Social_Network_Ads.csv deleted file mode 100644 index 4a53849c2baf..000000000000 --- a/machine_learning/random_forest_classification/Social_Network_Ads.csv +++ /dev/null @@ -1,401 +0,0 @@ -User ID,Gender,Age,EstimatedSalary,Purchased -15624510,Male,19,19000,0 -15810944,Male,35,20000,0 -15668575,Female,26,43000,0 -15603246,Female,27,57000,0 -15804002,Male,19,76000,0 -15728773,Male,27,58000,0 -15598044,Female,27,84000,0 -15694829,Female,32,150000,1 -15600575,Male,25,33000,0 -15727311,Female,35,65000,0 -15570769,Female,26,80000,0 -15606274,Female,26,52000,0 -15746139,Male,20,86000,0 -15704987,Male,32,18000,0 -15628972,Male,18,82000,0 -15697686,Male,29,80000,0 -15733883,Male,47,25000,1 -15617482,Male,45,26000,1 -15704583,Male,46,28000,1 -15621083,Female,48,29000,1 -15649487,Male,45,22000,1 -15736760,Female,47,49000,1 -15714658,Male,48,41000,1 -15599081,Female,45,22000,1 -15705113,Male,46,23000,1 -15631159,Male,47,20000,1 -15792818,Male,49,28000,1 -15633531,Female,47,30000,1 -15744529,Male,29,43000,0 -15669656,Male,31,18000,0 -15581198,Male,31,74000,0 -15729054,Female,27,137000,1 -15573452,Female,21,16000,0 -15776733,Female,28,44000,0 -15724858,Male,27,90000,0 -15713144,Male,35,27000,0 -15690188,Female,33,28000,0 -15689425,Male,30,49000,0 -15671766,Female,26,72000,0 -15782806,Female,27,31000,0 -15764419,Female,27,17000,0 -15591915,Female,33,51000,0 -15772798,Male,35,108000,0 -15792008,Male,30,15000,0 -15715541,Female,28,84000,0 -15639277,Male,23,20000,0 -15798850,Male,25,79000,0 -15776348,Female,27,54000,0 -15727696,Male,30,135000,1 -15793813,Female,31,89000,0 -15694395,Female,24,32000,0 -15764195,Female,18,44000,0 -15744919,Female,29,83000,0 -15671655,Female,35,23000,0 -15654901,Female,27,58000,0 -15649136,Female,24,55000,0 -15775562,Female,23,48000,0 -15807481,Male,28,79000,0 -15642885,Male,22,18000,0 -15789109,Female,32,117000,0 -15814004,Male,27,20000,0 -15673619,Male,25,87000,0 -15595135,Female,23,66000,0 -15583681,Male,32,120000,1 -15605000,Female,59,83000,0 -15718071,Male,24,58000,0 -15679760,Male,24,19000,0 -15654574,Female,23,82000,0 -15577178,Female,22,63000,0 -15595324,Female,31,68000,0 -15756932,Male,25,80000,0 -15726358,Female,24,27000,0 -15595228,Female,20,23000,0 -15782530,Female,33,113000,0 -15592877,Male,32,18000,0 -15651983,Male,34,112000,1 -15746737,Male,18,52000,0 -15774179,Female,22,27000,0 -15667265,Female,28,87000,0 -15655123,Female,26,17000,0 -15595917,Male,30,80000,0 -15668385,Male,39,42000,0 -15709476,Male,20,49000,0 -15711218,Male,35,88000,0 -15798659,Female,30,62000,0 -15663939,Female,31,118000,1 -15694946,Male,24,55000,0 -15631912,Female,28,85000,0 -15768816,Male,26,81000,0 -15682268,Male,35,50000,0 -15684801,Male,22,81000,0 -15636428,Female,30,116000,0 -15809823,Male,26,15000,0 -15699284,Female,29,28000,0 -15786993,Female,29,83000,0 -15709441,Female,35,44000,0 -15710257,Female,35,25000,0 -15582492,Male,28,123000,1 -15575694,Male,35,73000,0 -15756820,Female,28,37000,0 -15766289,Male,27,88000,0 -15593014,Male,28,59000,0 -15584545,Female,32,86000,0 -15675949,Female,33,149000,1 -15672091,Female,19,21000,0 -15801658,Male,21,72000,0 -15706185,Female,26,35000,0 -15789863,Male,27,89000,0 -15720943,Male,26,86000,0 -15697997,Female,38,80000,0 -15665416,Female,39,71000,0 -15660200,Female,37,71000,0 -15619653,Male,38,61000,0 -15773447,Male,37,55000,0 -15739160,Male,42,80000,0 -15689237,Male,40,57000,0 -15679297,Male,35,75000,0 -15591433,Male,36,52000,0 -15642725,Male,40,59000,0 -15701962,Male,41,59000,0 -15811613,Female,36,75000,0 -15741049,Male,37,72000,0 -15724423,Female,40,75000,0 -15574305,Male,35,53000,0 -15678168,Female,41,51000,0 -15697020,Female,39,61000,0 -15610801,Male,42,65000,0 -15745232,Male,26,32000,0 -15722758,Male,30,17000,0 -15792102,Female,26,84000,0 -15675185,Male,31,58000,0 -15801247,Male,33,31000,0 -15725660,Male,30,87000,0 -15638963,Female,21,68000,0 -15800061,Female,28,55000,0 -15578006,Male,23,63000,0 -15668504,Female,20,82000,0 -15687491,Male,30,107000,1 -15610403,Female,28,59000,0 -15741094,Male,19,25000,0 -15807909,Male,19,85000,0 -15666141,Female,18,68000,0 -15617134,Male,35,59000,0 -15783029,Male,30,89000,0 -15622833,Female,34,25000,0 -15746422,Female,24,89000,0 -15750839,Female,27,96000,1 -15749130,Female,41,30000,0 -15779862,Male,29,61000,0 -15767871,Male,20,74000,0 -15679651,Female,26,15000,0 -15576219,Male,41,45000,0 -15699247,Male,31,76000,0 -15619087,Female,36,50000,0 -15605327,Male,40,47000,0 -15610140,Female,31,15000,0 -15791174,Male,46,59000,0 -15602373,Male,29,75000,0 -15762605,Male,26,30000,0 -15598840,Female,32,135000,1 -15744279,Male,32,100000,1 -15670619,Male,25,90000,0 -15599533,Female,37,33000,0 -15757837,Male,35,38000,0 -15697574,Female,33,69000,0 -15578738,Female,18,86000,0 -15762228,Female,22,55000,0 -15614827,Female,35,71000,0 -15789815,Male,29,148000,1 -15579781,Female,29,47000,0 -15587013,Male,21,88000,0 -15570932,Male,34,115000,0 -15794661,Female,26,118000,0 -15581654,Female,34,43000,0 -15644296,Female,34,72000,0 -15614420,Female,23,28000,0 -15609653,Female,35,47000,0 -15594577,Male,25,22000,0 -15584114,Male,24,23000,0 -15673367,Female,31,34000,0 -15685576,Male,26,16000,0 -15774727,Female,31,71000,0 -15694288,Female,32,117000,1 -15603319,Male,33,43000,0 -15759066,Female,33,60000,0 -15814816,Male,31,66000,0 -15724402,Female,20,82000,0 -15571059,Female,33,41000,0 -15674206,Male,35,72000,0 -15715160,Male,28,32000,0 -15730448,Male,24,84000,0 -15662067,Female,19,26000,0 -15779581,Male,29,43000,0 -15662901,Male,19,70000,0 -15689751,Male,28,89000,0 -15667742,Male,34,43000,0 -15738448,Female,30,79000,0 -15680243,Female,20,36000,0 -15745083,Male,26,80000,0 -15708228,Male,35,22000,0 -15628523,Male,35,39000,0 -15708196,Male,49,74000,0 -15735549,Female,39,134000,1 -15809347,Female,41,71000,0 -15660866,Female,58,101000,1 -15766609,Female,47,47000,0 -15654230,Female,55,130000,1 -15794566,Female,52,114000,0 -15800890,Female,40,142000,1 -15697424,Female,46,22000,0 -15724536,Female,48,96000,1 -15735878,Male,52,150000,1 -15707596,Female,59,42000,0 -15657163,Male,35,58000,0 -15622478,Male,47,43000,0 -15779529,Female,60,108000,1 -15636023,Male,49,65000,0 -15582066,Male,40,78000,0 -15666675,Female,46,96000,0 -15732987,Male,59,143000,1 -15789432,Female,41,80000,0 -15663161,Male,35,91000,1 -15694879,Male,37,144000,1 -15593715,Male,60,102000,1 -15575002,Female,35,60000,0 -15622171,Male,37,53000,0 -15795224,Female,36,126000,1 -15685346,Male,56,133000,1 -15691808,Female,40,72000,0 -15721007,Female,42,80000,1 -15794253,Female,35,147000,1 -15694453,Male,39,42000,0 -15813113,Male,40,107000,1 -15614187,Male,49,86000,1 -15619407,Female,38,112000,0 -15646227,Male,46,79000,1 -15660541,Male,40,57000,0 -15753874,Female,37,80000,0 -15617877,Female,46,82000,0 -15772073,Female,53,143000,1 -15701537,Male,42,149000,1 -15736228,Male,38,59000,0 -15780572,Female,50,88000,1 -15769596,Female,56,104000,1 -15586996,Female,41,72000,0 -15722061,Female,51,146000,1 -15638003,Female,35,50000,0 -15775590,Female,57,122000,1 -15730688,Male,41,52000,0 -15753102,Female,35,97000,1 -15810075,Female,44,39000,0 -15723373,Male,37,52000,0 -15795298,Female,48,134000,1 -15584320,Female,37,146000,1 -15724161,Female,50,44000,0 -15750056,Female,52,90000,1 -15609637,Female,41,72000,0 -15794493,Male,40,57000,0 -15569641,Female,58,95000,1 -15815236,Female,45,131000,1 -15811177,Female,35,77000,0 -15680587,Male,36,144000,1 -15672821,Female,55,125000,1 -15767681,Female,35,72000,0 -15600379,Male,48,90000,1 -15801336,Female,42,108000,1 -15721592,Male,40,75000,0 -15581282,Male,37,74000,0 -15746203,Female,47,144000,1 -15583137,Male,40,61000,0 -15680752,Female,43,133000,0 -15688172,Female,59,76000,1 -15791373,Male,60,42000,1 -15589449,Male,39,106000,1 -15692819,Female,57,26000,1 -15727467,Male,57,74000,1 -15734312,Male,38,71000,0 -15764604,Male,49,88000,1 -15613014,Female,52,38000,1 -15759684,Female,50,36000,1 -15609669,Female,59,88000,1 -15685536,Male,35,61000,0 -15750447,Male,37,70000,1 -15663249,Female,52,21000,1 -15638646,Male,48,141000,0 -15734161,Female,37,93000,1 -15631070,Female,37,62000,0 -15761950,Female,48,138000,1 -15649668,Male,41,79000,0 -15713912,Female,37,78000,1 -15586757,Male,39,134000,1 -15596522,Male,49,89000,1 -15625395,Male,55,39000,1 -15760570,Male,37,77000,0 -15566689,Female,35,57000,0 -15725794,Female,36,63000,0 -15673539,Male,42,73000,1 -15705298,Female,43,112000,1 -15675791,Male,45,79000,0 -15747043,Male,46,117000,1 -15736397,Female,58,38000,1 -15678201,Male,48,74000,1 -15720745,Female,37,137000,1 -15637593,Male,37,79000,1 -15598070,Female,40,60000,0 -15787550,Male,42,54000,0 -15603942,Female,51,134000,0 -15733973,Female,47,113000,1 -15596761,Male,36,125000,1 -15652400,Female,38,50000,0 -15717893,Female,42,70000,0 -15622585,Male,39,96000,1 -15733964,Female,38,50000,0 -15753861,Female,49,141000,1 -15747097,Female,39,79000,0 -15594762,Female,39,75000,1 -15667417,Female,54,104000,1 -15684861,Male,35,55000,0 -15742204,Male,45,32000,1 -15623502,Male,36,60000,0 -15774872,Female,52,138000,1 -15611191,Female,53,82000,1 -15674331,Male,41,52000,0 -15619465,Female,48,30000,1 -15575247,Female,48,131000,1 -15695679,Female,41,60000,0 -15713463,Male,41,72000,0 -15785170,Female,42,75000,0 -15796351,Male,36,118000,1 -15639576,Female,47,107000,1 -15693264,Male,38,51000,0 -15589715,Female,48,119000,1 -15769902,Male,42,65000,0 -15587177,Male,40,65000,0 -15814553,Male,57,60000,1 -15601550,Female,36,54000,0 -15664907,Male,58,144000,1 -15612465,Male,35,79000,0 -15810800,Female,38,55000,0 -15665760,Male,39,122000,1 -15588080,Female,53,104000,1 -15776844,Male,35,75000,0 -15717560,Female,38,65000,0 -15629739,Female,47,51000,1 -15729908,Male,47,105000,1 -15716781,Female,41,63000,0 -15646936,Male,53,72000,1 -15768151,Female,54,108000,1 -15579212,Male,39,77000,0 -15721835,Male,38,61000,0 -15800515,Female,38,113000,1 -15591279,Male,37,75000,0 -15587419,Female,42,90000,1 -15750335,Female,37,57000,0 -15699619,Male,36,99000,1 -15606472,Male,60,34000,1 -15778368,Male,54,70000,1 -15671387,Female,41,72000,0 -15573926,Male,40,71000,1 -15709183,Male,42,54000,0 -15577514,Male,43,129000,1 -15778830,Female,53,34000,1 -15768072,Female,47,50000,1 -15768293,Female,42,79000,0 -15654456,Male,42,104000,1 -15807525,Female,59,29000,1 -15574372,Female,58,47000,1 -15671249,Male,46,88000,1 -15779744,Male,38,71000,0 -15624755,Female,54,26000,1 -15611430,Female,60,46000,1 -15774744,Male,60,83000,1 -15629885,Female,39,73000,0 -15708791,Male,59,130000,1 -15793890,Female,37,80000,0 -15646091,Female,46,32000,1 -15596984,Female,46,74000,0 -15800215,Female,42,53000,0 -15577806,Male,41,87000,1 -15749381,Female,58,23000,1 -15683758,Male,42,64000,0 -15670615,Male,48,33000,1 -15715622,Female,44,139000,1 -15707634,Male,49,28000,1 -15806901,Female,57,33000,1 -15775335,Male,56,60000,1 -15724150,Female,49,39000,1 -15627220,Male,39,71000,0 -15672330,Male,47,34000,1 -15668521,Female,48,35000,1 -15807837,Male,48,33000,1 -15592570,Male,47,23000,1 -15748589,Female,45,45000,1 -15635893,Male,60,42000,1 -15757632,Female,39,59000,0 -15691863,Female,46,41000,1 -15706071,Male,51,23000,1 -15654296,Female,50,20000,1 -15755018,Male,36,33000,0 -15594041,Female,49,36000,1 \ No newline at end of file diff --git a/machine_learning/random_forest_classification/random_forest_classification.py b/machine_learning/random_forest_classification/random_forest_classification.py deleted file mode 100644 index 6aed4e6e66de..000000000000 --- a/machine_learning/random_forest_classification/random_forest_classification.py +++ /dev/null @@ -1,103 +0,0 @@ -# Random Forest Classification - -# Importing the libraries -import os -import numpy as np -import matplotlib.pyplot as plt -import pandas as pd - -# Importing the dataset -script_dir = os.path.dirname(os.path.realpath(__file__)) -dataset = pd.read_csv(os.path.join(script_dir, "Social_Network_Ads.csv")) -X = dataset.iloc[:, [2, 3]].values -y = dataset.iloc[:, 4].values - -# Splitting the dataset into the Training set and Test set -from sklearn.model_selection import train_test_split - -X_train, X_test, y_train, y_test = train_test_split( - X, y, test_size=0.25, random_state=0 -) - -# Feature Scaling -from sklearn.preprocessing import StandardScaler - -sc = StandardScaler() -X_train = sc.fit_transform(X_train) -X_test = sc.transform(X_test) - -# Fitting Random Forest Classification to the Training set -from sklearn.ensemble import RandomForestClassifier - -classifier = RandomForestClassifier( - n_estimators=10, criterion="entropy", random_state=0 -) -classifier.fit(X_train, y_train) - -# Predicting the Test set results -y_pred = classifier.predict(X_test) - -# Making the Confusion Matrix -from sklearn.metrics import confusion_matrix - -cm = confusion_matrix(y_test, y_pred) - -# Visualising the Training set results -from matplotlib.colors import ListedColormap - -X_set, y_set = X_train, y_train -X1, X2 = np.meshgrid( - np.arange(start=X_set[:, 0].min() - 1, stop=X_set[:, 0].max() + 1, step=0.01), - np.arange(start=X_set[:, 1].min() - 1, stop=X_set[:, 1].max() + 1, step=0.01), -) -plt.contourf( - X1, - X2, - classifier.predict(np.array([X1.ravel(), X2.ravel()]).T).reshape(X1.shape), - alpha=0.75, - cmap=ListedColormap(("red", "green")), -) -plt.xlim(X1.min(), X1.max()) -plt.ylim(X2.min(), X2.max()) -for i, j in enumerate(np.unique(y_set)): - plt.scatter( - X_set[y_set == j, 0], - X_set[y_set == j, 1], - c=ListedColormap(("red", "green"))(i), - label=j, - ) -plt.title("Random Forest Classification (Training set)") -plt.xlabel("Age") -plt.ylabel("Estimated Salary") -plt.legend() -plt.show() - -# Visualising the Test set results -from matplotlib.colors import ListedColormap - -X_set, y_set = X_test, y_test -X1, X2 = np.meshgrid( - np.arange(start=X_set[:, 0].min() - 1, stop=X_set[:, 0].max() + 1, step=0.01), - np.arange(start=X_set[:, 1].min() - 1, stop=X_set[:, 1].max() + 1, step=0.01), -) -plt.contourf( - X1, - X2, - classifier.predict(np.array([X1.ravel(), X2.ravel()]).T).reshape(X1.shape), - alpha=0.75, - cmap=ListedColormap(("red", "green")), -) -plt.xlim(X1.min(), X1.max()) -plt.ylim(X2.min(), X2.max()) -for i, j in enumerate(np.unique(y_set)): - plt.scatter( - X_set[y_set == j, 0], - X_set[y_set == j, 1], - c=ListedColormap(("red", "green"))(i), - label=j, - ) -plt.title("Random Forest Classification (Test set)") -plt.xlabel("Age") -plt.ylabel("Estimated Salary") -plt.legend() -plt.show() diff --git a/machine_learning/random_forest_classification/random_forest_classifier.ipynb b/machine_learning/random_forest_classification/random_forest_classifier.ipynb deleted file mode 100644 index 7ee66124c371..000000000000 --- a/machine_learning/random_forest_classification/random_forest_classifier.ipynb +++ /dev/null @@ -1,196 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": 1, - "metadata": {}, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "C:\\Users\\Satyam\\AppData\\Roaming\\Python\\Python35\\site-packages\\sklearn\\ensemble\\weight_boosting.py:29: DeprecationWarning: numpy.core.umath_tests is an internal NumPy module and should not be imported. It will be removed in a future NumPy release.\n", - " from numpy.core.umath_tests import inner1d\n" - ] - } - ], - "source": [ - "# Importing the libraries\n", - "import numpy as np\n", - "import matplotlib.pyplot as plt\n", - "import pandas as pd\n", - "from sklearn.model_selection import train_test_split\n", - "from sklearn.preprocessing import StandardScaler\n", - "from sklearn.metrics import confusion_matrix\n", - "from matplotlib.colors import ListedColormap\n", - "from sklearn.ensemble import RandomForestClassifier" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "metadata": { - "collapsed": true - }, - "outputs": [], - "source": [ - "# Importing the dataset\n", - "dataset = pd.read_csv('Social_Network_Ads.csv')\n", - "X = dataset.iloc[:, [2, 3]].values\n", - "y = dataset.iloc[:, 4].values" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "metadata": { - "collapsed": true - }, - "outputs": [], - "source": [ - "# Splitting the dataset into the Training set and Test set\n", - "X_train, X_test, y_train, y_test = train_test_split(X, y, test_size = 0.25, random_state = 0)" - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "metadata": {}, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "C:\\Users\\Satyam\\AppData\\Roaming\\Python\\Python35\\site-packages\\sklearn\\utils\\validation.py:475: DataConversionWarning: Data with input dtype int64 was converted to float64 by StandardScaler.\n", - " warnings.warn(msg, DataConversionWarning)\n" - ] - } - ], - "source": [ - "# Feature Scaling\n", - "sc = StandardScaler()\n", - "X_train = sc.fit_transform(X_train)\n", - "X_test = sc.transform(X_test)" - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "[[63 5]\n", - " [ 3 29]]\n" - ] - } - ], - "source": [ - "# Fitting classifier to the Training set\n", - "# Create your classifier here\n", - "classifier = RandomForestClassifier(n_estimators=10,criterion='entropy',random_state=0)\n", - "classifier.fit(X_train,y_train)\n", - "# Predicting the Test set results\n", - "y_pred = classifier.predict(X_test)\n", - "\n", - "# Making the Confusion Matrix\n", - "cm = confusion_matrix(y_test, y_pred)\n", - "print(cm)" - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "metadata": {}, - "outputs": [ - { - "data": { - "image/png": "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\nAfyzKuSV3NT5AAAAAElFTkSuQmCC\n", - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "image/png": "iVBORw0KGgoAAAANSUhEUgAAAYQAAAEWCAYAAABmE+CbAAAABHNCSVQICAgIfAhkiAAAAAlwSFlz\nAAALEgAACxIB0t1+/AAAIABJREFUeJzt3XuYHGWZ9/HvPTPJJJqQxEAm4RDirBBR1KAoB8ObCKLo\nyiKiLmzURcWou66IqyhGRF2j664r63pYRURUsrIqooKgIjDRaOQgjiDkADuBcEgmEEjIQDLJzNzv\nH1Wd9PRU91RPV3VVT/8+15Ur3VXVVU91J89dz9ncHRERkZasEyAiIvmggCAiIoACgoiIhBQQREQE\nUEAQEZGQAoKIiAAKCFLCzBab2UNZp6NRpP19mdnXzezCovfvNbNeM+szs5nh350JXu8AM1trZpOT\nOmeWzOzDZvaprNPRKBQQGoCZ3W9mO8P//JvN7HIzm5J1umplZm5mT4X31Wdm2+p8/ViZuZm9zMyu\nM7NtZva4md1qZm+vRxrd/T3u/i9hOiYAXwRe5e5T3H1r+HdPgpf8KHC5u+80s7uLfptBM9tV9P5j\nY72AmV1pZh9PMM2F855iZveVbP4a8C4zm5H09cYjBYTGcaq7TwEWAEcBF2ScnqS8KMzUprj79Go/\nbGZtaSSq6PzHATcBK4HnADOB9wKvSfO6ZXQAk4C7az1R1PdmZu3A3wNXALj78wu/DfBb4H1Fv9Vn\na01DPbj7U8CNwJKs09IIFBAajLtvBn5JEBgAMLO/NrM/mdmTZvagmX2yaN+88En8781so5k9ZmbL\nivZPDkscT5jZPcBLi69nZkeYWVf4dHy3mf1N0b7LzexrZnZ9+NT4OzObbWb/GZ5vrZkdNZb7NLN3\nmdl94RP5z8zswKJ9bmb/aGb3AveG255rZjeEx68zszcXHf9aM7vHzHaY2cNm9iEzeyZwPXBg0VPv\ngSMSAv8OfMfdP+/uj3ngj+7+5ohjMbOPmtn/hde6x8xOL9r3HDNbaWbbw9/hf8PtZmYXm9mW8De8\ny8yOLPqOP2NmhwPrwlNtM7Obir6L54Sv283sC+Hv3GtBddPkcN9iM3vIzD5iZpuBb0ck/xhgm7vH\nrgIzs3eH3/fjZvZzMzso3N5qZl81s0fD+/2zmc03s/cDZwAXht/5DyPOGfnZcN/k8N/XgxaUlr8c\n3vdM4Gqgs+j3nBmesgv467j31NTcXX9y/ge4H3hl+Ppg4C7gS0X7FwMvIAjwLwR6gdeH++YBDnwT\nmAy8COgHjgj3/yvB09+zgEOAvwAPhfsmAPcBHwMmAicCO4D54f7LgceAlxA8ud4EbADeBrQCnwFu\nrnBfDjwnYvuJ4XlfDLQDXwZ+U/K5G8I0TwaeCTwIvB1oIyhBPQY8Lzx+E3BC+HoG8OKi7+2hCul7\nBjAIvKLCMcPOAbwJODD8Lf4WeAqYE+77PrAs3DcJWBhufzXwR2A6YMARRZ+5HPhMyW/ZFvUdAhcD\nPwu/l6nANcDnitI5AHw+/E4nR9zLPwI/L3OfXcA5Jdv+FlgDHB7+W9n7ewOnAauB/cL7fT4wK9x3\nJfDxCt9ppc/+N/Cj8LuaRvBwdFG47xTgvojzHQ88kvX/40b4oxJC4/iJme0gyPi2ABcVdrh7l7vf\n5e5D7n4nQcazqOTzn3L3ne7+Z+DPBIEB4M3Acnd/3N0fBP6r6DPHAlOAf3X33e5+E3AtcFbRMVd7\n8MS8i+AJbZe7f9fdB4H/JcicK7kjLH1sM7PCtZcAl7n7He7eT1A9dpyZzSv63OfCNO8EXgfc7+7f\ndvcBd/8TcBVB5gywB3ieme3n7k+4+x2jpKlgBkGGtCnm8bj7D939kfC3+F+CEszLitJxKHCgu+9y\n91VF26cCzwXM3de4e+xrQlDKAJYC54Xfyw7gs8CZRYcNEWSe/eH3Vmo6QcCP6z0EwWq9u+8BPgUs\nNLOO8J72C+8Jd7/b3bfEPG/kZy2o5noncK67b3P37QQPNGeWPxWE91R1dWQzUkBoHK9396kET3rP\nBfYv7DCzY8zs5kIRm+A/6v4ln99c9PppgowegqfZB4v2PVD0+kDgQXcfKtl/UNH73qLXOyPej9b4\n/WJ3nx7+eX/Rdfemw937gK0l1y1O86HAMUWBZRtBUJkd7j8DeC3wQFhlc9woaSp4giATnRPzeMzs\nbWbWXZSOI9n3W5xPUAK4Nax+e0d4fzcBXwG+Cmwxs0vMbL+41wwdQFCi+WPRtX8Rbi94NAzc5TxB\nEJjiOhT4etH1HiUohRxMUB33LeAbwGYLqhbjdoQo99kDCUoidxdd8yfArFHONxWoa4eFRqWA0GDc\nfSVBNcIXijb/D0FVwSHuPg34OkHGE8cmgqqigrlFrx8BDjGzlpL9D1eZ7Go9QpDZABDW988suW7x\nNL0PAiuLAst0Dxo+3wvg7re5+2kEGcdPgB9EnGMEd3+aoOrijDiJNrNDCarm3gfM9KCR/C+Ev4W7\nb3b3d7n7gcC7ga8V6v/d/b/c/SXA8wiqYD4c55pFHiMIwM8v+g6medAgvPeWRjnHneG143oQOLvk\ne58clhjd3b/o7kcRVGO+CDg3TjoqfHYTQcD5q5J7LLQVlDvvEQSlYhmFAkJj+k/gZDMrVPtMBR53\n911m9jLg76o41w+AC8xshpkdDPxT0b5bCEoT55vZBDNbDJxKUAecpu8DbzezBRb0fPkscIu731/m\n+GuBw83srWE6J5jZSy1oEJ9oZkvMbFpYrfEkwVM/BKWZmWY2rUJazgfOtqA/+0wAM3uRmUV9B88k\nyJQeDY97O0EJgfD9m8LvGIKncQeGwrQeY0G30qeAXUVpjCUsxX0TuNjMZoXXO8jMXl3FaW4Fphca\nhmP4OvDxogbfGWZ2Rvj6WDM7OqzmeQrYzfDvvezYiXKfDX+/y4Avmdn+FjjEzE4uOu+siJLIIoJS\nh4xCAaEBufujwHeBT4Sb/gH4dNjG8An2PQHH8SmC6pkNwK+A7xVdZzdBAHgNwRPo14C3ufvaWu+h\nEnf/NXAhQTvAJuCvqFBPHNaXvyo85hGC6rFC4ynAW4H7zexJguq0JeHn1hIEn56wCmJELyN3/z1B\nI/eJ4XGPA5cA10Ucew/wHwSlil6Chv7fFR3yUuAWM+sjKNGd68EYgv0IMvMnCH6LrQS9m6r1EYJO\nAH8I7/XXwPy4Hw5/78uBt8Q8/vsEVV0/Dq/XDRQy5+nhubYBPQT39aVw3yXAS8PvPCqwVvrsBwh+\n49uB7QTVYs8J9/2Z4Ht9IDz3s8LS5SsJu9JKZeauBXJEJGBmBxD0OjuqTMNzQzGzDwNT3f0Tox4s\nCggiIhJQlZGIiAAKCCIiElJAEBERIBjm3zAmTJ3gk/aflHUyRMaNvv4+XrIj2Ylz/zi1j9aWViZP\nGBczaI8Lfff3PebuB4x2XEMFhEn7T+LoTx6ddTJExo2VG7q4fWWy/6cmnNDFlGdOZcHsBaMfLHXR\ndXbXA6MfpSojEREJKSCIiAiggCAiIqGGakMQEcnClNYpnDn3TOZMnkNLTp+jhxhi085NXLnxSvoG\n+8Z0DgUEEZFRnDn3TI48+Ejap7YTLD2RP+7OzB0zOZMzuXTDpWM6Rz5DnYhIjsyZPCfXwQDAzGif\n2s6cybGX7xhBAUFEZBQttOQ6GBSYWU1VWpkFBDObZGa3hgto321mn8oqLSIikm0JoR840d1fBCwA\nTjGzYzNMj4hIrv32xt9yyrGn8KqXvopLvnRJ4ufPLCCEy+QVmsInhH80F7eISITBwUE+/dFP880r\nv8m1v7uWn1/9c+5bd1+i18i0DcHMWs2sG9gC3ODut0Qcs9TMbjez2/fs2FP/RIqIVGnqj66h86gT\nOXzWEXQedSJTf3RNzee88447mTtvLofMO4SJEyfy2te/lhuvvzGB1O6TaUBw90F3XwAcDLzMzI6M\nOOYSdz/a3Y+eMHVC/RMpIlKFqT+6htkfvJAJDz2CuTPhoUeY/cELaw4KvZt6mXPQvh5Esw+cTe+m\n3lqTO0wuehm5+zbgZuCUrNMiIlKLA5ZfTMvOXcO2tezcxQHLL84oRfFl2cvoADObHr6eTLA4d6qL\nt4uIpK3t4U1VbY+rY04Hm4rOsfmRzXTM6ajpnKWyLCHMAW42szuB2wjaEK7NMD0iIjUbOCh6YFi5\n7XG94KgX8MCGB3jogYfYvXs31/3kOk485cSazlkqs6kr3P1O4Kisri8ikoZHl53H7A9eOKzaaGjy\nJB5ddl5N521ra+PCz13IO9/8ToaGhjjjrDM47LmH1Zrc4ddI9GwiIk1uxxtPBYK2hLaHNzFw0Bwe\nXXbe3u21WHTyIhadvKjm85SjgCAikrAdbzw1kQBQb7noZSQiItlTQBAREUABQUREQgoIIiICKCCI\niEhIAUFEpEF87P0f4/gjjufUE9LpwaSAICLSIE4/83S+eeU3Uzu/AoKISMKuWX8NJ37nRI746hGc\n+J0TuWZ97dNfA7z0+Jcybca0RM4VRQPTREQSdM36a7jw5gvZNRBMXfFI3yNcePOFAJx6eL4Hq6mE\nICKSoItXX7w3GBTsGtjFxas1/bWISFPZ1Bc9zXW57XmigCAikqA5U6KnuS63PU8UEEREEnTececx\nqW3SsG2T2iZx3nG1TX8N8MGlH+Ss15zFhvs2sOiFi/jRFT+q+ZzF1KgsIpKgQsPxxasvZlPfJuZM\nmcN5x52XSIPyFy/5Ys3nqEQBQRpGb18vPU/00D/YT3trO50zOumYkuwSgiJJOPXwU3PfoyiKAoI0\nhN6+XtZtXceQDwHQP9jPuq3rABQURBKiNgRpCD1P9OwNBgVDPkTPEz0ZpUiayRBDuHvWyRiVuzPE\n0OgHlqGAIA2hf7C/qu0iSdq0cxP9O/pzHRTcnf4d/WzaOfburaoykobQ3toemfm3t7ancj21V0ix\nKzdeyZmcyZzJc2jJ6XP0EENs2rmJKzdeOeZzKCBIQ+ic0TmsDQGgxVronNGZ+LXUXiGl+gb7uHTD\npVknI3UKCNIQChlx0k/tUSWBSu0VjR4QSu83vxUgtVMpr3oKCNIwOqZ0JPofulxJoDQYFDR6e0XU\n/QKsmNXLki3jK6NUKW9s8lkZJlIH5UoC5aTVXlEvUfeLwbLO8ddTS73SxkYBQZpWpSf+FmsZ8T6N\n9op6Kne/G9sbu+QTRb3SxkYBQZpWuSf+9tZ25s+cv3d/4X2jVzWUu9+5/dHbV8zqZd6xq2lZ1MW8\nY1ezYlZvmslLVKXfVspTG4I0rUo9l5Jur8iDqPvFYXnPyJLPilm9LJ2/jqdbg2MfmNTP0vlBHXwj\ntDfUs1faeJJZCcHMDjGzm83sHjO728zOzSot0pw6pnSMy5JAOYX7xcEcDt3VzhVrjojM4Jd19uwN\nBgVPtw41THtDs/22ScmyhDAA/LO732FmU4E/mtkN7n5PhmmSJjMeSwKVdEzpYO2ja4Cg7eCtR6yJ\nDAjl2hUaqb2h2X7bJGQWENx9E7ApfL3DzNYABwEKCCIpWvTsxXtfr9zQRcuirhHHlBuf4DDi+KGV\ni6MOlQaUizYEM5sHHAXcErFvKbAUoH2mGoREklQcHIqV9uOHoA5+/v7Dq11WbuhKOYVST5n3MjKz\nKcBVwAfc/cnS/e5+ibsf7e5HT5g6of4JFGlCqoNvTpmWEMxsAkEwWOHuP84yLSIyXGkd/KqNq7j3\n8XszTJGkLbOAYGYGfAtY4+7prgsnIjVZuaGL1iGYsnv49gW9lk2CJBVZlhBeDrwVuMvMusNtH3P3\n68p9oK+/T3WWOVOuDloa16qNqxgcHBixfc/n2mDhwgxSJPWSZS+jVUBVjxcv2TGF21cenVKKpFpR\nvVOkduUeeqZNns6C2QvG/HkIAnich6ppu+CJWxYP36hYMO7lopeRiAxX2pVzwglddb3+9kmVA349\nupqmMX21psSuTAFBZJwZrRqv1mq+elTbpjF9tabEHp0CgkgORT2dx6kuqpfi9KVRWkhjkaLxvPBR\nUhQQRHIm7w31pSOd05DG9NWaEnt0mQ9MExEplcb01ZoSe3QKCCKSO50zOhNfpCiNc443qjISkdwp\n1Okn2SMojXOONwoIIpK47Tu3RbYvVNM+ksb01ZoSuzIFBBFJ1J7fLo7croGM+aeAICINTwPOkqGA\nICINTQPOkqNeRiLS0CoNOJPqKCCISEPTgLPkjFplZGb/BFzh7k/UIT3SYKJ6ksSdlVMkCe2t7ZGZ\nvwacVS9OG0IHcJuZ3QFcBvzS3cutwS1NJGoOmzRn5Tzp972cc1UPs7b2s2VmO5ee0cmNx6uOuNl1\nzuiMXP9ZA86qN2qVkbt/HDiMYHWzs4F7zeyzZvZXKadNZK+Tft/Lhy5fx+yt/bQAs7f286HL13HS\n73uzTppkTOs/JydWLyN3dzPbDGwGBoAZwI/M7AZ3Pz/NBIoAnHNVD5N2D284nLR7iHOu6oksJag0\n0Vw04CwZcdoQzgXeBjwGXAp82N33mFkLcC+ggCDDlBulOhaFka2ztkY3EEZtL5QmCgGkUJoAFBRE\nKohTQpgBvMHdHyje6O5DZva6dJIljarcKNWxKB7ZumVmO7MjMv8tM0c2HFZbmhCRQMWAYGatwBvd\n/ZNR+919TRqJEil16Rmdw576AZ6aAP+8qH9EaWTW1uhzlCtliEigYkBw90Ez+7OZzXX3jfVKlIwv\nScxhU3iyL24X+OdF/Xz/BSOPfXAaHLp95Pao0kReaSoGyUKcKqM5wN1mdivwVGGju/9NaqmS8aG7\nG/r6YFEyq4DdeHzHiCqfRRHHfe9ve0eUJnZNbOHSM8p3Q+ze3E3f7r6q07Rw7sKqPzMaTcUgWYkT\nED6VeipkXGo5d1sm140qTYzWy2j7zm1M21X9tVZu6Ep8yUut/StZGTUguPvKeiRExqes1geOKk2M\n5olbFld3ke7uVIKepmKQrMTpdnos8GXgCGAi0Ao85e77pZw2kcSktRh8GjQVg2QlTpXRV4AzgR8C\nRxOMSTgszUSJpCFqqo08SnMqBjVWSyVxRyrfZ2at7j4IfNvMfp9yukSaVlpr/6qxWkYTJyA8bWYT\ngW4z+zdgE/DMdJMlkrzEl3CM6uKUkDSmYlBjtYwmTkB4K0G7wfuA84BDgDOSuLiZXQa8Dtji7kcm\ncU6RKFk1bseRdDVOufYSB7CR29VYLQVxehkVpqzYSfJdUC8naKP4bsLnFWkIaVXjjGgv6e7GMuoG\nLI2jbEAws7sIHyqiuPsLa724u//GzObVeh6RRqVqHMmTSiWEXExcZ2ZLgaUAc9vV7U7GlzyMOVB3\nVikoGxBKZzfNirtfAlwCcPTUqVqpTcaVeo85aLEWrSwmZY26YpqZHWtmt5lZn5ntNrNBM3uyHokT\nSVtvXy+rH1xN1/1drH5wNb199V2BrXNGJy02/L9hWpm0gVYWk4rGOjDtOWkmSqQe8tAvP60xB5Wu\npwAg5WQ6MM3Mvg8sBvY3s4eAi9z9W0mcW2Q0eWnQVSYteZHpwDR3PyuJ84iMRR4adEXyZNQ2BIKB\naS0EA9OeIsGBadL4VszqZd6xq2lZ1MW8Y1ezYlZ96+BrUa7hVr1upFnFHphmZoPAz4CH3X1L2gmT\n/Fsxq5el89fxdGtQ7fLApH6Wzg/q4JdsyX8VSJqTyIk0orIlBDP7upk9P3w9DfgzwYjiP5mZqnqE\nZZ09e4NBwdOtQyzr7MkoRdXpmNKhXjciRSqVEE5w9/eEr98OrHf315vZbOB64Pupp05ybWN7dF17\nue15pAZdkX0qtSHsLnp9MvATAHffnGqKpGHM7Y+uay+3XUTyrVJA2GZmrzOzo4CXA78AMLM2YHI9\nEif5trynk2cMDv8n9IzBFpb3qA5epBFVqjJ6N/BfwGzgA0Ulg5OAn6edMMm/QsPxss4eNrb3M7e/\nneU9nQ3RoDzejVj7ocLaDWmtorZiVu+wfxuadyb/Ks1ltB44JWL7L4FfppkoqaPeXujpgf5+aG+H\nzk7oiJ8ZLLkLlvwU6AfagU5A8SBT1az9kNZo7ageaHhwPbXZ5FeskcqSY7Vk6L29sG4dDIU9hfr7\ng/cQ7xy9vbB2Lbjv+/zatcM+X/Pi9haxokuBp/jMWXrdkmvlecGdaqQxWnvGMV1sm8TIxXiMzKf1\n1prSlSkgNLJaM/Senn2fLRgaCrbH+fy9947MlN1h/Xro6WGoi9GDVKWAtmoVr3jLYNnL33xFGyxc\nOHo6qxVx3eJrtSzqGhboGjk4pDFau28ikSuz1XreWuVh7qq8U0BoFFEZZ60Zen+Z/5zltpcaGIje\nPjgY/Cmcq1yQGi2gLVzIzfeXHF/8HRyWUuN16XUBiuLO3tXIurtpafBVyCpNv13L07RheESrQZaj\nwPMyd1WeVVox7YOVPujuX0w+OQKMzPhmzoTNm0dmnKXBoCBuht7eHn1s0gsRlQtS1QS0WktDEqnc\naO2Zk2fW9DQ9qW0S/YP9uRoFrrmrRlephDA1/Hs+8FKCaSsATgV+k2aimlpUxvfIIyOPKxcMIH6G\n3tkJa9ZEb4+jtXVfSWA0UYGnmhJKraWhFNXcTpKCuNVY5abfrvppuqS0NKWtnXnT5+Wqvr7eixE1\nokq9jD4FYGa/Al7s7jvC958kWBtB0hCV8VXS0jL8+JaW+Bk6BI2nxe0AlRpxS3V0RAerKO3tI0s+\n5QJKVECrtXorDQsWMLQyu8uXM6LL6SiiRmuveSziQYHRn6ZLA1GeqmI6Z3Ry35a17GnZ9+99wpDR\nuX+nGptDcdoQ5jJ81PJuYF4qqZHqMrjitoSx9DLq6YluFI771L11a7zrtLQE1V6lJZ+o4FMuoNWr\nemucKFdqiVNyWLVxFTiRDcON/DT9d3fCwbc6n1wMG6fB3O3wyS7n54u28+NnbVZjM/ECwveAW83s\n6vD96wkmuZNaRTUUl8v4ShUyzo6OsVeZ1PrUXem4wn1UagB3h7a2oKQwWkDr7BzZblJtaahJ7G30\nLhZW6azc0AVmLJpXYaQaMHkPWGvL8MkLHfoH+nNZRRbHOVf1MHsrnN09fPuFJz7CUMlzUbM2NseZ\n/nq5mV0PnBBueru7/yndZDWBco2ks2cPb0CGIOObPTt4Io9TEog7NqHWp+5Knz/uuOHbotoqIOip\nFKfraCH9NQyia2oLFjC0fBUALcsGWLVxFQvnlv/e2wfhK/fNH1ej0GdtjX6AeXhq5OambGyO2+30\nGcCT7v5tMzvAzJ7t7hvSTNi4V66RdOtWmD+/PoPNqn3qHq33U6XPJ1HlU0tpqFo1juCuRukUD6ll\nvGHgnbari74Yax4u2dLR0AGg1JaZ7cyOCAoH7YCH9ht5fCNXj43VqCummdlFwEeAC8JNE4Ar0kxU\nU6hUXdPRETxhL14c/F1NRlSpN06pjo4g+BQy5fb24H3U9QqBppDu/v4gGMyeHe/zM2dGp3fyZFi9\nGrq6gr97c7DiWm8vK9rWMO+9/bRcBPPe28+KtjWppG3FrF6WHr6WByb14xYuMnT42oZaea5RXHpG\nJ7smDs/ydk1s4djBA2mx4duz7iKblTglhNOBo4A7ANz9ETMrU8iS2NJqJK22XSDuU3elEk1p9VCU\ncg3Q24oGduVkbMGKSetZ+hp4emLw/oHpsPRU4Pr1LEl4oqZlh97L023DK7CfbnOWHXrvmJ/O4/Qy\nah3LiVetit6exmjxFNx4fPB9nnNVD7O29rNlZjuXntHJY0d1ML9vmnoZES8g7HZ3NzMHMLMYhU0Z\nVVqNpHkJNGM9Ls2xBTGrgT62aHBvMCh4eiIsWzTIku4Rh9dUvbTxGdGjvcttjyvp6TReMW8lKxdF\nzx2Vx6635dx4fMfewFBMCyUF4gSEH5jZN4DpZvYu4B3ApekmqwlUaiStpf46r4Embu8pSGdsQRVt\nKw9Oiz7FxqjtNY6gnrs9KIFEbc+diN5JjdrjSKLF6WX0BTM7GXiSYNTyJ9z9htRT1gyiqmtqnaIh\nrd44tQaaqM+Xk8bYgipGOh/0JDwUkflHZtLlzhtO8Dfab7B8ZStLXzO8RPKM3cF2ygSmzLgrAIxz\nowYEM/u8u38EuCFimyQtiSka0uiNU2ugifp8Nb2UalVFldfnfg3vPpWRmfSNQGnbeLnzxpzgb8mu\nw+GaNSw7ad9gqeU3wpKBw2sKCKNl3K2tbRW7nZa6+f5FcEW5NoQqEia5FqfK6GSCXkbFXhOxTZKQ\nxykaCmoNNFGfnzYtd2ML3rK+Hbumf2Qmvb4dStvP41aFlQvqHR0s6YUl/53cdxA5MK3IjGPidTsd\noUEaj2XsKs12+l7gH4BOM7uzaNdU4HdpJ6xpNdsUDfUcWxBXZydL7l7HkrtKSi7zI0ou1VSF1drT\nK88WLAD2rRMxbfJ0FsxekGmSpHqVSgj/A1wPfA74aNH2He7+eKqpamaaoiEd1QTaaqrHoo4dHIxe\nK6KGoD7jmC62T4reN1qJoF6GlrfxircMsvJQrZ7cqCrNdrod2A6cBWBms4BJwBQzm+LuG+uTxCaj\nKRrSUW2greapvfTY0o4Bo10rRq+yvonRH82N7m5alg2EExaaSgcNKk6j8qnAF4EDgS3AocAa4Pm1\nXtzMTgG+RDBO5lJ3/9dazzkujIcqhLypZ6Ct5loxe5Xt+e3i5NOZgtEmzZN8i9Oo/BngWODX7n6U\nmb2CsNRQCzNrBb5K0Gj9EHCbmf3M3e+p9dwNo47z5QixA+0r5q2ku2N4tceCXgt62iR8rdi9yrq7\nmfHukct1PvEfKa0rLU0pTkDY4+5bzazFzFrc/WYz+3wC134ZcJ+79wCY2ZXAaUBzBAQtCZlbUXXg\nKw91uD+Fi8XsVTbhn7YxGDHzWMuygcRGCicxxkCNyo0tTkDYZmZTCJbNXGFmW4DaxtUHDgIeLHr/\nEHBM6UFmthRYCjB3PPW0yfGSkM2uro20MRu761VlNOZ7L1o9bsIJXUklR+ps1NlOCZ7adwLnAb8A\n/o9gXeW6cPdL3P1odz/6gAkT6nXZ9OV5vIHUT2dn0OBcTL3KJCNxpq54CsDM9gOuSfDaDwOHFL0/\nONzWHJptvIFEU68yyZE46yG828w2A3cCtwN/DP+u1W3AYWb2bDObCJwJ/CyB8zYGPRmKSM7EaUP4\nEHCkuz9738tOAAAQ1UlEQVSW5IXdfcDM3gf8kqDb6WXufneS18i1NJ8Mo3ovpXUtqY06F0iOxAkI\n/wc8ncbF3f064Lo0zt0Q0hhvEJXBrFkTDBhy37dNmU5l9eoSrM4FkiNxAsIFwO/N7BZgb6W3u78/\ntVTJ2EVlMLAvGBQo0ymvnk/t6lwgORInIHwDuAm4C4gxg5dkqpqMRJlOtCSe2uNW26lzQSJ6+3q1\nBGYC4gSEAXf/YOopkWRUszKZMp1otT61V1NtN3t2/daEGKd6+3pZt3UdQx58h/2D/azbGpToFBSq\nEycg3BwODruG4VVGmvE0j8pNx1ycGcG+TKfWuvL16+GRR/a9P/BAOPzw2u4ha7U+tVdTbbd1K8yf\nn5sG/5ZFXQAseqDKqTq6u2k5d+TUGvXQ80TP3mBQMORD9DzRo4BQpTgB4e/Cvy8o2uaAHmHyqFzv\npXLbaqkrLw0GsO99HoNC3OBX6xTk1VbbaTLDmvQPRn/f5bZLeXEGpj27HgmRBJXLYEq3rV5dW115\naTAo3p63gFBNQ3GtXYIbuNquEaeuaG9tj8z821vz9d02gkorpp3o7jeZ2Rui9rv7j9NLltRFmj1c\nVq/ORRXIXvXs3llttV3CCtU+lUybOKXiZ6ftgiduWZxcolLUOaNzWBsCQIu10DlDlRjVqlRCWETQ\nuyhq3iIHFBAaXWvrvoXgS7fXqhBU8jLmoZrgV2u302qq7VL6ThY9e/GYP7Nq4yqSmb+yPgrtBOpl\nVLtKK6ZdFL78tLtvKN5nZqpGGg/Mqtte6sADy1cbFcvDmIdqGoqTKE3ErbaTRHRM6VAASECc2U6v\nitj2o6QTIhmIWve30vZShx8eBIU4sh7zUM3cURosJk2qUhvCcwmWyZxW0o6wH8HaylKrrFdMS2JQ\n1OGHD29ALrQd1HLONORhVtGsf2+RUVRqQ5gPvA6YzvB2hB3Au9JMVFPIw6RmtXavrNc5k5Jl987e\nXli7dvjAtLVr96Wr0WU4DkGSU6kN4afAT83sOHdfXcc0NYc8TGqWxlNzHp7Ey4n7hF6u5NTWNvbe\nU/feO3JgmnuwPQ/fTULG0pgt+RFnYNrpZnY3wappvwBeBHzA3a9INWXjXV7qqdN4aq7mnPWqRqmm\nRBZVyjEL2lYK7SvVluhqba8pI04X02pNmTiF7YPbqjt3FYOaJb/iBIRXufv5ZnY6wbrHbwJuBhQQ\nalHvSc3yWH9dz2qzakpkUaWcgYGRXXTz0HuK5J/KF8xekOj5pHHECQiFhYz/Gvi+uz9ucbslSnn1\nrGvPQ3tFlHpWm1VbIist5XR1Vff50gBcOiitIIkxHyIJiRMQrjGztQRVRu81swOAXekmqwnUs649\nD+0VUepZbVbrILxqSnRRAbjcQ1Tepvgoo3tzN9t3xm80VltCY4ozl9FHzezfgO3uPmhmTwOnpZ+0\nJlCvXi95aa8oVc9qs1oH4VVToosKwO5Bo3Rra76q7WLavnMbQ8vbYOHCUY+dcEIX3Zu7VfXUgMoO\nTDOz84venuTugwDu/hSg1dIaSbkMNuuxAdUMFqtVrY26HR3BNNWF76y9PXgflaGXC7QDA3DccbB4\ncfB3gwQDaR6VSghnAv8Wvr4A+GHRvlOAj6WVKElYXscGpFltVlqHX67KqJqgGLdEl+NV0E76fS/n\nXNXDrK39bJnZzqVndHLj8c0RmFZu6IrcPm3ydJVmQpUCgpV5HfVe8izPYwPSqDaLW4efVlDMUQAu\nzgTPugs+dG0Lk3YH6Zq9tZ8PXR50Lrjx+I6yGWa1puyG7S3bEjtfkkqrvQpTdWsJzkClgOBlXke9\nl7xrpkVYsq7Dz0EALmRwDhza387ym1t4+dqde4NBwaTdQ5xzVc/eUkLF9RBGbz4AKkyb3d0d7wRp\nWjiyJNA/0K8lOEOVAsKLzOxJgtLA5PA14XvNZST5VakOP0ajaCIyDMDD1hg2eGBSP285Bb73NMy7\na+Txs7bu+77GMtBtaOXiEVNXRAWWPExtMbR81Yh/A7sGduElz7jNugRnpakr1EFaGlOO6/DrIWqN\nYQwuOBneEhEQtswMvpexdBVduaGLCSd0MRiOVF707MWs3NBFy6IuWiOWlc6yO+rKDV20LBugdahr\n77bBFkYEg4JmXIIzzjgEkcaSozr8LJTLyB6aCrsmtgyrNto1sYVLzxj797Lo2Yvp3hxUBRUaZou3\nFat3w21pu8BzDziCTX2bRhy3c89OLcEZUkCQ8ScHdfhZKrvGcFs7Xzi7M/FeRlEZfda9doZVm7Gv\nXWD+zPkjqoFKj4XmXYJTAUHGp2ZqRC9RaY3hGw/paIpuplHVZuXaBbQE5z6ZBAQzexPwSeAI4GXu\nfnsW6RAZj5LK4Bq5K2a5arNy27UEZyCrEsJfgDcA38jo+jIWeZwxVSLVmsGVq3IpnDvvylabNWG7\nQDUyCQjuvgZAs6Y2kHrOmKrAk7lqqlyqUa9SR6VqMylPbQgyUlSGXK8ZU/M6Vfc4FpVJV1vlEvc6\n9Sp1qF1gbFILCGb2a2B2xK5l4fKccc+zFFgKMLdJ+pFnqlyGXBoMCpKeMTWvU3XnWC1P3eUy6VZr\nZdBHzv1US5VLWqWOctQuUL3UAoK7vzKh81wCXAJw9NSpmjIjbeUy5HKSDtJ5nao7pxxqeuoul0m3\ntbTRQkuiVS5plDokWWWnv5YmVSnjLW3zMUt+sFdep+rOsXJP3XGUy4wHhgaYP3P+3hJBe2t7ZB/+\napQrXaihNz+y6nZ6OvBl4ADg52bW7e6vziItUqLctA9tbSPXDohaErJWTT7KOClxn7or9cZJuspF\nDb35l1Uvo6uBq7O4dtOK23OnXIZcLvNPum6/yUcZJyXuU3elTDrpHkFq6M0/9TJqBtX03CmXIa9Z\nE33uNOr2m3iU8Vi02Njr+stl0lBb20Sl6ykA5JcCQh4l3Q+/2p47URlyIT2lVLefKQPmz5xf01N3\nVCa9+sHVde0RJPmggJA3afTDT6LnTqPV7TfR4LY0nrrVI6g5qZdR3lR6mh+rJHruVLPIfNYKQbUQ\n8ApBtbc323Q1EPUIak4qIeRNGv3wk3q6b5S6fQ1uq5l6BDUnBYS8SWO1r2bruaPBbTVTj6DmpICQ\nN2nV1TfK030SKo2lWL163AXFtCaMU4+g5qOAkDfN9jSfhqigahYMrCsMrhsnk+aVm7ri/m330942\nvFSZ9Spmkn8KCHmUxtN8Wr1u8tibJyqoDgzAYMlkbeOkXSGqe+jOPTvZ079z2PaVG7oyXeRe8k8B\noRmkNaV0nqeqLg2qXV3Rx43jdoVB9SGUKikgNIO0et3UuzdPHksj0lBWbVw1YtvCuQszSEk+KSA0\ng7R63dSzN0+eSyMZK526Aocr1hzBki1F30t3Ny3nbqt/4nJk5YYuWodgyu5927ZPgu7N3WpfCalQ\n2QzSmlK6nlNV1zpgb5xOq12YuqJ4mmpgeDCQvfZ8ro0nblm8909rhaU+mpFKCM0gra6saZ03qmqo\n1tJIo029UYXS7qErN3RllxhpaAoIzSCtrqxpnLdc1VDUegwQ/wlf3XlFRqWA0CzSGpiW9HnLVQ2Z\nBU/0tTzhN9PgPJExUBuC5Eu5KqDBwcaZXE+kQamEIPlSaS4nPeGLpEolBMmXzs6gKqjYOGn8Fck7\nlRAkX9T4K5IZBQTJH1UNiWRCVUYiIgIoIIiISEgBQUREAAUEEREJKSCIiAiggCAiIiEFBBERATIK\nCGb272a21szuNLOrzWx6FukQEZF9sioh3AAc6e4vBNYDF2SUDhERCWUSENz9V+5emNz+D8DBWaRD\nRET2yUMbwjuA68vtNLOlZna7md3+6J49dUyWiEhzSW0uIzP7NTA7Ytcyd/9peMwyYABYUe487n4J\ncAnA0VOnegpJFRERUgwI7v7KSvvN7GzgdcBJ7q6MXkQkY5nMdmpmpwDnA4vc/eks0iAiIsNl1Ybw\nFWAqcIOZdZvZ1zNKh4iIhDIpIbj7c7K4roiIlJeHXkYiIpIDCggiIgIoIIiISEgBQUREAAUEEREJ\nKSCIiAiggCAiIiEFBBERARQQREQkpIAgIiKAAoKIiIQUEEREBFBAEBGRkAKCiIgACggiIhJSQBCR\npjVld9YpyBdrpOWMzWwHsC7rdKRgf+CxrBORgvF6XzB+72283heM33uLc1+HuvsBo50okxXTarDO\n3Y/OOhFJM7PbdV+NZbze23i9Lxi/95bkfanKSEREAAUEEREJNVpAuCTrBKRE99V4xuu9jdf7gvF7\nb4ndV0M1KouISHoarYQgIiIpUUAQERGgwQKCmf2Lmd1pZt1m9iszOzDrNCXFzP7dzNaG93e1mU3P\nOk1JMLM3mdndZjZkZg3f5c/MTjGzdWZ2n5l9NOv0JMXMLjOzLWb2l6zTkiQzO8TMbjaze8J/h+dm\nnaakmNkkM7vVzP4c3tunaj5nI7UhmNl+7v5k+Pr9wPPc/T0ZJysRZvYq4CZ3HzCzzwO4+0cyTlbN\nzOwIYAj4BvAhd7894ySNmZm1AuuBk4GHgNuAs9z9nkwTlgAz+39AH/Bddz8y6/QkxczmAHPc/Q4z\nmwr8EXj9OPnNDHimu/eZ2QRgFXCuu/9hrOdsqBJCIRiEngk0TjQbhbv/yt0Hwrd/AA7OMj1Jcfc1\n7j5eRpe/DLjP3XvcfTdwJXBaxmlKhLv/Bng863Qkzd03ufsd4esdwBrgoGxTlQwP9IVvJ4R/asoT\nGyogAJjZcjN7EFgCfCLr9KTkHcD1WSdCRjgIeLDo/UOMk8ylGZjZPOAo4JZsU5IcM2s1s25gC3CD\nu9d0b7kLCGb2azP7S8Sf0wDcfZm7HwKsAN6XbWqrM9q9hccsAwYI7q8hxLkvkSyZ2RTgKuADJTUN\nDc3dB919AUGNwsvMrKbqvtzNZeTur4x56ArgOuCiFJOTqNHuzczOBl4HnOQN1LhTxW/W6B4GDil6\nf3C4TXIsrF+/Cljh7j/OOj1pcPdtZnYzcAow5o4BuSshVGJmhxW9PQ1Ym1VakmZmpwDnA3/j7k9n\nnR6JdBtwmJk928wmAmcCP8s4TVJB2PD6LWCNu38x6/QkycwOKPRGNLPJBJ0dasoTG62X0VXAfIJe\nKw8A73H3cfGEZmb3Ae3A1nDTH8ZDDyozOx34MnAAsA3odvdXZ5uqsTOz1wL/CbQCl7n78oyTlAgz\n+z6wmGAq5V7gInf/VqaJSoCZLQR+C9xFkG8AfMzdr8suVckwsxcC3yH4t9gC/MDdP13TORspIIiI\nSHoaqspIRETSo4AgIiKAAoKIiIQUEEREBFBAEBGRkAKCSExm9nozczN7btZpEUmDAoJIfGcRzCh5\nVtYJEUmDAoJIDOFcOAuBdxKMUMbMWszsa+Fc9Nea2XVm9sZw30vMbKWZ/dHMfhlOwyySawoIIvGc\nBvzC3dcDW83sJcAbgHnAC4BzgONg79w5Xwbe6O4vAS4DxsWIZhnfcje5nUhOnQV8KXx9Zfi+Dfih\nuw8Bm8PJxSCYXuVI4IZgKh1agU31Ta5I9RQQREZhZs8CTgReYGZOkME7cHW5jwB3u/txdUqiSCJU\nZSQyujcC33P3Q919XrgexwaCFcbOCNsSOggmhwNYBxxgZnurkMzs+VkkXKQaCggiozuLkaWBq4DZ\nBKum/QX4OsFKXNvD5TXfCHzezP4MdAPH1y+5ImOj2U5FamBmU8JFzmcCtwIvd/fNWadLZCzUhiBS\nm2vDRUomAv+iYCCNTCUEEREB1IYgIiIhBQQREQEUEEREJKSAICIigAKCiIiE/j8wn8IRk+gohgAA\nAABJRU5ErkJggg==\n", - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "# Visualising the Training set results\n", - "X_set, y_set = X_train, y_train\n", - "X1, X2 = np.meshgrid(np.arange(start = X_set[:, 0].min() - 1, stop = X_set[:, 0].max() + 1, step = 0.01),\n", - " np.arange(start = X_set[:, 1].min() - 1, stop = X_set[:, 1].max() + 1, step = 0.01))\n", - "plt.contourf(X1, X2, classifier.predict(np.array([X1.ravel(), X2.ravel()]).T).reshape(X1.shape),\n", - " alpha = 0.75, cmap = ListedColormap(('red', 'green')))\n", - "plt.xlim(X1.min(), X1.max())\n", - "plt.ylim(X2.min(), X2.max())\n", - "for i, j in enumerate(np.unique(y_set)):\n", - " plt.scatter(X_set[y_set == j, 0], X_set[y_set == j, 1],\n", - " c = ListedColormap(('red', 'green'))(i), label = j)\n", - "plt.title('Random Forest Classifier (Training set)')\n", - "plt.xlabel('Age')\n", - "plt.ylabel('Estimated Salary')\n", - "plt.legend()\n", - "plt.show()\n", - "\n", - "# Visualising the Test set results\n", - "X_set, y_set = X_test, y_test\n", - "X1, X2 = np.meshgrid(np.arange(start = X_set[:, 0].min() - 1, stop = X_set[:, 0].max() + 1, step = 0.01),\n", - " np.arange(start = X_set[:, 1].min() - 1, stop = X_set[:, 1].max() + 1, step = 0.01))\n", - "plt.contourf(X1, X2, classifier.predict(np.array([X1.ravel(), X2.ravel()]).T).reshape(X1.shape),\n", - " alpha = 0.75, cmap = ListedColormap(('red', 'green')))\n", - "plt.xlim(X1.min(), X1.max())\n", - "plt.ylim(X2.min(), X2.max())\n", - "for i, j in enumerate(np.unique(y_set)):\n", - " plt.scatter(X_set[y_set == j, 0], X_set[y_set == j, 1],\n", - " c = ListedColormap(('red', 'green'))(i), label = j)\n", - "plt.title('Random Forest Classifier (Test set)')\n", - "plt.xlabel('Age')\n", - "plt.ylabel('Estimated Salary')\n", - "plt.legend()\n", - "plt.show()" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": true - }, - "outputs": [], - "source": [] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.5.1" - } - }, - "nbformat": 4, - "nbformat_minor": 2 -} diff --git a/machine_learning/random_forest_regression/Position_Salaries.csv b/machine_learning/random_forest_regression/Position_Salaries.csv deleted file mode 100644 index 0c752c72a1d1..000000000000 --- a/machine_learning/random_forest_regression/Position_Salaries.csv +++ /dev/null @@ -1,11 +0,0 @@ -Position,Level,Salary -Business Analyst,1,45000 -Junior Consultant,2,50000 -Senior Consultant,3,60000 -Manager,4,80000 -Country Manager,5,110000 -Region Manager,6,150000 -Partner,7,200000 -Senior Partner,8,300000 -C-level,9,500000 -CEO,10,1000000 \ No newline at end of file diff --git a/machine_learning/random_forest_regression/random_forest_regression.ipynb b/machine_learning/random_forest_regression/random_forest_regression.ipynb deleted file mode 100644 index 17f4d42bfb0d..000000000000 --- a/machine_learning/random_forest_regression/random_forest_regression.ipynb +++ /dev/null @@ -1,147 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": 1, - "metadata": { - "collapsed": true - }, - "outputs": [], - "source": [ - "# Importing the libraries\n", - "import numpy as np\n", - "import matplotlib.pyplot as plt\n", - "import pandas as pd\n", - "from sklearn.ensemble import RandomForestRegressor" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "metadata": { - "collapsed": true - }, - "outputs": [], - "source": [ - "# Importing the dataset\n", - "dataset = pd.read_csv('Position_Salaries.csv')\n", - "X = dataset.iloc[:, 1:2].values\n", - "y = dataset.iloc[:, 2].values" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "RandomForestRegressor(bootstrap=True, criterion='mse', max_depth=None,\n", - " max_features='auto', max_leaf_nodes=None,\n", - " min_impurity_split=1e-07, min_samples_leaf=1,\n", - " min_samples_split=2, min_weight_fraction_leaf=0.0,\n", - " n_estimators=300, n_jobs=1, oob_score=False, random_state=0,\n", - " verbose=0, warm_start=False)" - ] - }, - "execution_count": 3, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "# Fitting Random Forest Regression to the dataset\n", - "regressor = RandomForestRegressor(n_estimators = 300, random_state = 0)\n", - "regressor.fit(X, y)" - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "metadata": { - "collapsed": true - }, - "outputs": [], - "source": [ - "# Predicting a new result\n", - "y_pred = regressor.predict(6.5)" - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "[ 160333.33333333]\n" - ] - } - ], - "source": [ - "print(y_pred)" - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "metadata": {}, - "outputs": [ - { - "data": { - "image/png": "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\nAElFTkSuQmCC\n", - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "# Visualising the Random Forest Regression results (higher resolution)\n", - "X_grid = np.arange(min(X), max(X), 0.01)\n", - "X_grid = X_grid.reshape((len(X_grid), 1))\n", - "plt.scatter(X, y, color = 'red')\n", - "plt.plot(X_grid, regressor.predict(X_grid), color = 'blue')\n", - "plt.title('Truth or Bluff (Random Forest Regression)')\n", - "plt.xlabel('Position level')\n", - "plt.ylabel('Salary')\n", - "plt.show()" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": true - }, - "outputs": [], - "source": [] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.5.1" - } - }, - "nbformat": 4, - "nbformat_minor": 2 -} diff --git a/machine_learning/random_forest_regression/random_forest_regression.py b/machine_learning/random_forest_regression/random_forest_regression.py deleted file mode 100644 index 2599e97e957e..000000000000 --- a/machine_learning/random_forest_regression/random_forest_regression.py +++ /dev/null @@ -1,44 +0,0 @@ -# Random Forest Regression - -# Importing the libraries -import os -import numpy as np -import matplotlib.pyplot as plt -import pandas as pd - -# Importing the dataset -script_dir = os.path.dirname(os.path.realpath(__file__)) -dataset = pd.read_csv(os.path.join(script_dir, "Position_Salaries.csv")) -X = dataset.iloc[:, 1:2].values -y = dataset.iloc[:, 2].values - -# Splitting the dataset into the Training set and Test set -"""from sklearn.cross_validation import train_test_split -X_train, X_test, y_train, y_test = train_test_split(X, y, test_size = 0.2, random_state = 0)""" - -# Feature Scaling -"""from sklearn.preprocessing import StandardScaler -sc_X = StandardScaler() -X_train = sc_X.fit_transform(X_train) -X_test = sc_X.transform(X_test) -sc_y = StandardScaler() -y_train = sc_y.fit_transform(y_train)""" - -# Fitting Random Forest Regression to the dataset -from sklearn.ensemble import RandomForestRegressor - -regressor = RandomForestRegressor(n_estimators=10, random_state=0) -regressor.fit(X, y) - -# Predicting a new result -y_pred = regressor.predict([[6.5]]) - -# Visualising the Random Forest Regression results (higher resolution) -X_grid = np.arange(min(X), max(X), 0.01) -X_grid = X_grid.reshape((len(X_grid), 1)) -plt.scatter(X, y, color="red") -plt.plot(X_grid, regressor.predict(X_grid), color="blue") -plt.title("Truth or Bluff (Random Forest Regression)") -plt.xlabel("Position level") -plt.ylabel("Salary") -plt.show() diff --git a/machine_learning/reuters_one_vs_rest_classifier.ipynb b/machine_learning/reuters_one_vs_rest_classifier.ipynb deleted file mode 100644 index 968130a6053a..000000000000 --- a/machine_learning/reuters_one_vs_rest_classifier.ipynb +++ /dev/null @@ -1,405 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": 1, - "metadata": {}, - "outputs": [], - "source": [ - "try:\n", - " import nltk\n", - "except ModuleNotFoundError:\n", - " !pip install nltk" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "metadata": {}, - "outputs": [], - "source": [ - "## This code downloads the required packages.\n", - "## You can run `nltk.download('all')` to download everything.\n", - "\n", - "nltk_packages = [\n", - " (\"reuters\", \"corpora/reuters.zip\")\n", - "]\n", - "\n", - "for pid, fid in nltk_packages:\n", - " try:\n", - " nltk.data.find(fid)\n", - " except LookupError:\n", - " nltk.download(pid)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Setting up corpus" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "metadata": {}, - "outputs": [], - "source": [ - "from nltk.corpus import reuters" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Setting up train/test data" - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "metadata": {}, - "outputs": [], - "source": [ - "train_documents, train_categories = zip(*[(reuters.raw(i), reuters.categories(i)) for i in reuters.fileids() if i.startswith('training/')])\n", - "test_documents, test_categories = zip(*[(reuters.raw(i), reuters.categories(i)) for i in reuters.fileids() if i.startswith('test/')])" - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "metadata": {}, - "outputs": [], - "source": [ - "all_categories = sorted(list(set(reuters.categories())))" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "The following cell defines a function **tokenize** that performs following actions:\n", - "- Receive a document as an argument to the function\n", - "- Tokenize the document using `nltk.word_tokenize()`\n", - "- Use `PorterStemmer` provided by the `nltk` to remove morphological affixes from each token\n", - "- Append stemmed token to an already defined list `stems`\n", - "- Return the list `stems`" - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "metadata": {}, - "outputs": [], - "source": [ - "from nltk.stem.porter import PorterStemmer\n", - "def tokenize(text):\n", - " tokens = nltk.word_tokenize(text)\n", - " stems = []\n", - " for item in tokens:\n", - " stems.append(PorterStemmer().stem(item))\n", - " return stems" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "To begin, I first used TF-IDF for feature selection on both train as well as test data using `TfidfVectorizer`.\n", - "\n", - "But first, What `TfidfVectorizer` actually does?\n", - "- `TfidfVectorizer` converts a collection of raw documents to a matrix of **TF-IDF** features.\n", - "\n", - "**TF-IDF**?\n", - "- TFIDF (abbreviation of the term *frequency–inverse document frequency*) is a numerical statistic that is intended to reflect how important a word is to a document in a collection or corpus. [tf–idf](https://en.wikipedia.org/wiki/Tf%E2%80%93idf)\n", - "\n", - "**Why `TfidfVectorizer`**?\n", - "- `TfidfVectorizer` scale down the impact of tokens that occur very frequently (e.g., “a”, “the”, and “of”) in a given corpus. [Feature Extraction and Transformation](https://spark.apache.org/docs/latest/mllib-feature-extraction.html#tf-idf)\n", - "\n", - "I gave following two arguments to `TfidfVectorizer`:\n", - "- tokenizer: `tokenize` function\n", - "- stop_words\n", - "\n", - "Then I used `fit_transform` and `transform` on the train and test documents repectively.\n", - "\n", - "**Why `fit_transform` for training data while `transform` for test data**?\n", - "\n", - "To avoid data leakage during cross-validation, imputer computes the statistic on the train data during the `fit`, **stores it** and uses the same on the test data, during the `transform`. This also prevents the test data from appearing in `fit` operation." - ] - }, - { - "cell_type": "code", - "execution_count": 7, - "metadata": {}, - "outputs": [], - "source": [ - "from sklearn.feature_extraction.text import TfidfVectorizer\n", - "\n", - "vectorizer = TfidfVectorizer(tokenizer = tokenize, stop_words = 'english')\n", - "\n", - "vectorised_train_documents = vectorizer.fit_transform(train_documents)\n", - "vectorised_test_documents = vectorizer.transform(test_documents)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "For the **efficient implementation** of machine learning algorithms, many machine learning algorithms **requires all input variables and output variables to be numeric**. This means that categorical data must be converted to a numerical form.\n", - "\n", - "For this purpose, I used `MultiLabelBinarizer` from `sklearn.preprocessing`." - ] - }, - { - "cell_type": "code", - "execution_count": 8, - "metadata": {}, - "outputs": [], - "source": [ - "from sklearn.preprocessing import MultiLabelBinarizer\n", - "\n", - "mlb = MultiLabelBinarizer()\n", - "train_labels = mlb.fit_transform(train_categories)\n", - "test_labels = mlb.transform(test_categories)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Now, To **train** the classifier, I used `LinearSVC` in combination with the `OneVsRestClassifier` function in the scikit-learn package.\n", - "\n", - "The strategy of `OneVsRestClassifier` is of **fitting one classifier per label** and the `OneVsRestClassifier` can efficiently do this task and also outputs are easy to interpret. Since each label is represented by **one and only one classifier**, it is possible to gain knowledge about the label by inspecting its corresponding classifier. [OneVsRestClassifier](http://scikit-learn.org/stable/modules/multiclass.html#one-vs-the-rest)\n", - "\n", - "The reason I combined `LinearSVC` with `OneVsRestClassifier` is because `LinearSVC` supports **Multi-class**, while we want to perform **Multi-label** classification." - ] - }, - { - "cell_type": "code", - "execution_count": 9, - "metadata": {}, - "outputs": [], - "source": [ - "%%capture\n", - "from sklearn.multiclass import OneVsRestClassifier\n", - "from sklearn.svm import LinearSVC\n", - "\n", - "classifier = OneVsRestClassifier(LinearSVC())\n", - "classifier.fit(vectorised_train_documents, train_labels)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "After fitting the classifier, I decided to use `cross_val_score` to **measure score** of the classifier by **cross validation** on the training data. But the only problem was, I wanted to **shuffle** data to use with `cross_val_score`, but it does not support shuffle argument.\n", - "\n", - "So, I decided to use `KFold` with `cross_val_score` as `KFold` supports shuffling the data.\n", - "\n", - "I also enabled `random_state`, because `random_state` will guarantee the same output in each run. By setting the `random_state`, it is guaranteed that the pseudorandom number generator will generate the same sequence of random integers each time, which in turn will affect the split.\n", - "\n", - "Why **42**?\n", - "- [Why '42' is the preferred number when indicating something random?](https://softwareengineering.stackexchange.com/questions/507/why-42-is-the-preferred-number-when-indicating-something-random)" - ] - }, - { - "cell_type": "code", - "execution_count": 10, - "metadata": {}, - "outputs": [], - "source": [ - "%%capture\n", - "from sklearn.model_selection import KFold, cross_val_score\n", - "\n", - "kf = KFold(n_splits=10, random_state = 42, shuffle = True)\n", - "scores = cross_val_score(classifier, vectorised_train_documents, train_labels, cv = kf)" - ] - }, - { - "cell_type": "code", - "execution_count": 11, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Cross-validation scores: [0.83655084 0.86743887 0.8043758 0.83011583 0.83655084 0.81724582\n", - " 0.82754183 0.8030888 0.80694981 0.82731959]\n", - "Cross-validation accuracy: 0.8257 (+/- 0.0368)\n" - ] - } - ], - "source": [ - "print('Cross-validation scores:', scores)\n", - "print('Cross-validation accuracy: {:.4f} (+/- {:.4f})'.format(scores.mean(), scores.std() * 2))" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "In the end, I used different methods (`accuracy_score`, `precision_score`, `recall_score`, `f1_score` and `confusion_matrix`) provided by scikit-learn **to evaluate** the classifier. (both *Macro-* and *Micro-averages*)" - ] - }, - { - "cell_type": "code", - "execution_count": 12, - "metadata": {}, - "outputs": [], - "source": [ - "%%capture\n", - "from sklearn.metrics import accuracy_score, precision_score, recall_score, f1_score, confusion_matrix\n", - "\n", - "predictions = classifier.predict(vectorised_test_documents)\n", - "\n", - "accuracy = accuracy_score(test_labels, predictions)\n", - "\n", - "macro_precision = precision_score(test_labels, predictions, average='macro')\n", - "macro_recall = recall_score(test_labels, predictions, average='macro')\n", - "macro_f1 = f1_score(test_labels, predictions, average='macro')\n", - "\n", - "micro_precision = precision_score(test_labels, predictions, average='micro')\n", - "micro_recall = recall_score(test_labels, predictions, average='micro')\n", - "micro_f1 = f1_score(test_labels, predictions, average='micro')\n", - "\n", - "cm = confusion_matrix(test_labels.argmax(axis = 1), predictions.argmax(axis = 1))" - ] - }, - { - "cell_type": "code", - "execution_count": 13, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Accuracy: 0.8099\n", - "Precision:\n", - "- Macro: 0.6076\n", - "- Micro: 0.9471\n", - "Recall:\n", - "- Macro: 0.3708\n", - "- Micro: 0.7981\n", - "F1-measure:\n", - "- Macro: 0.4410\n", - "- Micro: 0.8662\n" - ] - } - ], - "source": [ - "print(\"Accuracy: {:.4f}\\nPrecision:\\n- Macro: {:.4f}\\n- Micro: {:.4f}\\nRecall:\\n- Macro: {:.4f}\\n- Micro: {:.4f}\\nF1-measure:\\n- Macro: {:.4f}\\n- Micro: {:.4f}\".format(accuracy, macro_precision, micro_precision, macro_recall, micro_recall, macro_f1, micro_f1))" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "In below cell, I used `matplotlib.pyplot` to **plot the confusion matrix** (of first *few results only* to keep the readings readable) using `heatmap` of `seaborn`." - ] - }, - { - "cell_type": "code", - "execution_count": 16, - "metadata": {}, - "outputs": [ - { - "data": { - "image/png": "iVBORw0KGgoAAAANSUhEUgAABSUAAAV0CAYAAAAhI3i0AAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAALEgAACxIB0t1+/AAAADl0RVh0U29mdHdhcmUAbWF0cGxvdGxpYiB2ZXJzaW9uIDIuMS4yLCBodHRwOi8vbWF0cGxvdGxpYi5vcmcvNQv5yAAAIABJREFUeJzs3Xl8lOW5//HvPUlYVRRRIQkVW1xarYUWUKsiFQvUqnSlP0+1ttXD6XGptlW7aGu1p9upnurpplgFl8qiPXUFi2AtUBGIEiAQQBCKCRFXVHAhJPfvjxnoCDPPMpPMM3fuz/v1mhfJJN9c1/XMTeaZJ8/MGGutAAAAAAAAAKBUUkk3AAAAAAAAAMAvHJQEAAAAAAAAUFIclAQAAAAAAABQUhyUBAAAAAAAAFBSHJQEAAAAAAAAUFIclAQAAAAAAABQUokdlDTGjDPGrDHGrDPGfC9m9nZjzIvGmIYC6g40xvzNGNNojFlpjLk0RraHMWaxMWZZJnttAfUrjDFLjTEPF5DdaIxZYYypN8bUxczub4y5zxizOjP7CRFzR2bq7bq8YYy5LGbtb2W2V4MxZqoxpkeM7KWZ3MoodXOtDWNMX2PMY8aYZzP/HhAj+8VM7XZjzLCYdX+V2d7LjTF/McbsHyP7k0yu3hgz2xhTHad21tcuN8ZYY0y/GLV/bIxpzrrNT49T1xhzSeb/9kpjzH/HqDs9q+ZGY0x9nJmNMUOMMU/t+v9hjBkRI/sRY8zCzP+vh4wx++XJ5vz9EWWNBWSjrrF8+dB1FpANXWf5sllfz7vGAupGXWN5a4ets4DaoessIBt1jeXLh64zk+d+xhhzmDFmUWaNTTfGdIuRvdik72uDfhfky/4ps50bTPr/TlXM/G2Z65ab9H3QPlGzWV//jTFmW8y6U4wxG7Ju6yEx88YY81NjzNrM7fjNGNn5WXU3G2Puj5EdbYx5JpNdYIwZHCN7aibbYIy5wxhTmWvmrJ/znv2RKGssIBu6xgKykdZYnmzo+grKZ12fd40F1I60xvJkQ9dXQDZ0fYXkQ9dYQDbyGjM59llN9P2xXNmo95W5spH2xwLykfbJcmWzvha2P5arbtT7ypx1TYT9sYDakfbJ8mSj3lfmykbaH8t8716PbWKssVzZqGssVzbqPn+ubJx9/ryP5yKssVy1o66xnHWjrLE8dePs8+fKR11jubJR9sVyPv6Nsb7y5UPXWEA28u8xwDnW2pJfJFVIWi/p/ZK6SVom6UMx8iMlfVRSQwG1B0j6aObjfSWtjVpbkpG0T+bjKkmLJB0fs/63Jd0j6eECet8oqV+B2/wOSRdkPu4maf8Cb7cXJB0aI1MjaYOknpnPZ0j6asTsMZIaJPWSVClpjqTD464NSf8t6XuZj78n6Zcxsh+UdKSkJyQNi1l3jKTKzMe/jFl3v6yPvynp5ji1M9cPlPRXSf/Mt27y1P6xpMsj3D65sp/I3E7dM58fHKfnrK/fIOlHMWvPlvSpzMenS3oiRnaJpFMyH39d0k/yZHP+/oiyxgKyUddYvnzoOgvIhq6zfNkoayygbtQ1li8fus6C+g5bZwF1o66xfPnQdaY89zNK/+78f5nrb5b0nzGyQyUNUsB9SED29MzXjKSpueqG5LPX2P8o8/8kSjbz+TBJd0naFrPuFElfiLDG8uW/JulOSamANRa6TyDpz5K+EqPuWkkfzFx/oaQpEbMfl/S8pCMy118n6fyQ2d+zPxJljQVkQ9dYQDbSGsuTDV1fQfkoayygdqQ1licbur6Ceg5bXyG1Q9dYrqzSJzJEXmO51oKi74/lyka9r8yVjbQ/FpCPtE+Wb/0r2v5Yrro/VrT7ylzZSPtjQX1nfT3vPlme2lHvK3NlI+2PZb6+12ObGGssVzbqGsuVjbrPnysbZ58/5+O5iGssV+2oayxXNuo+f+Bj0KD1FVA76hrLlY28xjLfs/vxb9T1FZCPtMbyZCP/HuPCxbVLUmdKjpC0zlr7nLV2h6RpksZHDVtr50l6tZDC1toWa+0zmY/flNSo9IGzKFlrrd31l/SqzMVGrW2MqZX0aUl/jNV0kTJ/ARop6TZJstbusNZuLeBHjZa03lr7z5i5Skk9Tfov6r0kbY6Y+6Ckp6y1b1lrd0r6u6TPBgXyrI3xSt8pKfPvZ6JmrbWN1to1YY3myc7O9C1JT0mqjZF9I+vT3gpYZwH/H34t6coCs6HyZP9T0i+ste9mvufFuHWNMUbSBKUfnMapbSXt+mtnH+VZZ3myR0qal/n4MUmfz5PN9/sjdI3ly8ZYY/nyoessIBu6zkJ+ZwausWJ+34bkQ9dZWO2gdRaQjbrG8uVD11nA/cypku7LXJ9vjeXMWmuXWms35uo1QnZm5mtW0mLl/z2WL/+GtHt791TuNZYza4ypkPQrpddYrL6DZo2Y/09J11lr2zPfl2uNBdY2xuyr9O2215lsAdnQNZYn2ybpXWvt2sz1eX+PZXp7z/5I5vYJXWO5spmeQtdYQDbSGsuTDV1fQfkoayxfNqo82dD1FVY3aH2F5CP9HsuRPVAx1lgekfbHcol6X5knG2l/LCAfeZ8sj9D9sU4QaX8sTJR9shwirbE8Iu2PBTy2CV1j+bJR1lhANnSNBWQjra+Qx3OBa6yYx4IB2dA1FlY3bH0F5EPXWEA20hrLkv34t5DfYbvzBfwey84W9XsMKGdJHZSsUfqvrbs0KcYD1Y5ijBmk9F/3F8XIVGROMX9R0mPW2shZSTcqfYfRHiOTzUqabYx52hgzMUbu/ZJekjTZpJ+G80djTO8C6v8/xdspkbW2WdL1kjZJapH0urV2dsR4g6SRxpgDjTG9lP5L2MA49TMOsda2ZPppkXRwAT+jWF+XNCtOwKSf2vW8pC9L+lHM7FmSmq21y+LkslyceXrA7fmempDHEZJONumnAP7dGDO8gNonS9pirX02Zu4ySb/KbLPrJX0/RrZB0lmZj7+oCOtsj98fsdZYIb97IuZD19me2TjrLDsbd43l6DnWGtsjH2ud5dlekdbZHtnYa2yPfKR1tuf9jNLPLNiatTOa9z6zmPuooKxJP6X2XEmPxs0bYyYr/Zf+oyT9Jkb2YkkP7vq/VUDfP82ssV8bY7rHzH9A0pdM+mlhs4wxh8esLaX/iDZ3jwecYdkLJM00xjQpvb1/ESWr9MG8qqyng31Bwb/H9twfOVAR11iObBx5sxHWWM5slPUVkI+0xgL6jrLGcmUjra+AulLI+grIR1pjObIvK94ay7XPGvW+stD93SjZsPvJnPmI95V7ZWPcV+brO8p9Za5snPvJoG0Wdl+ZKxv1vjJXNur+WL7HNlHWWDGPi6Jk862xvNmI6ytnPuIaC+o7bI3ly0ZZY2HbK2x95ctHWWP5snH3+bMf/xbymDL24+cI2diPK4FyltRBSZPjulL+9VAm/bpDf5Z0WcgO3XtYa9ustUOU/uvECGPMMRHrnSHpRWvt0wU1nHaitfajkj4l6SJjzMiIuUqln676B2vtUEnblT7lPDKTfm2psyTdGzN3gNJ/VTpMUrWk3saYc6JkrbWNSp+e/pjSD1KWSdoZGCpDxpirlO77T3Fy1tqrrLUDM7mLY9TrJekqxTyQmeUPSj9gGqL0geQbYmQrJR2g9NMQr5A0wxiT6/97kLNV2J33f0r6VmabfUuZv4xG9HWl/089rfTTbXcEfXOhvz+KzQblo6yzXNmo6yw7m6kTeY3lqBtrjeXIR15nAds7dJ3lyMZaYznykdbZnvczSp81vte3RclGvY+KkP29pHnW2vlx89baryn9+79R0pciZkcq/WAh6CBTUN3vK32QarikvpK+GzPfXdI71tphkm6VdHucmTMC11ie7LcknW6trZU0WemnJIdmJR2t9IOXXxtjFkt6U3nuL/Psj0TaLytmXyZCNu8aC8pGWV+58ib9um2hayygdugaC8iGrq8I2ytwfQXkQ9dYrqy11iriGssodJ+107IR98dy5iPeV+bKRr2vzJWNel+ZKxtnfyxoe4fdV+bKRr2vzJWNuj9WzGObTsuGrLG82YjrK1f+x4q2xvLVjrLG8mWjrLGwbR22vvLlo6yxfNnI+/yFPv7tiHy+bKGPK4GyZhN4zrikEyT9Nevz70v6fsyfMUgFvKZkJlul9OtufLvIOa5RhNfhyHzvz5U+82Cj0n/Rf0vS3UXU/nGM2v0lbcz6/GRJj8SsN17S7AL6/KKk27I+/4qk3xc4888kXRh3bUhaI2lA5uMBktbEXVeK8NofubKSzpO0UFKvuNmsrx0attaz85I+rPTZMxszl51Kn6nav4Dagf/PcmzrRyWNyvp8vaSDYmyvSklbJNUWcDu/LslkPjaS3ihwex8haXFAdq/fH1HXWK5szDWWMx9lnQXVDltne2bjrLEIdcPWWK7tHWmdBWyv0HWWp26cNRY2d+A6y/q+a5Te2X9Z/3otoffch4ZkL8/6fKMivi5xdjbz8f3KvP5d3HzWdacowuspZ7LXKH1fuWuNtSv9si+F1B0VpW52XtJqSYOybuvXY26zAyW9IqlHjLpXKP00rV3XvU/SqgJnHiNpRp7vz7U/8qcoayxP9u6sr+ddY0HZsDUWVjdsfeXJvxZljUWsnXON5ctGWV8h2yt0feXJPxJljUWcOe8ay/Hzfqz0/6vI+2N7ZrM+f0IRXottz6wi7o8F1c5cF7pPlpX9oWLsj4XUHRSj7uWKsT8WsM0i75PtUTvyfWXIzHnvJ5XnsU2UNZYvG2WNBWXD1lhY3bD1lSc/N8oai1g75xoL2Nahayxke0XZF8tXO3SNRZw5bJ//PY9/o6yvoHyUNRaUDVtjXLi4eknqTMklkg436Xd67Kb0X14fLEXhzF9wbpPUaK3NeQZCQPYgk3mnK2NMT0mnKb1jGcpa+31rba21dpDS8z5urY10xmCmXm+Tfv0gZU49H6P06edRar8g6XljzJGZq0ZLWhW1dkahZ69tknS8MaZXZtuPVvpshkiMMQdn/n2fpM8V2MODSv8SV+bfBwr4GbEZY8YpfebEWdbat2Jms5/KdZYirjNJstausNYebK0dlFlvTUq/6cYLEWsPyPr0s4q4zjLuV/o1rmSMOULpF5V+OUb+NEmrrbVNMTK7bFb6QakyPUR++nfWOktJulrpN3nI9X35fn+ErrFifvcE5aOss4Bs6DrLlY26xgLqRlpjAdssdJ2FbO/AdRaQjbTGAuYOXWd57mcaJf1N6adLSvnXWMH3UfmyxpgLJI2VdLbNvP5djPwak3ln38w2OTNXP3myT1tr+2etsbestbneiTpf3wOy6n5G+ddYvm22e40pfZuvjZGV0n+Qe9ha+06Muo2S+mTWtCR9UjnuLwNm3rW+uiv9OyHn77E8+yNfVoQ1Vsy+TL5slDWWKyvp3CjrK6D2AVHWWEDfoWssYHuFrq+QbR24vgK22XhFWGMBM0daYwH7rFHuKwve382Xjbo/FpCPcl+ZK7sk4n1lvrqh95UB2yvS/ljI9g67r8yXDb2vDJg50v5YwGOb0DVWzOOifNkoaywgG2mfP0/+mShrLKB26BoL2F6hayxkW4fu8wfkQ9dYwMyR1ljGno9/4z6mLPTx817ZqL/HACeV4shnrovSrw+4Vum/qlwVMztV6VPMW5X+5Rv4DpN7ZE9S+ilJyyXVZy6nR8weK2lpJtuggHcKC/k5oxTz3beVfl2MZZnLygK22RBJdZne75d0QIxsL6X/It+nwHmvVfoOtkHpd7jsHiM7X+k7n2WSRheyNpQ+o2Cu0ndYcyX1jZH9bObjd5X+a17Os5PyZNcp/dqpu9ZZvndrzJX9c2Z7LZf0kNJvSlLQ/wcFn7mSq/ZdklZkaj+ozF8EI2a7KX0WSIOkZySdGqdnpd/N9BsF3s4nSXo6s1YWSfpYjOylSv8+Wqv062uZPNmcvz+irLGAbNQ1li8fus4CsqHrLF82yhoLqBt1jeXLh66zoL7D1llA3ahrLF8+dJ0pz/2M0vcBizO3973K8Xs0IPvNzBrbqfSO/B9jZHcqfT+9a45878C6V17pl4j5R+a2blD6bLz9otbe43vyvft2vr4fz6p7tzLvVh0jv7/SZ2OsUPqshI/E6VvpsyDGBayxfHU/m6m5LPMz3h8j+yulDzCtUfolAwJ/j2Yyo/Svd2UOXWMB2dA1FpCNtMb2zEZdX0G1o6yxgL4jrbE82dD1FdRz2PoKqR26xgKykdaY8uyzKtp9Zb5s6H1lQDbq/li+fJT7ytD9dOW/r8xXN/S+MiAbdX8sb98Kv6/MVzv0vjIgG2l/LPO9ez22ibLGArJR98dyZaOusVzZOPv8gY/n8q2xgNpR98dyZaOusZw9h62vkNpR98dyZaPu8+/1+Dfq+grIR11jubKR1hgXLi5edp32DAAAAAAAAAAlkdTTtwEAAAAAAAB4ioOSAAAAAAAAAEqKg5IAAAAAAAAASoqDkgAAAAAAAABKioOSAAAAAAAAAEoq8YOSxpiJrmWTrO1j3z7OnGRtZnanNjO7U5uZ3anNzO7U9rFvH2dOsjYzu1Obmd2pzcylzwPlLPGDkpKK+Q+WVDbJ2j727ePMSdZmZndqM7M7tZnZndrM7E5tH/v2ceYkazOzO7WZ2Z3azFz6PFC2yuGgJAAAAAAAAACPGGttpxZ4/WunBRaYsqZZXz2yJufXDvxTY+DPbm/frlSqd0F9FZNNsraPffs4c5K1u+LMpohslN+QLm7vcr2tOjObZG1mdqc2M7tT28e+fZw5ydrM7E5tZnanNjN3bH7njuawhzpean35uc490OWYqn7vL9t1kvhBySBhByUBIIpifgNzbwYAAACgHHFQMjcOSr5XOR+U5OnbAAAAAAAAAEqKg5IAAAAAAAAASoqDkgAAAAAAAABKKu5BySMl1Wdd3pB02R7fc5SkhZLelXR5sQ1KUrdu3XTPn/6g1asW6MkFD2nqPTdrc9MyrVv7lBY9NUtLn5mjRU/N0idGnRjp540dM0orG+Zp9aoFuvKKi2L1klQ2ydqu9n3rpBu0uWmZ6pfOjZXriNouZrt3766F/3hYT9c9pmX1j+uaH32nZLWTXGPPrn1KS5+Zo7ols/XUwpklq+vq/ysf+/Zx5mLyrv7uTbK2j327OrOr69vH28rHvn2cOcnazOxH367ODDjDWlvopcJa+4K19tA9rj/YWjvcWvtTa+3lW7862ka9vP6df7OtjfW7P6+oqrYVVdX2oou/b2++5U5bUVVtz/7yN+zcx+fbYcPH2HXrNtja9w21FVXV9tghn7BNTZt3Z/JdqrrX2nXrNtjBRxxve/Q61NYvW2mPOfaU0FySWfourPaoT3zWDhs+xq5oaIycSbrvJLdXRVW13W//wbaiqtp27/k+u2jR0/bjJ55R9n1HyVcGXDZs2GQP6X903q+7OnO5ZV3t28eZi827+LvX19vKxWzStV1c3z7eVj727ePMrvbt48yu9u3CzEUcz+nSlx1b1lou/7okfXsEXULPlDTGHGWM+a4x5n+NMTdlPv6gpNGS1kv65x6RFyUtkdS658+qOmG0ev/wt9rn2pvV47zLJBPtRM2zzhyju+66V5L05z8/omM//CG9+tpWvf3OO2pp2SJJWrlyjXr06KFu3boF/qwRw4dq/fqN2rBhk1pbWzVjxgM668yxkfpIKkvfhdWev2CRXn1ta+TvL4e+k9xekrR9+1uSpKqqSlVWVcnaaG9a5uoaK4arM9M3M3d23sXfvUnW9rFvV2eW3FzfPt5WPvbt48yu9u3jzK727erMgEsCjwoaY74raZokI2mx0gcbjaSpTz755E8lTY1caMD7VDVilLb/7FJtu+YbUnu7qk4YHSlbXdNfzzdtliS1tbXp9dff0AH793nP93zuc59WfX2DduzYEflnSVJTc4uqq/vH7qOU2SRru9p3sVzc3h2xvVKplOqWzFZL83LNnTtPi5csLfu+i81bazVr5lQtemqWLjj/yyWp6+r/Kx/79nHmjsgXytWZ6duPmYvl4vZ29bbysW8fZ06yNjP70berMwMuqQz5+vmSjrbWvuesx+uuu+43Rx111BuSzsgVMsZMvOGGGyZu27atrc+aZn31yBpVfmioKg49XPv86Hfpb6rqLvtG+i/NvS7+sVIH9ZcqqpQ68GDtc+3NkqTzKn6nO+6cIWPMXjWyz9/60IeO0M9/+gN96tP/Fjpwzp8V8WywpLJJ1na172K5uL07Ynu1t7dr2PAx6tNnP/353tt09NFHauXKNZ1aO8k1JkmnjPqMWlq26KCDDtSjs6Zp9Zp1WrBgUafWdfX/lY99+zhzR+QL5erM9F26bNK1i+Hi9nb1tvKxbx9nTrI2M8fLJlnbx5kBl4QdlGyXVK09nqI9duzYsxsaGt4ZOXLkllwha+2kTG7b6xvm/Sp9rdGOJx/Tu/fdttf3v/XbH6e/48BD1OuCK7X9l+k32LjjT42SpOamFg2srVZzc4sqKirUp89+2rr1dUlSTc0A3Xfvbfra1y/Vc8/t+Uzyve36WbvU1gzY/RTwcs0mWdvVvovl4vbuyO31+utv6O/znky/uHKEg5KurjFJu7/3pZde0f0PzNLw4UMiHZR0dWb6ZuZS5Avl6sz07cfMxXJxe7t6W/nYt48zJ1mbmf3o29WZAZeEvajjZZLmGmNmGWMmZS6PtrS0/PqVV165OU6hnY3PqGrYyTL77i9JMr33lTnw4EjZhx6erXPP/aIk6fOf/7T+9sQ/JEkVqQo9+MCduurqn+vJhXWRftaSunoNHnyYBg0aqKqqKk2YMF4PPTy7rLP0XVjtYri4vYvdXv369VWfPvtJknr06KHRp56sNWvWl33fxeR79eqpffbpvfvjT552SqSDsMXWdfX/lY99+zhzR+QL5erM9O3HzMVycXu7elv52LePM7vat48zu9q3qzNDkm3nkn0pY4FnSlprHzXGHCFphKQaSebII498afz48f9njLku61u/kfn3Zkn9JdVJ2k9S+743TNWbV52v9s2b9O7/TVHvy3+RfoObtp16+67fqO2VF0ObvH3yNN0x5X+1etUCvfbaVm3Z8pIWzHtQBx/cT8YY3XD9j3XVDy6TJH3q9LP10kuv5P1ZbW1tuvSyqzXzkXtUkUppyh3TtWrV2tAekszSd2G1777rdzpl5Anq16+vNj5Xp2uvu16Tp0wr676T3F4DBhyi22+7URUVKaVSKd1330N6ZOacsu+7mPwhhxyk++5Nn71dUVmhadPu1+zZT3R6XVf/X/nYt48zF5t38XdvkrV97NvVmSU317ePt5WPffs4s6t9+zizq327OjPgEtPZr0vw+tdOK7jAgZmnbwNAMfZ+RZboeOUWAAAAAOVo547mYh7qdFmtW9bwMC5L1SFHlu06CXv6NgAAAAAAAAB0KA5KAgAAAAAAACipsHffBgAAAAAAANzQXt5v7oJ/4UxJAAAAAAAAACXV6WdKHnTP6oKzKVP4a3G2d/Ib+ABwB78NAAAAAAAoL5wpCQAAAAAAAKCkOCgJAAAAAAAAoKQ4KAkAAAAAAACgpHj3bQAAAAAAAHQJ1vLu265I7EzJiy8+X0ufmaP6pXN1ySXnh37/pFuuV9Pz9Vr6zJzd133+c59W/dK5euftTfroR4+NXHvsmFFa2TBPq1ct0JVXXBSr76SySdamb3f69nHmJGszsx99+zhzkrWZ2Y++fZw5ydrM7EffPs5cW1utObPv1YrlT2hZ/eO65OLwx5UdVZvbyo++XZ0ZcIa1tlMvVd1q7J6XIUNOtQ0NjXa/Ph+wPXq+z86ZO89+8EMn7fV92ZdPnPo5O3zEWNvQ0Lj7ug8fe4o9+piT7RNPPGmPO/5T7/n+iqrqnJeq7rV23boNdvARx9sevQ619ctW2mOOPSXv95dDlr7pm5nLrzYz+9G3jzO72rePM7vat48zu9q3jzO72rePM1dUVduagUPssOFjbEVVte1zwOF2zdr1Zd+3r7eVi327MHNnH89x9fJuc4Pl8q9L0rdH0CWRMyWPOmqwFi1aqrfffkdtbW2aP+8pjR8/LjCzYMEivfba1vdct3r1Oq1d+1ys2iOGD9X69Ru1YcMmtba2asaMB3TWmWPLOkvf9N3ZWfp2J0vf7mTp250sfbuTpW93svTtTtblvl944UUtrW+QJG3btl2rVz+rmur+Zd23r7eVi327OjPgkoIPShpjvlZoduWqNTr55OPUt+/+6tmzh8aNO1W1tdWF/rhYqmv66/mmzbs/b2puUXXEO66ksknWpu/S1mZmP/r2ceYkazOzH337OHOStZnZj759nDnJ2swcv+9shx5aqyEfOUaLFi/t9NrcVn707erMgEuKeaObayVNLiS4evU6/er632vWzKnatm27lq9YpZ07dxbRSnTGmL2us9aWdTbJ2vRd2trMHC+bZG1mjpdNsjYzx8smWZuZ42WTrM3M8bJJ1mbmeNkkazNzvGy23r17acb0W/Xty6/Rm29u6/Ta3FbxsknW9nFmSGrnjW5cEXhQ0hizPN+XJB0SkJsoaaIkVVTsr1RF772+Z8qUaZoyZZok6SfXfVdNzS0RWy5Oc1OLBmadlVlbM0AtLVvKOptkbfoubW1m9qNvH2dOsjYz+9G3jzMnWZuZ/ejbx5mTrM3M8fuWpMrKSt07/VZNnfoX3X//rMg5V2embzeySdcGXBH29O1DJH1F0pk5Lq/kC1lrJ1lrh1lrh+U6IClJBx10oCRp4MBqfeYzn9L06Q/E774AS+rqNXjwYRo0aKCqqqo0YcJ4PfTw7LLO0jd9d3aWvt3J0rc7Wfp2J0vf7mTp250sfbuTdblvSbp10g1qXL1ON940KVbO1Znp241s0rUBV4Q9ffthSftYa+v3/IIx5oliCk+fNkkHHniAWlt36puXXqWtW18P/P677vytRo48Qf369dVz65foup/coNde3apf//onOuigvnrg/ju0bPlKnXHGOYE/p62tTZdedrVmPnKPKlIpTbljulatWhup56Sy9E3fnZ2lb3ey9O1Olr7dydK3O1n6didL3+5kXe77xI8P17nnfEHLV6xS3ZL0AZsf/vAXmvXo42Xbt6+3lYt9uzoz4BI2RzUVAAAgAElEQVTT2a9L0K17bSIvfNDO6y0AAAAAAIAuaueO5r1ffBLa0bSCA0JZutV+uGzXSTFvdAMAAAAAAACUD8sb3bgi7DUlAQAAAAAAAKBDcVASAAAAAAAAQElxUBIAAAAAAABASXFQEgAAAAAAAEBJdfob3ST1LtgpU9ybC/Hu3QAAAAAAAI5pb0u6A0TEmZIAAAAAAAAASoqDkgAAAAAAAABKioOSAAAAAAAAAEqKg5IAAAAAAAAASiqxg5K3TrpBm5uWqX7p3ILyY8eM0sqGeVq9aoGuvOKiWNmLLz5fS5+Zo/qlc3XJJeeXrG4x2SRr+9h3kuuT28qPvl2cuba2WnNm36sVy5/QsvrHdcnF8X5/FlPb1WyStX3s28eZk6zNzH707ePMSdZmZvbZy7m2j327OjPgDGttp14qqqptrsuoT3zWDhs+xq5oaMz59aBLVfdau27dBjv4iONtj16H2vplK+0xx57y3u/pVpPzMmTIqbahodHu1+cDtkfP99k5c+fZD37opL2+r9C6xfTcWXn6jt93Z6/PcsvStzvZJGvXDBxihw0fYyuqqm2fAw63a9aud6JvH28rH/v2cWZX+/ZxZlf79nFmV/v2ceaKKvbZ6bt8s6Wq3dnHc1y9vLthieXyr0vSt0fQJfRMSWPMUcaY0caYffa4flwxB0PnL1ikV1/bWlB2xPChWr9+ozZs2KTW1lbNmPGAzjpzbKTsUUcN1qJFS/X22++ora1N8+c9pfHjo41STN1isknW9rXvpNYnt5Uffbs68wsvvKil9Q2SpG3btmv16mdVU92/7Pv28bbysW8fZ3a1bx9ndrVvH2d2tW8fZ5bYZ6fv8s0mXRtwReBBSWPMNyU9IOkSSQ3GmPFZX/5ZZzYWpLqmv55v2rz786bmFlVHfGC8ctUanXzycerbd3/17NlD48adqtra6k6vW0w2ydq+9l0MV2embzeySdfe5dBDazXkI8do0eKlkTMubm9Xbysf+/Zx5iRrM7Mfffs4c5K1mZl99nKu7WPfrs4MuKQy5Ov/Lulj1tptxphBku4zxgyy1t4kyeQLGWMmSpooSaaij1Kp3h3U7u6fv9d11tpI2dWr1+lX1/9es2ZO1bZt27V8xSrt3Lmz0+sWk02ytq99F8PVmenbjWzStSWpd+9emjH9Vn378mv05pvbIudc3N6u3lY+9u3jzEnWZuZ42SRrM3O8bJK1mTletliuzkzfbmSTrg24Iuzp2xXW2m2SZK3dKGmUpE8ZY/5HAQclrbWTrLXDrLXDOvqApCQ1N7VoYNbZjbU1A9TSsiVyfsqUaTru+E9p9Glf0GuvbtW6dRs6vW6xPSdV29e+i+HqzPTtRjbp2pWVlbp3+q2aOvUvuv/+WZFzxdZ2MZtkbR/79nHmJGszsx99+zhzkrWZmX32cq7tY9+uzgy4JOyg5AvGmCG7PskcoDxDUj9JH+7MxoIsqavX4MGHadCggaqqqtKECeP10MOzI+cPOuhASdLAgdX6zGc+penTH+j0usX2nFRtX/suhqsz07cb2aRr3zrpBjWuXqcbb5oUOZN03z7eVj727ePMrvbt48yu9u3jzK727ePMxXJ1Zvp2I5t0be+1t3PJvpSxsKdvf0XSe57bbK3dKekrxphbiil8912/0ykjT1C/fn218bk6XXvd9Zo8ZVqkbFtbmy697GrNfOQeVaRSmnLHdK1atTZy7enTJunAAw9Qa+tOffPSq7R16+udXrfYnpOq7WvfSa1Pbis/+nZ15hM/PlznnvMFLV+xSnVL0jtFP/zhLzTr0cfLum8fbysf+/ZxZlf79nFmV/v2cWZX+/ZxZol9dvou32zStQFXmM5+XYLKbjWJvPBByuR9dnkk7bxeAwAAAAAAKFM7dzQXd+Cji9rx3GIO6GTp9v4RZbtOwp6+DQAAAAAAAAAdioOSAAAAAAAAAEoq7DUlAQAAAAAAACdYW95v7oJ/4UxJAAAAAAAAACXVZc+ULPaNaipTFQVnd7a3FVUbACSpmFcj5pWdAQAAAADljDMlAQAAAAAAAJQUByUBAAAAAAAAlBQHJQEAAAAAAACUVJd9TUkAAAAAAAB4pp1333ZFImdK1tZWa87se7Vi+RNaVv+4Lrn4/Ng/Y+yYUVrZME+rVy3QlVdc1GnZW275lTZtekZPP/3Y7us+/OEP6okn/qK6utn6859v17777tPpPRebTyqbZG0f+3Z15lsn3aDNTctUv3RurFxH1HYxK0l9+uynadMmacWKv2v58id0/HEfK0ltV9cYM/vRt48zJ1mbmf3o28eZk6zNzH707ePMxeSLPX7g4swdURtwgrW2Uy8VVdV2z0vNwCF22PAxtqKq2vY54HC7Zu16e8yxp+z1ffkuVd1r7bp1G+zgI463PXodauuXrYycj5rt3n2g7d59oB09+vP2uOM+ZRsaVu++bsmSenvaaV+w3bsPtBMnfsf+7Gc37v5a9+4DO7znUs1M38nX9nHmiqpqO+oTn7XDho+xKxoaI2eS7rsU2cqAy513zrATJ37HVlZV2569DrUH9jvqPV8vt5ld2N7MnHxtZvajbx9ndrVvH2d2tW8fZ3a1bx9nLjZfzPEDV2eOmu3s4zmuXt5Z+w/L5V+XpG+PoEsiZ0q+8MKLWlrfIEnatm27Vq9+VjXV/SPnRwwfqvXrN2rDhk1qbW3VjBkP6Kwzx3ZKdsGCxXrtta3vue6II96v+fMXSZLmzp2vz3zm9E7tudh8Uln6diebdO35Cxbp1T3+n5V730lur3333UcnnXScbp88VZLU2tqq119/o+z7dnF7+zizq337OLOrffs4s6t9+zizq337OLOrffs4c7H5Yo4fuDpzsbUBV4QelDTGjDDGDM98/CFjzLeNMeFH4SI69NBaDfnIMVq0eGnkTHVNfz3ftHn3503NLaqO+EupmOwuK1eu0RlnfFKS9LnPfVq1tQM6vW5SM9N3aWv7OHOxXNzexW6v97//UL388iu67Y+/1pLFf9UtN/9KvXr1LPu+XdzePs6cZG1m9qNvH2dOsjYz+9G3jzMnWZuZS9t3trjHD1ydOcnHV0ApBR6UNMZcI+l/Jf3BGPNzSb+VtI+k7xljriq2eO/evTRj+q369uXX6M03t0XOGWP2us5a2+nZXf7jP67QN75xnp588hHtu+8+2rGjtdPrJjUzfZe2to8zF8vF7V3s9qqsqNDQoR/WLbfcqeEjxmr79rd05ZUXd3ptV9cYM8fLJlmbmeNlk6zNzPGySdZm5njZJGszc7xskrWZOV62I/JSYccPXJ05ycdXXYJt55J9KWNh7779BUlDJHWX9IKkWmvtG8aYX0laJOmnuULGmImSJkqSqeijVKr33oUrK3Xv9Fs1depfdP/9s2I13dzUooG11bs/r60ZoJaWLZ2e3WXt2vU644xzJEmDBx+mceNO7fS6Sc1M36Wt7ePMxXJxexe7vZqaW9TU1KLFS9J/If7z/z2iK6+IdlDSxzXGzH707ePMSdZmZj/69nHmJGszsx99+zhzR+QLPX7g6sxJPr4CSins6ds7rbVt1tq3JK231r4hSdbatyXlPdxqrZ1krR1mrR2W64CklH633cbV63TjTZNiN72krl6DBx+mQYMGqqqqShMmjNdDD8/u9OwuBx10oKT0Xy++//1v6o9/vLvT6yY1M32707erMxfLxe1d7PbasuUlNTVt1hFHfECSdOqpJ6mxcW3Z9+3i9vZxZlf79nFmV/v2cWZX+/ZxZlf79nFmV/v2ceaOyBd6/MDVmZN8fAWUUtiZkjuMMb0yByU/tutKY0wfBRyUDHPix4fr3HO+oOUrVqluSfo/1g9/+AvNevTxSPm2tjZdetnVmvnIPapIpTTljulatSraA/K42Tvv/I1OPvkE9et3gNatW6T/+q//Ue/evfWNb3xFknT//Y/qjjtmdGrPxeaTytK3O9mka9991+90ysgT1K9fX218rk7XXne9Jk+ZVtZ9J7m9JOmyb/1Qd97xG3XrVqXnNmzSBRd8u+z7dnF7+zizq337OLOrffs4s6t9+zizq337OLOrffs4c7H5Yo4fuDpzRzxeAFxggl6XwBjT3Vr7bo7r+0kaYK1dEVagsluNky98UJmqKDi7s72tAzsB4Ku9X0kmOid/8QIAAACIbOeO5mIeMnRZ765dwMOhLN2POKls10ngmZK5Dkhmrn9Z0sud0hEAAAAAAABQCE4Uc0bYa0oCAAAAAAAAQIfioCQAAAAAAACAkuKgJAAAAAAAAICS4qAkAAAAAAAAgJIKfKMbnxXzDtq8Yy6AjsDvAwAAAABAV8VBSQAAAAAAAHQNtj3pDhART98GAAAAAAAAUFIclAQAAAAAAABQUhyUBAAAAAAAADxkjLndGPOiMaYh67q+xpjHjDHPZv49IHO9Mcb8rzFmnTFmuTHmo1mZ8zLf/6wx5rwotRM5KNm9e3ct/MfDerruMS2rf1zX/Og7sX/G2DGjtLJhnlavWqArr7jIiewRR3xAdUtm77688vJqffOSC8q+72KySdYuJnvrpBu0uWmZ6pfOjZXriNrcVn707ePMSdZmZj/69nHmYvepXJw5ydo+9u3jzEnWZmY/+vZx5mLyrt7XJV0biGGKpHF7XPc9SXOttYdLmpv5XJI+JenwzGWipD9I6YOYkq6RdJykEZKu2XUgM4ixtnPf37WyW03OAr1799L27W+psrJS8574i7717Wu0aPEzkX5mKpVS48r5Gnf62WpqatFTC2fqnHMvVGPjs2WRjfLu26lUSv/c+LROPOkMbdrUvPv6fLdGuc9cbrWL7fvkk47Ttm3bNXnyTRoydHSkTNJ9+3pbudi3jzO72rePM7vat48z71LoPpWrM9O3G1n6didL3+5kfe1bcu++rlS1d+5ojnL4wTvvrpzbuQe6HNP96NGh68QYM0jSw9baYzKfr5E0ylrbYowZIOkJa+2RxphbMh9Pzf6+XRdr7X9krn/P9+UT+0xJY8ydcTO5bN/+liSpqqpSlVVVinNwdMTwoVq/fqM2bNik1tZWzZjxgM46c2xZZ/d06qkn6bnn/vmeA5Ll2HexM7va9/wFi/Tqa1sjf3859O3rbeVi3z7O7GrfPs7sat8+zrxLoftUrs5M325k6dudLH27k/W1b8m9+7qkawMd4BBrbYskZf49OHN9jaTns76vKXNdvusDBR6UNMY8uMflIUmf2/V59FlyFE6lVLdktlqal2vu3HlavGRp5Gx1TX8937R59+dNzS2qru5f1tk9fWnCeE2ffn/k73d1Zlf7LoarM9O3G9kka/vYt48zJ1mbmQu7vyp0n8rVmenbjWyStX3s28eZk6zNzKXtW3Lvvi7p2kA2Y8xEY0xd1mViMT8ux3U24PpAYWdK1kp6Q9L/SLohc3kz6+OCtbe3a9jwMTr0sGEaPmyojj76yMhZY/aeNepfSpLKZquqqtIZZ4zRfX9+OHLG1Zld7bsYrs5M325kk6ztY98+zpxkbWaOl92l0H0qV2embzeySdb2sW8fZ06yNjPHy3ZE3rX7uqRrA9mstZOstcOyLpMixLZknratzL8vZq5vkjQw6/tqJW0OuD5Q2EHJYZKelnSVpNettU9Ietta+3dr7d/zhbKPwra3bw8s8Prrb+jv857U2DGjwnrdrbmpRQNrq3d/XlszQC0tW8o6m23cuE9o6dIVevHFlyNnXJ3Z1b6L4erM9O1GNsnaPvbt48xJ1mbm4u6v4u5TuTozfbuRTbK2j337OHOStZm5tH1nc+W+LunaQAd4UNKud9A+T9IDWdd/JfMu3McrfaywRdJfJY0xxhyQeYObMZnrAgUelLTWtltrfy3pa5KuMsb8VlJl2A/NPgqbSvXe6+v9+vVVnz77SZJ69Oih0aeerDVr1of92N2W1NVr8ODDNGjQQFVVVWnChPF66OHZZZ3N9qUvfSbWU7eT7LvYmV3tuxiuzkzfbmTp250sfbuTdbnvYvapXJ2Zvt3I0rc7Wfp2J+tr3y7e1yVd23u2nUv2JYQxZqqkhZKONMY0GWPOl/QLSZ80xjwr6ZOZzyVppqTnJK2TdKukCyXJWvuqpJ9IWpK5XJe5LlDoAcbMD2+S9EVjzKeVfjp3UQYMOES333ajKipSSqVSuu++h/TIzDmR821tbbr0sqs185F7VJFKacod07Vq1dqyzu7Ss2cPnTZ6pC688Luxcq7O7Grfd9/1O50y8gT169dXG5+r07XXXa/JU6aVdd++3lYu9u3jzK727ePMrvbt48xScftUrs5M325k6dudLH27k/W1bxfv65KuDcRhrT07z5dG5/heK+miPD/ndkm3x6ltOvt1CSq71Xj3wgeh77UewLuNBQAAAAAAYtu5o7mYww9d1rsNj3FoJUv3Yz5Ztusk7DUlAQAAAAAAAKBDcVASAAAAAAAAQElxUBIAAAAAAABASUV6oxsAAAAAAACg7LWHv+M0ygMHJTsBr6gKAAAAAAAA5MfTtwEAAAAAAACUFAclAQAAAAAAAJQUByUBAAAAAAAAlBSvKQkAAAAAAIAuwdq2pFtARJwpCQAAAAAAAKCkEjsoeeukG7S5aZnql84tKD92zCitbJin1asW6MorLury2SRr07c7ffs4c5K1mdmPvn2cOcnazOxH3z7OnGRtZvajbx9nTrI2M/vRt6szA64w1tpOLVDZrSZngZNPOk7btm3X5Mk3acjQ0bF+ZiqVUuPK+Rp3+tlqamrRUwtn6pxzL1Rj47NdMkvf9M3M5Vebmf3o28eZXe3bx5ld7dvHmV3t28eZXe3bx5ld7dvHmV3t24WZd+5oNpGa8cw7y2Z27oEux/T4yOllu05inSlpjDnJGPNtY8yYYgvPX7BIr762taDsiOFDtX79Rm3YsEmtra2aMeMBnXXm2C6bpW/67uwsfbuTpW93svTtTpa+3cnStztZ+nYnS9/uZOnbnWzStQFXBB6UNMYszvr43yX9VtK+kq4xxnyvk3vLq7qmv55v2rz786bmFlVX9++y2SRr03dpazOzH337OHOStZnZj759nDnJ2szsR98+zpxkbWb2o28fZ06yto8zQ5Jt55J9KWNhZ0pWZX08UdInrbXXShoj6cv5QsaYicaYOmNMXXv79g5oc6+fv9d1UZ+G7mI2ydr0XdrazBwvm2RtZo6XTbI2M8fLJlmbmeNlk6zNzPGySdZm5njZJGszc7xskrWZOV42ydo+zgy4pDLk6yljzAFKH7w01tqXJMlau90YszNfyFo7SdIkKf9rShajualFA2urd39eWzNALS1bumw2ydr0XdrazOxH3z7OnGRtZvajbx9nTrI2M/vRt48zJ1mbmf3o28eZk6zt48yAS8LOlOwj6WlJdZL6GmP6S5IxZh9Jib1Q5pK6eg0efJgGDRqoqqoqTZgwXg89PLvLZumbvjs7S9/uZOnbnSx9u5Olb3ey9O1Olr7dydK3O1n6diebdG3AFYFnSlprB+X5UrukzxZT+O67fqdTRp6gfv36auNzdbr2uus1ecq0SNm2tjZdetnVmvnIPapIpTTljulatWptl83SN313dpa+3cnStztZ+nYnS9/uZOnbnSx9u5Olb3ey9O1ONunagCtMZ78uQWc8fRsAAAAAAMBnO3c0J/YM1nL2zjMPchwqS4+PnlW26yTs6dsAAAAAAAAA0KE4KAkAAAAAAACgpDgoCQAAAAAAAKCkOCgJAAAAAAAAoKQC330bbqlIFXeMua29vYM6AQAAAAAAAPLjoCQAAAAAAAC6BssJV67g6dsAAAAAAAAASoqDkgAAAAAAAABKioOSAAAAAAAAAEoqsYOSt066QZublql+6dyC8mPHjNLKhnlavWqBrrzioi6fjZu/5Zbr9fympXrm6Tnvuf7C//yqVix/QkufmaOf/fQHZdd3uWSTrM3MfvTt48xJ1mZmP/r2ceYkazOzH337OHOStZnZj759nDnJ2j7ODLjCWGs7tUBlt5qcBU4+6Tht27ZdkyffpCFDR8f6malUSo0r52vc6WerqalFTy2cqXPOvVCNjc92yWzUfPa7b5+U2b6333ajPvqx0yRJp5xygr733Us0/jNf1Y4dO3TQQQfqpZde2Z3J9e7bpei73LKu9u3jzK727ePMrvbt48yu9u3jzK727ePMrvbt48yu9u3jzK727ePMrvbtwsw7dzSbSM145p0lf+7cA12O6TH882W7ThI7U3L+gkV69bWtBWVHDB+q9es3asOGTWptbdWMGQ/orDPHdtlsIfkFCxbptT2278R/P1e/uv732rFjhyS954BkufRdDllX+/ZxZlf79nFmV/v2cWZX+/ZxZlf79nFmV/v2cWZX+/ZxZlf79nFmV/t2dWbAJYEHJY0xxxlj9st83NMYc60x5iFjzC+NMX1K0+Leqmv66/mmzbs/b2puUXV1/y6b7Yi8JB1++Pt14okjNH/eg3rssXv1sY99pKz7dnV7u5hNsraPffs4c5K1mdmPvn2cOcnazOxH3z7OnGRtZvajbx9nTrK2jzMDLgk7U/J2SW9lPr5JUh9Jv8xcN7kT+wpkzN5nnkZ9GrqL2Y7IS1JlZaUO2L+PTh55lr7//Z/qnj/9vtPr+ri9XcwmWdvHvn2cOcnazBwvm2RtZo6XTbI2M8fLJlmbmeNlk6zNzPGySdZm5njZJGv7ODPgksqQr6estTszHw+z1n408/ECY0x9vpAxZqKkiZJkKvoolepdfKdZmptaNLC2evfntTUD1NKypctmOyIvSc3NLbr/gVmSpLq6erW3W/Xr11cvv/xqWfbt6vZ2MZtkbR/79nHmJGszsx99+zhzkrWZ2Y++fZw5ydrM7EffPs6cZG0fZwZcEnamZIMx5muZj5cZY4ZJkjHmCEmt+ULW2knW2mHW2mEdfUBSkpbU1Wvw4MM0aNBAVVVVacKE8Xro4dldNtsReUl68MG/atSoEyVJhw8+TFXdqgIPSCbdt6vb28UsfbuTpW93svTtTpa+3cnStztZ+nYnS9/uZOnbnWzStb1n27lkX8pY2JmSF0i6yRhztaSXJS00xjwv6fnM1wp2912/0ykjT1C/fn218bk6XXvd9Zo8ZVqkbFtbmy697GrNfOQeVaRSmnLHdK1atbbLZgvJ33nnbzXy5OPVr19frV+3WD/5rxs05Y7pmjTpej3z9Bzt2LFDF1zwrbLruxyyrvbt48yu9u3jzK727ePMrvbt48yu9u3jzK727ePMrvbt48yu9u3jzK727erMgEtMlNclMMbsK+n9Sh/EbLLWRj5vuLJbDS98UCIVqeLeTL2tvbyPoAMAAAAAgLSdO5r3fvFJ6J3F93IcKkuPEV8s23USdqakJMla+6akZZ3cCwAAAAAAAAAPFHdqHQAAAAAAAADExEFJAAAAAAAAACUV6enbAAAAAAAAQNnj/TKcwZmSAAAAAAAAAEqKMyW7EN49GwAAAAAAAC7gTEkAAAAAAAAAJcVBSQAAAAAAAAAlxdO3AQAAAAAA0DVYXtrOFZwpCQAAAAAAAKCkEjsoeeukG7S5aZnql84tKD92zCitbJin1asW6MorLury2SRr+3hbJVmbmf3o28eZk6zNzH707ePMSdZmZj/69nHmJGszsx99+zhzkrV9nBlwhbHWdmqBym41OQucfNJx2rZtuyZPvklDho6O9TNTqZQaV87XuNPPVlNTi55aOFPnnHuhGhuf7ZLZpGv7dlu52rePM7vat48zu9q3jzO72rePM7vat48zu9q3jzO72rePM7vat48zu9q3CzPv3NFsIjXjmXcWTu3cA12O6XHC2WW7TgLPlDTGfNMYM7AzCs9fsEivvra1oOyI4UO1fv1GbdiwSa2trZox4wGddebYLptNurZvt5Wrffs4s6t9+zizq337OLOrffs4s6t9+zizq337OLOrffs4s6t9+zizq327OjPgkrCnb/9E0iJjzHxjzIXGmINK0VSY6pr+er5p8+7Pm5pbVF3dv8tmk65dDLa3G9kka/vYt48zJ1mbmf3o28eZk6zNzH707ePMSdZmZj/69nHmJGv7ODMktbdzyb6UsbCDks9JqlX64OTHJK0yxjxqjDnPGLNvvpAxZqIxps4YU9fevr0D29398/e6LurT0F3MJl27GGxvN7JJ1vaxbx9nTrI2M8fLJlmbmeNlk6zNzPGySdZm5njZJGszc7xskrWZOV42ydo+zgy4JOygpLXWtltrZ1trz5dULen3ksYpfcAyX2iStXaYtXZYKtW7A9tNa25q0cDa6t2f19YMUEvLli6bTbp2MdjebmSTrO1j3z7OnGRtZvajbx9nTrI2M/vRt48zJ1mbmf3o28eZk6zt48yAS8IOSr7n8Ly1ttVa+6C19mxJ7+u8toItqavX4MGHadCggaqqqtKECeP10MOzu2w26drFYHu7kaVvd7L07U6Wvt3J0rc7Wfp2J0vf7mTp250sfbuTTbo24IrKkK9/Kd8XrLVvF1P47rt+p1NGnqB+/fpq43N1uva66zV5yrRI2ba2Nl162dWa+cg9qkilNOWO6Vq1am2XzSZd27fbytW+fZzZ1b59nNnVvn2c2dW+fZzZ1b59nNnVvn2c2dW+fZzZ1b59nNnVvl2dGXCJ6ezXJajsVsMLHwAAAAAAAHSgnTua937xSeidf/yJ41BZepz45bJdJ2FnSgIAAAAAAABuKPN3nMa/hL2mJAAAAAAAAAB0KA5KAgAAAAAAACgpDkoCAAAAAAAAKCkOSgIAAAAAAAAoKd7oBgAAAAAAAF2CtW1Jt4CIOFMSAAAAAAAAQElxUBIAAAAAAABASXFQEgAAAAAAAEBJJXZQcuyYUVrZME+rVy3QlVdcVNK8i9kka9O3O337OHOStZnZj759nDnJ2szsR98+zlxMvra2WnNm36sVy5/QsvrHdcnF55ekbrHZJGv72LePMydZm5n96NYHzSAAACAASURBVNvVmQFXGGttpxao7FazV4FUKqXGlfM17vSz1dTUoqcWztQ5516oxsZnI/3MYvIuZumbvpm5/Gozsx99+zizq337OLOrffs4c7H5/v0P1oD+B2tpfYP22ae3Fi96VJ//wte79Mz0zcxdtW8fZ3a1bxdm3rmj2URqxjNvP3F75x7ockzPUV8v23WSyJmSI4YP1fr1G7Vhwya1trZqxowHdNaZY0uSdzFL3/Td2Vn6didL3+5k6dudLH27k/W17xdeeFFL6xskSdu2bdfq1c+qprp/p9fltnKnbx9ndrVvH2d2tW9XZwZcEnhQ0hjTzRjzFWPMaZnP/80Y81tjzEXGmKpCi1bX9NfzTZt3f97U3KLqiDtWxeZdzCZZm75LW5uZ/ejbx5mTrM3MfvTt48xJ1mbm0vad7dBDazXkI8do0eKlnV6X26q0tZnZj759nDnJ2j7ODLikMuTrkzPf08sYc56kfST9n6TRkkZIOq+QosbsfeZonKeRF5N3MZtkbfoubW1mjpdNsjYzx8smWZuZ42WTrM3M8bJJ1mbmeNmOyEtS7969NGP6rfr25dfozTe3dXpdbqvS1mbmeNkkazNzvGyStX2cGXBJ2EHJD1trjzXGVEpqllRtrW0zxtwtaVm+kDFmoqSJkmQq+iiV6v2erzc3tWhgbfXuz2trBqilZUvkpovJu5hNsjZ9l7Y2M/vRt48zJ1mbmf3o28eZk6zNzKXtW5IqKyt17/RbNXXqX3T//bNKUpfbqrS1mdmPvn2cOcnaPs4MuCTsNSVTxphukvaV1EtSn8z13SXlffq2tXaStXaYtXbYngckJWlJXb0GDz5MgwYNVFVVlSZMGK+HHp4dueli8i5m6Zu+OztL3+5k6dudLH27k6Vvd7K+9i1Jt066QY2r1+nGmyZFzhRbl9vKnb59nNnVvn2c2dW+XZ0ZcEnYmZK3SVotqULSVZLuNcY8J+l4SdMKLdrW1qZLL7taMx+5RxWplKbcMV2rVq0tSd7FLH3Td2dn6dudLH27k6Vvd7L07U7W175P/PhwnXvOF7R8xSrVLUk/KP3hD3+hWY8+3ql1ua3c6dvHmV3t28eZXe3b1ZkhybYn3QEiMmGvS2CMqZYka+1mY8z+kk6TtMlauzhKgcpuNbzwAQAAAAAAQAfauaN57xefhN7+2x85DpWl5ycuKNt1EnampKy1m7M+3irpvk7tCAAAAAAAAECXFvaakgAAAAAAAADQoTgoCQAAAAAAAKCkQp++DQAAAAAAADihnTe6cQVnSgIAAAAAAAAoKc6UROK6V1YVlX93Z2sHdQIAAAAAAIBS4ExJAAAAAAAAACXFQUkAAAAAAAAAJcXTtwEAAAAAANA1WN7oxhWcKQkAAAAAAACgpBI7KDl2zCitbJin1asW6MorLipp3sVskrVL2XdNzQDNnDVVTz8zR0vqZuvCC78mSfrBVZfp2XVPaeFTM7XwqZkaO3ZUWfXdFbJJ1vaxbx9nTrI2M/vRt48zJ1mbmf3o28eZk6zNzH707ePMSdb2cWbAFcZa26kFKrvV7FUglUqpceV8jTv9bDU1teiphTN1zrkXqrHx2Ug/s5i8i9mu3nf2u2/373+Q+vc/WPX1K7XPPr214B8P6f99aaI+9/kztH3bdt1006171cj17ts+bm8fZ3a1bx9ndrVvH2d2tW8fZ3a1bx9ndrVvH2d2tW8fZ3a1bx9ndrVvF2beuaPZRGrGM2/PublzD3Q5pudp3yjbdRJ6pqQx5gPGmMuNMTcZY24wxnzDGNOnmKIjhg/V+vUbtWHDJrW2tmrGjAd01pljS5J3MetT3y+88JLq61dKkrZt2641a9arurp/5HpJ9e16lr7dydK3O1n6didL3+5k6dudLH27k6Vvd7L07U426dqAKwIPShpjvinpZkk9JA2X1FPSQEkLjTGjCi1aXdNfzzdt3v15U3NLrANPxeRdzCZZO8m+3/e+Wn3kIx/SkiX1kqT/+MZ5WrRolv5w839r//33K9u+XcwmWdvHvn2cOcnazOxH3z7OnGRtZvajbx9nTrI2M/vRt48zJ1nbx5khqb2dS/aljIWdKfnvksZZa/9L0mmSPmStvUrSOEm/LrSoMXufORrnaeTF5F3MJlk7qb579+6le6b+QVdeeZ3efHOb/njr3Trm6JE6/vjT9cILL+rnv7i6LPt2NZtkbR/79nHmJGszc7xskrWZOV42ydrMHC+bZG1mjpdNsjYzx8smWZuZ42WTrO3jzIBLorzRTWXm3+6S9pUka+0mSVX5AsaYicaYOmNMXXv79r2+3tzUooG11bs/r60ZoJaWLZGbLibvYjbJ2kn0XVlZqXvuuVnTp92vBx/4qyTpxRdfVnt7u6y1mnz7NA372EfKrm+Xs0nW9rFvH2dOsjYz+9G3jzMnWZuZ/ejbx5mTrM3MfvTt48xJ1vZxZsAlYQcl/yhpiTFmkqSFkn4rScaYgyS9mi9krZ1krR1mrR2WSvXe6+tL6uo1ePBhGjRooKqqqjRhwng99PDsyE0Xk3cx61vff/jDL7VmzTr95je37b6uf/+Ddn981lljtXLV2rLr2+UsfbuTpW93svTtTpa+3cnStztZ+nYnS9/uZOnbnWzStQFXVAZ90Vp7kzFmjqQPSvofa+3qzPUvSRpZaNG2tjZdetnVmvnIPapIpTTljulaFXKQqaPyLmZ96vuEE4bp3778eTWsaNTCp2ZKkn58zX/ri188S8ce+yFZa/XPTU365iU/KKu+Xc/StztZ+nYnS9/uZOnbnSx9u5Olb3ey9O1Olr7dySZdG3CF6ezXJajsVsMLHyBQ98q8rwQQybs7WzuoEwAAAAAA3LBzR/PeLz4JvT379xyHytJzzIVlu04Cz5QEAAAAAAAAnGHL+x2n8S9R3ugGAAAAAAAAADoMByUBAAAAAAAAlBQHJQEAAAAAAACUFK8picQV+0Y1KVP4a7a2d/IbPQEAAAAAAGBvHJQEAAAAAABA19DOG924gqdvAwAAAAAAACgpDkoCAAAAAAAAKCkOSgIAAAAAAAAoKQ5KAgAAAAAAACipRA5Kdu/eXQv/8bCerntMy+of1zU/+k7snzF2zCitbJin1asW6MorLury2SRru9L3pFuuV9Pz9Vr6zJzd1/3851drxfIn9HTdY7p3xh/Vp89+Zdd3uWSTrO1j3z7OnGRtZvajbx9nTrI2M/vRt48zJ1mbmf3o28eZk6zt48zea2/nkn0pY8Za26kFKrvV5CzQu3cvbd/+liorKzXvib/oW9++RosWPxPpZ6ZSKTWunK9xp5+tpqYWPbVwps4590I1Nj7bJbP0HZxNGSNJOumk47Rt23ZNvv1GDf3oaZKk004bqb/97R9qa2vTz376A0nSD6762e5se5717+L2duG2om9/Z3a1bx9ndrVvH2d2tW8fZ3a1bx9ndrVvH2d2tW8fZ3a1bxdm3rmj2URqxjNvP3Jj5x7ockzPT19Wtusksadvb9/+liSpqqpSlVVVinNwdMTwoVq/fqM2bNik1tZWzZjxgM46c2yXzdJ3tOyCBYv02mtb33PdnDnz1NbWJklatOgZ1dQMKLu+yyFL3+5k6dudLH27k6Vvd7L07U6Wvt3J0rc7Wfp2J5t0bcAViR2UTKVSqlsyWy3NyzV37jwtXrI0cra6pr+eb9q8+/Om5hZVV/fvstkka7vady5f/eqX9Ne//q3Ta7uYTbK2j337OHOStZnZj759nDnJ2szsR98+zpxkbWb2o28fZ06yto8zAy4JPChpjOljjPmFMWa1MeaVzKUxc93+AbmJxpg6Y0xde/v2nN/T3t6uYcPH6NDDhmn4sKE6+ugjIzdtzN5nnkY909LFbJK1Xe17T9/77iXaubNN90z9v06v7WI2ydo+9u3jzEnWZuZ42SRrM3O8bJK1mTleNsnazBwvm2RtZo6XTbI2M8fLJlnbx5kBl4SdKTlD0muSRllrD7TWHijpE5nr7s0XstZOstYOs9YOS6V6BxZ4/fU39Pd5T2rsmFGRm25uatHA2urdn9fWDFBLy5Yum02ytqt9Zzv3nC/o9NNP01fOuzhyxsXt7ept5WPfPs6cZG1m9qNvH2dOsjYz+9G3jzMnWZuZ/ejbx5mTrO3jzIBLwg5KDrLW/tJa+8KuK6y1L1hrfynpfYUW7dev7+53Qe7Ro4dGn3qy1qxZHzm/pK5egwcfpkGDBqqqqkoTJozXQw/P7rJZ+i6stiSNGTNKl19+oT73+a/p7bffKfu+fbytfOzbx5ld7dvHmV3t28eZXe3bx5ld7dvHmV3t28eZXe3bx5ld7dvVmSHJtnPJvpSxypCv/9MYc6WkO6y1WyTJGHOIpK9Ker7QogMGHKLbb7tRFRUppVIp3XffQ3pk5pzI+ba2Nl162dWa+cg9qkilNOWO6Vq1am2XzdJ3tOxdd/5WI0eeoH79+uq59Ut03U9u0JVXXqzu3bpp1sypkqRFi5/RxRd/v6z6LocsfbuTpW93svTtTpa+3cnStztZ+nYnS9/uZOnbnWzStQFXmKDXJTDGHCDpe5LG6/+zd+fhUZZn+8fPe5KwK5YihCQUbKltf31tQcGtiLgUcKV9tbRaUFv70hZ3q9RWraKt2gp1aW0VqoBYEdQqZZHiRiFVQqJElgQXlsKEgBtaElGSzP37g5BGQmbJZOaZe+7v5zhySGa4cp1nnmHxYWYeqVfjzTsk/V3SHdbanbEW5HYo5I0PkFIh0/ar20d4Xw4AAAAAgIPq91S1/X+Gs9juBb/nf/Sb6Xzm1Rn7OIn6TMnGk44/b/z4FGPMDyRNT1EuAAAAAAAAAFkq1ntKRjOp3VIAAAAAAAAA8EbUZ0oaY1a3dpek3u0fBwAAAAAAAGijSGZf3AX/FetCN70ljZS0/3tHGkkvpSQRAAAAAAAAgKwW66TkAkndrLXl+99hjFmakkRAgpK5WE2XvI5tnv2o7pM2zwKZjgtIAQAAAABSKdaFbi6Oct/57R8HAAAAAAAAQLZL5kI3AAAAAAAAAJCwWC/fBgAAAAAAANxgudCNK3imJAAAAAAAAIC0Cuyk5MgRw7Vu7TKtryjWxGsvSeu8i7NB7vYhd8eOHfTiP5/Sv1YsVEnpYv3y+islSYuXzFHxywtU/PICvf7Wy3r0sfszKnd7zga528fcLnWe+sBkhbeWa9WrzzXd9pnPHKJFix7VunXLtWjRozrkkO4ZlzsTZoPc7WNuHzsHuZvOfuT2sXOQu+nsR24fOyczP23qFG0Lv6byVc8nvDOZvcnOBr0bcIGxKb5Kam6HwhYLQqGQKtct16jTz1M4XK0VLy/S2HETVFn5ZlxfM5l5F2fJnbrZ5lff7tq1i2prP1Jubq6WPDdXP7/2FpWW/vfC87P++ictWvisZj/6lKTWr76d6Z0zbbePuV3o3Pzq20OHHqOamlpNf+huDTryVEnS7bddr/ff/0B3Tr5P115ziT7zme765fW3SWr96tsufr9dOFbk9rezq7l97Oxqbh87u5rbx86u5vaxc7LzJ+z7u+j0ezRw0Clx7WuPvS4cq/o9VaaVL+G13fN+l9oTXY7pPHpixj5OAnmm5NFDBmnDhs3atGmL6urqNHfuPJ191si0zLs4S+70zNbWfiRJysvLVW5erpqfsO/WrauGnXicFsx/NuNyt8csud2ZDWJ3cXGJdu784FO3nXXWCM165HFJ0qxHHtfZZ8fe7+L327Vj5XNuHzu7mtvHzq7m9rGzq7l97Oxqbh87Jzu/vLhE7+/3d9F07HX1WAEuCeSkZEFhvraGtzV9Hq6qVkFBflrmXZwNcrdPuUOhkIpfXqANm0v14gv/UlnZa033nXX2CP1z6Uvatasm43K3x2yQu33M7Wrn5nr16qnt29+WJG3f/rYOPfSzKd3t4myQu33M7WPnIHfT2Y/cPnYOcjed/cjtY+f2mG8rVzsH9f0C0i2Qq28b0/KZo4m8jDyZeRdng9ztU+5IJKKhx52p7t0P0l9n36+v/L/DVVnxhiTp3O+cpZkz5qZsd9CzQe72MbernZPl4vfb1WPlY24fOwe5m86JzQa5m86JzQa5m86JzQa5m86JzbbHfFu52jnIv7NnhQhX33ZFm58paYx5Jsp9440xZcaYskiktsX9VeFq9S0qaPq8qLCPqqt3xL07mXkXZ4Pc7WPuDz/cpeLlJTr1m8MkST16HKKjjvq6/rH4hYzO7eOxCnK3j52be/vtd5Wf30uSlJ/fS++8815Kd7s4G+RuH3P72DnI3XT2I7ePnYPcTWc/cvvYuT3m28rVzkF9v4B0i3pS0hhzZCsfR0ka2NqctXaqtXawtXZwKNS1xf2lZeUaMOAw9e/fV3l5eRozZrTmL1gSd+hk5l2cJXfqZz/bs4e6dz9IktSpU0cNP+kbevP1jZKkb337dC1e/II++WRPxuVur1lyuzMb9O595i94VuPGfkeSNG7sdzR/fuyv4eL329Vj5WNuHzu7mtvHzq7m9rGzq7l97Oxqbh87t8d8W7naOajvF5BusV6+XSrpn5IOdKWeQ9q6tKGhQVdceYMWLXxUOaGQZsyco4rGl8mmet7FWXKnfjY/v5fun3qncnJyFAoZPfXkIi1ufGbkOeeeqbt+f39ce9Odu71mye3ObBC7Zz38Rw0bdpx69uyhjRtKdcutU3TnnX/Uo4/er4t+8D1t3Vql8877ScblDnqW3O7MktudWXK7M0tud2bJ7c6sr7kfmXWfTmz8u+jmjWWadMtkTZ/xWMr3unqsAJeYaO9LYIxZK+nb1toW16w3xmy11vaNtSC3QyFvfICM1SWvY5tnP6r7pB2TAJklZA70b1HxifB+NwAAAEDK1e+pavtf2rPY7qfu4H9Imun87esy9nES65mSN6v1l3hf1r5RAAAAAAAAgCRYLnTjiqgnJa21T0S5+zPtnAUAAAAAAACAB9p89W1Jk9otBQAAAAAAAABvRH2mpDFmdWt3Serd/nEAAAAAAAAAZLtY7ynZW9JISTv3u91IeikliQAAAAAAAABktVgnJRdI6matLd//DmPM0pQkAtIomStoc3ViZDMeowAAAACcFOFCN66IdaGbi6Pcd377xwEAAAAAAACQ7ZK50A0AAAAAAAAAJIyTkgAAAAAAAADSipOSAAAAAAAAANIqsJOSI0cM17q1y7S+olgTr70krfMuzga5m9yx56c+MFnhreVa9epzTbed879nqHzV8/p49xYdeeTX0pKbY5XY/LSpU7Qt/JrKVz2f8M5k9iY7G+RuV79nPh4rH3P72DnI3fxe4sex8rFzkLvp7EduHzsHudvHzoArjE3xFVZzOxS2WBAKhVS5brlGnX6ewuFqrXh5kcaOm6DKyjfj+prJzLs4S+7MzN386ttDhx6jmppaTX/obg068lRJ0pe/PECRSET3/fG3+vl1t+rVV1c3/fzWrmyc6Z0zbTbZ+RP2Hbfp92jgoFPi2tcee109VpKb3zMfj5WPuX3s7HJu334vcTW3j51dze1jZ1dz+9jZ1dwudK7fU2Va+RJe2z33ltSe6HJM5zG/ytjHSSDPlDx6yCBt2LBZmzZtUV1dnebOnaezzxqZlnkXZ8md+bmLi0u0c+cHn7pt/fq39MYbG+PemWxujlXi88uLS/T+fsctHXtdPVaSm98zH4+Vj7l97Oxybt9+L3E1t4+dXc3tY2dXc/vY2dXcrnYGXBLIScmCwnxtDW9r+jxcVa2Cgvy0zLs4G+Rucrdtvq1c7exq7mS42jmo71eyu12cDXK3j7l97Bzkbn4v8eNY+dg5yN109iO3j52D3O1jZ8AlgZyUNKblM0cTeRl5MvMuzga5m9xtm28rVzu7mjsZrnYO6vuV7G4XZ4Pc7WNuHzsHuZvfSxKbDXI3nRObDXI3nRObDXI3nRObDXK3j50Bl0Q9KWmMOdgYc7sxZpYx5vz97vtTlLnxxpgyY0xZJFLb4v6qcLX6FhU0fV5U2EfV1TviDp3MvIuzQe4md9vm28rVzq7mToarnYP6fiW728XZIHf7mNvHzkHu5vcSP46Vj52D3E1nP3L72DnI3T52BlwS65mS0yUZSU9K+p4x5kljTMfG+45tbchaO9VaO9haOzgU6tri/tKycg0YcJj69++rvLw8jRkzWvMXLIk7dDLzLs6S263cyXC1s6u5k+Fq56C+X8nudnGW3O7MkpvfS1I962puHzu7mtvHzq7m9rGzq7ld7QxJ1vLR/COD5ca4/wvW2nMaf/y0MeZ6SS8YY85OZmlDQ4OuuPIGLVr4qHJCIc2YOUcVFW+kZd7FWXJnfu5ZD/9Rw4Ydp549e2jjhlLdcusU7Xz/A91116069NAemvf0TL22ep3OPHNs1nTOhNlk5x+ZdZ9ObDxumzeWadItkzV9xmMp3+vqsZLc/J75eKx8zO1jZ5dz+/Z7iau5fezsam4fO7ua28fOruZ2tTPgEhPtfQmMMZWSvmqtjTS77UJJEyV1s9b2i7Ugt0NhZp+WBdooZFq+z0e8Ihn+rxUAAAAAgMxWv6eq7f9TmsV2z5nE/3A30/m7N2Xs4yTWy7fnSzq5+Q3W2pmSfiZpT6pCAQAAAAAAAMheUV++ba2d2Mrti40xt6UmEgAAAAAAAIBsFus9JaOZpL0XwgEAAAAAAACCF4nE/jnICFFPShpjVrd2l6Te7R8HAAAAAAAAQLaL9UzJ3pJGStq53+1G0kspSQQ4IpmL1SRzkZxkd8MdyTxKeIQAAAAAADJZrJOSC7T3Ktvl+99hjFmakkQAAAAAAAAAslqsC91cHOW+89s/DgAAAAAAAIBsl8yFbgAAAAAAAIDMwYVunBEKOgAAAAAAAAAAv3BSEgAAAAAAAEBaBXpSMhQKqXTlPzTvqZkJz44cMVzr1i7T+opiTbz2kqyfDXI3uVO7e+oDkxXeWq5Vrz7XdNvtt9+gNauX6pWyZ/X43L+oe/eDU5552tQp2hZ+TeWrnk9orj12u3KsMmVWkt58Y4VWvfqcykqXaMXLi9K2m2PlTmdXf037eKx8zO1j5yB309mP3D52DnI3ndOX29W/0wS9G3CBsdamdEFuh8JWF1x5xXgdddTXdPBBB2n0ty+M+2uGQiFVrluuUaefp3C4WiteXqSx4yaosvLNrJwld/blDhnT9OOhQ49RTU2tpj90twYdeaok6dRTh+nFF/+lhoYG3fabX0qSfnn9bU0zkQP8uk228wn7cky/RwMHnRLXTHvszvRjFeSsaWVe2ntS8tjjTtN77+084P2t/cbLsfKjs+Tmr2kfj5WPuX3s7GpuHzu7mtvHzq7m9rFzsvMu/p0mXbvr91RF+18Gb+3+642pPdHlmM7fvzVjHyeBPVOysLCPTj/tFD300OyEZ48eMkgbNmzWpk1bVFdXp7lz5+nss0Zm7Sy5szt3cXGJdu784FO3PffcMjU0NEiSSkpeVWFhn5RmlqTlxSV6f78c8fLlWGXCbLI4Vn50ltz8Ne3jsfIxt4+dXc3tY2dXc/vY2dXcPnZOdt7Fv9MEvRtwRWAnJX8/ZZKu+8WvFWnDVZEKCvO1Nbyt6fNwVbUKCvKzdjbI3eRO/+79XXTRd/WPf7yY9r2J8PFYBf0YsdbqmUWzVbLiGf3o4u/HPcex8qNzslz8frt6rHzM7WPnIHfT2Y/cPnYOcjed05s7Ga52DvLvgVnBRvho/pHBop6UNMbkG2P+bIy5zxjzWWPMzcaYNcaYucaYVp+6ZYwZb4wpM8aURSK1Le4/4/RT9fbb7+rVVWvaFNqYls88jfdl6C7OBrmb3Onf3dx1P79M9fUNenT239K6N1E+HqugHyMnDv+Wjj5mlM48a6x++tOLNHToMSnfzbFKbDbo3clw8fvt6rHyMbePnYPcTefEZoPcTefEZoPcTefEZttjvq1c7Rzk3wOBdIr1TMkZkiokbZX0oqTdks6QtFzS/a0NWWunWmsHW2sHh0JdW9x//PGDddaZI/TWGyv010f+pJNO+oZmzrg37tBV4Wr1LSpo+ryosI+qq3dk7WyQu8md/t37jBt7rk4//VRdcOGlad3bFj4eq6AfI/t+/jvvvKen5z2jIUMGpnw3x8qdzsly8fvt6rHyMbePnYPcTWc/cvvYOcjddE5v7mS42jnIvwcC6RTrpGRva+0frLV3SDrEWvtba+0Wa+0fJPVr69Lrb7hD/T8/WAMOP1bfHztBL774L1140eVxz5eWlWvAgMPUv39f5eXlacyY0Zq/YEnWzpLbn9z7jBgxXNdcM0H/e84PtHv3x2nb21Y+HqsgO3fp0lndunVt+vE3Tz1R69a9nvG5Xfx+u9o5WS5+v109Vj7m9rGzq7l97Oxqbh87u5rbx87tMd9WrnYO8u+BQDrlxri/+UnLh/e7L6eds8StoaFBV1x5gxYtfFQ5oZBmzJyjioo3snaW3Nmde9bDf9SwYcepZ88e2rihVLfcOkUTJ16qjh066JlFey8EVbLyVV166S9S2vmRWffpxMYcmzeWadItkzV9xmMp6dyeuV18jCXbuXfvQ/XE4w9KknJyc/TYY09ryZKlGZ/bxe+3q50lN39N+3isfMztY2dXc/vY2dXcPnZ2NbePnZOdd/HvNEHvBlxhor0vgTHmFkm/s9bW7Hf7AEl3WGvPjbUgt0Mhb3wA7CdkWr5HSCIivJ+IF5J5lPAIAQAAALJb/Z6q5P7HMkvtfvgX/O9QM50vuD1jHydRnylprf1VK7e/ZYxZmJpIAAAAAAAAALJZrPeUjGZSu6UAAAAAAAAA4I2oz5Q0xqxu7S5Jvds/DgAAAAAAAIBsF+tCN70ljZS0c7/bjaSXUpIIAAAAAAAAQFaLdVJygaRu1try/e8wxixNSSLAA1yoBvHgUQIAAAAACeL/t50R60I3F0e57/z2jwMAAAAAAAAg2yVzoRsAAAAAAAAASBgnJQEAAAAAAACkFScl1/yoWAAAIABJREFUAQAAAAAAAKRVYCclp02dom3h11S+6vk2zY8cMVzr1i7T+opiTbz2kqyfDXI3ud3J7WPnIHfT2Y/cPnYOcjed/cjtY+cgd9PZj9w+dg5yN539yO1qZ8AVxqb4qkS5HQoPuOCEoceopqZW06ffo4GDTknoa4ZCIVWuW65Rp5+ncLhaK15epLHjJqiy8s2snCU3uemcebvp7EduHzu7mtvHzq7m9rGzq7l97Oxqbh87u5rbx86u5nahc/2eKhNXGM/snj6Ry2830/kHv8vYx0lgz5RcXlyi93d+0KbZo4cM0oYNm7Vp0xbV1dVp7tx5OvuskVk7S25yp3qW3O7MktudWXK7M0tud2bJ7c4sud2ZJbc7s+R2Zzbo3YArnHxPyYLCfG0Nb2v6PFxVrYKC/KydDXI3udO7m85+5Paxc5C76exHbh87B7mbzn7k9rFzkLvp7EduHzsHudvHzoBLEj4paYzplYogCWZocVu8L0N3cTbI3eRO7246JzYb5G46JzYb5G46JzYb5G46JzYb5G46JzYb5G46JzYb5G46JzYb5G46JzYb5G4fOwMuyY12pzGmx/43SVppjBmkve9H+X4rc+MljZckk9NdoVDX9sjapCpcrb5FBU2fFxX2UXX1jqydDXI3udO7m85+5Paxc5C76exHbh87B7mbzn7k9rFzkLvp7EduHzsHudvHzoBLYj1T8l1JrzT7KJNUKOnVxh8fkLV2qrV2sLV2cHufkJSk0rJyDRhwmPr376u8vDyNGTNa8xcsydpZcpM71bPkdmeW3O7MktudWXK7M0tud2bJ7c4sud2ZJbc7s0Hv9l4kwkfzjwwW9ZmSkiZKOlXStdbaNZJkjNlkrT0s2cWPzLpPJw47Tj179tDmjWWadMtkTZ/xWFyzDQ0NuuLKG7Ro4aPKCYU0Y+YcVVS8kbWz5CZ3qmfJ7c4sud2ZJbc7s+R2Z5bc7syS251ZcrszS253ZoPeDbjCxHpfAmNMkaS7JG2VdJOk16y1n493QW6HQt74AAAAAAAAoB3V76lq+eaT0O4Hr+E8VDOdL56csY+TmBe6sdaGrbXfkfSipGcldUl5KgAAAAAAAABZK+6rb1tr50s6SXtfzi1jzA9SFQoAAAAAAABA9or1npKfYq3dLWlt46eTJE1v90QAAAAAAABAW9jMvrgL/ivqSUljzOrW7pLUu/3jAAAAAAAAAMh2sZ4p2VvSSEk797vdSHopJYkAAAAAAAAAZLVYJyUXSOpmrS3f/w5jzNJ4FuTlJPQK8U+pa6hv8yyAAxvc84ttni179812TAIAAAAAAHwV9YyhtfbiKPed3/5xAAAAAAAAAGS7uK++DQAAAAAAAADtoe2vrQYAAAAAAAAyiI3YoCMgTjxTEgAAAAAAAEBape2kZFFRHy1e/JhWrXper7zyrC655AeSpF/96mdauXKxVqxYpPnzZ6lPn15xfb2RI4Zr3dplWl9RrInXXpJQFhdng9xN7szOPW3qFG0Lv6byVc8fcPbEYcfpvXcqVVa6RGWlS/TDqy5IKM+BdOjQQY/+9c9aX1Gsl4rnq1+/Io0cMVybN5aq5j8bFd6ySiUrntFJw78R19fz5Vi112yQu33M7WPnIHfT2Y/crnZu/mduW7j4/Xb1WPmY28fOkhQKhVS68h+a99TMhGdd7UxuN2aD3g24wFib2qe1du7cz0pSfn4v5ef3Unn5WnXr1lUvvbRAY8aMV1VVtXbtqpEkTZhwkb785S/q8suvl9T61bdDoZAq1y3XqNPPUzhcrRUvL9LYcRNUWRn7ysAuzpKb3NFmTxh6jGpqajVj+r3q1Klji9leh/bU1Vf9RKO/faGkxK6+3acoXzfefZ0mnHulpP9effsnP75QRxzxFV1y6XUaM+ZsfXv0aTryyK/p6p/9SmvWrtdTf5uhmyfdqT/ee5v6HTa43Tu317yLs+R2Z5bc7syS253ZoHfv+zN3+vR7NHDQKXHNBJ3bx2PlY24fO+9z5RXjddRRX9PBBx3U9PfdeLjamdxuzKZrd/2eKhNXGM98NPUqXr/dTJfxd2Xs4yRtz5Tcvv1tlZevlSTV1NRq/fq3VFDQu+mEpCR16dJF8ZwkPXrIIG3YsFmbNm1RXV2d5s6dp7PPGhlXDhdnyU3uaJYXl+j9nR+oc+dOCc+O+t9v6sGFf9bDz/5FP//t1QqF4vst4eyzRmjWrMclSU8+uVDf/OZwbdiwWQsXPa8tW6o0d+48/b+vHK5OnTqpQ4cO7d65veZdnCW3O7PkdmeW3O7MBr1735+5beHi99vVY+Vjbh87S1JhYR+dftopeuih2XHPBJ3b12PlYm5XOwMuCeQ9JT/3uSINHPhVlZaWS5Juvvlavfnmy/re976lW2/9fcz5gsJ8bQ1va/o8XFWtgoL8uHa7OBvkbnKnd3cys7l5ua3OHnvsUXql7Fkt+PssHXZ4f0lS/wGf06mjT9L40Zfqgm/+SJGGiEb+76kJ52xoaNDHH3+sd95571O7TzjhWJWXr9WePXtS1jnZeRdng9ztY24fOwe5m85+5Ha1c7Jc/H67eqx8zO1jZ0n6/ZRJuu4Xv1YkEol7pj12c6z8yO1qZ0iKRPho/pHBUnL1bWPMeEnjJSk3t4dyc7s13de1axfNnn2/rr32lqZnSd588526+eY7dc01E/STn1yoX//6rlhfv8Vt8b4M3cXZIHeTO727k5o9wG3WWr26ao0+P+Bo1dZ+pNNGnaw/PXS7vjN0rAafcJS+dMThmv7MA5Kkjp06aOd7e5/9cceDt6rgc32Ul5er3oW99fCzf5Ek/fau+zTz4bkHztnsx4UF+Tru2KM05JhRsXP7eKw87BzkbjonNhvkbjonNhvkbh87J8vF77erx8rH3D52PuP0U/X22+/q1VVrdOKw4+Kaaa/dHKvEZoPc7WNnwCVRT0oaY0ZZaxc3/ri7pN9LGiJpraSrrLU7DjRnrZ0qaar03/eUlKTc3FzNnn2/5sx5WvPmLW4xN3fuPP3tb9NjnpSsClerb1FB0+dFhX1UXX3AKFkxG+Rucqd3dzKzdXX1B5xt/hYJzyx+Qbl5uereo7uMkRY9/g/9+fZpLb7WdRffKKn195Tcl7Oqqlo5OTnq1KmTeh36WUl7X0Zz1VU/0WNzntbGjf9Oaedk512cDXK3j7l97Bzkbjr7kdvVzsly8fvt6rHyMbePnY8/frDOOnOETht1sjp16qiDDz5IM2fcqwsvujyjc/t4rILc7WNnwCWxXr59W7MfT5FULeksSaWSHkh02f33/06vv/6W7r33L023feEL/Zt+fMYZ39Qbb2yI+XVKy8o1YMBh6t+/r/Ly8jRmzGjNX7AkrgwuzpKb3PHYvfvjA8727n1o088ZMnigTMjow/c/VOnyV3XyGSfqM589RJJ08CEHKb+wd1y75i9YonHjviNJOuecM/Tc88s0YMBhOuKIr2j+3x9WbW2t7vvT9JR3TnbexVlyuzNLbndmye3ObNC7k+Hi99vVY+Vjbh87X3/DHer/+cEacPix+v7YCXrxxX/FfUIyyNw+HitXc7vaGXBJIi/fHmytHdj447uMMfFf2kx7/yXr+98/R2vWVGrFikWSpJtuulMXXfRdffGLn1ckEtGWLVW6/PJfxvxaDQ0NuuLKG7Ro4aPKCYU0Y+YcVVS8EVcOF2fJTe5oHpl1n04cdpx69uyhDz/cpeLl85UTCunll8tUUfGGJvz0Iv34xxeovr5BH+/+WDf+9BZJ0uY3/60Hfveg7nlsskLGqL6+Xnf+8h5tr4r9L3APTX9MM2fcq/UVxdq58wOdP3aCvvylAXr2H3PVo8ch2rHjHT0884+SpNNOP+9T7zfZnt+vZOddnCW3O7PkdmeW3O7MBr27+Z+5mzeWadItkzV9xmMZndvHY+Vjbh87J8vVzuR2Yzbo3YArTLT3JTDGhLX3JdtG0iWSvmAbB4wxq621X4u1oPnLtxNV11Df1lEArRjc84ttnt338m0AAAAAQLDq91Qd6NIC3vvoz5fxBpzNdPnpHzL2cRLr5dvTJB0kqZukmZJ6SpIxJl9SeWqjAQAAAAAAAMhGUV++ba2d1Mrt240xL6YmEgAAAAAAAIBsFuuZktEc8IQlAAAAAAAAAEQT9ZmSxpjVrd0lKb7L9AIAAAAAAABAM7Guvt1b0khJO/e73Uh6KZ4FXKwGyCxcrAYAAAAAAAQt1knJBZK6WWtbXNTGGLM0JYkAAAAAAACAtohw8W1XxLrQzcVR7ju//eMAAAAAAAAAyHbJXOgGAAAAAAAAABLGSUkAAAAAAAAAaRXYScmRI4Zr3dplWl9RrInXXpLWeRdng9xNbndy+9g5yN109iO3q52nTZ2ibeHXVL7q+YTm2mO3i7NB7vYxt4+dg9xNZz9y+9g5yN109iO3q50BVxhrU/sGoLkdClssCIVCqly3XKNOP0/hcLVWvLxIY8dNUGVlfFcFTmbexVlyk5vOmbebzn7kdrWzJJ0w9BjV1NRq+vR7NHDQKXHNBJ3bx2PlY24fO7ua28fOrub2sbOruX3s7GpuFzrX76kycYXxzEd/mMCVbprpctmfMvZxEsgzJY8eMkgbNmzWpk1bVFdXp7lz5+nss0amZd7FWXKTO9Wz5HZnltzuzAa9e3lxid7f+UHcPz8Tcvt4rHzM7WNnV3P72NnV3D52djW3j51dze1qZ8AlgZyULCjM19bwtqbPw1XVKijIT8u8i7NB7iZ3enfT2Y/cPnYOcrePnZPl4vfb1WPlY24fOwe5m85+5Paxc5C76exHblc7Ay5J+KSkMeazyS41puUzRxN5GXky8y7OBrmb3OndTefEZoPcTefEZoPc7WPnZLn4/Xb1WPmY28fOQe6mc2KzQe6mc2KzQe6mc2KzQe72sTPgkqgnJY0xdxhjejb+eLAxZqOkEmPMv40xJ0aZG2+MKTPGlEUitS3urwpXq29RQdPnRYV9VF29I+7Qycy7OBvkbnKndzed/cjtY+cgd/vYOVkufr9dPVY+5vaxc5C76exHbh87B7mbzn7kdrUz4JJYz5Q8w1r7buOP75T0XWvtAEnflDSltSFr7VRr7WBr7eBQqGuL+0vLyjVgwGHq37+v8vLyNGbMaM1fsCTu0MnMuzhLbnKnepbc7syS253ZoHcnw8Xvt6vHysfcPnZ2NbePnV3N7WNnV3P72NnV3K52hqRIhI/mHxksN8b9ecaYXGttvaTO1tpSSbLWvmGM6djWpQ0NDbriyhu0aOGjygmFNGPmHFVUvJGWeRdnyU3uVM+S251ZcrszG/TuR2bdpxOHHaeePXto88YyTbplsqbPeCyjc/t4rHzM7WNnV3P72NnV3D52djW3j51dze1qZ8AlJtr7EhhjLpN0lqQ7JA2TdIikv0k6RdLnrbXjYi3I7VDIGx8AAAAAAAC0o/o9VS3ffBL66J6fcB6qmS5X3J+xj5Ooz5S01v7BGLNG0k8lHd748w+X9LSkW1MfDwAAAAAAAEC2ifXybVlrl0pauv/txpgfSJre/pEAAAAAAAAAZLNYF7qJZlK7pQAAAAAAAADgjajPlDTGrG7tLkm92z8OAAAAAAAA0EZRrp2CzBLr5du9JY2UtHO/242kl1KSCAAAAAAAAEBWi3VScoGkbtba8v3vMMYsTUkiAFmrU26HNs9+XL+nHZMAANIhmUs98hwHAACA7Bbr6tsXR7nv/PaPAwAAAAAAACDbJXOhGwAAAAAAAABIWKyXbwMAAAAAAABuiESCToA48UxJAAAAAAAAAGkVyEnJoqICPbfkca1ZvVSvlb+gyy5t9a0rWzVyxHCtW7tM6yuKNfHaS7J+Nsjd5E7v7mlTp2hb+DWVr3o+oblk9yY7n8hsx44dtHTZ03p5xSKVlv1D199wpSSpX78ivfjPp1S++gXNfPgPysvLy6jcmTIb5G4fc7vYuWPHjnr5Xwv0Stmzeq38Bd30q58lGtvJ77eLxyrZ2SB3JzPbvfvBeuyxqVqz5p9avXqpjj3mqLhnk/lzUuJY0Tmzd9PZj9w+dg5yt4+dAVcYa1N7bcPcDoUtFuTn91Kf/F5aVb5W3bp11cqSxTrn3B+qsvLNuL5mKBRS5brlGnX6eQqHq7Xi5UUaO25CXPMuzpLbn9ySdMLQY1RTU6vp0+/RwEGnxDXTHnvT0bn51be7du2i2tqPlJubq2eff1wTr5mkyy7/kf4+b7GeeGKB7rn311qzplJ/mfZXSa1ffdvFx5gLx4rcbneWPv1rbNnSp3TV1TepZOWrGZ3bx2OV7blbu/r2Qw/ereLiEj00fbby8vLUpUtnffjhfz71c1r7G2pb/5xMJHd7zwa5m85+5Paxs6u5fezsam4XOtfvqWrtj1qvffT7/0vtiS7HdLl6WsY+TgJ5puT27W9rVflaSVJNTa3Wr39ThQX5cc8fPWSQNmzYrE2btqiurk5z587T2WeNzNpZcvuTW5KWF5fo/Z0fxP3z22tvujvX1n4kScrLy1VeXq6spBNPPE5PPfWMJOmvjzypM88ckXG5g54ltzuzQe9u/mssNy9PifwjpIvfb1ePlY+5Dzqom4YOPUYPTZ8tSaqrq2txQjKatv45mWxuH4+Vj51dze1jZ1dz+9jZ1dyudgZcEvh7SvbrV6SBX/8flaxcFfdMQWG+toa3NX0erqpWQZwnNV2cDXI3udO/u61c6xwKhfTSioXa9O8yvfB8sTZt/Lc++PA/amhokCRVVW1XQUHvjMsd9GyQu33M7Wpnae+vsbLSJaquWq3nn1+mlaX8OZuJu33M/fnP99O7776nB/9yl0pX/kMP3H+nunTpHNdssjhWdM7k3XT2I7ePnYPc7WNnSIpYPpp/ZLBAT0p27dpFc+dM09XX3KRdu2rinjOm5TNP430GiIuzQe4md/p3t5VrnSORiI4/9gx96YvHafDgr+tLXxrQpv0uPsZcO1btMRvkbh87S3t/jQ0eMkL9DhusIYMH6atf/VLcsy5+v109Vj7mzs3J0aBBR+iBBx7WkKNHqrb2I02ceGlcs8niWKVvNsjdPub2sXOQu+mc2GyQu33sDLgk6klJY8yrxpgbjDFfSOSLGmPGG2PKjDFlkUjtAX9Obm6uHp8zTbNnP6Wnn34mkS+vqnC1+hYVNH1eVNhH1dU7snY2yN3kTv/utnK184cf7tLy5Ss05OhBOqT7wcrJyZEkFRbmq7r67YzN7ePj08fcrnZu7sMP/6N/LntJI0cMj3vGxe+3q8fKx9zhqmqFw9VNz9598m8LNWjgEXHNJotjRedM3k1nP3L72DnI3T52BlwS65mSn5F0iKQXjTErjTFXGWMKYszIWjvVWjvYWjs4FOp6wJ8zbeoUVa5/S3ffMzXh0KVl5Row4DD1799XeXl5GjNmtOYvWJK1s+T2J3cyXOrcs2cPde9+kCSpU6eOOumkoXr99be0bNkKffvbp0mSvj/2HC1c+GxG5c6EWXK7Mxvk7r2/xg6WJHXq1EmnnHyCXn99Q8bn9vFY+Zh7x453FA5v0+GH7/0375NPHqrKyjfimk0Wx4rOmbybzn7k9rGzq7ld7Qy4JDfG/TuttddIusYYc4Kk8yS9aoyplDTbWpv4GUVJ3zh+iMaNPVer11SorHTvL6wbb7xDzyx+Ia75hoYGXXHlDVq08FHlhEKaMXOOKiri+8usi7Pk9ie3JD0y6z6dOOw49ezZQ5s3lmnSLZM1fcZjKd+bzs6983tp6rTJygnlKBQy+tvfFmrxMy9ofeWbmvHwH3TjTT/T6tcqNHPG3IzKnQmz5HZnNsjdffr01kMP3q2cnJBCoZCeeGK+Fi56LuNz+3isfM195VU36uGZf1CHDnnauGmLfvSjq+Oebeufk8nm9vFY+djZ1dw+dnY1t4+dXc3tamfAJSba+xIYY1611h653205kr4p6bvW2h/EWpDboZA3PgAgSeqU26HNsx/X72nHJACAdGj5jljx4y+QAABEV7+nKpk/arPWR3f+kL9GNNPl2ocy9nES65mSLU7FW2sbJC1u/AAAAAAAAACAhER9T0lr7fdau88YE/NZkgAAAAAAAACwv1gXuolmUrulAAAAAAAAAOCNqC/fNsasbu0uSb3bPw4AAAAAAACAdDHGXCXpR9r7tt5rJP1AUh9Jj0nqIelVSeOstXuMMR0lPSzpKEnvae81Zza3ZW+s95TsLWmkpJ3755X0UlsWAvAXF6sBAL/wLvMAAACZzRhTKOlySf/PWrvbGDNX0vcknS7pLmvtY8aY+yVdLOnPjf/daa0dYIz5nqTfSvpuW3bHOim5QFI3a235AUIvbctCAAAAAAAAICUi/LNoG+RK6myMqZPURVK1pJMlnd94/0xJN2vvScnRjT+WpCck/dEYY6y1CX/jY13o5mJrbXEr951/oNsBAAAAAAAAZD5rbZWkyZK2aO/JyA8lvSLpA2ttfeNPC0sqbPxxoaStjbP1jT//s23ZncyFbgAAAAAAAABkKGPMeGNMWbOP8fvd/xntffbjYZIKJHWVdNoBvtS+Z0KaKPclJNbLtwEAAAAAAAA4yFo7VdLUKD/lVEmbrLXvSJIx5m+Sjpd0iDEmt/HZkEWStjX+/LCkvpLCxphcSd0lvd+WbDxTEgAAAAAAAPDTFknHGmO6GGOMpFMkVUh6UdK5jT/nQknzGn/898bP1Xj/C215P0kpwJOSI0cM17q1y7S+olgTr70krfMuzga5m9zu5Paxc5C76exHbh87B7mbzn7knjZ1iraFX1P5qucTmmuP3RwrOmfybjr7kdvHzkHu9rGz72wkwkezj5jfL2tLtPeCNa9KWqO95wqnSvq5pKuNMW9p73tGPtg48qCkzzbefrWk69p6rEwbT2bGLbdDYYsFoVBIleuWa9Tp5ykcrtaKlxdp7LgJqqx8M66vmcy8i7PkJjedM283nf3I7WNnV3P72Nnl3CcMPUY1NbWaPv0eDRx0SlwzQef28Vj52NnV3D52djW3j51dze1C5/o9VQd6bz/v1d5+IZffbqbrL2Zm7OMkkGdKHj1kkDZs2KxNm7aorq5Oc+fO09lnjUzLvIuz5CZ3qmfJ7c4sud2ZJbc7s+ROf+7lxSV6f+cHcf/8TMjt47HysbOruX3s7GpuHzu7mtvVzoBLAjkpWVCYr63hbU2fh6uqVVCQn5Z5F2eD3E3u9O6msx+5fewc5G46+5Hbx85B7k42dzJc7exibh87B7mbzn7k9rFzkLt97Ay4JOpJSWPMYGPMi8aYR4wxfY0xzxpjPjTGlBpjBkWZa7rceCRSe6D7W9yWyMvIk5l3cTbI3eRO7246JzYb5G46JzYb5G46JzYb5G46JzYb5O5kcyfD1c4u5vaxc5C76ZzYbJC76ZzYbJC7fewMuCQ3xv1/knSTpEMkvSTpKmvtN40xpzTed9yBhppfbvxA7ylZFa5W36KCps+LCvuounpH3KGTmXdxNsjd5E7vbjr7kdvHzkHuprMfuX3sHOTuZHMnw9XOLub2sXOQu+nsR24fOwe528fOkBThBK4rYr18O89a+4y1drYka619Qnt/8LykTm1dWlpWrgEDDlP//n2Vl5enMWNGa/6CJWmZd3GW3ORO9Sy53Zkltzuz5HZnltzpz50MVzu7mNvHzq7m9rGzq7l97Oxqblc7Ay6J9UzJj40xIyR1l2SNMd+y1j5tjDlRUkNblzY0NOiKK2/QooWPKicU0oyZc1RR8UZa5l2cJTe5Uz1Lbndmye3OLLndmSV3+nM/Mus+nTjsOPXs2UObN5Zp0i2TNX3GYxmd28dj5WNnV3P72NnV3D52djW3q50Bl5ho70tgjPm6pN9Jiki6StJPJV0oqUrS/1lrX4q14EAv3wYAAAAAAEDb1e+pavnmk1Dtby7gPFQzXa9/OGMfJ1Ffvm2tfc1aO9Jae5q1dr219gpr7SHW2q9K+lKaMgIAAAAAAADIIrHeUzKaSe2WAgAAAAAAAIA3or6npDFmdWt3Serd/nEAAAAAAACANrKRoBMgTrEudNNb0khJO/e73UiK+X6SAAAAAAAAALC/WCclF0jqZq0t3/8OY8zSuBaEctoQa6/6SJsv8A0ATXZvW97m2c4FJ7RjEgAAAAAAIMU4KWmtvTjKfee3fxwAAAAAAAAA2S6ZC90AAAAAAAAAQMJivXwbAAAAAAAAcEPEBp0AceKZkgAAAAAAAADSKq0nJR944E5t2fKqXnnl2abbZs26TyUlz6ik5Bm9/vq/VFLyTFxfa+SI4Vq3dpnWVxRr4rWXJJTDxdkgd5PbndzJzBYVFei5JY9rzeqleq38BV12aatvKdvuuxOdPbRnR/X/XBf1Lex8wPm8PKPCPp31+f5d1f3gvISyRNP70I76XFEXFfbprNxco5Ejhqti7TK99cZL+s2tV6qooLO6donv4l4+Pj6D3E1nP3L72DnI3XT2I7ePnYPcTWc/cvvYOcjdPnYGXGGsTe3TWjt1+lzTgqFDj1ZNzUd68MG7dNRR32zxc++44wb95z+7dNtt90hq/erboVBIleuWa9Tp5ykcrtaKlxdp7LgJqqx8M2YeF2fJTe50dM7P76U++b20qnytunXrqpUli3XOuT/MyNydOoUUiew9SVhV/UmL+e+P/bE2bdysrl1z1dBgtX39C3F9DySpqnqHrv/NFM344+8k/ffq2wcflKsOHXL07nufqFvXXB3ULU9Llz6n0844T1u37t17wYWXqHZXWJu3fJSy71ey8/y6onO25vaxs6u5fezsam4fO7ua28fOrub2sbOruV3oXL+nysQVxjO1t3yf12830/VXf83Yx0lanylZXLxSO3d+0Or95557pubMmRfz6xw9ZJA2bNisTZu2qK6uTnPnztPZZ42MK4OLs+Qmd6pnJWn79re1qnytJKmmplbr17+pwoL8jMz98ccRRRrfJ+RA86PPPl2f7IkO+TCkAAAgAElEQVToQP/mMv8fL+h7P7pC51x4iSb97l41NBz4Hz/217VLrnbV1EmSamrrdeyxR2rDhs3auLF57hGK508/Hx+frub2sbOruX3s7GpuHzu7mtvHzq7m9rGzq7l97Oxqblc7Ay7JmPeUHDr0aO3Y8a42bNgc8+cWFOZra3hb0+fhqmoVxHnyxMXZIHeTO727g+zcXL9+RRr49f9RycpVKd+dzmO1YfMWLX7+n5p1/xQ9OfM+hUIhLVjyYlx7cnON6uv/e8qxV6/eClft3duxY0i7P3pPh3+xr95995N2zdze864cq2yYDXK3j7l97Bzkbjr7kdvHzkHuprMfuX3sHORuHztDUiTCR/OPDJYxV98eM2a05s6N/SxJSTKm5TNP430ZuouzQe4md3p3B9l5n65du2junGm6+pqbtGtXTcp3p/NYlZSVq2L9W/rexVdIkj755BP1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\n", - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "import matplotlib.pyplot as plt\n", - "import seaborn as sb\n", - "import pandas as pd\n", - "\n", - "cm_plt = pd.DataFrame(cm[:73])\n", - "\n", - "plt.figure(figsize = (25, 25))\n", - "ax = plt.axes()\n", - "\n", - "sb.heatmap(cm_plt, annot=True)\n", - "\n", - "ax.xaxis.set_ticks_position('top')\n", - "\n", - "plt.show()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Pipeline" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Now, I took the data from [Coconut - Wikipedia](https://en.wikipedia.org/wiki/Coconut) to check if the classifier is able to **correctly** predict the label(s) or not.\n", - "\n", - "And here is the output:" - ] - }, - { - "cell_type": "code", - "execution_count": 15, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Example labels: [('coconut', 'oilseed')]\n" - ] - } - ], - "source": [ - "example_text = '''The coconut tree (Cocos nucifera) is a member of the family Arecaceae (palm family) and the only species of the genus Cocos.\n", - "The term coconut can refer to the whole coconut palm or the seed, or the fruit, which, botanically, is a drupe, not a nut.\n", - "The spelling cocoanut is an archaic form of the word.\n", - "The term is derived from the 16th-century Portuguese and Spanish word coco meaning \"head\" or \"skull\", from the three indentations on the coconut shell that resemble facial features.\n", - "Coconuts are known for their versatility ranging from food to cosmetics.\n", - "They form a regular part of the diets of many people in the tropics and subtropics.\n", - "Coconuts are distinct from other fruits for their endosperm containing a large quantity of water (also called \"milk\"), and when immature, may be harvested for the potable coconut water.\n", - "When mature, they can be used as seed nuts or processed for oil, charcoal from the hard shell, and coir from the fibrous husk.\n", - "When dried, the coconut flesh is called copra.\n", - "The oil and milk derived from it are commonly used in cooking and frying, as well as in soaps and cosmetics.\n", - "The husks and leaves can be used as material to make a variety of products for furnishing and decorating.\n", - "The coconut also has cultural and religious significance in certain societies, particularly in India, where it is used in Hindu rituals.'''\n", - "\n", - "example_preds = classifier.predict(vectorizer.transform([example_text]))\n", - "example_labels = mlb.inverse_transform(example_preds)\n", - "print(\"Example labels: {}\".format(example_labels))" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.6.6" - } - }, - "nbformat": 4, - "nbformat_minor": 2 -} diff --git a/neural_network/fully_connected_neural_network.ipynb b/neural_network/fully_connected_neural_network.ipynb deleted file mode 100644 index a8bcf4beeea1..000000000000 --- a/neural_network/fully_connected_neural_network.ipynb +++ /dev/null @@ -1,327 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Standard (Fully Connected) Neural Network" - ] - }, - { - "cell_type": "code", - "execution_count": 1, - "metadata": {}, - "outputs": [], - "source": [ - "#Use in Markup cell type\n", - "#![alt text](imagename.png \"Title\") " - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Implementing Fully connected Neural Net" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "#### Loading Required packages and Data" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "metadata": {}, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "Using TensorFlow backend.\n" - ] - } - ], - "source": [ - "###1. Load Data and Splot Data\n", - "from keras.datasets import mnist\n", - "from keras.models import Sequential \n", - "from keras.layers.core import Dense, Activation\n", - "from keras.utils import np_utils\n", - "(X_train, Y_train), (X_test, Y_test) = mnist.load_data()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "#### Preprocessing" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "
" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "import matplotlib.pyplot as plt\n", - "n = 10 # how many digits we will display\n", - "plt.figure(figsize=(20, 4))\n", - "for i in range(n):\n", - " # display original\n", - " ax = plt.subplot(2, n, i + 1)\n", - " plt.imshow(X_test[i].reshape(28, 28))\n", - " plt.gray()\n", - " ax.get_xaxis().set_visible(False)\n", - " ax.get_yaxis().set_visible(False)\n", - "plt.show()\n", - "plt.close()" - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Previous X_train shape: (60000, 28, 28) \n", - "Previous Y_train shape:(60000,)\n", - "New X_train shape: (60000, 784) \n", - "New Y_train shape:(60000, 10)\n" - ] - } - ], - "source": [ - "print(\"Previous X_train shape: {} \\nPrevious Y_train shape:{}\".format(X_train.shape, Y_train.shape))\n", - "X_train = X_train.reshape(60000, 784) \n", - "X_test = X_test.reshape(10000, 784)\n", - "X_train = X_train.astype('float32') \n", - "X_test = X_test.astype('float32') \n", - "X_train /= 255 \n", - "X_test /= 255\n", - "classes = 10\n", - "Y_train = np_utils.to_categorical(Y_train, classes) \n", - "Y_test = np_utils.to_categorical(Y_test, classes)\n", - "print(\"New X_train shape: {} \\nNew Y_train shape:{}\".format(X_train.shape, Y_train.shape))" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "#### Setting up parameters" - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "metadata": {}, - "outputs": [], - "source": [ - "input_size = 784\n", - "batch_size = 200 \n", - "hidden1 = 400\n", - "hidden2 = 20\n", - "epochs = 2" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "#### Building the FCN Model" - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "_________________________________________________________________\n", - "Layer (type) Output Shape Param # \n", - "=================================================================\n", - "dense_1 (Dense) (None, 400) 314000 \n", - "_________________________________________________________________\n", - "dense_2 (Dense) (None, 20) 8020 \n", - "_________________________________________________________________\n", - "dense_3 (Dense) (None, 10) 210 \n", - "=================================================================\n", - "Total params: 322,230\n", - "Trainable params: 322,230\n", - "Non-trainable params: 0\n", - "_________________________________________________________________\n" - ] - } - ], - "source": [ - "###4.Build the model\n", - "model = Sequential() \n", - "model.add(Dense(hidden1, input_dim=input_size, activation='relu'))\n", - "# output = relu (dot (W, input) + bias)\n", - "model.add(Dense(hidden2, activation='relu'))\n", - "model.add(Dense(classes, activation='softmax')) \n", - "\n", - "# Compilation\n", - "model.compile(loss='categorical_crossentropy', \n", - " metrics=['accuracy'], optimizer='sgd')\n", - "model.summary()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "#### Training The Model" - ] - }, - { - "cell_type": "code", - "execution_count": 7, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Epoch 1/10\n", - " - 12s - loss: 1.4482 - acc: 0.6251\n", - "Epoch 2/10\n", - " - 3s - loss: 0.6239 - acc: 0.8482\n", - "Epoch 3/10\n", - " - 3s - loss: 0.4582 - acc: 0.8798\n", - "Epoch 4/10\n", - " - 3s - loss: 0.3941 - acc: 0.8936\n", - "Epoch 5/10\n", - " - 3s - loss: 0.3579 - acc: 0.9011\n", - "Epoch 6/10\n", - " - 4s - loss: 0.3328 - acc: 0.9070\n", - "Epoch 7/10\n", - " - 3s - loss: 0.3138 - acc: 0.9118\n", - "Epoch 8/10\n", - " - 3s - loss: 0.2980 - acc: 0.9157\n", - "Epoch 9/10\n", - " - 3s - loss: 0.2849 - acc: 0.9191\n", - "Epoch 10/10\n", - " - 3s - loss: 0.2733 - acc: 0.9223\n" - ] - }, - { - "data": { - "text/plain": [ - "" - ] - }, - "execution_count": 7, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "# Fitting on Data\n", - "model.fit(X_train, Y_train, batch_size=batch_size, epochs=10, verbose=2)\n", - "###5.Test " - ] - }, - { - "cell_type": "markdown", - "metadata": { - "collapsed": true - }, - "source": [ - "#### Testing The Model" - ] - }, - { - "cell_type": "code", - "execution_count": 8, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "10000/10000 [==============================] - 1s 121us/step\n", - "\n", - "Test accuracy: 0.9257\n", - "[0 6 9 0 1 5 9 7 3 4]\n" - ] - }, - { - "data": { - "image/png": "iVBORw0KGgoAAAANSUhEUgAABHEAAABzCAYAAAAfb55ZAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAALEgAACxIB0t1+/AAAADl0RVh0U29mdHdhcmUAbWF0cGxvdGxpYiB2ZXJzaW9uIDIuMi4zLCBodHRwOi8vbWF0cGxvdGxpYi5vcmcvIxREBQAAHPJJREFUeJzt3XeYVNUZx/GzgBAQEAQVLKAuoQkmNJUIrkCKi4jUUESIgLTE8AASejcohhIeJVIEgVCCNAF5gokoIKCIVKVbQFoiCIhU4XHzB+H1Pce9w+zsnZ25M9/PX7/rOdw5OtzZ2et9z5uSkZFhAAAAAAAAEN9yxXoBAAAAAAAAuDZu4gAAAAAAAAQAN3EAAAAAAAACgJs4AAAAAAAAAcBNHAAAAAAAgADgJg4AAAAAAEAAcBMHAAAAAAAgALiJAwAAAAAAEADcxAEAAAAAAAiAPFmZnJKSkhGthSC0jIyMFD/Ow3sYU8czMjJu8uNEvI+xw7WYELgWEwDXYkLgWkwAXIsJgWsxAXAtJoSwrkWexAFyzoFYLwCAMYZrEYgXXItAfOBaBOJDWNciN3EAAAAAAAACgJs4AAAAAAAAAcBNHAAAAAAAgADgJg4AAAAAAEAAcBMHAAAAAAAgALiJAwAAAAAAEADcxAEAAAAAAAiAPLFeAJJTvnz5JK9bt84aq1KliuRly5ZJbtSoUfQXBgAAAABAnOJJHAAAAAAAgADgJg4AAAAAAEAAcBMHAAAAAAAgAAK/J06tWrWs4/fff19yuXLlJDdo0MCa9+ijj0pevny55/nXr18vee3atRGvE/Y+OOPGjZP885//3JqXkZEhedOmTdFfGAAkiaFDh0oeMmSINbZq1SrJderUyaEVIRzVqlWTrPeHa9q0qTVPf+9JSUmxxvTP1s2bN0vetWuXNW/kyJGSd+/eHeGKAcAfBQsWtI5vv/12yd26dfP8c9OmTZO8detW/xcGxBBP4gAAAAAAAAQAN3EAAAAAAAACIDDlVIULF5Y8e/ZsyXXr1rXmnT9/XnLevHklu4/iabVr1/Yc0+c7d+6cNda1a1fJCxYs8DwHrvjjH/8ouVOnTpLfeecda97gwYMlf/DBB9FfGIBMFS1aVLIue0xPT7fm9e7dW/L3339vjenPxgMHDkgeM2aMNe+///1v9haLsKSlpXmOPfzww5lmY+xSK0RO/+wzxpjy5ctLDvVdpGrVqpJ1WVSokqnJkydbY4sXL5b8r3/9K8wVA0DO07+36e8YxhgzcODAsM7RpUsXyfPmzbPGunfvLvnEiRORLBEJ5h//+IfkZcuWWWP63kO84EkcAAAAAACAAOAmDgAAAAAAQAAEppxq1KhRknVnKVf+/Pkl644Lx44ds+adPn3a8xz68WT9WvrcxhgzdepUyXv37rXGtm/f7nn+ZFWiRIlM//nbb79tHVNCBeSc6667TnKvXr2ssd///veSS5Ys6XkOXUKlyzmM+XH3nKuKFy9uHbdv3/7ai0W2uWVS4c6jnMofEydOtI719aJLtt2uUOPHj890zP1uo0umEHvuddSkSRPJ+rPx1ltvtebp7mHz58+3xl544QUfVwjEp379+knu27dvROfInTu35NatW1tjejuOp556SjKlpsklV64fnmfRfyd27twZi+VkCU/iAAAAAAAABAA3cQAAAAAAAAKAmzgAAAAAAAABELd74txzzz3WcbNmzTKdd+jQIeu4bdu2kj/99FPJp06dsuadOXPG87V1fZxud+22tNNtz4cMGWKNdezYUfLJkyc9XyuZFCpUSPKlS5cku3viIDHoltQjRoyQXL9+fWuevt5CtaceMGCA5KNHj1rz6tSpI3nlypXW2Pnz57Oy7KTTuXNnyc8991xE51i9erXkhx56KKw/oz+rjWFPnHgzdOjQWC8hIS1atMg6btSokWS9102NGjVybE3IPr3nn36P77vvPmue3nNRf3/ds2ePNa9UqVKS3c/lAwcOSJ47d26EK04s6enpkt944w3Jes+3a9HfFZYuXeo5T//313tV3X///da848ePS167dm3Y68AV+/fv9xzTe4lNmDDBGtuxY4dk/f4PHz7cmqev2SVLlkjWe7AaY8yLL74oWe9bhsRQpUoVye5ejfGOJ3EAAAAAAAACgJs4AAAAAAAAARC35VS69MYYY4oVKyZZP0bnPvbmRxtUXdKhHynPmzevNe/ZZ5+V3LhxY2ts2rRpkpcvX57tNQWR2zKzQ4cOktevXy9Zt9JEsOhHVdPS0qyx1157TbJuT+22oA63PbV+1PmOO+6w5uk2ru3atbPGZs2a5bn+ZKXLVQcNGpTlP++2+9SPlLuPLPfu3TvL5wcSVdeuXa3jatWqSS5durRkXU5jjDFffvlldBeGLHEfu9ff83Qpsfu+6fLVDRs2SP7mm2+sefpnnC71MMaY5s2bS543b16m/9wYY7Zs2SJ537591pj7szbo9LWTlRIqLX/+/JJbtGgR1p/p0aOH5+vq7zb6vTbGLhXXrYzdEiK3zC6Z6FJT1/z58yV37949rPNt27bNOl68eLHkG2+8UbL7nSg1NVWyW/att4aAf8qWLSt59OjRkp955hlrni5t9NvHH38ctXP7hSdxAAAAAAAAAoCbOAAAAAAAAAHATRwAAAAAAIAAiNs9cfLly+c5NmPGDMlua7lo6t+/v3Wsa2bvuusua6xJkyaSk3VPHLcle6w88MADkt29VDS3Xnbv3r1RW1OiqFq1quQVK1Z4ztMtwf/whz9YY6FaNuo697Nnz0p+6aWXrHnfffddpq+FK/QeOMYY8/zzz0vWezu4+yToeuOGDRtK3rVrlzVP1/4PHjzYGtN157ptq7unxPbt2yXfe++9mfxbwA/Dhg2TPGTIEM95botxWo7749ixY9bx5MmTJetW0u71wZ448cXd60vvg3PkyBHJ5cqVs+bpn1WhHDx4ULK7183Fixcl169fX/KcOXM8z1ewYEHrWO8xlwimTp0qWe9TUqZMGWteqOvoJz/5ieTHH388rNetUKGC5Jtuuskay5Xrh/9PXrNmTWvMPb7qwoUL1vFf/vIXyaE+rxOR/rutv2MYY39Whstt867fY/2dqFatWta81q1be57zqaeeknz58uUsrwmZ07+3NWjQQLL+/d8Yf/bEcT8jrjp8+HC2zx1tPIkDAAAAAAAQANzEAQAAAAAACIC4LacaMWKE55jbqi9W3nrrLcldunSxxvSjYMnq0Ucf9RzTj7764ZVXXvF87aJFi0rWLSRdp0+fto7HjRsnOdTfx2SjS3N0eYxr5cqVkvv16yc5Ky3ldZt63Wa1SJEi1jz9yLF+XVyhy96Msa8P/ci3+6j/3/72N8k7duwI67Xclpsffvih5OnTp0vu1auXNa9y5cqSdYmJMcZ06tQprNfGtSXbI/nxTl9/KSkpknWZhjsWii51DFWqiqxr2bKl5J49e1pjJ06ckKzfu3DLp0L57LPPrOOKFStKnjlzpuef0z8z3TKdRKN/7vjx/VJ//wulUqVKkn/1q195znNLcqpVq5bpPF3SZYzdPnvs2LHWmNuWPtG8/fbbkuvWrWuN6fL6SK1fv17yn/70J8nuFhj6dwj3fVy2bJnk119/PdtrwhXu+31VNEqc9PfLU6dOSc7K7yqxwpM4AAAAAAAAAcBNHAAAAAAAgACIq3Kqu+++W7IuozDGfmzw448/zrE1hfLOO+9IdsupklWBAgUk58lj//XSj8HpsopQ9DnckhDd9aZEiRLWmH5EXXcD0Y9nuucsVaqUNaYfsdOPLPuxG3qQDRo0SLLuoOI+gqofN//0008jei39qHKVKlU854XqjAVj0tPTrWPdhUp3fVi1apU1b8yYMb6uo2/fvp5r0u919erVfX1dIF64HWw6duwoWV+XbhcOXU6l57llVvrn4uzZsz3HkHW6a57+jmGMXW565syZqK7j0KFDYc379ttvJbudB+GPTz75JNPsckv+b7vtNsn652KHDh2seYULF5bsliC7nSATjS4N9SqvyYz+TNXlT5MmTQrrz8+dO9c67tatm+fcn/70p2GvC94KFSpkHderV0+yLlPT5fl+ue666yTr78NB6DbGkzgAAAAAAAABwE0cAAAAAACAAOAmDgAAAAAAQADE1Z44bdq0kaz3xzHGmIULF0rWbeEQX3Qt6i233GKNuW2Dvej9kPS+NAMHDvT8M0eOHLGO//73v0vWbZJD1ZK77bLr168vuWTJkpKTbU+cKVOmWMfNmzeXrNs86rpuYyLbB0fXphpjtybXez+sXr3amucew5hixYpJvu+++8L6M/q6iTb3tUaNGpVjrw3kJL0PjvtZpfdi0y1N9X4Qxhizdu3aTM/99NNPW8e6dXGTJk2sMb0viv5McF+L1uSZS01N9RzLyc+v3/zmN5Lz58/vOY+Wx/HDbfGu28brvzvunjh6X6Nw95JMFB999JHnmN6fym3L/vLLL0vW3ynT0tJ8XN0V+neePXv2SP73v/9tzUv0dvDZVbFiRetY7xm1YcMGyXrPmkgVKVLEOq5QoYJk932LdzyJAwAAAAAAEADcxAEAAAAAAAiAuCqnatmypWT30bPx48fn9HIQgVBtoPft2xfWOXTZVOfOnSW7LTJ1i/cePXpYY7rdZ7jCXV+ycds96/dBt1LduXNnROfXj7uOGDHCGqtdu3amrzt8+PCIXiuZ6LKKO++803Pee++9J9ltEx8rRYsWtY51OePRo0dzejlAtpQrVy7TbIwxixYtkqxLVcPllikXL15csi5RN8aYRo0aSdatWt3Pbr2O3bt3Z3lNiaJAgQLWcePGjT3nuiXdfsqbN691PHLkyEzH3NbmoVpeI348/vjjnmO69XKzZs2ssRdffDFqa4oHb7zxhmS3jEZ//3e3btCla26Jvt90Oey8efMkuyWpemuIJUuWWGOUrxpTq1YtzzG/t0to0aKFday3HlizZo2vrxVtPIkDAAAAAAAQANzEAQAAAAAACIC4KqfS3Ed4vTozIL7ozlLhKlu2rHXsPup2ldslqXv37pK/++67LL/utehOIToje9zSnm7duknu2bOn55/TZTRbt271fV2JRpdThTJkyBDJJ0+ejNZysuSOO+6wjitVqiSZcqqcMXTo0FgvIWHo7y+5c+eO6msdP35c8l//+ldrTB/rx/vdDlf6kfL09HRrbNOmTb6sM4ii/d5pugykbt261pjbvfWqadOmWcfJ1kkzSPR7GOqz9vTp05Ld78CJTv+7z5o1y3OeW0b4xBNPSP7tb38r+cYbb7Tm6Q60fnNLMfX63TLH1q1bS45kK4igypcvn2T9e4Axxpw4cUKyLqd/9dVXrXm6lO7666+X/NBDD3m+ru5063I7ncU7nsQBAAAAAAAIAG7iAAAAAAAABAA3cQAAAAAAAAIgpnvi6Po1Y6LfCg7Rp9shhqo71J555hnruEiRIpLnzJkjuWvXrtlcXWh67cYYc+nSJcnR2HMnKNz2s5UrV5asW/Nt2bIlrPPpFrjG2PsouW3ktZUrV0o+depUWK+VzHRNdqhr0e/2jZHKleuH/6fgthMF4C/dmly3OTfG/kxYvny5NaZ/Di9evDhKq4sPly9fto73798v2d3b7de//rXkbdu2Zfm19L4Pxhjz5JNPSn7++efDOsf06dOz/LqIjccee0yy+7uQpvfBiZc96+Kd/szS2d3Tyv3Of5Xbslx/L/3qq688X3fYsGGS27dvb43p72N6jz9jjBk7dqzkPn36SE70vR/1/jN33XWX57xly5ZJdr8b7tq1S7L+fP7nP//peb569ep5rmPkyJGSv/76a2vezJkzPc8ZKzyJAwAAAAAAEADcxAEAAAAAAAiAmJZT6dZvxhiTmpoqWbfJjFcNGzb0HHMfw00W+rHDUKUxmvsYsf5z7pjfdClPhw4drDH3EfNk1bFjR+u4cOHCknWLRl1mlRX6Omrbtq011rRpU8kTJ06M6PzJqkaNGpLDvRZjST8mG4T1AonC/b6lS6bGjBljjU2aNEly6dKlJbvtzBOBW0adlpYm2S0zHjVqlGRdWrVw4UJrXsWKFSXrco7atWtb83RJh261bIwxN9xwg+Qvv/xS8sGDBzP5t0A8KFOmjHX83HPPZTrv7Nmz1vHUqVOjtqZEpUv2y5YtK3n9+vXWPK+y/EjL9bt37y553rx51tgrr7wi2S2n+uUvfylZl06mp6dHtI6guHjxouR9+/ZZYzfffLNkXeI0Y8YMa16o8jYv+jPTGGNuv/12yXobjc6dO1vzKKcCAAAAAABARLiJAwAAAAAAEADcxAEAAAAAAAiAmO6JEzTVqlWzjhs0aOA5t3///tFeTsJw6w4ffPDBTHO/fv2sebpFqtsKLlx635tz585ZY+5eAMnq/Pnz1rFujfnwww9Lrl69uuc5duzYIdlt/TdhwgTJzZo1s8b27t0r+bPPPgtvwQi8M2fOWMeRXt8Asm7NmjWS3X0ZdPvx0aNHS07EPXFchw4dktymTRtrbMCAAZLr1q2baTbG3nPhiy++kLxq1Spr3ty5cyW/+eab1pjeM2zlypWST5w4EXL9yFl6bxZ9rRjj3VZ88ODB1vHu3bv9X1iC0d9JjbE/i/S+ly1btrTmLVmyJGprcvffqVWrluTNmzdbY3fffbfkmjVrSn7kkUeseStWrPBziTF34cIFyXoPR2OMyZPnh9sTfnyu3XbbbZKLFi1qjW3btk1yu3btJLu/E8YjnsQBAAAAAAAIAG7iAAAAAAAABADlVNegS6h69uxpjRUpUkTyunXrrLG33noruguLE/pRRWMiawnulkpUrVpV8tKlSyWPGDHCmqcfNXRL27799ttMxwYOHGjNq1KlimS35eMHH3xwzbUnO/0IuPs4eLi6dOki2W0tvXHjRsnHjh2L6PyIT247eW3o0KHWsfv4MSKnr1NdDuly3wP3GMnBbT++du1ayeXLl8/p5cQN/d3EGLtM2C2913Tb8lCfa7o1ct68eT3nLViwIOQ6ETt9+/aV3LBhQ895n3/+ueTx48dHdU2JqGDBgtax/r1EXzsLFy605ukSp2h/39e/k7Rq1coae//99yUXKlRIcp8+fax5iVZOpZ0+fTqq59e/L7qljLpcdfv27VFdh994EgcAAAAAACAAuIkDAAAAAAAQADEtp9q/f791rB83i6XcuXNLfvbZZyW3aNHCmnf48OFM5xljzOXLl6O0uvhy5MgR63jfvn2SS5cubY3pLg2TJk2S7O4AfvToUcl6x3K3ZGrXrl2SdWmbMXZnqQ4dOni+li6hcsu1EB133nmn55jblSgZOp5Ei36U230MV3fNmDZtmuT27dtHf2GZrMEYu1xu4sSJObYOAN7ckqlGjRpJ3rlzZ04vJ27prlN+lGbobiqhbNiwIduvBX+43Y969OjhOffs2bOS9TX1/fff+7+wBKc7uRljXzujRo2SnJKSYs3Tv+vlpJ/97GfWsbuuq4JW2hPP3I5UWqRbQcQDnsQBAAAAAAAIAG7iAAAAAAAABAA3cQAAAAAAAAIgpnvivPvuu9ax3mOmcOHC1pjeP8FteRmJe++9V3K3bt2sMd3iunr16p7naNOmjWTqkq/Q+88sX77cGqtfv75k3YJ97Nix1jy9J452//33W8f9+vXzHNM1pnv27JE8YMAAa97ixYszfS1Ez6BBgzzHli1bZh3TWjpyW7duldy7d29rbPr06ZKbN28u+eWXX7bm+f3ff8qUKZJvueUWa2z+/PmSL1y44OvrJjvdSjxUW3FEn7tPht4LatasWTm9nEzp/ez+/Oc/W2MFChSQrD874K9mzZrFegkIQ1pammS916Mx3nudGGPM7373O8mffPKJ7+tKZpMnT5asW0vXqVPHmjdz5kzJq1evlvzCCy9Y8/bu3ZvlNXTv3t067tixo+TU1FRrLNTfE0TfxYsXY72EiPEkDgAAAAAAQABwEwcAAAAAACAAYlpOFUqFChWsY90i16vcJiseeOABycWKFfOcp0u3li5dao1t3Lgx2+tINIcOHZKsH2M0xi6fq1mzpmRdRuHSjxlmZGSEvY7XXntNcp8+fSR//fXXYZ8D/rnnnnskN23a1HOeLrODf9atW2cdz5kzR3Lr1q0l60fDjfGnnEo/wty4cWPJX331lTVv+PDh2X4tZG7IkCGxXkJS03/vR48ebY3pR//9Lqe66aabPNcR6p/rknL3Om3btq3k3bt3Z3eJ+L9SpUpZx61atfKcu2bNGsmnT5+O2pqQuSJFikh+8803JV9//fWef2bChAnWsfv7BPyjrwndvn3btm3WvJIlS0pu166d5CeffNKaF0nb9zx5Ivv1Wv9eyXciXAtP4gAAAAAAAAQAN3EAAAAAAAACgJs4AAAAAAAAARBXe+Lo9s8DBw60xnSNtt/cescTJ05I1u2v3bZzCM3du0jvQ9SiRQvJZcqUseY9/fTTkl999VXJofbEmTp1qnVMrX580ddvoUKFrDH9vtJaOjo+//xz61i3eX/wwQclu3un6D01+vfv73n+smXLSq5Ro4Y1Nm7cOMl6L4ExY8ZY83bu3Ol5fmSN20Y83Lbiev+iVatW+bcgiFy57P931qlTJ8l6v7BFixZZ8/T+cOXLl5es9+0zxt4Dwm1dqz9r9diuXbusebNnz5Y8cuRIa8x9PfjDbTt8ww03eM5dsmSJ5MuXL0dtTbjCvWb1/imh9sHZtGmT5J49e1pjly5d8ml1COXMmTOS3WtMv48tW7aUXKlSJWverbfe6uua1q9fbx3rvSCnTJkimT08/fOLX/xCsvtzUf88Xbt2bY6tyQ88iQMAAAAAABAA3MQBAAAAAAAIgLgqp1q8eLHkDRs2WGO6xbj7qFsk9CNrW7ZsscYmTpyY7fPjx06dOiV50qRJnvN69+6dE8tBDipevLhktyxux44dkhcsWJBja0pm+/fvl6zLqdzPvm7duklOT0/3nKdbYRYrVszzdXU7Vt1aGTln2LBhkocOHRq7hSQR/d3mkUcescZ0+ZPmtv3WpY269ND9PNXXlVv6pNehueXH586dy3Qeoufmm2/2HHPfj5deeinay4GitwIwxi4RDmXUqFGSKZ+KPzNmzMg0lyhRwppXsGBBybr81Rhj3n33Xcm6lHzv3r3WvI8++kjywYMHrbGLFy9mZdmIgN7Gwf2ZefLkyZxejm94EgcAAAAAACAAuIkDAAAAAAAQACmhOv78aHJKSviT4auMjIyUa8+6Nt7DmNqUkZFR3Y8TBe191CWLlStXtsb69u0refTo0Tm2pkgl8rXodkQpV66cZN3RSpdWGfPjTlPawoULJW/evFlyjLuqJO21mEgS+VpMIlyLxpjXX3/dOtadytztBXSnlXiRaNdi4cKFJX/xxRfWWNGiRSXrTjfvvfeeNa9u3bqSA9JFjGsxASTateiHXr16Sa5du7Y11rp1a8lxVEoc1rXIkzgAAAAAAAABwE0cAAAAAACAAOAmDgAAAAAAQADEVYtxAIlJt8R198RB/Pjmm2+s4w8//FDyY489ltPLAYCk0KxZM+tY71ep95RDzqhXr55kvQeOS++D06pVK2ssIPvgAAlP79sYag/HoOFJHAAAAAAAgADgJg4AAAAAAEAAUE4FIOpWrFghOTU11RrbuHFjTi8HAIC4kSsX/081nugS8P/85z/W2L59+yQ/8cQTkg8fPhz9hQHA//FTAwAAAAAAIAC4iQMAAAAAABAA3MQBAAAAAAAIgBTdxvCak1NSwp8MX2VkZKT4cR7ew5jalJGRUd2PE/E+xg7XYkLgWkwAXIsJgWsxAXAtJgSuxQTAtZgQwroWeRIHAAAAAAAgALiJAwAAAAAAEABZbTF+3BhzIBoLQUilfTwX72Hs8D4GH+9hYuB9DD7ew8TA+xh8vIeJgfcx+HgPE0NY72OW9sQBAAAAAABAbFBOBQAAAAAAEADcxAEAAAAAAAgAbuIAAAAAAAAEADdxAAAAAAAAAoCbOAAAAAAAAAHATRwAAAAAAIAA4CYOAAAAAABAAHATBwAAAAAAIAC4iQMAAAAAABAA/wOj6vqySBf1wwAAAABJRU5ErkJggg==\n", - "text/plain": [ - "
" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "score = model.evaluate(X_test, Y_test, verbose=1)\n", - "print('\\n''Test accuracy:', score[1])\n", - "mask = range(10,20)\n", - "X_valid = X_test[mask]\n", - "y_pred = model.predict_classes(X_valid)\n", - "print(y_pred)\n", - "plt.figure(figsize=(20, 4))\n", - "for i in range(n):\n", - " # display original\n", - " ax = plt.subplot(2, n, i + 1)\n", - " plt.imshow(X_valid[i].reshape(28, 28))\n", - " plt.gray()\n", - " ax.get_xaxis().set_visible(False)\n", - " ax.get_yaxis().set_visible(False)\n", - "plt.show()\n", - "plt.close()" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.6.6" - } - }, - "nbformat": 4, - "nbformat_minor": 2 -} From ce7faa5a3ae4824ebc89bf7b40b715cb4c898999 Mon Sep 17 00:00:00 2001 From: anubhav-sharma13 <45630457+anubhav-sharma13@users.noreply.github.com> Date: Tue, 22 Oct 2019 13:00:11 +0530 Subject: [PATCH 0291/1071] Largest subarray sum (#1404) * Insertion_sort * largest subarray sum * updated print command * removed extraspaces * removed sys.maxint * added explaination * Updated function style * Update largest_subarray_sum.py * Update i_sort.py * Delete bogo_bogo_sort.py --- other/largest_subarray_sum.py | 23 +++++++++++++++ sorts/bogo_bogo_sort.py | 54 ----------------------------------- sorts/i_sort.py | 21 ++++++++++++++ 3 files changed, 44 insertions(+), 54 deletions(-) create mode 100644 other/largest_subarray_sum.py delete mode 100644 sorts/bogo_bogo_sort.py create mode 100644 sorts/i_sort.py diff --git a/other/largest_subarray_sum.py b/other/largest_subarray_sum.py new file mode 100644 index 000000000000..0449e72e64e3 --- /dev/null +++ b/other/largest_subarray_sum.py @@ -0,0 +1,23 @@ +from sys import maxsize + + +def max_sub_array_sum(a: list, size: int = 0): + """ + >>> max_sub_array_sum([-13, -3, -25, -20, -3, -16, -23, -12, -5, -22, -15, -4, -7]) + -3 + """ + size = size or len(a) + max_so_far = -maxsize - 1 + max_ending_here = 0 + for i in range(0, size): + max_ending_here = max_ending_here + a[i] + if max_so_far < max_ending_here: + max_so_far = max_ending_here + if max_ending_here < 0: + max_ending_here = 0 + return max_so_far + + +if __name__ == "__main__": + a = [-13, -3, -25, -20, 1, -16, -23, -12, -5, -22, -15, -4, -7] + print(("Maximum contiguous sum is", max_sub_array_sum(a, len(a)))) diff --git a/sorts/bogo_bogo_sort.py b/sorts/bogo_bogo_sort.py deleted file mode 100644 index f26a46e78645..000000000000 --- a/sorts/bogo_bogo_sort.py +++ /dev/null @@ -1,54 +0,0 @@ -""" -Python implementation of bogobogosort, a "sorting algorithm -designed not to succeed before the heat death of the universe -on any sizable list" - https://en.wikipedia.org/wiki/Bogosort. - -Author: WilliamHYZhang -""" - -import random - - -def bogo_bogo_sort(collection): - """ - returns the collection sorted in ascending order - :param collection: list of comparable items - :return: the list sorted in ascending order - - Examples: - >>> bogo_bogo_sort([0, 5, 3, 2, 2]) - [0, 2, 2, 3, 5] - >>> bogo_bogo_sort([-2, -5, -45]) - [-45, -5, -2] - >>> bogo_bogo_sort([420, 69]) - [69, 420] - """ - - def is_sorted(collection): - if len(collection) == 1: - return True - - clone = collection.copy() - while True: - random.shuffle(clone) - ordered = bogo_bogo_sort(clone[:-1]) - if clone[len(clone) - 1] >= max(ordered): - break - - for i in range(len(ordered)): - clone[i] = ordered[i] - - for i in range(len(collection)): - if clone[i] != collection[i]: - return False - return True - - while not is_sorted(collection): - random.shuffle(collection) - return collection - - -if __name__ == "__main__": - user_input = input("Enter numbers separated by a comma:\n").strip() - unsorted = [int(item) for item in user_input.split(",")] - print(bogo_bogo_sort(unsorted)) diff --git a/sorts/i_sort.py b/sorts/i_sort.py new file mode 100644 index 000000000000..f6100a8d0819 --- /dev/null +++ b/sorts/i_sort.py @@ -0,0 +1,21 @@ +def insertionSort(arr): + """ + >>> a = arr[:] + >>> insertionSort(a) + >>> a == sorted(a) + True + """ + for i in range(1, len(arr)): + key = arr[i] + j = i - 1 + while j >= 0 and key < arr[j]: + arr[j + 1] = arr[j] + j -= 1 + arr[j + 1] = key + + +arr = [12, 11, 13, 5, 6] +insertionSort(arr) +print("Sorted array is:") +for i in range(len(arr)): + print("%d" % arr[i]) From 8b572e6cfd7da8d96443c3e1fd0b013153854265 Mon Sep 17 00:00:00 2001 From: Suad Djelili Date: Tue, 22 Oct 2019 10:31:17 +0300 Subject: [PATCH 0292/1071] added solution 7 for problem_01 (#1416) * added solution 7 for problem_01 * added solution 5 for problem_02 --- project_euler/problem_01/sol7.py | 32 ++++++++++++++++++++++ project_euler/problem_02/sol5.py | 46 ++++++++++++++++++++++++++++++++ 2 files changed, 78 insertions(+) create mode 100644 project_euler/problem_01/sol7.py create mode 100644 project_euler/problem_02/sol5.py diff --git a/project_euler/problem_01/sol7.py b/project_euler/problem_01/sol7.py new file mode 100644 index 000000000000..a0510b54c409 --- /dev/null +++ b/project_euler/problem_01/sol7.py @@ -0,0 +1,32 @@ +""" +Problem Statement: +If we list all the natural numbers below 10 that are multiples of 3 or 5, +we get 3,5,6 and 9. The sum of these multiples is 23. +Find the sum of all the multiples of 3 or 5 below N. +""" + + +def solution(n): + """Returns the sum of all the multiples of 3 or 5 below n. + + >>> solution(3) + 0 + >>> solution(4) + 3 + >>> solution(10) + 23 + >>> solution(600) + 83700 + """ + + result = 0 + for i in range(n): + if i % 3 == 0: + result += i + elif i % 5 == 0: + result += i + return result + + +if __name__ == "__main__": + print(solution(int(input().strip()))) diff --git a/project_euler/problem_02/sol5.py b/project_euler/problem_02/sol5.py new file mode 100644 index 000000000000..8df2068dd8c3 --- /dev/null +++ b/project_euler/problem_02/sol5.py @@ -0,0 +1,46 @@ +""" +Problem: +Each new term in the Fibonacci sequence is generated by adding the previous two +terms. By starting with 1 and 2, the first 10 terms will be: + + 1,2,3,5,8,13,21,34,55,89,.. + +By considering the terms in the Fibonacci sequence whose values do not exceed +n, find the sum of the even-valued terms. e.g. for n=10, we have {2,8}, sum is +10. +""" + + +def solution(n): + """Returns the sum of all fibonacci sequence even elements that are lower + or equals to n. + + >>> solution(10) + 10 + >>> solution(15) + 10 + >>> solution(2) + 2 + >>> solution(1) + 0 + >>> solution(34) + 44 + """ + + a = [0,1] + i = 0 + while a[i] <= n: + a.append(a[i] + a[i+1]) + if a[i+2] > n: + break + i += 1 + sum = 0 + for j in range(len(a) - 1): + if a[j] % 2 == 0: + sum += a[j] + + return sum + + +if __name__ == "__main__": + print(solution(int(input().strip()))) From 92268561a54f6f443a71c0e83fdd0a0d69ab24d7 Mon Sep 17 00:00:00 2001 From: Aashay Shingre Date: Tue, 22 Oct 2019 13:12:56 +0530 Subject: [PATCH 0293/1071] Aho-Corasick String Matching Algorithm (#346) * add aho-corasick algorithm * Add a doctest and format with black --- strings/aho-corasick.py | 92 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 92 insertions(+) create mode 100644 strings/aho-corasick.py diff --git a/strings/aho-corasick.py b/strings/aho-corasick.py new file mode 100644 index 000000000000..6790892a358d --- /dev/null +++ b/strings/aho-corasick.py @@ -0,0 +1,92 @@ +from collections import deque + + +class Automaton: + def __init__(self, keywords): + self.adlist = list() + self.adlist.append( + {"value": "", "next_states": [], "fail_state": 0, "output": []} + ) + + for keyword in keywords: + self.add_keyword(keyword) + self.set_fail_transitions() + + def find_next_state(self, current_state, char): + for state in self.adlist[current_state]["next_states"]: + if char == self.adlist[state]["value"]: + return state + return None + + def add_keyword(self, keyword): + current_state = 0 + for character in keyword: + if self.find_next_state(current_state, character): + current_state = self.find_next_state(current_state, character) + else: + self.adlist.append( + { + "value": character, + "next_states": [], + "fail_state": 0, + "output": [], + } + ) + self.adlist[current_state]["next_states"].append(len(self.adlist) - 1) + current_state = len(self.adlist) - 1 + self.adlist[current_state]["output"].append(keyword) + + def set_fail_transitions(self): + q = deque() + for node in self.adlist[0]["next_states"]: + q.append(node) + self.adlist[node]["fail_state"] = 0 + while q: + r = q.popleft() + for child in self.adlist[r]["next_states"]: + q.append(child) + state = self.adlist[r]["fail_state"] + while ( + self.find_next_state(state, self.adlist[child]["value"]) == None + and state != 0 + ): + state = self.adlist[state]["fail_state"] + self.adlist[child]["fail_state"] = self.find_next_state( + state, self.adlist[child]["value"] + ) + if self.adlist[child]["fail_state"] == None: + self.adlist[child]["fail_state"] = 0 + self.adlist[child]["output"] = ( + self.adlist[child]["output"] + + self.adlist[self.adlist[child]["fail_state"]]["output"] + ) + + def search_in(self, string): + """ + >>> A = Automaton(["what", "hat", "ver", "er"]) + >>> A.search_in("whatever, err ... , wherever") + {'what': [0], 'hat': [1], 'ver': [5, 25], 'er': [6, 10, 22, 26]} + """ + result = dict() # returns a dict with keywords and list of its occurences + current_state = 0 + for i in range(len(string)): + while ( + self.find_next_state(current_state, string[i]) == None + and current_state != 0 + ): + current_state = self.adlist[current_state]["fail_state"] + current_state = self.find_next_state(current_state, string[i]) + if current_state is None: + current_state = 0 + else: + for key in self.adlist[current_state]["output"]: + if not (key in result): + result[key] = [] + result[key].append((i - len(key) + 1)) + return result + + +if __name__ == "__main__": + import doctest + + doctest.testmod() From 54830644a280ee2b8e59497f303220a211422118 Mon Sep 17 00:00:00 2001 From: Anmol Jain <30307833+jainanmol123@users.noreply.github.com> Date: Tue, 22 Oct 2019 13:31:14 +0530 Subject: [PATCH 0294/1071] Create newton_forward_interpolation.py (#333) * Create newton_forward_interpolation.py This code is for calculating newton forward difference interpolation for fixed difference. * Add doctests and reformat with black --- .../newton_forward_interpolation.py | 55 +++++++++++++++++++ 1 file changed, 55 insertions(+) create mode 100644 arithmetic_analysis/newton_forward_interpolation.py diff --git a/arithmetic_analysis/newton_forward_interpolation.py b/arithmetic_analysis/newton_forward_interpolation.py new file mode 100644 index 000000000000..09adb5113f82 --- /dev/null +++ b/arithmetic_analysis/newton_forward_interpolation.py @@ -0,0 +1,55 @@ +# https://www.geeksforgeeks.org/newton-forward-backward-interpolation/ + +import math + +# for calculating u value +def ucal(u, p): + """ + >>> ucal(1, 2) + 0 + >>> ucal(1.1, 2) + 0.11000000000000011 + >>> ucal(1.2, 2) + 0.23999999999999994 + """ + temp = u + for i in range(1, p): + temp = temp * (u - i) + return temp + + +def main(): + n = int(input("enter the numbers of values")) + y = [] + for i in range(n): + y.append([]) + for i in range(n): + for j in range(n): + y[i].append(j) + y[i][j] = 0 + + print("enter the values of parameters in a list") + x = list(map(int, input().split())) + + print("enter the values of corresponding parameters") + for i in range(n): + y[i][0] = float(input()) + + value = int(input("enter the value to interpolate")) + u = (value - x[0]) / (x[1] - x[0]) + + # for calculating forward difference table + + for i in range(1, n): + for j in range(n - i): + y[j][i] = y[j + 1][i - 1] - y[j][i - 1] + + summ = y[0][0] + for i in range(1, n): + summ += (ucal(u, i) * y[0][i]) / math.factorial(i) + + print("the value at {} is {}".format(value, summ)) + + +if __name__ == "__main__": + main() From 13802fcca154803e1cf619ddfb7f7975d0ca8f59 Mon Sep 17 00:00:00 2001 From: DanishSheikh1999 <43725095+DanishSheikh1999@users.noreply.github.com> Date: Tue, 22 Oct 2019 14:25:01 +0530 Subject: [PATCH 0295/1071] Create greedy.py (#1359) * Create greedy.py * Update greedy.py * Add a doctest and format with black * Update build_directory_md.py --- other/greedy.py | 63 +++++++++++++++++++++++++++++++++++ scripts/build_directory_md.py | 6 ++-- 2 files changed, 65 insertions(+), 4 deletions(-) create mode 100644 other/greedy.py diff --git a/other/greedy.py b/other/greedy.py new file mode 100644 index 000000000000..d1bc156304b0 --- /dev/null +++ b/other/greedy.py @@ -0,0 +1,63 @@ +class things: + def __init__(self, n, v, w): + self.name = n + self.value = v + self.weight = w + + def __repr__(self): + return f"{self.__class__.__name__}({self.name}, {self.value}, {self.weight})" + + def get_value(self): + return self.value + + def get_name(self): + return self.name + + def get_weight(self): + return self.weight + + def value_Weight(self): + return self.value / self.weight + + +def build_menu(name, value, weight): + menu = [] + for i in range(len(value)): + menu.append(things(name[i], value[i], weight[i])) + return menu + + +def greedy(item, maxCost, keyFunc): + itemsCopy = sorted(item, key=keyFunc, reverse=True) + result = [] + totalValue, total_cost = 0.0, 0.0 + for i in range(len(itemsCopy)): + if (total_cost + itemsCopy[i].get_weight()) <= maxCost: + result.append(itemsCopy[i]) + total_cost += itemsCopy[i].get_weight() + totalValue += itemsCopy[i].get_value() + return (result, totalValue) + + +def test_greedy(): + """ + >>> food = ["Burger", "Pizza", "Coca Cola", "Rice", + ... "Sambhar", "Chicken", "Fries", "Milk"] + >>> value = [80, 100, 60, 70, 50, 110, 90, 60] + >>> weight = [40, 60, 40, 70, 100, 85, 55, 70] + >>> foods = build_menu(food, value, weight) + >>> foods # doctest: +NORMALIZE_WHITESPACE + [things(Burger, 80, 40), things(Pizza, 100, 60), things(Coca Cola, 60, 40), + things(Rice, 70, 70), things(Sambhar, 50, 100), things(Chicken, 110, 85), + things(Fries, 90, 55), things(Milk, 60, 70)] + >>> greedy(foods, 500, things.get_value) # doctest: +NORMALIZE_WHITESPACE + ([things(Chicken, 110, 85), things(Pizza, 100, 60), things(Fries, 90, 55), + things(Burger, 80, 40), things(Rice, 70, 70), things(Coca Cola, 60, 40), + things(Milk, 60, 70)], 570.0) + """ + + +if __name__ == "__main__": + import doctest + + doctest.testmod() diff --git a/scripts/build_directory_md.py b/scripts/build_directory_md.py index cae38c13dbf4..2ebd445b3667 100755 --- a/scripts/build_directory_md.py +++ b/scripts/build_directory_md.py @@ -25,7 +25,7 @@ def print_path(old_path: str, new_path: str) -> str: for i, new_part in enumerate(new_path.split(os.sep)): if i + 1 > len(old_parts) or old_parts[i] != new_part: if new_part: - print(f"{md_prefix(i-1)} {new_part.replace('_', ' ').title()}") + print(f"{md_prefix(i)} {new_part.replace('_', ' ').title()}") return new_path @@ -36,9 +36,7 @@ def print_directory_md(top_dir: str = ".") -> None: if filepath != old_path: old_path = print_path(old_path, filepath) indent = (filepath.count(os.sep) + 1) if filepath else 0 - url = "/".join((URL_BASE, filepath.split(os.sep)[1], filename)).replace( - " ", "%20" - ) + url = "/".join((URL_BASE, filepath, filename)).replace(" ", "%20") filename = os.path.splitext(filename.replace("_", " "))[0] print(f"{md_prefix(indent)} [{filename}]({url})") From 7444a1f0693c5e08d6859d549ce5485db5a988b1 Mon Sep 17 00:00:00 2001 From: Ghulam Mohiyuddin Date: Tue, 22 Oct 2019 14:56:06 +0530 Subject: [PATCH 0296/1071] Another method added for GCD (#1387) * Another method added for GCD * Now doctest fulfilled for added method. * Update greatest_common_divisor.py * Now unnecessary white spaces removed. * Cycle_Detection_Undirected_Graph Cycle_Detection_Undirected_Graph using Disjoint set DataStructure * Update greatest_common_divisor.py again * Again Updated cycle_detection_undirected_graph.py * Delete cycle_detection_undirected_graph.py * Add doctests and format the code with psf/black * fixup: Typo * Update greatest_common_divisor.py * greatest_common_divisor() --- maths/greatest_common_divisor.py | 38 ++++++++++++++++++++++++++------ 1 file changed, 31 insertions(+), 7 deletions(-) diff --git a/maths/greatest_common_divisor.py b/maths/greatest_common_divisor.py index ebc08f37ffa6..a1b915bc3cfb 100644 --- a/maths/greatest_common_divisor.py +++ b/maths/greatest_common_divisor.py @@ -5,20 +5,44 @@ """ -def gcd(a, b): - """Calculate Greatest Common Divisor (GCD).""" - return b if a == 0 else gcd(b % a, a) +def greatest_common_divisor(a, b): + """ + Calculate Greatest Common Divisor (GCD). + >>> greatest_common_divisor(24, 40) + 8 + """ + return b if a == 0 else greatest_common_divisor(b % a, a) + + +""" +Below method is more memory efficient because it does not use the stack (chunk of memory). +While above method is good, uses more memory for huge numbers because of the recursive calls +required to calculate the greatest common divisor. +""" + + +def gcd_by_iterative(x, y): + """ + >>> gcd_by_iterative(24, 40) + 8 + >>> greatest_common_divisor(24, 40) == gcd_by_iterative(24, 40) + True + """ + while y: # --> when y=0 then loop will terminate and return x as final GCD. + x, y = y, x % y + return x def main(): - """Call GCD Function.""" + """Call Greatest Common Divisor function.""" try: - nums = input("Enter two Integers separated by comma (,): ").split(",") + nums = input("Enter two integers separated by comma (,): ").split(",") num_1 = int(nums[0]) num_2 = int(nums[1]) - print(f"gcd({num_1}, {num_2}) = {gcd(num_1, num_2)}") + print(f"greatest_common_divisor({num_1}, {num_2}) = {greatest_common_divisor(num_1, num_2)}") + print(f"By iterative gcd({num_1}, {num_2}) = {gcd_by_iterative(num_1, num_2)}") except (IndexError, UnboundLocalError, ValueError): - print("Wrong Input") + print("Wrong input") if __name__ == "__main__": From 906c985de3b0156ac50a1bd10d0c803f3439cf4e Mon Sep 17 00:00:00 2001 From: Daniel Melo <38673841+danielx285@users.noreply.github.com> Date: Tue, 22 Oct 2019 06:37:43 -0300 Subject: [PATCH 0297/1071] Create dinic.py (#1396) * Create dinic.py Dinic's algorithm for maximum flow * Update dinic.py Changes made. --- graphs/dinic.py | 93 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 93 insertions(+) create mode 100644 graphs/dinic.py diff --git a/graphs/dinic.py b/graphs/dinic.py new file mode 100644 index 000000000000..38e91e4940e0 --- /dev/null +++ b/graphs/dinic.py @@ -0,0 +1,93 @@ +INF = float("inf") + +class Dinic: + def __init__(self, n): + self.lvl = [0] * n + self.ptr = [0] * n + self.q = [0] * n + self.adj = [[] for _ in range(n)] + + ''' + Here we will add our edges containing with the following parameters: + vertex closest to source, vertex closest to sink and flow capacity + through that edge ... + ''' + def add_edge(self, a, b, c, rcap=0): + self.adj[a].append([b, len(self.adj[b]), c, 0]) + self.adj[b].append([a, len(self.adj[a]) - 1, rcap, 0]) + + #This is a sample depth first search to be used at max_flow + def depth_first_search(self, vertex, sink, flow): + if vertex == sink or not flow: + return flow + + for i in range(self.ptr[vertex], len(self.adj[vertex])): + e = self.adj[vertex][i] + if self.lvl[e[0]] == self.lvl[vertex] + 1: + p = self.depth_first_search(e[0], sink, min(flow, e[2] - e[3])) + if p: + self.adj[vertex][i][3] += p + self.adj[e[0]][e[1]][3] -= p + return p + self.ptr[vertex] = self.ptr[vertex] + 1 + return 0 + + #Here we calculate the flow that reaches the sink + def max_flow(self, source, sink): + flow, self.q[0] = 0, source + for l in range(31): # l = 30 maybe faster for random data + while True: + self.lvl, self.ptr = [0] * len(self.q), [0] * len(self.q) + qi, qe, self.lvl[source] = 0, 1, 1 + while qi < qe and not self.lvl[sink]: + v = self.q[qi] + qi += 1 + for e in self.adj[v]: + if not self.lvl[e[0]] and (e[2] - e[3]) >> (30 - l): + self.q[qe] = e[0] + qe += 1 + self.lvl[e[0]] = self.lvl[v] + 1 + + p = self.depth_first_search(source, sink, INF) + while p: + flow += p + p = self.depth_first_search(source, sink, INF) + + if not self.lvl[sink]: + break + + return flow + +#Example to use + +''' +Will be a bipartite graph, than it has the vertices near the source(4) +and the vertices near the sink(4) +''' +#Here we make a graphs with 10 vertex(source and sink includes) +graph = Dinic(10) +source = 0 +sink = 9 +''' +Now we add the vertices next to the font in the font with 1 capacity in this edge +(source -> source vertices) +''' +for vertex in range(1, 5): + graph.add_edge(source, vertex, 1) +''' +We will do the same thing for the vertices near the sink, but from vertex to sink +(sink vertices -> sink) +''' +for vertex in range(5, 9): + graph.add_edge(vertex, sink, 1) +''' +Finally we add the verices near the sink to the vertices near the source. +(source vertices -> sink vertices) +''' +for vertex in range(1, 5): + graph.add_edge(vertex, vertex+4, 1) + +#Now we can know that is the maximum flow(source -> sink) +print(graph.max_flow(source, sink)) + + From 11e2207182829ee6b1d13c0aec326d5a123d2c9b Mon Sep 17 00:00:00 2001 From: Ankur Chattopadhyay <39518771+chttrjeankr@users.noreply.github.com> Date: Tue, 22 Oct 2019 21:32:03 +0530 Subject: [PATCH 0298/1071] Project Euler problems 06, 20 (#1419) * added sol3.py for problem_20 * added sol4.py for problem_06 --- project_euler/problem_06/sol4.py | 40 ++++++++++++++++++++++++++++++++ project_euler/problem_20/sol3.py | 39 +++++++++++++++++++++++++++++++ 2 files changed, 79 insertions(+) create mode 100644 project_euler/problem_06/sol4.py create mode 100644 project_euler/problem_20/sol3.py diff --git a/project_euler/problem_06/sol4.py b/project_euler/problem_06/sol4.py new file mode 100644 index 000000000000..1e1de5570e7d --- /dev/null +++ b/project_euler/problem_06/sol4.py @@ -0,0 +1,40 @@ +# -*- coding: utf-8 -*- +""" +Problem: + +The sum of the squares of the first ten natural numbers is, + 1^2 + 2^2 + ... + 10^2 = 385 + +The square of the sum of the first ten natural numbers is, + (1 + 2 + ... + 10)^2 = 552 = 3025 + +Hence the difference between the sum of the squares of the first ten natural +numbers and the square of the sum is 3025 − 385 = 2640. + +Find the difference between the sum of the squares of the first N natural +numbers and the square of the sum. +""" + + +def solution(n): + """Returns the difference between the sum of the squares of the first n + natural numbers and the square of the sum. + + >>> solution(10) + 2640 + >>> solution(15) + 13160 + >>> solution(20) + 41230 + >>> solution(50) + 1582700 + >>> solution(100) + 25164150 + """ + sum_of_squares = n * (n + 1) * (2 * n + 1) / 6 + square_of_sum = (n * (n + 1) / 2) ** 2 + return int(square_of_sum - sum_of_squares) + + +if __name__ == "__main__": + print(solution(int(input("Enter a number: ").strip()))) diff --git a/project_euler/problem_20/sol3.py b/project_euler/problem_20/sol3.py new file mode 100644 index 000000000000..13f9d7831c47 --- /dev/null +++ b/project_euler/problem_20/sol3.py @@ -0,0 +1,39 @@ +""" +n! means n × (n − 1) × ... × 3 × 2 × 1 + +For example, 10! = 10 × 9 × ... × 3 × 2 × 1 = 3628800, +and the sum of the digits in the number 10! is 3 + 6 + 2 + 8 + 8 + 0 + 0 = 27. + +Find the sum of the digits in the number 100! +""" +from math import factorial + + +def solution(n): + """Returns the sum of the digits in the number 100! + >>> solution(1000) + 10539 + >>> solution(200) + 1404 + >>> solution(100) + 648 + >>> solution(50) + 216 + >>> solution(10) + 27 + >>> solution(5) + 3 + >>> solution(3) + 6 + >>> solution(2) + 2 + >>> solution(1) + 1 + >>> solution(0) + 1 + """ + return sum(map(int, str(factorial(n)))) + + +if __name__ == "__main__": + print(solution(int(input("Enter the Number: ").strip()))) From 7592cba417b1766b8c7c5721ffe916f5c14cdc60 Mon Sep 17 00:00:00 2001 From: Ankur Chattopadhyay <39518771+chttrjeankr@users.noreply.github.com> Date: Tue, 22 Oct 2019 22:43:48 +0530 Subject: [PATCH 0299/1071] psf/black code formatting (#1421) * added sol3.py for problem_20 * added sol4.py for problem_06 * ran `black .` on `\Python` --- ciphers/base64_cipher.py | 2 +- ciphers/caesar_cipher.py | 12 +- data_structures/binary_tree/treap.py | 26 ++-- divide_and_conquer/mergesort.py | 67 ++++++----- dynamic_programming/abbreviation.py | 1 + dynamic_programming/fibonacci.py | 2 +- dynamic_programming/fractional_knapsack.py | 1 + .../longest_common_subsequence.py | 2 +- dynamic_programming/sum_of_subset.py | 1 + fuzzy_logic/fuzzy_operations.py | 80 +++++++------ graphs/dinic.py | 43 +++---- machine_learning/decision_tree.py | 8 +- machine_learning/polymonial_regression.py | 22 ++-- maths/3n+1.py | 113 +++++++++++++++++- maths/explicit_euler.py | 5 +- maths/factorial_python.py | 2 +- maths/greatest_common_divisor.py | 4 +- maths/karatsuba.py | 9 +- maths/prime_sieve_eratosthenes.py | 19 +-- maths/qr_decomposition.py | 12 +- maths/runge_kutta.py | 8 +- other/activity_selection.py | 46 +++---- other/magicdiamondpattern.py | 67 ++++++----- other/password_generator.py | 42 ++++--- project_euler/problem_02/sol5.py | 6 +- searches/fibonacci_search.py | 30 +++-- sorts/bubble_sort.py | 3 +- sorts/double_sort.py | 36 +++--- 28 files changed, 415 insertions(+), 254 deletions(-) diff --git a/ciphers/base64_cipher.py b/ciphers/base64_cipher.py index eea065b94ee0..f95403c7b426 100644 --- a/ciphers/base64_cipher.py +++ b/ciphers/base64_cipher.py @@ -18,7 +18,7 @@ def encode_base64(text): i = 0 while i < len(s): if i > 0 and ((i / 3 * 4) % 76) == 0: - r = r + "\r\n" # for unix newline, put "\n" + r = r + "\r\n" # for unix newline, put "\n" n = (s[i] << 16) + (s[i + 1] << 8) + s[i + 2] diff --git a/ciphers/caesar_cipher.py b/ciphers/caesar_cipher.py index 52155bbdc49e..200f868051d4 100644 --- a/ciphers/caesar_cipher.py +++ b/ciphers/caesar_cipher.py @@ -1,5 +1,5 @@ def encrypt(input_string: str, key: int) -> str: - result = '' + result = "" for x in input_string: if not x.isalpha(): result += x @@ -11,7 +11,7 @@ def encrypt(input_string: str, key: int) -> str: def decrypt(input_string: str, key: int) -> str: - result = '' + result = "" for x in input_string: if not x.isalpha(): result += x @@ -24,15 +24,15 @@ def decrypt(input_string: str, key: int) -> str: def brute_force(input_string: str) -> None: key = 1 - result = '' + result = "" while key <= 94: for x in input_string: indx = (ord(x) - key) % 256 if indx < 32: indx = indx + 95 result = result + chr(indx) - print(f'Key: {key}\t| Message: {result}') - result = '' + print(f"Key: {key}\t| Message: {result}") + result = "" key += 1 return None @@ -40,7 +40,7 @@ def brute_force(input_string: str) -> None: def main(): while True: print(f'{"-" * 10}\n Menu\n{"-", * 10}') - print(*["1.Encrpyt", "2.Decrypt", "3.BruteForce", "4.Quit"], sep='\n') + print(*["1.Encrpyt", "2.Decrypt", "3.BruteForce", "4.Quit"], sep="\n") choice = input("What would you like to do?: ") if choice not in ["1", "2", "3", "4"]: print("Invalid choice, please enter a valid choice") diff --git a/data_structures/binary_tree/treap.py b/data_structures/binary_tree/treap.py index 0b5947f4cc04..a6ff3c9d798b 100644 --- a/data_structures/binary_tree/treap.py +++ b/data_structures/binary_tree/treap.py @@ -7,6 +7,7 @@ class Node(object): Treap's node Treap is a binary tree by value and heap by priority """ + def __init__(self, value: int = None): self.value = value self.prior = random() @@ -20,10 +21,7 @@ def __repr__(self): return "'%s: %.5s'" % (self.value, self.prior) else: return pformat( - { - "%s: %.5s" - % (self.value, self.prior): (self.left, self.right) - }, + {"%s: %.5s" % (self.value, self.prior): (self.left, self.right)}, indent=1, ) @@ -33,6 +31,7 @@ def __str__(self): right = str(self.right or "") return value + left + right + def split(root: Node, value: int) -> Tuple[Node, Node]: """ We split current tree into 2 trees with value: @@ -61,12 +60,13 @@ def split(root: Node, value: int) -> Tuple[Node, Node]: root.right, right = split(root.right, value) return (root, right) + def merge(left: Node, right: Node) -> Node: """ We merge 2 trees into one. Note: all left tree's values must be less than all right tree's """ - if (not left) or (not right): # If one node is None, return the other + if (not left) or (not right): # If one node is None, return the other return left or right elif left.prior < right.prior: """ @@ -82,6 +82,7 @@ def merge(left: Node, right: Node) -> Node: right.left = merge(left, right.left) return right + def insert(root: Node, value: int) -> Node: """ Insert element @@ -94,6 +95,7 @@ def insert(root: Node, value: int) -> Node: left, right = split(root, value) return merge(merge(left, node), right) + def erase(root: Node, value: int) -> Node: """ Erase element @@ -102,15 +104,16 @@ def erase(root: Node, value: int) -> Node: Split all nodes with values greater into right. Merge left, right """ - left, right = split(root, value-1) + left, right = split(root, value - 1) _, right = split(right, value) return merge(left, right) + def inorder(root: Node): """ Just recursive print of a tree """ - if not root: # None + if not root: # None return else: inorder(root.left) @@ -154,13 +157,16 @@ def interactTreap(root, args): return root + def main(): """After each command, program prints treap""" root = None - print("enter numbers to creat a tree, + value to add value into treap, - value to erase all nodes with value. 'q' to quit. ") + print( + "enter numbers to creat a tree, + value to add value into treap, - value to erase all nodes with value. 'q' to quit. " + ) args = input() - while args != 'q': + while args != "q": root = interactTreap(root, args) print(root) args = input() @@ -168,7 +174,9 @@ def main(): print("good by!") pass + if __name__ == "__main__": import doctest + doctest.testmod() main() diff --git a/divide_and_conquer/mergesort.py b/divide_and_conquer/mergesort.py index b2a5a4c321ae..d6693eb36a0a 100644 --- a/divide_and_conquer/mergesort.py +++ b/divide_and_conquer/mergesort.py @@ -1,45 +1,48 @@ -def merge(a,b,m,e): - l=a[b:m+1] - r=a[m+1:e+1] - k=b - i=0 - j=0 - while i>> mergesort([3,2,1],0,2) [1, 2, 3] >>> mergesort([3,2,1,0,1,2,3,5,4],0,8) [0, 1, 1, 2, 2, 3, 3, 4, 5] """ - if b capitalize a and c(dABCd) -> remove d (ABC) """ + def abbr(a, b): """ >>> abbr("daBcd", "ABC") diff --git a/dynamic_programming/fibonacci.py b/dynamic_programming/fibonacci.py index 125686416603..923560b54d30 100644 --- a/dynamic_programming/fibonacci.py +++ b/dynamic_programming/fibonacci.py @@ -58,7 +58,7 @@ def get(self, sequence_no=None): print("\nInvalid input, please try again.") except NameError: print("\n********* Invalid input, good bye!! ************\n") - + import doctest doctest.testmod() diff --git a/dynamic_programming/fractional_knapsack.py b/dynamic_programming/fractional_knapsack.py index 728cdeb009ac..15210146bf66 100644 --- a/dynamic_programming/fractional_knapsack.py +++ b/dynamic_programming/fractional_knapsack.py @@ -20,6 +20,7 @@ def fracKnapsack(vl, wt, W, n): else sum(vl[:k]) ) + if __name__ == "__main__": import doctest diff --git a/dynamic_programming/longest_common_subsequence.py b/dynamic_programming/longest_common_subsequence.py index 4bb1db044d3b..a7206b221d96 100644 --- a/dynamic_programming/longest_common_subsequence.py +++ b/dynamic_programming/longest_common_subsequence.py @@ -76,7 +76,7 @@ def longest_common_subsequence(x: str, y: str): expected_subseq = "GTAB" ln, subseq = longest_common_subsequence(a, b) -## print("len =", ln, ", sub-sequence =", subseq) + ## print("len =", ln, ", sub-sequence =", subseq) import doctest doctest.testmod() diff --git a/dynamic_programming/sum_of_subset.py b/dynamic_programming/sum_of_subset.py index 5c7944d5090e..9394d29dabc0 100644 --- a/dynamic_programming/sum_of_subset.py +++ b/dynamic_programming/sum_of_subset.py @@ -29,6 +29,7 @@ def isSumSubset(arr, arrLen, requiredSum): # print(subset[i]) print(subset[arrLen][requiredSum]) + if __name__ == "__main__": import doctest diff --git a/fuzzy_logic/fuzzy_operations.py b/fuzzy_logic/fuzzy_operations.py index e497eabd1690..ba4a8a22a4d1 100644 --- a/fuzzy_logic/fuzzy_operations.py +++ b/fuzzy_logic/fuzzy_operations.py @@ -8,37 +8,39 @@ """ # Create universe of discourse in python using linspace () import numpy as np + X = np.linspace(start=0, stop=75, num=75, endpoint=True, retstep=False) # Create two fuzzy sets by defining any membership function (trapmf(), gbellmf(),gaussmf(), etc). import skfuzzy as fuzz -abc1=[0,25,50] -abc2=[25,50,75] -young = fuzz.membership.trimf(X,abc1) -middle_aged = fuzz.membership.trimf(X,abc2) + +abc1 = [0, 25, 50] +abc2 = [25, 50, 75] +young = fuzz.membership.trimf(X, abc1) +middle_aged = fuzz.membership.trimf(X, abc2) # Compute the different operations using inbuilt functions. one = np.ones(75) zero = np.zeros((75,)) -#1. Union = max(µA(x), µB(x)) +# 1. Union = max(µA(x), µB(x)) union = fuzz.fuzzy_or(X, young, X, middle_aged)[1] -#2. Intersection = min(µA(x), µB(x)) +# 2. Intersection = min(µA(x), µB(x)) intersection = fuzz.fuzzy_and(X, young, X, middle_aged)[1] -#3. Complement (A) = (1- min(µA(x)) +# 3. Complement (A) = (1- min(µA(x)) complement_a = fuzz.fuzzy_not(young) -#4. Difference (A/B) = min(µA(x),(1- µB(x))) +# 4. Difference (A/B) = min(µA(x),(1- µB(x))) difference = fuzz.fuzzy_and(X, young, X, fuzz.fuzzy_not(middle_aged)[1])[1] -#5. Algebraic Sum = [µA(x) + µB(x) – (µA(x) * µB(x))] -alg_sum = young + middle_aged - (young*middle_aged) -#6. Algebraic Product = (µA(x) * µB(x)) -alg_product = young*middle_aged -#7. Bounded Sum = min[1,(µA(x), µB(x))] -bdd_sum = fuzz.fuzzy_and(X, one, X, young+middle_aged)[1] -#8. Bounded difference = min[0,(µA(x), µB(x))] -bdd_difference = fuzz.fuzzy_or(X, zero, X, young-middle_aged)[1] +# 5. Algebraic Sum = [µA(x) + µB(x) – (µA(x) * µB(x))] +alg_sum = young + middle_aged - (young * middle_aged) +# 6. Algebraic Product = (µA(x) * µB(x)) +alg_product = young * middle_aged +# 7. Bounded Sum = min[1,(µA(x), µB(x))] +bdd_sum = fuzz.fuzzy_and(X, one, X, young + middle_aged)[1] +# 8. Bounded difference = min[0,(µA(x), µB(x))] +bdd_difference = fuzz.fuzzy_or(X, zero, X, young - middle_aged)[1] -#max-min composition -#max-product composition +# max-min composition +# max-product composition # Plot each set A, set B and each operation result using plot() and subplot(). @@ -46,55 +48,55 @@ plt.figure() -plt.subplot(4,3,1) -plt.plot(X,young) +plt.subplot(4, 3, 1) +plt.plot(X, young) plt.title("Young") plt.grid(True) -plt.subplot(4,3,2) -plt.plot(X,middle_aged) +plt.subplot(4, 3, 2) +plt.plot(X, middle_aged) plt.title("Middle aged") plt.grid(True) -plt.subplot(4,3,3) -plt.plot(X,union) +plt.subplot(4, 3, 3) +plt.plot(X, union) plt.title("union") plt.grid(True) -plt.subplot(4,3,4) -plt.plot(X,intersection) +plt.subplot(4, 3, 4) +plt.plot(X, intersection) plt.title("intersection") plt.grid(True) -plt.subplot(4,3,5) -plt.plot(X,complement_a) +plt.subplot(4, 3, 5) +plt.plot(X, complement_a) plt.title("complement_a") plt.grid(True) -plt.subplot(4,3,6) -plt.plot(X,difference) +plt.subplot(4, 3, 6) +plt.plot(X, difference) plt.title("difference a/b") plt.grid(True) -plt.subplot(4,3,7) -plt.plot(X,alg_sum) +plt.subplot(4, 3, 7) +plt.plot(X, alg_sum) plt.title("alg_sum") plt.grid(True) -plt.subplot(4,3,8) -plt.plot(X,alg_product) +plt.subplot(4, 3, 8) +plt.plot(X, alg_product) plt.title("alg_product") plt.grid(True) -plt.subplot(4,3,9) -plt.plot(X,bdd_sum) +plt.subplot(4, 3, 9) +plt.plot(X, bdd_sum) plt.title("bdd_sum") plt.grid(True) -plt.subplot(4,3,10) -plt.plot(X,bdd_difference) +plt.subplot(4, 3, 10) +plt.plot(X, bdd_difference) plt.title("bdd_difference") plt.grid(True) -plt.subplots_adjust(hspace = 0.5) +plt.subplots_adjust(hspace=0.5) plt.show() diff --git a/graphs/dinic.py b/graphs/dinic.py index 38e91e4940e0..4f5e81236984 100644 --- a/graphs/dinic.py +++ b/graphs/dinic.py @@ -1,5 +1,6 @@ INF = float("inf") + class Dinic: def __init__(self, n): self.lvl = [0] * n @@ -7,16 +8,17 @@ def __init__(self, n): self.q = [0] * n self.adj = [[] for _ in range(n)] - ''' + """ Here we will add our edges containing with the following parameters: vertex closest to source, vertex closest to sink and flow capacity through that edge ... - ''' + """ + def add_edge(self, a, b, c, rcap=0): self.adj[a].append([b, len(self.adj[b]), c, 0]) self.adj[b].append([a, len(self.adj[a]) - 1, rcap, 0]) - #This is a sample depth first search to be used at max_flow + # This is a sample depth first search to be used at max_flow def depth_first_search(self, vertex, sink, flow): if vertex == sink or not flow: return flow @@ -31,8 +33,8 @@ def depth_first_search(self, vertex, sink, flow): return p self.ptr[vertex] = self.ptr[vertex] + 1 return 0 - - #Here we calculate the flow that reaches the sink + + # Here we calculate the flow that reaches the sink def max_flow(self, source, sink): flow, self.q[0] = 0, source for l in range(31): # l = 30 maybe faster for random data @@ -58,36 +60,35 @@ def max_flow(self, source, sink): return flow -#Example to use -''' +# Example to use + +""" Will be a bipartite graph, than it has the vertices near the source(4) and the vertices near the sink(4) -''' -#Here we make a graphs with 10 vertex(source and sink includes) +""" +# Here we make a graphs with 10 vertex(source and sink includes) graph = Dinic(10) source = 0 sink = 9 -''' +""" Now we add the vertices next to the font in the font with 1 capacity in this edge (source -> source vertices) -''' +""" for vertex in range(1, 5): - graph.add_edge(source, vertex, 1) -''' + graph.add_edge(source, vertex, 1) +""" We will do the same thing for the vertices near the sink, but from vertex to sink (sink vertices -> sink) -''' +""" for vertex in range(5, 9): - graph.add_edge(vertex, sink, 1) -''' + graph.add_edge(vertex, sink, 1) +""" Finally we add the verices near the sink to the vertices near the source. (source vertices -> sink vertices) -''' +""" for vertex in range(1, 5): - graph.add_edge(vertex, vertex+4, 1) + graph.add_edge(vertex, vertex + 4, 1) -#Now we can know that is the maximum flow(source -> sink) +# Now we can know that is the maximum flow(source -> sink) print(graph.max_flow(source, sink)) - - diff --git a/machine_learning/decision_tree.py b/machine_learning/decision_tree.py index 14c02b64df0c..6b121c73f3b4 100644 --- a/machine_learning/decision_tree.py +++ b/machine_learning/decision_tree.py @@ -125,6 +125,7 @@ def predict(self, x): print("Error: Decision tree not yet trained") return None + class Test_Decision_Tree: """Decision Tres test class """ @@ -139,12 +140,9 @@ def helper_mean_squared_error_test(labels, prediction): """ squared_error_sum = np.float(0) for label in labels: - squared_error_sum += ((label-prediction) ** 2) + squared_error_sum += (label - prediction) ** 2 - return np.float(squared_error_sum/labels.size) - - - + return np.float(squared_error_sum / labels.size) def main(): diff --git a/machine_learning/polymonial_regression.py b/machine_learning/polymonial_regression.py index 03f5f0a9713d..0d9db0f7578a 100644 --- a/machine_learning/polymonial_regression.py +++ b/machine_learning/polymonial_regression.py @@ -2,19 +2,23 @@ import pandas as pd # Importing the dataset -dataset = pd.read_csv('https://s3.us-west-2.amazonaws.com/public.gamelab.fun/dataset/position_salaries.csv') +dataset = pd.read_csv( + "https://s3.us-west-2.amazonaws.com/public.gamelab.fun/dataset/position_salaries.csv" +) X = dataset.iloc[:, 1:2].values y = dataset.iloc[:, 2].values # Splitting the dataset into the Training set and Test set -from sklearn.model_selection import train_test_split +from sklearn.model_selection import train_test_split + X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=0) # Fitting Polynomial Regression to the dataset from sklearn.preprocessing import PolynomialFeatures from sklearn.linear_model import LinearRegression + poly_reg = PolynomialFeatures(degree=4) X_poly = poly_reg.fit_transform(X) pol_reg = LinearRegression() @@ -23,15 +27,17 @@ # Visualizing the Polymonial Regression results def viz_polymonial(): - plt.scatter(X, y, color='red') - plt.plot(X, pol_reg.predict(poly_reg.fit_transform(X)), color='blue') - plt.title('Truth or Bluff (Linear Regression)') - plt.xlabel('Position level') - plt.ylabel('Salary') + plt.scatter(X, y, color="red") + plt.plot(X, pol_reg.predict(poly_reg.fit_transform(X)), color="blue") + plt.title("Truth or Bluff (Linear Regression)") + plt.xlabel("Position level") + plt.ylabel("Salary") plt.show() return + + viz_polymonial() # Predicting a new result with Polymonial Regression pol_reg.predict(poly_reg.fit_transform([[5.5]])) -#output should be 132148.43750003 +# output should be 132148.43750003 diff --git a/maths/3n+1.py b/maths/3n+1.py index ff769550297c..f6fe77b2b3fe 100644 --- a/maths/3n+1.py +++ b/maths/3n+1.py @@ -29,7 +29,118 @@ def test_n31(): """ assert n31(4) == ([4, 2, 1], 3) assert n31(11) == ([11, 34, 17, 52, 26, 13, 40, 20, 10, 5, 16, 8, 4, 2, 1], 15) - assert n31(31) == ([31, 94, 47, 142, 71, 214, 107, 322, 161, 484, 242, 121, 364, 182, 91, 274, 137, 412, 206, 103, 310, 155, 466, 233, 700, 350, 175, 526, 263, 790, 395, 1186, 593, 1780, 890, 445, 1336, 668, 334, 167, 502, 251, 754, 377, 1132, 566, 283, 850, 425, 1276, 638, 319, 958, 479, 1438, 719, 2158, 1079, 3238, 1619, 4858, 2429, 7288, 3644, 1822, 911, 2734, 1367, 4102, 2051, 6154, 3077, 9232, 4616, 2308, 1154, 577, 1732, 866, 433, 1300, 650, 325, 976, 488, 244, 122, 61, 184, 92, 46, 23, 70, 35, 106, 53, 160, 80, 40, 20, 10, 5, 16, 8, 4, 2, 1], 107) + assert n31(31) == ( + [ + 31, + 94, + 47, + 142, + 71, + 214, + 107, + 322, + 161, + 484, + 242, + 121, + 364, + 182, + 91, + 274, + 137, + 412, + 206, + 103, + 310, + 155, + 466, + 233, + 700, + 350, + 175, + 526, + 263, + 790, + 395, + 1186, + 593, + 1780, + 890, + 445, + 1336, + 668, + 334, + 167, + 502, + 251, + 754, + 377, + 1132, + 566, + 283, + 850, + 425, + 1276, + 638, + 319, + 958, + 479, + 1438, + 719, + 2158, + 1079, + 3238, + 1619, + 4858, + 2429, + 7288, + 3644, + 1822, + 911, + 2734, + 1367, + 4102, + 2051, + 6154, + 3077, + 9232, + 4616, + 2308, + 1154, + 577, + 1732, + 866, + 433, + 1300, + 650, + 325, + 976, + 488, + 244, + 122, + 61, + 184, + 92, + 46, + 23, + 70, + 35, + 106, + 53, + 160, + 80, + 40, + 20, + 10, + 5, + 16, + 8, + 4, + 2, + 1, + ], + 107, + ) if __name__ == "__main__": diff --git a/maths/explicit_euler.py b/maths/explicit_euler.py index 9fce4e4185a6..8a43d71fb432 100644 --- a/maths/explicit_euler.py +++ b/maths/explicit_euler.py @@ -22,13 +22,13 @@ def explicit_euler(ode_func, y0, x0, stepsize, x_end): >>> y[-1] 144.77277243257308 """ - N = int(np.ceil((x_end - x0)/stepsize)) + N = int(np.ceil((x_end - x0) / stepsize)) y = np.zeros((N + 1,)) y[0] = y0 x = x0 for k in range(N): - y[k + 1] = y[k] + stepsize*ode_func(x, y[k]) + y[k + 1] = y[k] + stepsize * ode_func(x, y[k]) x += stepsize return y @@ -38,4 +38,3 @@ def explicit_euler(ode_func, y0, x0, stepsize, x_end): import doctest doctest.testmod() - diff --git a/maths/factorial_python.py b/maths/factorial_python.py index 10083af0bef2..ab97cd41e681 100644 --- a/maths/factorial_python.py +++ b/maths/factorial_python.py @@ -11,7 +11,7 @@ def factorial(input_number: int) -> int: """ if input_number < 0: - raise ValueError('Input input_number should be non-negative') + raise ValueError("Input input_number should be non-negative") elif input_number == 0: return 1 else: diff --git a/maths/greatest_common_divisor.py b/maths/greatest_common_divisor.py index a1b915bc3cfb..07dddab9aeff 100644 --- a/maths/greatest_common_divisor.py +++ b/maths/greatest_common_divisor.py @@ -39,7 +39,9 @@ def main(): nums = input("Enter two integers separated by comma (,): ").split(",") num_1 = int(nums[0]) num_2 = int(nums[1]) - print(f"greatest_common_divisor({num_1}, {num_2}) = {greatest_common_divisor(num_1, num_2)}") + print( + f"greatest_common_divisor({num_1}, {num_2}) = {greatest_common_divisor(num_1, num_2)}" + ) print(f"By iterative gcd({num_1}, {num_2}) = {gcd_by_iterative(num_1, num_2)}") except (IndexError, UnboundLocalError, ValueError): print("Wrong input") diff --git a/maths/karatsuba.py b/maths/karatsuba.py index be4630184933..df29c77a5cf2 100644 --- a/maths/karatsuba.py +++ b/maths/karatsuba.py @@ -1,5 +1,6 @@ """ Multiply two numbers using Karatsuba algorithm """ + def karatsuba(a, b): """ >>> karatsuba(15463, 23489) == 15463 * 23489 @@ -8,19 +9,19 @@ def karatsuba(a, b): True """ if len(str(a)) == 1 or len(str(b)) == 1: - return (a * b) + return a * b else: m1 = max(len(str(a)), len(str(b))) m2 = m1 // 2 - a1, a2 = divmod(a, 10**m2) - b1, b2 = divmod(b, 10**m2) + a1, a2 = divmod(a, 10 ** m2) + b1, b2 = divmod(b, 10 ** m2) x = karatsuba(a2, b2) y = karatsuba((a1 + a2), (b1 + b2)) z = karatsuba(a1, b1) - return ((z * 10**(2*m2)) + ((y - z - x) * 10**(m2)) + (x)) + return (z * 10 ** (2 * m2)) + ((y - z - x) * 10 ** (m2)) + (x) def main(): diff --git a/maths/prime_sieve_eratosthenes.py b/maths/prime_sieve_eratosthenes.py index 7d039aaadd7d..4fa19d6db220 100644 --- a/maths/prime_sieve_eratosthenes.py +++ b/maths/prime_sieve_eratosthenes.py @@ -1,4 +1,4 @@ -''' +""" Sieve of Eratosthenes Input : n =10 @@ -9,7 +9,8 @@ you can read in detail about this at https://en.wikipedia.org/wiki/Sieve_of_Eratosthenes -''' +""" + def prime_sieve_eratosthenes(num): """ @@ -20,22 +21,22 @@ def prime_sieve_eratosthenes(num): >>> prime_sieve_eratosthenes(20) 2 3 5 7 11 13 17 19 """ - - + primes = [True for i in range(num + 1)] p = 2 - + while p * p <= num: if primes[p] == True: - for i in range(p*p, num+1, p): + for i in range(p * p, num + 1, p): primes[i] = False - p+=1 + p += 1 - for prime in range(2, num+1): + for prime in range(2, num + 1): if primes[prime]: print(prime, end=" ") + if __name__ == "__main__": num = int(input()) - + prime_sieve_eratosthenes(num) diff --git a/maths/qr_decomposition.py b/maths/qr_decomposition.py index 197211f1e694..5e15fede4f2a 100644 --- a/maths/qr_decomposition.py +++ b/maths/qr_decomposition.py @@ -51,21 +51,21 @@ def qr_householder(A): # determine scaling factor alpha = np.linalg.norm(x) # construct vector v for Householder reflection - v = x + np.sign(x[0])*alpha*e1 + v = x + np.sign(x[0]) * alpha * e1 v /= np.linalg.norm(v) # construct the Householder matrix - Q_k = np.eye(m - k) - 2.0*v@v.T + Q_k = np.eye(m - k) - 2.0 * v @ v.T # pad with ones and zeros as necessary - Q_k = np.block([[np.eye(k), np.zeros((k, m - k))], - [np.zeros((m - k, k)), Q_k ]]) + Q_k = np.block([[np.eye(k), np.zeros((k, m - k))], [np.zeros((m - k, k)), Q_k]]) - Q = Q@Q_k.T - R = Q_k@R + Q = Q @ Q_k.T + R = Q_k @ R return Q, R if __name__ == "__main__": import doctest + doctest.testmod() diff --git a/maths/runge_kutta.py b/maths/runge_kutta.py index b0ba9025883f..383797daa5ac 100644 --- a/maths/runge_kutta.py +++ b/maths/runge_kutta.py @@ -22,17 +22,17 @@ def runge_kutta(f, y0, x0, h, x_end): >>> y[-1] 148.41315904125113 """ - N = int(np.ceil((x_end - x0)/h)) + N = int(np.ceil((x_end - x0) / h)) y = np.zeros((N + 1,)) y[0] = y0 x = x0 for k in range(N): k1 = f(x, y[k]) - k2 = f(x + 0.5*h, y[k] + 0.5*h*k1) - k3 = f(x + 0.5*h, y[k] + 0.5*h*k2) + k2 = f(x + 0.5 * h, y[k] + 0.5 * h * k1) + k3 = f(x + 0.5 * h, y[k] + 0.5 * h * k2) k4 = f(x + h, y[k] + h * k3) - y[k + 1] = y[k] + (1/6)*h*(k1 + 2*k2 + 2*k3 + k4) + y[k + 1] = y[k] + (1 / 6) * h * (k1 + 2 * k2 + 2 * k3 + k4) x += h return y diff --git a/other/activity_selection.py b/other/activity_selection.py index 5c14df7d6aa7..4e8e6c78e3f5 100644 --- a/other/activity_selection.py +++ b/other/activity_selection.py @@ -1,12 +1,13 @@ """The following implementation assumes that the activities are already sorted according to their finish time""" - + """Prints a maximum set of activities that can be done by a single person, one at a time""" -# n --> Total number of activities -# start[]--> An array that contains start time of all activities -# finish[] --> An array that contains finish time of all activities - +# n --> Total number of activities +# start[]--> An array that contains start time of all activities +# finish[] --> An array that contains finish time of all activities + + def printMaxActivities(start, finish): """ >>> start = [1, 3, 0, 5, 8, 5] @@ -15,27 +16,28 @@ def printMaxActivities(start, finish): The following activities are selected: 0 1 3 4 """ - n = len(finish) + n = len(finish) print("The following activities are selected:") - - # The first activity is always selected + + # The first activity is always selected i = 0 print(i, end=" ") - - # Consider rest of the activities - for j in range(n): - - # If this activity has start time greater than - # or equal to the finish time of previously - # selected activity, then select it - if start[j] >= finish[i]: + + # Consider rest of the activities + for j in range(n): + + # If this activity has start time greater than + # or equal to the finish time of previously + # selected activity, then select it + if start[j] >= finish[i]: print(j, end=" ") - i = j - -# Driver program to test above function -start = [1, 3, 0, 5, 8, 5] -finish = [2, 4, 6, 7, 9, 9] -printMaxActivities(start, finish) + i = j + + +# Driver program to test above function +start = [1, 3, 0, 5, 8, 5] +finish = [2, 4, 6, 7, 9, 9] +printMaxActivities(start, finish) """ The following activities are selected: diff --git a/other/magicdiamondpattern.py b/other/magicdiamondpattern.py index 024fbd0f569a..9b434a7b6e0b 100644 --- a/other/magicdiamondpattern.py +++ b/other/magicdiamondpattern.py @@ -2,52 +2,53 @@ # Function to print upper half of diamond (pyramid) def floyd(n): - ''' + """ Parameters: n : size of pattern - ''' - for i in range(0, n): - for j in range(0, n-i-1): # printing spaces - print(" ", end = "") - for k in range(0, i + 1): # printing stars - print("* ", end = "") - print() + """ + for i in range(0, n): + for j in range(0, n - i - 1): # printing spaces + print(" ", end="") + for k in range(0, i + 1): # printing stars + print("* ", end="") + print() # Function to print lower half of diamond (pyramid) def reverse_floyd(n): - ''' + """ Parameters: n : size of pattern - ''' - for i in range(n, 0, -1): - for j in range(i, 0, -1): # printing stars - print("* ", end = "") - print() - for k in range(n-i+1, 0, -1): # printing spaces - print(" ", end = "") + """ + for i in range(n, 0, -1): + for j in range(i, 0, -1): # printing stars + print("* ", end="") + print() + for k in range(n - i + 1, 0, -1): # printing spaces + print(" ", end="") + # Function to print complete diamond pattern of "*" def pretty_print(n): - ''' + """ Parameters: n : size of pattern - ''' - if n <= 0: - print(" ... .... nothing printing :(") - return - floyd(n) # upper half - reverse_floyd(n) # lower half + """ + if n <= 0: + print(" ... .... nothing printing :(") + return + floyd(n) # upper half + reverse_floyd(n) # lower half if __name__ == "__main__": - print(r"| /\ | |- | |- |--| |\ /| |-") - print(r"|/ \| |- |_ |_ |__| | \/ | |_") - K = 1 - while(K): - user_number = int(input("enter the number and , and see the magic : ")) - print() - pretty_print(user_number) - K = int(input("press 0 to exit... and 1 to continue...")) - - print("Good Bye...") + print(r"| /\ | |- | |- |--| |\ /| |-") + print(r"|/ \| |- |_ |_ |__| | \/ | |_") + K = 1 + while K: + user_number = int(input("enter the number and , and see the magic : ")) + print() + pretty_print(user_number) + K = int(input("press 0 to exit... and 1 to continue...")) + + print("Good Bye...") diff --git a/other/password_generator.py b/other/password_generator.py index b4c7999ca44a..598f8d0eeade 100644 --- a/other/password_generator.py +++ b/other/password_generator.py @@ -27,21 +27,27 @@ def alternative_password_generator(ctbi, i): # Password generator = full boot with random_number, random_letters, and # random_character FUNCTIONS # Put your code here... - i = i - len(ctbi) - quotient = int(i / 3) - remainder = i % 3 - #chars = ctbi + random_letters(ascii_letters, i / 3 + remainder) + random_number(digits, i / 3) + random_characters(punctuation, i / 3) - chars = ctbi + random(ascii_letters, quotient + remainder) + random(digits, quotient) + random(punctuation, quotient) - chars = list(chars) - shuffle(chars) - return "".join(chars) - - - #random is a generalised function for letters, characters and numbers + i = i - len(ctbi) + quotient = int(i / 3) + remainder = i % 3 + # chars = ctbi + random_letters(ascii_letters, i / 3 + remainder) + random_number(digits, i / 3) + random_characters(punctuation, i / 3) + chars = ( + ctbi + + random(ascii_letters, quotient + remainder) + + random(digits, quotient) + + random(punctuation, quotient) + ) + chars = list(chars) + shuffle(chars) + return "".join(chars) + + # random is a generalised function for letters, characters and numbers + + def random(ctbi, i): - return "".join(choice(ctbi) for x in range(i)) - - + return "".join(choice(ctbi) for x in range(i)) + + def random_number(ctbi, i): pass # Put your code here... @@ -56,9 +62,13 @@ def random_characters(ctbi, i): def main(): length = int(input("Please indicate the max length of your password: ").strip()) - ctbi = input("Please indicate the characters that must be in your password: ").strip() + ctbi = input( + "Please indicate the characters that must be in your password: " + ).strip() print("Password generated:", password_generator(length)) - print("Alternative Password generated:", alternative_password_generator(ctbi, length)) + print( + "Alternative Password generated:", alternative_password_generator(ctbi, length) + ) print("[If you are thinking of using this passsword, You better save it.]") diff --git a/project_euler/problem_02/sol5.py b/project_euler/problem_02/sol5.py index 8df2068dd8c3..180906cf8717 100644 --- a/project_euler/problem_02/sol5.py +++ b/project_euler/problem_02/sol5.py @@ -27,11 +27,11 @@ def solution(n): 44 """ - a = [0,1] + a = [0, 1] i = 0 while a[i] <= n: - a.append(a[i] + a[i+1]) - if a[i+2] > n: + a.append(a[i] + a[i + 1]) + if a[i + 2] > n: break i += 1 sum = 0 diff --git a/searches/fibonacci_search.py b/searches/fibonacci_search.py index f76528b9c283..67f2df505d4e 100644 --- a/searches/fibonacci_search.py +++ b/searches/fibonacci_search.py @@ -1,12 +1,14 @@ -#run using python fibonacci_search.py -v +# run using python fibonacci_search.py -v -''' +""" @params arr: input array val: the value to be searched output: the index of element in the array or -1 if not found return 0 if input array is empty -''' +""" + + def fibonacci_search(arr, val): """ @@ -22,29 +24,31 @@ def fibonacci_search(arr, val): fibNext = fib_N_1 + fib_N_2 length = len(arr) if length == 0: - return 0 - while (fibNext < len(arr)): + return 0 + while fibNext < len(arr): fib_N_2 = fib_N_1 fib_N_1 = fibNext fibNext = fib_N_1 + fib_N_2 - index = -1; - while (fibNext > 1): - i = min(index + fib_N_2, (length-1)) - if (arr[i] < val): + index = -1 + while fibNext > 1: + i = min(index + fib_N_2, (length - 1)) + if arr[i] < val: fibNext = fib_N_1 fib_N_1 = fib_N_2 fib_N_2 = fibNext - fib_N_1 index = i - elif (arr[i] > val): + elif arr[i] > val: fibNext = fib_N_2 fib_N_1 = fib_N_1 - fib_N_2 fib_N_2 = fibNext - fib_N_1 - else : + else: return i - if (fib_N_1 and index < length-1) and (arr[index+1] == val): - return index+1; + if (fib_N_1 and index < length - 1) and (arr[index + 1] == val): + return index + 1 return -1 + if __name__ == "__main__": import doctest + doctest.testmod() diff --git a/sorts/bubble_sort.py b/sorts/bubble_sort.py index 4faa40da1d8e..eb356bc7dcad 100644 --- a/sorts/bubble_sort.py +++ b/sorts/bubble_sort.py @@ -29,12 +29,13 @@ def bubble_sort(collection): swapped = True collection[j], collection[j + 1] = collection[j + 1], collection[j] if not swapped: - break # Stop iteration if the collection is sorted. + break # Stop iteration if the collection is sorted. return collection if __name__ == "__main__": import time + user_input = input("Enter numbers separated by a comma:").strip() unsorted = [int(item) for item in user_input.split(",")] start = time.process_time() diff --git a/sorts/double_sort.py b/sorts/double_sort.py index 011e17d8f035..aca4b97ca775 100644 --- a/sorts/double_sort.py +++ b/sorts/double_sort.py @@ -1,4 +1,4 @@ -def double_sort(lst): +def double_sort(lst): """this sorting algorithm sorts an array using the principle of bubble sort , but does it both from left to right and right to left , hence i decided to call it "double sort" @@ -14,21 +14,29 @@ def double_sort(lst): >>> double_sort([-3, 10, 16, -42, 29]) == sorted([-3, 10, 16, -42, 29]) True """ - no_of_elements=len(lst) - for i in range(0,int(((no_of_elements-1)/2)+1)): # we dont need to traverse to end of list as - for j in range(0,no_of_elements-1): - if (lst[j+1] Date: Tue, 22 Oct 2019 16:19:38 -0300 Subject: [PATCH 0300/1071] Add algorithm to rotate images (#1420) * Add algorithm to rotate image * Edit function to be compliant in Black and Flake8 formats * Add type hints in get_rotation() and enumerate() in loop --- digital_image_processing/rotation/__init__.py | 0 digital_image_processing/rotation/rotation.py | 52 +++++++++++++++++++ 2 files changed, 52 insertions(+) create mode 100644 digital_image_processing/rotation/__init__.py create mode 100644 digital_image_processing/rotation/rotation.py diff --git a/digital_image_processing/rotation/__init__.py b/digital_image_processing/rotation/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/digital_image_processing/rotation/rotation.py b/digital_image_processing/rotation/rotation.py new file mode 100644 index 000000000000..37b45ca39897 --- /dev/null +++ b/digital_image_processing/rotation/rotation.py @@ -0,0 +1,52 @@ +from matplotlib import pyplot as plt +import numpy as np +import cv2 + + +def get_rotation( + img: np.array, pt1: np.float32, pt2: np.float32, rows: int, cols: int +) -> np.array: + """ + Get image rotation + :param img: np.array + :param pt1: 3x2 list + :param pt2: 3x2 list + :param rows: columns image shape + :param cols: rows image shape + :return: np.array + """ + matrix = cv2.getAffineTransform(pt1, pt2) + return cv2.warpAffine(img, matrix, (rows, cols)) + + +if __name__ == "__main__": + # read original image + image = cv2.imread("lena.jpg") + # turn image in gray scale value + gray_img = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) + # get image shape + img_rows, img_cols = gray_img.shape + + # set different points to rotate image + pts1 = np.float32([[50, 50], [200, 50], [50, 200]]) + pts2 = np.float32([[10, 100], [200, 50], [100, 250]]) + pts3 = np.float32([[50, 50], [150, 50], [120, 200]]) + pts4 = np.float32([[10, 100], [80, 50], [180, 250]]) + + # add all rotated images in a list + images = [ + gray_img, + get_rotation(gray_img, pts1, pts2, img_rows, img_cols), + get_rotation(gray_img, pts2, pts3, img_rows, img_cols), + get_rotation(gray_img, pts2, pts4, img_rows, img_cols), + ] + + # plot different image rotations + fig = plt.figure(1) + titles = ["Original", "Rotation 1", "Rotation 2", "Rotation 3"] + for i, image in enumerate(images): + plt.subplot(2, 2, i + 1), plt.imshow(image, "gray") + plt.title(titles[i]) + plt.axis("off") + plt.subplots_adjust(left=0.0, bottom=0.05, right=1.0, top=0.95) + plt.show() From 56830255ca30b063a300439d8bc070399bc0ed01 Mon Sep 17 00:00:00 2001 From: John Law Date: Wed, 23 Oct 2019 04:07:50 +0800 Subject: [PATCH 0301/1071] Readability of CONTRIBUTING.md (#1422) * Readability of guidelines * Update README.md --- CONTRIBUTING.md | 69 +++++++++++++++++++++++++++---------------------- README.md | 12 ++++++--- 2 files changed, 47 insertions(+), 34 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 6dd2f6c6ff78..ce2f03886e01 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -30,25 +30,32 @@ Your contribution will be tested by our [automated testing on Travis CI](https:/ We want your work to be readable by others; therefore, we encourage you to note the following: - Please write in Python 3.7+. __print()__ is a function in Python 3 so __print "Hello"__ will _not_ work but __print("Hello")__ will. - - Please focus hard on naming of functions, classes, and variables. Help your reader by using __descriptive names__ that can help you to remove redundant comments. - - Single letter variable names are _old school_ so please avoid them unless their life only spans a few lines. - - Expand acronyms because __gcd()__ is hard to understand but __greatest_common_divisor()__ is not. - - Please follow the [Python Naming Conventions](https://pep8.org/#prescriptive-naming-conventions) so variable_names and function_names should be lower_case, CONSTANTS in UPPERCASE, ClassNames should be CamelCase, etc. + - Single letter variable names are _old school_ so please avoid them unless their life only spans a few lines. + - Expand acronyms because __gcd()__ is hard to understand but __greatest_common_divisor()__ is not. + - Please follow the [Python Naming Conventions](https://pep8.org/#prescriptive-naming-conventions) so variable_names and function_names should be lower_case, CONSTANTS in UPPERCASE, ClassNames should be CamelCase, etc. + + - We encourage the use of Python [f-strings](https://realpython.com/python-f-strings/#f-strings-a-new-and-improved-way-to-format-strings-in-python) where the make the code easier to read. -- Please consider running [__psf/black__](https://github.com/python/black) on your Python file(s) before submitting your pull request. This is not yet a requirement but it does make your code more readable and automatically aligns it with much of [PEP 8](https://www.python.org/dev/peps/pep-0008/). There are other code formatters (autopep8, yapf) but the __black__ style is now the recommendation of the Python Core Team. To use it, - ```bash - pip3 install black # only required the first time - black . - ``` + + +- Please consider running [__psf/black__](https://github.com/python/black) on your Python file(s) before submitting your pull request. This is not yet a requirement but it does make your code more readable and automatically aligns it with much of [PEP 8](https://www.python.org/dev/peps/pep-0008/). There are other code formatters (autopep8, yapf) but the __black__ style is now the recommendation of the Python Core Team. To use it, + + ```bash + pip3 install black # only required the first time + black . + ``` - All submissions will need to pass the test __flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics__ before they will be accepted so if possible, try this test locally on your Python file(s) before submitting your pull request. - ```bash - pip3 install flake8 # only required the first time - flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics - ``` + + ```bash + pip3 install flake8 # only required the first time + flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics + ``` + + - Original code submission require docstrings or comments to describe your work. @@ -93,9 +100,10 @@ We want your work to be readable by others; therefore, we encourage you to note ``` These doctests will be run by pytest as part of our automated testing so please try to run your doctests locally and make sure that they are found and pass: - ```bash - python3 -m doctest -v my_submission.py - ``` + + ```bash + python3 -m doctest -v my_submission.py + ``` The use of the Python builtin __input()__ function is **not** encouraged: @@ -110,44 +118,43 @@ We want your work to be readable by others; therefore, we encourage you to note ```python starting_value = int(input("Please enter a starting value: ").strip()) ``` - + The use of [Python type hints](https://docs.python.org/3/library/typing.html) is encouraged for function parameters and return values. Our automated testing will run [mypy](http://mypy-lang.org) so run that locally before making your submission. -```python -def sumab(a: int, b: int) --> int: + + ```python + def sumab(a: int, b: int) --> int: pass ``` -- [__list comprehensions and generators__](https://docs.python.org/3/tutorial/datastructures.html#list-comprehensions) are preferred over the use of `lambda`, `map`, `filter`, `reduce` but the important thing is to demonstrate the power of Python in code that is easy to read and maintain. -- Avoid importing external libraries for basic algorithms. Only use those libraries for complicated algorithms. +- [__List comprehensions and generators__](https://docs.python.org/3/tutorial/datastructures.html#list-comprehensions) are preferred over the use of `lambda`, `map`, `filter`, `reduce` but the important thing is to demonstrate the power of Python in code that is easy to read and maintain. + + + +- Avoid importing external libraries for basic algorithms. Only use those libraries for complicated algorithms. - If you need a third party module that is not in the file __requirements.txt__, please add it to that file as part of your submission. #### Other Standard While Submitting Your Work - File extension for code should be `.py`. Jupiter notebook files are acceptable in machine learning algorithms. - -- Please avoid creating new directories if at all possible. Try to fit your work into the existing directory structue. - - Strictly use snake_case (underscore_separated) in your file_name, as it will be easy to parse in future using scripts. +- Please avoid creating new directories if at all possible. Try to fit your work into the existing directory structure. +- If possible, follow the standard *within* the folder you are submitting to. - If possible, follow the standard *within* the folder you are submitting to. -- If you have modified/added code work, make sure the code compiles before submitting. +- If you have modified/added code work, make sure the code compiles before submitting. - If you have modified/added documentation work, ensure your language is concise and contains no grammar errors. - - Do not update the README.md or DIRECTORY.md file which will be periodically autogenerated by our Travis CI processes. - - Add a corresponding explanation to [Algorithms-Explanation](https://github.com/TheAlgorithms/Algorithms-Explanation) (Optional but recommended). - - All submissions will be tested with [__mypy__](http://www.mypy-lang.org) so we encourage to add [__Python type hints__](https://docs.python.org/3/library/typing.html) where it makes sense to do so. -- Most importantly, + +- Most importantly, - **Be consistent in the use of these guidelines when submitting.** - **Join** [Gitter](https://gitter.im/TheAlgorithms) **now!** - Happy coding! - Writer [@poyea](https://github.com/poyea), Jun 2019. diff --git a/README.md b/README.md index 8ccb789be7e9..51b2cf8c854c 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,5 @@ # The Algorithms - Python + [![Donate](https://img.shields.io/badge/Donate-PayPal-green.svg?logo=paypal&style=flat-square)](https://www.paypal.me/TheAlgorithms/100)  [![Build Status](https://img.shields.io/travis/TheAlgorithms/Python.svg?label=Travis%20CI&logo=travis&style=flat-square)](https://travis-ci.com/TheAlgorithms/Python)  [![LGTM](https://img.shields.io/lgtm/alerts/github/TheAlgorithms/Python.svg?label=LGTM&logo=LGTM&style=flat-square)](https://lgtm.com/projects/g/TheAlgorithms/Python/alerts)  @@ -6,6 +7,7 @@ [![contributions welcome](https://img.shields.io/static/v1.svg?label=Contributions&message=Welcome&color=0059b3&style=flat-square)](https://github.com/TheAlgorithms/Python/blob/master/CONTRIBUTING.md)  ![](https://img.shields.io/github/repo-size/TheAlgorithms/Python.svg?label=Repo%20size&style=flat-square)  + ### All algorithms implemented in Python (for education) These implementations are for learning purposes. They may be less efficient than the implementations in the Python standard library. @@ -14,12 +16,16 @@ These implementations are for learning purposes. They may be less efficient than Read our [Contribution Guidelines](CONTRIBUTING.md) before you contribute. -[![Open in Gitpod](https://gitpod.io/button/open-in-gitpod.svg?style=flat-square)](https://gitpod.io/#https://github.com/TheAlgorithms/Python) - ## Community Channel We're on [Gitter](https://gitter.im/TheAlgorithms)! Please join us. -## Algorithms +## List of Algorithms See our [directory](DIRECTORY.md). + + + + + +[![Open in Gitpod](https://gitpod.io/button/open-in-gitpod.svg?style=flat-square)](https://gitpod.io/#https://github.com/TheAlgorithms/Python) From d477a4ddf2afa0b954f4d283bd73cdfdc44853d7 Mon Sep 17 00:00:00 2001 From: Ankur Chattopadhyay <39518771+chttrjeankr@users.noreply.github.com> Date: Wed, 23 Oct 2019 23:50:38 +0530 Subject: [PATCH 0302/1071] introduced shuffled_shift_cipher.py in /ciphers (#1424) * introduced shuffled_shift_cipher.py in /ciphers * made requested changes * introduced doctests, type hints removed __make_one_digit() * test_end_to_end() inserted * Make test_end_to_end() a test ;-) --- ciphers/shuffled_shift_cipher.py | 177 +++++++++++++++++++++++++++++++ 1 file changed, 177 insertions(+) create mode 100644 ciphers/shuffled_shift_cipher.py diff --git a/ciphers/shuffled_shift_cipher.py b/ciphers/shuffled_shift_cipher.py new file mode 100644 index 000000000000..bbefe3305fa7 --- /dev/null +++ b/ciphers/shuffled_shift_cipher.py @@ -0,0 +1,177 @@ +import random +import string + + +class ShuffledShiftCipher(object): + """ + This algorithm uses the Caesar Cipher algorithm but removes the option to + use brute force to decrypt the message. + + The passcode is a a random password from the selection buffer of + 1. uppercase letters of the English alphabet + 2. lowercase letters of the English alphabet + 3. digits from 0 to 9 + + Using unique characters from the passcode, the normal list of characters, + that can be allowed in the plaintext, is pivoted and shuffled. Refer to docstring + of __make_key_list() to learn more about the shuffling. + + Then, using the passcode, a number is calculated which is used to encrypt the + plaintext message with the normal shift cipher method, only in this case, the + reference, to look back at while decrypting, is shuffled. + + Each cipher object can possess an optional argument as passcode, without which a + new passcode is generated for that object automatically. + cip1 = ShuffledShiftCipher('d4usr9TWxw9wMD') + cip2 = ShuffledShiftCipher() + """ + + def __init__(self, passcode: str = None): + """ + Initializes a cipher object with a passcode as it's entity + Note: No new passcode is generated if user provides a passcode + while creating the object + """ + self.__passcode = passcode or self.__passcode_creator() + self.__key_list = self.__make_key_list() + self.__shift_key = self.__make_shift_key() + + def __str__(self): + """ + :return: passcode of the cipher object + """ + return "Passcode is: " + "".join(self.__passcode) + + def __neg_pos(self, iterlist: list) -> list: + """ + Mutates the list by changing the sign of each alternate element + + :param iterlist: takes a list iterable + :return: the mutated list + + """ + for i in range(1, len(iterlist), 2): + iterlist[i] *= -1 + return iterlist + + def __passcode_creator(self) -> list: + """ + Creates a random password from the selection buffer of + 1. uppercase letters of the English alphabet + 2. lowercase letters of the English alphabet + 3. digits from 0 to 9 + + :rtype: list + :return: a password of a random length between 10 to 20 + """ + choices = string.ascii_letters + string.digits + password = [random.choice(choices) for i in range(random.randint(10, 20))] + return password + + def __make_key_list(self) -> list: + """ + Shuffles the ordered character choices by pivoting at breakpoints + Breakpoints are the set of characters in the passcode + + eg: + if, ABCDEFGHIJKLMNOPQRSTUVWXYZ are the possible characters + and CAMERA is the passcode + then, breakpoints = [A,C,E,M,R] # sorted set of characters from passcode + shuffled parts: [A,CB,ED,MLKJIHGF,RQPON,ZYXWVUTS] + shuffled __key_list : ACBEDMLKJIHGFRQPONZYXWVUTS + + Shuffling only 26 letters of the english alphabet can generate 26! + combinations for the shuffled list. In the program we consider, a set of + 97 characters (including letters, digits, punctuation and whitespaces), + thereby creating a possibility of 97! combinations (which is a 152 digit number in itself), + thus diminishing the possibility of a brute force approach. Moreover, + shift keys even introduce a multiple of 26 for a brute force approach + for each of the already 97! combinations. + """ + # key_list_options contain nearly all printable except few elements from string.whitespace + key_list_options = ( + string.ascii_letters + string.digits + string.punctuation + " \t\n" + ) + + keys_l = [] + + # creates points known as breakpoints to break the key_list_options at those points and pivot each substring + breakpoints = sorted(set(self.__passcode)) + temp_list = [] + + # algorithm for creating a new shuffled list, keys_l, out of key_list_options + for i in key_list_options: + temp_list.extend(i) + + # checking breakpoints at which to pivot temporary sublist and add it into keys_l + if i in breakpoints or i == key_list_options[-1]: + keys_l.extend(temp_list[::-1]) + temp_list = [] + + # returning a shuffled keys_l to prevent brute force guessing of shift key + return keys_l + + def __make_shift_key(self) -> int: + """ + sum() of the mutated list of ascii values of all characters where the + mutated list is the one returned by __neg_pos() + """ + num = sum(self.__neg_pos([ord(x) for x in self.__passcode])) + return num if num > 0 else len(self.__passcode) + + def decrypt(self, encoded_message: str) -> str: + """ + Performs shifting of the encoded_message w.r.t. the shuffled __key_list + to create the decoded_message + + >>> ssc = ShuffledShiftCipher('4PYIXyqeQZr44') + >>> ssc.decrypt("d>**-1z6&'5z'5z:z+-='$'>=zp:>5:#z<'.&>#") + 'Hello, this is a modified Caesar cipher' + + """ + decoded_message = "" + + # decoding shift like Caesar cipher algorithm implementing negative shift or reverse shift or left shift + for i in encoded_message: + position = self.__key_list.index(i) + decoded_message += self.__key_list[ + (position - self.__shift_key) % -len(self.__key_list) + ] + + return decoded_message + + def encrypt(self, plaintext: str) -> str: + """ + Performs shifting of the plaintext w.r.t. the shuffled __key_list + to create the encoded_message + + >>> ssc = ShuffledShiftCipher('4PYIXyqeQZr44') + >>> ssc.encrypt('Hello, this is a modified Caesar cipher') + "d>**-1z6&'5z'5z:z+-='$'>=zp:>5:#z<'.&>#" + + """ + encoded_message = "" + + # encoding shift like Caesar cipher algorithm implementing positive shift or forward shift or right shift + for i in plaintext: + position = self.__key_list.index(i) + encoded_message += self.__key_list[ + (position + self.__shift_key) % len(self.__key_list) + ] + + return encoded_message + + +def test_end_to_end(msg: str = "Hello, this is a modified Caesar cipher"): + """ + >>> test_end_to_end() + 'Hello, this is a modified Caesar cipher' + """ + cip1 = ShuffledShiftCipher() + return cip1.decrypt(cip1.encrypt(msg)) + + +if __name__ == "__main__": + import doctest + + doctest.testmod() From 7b3d385ad62a74b7e984c9b5424006566e4f848d Mon Sep 17 00:00:00 2001 From: praisearts <34782930+praisearts@users.noreply.github.com> Date: Thu, 24 Oct 2019 09:31:58 +0100 Subject: [PATCH 0303/1071] create simple binary search (#1430) * create simnple binary search #A binary search implementation to test if a number is in a list of elements * Add .py, format with psf/black, and add doctests --- searches/simple-binary-search.py | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 searches/simple-binary-search.py diff --git a/searches/simple-binary-search.py b/searches/simple-binary-search.py new file mode 100644 index 000000000000..80e43ea346b2 --- /dev/null +++ b/searches/simple-binary-search.py @@ -0,0 +1,26 @@ +# A binary search implementation to test if a number is in a list of elements + + +def binary_search(a_list, item): + """ + >>> test_list = [0, 1, 2, 8, 13, 17, 19, 32, 42] + >>> print(binary_search(test_list, 3)) + False + >>> print(binary_search(test_list, 13)) + True + """ + if len(a_list) == 0: + return False + midpoint = len(a_list) // 2 + if a_list[midpoint] == item: + return True + if item < a_list[midpoint]: + return binary_search(a_list[:midpoint], item) + else: + return binary_search(a_list[midpoint + 1 :], item) + + +if __name__ == "__main__": + import doctest + + doctest.testmod() From 07483139d14214adaebbd58ee22fd6bb552e1a00 Mon Sep 17 00:00:00 2001 From: Alex Veltman Date: Thu, 24 Oct 2019 11:08:45 +0200 Subject: [PATCH 0304/1071] Added determinate function (#1429) * Added determinate function * Changed determinate function name * Changed instance of .det() to .determinate() * Added force_test() function * Update tests.py --- linear_algebra/README.md | 3 ++- linear_algebra/src/lib.py | 27 +++++++++++++++++++++++++++ linear_algebra/src/tests.py | 16 +++++++++++++++- 3 files changed, 44 insertions(+), 2 deletions(-) diff --git a/linear_algebra/README.md b/linear_algebra/README.md index f1b554e139de..169cd074d396 100644 --- a/linear_algebra/README.md +++ b/linear_algebra/README.md @@ -45,7 +45,8 @@ This module contains some useful classes and functions for dealing with linear a - changeComponent(x,y,value) : changes the specified component. - component(x,y) : returns the specified component. - width() : returns the width of the matrix - - height() : returns the height of the matrix + - height() : returns the height of the matrix + - determinate() : returns the determinate of the matrix if it is square - operator + : implements the matrix-addition. - operator - _ implements the matrix-subtraction diff --git a/linear_algebra/src/lib.py b/linear_algebra/src/lib.py index bf9e0d302a89..2f7a1775371f 100644 --- a/linear_algebra/src/lib.py +++ b/linear_algebra/src/lib.py @@ -277,6 +277,33 @@ def height(self): """ return self.__height + def determinate(self) -> float: + """ + returns the determinate of an nxn matrix using Laplace expansion + """ + if self.__height == self.__width and self.__width >= 2: + total = 0 + if self.__width > 2: + for x in range(0, self.__width): + for y in range(0, self.__height): + total += ( + self.__matrix[x][y] + * (-1) ** (x + y) + * Matrix( + self.__matrix[0:x] + self.__matrix[x + 1 :], + self.__width - 1, + self.__height - 1, + ).determinate() + ) + else: + return ( + self.__matrix[0][0] * self.__matrix[1][1] + - self.__matrix[0][1] * self.__matrix[1][0] + ) + return total + else: + raise Exception("matrix is not square") + def __mul__(self, other): """ implements the matrix-vector multiplication. diff --git a/linear_algebra/src/tests.py b/linear_algebra/src/tests.py index b63f2ae8c2db..4123a7c9e663 100644 --- a/linear_algebra/src/tests.py +++ b/linear_algebra/src/tests.py @@ -118,6 +118,13 @@ def test_str_matrix(self): A = Matrix([[1, 2, 3], [2, 4, 5], [6, 7, 8]], 3, 3) self.assertEqual("|1,2,3|\n|2,4,5|\n|6,7,8|\n", str(A)) + def test_determinate(self): + """ + test for determinate() + """ + A = Matrix([[1, 1, 4, 5], [3, 3, 3, 2], [5, 1, 9, 0], [9, 7, 7, 9]], 4, 4) + self.assertEqual(-376, A.determinate()) + def test__mul__matrix(self): A = Matrix([[1, 2, 3], [4, 5, 6], [7, 8, 9]], 3, 3) x = Vector([1, 2, 3]) @@ -149,6 +156,13 @@ def test_squareZeroMatrix(self): str(squareZeroMatrix(5)), ) - +def force_test() -> None: + """ + This will ensure that pytest runs the unit tests above. + To explore https://github.com/TheAlgorithms/Python/pull/1124 uncomment the line below. + >>> # unittest.main() + """ + pass + if __name__ == "__main__": unittest.main() From ec85cc81915cda82cec22e6ed9a4177b6dc1103e Mon Sep 17 00:00:00 2001 From: Alex Veltman Date: Thu, 24 Oct 2019 12:39:51 +0200 Subject: [PATCH 0305/1071] Fixes in methods and tests in Linear Algebra (#1432) * Fixes in methods and tests * Renamed tests.py to test_linear_algebra.py * removed force_test() * Delete test_linear_algebra.py * Format code with psf/black * Rename tests.py to test_linear_algebra.py --- linear_algebra/src/lib.py | 4 ++-- .../src/{tests.py => test_linear_algebra.py} | 11 ++--------- 2 files changed, 4 insertions(+), 11 deletions(-) rename linear_algebra/src/{tests.py => test_linear_algebra.py} (94%) diff --git a/linear_algebra/src/lib.py b/linear_algebra/src/lib.py index 2f7a1775371f..15d176cc6392 100644 --- a/linear_algebra/src/lib.py +++ b/linear_algebra/src/lib.py @@ -119,7 +119,7 @@ def __sub__(self, other): size = len(self) if size == len(other): result = [self.__components[i] - other.component(i) for i in range(size)] - return result + return Vector(result) else: # error case raise Exception("must have the same size") @@ -130,7 +130,7 @@ def __mul__(self, other): """ if isinstance(other, float) or isinstance(other, int): ans = [c * other for c in self.__components] - return ans + return Vector(ans) elif isinstance(other, Vector) and (len(self) == len(other)): size = len(self) summe = 0 diff --git a/linear_algebra/src/tests.py b/linear_algebra/src/test_linear_algebra.py similarity index 94% rename from linear_algebra/src/tests.py rename to linear_algebra/src/test_linear_algebra.py index 4123a7c9e663..f8e7db7de6cc 100644 --- a/linear_algebra/src/tests.py +++ b/linear_algebra/src/test_linear_algebra.py @@ -45,7 +45,7 @@ def test_euclidLength(self): test for the eulidean length """ x = Vector([1, 2]) - self.assertAlmostEqual(x.eulidLength(), 2.236, 3) + self.assertAlmostEqual(x.euclidLength(), 2.236, 3) def test_add(self): """ @@ -156,13 +156,6 @@ def test_squareZeroMatrix(self): str(squareZeroMatrix(5)), ) -def force_test() -> None: - """ - This will ensure that pytest runs the unit tests above. - To explore https://github.com/TheAlgorithms/Python/pull/1124 uncomment the line below. - >>> # unittest.main() - """ - pass - + if __name__ == "__main__": unittest.main() From 03e9a75d69538649df3e05d40a1c8e7d870fd807 Mon Sep 17 00:00:00 2001 From: Isaac Gomes de Oliveira Date: Fri, 25 Oct 2019 00:56:56 -0300 Subject: [PATCH 0306/1071] Add gaussian_elimination.py for solving linear systems (#1448) --- arithmetic_analysis/gaussian_elimination.py | 83 +++++++++++++++++++++ 1 file changed, 83 insertions(+) create mode 100644 arithmetic_analysis/gaussian_elimination.py diff --git a/arithmetic_analysis/gaussian_elimination.py b/arithmetic_analysis/gaussian_elimination.py new file mode 100644 index 000000000000..51207686c12a --- /dev/null +++ b/arithmetic_analysis/gaussian_elimination.py @@ -0,0 +1,83 @@ +""" +Gaussian elimination method for solving a system of linear equations. +Gaussian elimination - https://en.wikipedia.org/wiki/Gaussian_elimination +""" + + +import numpy as np + + +def retroactive_resolution(coefficients: np.matrix, vector: np.array) -> np.array: + """ + This function performs a retroactive linear system resolution + for triangular matrix + + Examples: + 2x1 + 2x2 - 1x3 = 5 2x1 + 2x2 = -1 + 0x1 - 2x2 - 1x3 = -7 0x1 - 2x2 = -1 + 0x1 + 0x2 + 5x3 = 15 + >>> gaussian_elimination([[2, 2, -1], [0, -2, -1], [0, 0, 5]], [[5], [-7], [15]]) + array([[2.], + [2.], + [3.]]) + >>> gaussian_elimination([[2, 2], [0, -2]], [[-1], [-1]]) + array([[-1. ], + [ 0.5]]) + """ + + rows, columns = np.shape(coefficients) + + x = np.zeros((rows, 1), dtype=float) + for row in reversed(range(rows)): + sum = 0 + for col in range(row + 1, columns): + sum += coefficients[row, col] * x[col] + + x[row, 0] = (vector[row] - sum) / coefficients[row, row] + + return x + + +def gaussian_elimination(coefficients: np.matrix, vector: np.array) -> np.array: + """ + This function performs Gaussian elimination method + + Examples: + 1x1 - 4x2 - 2x3 = -2 1x1 + 2x2 = 5 + 5x1 + 2x2 - 2x3 = -3 5x1 + 2x2 = 5 + 1x1 - 1x2 + 0x3 = 4 + >>> gaussian_elimination([[1, -4, -2], [5, 2, -2], [1, -1, 0]], [[-2], [-3], [4]]) + array([[ 2.3 ], + [-1.7 ], + [ 5.55]]) + >>> gaussian_elimination([[1, 2], [5, 2]], [[5], [5]]) + array([[0. ], + [2.5]]) + """ + # coefficients must to be a square matrix so we need to check first + rows, columns = np.shape(coefficients) + if rows != columns: + return [] + + # augmented matrix + augmented_mat = np.concatenate((coefficients, vector), axis=1) + augmented_mat = augmented_mat.astype("float64") + + # scale the matrix leaving it triangular + for row in range(rows - 1): + pivot = augmented_mat[row, row] + for col in range(row + 1, columns): + factor = augmented_mat[col, row] / pivot + augmented_mat[col, :] -= factor * augmented_mat[row, :] + + x = retroactive_resolution( + augmented_mat[:, 0:columns], augmented_mat[:, columns : columns + 1] + ) + + return x + + +if __name__ == "__main__": + import doctest + + doctest.testmod() From a2a3ca674f908c29499b78592339d10c44ec8da2 Mon Sep 17 00:00:00 2001 From: Samarth Sehgal Date: Fri, 25 Oct 2019 21:26:27 +0530 Subject: [PATCH 0307/1071] Update treap.py (#1455) --- data_structures/binary_tree/treap.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data_structures/binary_tree/treap.py b/data_structures/binary_tree/treap.py index a6ff3c9d798b..b603eec3ef3c 100644 --- a/data_structures/binary_tree/treap.py +++ b/data_structures/binary_tree/treap.py @@ -172,7 +172,7 @@ def main(): args = input() print("good by!") - pass + if __name__ == "__main__": From 182062d60be1ea6695d22c196ea039bda29a9311 Mon Sep 17 00:00:00 2001 From: bizzfitch <56891892+bizzfitch@users.noreply.github.com> Date: Fri, 25 Oct 2019 13:04:06 -0400 Subject: [PATCH 0308/1071] Adding deterministic miller rabin primality test (#1453) * Adding deterministic miller rabin primality test * Moved to ciphers directory and renamed for clarity. Changed docstring to add test --- ciphers/deterministic_miller_rabin.py | 135 ++++++++++++++++++++++++++ 1 file changed, 135 insertions(+) create mode 100644 ciphers/deterministic_miller_rabin.py diff --git a/ciphers/deterministic_miller_rabin.py b/ciphers/deterministic_miller_rabin.py new file mode 100644 index 000000000000..37845d6c9b41 --- /dev/null +++ b/ciphers/deterministic_miller_rabin.py @@ -0,0 +1,135 @@ +"""Created by Nathan Damon, @bizzfitch on github +>>> test_miller_rabin() +""" + + +def miller_rabin(n, allow_probable=False): + """Deterministic Miller-Rabin algorithm for primes ~< 3.32e24. + + Uses numerical analysis results to return whether or not the passed number + is prime. If the passed number is above the upper limit, and + allow_probable is True, then a return value of True indicates that n is + probably prime. This test does not allow False negatives- a return value + of False is ALWAYS composite. + + Parameters + ---------- + n : int + The integer to be tested. Since we usually care if a number is prime, + n < 2 returns False instead of raising a ValueError. + allow_probable: bool, default False + Whether or not to test n above the upper bound of the deterministic test. + + Raises + ------ + ValueError + + Reference + --------- + https://en.wikipedia.org/wiki/Miller%E2%80%93Rabin_primality_test + """ + if n == 2: + return True + if not n % 2 or n < 2: + return False + if n > 5 and n % 10 not in (1, 3, 7, 9): # can quickly check last digit + return False + if n > 3_317_044_064_679_887_385_961_981 and not allow_probable: + raise ValueError( + "Warning: upper bound of deterministic test is exceeded. " + "Pass allow_probable=True to allow probabilistic test. " + "A return value of True indicates a probable prime." + ) + # array bounds provided by analysis + bounds = [2_047, + 1_373_653, + 25_326_001, + 3_215_031_751, + 2_152_302_898_747, + 3_474_749_660_383, + 341_550_071_728_321, + 1, + 3_825_123_056_546_413_051, + 1, + 1, + 318_665_857_834_031_151_167_461, + 3_317_044_064_679_887_385_961_981] + + primes = [2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41] + for idx, _p in enumerate(bounds, 1): + if n < _p: + # then we have our last prime to check + plist = primes[:idx] + break + d, s = n - 1, 0 + # break up n -1 into a power of 2 (s) and + # remaining odd component + # essentially, solve for d * 2 ** s == n - 1 + while d % 2 == 0: + d //= 2 + s += 1 + for prime in plist: + pr = False + for r in range(s): + m = pow(prime, d * 2 ** r, n) + # see article for analysis explanation for m + if (r == 0 and m == 1) or ((m + 1) % n == 0): + pr = True + # this loop will not determine compositeness + break + if pr: + continue + # if pr is False, then the above loop never evaluated to true, + # and the n MUST be composite + return False + return True + + +def test_miller_rabin(): + """Testing a nontrivial (ends in 1, 3, 7, 9) composite + and a prime in each range. + """ + assert not miller_rabin(561) + assert miller_rabin(563) + # 2047 + + assert not miller_rabin(838_201) + assert miller_rabin(838_207) + # 1_373_653 + + assert not miller_rabin(17_316_001) + assert miller_rabin(17_316_017) + # 25_326_001 + + assert not miller_rabin(3_078_386_641) + assert miller_rabin(3_078_386_653) + # 3_215_031_751 + + assert not miller_rabin(1_713_045_574_801) + assert miller_rabin(1_713_045_574_819) + # 2_152_302_898_747 + + assert not miller_rabin(2_779_799_728_307) + assert miller_rabin(2_779_799_728_327) + # 3_474_749_660_383 + + assert not miller_rabin(113_850_023_909_441) + assert miller_rabin(113_850_023_909_527) + # 341_550_071_728_321 + + assert not miller_rabin(1_275_041_018_848_804_351) + assert miller_rabin(1_275_041_018_848_804_391) + # 3_825_123_056_546_413_051 + + assert not miller_rabin(79_666_464_458_507_787_791_867) + assert miller_rabin(79_666_464_458_507_787_791_951) + # 318_665_857_834_031_151_167_461 + + assert not miller_rabin(552_840_677_446_647_897_660_333) + assert miller_rabin(552_840_677_446_647_897_660_359) + # 3_317_044_064_679_887_385_961_981 + # upper limit for probabilistic test + + +if __name__ == '__main__': + test_miller_rabin() From 3ea0992dc70e45d17d7f354b101ef3f4ea216bff Mon Sep 17 00:00:00 2001 From: Samarth Sehgal Date: Fri, 25 Oct 2019 22:35:23 +0530 Subject: [PATCH 0309/1071] Update aho-corasick.py (#1457) --- strings/aho-corasick.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/strings/aho-corasick.py b/strings/aho-corasick.py index 6790892a358d..b2f89450ee7a 100644 --- a/strings/aho-corasick.py +++ b/strings/aho-corasick.py @@ -54,7 +54,7 @@ def set_fail_transitions(self): self.adlist[child]["fail_state"] = self.find_next_state( state, self.adlist[child]["value"] ) - if self.adlist[child]["fail_state"] == None: + if self.adlist[child]["fail_state"] is None: self.adlist[child]["fail_state"] = 0 self.adlist[child]["output"] = ( self.adlist[child]["output"] @@ -71,7 +71,7 @@ def search_in(self, string): current_state = 0 for i in range(len(string)): while ( - self.find_next_state(current_state, string[i]) == None + self.find_next_state(current_state, string[i]) is None and current_state != 0 ): current_state = self.adlist[current_state]["fail_state"] From a61b05b10ca843282b5497ac9367e73906b374b9 Mon Sep 17 00:00:00 2001 From: Ankur Chattopadhyay <39518771+chttrjeankr@users.noreply.github.com> Date: Fri, 25 Oct 2019 23:03:24 +0530 Subject: [PATCH 0310/1071] minor changes in format of DIRECTORY.md (#1461) --- DIRECTORY.md | 853 ++++++++++++++++++---------------- scripts/build_directory_md.py | 4 +- 2 files changed, 447 insertions(+), 410 deletions(-) diff --git a/DIRECTORY.md b/DIRECTORY.md index a4838d24dab7..e2d74d39828f 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -1,459 +1,496 @@ -## Arithmetic Analysis -- [bisection](https://github.com/TheAlgorithms/Python/blob/master/arithmetic_analysis/bisection.py) -- [in static equilibrium](https://github.com/TheAlgorithms/Python/blob/master/arithmetic_analysis/in_static_equilibrium.py) -- [intersection](https://github.com/TheAlgorithms/Python/blob/master/arithmetic_analysis/intersection.py) -- [lu decomposition](https://github.com/TheAlgorithms/Python/blob/master/arithmetic_analysis/lu_decomposition.py) -- [newton method](https://github.com/TheAlgorithms/Python/blob/master/arithmetic_analysis/newton_method.py) -- [newton raphson method](https://github.com/TheAlgorithms/Python/blob/master/arithmetic_analysis/newton_raphson_method.py) +## Arithmetic Analysis + * [Bisection](https://github.com/TheAlgorithms/Python/blob/master/arithmetic_analysis/bisection.py) + * [Gaussian Elimination](https://github.com/TheAlgorithms/Python/blob/master/arithmetic_analysis/gaussian_elimination.py) + * [In Static Equilibrium](https://github.com/TheAlgorithms/Python/blob/master/arithmetic_analysis/in_static_equilibrium.py) + * [Intersection](https://github.com/TheAlgorithms/Python/blob/master/arithmetic_analysis/intersection.py) + * [Lu Decomposition](https://github.com/TheAlgorithms/Python/blob/master/arithmetic_analysis/lu_decomposition.py) + * [Newton Forward Interpolation](https://github.com/TheAlgorithms/Python/blob/master/arithmetic_analysis/newton_forward_interpolation.py) + * [Newton Method](https://github.com/TheAlgorithms/Python/blob/master/arithmetic_analysis/newton_method.py) + * [Newton Raphson Method](https://github.com/TheAlgorithms/Python/blob/master/arithmetic_analysis/newton_raphson_method.py) ## Backtracking - -- [all combinations](https://github.com/TheAlgorithms/Python/blob/master/backtracking/all_combinations.py) -- [all permutations](https://github.com/TheAlgorithms/Python/blob/master/backtracking/all_permutations.py) -- [all subsequences](https://github.com/TheAlgorithms/Python/blob/master/backtracking/all_subsequences.py) -- [minimax](https://github.com/TheAlgorithms/Python/blob/master/backtracking/minimax.py) -- [n queens](https://github.com/TheAlgorithms/Python/blob/master/backtracking/n_queens.py) -- [sudoku](https://github.com/TheAlgorithms/Python/blob/master/backtracking/sudoku.py) -- [sum of subsets](https://github.com/TheAlgorithms/Python/blob/master/backtracking/sum_of_subsets.py) + * [All Combinations](https://github.com/TheAlgorithms/Python/blob/master/backtracking/all_combinations.py) + * [All Permutations](https://github.com/TheAlgorithms/Python/blob/master/backtracking/all_permutations.py) + * [All Subsequences](https://github.com/TheAlgorithms/Python/blob/master/backtracking/all_subsequences.py) + * [Minimax](https://github.com/TheAlgorithms/Python/blob/master/backtracking/minimax.py) + * [N Queens](https://github.com/TheAlgorithms/Python/blob/master/backtracking/n_queens.py) + * [Sudoku](https://github.com/TheAlgorithms/Python/blob/master/backtracking/sudoku.py) + * [Sum Of Subsets](https://github.com/TheAlgorithms/Python/blob/master/backtracking/sum_of_subsets.py) + +## Blockchain + * [Chinese Remainder Theorem](https://github.com/TheAlgorithms/Python/blob/master/blockchain/chinese_remainder_theorem.py) + * [Diophantine Equation](https://github.com/TheAlgorithms/Python/blob/master/blockchain/diophantine_equation.py) + * [Modular Division](https://github.com/TheAlgorithms/Python/blob/master/blockchain/modular_division.py) ## Boolean Algebra - -- [quine mc cluskey](https://github.com/TheAlgorithms/Python/blob/master/boolean_algebra/quine_mc_cluskey.py) + * [Quine Mc Cluskey](https://github.com/TheAlgorithms/Python/blob/master/boolean_algebra/quine_mc_cluskey.py) ## Ciphers - -- [affine cipher](https://github.com/TheAlgorithms/Python/blob/master/ciphers/affine_cipher.py) -- [atbash](https://github.com/TheAlgorithms/Python/blob/master/ciphers/atbash.py) -- [base16](https://github.com/TheAlgorithms/Python/blob/master/ciphers/base16.py) -- [base32](https://github.com/TheAlgorithms/Python/blob/master/ciphers/base32.py) -- [base64 cipher](https://github.com/TheAlgorithms/Python/blob/master/ciphers/base64_cipher.py) -- [base85](https://github.com/TheAlgorithms/Python/blob/master/ciphers/base85.py) -- [brute force caesar cipher](https://github.com/TheAlgorithms/Python/blob/master/ciphers/brute_force_caesar_cipher.py) -- [caesar cipher](https://github.com/TheAlgorithms/Python/blob/master/ciphers/caesar_cipher.py) -- [cryptomath module](https://github.com/TheAlgorithms/Python/blob/master/ciphers/cryptomath_module.py) -- [elgamal key generator](https://github.com/TheAlgorithms/Python/blob/master/ciphers/elgamal_key_generator.py) -- [hill cipher](https://github.com/TheAlgorithms/Python/blob/master/ciphers/hill_cipher.py) -- [morse code implementation](https://github.com/TheAlgorithms/Python/blob/master/ciphers/morse_code_implementation.py) -- [onepad cipher](https://github.com/TheAlgorithms/Python/blob/master/ciphers/onepad_cipher.py) -- [playfair cipher](https://github.com/TheAlgorithms/Python/blob/master/ciphers/playfair_cipher.py) -- [rabin miller](https://github.com/TheAlgorithms/Python/blob/master/ciphers/rabin_miller.py) -- [rot13](https://github.com/TheAlgorithms/Python/blob/master/ciphers/rot13.py) -- [rsa cipher](https://github.com/TheAlgorithms/Python/blob/master/ciphers/rsa_cipher.py) -- [rsa key generator](https://github.com/TheAlgorithms/Python/blob/master/ciphers/rsa_key_generator.py) -- [simple substitution cipher](https://github.com/TheAlgorithms/Python/blob/master/ciphers/simple_substitution_cipher.py) -- [trafid cipher](https://github.com/TheAlgorithms/Python/blob/master/ciphers/trafid_cipher.py) -- [transposition cipher](https://github.com/TheAlgorithms/Python/blob/master/ciphers/transposition_cipher.py) -- [transposition cipher encrypt decrypt file](https://github.com/TheAlgorithms/Python/blob/master/ciphers/transposition_cipher_encrypt_decrypt_file.py) -- [vigenere cipher](https://github.com/TheAlgorithms/Python/blob/master/ciphers/vigenere_cipher.py) -- [xor cipher](https://github.com/TheAlgorithms/Python/blob/master/ciphers/xor_cipher.py) + * [Affine Cipher](https://github.com/TheAlgorithms/Python/blob/master/ciphers/affine_cipher.py) + * [Atbash](https://github.com/TheAlgorithms/Python/blob/master/ciphers/atbash.py) + * [Base16](https://github.com/TheAlgorithms/Python/blob/master/ciphers/base16.py) + * [Base32](https://github.com/TheAlgorithms/Python/blob/master/ciphers/base32.py) + * [Base64 Cipher](https://github.com/TheAlgorithms/Python/blob/master/ciphers/base64_cipher.py) + * [Base85](https://github.com/TheAlgorithms/Python/blob/master/ciphers/base85.py) + * [Brute Force Caesar Cipher](https://github.com/TheAlgorithms/Python/blob/master/ciphers/brute_force_caesar_cipher.py) + * [Caesar Cipher](https://github.com/TheAlgorithms/Python/blob/master/ciphers/caesar_cipher.py) + * [Cryptomath Module](https://github.com/TheAlgorithms/Python/blob/master/ciphers/cryptomath_module.py) + * [Elgamal Key Generator](https://github.com/TheAlgorithms/Python/blob/master/ciphers/elgamal_key_generator.py) + * [Hill Cipher](https://github.com/TheAlgorithms/Python/blob/master/ciphers/hill_cipher.py) + * [Morse Code Implementation](https://github.com/TheAlgorithms/Python/blob/master/ciphers/morse_code_implementation.py) + * [Onepad Cipher](https://github.com/TheAlgorithms/Python/blob/master/ciphers/onepad_cipher.py) + * [Playfair Cipher](https://github.com/TheAlgorithms/Python/blob/master/ciphers/playfair_cipher.py) + * [Rabin Miller](https://github.com/TheAlgorithms/Python/blob/master/ciphers/rabin_miller.py) + * [Rot13](https://github.com/TheAlgorithms/Python/blob/master/ciphers/rot13.py) + * [Rsa Cipher](https://github.com/TheAlgorithms/Python/blob/master/ciphers/rsa_cipher.py) + * [Rsa Key Generator](https://github.com/TheAlgorithms/Python/blob/master/ciphers/rsa_key_generator.py) + * [Shuffled Shift Cipher](https://github.com/TheAlgorithms/Python/blob/master/ciphers/shuffled_shift_cipher.py) + * [Simple Substitution Cipher](https://github.com/TheAlgorithms/Python/blob/master/ciphers/simple_substitution_cipher.py) + * [Trafid Cipher](https://github.com/TheAlgorithms/Python/blob/master/ciphers/trafid_cipher.py) + * [Transposition Cipher](https://github.com/TheAlgorithms/Python/blob/master/ciphers/transposition_cipher.py) + * [Transposition Cipher Encrypt Decrypt File](https://github.com/TheAlgorithms/Python/blob/master/ciphers/transposition_cipher_encrypt_decrypt_file.py) + * [Vigenere Cipher](https://github.com/TheAlgorithms/Python/blob/master/ciphers/vigenere_cipher.py) + * [Xor Cipher](https://github.com/TheAlgorithms/Python/blob/master/ciphers/xor_cipher.py) ## Compression - -- [burrows wheeler](https://github.com/TheAlgorithms/Python/blob/master/compression/burrows_wheeler.py) -- [huffman](https://github.com/TheAlgorithms/Python/blob/master/compression/huffman.py) -- [peak signal to noise ratio](https://github.com/TheAlgorithms/Python/blob/master/compression/peak_signal_to_noise_ratio.py) + * [Burrows Wheeler](https://github.com/TheAlgorithms/Python/blob/master/compression/burrows_wheeler.py) + * [Huffman](https://github.com/TheAlgorithms/Python/blob/master/compression/huffman.py) + * [Peak Signal To Noise Ratio](https://github.com/TheAlgorithms/Python/blob/master/compression/peak_signal_to_noise_ratio.py) ## Conversions - -- [decimal to binary](https://github.com/TheAlgorithms/Python/blob/master/conversions/decimal_to_binary.py) -- [decimal to hexadecimal](https://github.com/TheAlgorithms/Python/blob/master/conversions/decimal_to_hexadecimal.py) -- [decimal to octal](https://github.com/TheAlgorithms/Python/blob/master/conversions/decimal_to_octal.py) + * [Decimal To Binary](https://github.com/TheAlgorithms/Python/blob/master/conversions/decimal_to_binary.py) + * [Decimal To Hexadecimal](https://github.com/TheAlgorithms/Python/blob/master/conversions/decimal_to_hexadecimal.py) + * [Decimal To Octal](https://github.com/TheAlgorithms/Python/blob/master/conversions/decimal_to_octal.py) ## Data Structures - -- Binary Tree - - [avl tree](https://github.com/TheAlgorithms/Python/blob/master/data_structures/binary_tree/avl_tree.py) - - [basic binary tree](https://github.com/TheAlgorithms/Python/blob/master/data_structures/binary_tree/basic_binary_tree.py) - - [binary search tree](https://github.com/TheAlgorithms/Python/blob/master/data_structures/binary_tree/binary_search_tree.py) - - [fenwick tree](https://github.com/TheAlgorithms/Python/blob/master/data_structures/binary_tree/fenwick_tree.py) - - [lazy segment tree](https://github.com/TheAlgorithms/Python/blob/master/data_structures/binary_tree/lazy_segment_tree.py) - - [lca](https://github.com/TheAlgorithms/Python/blob/master/data_structures/binary_tree/lca.py) - - [red black tree](https://github.com/TheAlgorithms/Python/blob/master/data_structures/binary_tree/red_black_tree.py) - - [segment tree](https://github.com/TheAlgorithms/Python/blob/master/data_structures/binary_tree/segment_tree.py) - - [treap](https://github.com/TheAlgorithms/Python/blob/master/data_structures/binary_tree/treap.py) -- Hashing - - [double hash](https://github.com/TheAlgorithms/Python/blob/master/data_structures/hashing/double_hash.py) - - [hash table](https://github.com/TheAlgorithms/Python/blob/master/data_structures/hashing/hash_table.py) - - [hash table with linked list](https://github.com/TheAlgorithms/Python/blob/master/data_structures/hashing/hash_table_with_linked_list.py) -- Number Theory - - [prime numbers](https://github.com/TheAlgorithms/Python/blob/master/data_structures/number_theory/prime_numbers.py) - - [quadratic probing](https://github.com/TheAlgorithms/Python/blob/master/data_structures/number_theory/quadratic_probing.py) -- Heap - - [heap](https://github.com/TheAlgorithms/Python/blob/master/data_structures/heap/heap.py) -- Linked List - - [doubly linked list](https://github.com/TheAlgorithms/Python/blob/master/data_structures/linked_list/doubly_linked_list.py) - - [is palindrome](https://github.com/TheAlgorithms/Python/blob/master/data_structures/linked_list/is_palindrome.py) - - [singly linked list](https://github.com/TheAlgorithms/Python/blob/master/data_structures/linked_list/singly_linked_list.py) - - [swap nodes](https://github.com/TheAlgorithms/Python/blob/master/data_structures/linked_list/swap_nodes.py) -- Queue - - [double ended queue](https://github.com/TheAlgorithms/Python/blob/master/data_structures/queue/double_ended_queue.py) - - [queue on list](https://github.com/TheAlgorithms/Python/blob/master/data_structures/queue/queue_on_list.py) - - [queue on pseudo stack](https://github.com/TheAlgorithms/Python/blob/master/data_structures/queue/queue_on_pseudo_stack.py) -- Stacks - - [balanced parentheses](https://github.com/TheAlgorithms/Python/blob/master/data_structures/stacks/balanced_parentheses.py) - - [infix to postfix conversion](https://github.com/TheAlgorithms/Python/blob/master/data_structures/stacks/infix_to_postfix_conversion.py) - - [infix to prefix conversion](https://github.com/TheAlgorithms/Python/blob/master/data_structures/stacks/infix_to_prefix_conversion.py) - - [next greater element](https://github.com/TheAlgorithms/Python/blob/master/data_structures/stacks/next_greater_element.py) - - [postfix evaluation](https://github.com/TheAlgorithms/Python/blob/master/data_structures/stacks/postfix_evaluation.py) - - [stack](https://github.com/TheAlgorithms/Python/blob/master/data_structures/stacks/stack.py) - - [stock span problem](https://github.com/TheAlgorithms/Python/blob/master/data_structures/stacks/stock_span_problem.py) -- Trie - - [trie](https://github.com/TheAlgorithms/Python/blob/master/data_structures/trie/trie.py) + * Binary Tree + * [Avl Tree](https://github.com/TheAlgorithms/Python/blob/master/data_structures/binary_tree/avl_tree.py) + * [Basic Binary Tree](https://github.com/TheAlgorithms/Python/blob/master/data_structures/binary_tree/basic_binary_tree.py) + * [Binary Search Tree](https://github.com/TheAlgorithms/Python/blob/master/data_structures/binary_tree/binary_search_tree.py) + * [Fenwick Tree](https://github.com/TheAlgorithms/Python/blob/master/data_structures/binary_tree/fenwick_tree.py) + * [Lazy Segment Tree](https://github.com/TheAlgorithms/Python/blob/master/data_structures/binary_tree/lazy_segment_tree.py) + * [Lca](https://github.com/TheAlgorithms/Python/blob/master/data_structures/binary_tree/lca.py) + * [Red Black Tree](https://github.com/TheAlgorithms/Python/blob/master/data_structures/binary_tree/red_black_tree.py) + * [Segment Tree](https://github.com/TheAlgorithms/Python/blob/master/data_structures/binary_tree/segment_tree.py) + * [Treap](https://github.com/TheAlgorithms/Python/blob/master/data_structures/binary_tree/treap.py) + * Disjoint Set + * [Disjoint Set](https://github.com/TheAlgorithms/Python/blob/master/data_structures/disjoint_set/disjoint_set.py) + * Hashing + * [Double Hash](https://github.com/TheAlgorithms/Python/blob/master/data_structures/hashing/double_hash.py) + * [Hash Table](https://github.com/TheAlgorithms/Python/blob/master/data_structures/hashing/hash_table.py) + * [Hash Table With Linked List](https://github.com/TheAlgorithms/Python/blob/master/data_structures/hashing/hash_table_with_linked_list.py) + * Number Theory + * [Prime Numbers](https://github.com/TheAlgorithms/Python/blob/master/data_structures/hashing/number_theory/prime_numbers.py) + * [Quadratic Probing](https://github.com/TheAlgorithms/Python/blob/master/data_structures/hashing/quadratic_probing.py) + * Heap + * [Binomial Heap](https://github.com/TheAlgorithms/Python/blob/master/data_structures/heap/binomial_heap.py) + * [Heap](https://github.com/TheAlgorithms/Python/blob/master/data_structures/heap/heap.py) + * [Min Heap](https://github.com/TheAlgorithms/Python/blob/master/data_structures/heap/min_heap.py) + * Linked List + * [Doubly Linked List](https://github.com/TheAlgorithms/Python/blob/master/data_structures/linked_list/doubly_linked_list.py) + * [Is Palindrome](https://github.com/TheAlgorithms/Python/blob/master/data_structures/linked_list/is_palindrome.py) + * [Singly Linked List](https://github.com/TheAlgorithms/Python/blob/master/data_structures/linked_list/singly_linked_list.py) + * [Swap Nodes](https://github.com/TheAlgorithms/Python/blob/master/data_structures/linked_list/swap_nodes.py) + * Queue + * [Double Ended Queue](https://github.com/TheAlgorithms/Python/blob/master/data_structures/queue/double_ended_queue.py) + * [Linked Queue](https://github.com/TheAlgorithms/Python/blob/master/data_structures/queue/linked_queue.py) + * [Queue On List](https://github.com/TheAlgorithms/Python/blob/master/data_structures/queue/queue_on_list.py) + * [Queue On Pseudo Stack](https://github.com/TheAlgorithms/Python/blob/master/data_structures/queue/queue_on_pseudo_stack.py) + * Stacks + * [Balanced Parentheses](https://github.com/TheAlgorithms/Python/blob/master/data_structures/stacks/balanced_parentheses.py) + * [Infix To Postfix Conversion](https://github.com/TheAlgorithms/Python/blob/master/data_structures/stacks/infix_to_postfix_conversion.py) + * [Infix To Prefix Conversion](https://github.com/TheAlgorithms/Python/blob/master/data_structures/stacks/infix_to_prefix_conversion.py) + * [Linked Stack](https://github.com/TheAlgorithms/Python/blob/master/data_structures/stacks/linked_stack.py) + * [Next Greater Element](https://github.com/TheAlgorithms/Python/blob/master/data_structures/stacks/next_greater_element.py) + * [Postfix Evaluation](https://github.com/TheAlgorithms/Python/blob/master/data_structures/stacks/postfix_evaluation.py) + * [Stack](https://github.com/TheAlgorithms/Python/blob/master/data_structures/stacks/stack.py) + * [Stock Span Problem](https://github.com/TheAlgorithms/Python/blob/master/data_structures/stacks/stock_span_problem.py) + * Trie + * [Trie](https://github.com/TheAlgorithms/Python/blob/master/data_structures/trie/trie.py) ## Digital Image Processing - -- [change contrast](https://github.com/TheAlgorithms/Python/blob/master/digital_image_processing/change_contrast.py) -- Edge Detection - - [canny](https://github.com/TheAlgorithms/Python/blob/master/digital_image_processing/edge_detection/canny.py) -- Filters - - [convolve](https://github.com/TheAlgorithms/Python/blob/master/digital_image_processing/filters/convolve.py) - - [gaussian filter](https://github.com/TheAlgorithms/Python/blob/master/digital_image_processing/filters/gaussian_filter.py) - - [median filter](https://github.com/TheAlgorithms/Python/blob/master/digital_image_processing/filters/median_filter.py) - - [sobel filter](https://github.com/TheAlgorithms/Python/blob/master/digital_image_processing/filters/sobel_filter.py) -- [test digital image processing](https://github.com/TheAlgorithms/Python/blob/master/digital_image_processing/test_digital_image_processing.py) + * [Change Contrast](https://github.com/TheAlgorithms/Python/blob/master/digital_image_processing/change_contrast.py) + * Edge Detection + * [Canny](https://github.com/TheAlgorithms/Python/blob/master/digital_image_processing/edge_detection/canny.py) + * Filters + * [Convolve](https://github.com/TheAlgorithms/Python/blob/master/digital_image_processing/filters/convolve.py) + * [Gaussian Filter](https://github.com/TheAlgorithms/Python/blob/master/digital_image_processing/filters/gaussian_filter.py) + * [Median Filter](https://github.com/TheAlgorithms/Python/blob/master/digital_image_processing/filters/median_filter.py) + * [Sobel Filter](https://github.com/TheAlgorithms/Python/blob/master/digital_image_processing/filters/sobel_filter.py) + * Rotation + * [Rotation](https://github.com/TheAlgorithms/Python/blob/master/digital_image_processing/rotation/rotation.py) + * [Test Digital Image Processing](https://github.com/TheAlgorithms/Python/blob/master/digital_image_processing/test_digital_image_processing.py) ## Divide And Conquer - -- [closest pair of points](https://github.com/TheAlgorithms/Python/blob/master/divide_and_conquer/closest_pair_of_points.py) -- [convex hull](https://github.com/TheAlgorithms/Python/blob/master/divide_and_conquer/convex_hull.py) -- [inversions](https://github.com/TheAlgorithms/Python/blob/master/divide_and_conquer/inversions.py) -- [max subarray sum](https://github.com/TheAlgorithms/Python/blob/master/divide_and_conquer/max_subarray_sum.py) + * [Closest Pair Of Points](https://github.com/TheAlgorithms/Python/blob/master/divide_and_conquer/closest_pair_of_points.py) + * [Convex Hull](https://github.com/TheAlgorithms/Python/blob/master/divide_and_conquer/convex_hull.py) + * [Inversions](https://github.com/TheAlgorithms/Python/blob/master/divide_and_conquer/inversions.py) + * [Max Subarray Sum](https://github.com/TheAlgorithms/Python/blob/master/divide_and_conquer/max_subarray_sum.py) + * [Mergesort](https://github.com/TheAlgorithms/Python/blob/master/divide_and_conquer/mergesort.py) ## Dynamic Programming - -- [abbreviation](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/abbreviation.py) -- [bitmask](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/bitmask.py) -- [climbing stairs](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/climbing_stairs.py) -- [coin change](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/coin_change.py) -- [edit distance](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/edit_distance.py) -- [factorial](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/factorial.py) -- [fast fibonacci](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/fast_fibonacci.py) -- [fibonacci](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/fibonacci.py) -- [floyd warshall](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/floyd_warshall.py) -- [fractional knapsack](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/fractional_knapsack.py) -- [integer partition](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/integer_partition.py) -- [k means clustering tensorflow](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/k_means_clustering_tensorflow.py) -- [knapsack](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/knapsack.py) -- [longest common subsequence](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/longest_common_subsequence.py) -- [longest increasing subsequence](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/longest_increasing_subsequence.py) -- [longest increasing subsequence o(nlogn)]() -- [longest sub array](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/longest_sub_array.py) -- [matrix chain order](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/matrix_chain_order.py) -- [max sub array](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/max_sub_array.py) -- [minimum partition](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/minimum_partition.py) -- [rod cutting](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/rod_cutting.py) -- [subset generation](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/subset_generation.py) -- [sum of subset](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/sum_of_subset.py) + * [Abbreviation](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/abbreviation.py) + * [Bitmask](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/bitmask.py) + * [Climbing Stairs](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/climbing_stairs.py) + * [Coin Change](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/coin_change.py) + * [Edit Distance](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/edit_distance.py) + * [Factorial](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/factorial.py) + * [Fast Fibonacci](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/fast_fibonacci.py) + * [Fibonacci](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/fibonacci.py) + * [Floyd Warshall](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/floyd_warshall.py) + * [Fractional Knapsack](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/fractional_knapsack.py) + * [Integer Partition](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/integer_partition.py) + * [K Means Clustering Tensorflow](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/k_means_clustering_tensorflow.py) + * [Knapsack](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/knapsack.py) + * [Longest Common Subsequence](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/longest_common_subsequence.py) + * [Longest Increasing Subsequence](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/longest_increasing_subsequence.py) + * [Longest Increasing Subsequence O(Nlogn)](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/longest_increasing_subsequence_o(nlogn).py) + * [Longest Sub Array](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/longest_sub_array.py) + * [Matrix Chain Order](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/matrix_chain_order.py) + * [Max Sub Array](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/max_sub_array.py) + * [Minimum Partition](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/minimum_partition.py) + * [Rod Cutting](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/rod_cutting.py) + * [Subset Generation](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/subset_generation.py) + * [Sum Of Subset](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/sum_of_subset.py) ## File Transfer + * [Recieve File](https://github.com/TheAlgorithms/Python/blob/master/file_transfer/recieve_file.py) + * [Send File](https://github.com/TheAlgorithms/Python/blob/master/file_transfer/send_file.py) -- [recieve file](https://github.com/TheAlgorithms/Python/blob/master/file_transfer/recieve_file.py) -- [send file](https://github.com/TheAlgorithms/Python/blob/master/file_transfer/send_file.py) +## Fuzzy Logic + * [Fuzzy Operations](https://github.com/TheAlgorithms/Python/blob/master/fuzzy_logic/fuzzy_operations.py) ## Graphs - -- [a star](https://github.com/TheAlgorithms/Python/blob/master/graphs/a_star.py) -- [articulation points](https://github.com/TheAlgorithms/Python/blob/master/graphs/articulation_points.py) -- [basic graphs](https://github.com/TheAlgorithms/Python/blob/master/graphs/basic_graphs.py) -- [bellman ford](https://github.com/TheAlgorithms/Python/blob/master/graphs/bellman_ford.py) -- [bfs](https://github.com/TheAlgorithms/Python/blob/master/graphs/bfs.py) -- [bfs shortest path](https://github.com/TheAlgorithms/Python/blob/master/graphs/bfs_shortest_path.py) -- [breadth first search](https://github.com/TheAlgorithms/Python/blob/master/graphs/breadth_first_search.py) -- [check bipartite graph bfs](https://github.com/TheAlgorithms/Python/blob/master/graphs/check_bipartite_graph_bfs.py) -- [check bipartite graph dfs](https://github.com/TheAlgorithms/Python/blob/master/graphs/check_bipartite_graph_dfs.py) -- [depth first search](https://github.com/TheAlgorithms/Python/blob/master/graphs/depth_first_search.py) -- [dfs](https://github.com/TheAlgorithms/Python/blob/master/graphs/dfs.py) -- [dijkstra](https://github.com/TheAlgorithms/Python/blob/master/graphs/dijkstra.py) -- [dijkstra 2](https://github.com/TheAlgorithms/Python/blob/master/graphs/dijkstra_2.py) -- [dijkstra algorithm](https://github.com/TheAlgorithms/Python/blob/master/graphs/dijkstra_algorithm.py) -- [directed and undirected (weighted) graph]() -- [edmonds karp multiple source and sink](https://github.com/TheAlgorithms/Python/blob/master/graphs/edmonds_karp_multiple_source_and_sink.py) -- [eulerian path and circuit for undirected graph](https://github.com/TheAlgorithms/Python/blob/master/graphs/eulerian_path_and_circuit_for_undirected_graph.py) -- [even tree](https://github.com/TheAlgorithms/Python/blob/master/graphs/even_tree.py) -- [finding bridges](https://github.com/TheAlgorithms/Python/blob/master/graphs/finding_bridges.py) -- [graph list](https://github.com/TheAlgorithms/Python/blob/master/graphs/graph_list.py) -- [graph matrix](https://github.com/TheAlgorithms/Python/blob/master/graphs/graph_matrix.py) -- [graphs floyd warshall](https://github.com/TheAlgorithms/Python/blob/master/graphs/graphs_floyd_warshall.py) -- [kahns algorithm long](https://github.com/TheAlgorithms/Python/blob/master/graphs/kahns_algorithm_long.py) -- [kahns algorithm topo](https://github.com/TheAlgorithms/Python/blob/master/graphs/kahns_algorithm_topo.py) -- [minimum spanning tree kruskal](https://github.com/TheAlgorithms/Python/blob/master/graphs/minimum_spanning_tree_kruskal.py) -- [minimum spanning tree prims](https://github.com/TheAlgorithms/Python/blob/master/graphs/minimum_spanning_tree_prims.py) -- [multi hueristic astar](https://github.com/TheAlgorithms/Python/blob/master/graphs/multi_hueristic_astar.py) -- [page rank](https://github.com/TheAlgorithms/Python/blob/master/graphs/page_rank.py) -- [prim](https://github.com/TheAlgorithms/Python/blob/master/graphs/prim.py) -- [scc kosaraju](https://github.com/TheAlgorithms/Python/blob/master/graphs/scc_kosaraju.py) -- [tarjans scc](https://github.com/TheAlgorithms/Python/blob/master/graphs/tarjans_scc.py) + * [A Star](https://github.com/TheAlgorithms/Python/blob/master/graphs/a_star.py) + * [Articulation Points](https://github.com/TheAlgorithms/Python/blob/master/graphs/articulation_points.py) + * [Basic Graphs](https://github.com/TheAlgorithms/Python/blob/master/graphs/basic_graphs.py) + * [Bellman Ford](https://github.com/TheAlgorithms/Python/blob/master/graphs/bellman_ford.py) + * [Bfs](https://github.com/TheAlgorithms/Python/blob/master/graphs/bfs.py) + * [Bfs Shortest Path](https://github.com/TheAlgorithms/Python/blob/master/graphs/bfs_shortest_path.py) + * [Breadth First Search](https://github.com/TheAlgorithms/Python/blob/master/graphs/breadth_first_search.py) + * [Check Bipartite Graph Bfs](https://github.com/TheAlgorithms/Python/blob/master/graphs/check_bipartite_graph_bfs.py) + * [Check Bipartite Graph Dfs](https://github.com/TheAlgorithms/Python/blob/master/graphs/check_bipartite_graph_dfs.py) + * [Depth First Search](https://github.com/TheAlgorithms/Python/blob/master/graphs/depth_first_search.py) + * [Dfs](https://github.com/TheAlgorithms/Python/blob/master/graphs/dfs.py) + * [Dijkstra](https://github.com/TheAlgorithms/Python/blob/master/graphs/dijkstra.py) + * [Dijkstra 2](https://github.com/TheAlgorithms/Python/blob/master/graphs/dijkstra_2.py) + * [Dijkstra Algorithm](https://github.com/TheAlgorithms/Python/blob/master/graphs/dijkstra_algorithm.py) + * [Dinic](https://github.com/TheAlgorithms/Python/blob/master/graphs/dinic.py) + * [Directed And Undirected (Weighted) Graph](https://github.com/TheAlgorithms/Python/blob/master/graphs/directed_and_undirected_(weighted)_graph.py) + * [Edmonds Karp Multiple Source And Sink](https://github.com/TheAlgorithms/Python/blob/master/graphs/edmonds_karp_multiple_source_and_sink.py) + * [Eulerian Path And Circuit For Undirected Graph](https://github.com/TheAlgorithms/Python/blob/master/graphs/eulerian_path_and_circuit_for_undirected_graph.py) + * [Even Tree](https://github.com/TheAlgorithms/Python/blob/master/graphs/even_tree.py) + * [Finding Bridges](https://github.com/TheAlgorithms/Python/blob/master/graphs/finding_bridges.py) + * [G Topological Sort](https://github.com/TheAlgorithms/Python/blob/master/graphs/g_topological_sort.py) + * [Graph List](https://github.com/TheAlgorithms/Python/blob/master/graphs/graph_list.py) + * [Graph Matrix](https://github.com/TheAlgorithms/Python/blob/master/graphs/graph_matrix.py) + * [Graphs Floyd Warshall](https://github.com/TheAlgorithms/Python/blob/master/graphs/graphs_floyd_warshall.py) + * [Kahns Algorithm Long](https://github.com/TheAlgorithms/Python/blob/master/graphs/kahns_algorithm_long.py) + * [Kahns Algorithm Topo](https://github.com/TheAlgorithms/Python/blob/master/graphs/kahns_algorithm_topo.py) + * [Minimum Spanning Tree Kruskal](https://github.com/TheAlgorithms/Python/blob/master/graphs/minimum_spanning_tree_kruskal.py) + * [Minimum Spanning Tree Prims](https://github.com/TheAlgorithms/Python/blob/master/graphs/minimum_spanning_tree_prims.py) + * [Multi Hueristic Astar](https://github.com/TheAlgorithms/Python/blob/master/graphs/multi_hueristic_astar.py) + * [Page Rank](https://github.com/TheAlgorithms/Python/blob/master/graphs/page_rank.py) + * [Prim](https://github.com/TheAlgorithms/Python/blob/master/graphs/prim.py) + * [Scc Kosaraju](https://github.com/TheAlgorithms/Python/blob/master/graphs/scc_kosaraju.py) + * [Tarjans Scc](https://github.com/TheAlgorithms/Python/blob/master/graphs/tarjans_scc.py) ## Hashes - -- [chaos machine](https://github.com/TheAlgorithms/Python/blob/master/hashes/chaos_machine.py) -- [enigma machine](https://github.com/TheAlgorithms/Python/blob/master/hashes/enigma_machine.py) -- [md5](https://github.com/TheAlgorithms/Python/blob/master/hashes/md5.py) -- [sha1](https://github.com/TheAlgorithms/Python/blob/master/hashes/sha1.py) + * [Chaos Machine](https://github.com/TheAlgorithms/Python/blob/master/hashes/chaos_machine.py) + * [Enigma Machine](https://github.com/TheAlgorithms/Python/blob/master/hashes/enigma_machine.py) + * [Md5](https://github.com/TheAlgorithms/Python/blob/master/hashes/md5.py) + * [Sha1](https://github.com/TheAlgorithms/Python/blob/master/hashes/sha1.py) ## Linear Algebra - -- Src - - [lib](https://github.com/TheAlgorithms/Python/blob/master/linear_algebra/src/lib.py) - - [polynom-for-points](https://github.com/TheAlgorithms/Python/blob/master/linear_algebra/src/polynom-for-points.py) - - [tests](https://github.com/TheAlgorithms/Python/blob/master/linear_algebra/src/tests.py) + * Src + * [Lib](https://github.com/TheAlgorithms/Python/blob/master/linear_algebra/src/lib.py) + * [Polynom-For-Points](https://github.com/TheAlgorithms/Python/blob/master/linear_algebra/src/polynom-for-points.py) + * [Test Linear Algebra](https://github.com/TheAlgorithms/Python/blob/master/linear_algebra/src/test_linear_algebra.py) ## Machine Learning - -- [decision tree](https://github.com/TheAlgorithms/Python/blob/master/machine_learning/decision_tree.py) -- [gradient descent](https://github.com/TheAlgorithms/Python/blob/master/machine_learning/gradient_descent.py) -- [k means clust](https://github.com/TheAlgorithms/Python/blob/master/machine_learning/k_means_clust.py) -- [knn sklearn](https://github.com/TheAlgorithms/Python/blob/master/machine_learning/knn_sklearn.py) -- [linear regression](https://github.com/TheAlgorithms/Python/blob/master/machine_learning/linear_regression.py) -- [logistic regression](https://github.com/TheAlgorithms/Python/blob/master/machine_learning/logistic_regression.py) -- [naive bayes](https://github.com/TheAlgorithms/Python/blob/master/machine_learning/naive_bayes.ipynb) -- Random Forest Classification - - [random forest classification](https://github.com/TheAlgorithms/Python/blob/master/machine_learning/random_forest_classification/random_forest_classification.py) - - [random forest classifier](https://github.com/TheAlgorithms/Python/blob/master/machine_learning/random_forest_classification/random_forest_classifier.ipynb) -- Random Forest Regression - - [random forest regression](https://github.com/TheAlgorithms/Python/blob/master/machine_learning/random_forest_regression/random_forest_regression.ipynb) - - [random forest regression](https://github.com/TheAlgorithms/Python/blob/master/machine_learning/random_forest_regression/random_forest_regression.py) -- [reuters one vs rest classifier](https://github.com/TheAlgorithms/Python/blob/master/machine_learning/reuters_one_vs_rest_classifier.ipynb) -- [scoring functions](https://github.com/TheAlgorithms/Python/blob/master/machine_learning/scoring_functions.py) -- [sorted vector machines](https://github.com/TheAlgorithms/Python/blob/master/machine_learning/sorted_vector_machines.py) + * [Decision Tree](https://github.com/TheAlgorithms/Python/blob/master/machine_learning/decision_tree.py) + * [Gradient Descent](https://github.com/TheAlgorithms/Python/blob/master/machine_learning/gradient_descent.py) + * [K Means Clust](https://github.com/TheAlgorithms/Python/blob/master/machine_learning/k_means_clust.py) + * [K Nearest Neighbours](https://github.com/TheAlgorithms/Python/blob/master/machine_learning/k_nearest_neighbours.py) + * [Knn Sklearn](https://github.com/TheAlgorithms/Python/blob/master/machine_learning/knn_sklearn.py) + * [Linear Regression](https://github.com/TheAlgorithms/Python/blob/master/machine_learning/linear_regression.py) + * [Logistic Regression](https://github.com/TheAlgorithms/Python/blob/master/machine_learning/logistic_regression.py) + * [Polymonial Regression](https://github.com/TheAlgorithms/Python/blob/master/machine_learning/polymonial_regression.py) + * [Scoring Functions](https://github.com/TheAlgorithms/Python/blob/master/machine_learning/scoring_functions.py) + * [Sequential Minimum Optimization](https://github.com/TheAlgorithms/Python/blob/master/machine_learning/sequential_minimum_optimization.py) + * [Support Vector Machines](https://github.com/TheAlgorithms/Python/blob/master/machine_learning/support_vector_machines.py) ## Maths - -- [3n+1](https://github.com/TheAlgorithms/Python/blob/master/maths/3n+1.py) -- [abs](https://github.com/TheAlgorithms/Python/blob/master/maths/abs.py) -- [abs max](https://github.com/TheAlgorithms/Python/blob/master/maths/abs_max.py) -- [abs min](https://github.com/TheAlgorithms/Python/blob/master/maths/abs_min.py) -- [average mean](https://github.com/TheAlgorithms/Python/blob/master/maths/average_mean.py) -- [average median](https://github.com/TheAlgorithms/Python/blob/master/maths/average_median.py) -- [basic maths](https://github.com/TheAlgorithms/Python/blob/master/maths/basic_maths.py) -- [binary exponentiation](https://github.com/TheAlgorithms/Python/blob/master/maths/binary_exponentiation.py) -- [collatz sequence](https://github.com/TheAlgorithms/Python/blob/master/maths/collatz_sequence.py) -- [extended euclidean algorithm](https://github.com/TheAlgorithms/Python/blob/master/maths/extended_euclidean_algorithm.py) -- [factorial python](https://github.com/TheAlgorithms/Python/blob/master/maths/factorial_python.py) -- [factorial recursive](https://github.com/TheAlgorithms/Python/blob/master/maths/factorial_recursive.py) -- [fermat little theorem](https://github.com/TheAlgorithms/Python/blob/master/maths/fermat_little_theorem.py) -- [fibonacci](https://github.com/TheAlgorithms/Python/blob/master/maths/fibonacci.py) -- [fibonacci sequence recursion](https://github.com/TheAlgorithms/Python/blob/master/maths/fibonacci_sequence_recursion.py) -- [find lcm](https://github.com/TheAlgorithms/Python/blob/master/maths/find_lcm.py) -- [find max](https://github.com/TheAlgorithms/Python/blob/master/maths/find_max.py) -- [find min](https://github.com/TheAlgorithms/Python/blob/master/maths/find_min.py) -- [gaussian](https://github.com/TheAlgorithms/Python/blob/master/maths/gaussian.py) -- [greater common divisor](https://github.com/TheAlgorithms/Python/blob/master/maths/greater_common_divisor.py) -- [is square free](https://github.com/TheAlgorithms/Python/blob/master/maths/is_square_free.py) -- [largest of very large numbers](https://github.com/TheAlgorithms/Python/blob/master/maths/largest_of_very_large_numbers.py) -- [lucas lehmer primality test](https://github.com/TheAlgorithms/Python/blob/master/maths/lucas_lehmer_primality_test.py) -- [lucas series](https://github.com/TheAlgorithms/Python/blob/master/maths/lucas_series.py) -- [mobius function](https://github.com/TheAlgorithms/Python/blob/master/maths/mobius_function.py) -- [modular exponential](https://github.com/TheAlgorithms/Python/blob/master/maths/modular_exponential.py) -- [newton raphson](https://github.com/TheAlgorithms/Python/blob/master/maths/newton_raphson.py) -- [prime check](https://github.com/TheAlgorithms/Python/blob/master/maths/prime_check.py) -- [prime factors](https://github.com/TheAlgorithms/Python/blob/master/maths/prime_factors.py) -- [quadratic equations complex numbers](https://github.com/TheAlgorithms/Python/blob/master/maths/quadratic_equations_complex_numbers.py) -- [segmented sieve](https://github.com/TheAlgorithms/Python/blob/master/maths/segmented_sieve.py) -- [sieve of eratosthenes](https://github.com/TheAlgorithms/Python/blob/master/maths/sieve_of_eratosthenes.py) -- [simpson rule](https://github.com/TheAlgorithms/Python/blob/master/maths/simpson_rule.py) -- [test prime check](https://github.com/TheAlgorithms/Python/blob/master/maths/test_prime_check.py) -- [trapezoidal rule](https://github.com/TheAlgorithms/Python/blob/master/maths/trapezoidal_rule.py) -- [volume](https://github.com/TheAlgorithms/Python/blob/master/maths/volume.py) -- [zellers congruence](https://github.com/TheAlgorithms/Python/blob/master/maths/zellers_congruence.py) + * [3N+1](https://github.com/TheAlgorithms/Python/blob/master/maths/3n+1.py) + * [Abs](https://github.com/TheAlgorithms/Python/blob/master/maths/abs.py) + * [Abs Max](https://github.com/TheAlgorithms/Python/blob/master/maths/abs_max.py) + * [Abs Min](https://github.com/TheAlgorithms/Python/blob/master/maths/abs_min.py) + * [Average Mean](https://github.com/TheAlgorithms/Python/blob/master/maths/average_mean.py) + * [Average Median](https://github.com/TheAlgorithms/Python/blob/master/maths/average_median.py) + * [Basic Maths](https://github.com/TheAlgorithms/Python/blob/master/maths/basic_maths.py) + * [Binary Exponentiation](https://github.com/TheAlgorithms/Python/blob/master/maths/binary_exponentiation.py) + * [Collatz Sequence](https://github.com/TheAlgorithms/Python/blob/master/maths/collatz_sequence.py) + * [Explicit Euler](https://github.com/TheAlgorithms/Python/blob/master/maths/explicit_euler.py) + * [Extended Euclidean Algorithm](https://github.com/TheAlgorithms/Python/blob/master/maths/extended_euclidean_algorithm.py) + * [Factorial Python](https://github.com/TheAlgorithms/Python/blob/master/maths/factorial_python.py) + * [Factorial Recursive](https://github.com/TheAlgorithms/Python/blob/master/maths/factorial_recursive.py) + * [Fermat Little Theorem](https://github.com/TheAlgorithms/Python/blob/master/maths/fermat_little_theorem.py) + * [Fibonacci](https://github.com/TheAlgorithms/Python/blob/master/maths/fibonacci.py) + * [Fibonacci Sequence Recursion](https://github.com/TheAlgorithms/Python/blob/master/maths/fibonacci_sequence_recursion.py) + * [Find Max](https://github.com/TheAlgorithms/Python/blob/master/maths/find_max.py) + * [Find Max Recursion](https://github.com/TheAlgorithms/Python/blob/master/maths/find_max_recursion.py) + * [Find Min](https://github.com/TheAlgorithms/Python/blob/master/maths/find_min.py) + * [Find Min Recursion](https://github.com/TheAlgorithms/Python/blob/master/maths/find_min_recursion.py) + * [Gaussian](https://github.com/TheAlgorithms/Python/blob/master/maths/gaussian.py) + * [Greatest Common Divisor](https://github.com/TheAlgorithms/Python/blob/master/maths/greatest_common_divisor.py) + * [Hardy Ramanujanalgo](https://github.com/TheAlgorithms/Python/blob/master/maths/hardy_ramanujanalgo.py) + * [Is Square Free](https://github.com/TheAlgorithms/Python/blob/master/maths/is_square_free.py) + * [Jaccard Similarity](https://github.com/TheAlgorithms/Python/blob/master/maths/jaccard_similarity.py) + * [Karatsuba](https://github.com/TheAlgorithms/Python/blob/master/maths/karatsuba.py) + * [Kth Lexicographic Permutation](https://github.com/TheAlgorithms/Python/blob/master/maths/kth_lexicographic_permutation.py) + * [Largest Of Very Large Numbers](https://github.com/TheAlgorithms/Python/blob/master/maths/largest_of_very_large_numbers.py) + * [Least Common Multiple](https://github.com/TheAlgorithms/Python/blob/master/maths/least_common_multiple.py) + * [Lucas Lehmer Primality Test](https://github.com/TheAlgorithms/Python/blob/master/maths/lucas_lehmer_primality_test.py) + * [Lucas Series](https://github.com/TheAlgorithms/Python/blob/master/maths/lucas_series.py) + * [Matrix Exponentiation](https://github.com/TheAlgorithms/Python/blob/master/maths/matrix_exponentiation.py) + * [Mobius Function](https://github.com/TheAlgorithms/Python/blob/master/maths/mobius_function.py) + * [Modular Exponential](https://github.com/TheAlgorithms/Python/blob/master/maths/modular_exponential.py) + * [Newton Raphson](https://github.com/TheAlgorithms/Python/blob/master/maths/newton_raphson.py) + * [Polynomial Evaluation](https://github.com/TheAlgorithms/Python/blob/master/maths/polynomial_evaluation.py) + * [Prime Check](https://github.com/TheAlgorithms/Python/blob/master/maths/prime_check.py) + * [Prime Factors](https://github.com/TheAlgorithms/Python/blob/master/maths/prime_factors.py) + * [Prime Sieve Eratosthenes](https://github.com/TheAlgorithms/Python/blob/master/maths/prime_sieve_eratosthenes.py) + * [Qr Decomposition](https://github.com/TheAlgorithms/Python/blob/master/maths/qr_decomposition.py) + * [Quadratic Equations Complex Numbers](https://github.com/TheAlgorithms/Python/blob/master/maths/quadratic_equations_complex_numbers.py) + * [Radix2 Fft](https://github.com/TheAlgorithms/Python/blob/master/maths/radix2_fft.py) + * [Runge Kutta](https://github.com/TheAlgorithms/Python/blob/master/maths/runge_kutta.py) + * [Segmented Sieve](https://github.com/TheAlgorithms/Python/blob/master/maths/segmented_sieve.py) + * [Sieve Of Eratosthenes](https://github.com/TheAlgorithms/Python/blob/master/maths/sieve_of_eratosthenes.py) + * [Simpson Rule](https://github.com/TheAlgorithms/Python/blob/master/maths/simpson_rule.py) + * [Softmax](https://github.com/TheAlgorithms/Python/blob/master/maths/softmax.py) + * [Sum Of Arithmetic Series](https://github.com/TheAlgorithms/Python/blob/master/maths/sum_of_arithmetic_series.py) + * [Test Prime Check](https://github.com/TheAlgorithms/Python/blob/master/maths/test_prime_check.py) + * [Trapezoidal Rule](https://github.com/TheAlgorithms/Python/blob/master/maths/trapezoidal_rule.py) + * [Volume](https://github.com/TheAlgorithms/Python/blob/master/maths/volume.py) + * [Zellers Congruence](https://github.com/TheAlgorithms/Python/blob/master/maths/zellers_congruence.py) ## Matrix - -- [matrix operation](https://github.com/TheAlgorithms/Python/blob/master/matrix/matrix_operation.py) -- [nth fibonacci using matrix exponentiation](https://github.com/TheAlgorithms/Python/blob/master/matrix/nth_fibonacci_using_matrix_exponentiation.py) -- [rotate matrix](https://github.com/TheAlgorithms/Python/blob/master/matrix/rotate_matrix.py) -- [searching in sorted matrix](https://github.com/TheAlgorithms/Python/blob/master/matrix/searching_in_sorted_matrix.py) -- [spiral print](https://github.com/TheAlgorithms/Python/blob/master/matrix/spiral_print.py) -- Tests - - [test matrix operation](https://github.com/TheAlgorithms/Python/blob/master/matrix/tests/test_matrix_operation.py) + * [Matrix Class](https://github.com/TheAlgorithms/Python/blob/master/matrix/matrix_class.py) + * [Matrix Operation](https://github.com/TheAlgorithms/Python/blob/master/matrix/matrix_operation.py) + * [Nth Fibonacci Using Matrix Exponentiation](https://github.com/TheAlgorithms/Python/blob/master/matrix/nth_fibonacci_using_matrix_exponentiation.py) + * [Rotate Matrix](https://github.com/TheAlgorithms/Python/blob/master/matrix/rotate_matrix.py) + * [Searching In Sorted Matrix](https://github.com/TheAlgorithms/Python/blob/master/matrix/searching_in_sorted_matrix.py) + * [Sherman Morrison](https://github.com/TheAlgorithms/Python/blob/master/matrix/sherman_morrison.py) + * [Spiral Print](https://github.com/TheAlgorithms/Python/blob/master/matrix/spiral_print.py) + * Tests + * [Test Matrix Operation](https://github.com/TheAlgorithms/Python/blob/master/matrix/tests/test_matrix_operation.py) ## Networking Flow - -- [ford fulkerson](https://github.com/TheAlgorithms/Python/blob/master/networking_flow/ford_fulkerson.py) -- [minimum cut](https://github.com/TheAlgorithms/Python/blob/master/networking_flow/minimum_cut.py) + * [Ford Fulkerson](https://github.com/TheAlgorithms/Python/blob/master/networking_flow/ford_fulkerson.py) + * [Minimum Cut](https://github.com/TheAlgorithms/Python/blob/master/networking_flow/minimum_cut.py) ## Neural Network - -- [back propagation neural network](https://github.com/TheAlgorithms/Python/blob/master/neural_network/back_propagation_neural_network.py) -- [convolution neural network](https://github.com/TheAlgorithms/Python/blob/master/neural_network/convolution_neural_network.py) -- [fully connected neural network](https://github.com/TheAlgorithms/Python/blob/master/neural_network/fully_connected_neural_network.ipynb) -- [perceptron](https://github.com/TheAlgorithms/Python/blob/master/neural_network/perceptron.py) + * [Back Propagation Neural Network](https://github.com/TheAlgorithms/Python/blob/master/neural_network/back_propagation_neural_network.py) + * [Convolution Neural Network](https://github.com/TheAlgorithms/Python/blob/master/neural_network/convolution_neural_network.py) + * [Perceptron](https://github.com/TheAlgorithms/Python/blob/master/neural_network/perceptron.py) ## Other - -- [anagrams](https://github.com/TheAlgorithms/Python/blob/master/other/anagrams.py) -- [binary exponentiation](https://github.com/TheAlgorithms/Python/blob/master/other/binary_exponentiation.py) -- [binary exponentiation 2](https://github.com/TheAlgorithms/Python/blob/master/other/binary_exponentiation_2.py) -- [detecting english programmatically](https://github.com/TheAlgorithms/Python/blob/master/other/detecting_english_programmatically.py) -- [euclidean gcd](https://github.com/TheAlgorithms/Python/blob/master/other/euclidean_gcd.py) -- [fischer yates shuffle](https://github.com/TheAlgorithms/Python/blob/master/other/fischer_yates_shuffle.py) -- [food wastage analysis from 1961-2013 fao](https://github.com/TheAlgorithms/Python/blob/master/other/food_wastage_analysis_from_1961-2013_fao.ipynb) -- [frequency finder](https://github.com/TheAlgorithms/Python/blob/master/other/frequency_finder.py) -- [game of life](https://github.com/TheAlgorithms/Python/blob/master/other/game_of_life.py) -- [linear congruential generator](https://github.com/TheAlgorithms/Python/blob/master/other/linear_congruential_generator.py) -- [nested brackets](https://github.com/TheAlgorithms/Python/blob/master/other/nested_brackets.py) -- [palindrome](https://github.com/TheAlgorithms/Python/blob/master/other/palindrome.py) -- [password generator](https://github.com/TheAlgorithms/Python/blob/master/other/password_generator.py) -- [primelib](https://github.com/TheAlgorithms/Python/blob/master/other/primelib.py) -- [sierpinski triangle](https://github.com/TheAlgorithms/Python/blob/master/other/sierpinski_triangle.py) -- [tower of hanoi](https://github.com/TheAlgorithms/Python/blob/master/other/tower_of_hanoi.py) -- [two sum](https://github.com/TheAlgorithms/Python/blob/master/other/two_sum.py) -- [word patterns](https://github.com/TheAlgorithms/Python/blob/master/other/word_patterns.py) + * [Activity Selection](https://github.com/TheAlgorithms/Python/blob/master/other/activity_selection.py) + * [Anagrams](https://github.com/TheAlgorithms/Python/blob/master/other/anagrams.py) + * [Autocomplete Using Trie](https://github.com/TheAlgorithms/Python/blob/master/other/autocomplete_using_trie.py) + * [Binary Exponentiation](https://github.com/TheAlgorithms/Python/blob/master/other/binary_exponentiation.py) + * [Binary Exponentiation 2](https://github.com/TheAlgorithms/Python/blob/master/other/binary_exponentiation_2.py) + * [Detecting English Programmatically](https://github.com/TheAlgorithms/Python/blob/master/other/detecting_english_programmatically.py) + * [Euclidean Gcd](https://github.com/TheAlgorithms/Python/blob/master/other/euclidean_gcd.py) + * [Fischer Yates Shuffle](https://github.com/TheAlgorithms/Python/blob/master/other/fischer_yates_shuffle.py) + * [Food Wastage Analysis From 1961-2013 Fao](https://github.com/TheAlgorithms/Python/blob/master/other/food_wastage_analysis_from_1961-2013_fao.ipynb) + * [Frequency Finder](https://github.com/TheAlgorithms/Python/blob/master/other/frequency_finder.py) + * [Game Of Life](https://github.com/TheAlgorithms/Python/blob/master/other/game_of_life.py) + * [Greedy](https://github.com/TheAlgorithms/Python/blob/master/other/greedy.py) + * [Largest Subarray Sum](https://github.com/TheAlgorithms/Python/blob/master/other/largest_subarray_sum.py) + * [Linear Congruential Generator](https://github.com/TheAlgorithms/Python/blob/master/other/linear_congruential_generator.py) + * [Magicdiamondpattern](https://github.com/TheAlgorithms/Python/blob/master/other/magicdiamondpattern.py) + * [Nested Brackets](https://github.com/TheAlgorithms/Python/blob/master/other/nested_brackets.py) + * [Palindrome](https://github.com/TheAlgorithms/Python/blob/master/other/palindrome.py) + * [Password Generator](https://github.com/TheAlgorithms/Python/blob/master/other/password_generator.py) + * [Primelib](https://github.com/TheAlgorithms/Python/blob/master/other/primelib.py) + * [Sierpinski Triangle](https://github.com/TheAlgorithms/Python/blob/master/other/sierpinski_triangle.py) + * [Tower Of Hanoi](https://github.com/TheAlgorithms/Python/blob/master/other/tower_of_hanoi.py) + * [Two Sum](https://github.com/TheAlgorithms/Python/blob/master/other/two_sum.py) + * [Word Patterns](https://github.com/TheAlgorithms/Python/blob/master/other/word_patterns.py) ## Project Euler - -- Problem 01 - - [sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_01/sol1.py) - - [sol2](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_01/sol2.py) - - [sol3](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_01/sol3.py) - - [sol4](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_01/sol4.py) - - [sol5](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_01/sol5.py) - - [sol6](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_01/sol6.py) -- Problem 02 - - [sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_02/sol1.py) - - [sol2](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_02/sol2.py) - - [sol3](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_02/sol3.py) - - [sol4](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_02/sol4.py) -- Problem 03 - - [sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_03/sol1.py) - - [sol2](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_03/sol2.py) -- Problem 04 - - [sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_04/sol1.py) - - [sol2](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_04/sol2.py) -- Problem 05 - - [sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_05/sol1.py) - - [sol2](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_05/sol2.py) -- Problem 06 - - [sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_06/sol1.py) - - [sol2](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_06/sol2.py) - - [sol3](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_06/sol3.py) -- Problem 07 - - [sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_07/sol1.py) - - [sol2](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_07/sol2.py) - - [sol3](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_07/sol3.py) -- Problem 08 - - [sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_08/sol1.py) - - [sol2](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_08/sol2.py) -- Problem 09 - - [sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_09/sol1.py) - - [sol2](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_09/sol2.py) - - [sol3](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_09/sol3.py) -- Problem 10 - - [sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_10/sol1.py) - - [sol2](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_10/sol2.py) - - [sol3](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_10/sol3.py) -- Problem 11 - - [sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_11/sol1.py) - - [sol2](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_11/sol2.py) -- Problem 12 - - [sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_12/sol1.py) - - [sol2](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_12/sol2.py) -- Problem 13 - - [sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_13/sol1.py) -- Problem 14 - - [sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_14/sol1.py) - - [sol2](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_14/sol2.py) -- Problem 15 - - [sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_15/sol1.py) -- Problem 16 - - [sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_16/sol1.py) - - [sol2](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_16/sol2.py) -- Problem 17 - - [sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_17/sol1.py) -- Problem 18 - - [sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_18/sol1.py) -- Problem 19 - - [sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_19/sol1.py) -- Problem 20 - - [sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_20/sol1.py) - - [sol2](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_20/sol2.py) -- Problem 21 - - [sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_21/sol1.py) -- Problem 22 - - [sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_22/sol1.py) - - [sol2](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_22/sol2.py) -- Problem 234 - - [sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_234/sol1.py) -- Problem 24 - - [sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_24/sol1.py) -- Problem 25 - - [sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_25/sol1.py) - - [sol2](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_25/sol2.py) -- Problem 28 - - [sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_28/sol1.py) -- Problem 29 - - [solution](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_28/solution.py) -- Problem 31 - - [sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_31/sol1.py) -- Problem 36 - - [sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_36/sol1.py) -- Problem 40 - - [sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_40/sol1.py) -- Problem 48 - - [sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_48/sol1.py) -- Problem 52 - - [sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_52/sol1.py) -- Problem 53 - - [sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_53/sol1.py) -- Problem 56 - - [sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_56/sol1.py) -- Problem 76 - - [sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_76/sol1.py) + * Problem 01 + * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_01/sol1.py) + * [Sol2](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_01/sol2.py) + * [Sol3](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_01/sol3.py) + * [Sol4](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_01/sol4.py) + * [Sol5](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_01/sol5.py) + * [Sol6](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_01/sol6.py) + * [Sol7](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_01/sol7.py) + * Problem 02 + * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_02/sol1.py) + * [Sol2](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_02/sol2.py) + * [Sol3](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_02/sol3.py) + * [Sol4](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_02/sol4.py) + * [Sol5](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_02/sol5.py) + * Problem 03 + * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_03/sol1.py) + * [Sol2](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_03/sol2.py) + * Problem 04 + * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_04/sol1.py) + * [Sol2](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_04/sol2.py) + * Problem 05 + * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_05/sol1.py) + * [Sol2](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_05/sol2.py) + * Problem 06 + * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_06/sol1.py) + * [Sol2](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_06/sol2.py) + * [Sol3](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_06/sol3.py) + * [Sol4](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_06/sol4.py) + * Problem 07 + * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_07/sol1.py) + * [Sol2](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_07/sol2.py) + * [Sol3](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_07/sol3.py) + * Problem 08 + * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_08/sol1.py) + * [Sol2](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_08/sol2.py) + * Problem 09 + * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_09/sol1.py) + * [Sol2](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_09/sol2.py) + * [Sol3](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_09/sol3.py) + * Problem 10 + * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_10/sol1.py) + * [Sol2](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_10/sol2.py) + * [Sol3](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_10/sol3.py) + * Problem 11 + * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_11/sol1.py) + * [Sol2](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_11/sol2.py) + * Problem 12 + * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_12/sol1.py) + * [Sol2](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_12/sol2.py) + * Problem 13 + * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_13/sol1.py) + * Problem 14 + * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_14/sol1.py) + * [Sol2](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_14/sol2.py) + * Problem 15 + * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_15/sol1.py) + * Problem 16 + * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_16/sol1.py) + * [Sol2](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_16/sol2.py) + * Problem 17 + * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_17/sol1.py) + * Problem 18 + * [Solution](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_18/solution.py) + * Problem 19 + * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_19/sol1.py) + * Problem 20 + * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_20/sol1.py) + * [Sol2](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_20/sol2.py) + * [Sol3](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_20/sol3.py) + * Problem 21 + * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_21/sol1.py) + * Problem 22 + * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_22/sol1.py) + * [Sol2](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_22/sol2.py) + * Problem 23 + * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_23/sol1.py) + * Problem 234 + * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_234/sol1.py) + * Problem 24 + * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_24/sol1.py) + * Problem 25 + * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_25/sol1.py) + * [Sol2](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_25/sol2.py) + * Problem 28 + * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_28/sol1.py) + * Problem 29 + * [Solution](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_29/solution.py) + * Problem 31 + * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_31/sol1.py) + * Problem 32 + * [Sol32](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_32/sol32.py) + * Problem 36 + * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_36/sol1.py) + * Problem 40 + * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_40/sol1.py) + * Problem 42 + * [Solution42](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_42/solution42.py) + * Problem 48 + * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_48/sol1.py) + * Problem 52 + * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_52/sol1.py) + * Problem 53 + * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_53/sol1.py) + * Problem 551 + * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_551/sol1.py) + * Problem 56 + * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_56/sol1.py) + * Problem 67 + * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_67/sol1.py) + * Problem 76 + * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_76/sol1.py) ## Searches - -- [binary search](https://github.com/TheAlgorithms/Python/blob/master/searches/binary_search.py) -- [interpolation search](https://github.com/TheAlgorithms/Python/blob/master/searches/interpolation_search.py) -- [jump search](https://github.com/TheAlgorithms/Python/blob/master/searches/jump_search.py) -- [linear search](https://github.com/TheAlgorithms/Python/blob/master/searches/linear_search.py) -- [quick select](https://github.com/TheAlgorithms/Python/blob/master/searches/quick_select.py) -- [sentinel linear search](https://github.com/TheAlgorithms/Python/blob/master/searches/sentinel_linear_search.py) -- [tabu search](https://github.com/TheAlgorithms/Python/blob/master/searches/tabu_search.py) -- [ternary search](https://github.com/TheAlgorithms/Python/blob/master/searches/ternary_search.py) + * [Binary Search](https://github.com/TheAlgorithms/Python/blob/master/searches/binary_search.py) + * [Fibonacci Search](https://github.com/TheAlgorithms/Python/blob/master/searches/fibonacci_search.py) + * [Interpolation Search](https://github.com/TheAlgorithms/Python/blob/master/searches/interpolation_search.py) + * [Jump Search](https://github.com/TheAlgorithms/Python/blob/master/searches/jump_search.py) + * [Linear Search](https://github.com/TheAlgorithms/Python/blob/master/searches/linear_search.py) + * [Quick Select](https://github.com/TheAlgorithms/Python/blob/master/searches/quick_select.py) + * [Sentinel Linear Search](https://github.com/TheAlgorithms/Python/blob/master/searches/sentinel_linear_search.py) + * [Simple-Binary-Search](https://github.com/TheAlgorithms/Python/blob/master/searches/simple-binary-search.py) + * [Tabu Search](https://github.com/TheAlgorithms/Python/blob/master/searches/tabu_search.py) + * [Ternary Search](https://github.com/TheAlgorithms/Python/blob/master/searches/ternary_search.py) ## Sorts - -- [bitonic sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/bitonic_sort.py) -- [bogo sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/bogo_sort.py) -- [bubble sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/bubble_sort.py) -- [bucket sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/bucket_sort.py) -- [cocktail shaker sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/cocktail_shaker_sort.py) -- [comb sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/comb_sort.py) -- [counting sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/counting_sort.py) -- [cycle sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/cycle_sort.py) -- [external sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/external_sort.py) -- [gnome sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/gnome_sort.py) -- [heap sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/heap_sort.py) -- [insertion sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/insertion_sort.py) -- [merge sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/merge_sort.py) -- [merge sort fastest](https://github.com/TheAlgorithms/Python/blob/master/sorts/merge_sort_fastest.py) -- [odd even transposition parallel](https://github.com/TheAlgorithms/Python/blob/master/sorts/odd_even_transposition_parallel.py) -- [odd even transposition single threaded](https://github.com/TheAlgorithms/Python/blob/master/sorts/odd_even_transposition_single_threaded.py) -- [pancake sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/pancake_sort.py) -- [pigeon sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/pigeon_sort.py) -- [quick sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/quick_sort.py) -- [quick sort 3 partition](https://github.com/TheAlgorithms/Python/blob/master/sorts/quick_sort_3_partition.py) -- [radix sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/radix_sort.py) -- [random normal distribution quicksort](https://github.com/TheAlgorithms/Python/blob/master/sorts/random_normal_distribution_quicksort.py) -- [random pivot quick sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/random_pivot_quick_sort.py) -- [selection sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/selection_sort.py) -- [shell sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/shell_sort.py) -- [tim sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/tim_sort.py) -- [topological sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/topological_sort.py) -- [tree sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/tree_sort.py) -- [wiggle sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/wiggle_sort.py) + * [Bitonic Sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/bitonic_sort.py) + * [Bogo Sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/bogo_sort.py) + * [Bubble Sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/bubble_sort.py) + * [Bucket Sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/bucket_sort.py) + * [Cocktail Shaker Sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/cocktail_shaker_sort.py) + * [Comb Sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/comb_sort.py) + * [Counting Sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/counting_sort.py) + * [Cycle Sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/cycle_sort.py) + * [Double Sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/double_sort.py) + * [External Sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/external_sort.py) + * [Gnome Sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/gnome_sort.py) + * [Heap Sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/heap_sort.py) + * [I Sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/i_sort.py) + * [Insertion Sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/insertion_sort.py) + * [Merge Sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/merge_sort.py) + * [Merge Sort Fastest](https://github.com/TheAlgorithms/Python/blob/master/sorts/merge_sort_fastest.py) + * [Odd Even Transposition Parallel](https://github.com/TheAlgorithms/Python/blob/master/sorts/odd_even_transposition_parallel.py) + * [Odd Even Transposition Single Threaded](https://github.com/TheAlgorithms/Python/blob/master/sorts/odd_even_transposition_single_threaded.py) + * [Pancake Sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/pancake_sort.py) + * [Pigeon Sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/pigeon_sort.py) + * [Quick Sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/quick_sort.py) + * [Quick Sort 3 Partition](https://github.com/TheAlgorithms/Python/blob/master/sorts/quick_sort_3_partition.py) + * [Radix Sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/radix_sort.py) + * [Random Normal Distribution Quicksort](https://github.com/TheAlgorithms/Python/blob/master/sorts/random_normal_distribution_quicksort.py) + * [Random Pivot Quick Sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/random_pivot_quick_sort.py) + * [Selection Sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/selection_sort.py) + * [Shell Sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/shell_sort.py) + * [Stooge Sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/stooge_sort.py) + * [Tim Sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/tim_sort.py) + * [Topological Sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/topological_sort.py) + * [Tree Sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/tree_sort.py) + * [Wiggle Sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/wiggle_sort.py) ## Strings - -- [boyer moore search](https://github.com/TheAlgorithms/Python/blob/master/strings/boyer_moore_search.py) -- [knuth morris pratt](https://github.com/TheAlgorithms/Python/blob/master/strings/knuth_morris_pratt.py) -- [levenshtein distance](https://github.com/TheAlgorithms/Python/blob/master/strings/levenshtein_distance.py) -- [manacher](https://github.com/TheAlgorithms/Python/blob/master/strings/manacher.py) -- [min cost string conversion](https://github.com/TheAlgorithms/Python/blob/master/strings/min_cost_string_conversion.py) -- [naive string search](https://github.com/TheAlgorithms/Python/blob/master/strings/naive_string_search.py) -- [rabin karp](https://github.com/TheAlgorithms/Python/blob/master/strings/rabin_karp.py) + * [Aho-Corasick](https://github.com/TheAlgorithms/Python/blob/master/strings/aho-corasick.py) + * [Boyer Moore Search](https://github.com/TheAlgorithms/Python/blob/master/strings/boyer_moore_search.py) + * [Knuth Morris Pratt](https://github.com/TheAlgorithms/Python/blob/master/strings/knuth_morris_pratt.py) + * [Levenshtein Distance](https://github.com/TheAlgorithms/Python/blob/master/strings/levenshtein_distance.py) + * [Manacher](https://github.com/TheAlgorithms/Python/blob/master/strings/manacher.py) + * [Min Cost String Conversion](https://github.com/TheAlgorithms/Python/blob/master/strings/min_cost_string_conversion.py) + * [Naive String Search](https://github.com/TheAlgorithms/Python/blob/master/strings/naive_string_search.py) + * [Rabin Karp](https://github.com/TheAlgorithms/Python/blob/master/strings/rabin_karp.py) ## Traversals + * [Binary Tree Traversals](https://github.com/TheAlgorithms/Python/blob/master/traversals/binary_tree_traversals.py) -- [binary tree traversals](https://github.com/TheAlgorithms/Python/blob/master/traversals/binary_tree_traversals.py) +## Web Programming + * [Crawl Google Results](https://github.com/TheAlgorithms/Python/blob/master/web_programming/crawl_google_results.py) diff --git a/scripts/build_directory_md.py b/scripts/build_directory_md.py index 2ebd445b3667..1043e6ccb696 100755 --- a/scripts/build_directory_md.py +++ b/scripts/build_directory_md.py @@ -17,7 +17,7 @@ def good_filepaths(top_dir: str = ".") -> Iterator[str]: def md_prefix(i): - return f"{i * ' '}*" if i else "##" + return f"{i * ' '}*" if i else "\n##" def print_path(old_path: str, new_path: str) -> str: @@ -37,7 +37,7 @@ def print_directory_md(top_dir: str = ".") -> None: old_path = print_path(old_path, filepath) indent = (filepath.count(os.sep) + 1) if filepath else 0 url = "/".join((URL_BASE, filepath, filename)).replace(" ", "%20") - filename = os.path.splitext(filename.replace("_", " "))[0] + filename = os.path.splitext(filename.replace("_", " ").title())[0] print(f"{md_prefix(indent)} [{filename}]({url})") From a57809af9c7611ae119b44b3279fee6f7ce7049c Mon Sep 17 00:00:00 2001 From: prathmesh1199 <51616294+prathmesh1199@users.noreply.github.com> Date: Sat, 26 Oct 2019 14:48:28 +0530 Subject: [PATCH 0311/1071] Added binomial coefficient (#1467) * Added binomial coefficient * Format code with psf/black and add a doctest --- maths/binomial_coefficient.py | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 maths/binomial_coefficient.py diff --git a/maths/binomial_coefficient.py b/maths/binomial_coefficient.py new file mode 100644 index 000000000000..4def041492f3 --- /dev/null +++ b/maths/binomial_coefficient.py @@ -0,0 +1,20 @@ +def binomial_coefficient(n, r): + """ + Find binomial coefficient using pascals triangle. + + >>> binomial_coefficient(10, 5) + 252 + """ + C = [0 for i in range(r + 1)] + # nc0 = 1 + C[0] = 1 + for i in range(1, n + 1): + # to compute current row from previous row. + j = min(i, r) + while j > 0: + C[j] += C[j - 1] + j -= 1 + return C[r] + + +print(binomial_coefficient(n=10, r=5)) From bc52aa6d4d5c96322817b5073d5308739f8c188b Mon Sep 17 00:00:00 2001 From: ArjunwadkarAjay <41279300+ArjunwadkarAjay@users.noreply.github.com> Date: Sun, 27 Oct 2019 23:07:25 +0530 Subject: [PATCH 0312/1071] Some grammatical and spelling corrections (#1475) --- maths/collatz_sequence.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/maths/collatz_sequence.py b/maths/collatz_sequence.py index c83da3f0f0e8..a5f044a62b18 100644 --- a/maths/collatz_sequence.py +++ b/maths/collatz_sequence.py @@ -1,16 +1,16 @@ def collatz_sequence(n): """ - Collatz conjecture: start with any positive integer n.Next termis obtained from the previous term as follows: - if the previous term is even, the next term is one half the previous term. + Collatz conjecture: start with any positive integer n.Next term is obtained from the previous term as follows: + if the previous term is even, the next term is one half of the previous term. If the previous term is odd, the next term is 3 times the previous term plus 1. - The conjecture states the sequence will always reach 1 regaardess of starting n. + The conjecture states the sequence will always reach 1 regaardless of starting value n. Example: >>> collatz_sequence(43) [43, 130, 65, 196, 98, 49, 148, 74, 37, 112, 56, 28, 14, 7, 22, 11, 34, 17, 52, 26, 13, 40, 20, 10, 5, 16, 8, 4, 2, 1] """ sequence = [n] while n != 1: - if n % 2 == 0: # even + if n % 2 == 0: # even number condition n //= 2 else: n = 3 * n + 1 From a7078d7c27df2ee1fd096635c06d599a6b20ade9 Mon Sep 17 00:00:00 2001 From: Suad Djelili Date: Sun, 27 Oct 2019 21:07:04 +0300 Subject: [PATCH 0313/1071] added solution 4 for problem_20 (#1476) --- project_euler/problem_20/sol4.py | 40 ++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 project_euler/problem_20/sol4.py diff --git a/project_euler/problem_20/sol4.py b/project_euler/problem_20/sol4.py new file mode 100644 index 000000000000..50ebca5a0bf7 --- /dev/null +++ b/project_euler/problem_20/sol4.py @@ -0,0 +1,40 @@ +""" +n! means n × (n − 1) × ... × 3 × 2 × 1 + +For example, 10! = 10 × 9 × ... × 3 × 2 × 1 = 3628800, +and the sum of the digits in the number 10! is 3 + 6 + 2 + 8 + 8 + 0 + 0 = 27. + +Find the sum of the digits in the number 100! +""" + + +def solution(n): + """Returns the sum of the digits in the number 100! + >>> solution(100) + 648 + >>> solution(50) + 216 + >>> solution(10) + 27 + >>> solution(5) + 3 + >>> solution(3) + 6 + >>> solution(2) + 2 + >>> solution(1) + 1 + """ + fact = 1 + result = 0 + for i in range(1,n + 1): + fact *= i + + for j in str(fact): + result += int(j) + + return result + + +if __name__ == "__main__": + print(solution(int(input("Enter the Number: ").strip()))) From 8b52e442307d3d8a6f18468964381e2c2df16b33 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Orkun=20=C4=B0ncili?= Date: Sun, 27 Oct 2019 23:48:38 +0300 Subject: [PATCH 0314/1071] Python program that listing top 'n' movie in imdb (#1477) * Python program that listing top 'n' movie in imdb * Update get_imdbtop.py --- web_programming/get_imdbtop.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 web_programming/get_imdbtop.py diff --git a/web_programming/get_imdbtop.py b/web_programming/get_imdbtop.py new file mode 100644 index 000000000000..95fbeba7a772 --- /dev/null +++ b/web_programming/get_imdbtop.py @@ -0,0 +1,18 @@ +from bs4 import BeautifulSoup +import requests + + +def imdb_top(imdb_top_n): + base_url = (f"https://www.imdb.com/search/title?title_type=" + f"feature&sort=num_votes,desc&count={imdb_top_n}") + source = BeautifulSoup(requests.get(base_url).content, "html.parser") + for m in source.findAll("div", class_="lister-item mode-advanced"): + print("\n" + m.h3.a.text) # movie's name + print(m.find("span", attrs={"class": "genre"}).text) # genre + print(m.strong.text) # movie's rating + print(f"https://www.imdb.com{m.a.get('href')}") # movie's page link + print("*" * 40) + + +if __name__ == "__main__": + imdb_top(input("How many movies would you like to see? ")) From 39c40e7e40b908d31f7cb1dd9d46f1277bb2e343 Mon Sep 17 00:00:00 2001 From: Suad Djelili Date: Mon, 28 Oct 2019 02:52:34 +0300 Subject: [PATCH 0315/1071] added solution 3 for problem_25 (#1478) * added solution 4 for problem_20 * added solution 3 for problem_25 --- project_euler/problem_25/sol3.py | 57 ++++++++++++++++++++++++++++++++ 1 file changed, 57 insertions(+) create mode 100644 project_euler/problem_25/sol3.py diff --git a/project_euler/problem_25/sol3.py b/project_euler/problem_25/sol3.py new file mode 100644 index 000000000000..4e3084ce5456 --- /dev/null +++ b/project_euler/problem_25/sol3.py @@ -0,0 +1,57 @@ +# -*- coding: utf-8 -*- +""" +The Fibonacci sequence is defined by the recurrence relation: + + Fn = Fn−1 + Fn−2, where F1 = 1 and F2 = 1. + +Hence the first 12 terms will be: + + F1 = 1 + F2 = 1 + F3 = 2 + F4 = 3 + F5 = 5 + F6 = 8 + F7 = 13 + F8 = 21 + F9 = 34 + F10 = 55 + F11 = 89 + F12 = 144 + +The 12th term, F12, is the first term to contain three digits. + +What is the index of the first term in the Fibonacci sequence to contain 1000 +digits? +""" + + +def solution(n): + """Returns the index of the first term in the Fibonacci sequence to contain + n digits. + + >>> solution(1000) + 4782 + >>> solution(100) + 476 + >>> solution(50) + 237 + >>> solution(3) + 12 + """ + f1, f2 = 1, 1 + index = 2 + while True: + i = 0 + f = f1 + f2 + f1, f2 = f2, f + index += 1 + for j in str(f): + i += 1 + if i == n: + break + return index + + +if __name__ == "__main__": + print(solution(int(str(input()).strip()))) From 8a5b1abd0a2a110db80d4a67c0db979ce3c4cf4e Mon Sep 17 00:00:00 2001 From: Deekshaesha <57080015+Deekshaesha@users.noreply.github.com> Date: Mon, 28 Oct 2019 13:44:53 +0530 Subject: [PATCH 0316/1071] finding max (#1488) * Update find_max.py * Update find_max.py * Format with psf/black and add doctests --- maths/find_max.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/maths/find_max.py b/maths/find_max.py index 7cc82aacfb09..8b5ab48e6185 100644 --- a/maths/find_max.py +++ b/maths/find_max.py @@ -2,15 +2,23 @@ def find_max(nums): + """ + >>> for nums in ([3, 2, 1], [-3, -2, -1], [3, -3, 0], [3.0, 3.1, 2.9]): + ... find_max(nums) == max(nums) + True + True + True + True + """ max = nums[0] for x in nums: if x > max: max = x - print(max) + return max def main(): - find_max([2, 4, 9, 7, 19, 94, 5]) + print(find_max([2, 4, 9, 7, 19, 94, 5])) # 94 if __name__ == "__main__": From e36fe34b0b00588d8665b53918d9cdf40d5fee98 Mon Sep 17 00:00:00 2001 From: arjun1299 Date: Mon, 28 Oct 2019 17:36:28 +0530 Subject: [PATCH 0317/1071] Hard coded inputs to mixed_keyword cypher (#1500) * Update morse_code_implementation.py * Delete porta_cipher.py * Update mixed_keyword_cypher.py * Mixed keyword cypher added * issue with mixed keyword fixed * no math included * hardcoded inputs * porta cypher added * porta cypher added * commented in mixed keyword according to contrib.md --- ciphers/mixed_keyword_cypher.py | 71 +++++++++++++++++++++++++++++++++ 1 file changed, 71 insertions(+) create mode 100644 ciphers/mixed_keyword_cypher.py diff --git a/ciphers/mixed_keyword_cypher.py b/ciphers/mixed_keyword_cypher.py new file mode 100644 index 000000000000..c8d3ad6a535f --- /dev/null +++ b/ciphers/mixed_keyword_cypher.py @@ -0,0 +1,71 @@ +def mixed_keyword(key="college", pt="UNIVERSITY"): + """ + + For key:hello + + H E L O + A B C D + F G I J + K M N P + Q R S T + U V W X + Y Z + and map vertically + + >>> mixed_keyword("college", "UNIVERSITY") # doctest: +NORMALIZE_WHITESPACE + {'A': 'C', 'B': 'A', 'C': 'I', 'D': 'P', 'E': 'U', 'F': 'Z', 'G': 'O', 'H': 'B', + 'I': 'J', 'J': 'Q', 'K': 'V', 'L': 'L', 'M': 'D', 'N': 'K', 'O': 'R', 'P': 'W', + 'Q': 'E', 'R': 'F', 'S': 'M', 'T': 'S', 'U': 'X', 'V': 'G', 'W': 'H', 'X': 'N', + 'Y': 'T', 'Z': 'Y'} + 'XKJGUFMJST' + """ + key = key.upper() + pt = pt.upper() + temp = [] + for i in key: + if i not in temp: + temp.append(i) + l = len(temp) + # print(temp) + alpha = [] + modalpha = [] + # modalpha.append(temp) + dic = dict() + c = 0 + for i in range(65, 91): + t = chr(i) + alpha.append(t) + if t not in temp: + temp.append(t) + # print(temp) + r = int(26 / 4) + # print(r) + k = 0 + for i in range(r): + t = [] + for j in range(l): + t.append(temp[k]) + if not (k < 25): + break + k += 1 + modalpha.append(t) + # print(modalpha) + d = dict() + j = 0 + k = 0 + for j in range(l): + for i in modalpha: + if not (len(i) - 1 >= j): + break + d[alpha[k]] = i[j] + if not k < 25: + break + k += 1 + print(d) + cypher = "" + for i in pt: + cypher += d[i] + return cypher + + +print(mixed_keyword("college", "UNIVERSITY")) From bf50ea09aea7ab24fe6591b0542c19e4bac0f87a Mon Sep 17 00:00:00 2001 From: Christian Clauss Date: Mon, 28 Oct 2019 13:38:08 +0100 Subject: [PATCH 0318/1071] Add Travis_CI_tests_are_failing.md (#1499) * Add Travis_CI_tests_are_failing.md * Update Travis_CI_tests_are_failing.md --- Travis_CI_tests_are_failing.md | 9 +++++++++ images/Travis_CI_fail_1.png | Bin 0 -> 80257 bytes images/Travis_CI_fail_2.png | Bin 0 -> 45660 bytes 3 files changed, 9 insertions(+) create mode 100644 Travis_CI_tests_are_failing.md create mode 100644 images/Travis_CI_fail_1.png create mode 100644 images/Travis_CI_fail_2.png diff --git a/Travis_CI_tests_are_failing.md b/Travis_CI_tests_are_failing.md new file mode 100644 index 000000000000..10bf5a6655d2 --- /dev/null +++ b/Travis_CI_tests_are_failing.md @@ -0,0 +1,9 @@ +# Travis CI test are failing +### How do I find out what is wrong with my pull request? +1. In your PR look for the failing test and click the `Details` link: ![Travis_CI_fail_1.png](images/Travis_CI_fail_1.png) +2. On the next page, click `The build failed` link: ![Travis_CI_fail_2.png](images/Travis_CI_fail_2.png) +3. Now scroll down and look for `red` text describing the error(s) in the test log. + +Pull requests will __not__ be merged if the Travis CI tests are failing. + +If anything is unclear, please read through [CONTRIBUTING.md](CONTRIBUTING.md) and attempt to run the failing tests on your computer before asking for assistance. diff --git a/images/Travis_CI_fail_1.png b/images/Travis_CI_fail_1.png new file mode 100644 index 0000000000000000000000000000000000000000..451e54e4844a0b9f7d501c3d7682869d9acd97fc GIT binary patch literal 80257 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 literal 0 HcmV?d00001 diff --git a/images/Travis_CI_fail_2.png b/images/Travis_CI_fail_2.png new file mode 100644 index 0000000000000000000000000000000000000000..caa406099da12215efb783b13ae780c4c052fe7d GIT binary patch literal 45660 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 Date: Mon, 28 Oct 2019 20:50:36 +0800 Subject: [PATCH 0319/1071] rename and add doctest (#1501) --- maths/find_max.py | 8 ++++---- maths/find_min.py | 30 +++++++++++++++++++----------- 2 files changed, 23 insertions(+), 15 deletions(-) diff --git a/maths/find_max.py b/maths/find_max.py index 8b5ab48e6185..4d92e37eb2e1 100644 --- a/maths/find_max.py +++ b/maths/find_max.py @@ -10,11 +10,11 @@ def find_max(nums): True True """ - max = nums[0] + max_num = nums[0] for x in nums: - if x > max: - max = x - return max + if x > max_num: + max_num = x + return max_num def main(): diff --git a/maths/find_min.py b/maths/find_min.py index e24982a9369b..4d721ce82194 100644 --- a/maths/find_min.py +++ b/maths/find_min.py @@ -1,17 +1,25 @@ -"""Find Minimum Number in a List.""" +def find_min(nums): + """ + Find Minimum Number in a List + :param nums: contains elements + :return: max number in list + >>> for nums in ([3, 2, 1], [-3, -2, -1], [3, -3, 0], [3.0, 3.1, 2.9]): + ... find_min(nums) == min(nums) + True + True + True + True + """ + min_num = nums[0] + for num in nums: + if min_num > num: + min_num = num + return min_num -def main(): - """Find Minimum Number in a List.""" - - def find_min(x): - min_num = x[0] - for i in x: - if min_num > i: - min_num = i - return min_num - print(find_min([0, 1, 2, 3, 4, 5, -3, 24, -56])) # = -56 +def main(): + assert find_min([0, 1, 2, 3, 4, 5, -3, 24, -56]) == -56 if __name__ == "__main__": From 1da1ab0773e8cb74693dc53b29a0511f83c94098 Mon Sep 17 00:00:00 2001 From: John Law Date: Tue, 29 Oct 2019 00:44:57 +0800 Subject: [PATCH 0320/1071] Improve doctest and comment for maximum sub-array problem (#1503) * Doctest and comment for maximum sub-array problem More examples and description for max_sub_array.py * Update max_sub_array.py * Update max_sub_array.py * Fix doctest --- dynamic_programming/max_sub_array.py | 25 ++++++++++++++++++------- 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/dynamic_programming/max_sub_array.py b/dynamic_programming/max_sub_array.py index eb6ab41bf52d..f7c8209718ef 100644 --- a/dynamic_programming/max_sub_array.py +++ b/dynamic_programming/max_sub_array.py @@ -2,9 +2,6 @@ author : Mayank Kumar Jha (mk9440) """ from typing import List -import time -import matplotlib.pyplot as plt -from random import randint def find_max_sub_array(A, low, high): @@ -43,15 +40,23 @@ def find_max_cross_sum(A, low, mid, high): def max_sub_array(nums: List[int]) -> int: """ - Finds the contiguous subarray (can be empty array) - which has the largest sum and return its sum. + Finds the contiguous subarray which has the largest sum and return its sum. - >>> max_sub_array([-2,1,-3,4,-1,2,1,-5,4]) + >>> max_sub_array([-2, 1, -3, 4, -1, 2, 1, -5, 4]) 6 + + An empty (sub)array has sum 0. >>> max_sub_array([]) 0 - >>> max_sub_array([-1,-2,-3]) + + If all elements are negative, the largest subarray would be the empty array, + having the sum 0. + >>> max_sub_array([-1, -2, -3]) 0 + >>> max_sub_array([5, -2, -3]) + 5 + >>> max_sub_array([31, -41, 59, 26, -53, 58, 97, -93, -23, 84]) + 187 """ best = 0 current = 0 @@ -64,6 +69,12 @@ def max_sub_array(nums: List[int]) -> int: if __name__ == "__main__": + """ + A random simulation of this algorithm. + """ + import time + import matplotlib.pyplot as plt + from random import randint inputs = [10, 100, 1000, 10000, 50000, 100000, 200000, 300000, 400000, 500000] tim = [] for i in inputs: From 3ada8bb580d64a06d88a3613ba31e41b010f2a45 Mon Sep 17 00:00:00 2001 From: Phileas Date: Mon, 28 Oct 2019 14:04:26 -0400 Subject: [PATCH 0321/1071] Page replacement algorithm, LRU (#871) * Page replacement algorithm, LRU * small rectifications * Rename paging/LRU.py to other/least_recently_used.py --- other/least_recently_used.py | 62 ++++++++++++++++++++++++++++++++++++ 1 file changed, 62 insertions(+) create mode 100644 other/least_recently_used.py diff --git a/other/least_recently_used.py b/other/least_recently_used.py new file mode 100644 index 000000000000..2932e9c185e8 --- /dev/null +++ b/other/least_recently_used.py @@ -0,0 +1,62 @@ +from abc import abstractmethod +import sys +from collections import deque + +class LRUCache: + """ Page Replacement Algorithm, Least Recently Used (LRU) Caching.""" + + dq_store = object() # Cache store of keys + key_reference_map = object() # References of the keys in cache + _MAX_CAPACITY: int = 10 # Maximum capacity of cache + + @abstractmethod + def __init__(self, n: int): + """ Creates an empty store and map for the keys. + The LRUCache is set the size n. + """ + self.dq_store = deque() + self.key_reference_map = set() + if not n: + LRUCache._MAX_CAPACITY = sys.maxsize + elif n < 0: + raise ValueError('n should be an integer greater than 0.') + else: + LRUCache._MAX_CAPACITY = n + + def refer(self, x): + """ + Looks for a page in the cache store and adds reference to the set. + Remove the least recently used key if the store is full. + Update store to reflect recent access. + """ + if x not in self.key_reference_map: + if len(self.dq_store) == LRUCache._MAX_CAPACITY: + last_element = self.dq_store.pop() + self.key_reference_map.remove(last_element) + else: + index_remove = 0 + for idx, key in enumerate(self.dq_store): + if key == x: + index_remove = idx + break + self.dq_store.remove(index_remove) + + self.dq_store.appendleft(x) + self.key_reference_map.add(x) + + def display(self): + """ + Prints all the elements in the store. + """ + for k in self.dq_store: + print(k) + +if __name__ == "__main__": + lru_cache = LRUCache(4) + lru_cache.refer(1) + lru_cache.refer(2) + lru_cache.refer(3) + lru_cache.refer(1) + lru_cache.refer(4) + lru_cache.refer(5) + lru_cache.display() From f8a30b42cea154d190544216b92d204c832de784 Mon Sep 17 00:00:00 2001 From: dimgrichr <32580033+dimgrichr@users.noreply.github.com> Date: Mon, 28 Oct 2019 20:27:00 +0200 Subject: [PATCH 0322/1071] Addition of Secant Method (#876) * Add files via upload * Update secant_method.py * Remove unused import * Remove unused import --- arithmetic_analysis/secant_method.py | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 arithmetic_analysis/secant_method.py diff --git a/arithmetic_analysis/secant_method.py b/arithmetic_analysis/secant_method.py new file mode 100644 index 000000000000..b05d44c627d8 --- /dev/null +++ b/arithmetic_analysis/secant_method.py @@ -0,0 +1,28 @@ +# Implementing Secant method in Python +# Author: dimgrichr + + +from math import exp + + +def f(x): + """ + >>> f(5) + 39.98652410600183 + """ + return 8 * x - 2 * exp(-x) + + +def SecantMethod(lower_bound, upper_bound, repeats): + """ + >>> SecantMethod(1, 3, 2) + 0.2139409276214589 + """ + x0 = lower_bound + x1 = upper_bound + for i in range(0, repeats): + x0, x1 = x1, x1 - (f(x1) * (x1 - x0)) / (f(x1) - f(x0)) + return x1 + + +print(f"The solution is: {SecantMethod(1, 3, 2)}") From 4f86f5848275bf87437a698531b6069f8e4938b8 Mon Sep 17 00:00:00 2001 From: Devil Lord <46564519+DevilLord9967@users.noreply.github.com> Date: Mon, 28 Oct 2019 23:59:08 +0530 Subject: [PATCH 0323/1071] Create GAN.py (#1445) * Create GAN.py * gan update * Delete train-labels-idx1-ubyte.gz * Update GAN.py * Update GAN.py * Delete GAN.py * Create gan.py * Update gan.py * input_data import file --- neural_network/gan.py | 391 +++++++++++++++++++++++++++++++++++ neural_network/input_data.py | 332 +++++++++++++++++++++++++++++ 2 files changed, 723 insertions(+) create mode 100644 neural_network/gan.py create mode 100644 neural_network/input_data.py diff --git a/neural_network/gan.py b/neural_network/gan.py new file mode 100644 index 000000000000..edfff420547b --- /dev/null +++ b/neural_network/gan.py @@ -0,0 +1,391 @@ +import matplotlib.gridspec as gridspec +import matplotlib.pyplot as plt +import numpy as np +from sklearn.utils import shuffle +import input_data + +random_numer = 42 + +np.random.seed(random_numer) +def ReLu(x): + mask = (x>0) * 1.0 + return mask *x +def d_ReLu(x): + mask = (x>0) * 1.0 + return mask + +def arctan(x): + return np.arctan(x) +def d_arctan(x): + return 1 / (1 + x ** 2) + +def log(x): + return 1 / ( 1+ np.exp(-1*x)) +def d_log(x): + return log(x) * (1 - log(x)) + +def tanh(x): + return np.tanh(x) +def d_tanh(x): + return 1 - np.tanh(x) ** 2 + +def plot(samples): + fig = plt.figure(figsize=(4, 4)) + gs = gridspec.GridSpec(4, 4) + gs.update(wspace=0.05, hspace=0.05) + + for i, sample in enumerate(samples): + ax = plt.subplot(gs[i]) + plt.axis('off') + ax.set_xticklabels([]) + ax.set_yticklabels([]) + ax.set_aspect('equal') + plt.imshow(sample.reshape(28, 28), cmap='Greys_r') + + return fig + + + +# 1. Load Data and declare hyper +print('--------- Load Data ----------') +mnist = input_data.read_data_sets('MNIST_data', one_hot=False) +temp = mnist.test +images, labels = temp.images, temp.labels +images, labels = shuffle(np.asarray(images),np.asarray(labels)) +num_epoch = 10 +learing_rate = 0.00009 +G_input = 100 +hidden_input,hidden_input2,hidden_input3 = 128,256,346 +hidden_input4,hidden_input5,hidden_input6 = 480,560,686 + + + +print('--------- Declare Hyper Parameters ----------') +# 2. Declare Weights +D_W1 = np.random.normal(size=(784,hidden_input),scale=(1. / np.sqrt(784 / 2.))) *0.002 +# D_b1 = np.random.normal(size=(128),scale=(1. / np.sqrt(128 / 2.))) *0.002 +D_b1 = np.zeros(hidden_input) + +D_W2 = np.random.normal(size=(hidden_input,1),scale=(1. / np.sqrt(hidden_input / 2.))) *0.002 +# D_b2 = np.random.normal(size=(1),scale=(1. / np.sqrt(1 / 2.))) *0.002 +D_b2 = np.zeros(1) + + +G_W1 = np.random.normal(size=(G_input,hidden_input),scale=(1. / np.sqrt(G_input / 2.))) *0.002 +# G_b1 = np.random.normal(size=(128),scale=(1. / np.sqrt(128 / 2.))) *0.002 +G_b1 = np.zeros(hidden_input) + +G_W2 = np.random.normal(size=(hidden_input,hidden_input2),scale=(1. / np.sqrt(hidden_input / 2.))) *0.002 +# G_b1 = np.random.normal(size=(128),scale=(1. / np.sqrt(128 / 2.))) *0.002 +G_b2 = np.zeros(hidden_input2) + +G_W3 = np.random.normal(size=(hidden_input2,hidden_input3),scale=(1. / np.sqrt(hidden_input2 / 2.))) *0.002 +# G_b1 = np.random.normal(size=(128),scale=(1. / np.sqrt(128 / 2.))) *0.002 +G_b3 = np.zeros(hidden_input3) + +G_W4 = np.random.normal(size=(hidden_input3,hidden_input4),scale=(1. / np.sqrt(hidden_input3 / 2.))) *0.002 +# G_b1 = np.random.normal(size=(128),scale=(1. / np.sqrt(128 / 2.))) *0.002 +G_b4 = np.zeros(hidden_input4) + +G_W5 = np.random.normal(size=(hidden_input4,hidden_input5),scale=(1. / np.sqrt(hidden_input4 / 2.))) *0.002 +# G_b1 = np.random.normal(size=(128),scale=(1. / np.sqrt(128 / 2.))) *0.002 +G_b5 = np.zeros(hidden_input5) + +G_W6 = np.random.normal(size=(hidden_input5,hidden_input6),scale=(1. / np.sqrt(hidden_input5 / 2.))) *0.002 +# G_b1 = np.random.normal(size=(128),scale=(1. / np.sqrt(128 / 2.))) *0.002 +G_b6 = np.zeros(hidden_input6) + +G_W7 = np.random.normal(size=(hidden_input6,784),scale=(1. / np.sqrt(hidden_input6 / 2.))) *0.002 +# G_b2 = np.random.normal(size=(784),scale=(1. / np.sqrt(784 / 2.))) *0.002 +G_b7 = np.zeros(784) + +# 3. For Adam Optimzier +v1,m1 = 0,0 +v2,m2 = 0,0 +v3,m3 = 0,0 +v4,m4 = 0,0 + +v5,m5 = 0,0 +v6,m6 = 0,0 +v7,m7 = 0,0 +v8,m8 = 0,0 +v9,m9 = 0,0 +v10,m10 = 0,0 +v11,m11 = 0,0 +v12,m12 = 0,0 + +v13,m13 = 0,0 +v14,m14 = 0,0 + +v15,m15 = 0,0 +v16,m16 = 0,0 + +v17,m17 = 0,0 +v18,m18 = 0,0 + + +beta_1,beta_2,eps = 0.9,0.999,0.00000001 + +print('--------- Started Training ----------') +for iter in range(num_epoch): + + random_int = np.random.randint(len(images) - 5) + current_image = np.expand_dims(images[random_int],axis=0) + + # Func: Generate The first Fake Data + Z = np.random.uniform(-1., 1., size=[1, G_input]) + Gl1 = Z.dot(G_W1) + G_b1 + Gl1A = arctan(Gl1) + Gl2 = Gl1A.dot(G_W2) + G_b2 + Gl2A = ReLu(Gl2) + Gl3 = Gl2A.dot(G_W3) + G_b3 + Gl3A = arctan(Gl3) + + Gl4 = Gl3A.dot(G_W4) + G_b4 + Gl4A = ReLu(Gl4) + Gl5 = Gl4A.dot(G_W5) + G_b5 + Gl5A = tanh(Gl5) + Gl6 = Gl5A.dot(G_W6) + G_b6 + Gl6A = ReLu(Gl6) + Gl7 = Gl6A.dot(G_W7) + G_b7 + + current_fake_data = log(Gl7) + + # Func: Forward Feed for Real data + Dl1_r = current_image.dot(D_W1) + D_b1 + Dl1_rA = ReLu(Dl1_r) + Dl2_r = Dl1_rA.dot(D_W2) + D_b2 + Dl2_rA = log(Dl2_r) + + # Func: Forward Feed for Fake Data + Dl1_f = current_fake_data.dot(D_W1) + D_b1 + Dl1_fA = ReLu(Dl1_f) + Dl2_f = Dl1_fA.dot(D_W2) + D_b2 + Dl2_fA = log(Dl2_f) + + # Func: Cost D + D_cost = -np.log(Dl2_rA) + np.log(1.0- Dl2_fA) + + # Func: Gradient + grad_f_w2_part_1 = 1/(1.0- Dl2_fA) + grad_f_w2_part_2 = d_log(Dl2_f) + grad_f_w2_part_3 = Dl1_fA + grad_f_w2 = grad_f_w2_part_3.T.dot(grad_f_w2_part_1 * grad_f_w2_part_2) + grad_f_b2 = grad_f_w2_part_1 * grad_f_w2_part_2 + + grad_f_w1_part_1 = (grad_f_w2_part_1 * grad_f_w2_part_2).dot(D_W2.T) + grad_f_w1_part_2 = d_ReLu(Dl1_f) + grad_f_w1_part_3 = current_fake_data + grad_f_w1 = grad_f_w1_part_3.T.dot(grad_f_w1_part_1 * grad_f_w1_part_2) + grad_f_b1 = grad_f_w1_part_1 * grad_f_w1_part_2 + + grad_r_w2_part_1 = - 1/Dl2_rA + grad_r_w2_part_2 = d_log(Dl2_r) + grad_r_w2_part_3 = Dl1_rA + grad_r_w2 = grad_r_w2_part_3.T.dot(grad_r_w2_part_1 * grad_r_w2_part_2) + grad_r_b2 = grad_r_w2_part_1 * grad_r_w2_part_2 + + grad_r_w1_part_1 = (grad_r_w2_part_1 * grad_r_w2_part_2).dot(D_W2.T) + grad_r_w1_part_2 = d_ReLu(Dl1_r) + grad_r_w1_part_3 = current_image + grad_r_w1 = grad_r_w1_part_3.T.dot(grad_r_w1_part_1 * grad_r_w1_part_2) + grad_r_b1 = grad_r_w1_part_1 * grad_r_w1_part_2 + + grad_w1 =grad_f_w1 + grad_r_w1 + grad_b1 =grad_f_b1 + grad_r_b1 + + grad_w2 =grad_f_w2 + grad_r_w2 + grad_b2 =grad_f_b2 + grad_r_b2 + + # ---- Update Gradient ---- + m1 = beta_1 * m1 + (1 - beta_1) * grad_w1 + v1 = beta_2 * v1 + (1 - beta_2) * grad_w1 ** 2 + + m2 = beta_1 * m2 + (1 - beta_1) * grad_b1 + v2 = beta_2 * v2 + (1 - beta_2) * grad_b1 ** 2 + + m3 = beta_1 * m3 + (1 - beta_1) * grad_w2 + v3 = beta_2 * v3 + (1 - beta_2) * grad_w2 ** 2 + + m4 = beta_1 * m4 + (1 - beta_1) * grad_b2 + v4 = beta_2 * v4 + (1 - beta_2) * grad_b2 ** 2 + + D_W1 = D_W1 - (learing_rate / (np.sqrt(v1 /(1-beta_2) ) + eps)) * (m1/(1-beta_1)) + D_b1 = D_b1 - (learing_rate / (np.sqrt(v2 /(1-beta_2) ) + eps)) * (m2/(1-beta_1)) + + D_W2 = D_W2 - (learing_rate / (np.sqrt(v3 /(1-beta_2) ) + eps)) * (m3/(1-beta_1)) + D_b2 = D_b2 - (learing_rate / (np.sqrt(v4 /(1-beta_2) ) + eps)) * (m4/(1-beta_1)) + + # Func: Forward Feed for G + Z = np.random.uniform(-1., 1., size=[1, G_input]) + Gl1 = Z.dot(G_W1) + G_b1 + Gl1A = arctan(Gl1) + Gl2 = Gl1A.dot(G_W2) + G_b2 + Gl2A = ReLu(Gl2) + Gl3 = Gl2A.dot(G_W3) + G_b3 + Gl3A = arctan(Gl3) + + Gl4 = Gl3A.dot(G_W4) + G_b4 + Gl4A = ReLu(Gl4) + Gl5 = Gl4A.dot(G_W5) + G_b5 + Gl5A = tanh(Gl5) + Gl6 = Gl5A.dot(G_W6) + G_b6 + Gl6A = ReLu(Gl6) + Gl7 = Gl6A.dot(G_W7) + G_b7 + + current_fake_data = log(Gl7) + + Dl1 = current_fake_data.dot(D_W1) + D_b1 + Dl1_A = ReLu(Dl1) + Dl2 = Dl1_A.dot(D_W2) + D_b2 + Dl2_A = log(Dl2) + + # Func: Cost G + G_cost = -np.log(Dl2_A) + + # Func: Gradient + grad_G_w7_part_1 = ((-1/Dl2_A) * d_log(Dl2).dot(D_W2.T) * (d_ReLu(Dl1))).dot(D_W1.T) + grad_G_w7_part_2 = d_log(Gl7) + grad_G_w7_part_3 = Gl6A + grad_G_w7 = grad_G_w7_part_3.T.dot(grad_G_w7_part_1 * grad_G_w7_part_1) + grad_G_b7 = grad_G_w7_part_1 * grad_G_w7_part_2 + + grad_G_w6_part_1 = (grad_G_w7_part_1 * grad_G_w7_part_2).dot(G_W7.T) + grad_G_w6_part_2 = d_ReLu(Gl6) + grad_G_w6_part_3 = Gl5A + grad_G_w6 = grad_G_w6_part_3.T.dot(grad_G_w6_part_1 * grad_G_w6_part_2) + grad_G_b6 = (grad_G_w6_part_1 * grad_G_w6_part_2) + + grad_G_w5_part_1 = (grad_G_w6_part_1 * grad_G_w6_part_2).dot(G_W6.T) + grad_G_w5_part_2 = d_tanh(Gl5) + grad_G_w5_part_3 = Gl4A + grad_G_w5 = grad_G_w5_part_3.T.dot(grad_G_w5_part_1 * grad_G_w5_part_2) + grad_G_b5 = (grad_G_w5_part_1 * grad_G_w5_part_2) + + grad_G_w4_part_1 = (grad_G_w5_part_1 * grad_G_w5_part_2).dot(G_W5.T) + grad_G_w4_part_2 = d_ReLu(Gl4) + grad_G_w4_part_3 = Gl3A + grad_G_w4 = grad_G_w4_part_3.T.dot(grad_G_w4_part_1 * grad_G_w4_part_2) + grad_G_b4 = (grad_G_w4_part_1 * grad_G_w4_part_2) + + grad_G_w3_part_1 = (grad_G_w4_part_1 * grad_G_w4_part_2).dot(G_W4.T) + grad_G_w3_part_2 = d_arctan(Gl3) + grad_G_w3_part_3 = Gl2A + grad_G_w3 = grad_G_w3_part_3.T.dot(grad_G_w3_part_1 * grad_G_w3_part_2) + grad_G_b3 = (grad_G_w3_part_1 * grad_G_w3_part_2) + + grad_G_w2_part_1 = (grad_G_w3_part_1 * grad_G_w3_part_2).dot(G_W3.T) + grad_G_w2_part_2 = d_ReLu(Gl2) + grad_G_w2_part_3 = Gl1A + grad_G_w2 = grad_G_w2_part_3.T.dot(grad_G_w2_part_1 * grad_G_w2_part_2) + grad_G_b2 = (grad_G_w2_part_1 * grad_G_w2_part_2) + + grad_G_w1_part_1 = (grad_G_w2_part_1 * grad_G_w2_part_2).dot(G_W2.T) + grad_G_w1_part_2 = d_arctan(Gl1) + grad_G_w1_part_3 = Z + grad_G_w1 = grad_G_w1_part_3.T.dot(grad_G_w1_part_1 * grad_G_w1_part_2) + grad_G_b1 = grad_G_w1_part_1 * grad_G_w1_part_2 + + # ---- Update Gradient ---- + m5 = beta_1 * m5 + (1 - beta_1) * grad_G_w1 + v5 = beta_2 * v5 + (1 - beta_2) * grad_G_w1 ** 2 + + m6 = beta_1 * m6 + (1 - beta_1) * grad_G_b1 + v6 = beta_2 * v6 + (1 - beta_2) * grad_G_b1 ** 2 + + m7 = beta_1 * m7 + (1 - beta_1) * grad_G_w2 + v7 = beta_2 * v7 + (1 - beta_2) * grad_G_w2 ** 2 + + m8 = beta_1 * m8 + (1 - beta_1) * grad_G_b2 + v8 = beta_2 * v8 + (1 - beta_2) * grad_G_b2 ** 2 + + m9 = beta_1 * m9 + (1 - beta_1) * grad_G_w3 + v9 = beta_2 * v9 + (1 - beta_2) * grad_G_w3 ** 2 + + m10 = beta_1 * m10 + (1 - beta_1) * grad_G_b3 + v10 = beta_2 * v10 + (1 - beta_2) * grad_G_b3 ** 2 + + m11 = beta_1 * m11 + (1 - beta_1) * grad_G_w4 + v11 = beta_2 * v11 + (1 - beta_2) * grad_G_w4 ** 2 + + m12 = beta_1 * m12 + (1 - beta_1) * grad_G_b4 + v12 = beta_2 * v12 + (1 - beta_2) * grad_G_b4 ** 2 + + m13 = beta_1 * m13 + (1 - beta_1) * grad_G_w5 + v13 = beta_2 * v13 + (1 - beta_2) * grad_G_w5 ** 2 + + m14 = beta_1 * m14 + (1 - beta_1) * grad_G_b5 + v14 = beta_2 * v14 + (1 - beta_2) * grad_G_b5 ** 2 + + m15 = beta_1 * m15 + (1 - beta_1) * grad_G_w6 + v15 = beta_2 * v15 + (1 - beta_2) * grad_G_w6 ** 2 + + m16 = beta_1 * m16 + (1 - beta_1) * grad_G_b6 + v16 = beta_2 * v16 + (1 - beta_2) * grad_G_b6 ** 2 + + m17 = beta_1 * m17 + (1 - beta_1) * grad_G_w7 + v17 = beta_2 * v17 + (1 - beta_2) * grad_G_w7 ** 2 + + m18 = beta_1 * m18 + (1 - beta_1) * grad_G_b7 + v18 = beta_2 * v18 + (1 - beta_2) * grad_G_b7 ** 2 + + G_W1 = G_W1 - (learing_rate / (np.sqrt(v5 /(1-beta_2) ) + eps)) * (m5/(1-beta_1)) + G_b1 = G_b1 - (learing_rate / (np.sqrt(v6 /(1-beta_2) ) + eps)) * (m6/(1-beta_1)) + + G_W2 = G_W2 - (learing_rate / (np.sqrt(v7 /(1-beta_2) ) + eps)) * (m7/(1-beta_1)) + G_b2 = G_b2 - (learing_rate / (np.sqrt(v8 /(1-beta_2) ) + eps)) * (m8/(1-beta_1)) + + G_W3 = G_W3 - (learing_rate / (np.sqrt(v9 /(1-beta_2) ) + eps)) * (m9/(1-beta_1)) + G_b3 = G_b3 - (learing_rate / (np.sqrt(v10 /(1-beta_2) ) + eps)) * (m10/(1-beta_1)) + + G_W4 = G_W4 - (learing_rate / (np.sqrt(v11 /(1-beta_2) ) + eps)) * (m11/(1-beta_1)) + G_b4 = G_b4 - (learing_rate / (np.sqrt(v12 /(1-beta_2) ) + eps)) * (m12/(1-beta_1)) + + G_W5 = G_W5 - (learing_rate / (np.sqrt(v13 /(1-beta_2) ) + eps)) * (m13/(1-beta_1)) + G_b5 = G_b5 - (learing_rate / (np.sqrt(v14 /(1-beta_2) ) + eps)) * (m14/(1-beta_1)) + + G_W6 = G_W6 - (learing_rate / (np.sqrt(v15 /(1-beta_2) ) + eps)) * (m15/(1-beta_1)) + G_b6 = G_b6 - (learing_rate / (np.sqrt(v16 /(1-beta_2) ) + eps)) * (m16/(1-beta_1)) + + G_W7 = G_W7 - (learing_rate / (np.sqrt(v17 /(1-beta_2) ) + eps)) * (m17/(1-beta_1)) + G_b7 = G_b7 - (learing_rate / (np.sqrt(v18 /(1-beta_2) ) + eps)) * (m18/(1-beta_1)) + + # --- Print Error ---- + #print("Current Iter: ",iter, " Current D cost:",D_cost, " Current G cost: ", G_cost,end='\r') + + if iter == 0: + learing_rate = learing_rate * 0.01 + if iter == 40: + learing_rate = learing_rate * 0.01 + + # ---- Print to Out put ---- + if iter%10 == 0: + + print("Current Iter: ",iter, " Current D cost:",D_cost, " Current G cost: ", G_cost,end='\r') + print('--------- Show Example Result See Tab Above ----------') + print('--------- Wait for the image to load ---------') + Z = np.random.uniform(-1., 1., size=[16, G_input]) + + Gl1 = Z.dot(G_W1) + G_b1 + Gl1A = arctan(Gl1) + Gl2 = Gl1A.dot(G_W2) + G_b2 + Gl2A = ReLu(Gl2) + Gl3 = Gl2A.dot(G_W3) + G_b3 + Gl3A = arctan(Gl3) + + Gl4 = Gl3A.dot(G_W4) + G_b4 + Gl4A = ReLu(Gl4) + Gl5 = Gl4A.dot(G_W5) + G_b5 + Gl5A = tanh(Gl5) + Gl6 = Gl5A.dot(G_W6) + G_b6 + Gl6A = ReLu(Gl6) + Gl7 = Gl6A.dot(G_W7) + G_b7 + + current_fake_data = log(Gl7) + + fig = plot(current_fake_data) + fig.savefig('Click_Me_{}.png'.format(str(iter).zfill(3)+"_Ginput_"+str(G_input)+ \ + "_hiddenone"+str(hidden_input) + "_hiddentwo"+str(hidden_input2) + "_LR_" + str(learing_rate) + ), bbox_inches='tight') +#for complete explanation visit https://towardsdatascience.com/only-numpy-implementing-gan-general-adversarial-networks-and-adam-optimizer-using-numpy-with-2a7e4e032021 +# -- end code -- diff --git a/neural_network/input_data.py b/neural_network/input_data.py new file mode 100644 index 000000000000..983063f0b72d --- /dev/null +++ b/neural_network/input_data.py @@ -0,0 +1,332 @@ +# Copyright 2016 The TensorFlow Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================== +"""Functions for downloading and reading MNIST data (deprecated). + +This module and all its submodules are deprecated. +""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import collections +import gzip +import os + +import numpy +from six.moves import urllib +from six.moves import xrange # pylint: disable=redefined-builtin + +from tensorflow.python.framework import dtypes +from tensorflow.python.framework import random_seed +from tensorflow.python.platform import gfile +from tensorflow.python.util.deprecation import deprecated + +_Datasets = collections.namedtuple('_Datasets', ['train', 'validation', 'test']) + +# CVDF mirror of http://yann.lecun.com/exdb/mnist/ +DEFAULT_SOURCE_URL = 'https://storage.googleapis.com/cvdf-datasets/mnist/' + + +def _read32(bytestream): + dt = numpy.dtype(numpy.uint32).newbyteorder('>') + return numpy.frombuffer(bytestream.read(4), dtype=dt)[0] + + +@deprecated(None, 'Please use tf.data to implement this functionality.') +def _extract_images(f): + """Extract the images into a 4D uint8 numpy array [index, y, x, depth]. + + Args: + f: A file object that can be passed into a gzip reader. + + Returns: + data: A 4D uint8 numpy array [index, y, x, depth]. + + Raises: + ValueError: If the bytestream does not start with 2051. + + """ + print('Extracting', f.name) + with gzip.GzipFile(fileobj=f) as bytestream: + magic = _read32(bytestream) + if magic != 2051: + raise ValueError('Invalid magic number %d in MNIST image file: %s' % + (magic, f.name)) + num_images = _read32(bytestream) + rows = _read32(bytestream) + cols = _read32(bytestream) + buf = bytestream.read(rows * cols * num_images) + data = numpy.frombuffer(buf, dtype=numpy.uint8) + data = data.reshape(num_images, rows, cols, 1) + return data + + +@deprecated(None, 'Please use tf.one_hot on tensors.') +def _dense_to_one_hot(labels_dense, num_classes): + """Convert class labels from scalars to one-hot vectors.""" + num_labels = labels_dense.shape[0] + index_offset = numpy.arange(num_labels) * num_classes + labels_one_hot = numpy.zeros((num_labels, num_classes)) + labels_one_hot.flat[index_offset + labels_dense.ravel()] = 1 + return labels_one_hot + + +@deprecated(None, 'Please use tf.data to implement this functionality.') +def _extract_labels(f, one_hot=False, num_classes=10): + """Extract the labels into a 1D uint8 numpy array [index]. + + Args: + f: A file object that can be passed into a gzip reader. + one_hot: Does one hot encoding for the result. + num_classes: Number of classes for the one hot encoding. + + Returns: + labels: a 1D uint8 numpy array. + + Raises: + ValueError: If the bystream doesn't start with 2049. + """ + print('Extracting', f.name) + with gzip.GzipFile(fileobj=f) as bytestream: + magic = _read32(bytestream) + if magic != 2049: + raise ValueError('Invalid magic number %d in MNIST label file: %s' % + (magic, f.name)) + num_items = _read32(bytestream) + buf = bytestream.read(num_items) + labels = numpy.frombuffer(buf, dtype=numpy.uint8) + if one_hot: + return _dense_to_one_hot(labels, num_classes) + return labels + + +class _DataSet(object): + """Container class for a _DataSet (deprecated). + + THIS CLASS IS DEPRECATED. + """ + + @deprecated(None, 'Please use alternatives such as official/mnist/_DataSet.py' + ' from tensorflow/models.') + def __init__(self, + images, + labels, + fake_data=False, + one_hot=False, + dtype=dtypes.float32, + reshape=True, + seed=None): + """Construct a _DataSet. + + one_hot arg is used only if fake_data is true. `dtype` can be either + `uint8` to leave the input as `[0, 255]`, or `float32` to rescale into + `[0, 1]`. Seed arg provides for convenient deterministic testing. + + Args: + images: The images + labels: The labels + fake_data: Ignore inages and labels, use fake data. + one_hot: Bool, return the labels as one hot vectors (if True) or ints (if + False). + dtype: Output image dtype. One of [uint8, float32]. `uint8` output has + range [0,255]. float32 output has range [0,1]. + reshape: Bool. If True returned images are returned flattened to vectors. + seed: The random seed to use. + """ + seed1, seed2 = random_seed.get_seed(seed) + # If op level seed is not set, use whatever graph level seed is returned + numpy.random.seed(seed1 if seed is None else seed2) + dtype = dtypes.as_dtype(dtype).base_dtype + if dtype not in (dtypes.uint8, dtypes.float32): + raise TypeError('Invalid image dtype %r, expected uint8 or float32' % + dtype) + if fake_data: + self._num_examples = 10000 + self.one_hot = one_hot + else: + assert images.shape[0] == labels.shape[0], ( + 'images.shape: %s labels.shape: %s' % (images.shape, labels.shape)) + self._num_examples = images.shape[0] + + # Convert shape from [num examples, rows, columns, depth] + # to [num examples, rows*columns] (assuming depth == 1) + if reshape: + assert images.shape[3] == 1 + images = images.reshape(images.shape[0], + images.shape[1] * images.shape[2]) + if dtype == dtypes.float32: + # Convert from [0, 255] -> [0.0, 1.0]. + images = images.astype(numpy.float32) + images = numpy.multiply(images, 1.0 / 255.0) + self._images = images + self._labels = labels + self._epochs_completed = 0 + self._index_in_epoch = 0 + + @property + def images(self): + return self._images + + @property + def labels(self): + return self._labels + + @property + def num_examples(self): + return self._num_examples + + @property + def epochs_completed(self): + return self._epochs_completed + + def next_batch(self, batch_size, fake_data=False, shuffle=True): + """Return the next `batch_size` examples from this data set.""" + if fake_data: + fake_image = [1] * 784 + if self.one_hot: + fake_label = [1] + [0] * 9 + else: + fake_label = 0 + return [fake_image for _ in xrange(batch_size) + ], [fake_label for _ in xrange(batch_size)] + start = self._index_in_epoch + # Shuffle for the first epoch + if self._epochs_completed == 0 and start == 0 and shuffle: + perm0 = numpy.arange(self._num_examples) + numpy.random.shuffle(perm0) + self._images = self.images[perm0] + self._labels = self.labels[perm0] + # Go to the next epoch + if start + batch_size > self._num_examples: + # Finished epoch + self._epochs_completed += 1 + # Get the rest examples in this epoch + rest_num_examples = self._num_examples - start + images_rest_part = self._images[start:self._num_examples] + labels_rest_part = self._labels[start:self._num_examples] + # Shuffle the data + if shuffle: + perm = numpy.arange(self._num_examples) + numpy.random.shuffle(perm) + self._images = self.images[perm] + self._labels = self.labels[perm] + # Start next epoch + start = 0 + self._index_in_epoch = batch_size - rest_num_examples + end = self._index_in_epoch + images_new_part = self._images[start:end] + labels_new_part = self._labels[start:end] + return numpy.concatenate((images_rest_part, images_new_part), + axis=0), numpy.concatenate( + (labels_rest_part, labels_new_part), axis=0) + else: + self._index_in_epoch += batch_size + end = self._index_in_epoch + return self._images[start:end], self._labels[start:end] + + +@deprecated(None, 'Please write your own downloading logic.') +def _maybe_download(filename, work_directory, source_url): + """Download the data from source url, unless it's already here. + + Args: + filename: string, name of the file in the directory. + work_directory: string, path to working directory. + source_url: url to download from if file doesn't exist. + + Returns: + Path to resulting file. + """ + if not gfile.Exists(work_directory): + gfile.MakeDirs(work_directory) + filepath = os.path.join(work_directory, filename) + if not gfile.Exists(filepath): + urllib.request.urlretrieve(source_url, filepath) + with gfile.GFile(filepath) as f: + size = f.size() + print('Successfully downloaded', filename, size, 'bytes.') + return filepath + + +@deprecated(None, 'Please use alternatives such as:' + ' tensorflow_datasets.load(\'mnist\')') +def read_data_sets(train_dir, + fake_data=False, + one_hot=False, + dtype=dtypes.float32, + reshape=True, + validation_size=5000, + seed=None, + source_url=DEFAULT_SOURCE_URL): + if fake_data: + + def fake(): + return _DataSet([], [], + fake_data=True, + one_hot=one_hot, + dtype=dtype, + seed=seed) + + train = fake() + validation = fake() + test = fake() + return _Datasets(train=train, validation=validation, test=test) + + if not source_url: # empty string check + source_url = DEFAULT_SOURCE_URL + + train_images_file = 'train-images-idx3-ubyte.gz' + train_labels_file = 'train-labels-idx1-ubyte.gz' + test_images_file = 't10k-images-idx3-ubyte.gz' + test_labels_file = 't10k-labels-idx1-ubyte.gz' + + local_file = _maybe_download(train_images_file, train_dir, + source_url + train_images_file) + with gfile.Open(local_file, 'rb') as f: + train_images = _extract_images(f) + + local_file = _maybe_download(train_labels_file, train_dir, + source_url + train_labels_file) + with gfile.Open(local_file, 'rb') as f: + train_labels = _extract_labels(f, one_hot=one_hot) + + local_file = _maybe_download(test_images_file, train_dir, + source_url + test_images_file) + with gfile.Open(local_file, 'rb') as f: + test_images = _extract_images(f) + + local_file = _maybe_download(test_labels_file, train_dir, + source_url + test_labels_file) + with gfile.Open(local_file, 'rb') as f: + test_labels = _extract_labels(f, one_hot=one_hot) + + if not 0 <= validation_size <= len(train_images): + raise ValueError( + 'Validation size should be between 0 and {}. Received: {}.'.format( + len(train_images), validation_size)) + + validation_images = train_images[:validation_size] + validation_labels = train_labels[:validation_size] + train_images = train_images[validation_size:] + train_labels = train_labels[validation_size:] + + options = dict(dtype=dtype, reshape=reshape, seed=seed) + + train = _DataSet(train_images, train_labels, **options) + validation = _DataSet(validation_images, validation_labels, **options) + test = _DataSet(test_images, test_labels, **options) + + return _Datasets(train=train, validation=validation, test=test) From e463c0b5739ccabe2694829d410ef095d794b91c Mon Sep 17 00:00:00 2001 From: Kumar-Nishchay <56668486+Kumar-Nishchay@users.noreply.github.com> Date: Tue, 29 Oct 2019 12:16:29 +0530 Subject: [PATCH 0324/1071] Update dictionary.txt (#1507) Added a new word Microfinance. This is one of the recently added word in oxford dictionary --- other/dictionary.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/other/dictionary.txt b/other/dictionary.txt index 14528efe844f..75838996ba08 100644 --- a/other/dictionary.txt +++ b/other/dictionary.txt @@ -26131,6 +26131,7 @@ MICROECONOMICS MICROELECTRONICS MICROFILM MICROFILMS +MICROFINANCE MICROGRAMMING MICROINSTRUCTION MICROINSTRUCTIONS @@ -45330,4 +45331,4 @@ ZOROASTER ZOROASTRIAN ZULU ZULUS -ZURICH \ No newline at end of file +ZURICH From e3d4d2bb57ac3d5dabe93cac3957981782bd7d96 Mon Sep 17 00:00:00 2001 From: percy07 <56677891+percy07@users.noreply.github.com> Date: Tue, 29 Oct 2019 15:36:37 +0530 Subject: [PATCH 0325/1071] Update greatest_common_divisor.py (#1513) Add Doctests. --- maths/greatest_common_divisor.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/maths/greatest_common_divisor.py b/maths/greatest_common_divisor.py index 07dddab9aeff..21c427d5b227 100644 --- a/maths/greatest_common_divisor.py +++ b/maths/greatest_common_divisor.py @@ -10,6 +10,16 @@ def greatest_common_divisor(a, b): Calculate Greatest Common Divisor (GCD). >>> greatest_common_divisor(24, 40) 8 + >>> greatest_common_divisor(1, 1) + 1 + >>> greatest_common_divisor(1, 800) + 1 + >>> greatest_common_divisor(11, 37) + 1 + >>> greatest_common_divisor(3, 5) + 1 + >>> greatest_common_divisor(16, 4) + 4 """ return b if a == 0 else greatest_common_divisor(b % a, a) From 1ed47ad6f4b1f38d4d0a52b7f69d43c4a4294cc9 Mon Sep 17 00:00:00 2001 From: percy07 <56677891+percy07@users.noreply.github.com> Date: Tue, 29 Oct 2019 15:52:49 +0530 Subject: [PATCH 0326/1071] Update palindrome.py (#1509) * Update palindrome.py Add Doctests. * Use test_data to drive the testing --- other/palindrome.py | 63 +++++++++++++++++++++++++++++++++++---------- 1 file changed, 49 insertions(+), 14 deletions(-) diff --git a/other/palindrome.py b/other/palindrome.py index 2ca453b64702..dd1fe316f479 100644 --- a/other/palindrome.py +++ b/other/palindrome.py @@ -1,9 +1,31 @@ -# Program to find whether given string is palindrome or not -def is_palindrome(str): +# Algorithms to determine if a string is palindrome + +test_data = { + "MALAYALAM": True, + "String": False, + "rotor": True, + "level": True, + "A": True, + "BB": True, + "ABC": False, + "amanaplanacanalpanama": True, # "a man a plan a canal panama" +} +# Ensure our test data is valid +assert all((key == key[::-1]) is value for key, value in test_data.items()) + + +def is_palindrome(s: str) -> bool: + """ + Return True if s is a palindrome otherwise return False. + + >>> all(is_palindrome(key) is value for key, value in test_data.items()) + True + """ + start_i = 0 - end_i = len(str) - 1 + end_i = len(s) - 1 while start_i < end_i: - if str[start_i] == str[end_i]: + if s[start_i] == s[end_i]: start_i += 1 end_i -= 1 else: @@ -11,21 +33,34 @@ def is_palindrome(str): return True -# Recursive method -def recursive_palindrome(str): - if len(str) <= 1: +def is_palindrome_recursive(s: str) -> bool: + """ + Return True if s is a palindrome otherwise return False. + + >>> all(is_palindrome_recursive(key) is value for key, value in test_data.items()) + True + """ + if len(s) <= 1: return True - if str[0] == str[len(str) - 1]: - return recursive_palindrome(str[1:-1]) + if s[0] == s[len(s) - 1]: + return is_palindrome_recursive(s[1:-1]) else: return False -def main(): - str = "ama" - print(recursive_palindrome(str.lower())) - print(is_palindrome(str.lower())) +def is_palindrome_slice(s: str) -> bool: + """ + Return True if s is a palindrome otherwise return False. + + >>> all(is_palindrome_slice(key) is value for key, value in test_data.items()) + True + """ + return s == s[::-1] if __name__ == "__main__": - main() + for key, value in test_data.items(): + assert is_palindrome(key) is is_palindrome_recursive(key) + assert is_palindrome(key) is is_palindrome_slice(key) + print(f"{key:21} {value}") + print("a man a plan a canal panama") From dfea6f3f0b0dc3ad834c45b1c3a322827b9e1bbf Mon Sep 17 00:00:00 2001 From: Janith Wanniarachchi Date: Tue, 29 Oct 2019 16:23:29 +0530 Subject: [PATCH 0327/1071] :white_check_mark: added tests for Perceptron in Neural Networks (#1506) * :white_check_mark: added tests for Perceptron in Neural Networks * Space * Format code with psf/black --- neural_network/perceptron.py | 113 +++++++++++++++++++++++++++-------- 1 file changed, 88 insertions(+), 25 deletions(-) diff --git a/neural_network/perceptron.py b/neural_network/perceptron.py index 5feb8610a911..d64c0a5c4341 100644 --- a/neural_network/perceptron.py +++ b/neural_network/perceptron.py @@ -1,29 +1,53 @@ """ - Perceptron w = w + N * (d(k) - y) * x(k) - Using perceptron network for oil analysis, - with Measuring of 3 parameters that represent chemical characteristics we can classify the oil, in p1 or p2 + Using perceptron network for oil analysis, with Measuring of 3 parameters + that represent chemical characteristics we can classify the oil, in p1 or p2 p1 = -1 p2 = 1 - """ import random class Perceptron: - def __init__(self, sample, exit, learn_rate=0.01, epoch_number=1000, bias=-1): + def __init__(self, sample, target, learning_rate=0.01, epoch_number=1000, bias=-1): + """ + Initializes a Perceptron network for oil analysis + :param sample: sample dataset of 3 parameters with shape [30,3] + :param target: variable for classification with two possible states -1 or 1 + :param learning_rate: learning rate used in optimizing. + :param epoch_number: number of epochs to train network on. + :param bias: bias value for the network. + """ self.sample = sample - self.exit = exit - self.learn_rate = learn_rate + if len(self.sample) == 0: + raise AttributeError("Sample data can not be empty") + self.target = target + if len(self.target) == 0: + raise AttributeError("Target data can not be empty") + if len(self.sample) != len(self.target): + raise AttributeError( + "Sample data and Target data do not have matching lengths" + ) + self.learning_rate = learning_rate self.epoch_number = epoch_number self.bias = bias self.number_sample = len(sample) - self.col_sample = len(sample[0]) + self.col_sample = len(sample[0]) # number of columns in dataset self.weight = [] - def training(self): + def training(self) -> None: + """ + Trains perceptron for epochs <= given number of epochs + :return: None + >>> data = [[2.0149, 0.6192, 10.9263]] + >>> targets = [-1] + >>> perceptron = Perceptron(data,targets) + >>> perceptron.training() # doctest: +ELLIPSIS + ('\\nEpoch:\\n', ...) + ... + """ for sample in self.sample: sample.insert(0, self.bias) @@ -35,31 +59,47 @@ def training(self): epoch_count = 0 while True: - erro = False + has_misclassified = False for i in range(self.number_sample): u = 0 for j in range(self.col_sample + 1): u = u + self.weight[j] * self.sample[i][j] y = self.sign(u) - if y != self.exit[i]: - + if y != self.target[i]: for j in range(self.col_sample + 1): - self.weight[j] = ( self.weight[j] - + self.learn_rate * (self.exit[i] - y) * self.sample[i][j] + + self.learning_rate + * (self.target[i] - y) + * self.sample[i][j] ) - erro = True + has_misclassified = True # print('Epoch: \n',epoch_count) epoch_count = epoch_count + 1 # if you want controle the epoch or just by erro - if erro == False: + if not has_misclassified: print(("\nEpoch:\n", epoch_count)) print("------------------------\n") # if epoch_count > self.epoch_number or not erro: break - def sort(self, sample): + def sort(self, sample) -> None: + """ + :param sample: example row to classify as P1 or P2 + :return: None + >>> data = [[2.0149, 0.6192, 10.9263]] + >>> targets = [-1] + >>> perceptron = Perceptron(data,targets) + >>> perceptron.training() # doctest:+ELLIPSIS + ('\\nEpoch:\\n', ...) + ... + >>> perceptron.sort([-0.6508, 0.1097, 4.0009]) # doctest: +ELLIPSIS + ('Sample: ', ...) + classification: P1 + + """ + if len(self.sample) == 0: + raise AttributeError("Sample data can not be empty") sample.insert(0, self.bias) u = 0 for i in range(self.col_sample + 1): @@ -74,7 +114,21 @@ def sort(self, sample): print(("Sample: ", sample)) print("classification: P2") - def sign(self, u): + def sign(self, u: float) -> int: + """ + threshold function for classification + :param u: input number + :return: 1 if the input is greater than 0, otherwise -1 + >>> data = [[0],[-0.5],[0.5]] + >>> targets = [1,-1,1] + >>> perceptron = Perceptron(data,targets) + >>> perceptron.sign(0) + 1 + >>> perceptron.sign(-0.5) + -1 + >>> perceptron.sign(0.5) + 1 + """ return 1 if u >= 0 else -1 @@ -144,15 +198,24 @@ def sign(self, u): 1, ] -network = Perceptron( - sample=samples, exit=exit, learn_rate=0.01, epoch_number=1000, bias=-1 -) - -network.training() if __name__ == "__main__": + import doctest + + doctest.testmod() + + network = Perceptron( + sample=samples, target=exit, learning_rate=0.01, epoch_number=1000, bias=-1 + ) + network.training() + print("Finished training perceptron") + print("Enter values to predict or q to exit") while True: sample = [] - for i in range(3): - sample.insert(i, float(input("value: "))) + for i in range(len(samples[0])): + observation = input("value: ").strip() + if observation == "q": + break + observation = float(observation) + sample.insert(i, observation) network.sort(sample) From bfac867e27328eeedf28b3cd4d8f8195d15e44a4 Mon Sep 17 00:00:00 2001 From: Christian Clauss Date: Tue, 29 Oct 2019 21:05:36 +0100 Subject: [PATCH 0328/1071] Add doctests to other/word_patterns.py (#1518) --- other/word_patterns.py | 63 ++++++++++++++++++++++-------------------- 1 file changed, 33 insertions(+), 30 deletions(-) diff --git a/other/word_patterns.py b/other/word_patterns.py index 16089019704b..d229954dea93 100644 --- a/other/word_patterns.py +++ b/other/word_patterns.py @@ -1,41 +1,44 @@ -import pprint, time - - -def getWordPattern(word): +def get_word_pattern(word: str) -> str: + """ + >>> get_word_pattern("pattern") + '0.1.2.2.3.4.5' + >>> get_word_pattern("word pattern") + '0.1.2.3.4.5.6.7.7.8.2.9' + >>> get_word_pattern("get word pattern") + '0.1.2.3.4.5.6.7.3.8.9.2.2.1.6.10' + """ word = word.upper() - nextNum = 0 - letterNums = {} - wordPattern = [] + next_num = 0 + letter_nums = {} + word_pattern = [] for letter in word: - if letter not in letterNums: - letterNums[letter] = str(nextNum) - nextNum += 1 - wordPattern.append(letterNums[letter]) - return ".".join(wordPattern) + if letter not in letter_nums: + letter_nums[letter] = str(next_num) + next_num += 1 + word_pattern.append(letter_nums[letter]) + return ".".join(word_pattern) -def main(): - startTime = time.time() - allPatterns = {} +if __name__ == "__main__": + import pprint + import time - with open("Dictionary.txt") as fo: - wordList = fo.read().split("\n") + start_time = time.time() + with open("dictionary.txt") as in_file: + wordList = in_file.read().splitlines() + all_patterns = {} for word in wordList: - pattern = getWordPattern(word) - - if pattern not in allPatterns: - allPatterns[pattern] = [word] + pattern = get_word_pattern(word) + if pattern in all_patterns: + all_patterns[pattern].append(word) else: - allPatterns[pattern].append(word) + all_patterns[pattern] = [word] - with open("Word Patterns.txt", "w") as fo: - fo.write(pprint.pformat(allPatterns)) + with open("word_patterns.txt", "w") as out_file: + out_file.write(pprint.pformat(all_patterns)) - totalTime = round(time.time() - startTime, 2) - print(("Done! [", totalTime, "seconds ]")) - - -if __name__ == "__main__": - main() + totalTime = round(time.time() - start_time, 2) + print(f"Done! {len(all_patterns):,} word patterns found in {totalTime} seconds.") + # Done! 9,581 word patterns found in 0.58 seconds. From 4880e5479a85fd106d2b2f9c06755a3350c3db2a Mon Sep 17 00:00:00 2001 From: 5ur3 <43802815+5ur3@users.noreply.github.com> Date: Wed, 30 Oct 2019 00:28:51 +0400 Subject: [PATCH 0329/1071] Create prime_numbers.py (#1519) * Create prime_numbers.py * Update prime_numbers.py --- maths/prime_numbers.py | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 maths/prime_numbers.py diff --git a/maths/prime_numbers.py b/maths/prime_numbers.py new file mode 100644 index 000000000000..aafbebe07be9 --- /dev/null +++ b/maths/prime_numbers.py @@ -0,0 +1,28 @@ +"""Prime numbers calculation.""" + + +def primes(max: int) -> int: + """ + Return a list of all primes up to max. + >>> primes(10) + [2, 3, 5, 7] + >>> primes(11) + [2, 3, 5, 7, 11] + >>> primes(25) + [2, 3, 5, 7, 11, 13, 17, 19, 23] + >>> primes(1_000_000)[-1] + 999983 + """ + max += 1 + numbers = [False] * max + ret = [] + for i in range(2, max): + if not numbers[i]: + for j in range(i, max, i): + numbers[j] = True + ret.append(i) + return ret + + +if __name__ == "__main__": + print(primes(int(input("Calculate primes up to:\n>> ")))) From f8e97aa597e71ed7f9d4683d00cc910cdaab3a99 Mon Sep 17 00:00:00 2001 From: percy07 <56677891+percy07@users.noreply.github.com> Date: Wed, 30 Oct 2019 02:24:31 +0530 Subject: [PATCH 0330/1071] Update basic_maths.py (#1517) * Update basic_maths.py Add Doctests. * Update basic_maths.py * Add a space to fix the doctest --- maths/basic_maths.py | 52 +++++++++++++++++------------------- neural_network/perceptron.py | 2 +- 2 files changed, 26 insertions(+), 28 deletions(-) diff --git a/maths/basic_maths.py b/maths/basic_maths.py index 34ffd1031527..d493ca7eb6c9 100644 --- a/maths/basic_maths.py +++ b/maths/basic_maths.py @@ -2,55 +2,56 @@ import math -def prime_factors(n): - """Find Prime Factors.""" +def prime_factors(n: int) -> list: + """Find Prime Factors. + >>> prime_factors(100) + [2, 2, 5, 5] + """ pf = [] while n % 2 == 0: pf.append(2) n = int(n / 2) - for i in range(3, int(math.sqrt(n)) + 1, 2): while n % i == 0: pf.append(i) n = int(n / i) - if n > 2: pf.append(n) - return pf -def number_of_divisors(n): - """Calculate Number of Divisors of an Integer.""" +def number_of_divisors(n: int) -> int: + """Calculate Number of Divisors of an Integer. + >>> number_of_divisors(100) + 9 + """ div = 1 - temp = 1 while n % 2 == 0: temp += 1 n = int(n / 2) - div = div * (temp) - + div *= temp for i in range(3, int(math.sqrt(n)) + 1, 2): temp = 1 while n % i == 0: temp += 1 n = int(n / i) - div = div * (temp) - + div *= temp return div -def sum_of_divisors(n): - """Calculate Sum of Divisors.""" +def sum_of_divisors(n: int) -> int: + """Calculate Sum of Divisors. + >>> sum_of_divisors(100) + 217 + """ s = 1 - temp = 1 while n % 2 == 0: temp += 1 n = int(n / 2) if temp > 1: s *= (2 ** temp - 1) / (2 - 1) - for i in range(3, int(math.sqrt(n)) + 1, 2): temp = 1 while n % i == 0: @@ -58,27 +59,24 @@ def sum_of_divisors(n): n = int(n / i) if temp > 1: s *= (i ** temp - 1) / (i - 1) - - return s + return int(s) -def euler_phi(n): - """Calculte Euler's Phi Function.""" +def euler_phi(n: int) -> int: + """Calculte Euler's Phi Function. + >>> euler_phi(100) + 40 + """ l = prime_factors(n) l = set(l) s = n for x in l: s *= (x - 1) / x - return s + return int(s) -def main(): - """Print the Results of Basic Math Operations.""" +if __name__ == "__main__": print(prime_factors(100)) print(number_of_divisors(100)) print(sum_of_divisors(100)) print(euler_phi(100)) - - -if __name__ == "__main__": - main() diff --git a/neural_network/perceptron.py b/neural_network/perceptron.py index d64c0a5c4341..26d48506254b 100644 --- a/neural_network/perceptron.py +++ b/neural_network/perceptron.py @@ -90,7 +90,7 @@ def sort(self, sample) -> None: >>> data = [[2.0149, 0.6192, 10.9263]] >>> targets = [-1] >>> perceptron = Perceptron(data,targets) - >>> perceptron.training() # doctest:+ELLIPSIS + >>> perceptron.training() # doctest: +ELLIPSIS ('\\nEpoch:\\n', ...) ... >>> perceptron.sort([-0.6508, 0.1097, 4.0009]) # doctest: +ELLIPSIS From 53ff735701db42d2609416ff6772db17383c3f10 Mon Sep 17 00:00:00 2001 From: himanshujain171 <43314193+himanshujain171@users.noreply.github.com> Date: Wed, 30 Oct 2019 04:24:31 +0530 Subject: [PATCH 0331/1071] Factors of a number (#1493) * Factors of a number * Update factors.py * Fix mypy issue in basic_maths.py * Fix mypy error in perceptron.py * def primes(max: int) -> List[int]: * Update binomial_heap.py * Add a space * Remove a space * Add a space --- data_structures/heap/binomial_heap.py | 177 +++++++++++++------------- maths/basic_maths.py | 4 +- maths/factors.py | 18 +++ maths/prime_numbers.py | 6 +- neural_network/perceptron.py | 3 +- 5 files changed, 109 insertions(+), 99 deletions(-) create mode 100644 maths/factors.py diff --git a/data_structures/heap/binomial_heap.py b/data_structures/heap/binomial_heap.py index e1a005487e34..7f570f1c755b 100644 --- a/data_structures/heap/binomial_heap.py +++ b/data_structures/heap/binomial_heap.py @@ -1,7 +1,6 @@ """ - Binomial Heap - - Reference: Advanced Data Structures, Peter Brass +Binomial Heap +Reference: Advanced Data Structures, Peter Brass """ @@ -10,7 +9,7 @@ class Node: Node in a doubly-linked binomial tree, containing: - value - size of left subtree - - link to left, right and parent nodes + - link to left, right and parent nodes """ def __init__(self, val): @@ -23,8 +22,8 @@ def __init__(self, val): def mergeTrees(self, other): """ - In-place merge of two binomial trees of equal size. - Returns the root of the resulting tree + In-place merge of two binomial trees of equal size. + Returns the root of the resulting tree """ assert self.left_tree_size == other.left_tree_size, "Unequal Sizes of Blocks" @@ -47,83 +46,79 @@ def mergeTrees(self, other): class BinomialHeap: - """ - Min-oriented priority queue implemented with the Binomial Heap data - structure implemented with the BinomialHeap class. It supports: - + r""" + Min-oriented priority queue implemented with the Binomial Heap data + structure implemented with the BinomialHeap class. It supports: - Insert element in a heap with n elemnts: Guaranteed logn, amoratized 1 - Merge (meld) heaps of size m and n: O(logn + logm) - - Delete Min: O(logn) + - Delete Min: O(logn) - Peek (return min without deleting it): O(1) - - Example: - - Create a random permutation of 30 integers to be inserted and - 19 of them deleted - >>> import numpy as np - >>> permutation = np.random.permutation(list(range(30))) - - Create a Heap and insert the 30 integers - - __init__() test - >>> first_heap = BinomialHeap() - - 30 inserts - insert() test - >>> for number in permutation: - ... first_heap.insert(number) - - Size test - >>> print(first_heap.size) - 30 - - Deleting - delete() test - >>> for i in range(25): - ... print(first_heap.deleteMin(), end=" ") - 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 - - Create a new Heap - >>> second_heap = BinomialHeap() - >>> vals = [17, 20, 31, 34] - >>> for value in vals: - ... second_heap.insert(value) - - - The heap should have the following structure: - - 17 - / \ - # 31 - / \ - 20 34 - / \ / \ - # # # # - - preOrder() test - >>> print(second_heap.preOrder()) - [(17, 0), ('#', 1), (31, 1), (20, 2), ('#', 3), ('#', 3), (34, 2), ('#', 3), ('#', 3)] - - printing Heap - __str__() test - >>> print(second_heap) - 17 - -# - -31 - --20 - ---# - ---# - --34 - ---# - ---# - - mergeHeaps() test - >>> merged = second_heap.mergeHeaps(first_heap) - >>> merged.peek() - 17 - - values in merged heap; (merge is inplace) - >>> while not first_heap.isEmpty(): - ... print(first_heap.deleteMin(), end=" ") - 17 20 25 26 27 28 29 31 34 - + + Example: + + Create a random permutation of 30 integers to be inserted and 19 of them deleted + >>> import numpy as np + >>> permutation = np.random.permutation(list(range(30))) + + Create a Heap and insert the 30 integers + __init__() test + >>> first_heap = BinomialHeap() + + 30 inserts - insert() test + >>> for number in permutation: + ... first_heap.insert(number) + + Size test + >>> print(first_heap.size) + 30 + + Deleting - delete() test + >>> for i in range(25): + ... print(first_heap.deleteMin(), end=" ") + 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 + + Create a new Heap + >>> second_heap = BinomialHeap() + >>> vals = [17, 20, 31, 34] + >>> for value in vals: + ... second_heap.insert(value) + + + The heap should have the following structure: + + 17 + / \ + # 31 + / \ + 20 34 + / \ / \ + # # # # + + preOrder() test + >>> print(second_heap.preOrder()) + [(17, 0), ('#', 1), (31, 1), (20, 2), ('#', 3), ('#', 3), (34, 2), ('#', 3), ('#', 3)] + + printing Heap - __str__() test + >>> print(second_heap) + 17 + -# + -31 + --20 + ---# + ---# + --34 + ---# + ---# + + mergeHeaps() test + >>> merged = second_heap.mergeHeaps(first_heap) + >>> merged.peek() + 17 + + values in merged heap; (merge is inplace) + >>> while not first_heap.isEmpty(): + ... print(first_heap.deleteMin(), end=" ") + 17 20 25 26 27 28 29 31 34 """ def __init__(self, bottom_root=None, min_node=None, heap_size=0): @@ -133,8 +128,8 @@ def __init__(self, bottom_root=None, min_node=None, heap_size=0): def mergeHeaps(self, other): """ - In-place merge of two binomial heaps. - Both of them become the resulting merged heap + In-place merge of two binomial heaps. + Both of them become the resulting merged heap """ # Empty heaps corner cases @@ -209,7 +204,7 @@ def mergeHeaps(self, other): def insert(self, val): """ - insert a value in the heap + insert a value in the heap """ if self.size == 0: self.bottom_root = Node(val) @@ -251,7 +246,7 @@ def insert(self, val): def peek(self): """ - return min element without deleting it + return min element without deleting it """ return self.min_node.val @@ -260,7 +255,7 @@ def isEmpty(self): def deleteMin(self): """ - delete min element and return it + delete min element and return it """ # assert not self.isEmpty(), "Empty Heap" @@ -363,9 +358,9 @@ def deleteMin(self): def preOrder(self): """ - Returns the Pre-order representation of the heap including - values of nodes plus their level distance from the root; - Empty nodes appear as # + Returns the Pre-order representation of the heap including + values of nodes plus their level distance from the root; + Empty nodes appear as # """ # Find top root top_root = self.bottom_root @@ -378,7 +373,7 @@ def preOrder(self): def __traversal(self, curr_node, preorder, level=0): """ - Pre-order traversal of nodes + Pre-order traversal of nodes """ if curr_node: preorder.append((curr_node.val, level)) @@ -389,8 +384,8 @@ def __traversal(self, curr_node, preorder, level=0): def __str__(self): """ - Overwriting str for a pre-order print of nodes in heap; - Performance is poor, so use only for small examples + Overwriting str for a pre-order print of nodes in heap; + Performance is poor, so use only for small examples """ if self.isEmpty(): return "" diff --git a/maths/basic_maths.py b/maths/basic_maths.py index d493ca7eb6c9..5dbfd250d308 100644 --- a/maths/basic_maths.py +++ b/maths/basic_maths.py @@ -67,10 +67,8 @@ def euler_phi(n: int) -> int: >>> euler_phi(100) 40 """ - l = prime_factors(n) - l = set(l) s = n - for x in l: + for x in set(prime_factors(n)): s *= (x - 1) / x return int(s) diff --git a/maths/factors.py b/maths/factors.py new file mode 100644 index 000000000000..e2fdc4063a13 --- /dev/null +++ b/maths/factors.py @@ -0,0 +1,18 @@ +def factors_of_a_number(num: int) -> list: + """ + >>> factors_of_a_number(1) + [1] + >>> factors_of_a_number(5) + [1, 5] + >>> factors_of_a_number(24) + [1, 2, 3, 4, 6, 8, 12, 24] + >>> factors_of_a_number(-24) + [] + """ + return [i for i in range(1, num + 1) if num % i == 0] + + +if __name__ == "__main__": + num = int(input("Enter a number to find its factors: ")) + factors = factors_of_a_number(num) + print(f"{num} has {len(factors)} factors: {', '.join(str(f) for f in factors)}") diff --git a/maths/prime_numbers.py b/maths/prime_numbers.py index aafbebe07be9..a29a95ea2280 100644 --- a/maths/prime_numbers.py +++ b/maths/prime_numbers.py @@ -1,9 +1,9 @@ -"""Prime numbers calculation.""" +from typing import List -def primes(max: int) -> int: +def primes(max: int) -> List[int]: """ - Return a list of all primes up to max. + Return a list of all primes numbers up to max. >>> primes(10) [2, 3, 5, 7] >>> primes(11) diff --git a/neural_network/perceptron.py b/neural_network/perceptron.py index 26d48506254b..3610dd2ab227 100644 --- a/neural_network/perceptron.py +++ b/neural_network/perceptron.py @@ -95,8 +95,7 @@ def sort(self, sample) -> None: ... >>> perceptron.sort([-0.6508, 0.1097, 4.0009]) # doctest: +ELLIPSIS ('Sample: ', ...) - classification: P1 - + classification: P... """ if len(self.sample) == 0: raise AttributeError("Sample data can not be empty") From 63433616c9a8f690e416c32dc01ff00bc515d9e2 Mon Sep 17 00:00:00 2001 From: LokiUvaraj <54948079+LokiUvaraj@users.noreply.github.com> Date: Wed, 30 Oct 2019 04:56:28 +0530 Subject: [PATCH 0332/1071] average_mode.py (#1491) * Add files via upload Finds the mode in the input data. * Update average_mode.py * Update average_mode.py * Update average_mode.py * Update average_mode.py * Update average_mode.py * Update average_mode.py * Tabs do not belong in Python files! --- maths/average_mode.py | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 maths/average_mode.py diff --git a/maths/average_mode.py b/maths/average_mode.py new file mode 100644 index 000000000000..c1a4b3521448 --- /dev/null +++ b/maths/average_mode.py @@ -0,0 +1,31 @@ +import statistics + + +def mode(input_list): # Defining function "mode." + """This function returns the mode(Mode as in the measures of + central tendency) of the input data. + + The input list may contain any Datastructure or any Datatype. + + >>> input_list = [2, 3, 4, 5, 3, 4, 2, 5, 2, 2, 4, 2, 2, 2] + >>> mode(input_list) + 2 + >>> input_list = [2, 3, 4, 5, 3, 4, 2, 5, 2, 2, 4, 2, 2, 2] + >>> mode(input_list) == statistics.mode(input_list) + True + """ + # Copying inputlist to check with the index number later. + check_list = input_list.copy() + result = list() # Empty list to store the counts of elements in input_list + for x in input_list: + result.append(input_list.count(x)) + input_list.remove(x) + y = max(result) # Gets the maximum value in the result list. + # Returns the value with the maximum number of repetitions. + return check_list[result.index(y)] + + +if __name__ == "__main__": + data = [2, 3, 4, 5, 3, 4, 2, 5, 2, 2, 4, 2, 2, 2] + print(mode(data)) + print(statistics.mode(data)) From fc533a759878f90f38c4d8226a9e828724fc2a2e Mon Sep 17 00:00:00 2001 From: Sri Suma <41474384+srisumapasumarthi@users.noreply.github.com> Date: Wed, 30 Oct 2019 17:22:20 +0530 Subject: [PATCH 0333/1071] Simplified DES (#1382) * Simplified DES * Add files via upload Diffie Hellman algorithm to generate a secret key. * Update sdes.py * Format code with psf/black and add doctests --- ciphers/diffie.py | 26 +++++++++++++ other/sdes.py | 97 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 123 insertions(+) create mode 100644 ciphers/diffie.py create mode 100644 other/sdes.py diff --git a/ciphers/diffie.py b/ciphers/diffie.py new file mode 100644 index 000000000000..6b0cca1f45e6 --- /dev/null +++ b/ciphers/diffie.py @@ -0,0 +1,26 @@ +def find_primitive(n): + for r in range(1, n): + li = [] + for x in range(n-1): + val = pow(r,x,n) + if val in li: + break + li.append(val) + else: + return r + + +if __name__ == "__main__": + q = int(input('Enter a prime number q: ')) + a = find_primitive(q) + a_private = int(input('Enter private key of A: ')) + a_public = pow(a, a_private, q) + b_private = int(input('Enter private key of B: ')) + b_public = pow(a, b_private, q) + + a_secret = pow(b_public, a_private, q) + b_secret = pow(a_public, b_private, q) + + print('The key value generated by A is: ', a_secret) + print('The key value generated by B is: ', b_secret) + diff --git a/other/sdes.py b/other/sdes.py new file mode 100644 index 000000000000..3038ff193ae9 --- /dev/null +++ b/other/sdes.py @@ -0,0 +1,97 @@ +def apply_table(inp, table): + """ + >>> apply_table("0123456789", list(range(10))) + '9012345678' + >>> apply_table("0123456789", list(range(9, -1, -1))) + '8765432109' + """ + res = "" + for i in table: + res += inp[i - 1] + return res + + +def left_shift(data): + """ + >>> left_shift("0123456789") + '1234567890' + """ + return data[1:] + data[0] + + +def XOR(a, b): + """ + >>> XOR("01010101", "00001111") + '01011010' + """ + res = "" + for i in range(len(a)): + if a[i] == b[i]: + res += "0" + else: + res += "1" + return res + + +def apply_sbox(s, data): + row = int("0b" + data[0] + data[-1], 2) + col = int("0b" + data[1:3], 2) + return bin(s[row][col])[2:] + + +def function(expansion, s0, s1, key, message): + left = message[:4] + right = message[4:] + temp = apply_table(right, expansion) + temp = XOR(temp, key) + l = apply_sbox(s0, temp[:4]) + r = apply_sbox(s1, temp[4:]) + l = "0" * (2 - len(l)) + l + r = "0" * (2 - len(r)) + r + temp = apply_table(l + r, p4_table) + temp = XOR(left, temp) + return temp + right + + +if __name__ == "__main__": + + key = input("Enter 10 bit key: ") + message = input("Enter 8 bit message: ") + + p8_table = [6, 3, 7, 4, 8, 5, 10, 9] + p10_table = [3, 5, 2, 7, 4, 10, 1, 9, 8, 6] + p4_table = [2, 4, 3, 1] + IP = [2, 6, 3, 1, 4, 8, 5, 7] + IP_inv = [4, 1, 3, 5, 7, 2, 8, 6] + expansion = [4, 1, 2, 3, 2, 3, 4, 1] + s0 = [[1, 0, 3, 2], [3, 2, 1, 0], [0, 2, 1, 3], [3, 1, 3, 2]] + s1 = [[0, 1, 2, 3], [2, 0, 1, 3], [3, 0, 1, 0], [2, 1, 0, 3]] + + # key generation + temp = apply_table(key, p10_table) + left = temp[:5] + right = temp[5:] + left = left_shift(left) + right = left_shift(right) + key1 = apply_table(left + right, p8_table) + left = left_shift(left) + right = left_shift(right) + left = left_shift(left) + right = left_shift(right) + key2 = apply_table(left + right, p8_table) + + # encryption + temp = apply_table(message, IP) + temp = function(expansion, s0, s1, key1, temp) + temp = temp[4:] + temp[:4] + temp = function(expansion, s0, s1, key2, temp) + CT = apply_table(temp, IP_inv) + print("Cipher text is:", CT) + + # decryption + temp = apply_table(CT, IP) + temp = function(expansion, s0, s1, key2, temp) + temp = temp[4:] + temp[:4] + temp = function(expansion, s0, s1, key1, temp) + PT = apply_table(temp, IP_inv) + print("Plain text after decypting is:", PT) From df95f439072fc055f0611a5006bf2006c62536e7 Mon Sep 17 00:00:00 2001 From: percy07 <56677891+percy07@users.noreply.github.com> Date: Wed, 30 Oct 2019 20:40:30 +0530 Subject: [PATCH 0334/1071] Update quick_select.py (#1523) * Update quick_select.py Add Doctests. * Add typehints * Don't pre-allocate "smaller" and "larger" --- searches/quick_select.py | 44 ++++++++++++++++++++++++---------------- 1 file changed, 27 insertions(+), 17 deletions(-) diff --git a/searches/quick_select.py b/searches/quick_select.py index 6b70562bd78f..17dca395f73c 100644 --- a/searches/quick_select.py +++ b/searches/quick_select.py @@ -1,12 +1,13 @@ -import random - """ -A python implementation of the quick select algorithm, which is efficient for calculating the value that would appear in the index of a list if it would be sorted, even if it is not already sorted +A Python implementation of the quick select algorithm, which is efficient for +calculating the value that would appear in the index of a list if it would be +sorted, even if it is not already sorted https://en.wikipedia.org/wiki/Quickselect """ +import random -def _partition(data, pivot): +def _partition(data: list, pivot) -> tuple: """ Three way partition the data into smaller, equal and greater lists, in relationship to the pivot @@ -25,28 +26,37 @@ def _partition(data, pivot): return less, equal, greater -def quickSelect(list, k): - # k = len(list) // 2 when trying to find the median (index that value would be when list is sorted) +def quick_select(items: list, index: int): + """ + >>> quick_select([2, 4, 5, 7, 899, 54, 32], 5) + 54 + >>> quick_select([2, 4, 5, 7, 899, 54, 32], 1) + 4 + >>> quick_select([5, 4, 3, 2], 2) + 4 + >>> quick_select([3, 5, 7, 10, 2, 12], 3) + 7 + """ + # index = len(items) // 2 when trying to find the median + # (value of index when items is sorted) # invalid input - if k >= len(list) or k < 0: + if index >= len(items) or index < 0: return None - smaller = [] - larger = [] - pivot = random.randint(0, len(list) - 1) - pivot = list[pivot] + pivot = random.randint(0, len(items) - 1) + pivot = items[pivot] count = 0 - smaller, equal, larger = _partition(list, pivot) + smaller, equal, larger = _partition(items, pivot) count = len(equal) m = len(smaller) - # k is the pivot - if m <= k < m + count: + # index is the pivot + if m <= index < m + count: return pivot # must be in smaller - elif m > k: - return quickSelect(smaller, k) + elif m > index: + return quick_select(smaller, index) # must be in larger else: - return quickSelect(larger, k - (m + count)) + return quick_select(larger, index - (m + count)) From 357dbd4ada738ed9d4b814eeec99bcc833e94aff Mon Sep 17 00:00:00 2001 From: John Law Date: Thu, 31 Oct 2019 01:06:07 +0800 Subject: [PATCH 0335/1071] Doctest, type hints and bug in LIS_O(nlogn) (#1525) * Update longest_increasing_subsequence_o(nlogn).py * Update longest_increasing_subsequence_o(nlogn).py --- ...longest_increasing_subsequence_o(nlogn).py | 21 ++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/dynamic_programming/longest_increasing_subsequence_o(nlogn).py b/dynamic_programming/longest_increasing_subsequence_o(nlogn).py index f7b2c6915bcb..4b06e0d885f2 100644 --- a/dynamic_programming/longest_increasing_subsequence_o(nlogn).py +++ b/dynamic_programming/longest_increasing_subsequence_o(nlogn).py @@ -4,18 +4,29 @@ # comments: This programme outputs the Longest Strictly Increasing Subsequence in O(NLogN) # Where N is the Number of elements in the list ############################# +from typing import List + def CeilIndex(v, l, r, key): while r - l > 1: - m = (l + r) / 2 + m = (l + r) // 2 if v[m] >= key: r = m else: l = m - return r -def LongestIncreasingSubsequenceLength(v): +def LongestIncreasingSubsequenceLength(v: List[int]) -> int: + """ + >>> LongestIncreasingSubsequenceLength([2, 5, 3, 7, 11, 8, 10, 13, 6]) + 6 + >>> LongestIncreasingSubsequenceLength([]) + 0 + >>> LongestIncreasingSubsequenceLength([0, 8, 4, 12, 2, 10, 6, 14, 1, 9, 5, 13, 3, 11, 7, 15]) + 6 + >>> LongestIncreasingSubsequenceLength([5, 4, 3, 2, 1]) + 1 + """ if len(v) == 0: return 0 @@ -37,5 +48,5 @@ def LongestIncreasingSubsequenceLength(v): if __name__ == "__main__": - v = [2, 5, 3, 7, 11, 8, 10, 13, 6] - print(LongestIncreasingSubsequenceLength(v)) + import doctest + doctest.testmod() From 8a5633a23376d2eb16f48f583890fde4c1b27e3b Mon Sep 17 00:00:00 2001 From: RitwickGhosh <56272528+RitwickGhosh@users.noreply.github.com> Date: Thu, 31 Oct 2019 12:49:10 +0530 Subject: [PATCH 0336/1071] Euler Problem 27 solution script Added (#1466) * Add files via upload * Update DIRECTORY.md * Create sol1.py * Update sol1.py * Create __init__.py * Update DIRECTORY.md * Delete isotonic.py * Update sol1.py * Problem_27_project_euler * project_euler/Problem_27/sol1.py * project_euler/Problem_27/sol1.py * project_euler/problem_27/ * project_euler/problem_27 * project_euler/problem_27 * update sol1 of Euler Problem 27 solution script Added * Remove slow test, wrap long comments, format with psf/black * Delete __init__.py * Add type hints * Add doctests to function is_prime() * Rename project_euler/problem_27/project_euler/problem_27/sol1.pysol1.py to project_euler/problem_27/problem_27_sol1.py --- project_euler/problem_27/problem_27_sol1.py | 69 +++++++++++++++++++++ 1 file changed, 69 insertions(+) create mode 100644 project_euler/problem_27/problem_27_sol1.py diff --git a/project_euler/problem_27/problem_27_sol1.py b/project_euler/problem_27/problem_27_sol1.py new file mode 100644 index 000000000000..dbd07f81b713 --- /dev/null +++ b/project_euler/problem_27/problem_27_sol1.py @@ -0,0 +1,69 @@ +""" +Euler discovered the remarkable quadratic formula: +n2 + n + 41 +It turns out that the formula will produce 40 primes for the consecutive values +n = 0 to 39. However, when n = 40, 402 + 40 + 41 = 40(40 + 1) + 41 is divisible +by 41, and certainly when n = 41, 412 + 41 + 41 is clearly divisible by 41. +The incredible formula n2 − 79n + 1601 was discovered, which produces 80 primes +for the consecutive values n = 0 to 79. The product of the coefficients, −79 and +1601, is −126479. +Considering quadratics of the form: +n² + an + b, where |a| < 1000 and |b| < 1000 +where |n| is the modulus/absolute value of ne.g. |11| = 11 and |−4| = 4 +Find the product of the coefficients, a and b, for the quadratic expression that +produces the maximum number of primes for consecutive values of n, starting with +n = 0. +""" + +import math + + +def is_prime(k: int) -> bool: + """ + Determine if a number is prime + >>> is_prime(10) + False + >>> is_prime(11) + True + """ + if k < 2 or k % 2 == 0: + return False + elif k == 2: + return True + else: + for x in range(3, int(math.sqrt(k) + 1), 2): + if k % x == 0: + return False + return True + + +def solution(a_limit: int, b_limit: int) -> int: + """ + >>> solution(1000, 1000) + -59231 + >>> solution(200, 1000) + -59231 + >>> solution(200, 200) + -4925 + >>> solution(-1000, 1000) + 0 + >>> solution(-1000, -1000) + 0 + """ + longest = [0, 0, 0] # length, a, b + for a in range((a_limit * -1) + 1, a_limit): + for b in range(2, b_limit): + if is_prime(b): + count = 0 + n = 0 + while is_prime((n ** 2) + (a * n) + b): + count += 1 + n += 1 + if count > longest[0]: + longest = [count, a, b] + ans = longest[1] * longest[2] + return ans + + +if __name__ == "__main__": + print(solution(1000, 1000)) From 6d44cdd315e1fbff10fb03d9d8b4645183a6a8e5 Mon Sep 17 00:00:00 2001 From: Du YuanChao Date: Thu, 31 Oct 2019 19:33:40 +0800 Subject: [PATCH 0337/1071] perfect square (#1534) * perfect square * perfect square --- maths/perfect_square.py | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 maths/perfect_square.py diff --git a/maths/perfect_square.py b/maths/perfect_square.py new file mode 100644 index 000000000000..9b868c5de98a --- /dev/null +++ b/maths/perfect_square.py @@ -0,0 +1,27 @@ +import math + + +def perfect_square(num: int) -> bool: + """ + Check if a number is perfect square number or not + :param num: the number to be checked + :return: True if number is square number, otherwise False + + >>> perfect_square(9) + True + >>> perfect_square(16) + True + >>> perfect_square(1) + True + >>> perfect_square(0) + True + >>> perfect_square(10) + False + """ + return math.sqrt(num) * math.sqrt(num) == num + + +if __name__ == '__main__': + import doctest + + doctest.testmod() From 80e1c8748a947cd2818aa3d8cdf2d224fd13bda1 Mon Sep 17 00:00:00 2001 From: Charley <56961474+pingings@users.noreply.github.com> Date: Thu, 31 Oct 2019 12:20:39 +0000 Subject: [PATCH 0338/1071] Added Problem 33 (#1440) * Create sol1.py * Create __init__.py * Update sol1.py * corrected range * Update sol1.py --- project_euler/problem_33/__init__.py | 1 + project_euler/problem_33/sol1.py | 55 ++++++++++++++++++++++++++++ 2 files changed, 56 insertions(+) create mode 100644 project_euler/problem_33/__init__.py create mode 100644 project_euler/problem_33/sol1.py diff --git a/project_euler/problem_33/__init__.py b/project_euler/problem_33/__init__.py new file mode 100644 index 000000000000..8b137891791f --- /dev/null +++ b/project_euler/problem_33/__init__.py @@ -0,0 +1 @@ + diff --git a/project_euler/problem_33/sol1.py b/project_euler/problem_33/sol1.py new file mode 100644 index 000000000000..0992c96935f5 --- /dev/null +++ b/project_euler/problem_33/sol1.py @@ -0,0 +1,55 @@ +""" +Problem: + +The fraction 49/98 is a curious fraction, as an inexperienced +mathematician in attempting to simplify it may incorrectly believe +that 49/98 = 4/8, which is correct, is obtained by cancelling the 9s. + +We shall consider fractions like, 30/50 = 3/5, to be trivial examples. + +There are exactly four non-trivial examples of this type of fraction, +less than one in value, and containing two digits in the numerator +and denominator. + +If the product of these four fractions is given in its lowest common +terms, find the value of the denominator. +""" + + +def isDigitCancelling(num, den): + if num != den: + if num % 10 == den // 10: + if (num // 10) / (den % 10) == num / den: + return True + + +def solve(digit_len: int) -> str: + """ + >>> solve(2) + '16/64 , 19/95 , 26/65 , 49/98' + >>> solve(3) + '16/64 , 19/95 , 26/65 , 49/98' + >>> solve(4) + '16/64 , 19/95 , 26/65 , 49/98' + >>> solve(0) + '' + >>> solve(5) + '16/64 , 19/95 , 26/65 , 49/98' + """ + solutions = [] + den = 11 + last_digit = int("1" + "0" * digit_len) + for num in range(den, last_digit): + while den <= 99: + if (num != den) and (num % 10 == den // 10) and (den % 10 != 0): + if isDigitCancelling(num, den): + solutions.append("{}/{}".format(num, den)) + den += 1 + num += 1 + den = 10 + solutions = " , ".join(solutions) + return solutions + + +if __name__ == "__main__": + print(solve(2)) From 814750e637ff3a28442808dc10b2c23170abc85b Mon Sep 17 00:00:00 2001 From: Du YuanChao Date: Thu, 31 Oct 2019 20:45:32 +0800 Subject: [PATCH 0339/1071] update factorial (#1535) * update factorial * update factorial --- maths/factorial_python.py | 43 +++++++++++++++++++++++------------- maths/factorial_recursive.py | 36 +++++++++++++++++++++--------- 2 files changed, 54 insertions(+), 25 deletions(-) diff --git a/maths/factorial_python.py b/maths/factorial_python.py index ab97cd41e681..b9adfdbaeaff 100644 --- a/maths/factorial_python.py +++ b/maths/factorial_python.py @@ -1,21 +1,34 @@ def factorial(input_number: int) -> int: """ - Non-recursive algorithm of finding factorial of the - input number. - >>> factorial(1) - 1 - >>> factorial(6) - 720 - >>> factorial(0) - 1 + Calculate the factorial of specified number + + >>> factorial(1) + 1 + >>> factorial(6) + 720 + >>> factorial(0) + 1 + >>> factorial(-1) + Traceback (most recent call last): + ... + ValueError: factorial() not defined for negative values + >>> factorial(0.1) + Traceback (most recent call last): + ... + ValueError: factorial() only accepts integral values """ if input_number < 0: - raise ValueError("Input input_number should be non-negative") - elif input_number == 0: - return 1 - else: - result = 1 - for i in range(input_number): - result = result * (i + 1) + raise ValueError("factorial() not defined for negative values") + if not isinstance(input_number, int): + raise ValueError("factorial() only accepts integral values") + result = 1 + for i in range(1, input_number): + result = result * (i + 1) return result + + +if __name__ == '__main__': + import doctest + + doctest.testmod() diff --git a/maths/factorial_recursive.py b/maths/factorial_recursive.py index f346c65f1962..013560b28b42 100644 --- a/maths/factorial_recursive.py +++ b/maths/factorial_recursive.py @@ -1,14 +1,30 @@ -def fact(n): +def factorial(n: int) -> int: """ - Return 1, if n is 1 or below, - otherwise, return n * fact(n-1). + Calculate the factorial of specified number + + >>> factorial(1) + 1 + >>> factorial(6) + 720 + >>> factorial(0) + 1 + >>> factorial(-1) + Traceback (most recent call last): + ... + ValueError: factorial() not defined for negative values + >>> factorial(0.1) + Traceback (most recent call last): + ... + ValueError: factorial() only accepts integral values """ - return 1 if n <= 1 else n * fact(n - 1) + if n < 0: + raise ValueError("factorial() not defined for negative values") + if not isinstance(n, int): + raise ValueError("factorial() only accepts integral values") + return 1 if n == 0 or n == 1 else n * factorial(n - 1) + +if __name__ == '__main__': + import doctest -""" -Show factorial for i, -where i ranges from 1 to 20. -""" -for i in range(1, 21): - print(i, ": ", fact(i), sep="") + doctest.testmod() From c717f8d86091011013d4731a91161143fa61b7d7 Mon Sep 17 00:00:00 2001 From: Taufik Algi F <43858377+taufikalgi@users.noreply.github.com> Date: Fri, 1 Nov 2019 00:45:01 +0700 Subject: [PATCH 0340/1071] add max sum contigous subsequence (#1537) * add max sum contigous subsequence * fix typo * Add doctest and type hints * Create autoblack.yml --- .github/workflows/autoblack.yml | 34 +++++++++++++++++++ .../max_sum_contigous_subsequence.py | 20 +++++++++++ 2 files changed, 54 insertions(+) create mode 100644 .github/workflows/autoblack.yml create mode 100644 dynamic_programming/max_sum_contigous_subsequence.py diff --git a/.github/workflows/autoblack.yml b/.github/workflows/autoblack.yml new file mode 100644 index 000000000000..98310ac80b11 --- /dev/null +++ b/.github/workflows/autoblack.yml @@ -0,0 +1,34 @@ +# GitHub Action that uses Black to reformat the Python code in an incoming pull request. +# If all Python code in the pull request is complient with Black then this Action does nothing. +# Othewrwise, Black is run and its changes are committed back to the incoming pull request. +# https://github.com/cclauss/autoblack + +name: autoblack +on: [pull_request] +jobs: + build: + runs-on: ubuntu-latest + strategy: + max-parallel: 1 + matrix: + python-version: [3.7] + steps: + - uses: actions/checkout@v1 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v1 + with: + python-version: ${{ matrix.python-version }} + - name: Install psf/black + run: pip install black + - name: Run black --check . + run: black --check . + - name: If needed, commit black changes to the pull request + if: failure() + run: | + black . + git config --global user.name 'autoblack' + git config --global user.email 'cclauss@users.noreply.github.com' + git remote set-url origin https://x-access-token:${{ secrets.GITHUB_TOKEN }}@github.com/$GITHUB_REPOSITORY + git checkout $GITHUB_HEAD_REF + git commit -am "fixup: Format Python code with psf/black" + git push diff --git a/dynamic_programming/max_sum_contigous_subsequence.py b/dynamic_programming/max_sum_contigous_subsequence.py new file mode 100644 index 000000000000..2cbdb97a1759 --- /dev/null +++ b/dynamic_programming/max_sum_contigous_subsequence.py @@ -0,0 +1,20 @@ +def max_subarray_sum(nums: list) -> int: + """ + >>> max_subarray_sum([6 , 9, -1, 3, -7, -5, 10]) + 17 + """ + if not nums: + return 0 + n = len(nums) + s = [0] * n + res, s, s_pre = nums[0], nums[0], nums[0] + for i in range(1, n): + s = max(nums[i], s_pre + nums[i]) + s_pre = s + res = max(res, s) + return res + + +if __name__ == "__main__": + nums = [6, 9, -1, 3, -7, -5, 10] + print(max_subarray_sum(nums)) From 62e51fe48753a93a6c38834fd0b22eda1941644f Mon Sep 17 00:00:00 2001 From: Metehan Date: Thu, 31 Oct 2019 21:49:25 +0300 Subject: [PATCH 0341/1071] recursive quick sort (#1536) * recursive quick sort * recursive quick sort * Delete recursive-quick-sort * Update recursive-quick-sort.py --- sorts/recursive-quick-sort.py | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 sorts/recursive-quick-sort.py diff --git a/sorts/recursive-quick-sort.py b/sorts/recursive-quick-sort.py new file mode 100644 index 000000000000..c28a14e37ebd --- /dev/null +++ b/sorts/recursive-quick-sort.py @@ -0,0 +1,22 @@ +def quick_sort(data: list) -> list: + """ + >>> for data in ([2, 1, 0], [2.2, 1.1, 0], "quick_sort"): + ... quick_sort(data) == sorted(data) + True + True + True + """ + if len(data) <= 1: + return data + else: + return ( + quick_sort([e for e in data[1:] if e <= data[0]]) + + [data[0]] + + quick_sort([e for e in data[1:] if e > data[0]]) + ) + + +if __name__ == "__main__": + import doctest + + doctest.testmod() From 7bc0462e7936675d9c4f6435d42afa915eef0248 Mon Sep 17 00:00:00 2001 From: Jonathan Alberth Quispe Fuentes Date: Thu, 31 Oct 2019 22:00:46 -0500 Subject: [PATCH 0342/1071] Non-recursive Segment Tree implementation (#1543) * Non-recursive Segment Tree implementation * Added type hints and explanations links --- .../binary_tree/non_recursive_segment_tree.py | 153 ++++++++++++++++++ 1 file changed, 153 insertions(+) create mode 100644 data_structures/binary_tree/non_recursive_segment_tree.py diff --git a/data_structures/binary_tree/non_recursive_segment_tree.py b/data_structures/binary_tree/non_recursive_segment_tree.py new file mode 100644 index 000000000000..877ee45b5baa --- /dev/null +++ b/data_structures/binary_tree/non_recursive_segment_tree.py @@ -0,0 +1,153 @@ +""" +A non-recursive Segment Tree implementation with range query and single element update, +works virtually with any list of the same type of elements with a "commutative" combiner. + +Explanation: +https://www.geeksforgeeks.org/iterative-segment-tree-range-minimum-query/ +https://www.geeksforgeeks.org/segment-tree-efficient-implementation/ + +>>> SegmentTree([1, 2, 3], lambda a, b: a + b).query(0, 2) +6 +>>> SegmentTree([3, 1, 2], min).query(0, 2) +1 +>>> SegmentTree([2, 3, 1], max).query(0, 2) +3 +>>> st = SegmentTree([1, 5, 7, -1, 6], lambda a, b: a + b) +>>> st.update(1, -1) +>>> st.update(2, 3) +>>> st.query(1, 2) +2 +>>> st.query(1, 1) +-1 +>>> st.update(4, 1) +>>> st.query(3, 4) +0 +>>> st = SegmentTree([[1, 2, 3], [3, 2, 1], [1, 1, 1]], lambda a, b: [a[i] + b[i] for i in range(len(a))]) +>>> st.query(0, 1) +[4, 4, 4] +>>> st.query(1, 2) +[4, 3, 2] +>>> st.update(1, [-1, -1, -1]) +>>> st.query(1, 2) +[0, 0, 0] +>>> st.query(0, 2) +[1, 2, 3] +""" +from typing import List, Callable, TypeVar + +T = TypeVar("T") + + +class SegmentTree: + def __init__(self, arr: List[T], fnc: Callable[[T, T], T]) -> None: + """ + Segment Tree constructor, it works just with commutative combiner. + :param arr: list of elements for the segment tree + :param fnc: commutative function for combine two elements + + >>> SegmentTree(['a', 'b', 'c'], lambda a, b: '{}{}'.format(a, b)).query(0, 2) + 'abc' + >>> SegmentTree([(1, 2), (2, 3), (3, 4)], lambda a, b: (a[0] + b[0], a[1] + b[1])).query(0, 2) + (6, 9) + """ + self.N = len(arr) + self.st = [None for _ in range(len(arr))] + arr + self.fn = fnc + self.build() + + def build(self) -> None: + for p in range(self.N - 1, 0, -1): + self.st[p] = self.fn(self.st[p * 2], self.st[p * 2 + 1]) + + def update(self, p: int, v: T) -> None: + """ + Update an element in log(N) time + :param p: position to be update + :param v: new value + + >>> st = SegmentTree([3, 1, 2, 4], min) + >>> st.query(0, 3) + 1 + >>> st.update(2, -1) + >>> st.query(0, 3) + -1 + """ + p += self.N + self.st[p] = v + while p > 1: + p = p // 2 + self.st[p] = self.fn(self.st[p * 2], self.st[p * 2 + 1]) + + def query(self, l: int, r: int) -> T: + """ + Get range query value in log(N) time + :param l: left element index + :param r: right element index + :return: element combined in the range [l, r] + + >>> st = SegmentTree([1, 2, 3, 4], lambda a, b: a + b) + >>> st.query(0, 2) + 6 + >>> st.query(1, 2) + 5 + >>> st.query(0, 3) + 10 + >>> st.query(2, 3) + 7 + """ + l, r = l + self.N, r + self.N + res = None + while l <= r: + if l % 2 == 1: + res = self.st[l] if res is None else self.fn(res, self.st[l]) + if r % 2 == 0: + res = self.st[r] if res is None else self.fn(res, self.st[r]) + l, r = (l + 1) // 2, (r - 1) // 2 + return res + + +if __name__ == "__main__": + from functools import reduce + + test_array = [1, 10, -2, 9, -3, 8, 4, -7, 5, 6, 11, -12] + + test_updates = { + 0: 7, + 1: 2, + 2: 6, + 3: -14, + 4: 5, + 5: 4, + 6: 7, + 7: -10, + 8: 9, + 9: 10, + 10: 12, + 11: 1, + } + + min_segment_tree = SegmentTree(test_array, min) + max_segment_tree = SegmentTree(test_array, max) + sum_segment_tree = SegmentTree(test_array, lambda a, b: a + b) + + def test_all_segments(): + """ + Test all possible segments + """ + for i in range(len(test_array)): + for j in range(i, len(test_array)): + min_range = reduce(min, test_array[i : j + 1]) + max_range = reduce(max, test_array[i : j + 1]) + sum_range = reduce(lambda a, b: a + b, test_array[i : j + 1]) + assert min_range == min_segment_tree.query(i, j) + assert max_range == max_segment_tree.query(i, j) + assert sum_range == sum_segment_tree.query(i, j) + + test_all_segments() + + for index, value in test_updates.items(): + test_array[index] = value + min_segment_tree.update(index, value) + max_segment_tree.update(index, value) + sum_segment_tree.update(index, value) + test_all_segments() From 8107548cc11d226d35a343bb14f8b8a9cc2fb32b Mon Sep 17 00:00:00 2001 From: "Marvin M. Michum" <11682032+mrvnmchm@users.noreply.github.com> Date: Thu, 31 Oct 2019 23:06:20 -0400 Subject: [PATCH 0343/1071] Travis CI: Write & print DIRECTORY.md on one line (#1542) * travis test * travis pull ID test * get pr branch test * retry pr build * test pushing back - probable git error for origin 'not found' * github auth? * add .sh * chmod * add index update for permission fix * run sh for script * add all * add pull directory * fetch pr branch * swap placement of adding commits * rotate * quit trying to update Travis * formatting leftovers * Travis CI: Write & print DIRECTORY.md on one line --- .travis.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 877dbee9ade2..0c7c9fd0e1c7 100644 --- a/.travis.yml +++ b/.travis.yml @@ -11,5 +11,4 @@ script: - mypy --ignore-missing-imports . - pytest . --doctest-modules after_success: - - scripts/build_directory_md.py > DIRECTORY.md - - cat DIRECTORY.md + - scripts/build_directory_md.py 2>&1 | tee DIRECTORY.md From 0e3357ae35314740e9f5e5515dca924b20d39492 Mon Sep 17 00:00:00 2001 From: vinayak Date: Fri, 1 Nov 2019 13:57:16 +0530 Subject: [PATCH 0344/1071] added solution 1 for problem_99 in project_euler (#1545) * Create sol1.py * Create __init__.py * Update sol1.py * corrected range * Add files via upload * Update DIRECTORY.md * Create sol1.py * Update sol1.py * Create __init__.py * Update DIRECTORY.md * Delete isotonic.py * Update sol1.py * Problem_27_project_euler * project_euler/Problem_27/sol1.py * project_euler/Problem_27/sol1.py * project_euler/problem_27/ * project_euler/problem_27 * project_euler/problem_27 * update sol1 of Euler Problem 27 solution script Added * Remove slow test, wrap long comments, format with psf/black * Delete __init__.py * Add type hints * Add doctests to function is_prime() * Rename project_euler/problem_27/project_euler/problem_27/sol1.pysol1.py to project_euler/problem_27/problem_27_sol1.py * Added Problem 33 * added solution 1 for problem_99 * update added solution 1 for problem_99 * update * Update sol1.py --- project_euler/problem_99/__init__.py | 0 project_euler/problem_99/base_exp.txt | 1000 +++++++++++++++++++++++++ project_euler/problem_99/sol1.py | 33 + 3 files changed, 1033 insertions(+) create mode 100644 project_euler/problem_99/__init__.py create mode 100644 project_euler/problem_99/base_exp.txt create mode 100644 project_euler/problem_99/sol1.py diff --git a/project_euler/problem_99/__init__.py b/project_euler/problem_99/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/project_euler/problem_99/base_exp.txt b/project_euler/problem_99/base_exp.txt new file mode 100644 index 000000000000..abe95aa86036 --- /dev/null +++ b/project_euler/problem_99/base_exp.txt @@ -0,0 +1,1000 @@ +519432,525806 +632382,518061 +78864,613712 +466580,530130 +780495,510032 +525895,525320 +15991,714883 +960290,502358 +760018,511029 +166800,575487 +210884,564478 +555151,523163 +681146,515199 +563395,522587 +738250,512126 +923525,503780 +595148,520429 +177108,572629 +750923,511482 +440902,532446 +881418,505504 +422489,534197 +979858,501616 +685893,514935 +747477,511661 +167214,575367 +234140,559696 +940238,503122 +728969,512609 +232083,560102 +900971,504694 +688801,514772 +189664,569402 +891022,505104 +445689,531996 +119570,591871 +821453,508118 +371084,539600 +911745,504251 +623655,518600 +144361,582486 +352442,541775 +420726,534367 +295298,549387 +6530,787777 +468397,529976 +672336,515696 +431861,533289 +84228,610150 +805376,508857 +444409,532117 +33833,663511 +381850,538396 +402931,536157 +92901,604930 +304825,548004 +731917,512452 +753734,511344 +51894,637373 +151578,580103 +295075,549421 +303590,548183 +333594,544123 +683952,515042 +60090,628880 +951420,502692 +28335,674991 +714940,513349 +343858,542826 +549279,523586 +804571,508887 +260653,554881 +291399,549966 +402342,536213 +408889,535550 +40328,652524 +375856,539061 +768907,510590 +165993,575715 +976327,501755 +898500,504795 +360404,540830 +478714,529095 +694144,514472 +488726,528258 +841380,507226 +328012,544839 +22389,690868 +604053,519852 +329514,544641 +772965,510390 +492798,527927 +30125,670983 +895603,504906 +450785,531539 +840237,507276 +380711,538522 +63577,625673 +76801,615157 +502694,527123 +597706,520257 +310484,547206 +944468,502959 +121283,591152 +451131,531507 +566499,522367 +425373,533918 +40240,652665 +39130,654392 +714926,513355 +469219,529903 +806929,508783 +287970,550487 +92189,605332 +103841,599094 +671839,515725 +452048,531421 +987837,501323 +935192,503321 +88585,607450 +613883,519216 +144551,582413 +647359,517155 +213902,563816 +184120,570789 +258126,555322 +502546,527130 +407655,535678 +401528,536306 +477490,529193 +841085,507237 +732831,512408 +833000,507595 +904694,504542 +581435,521348 +455545,531110 +873558,505829 +94916,603796 +720176,513068 +545034,523891 +246348,557409 +556452,523079 +832015,507634 +173663,573564 +502634,527125 +250732,556611 +569786,522139 +216919,563178 +521815,525623 +92304,605270 +164446,576167 +753413,511364 +11410,740712 +448845,531712 +925072,503725 +564888,522477 +7062,780812 +641155,517535 +738878,512100 +636204,517828 +372540,539436 +443162,532237 +571192,522042 +655350,516680 +299741,548735 +581914,521307 +965471,502156 +513441,526277 +808682,508700 +237589,559034 +543300,524025 +804712,508889 +247511,557192 +543486,524008 +504383,526992 +326529,545039 +792493,509458 +86033,609017 +126554,589005 +579379,521481 +948026,502823 +404777,535969 +265767,554022 +266876,553840 +46631,643714 +492397,527958 +856106,506581 +795757,509305 +748946,511584 +294694,549480 +409781,535463 +775887,510253 +543747,523991 +210592,564536 +517119,525990 +520253,525751 +247926,557124 +592141,520626 +346580,542492 +544969,523902 +506501,526817 +244520,557738 +144745,582349 +69274,620858 +292620,549784 +926027,503687 +736320,512225 +515528,526113 +407549,535688 +848089,506927 +24141,685711 +9224,757964 +980684,501586 +175259,573121 +489160,528216 +878970,505604 +969546,502002 +525207,525365 +690461,514675 +156510,578551 +659778,516426 +468739,529945 +765252,510770 +76703,615230 +165151,575959 +29779,671736 +928865,503569 +577538,521605 +927555,503618 +185377,570477 +974756,501809 +800130,509093 +217016,563153 +365709,540216 +774508,510320 +588716,520851 +631673,518104 +954076,502590 +777828,510161 +990659,501222 +597799,520254 +786905,509727 +512547,526348 +756449,511212 +869787,505988 +653747,516779 +84623,609900 +839698,507295 +30159,670909 +797275,509234 +678136,515373 +897144,504851 +989554,501263 +413292,535106 +55297,633667 +788650,509637 +486748,528417 +150724,580377 +56434,632490 +77207,614869 +588631,520859 +611619,519367 +100006,601055 +528924,525093 +190225,569257 +851155,506789 +682593,515114 +613043,519275 +514673,526183 +877634,505655 +878905,505602 +1926,914951 +613245,519259 +152481,579816 +841774,507203 +71060,619442 +865335,506175 +90244,606469 +302156,548388 +399059,536557 +478465,529113 +558601,522925 +69132,620966 +267663,553700 +988276,501310 +378354,538787 +529909,525014 +161733,576968 +758541,511109 +823425,508024 +149821,580667 +269258,553438 +481152,528891 +120871,591322 +972322,501901 +981350,501567 +676129,515483 +950860,502717 +119000,592114 +392252,537272 +191618,568919 +946699,502874 +289555,550247 +799322,509139 +703886,513942 +194812,568143 +261823,554685 +203052,566221 +217330,563093 +734748,512313 +391759,537328 +807052,508777 +564467,522510 +59186,629748 +113447,594545 +518063,525916 +905944,504492 +613922,519213 +439093,532607 +445946,531981 +230530,560399 +297887,549007 +459029,530797 +403692,536075 +855118,506616 +963127,502245 +841711,507208 +407411,535699 +924729,503735 +914823,504132 +333725,544101 +176345,572832 +912507,504225 +411273,535308 +259774,555036 +632853,518038 +119723,591801 +163902,576321 +22691,689944 +402427,536212 +175769,572988 +837260,507402 +603432,519893 +313679,546767 +538165,524394 +549026,523608 +61083,627945 +898345,504798 +992556,501153 +369999,539727 +32847,665404 +891292,505088 +152715,579732 +824104,507997 +234057,559711 +730507,512532 +960529,502340 +388395,537687 +958170,502437 +57105,631806 +186025,570311 +993043,501133 +576770,521664 +215319,563513 +927342,503628 +521353,525666 +39563,653705 +752516,511408 +110755,595770 +309749,547305 +374379,539224 +919184,503952 +990652,501226 +647780,517135 +187177,570017 +168938,574877 +649558,517023 +278126,552016 +162039,576868 +658512,516499 +498115,527486 +896583,504868 +561170,522740 +747772,511647 +775093,510294 +652081,516882 +724905,512824 +499707,527365 +47388,642755 +646668,517204 +571700,522007 +180430,571747 +710015,513617 +435522,532941 +98137,602041 +759176,511070 +486124,528467 +526942,525236 +878921,505604 +408313,535602 +926980,503640 +882353,505459 +566887,522345 +3326,853312 +911981,504248 +416309,534800 +392991,537199 +622829,518651 +148647,581055 +496483,527624 +666314,516044 +48562,641293 +672618,515684 +443676,532187 +274065,552661 +265386,554079 +347668,542358 +31816,667448 +181575,571446 +961289,502320 +365689,540214 +987950,501317 +932299,503440 +27388,677243 +746701,511701 +492258,527969 +147823,581323 +57918,630985 +838849,507333 +678038,515375 +27852,676130 +850241,506828 +818403,508253 +131717,587014 +850216,506834 +904848,504529 +189758,569380 +392845,537217 +470876,529761 +925353,503711 +285431,550877 +454098,531234 +823910,508003 +318493,546112 +766067,510730 +261277,554775 +421530,534289 +694130,514478 +120439,591498 +213308,563949 +854063,506662 +365255,540263 +165437,575872 +662240,516281 +289970,550181 +847977,506933 +546083,523816 +413252,535113 +975829,501767 +361540,540701 +235522,559435 +224643,561577 +736350,512229 +328303,544808 +35022,661330 +307838,547578 +474366,529458 +873755,505819 +73978,617220 +827387,507845 +670830,515791 +326511,545034 +309909,547285 +400970,536363 +884827,505352 +718307,513175 +28462,674699 +599384,520150 +253565,556111 +284009,551093 +343403,542876 +446557,531921 +992372,501160 +961601,502308 +696629,514342 +919537,503945 +894709,504944 +892201,505051 +358160,541097 +448503,531745 +832156,507636 +920045,503924 +926137,503675 +416754,534757 +254422,555966 +92498,605151 +826833,507873 +660716,516371 +689335,514746 +160045,577467 +814642,508425 +969939,501993 +242856,558047 +76302,615517 +472083,529653 +587101,520964 +99066,601543 +498005,527503 +709800,513624 +708000,513716 +20171,698134 +285020,550936 +266564,553891 +981563,501557 +846502,506991 +334,1190800 +209268,564829 +9844,752610 +996519,501007 +410059,535426 +432931,533188 +848012,506929 +966803,502110 +983434,501486 +160700,577267 +504374,526989 +832061,507640 +392825,537214 +443842,532165 +440352,532492 +745125,511776 +13718,726392 +661753,516312 +70500,619875 +436952,532814 +424724,533973 +21954,692224 +262490,554567 +716622,513264 +907584,504425 +60086,628882 +837123,507412 +971345,501940 +947162,502855 +139920,584021 +68330,621624 +666452,516038 +731446,512481 +953350,502619 +183157,571042 +845400,507045 +651548,516910 +20399,697344 +861779,506331 +629771,518229 +801706,509026 +189207,569512 +737501,512168 +719272,513115 +479285,529045 +136046,585401 +896746,504860 +891735,505067 +684771,514999 +865309,506184 +379066,538702 +503117,527090 +621780,518717 +209518,564775 +677135,515423 +987500,501340 +197049,567613 +329315,544673 +236756,559196 +357092,541226 +520440,525733 +213471,563911 +956852,502490 +702223,514032 +404943,535955 +178880,572152 +689477,514734 +691351,514630 +866669,506128 +370561,539656 +739805,512051 +71060,619441 +624861,518534 +261660,554714 +366137,540160 +166054,575698 +601878,519990 +153445,579501 +279899,551729 +379166,538691 +423209,534125 +675310,515526 +145641,582050 +691353,514627 +917468,504026 +284778,550976 +81040,612235 +161699,576978 +616394,519057 +767490,510661 +156896,578431 +427408,533714 +254849,555884 +737217,512182 +897133,504851 +203815,566051 +270822,553189 +135854,585475 +778805,510111 +784373,509847 +305426,547921 +733418,512375 +732087,512448 +540668,524215 +702898,513996 +628057,518328 +640280,517587 +422405,534204 +10604,746569 +746038,511733 +839808,507293 +457417,530938 +479030,529064 +341758,543090 +620223,518824 +251661,556451 +561790,522696 +497733,527521 +724201,512863 +489217,528217 +415623,534867 +624610,518548 +847541,506953 +432295,533249 +400391,536421 +961158,502319 +139173,584284 +421225,534315 +579083,521501 +74274,617000 +701142,514087 +374465,539219 +217814,562985 +358972,540995 +88629,607424 +288597,550389 +285819,550812 +538400,524385 +809930,508645 +738326,512126 +955461,502535 +163829,576343 +826475,507891 +376488,538987 +102234,599905 +114650,594002 +52815,636341 +434037,533082 +804744,508880 +98385,601905 +856620,506559 +220057,562517 +844734,507078 +150677,580387 +558697,522917 +621751,518719 +207067,565321 +135297,585677 +932968,503404 +604456,519822 +579728,521462 +244138,557813 +706487,513800 +711627,513523 +853833,506674 +497220,527562 +59428,629511 +564845,522486 +623621,518603 +242689,558077 +125091,589591 +363819,540432 +686453,514901 +656813,516594 +489901,528155 +386380,537905 +542819,524052 +243987,557841 +693412,514514 +488484,528271 +896331,504881 +336730,543721 +728298,512647 +604215,519840 +153729,579413 +595687,520398 +540360,524240 +245779,557511 +924873,503730 +509628,526577 +528523,525122 +3509,847707 +522756,525555 +895447,504922 +44840,646067 +45860,644715 +463487,530404 +398164,536654 +894483,504959 +619415,518874 +966306,502129 +990922,501212 +835756,507474 +548881,523618 +453578,531282 +474993,529410 +80085,612879 +737091,512193 +50789,638638 +979768,501620 +792018,509483 +665001,516122 +86552,608694 +462772,530469 +589233,520821 +891694,505072 +592605,520594 +209645,564741 +42531,649269 +554376,523226 +803814,508929 +334157,544042 +175836,572970 +868379,506051 +658166,516520 +278203,551995 +966198,502126 +627162,518387 +296774,549165 +311803,547027 +843797,507118 +702304,514032 +563875,522553 +33103,664910 +191932,568841 +543514,524006 +506835,526794 +868368,506052 +847025,506971 +678623,515342 +876139,505726 +571997,521984 +598632,520198 +213590,563892 +625404,518497 +726508,512738 +689426,514738 +332495,544264 +411366,535302 +242546,558110 +315209,546555 +797544,509219 +93889,604371 +858879,506454 +124906,589666 +449072,531693 +235960,559345 +642403,517454 +720567,513047 +705534,513858 +603692,519870 +488137,528302 +157370,578285 +63515,625730 +666326,516041 +619226,518883 +443613,532186 +597717,520257 +96225,603069 +86940,608450 +40725,651929 +460976,530625 +268875,553508 +270671,553214 +363254,540500 +384248,538137 +762889,510892 +377941,538833 +278878,551890 +176615,572755 +860008,506412 +944392,502967 +608395,519571 +225283,561450 +45095,645728 +333798,544090 +625733,518476 +995584,501037 +506135,526853 +238050,558952 +557943,522972 +530978,524938 +634244,517949 +177168,572616 +85200,609541 +953043,502630 +523661,525484 +999295,500902 +840803,507246 +961490,502312 +471747,529685 +380705,538523 +911180,504275 +334149,544046 +478992,529065 +325789,545133 +335884,543826 +426976,533760 +749007,511582 +667067,516000 +607586,519623 +674054,515599 +188534,569675 +565185,522464 +172090,573988 +87592,608052 +907432,504424 +8912,760841 +928318,503590 +757917,511138 +718693,513153 +315141,546566 +728326,512645 +353492,541647 +638429,517695 +628892,518280 +877286,505672 +620895,518778 +385878,537959 +423311,534113 +633501,517997 +884833,505360 +883402,505416 +999665,500894 +708395,513697 +548142,523667 +756491,511205 +987352,501340 +766520,510705 +591775,520647 +833758,507563 +843890,507108 +925551,503698 +74816,616598 +646942,517187 +354923,541481 +256291,555638 +634470,517942 +930904,503494 +134221,586071 +282663,551304 +986070,501394 +123636,590176 +123678,590164 +481717,528841 +423076,534137 +866246,506145 +93313,604697 +783632,509880 +317066,546304 +502977,527103 +141272,583545 +71708,618938 +617748,518975 +581190,521362 +193824,568382 +682368,515131 +352956,541712 +351375,541905 +505362,526909 +905165,504518 +128645,588188 +267143,553787 +158409,577965 +482776,528754 +628896,518282 +485233,528547 +563606,522574 +111001,595655 +115920,593445 +365510,540237 +959724,502374 +938763,503184 +930044,503520 +970959,501956 +913658,504176 +68117,621790 +989729,501253 +567697,522288 +820427,508163 +54236,634794 +291557,549938 +124961,589646 +403177,536130 +405421,535899 +410233,535417 +815111,508403 +213176,563974 +83099,610879 +998588,500934 +513640,526263 +129817,587733 +1820,921851 +287584,550539 +299160,548820 +860621,506386 +529258,525059 +586297,521017 +953406,502616 +441234,532410 +986217,501386 +781938,509957 +461247,530595 +735424,512277 +146623,581722 +839838,507288 +510667,526494 +935085,503327 +737523,512167 +303455,548204 +992779,501145 +60240,628739 +939095,503174 +794368,509370 +501825,527189 +459028,530798 +884641,505363 +512287,526364 +835165,507499 +307723,547590 +160587,577304 +735043,512300 +493289,527887 +110717,595785 +306480,547772 +318593,546089 +179810,571911 +200531,566799 +314999,546580 +197020,567622 +301465,548487 +237808,559000 +131944,586923 +882527,505449 +468117,530003 +711319,513541 +156240,578628 +965452,502162 +992756,501148 +437959,532715 +739938,512046 +614249,519196 +391496,537356 +62746,626418 +688215,514806 +75501,616091 +883573,505412 +558824,522910 +759371,511061 +173913,573489 +891351,505089 +727464,512693 +164833,576051 +812317,508529 +540320,524243 +698061,514257 +69149,620952 +471673,529694 +159092,577753 +428134,533653 +89997,606608 +711061,513557 +779403,510081 +203327,566155 +798176,509187 +667688,515963 +636120,517833 +137410,584913 +217615,563034 +556887,523038 +667229,515991 +672276,515708 +325361,545187 +172115,573985 +13846,725685 \ No newline at end of file diff --git a/project_euler/problem_99/sol1.py b/project_euler/problem_99/sol1.py new file mode 100644 index 000000000000..6729927dfa63 --- /dev/null +++ b/project_euler/problem_99/sol1.py @@ -0,0 +1,33 @@ +""" +Problem: + +Comparing two numbers written in index form like 2'11 and 3'7 is not difficult, as any calculator would confirm that 2^11 = 2048 < 3^7 = 2187. + +However, confirming that 632382^518061 > 519432^525806 would be much more difficult, as both numbers contain over three million digits. + +Using base_exp.txt, a 22K text file containing one thousand lines with a base/exponent pair on each line, determine which line number has the greatest numerical value. + +NOTE: The first two lines in the file represent the numbers in the example given above. +""" + +import os +from math import log10 + + +def find_largest(data_file: str="base_exp.txt") -> int: + """ + >>> find_largest() + 709 + """ + largest = [0, 0] + for i, line in enumerate( + open(os.path.join(os.path.dirname(__file__), data_file)) + ): + a, x = list(map(int, line.split(","))) + if x * log10(a) > largest[0]: + largest = [x * log10(a), i + 1] + return largest[1] + + +if __name__ == "__main__": + print(find_largest()) From 8c443ccfad7ff98d72e017d2c7cb0482e981d184 Mon Sep 17 00:00:00 2001 From: Du YuanChao Date: Mon, 4 Nov 2019 15:28:51 +0800 Subject: [PATCH 0345/1071] add floor() (#1551) * ceil and floor * ceil and floor --- maths/ceil.py | 18 ++++++++++++++++++ maths/floor.py | 18 ++++++++++++++++++ 2 files changed, 36 insertions(+) create mode 100644 maths/ceil.py create mode 100644 maths/floor.py diff --git a/maths/ceil.py b/maths/ceil.py new file mode 100644 index 000000000000..3e46f1474dcf --- /dev/null +++ b/maths/ceil.py @@ -0,0 +1,18 @@ +def ceil(x) -> int: + """ + Return the ceiling of x as an Integral. + + :param x: the number + :return: the smallest integer >= x. + + >>> import math + >>> all(ceil(n) == math.ceil(n) for n in (1, -1, 0, -0, 1.1, -1.1, 1.0, -1.0, 1_000_000_000)) + True + """ + return x if isinstance(x, int) or x - int(x) == 0 else int(x + 1) if x > 0 else int(x) + + +if __name__ == '__main__': + import doctest + + doctest.testmod() diff --git a/maths/floor.py b/maths/floor.py new file mode 100644 index 000000000000..a9b680b37b97 --- /dev/null +++ b/maths/floor.py @@ -0,0 +1,18 @@ +def floor(x) -> int: + """ + Return the floor of x as an Integral. + + :param x: the number + :return: the largest integer <= x. + + >>> import math + >>> all(floor(n) == math.floor(n) for n in (1, -1, 0, -0, 1.1, -1.1, 1.0, -1.0, 1_000_000_000)) + True + """ + return x if isinstance(x, int) or x - int(x) == 0 else int(x) if x > 0 else int(x - 1) + + +if __name__ == '__main__': + import doctest + + doctest.testmod() From 5452e94528ea357746f275c55bcdfd2e4604e19c Mon Sep 17 00:00:00 2001 From: "Marvin M. Michum" <11682032+mrvnmchm@users.noreply.github.com> Date: Mon, 4 Nov 2019 02:45:29 -0500 Subject: [PATCH 0346/1071] Porta cipher (#1550) * directory_writer * add porta cipher for #1492 * formatting and one line comprehensions * remove directory_writer * indentions * Wrap long lines --- ciphers/porta_cipher.py | 110 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 110 insertions(+) create mode 100644 ciphers/porta_cipher.py diff --git a/ciphers/porta_cipher.py b/ciphers/porta_cipher.py new file mode 100644 index 000000000000..a8e79415958d --- /dev/null +++ b/ciphers/porta_cipher.py @@ -0,0 +1,110 @@ +alphabet = { + "A": ("ABCDEFGHIJKLM", "NOPQRSTUVWXYZ"), + "B": ("ABCDEFGHIJKLM", "NOPQRSTUVWXYZ"), + "C": ("ABCDEFGHIJKLM", "ZNOPQRSTUVWXY"), + "D": ("ABCDEFGHIJKLM", "ZNOPQRSTUVWXY"), + "E": ("ABCDEFGHIJKLM", "YZNOPQRSTUVWX"), + "F": ("ABCDEFGHIJKLM", "YZNOPQRSTUVWX"), + "G": ("ABCDEFGHIJKLM", "XYZNOPQRSTUVW"), + "H": ("ABCDEFGHIJKLM", "XYZNOPQRSTUVW"), + "I": ("ABCDEFGHIJKLM", "WXYZNOPQRSTUV"), + "J": ("ABCDEFGHIJKLM", "WXYZNOPQRSTUV"), + "K": ("ABCDEFGHIJKLM", "VWXYZNOPQRSTU"), + "L": ("ABCDEFGHIJKLM", "VWXYZNOPQRSTU"), + "M": ("ABCDEFGHIJKLM", "UVWXYZNOPQRST"), + "N": ("ABCDEFGHIJKLM", "UVWXYZNOPQRST"), + "O": ("ABCDEFGHIJKLM", "TUVWXYZNOPQRS"), + "P": ("ABCDEFGHIJKLM", "TUVWXYZNOPQRS"), + "Q": ("ABCDEFGHIJKLM", "STUVWXYZNOPQR"), + "R": ("ABCDEFGHIJKLM", "STUVWXYZNOPQR"), + "S": ("ABCDEFGHIJKLM", "RSTUVWXYZNOPQ"), + "T": ("ABCDEFGHIJKLM", "RSTUVWXYZNOPQ"), + "U": ("ABCDEFGHIJKLM", "QRSTUVWXYZNOP"), + "V": ("ABCDEFGHIJKLM", "QRSTUVWXYZNOP"), + "W": ("ABCDEFGHIJKLM", "PQRSTUVWXYZNO"), + "X": ("ABCDEFGHIJKLM", "PQRSTUVWXYZNO"), + "Y": ("ABCDEFGHIJKLM", "OPQRSTUVWXYZN"), + "Z": ("ABCDEFGHIJKLM", "OPQRSTUVWXYZN"), +} + + +def generate_table(key): + """ + >>> generate_table('marvin') # doctest: +NORMALIZE_WHITESPACE + [('ABCDEFGHIJKLM', 'UVWXYZNOPQRST'), ('ABCDEFGHIJKLM', 'NOPQRSTUVWXYZ'), + ('ABCDEFGHIJKLM', 'STUVWXYZNOPQR'), ('ABCDEFGHIJKLM', 'QRSTUVWXYZNOP'), + ('ABCDEFGHIJKLM', 'WXYZNOPQRSTUV'), ('ABCDEFGHIJKLM', 'UVWXYZNOPQRST')] + """ + return [alphabet[char] for char in key.upper()] + + +def encrypt(key, words): + """ + >>> encrypt('marvin', 'jessica') + 'QRACRWU' + """ + cipher = "" + count = 0 + table = generate_table(key) + for char in words.upper(): + cipher += get_opponent(table[count], char) + count = (count + 1) % len(table) + return cipher + + +def decrypt(key, words): + """ + >>> decrypt('marvin', 'QRACRWU') + 'JESSICA' + """ + return encrypt(key, words) + + +def get_position(table, char): + """ + >>> table = [ + ... ('ABCDEFGHIJKLM', 'UVWXYZNOPQRST'), ('ABCDEFGHIJKLM', 'NOPQRSTUVWXYZ'), + ... ('ABCDEFGHIJKLM', 'STUVWXYZNOPQR'), ('ABCDEFGHIJKLM', 'QRSTUVWXYZNOP'), + ... ('ABCDEFGHIJKLM', 'WXYZNOPQRSTUV'), ('ABCDEFGHIJKLM', 'UVWXYZNOPQRST')] + >>> get_position(table, 'A') + (None, None) + """ + if char in table[0]: + row = 0 + else: + row = 1 if char in table[1] else -1 + return (None, None) if row == -1 else (row, table[row].index(char)) + + +def get_opponent(table, char): + """ + >>> table = [ + ... ('ABCDEFGHIJKLM', 'UVWXYZNOPQRST'), ('ABCDEFGHIJKLM', 'NOPQRSTUVWXYZ'), + ... ('ABCDEFGHIJKLM', 'STUVWXYZNOPQR'), ('ABCDEFGHIJKLM', 'QRSTUVWXYZNOP'), + ... ('ABCDEFGHIJKLM', 'WXYZNOPQRSTUV'), ('ABCDEFGHIJKLM', 'UVWXYZNOPQRST')] + >>> get_opponent(table, 'A') + 'A' + """ + row, col = get_position(table, char.upper()) + if row == 1: + return table[0][col] + else: + return table[1][col] if row == 0 else char + + +if __name__ == "__main__": + import doctest + + doctest.testmod() # Fist ensure that all our tests are passing... + """ + ENTER KEY: marvin + ENTER TEXT TO ENCRYPT: jessica + ENCRYPTED: QRACRWU + DECRYPTED WITH KEY: JESSICA + """ + key = input("ENTER KEY: ").strip() + text = input("ENTER TEXT TO ENCRYPT: ").strip() + cipher_text = encrypt(key, text) + + print(f"ENCRYPTED: {cipher_text}") + print(f"DECRYPTED WITH KEY: {decrypt(key, cipher_text)}") From a9d5378ce2251b43d4f2541283ff6f1a558a3bfa Mon Sep 17 00:00:00 2001 From: John Law Date: Tue, 5 Nov 2019 02:06:16 +0800 Subject: [PATCH 0347/1071] Doctest and typing for longest_increasing_subsequence.py (#1526) * Update longest_increasing_subsequence.py * Update longest_increasing_subsequence.py * Format longest_increasing_subsequence.py to PEP8 * Update longest_increasing_subsequence.py --- .../longest_increasing_subsequence.py | 62 +++++++++++-------- 1 file changed, 36 insertions(+), 26 deletions(-) diff --git a/dynamic_programming/longest_increasing_subsequence.py b/dynamic_programming/longest_increasing_subsequence.py index 3cb57806e06f..6d12f1c7caf0 100644 --- a/dynamic_programming/longest_increasing_subsequence.py +++ b/dynamic_programming/longest_increasing_subsequence.py @@ -4,42 +4,52 @@ This is a pure Python implementation of Dynamic Programming solution to the longest increasing subsequence of a given sequence. The problem is : -Given an ARRAY, to find the longest and increasing sub ARRAY in that given ARRAY and return it. +Given an array, to find the longest and increasing sub-array in that given array and return it. Example: [10, 22, 9, 33, 21, 50, 41, 60, 80] as input will return [10, 22, 33, 41, 60, 80] as output """ - - -def longestSub(ARRAY): # This function is recursive - - ARRAY_LENGTH = len(ARRAY) +from typing import List + + +def longest_subsequence(array: List[int]) -> List[int]: # This function is recursive + """ + Some examples + >>> longest_subsequence([10, 22, 9, 33, 21, 50, 41, 60, 80]) + [10, 22, 33, 41, 60, 80] + >>> longest_subsequence([4, 8, 7, 5, 1, 12, 2, 3, 9]) + [1, 2, 3, 9] + >>> longest_subsequence([9, 8, 7, 6, 5, 7]) + [8] + >>> longest_subsequence([1, 1, 1]) + [1, 1, 1] + """ + array_length = len(array) if ( - ARRAY_LENGTH <= 1 + array_length <= 1 ): # If the array contains only one element, we return it (it's the stop condition of recursion) - return ARRAY + return array # Else - PIVOT = ARRAY[0] + pivot = array[0] isFound = False i = 1 - LONGEST_SUB = [] - while not isFound and i < ARRAY_LENGTH: - if ARRAY[i] < PIVOT: + longest_subseq = [] + while not isFound and i < array_length: + if array[i] < pivot: isFound = True - TEMPORARY_ARRAY = [element for element in ARRAY[i:] if element >= ARRAY[i]] - TEMPORARY_ARRAY = longestSub(TEMPORARY_ARRAY) - if len(TEMPORARY_ARRAY) > len(LONGEST_SUB): - LONGEST_SUB = TEMPORARY_ARRAY + temp_array = [element for element in array[i:] if element >= array[i]] + temp_array = longest_subsequence(temp_array) + if len(temp_array) > len(longest_subseq): + longest_subseq = temp_array else: i += 1 - TEMPORARY_ARRAY = [element for element in ARRAY[1:] if element >= PIVOT] - TEMPORARY_ARRAY = [PIVOT] + longestSub(TEMPORARY_ARRAY) - if len(TEMPORARY_ARRAY) > len(LONGEST_SUB): - return TEMPORARY_ARRAY + temp_array = [element for element in array[1:] if element >= pivot] + temp_array = [pivot] + longest_subsequence(temp_array) + if len(temp_array) > len(longest_subseq): + return temp_array else: - return LONGEST_SUB - - -# Some examples + return longest_subseq + -print(longestSub([4, 8, 7, 5, 1, 12, 2, 3, 9])) -print(longestSub([9, 8, 7, 6, 5, 7])) +if __name__ == "__main__": + import doctest + doctest.testmod() From ad2db80f8aeba7fe407eccd69327cfb3bdfaacd2 Mon Sep 17 00:00:00 2001 From: jwmneu Date: Thu, 7 Nov 2019 16:37:28 +0800 Subject: [PATCH 0348/1071] Add sol3 for project_euler problem_03 (#1553) * Add sol3 for project_euler proble_03 * Update sol3.py add type hint remove unused variable * Format code with psf/black --- project_euler/problem_03/sol3.py | 63 ++++++++++++++++++++++++++++++++ 1 file changed, 63 insertions(+) create mode 100644 project_euler/problem_03/sol3.py diff --git a/project_euler/problem_03/sol3.py b/project_euler/problem_03/sol3.py new file mode 100644 index 000000000000..5fe45df59984 --- /dev/null +++ b/project_euler/problem_03/sol3.py @@ -0,0 +1,63 @@ +""" +Problem: +The prime factors of 13195 are 5,7,13 and 29. What is the largest prime factor +of a given number N? + +e.g. for 10, largest prime factor = 5. For 17, largest prime factor = 17. +""" + + +def solution(n: int) -> int: + """Returns the largest prime factor of a given number n. + + >>> solution(13195) + 29 + >>> solution(10) + 5 + >>> solution(17) + 17 + >>> solution(3.4) + 3 + >>> solution(0) + Traceback (most recent call last): + ... + ValueError: Parameter n must be greater or equal to one. + >>> solution(-17) + Traceback (most recent call last): + ... + ValueError: Parameter n must be greater or equal to one. + >>> solution([]) + Traceback (most recent call last): + ... + TypeError: Parameter n must be int or passive of cast to int. + >>> solution("asd") + Traceback (most recent call last): + ... + TypeError: Parameter n must be int or passive of cast to int. + """ + try: + n = int(n) + except (TypeError, ValueError): + raise TypeError("Parameter n must be int or passive of cast to int.") + if n <= 0: + raise ValueError("Parameter n must be greater or equal to one.") + i = 2 + ans = 0 + if n == 2: + return 2 + while n > 2: + while n % i != 0: + i += 1 + ans = i + while n % i == 0: + n = n / i + i += 1 + + return int(ans) + + +if __name__ == "__main__": + # print(solution(int(input().strip()))) + import doctest + + doctest.testmod() From db515e585e3e53c47b090dbe135598c74c1e0f59 Mon Sep 17 00:00:00 2001 From: Zizhou Zhang Date: Sun, 10 Nov 2019 01:02:30 +1100 Subject: [PATCH 0349/1071] added rsa_factorization.py (#1556) * added RSA_factorization.py This algorithm can effectively factor RSA large prime N given public key e and private key d. * Rename RSA_factorization.py to rsa_factorization.py * Add definitions for d, e, and N --- ciphers/rsa_factorization.py | 51 ++++++++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) create mode 100644 ciphers/rsa_factorization.py diff --git a/ciphers/rsa_factorization.py b/ciphers/rsa_factorization.py new file mode 100644 index 000000000000..58bdc554a861 --- /dev/null +++ b/ciphers/rsa_factorization.py @@ -0,0 +1,51 @@ +""" +An RSA prime factor algorithm. + +The program can efficiently factor RSA prime number given the private key d and +public key e. +Source: on page 3 of https://crypto.stanford.edu/~dabo/papers/RSA-survey.pdf +large number can take minutes to factor, therefore are not included in doctest. +""" +import math +import random +from typing import List + + +def rsafactor(d: int, e: int, N: int) -> List[int]: + """ + This function returns the factors of N, where p*q=N + Return: [p, q] + + We call N the RSA modulus, e the encryption exponent, and d the decryption exponent. + The pair (N, e) is the public key. As its name suggests, it is public and is used to + encrypt messages. + The pair (N, d) is the secret key or private key and is known only to the recipient + of encrypted messages. + + >>> rsafactor(3, 16971, 25777) + [149, 173] + >>> rsafactor(7331, 11, 27233) + [113, 241] + >>> rsafactor(4021, 13, 17711) + [89, 199] + """ + k = d * e - 1 + p = 0 + q = 0 + while p == 0: + g = random.randint(2, N - 1) + t = k + if t % 2 == 0: + t = t // 2 + x = (g ** t) % N + y = math.gcd(x - 1, N) + if x > 1 and y > 1: + p = y + q = N // y + return sorted([p, q]) + + +if __name__ == "__main__": + import doctest + + doctest.testmod() From 4c37eb7d07b62efb84b535712972a1fa5e657218 Mon Sep 17 00:00:00 2001 From: Hanif Ali Date: Sun, 10 Nov 2019 13:47:04 +0500 Subject: [PATCH 0350/1071] Improved on Singly Linked List Programs (#1558) * Improved Singly Linked List Added String Representations of Nodes and Linked Lists Added support for indexing and changing of Node data using indices. * Added a few comments to Linked Lists * Reformatted to conform to PEP8 * Added from_sequence.py Convert a Python List to Linked List comprising of Nodes and return head. * Added print_reverse.py Recursive program to print the elements of a Linked List in reverse. * Change 'is not None' for more Pythonicness --- data_structures/linked_list/from_sequence.py | 45 +++++++++++++ data_structures/linked_list/print_reverse.py | 55 ++++++++++++++++ .../linked_list/singly_linked_list.py | 65 ++++++++++++++++--- 3 files changed, 157 insertions(+), 8 deletions(-) create mode 100644 data_structures/linked_list/from_sequence.py create mode 100644 data_structures/linked_list/print_reverse.py diff --git a/data_structures/linked_list/from_sequence.py b/data_structures/linked_list/from_sequence.py new file mode 100644 index 000000000000..e6d335e81326 --- /dev/null +++ b/data_structures/linked_list/from_sequence.py @@ -0,0 +1,45 @@ +# Recursive Prorgam to create a Linked List from a sequence and +# print a string representation of it. + +class Node: + def __init__(self, data=None): + self.data = data + self.next = None + + def __repr__(self): + """Returns a visual representation of the node and all its following nodes.""" + string_rep = "" + temp = self + while temp: + string_rep += f"<{temp.data}> ---> " + temp = temp.next + string_rep += "" + return string_rep + + + +def make_linked_list(elements_list): + """Creates a Linked List from the elements of the given sequence + (list/tuple) and returns the head of the Linked List.""" + + # if elements_list is empty + if not elements_list: + raise Exception("The Elements List is empty") + + # Set first element as Head + head = Node(elements_list[0]) + current = head + # Loop through elements from position 1 + for data in elements_list[1:]: + current.next = Node(data) + current = current.next + return head + + + +list_data = [1,3,5,32,44,12,43] +print(f"List: {list_data}") +print("Creating Linked List from List.") +linked_list = make_linked_list(list_data) +print("Linked List:") +print(linked_list) diff --git a/data_structures/linked_list/print_reverse.py b/data_structures/linked_list/print_reverse.py new file mode 100644 index 000000000000..6572ccd8f4a9 --- /dev/null +++ b/data_structures/linked_list/print_reverse.py @@ -0,0 +1,55 @@ +# Program to print the elements of a linked list in reverse + +class Node: + def __init__(self, data=None): + self.data = data + self.next = None + + def __repr__(self): + """Returns a visual representation of the node and all its following nodes.""" + string_rep = "" + temp = self + while temp: + string_rep += f"<{temp.data}> ---> " + temp = temp.next + string_rep += "" + return string_rep + + + +def make_linked_list(elements_list): + """Creates a Linked List from the elements of the given sequence + (list/tuple) and returns the head of the Linked List.""" + + # if elements_list is empty + if not elements_list: + raise Exception("The Elements List is empty") + + # Set first element as Head + head = Node(elements_list[0]) + current = head + # Loop through elements from position 1 + for data in elements_list[1:]: + current.next = Node(data) + current = current.next + return head + +def print_reverse(head_node): + """Prints the elements of the given Linked List in reverse order""" + + # If reached end of the List + if head_node is None: + return None + else: + # Recurse + print_reverse(head_node.next) + print(head_node.data) + + + +list_data = [14,52,14,12,43] +linked_list = make_linked_list(list_data) +print("Linked List:") +print(linked_list) +print("Elements in Reverse:") +print_reverse(linked_list) diff --git a/data_structures/linked_list/singly_linked_list.py b/data_structures/linked_list/singly_linked_list.py index 73b982316e76..7137f4e66deb 100644 --- a/data_structures/linked_list/singly_linked_list.py +++ b/data_structures/linked_list/singly_linked_list.py @@ -3,8 +3,11 @@ def __init__(self, data): self.data = data # given data self.next = None # given next to None + def __repr__(self): # String Representation of a Node + return f"" -class Linked_List: + +class LinkedList: def __init__(self): self.head = None # Initialize head to None @@ -13,36 +16,36 @@ def insert_tail(self, data): self.insert_head(data) # If this is first node, call insert_head else: temp = self.head - while temp.next != None: # traverse to last node + while temp.next: # traverse to last node temp = temp.next temp.next = Node(data) # create node & link to tail def insert_head(self, data): newNod = Node(data) # create a new node - if self.head != None: + if self.head: newNod.next = self.head # link newNode to head self.head = newNod # make NewNode as head def printList(self): # print every node data temp = self.head - while temp is not None: + while temp: print(temp.data) temp = temp.next def delete_head(self): # delete from head temp = self.head - if self.head != None: + if self.head: self.head = self.head.next temp.next = None return temp def delete_tail(self): # delete from tail temp = self.head - if self.head != None: + if self.head: if self.head.next is None: # if head is the only Node in the Linked List self.head = None else: - while temp.next.next is not None: # find the 2nd last element + while temp.next.next: # find the 2nd last element temp = temp.next temp.next, temp = ( None, @@ -69,9 +72,47 @@ def reverse(self): # Return prev in order to put the head at the end self.head = prev + def __repr__(self): # String representation/visualization of a Linked Lists + current = self.head + string_repr = "" + while current: + string_repr += f"{current} ---> " + current = current.next + # END represents end of the LinkedList + string_repr += "END" + return string_repr + + # Indexing Support. Used to get a node at particaular position + def __getitem__(self, index): + current = self.head + + # If LinkedList is Empty + if current is None: + raise IndexError("The Linked List is empty") + + # Move Forward 'index' times + for _ in range(index): + # If the LinkedList ends before reaching specified node + if current.next is None: + raise IndexError("Index out of range.") + current = current.next + return current + + # Used to change the data of a particular node + def __setitem__(self, index, data): + current = self.head + # If list is empty + if current is None: + raise IndexError("The Linked List is empty") + for i in range(index): + if current.next is None: + raise IndexError("Index out of range.") + current = current.next + current.data = data + def main(): - A = Linked_List() + A = LinkedList() print("Inserting 1st at head") a1 = input() A.insert_head(a1) @@ -98,6 +139,14 @@ def main(): A.reverse() print("\nPrint List : ") A.printList() + print("\nString Representation of Linked List:") + print(A) + print("\n Reading/Changing Node Data using Indexing:") + print(f"Element at Position 1: {A[1]}") + p1 = input("Enter New Value: ") + A[1] = p1 + print("New List:") + print(A) if __name__ == "__main__": From 82a11d7f31d06f781e3902984fa2902c0676816a Mon Sep 17 00:00:00 2001 From: JakobZhao <52325554+JakobMusik@users.noreply.github.com> Date: Sun, 10 Nov 2019 17:01:38 +0800 Subject: [PATCH 0351/1071] Fix bug in bellman_ford.py (#1544) --- graphs/bellman_ford.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/graphs/bellman_ford.py b/graphs/bellman_ford.py index b782a899fda9..5c36468e79de 100644 --- a/graphs/bellman_ford.py +++ b/graphs/bellman_ford.py @@ -13,14 +13,14 @@ def BellmanFord(graph, V, E, src): mdist[src] = 0.0 for i in range(V - 1): - for j in range(V): + for j in range(E): u = graph[j]["src"] v = graph[j]["dst"] w = graph[j]["weight"] if mdist[u] != float("inf") and mdist[u] + w < mdist[v]: mdist[v] = mdist[u] + w - for j in range(V): + for j in range(E): u = graph[j]["src"] v = graph[j]["dst"] w = graph[j]["weight"] From 5ac4391420991d010a1af7bc4a6b04084bd13c27 Mon Sep 17 00:00:00 2001 From: Sarath Kaul Date: Tue, 12 Nov 2019 13:57:38 +0530 Subject: [PATCH 0352/1071] Python Program that fetches top trending news (#1559) * Python Program that fetches top trending news * Python Program that fetches top trending news * Revisions in Fetch BBC News --- web_programming/fetch_bbc_news.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 web_programming/fetch_bbc_news.py diff --git a/web_programming/fetch_bbc_news.py b/web_programming/fetch_bbc_news.py new file mode 100644 index 000000000000..43ffc305ea48 --- /dev/null +++ b/web_programming/fetch_bbc_news.py @@ -0,0 +1,18 @@ +# Created by sarathkaul on 12/11/19 + +import requests + +# Enter Your API Key in following URL +_NEWS_API = "https://newsapi.org/v1/articles?source=bbc-news&sortBy=top&apiKey=" + + +def fetch_bbc_news(bbc_news_api_key: str) -> None: + # fetching a list of articles in json format + bbc_news_page = requests.get(_NEWS_API + bbc_news_api_key).json() + # each article in the list is a dict + for i, article in enumerate(bbc_news_page["articles"], 1): + print(f"{i}.) {article['title']}") + + +if __name__ == '__main__': + fetch_bbc_news(bbc_news_api_key="") From b7e37a856ff2aaa8d2ccc20936cb0b787c0376ef Mon Sep 17 00:00:00 2001 From: Sarath Kaul Date: Tue, 12 Nov 2019 16:41:54 +0530 Subject: [PATCH 0353/1071] Added a new Python script and some changes in existing one (#1560) * Python Program that fetches top trending news * Python Program that fetches top trending news * Revisions in Fetch BBC News * psf/black Changes * Python Program to send slack message to a channel * Slack Message Revision Changes --- web_programming/fetch_bbc_news.py | 3 +-- web_programming/slack_message.py | 18 ++++++++++++++++++ 2 files changed, 19 insertions(+), 2 deletions(-) create mode 100644 web_programming/slack_message.py diff --git a/web_programming/fetch_bbc_news.py b/web_programming/fetch_bbc_news.py index 43ffc305ea48..7f8bc57b69f5 100644 --- a/web_programming/fetch_bbc_news.py +++ b/web_programming/fetch_bbc_news.py @@ -2,7 +2,6 @@ import requests -# Enter Your API Key in following URL _NEWS_API = "https://newsapi.org/v1/articles?source=bbc-news&sortBy=top&apiKey=" @@ -14,5 +13,5 @@ def fetch_bbc_news(bbc_news_api_key: str) -> None: print(f"{i}.) {article['title']}") -if __name__ == '__main__': +if __name__ == "__main__": fetch_bbc_news(bbc_news_api_key="") diff --git a/web_programming/slack_message.py b/web_programming/slack_message.py new file mode 100644 index 000000000000..8dd0462d48e3 --- /dev/null +++ b/web_programming/slack_message.py @@ -0,0 +1,18 @@ +# Created by sarathkaul on 12/11/19 + +import requests + + +def send_slack_message(message_body: str, slack_url: str) -> None: + headers = {"Content-Type": "application/json"} + response = requests.post(slack_url, json={"text": message_body}, headers=headers) + if response.status_code != 200: + raise ValueError( + f"Request to slack returned an error {response.status_code}, " + f"the response is:\n{response.text}" + ) + + +if __name__ == "main": + # Set the slack url to the one provided by Slack when you create the webhook at https://my.slack.com/services/new/incoming-webhook/ + send_slack_message("", "") From fa6331aa82dd18d949b7be60c85277e41f018ab5 Mon Sep 17 00:00:00 2001 From: Christian Clauss Date: Thu, 14 Nov 2019 06:26:29 +0100 Subject: [PATCH 0354/1071] Moved to TheAlgorithms/Jupyter (#1563) https://github.com/TheAlgorithms/Jupyter/tree/master/other --- ..._wastage_analysis_from_1961-2013_fao.ipynb | 5916 ----------------- 1 file changed, 5916 deletions(-) delete mode 100644 other/food_wastage_analysis_from_1961-2013_fao.ipynb diff --git a/other/food_wastage_analysis_from_1961-2013_fao.ipynb b/other/food_wastage_analysis_from_1961-2013_fao.ipynb deleted file mode 100644 index 384314c7e8f1..000000000000 --- a/other/food_wastage_analysis_from_1961-2013_fao.ipynb +++ /dev/null @@ -1,5916 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": { - "_cell_guid": "1eecdb4a-89ca-4a1e-9c4c-7c44b2e628a1", - "_uuid": "110a8132a8179a9bed2fc8f1096592dc791f1661" - }, - "source": [ - "# About the dataset\n", - "\n", - "Context\n", - "Our world population is expected to grow from 7.3 billion today to 9.7 billion in the year 2050. Finding solutions for feeding the growing world population has become a hot topic for food and agriculture organizations, entrepreneurs and philanthropists. These solutions range from changing the way we grow our food to changing the way we eat. To make things harder, the world's climate is changing and it is both affecting and affected by the way we grow our food – agriculture. This dataset provides an insight on our worldwide food production - focusing on a comparison between food produced for human consumption and feed produced for animals.\n", - "\n", - "Content\n", - "The Food and Agriculture Organization of the United Nations provides free access to food and agriculture data for over 245 countries and territories, from the year 1961 to the most recent update (depends on the dataset). One dataset from the FAO's database is the Food Balance Sheets. It presents a comprehensive picture of the pattern of a country's food supply during a specified reference period, the last time an update was loaded to the FAO database was in 2013. The food balance sheet shows for each food item the sources of supply and its utilization. This chunk of the dataset is focused on two utilizations of each food item available:\n", - "\n", - "Food - refers to the total amount of the food item available as human food during the reference period.\n", - "Feed - refers to the quantity of the food item available for feeding to the livestock and poultry during the reference period.\n", - "Dataset's attributes:\n", - "\n", - "Area code - Country name abbreviation\n", - "Area - County name\n", - "Item - Food item\n", - "Element - Food or Feed\n", - "Latitude - geographic coordinate that specifies the north–south position of a point on the Earth's surface\n", - "Longitude - geographic coordinate that specifies the east-west position of a point on the Earth's surface\n", - "Production per year - Amount of food item produced in 1000 tonnes\n", - "\n", - "This is a simple exploratory notebook that heavily expolits pandas and seaborn" - ] - }, - { - "cell_type": "code", - "execution_count": 1, - "metadata": { - "_cell_guid": "b1076dfc-b9ad-4769-8c92-a6c4dae69d19", - "_uuid": "8f2839f25d086af736a60e9eeb907d3b93b6e0e5" - }, - "outputs": [], - "source": [ - "# Importing libraries\n", - "import numpy as np\n", - "import pandas as pd\n", - "import matplotlib.pyplot as plt\n", - "import seaborn as sns\n", - "%matplotlib inline\n", - "# importing data\n", - "df = pd.read_csv(\"FAO.csv\", encoding = \"ISO-8859-1\")\n", - "pd.options.mode.chained_assignment = None\n", - "from sklearn.linear_model import LinearRegression" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "
\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
Area AbbreviationArea CodeAreaItem CodeItemElement CodeElementUnitlatitudelongitude...Y2004Y2005Y2006Y2007Y2008Y2009Y2010Y2011Y2012Y2013
0AFG2Afghanistan2511Wheat and products5142Food1000 tonnes33.9467.71...3249.03486.03704.04164.04252.04538.04605.04711.048104895
1AFG2Afghanistan2805Rice (Milled Equivalent)5142Food1000 tonnes33.9467.71...419.0445.0546.0455.0490.0415.0442.0476.0425422
2AFG2Afghanistan2513Barley and products5521Feed1000 tonnes33.9467.71...58.0236.0262.0263.0230.0379.0315.0203.0367360
3AFG2Afghanistan2513Barley and products5142Food1000 tonnes33.9467.71...185.043.044.048.062.055.060.072.07889
4AFG2Afghanistan2514Maize and products5521Feed1000 tonnes33.9467.71...120.0208.0233.0249.0247.0195.0178.0191.0200200
5AFG2Afghanistan2514Maize and products5142Food1000 tonnes33.9467.71...231.067.082.067.069.071.082.073.07776
6AFG2Afghanistan2517Millet and products5142Food1000 tonnes33.9467.71...15.021.011.019.021.018.014.014.01412
7AFG2Afghanistan2520Cereals, Other5142Food1000 tonnes33.9467.71...2.01.01.00.00.00.00.00.000
8AFG2Afghanistan2531Potatoes and products5142Food1000 tonnes33.9467.71...276.0294.0294.0260.0242.0250.0192.0169.0196230
9AFG2Afghanistan2536Sugar cane5521Feed1000 tonnes33.9467.71...50.029.061.065.054.0114.083.083.06981
10AFG2Afghanistan2537Sugar beet5521Feed1000 tonnes33.9467.71...0.00.00.00.00.00.00.00.000
11AFG2Afghanistan2542Sugar (Raw Equivalent)5142Food1000 tonnes33.9467.71...124.0152.0169.0192.0217.0231.0240.0240.0250255
12AFG2Afghanistan2543Sweeteners, Other5142Food1000 tonnes33.9467.71...9.015.012.06.011.02.09.021.02416
13AFG2Afghanistan2745Honey5142Food1000 tonnes33.9467.71...3.03.03.03.03.03.03.02.022
14AFG2Afghanistan2549Pulses, Other and products5521Feed1000 tonnes33.9467.71...3.02.03.03.03.05.04.05.044
15AFG2Afghanistan2549Pulses, Other and products5142Food1000 tonnes33.9467.71...17.035.037.040.054.080.066.081.06374
16AFG2Afghanistan2551Nuts and products5142Food1000 tonnes33.9467.71...11.013.024.034.042.028.066.071.07044
17AFG2Afghanistan2560Coconuts - Incl Copra5142Food1000 tonnes33.9467.71...0.00.00.00.00.00.00.00.000
18AFG2Afghanistan2561Sesame seed5142Food1000 tonnes33.9467.71...16.016.013.016.016.016.019.017.01616
19AFG2Afghanistan2563Olives (including preserved)5142Food1000 tonnes33.9467.71...1.01.00.00.02.03.02.02.022
20AFG2Afghanistan2571Soyabean Oil5142Food1000 tonnes33.9467.71...6.035.018.021.011.06.015.016.01616
21AFG2Afghanistan2572Groundnut Oil5142Food1000 tonnes33.9467.71...0.00.00.00.00.00.00.00.000
22AFG2Afghanistan2573Sunflowerseed Oil5142Food1000 tonnes33.9467.71...4.06.05.09.03.08.015.016.01723
23AFG2Afghanistan2574Rape and Mustard Oil5142Food1000 tonnes33.9467.71...0.01.03.05.06.06.01.02.022
24AFG2Afghanistan2575Cottonseed Oil5142Food1000 tonnes33.9467.71...2.03.03.03.03.04.03.03.034
25AFG2Afghanistan2577Palm Oil5142Food1000 tonnes33.9467.71...71.069.056.051.036.053.059.051.06164
26AFG2Afghanistan2579Sesameseed Oil5142Food1000 tonnes33.9467.71...1.01.01.02.02.01.01.02.011
27AFG2Afghanistan2580Olive Oil5142Food1000 tonnes33.9467.71...0.00.00.00.00.01.01.01.011
28AFG2Afghanistan2586Oilcrops Oil, Other5142Food1000 tonnes33.9467.71...0.01.00.00.03.01.02.02.022
29AFG2Afghanistan2601Tomatoes and products5142Food1000 tonnes33.9467.71...2.02.08.01.00.00.00.00.000
..................................................................
21447ZWE181Zimbabwe2765Crustaceans5142Food1000 tonnes-19.0229.15...0.00.00.00.00.00.00.00.000
21448ZWE181Zimbabwe2766Cephalopods5142Food1000 tonnes-19.0229.15...0.00.00.00.00.00.00.00.000
21449ZWE181Zimbabwe2767Molluscs, Other5142Food1000 tonnes-19.0229.15...0.00.00.00.00.00.01.00.000
21450ZWE181Zimbabwe2775Aquatic Plants5142Food1000 tonnes-19.0229.15...0.00.00.00.00.00.00.00.000
21451ZWE181Zimbabwe2680Infant food5142Food1000 tonnes-19.0229.15...0.00.00.00.00.00.00.00.000
21452ZWE181Zimbabwe2905Cereals - Excluding Beer5521Feed1000 tonnes-19.0229.15...75.054.075.055.063.062.055.055.05555
21453ZWE181Zimbabwe2905Cereals - Excluding Beer5142Food1000 tonnes-19.0229.15...1844.01842.01944.01962.01918.01980.02011.02094.020712016
21454ZWE181Zimbabwe2907Starchy Roots5142Food1000 tonnes-19.0229.15...223.0236.0238.0228.0245.0258.0258.0269.0272276
21455ZWE181Zimbabwe2908Sugar Crops5142Food1000 tonnes-19.0229.15...0.00.00.00.00.00.00.00.000
21456ZWE181Zimbabwe2909Sugar & Sweeteners5142Food1000 tonnes-19.0229.15...335.0313.0339.0302.0285.0287.0314.0336.0396416
21457ZWE181Zimbabwe2911Pulses5142Food1000 tonnes-19.0229.15...63.059.061.057.069.078.068.056.05255
21458ZWE181Zimbabwe2912Treenuts5142Food1000 tonnes-19.0229.15...1.02.01.02.02.03.04.02.043
21459ZWE181Zimbabwe2913Oilcrops5521Feed1000 tonnes-19.0229.15...36.046.041.033.031.019.024.017.02730
21460ZWE181Zimbabwe2913Oilcrops5142Food1000 tonnes-19.0229.15...60.059.061.062.048.044.041.040.03838
21461ZWE181Zimbabwe2914Vegetable Oils5142Food1000 tonnes-19.0229.15...111.0114.0112.0114.0134.0135.0137.0147.0159160
21462ZWE181Zimbabwe2918Vegetables5142Food1000 tonnes-19.0229.15...161.0166.0208.0185.0137.0179.0215.0217.0227227
21463ZWE181Zimbabwe2919Fruits - Excluding Wine5142Food1000 tonnes-19.0229.15...191.0134.0167.0177.0185.0184.0211.0230.0246217
21464ZWE181Zimbabwe2922Stimulants5142Food1000 tonnes-19.0229.15...7.021.014.024.016.011.023.011.01010
21465ZWE181Zimbabwe2923Spices5142Food1000 tonnes-19.0229.15...7.011.07.012.016.016.014.011.01212
21466ZWE181Zimbabwe2924Alcoholic Beverages5142Food1000 tonnes-19.0229.15...294.0290.0316.0355.0398.0437.0448.0476.0525516
21467ZWE181Zimbabwe2943Meat5142Food1000 tonnes-19.0229.15...222.0228.0233.0238.0242.0265.0262.0277.0280258
21468ZWE181Zimbabwe2945Offals5142Food1000 tonnes-19.0229.15...20.020.021.021.021.021.021.021.02222
21469ZWE181Zimbabwe2946Animal fats5142Food1000 tonnes-19.0229.15...26.026.029.029.027.031.030.025.02620
21470ZWE181Zimbabwe2949Eggs5142Food1000 tonnes-19.0229.15...15.018.018.021.022.027.027.024.02425
21471ZWE181Zimbabwe2948Milk - Excluding Butter5521Feed1000 tonnes-19.0229.15...21.021.021.021.021.023.025.025.03031
21472ZWE181Zimbabwe2948Milk - Excluding Butter5142Food1000 tonnes-19.0229.15...373.0357.0359.0356.0341.0385.0418.0457.0426451
21473ZWE181Zimbabwe2960Fish, Seafood5521Feed1000 tonnes-19.0229.15...5.04.09.06.09.05.015.015.01515
21474ZWE181Zimbabwe2960Fish, Seafood5142Food1000 tonnes-19.0229.15...18.014.017.014.015.018.029.040.04040
21475ZWE181Zimbabwe2961Aquatic Products, Other5142Food1000 tonnes-19.0229.15...0.00.00.00.00.00.00.00.000
21476ZWE181Zimbabwe2928Miscellaneous5142Food1000 tonnes-19.0229.15...0.00.00.00.00.00.00.00.000
\n", - "

21477 rows × 63 columns

\n", - "
" - ], - "text/plain": [ - " Area Abbreviation Area Code Area Item Code \\\n", - "0 AFG 2 Afghanistan 2511 \n", - "1 AFG 2 Afghanistan 2805 \n", - "2 AFG 2 Afghanistan 2513 \n", - "3 AFG 2 Afghanistan 2513 \n", - "4 AFG 2 Afghanistan 2514 \n", - "5 AFG 2 Afghanistan 2514 \n", - "6 AFG 2 Afghanistan 2517 \n", - "7 AFG 2 Afghanistan 2520 \n", - "8 AFG 2 Afghanistan 2531 \n", - "9 AFG 2 Afghanistan 2536 \n", - "10 AFG 2 Afghanistan 2537 \n", - "11 AFG 2 Afghanistan 2542 \n", - "12 AFG 2 Afghanistan 2543 \n", - "13 AFG 2 Afghanistan 2745 \n", - "14 AFG 2 Afghanistan 2549 \n", - "15 AFG 2 Afghanistan 2549 \n", - "16 AFG 2 Afghanistan 2551 \n", - "17 AFG 2 Afghanistan 2560 \n", - "18 AFG 2 Afghanistan 2561 \n", - "19 AFG 2 Afghanistan 2563 \n", - "20 AFG 2 Afghanistan 2571 \n", - "21 AFG 2 Afghanistan 2572 \n", - "22 AFG 2 Afghanistan 2573 \n", - "23 AFG 2 Afghanistan 2574 \n", - "24 AFG 2 Afghanistan 2575 \n", - "25 AFG 2 Afghanistan 2577 \n", - "26 AFG 2 Afghanistan 2579 \n", - "27 AFG 2 Afghanistan 2580 \n", - "28 AFG 2 Afghanistan 2586 \n", - "29 AFG 2 Afghanistan 2601 \n", - "... ... ... ... ... \n", - "21447 ZWE 181 Zimbabwe 2765 \n", - "21448 ZWE 181 Zimbabwe 2766 \n", - "21449 ZWE 181 Zimbabwe 2767 \n", - "21450 ZWE 181 Zimbabwe 2775 \n", - "21451 ZWE 181 Zimbabwe 2680 \n", - "21452 ZWE 181 Zimbabwe 2905 \n", - "21453 ZWE 181 Zimbabwe 2905 \n", - "21454 ZWE 181 Zimbabwe 2907 \n", - "21455 ZWE 181 Zimbabwe 2908 \n", - "21456 ZWE 181 Zimbabwe 2909 \n", - "21457 ZWE 181 Zimbabwe 2911 \n", - "21458 ZWE 181 Zimbabwe 2912 \n", - "21459 ZWE 181 Zimbabwe 2913 \n", - "21460 ZWE 181 Zimbabwe 2913 \n", - "21461 ZWE 181 Zimbabwe 2914 \n", - "21462 ZWE 181 Zimbabwe 2918 \n", - "21463 ZWE 181 Zimbabwe 2919 \n", - "21464 ZWE 181 Zimbabwe 2922 \n", - "21465 ZWE 181 Zimbabwe 2923 \n", - "21466 ZWE 181 Zimbabwe 2924 \n", - "21467 ZWE 181 Zimbabwe 2943 \n", - "21468 ZWE 181 Zimbabwe 2945 \n", - "21469 ZWE 181 Zimbabwe 2946 \n", - "21470 ZWE 181 Zimbabwe 2949 \n", - "21471 ZWE 181 Zimbabwe 2948 \n", - "21472 ZWE 181 Zimbabwe 2948 \n", - "21473 ZWE 181 Zimbabwe 2960 \n", - "21474 ZWE 181 Zimbabwe 2960 \n", - "21475 ZWE 181 Zimbabwe 2961 \n", - "21476 ZWE 181 Zimbabwe 2928 \n", - "\n", - " Item Element Code Element Unit \\\n", - "0 Wheat and products 5142 Food 1000 tonnes \n", - "1 Rice (Milled Equivalent) 5142 Food 1000 tonnes \n", - "2 Barley and products 5521 Feed 1000 tonnes \n", - "3 Barley and products 5142 Food 1000 tonnes \n", - "4 Maize and products 5521 Feed 1000 tonnes \n", - "5 Maize and products 5142 Food 1000 tonnes \n", - "6 Millet and products 5142 Food 1000 tonnes \n", - "7 Cereals, Other 5142 Food 1000 tonnes \n", - "8 Potatoes and products 5142 Food 1000 tonnes \n", - "9 Sugar cane 5521 Feed 1000 tonnes \n", - "10 Sugar beet 5521 Feed 1000 tonnes \n", - "11 Sugar (Raw Equivalent) 5142 Food 1000 tonnes \n", - "12 Sweeteners, Other 5142 Food 1000 tonnes \n", - "13 Honey 5142 Food 1000 tonnes \n", - "14 Pulses, Other and products 5521 Feed 1000 tonnes \n", - "15 Pulses, Other and products 5142 Food 1000 tonnes \n", - "16 Nuts and products 5142 Food 1000 tonnes \n", - "17 Coconuts - Incl Copra 5142 Food 1000 tonnes \n", - "18 Sesame seed 5142 Food 1000 tonnes \n", - "19 Olives (including preserved) 5142 Food 1000 tonnes \n", - "20 Soyabean Oil 5142 Food 1000 tonnes \n", - "21 Groundnut Oil 5142 Food 1000 tonnes \n", - "22 Sunflowerseed Oil 5142 Food 1000 tonnes \n", - "23 Rape and Mustard Oil 5142 Food 1000 tonnes \n", - "24 Cottonseed Oil 5142 Food 1000 tonnes \n", - "25 Palm Oil 5142 Food 1000 tonnes \n", - "26 Sesameseed Oil 5142 Food 1000 tonnes \n", - "27 Olive Oil 5142 Food 1000 tonnes \n", - "28 Oilcrops Oil, Other 5142 Food 1000 tonnes \n", - "29 Tomatoes and products 5142 Food 1000 tonnes \n", - "... ... ... ... ... \n", - "21447 Crustaceans 5142 Food 1000 tonnes \n", - "21448 Cephalopods 5142 Food 1000 tonnes \n", - "21449 Molluscs, Other 5142 Food 1000 tonnes \n", - "21450 Aquatic Plants 5142 Food 1000 tonnes \n", - "21451 Infant food 5142 Food 1000 tonnes \n", - "21452 Cereals - Excluding Beer 5521 Feed 1000 tonnes \n", - "21453 Cereals - Excluding Beer 5142 Food 1000 tonnes \n", - "21454 Starchy Roots 5142 Food 1000 tonnes \n", - "21455 Sugar Crops 5142 Food 1000 tonnes \n", - "21456 Sugar & Sweeteners 5142 Food 1000 tonnes \n", - "21457 Pulses 5142 Food 1000 tonnes \n", - "21458 Treenuts 5142 Food 1000 tonnes \n", - "21459 Oilcrops 5521 Feed 1000 tonnes \n", - "21460 Oilcrops 5142 Food 1000 tonnes \n", - "21461 Vegetable Oils 5142 Food 1000 tonnes \n", - "21462 Vegetables 5142 Food 1000 tonnes \n", - "21463 Fruits - Excluding Wine 5142 Food 1000 tonnes \n", - "21464 Stimulants 5142 Food 1000 tonnes \n", - "21465 Spices 5142 Food 1000 tonnes \n", - "21466 Alcoholic Beverages 5142 Food 1000 tonnes \n", - "21467 Meat 5142 Food 1000 tonnes \n", - "21468 Offals 5142 Food 1000 tonnes \n", - "21469 Animal fats 5142 Food 1000 tonnes \n", - "21470 Eggs 5142 Food 1000 tonnes \n", - "21471 Milk - Excluding Butter 5521 Feed 1000 tonnes \n", - "21472 Milk - Excluding Butter 5142 Food 1000 tonnes \n", - "21473 Fish, Seafood 5521 Feed 1000 tonnes \n", - "21474 Fish, Seafood 5142 Food 1000 tonnes \n", - "21475 Aquatic Products, Other 5142 Food 1000 tonnes \n", - "21476 Miscellaneous 5142 Food 1000 tonnes \n", - "\n", - " latitude longitude ... Y2004 Y2005 Y2006 Y2007 Y2008 \\\n", - "0 33.94 67.71 ... 3249.0 3486.0 3704.0 4164.0 4252.0 \n", - "1 33.94 67.71 ... 419.0 445.0 546.0 455.0 490.0 \n", - "2 33.94 67.71 ... 58.0 236.0 262.0 263.0 230.0 \n", - "3 33.94 67.71 ... 185.0 43.0 44.0 48.0 62.0 \n", - "4 33.94 67.71 ... 120.0 208.0 233.0 249.0 247.0 \n", - "5 33.94 67.71 ... 231.0 67.0 82.0 67.0 69.0 \n", - "6 33.94 67.71 ... 15.0 21.0 11.0 19.0 21.0 \n", - "7 33.94 67.71 ... 2.0 1.0 1.0 0.0 0.0 \n", - "8 33.94 67.71 ... 276.0 294.0 294.0 260.0 242.0 \n", - "9 33.94 67.71 ... 50.0 29.0 61.0 65.0 54.0 \n", - "10 33.94 67.71 ... 0.0 0.0 0.0 0.0 0.0 \n", - "11 33.94 67.71 ... 124.0 152.0 169.0 192.0 217.0 \n", - "12 33.94 67.71 ... 9.0 15.0 12.0 6.0 11.0 \n", - "13 33.94 67.71 ... 3.0 3.0 3.0 3.0 3.0 \n", - "14 33.94 67.71 ... 3.0 2.0 3.0 3.0 3.0 \n", - "15 33.94 67.71 ... 17.0 35.0 37.0 40.0 54.0 \n", - "16 33.94 67.71 ... 11.0 13.0 24.0 34.0 42.0 \n", - "17 33.94 67.71 ... 0.0 0.0 0.0 0.0 0.0 \n", - "18 33.94 67.71 ... 16.0 16.0 13.0 16.0 16.0 \n", - "19 33.94 67.71 ... 1.0 1.0 0.0 0.0 2.0 \n", - "20 33.94 67.71 ... 6.0 35.0 18.0 21.0 11.0 \n", - "21 33.94 67.71 ... 0.0 0.0 0.0 0.0 0.0 \n", - "22 33.94 67.71 ... 4.0 6.0 5.0 9.0 3.0 \n", - "23 33.94 67.71 ... 0.0 1.0 3.0 5.0 6.0 \n", - "24 33.94 67.71 ... 2.0 3.0 3.0 3.0 3.0 \n", - "25 33.94 67.71 ... 71.0 69.0 56.0 51.0 36.0 \n", - "26 33.94 67.71 ... 1.0 1.0 1.0 2.0 2.0 \n", - "27 33.94 67.71 ... 0.0 0.0 0.0 0.0 0.0 \n", - "28 33.94 67.71 ... 0.0 1.0 0.0 0.0 3.0 \n", - "29 33.94 67.71 ... 2.0 2.0 8.0 1.0 0.0 \n", - "... ... ... ... ... ... ... ... ... \n", - "21447 -19.02 29.15 ... 0.0 0.0 0.0 0.0 0.0 \n", - "21448 -19.02 29.15 ... 0.0 0.0 0.0 0.0 0.0 \n", - "21449 -19.02 29.15 ... 0.0 0.0 0.0 0.0 0.0 \n", - "21450 -19.02 29.15 ... 0.0 0.0 0.0 0.0 0.0 \n", - "21451 -19.02 29.15 ... 0.0 0.0 0.0 0.0 0.0 \n", - "21452 -19.02 29.15 ... 75.0 54.0 75.0 55.0 63.0 \n", - "21453 -19.02 29.15 ... 1844.0 1842.0 1944.0 1962.0 1918.0 \n", - "21454 -19.02 29.15 ... 223.0 236.0 238.0 228.0 245.0 \n", - "21455 -19.02 29.15 ... 0.0 0.0 0.0 0.0 0.0 \n", - "21456 -19.02 29.15 ... 335.0 313.0 339.0 302.0 285.0 \n", - "21457 -19.02 29.15 ... 63.0 59.0 61.0 57.0 69.0 \n", - "21458 -19.02 29.15 ... 1.0 2.0 1.0 2.0 2.0 \n", - "21459 -19.02 29.15 ... 36.0 46.0 41.0 33.0 31.0 \n", - "21460 -19.02 29.15 ... 60.0 59.0 61.0 62.0 48.0 \n", - "21461 -19.02 29.15 ... 111.0 114.0 112.0 114.0 134.0 \n", - "21462 -19.02 29.15 ... 161.0 166.0 208.0 185.0 137.0 \n", - "21463 -19.02 29.15 ... 191.0 134.0 167.0 177.0 185.0 \n", - "21464 -19.02 29.15 ... 7.0 21.0 14.0 24.0 16.0 \n", - "21465 -19.02 29.15 ... 7.0 11.0 7.0 12.0 16.0 \n", - "21466 -19.02 29.15 ... 294.0 290.0 316.0 355.0 398.0 \n", - "21467 -19.02 29.15 ... 222.0 228.0 233.0 238.0 242.0 \n", - "21468 -19.02 29.15 ... 20.0 20.0 21.0 21.0 21.0 \n", - "21469 -19.02 29.15 ... 26.0 26.0 29.0 29.0 27.0 \n", - "21470 -19.02 29.15 ... 15.0 18.0 18.0 21.0 22.0 \n", - "21471 -19.02 29.15 ... 21.0 21.0 21.0 21.0 21.0 \n", - "21472 -19.02 29.15 ... 373.0 357.0 359.0 356.0 341.0 \n", - "21473 -19.02 29.15 ... 5.0 4.0 9.0 6.0 9.0 \n", - "21474 -19.02 29.15 ... 18.0 14.0 17.0 14.0 15.0 \n", - "21475 -19.02 29.15 ... 0.0 0.0 0.0 0.0 0.0 \n", - "21476 -19.02 29.15 ... 0.0 0.0 0.0 0.0 0.0 \n", - "\n", - " Y2009 Y2010 Y2011 Y2012 Y2013 \n", - "0 4538.0 4605.0 4711.0 4810 4895 \n", - "1 415.0 442.0 476.0 425 422 \n", - "2 379.0 315.0 203.0 367 360 \n", - "3 55.0 60.0 72.0 78 89 \n", - "4 195.0 178.0 191.0 200 200 \n", - "5 71.0 82.0 73.0 77 76 \n", - "6 18.0 14.0 14.0 14 12 \n", - "7 0.0 0.0 0.0 0 0 \n", - "8 250.0 192.0 169.0 196 230 \n", - "9 114.0 83.0 83.0 69 81 \n", - "10 0.0 0.0 0.0 0 0 \n", - "11 231.0 240.0 240.0 250 255 \n", - "12 2.0 9.0 21.0 24 16 \n", - "13 3.0 3.0 2.0 2 2 \n", - "14 5.0 4.0 5.0 4 4 \n", - "15 80.0 66.0 81.0 63 74 \n", - "16 28.0 66.0 71.0 70 44 \n", - "17 0.0 0.0 0.0 0 0 \n", - "18 16.0 19.0 17.0 16 16 \n", - "19 3.0 2.0 2.0 2 2 \n", - "20 6.0 15.0 16.0 16 16 \n", - "21 0.0 0.0 0.0 0 0 \n", - "22 8.0 15.0 16.0 17 23 \n", - "23 6.0 1.0 2.0 2 2 \n", - "24 4.0 3.0 3.0 3 4 \n", - "25 53.0 59.0 51.0 61 64 \n", - "26 1.0 1.0 2.0 1 1 \n", - "27 1.0 1.0 1.0 1 1 \n", - "28 1.0 2.0 2.0 2 2 \n", - "29 0.0 0.0 0.0 0 0 \n", - "... ... ... ... ... ... \n", - "21447 0.0 0.0 0.0 0 0 \n", - "21448 0.0 0.0 0.0 0 0 \n", - "21449 0.0 1.0 0.0 0 0 \n", - "21450 0.0 0.0 0.0 0 0 \n", - "21451 0.0 0.0 0.0 0 0 \n", - "21452 62.0 55.0 55.0 55 55 \n", - "21453 1980.0 2011.0 2094.0 2071 2016 \n", - "21454 258.0 258.0 269.0 272 276 \n", - "21455 0.0 0.0 0.0 0 0 \n", - "21456 287.0 314.0 336.0 396 416 \n", - "21457 78.0 68.0 56.0 52 55 \n", - "21458 3.0 4.0 2.0 4 3 \n", - "21459 19.0 24.0 17.0 27 30 \n", - "21460 44.0 41.0 40.0 38 38 \n", - "21461 135.0 137.0 147.0 159 160 \n", - "21462 179.0 215.0 217.0 227 227 \n", - "21463 184.0 211.0 230.0 246 217 \n", - "21464 11.0 23.0 11.0 10 10 \n", - "21465 16.0 14.0 11.0 12 12 \n", - "21466 437.0 448.0 476.0 525 516 \n", - "21467 265.0 262.0 277.0 280 258 \n", - "21468 21.0 21.0 21.0 22 22 \n", - "21469 31.0 30.0 25.0 26 20 \n", - "21470 27.0 27.0 24.0 24 25 \n", - "21471 23.0 25.0 25.0 30 31 \n", - "21472 385.0 418.0 457.0 426 451 \n", - "21473 5.0 15.0 15.0 15 15 \n", - "21474 18.0 29.0 40.0 40 40 \n", - "21475 0.0 0.0 0.0 0 0 \n", - "21476 0.0 0.0 0.0 0 0 \n", - "\n", - "[21477 rows x 63 columns]" - ] - }, - "execution_count": 2, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "df" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "_cell_guid": "731a952c-b292-46e3-be7a-4afffe2b4ff1", - "_uuid": "5d165c279ce22afc0a874e32931d7b0ebb0717f9" - }, - "source": [ - "Let's see what the data looks like..." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "_cell_guid": "79c7e3d0-c299-4dcb-8224-4455121ee9b0", - "_uuid": "d629ff2d2480ee46fbb7e2d37f6b5fab8052498a", - "scrolled": true - }, - "outputs": [], - "source": [] - }, - { - "cell_type": "markdown", - "metadata": { - "_cell_guid": "25c3f986-fd14-4a3f-baff-02571ad665eb", - "_uuid": "5a7da58320ab35ab1bcf83a62209afbe40b672fe" - }, - "source": [ - "# Plot for annual produce of different countries with quantity in y-axis and years in x-axis" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "
\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
Area AbbreviationArea CodeAreaItem CodeItemElement CodeElementUnitlatitudelongitude...Y2004Y2005Y2006Y2007Y2008Y2009Y2010Y2011Y2012Y2013
0AFG2Afghanistan2511Wheat and products5142Food1000 tonnes33.9467.71...3249.03486.03704.04164.04252.04538.04605.04711.048104895
1AFG2Afghanistan2805Rice (Milled Equivalent)5142Food1000 tonnes33.9467.71...419.0445.0546.0455.0490.0415.0442.0476.0425422
2AFG2Afghanistan2513Barley and products5521Feed1000 tonnes33.9467.71...58.0236.0262.0263.0230.0379.0315.0203.0367360
3AFG2Afghanistan2513Barley and products5142Food1000 tonnes33.9467.71...185.043.044.048.062.055.060.072.07889
4AFG2Afghanistan2514Maize and products5521Feed1000 tonnes33.9467.71...120.0208.0233.0249.0247.0195.0178.0191.0200200
5AFG2Afghanistan2514Maize and products5142Food1000 tonnes33.9467.71...231.067.082.067.069.071.082.073.07776
6AFG2Afghanistan2517Millet and products5142Food1000 tonnes33.9467.71...15.021.011.019.021.018.014.014.01412
7AFG2Afghanistan2520Cereals, Other5142Food1000 tonnes33.9467.71...2.01.01.00.00.00.00.00.000
8AFG2Afghanistan2531Potatoes and products5142Food1000 tonnes33.9467.71...276.0294.0294.0260.0242.0250.0192.0169.0196230
9AFG2Afghanistan2536Sugar cane5521Feed1000 tonnes33.9467.71...50.029.061.065.054.0114.083.083.06981
10AFG2Afghanistan2537Sugar beet5521Feed1000 tonnes33.9467.71...0.00.00.00.00.00.00.00.000
11AFG2Afghanistan2542Sugar (Raw Equivalent)5142Food1000 tonnes33.9467.71...124.0152.0169.0192.0217.0231.0240.0240.0250255
12AFG2Afghanistan2543Sweeteners, Other5142Food1000 tonnes33.9467.71...9.015.012.06.011.02.09.021.02416
13AFG2Afghanistan2745Honey5142Food1000 tonnes33.9467.71...3.03.03.03.03.03.03.02.022
14AFG2Afghanistan2549Pulses, Other and products5521Feed1000 tonnes33.9467.71...3.02.03.03.03.05.04.05.044
15AFG2Afghanistan2549Pulses, Other and products5142Food1000 tonnes33.9467.71...17.035.037.040.054.080.066.081.06374
16AFG2Afghanistan2551Nuts and products5142Food1000 tonnes33.9467.71...11.013.024.034.042.028.066.071.07044
17AFG2Afghanistan2560Coconuts - Incl Copra5142Food1000 tonnes33.9467.71...0.00.00.00.00.00.00.00.000
18AFG2Afghanistan2561Sesame seed5142Food1000 tonnes33.9467.71...16.016.013.016.016.016.019.017.01616
19AFG2Afghanistan2563Olives (including preserved)5142Food1000 tonnes33.9467.71...1.01.00.00.02.03.02.02.022
20AFG2Afghanistan2571Soyabean Oil5142Food1000 tonnes33.9467.71...6.035.018.021.011.06.015.016.01616
21AFG2Afghanistan2572Groundnut Oil5142Food1000 tonnes33.9467.71...0.00.00.00.00.00.00.00.000
22AFG2Afghanistan2573Sunflowerseed Oil5142Food1000 tonnes33.9467.71...4.06.05.09.03.08.015.016.01723
23AFG2Afghanistan2574Rape and Mustard Oil5142Food1000 tonnes33.9467.71...0.01.03.05.06.06.01.02.022
24AFG2Afghanistan2575Cottonseed Oil5142Food1000 tonnes33.9467.71...2.03.03.03.03.04.03.03.034
25AFG2Afghanistan2577Palm Oil5142Food1000 tonnes33.9467.71...71.069.056.051.036.053.059.051.06164
26AFG2Afghanistan2579Sesameseed Oil5142Food1000 tonnes33.9467.71...1.01.01.02.02.01.01.02.011
27AFG2Afghanistan2580Olive Oil5142Food1000 tonnes33.9467.71...0.00.00.00.00.01.01.01.011
28AFG2Afghanistan2586Oilcrops Oil, Other5142Food1000 tonnes33.9467.71...0.01.00.00.03.01.02.02.022
29AFG2Afghanistan2601Tomatoes and products5142Food1000 tonnes33.9467.71...2.02.08.01.00.00.00.00.000
..................................................................
21447ZWE181Zimbabwe2765Crustaceans5142Food1000 tonnes-19.0229.15...0.00.00.00.00.00.00.00.000
21448ZWE181Zimbabwe2766Cephalopods5142Food1000 tonnes-19.0229.15...0.00.00.00.00.00.00.00.000
21449ZWE181Zimbabwe2767Molluscs, Other5142Food1000 tonnes-19.0229.15...0.00.00.00.00.00.01.00.000
21450ZWE181Zimbabwe2775Aquatic Plants5142Food1000 tonnes-19.0229.15...0.00.00.00.00.00.00.00.000
21451ZWE181Zimbabwe2680Infant food5142Food1000 tonnes-19.0229.15...0.00.00.00.00.00.00.00.000
21452ZWE181Zimbabwe2905Cereals - Excluding Beer5521Feed1000 tonnes-19.0229.15...75.054.075.055.063.062.055.055.05555
21453ZWE181Zimbabwe2905Cereals - Excluding Beer5142Food1000 tonnes-19.0229.15...1844.01842.01944.01962.01918.01980.02011.02094.020712016
21454ZWE181Zimbabwe2907Starchy Roots5142Food1000 tonnes-19.0229.15...223.0236.0238.0228.0245.0258.0258.0269.0272276
21455ZWE181Zimbabwe2908Sugar Crops5142Food1000 tonnes-19.0229.15...0.00.00.00.00.00.00.00.000
21456ZWE181Zimbabwe2909Sugar & Sweeteners5142Food1000 tonnes-19.0229.15...335.0313.0339.0302.0285.0287.0314.0336.0396416
21457ZWE181Zimbabwe2911Pulses5142Food1000 tonnes-19.0229.15...63.059.061.057.069.078.068.056.05255
21458ZWE181Zimbabwe2912Treenuts5142Food1000 tonnes-19.0229.15...1.02.01.02.02.03.04.02.043
21459ZWE181Zimbabwe2913Oilcrops5521Feed1000 tonnes-19.0229.15...36.046.041.033.031.019.024.017.02730
21460ZWE181Zimbabwe2913Oilcrops5142Food1000 tonnes-19.0229.15...60.059.061.062.048.044.041.040.03838
21461ZWE181Zimbabwe2914Vegetable Oils5142Food1000 tonnes-19.0229.15...111.0114.0112.0114.0134.0135.0137.0147.0159160
21462ZWE181Zimbabwe2918Vegetables5142Food1000 tonnes-19.0229.15...161.0166.0208.0185.0137.0179.0215.0217.0227227
21463ZWE181Zimbabwe2919Fruits - Excluding Wine5142Food1000 tonnes-19.0229.15...191.0134.0167.0177.0185.0184.0211.0230.0246217
21464ZWE181Zimbabwe2922Stimulants5142Food1000 tonnes-19.0229.15...7.021.014.024.016.011.023.011.01010
21465ZWE181Zimbabwe2923Spices5142Food1000 tonnes-19.0229.15...7.011.07.012.016.016.014.011.01212
21466ZWE181Zimbabwe2924Alcoholic Beverages5142Food1000 tonnes-19.0229.15...294.0290.0316.0355.0398.0437.0448.0476.0525516
21467ZWE181Zimbabwe2943Meat5142Food1000 tonnes-19.0229.15...222.0228.0233.0238.0242.0265.0262.0277.0280258
21468ZWE181Zimbabwe2945Offals5142Food1000 tonnes-19.0229.15...20.020.021.021.021.021.021.021.02222
21469ZWE181Zimbabwe2946Animal fats5142Food1000 tonnes-19.0229.15...26.026.029.029.027.031.030.025.02620
21470ZWE181Zimbabwe2949Eggs5142Food1000 tonnes-19.0229.15...15.018.018.021.022.027.027.024.02425
21471ZWE181Zimbabwe2948Milk - Excluding Butter5521Feed1000 tonnes-19.0229.15...21.021.021.021.021.023.025.025.03031
21472ZWE181Zimbabwe2948Milk - Excluding Butter5142Food1000 tonnes-19.0229.15...373.0357.0359.0356.0341.0385.0418.0457.0426451
21473ZWE181Zimbabwe2960Fish, Seafood5521Feed1000 tonnes-19.0229.15...5.04.09.06.09.05.015.015.01515
21474ZWE181Zimbabwe2960Fish, Seafood5142Food1000 tonnes-19.0229.15...18.014.017.014.015.018.029.040.04040
21475ZWE181Zimbabwe2961Aquatic Products, Other5142Food1000 tonnes-19.0229.15...0.00.00.00.00.00.00.00.000
21476ZWE181Zimbabwe2928Miscellaneous5142Food1000 tonnes-19.0229.15...0.00.00.00.00.00.00.00.000
\n", - "

21477 rows × 63 columns

\n", - "
" - ], - "text/plain": [ - " Area Abbreviation Area Code Area Item Code \\\n", - "0 AFG 2 Afghanistan 2511 \n", - "1 AFG 2 Afghanistan 2805 \n", - "2 AFG 2 Afghanistan 2513 \n", - "3 AFG 2 Afghanistan 2513 \n", - "4 AFG 2 Afghanistan 2514 \n", - "5 AFG 2 Afghanistan 2514 \n", - "6 AFG 2 Afghanistan 2517 \n", - "7 AFG 2 Afghanistan 2520 \n", - "8 AFG 2 Afghanistan 2531 \n", - "9 AFG 2 Afghanistan 2536 \n", - "10 AFG 2 Afghanistan 2537 \n", - "11 AFG 2 Afghanistan 2542 \n", - "12 AFG 2 Afghanistan 2543 \n", - "13 AFG 2 Afghanistan 2745 \n", - "14 AFG 2 Afghanistan 2549 \n", - "15 AFG 2 Afghanistan 2549 \n", - "16 AFG 2 Afghanistan 2551 \n", - "17 AFG 2 Afghanistan 2560 \n", - "18 AFG 2 Afghanistan 2561 \n", - "19 AFG 2 Afghanistan 2563 \n", - "20 AFG 2 Afghanistan 2571 \n", - "21 AFG 2 Afghanistan 2572 \n", - "22 AFG 2 Afghanistan 2573 \n", - "23 AFG 2 Afghanistan 2574 \n", - "24 AFG 2 Afghanistan 2575 \n", - "25 AFG 2 Afghanistan 2577 \n", - "26 AFG 2 Afghanistan 2579 \n", - "27 AFG 2 Afghanistan 2580 \n", - "28 AFG 2 Afghanistan 2586 \n", - "29 AFG 2 Afghanistan 2601 \n", - "... ... ... ... ... \n", - "21447 ZWE 181 Zimbabwe 2765 \n", - "21448 ZWE 181 Zimbabwe 2766 \n", - "21449 ZWE 181 Zimbabwe 2767 \n", - "21450 ZWE 181 Zimbabwe 2775 \n", - "21451 ZWE 181 Zimbabwe 2680 \n", - "21452 ZWE 181 Zimbabwe 2905 \n", - "21453 ZWE 181 Zimbabwe 2905 \n", - "21454 ZWE 181 Zimbabwe 2907 \n", - "21455 ZWE 181 Zimbabwe 2908 \n", - "21456 ZWE 181 Zimbabwe 2909 \n", - "21457 ZWE 181 Zimbabwe 2911 \n", - "21458 ZWE 181 Zimbabwe 2912 \n", - "21459 ZWE 181 Zimbabwe 2913 \n", - "21460 ZWE 181 Zimbabwe 2913 \n", - "21461 ZWE 181 Zimbabwe 2914 \n", - "21462 ZWE 181 Zimbabwe 2918 \n", - "21463 ZWE 181 Zimbabwe 2919 \n", - "21464 ZWE 181 Zimbabwe 2922 \n", - "21465 ZWE 181 Zimbabwe 2923 \n", - "21466 ZWE 181 Zimbabwe 2924 \n", - "21467 ZWE 181 Zimbabwe 2943 \n", - "21468 ZWE 181 Zimbabwe 2945 \n", - "21469 ZWE 181 Zimbabwe 2946 \n", - "21470 ZWE 181 Zimbabwe 2949 \n", - "21471 ZWE 181 Zimbabwe 2948 \n", - "21472 ZWE 181 Zimbabwe 2948 \n", - "21473 ZWE 181 Zimbabwe 2960 \n", - "21474 ZWE 181 Zimbabwe 2960 \n", - "21475 ZWE 181 Zimbabwe 2961 \n", - "21476 ZWE 181 Zimbabwe 2928 \n", - "\n", - " Item Element Code Element Unit \\\n", - "0 Wheat and products 5142 Food 1000 tonnes \n", - "1 Rice (Milled Equivalent) 5142 Food 1000 tonnes \n", - "2 Barley and products 5521 Feed 1000 tonnes \n", - "3 Barley and products 5142 Food 1000 tonnes \n", - "4 Maize and products 5521 Feed 1000 tonnes \n", - "5 Maize and products 5142 Food 1000 tonnes \n", - "6 Millet and products 5142 Food 1000 tonnes \n", - "7 Cereals, Other 5142 Food 1000 tonnes \n", - "8 Potatoes and products 5142 Food 1000 tonnes \n", - "9 Sugar cane 5521 Feed 1000 tonnes \n", - "10 Sugar beet 5521 Feed 1000 tonnes \n", - "11 Sugar (Raw Equivalent) 5142 Food 1000 tonnes \n", - "12 Sweeteners, Other 5142 Food 1000 tonnes \n", - "13 Honey 5142 Food 1000 tonnes \n", - "14 Pulses, Other and products 5521 Feed 1000 tonnes \n", - "15 Pulses, Other and products 5142 Food 1000 tonnes \n", - "16 Nuts and products 5142 Food 1000 tonnes \n", - "17 Coconuts - Incl Copra 5142 Food 1000 tonnes \n", - "18 Sesame seed 5142 Food 1000 tonnes \n", - "19 Olives (including preserved) 5142 Food 1000 tonnes \n", - "20 Soyabean Oil 5142 Food 1000 tonnes \n", - "21 Groundnut Oil 5142 Food 1000 tonnes \n", - "22 Sunflowerseed Oil 5142 Food 1000 tonnes \n", - "23 Rape and Mustard Oil 5142 Food 1000 tonnes \n", - "24 Cottonseed Oil 5142 Food 1000 tonnes \n", - "25 Palm Oil 5142 Food 1000 tonnes \n", - "26 Sesameseed Oil 5142 Food 1000 tonnes \n", - "27 Olive Oil 5142 Food 1000 tonnes \n", - "28 Oilcrops Oil, Other 5142 Food 1000 tonnes \n", - "29 Tomatoes and products 5142 Food 1000 tonnes \n", - "... ... ... ... ... \n", - "21447 Crustaceans 5142 Food 1000 tonnes \n", - "21448 Cephalopods 5142 Food 1000 tonnes \n", - "21449 Molluscs, Other 5142 Food 1000 tonnes \n", - "21450 Aquatic Plants 5142 Food 1000 tonnes \n", - "21451 Infant food 5142 Food 1000 tonnes \n", - "21452 Cereals - Excluding Beer 5521 Feed 1000 tonnes \n", - "21453 Cereals - Excluding Beer 5142 Food 1000 tonnes \n", - "21454 Starchy Roots 5142 Food 1000 tonnes \n", - "21455 Sugar Crops 5142 Food 1000 tonnes \n", - "21456 Sugar & Sweeteners 5142 Food 1000 tonnes \n", - "21457 Pulses 5142 Food 1000 tonnes \n", - "21458 Treenuts 5142 Food 1000 tonnes \n", - "21459 Oilcrops 5521 Feed 1000 tonnes \n", - "21460 Oilcrops 5142 Food 1000 tonnes \n", - "21461 Vegetable Oils 5142 Food 1000 tonnes \n", - "21462 Vegetables 5142 Food 1000 tonnes \n", - "21463 Fruits - Excluding Wine 5142 Food 1000 tonnes \n", - "21464 Stimulants 5142 Food 1000 tonnes \n", - "21465 Spices 5142 Food 1000 tonnes \n", - "21466 Alcoholic Beverages 5142 Food 1000 tonnes \n", - "21467 Meat 5142 Food 1000 tonnes \n", - "21468 Offals 5142 Food 1000 tonnes \n", - "21469 Animal fats 5142 Food 1000 tonnes \n", - "21470 Eggs 5142 Food 1000 tonnes \n", - "21471 Milk - Excluding Butter 5521 Feed 1000 tonnes \n", - "21472 Milk - Excluding Butter 5142 Food 1000 tonnes \n", - "21473 Fish, Seafood 5521 Feed 1000 tonnes \n", - "21474 Fish, Seafood 5142 Food 1000 tonnes \n", - "21475 Aquatic Products, Other 5142 Food 1000 tonnes \n", - "21476 Miscellaneous 5142 Food 1000 tonnes \n", - "\n", - " latitude longitude ... Y2004 Y2005 Y2006 Y2007 Y2008 \\\n", - "0 33.94 67.71 ... 3249.0 3486.0 3704.0 4164.0 4252.0 \n", - "1 33.94 67.71 ... 419.0 445.0 546.0 455.0 490.0 \n", - "2 33.94 67.71 ... 58.0 236.0 262.0 263.0 230.0 \n", - "3 33.94 67.71 ... 185.0 43.0 44.0 48.0 62.0 \n", - "4 33.94 67.71 ... 120.0 208.0 233.0 249.0 247.0 \n", - "5 33.94 67.71 ... 231.0 67.0 82.0 67.0 69.0 \n", - "6 33.94 67.71 ... 15.0 21.0 11.0 19.0 21.0 \n", - "7 33.94 67.71 ... 2.0 1.0 1.0 0.0 0.0 \n", - "8 33.94 67.71 ... 276.0 294.0 294.0 260.0 242.0 \n", - "9 33.94 67.71 ... 50.0 29.0 61.0 65.0 54.0 \n", - "10 33.94 67.71 ... 0.0 0.0 0.0 0.0 0.0 \n", - "11 33.94 67.71 ... 124.0 152.0 169.0 192.0 217.0 \n", - "12 33.94 67.71 ... 9.0 15.0 12.0 6.0 11.0 \n", - "13 33.94 67.71 ... 3.0 3.0 3.0 3.0 3.0 \n", - "14 33.94 67.71 ... 3.0 2.0 3.0 3.0 3.0 \n", - "15 33.94 67.71 ... 17.0 35.0 37.0 40.0 54.0 \n", - "16 33.94 67.71 ... 11.0 13.0 24.0 34.0 42.0 \n", - "17 33.94 67.71 ... 0.0 0.0 0.0 0.0 0.0 \n", - "18 33.94 67.71 ... 16.0 16.0 13.0 16.0 16.0 \n", - "19 33.94 67.71 ... 1.0 1.0 0.0 0.0 2.0 \n", - "20 33.94 67.71 ... 6.0 35.0 18.0 21.0 11.0 \n", - "21 33.94 67.71 ... 0.0 0.0 0.0 0.0 0.0 \n", - "22 33.94 67.71 ... 4.0 6.0 5.0 9.0 3.0 \n", - "23 33.94 67.71 ... 0.0 1.0 3.0 5.0 6.0 \n", - "24 33.94 67.71 ... 2.0 3.0 3.0 3.0 3.0 \n", - "25 33.94 67.71 ... 71.0 69.0 56.0 51.0 36.0 \n", - "26 33.94 67.71 ... 1.0 1.0 1.0 2.0 2.0 \n", - "27 33.94 67.71 ... 0.0 0.0 0.0 0.0 0.0 \n", - "28 33.94 67.71 ... 0.0 1.0 0.0 0.0 3.0 \n", - "29 33.94 67.71 ... 2.0 2.0 8.0 1.0 0.0 \n", - "... ... ... ... ... ... ... ... ... \n", - "21447 -19.02 29.15 ... 0.0 0.0 0.0 0.0 0.0 \n", - "21448 -19.02 29.15 ... 0.0 0.0 0.0 0.0 0.0 \n", - "21449 -19.02 29.15 ... 0.0 0.0 0.0 0.0 0.0 \n", - "21450 -19.02 29.15 ... 0.0 0.0 0.0 0.0 0.0 \n", - "21451 -19.02 29.15 ... 0.0 0.0 0.0 0.0 0.0 \n", - "21452 -19.02 29.15 ... 75.0 54.0 75.0 55.0 63.0 \n", - "21453 -19.02 29.15 ... 1844.0 1842.0 1944.0 1962.0 1918.0 \n", - "21454 -19.02 29.15 ... 223.0 236.0 238.0 228.0 245.0 \n", - "21455 -19.02 29.15 ... 0.0 0.0 0.0 0.0 0.0 \n", - "21456 -19.02 29.15 ... 335.0 313.0 339.0 302.0 285.0 \n", - "21457 -19.02 29.15 ... 63.0 59.0 61.0 57.0 69.0 \n", - "21458 -19.02 29.15 ... 1.0 2.0 1.0 2.0 2.0 \n", - "21459 -19.02 29.15 ... 36.0 46.0 41.0 33.0 31.0 \n", - "21460 -19.02 29.15 ... 60.0 59.0 61.0 62.0 48.0 \n", - "21461 -19.02 29.15 ... 111.0 114.0 112.0 114.0 134.0 \n", - "21462 -19.02 29.15 ... 161.0 166.0 208.0 185.0 137.0 \n", - "21463 -19.02 29.15 ... 191.0 134.0 167.0 177.0 185.0 \n", - "21464 -19.02 29.15 ... 7.0 21.0 14.0 24.0 16.0 \n", - "21465 -19.02 29.15 ... 7.0 11.0 7.0 12.0 16.0 \n", - "21466 -19.02 29.15 ... 294.0 290.0 316.0 355.0 398.0 \n", - "21467 -19.02 29.15 ... 222.0 228.0 233.0 238.0 242.0 \n", - "21468 -19.02 29.15 ... 20.0 20.0 21.0 21.0 21.0 \n", - "21469 -19.02 29.15 ... 26.0 26.0 29.0 29.0 27.0 \n", - "21470 -19.02 29.15 ... 15.0 18.0 18.0 21.0 22.0 \n", - "21471 -19.02 29.15 ... 21.0 21.0 21.0 21.0 21.0 \n", - "21472 -19.02 29.15 ... 373.0 357.0 359.0 356.0 341.0 \n", - "21473 -19.02 29.15 ... 5.0 4.0 9.0 6.0 9.0 \n", - "21474 -19.02 29.15 ... 18.0 14.0 17.0 14.0 15.0 \n", - "21475 -19.02 29.15 ... 0.0 0.0 0.0 0.0 0.0 \n", - "21476 -19.02 29.15 ... 0.0 0.0 0.0 0.0 0.0 \n", - "\n", - " Y2009 Y2010 Y2011 Y2012 Y2013 \n", - "0 4538.0 4605.0 4711.0 4810 4895 \n", - "1 415.0 442.0 476.0 425 422 \n", - "2 379.0 315.0 203.0 367 360 \n", - "3 55.0 60.0 72.0 78 89 \n", - "4 195.0 178.0 191.0 200 200 \n", - "5 71.0 82.0 73.0 77 76 \n", - "6 18.0 14.0 14.0 14 12 \n", - "7 0.0 0.0 0.0 0 0 \n", - "8 250.0 192.0 169.0 196 230 \n", - "9 114.0 83.0 83.0 69 81 \n", - "10 0.0 0.0 0.0 0 0 \n", - "11 231.0 240.0 240.0 250 255 \n", - "12 2.0 9.0 21.0 24 16 \n", - "13 3.0 3.0 2.0 2 2 \n", - "14 5.0 4.0 5.0 4 4 \n", - "15 80.0 66.0 81.0 63 74 \n", - "16 28.0 66.0 71.0 70 44 \n", - "17 0.0 0.0 0.0 0 0 \n", - "18 16.0 19.0 17.0 16 16 \n", - "19 3.0 2.0 2.0 2 2 \n", - "20 6.0 15.0 16.0 16 16 \n", - "21 0.0 0.0 0.0 0 0 \n", - "22 8.0 15.0 16.0 17 23 \n", - "23 6.0 1.0 2.0 2 2 \n", - "24 4.0 3.0 3.0 3 4 \n", - "25 53.0 59.0 51.0 61 64 \n", - "26 1.0 1.0 2.0 1 1 \n", - "27 1.0 1.0 1.0 1 1 \n", - "28 1.0 2.0 2.0 2 2 \n", - "29 0.0 0.0 0.0 0 0 \n", - "... ... ... ... ... ... \n", - "21447 0.0 0.0 0.0 0 0 \n", - "21448 0.0 0.0 0.0 0 0 \n", - "21449 0.0 1.0 0.0 0 0 \n", - "21450 0.0 0.0 0.0 0 0 \n", - "21451 0.0 0.0 0.0 0 0 \n", - "21452 62.0 55.0 55.0 55 55 \n", - "21453 1980.0 2011.0 2094.0 2071 2016 \n", - "21454 258.0 258.0 269.0 272 276 \n", - "21455 0.0 0.0 0.0 0 0 \n", - "21456 287.0 314.0 336.0 396 416 \n", - "21457 78.0 68.0 56.0 52 55 \n", - "21458 3.0 4.0 2.0 4 3 \n", - "21459 19.0 24.0 17.0 27 30 \n", - "21460 44.0 41.0 40.0 38 38 \n", - "21461 135.0 137.0 147.0 159 160 \n", - "21462 179.0 215.0 217.0 227 227 \n", - "21463 184.0 211.0 230.0 246 217 \n", - "21464 11.0 23.0 11.0 10 10 \n", - "21465 16.0 14.0 11.0 12 12 \n", - "21466 437.0 448.0 476.0 525 516 \n", - "21467 265.0 262.0 277.0 280 258 \n", - "21468 21.0 21.0 21.0 22 22 \n", - "21469 31.0 30.0 25.0 26 20 \n", - "21470 27.0 27.0 24.0 24 25 \n", - "21471 23.0 25.0 25.0 30 31 \n", - "21472 385.0 418.0 457.0 426 451 \n", - "21473 5.0 15.0 15.0 15 15 \n", - "21474 18.0 29.0 40.0 40 40 \n", - "21475 0.0 0.0 0.0 0 0 \n", - "21476 0.0 0.0 0.0 0 0 \n", - "\n", - "[21477 rows x 63 columns]" - ] - }, - "execution_count": 3, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "df" - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "metadata": { - "_cell_guid": "347e620f-b0e4-448e-81c7-e164f560c5a3", - "_uuid": "0acdd759950f5df3298224b0804562973663a11d", - "scrolled": false - }, - "outputs": [ - { - "data": { - "image/png": "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D37wg6vmzZtXfKZthwwZUrVgwYKYH/7wh6Uvv/zy125w9PDDD5eMHDmyX3x8fGNGRkadJP3617/uVFVV5T71pkG5ubnBaWlpDfPmzSuprq52bdq0KVzSMY/HY+vr601ISIg9088LgaglM3Uvp4SEBO/UqVNL33zzzfi77777mCRVVla6U1JSGiWp+dIeF8rn8+mVV16JmT17dumCBQviRowY8bWbcdXU1LgkKTExsam8vNy1ZMmS0842RCvkwO8dWVlZIS6XS80zaDdv3hyW1cPc5AAAIABJREFUlJTUcLHnUVRUlLeiouKsBeb48eOrn3322f3Tpk3rlZmZuevUG2hGR0d7T/0jSnR0tLeqqurk/rKzs4P79etXn56efmTv3r0hW7ZsCbv11ltPPhfO9rPImYwaNary5Zdfjvuv//qv4nfeeafDufKHhITYOXPmHP7d736XeO2111ZK0o033ljx29/+NmHBggUHXC6XPv/887DRo0fXStKdd955/D//8z8TKysr3SNGjPjWf2rs2LEjZOzYsdVjx46tXr58ece9e/cGJyYmnvY/OgAAuJzaZwHskOZrAEsn/pL8wgsv7PN4vv0pmD59evmLL77YqXfv3v179OhRN3jw4GpJ+uMf/xhXXl7uvu2223pKUkJCQsOaNWu4lhROy+VyafHixXseeeSR5GeffTYxJCTEJiUl1dfW1rqOHDkSNGTIkH6SNHHixLJnn3226F//9V8P3nTTTb19Pp+CgoLs/PnzD9x0003V8+fP33f77bf39Pl8iouLa1y7du3ulhz/wQcfPD5p0qSeAwYM6Jeenl6TlpZ21jud48pxuVxasmTJnh/96EfJ8+fPT4yNjW0KDw/3/vKXvywcP3581fPPP1/fp0+f9D59+tT279+/RpKuueaa2gEDBtT06tUrPSUlpf7UyzrMmjWrZNKkSb06d+7cuH79+tw//vGP+2bMmHFVQ0ODkaRf/OIXB1taALfkvOHcwpWSkJDgXbZsWe6YMWP6durUqem+++4re+CBB4499dRT3R544IFvzfRq9vvf/77g3nvvTZs/f37ihAkTyiIjI0/+4Sw5ObmpR48edVOnTi1rHnvuuecSg4KCbPPPCPfff//RsLAw3/z58xM9Ho8NDw/3vvHGG/mSdO+99x7t169f/wEDBtS8/fbb+0738wIuj3/5l385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\n", - "text/plain": [ - "
" - ] - }, - "metadata": { - "needs_background": "light" - }, - "output_type": "display_data" - } - ], - "source": [ - "area_list = list(df['Area'].unique())\n", - "year_list = list(df.iloc[:,10:].columns)\n", - "\n", - "plt.figure(figsize=(24,12))\n", - "for ar in area_list:\n", - " yearly_produce = []\n", - " for yr in year_list:\n", - " yearly_produce.append(df[yr][df['Area'] == ar].sum())\n", - " plt.plot(yearly_produce, label=ar)\n", - "plt.xticks(np.arange(53), tuple(year_list), rotation=60)\n", - "plt.legend(bbox_to_anchor=(0., 1.02, 1., .102), loc=3, ncol=8, mode=\"expand\", borderaxespad=0.)\n", - "plt.savefig('p.png')\n", - "plt.show()" - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "
" - ] - }, - "execution_count": 5, - "metadata": {}, - "output_type": "execute_result" - }, - { - "data": { - "text/plain": [ - "
" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "plt.figure(figsize=(24,12))" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "_cell_guid": "2ebe07e3-739b-4f39-8736-a512426c05bf", - "_uuid": "70900ec0ff5e248cd382ee53b5927cb671efa80e", - "collapsed": true - }, - "source": [ - "Clearly, China, India and US stand out here. So, these are the countries with most food and feed production.\n", - "\n", - "Now, let's have a close look at their food and feed data\n", - "\n", - "# Food and feed plot for the whole dataset" - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "metadata": { - "_cell_guid": "ec0c911d-e154-4f8a-a79f-ced4896d5115", - "_uuid": "683dc56125b3a4c66b1e140098ec91490cbbe96f", - "scrolled": true - }, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "/anaconda3/lib/python3.7/site-packages/seaborn/categorical.py:3666: UserWarning: The `factorplot` function has been renamed to `catplot`. The original name will be removed in a future release. Please update your code. Note that the default `kind` in `factorplot` (`'point'`) has changed `'strip'` in `catplot`.\n", - " warnings.warn(msg)\n" - ] - }, - { - "data": { - "image/png": "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\n", - "text/plain": [ - "
" - ] - }, - "metadata": { - "needs_background": "light" - }, - "output_type": "display_data" - } - ], - "source": [ - "sns.factorplot(\"Element\", data=df, kind=\"count\")\n", - "plt.show()" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "_cell_guid": "189c74af-e6e4-4ddd-a73c-3725f3aa8124", - "_uuid": "bfd404fb5dbb48c3e3bd1dcd45fb27a5fb475a00" - }, - "source": [ - "So, there is a huge difference in food and feed production. Now, we have obvious assumptions about the following plots after looking at this huge difference.\n", - "\n", - "# Food and feed plot for the largest producers(India, USA, China)" - ] - }, - { - "cell_type": "code", - "execution_count": 7, - "metadata": { - "_cell_guid": "0bf44e4e-d4c4-4f74-ae9f-82f52139d182", - "_uuid": "be1bc3d49c8cee62f48a09ada0db3170adcedc17" - }, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "/anaconda3/lib/python3.7/site-packages/seaborn/categorical.py:3666: UserWarning: The `factorplot` function has been renamed to `catplot`. The original name will be removed in a future release. Please update your code. Note that the default `kind` in `factorplot` (`'point'`) has changed `'strip'` in `catplot`.\n", - " warnings.warn(msg)\n", - "/anaconda3/lib/python3.7/site-packages/seaborn/categorical.py:3672: UserWarning: The `size` paramter has been renamed to `height`; please update your code.\n", - " warnings.warn(msg, UserWarning)\n" - ] - }, - { - "data": { - "text/plain": [ - "" - ] - }, - "execution_count": 7, - "metadata": {}, - "output_type": "execute_result" - }, - { - "data": { - "image/png": "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\n", - "text/plain": [ - "
" - ] - }, - "metadata": { - "needs_background": "light" - }, - "output_type": "display_data" - } - ], - "source": [ - "sns.factorplot(\"Area\", data=df[(df['Area'] == \"India\") | (df['Area'] == \"China, mainland\") | (df['Area'] == \"United States of America\")], kind=\"count\", hue=\"Element\", size=8, aspect=.8)" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "_cell_guid": "94c19dc8-b1e7-4b61-b81f-422c27184c4e", - "_uuid": "0d1cfc7acc74847dbc5813b9b3bd0eb9db450985" - }, - "source": [ - "Though, there is a huge difference between feed and food production, these countries' total production and their ranks depend on feed production." - ] - }, - { - "cell_type": "markdown", - "metadata": { - "_cell_guid": "9dba87b4-fa51-43ef-95ae-f31396c20146", - "_uuid": "43e0f00abf706ab1782ebb78cefc38aca17316e6" - }, - "source": [ - "Now, we create a dataframe with countries as index and their annual produce as columns from 1961 to 2013." - ] - }, - { - "cell_type": "code", - "execution_count": 8, - "metadata": { - "_cell_guid": "c4a5f859-0384-4c8e-b894-3f747aec8cf9", - "_uuid": "84dd7a2b601479728dd172d3100951553c2daff5", - "scrolled": true - }, - "outputs": [ - { - "data": { - "text/html": [ - "
\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
AfghanistanAlbaniaAlgeriaAngolaAntigua and BarbudaArgentinaArmeniaAustraliaAustriaAzerbaijan...United Republic of TanzaniaUnited States of AmericaUruguayUzbekistanVanuatuVenezuela (Bolivarian Republic of)Viet NamYemenZambiaZimbabwe
09481.01706.07488.04834.092.043402.00.025795.022542.00.0...12367.0559347.04631.00.097.09523.023856.02982.02976.03260.0
19414.01749.07235.04775.094.040784.00.027618.022627.00.0...12810.0556319.04448.00.0101.09369.025220.03038.03057.03503.0
29194.01767.06861.05240.0105.040219.00.028902.023637.00.0...13109.0552630.04682.00.0103.09788.026053.03147.03069.03479.0
310170.01889.07255.05286.095.041638.00.029107.024099.00.0...12965.0555677.04723.00.0102.010539.026377.03224.03121.03738.0
410473.01884.07509.05527.084.044936.00.028961.022664.00.0...13742.0589288.04581.00.0107.010641.026961.03328.03236.03940.0
\n", - "

5 rows × 174 columns

\n", - "
" - ], - "text/plain": [ - " Afghanistan Albania Algeria Angola Antigua and Barbuda Argentina \\\n", - "0 9481.0 1706.0 7488.0 4834.0 92.0 43402.0 \n", - "1 9414.0 1749.0 7235.0 4775.0 94.0 40784.0 \n", - "2 9194.0 1767.0 6861.0 5240.0 105.0 40219.0 \n", - "3 10170.0 1889.0 7255.0 5286.0 95.0 41638.0 \n", - "4 10473.0 1884.0 7509.0 5527.0 84.0 44936.0 \n", - "\n", - " Armenia Australia Austria Azerbaijan ... \\\n", - "0 0.0 25795.0 22542.0 0.0 ... \n", - "1 0.0 27618.0 22627.0 0.0 ... \n", - "2 0.0 28902.0 23637.0 0.0 ... \n", - "3 0.0 29107.0 24099.0 0.0 ... \n", - "4 0.0 28961.0 22664.0 0.0 ... \n", - "\n", - " United Republic of Tanzania United States of America Uruguay Uzbekistan \\\n", - "0 12367.0 559347.0 4631.0 0.0 \n", - "1 12810.0 556319.0 4448.0 0.0 \n", - "2 13109.0 552630.0 4682.0 0.0 \n", - "3 12965.0 555677.0 4723.0 0.0 \n", - "4 13742.0 589288.0 4581.0 0.0 \n", - "\n", - " Vanuatu Venezuela (Bolivarian Republic of) Viet Nam Yemen Zambia \\\n", - "0 97.0 9523.0 23856.0 2982.0 2976.0 \n", - "1 101.0 9369.0 25220.0 3038.0 3057.0 \n", - "2 103.0 9788.0 26053.0 3147.0 3069.0 \n", - "3 102.0 10539.0 26377.0 3224.0 3121.0 \n", - "4 107.0 10641.0 26961.0 3328.0 3236.0 \n", - "\n", - " Zimbabwe \n", - "0 3260.0 \n", - "1 3503.0 \n", - "2 3479.0 \n", - "3 3738.0 \n", - "4 3940.0 \n", - "\n", - "[5 rows x 174 columns]" - ] - }, - "execution_count": 8, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "new_df_dict = {}\n", - "for ar in area_list:\n", - " yearly_produce = []\n", - " for yr in year_list:\n", - " yearly_produce.append(df[yr][df['Area']==ar].sum())\n", - " new_df_dict[ar] = yearly_produce\n", - "new_df = pd.DataFrame(new_df_dict)\n", - "\n", - "new_df.head()" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "_cell_guid": "15fbe29c-5cea-4ac3-9b95-f92acd89b336", - "_uuid": "ea48f75e9824a0c4c1a5f19cbd63e59a6cb44fe1" - }, - "source": [ - "Now, this is not perfect so we transpose this dataframe and add column names." - ] - }, - { - "cell_type": "code", - "execution_count": 9, - "metadata": { - "_cell_guid": "145f751e-4f5b-4811-a68c-9d20b3c36e10", - "_uuid": "28e765d82bb4ebec3be49200a30fc4e0eabb24d7" - }, - "outputs": [ - { - "data": { - "text/html": [ - "
\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
Y1961Y1962Y1963Y1964Y1965Y1966Y1967Y1968Y1969Y1970...Y2004Y2005Y2006Y2007Y2008Y2009Y2010Y2011Y2012Y2013
Afghanistan9481.09414.09194.010170.010473.010169.011289.011508.011815.010454.0...16542.017658.018317.019248.019381.020661.021030.021100.022706.023007.0
Albania1706.01749.01767.01889.01884.01995.02046.02169.02230.02395.0...6637.06719.06911.06744.07168.07316.07907.08114.08221.08271.0
Algeria7488.07235.06861.07255.07509.07536.07986.08839.09003.09355.0...48619.049562.051067.049933.050916.057505.060071.065852.069365.072161.0
Angola4834.04775.05240.05286.05527.05677.05833.05685.06219.06460.0...25541.026696.028247.029877.032053.036985.038400.040573.038064.048639.0
Antigua and Barbuda92.094.0105.095.084.073.064.059.068.077.0...92.0115.0110.0122.0115.0114.0115.0118.0113.0119.0
\n", - "

5 rows × 53 columns

\n", - "
" - ], - "text/plain": [ - " Y1961 Y1962 Y1963 Y1964 Y1965 Y1966 \\\n", - "Afghanistan 9481.0 9414.0 9194.0 10170.0 10473.0 10169.0 \n", - "Albania 1706.0 1749.0 1767.0 1889.0 1884.0 1995.0 \n", - "Algeria 7488.0 7235.0 6861.0 7255.0 7509.0 7536.0 \n", - "Angola 4834.0 4775.0 5240.0 5286.0 5527.0 5677.0 \n", - "Antigua and Barbuda 92.0 94.0 105.0 95.0 84.0 73.0 \n", - "\n", - " Y1967 Y1968 Y1969 Y1970 ... Y2004 \\\n", - "Afghanistan 11289.0 11508.0 11815.0 10454.0 ... 16542.0 \n", - "Albania 2046.0 2169.0 2230.0 2395.0 ... 6637.0 \n", - "Algeria 7986.0 8839.0 9003.0 9355.0 ... 48619.0 \n", - "Angola 5833.0 5685.0 6219.0 6460.0 ... 25541.0 \n", - "Antigua and Barbuda 64.0 59.0 68.0 77.0 ... 92.0 \n", - "\n", - " Y2005 Y2006 Y2007 Y2008 Y2009 Y2010 \\\n", - "Afghanistan 17658.0 18317.0 19248.0 19381.0 20661.0 21030.0 \n", - "Albania 6719.0 6911.0 6744.0 7168.0 7316.0 7907.0 \n", - "Algeria 49562.0 51067.0 49933.0 50916.0 57505.0 60071.0 \n", - "Angola 26696.0 28247.0 29877.0 32053.0 36985.0 38400.0 \n", - "Antigua and Barbuda 115.0 110.0 122.0 115.0 114.0 115.0 \n", - "\n", - " Y2011 Y2012 Y2013 \n", - "Afghanistan 21100.0 22706.0 23007.0 \n", - "Albania 8114.0 8221.0 8271.0 \n", - "Algeria 65852.0 69365.0 72161.0 \n", - "Angola 40573.0 38064.0 48639.0 \n", - "Antigua and Barbuda 118.0 113.0 119.0 \n", - "\n", - "[5 rows x 53 columns]" - ] - }, - "execution_count": 9, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "new_df = pd.DataFrame.transpose(new_df)\n", - "new_df.columns = year_list\n", - "\n", - "new_df.head()" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "_cell_guid": "57929d23-e3d7-4955-92d1-6fa388eb774d", - "_uuid": "605f908af9ff88120fce2a2b59160816fcdcfa67" - }, - "source": [ - "Perfect! Now, we will do some feature engineering.\n", - "\n", - "# First, a new column which indicates mean produce of each state over the given years. Second, a ranking column which ranks countries on the basis of mean produce." - ] - }, - { - "cell_type": "code", - "execution_count": 10, - "metadata": { - "_cell_guid": "ab91a322-0cb9-4edf-b5a2-cde82a237824", - "_uuid": "979f875019abef3ed85af75e000fe59d1de5a381" - }, - "outputs": [ - { - "data": { - "text/html": [ - "
\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
Y1961Y1962Y1963Y1964Y1965Y1966Y1967Y1968Y1969Y1970...Y2006Y2007Y2008Y2009Y2010Y2011Y2012Y2013Mean_ProduceRank
Afghanistan9481.09414.09194.010170.010473.010169.011289.011508.011815.010454.0...18317.019248.019381.020661.021030.021100.022706.023007.013003.05660469.0
Albania1706.01749.01767.01889.01884.01995.02046.02169.02230.02395.0...6911.06744.07168.07316.07907.08114.08221.08271.04475.509434104.0
Algeria7488.07235.06861.07255.07509.07536.07986.08839.09003.09355.0...51067.049933.050916.057505.060071.065852.069365.072161.028879.49056638.0
Angola4834.04775.05240.05286.05527.05677.05833.05685.06219.06460.0...28247.029877.032053.036985.038400.040573.038064.048639.013321.05660468.0
Antigua and Barbuda92.094.0105.095.084.073.064.059.068.077.0...110.0122.0115.0114.0115.0118.0113.0119.083.886792172.0
\n", - "

5 rows × 55 columns

\n", - "
" - ], - "text/plain": [ - " Y1961 Y1962 Y1963 Y1964 Y1965 Y1966 \\\n", - "Afghanistan 9481.0 9414.0 9194.0 10170.0 10473.0 10169.0 \n", - "Albania 1706.0 1749.0 1767.0 1889.0 1884.0 1995.0 \n", - "Algeria 7488.0 7235.0 6861.0 7255.0 7509.0 7536.0 \n", - "Angola 4834.0 4775.0 5240.0 5286.0 5527.0 5677.0 \n", - "Antigua and Barbuda 92.0 94.0 105.0 95.0 84.0 73.0 \n", - "\n", - " Y1967 Y1968 Y1969 Y1970 ... Y2006 \\\n", - "Afghanistan 11289.0 11508.0 11815.0 10454.0 ... 18317.0 \n", - "Albania 2046.0 2169.0 2230.0 2395.0 ... 6911.0 \n", - "Algeria 7986.0 8839.0 9003.0 9355.0 ... 51067.0 \n", - "Angola 5833.0 5685.0 6219.0 6460.0 ... 28247.0 \n", - "Antigua and Barbuda 64.0 59.0 68.0 77.0 ... 110.0 \n", - "\n", - " Y2007 Y2008 Y2009 Y2010 Y2011 Y2012 \\\n", - "Afghanistan 19248.0 19381.0 20661.0 21030.0 21100.0 22706.0 \n", - "Albania 6744.0 7168.0 7316.0 7907.0 8114.0 8221.0 \n", - "Algeria 49933.0 50916.0 57505.0 60071.0 65852.0 69365.0 \n", - "Angola 29877.0 32053.0 36985.0 38400.0 40573.0 38064.0 \n", - "Antigua and Barbuda 122.0 115.0 114.0 115.0 118.0 113.0 \n", - "\n", - " Y2013 Mean_Produce Rank \n", - "Afghanistan 23007.0 13003.056604 69.0 \n", - "Albania 8271.0 4475.509434 104.0 \n", - "Algeria 72161.0 28879.490566 38.0 \n", - "Angola 48639.0 13321.056604 68.0 \n", - "Antigua and Barbuda 119.0 83.886792 172.0 \n", - "\n", - "[5 rows x 55 columns]" - ] - }, - "execution_count": 10, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "mean_produce = []\n", - "for i in range(174):\n", - " mean_produce.append(new_df.iloc[i,:].values.mean())\n", - "new_df['Mean_Produce'] = mean_produce\n", - "\n", - "new_df['Rank'] = new_df['Mean_Produce'].rank(ascending=False)\n", - "\n", - "new_df.head()" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "_cell_guid": "6f7c4fb7-1475-439f-9929-4cf4b29d8de7", - "_uuid": "da6c9c98eaff45edba1179103ae539bbfbe9753b" - }, - "source": [ - "Now, we create another dataframe with items and their total production each year from 1961 to 2013" - ] - }, - { - "cell_type": "code", - "execution_count": 11, - "metadata": { - "_cell_guid": "bfd692bc-dce4-4870-9ab9-9775cf69a87f", - "_uuid": "9e11017d381f175eee714643bc5fa763600aaa0b" - }, - "outputs": [], - "source": [ - "item_list = list(df['Item'].unique())\n", - "\n", - "item_df = pd.DataFrame()\n", - "item_df['Item_Name'] = item_list\n", - "\n", - "for yr in year_list:\n", - " item_produce = []\n", - " for it in item_list:\n", - " item_produce.append(df[yr][df['Item']==it].sum())\n", - " item_df[yr] = item_produce\n" - ] - }, - { - "cell_type": "code", - "execution_count": 12, - "metadata": { - "_cell_guid": "3b7ed0c2-6140-4285-861c-d0cd2324a1f5", - "_uuid": "cb4641df5ce90f516f88c536e8a6c6870c5b4f65" - }, - "outputs": [ - { - "data": { - "text/html": [ - "
\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
Item_NameY1961Y1962Y1963Y1964Y1965Y1966Y1967Y1968Y1969...Y2004Y2005Y2006Y2007Y2008Y2009Y2010Y2011Y2012Y2013
0Wheat and products138829.0144643.0147325.0156273.0168822.0169832.0171469.0179530.0189658.0...527394.0532263.0537279.0529271.0562239.0557245.0549926.0578179.0576597587492
1Rice (Milled Equivalent)122700.0131842.0139507.0148304.0150056.0155583.0158587.0164614.0167922.0...361107.0366025.0372629.0378698.0389708.0394221.0398559.0404152.0406787410880
2Barley and products46180.048915.051642.054184.054945.055463.056424.060455.065501.0...102055.097185.0100981.093310.098209.099135.092563.092570.08876699452
3Maize and products168039.0168305.0172905.0175468.0190304.0200860.0213050.0215613.0221953.0...545024.0549036.0543280.0573892.0592231.0557940.0584337.0603297.0608730671300
4Millet and products19075.019019.019740.020353.018377.020860.022997.021785.023966.0...25789.025496.025997.026750.026373.024575.027039.025740.02610526346
\n", - "

5 rows × 54 columns

\n", - "
" - ], - "text/plain": [ - " Item_Name Y1961 Y1962 Y1963 Y1964 Y1965 \\\n", - "0 Wheat and products 138829.0 144643.0 147325.0 156273.0 168822.0 \n", - "1 Rice (Milled Equivalent) 122700.0 131842.0 139507.0 148304.0 150056.0 \n", - "2 Barley and products 46180.0 48915.0 51642.0 54184.0 54945.0 \n", - "3 Maize and products 168039.0 168305.0 172905.0 175468.0 190304.0 \n", - "4 Millet and products 19075.0 19019.0 19740.0 20353.0 18377.0 \n", - "\n", - " Y1966 Y1967 Y1968 Y1969 ... Y2004 Y2005 \\\n", - "0 169832.0 171469.0 179530.0 189658.0 ... 527394.0 532263.0 \n", - "1 155583.0 158587.0 164614.0 167922.0 ... 361107.0 366025.0 \n", - "2 55463.0 56424.0 60455.0 65501.0 ... 102055.0 97185.0 \n", - "3 200860.0 213050.0 215613.0 221953.0 ... 545024.0 549036.0 \n", - "4 20860.0 22997.0 21785.0 23966.0 ... 25789.0 25496.0 \n", - "\n", - " Y2006 Y2007 Y2008 Y2009 Y2010 Y2011 Y2012 Y2013 \n", - "0 537279.0 529271.0 562239.0 557245.0 549926.0 578179.0 576597 587492 \n", - "1 372629.0 378698.0 389708.0 394221.0 398559.0 404152.0 406787 410880 \n", - "2 100981.0 93310.0 98209.0 99135.0 92563.0 92570.0 88766 99452 \n", - "3 543280.0 573892.0 592231.0 557940.0 584337.0 603297.0 608730 671300 \n", - "4 25997.0 26750.0 26373.0 24575.0 27039.0 25740.0 26105 26346 \n", - "\n", - "[5 rows x 54 columns]" - ] - }, - "execution_count": 12, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "item_df.head()" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "_cell_guid": "3fa01e1f-bedd-431b-90c3-8d7d70545f34", - "_uuid": "56a647293f1c1aba7c184f249021e008a4d5a8f2" - }, - "source": [ - "# Some more feature engineering\n", - "\n", - "This time, we will use the new features to get some good conclusions.\n", - "\n", - "# 1. Total amount of item produced from 1961 to 2013\n", - "# 2. Providing a rank to the items to know the most produced item" - ] - }, - { - "cell_type": "code", - "execution_count": 13, - "metadata": { - "_cell_guid": "3a6bb102-6749-4818-860d-59aaad6de07f", - "_uuid": "9e816786e7a161227ae72d164b25c0029e01e5b4", - "scrolled": true - }, - "outputs": [ - { - "data": { - "text/html": [ - "
\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
Item_NameY1961Y1962Y1963Y1964Y1965Y1966Y1967Y1968Y1969...Y2006Y2007Y2008Y2009Y2010Y2011Y2012Y2013SumProduction_Rank
0Wheat and products138829.0144643.0147325.0156273.0168822.0169832.0171469.0179530.0189658.0...537279.0529271.0562239.0557245.0549926.0578179.057659758749219194671.06.0
1Rice (Milled Equivalent)122700.0131842.0139507.0148304.0150056.0155583.0158587.0164614.0167922.0...372629.0378698.0389708.0394221.0398559.0404152.040678741088014475448.08.0
2Barley and products46180.048915.051642.054184.054945.055463.056424.060455.065501.0...100981.093310.098209.099135.092563.092570.088766994524442742.020.0
3Maize and products168039.0168305.0172905.0175468.0190304.0200860.0213050.0215613.0221953.0...543280.0573892.0592231.0557940.0584337.0603297.060873067130019960640.05.0
4Millet and products19075.019019.019740.020353.018377.020860.022997.021785.023966.0...25997.026750.026373.024575.027039.025740.026105263461225400.038.0
\n", - "

5 rows × 56 columns

\n", - "
" - ], - "text/plain": [ - " Item_Name Y1961 Y1962 Y1963 Y1964 Y1965 \\\n", - "0 Wheat and products 138829.0 144643.0 147325.0 156273.0 168822.0 \n", - "1 Rice (Milled Equivalent) 122700.0 131842.0 139507.0 148304.0 150056.0 \n", - "2 Barley and products 46180.0 48915.0 51642.0 54184.0 54945.0 \n", - "3 Maize and products 168039.0 168305.0 172905.0 175468.0 190304.0 \n", - "4 Millet and products 19075.0 19019.0 19740.0 20353.0 18377.0 \n", - "\n", - " Y1966 Y1967 Y1968 Y1969 ... Y2006 \\\n", - "0 169832.0 171469.0 179530.0 189658.0 ... 537279.0 \n", - "1 155583.0 158587.0 164614.0 167922.0 ... 372629.0 \n", - "2 55463.0 56424.0 60455.0 65501.0 ... 100981.0 \n", - "3 200860.0 213050.0 215613.0 221953.0 ... 543280.0 \n", - "4 20860.0 22997.0 21785.0 23966.0 ... 25997.0 \n", - "\n", - " Y2007 Y2008 Y2009 Y2010 Y2011 Y2012 Y2013 \\\n", - "0 529271.0 562239.0 557245.0 549926.0 578179.0 576597 587492 \n", - "1 378698.0 389708.0 394221.0 398559.0 404152.0 406787 410880 \n", - "2 93310.0 98209.0 99135.0 92563.0 92570.0 88766 99452 \n", - "3 573892.0 592231.0 557940.0 584337.0 603297.0 608730 671300 \n", - "4 26750.0 26373.0 24575.0 27039.0 25740.0 26105 26346 \n", - "\n", - " Sum Production_Rank \n", - "0 19194671.0 6.0 \n", - "1 14475448.0 8.0 \n", - "2 4442742.0 20.0 \n", - "3 19960640.0 5.0 \n", - "4 1225400.0 38.0 \n", - "\n", - "[5 rows x 56 columns]" - ] - }, - "execution_count": 13, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "sum_col = []\n", - "for i in range(115):\n", - " sum_col.append(item_df.iloc[i,1:].values.sum())\n", - "item_df['Sum'] = sum_col\n", - "item_df['Production_Rank'] = item_df['Sum'].rank(ascending=False)\n", - "\n", - "item_df.head()" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "_cell_guid": "7e20740c-565b-4969-a52e-d986e462b750", - "_uuid": "f483c9add5f6af9af9162b5425f6d65eb1c5f4aa" - }, - "source": [ - "# Now, we find the most produced food items in the last half-century" - ] - }, - { - "cell_type": "code", - "execution_count": 14, - "metadata": { - "_cell_guid": "3130fe83-404c-4b3c-addc-560b2e2f32bf", - "_uuid": "0403e9ab2e13587588e3a30d64b8b6638571d3d5" - }, - "outputs": [ - { - "data": { - "text/plain": [ - "56 Cereals - Excluding Beer\n", - "65 Fruits - Excluding Wine\n", - "3 Maize and products\n", - "53 Milk - Excluding Butter\n", - "6 Potatoes and products\n", - "1 Rice (Milled Equivalent)\n", - "57 Starchy Roots\n", - "64 Vegetables\n", - "27 Vegetables, Other\n", - "0 Wheat and products\n", - "Name: Item_Name, dtype: object" - ] - }, - "execution_count": 14, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "item_df['Item_Name'][item_df['Production_Rank'] < 11.0].sort_values()" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "_cell_guid": "b6212fed-588b-426e-9271-6d857cd6aacb", - "_uuid": "e2c83f4c851b755ea6cf19f1bca168e705bd4edd" - }, - "source": [ - "So, cereals, fruits and maize are the most produced items in the last 50 years\n", - "\n", - "# Food and feed plot for most produced items " - ] - }, - { - "cell_type": "code", - "execution_count": 15, - "metadata": { - "_cell_guid": "493f9940-1762-4718-acb4-fba5c4c73f4b", - "_uuid": "f8454c5200bdeb3995b9a0ada3deb5ca1c31f181" - }, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "/anaconda3/lib/python3.7/site-packages/seaborn/categorical.py:3666: UserWarning: The `factorplot` function has been renamed to `catplot`. The original name will be removed in a future release. Please update your code. Note that the default `kind` in `factorplot` (`'point'`) has changed `'strip'` in `catplot`.\n", - " warnings.warn(msg)\n", - "/anaconda3/lib/python3.7/site-packages/seaborn/categorical.py:3672: UserWarning: The `size` paramter has been renamed to `height`; please update your code.\n", - " warnings.warn(msg, UserWarning)\n" - ] - }, - { - "data": { - "image/png": "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AAAAtCWOAQAAANCWOAYAAABAW+IYAAAAAG2JYwAAAAC0JY4BAAAA0JY4BgAAAEBb4hgAAAAAbYljAAAAALQljgEAAADQljgGAAAAQFviGAAAAABtiWMAAAAAtCWOAQAAANCWOAYAAABAW+IYAAAAAG2JYwAAAAC0JY4BAAAA0JY4BgAAAEBb4hgAAAAAbYljAAAAALQljgEAAADQljgGAAAAQFviGAAAAABtiWMAAAAAtCWOAQAAANCWOAYAAABAW+IYAAAAAG2JYwAAAAC0JY4BAAAA0JY4BgAAAEBb4hgAAAAAbYljAAAAALQljgEAAADQljgGAAAAQFviGAAAAABtiWMAAAAAtCWOAQAAANCWOAYAAABAW+IYAAAAAG2JYwAAAAC0JY4BAAAA0JY4BgAAAEBb4hgAAAAAbYljAAAAALQljgEAAADQljgGAAAAQFviGAAAAABtiWMAAAAAtCWOAQAAANCWOAYAAABAW+IYAAAAAG2JYwAAAAC0JY4BAAAA0JY4BgAAAEBb4hgAAAAAbYljAAAAALQljgEAAADQljgGAAAAQFviGAAAAABtiWMAAAAAtCWOAQAAANCWOAYAAABAW+IYAAAAAG2JYwAAAAC0JY4BAAAA0JY4BgAAAEBb4hgAAAAAbYljAAAAALQljgEAAADQljgGAAAAQFviGAAAAABtiWMAAAAAtCWOAQAAANCWOAYAAABAW+IYAAAAAG2JYwAAAAC0JY4BAAAA0JY4BgAAAEBb4hgAAAAAbYljAAAAALQljgEAAADQljgGAAAAQFviGAAAAABtiWMAAAAAtCWOAQAAANCWOAYAAABAW+IYAAAAAG2JYwAAAAC0JY4BAAAA0JY4BgAAAEBb4hgAAAAAbYljAAAAALQljgEAAADQljgGAAAAQFviGAAAAABtiWMAAAAAtCWOAQAAANCWOAYAAABAW+IYAAAAAG2JYwAAAAC0JY4BAAAA0JY4BgAAAEBb4hgAAAAAbYljAAAAALQljgEAAADQljgGAAAAQFviGAAAAABtiWMAAAAAtCWOAQAAANCWOAYAAABAW+IYAAAAAG2JYwAAAAC0JY4BAAAA0JY4BgAAAEBb4hgAAAAAbYljAAAAALQljgEAAADQljgGAAAAQFviGAAAAABtiWMAAAAAtCWOAQAAANCWOAYAAABAW+IYAAAAAG2JYwAAAAC0JY4BAAAA0JY4BgAAAEBb4hgAAAAAbYljAAAAALQljgEAAADQljgGAAAAQFviGAAAAABtiWMAAAAAtHXIxbGqOq2qPlFVt1TVixc9HgAAAAAOX4dUHKuqI5L81yQ/keRRSZ5ZVY9a7KgAAAAAOFwdUnEsySlJbhljfGqM8U9J3pLkqQseEwAAAACHqRpjLHoMu1XV05OcNsZ47nT+Pyb5oTHGC+bmOTvJ2dPZRyb5xKYPdHM8MMkXFz0I1s1225pst63LttuabLetyXbbmmy3rcl225pst63pcN5uXxxjnLboQWwF2xY9gGVqhWl71bsxxoVJLtyc4SxOVV0zxtix6HGwPrbb1mS7bV223dZku21NttvWZLttTbbb1mS7bU22G8mh97HK25M8bO78Q5PcsaCxAAAAAHCYO9Ti2NVJTqqqh1fVtyY5I8llCx4TAAAAAIepQ+pjlWOMb1bVC5K8J8kRSS4aY3xswcNalMP+o6OHKdtta7Ldti7bbmuy3bYm221rst22Jttta7LdtibbjUPrD/IDAAAAwGY61D5WCQAAAACbRhwDAAAAoK3DLo5V1e9V1Yvmzr+nqt44d/5VVfWrVXVqVb3zIK3z9Kp61MFY1grLPreqfm0jlr1sPRdX1dP387K/sYZ57qqqG6rq5qr6y6q6/zT9wVX19v1Z7wrreFFVPXs6fXFVfa2qjp77+auralTVA6fzV03fT6iqm6fT675fVNUVVXW3f/07Tf/EdL1vOJDrWVVv3N/7WFXdunSd9+Oye923q+qVVfVjq8w7qupP5s5vq6o77+n2rKodVfWa/RnfwVRVX9mEdey+r+3HZU+tqsfu52XnH3//varus495719V/2kNy1zTfIt0IPu1da5nxX3AGi53wLfhtO4nLZv2oqp63YEsd4X1rOl5brXb/GA+584t81ur6ver6v9W1Ser6h1V9dDpZ3vdthux/lXGtM/9YFX9VFW9eDq9+/l9f+9Dc+tZeowvfb14P5ZxVlW9dp2X2b29D+R5aoXlLl2fG6vqurXs+6b7/X3mzq/62qSqvquq3jLddz5eVe+qqkccjLHvY50HZX80LefTc9v6qv1czrqe8+YfQ/P34wO17Pr8bVW99GAs9wDH9JKq+lhV3TSN64em6Xvdxw5wHQe0T5p7jXljVV1dVdsPYFn3+Dp+K9uM58lD8TnyYFvheeaEdV5+93PEwbzP1ez3nF1z41r37xT7c/svew4/r6qesN71rrDMX66q3587/4aqev/c+RcuXb/93fezb4ddHEtyVZLHJklV3SvJA5M8eu7nj03y4YO8ztOTbEgcOxBVtVn/cGEtO7ivjzG2jzG+N8mXkjw/ScYYd4wxDsaLxW1Jfi7Jn81NviXJU6ef3yvJ45L83dIPxxj7FRrW6Wem6739QK7nGOO5Y4yPH8yBrdHy+/YfJFntBfFXk3xvVR05nf/xzN3eqxljXDPG+KUDGuWCVdURm7CaUzPt2/bD/OPvn5L8wj7mvX+StQSbtc63JW3S/vNg3IaXZPafneedMU0/mA7F57nfTnJ0kkeMMU5K8hdJ/ryqKgf5/rmO+8M+94NjjMvGGK84WOOa8/W555rtG7SOfTrIz1NL1+f7k/x6kpev4TIvSjIfLlZ8bTLdPy5NcsUY48QxxqOmeY9dy768Zhb9+vmcuW29Ga9l9rIB9+Nzxhjbk2xPcmZVPfxAF7i/+/Cq+uEkT0nyA2OM70vyhCSfnX68/D62luVt5OuDn5keI69LcsEBLOewjmPZnOfJQ/E58mBb/jxz6/wP7+kxt+w54mDf5x43N65N/51ijLFzjPH+e57zHu3uGJPtSe43tx/Z3TEWse/vYNFP7hvhw9lzp3p0kpuT/GNVPaCq7p3ke5JcP/38qKp6+/RO1Z9OL5hSVY+pqr+uqmtrduTZcdP0n5/enbmxqv5HVd1nejfzp5JcMNXqE+cHU1U/WVX/q6qur6r3V9Wx0/Rzq+qi6d2MT1XVL81d5iXTu0HvT/LIla7k9M7Df6uqD1bV/6mqp0zTz6rZkSF/meS904u4C2p2xMiuqvrpab6qqtfW7B3Tv0ryoLll7z7SqGZH9VwxnT6qqv54Ws5NVfXvq+oVSY6crvufVtV9q+qvptvo5qX1LfORJA+Zljl/1NYRNTsyaWn5L9zX9ljmx5JcN8b45ty0S5Isrf/UzO4bu39e9/Cu6XRdLpq2+fVVtRTajqzZO843VdVbkxy5r+WssNyHV9VHpuX+5tI4atm7FtP2OWs6fcW0LX6xqn53bp6zquoPptN/Md1GH6uqs1dZ97Oq6qPT9nrD0s62qr5SVedP2+1vqurYle7bY4zPJPnOqvquVa7e/0zyb6fTz8zcC4+qOqWqrppuy6uq6pHLr3fN3r1feufny1V15nS/uGC6vW6qquetct1WvP4rXbeVtsMqyzyhZvuHN03rfntN7xpPj5OdVfWhJM+oqu3T8m+qqkur6gHTfI+Z1v2RTFF4btu9du78O6vq1On0aTU7WuLGqrq8Zu/O/UKSX5lumx+tqmdMj7Ebq+rKVbbHSj6Y5Lun9fzqtIyba88Rt69IcuK0ngtq9ri/fBrPrqXHwQrzVa2wr5nWc87c9nvZNO0e9xW1wj53mn5xVb1muh99qvYcuVK1yn5t2XKvqNkRR1dN6z5lmn5uVV1YVe9N8uaq+rbas8+7vqoeN8236j6g5vYrVfX0qrp4On3sdL+4cfp67Aq34XFVdWXtOcrvR9ewPd+e5Ck1e37LdF95cJIPrXbbT9P/83Tffl9VXVJ73v08sareXbPH0ger6l/UCvuC1bbN5Am17Llp2e2/2r710bVn/3RTVZ202jhpgCIAABc1SURBVJWe1vezSX5ljHFXkowx/jjJNzJ7Ptjrtp0utt7n/Cuq6rer6q+T/PIatsWSfe0H93rcr3C97lWz/c1vrWN9qy3rfjV7LbG0r72kqn5+Or3XPmaFy+51dEPteZ5a9TFWc0e/1er73ROn81fX7J32tRy99O1J/mG6/IrPkzV7DfXgJB+oqg/Ustcm07zPqqqPJvlkkhOS/OHcdft3SV6Z5IdXeszU7Lngf9fsSJPrkjysqp5Ys+eQ62r2uuuoad6d0+Vvrtn+pFa4fV8x3YY3VdUr13Ab3KOa7RN3TqefVLN9yb1q5X3P/OX29drjtOnx8qHpNlqaZ/f9uFbfH9+rql5Xs+fkd9bs+f2e3iT8tun7V6dlrPbYvNt+am4s/6WqPpDkd/bzpjwuyRfHGN9IkjHGF8cYdyy/j03re31VXTNdx/n96/LXB99ds98Blo6EXPpd4W77pKp6fFVdOresH6+qP7+HMe9+XT1d5pk1e966uap+Z1/Tlz9Wam2v47eaVZ8nV3q8T/Ns6efIzVJ3/71zLb/LbPh9rmZHbV9de15Xv7yqzp9O/+C0v7pxuj2PXnbZc2vuU1vTmE6YTq/4O3rtfRT1rVX1strzunlp/3TMdH+6rma/g32m7v7JnuuTPKJmrzPvl+RrSW5IcvL088dmFtDmn5dPnW7bNb++YR/GGIfdV5Jbkxyf5HmZ/UL5m0menORHklw5zXNqki8neWhmkfAjSf51km/J7E53zDTfTye5aDr9nXPr+K0kL5xOX5zk6auM5QHJ7v8K+twkr5pOnzut596ZHd3299O6H5NkV2bvTH17Zkc//doKy704ybunsZ+U5PbMXlScNZ3+/+2debhV1XXAf0sQQSaLklStUWNj1CgxqHEiAlVJ0sbEWZFUrVqrTZ2J0c8kJZoaGhpbhzjhgFMdiKiIUeRTEcV5gIezUVH8NBEVUeqMq3+sdd7d77xzzr0Xkcd7b/2+733v3H3P2WePa++99trrDvL79gJmAD2ALwOvYAP/nkn4OsA7WR68/Nby662x3VWwicb/pHnz/0uSsL2Aicnngek9/r7JwPf88wbAE359JHA90NM/D6qqj1x5/Cqrj7ROgAe8DiYCw3N5W1KQhhHANL8+HfixX68BPAf0BY5P2sQQTOG2dUGaZgLPYkJtDjDBw6cCB/r1T5J0tL7bP58DHJzEtTUwGPhTcs+twLCsvPx/H0wpvGZan5hi+GZgVQ8/N0mHArv59W+Bn5e1bS/LvQryu8TL4w9YW5yTK88BSd3uAlxflG8P2wpoAQYChyfpWQ14BNiw4P1l+S/LW2E95OLcwJ/f0T9fgvdHL9cTk3tbgOF+fSreV3LhE6i1tYOBc5Lnp3lZDMZ2qTfM5WsciSzA5MS6WfusIxOzNtYTuAnra5ms6Qv0A54EvkXSH5JnBvj1WphMkoL7ymTNKOznsQWTV9OAnSiRFbl0V8ncyR7fZnifoEKuFfTNiX69U1In44BHgT7++QTgUr/exPPUmwoZQFt5uDcwya+vBY5N5ODAgjI8ATgluad/Vb0mz90C/MivT6Ima8rKfmusf/bBLK+ep9au7wC+5tfbAncWyYI6dVM0No2gvmw9G7OEAOiV1UNJnocAjxeE/zdwdEHZjqD5MX8mcG4jddCEHDwY7/ckfdrftR2mSDulmXf680upjTVzgP08fFfP6/7AbR5WJmPStOXrO5MhVXOHmdT6QZncnQaM9usjKJC7ufw84/W2VVKPZePkfHx8L+iLreOft4+5tB3/9q3TZzYAPgO2S2ThLKCvf/4Z8Mu0PP36iqQcJmEyYRA2N8jmhpXyu6BsJgEvJXV9lYevjsnxkR7/RmWyJ1enhWWKtd8FWD8W4DqK2/EkiuXx3sAfPfyvMQVnkTxO87MEON3Dq/pmlZyaBvRotg8l6ennaXkOmycNT76bT9s2lvWdHlj7H5Lcl84PHgT28OveXlcjKJZJgrX7LN//m7WhXDpnUutvxybltg42Vg3Gxu87MaumwvCCvlJ3bO6MfxSMk3ThMfILKsN0nLnBww6m7bqzNR/+ud1aZnm3Oay/zUvSdpyHfwN4GhsHH/cy6wW8CGzj9wzw/pCW/zjazrefwMaA0jV6Wv+enqy+/xW4KCmLk/36e9jYs1ZBfmZi7fC72EbfoR7POsAryX2pDG9qfhN/5X8r6tjdiiazHtsBOAPbTdkBazjp+dyHVPVVABGZgzX8d4DNgRmudO0BvO73by62m7sGNnhObyAtfwNc65raXtgEIOMWtZ2pj0TkDWxB+R1M4Lzv6ZpaEfd1qvoZ8LyIvIgt3gBmqOrbfj0MuFptZ/0vYjvg22CdLgt/TUTubCAvu5CYJavqooJ75gH/5TtS01T1Hg/vk5Txo9jkuij+89Wtv1T1bRHZnPL6SFkbE4B5pniat8WUpc0wCvhhsnvQG1O67gSc5WlsEZGWijjGqOojubAdsYEAbOLc8O6mqi4U25ndDhusv07tmPDRIrKHX6+HDbpvJY/vjAn2h70s+wBv+HcfY5MCsPrZtSIZb2ACuih9Lb67MhqbFKcMBC7znS7FhHY7fBflCmyxslhERgFDpLbjPNDz9lLu0bL8l+Wt0XpYoKpZGV+JLayynf5rPc0DsQXO3R5+GTC5IPwK4Psl78nYDlPivwTWD0rumw1MEpHrsHZeRdb/wCzHLsYUZDeoarZDPwWTP3mZI8DpIrITtjhcF5NVeapkzSgSi12sbu6hWFakVMncG13+PSVulUJzcu1qAFWdJSIDxP0gAlNV9YMkT2f7fc+IyMvAxjQnAzL+DjjQn1kKLBa3Lkx4GLhERFb1/M2hMbIjIzf5/0M8fBTFZd8fuCnLp+/4Imb5sgPWdrO4Vyt5Z1XdlI1NGWWy9X7gFDG/YVNU9fmKPAsmRxoNh+bHfPA+3gx15GAVF2Bl9x/NvhM/7lKQlhkisg/we+CbHtyojCmi0T5WJne3xxbqYIv+Mqup1vyIHXO73OcDy0rr+IcptlYHvurfLcU25qC8z7wCvKyqD3j4dpgiaLa3m15Y+wUYKSIn+jsGYQqrm5O0vAt8CFwkZn23LH6GfqqqbfyYqur7YpaBs7DF4Qv+VTvZ0+A7NgFeyvqhiFyJbVYVUSSPhwGTPfzP4tZWVflxGXSHmCXOuxT0zQbk1GTP5zKhqktEZCtsPByJzeFPUtVJBbfvK2ap3hObh26GbYhBbX7QH9vIusHj/9DDoUAmqeq9Yn4Lfywil2J95sCS5F4lIn2xshnqYdtgm9oLPd6rsH6rJeE35uIsm8d3dorGyQPoumPkF0HhOEPbdeeysDza3EhVfTMNUNUnvS/dDGyvqh+LyBbA66r6sN/zLrT2x3o0s0bP5uWPUrO6HQbs4e+9TUSK1tFQ02P0wer8eewY6kLa6jFSlmV+ExTQVZVj2XndLTBt7wJsR/5dzPIj46PkeilWHgI8qarbF8Q7CdtlmStmIjqigbScDZyhqlPFTDvH1Xk/lE/s8+Tvyz7/XxJW1dvL3vMptSO3vZPwqkWHRaj6nE8q/h74jYjcrqqn4gLVlQXTMEudvMPEovir6iPlg1xaM67BjkBcpqqfNSj80nfvparPtgm0OBqtozKKnk/LHYrzAzbh2hfbWbxBVdXb1i6Y8H9f7Chs/nnByuHkgjg/UdUsTWlbLKI3Vt5lTMUWPCOANZPw04C7VHUPXzjOzD8odszzGuBUVc0c1wu2A1OqjK6T/6q8NVKPZf0M2va1wqRVvKOsvuv2MwBVPULMSfA/AHNEZEtVfavk9nYTGmm8M4zBdpq3UtVPRGQ+xW2zLD4BfqOqF7T7olhWpEyiXOam8jN9d0fLzzS8rA8XP2iKup2wOr1CRCao6uUNPHojcIaIDMV2kh/z8MKyF5HjSuJZBXinZPKbZxLldVPVZ7J0tZOtwNMi8iCW/+kicpiqlilf/gSsLyL9VfW9JHwobRURKc2O+VC/j5dRJgeruA9TrPwuW0BneF/P6vGXqlo1KU+fWwWzmvoAU9S8SmMyplU+uazolXzXSB9rZkypRFXv902TwTQ+TuZpHf9EZGfg31V1nH/3YaJMKeszG9BeNsxQ1dG5+3pj1kZbq+oCERmXT6Oqfip2lHtnbJH+b5gCK41nOrYJ8YiqHtZgHsHmvW9RsoFVQlWZNipPi+RxUxMuaFVMzcQWkbdS0DdFZADVcmpZ+2yajqXYHGWmiMwDDsJkXpqODYGxmAXKIrEj9GnZZemoKoeydcClmBz7EFP2fZp/0BmDWUGOxxTge1a8r6H6qJjHd3bajZMiMoauO0auSNI+17SMrtfmRGQ9auP6+ap6fhNp2wJTEmVK+6bGP+fzyMS0XzcqE+/DDDp6Y/16IaZ4X0i53/Rlmd8EBXRFn2NgDecHwNuqutS12Wtguy/3Vz5ppuiDfacSEVlVRDKH/v2xXatVsQEp4z3/roiB1JzxHtRA2mcBe4idNe4P7FZx7z5iPh02wnZA80I0i28/Mb9Ng7Fdooc8fH8PXxvbHcuYj+2wQs2yBuB2bBIHQGL18ImXCSKyDvC+ql6JLQyGJs+jqosxy5ux2TO5+I8Qd+goItnRg7L6SHka96OUe98rwCnYZLVZpgNHZUoEEfmWh8/C6993soc0Ge9sahZ4aTt6GdhMRFZzJeLOJc9PwXbeR1OzahgILHLF0CbYrnaeO4C9ReRLnvZBIrJ+nbQWte2NMaVzGZdgyq15ufC0Lxxc8ux4oEVVr0nCpgNHJm1sY98pzcddL/95yuohz1ey9oeV+b35G7xdL5Kaj6h/BO5W1XcwC6FhBe+ZD2zpfXg94Nsefj8w3CfeWT+AXF2I+YB7UFV/CbyJWcs1wyxgdzHfiX2x3ax78u/ByvYNV4yNBLI2k7+vTNZMBw6Rmj+edUXkS/VkhVMmc6vyVCbX8mT+F4cBi70Oi+LL+vrG2M7ts1TLgL+IyKaulNgjCb8Ds9bD0zeA9nW6PlbWEzHrvqIyaYeqLsEWcpfQ1sFwYdljbXg3MZ9q/XD/WL6D+pKYpVHmXyqzNsrXd1Xd1BubCmWriHwVeFFVz8KUS0M8/A4RWTeNQM3i8TJssZP5TjwQs9a5syC9ZTQ6xjRLmRys4mLM0myy5Bwbe1/PnA03pBhzjsPGx9HUrBLLZEzKfGrzgB9Rs/Rtpo8V8QC1eUXeQXYhLtN7YEqfqnEyX+etcxPajn93AquLyM+Sd2wjIsMp7zNF+dhRRDL/jau7jMgWUW96HEW/StcPOzb0R+w4XJHF33e9rhtWjLn8OAE7Hv998V9YpFj2pJSV6TPAhlLzjzWa5rgX2MtlwZdpYEPZ2/22wAuU9M06cupzIyJfl7a+nLbEygjatrEBmFJgseev0Crc0/uqiOzu8a8mdX7xUlVfA14Dfk5OKVdw7yd+33Yisil2hHO4iKzlsnE0cHdFODQxj++slIyTXWKMXMlodC3TzNpxQTL+NawYE5E9sc2pnYCzxE4IPAOsIyLb+D398+MtNv4N9e+HAtkPhDSzRi/iXsy4AbFTMfnTAxn3YeuYwar6hm80LcTG4mZ+ofKLmt90abqqcmweZjb/QC5scd7kMo+qfoxNZv5TROZiZ5cz56W/wAaXGVjnyrgG+KmY08Q2DvkxS7HJInIPtoCtxHf8r/X3Xo8tVst4FhvYbgWOyO80OzdgJt5zsQnhiar6Zw9/HiuX86gNkGD+u870NKem6b8G/krcCTi1SfGFQIuYifYWwENiJp2n+DP5PD7u6clPjC/Cji60ePwH1KmPlFsx4dcOVb1Aa8cLmuE0bEHQIvajAZnT9vMwJ6otwImYAqCMq6TmYD77FZNjgJ+IyMOY4iFL5wLMp0cLcBU1M+98fhYBTwHrq2r27tuAnp6m02jb9rPnnsImULf7fTOwYwBVtGnbPpD9Leb3qxBVfVVVzyz46rfYjtBsbKFTxFhgVFJmP8TaxVPAY14PF9DeCqFu/gsorIcCnsZ+PasFs7w4r+S+gzBnrC3YZDrb9fon4PdiDvlTi7vZ2NHQedhk4DGwo7PY0ZUp3uYzBejN2KA8R0wJN0HcsS42YM9tIM+tuKyZhLXfBzGfCI+79dls7+cTsLa4tYg8gk3wnvHn8/cVyhpVvR07PnW/2A78H7BJY11ZQbnMLaNKruVZJPYz2Odj/hyKOBfo4em+FvOb8RHVMuAkzDr2Ttqarx+DWQXNw8zsv1FQhiMwK8DHMQVCUT8q42rs2FyrYrms7NWOE0zF6moK1p8z5eAY4FBve0/iv/hL+3Guqm7qjU1lsnU/4AlvE5tgR+lWwWRO0ZGNkzHLiudE5HlgH8yvjxaUbSFNjDFNUSEH6z13BiYLrpDmfhExc26c/Y0XU9YcBpygdkxlFub7q0zGpEzEFtIPYcqKzDKgmT5WxLHA8R7v2pQf8WvNj6fvIN/srBonLwRuldrxvda5STr+Ye2+F7CriLyAHV0ZB7xWIa/a4GV4MHC1y4EHgE18Q2Sil8+N2DHOPP2Baf7c3ZgCs1km5Op7NUy5OtYVK4dixzZ7UyB7cnkpLFPvt4cDt4g5ln+Z5rges1TMxu0HKa/vCV7XLVjZTanTN8vk1PKgH+YC4imvo82onfpobWOqOhcrqycxhUuZRQfYhtnRHt99mA+2elyFuXWo+wuwasf/fofV/+uYbLwLa+uPqepNZeFJvhqex3di2oyTXWGM/Bxl8YXQ6FqG5d/m7krk4eVi1sbjgUNV9TnM39eZLlf2A872OpxBe+u264FBnp4jMZ9vza7Ri/gVtsZ5DFOmv44pVdvg67yFWPvKuB/7AZyG5/pf1Pymq5M5Aw06GWLm29M053OiOyP26z4n6oo/g/+5EJElqtqvo9PRCGI+vYaq6i86Oi0rArGjNNNU9fP4uglWMsSO7YzV9v4Auw0i0k/tCNPqmNLkcK0dx1xpELPMO0RVj+/otASfH29vH6iqisj+mHP+5anYCFYiEjmzJraJsKNv0AZ1EPs10MdV9eKOTkt3pLOMkUHnwTcxlqodrd8eOE8bO6IbrEC6qs+xoHtyErYT3amUY52MntjuZBAEnZsLRWQzbMf0spV10q/mezAUY12HrYBzREQwPzCH1Lk/6NxMEzvK1As4LRRjjSEij2LWmid0dFq6MZ1ijAw6FV8BrnOr8I+Bf+7g9AQFhOVYEARBEARBEARBEARB0G3pqj7HgiAIgiAIgiAIgiAIgqAuoRwLgiAIgiAIgiAIgiAIui2hHAuCIAiCIAiCIAiCIAi6LaEcC4IgCIIgqIOILPH/G4jIAR2dniAIgiAIgmD5EcqxIAiCIAiCxtkACOVYEARBEARBFyKUY0EQBEEQBI0zHviOiMwRkeNEpIeITBCRh0WkRUT+BUBERojI3SJynYg8JyLjRWSMiDwkIvNEZKMOzkcQBEEQBEHg9OzoBARBEARBEHQiTgLGquoPAETkcGCxqm4jIqsBs0Xkdr/3m8CmwNvAi8BFqvptETkGOAo4dsUnPwiCIAiCIMgTyrEgCIIgCIJlZxQwRET29s8Dga8BHwMPq+rrACLyApApzeYBI1d0QoMgCIIgCIJiQjkWBEEQBEGw7AhwlKpObxMoMgL4KAn6LPn8GTEHC4IgCIIgWGkIn2NBEARBEASN8x7QP/k8HThSRFYFEJGNRaRvh6QsCIIgCIIgWCZi1zIIgiAIgqBxWoBPRWQuMAk4E/sFy8dERICFwO4dlrogCIIgCIKgaURVOzoNQRAEQRAEQRAEQRAEQdAhxLHKIAiCIAiCIAiCIAiCoNsSyrEgCIIgCIIgCIIgCIKg2xLKsSAIgiAIgiAIgiAIgqDbEsqxIAiCIAiCIAiCIAiCoNsSyrEgCIIgCIIgCIIgCIKg2xLKsSAIgiAIgiAIgiAIgqDbEsqxIAiCIAiCIAiCIAiCoNvy/yCow6RV+SGaAAAAAElFTkSuQmCC\n", - "text/plain": [ - "
" - ] - }, - "metadata": { - "needs_background": "light" - }, - "output_type": "display_data" - } - ], - "source": [ - "sns.factorplot(\"Item\", data=df[(df['Item']=='Wheat and products') | (df['Item']=='Rice (Milled Equivalent)') | (df['Item']=='Maize and products') | (df['Item']=='Potatoes and products') | (df['Item']=='Vegetables, Other') | (df['Item']=='Milk - Excluding Butter') | (df['Item']=='Cereals - Excluding Beer') | (df['Item']=='Starchy Roots') | (df['Item']=='Vegetables') | (df['Item']=='Fruits - Excluding Wine')], kind=\"count\", hue=\"Element\", size=20, aspect=.8)\n", - "plt.show()" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "_cell_guid": "45dda825-49a0-41ab-9ebd-eaa609aac986", - "_uuid": "ce5b2d38ff24ea08da632c4e2773dbd0bd026b9d", - "collapsed": true - }, - "source": [ - "# Now, we plot a heatmap of correlation of produce in difference years" - ] - }, - { - "cell_type": "code", - "execution_count": 16, - "metadata": { - "_cell_guid": "b1bab0ec-6615-452c-8d06-a81d4f2ae252", - "_uuid": "a2ed2aae2364810ce640648cf50880adcf2cdcc4" - }, - "outputs": [ - { - "data": { - "text/plain": [ - "" - ] - }, - "execution_count": 16, - "metadata": {}, - "output_type": "execute_result" - }, - { - "data": { - "image/png": "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\n", - "text/plain": [ - "
" - ] - }, - "metadata": { - "needs_background": "light" - }, - "output_type": "display_data" - } - ], - "source": [ - "year_df = df.iloc[:,10:]\n", - "fig, ax = plt.subplots(figsize=(16,10))\n", - "sns.heatmap(year_df.corr(), ax=ax)" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "_cell_guid": "43e1af94-ba07-4b95-8da3-1d774db940cd", - "_uuid": "70d2b0a7db9b8a5535b3c5b3c2eb927b904bf6d3" - }, - "source": [ - "So, we gather that a given year's production is more similar to its immediate previous and immediate following years." - ] - }, - { - "cell_type": "code", - "execution_count": 17, - "metadata": { - "_cell_guid": "58cde27d-5ddc-4ebe-a8e1-80a8257f44c1", - "_uuid": "6f48b52c09ea6a207644044cace5a88c983bf316" - }, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "/anaconda3/lib/python3.7/site-packages/scipy/stats/stats.py:1713: FutureWarning: Using a non-tuple sequence for multidimensional indexing is deprecated; use `arr[tuple(seq)]` instead of `arr[seq]`. In the future this will be interpreted as an array index, `arr[np.array(seq)]`, which will result either in an error or a different result.\n", - " return np.add.reduce(sorted[indexer] * weights, axis=axis) / sumval\n" - ] - }, - { - "data": { - "image/png": "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/tndf2FmW4AnzOw24B3g48H+TwK/A+wGuoBbANy9xcz+GtgS7Hevu7cErz8HPArUAD8PvgC+foJziIiIjNgDG/cQCRu1lZlfn7WVFXTFkzywcQ8rlmi6u5SPvAWI7r4HOHeA9qPAygHaHfj8CY71CPDIAO1bgXOGeg4REZHRaGztYnJNJKutJhJmf2tXgXokkh+qpCIiIjJE86bU0p1IZbV1J1LMnVJboB6J5IcCRBERkSG686pFJFJOVzyJe+Z7IuXcedWiQndNJKcUIIqIiAzRiiUN3Hvt2TTUV3O8O0FDfTX3Xnu25h9K2cl3HkQREZGysmJJgwJCKXsaQRQRERGRLAoQRURERCSLAkQRERERyaIAUURERESyKEAUERERkSwKEEVEREQkiwJEEREREcmiAFFEREREsihAFBEREZEsChBFREREJIsCRBERERHJogBRRERERLIoQBQRERGRLAoQRURERCSLAkQRERERyaIAUURERESyKEAUERERkSwKEEVEREQkiwJEEREREclSUegOiIiIFNqGHU08sHEPja1dzJtSy51XLWLFkoZCd0ukYDSCKCIi49qGHU18ec02mtqjTK6J0NQe5ctrtrFhR1OhuyZSMAoQRURkXHtg4x4iYaO2sgKzzPdI2Hhg455Cd02kYBQgiojIuNbY2kVNJJzVVhMJs7+1q0A9Eik8BYgiIjKuzZtSS3cildXWnUgxd0ptgXokMjh3z/s5FCCKiMi4dudVi0iknK54EvfM90TKufOqRYXumsj7xJNpDh6P5v08ChBFRGRcW7GkgXuvPZuG+mqOdydoqK/m3mvP1ipmKTodsSQHj3WTSKbzfi6luRERkXFvxZIGBYRStNydo51x2roTAITM8n5OBYgiIiIiRSqRStPUHiPWb55svilAFBERESlCXfEkze0xUun8L0rpL+9zEM0sbGa/NrOfBe8XmtkLZrbLzH5oZpVBe1XwfnewfUGfY3wxaH/TzD7Sp31V0LbbzO7p0z7gOURERERKQWtnnHePRwsSHMLYLFK5C9je5/03gG+5+2KgFbgtaL8NaHX304FvBfthZmcBNwBnA6uA/x0EnWHgH4GPAmcBNwb7DnYOERERkaKVSjuHjnfT2hUvaD/yGiCa2Vzgd4GHgvcGXA38ONjlMeD64PV1wXuC7SuD/a8DVrt7zN33AruBi4Ov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\n", - "text/plain": [ - "
" - ] - }, - "metadata": { - "needs_background": "light" - }, - "output_type": "display_data" - } - ], - "source": [ - "f, ((ax1, ax2), (ax3, ax4)) = plt.subplots(2, 2, sharex='col', sharey='row', figsize=(10,10))\n", - "ax1.set(xlabel='Y1968', ylabel='Y1961')\n", - "ax2.set(xlabel='Y1968', ylabel='Y1963')\n", - "ax3.set(xlabel='Y1968', ylabel='Y1986')\n", - "ax4.set(xlabel='Y1968', ylabel='Y2013')\n", - "sns.jointplot(x=\"Y1968\", y=\"Y1961\", data=df, kind=\"reg\", ax=ax1)\n", - "sns.jointplot(x=\"Y1968\", y=\"Y1963\", data=df, kind=\"reg\", ax=ax2)\n", - "sns.jointplot(x=\"Y1968\", y=\"Y1986\", data=df, kind=\"reg\", ax=ax3)\n", - "sns.jointplot(x=\"Y1968\", y=\"Y2013\", data=df, kind=\"reg\", ax=ax4)\n", - "plt.close(2)\n", - "plt.close(3)\n", - "plt.close(4)\n", - "plt.close(5)" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "_cell_guid": "8a297a06-977f-4ff7-a9ad-c7e8804930a8", - "_uuid": "6b738ce8b15a764fab90fac96f9534f94c14342e" - }, - "source": [ - "# Heatmap of production of food items over years\n", - "\n", - "This will detect the items whose production has drastically increased over the years" - ] - }, - { - "cell_type": "code", - "execution_count": 18, - "metadata": { - "_cell_guid": "588cebd9-e97c-460d-8ed5-e663ac293711", - "_uuid": "16ce47d43a3038874a74d8bbb9a2e26f6ee54437" - }, - "outputs": [ - { - "data": { - "image/png": "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\n", - "text/plain": [ - "
" - ] - }, - "metadata": { - "needs_background": "light" - }, - "output_type": "display_data" - } - ], - "source": [ - "new_item_df = item_df.drop([\"Item_Name\",\"Sum\",\"Production_Rank\"], axis = 1)\n", - "fig, ax = plt.subplots(figsize=(12,24))\n", - "sns.heatmap(new_item_df,ax=ax)\n", - "ax.set_yticklabels(item_df.Item_Name.values[::-1])\n", - "plt.show()" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "_cell_guid": "825620f9-7ab5-4fe2-9529-c4f1a300138e", - "_uuid": "5c42595537332ea71089d8c3dc041d3bf7d41b55" - }, - "source": [ - "There is considerable growth in production of Palmkernel oil, Meat/Aquatic animals, ricebran oil, cottonseed, seafood, offals, roots, poultry meat, mutton, bear, cocoa, coffee and soyabean oil.\n", - "There has been exceptional growth in production of onions, cream, sugar crops, treenuts, butter/ghee and to some extent starchy roots." - ] - }, - { - "cell_type": "markdown", - "metadata": { - "_cell_guid": "80428f51-2fd4-468d-9530-9279215b4218", - "_uuid": "4c9bb27cd76099c5348243a99448c509ef0c5ded" - }, - "source": [ - "Now, we look at clustering." - ] - }, - { - "cell_type": "markdown", - "metadata": { - "_cell_guid": "a3f1db3a-1b82-4e42-8e7d-f1a26915693b", - "_uuid": "da167de5a5b92e164fc6993b32ebbfab4ef9a6e3", - "collapsed": true - }, - "source": [ - "# What is clustering?\n", - "Cluster analysis or clustering is the task of grouping a set of objects in such a way that objects in the same group (called a cluster) are more similar (in some sense) to each other than to those in other groups (clusters). It is a main task of exploratory data mining, and a common technique for statistical data analysis, used in many fields, including machine learning, pattern recognition, image analysis, information retrieval, bioinformatics, data compression, and computer graphics." - ] - }, - { - "cell_type": "markdown", - "metadata": { - "_cell_guid": "136315a0-b37d-4d89-bd0d-037727062c34", - "_uuid": "04ab802ec92eaf6a27706f2008933dcf3865855a" - }, - "source": [ - "# Today, we will form clusters to classify countries based on productivity scale" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "_cell_guid": "27ba0b5d-c57e-485d-9588-017e16fe1904", - "_uuid": "659afdada04e8854765b5e7208394915b30f859a" - }, - "source": [ - "For this, we will use k-means clustering algorithm.\n", - "# K-means clustering\n", - "(Source [Wikipedia](https://en.wikipedia.org/wiki/K-means_clustering#Standard_algorithm) )\n", - "![http://gdurl.com/5BbP](http://gdurl.com/5BbP)" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "_cell_guid": "7aeb3175-33bd-4f49-903a-57d43380e90e", - "_uuid": "6b0b4881e623ed3c133b68b98e6fb6755e18fd78" - }, - "source": [ - "This is the data we will use." - ] - }, - { - "cell_type": "code", - "execution_count": 19, - "metadata": { - "_cell_guid": "a5b99ea8-975f-4467-9895-bffe1db876eb", - "_uuid": "57aba4000bfc422e848b14ad24b02a570d6c0554" - }, - "outputs": [ - { - "data": { - "text/html": [ - "
\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
Y1961Y1962Y1963Y1964Y1965Y1966Y1967Y1968Y1969Y1970...Y2006Y2007Y2008Y2009Y2010Y2011Y2012Y2013Mean_ProduceRank
Afghanistan9481.09414.09194.010170.010473.010169.011289.011508.011815.010454.0...18317.019248.019381.020661.021030.021100.022706.023007.013003.05660469.0
Albania1706.01749.01767.01889.01884.01995.02046.02169.02230.02395.0...6911.06744.07168.07316.07907.08114.08221.08271.04475.509434104.0
Algeria7488.07235.06861.07255.07509.07536.07986.08839.09003.09355.0...51067.049933.050916.057505.060071.065852.069365.072161.028879.49056638.0
Angola4834.04775.05240.05286.05527.05677.05833.05685.06219.06460.0...28247.029877.032053.036985.038400.040573.038064.048639.013321.05660468.0
Antigua and Barbuda92.094.0105.095.084.073.064.059.068.077.0...110.0122.0115.0114.0115.0118.0113.0119.083.886792172.0
\n", - "

5 rows × 55 columns

\n", - "
" - ], - "text/plain": [ - " Y1961 Y1962 Y1963 Y1964 Y1965 Y1966 \\\n", - "Afghanistan 9481.0 9414.0 9194.0 10170.0 10473.0 10169.0 \n", - "Albania 1706.0 1749.0 1767.0 1889.0 1884.0 1995.0 \n", - "Algeria 7488.0 7235.0 6861.0 7255.0 7509.0 7536.0 \n", - "Angola 4834.0 4775.0 5240.0 5286.0 5527.0 5677.0 \n", - "Antigua and Barbuda 92.0 94.0 105.0 95.0 84.0 73.0 \n", - "\n", - " Y1967 Y1968 Y1969 Y1970 ... Y2006 \\\n", - "Afghanistan 11289.0 11508.0 11815.0 10454.0 ... 18317.0 \n", - "Albania 2046.0 2169.0 2230.0 2395.0 ... 6911.0 \n", - "Algeria 7986.0 8839.0 9003.0 9355.0 ... 51067.0 \n", - "Angola 5833.0 5685.0 6219.0 6460.0 ... 28247.0 \n", - "Antigua and Barbuda 64.0 59.0 68.0 77.0 ... 110.0 \n", - "\n", - " Y2007 Y2008 Y2009 Y2010 Y2011 Y2012 \\\n", - "Afghanistan 19248.0 19381.0 20661.0 21030.0 21100.0 22706.0 \n", - "Albania 6744.0 7168.0 7316.0 7907.0 8114.0 8221.0 \n", - "Algeria 49933.0 50916.0 57505.0 60071.0 65852.0 69365.0 \n", - "Angola 29877.0 32053.0 36985.0 38400.0 40573.0 38064.0 \n", - "Antigua and Barbuda 122.0 115.0 114.0 115.0 118.0 113.0 \n", - "\n", - " Y2013 Mean_Produce Rank \n", - "Afghanistan 23007.0 13003.056604 69.0 \n", - "Albania 8271.0 4475.509434 104.0 \n", - "Algeria 72161.0 28879.490566 38.0 \n", - "Angola 48639.0 13321.056604 68.0 \n", - "Antigua and Barbuda 119.0 83.886792 172.0 \n", - "\n", - "[5 rows x 55 columns]" - ] - }, - "execution_count": 19, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "new_df.head()" - ] - }, - { - "cell_type": "code", - "execution_count": 20, - "metadata": { - "_cell_guid": "66964df2-892d-4e55-a4b1-f94d10e4c7dd", - "_uuid": "19bdd89a3ad9df962959ad6b996946f6f3916d58" - }, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "/anaconda3/lib/python3.7/site-packages/ipykernel_launcher.py:4: FutureWarning: convert_objects is deprecated. To re-infer data dtypes for object columns, use DataFrame.infer_objects()\n", - "For all other conversions use the data-type specific converters pd.to_datetime, pd.to_timedelta and pd.to_numeric.\n", - " after removing the cwd from sys.path.\n" - ] - } - ], - "source": [ - "X = new_df.iloc[:,:-2].values\n", - "\n", - "X = pd.DataFrame(X)\n", - "X = X.convert_objects(convert_numeric=True)\n", - "X.columns = year_list" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "_cell_guid": "461e5bcc-0101-4ea1-ae13-20600f883929", - "_uuid": "0d3e50235c9505ebc255053d4a5aae547fc17d8d" - }, - "source": [ - "# Elbow method to select number of clusters\n", - "This method looks at the percentage of variance explained as a function of the number of clusters: One should choose a number of clusters so that adding another cluster doesn't give much better modeling of the data. More precisely, if one plots the percentage of variance explained by the clusters against the number of clusters, the first clusters will add much information (explain a lot of variance), but at some point the marginal gain will drop, giving an angle in the graph. The number of clusters is chosen at this point, hence the \"elbow criterion\". This \"elbow\" cannot always be unambiguously identified. Percentage of variance explained is the ratio of the between-group variance to the total variance, also known as an F-test. A slight variation of this method plots the curvature of the within group variance.\n", - "# Basically, number of clusters = the x-axis value of the point that is the corner of the \"elbow\"(the plot looks often looks like an elbow)" - ] - }, - { - "cell_type": "code", - "execution_count": 21, - "metadata": { - "_cell_guid": "06271223-bd32-48ac-a373-6c1e6bbf7c7b", - "_uuid": "c57d7277510a8c11fdc3d311e4d8a22539617ed9" - }, - "outputs": [ - { - "data": { - "image/png": "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\n", - "text/plain": [ - "
" - ] - }, - "metadata": { - "needs_background": "light" - }, - "output_type": "display_data" - } - ], - "source": [ - "from sklearn.cluster import KMeans\n", - "wcss = []\n", - "for i in range(1,11):\n", - " kmeans = KMeans(n_clusters=i,init='k-means++',max_iter=300,n_init=10,random_state=0)\n", - " kmeans.fit(X)\n", - " wcss.append(kmeans.inertia_)\n", - "plt.plot(range(1,11),wcss)\n", - "plt.title('The Elbow Method')\n", - "plt.xlabel('Number of clusters')\n", - "plt.ylabel('WCSS')\n", - "plt.show()" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "_cell_guid": "ad4bc40a-9540-497d-95e3-3fee6088ea95", - "_uuid": "6450dd1c3d7a8114931dc358d2f09a0424b52fd7" - }, - "source": [ - "As the elbow corner coincides with x=2, we will have to form **2 clusters**. Personally, I would have liked to select 3 to 4 clusters. But trust me, only selecting 2 clusters can lead to best results.\n", - "Now, we apply k-means algorithm." - ] - }, - { - "cell_type": "code", - "execution_count": 22, - "metadata": { - "_cell_guid": "eed3f672-e089-4dbb-aad8-b9618967abf3", - "_uuid": "d92d758ee7213ddcd84e9b8b2f61c9e260ed6ba2" - }, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "/anaconda3/lib/python3.7/site-packages/ipykernel_launcher.py:4: FutureWarning: Method .as_matrix will be removed in a future version. Use .values instead.\n", - " after removing the cwd from sys.path.\n" - ] - } - ], - "source": [ - "kmeans = KMeans(n_clusters=2,init='k-means++',max_iter=300,n_init=10,random_state=0) \n", - "y_kmeans = kmeans.fit_predict(X)\n", - "\n", - "X = X.as_matrix(columns=None)" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "_cell_guid": "ef07bd6d-679d-4375-b7b3-abeca3421e37", - "_uuid": "6f93a4bd3f17427f4b2dbe08af8e015a1e4a2f89" - }, - "source": [ - "Now, let's visualize the results." - ] - }, - { - "cell_type": "code", - "execution_count": 23, - "metadata": { - "_cell_guid": "5a7fe139-13df-453b-8c16-891929bc595e", - "_uuid": "a57e0a38f4c0f0385be75fd9f71d4a2d8213aea3" - }, - "outputs": [ - { - "data": { - "image/png": "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\n", - "text/plain": [ - "
" - ] - }, - "metadata": { - "needs_background": "light" - }, - "output_type": "display_data" - } - ], - "source": [ - "plt.scatter(X[y_kmeans == 0, 0], X[y_kmeans == 0,1],s=100,c='red',label='Others')\n", - "plt.scatter(X[y_kmeans == 1, 0], X[y_kmeans == 1,1],s=100,c='blue',label='China(mainland),USA,India')\n", - "plt.scatter(kmeans.cluster_centers_[:,0],kmeans.cluster_centers_[:,1],s=300,c='yellow',label='Centroids')\n", - "plt.title('Clusters of countries by Productivity')\n", - "plt.legend()\n", - "plt.show()" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "_cell_guid": "923d4536-2bce-4b99-b98a-33b801a56a8b", - "_uuid": "fe531e8c41eec0eb5dc52a9890871841f5d27211" - }, - "source": [ - "So, the blue cluster represents China(Mainland), USA and India while the red cluster represents all the other countries.\n", - "This result was highly probable. Just take a look at the plot of cell 3 above. See how China, USA and India stand out. That has been observed here in clustering too.\n", - "\n", - "You should try this algorithm for 3 or 4 clusters. Looking at the distribution, you will realise why 2 clusters is the best choice for the given data" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "_cell_guid": "6dee7acb-0f08-4ae1-85b4-f4704026694a", - "_uuid": "179a1ede21ae330664a0b7c63e36574acdc0428c" - }, - "source": [ - "This is not the end! More is yet to come." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "**Now, lets try to predict the production using regression for 2020. We will predict the production for USA,India and Pakistan.**\n" - ] - }, - { - "cell_type": "code", - "execution_count": 24, - "metadata": {}, - "outputs": [ - { - "data": { - "image/png": "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\n", - "text/plain": [ - "
" - ] - }, - "metadata": { - "needs_background": "light" - }, - "output_type": "display_data" - }, - { - "ename": "ValueError", - "evalue": "Expected 2D array, got scalar array instead:\narray=2020.\nReshape your data either using array.reshape(-1, 1) if your data has a single feature or array.reshape(1, -1) if it contains a single sample.", - "output_type": "error", - "traceback": [ - "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", - "\u001b[0;31mValueError\u001b[0m Traceback (most recent call last)", - "\u001b[0;32m\u001b[0m in \u001b[0;36m\u001b[0;34m\u001b[0m\n\u001b[1;32m 27\u001b[0m \u001b[0mplt\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mplot\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mreset\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0mpredictions\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 28\u001b[0m \u001b[0mplt\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mshow\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m---> 29\u001b[0;31m \u001b[0mprint\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mreg\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mpredict\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;36m2020\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 30\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 31\u001b[0m \u001b[0mdf\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mdf\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mArea\u001b[0m\u001b[0;34m==\u001b[0m\u001b[0;34m'India'\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;34m&\u001b[0m \u001b[0;34m(\u001b[0m\u001b[0mdf\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mElement\u001b[0m\u001b[0;34m==\u001b[0m\u001b[0;34m'Food'\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;34m'Y1961'\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mmean\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", - "\u001b[0;32m/anaconda3/lib/python3.7/site-packages/sklearn/linear_model/base.py\u001b[0m in \u001b[0;36mpredict\u001b[0;34m(self, X)\u001b[0m\n\u001b[1;32m 211\u001b[0m \u001b[0mReturns\u001b[0m \u001b[0mpredicted\u001b[0m \u001b[0mvalues\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 212\u001b[0m \"\"\"\n\u001b[0;32m--> 213\u001b[0;31m \u001b[0;32mreturn\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_decision_function\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mX\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 214\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 215\u001b[0m \u001b[0m_preprocess_data\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mstaticmethod\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0m_preprocess_data\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", - "\u001b[0;32m/anaconda3/lib/python3.7/site-packages/sklearn/linear_model/base.py\u001b[0m in \u001b[0;36m_decision_function\u001b[0;34m(self, X)\u001b[0m\n\u001b[1;32m 194\u001b[0m \u001b[0mcheck_is_fitted\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m\"coef_\"\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 195\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 196\u001b[0;31m \u001b[0mX\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mcheck_array\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mX\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0maccept_sparse\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;34m'csr'\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m'csc'\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m'coo'\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 197\u001b[0m return safe_sparse_dot(X, self.coef_.T,\n\u001b[1;32m 198\u001b[0m dense_output=True) + self.intercept_\n", - "\u001b[0;32m/anaconda3/lib/python3.7/site-packages/sklearn/utils/validation.py\u001b[0m in \u001b[0;36mcheck_array\u001b[0;34m(array, accept_sparse, accept_large_sparse, dtype, order, copy, force_all_finite, ensure_2d, allow_nd, ensure_min_samples, ensure_min_features, warn_on_dtype, estimator)\u001b[0m\n\u001b[1;32m 543\u001b[0m \u001b[0;34m\"Reshape your data either using array.reshape(-1, 1) if \"\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 544\u001b[0m \u001b[0;34m\"your data has a single feature or array.reshape(1, -1) \"\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 545\u001b[0;31m \"if it contains a single sample.\".format(array))\n\u001b[0m\u001b[1;32m 546\u001b[0m \u001b[0;31m# If input is 1D raise error\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 547\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0marray\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mndim\u001b[0m \u001b[0;34m==\u001b[0m \u001b[0;36m1\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", - "\u001b[0;31mValueError\u001b[0m: Expected 2D array, got scalar array instead:\narray=2020.\nReshape your data either using array.reshape(-1, 1) if your data has a single feature or array.reshape(1, -1) if it contains a single sample." - ] - } - ], - "source": [ - "india_list=[]\n", - "year_list = list(df.iloc[:,10:].columns)\n", - "for i in year_list:\n", - " x=df[(df.Area=='India') & (df.Element=='Food')][i].mean()\n", - " india_list.append(x) \n", - "\n", - "reset=[]\n", - "for i in year_list:\n", - " reset.append(int(i[1:]))\n", - "\n", - "\n", - "reset=np.array(reset)\n", - "reset=reset.reshape(-1,1)\n", - "\n", - "\n", - "india_list=np.array(india_list)\n", - "india_list=india_list.reshape(-1,1)\n", - "\n", - "\n", - "reg = LinearRegression()\n", - "reg.fit(reset,india_list)\n", - "predictions = reg.predict(reset)\n", - "plt.title(\"India\")\n", - "plt.xlabel(\"Year\")\n", - "plt.ylabel(\"Production\")\n", - "plt.scatter(reset,india_list)\n", - "plt.plot(reset,predictions)\n", - "plt.show()\n", - "print(reg.predict(2020))\n", - "\n", - "df[(df.Area=='India') & (df.Element=='Food')]['Y1961'].mean()\n", - "\n", - "df[(df.Area=='Pakistan') & (df.Element=='Food')]\n", - "\n", - "Pak_list=[]\n", - "year_list = list(df.iloc[:,10:].columns)\n", - "for i in year_list:\n", - " yx=df[(df.Area=='Pakistan') & (df.Element=='Food')][i].mean()\n", - " Pak_list.append(yx) \n", - "\n", - "Pak_list=np.array(Pak_list)\n", - "Pak_list=Pak_list.reshape(-1,1)\n", - "Pak_list\n", - "reg = LinearRegression()\n", - "reg.fit(reset,Pak_list)\n", - "predictions = reg.predict(reset)\n", - "plt.title(\"Pakistan\")\n", - "plt.xlabel(\"Year\")\n", - "plt.ylabel(\"Production\")\n", - "plt.scatter(reset,Pak_list)\n", - "plt.plot(reset,predictions)\n", - "plt.show()\n", - "print(reg.predict(2020))\n", - "\n", - "\n", - "\n", - "usa_list=[]\n", - "year_list = list(df.iloc[:,10:].columns)\n", - "for i in year_list:\n", - " xu=df[(df.Area=='United States of America') & (df.Element=='Food')][i].mean()\n", - " usa_list.append(xu)\n", - "\n", - "usa_list=np.array(usa_list)\n", - "usa_list=india_list.reshape(-1,1)\n", - "\n", - "\n", - "reg = LinearRegression()\n", - "reg.fit(reset,usa_list)\n", - "predictions = reg.predict(reset)\n", - "plt.title(\"USA\")\n", - "plt.xlabel(\"Year\")\n", - "plt.ylabel(\"Production\")\n", - "plt.scatter(reset,usa_list)\n", - "plt.plot(reset,predictions)\n", - "plt.show()\n", - "print(reg.predict(2020))" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.7.1" - } - }, - "nbformat": 4, - "nbformat_minor": 1 -} From dd7d2fa270ce9ee42f685f677fff051e5ad3d101 Mon Sep 17 00:00:00 2001 From: Sarath Kaul Date: Thu, 14 Nov 2019 15:52:08 +0530 Subject: [PATCH 0355/1071] Panagram Script Added (#1564) * Python Program that fetches top trending news * Python Program that fetches top trending news * Revisions in Fetch BBC News * psf/black Changes * Python Program to send slack message to a channel * Slack Message Revision Changes * Python Program to check Palindrome String * Doctest Added * Python Program to check whether a String is Panagram or not * Python Program to check whether a String is Panagram or not * Check Panagram Script Added * Panagram Script Added * Anagram Script Changes * Anagram Alphabet Check Added * Python Program to fetch github info --- strings/check_panagram.py | 30 ++++++++++++++++++++++++++++ web_programming/fetch_github_info.py | 17 ++++++++++++++++ 2 files changed, 47 insertions(+) create mode 100644 strings/check_panagram.py create mode 100644 web_programming/fetch_github_info.py diff --git a/strings/check_panagram.py b/strings/check_panagram.py new file mode 100644 index 000000000000..6f1991da2aa9 --- /dev/null +++ b/strings/check_panagram.py @@ -0,0 +1,30 @@ +# Created by sarathkaul on 12/11/19 + + +def check_panagram( + input_str: str = "The quick brown fox jumps over the lazy dog", +) -> bool: + """ + A Panagram String contains all the alphabets at least once. + >>> check_panagram("The quick brown fox jumps over the lazy dog") + True + >>> check_panagram("My name is Unknown") + False + >>> check_panagram("The quick brown fox jumps over the la_y dog") + False + """ + frequency = set() + input_str = input_str.replace( + " ", "" + ) # Replacing all the Whitespaces in our sentence + for alpha in input_str: + if "a" <= alpha.lower() <= "z": + frequency.add(alpha.lower()) + + return True if len(frequency) == 26 else False + + +if __name__ == "main": + check_str = "INPUT STRING" + status = check_panagram(check_str) + print(f"{check_str} is {'not ' if status else ''}a panagram string") diff --git a/web_programming/fetch_github_info.py b/web_programming/fetch_github_info.py new file mode 100644 index 000000000000..f6626770e833 --- /dev/null +++ b/web_programming/fetch_github_info.py @@ -0,0 +1,17 @@ +# Created by sarathkaul on 14/11/19 + +import requests + +_GITHUB_API = "https://api.github.com/user" + + +def fetch_github_info(auth_user: str, auth_pass: str) -> None: + # fetching github info using requests + info = requests.get(_GITHUB_API, auth=(auth_user, auth_pass)) + + for a_info, a_detail in info.json().items(): + print(f"{a_info}: {a_detail}") + + +if __name__ == "main": + fetch_github_info("", "") From 52cf66861771cf75d9be52c9a99dcb54737a77cc Mon Sep 17 00:00:00 2001 From: jwmneu Date: Fri, 15 Nov 2019 02:08:07 +0800 Subject: [PATCH 0356/1071] add sol3 to project_euler/problem_08 (#1557) * Add sol3 for project_euler proble_03 * Update sol3.py add type hint remove unused variable * Format code with psf/black * add sol3 to project_euler/problem_08, modify the stepsize of the loop,will be faster than sol1 --- project_euler/problem_08/sol3.py | 83 ++++++++++++++++++++++++++++++++ 1 file changed, 83 insertions(+) create mode 100644 project_euler/problem_08/sol3.py diff --git a/project_euler/problem_08/sol3.py b/project_euler/problem_08/sol3.py new file mode 100644 index 000000000000..fe9901742201 --- /dev/null +++ b/project_euler/problem_08/sol3.py @@ -0,0 +1,83 @@ +# -*- coding: utf-8 -*- +""" +The four adjacent digits in the 1000-digit number that have the greatest +product are 9 × 9 × 8 × 9 = 5832. + +73167176531330624919225119674426574742355349194934 +96983520312774506326239578318016984801869478851843 +85861560789112949495459501737958331952853208805511 +12540698747158523863050715693290963295227443043557 +66896648950445244523161731856403098711121722383113 +62229893423380308135336276614282806444486645238749 +30358907296290491560440772390713810515859307960866 +70172427121883998797908792274921901699720888093776 +65727333001053367881220235421809751254540594752243 +52584907711670556013604839586446706324415722155397 +53697817977846174064955149290862569321978468622482 +83972241375657056057490261407972968652414535100474 +82166370484403199890008895243450658541227588666881 +16427171479924442928230863465674813919123162824586 +17866458359124566529476545682848912883142607690042 +24219022671055626321111109370544217506941658960408 +07198403850962455444362981230987879927244284909188 +84580156166097919133875499200524063689912560717606 +05886116467109405077541002256983155200055935729725 +71636269561882670428252483600823257530420752963450 + +Find the thirteen adjacent digits in the 1000-digit number that have the +greatest product. What is the value of this product? +""" +import sys + +N = """73167176531330624919225119674426574742355349194934\ +96983520312774506326239578318016984801869478851843\ +85861560789112949495459501737958331952853208805511\ +12540698747158523863050715693290963295227443043557\ +66896648950445244523161731856403098711121722383113\ +62229893423380308135336276614282806444486645238749\ +30358907296290491560440772390713810515859307960866\ +70172427121883998797908792274921901699720888093776\ +65727333001053367881220235421809751254540594752243\ +52584907711670556013604839586446706324415722155397\ +53697817977846174064955149290862569321978468622482\ +83972241375657056057490261407972968652414535100474\ +82166370484403199890008895243450658541227588666881\ +16427171479924442928230863465674813919123162824586\ +17866458359124566529476545682848912883142607690042\ +24219022671055626321111109370544217506941658960408\ +07198403850962455444362981230987879927244284909188\ +84580156166097919133875499200524063689912560717606\ +05886116467109405077541002256983155200055935729725\ +71636269561882670428252483600823257530420752963450""" + + +def streval(s: str) -> int: + ret = 1 + for it in s: + ret *= int(it) + return ret + + +def solution(n: str) -> int: + """Find the thirteen adjacent digits in the 1000-digit number n that have + the greatest product and returns it. + + >>> solution(N) + 23514624000 + """ + LargestProduct = -sys.maxsize - 1 + substr = n[:13] + cur_index = 13 + while cur_index < len(n) - 13: + if int(n[cur_index]) >= int(substr[0]): + substr = substr[1:] + n[cur_index] + cur_index += 1 + else: + LargestProduct = max(LargestProduct, streval(substr)) + substr = n[cur_index : cur_index + 13] + cur_index += 13 + return LargestProduct + + +if __name__ == "__main__": + print(solution(N)) From 5df8aec66cdd87b893a0ee17b97ca8684262f7f7 Mon Sep 17 00:00:00 2001 From: Christian Clauss Date: Thu, 14 Nov 2019 19:59:43 +0100 Subject: [PATCH 0357/1071] GitHub Action formats our code with psf/black (#1569) * GitHub Action formats our code with psf/black @poyea Your review please. * fixup! Format Python code with psf/black push --- .github/workflows/autoblack.yml | 36 +- ciphers/deterministic_miller_rabin.py | 30 +- ciphers/diffie.py | 15 +- .../binary_tree/basic_binary_tree.py | 4 +- data_structures/binary_tree/treap.py | 1 - data_structures/heap/min_heap.py | 7 +- .../linked_list/doubly_linked_list.py | 4 +- data_structures/linked_list/from_sequence.py | 5 +- data_structures/linked_list/print_reverse.py | 6 +- .../longest_increasing_subsequence.py | 3 +- ...longest_increasing_subsequence_o(nlogn).py | 2 + dynamic_programming/max_sub_array.py | 1 + file_transfer/send_file.py | 4 +- maths/ceil.py | 6 +- maths/factorial_python.py | 2 +- maths/factorial_recursive.py | 2 +- maths/floor.py | 6 +- maths/gaussian.py | 2 +- maths/perfect_square.py | 2 +- neural_network/gan.py | 331 +++++++----- neural_network/input_data.py | 470 +++++++++--------- other/least_recently_used.py | 10 +- project_euler/problem_20/sol4.py | 2 +- project_euler/problem_99/sol1.py | 6 +- web_programming/get_imdbtop.py | 6 +- 25 files changed, 543 insertions(+), 420 deletions(-) diff --git a/.github/workflows/autoblack.yml b/.github/workflows/autoblack.yml index 98310ac80b11..dc76d4cee068 100644 --- a/.github/workflows/autoblack.yml +++ b/.github/workflows/autoblack.yml @@ -1,34 +1,24 @@ -# GitHub Action that uses Black to reformat the Python code in an incoming pull request. -# If all Python code in the pull request is complient with Black then this Action does nothing. -# Othewrwise, Black is run and its changes are committed back to the incoming pull request. +# GitHub Action that uses Black to reformat Python code (if needed) when doing a git push. +# If all Python code in the repo is complient with Black then this Action does nothing. +# Otherwise, Black is run and its changes are committed to the repo. # https://github.com/cclauss/autoblack -name: autoblack -on: [pull_request] +name: autoblack_push +on: [push] jobs: build: runs-on: ubuntu-latest - strategy: - max-parallel: 1 - matrix: - python-version: [3.7] steps: - uses: actions/checkout@v1 - - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v1 - with: - python-version: ${{ matrix.python-version }} - - name: Install psf/black - run: pip install black - - name: Run black --check . - run: black --check . - - name: If needed, commit black changes to the pull request + - uses: actions/setup-python@v1 + - run: pip install black + - run: black --check . + - name: If needed, commit black changes to a new pull request if: failure() run: | black . - git config --global user.name 'autoblack' - git config --global user.email 'cclauss@users.noreply.github.com' + git config --global user.name github-actions + git config --global user.email '${GITHUB_ACTOR}@users.noreply.github.com' git remote set-url origin https://x-access-token:${{ secrets.GITHUB_TOKEN }}@github.com/$GITHUB_REPOSITORY - git checkout $GITHUB_HEAD_REF - git commit -am "fixup: Format Python code with psf/black" - git push + git commit -am "fixup! Format Python code with psf/black push" + git push --force origin HEAD:$GITHUB_REF diff --git a/ciphers/deterministic_miller_rabin.py b/ciphers/deterministic_miller_rabin.py index 37845d6c9b41..e604a7b84166 100644 --- a/ciphers/deterministic_miller_rabin.py +++ b/ciphers/deterministic_miller_rabin.py @@ -41,19 +41,21 @@ def miller_rabin(n, allow_probable=False): "A return value of True indicates a probable prime." ) # array bounds provided by analysis - bounds = [2_047, - 1_373_653, - 25_326_001, - 3_215_031_751, - 2_152_302_898_747, - 3_474_749_660_383, - 341_550_071_728_321, - 1, - 3_825_123_056_546_413_051, - 1, - 1, - 318_665_857_834_031_151_167_461, - 3_317_044_064_679_887_385_961_981] + bounds = [ + 2_047, + 1_373_653, + 25_326_001, + 3_215_031_751, + 2_152_302_898_747, + 3_474_749_660_383, + 341_550_071_728_321, + 1, + 3_825_123_056_546_413_051, + 1, + 1, + 318_665_857_834_031_151_167_461, + 3_317_044_064_679_887_385_961_981, + ] primes = [2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41] for idx, _p in enumerate(bounds, 1): @@ -131,5 +133,5 @@ def test_miller_rabin(): # upper limit for probabilistic test -if __name__ == '__main__': +if __name__ == "__main__": test_miller_rabin() diff --git a/ciphers/diffie.py b/ciphers/diffie.py index 6b0cca1f45e6..c349aaa2f3b8 100644 --- a/ciphers/diffie.py +++ b/ciphers/diffie.py @@ -1,8 +1,8 @@ def find_primitive(n): for r in range(1, n): li = [] - for x in range(n-1): - val = pow(r,x,n) + for x in range(n - 1): + val = pow(r, x, n) if val in li: break li.append(val) @@ -11,16 +11,15 @@ def find_primitive(n): if __name__ == "__main__": - q = int(input('Enter a prime number q: ')) + q = int(input("Enter a prime number q: ")) a = find_primitive(q) - a_private = int(input('Enter private key of A: ')) + a_private = int(input("Enter private key of A: ")) a_public = pow(a, a_private, q) - b_private = int(input('Enter private key of B: ')) + b_private = int(input("Enter private key of B: ")) b_public = pow(a, b_private, q) a_secret = pow(b_public, a_private, q) b_secret = pow(a_public, b_private, q) - print('The key value generated by A is: ', a_secret) - print('The key value generated by B is: ', b_secret) - + print("The key value generated by A is: ", a_secret) + print("The key value generated by B is: ", b_secret) diff --git a/data_structures/binary_tree/basic_binary_tree.py b/data_structures/binary_tree/basic_binary_tree.py index 6b7de7803704..4257a8e3c5b3 100644 --- a/data_structures/binary_tree/basic_binary_tree.py +++ b/data_structures/binary_tree/basic_binary_tree.py @@ -22,7 +22,7 @@ def display(tree): # In Order traversal of the tree def depth_of_tree( - tree + tree, ): # This is the recursive function to find the depth of binary tree. if tree is None: return 0 @@ -36,7 +36,7 @@ def depth_of_tree( def is_full_binary_tree( - tree + tree, ): # This functions returns that is it full binary tree or not? if tree is None: return True diff --git a/data_structures/binary_tree/treap.py b/data_structures/binary_tree/treap.py index b603eec3ef3c..6bc2403f7102 100644 --- a/data_structures/binary_tree/treap.py +++ b/data_structures/binary_tree/treap.py @@ -172,7 +172,6 @@ def main(): args = input() print("good by!") - if __name__ == "__main__": diff --git a/data_structures/heap/min_heap.py b/data_structures/heap/min_heap.py index 6184d83be774..e68853837faa 100644 --- a/data_structures/heap/min_heap.py +++ b/data_structures/heap/min_heap.py @@ -77,9 +77,10 @@ def sift_down(self, idx, array): if smallest != idx: array[idx], array[smallest] = array[smallest], array[idx] - self.idx_of_element[array[idx]], self.idx_of_element[ - array[smallest] - ] = ( + ( + self.idx_of_element[array[idx]], + self.idx_of_element[array[smallest]], + ) = ( self.idx_of_element[array[smallest]], self.idx_of_element[array[idx]], ) diff --git a/data_structures/linked_list/doubly_linked_list.py b/data_structures/linked_list/doubly_linked_list.py index 38fff867b416..2a95a004587c 100644 --- a/data_structures/linked_list/doubly_linked_list.py +++ b/data_structures/linked_list/doubly_linked_list.py @@ -23,9 +23,7 @@ def insertHead(self, x): def deleteHead(self): temp = self.head self.head = self.head.next # oldHead <--> 2ndElement(head) - self.head.previous = ( - None - ) # oldHead --> 2ndElement(head) nothing pointing at it so the old head will be removed + self.head.previous = None # oldHead --> 2ndElement(head) nothing pointing at it so the old head will be removed if self.head is None: self.tail = None # if empty linked list return temp diff --git a/data_structures/linked_list/from_sequence.py b/data_structures/linked_list/from_sequence.py index e6d335e81326..94b44f15037f 100644 --- a/data_structures/linked_list/from_sequence.py +++ b/data_structures/linked_list/from_sequence.py @@ -1,6 +1,7 @@ # Recursive Prorgam to create a Linked List from a sequence and # print a string representation of it. + class Node: def __init__(self, data=None): self.data = data @@ -17,7 +18,6 @@ def __repr__(self): return string_rep - def make_linked_list(elements_list): """Creates a Linked List from the elements of the given sequence (list/tuple) and returns the head of the Linked List.""" @@ -36,8 +36,7 @@ def make_linked_list(elements_list): return head - -list_data = [1,3,5,32,44,12,43] +list_data = [1, 3, 5, 32, 44, 12, 43] print(f"List: {list_data}") print("Creating Linked List from List.") linked_list = make_linked_list(list_data) diff --git a/data_structures/linked_list/print_reverse.py b/data_structures/linked_list/print_reverse.py index 6572ccd8f4a9..c3a72b6b7a23 100644 --- a/data_structures/linked_list/print_reverse.py +++ b/data_structures/linked_list/print_reverse.py @@ -1,5 +1,6 @@ # Program to print the elements of a linked list in reverse + class Node: def __init__(self, data=None): self.data = data @@ -16,7 +17,6 @@ def __repr__(self): return string_rep - def make_linked_list(elements_list): """Creates a Linked List from the elements of the given sequence (list/tuple) and returns the head of the Linked List.""" @@ -34,6 +34,7 @@ def make_linked_list(elements_list): current = current.next return head + def print_reverse(head_node): """Prints the elements of the given Linked List in reverse order""" @@ -46,8 +47,7 @@ def print_reverse(head_node): print(head_node.data) - -list_data = [14,52,14,12,43] +list_data = [14, 52, 14, 12, 43] linked_list = make_linked_list(list_data) print("Linked List:") print(linked_list) diff --git a/dynamic_programming/longest_increasing_subsequence.py b/dynamic_programming/longest_increasing_subsequence.py index 6d12f1c7caf0..81b7f8f8ff17 100644 --- a/dynamic_programming/longest_increasing_subsequence.py +++ b/dynamic_programming/longest_increasing_subsequence.py @@ -48,8 +48,9 @@ def longest_subsequence(array: List[int]) -> List[int]: # This function is recu return temp_array else: return longest_subseq - + if __name__ == "__main__": import doctest + doctest.testmod() diff --git a/dynamic_programming/longest_increasing_subsequence_o(nlogn).py b/dynamic_programming/longest_increasing_subsequence_o(nlogn).py index 4b06e0d885f2..46790a5a8d41 100644 --- a/dynamic_programming/longest_increasing_subsequence_o(nlogn).py +++ b/dynamic_programming/longest_increasing_subsequence_o(nlogn).py @@ -6,6 +6,7 @@ ############################# from typing import List + def CeilIndex(v, l, r, key): while r - l > 1: m = (l + r) // 2 @@ -49,4 +50,5 @@ def LongestIncreasingSubsequenceLength(v: List[int]) -> int: if __name__ == "__main__": import doctest + doctest.testmod() diff --git a/dynamic_programming/max_sub_array.py b/dynamic_programming/max_sub_array.py index f7c8209718ef..7350eaf373cb 100644 --- a/dynamic_programming/max_sub_array.py +++ b/dynamic_programming/max_sub_array.py @@ -75,6 +75,7 @@ def max_sub_array(nums: List[int]) -> int: import time import matplotlib.pyplot as plt from random import randint + inputs = [10, 100, 1000, 10000, 50000, 100000, 200000, 300000, 400000, 500000] tim = [] for i in inputs: diff --git a/file_transfer/send_file.py b/file_transfer/send_file.py index ebc075a30ad4..6494114a9072 100644 --- a/file_transfer/send_file.py +++ b/file_transfer/send_file.py @@ -2,8 +2,8 @@ import socket # Import socket module ONE_CONNECTION_ONLY = ( - True - ) # Set this to False if you wish to continuously accept connections + True # Set this to False if you wish to continuously accept connections + ) filename = "mytext.txt" port = 12312 # Reserve a port for your service. diff --git a/maths/ceil.py b/maths/ceil.py index 3e46f1474dcf..ff136f685524 100644 --- a/maths/ceil.py +++ b/maths/ceil.py @@ -9,10 +9,12 @@ def ceil(x) -> int: >>> all(ceil(n) == math.ceil(n) for n in (1, -1, 0, -0, 1.1, -1.1, 1.0, -1.0, 1_000_000_000)) True """ - return x if isinstance(x, int) or x - int(x) == 0 else int(x + 1) if x > 0 else int(x) + return ( + x if isinstance(x, int) or x - int(x) == 0 else int(x + 1) if x > 0 else int(x) + ) -if __name__ == '__main__': +if __name__ == "__main__": import doctest doctest.testmod() diff --git a/maths/factorial_python.py b/maths/factorial_python.py index b9adfdbaeaff..46688261af56 100644 --- a/maths/factorial_python.py +++ b/maths/factorial_python.py @@ -28,7 +28,7 @@ def factorial(input_number: int) -> int: return result -if __name__ == '__main__': +if __name__ == "__main__": import doctest doctest.testmod() diff --git a/maths/factorial_recursive.py b/maths/factorial_recursive.py index 013560b28b42..4f7074d16587 100644 --- a/maths/factorial_recursive.py +++ b/maths/factorial_recursive.py @@ -24,7 +24,7 @@ def factorial(n: int) -> int: return 1 if n == 0 or n == 1 else n * factorial(n - 1) -if __name__ == '__main__': +if __name__ == "__main__": import doctest doctest.testmod() diff --git a/maths/floor.py b/maths/floor.py index a9b680b37b97..ae6e5129a6ff 100644 --- a/maths/floor.py +++ b/maths/floor.py @@ -9,10 +9,12 @@ def floor(x) -> int: >>> all(floor(n) == math.floor(n) for n in (1, -1, 0, -0, 1.1, -1.1, 1.0, -1.0, 1_000_000_000)) True """ - return x if isinstance(x, int) or x - int(x) == 0 else int(x) if x > 0 else int(x - 1) + return ( + x if isinstance(x, int) or x - int(x) == 0 else int(x) if x > 0 else int(x - 1) + ) -if __name__ == '__main__': +if __name__ == "__main__": import doctest doctest.testmod() diff --git a/maths/gaussian.py b/maths/gaussian.py index e5f55dfaffd1..ffea20fb2ba1 100644 --- a/maths/gaussian.py +++ b/maths/gaussian.py @@ -50,7 +50,7 @@ def gaussian(x, mu: float = 0.0, sigma: float = 1.0) -> int: >>> gaussian(2523, mu=234234, sigma=3425) 0.0 """ - return 1 / sqrt(2 * pi * sigma ** 2) * exp(-(x - mu) ** 2 / 2 * sigma ** 2) + return 1 / sqrt(2 * pi * sigma ** 2) * exp(-((x - mu) ** 2) / 2 * sigma ** 2) if __name__ == "__main__": diff --git a/maths/perfect_square.py b/maths/perfect_square.py index 9b868c5de98a..3e7a1c07a75f 100644 --- a/maths/perfect_square.py +++ b/maths/perfect_square.py @@ -21,7 +21,7 @@ def perfect_square(num: int) -> bool: return math.sqrt(num) * math.sqrt(num) == num -if __name__ == '__main__': +if __name__ == "__main__": import doctest doctest.testmod() diff --git a/neural_network/gan.py b/neural_network/gan.py index edfff420547b..76f46314c4ba 100644 --- a/neural_network/gan.py +++ b/neural_network/gan.py @@ -7,28 +7,42 @@ random_numer = 42 np.random.seed(random_numer) + + def ReLu(x): - mask = (x>0) * 1.0 - return mask *x + mask = (x > 0) * 1.0 + return mask * x + + def d_ReLu(x): - mask = (x>0) * 1.0 + mask = (x > 0) * 1.0 return mask + def arctan(x): return np.arctan(x) + + def d_arctan(x): return 1 / (1 + x ** 2) + def log(x): - return 1 / ( 1+ np.exp(-1*x)) + return 1 / (1 + np.exp(-1 * x)) + + def d_log(x): return log(x) * (1 - log(x)) + def tanh(x): return np.tanh(x) + + def d_tanh(x): return 1 - np.tanh(x) ** 2 + def plot(samples): fig = plt.figure(figsize=(4, 4)) gs = gridspec.GridSpec(4, 4) @@ -36,104 +50,140 @@ def plot(samples): for i, sample in enumerate(samples): ax = plt.subplot(gs[i]) - plt.axis('off') + plt.axis("off") ax.set_xticklabels([]) ax.set_yticklabels([]) - ax.set_aspect('equal') - plt.imshow(sample.reshape(28, 28), cmap='Greys_r') + ax.set_aspect("equal") + plt.imshow(sample.reshape(28, 28), cmap="Greys_r") return fig - # 1. Load Data and declare hyper -print('--------- Load Data ----------') -mnist = input_data.read_data_sets('MNIST_data', one_hot=False) +print("--------- Load Data ----------") +mnist = input_data.read_data_sets("MNIST_data", one_hot=False) temp = mnist.test images, labels = temp.images, temp.labels -images, labels = shuffle(np.asarray(images),np.asarray(labels)) +images, labels = shuffle(np.asarray(images), np.asarray(labels)) num_epoch = 10 learing_rate = 0.00009 G_input = 100 -hidden_input,hidden_input2,hidden_input3 = 128,256,346 -hidden_input4,hidden_input5,hidden_input6 = 480,560,686 +hidden_input, hidden_input2, hidden_input3 = 128, 256, 346 +hidden_input4, hidden_input5, hidden_input6 = 480, 560, 686 - -print('--------- Declare Hyper Parameters ----------') +print("--------- Declare Hyper Parameters ----------") # 2. Declare Weights -D_W1 = np.random.normal(size=(784,hidden_input),scale=(1. / np.sqrt(784 / 2.))) *0.002 +D_W1 = ( + np.random.normal(size=(784, hidden_input), scale=(1.0 / np.sqrt(784 / 2.0))) * 0.002 +) # D_b1 = np.random.normal(size=(128),scale=(1. / np.sqrt(128 / 2.))) *0.002 D_b1 = np.zeros(hidden_input) -D_W2 = np.random.normal(size=(hidden_input,1),scale=(1. / np.sqrt(hidden_input / 2.))) *0.002 +D_W2 = ( + np.random.normal(size=(hidden_input, 1), scale=(1.0 / np.sqrt(hidden_input / 2.0))) + * 0.002 +) # D_b2 = np.random.normal(size=(1),scale=(1. / np.sqrt(1 / 2.))) *0.002 D_b2 = np.zeros(1) -G_W1 = np.random.normal(size=(G_input,hidden_input),scale=(1. / np.sqrt(G_input / 2.))) *0.002 +G_W1 = ( + np.random.normal(size=(G_input, hidden_input), scale=(1.0 / np.sqrt(G_input / 2.0))) + * 0.002 +) # G_b1 = np.random.normal(size=(128),scale=(1. / np.sqrt(128 / 2.))) *0.002 G_b1 = np.zeros(hidden_input) -G_W2 = np.random.normal(size=(hidden_input,hidden_input2),scale=(1. / np.sqrt(hidden_input / 2.))) *0.002 +G_W2 = ( + np.random.normal( + size=(hidden_input, hidden_input2), scale=(1.0 / np.sqrt(hidden_input / 2.0)) + ) + * 0.002 +) # G_b1 = np.random.normal(size=(128),scale=(1. / np.sqrt(128 / 2.))) *0.002 G_b2 = np.zeros(hidden_input2) -G_W3 = np.random.normal(size=(hidden_input2,hidden_input3),scale=(1. / np.sqrt(hidden_input2 / 2.))) *0.002 +G_W3 = ( + np.random.normal( + size=(hidden_input2, hidden_input3), scale=(1.0 / np.sqrt(hidden_input2 / 2.0)) + ) + * 0.002 +) # G_b1 = np.random.normal(size=(128),scale=(1. / np.sqrt(128 / 2.))) *0.002 G_b3 = np.zeros(hidden_input3) -G_W4 = np.random.normal(size=(hidden_input3,hidden_input4),scale=(1. / np.sqrt(hidden_input3 / 2.))) *0.002 +G_W4 = ( + np.random.normal( + size=(hidden_input3, hidden_input4), scale=(1.0 / np.sqrt(hidden_input3 / 2.0)) + ) + * 0.002 +) # G_b1 = np.random.normal(size=(128),scale=(1. / np.sqrt(128 / 2.))) *0.002 G_b4 = np.zeros(hidden_input4) -G_W5 = np.random.normal(size=(hidden_input4,hidden_input5),scale=(1. / np.sqrt(hidden_input4 / 2.))) *0.002 +G_W5 = ( + np.random.normal( + size=(hidden_input4, hidden_input5), scale=(1.0 / np.sqrt(hidden_input4 / 2.0)) + ) + * 0.002 +) # G_b1 = np.random.normal(size=(128),scale=(1. / np.sqrt(128 / 2.))) *0.002 G_b5 = np.zeros(hidden_input5) -G_W6 = np.random.normal(size=(hidden_input5,hidden_input6),scale=(1. / np.sqrt(hidden_input5 / 2.))) *0.002 +G_W6 = ( + np.random.normal( + size=(hidden_input5, hidden_input6), scale=(1.0 / np.sqrt(hidden_input5 / 2.0)) + ) + * 0.002 +) # G_b1 = np.random.normal(size=(128),scale=(1. / np.sqrt(128 / 2.))) *0.002 G_b6 = np.zeros(hidden_input6) -G_W7 = np.random.normal(size=(hidden_input6,784),scale=(1. / np.sqrt(hidden_input6 / 2.))) *0.002 +G_W7 = ( + np.random.normal( + size=(hidden_input6, 784), scale=(1.0 / np.sqrt(hidden_input6 / 2.0)) + ) + * 0.002 +) # G_b2 = np.random.normal(size=(784),scale=(1. / np.sqrt(784 / 2.))) *0.002 G_b7 = np.zeros(784) # 3. For Adam Optimzier -v1,m1 = 0,0 -v2,m2 = 0,0 -v3,m3 = 0,0 -v4,m4 = 0,0 +v1, m1 = 0, 0 +v2, m2 = 0, 0 +v3, m3 = 0, 0 +v4, m4 = 0, 0 -v5,m5 = 0,0 -v6,m6 = 0,0 -v7,m7 = 0,0 -v8,m8 = 0,0 -v9,m9 = 0,0 -v10,m10 = 0,0 -v11,m11 = 0,0 -v12,m12 = 0,0 +v5, m5 = 0, 0 +v6, m6 = 0, 0 +v7, m7 = 0, 0 +v8, m8 = 0, 0 +v9, m9 = 0, 0 +v10, m10 = 0, 0 +v11, m11 = 0, 0 +v12, m12 = 0, 0 -v13,m13 = 0,0 -v14,m14 = 0,0 +v13, m13 = 0, 0 +v14, m14 = 0, 0 -v15,m15 = 0,0 -v16,m16 = 0,0 +v15, m15 = 0, 0 +v16, m16 = 0, 0 -v17,m17 = 0,0 -v18,m18 = 0,0 +v17, m17 = 0, 0 +v18, m18 = 0, 0 -beta_1,beta_2,eps = 0.9,0.999,0.00000001 +beta_1, beta_2, eps = 0.9, 0.999, 0.00000001 -print('--------- Started Training ----------') +print("--------- Started Training ----------") for iter in range(num_epoch): random_int = np.random.randint(len(images) - 5) - current_image = np.expand_dims(images[random_int],axis=0) + current_image = np.expand_dims(images[random_int], axis=0) # Func: Generate The first Fake Data - Z = np.random.uniform(-1., 1., size=[1, G_input]) + Z = np.random.uniform(-1.0, 1.0, size=[1, G_input]) Gl1 = Z.dot(G_W1) + G_b1 Gl1A = arctan(Gl1) Gl2 = Gl1A.dot(G_W2) + G_b2 @@ -164,38 +214,38 @@ def plot(samples): Dl2_fA = log(Dl2_f) # Func: Cost D - D_cost = -np.log(Dl2_rA) + np.log(1.0- Dl2_fA) + D_cost = -np.log(Dl2_rA) + np.log(1.0 - Dl2_fA) # Func: Gradient - grad_f_w2_part_1 = 1/(1.0- Dl2_fA) - grad_f_w2_part_2 = d_log(Dl2_f) - grad_f_w2_part_3 = Dl1_fA - grad_f_w2 = grad_f_w2_part_3.T.dot(grad_f_w2_part_1 * grad_f_w2_part_2) + grad_f_w2_part_1 = 1 / (1.0 - Dl2_fA) + grad_f_w2_part_2 = d_log(Dl2_f) + grad_f_w2_part_3 = Dl1_fA + grad_f_w2 = grad_f_w2_part_3.T.dot(grad_f_w2_part_1 * grad_f_w2_part_2) grad_f_b2 = grad_f_w2_part_1 * grad_f_w2_part_2 - grad_f_w1_part_1 = (grad_f_w2_part_1 * grad_f_w2_part_2).dot(D_W2.T) - grad_f_w1_part_2 = d_ReLu(Dl1_f) - grad_f_w1_part_3 = current_fake_data - grad_f_w1 = grad_f_w1_part_3.T.dot(grad_f_w1_part_1 * grad_f_w1_part_2) - grad_f_b1 = grad_f_w1_part_1 * grad_f_w1_part_2 + grad_f_w1_part_1 = (grad_f_w2_part_1 * grad_f_w2_part_2).dot(D_W2.T) + grad_f_w1_part_2 = d_ReLu(Dl1_f) + grad_f_w1_part_3 = current_fake_data + grad_f_w1 = grad_f_w1_part_3.T.dot(grad_f_w1_part_1 * grad_f_w1_part_2) + grad_f_b1 = grad_f_w1_part_1 * grad_f_w1_part_2 - grad_r_w2_part_1 = - 1/Dl2_rA - grad_r_w2_part_2 = d_log(Dl2_r) - grad_r_w2_part_3 = Dl1_rA - grad_r_w2 = grad_r_w2_part_3.T.dot(grad_r_w2_part_1 * grad_r_w2_part_2) - grad_r_b2 = grad_r_w2_part_1 * grad_r_w2_part_2 + grad_r_w2_part_1 = -1 / Dl2_rA + grad_r_w2_part_2 = d_log(Dl2_r) + grad_r_w2_part_3 = Dl1_rA + grad_r_w2 = grad_r_w2_part_3.T.dot(grad_r_w2_part_1 * grad_r_w2_part_2) + grad_r_b2 = grad_r_w2_part_1 * grad_r_w2_part_2 - grad_r_w1_part_1 = (grad_r_w2_part_1 * grad_r_w2_part_2).dot(D_W2.T) - grad_r_w1_part_2 = d_ReLu(Dl1_r) - grad_r_w1_part_3 = current_image - grad_r_w1 = grad_r_w1_part_3.T.dot(grad_r_w1_part_1 * grad_r_w1_part_2) - grad_r_b1 = grad_r_w1_part_1 * grad_r_w1_part_2 + grad_r_w1_part_1 = (grad_r_w2_part_1 * grad_r_w2_part_2).dot(D_W2.T) + grad_r_w1_part_2 = d_ReLu(Dl1_r) + grad_r_w1_part_3 = current_image + grad_r_w1 = grad_r_w1_part_3.T.dot(grad_r_w1_part_1 * grad_r_w1_part_2) + grad_r_b1 = grad_r_w1_part_1 * grad_r_w1_part_2 - grad_w1 =grad_f_w1 + grad_r_w1 - grad_b1 =grad_f_b1 + grad_r_b1 + grad_w1 = grad_f_w1 + grad_r_w1 + grad_b1 = grad_f_b1 + grad_r_b1 - grad_w2 =grad_f_w2 + grad_r_w2 - grad_b2 =grad_f_b2 + grad_r_b2 + grad_w2 = grad_f_w2 + grad_r_w2 + grad_b2 = grad_f_b2 + grad_r_b2 # ---- Update Gradient ---- m1 = beta_1 * m1 + (1 - beta_1) * grad_w1 @@ -210,14 +260,22 @@ def plot(samples): m4 = beta_1 * m4 + (1 - beta_1) * grad_b2 v4 = beta_2 * v4 + (1 - beta_2) * grad_b2 ** 2 - D_W1 = D_W1 - (learing_rate / (np.sqrt(v1 /(1-beta_2) ) + eps)) * (m1/(1-beta_1)) - D_b1 = D_b1 - (learing_rate / (np.sqrt(v2 /(1-beta_2) ) + eps)) * (m2/(1-beta_1)) + D_W1 = D_W1 - (learing_rate / (np.sqrt(v1 / (1 - beta_2)) + eps)) * ( + m1 / (1 - beta_1) + ) + D_b1 = D_b1 - (learing_rate / (np.sqrt(v2 / (1 - beta_2)) + eps)) * ( + m2 / (1 - beta_1) + ) - D_W2 = D_W2 - (learing_rate / (np.sqrt(v3 /(1-beta_2) ) + eps)) * (m3/(1-beta_1)) - D_b2 = D_b2 - (learing_rate / (np.sqrt(v4 /(1-beta_2) ) + eps)) * (m4/(1-beta_1)) + D_W2 = D_W2 - (learing_rate / (np.sqrt(v3 / (1 - beta_2)) + eps)) * ( + m3 / (1 - beta_1) + ) + D_b2 = D_b2 - (learing_rate / (np.sqrt(v4 / (1 - beta_2)) + eps)) * ( + m4 / (1 - beta_1) + ) # Func: Forward Feed for G - Z = np.random.uniform(-1., 1., size=[1, G_input]) + Z = np.random.uniform(-1.0, 1.0, size=[1, G_input]) Gl1 = Z.dot(G_W1) + G_b1 Gl1A = arctan(Gl1) Gl2 = Gl1A.dot(G_W2) + G_b2 @@ -244,7 +302,9 @@ def plot(samples): G_cost = -np.log(Dl2_A) # Func: Gradient - grad_G_w7_part_1 = ((-1/Dl2_A) * d_log(Dl2).dot(D_W2.T) * (d_ReLu(Dl1))).dot(D_W1.T) + grad_G_w7_part_1 = ((-1 / Dl2_A) * d_log(Dl2).dot(D_W2.T) * (d_ReLu(Dl1))).dot( + D_W1.T + ) grad_G_w7_part_2 = d_log(Gl7) grad_G_w7_part_3 = Gl6A grad_G_w7 = grad_G_w7_part_3.T.dot(grad_G_w7_part_1 * grad_G_w7_part_1) @@ -254,31 +314,31 @@ def plot(samples): grad_G_w6_part_2 = d_ReLu(Gl6) grad_G_w6_part_3 = Gl5A grad_G_w6 = grad_G_w6_part_3.T.dot(grad_G_w6_part_1 * grad_G_w6_part_2) - grad_G_b6 = (grad_G_w6_part_1 * grad_G_w6_part_2) + grad_G_b6 = grad_G_w6_part_1 * grad_G_w6_part_2 grad_G_w5_part_1 = (grad_G_w6_part_1 * grad_G_w6_part_2).dot(G_W6.T) grad_G_w5_part_2 = d_tanh(Gl5) grad_G_w5_part_3 = Gl4A grad_G_w5 = grad_G_w5_part_3.T.dot(grad_G_w5_part_1 * grad_G_w5_part_2) - grad_G_b5 = (grad_G_w5_part_1 * grad_G_w5_part_2) + grad_G_b5 = grad_G_w5_part_1 * grad_G_w5_part_2 grad_G_w4_part_1 = (grad_G_w5_part_1 * grad_G_w5_part_2).dot(G_W5.T) grad_G_w4_part_2 = d_ReLu(Gl4) grad_G_w4_part_3 = Gl3A grad_G_w4 = grad_G_w4_part_3.T.dot(grad_G_w4_part_1 * grad_G_w4_part_2) - grad_G_b4 = (grad_G_w4_part_1 * grad_G_w4_part_2) + grad_G_b4 = grad_G_w4_part_1 * grad_G_w4_part_2 grad_G_w3_part_1 = (grad_G_w4_part_1 * grad_G_w4_part_2).dot(G_W4.T) grad_G_w3_part_2 = d_arctan(Gl3) grad_G_w3_part_3 = Gl2A grad_G_w3 = grad_G_w3_part_3.T.dot(grad_G_w3_part_1 * grad_G_w3_part_2) - grad_G_b3 = (grad_G_w3_part_1 * grad_G_w3_part_2) + grad_G_b3 = grad_G_w3_part_1 * grad_G_w3_part_2 grad_G_w2_part_1 = (grad_G_w3_part_1 * grad_G_w3_part_2).dot(G_W3.T) grad_G_w2_part_2 = d_ReLu(Gl2) grad_G_w2_part_3 = Gl1A grad_G_w2 = grad_G_w2_part_3.T.dot(grad_G_w2_part_1 * grad_G_w2_part_2) - grad_G_b2 = (grad_G_w2_part_1 * grad_G_w2_part_2) + grad_G_b2 = grad_G_w2_part_1 * grad_G_w2_part_2 grad_G_w1_part_1 = (grad_G_w2_part_1 * grad_G_w2_part_2).dot(G_W2.T) grad_G_w1_part_2 = d_arctan(Gl1) @@ -329,29 +389,57 @@ def plot(samples): m18 = beta_1 * m18 + (1 - beta_1) * grad_G_b7 v18 = beta_2 * v18 + (1 - beta_2) * grad_G_b7 ** 2 - G_W1 = G_W1 - (learing_rate / (np.sqrt(v5 /(1-beta_2) ) + eps)) * (m5/(1-beta_1)) - G_b1 = G_b1 - (learing_rate / (np.sqrt(v6 /(1-beta_2) ) + eps)) * (m6/(1-beta_1)) - - G_W2 = G_W2 - (learing_rate / (np.sqrt(v7 /(1-beta_2) ) + eps)) * (m7/(1-beta_1)) - G_b2 = G_b2 - (learing_rate / (np.sqrt(v8 /(1-beta_2) ) + eps)) * (m8/(1-beta_1)) - - G_W3 = G_W3 - (learing_rate / (np.sqrt(v9 /(1-beta_2) ) + eps)) * (m9/(1-beta_1)) - G_b3 = G_b3 - (learing_rate / (np.sqrt(v10 /(1-beta_2) ) + eps)) * (m10/(1-beta_1)) - - G_W4 = G_W4 - (learing_rate / (np.sqrt(v11 /(1-beta_2) ) + eps)) * (m11/(1-beta_1)) - G_b4 = G_b4 - (learing_rate / (np.sqrt(v12 /(1-beta_2) ) + eps)) * (m12/(1-beta_1)) - - G_W5 = G_W5 - (learing_rate / (np.sqrt(v13 /(1-beta_2) ) + eps)) * (m13/(1-beta_1)) - G_b5 = G_b5 - (learing_rate / (np.sqrt(v14 /(1-beta_2) ) + eps)) * (m14/(1-beta_1)) - - G_W6 = G_W6 - (learing_rate / (np.sqrt(v15 /(1-beta_2) ) + eps)) * (m15/(1-beta_1)) - G_b6 = G_b6 - (learing_rate / (np.sqrt(v16 /(1-beta_2) ) + eps)) * (m16/(1-beta_1)) - - G_W7 = G_W7 - (learing_rate / (np.sqrt(v17 /(1-beta_2) ) + eps)) * (m17/(1-beta_1)) - G_b7 = G_b7 - (learing_rate / (np.sqrt(v18 /(1-beta_2) ) + eps)) * (m18/(1-beta_1)) + G_W1 = G_W1 - (learing_rate / (np.sqrt(v5 / (1 - beta_2)) + eps)) * ( + m5 / (1 - beta_1) + ) + G_b1 = G_b1 - (learing_rate / (np.sqrt(v6 / (1 - beta_2)) + eps)) * ( + m6 / (1 - beta_1) + ) + + G_W2 = G_W2 - (learing_rate / (np.sqrt(v7 / (1 - beta_2)) + eps)) * ( + m7 / (1 - beta_1) + ) + G_b2 = G_b2 - (learing_rate / (np.sqrt(v8 / (1 - beta_2)) + eps)) * ( + m8 / (1 - beta_1) + ) + + G_W3 = G_W3 - (learing_rate / (np.sqrt(v9 / (1 - beta_2)) + eps)) * ( + m9 / (1 - beta_1) + ) + G_b3 = G_b3 - (learing_rate / (np.sqrt(v10 / (1 - beta_2)) + eps)) * ( + m10 / (1 - beta_1) + ) + + G_W4 = G_W4 - (learing_rate / (np.sqrt(v11 / (1 - beta_2)) + eps)) * ( + m11 / (1 - beta_1) + ) + G_b4 = G_b4 - (learing_rate / (np.sqrt(v12 / (1 - beta_2)) + eps)) * ( + m12 / (1 - beta_1) + ) + + G_W5 = G_W5 - (learing_rate / (np.sqrt(v13 / (1 - beta_2)) + eps)) * ( + m13 / (1 - beta_1) + ) + G_b5 = G_b5 - (learing_rate / (np.sqrt(v14 / (1 - beta_2)) + eps)) * ( + m14 / (1 - beta_1) + ) + + G_W6 = G_W6 - (learing_rate / (np.sqrt(v15 / (1 - beta_2)) + eps)) * ( + m15 / (1 - beta_1) + ) + G_b6 = G_b6 - (learing_rate / (np.sqrt(v16 / (1 - beta_2)) + eps)) * ( + m16 / (1 - beta_1) + ) + + G_W7 = G_W7 - (learing_rate / (np.sqrt(v17 / (1 - beta_2)) + eps)) * ( + m17 / (1 - beta_1) + ) + G_b7 = G_b7 - (learing_rate / (np.sqrt(v18 / (1 - beta_2)) + eps)) * ( + m18 / (1 - beta_1) + ) # --- Print Error ---- - #print("Current Iter: ",iter, " Current D cost:",D_cost, " Current G cost: ", G_cost,end='\r') + # print("Current Iter: ",iter, " Current D cost:",D_cost, " Current G cost: ", G_cost,end='\r') if iter == 0: learing_rate = learing_rate * 0.01 @@ -359,12 +447,20 @@ def plot(samples): learing_rate = learing_rate * 0.01 # ---- Print to Out put ---- - if iter%10 == 0: - - print("Current Iter: ",iter, " Current D cost:",D_cost, " Current G cost: ", G_cost,end='\r') - print('--------- Show Example Result See Tab Above ----------') - print('--------- Wait for the image to load ---------') - Z = np.random.uniform(-1., 1., size=[16, G_input]) + if iter % 10 == 0: + + print( + "Current Iter: ", + iter, + " Current D cost:", + D_cost, + " Current G cost: ", + G_cost, + end="\r", + ) + print("--------- Show Example Result See Tab Above ----------") + print("--------- Wait for the image to load ---------") + Z = np.random.uniform(-1.0, 1.0, size=[16, G_input]) Gl1 = Z.dot(G_W1) + G_b1 Gl1A = arctan(Gl1) @@ -384,8 +480,19 @@ def plot(samples): current_fake_data = log(Gl7) fig = plot(current_fake_data) - fig.savefig('Click_Me_{}.png'.format(str(iter).zfill(3)+"_Ginput_"+str(G_input)+ \ - "_hiddenone"+str(hidden_input) + "_hiddentwo"+str(hidden_input2) + "_LR_" + str(learing_rate) - ), bbox_inches='tight') -#for complete explanation visit https://towardsdatascience.com/only-numpy-implementing-gan-general-adversarial-networks-and-adam-optimizer-using-numpy-with-2a7e4e032021 + fig.savefig( + "Click_Me_{}.png".format( + str(iter).zfill(3) + + "_Ginput_" + + str(G_input) + + "_hiddenone" + + str(hidden_input) + + "_hiddentwo" + + str(hidden_input2) + + "_LR_" + + str(learing_rate) + ), + bbox_inches="tight", + ) +# for complete explanation visit https://towardsdatascience.com/only-numpy-implementing-gan-general-adversarial-networks-and-adam-optimizer-using-numpy-with-2a7e4e032021 # -- end code -- diff --git a/neural_network/input_data.py b/neural_network/input_data.py index 983063f0b72d..5e6c433aa97d 100644 --- a/neural_network/input_data.py +++ b/neural_network/input_data.py @@ -34,20 +34,20 @@ from tensorflow.python.platform import gfile from tensorflow.python.util.deprecation import deprecated -_Datasets = collections.namedtuple('_Datasets', ['train', 'validation', 'test']) +_Datasets = collections.namedtuple("_Datasets", ["train", "validation", "test"]) # CVDF mirror of http://yann.lecun.com/exdb/mnist/ -DEFAULT_SOURCE_URL = 'https://storage.googleapis.com/cvdf-datasets/mnist/' +DEFAULT_SOURCE_URL = "https://storage.googleapis.com/cvdf-datasets/mnist/" def _read32(bytestream): - dt = numpy.dtype(numpy.uint32).newbyteorder('>') - return numpy.frombuffer(bytestream.read(4), dtype=dt)[0] + dt = numpy.dtype(numpy.uint32).newbyteorder(">") + return numpy.frombuffer(bytestream.read(4), dtype=dt)[0] -@deprecated(None, 'Please use tf.data to implement this functionality.') +@deprecated(None, "Please use tf.data to implement this functionality.") def _extract_images(f): - """Extract the images into a 4D uint8 numpy array [index, y, x, depth]. + """Extract the images into a 4D uint8 numpy array [index, y, x, depth]. Args: f: A file object that can be passed into a gzip reader. @@ -59,34 +59,35 @@ def _extract_images(f): ValueError: If the bytestream does not start with 2051. """ - print('Extracting', f.name) - with gzip.GzipFile(fileobj=f) as bytestream: - magic = _read32(bytestream) - if magic != 2051: - raise ValueError('Invalid magic number %d in MNIST image file: %s' % - (magic, f.name)) - num_images = _read32(bytestream) - rows = _read32(bytestream) - cols = _read32(bytestream) - buf = bytestream.read(rows * cols * num_images) - data = numpy.frombuffer(buf, dtype=numpy.uint8) - data = data.reshape(num_images, rows, cols, 1) - return data - - -@deprecated(None, 'Please use tf.one_hot on tensors.') + print("Extracting", f.name) + with gzip.GzipFile(fileobj=f) as bytestream: + magic = _read32(bytestream) + if magic != 2051: + raise ValueError( + "Invalid magic number %d in MNIST image file: %s" % (magic, f.name) + ) + num_images = _read32(bytestream) + rows = _read32(bytestream) + cols = _read32(bytestream) + buf = bytestream.read(rows * cols * num_images) + data = numpy.frombuffer(buf, dtype=numpy.uint8) + data = data.reshape(num_images, rows, cols, 1) + return data + + +@deprecated(None, "Please use tf.one_hot on tensors.") def _dense_to_one_hot(labels_dense, num_classes): - """Convert class labels from scalars to one-hot vectors.""" - num_labels = labels_dense.shape[0] - index_offset = numpy.arange(num_labels) * num_classes - labels_one_hot = numpy.zeros((num_labels, num_classes)) - labels_one_hot.flat[index_offset + labels_dense.ravel()] = 1 - return labels_one_hot + """Convert class labels from scalars to one-hot vectors.""" + num_labels = labels_dense.shape[0] + index_offset = numpy.arange(num_labels) * num_classes + labels_one_hot = numpy.zeros((num_labels, num_classes)) + labels_one_hot.flat[index_offset + labels_dense.ravel()] = 1 + return labels_one_hot -@deprecated(None, 'Please use tf.data to implement this functionality.') +@deprecated(None, "Please use tf.data to implement this functionality.") def _extract_labels(f, one_hot=False, num_classes=10): - """Extract the labels into a 1D uint8 numpy array [index]. + """Extract the labels into a 1D uint8 numpy array [index]. Args: f: A file object that can be passed into a gzip reader. @@ -99,37 +100,43 @@ def _extract_labels(f, one_hot=False, num_classes=10): Raises: ValueError: If the bystream doesn't start with 2049. """ - print('Extracting', f.name) - with gzip.GzipFile(fileobj=f) as bytestream: - magic = _read32(bytestream) - if magic != 2049: - raise ValueError('Invalid magic number %d in MNIST label file: %s' % - (magic, f.name)) - num_items = _read32(bytestream) - buf = bytestream.read(num_items) - labels = numpy.frombuffer(buf, dtype=numpy.uint8) - if one_hot: - return _dense_to_one_hot(labels, num_classes) - return labels + print("Extracting", f.name) + with gzip.GzipFile(fileobj=f) as bytestream: + magic = _read32(bytestream) + if magic != 2049: + raise ValueError( + "Invalid magic number %d in MNIST label file: %s" % (magic, f.name) + ) + num_items = _read32(bytestream) + buf = bytestream.read(num_items) + labels = numpy.frombuffer(buf, dtype=numpy.uint8) + if one_hot: + return _dense_to_one_hot(labels, num_classes) + return labels class _DataSet(object): - """Container class for a _DataSet (deprecated). + """Container class for a _DataSet (deprecated). THIS CLASS IS DEPRECATED. """ - @deprecated(None, 'Please use alternatives such as official/mnist/_DataSet.py' - ' from tensorflow/models.') - def __init__(self, - images, - labels, - fake_data=False, - one_hot=False, - dtype=dtypes.float32, - reshape=True, - seed=None): - """Construct a _DataSet. + @deprecated( + None, + "Please use alternatives such as official/mnist/_DataSet.py" + " from tensorflow/models.", + ) + def __init__( + self, + images, + labels, + fake_data=False, + one_hot=False, + dtype=dtypes.float32, + reshape=True, + seed=None, + ): + """Construct a _DataSet. one_hot arg is used only if fake_data is true. `dtype` can be either `uint8` to leave the input as `[0, 255]`, or `float32` to rescale into @@ -146,101 +153,105 @@ def __init__(self, reshape: Bool. If True returned images are returned flattened to vectors. seed: The random seed to use. """ - seed1, seed2 = random_seed.get_seed(seed) - # If op level seed is not set, use whatever graph level seed is returned - numpy.random.seed(seed1 if seed is None else seed2) - dtype = dtypes.as_dtype(dtype).base_dtype - if dtype not in (dtypes.uint8, dtypes.float32): - raise TypeError('Invalid image dtype %r, expected uint8 or float32' % - dtype) - if fake_data: - self._num_examples = 10000 - self.one_hot = one_hot - else: - assert images.shape[0] == labels.shape[0], ( - 'images.shape: %s labels.shape: %s' % (images.shape, labels.shape)) - self._num_examples = images.shape[0] - - # Convert shape from [num examples, rows, columns, depth] - # to [num examples, rows*columns] (assuming depth == 1) - if reshape: - assert images.shape[3] == 1 - images = images.reshape(images.shape[0], - images.shape[1] * images.shape[2]) - if dtype == dtypes.float32: - # Convert from [0, 255] -> [0.0, 1.0]. - images = images.astype(numpy.float32) - images = numpy.multiply(images, 1.0 / 255.0) - self._images = images - self._labels = labels - self._epochs_completed = 0 - self._index_in_epoch = 0 - - @property - def images(self): - return self._images - - @property - def labels(self): - return self._labels - - @property - def num_examples(self): - return self._num_examples - - @property - def epochs_completed(self): - return self._epochs_completed - - def next_batch(self, batch_size, fake_data=False, shuffle=True): - """Return the next `batch_size` examples from this data set.""" - if fake_data: - fake_image = [1] * 784 - if self.one_hot: - fake_label = [1] + [0] * 9 - else: - fake_label = 0 - return [fake_image for _ in xrange(batch_size) - ], [fake_label for _ in xrange(batch_size)] - start = self._index_in_epoch - # Shuffle for the first epoch - if self._epochs_completed == 0 and start == 0 and shuffle: - perm0 = numpy.arange(self._num_examples) - numpy.random.shuffle(perm0) - self._images = self.images[perm0] - self._labels = self.labels[perm0] - # Go to the next epoch - if start + batch_size > self._num_examples: - # Finished epoch - self._epochs_completed += 1 - # Get the rest examples in this epoch - rest_num_examples = self._num_examples - start - images_rest_part = self._images[start:self._num_examples] - labels_rest_part = self._labels[start:self._num_examples] - # Shuffle the data - if shuffle: - perm = numpy.arange(self._num_examples) - numpy.random.shuffle(perm) - self._images = self.images[perm] - self._labels = self.labels[perm] - # Start next epoch - start = 0 - self._index_in_epoch = batch_size - rest_num_examples - end = self._index_in_epoch - images_new_part = self._images[start:end] - labels_new_part = self._labels[start:end] - return numpy.concatenate((images_rest_part, images_new_part), - axis=0), numpy.concatenate( - (labels_rest_part, labels_new_part), axis=0) - else: - self._index_in_epoch += batch_size - end = self._index_in_epoch - return self._images[start:end], self._labels[start:end] - - -@deprecated(None, 'Please write your own downloading logic.') + seed1, seed2 = random_seed.get_seed(seed) + # If op level seed is not set, use whatever graph level seed is returned + numpy.random.seed(seed1 if seed is None else seed2) + dtype = dtypes.as_dtype(dtype).base_dtype + if dtype not in (dtypes.uint8, dtypes.float32): + raise TypeError("Invalid image dtype %r, expected uint8 or float32" % dtype) + if fake_data: + self._num_examples = 10000 + self.one_hot = one_hot + else: + assert ( + images.shape[0] == labels.shape[0] + ), "images.shape: %s labels.shape: %s" % (images.shape, labels.shape) + self._num_examples = images.shape[0] + + # Convert shape from [num examples, rows, columns, depth] + # to [num examples, rows*columns] (assuming depth == 1) + if reshape: + assert images.shape[3] == 1 + images = images.reshape( + images.shape[0], images.shape[1] * images.shape[2] + ) + if dtype == dtypes.float32: + # Convert from [0, 255] -> [0.0, 1.0]. + images = images.astype(numpy.float32) + images = numpy.multiply(images, 1.0 / 255.0) + self._images = images + self._labels = labels + self._epochs_completed = 0 + self._index_in_epoch = 0 + + @property + def images(self): + return self._images + + @property + def labels(self): + return self._labels + + @property + def num_examples(self): + return self._num_examples + + @property + def epochs_completed(self): + return self._epochs_completed + + def next_batch(self, batch_size, fake_data=False, shuffle=True): + """Return the next `batch_size` examples from this data set.""" + if fake_data: + fake_image = [1] * 784 + if self.one_hot: + fake_label = [1] + [0] * 9 + else: + fake_label = 0 + return ( + [fake_image for _ in xrange(batch_size)], + [fake_label for _ in xrange(batch_size)], + ) + start = self._index_in_epoch + # Shuffle for the first epoch + if self._epochs_completed == 0 and start == 0 and shuffle: + perm0 = numpy.arange(self._num_examples) + numpy.random.shuffle(perm0) + self._images = self.images[perm0] + self._labels = self.labels[perm0] + # Go to the next epoch + if start + batch_size > self._num_examples: + # Finished epoch + self._epochs_completed += 1 + # Get the rest examples in this epoch + rest_num_examples = self._num_examples - start + images_rest_part = self._images[start : self._num_examples] + labels_rest_part = self._labels[start : self._num_examples] + # Shuffle the data + if shuffle: + perm = numpy.arange(self._num_examples) + numpy.random.shuffle(perm) + self._images = self.images[perm] + self._labels = self.labels[perm] + # Start next epoch + start = 0 + self._index_in_epoch = batch_size - rest_num_examples + end = self._index_in_epoch + images_new_part = self._images[start:end] + labels_new_part = self._labels[start:end] + return ( + numpy.concatenate((images_rest_part, images_new_part), axis=0), + numpy.concatenate((labels_rest_part, labels_new_part), axis=0), + ) + else: + self._index_in_epoch += batch_size + end = self._index_in_epoch + return self._images[start:end], self._labels[start:end] + + +@deprecated(None, "Please write your own downloading logic.") def _maybe_download(filename, work_directory, source_url): - """Download the data from source url, unless it's already here. + """Download the data from source url, unless it's already here. Args: filename: string, name of the file in the directory. @@ -250,83 +261,90 @@ def _maybe_download(filename, work_directory, source_url): Returns: Path to resulting file. """ - if not gfile.Exists(work_directory): - gfile.MakeDirs(work_directory) - filepath = os.path.join(work_directory, filename) - if not gfile.Exists(filepath): - urllib.request.urlretrieve(source_url, filepath) - with gfile.GFile(filepath) as f: - size = f.size() - print('Successfully downloaded', filename, size, 'bytes.') - return filepath - - -@deprecated(None, 'Please use alternatives such as:' - ' tensorflow_datasets.load(\'mnist\')') -def read_data_sets(train_dir, - fake_data=False, - one_hot=False, - dtype=dtypes.float32, - reshape=True, - validation_size=5000, - seed=None, - source_url=DEFAULT_SOURCE_URL): - if fake_data: - - def fake(): - return _DataSet([], [], - fake_data=True, - one_hot=one_hot, - dtype=dtype, - seed=seed) - - train = fake() - validation = fake() - test = fake() - return _Datasets(train=train, validation=validation, test=test) - - if not source_url: # empty string check - source_url = DEFAULT_SOURCE_URL - - train_images_file = 'train-images-idx3-ubyte.gz' - train_labels_file = 'train-labels-idx1-ubyte.gz' - test_images_file = 't10k-images-idx3-ubyte.gz' - test_labels_file = 't10k-labels-idx1-ubyte.gz' - - local_file = _maybe_download(train_images_file, train_dir, - source_url + train_images_file) - with gfile.Open(local_file, 'rb') as f: - train_images = _extract_images(f) - - local_file = _maybe_download(train_labels_file, train_dir, - source_url + train_labels_file) - with gfile.Open(local_file, 'rb') as f: - train_labels = _extract_labels(f, one_hot=one_hot) - - local_file = _maybe_download(test_images_file, train_dir, - source_url + test_images_file) - with gfile.Open(local_file, 'rb') as f: - test_images = _extract_images(f) - - local_file = _maybe_download(test_labels_file, train_dir, - source_url + test_labels_file) - with gfile.Open(local_file, 'rb') as f: - test_labels = _extract_labels(f, one_hot=one_hot) - - if not 0 <= validation_size <= len(train_images): - raise ValueError( - 'Validation size should be between 0 and {}. Received: {}.'.format( - len(train_images), validation_size)) - - validation_images = train_images[:validation_size] - validation_labels = train_labels[:validation_size] - train_images = train_images[validation_size:] - train_labels = train_labels[validation_size:] - - options = dict(dtype=dtype, reshape=reshape, seed=seed) + if not gfile.Exists(work_directory): + gfile.MakeDirs(work_directory) + filepath = os.path.join(work_directory, filename) + if not gfile.Exists(filepath): + urllib.request.urlretrieve(source_url, filepath) + with gfile.GFile(filepath) as f: + size = f.size() + print("Successfully downloaded", filename, size, "bytes.") + return filepath + + +@deprecated( + None, "Please use alternatives such as:" " tensorflow_datasets.load('mnist')" +) +def read_data_sets( + train_dir, + fake_data=False, + one_hot=False, + dtype=dtypes.float32, + reshape=True, + validation_size=5000, + seed=None, + source_url=DEFAULT_SOURCE_URL, +): + if fake_data: - train = _DataSet(train_images, train_labels, **options) - validation = _DataSet(validation_images, validation_labels, **options) - test = _DataSet(test_images, test_labels, **options) + def fake(): + return _DataSet( + [], [], fake_data=True, one_hot=one_hot, dtype=dtype, seed=seed + ) + + train = fake() + validation = fake() + test = fake() + return _Datasets(train=train, validation=validation, test=test) + + if not source_url: # empty string check + source_url = DEFAULT_SOURCE_URL + + train_images_file = "train-images-idx3-ubyte.gz" + train_labels_file = "train-labels-idx1-ubyte.gz" + test_images_file = "t10k-images-idx3-ubyte.gz" + test_labels_file = "t10k-labels-idx1-ubyte.gz" + + local_file = _maybe_download( + train_images_file, train_dir, source_url + train_images_file + ) + with gfile.Open(local_file, "rb") as f: + train_images = _extract_images(f) + + local_file = _maybe_download( + train_labels_file, train_dir, source_url + train_labels_file + ) + with gfile.Open(local_file, "rb") as f: + train_labels = _extract_labels(f, one_hot=one_hot) + + local_file = _maybe_download( + test_images_file, train_dir, source_url + test_images_file + ) + with gfile.Open(local_file, "rb") as f: + test_images = _extract_images(f) + + local_file = _maybe_download( + test_labels_file, train_dir, source_url + test_labels_file + ) + with gfile.Open(local_file, "rb") as f: + test_labels = _extract_labels(f, one_hot=one_hot) + + if not 0 <= validation_size <= len(train_images): + raise ValueError( + "Validation size should be between 0 and {}. Received: {}.".format( + len(train_images), validation_size + ) + ) + + validation_images = train_images[:validation_size] + validation_labels = train_labels[:validation_size] + train_images = train_images[validation_size:] + train_labels = train_labels[validation_size:] + + options = dict(dtype=dtype, reshape=reshape, seed=seed) + + train = _DataSet(train_images, train_labels, **options) + validation = _DataSet(validation_images, validation_labels, **options) + test = _DataSet(test_images, test_labels, **options) - return _Datasets(train=train, validation=validation, test=test) + return _Datasets(train=train, validation=validation, test=test) diff --git a/other/least_recently_used.py b/other/least_recently_used.py index 2932e9c185e8..e1b5ab5bd380 100644 --- a/other/least_recently_used.py +++ b/other/least_recently_used.py @@ -2,12 +2,13 @@ import sys from collections import deque + class LRUCache: """ Page Replacement Algorithm, Least Recently Used (LRU) Caching.""" - dq_store = object() # Cache store of keys - key_reference_map = object() # References of the keys in cache - _MAX_CAPACITY: int = 10 # Maximum capacity of cache + dq_store = object() # Cache store of keys + key_reference_map = object() # References of the keys in cache + _MAX_CAPACITY: int = 10 # Maximum capacity of cache @abstractmethod def __init__(self, n: int): @@ -19,7 +20,7 @@ def __init__(self, n: int): if not n: LRUCache._MAX_CAPACITY = sys.maxsize elif n < 0: - raise ValueError('n should be an integer greater than 0.') + raise ValueError("n should be an integer greater than 0.") else: LRUCache._MAX_CAPACITY = n @@ -51,6 +52,7 @@ def display(self): for k in self.dq_store: print(k) + if __name__ == "__main__": lru_cache = LRUCache(4) lru_cache.refer(1) diff --git a/project_euler/problem_20/sol4.py b/project_euler/problem_20/sol4.py index 50ebca5a0bf7..4c597220f09b 100644 --- a/project_euler/problem_20/sol4.py +++ b/project_euler/problem_20/sol4.py @@ -27,7 +27,7 @@ def solution(n): """ fact = 1 result = 0 - for i in range(1,n + 1): + for i in range(1, n + 1): fact *= i for j in str(fact): diff --git a/project_euler/problem_99/sol1.py b/project_euler/problem_99/sol1.py index 6729927dfa63..713bf65caab7 100644 --- a/project_euler/problem_99/sol1.py +++ b/project_euler/problem_99/sol1.py @@ -14,15 +14,13 @@ from math import log10 -def find_largest(data_file: str="base_exp.txt") -> int: +def find_largest(data_file: str = "base_exp.txt") -> int: """ >>> find_largest() 709 """ largest = [0, 0] - for i, line in enumerate( - open(os.path.join(os.path.dirname(__file__), data_file)) - ): + for i, line in enumerate(open(os.path.join(os.path.dirname(__file__), data_file))): a, x = list(map(int, line.split(","))) if x * log10(a) > largest[0]: largest = [x * log10(a), i + 1] diff --git a/web_programming/get_imdbtop.py b/web_programming/get_imdbtop.py index 95fbeba7a772..522e423b4eab 100644 --- a/web_programming/get_imdbtop.py +++ b/web_programming/get_imdbtop.py @@ -3,8 +3,10 @@ def imdb_top(imdb_top_n): - base_url = (f"https://www.imdb.com/search/title?title_type=" - f"feature&sort=num_votes,desc&count={imdb_top_n}") + base_url = ( + f"https://www.imdb.com/search/title?title_type=" + f"feature&sort=num_votes,desc&count={imdb_top_n}" + ) source = BeautifulSoup(requests.get(base_url).content, "html.parser") for m in source.findAll("div", class_="lister-item mode-advanced"): print("\n" + m.h3.a.text) # movie's name From ea9bf0a90cb0a04ca5d51e1906e1d1afe52a4732 Mon Sep 17 00:00:00 2001 From: "Marvin M. Michum" <11682032+mrvnmchm@users.noreply.github.com> Date: Thu, 14 Nov 2019 14:27:31 -0500 Subject: [PATCH 0358/1071] directory_writer (#1) (#1549) * directory_writer * fixup: Format Python code with psf/black * fixup: DIRECTORY.md --- .github/workflows/directory_writer.yml | 25 +++++++++++++++++++++++++ DIRECTORY.md | 25 +++++++++++++++++++++++++ 2 files changed, 50 insertions(+) create mode 100644 .github/workflows/directory_writer.yml diff --git a/.github/workflows/directory_writer.yml b/.github/workflows/directory_writer.yml new file mode 100644 index 000000000000..dcbb9c304e15 --- /dev/null +++ b/.github/workflows/directory_writer.yml @@ -0,0 +1,25 @@ +# The objective of this GitHub Action is to add a new DIRECTORY.md file to a pull request if needed. +name: directory_writer +on: [pull_request] +jobs: + build: + runs-on: ubuntu-latest + strategy: + max-parallel: 1 + matrix: + python-version: [3.7] + steps: + + - uses: actions/checkout@v1 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v1 + with: + python-version: ${{ matrix.python-version }} + - name: Update DIRECTORY.md + run: | + scripts/build_directory_md.py 2>&1 | tee DIRECTORY.md + git config --global user.name 'directory_writer' + git config --global user.email 'mrvnmchm@users.noreply.github.com' + git remote set-url origin https://x-access-token:${{ secrets.gh_token }}@github.com/$GITHUB_REPOSITORY + git checkout $GITHUB_HEAD_REF + if git diff-files --quiet; then echo 0; else git commit -am "fixup: DIRECTORY.md" && git push; fi diff --git a/DIRECTORY.md b/DIRECTORY.md index e2d74d39828f..04b785b2b833 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -8,6 +8,7 @@ * [Newton Forward Interpolation](https://github.com/TheAlgorithms/Python/blob/master/arithmetic_analysis/newton_forward_interpolation.py) * [Newton Method](https://github.com/TheAlgorithms/Python/blob/master/arithmetic_analysis/newton_method.py) * [Newton Raphson Method](https://github.com/TheAlgorithms/Python/blob/master/arithmetic_analysis/newton_raphson_method.py) + * [Secant Method](https://github.com/TheAlgorithms/Python/blob/master/arithmetic_analysis/secant_method.py) ## Backtracking * [All Combinations](https://github.com/TheAlgorithms/Python/blob/master/backtracking/all_combinations.py) @@ -36,8 +37,11 @@ * [Brute Force Caesar Cipher](https://github.com/TheAlgorithms/Python/blob/master/ciphers/brute_force_caesar_cipher.py) * [Caesar Cipher](https://github.com/TheAlgorithms/Python/blob/master/ciphers/caesar_cipher.py) * [Cryptomath Module](https://github.com/TheAlgorithms/Python/blob/master/ciphers/cryptomath_module.py) + * [Deterministic Miller Rabin](https://github.com/TheAlgorithms/Python/blob/master/ciphers/deterministic_miller_rabin.py) + * [Diffie](https://github.com/TheAlgorithms/Python/blob/master/ciphers/diffie.py) * [Elgamal Key Generator](https://github.com/TheAlgorithms/Python/blob/master/ciphers/elgamal_key_generator.py) * [Hill Cipher](https://github.com/TheAlgorithms/Python/blob/master/ciphers/hill_cipher.py) + * [Mixed Keyword Cypher](https://github.com/TheAlgorithms/Python/blob/master/ciphers/mixed_keyword_cypher.py) * [Morse Code Implementation](https://github.com/TheAlgorithms/Python/blob/master/ciphers/morse_code_implementation.py) * [Onepad Cipher](https://github.com/TheAlgorithms/Python/blob/master/ciphers/onepad_cipher.py) * [Playfair Cipher](https://github.com/TheAlgorithms/Python/blob/master/ciphers/playfair_cipher.py) @@ -71,6 +75,7 @@ * [Fenwick Tree](https://github.com/TheAlgorithms/Python/blob/master/data_structures/binary_tree/fenwick_tree.py) * [Lazy Segment Tree](https://github.com/TheAlgorithms/Python/blob/master/data_structures/binary_tree/lazy_segment_tree.py) * [Lca](https://github.com/TheAlgorithms/Python/blob/master/data_structures/binary_tree/lca.py) + * [Non Recursive Segment Tree](https://github.com/TheAlgorithms/Python/blob/master/data_structures/binary_tree/non_recursive_segment_tree.py) * [Red Black Tree](https://github.com/TheAlgorithms/Python/blob/master/data_structures/binary_tree/red_black_tree.py) * [Segment Tree](https://github.com/TheAlgorithms/Python/blob/master/data_structures/binary_tree/segment_tree.py) * [Treap](https://github.com/TheAlgorithms/Python/blob/master/data_structures/binary_tree/treap.py) @@ -149,6 +154,7 @@ * [Longest Sub Array](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/longest_sub_array.py) * [Matrix Chain Order](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/matrix_chain_order.py) * [Max Sub Array](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/max_sub_array.py) + * [Max Sum Contigous Subsequence](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/max_sum_contigous_subsequence.py) * [Minimum Partition](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/minimum_partition.py) * [Rod Cutting](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/rod_cutting.py) * [Subset Generation](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/subset_generation.py) @@ -228,13 +234,16 @@ * [Abs Min](https://github.com/TheAlgorithms/Python/blob/master/maths/abs_min.py) * [Average Mean](https://github.com/TheAlgorithms/Python/blob/master/maths/average_mean.py) * [Average Median](https://github.com/TheAlgorithms/Python/blob/master/maths/average_median.py) + * [Average Mode](https://github.com/TheAlgorithms/Python/blob/master/maths/average_mode.py) * [Basic Maths](https://github.com/TheAlgorithms/Python/blob/master/maths/basic_maths.py) * [Binary Exponentiation](https://github.com/TheAlgorithms/Python/blob/master/maths/binary_exponentiation.py) + * [Binomial Coefficient](https://github.com/TheAlgorithms/Python/blob/master/maths/binomial_coefficient.py) * [Collatz Sequence](https://github.com/TheAlgorithms/Python/blob/master/maths/collatz_sequence.py) * [Explicit Euler](https://github.com/TheAlgorithms/Python/blob/master/maths/explicit_euler.py) * [Extended Euclidean Algorithm](https://github.com/TheAlgorithms/Python/blob/master/maths/extended_euclidean_algorithm.py) * [Factorial Python](https://github.com/TheAlgorithms/Python/blob/master/maths/factorial_python.py) * [Factorial Recursive](https://github.com/TheAlgorithms/Python/blob/master/maths/factorial_recursive.py) + * [Factors](https://github.com/TheAlgorithms/Python/blob/master/maths/factors.py) * [Fermat Little Theorem](https://github.com/TheAlgorithms/Python/blob/master/maths/fermat_little_theorem.py) * [Fibonacci](https://github.com/TheAlgorithms/Python/blob/master/maths/fibonacci.py) * [Fibonacci Sequence Recursion](https://github.com/TheAlgorithms/Python/blob/master/maths/fibonacci_sequence_recursion.py) @@ -257,9 +266,11 @@ * [Mobius Function](https://github.com/TheAlgorithms/Python/blob/master/maths/mobius_function.py) * [Modular Exponential](https://github.com/TheAlgorithms/Python/blob/master/maths/modular_exponential.py) * [Newton Raphson](https://github.com/TheAlgorithms/Python/blob/master/maths/newton_raphson.py) + * [Perfect Square](https://github.com/TheAlgorithms/Python/blob/master/maths/perfect_square.py) * [Polynomial Evaluation](https://github.com/TheAlgorithms/Python/blob/master/maths/polynomial_evaluation.py) * [Prime Check](https://github.com/TheAlgorithms/Python/blob/master/maths/prime_check.py) * [Prime Factors](https://github.com/TheAlgorithms/Python/blob/master/maths/prime_factors.py) + * [Prime Numbers](https://github.com/TheAlgorithms/Python/blob/master/maths/prime_numbers.py) * [Prime Sieve Eratosthenes](https://github.com/TheAlgorithms/Python/blob/master/maths/prime_sieve_eratosthenes.py) * [Qr Decomposition](https://github.com/TheAlgorithms/Python/blob/master/maths/qr_decomposition.py) * [Quadratic Equations Complex Numbers](https://github.com/TheAlgorithms/Python/blob/master/maths/quadratic_equations_complex_numbers.py) @@ -293,6 +304,8 @@ ## Neural Network * [Back Propagation Neural Network](https://github.com/TheAlgorithms/Python/blob/master/neural_network/back_propagation_neural_network.py) * [Convolution Neural Network](https://github.com/TheAlgorithms/Python/blob/master/neural_network/convolution_neural_network.py) + * [Gan](https://github.com/TheAlgorithms/Python/blob/master/neural_network/gan.py) + * [Input Data](https://github.com/TheAlgorithms/Python/blob/master/neural_network/input_data.py) * [Perceptron](https://github.com/TheAlgorithms/Python/blob/master/neural_network/perceptron.py) ## Other @@ -309,12 +322,14 @@ * [Game Of Life](https://github.com/TheAlgorithms/Python/blob/master/other/game_of_life.py) * [Greedy](https://github.com/TheAlgorithms/Python/blob/master/other/greedy.py) * [Largest Subarray Sum](https://github.com/TheAlgorithms/Python/blob/master/other/largest_subarray_sum.py) + * [Least Recently Used](https://github.com/TheAlgorithms/Python/blob/master/other/least_recently_used.py) * [Linear Congruential Generator](https://github.com/TheAlgorithms/Python/blob/master/other/linear_congruential_generator.py) * [Magicdiamondpattern](https://github.com/TheAlgorithms/Python/blob/master/other/magicdiamondpattern.py) * [Nested Brackets](https://github.com/TheAlgorithms/Python/blob/master/other/nested_brackets.py) * [Palindrome](https://github.com/TheAlgorithms/Python/blob/master/other/palindrome.py) * [Password Generator](https://github.com/TheAlgorithms/Python/blob/master/other/password_generator.py) * [Primelib](https://github.com/TheAlgorithms/Python/blob/master/other/primelib.py) + * [Sdes](https://github.com/TheAlgorithms/Python/blob/master/other/sdes.py) * [Sierpinski Triangle](https://github.com/TheAlgorithms/Python/blob/master/other/sierpinski_triangle.py) * [Tower Of Hanoi](https://github.com/TheAlgorithms/Python/blob/master/other/tower_of_hanoi.py) * [Two Sum](https://github.com/TheAlgorithms/Python/blob/master/other/two_sum.py) @@ -390,6 +405,7 @@ * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_20/sol1.py) * [Sol2](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_20/sol2.py) * [Sol3](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_20/sol3.py) + * [Sol4](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_20/sol4.py) * Problem 21 * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_21/sol1.py) * Problem 22 @@ -404,6 +420,9 @@ * Problem 25 * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_25/sol1.py) * [Sol2](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_25/sol2.py) + * [Sol3](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_25/sol3.py) + * Problem 27 + * [Problem 27 Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_27/problem_27_sol1.py) * Problem 28 * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_28/sol1.py) * Problem 29 @@ -412,6 +431,8 @@ * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_31/sol1.py) * Problem 32 * [Sol32](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_32/sol32.py) + * Problem 33 + * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_33/sol1.py) * Problem 36 * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_36/sol1.py) * Problem 40 @@ -432,6 +453,8 @@ * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_67/sol1.py) * Problem 76 * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_76/sol1.py) + * Problem 99 + * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_99/sol1.py) ## Searches * [Binary Search](https://github.com/TheAlgorithms/Python/blob/master/searches/binary_search.py) @@ -471,6 +494,7 @@ * [Radix Sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/radix_sort.py) * [Random Normal Distribution Quicksort](https://github.com/TheAlgorithms/Python/blob/master/sorts/random_normal_distribution_quicksort.py) * [Random Pivot Quick Sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/random_pivot_quick_sort.py) + * [Recursive-Quick-Sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/recursive-quick-sort.py) * [Selection Sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/selection_sort.py) * [Shell Sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/shell_sort.py) * [Stooge Sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/stooge_sort.py) @@ -494,3 +518,4 @@ ## Web Programming * [Crawl Google Results](https://github.com/TheAlgorithms/Python/blob/master/web_programming/crawl_google_results.py) + * [Get Imdbtop](https://github.com/TheAlgorithms/Python/blob/master/web_programming/get_imdbtop.py) From e3aa0f65e8a5a453e967ac2dc343c7cb96217548 Mon Sep 17 00:00:00 2001 From: Zizhou Zhang Date: Fri, 15 Nov 2019 06:29:54 +1100 Subject: [PATCH 0359/1071] fix implementation errors. (#1568) I revised my implementation and found out that I have miss a inner loop for t. x and y should be recalculated everytime when t is divisble by 2. I have also included a more readble source for this algorithm. --- ciphers/rsa_factorization.py | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/ciphers/rsa_factorization.py b/ciphers/rsa_factorization.py index 58bdc554a861..9ec34e6c5a17 100644 --- a/ciphers/rsa_factorization.py +++ b/ciphers/rsa_factorization.py @@ -4,6 +4,7 @@ The program can efficiently factor RSA prime number given the private key d and public key e. Source: on page 3 of https://crypto.stanford.edu/~dabo/papers/RSA-survey.pdf +More readable source: https://www.di-mgt.com.au/rsa_factorize_n.html large number can take minutes to factor, therefore are not included in doctest. """ import math @@ -15,7 +16,7 @@ def rsafactor(d: int, e: int, N: int) -> List[int]: """ This function returns the factors of N, where p*q=N Return: [p, q] - + We call N the RSA modulus, e the encryption exponent, and d the decryption exponent. The pair (N, e) is the public key. As its name suggests, it is public and is used to encrypt messages. @@ -35,13 +36,17 @@ def rsafactor(d: int, e: int, N: int) -> List[int]: while p == 0: g = random.randint(2, N - 1) t = k - if t % 2 == 0: - t = t // 2 - x = (g ** t) % N - y = math.gcd(x - 1, N) - if x > 1 and y > 1: - p = y - q = N // y + while True: + if t % 2 == 0: + t = t // 2 + x = (g ** t) % N + y = math.gcd(x - 1, N) + if x > 1 and y > 1: + p = y + q = N // y + break # find the correct factors + else: + break # t is not divisible by 2, break and choose another g return sorted([p, q]) From e3f55aecce9380381099421dceba985f353df87f Mon Sep 17 00:00:00 2001 From: Sarath Kaul Date: Fri, 15 Nov 2019 01:31:51 +0530 Subject: [PATCH 0360/1071] Remove Duplicate Script Added (#1570) * Added Remove duplicate script and updated requirements.txt * Requirements.txt Updated * Remove Duplicate Script Added * Directory Modified * Directory.md Updated --- DIRECTORY.md | 126 +++++++++++++++++++++++++++++++++++- strings/remove_duplicate.py | 20 ++++++ 2 files changed, 145 insertions(+), 1 deletion(-) create mode 100644 strings/remove_duplicate.py diff --git a/DIRECTORY.md b/DIRECTORY.md index 04b785b2b833..e807644b67db 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -45,9 +45,11 @@ * [Morse Code Implementation](https://github.com/TheAlgorithms/Python/blob/master/ciphers/morse_code_implementation.py) * [Onepad Cipher](https://github.com/TheAlgorithms/Python/blob/master/ciphers/onepad_cipher.py) * [Playfair Cipher](https://github.com/TheAlgorithms/Python/blob/master/ciphers/playfair_cipher.py) + * [Porta Cipher](https://github.com/TheAlgorithms/Python/blob/master/ciphers/porta_cipher.py) * [Rabin Miller](https://github.com/TheAlgorithms/Python/blob/master/ciphers/rabin_miller.py) * [Rot13](https://github.com/TheAlgorithms/Python/blob/master/ciphers/rot13.py) * [Rsa Cipher](https://github.com/TheAlgorithms/Python/blob/master/ciphers/rsa_cipher.py) + * [Rsa Factorization](https://github.com/TheAlgorithms/Python/blob/master/ciphers/rsa_factorization.py) * [Rsa Key Generator](https://github.com/TheAlgorithms/Python/blob/master/ciphers/rsa_key_generator.py) * [Shuffled Shift Cipher](https://github.com/TheAlgorithms/Python/blob/master/ciphers/shuffled_shift_cipher.py) * [Simple Substitution Cipher](https://github.com/TheAlgorithms/Python/blob/master/ciphers/simple_substitution_cipher.py) @@ -94,7 +96,9 @@ * [Min Heap](https://github.com/TheAlgorithms/Python/blob/master/data_structures/heap/min_heap.py) * Linked List * [Doubly Linked List](https://github.com/TheAlgorithms/Python/blob/master/data_structures/linked_list/doubly_linked_list.py) + * [From Sequence](https://github.com/TheAlgorithms/Python/blob/master/data_structures/linked_list/from_sequence.py) * [Is Palindrome](https://github.com/TheAlgorithms/Python/blob/master/data_structures/linked_list/is_palindrome.py) + * [Print Reverse](https://github.com/TheAlgorithms/Python/blob/master/data_structures/linked_list/print_reverse.py) * [Singly Linked List](https://github.com/TheAlgorithms/Python/blob/master/data_structures/linked_list/singly_linked_list.py) * [Swap Nodes](https://github.com/TheAlgorithms/Python/blob/master/data_structures/linked_list/swap_nodes.py) * Queue @@ -238,6 +242,7 @@ * [Basic Maths](https://github.com/TheAlgorithms/Python/blob/master/maths/basic_maths.py) * [Binary Exponentiation](https://github.com/TheAlgorithms/Python/blob/master/maths/binary_exponentiation.py) * [Binomial Coefficient](https://github.com/TheAlgorithms/Python/blob/master/maths/binomial_coefficient.py) + * [Ceil](https://github.com/TheAlgorithms/Python/blob/master/maths/ceil.py) * [Collatz Sequence](https://github.com/TheAlgorithms/Python/blob/master/maths/collatz_sequence.py) * [Explicit Euler](https://github.com/TheAlgorithms/Python/blob/master/maths/explicit_euler.py) * [Extended Euclidean Algorithm](https://github.com/TheAlgorithms/Python/blob/master/maths/extended_euclidean_algorithm.py) @@ -251,6 +256,7 @@ * [Find Max Recursion](https://github.com/TheAlgorithms/Python/blob/master/maths/find_max_recursion.py) * [Find Min](https://github.com/TheAlgorithms/Python/blob/master/maths/find_min.py) * [Find Min Recursion](https://github.com/TheAlgorithms/Python/blob/master/maths/find_min_recursion.py) + * [Floor](https://github.com/TheAlgorithms/Python/blob/master/maths/floor.py) * [Gaussian](https://github.com/TheAlgorithms/Python/blob/master/maths/gaussian.py) * [Greatest Common Divisor](https://github.com/TheAlgorithms/Python/blob/master/maths/greatest_common_divisor.py) * [Hardy Ramanujanalgo](https://github.com/TheAlgorithms/Python/blob/master/maths/hardy_ramanujanalgo.py) @@ -317,7 +323,6 @@ * [Detecting English Programmatically](https://github.com/TheAlgorithms/Python/blob/master/other/detecting_english_programmatically.py) * [Euclidean Gcd](https://github.com/TheAlgorithms/Python/blob/master/other/euclidean_gcd.py) * [Fischer Yates Shuffle](https://github.com/TheAlgorithms/Python/blob/master/other/fischer_yates_shuffle.py) - * [Food Wastage Analysis From 1961-2013 Fao](https://github.com/TheAlgorithms/Python/blob/master/other/food_wastage_analysis_from_1961-2013_fao.ipynb) * [Frequency Finder](https://github.com/TheAlgorithms/Python/blob/master/other/frequency_finder.py) * [Game Of Life](https://github.com/TheAlgorithms/Python/blob/master/other/game_of_life.py) * [Greedy](https://github.com/TheAlgorithms/Python/blob/master/other/greedy.py) @@ -353,6 +358,7 @@ * Problem 03 * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_03/sol1.py) * [Sol2](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_03/sol2.py) + * [Sol3](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_03/sol3.py) * Problem 04 * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_04/sol1.py) * [Sol2](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_04/sol2.py) @@ -371,6 +377,7 @@ * Problem 08 * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_08/sol1.py) * [Sol2](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_08/sol2.py) + * [Sol3](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_08/sol3.py) * Problem 09 * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_09/sol1.py) * [Sol2](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_09/sol2.py) @@ -506,16 +513,133 @@ ## Strings * [Aho-Corasick](https://github.com/TheAlgorithms/Python/blob/master/strings/aho-corasick.py) * [Boyer Moore Search](https://github.com/TheAlgorithms/Python/blob/master/strings/boyer_moore_search.py) + * [Check Panagram](https://github.com/TheAlgorithms/Python/blob/master/strings/check_panagram.py) * [Knuth Morris Pratt](https://github.com/TheAlgorithms/Python/blob/master/strings/knuth_morris_pratt.py) * [Levenshtein Distance](https://github.com/TheAlgorithms/Python/blob/master/strings/levenshtein_distance.py) * [Manacher](https://github.com/TheAlgorithms/Python/blob/master/strings/manacher.py) * [Min Cost String Conversion](https://github.com/TheAlgorithms/Python/blob/master/strings/min_cost_string_conversion.py) * [Naive String Search](https://github.com/TheAlgorithms/Python/blob/master/strings/naive_string_search.py) * [Rabin Karp](https://github.com/TheAlgorithms/Python/blob/master/strings/rabin_karp.py) + * [Remove Duplicate](https://github.com/TheAlgorithms/Python/blob/master/strings/remove_duplicate.py) ## Traversals * [Binary Tree Traversals](https://github.com/TheAlgorithms/Python/blob/master/traversals/binary_tree_traversals.py) +## Venv + * Lib + * Python3.7 + * Site-Packages + * Certifi + * [ Main ](https://github.com/TheAlgorithms/Python/blob/master/venv/lib/python3.7/site-packages/certifi/__main__.py) + * [Core](https://github.com/TheAlgorithms/Python/blob/master/venv/lib/python3.7/site-packages/certifi/core.py) + * Chardet + * [Big5Freq](https://github.com/TheAlgorithms/Python/blob/master/venv/lib/python3.7/site-packages/chardet/big5freq.py) + * [Big5Prober](https://github.com/TheAlgorithms/Python/blob/master/venv/lib/python3.7/site-packages/chardet/big5prober.py) + * [Chardistribution](https://github.com/TheAlgorithms/Python/blob/master/venv/lib/python3.7/site-packages/chardet/chardistribution.py) + * [Charsetgroupprober](https://github.com/TheAlgorithms/Python/blob/master/venv/lib/python3.7/site-packages/chardet/charsetgroupprober.py) + * [Charsetprober](https://github.com/TheAlgorithms/Python/blob/master/venv/lib/python3.7/site-packages/chardet/charsetprober.py) + * Cli + * [Chardetect](https://github.com/TheAlgorithms/Python/blob/master/venv/lib/python3.7/site-packages/chardet/cli/chardetect.py) + * [Codingstatemachine](https://github.com/TheAlgorithms/Python/blob/master/venv/lib/python3.7/site-packages/chardet/codingstatemachine.py) + * [Compat](https://github.com/TheAlgorithms/Python/blob/master/venv/lib/python3.7/site-packages/chardet/compat.py) + * [Cp949Prober](https://github.com/TheAlgorithms/Python/blob/master/venv/lib/python3.7/site-packages/chardet/cp949prober.py) + * [Enums](https://github.com/TheAlgorithms/Python/blob/master/venv/lib/python3.7/site-packages/chardet/enums.py) + * [Escprober](https://github.com/TheAlgorithms/Python/blob/master/venv/lib/python3.7/site-packages/chardet/escprober.py) + * [Escsm](https://github.com/TheAlgorithms/Python/blob/master/venv/lib/python3.7/site-packages/chardet/escsm.py) + * [Eucjpprober](https://github.com/TheAlgorithms/Python/blob/master/venv/lib/python3.7/site-packages/chardet/eucjpprober.py) + * [Euckrfreq](https://github.com/TheAlgorithms/Python/blob/master/venv/lib/python3.7/site-packages/chardet/euckrfreq.py) + * [Euckrprober](https://github.com/TheAlgorithms/Python/blob/master/venv/lib/python3.7/site-packages/chardet/euckrprober.py) + * [Euctwfreq](https://github.com/TheAlgorithms/Python/blob/master/venv/lib/python3.7/site-packages/chardet/euctwfreq.py) + * [Euctwprober](https://github.com/TheAlgorithms/Python/blob/master/venv/lib/python3.7/site-packages/chardet/euctwprober.py) + * [Gb2312Freq](https://github.com/TheAlgorithms/Python/blob/master/venv/lib/python3.7/site-packages/chardet/gb2312freq.py) + * [Gb2312Prober](https://github.com/TheAlgorithms/Python/blob/master/venv/lib/python3.7/site-packages/chardet/gb2312prober.py) + * [Hebrewprober](https://github.com/TheAlgorithms/Python/blob/master/venv/lib/python3.7/site-packages/chardet/hebrewprober.py) + * [Jisfreq](https://github.com/TheAlgorithms/Python/blob/master/venv/lib/python3.7/site-packages/chardet/jisfreq.py) + * [Jpcntx](https://github.com/TheAlgorithms/Python/blob/master/venv/lib/python3.7/site-packages/chardet/jpcntx.py) + * [Langbulgarianmodel](https://github.com/TheAlgorithms/Python/blob/master/venv/lib/python3.7/site-packages/chardet/langbulgarianmodel.py) + * [Langcyrillicmodel](https://github.com/TheAlgorithms/Python/blob/master/venv/lib/python3.7/site-packages/chardet/langcyrillicmodel.py) + * [Langgreekmodel](https://github.com/TheAlgorithms/Python/blob/master/venv/lib/python3.7/site-packages/chardet/langgreekmodel.py) + * [Langhebrewmodel](https://github.com/TheAlgorithms/Python/blob/master/venv/lib/python3.7/site-packages/chardet/langhebrewmodel.py) + * [Langhungarianmodel](https://github.com/TheAlgorithms/Python/blob/master/venv/lib/python3.7/site-packages/chardet/langhungarianmodel.py) + * [Langthaimodel](https://github.com/TheAlgorithms/Python/blob/master/venv/lib/python3.7/site-packages/chardet/langthaimodel.py) + * [Langturkishmodel](https://github.com/TheAlgorithms/Python/blob/master/venv/lib/python3.7/site-packages/chardet/langturkishmodel.py) + * [Latin1Prober](https://github.com/TheAlgorithms/Python/blob/master/venv/lib/python3.7/site-packages/chardet/latin1prober.py) + * [Mbcharsetprober](https://github.com/TheAlgorithms/Python/blob/master/venv/lib/python3.7/site-packages/chardet/mbcharsetprober.py) + * [Mbcsgroupprober](https://github.com/TheAlgorithms/Python/blob/master/venv/lib/python3.7/site-packages/chardet/mbcsgroupprober.py) + * [Mbcssm](https://github.com/TheAlgorithms/Python/blob/master/venv/lib/python3.7/site-packages/chardet/mbcssm.py) + * [Sbcharsetprober](https://github.com/TheAlgorithms/Python/blob/master/venv/lib/python3.7/site-packages/chardet/sbcharsetprober.py) + * [Sbcsgroupprober](https://github.com/TheAlgorithms/Python/blob/master/venv/lib/python3.7/site-packages/chardet/sbcsgroupprober.py) + * [Sjisprober](https://github.com/TheAlgorithms/Python/blob/master/venv/lib/python3.7/site-packages/chardet/sjisprober.py) + * [Universaldetector](https://github.com/TheAlgorithms/Python/blob/master/venv/lib/python3.7/site-packages/chardet/universaldetector.py) + * [Utf8Prober](https://github.com/TheAlgorithms/Python/blob/master/venv/lib/python3.7/site-packages/chardet/utf8prober.py) + * [Version](https://github.com/TheAlgorithms/Python/blob/master/venv/lib/python3.7/site-packages/chardet/version.py) + * Idna + * [Codec](https://github.com/TheAlgorithms/Python/blob/master/venv/lib/python3.7/site-packages/idna/codec.py) + * [Compat](https://github.com/TheAlgorithms/Python/blob/master/venv/lib/python3.7/site-packages/idna/compat.py) + * [Core](https://github.com/TheAlgorithms/Python/blob/master/venv/lib/python3.7/site-packages/idna/core.py) + * [Idnadata](https://github.com/TheAlgorithms/Python/blob/master/venv/lib/python3.7/site-packages/idna/idnadata.py) + * [Intranges](https://github.com/TheAlgorithms/Python/blob/master/venv/lib/python3.7/site-packages/idna/intranges.py) + * [Package Data](https://github.com/TheAlgorithms/Python/blob/master/venv/lib/python3.7/site-packages/idna/package_data.py) + * [Uts46Data](https://github.com/TheAlgorithms/Python/blob/master/venv/lib/python3.7/site-packages/idna/uts46data.py) + * [Ordered Set](https://github.com/TheAlgorithms/Python/blob/master/venv/lib/python3.7/site-packages/ordered_set.py) + * Pip-19.0.3-Py3.7.Egg + * Pip + * [ Main ](https://github.com/TheAlgorithms/Python/blob/master/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/__main__.py) + * Requests + * [ Version ](https://github.com/TheAlgorithms/Python/blob/master/venv/lib/python3.7/site-packages/requests/__version__.py) + * [ Internal Utils](https://github.com/TheAlgorithms/Python/blob/master/venv/lib/python3.7/site-packages/requests/_internal_utils.py) + * [Adapters](https://github.com/TheAlgorithms/Python/blob/master/venv/lib/python3.7/site-packages/requests/adapters.py) + * [Api](https://github.com/TheAlgorithms/Python/blob/master/venv/lib/python3.7/site-packages/requests/api.py) + * [Auth](https://github.com/TheAlgorithms/Python/blob/master/venv/lib/python3.7/site-packages/requests/auth.py) + * [Certs](https://github.com/TheAlgorithms/Python/blob/master/venv/lib/python3.7/site-packages/requests/certs.py) + * [Compat](https://github.com/TheAlgorithms/Python/blob/master/venv/lib/python3.7/site-packages/requests/compat.py) + * [Cookies](https://github.com/TheAlgorithms/Python/blob/master/venv/lib/python3.7/site-packages/requests/cookies.py) + * [Exceptions](https://github.com/TheAlgorithms/Python/blob/master/venv/lib/python3.7/site-packages/requests/exceptions.py) + * [Help](https://github.com/TheAlgorithms/Python/blob/master/venv/lib/python3.7/site-packages/requests/help.py) + * [Hooks](https://github.com/TheAlgorithms/Python/blob/master/venv/lib/python3.7/site-packages/requests/hooks.py) + * [Models](https://github.com/TheAlgorithms/Python/blob/master/venv/lib/python3.7/site-packages/requests/models.py) + * [Packages](https://github.com/TheAlgorithms/Python/blob/master/venv/lib/python3.7/site-packages/requests/packages.py) + * [Sessions](https://github.com/TheAlgorithms/Python/blob/master/venv/lib/python3.7/site-packages/requests/sessions.py) + * [Status Codes](https://github.com/TheAlgorithms/Python/blob/master/venv/lib/python3.7/site-packages/requests/status_codes.py) + * [Structures](https://github.com/TheAlgorithms/Python/blob/master/venv/lib/python3.7/site-packages/requests/structures.py) + * [Utils](https://github.com/TheAlgorithms/Python/blob/master/venv/lib/python3.7/site-packages/requests/utils.py) + * Urllib3 + * [ Collections](https://github.com/TheAlgorithms/Python/blob/master/venv/lib/python3.7/site-packages/urllib3/_collections.py) + * [Connection](https://github.com/TheAlgorithms/Python/blob/master/venv/lib/python3.7/site-packages/urllib3/connection.py) + * [Connectionpool](https://github.com/TheAlgorithms/Python/blob/master/venv/lib/python3.7/site-packages/urllib3/connectionpool.py) + * Contrib + * [ Appengine Environ](https://github.com/TheAlgorithms/Python/blob/master/venv/lib/python3.7/site-packages/urllib3/contrib/_appengine_environ.py) + * [Appengine](https://github.com/TheAlgorithms/Python/blob/master/venv/lib/python3.7/site-packages/urllib3/contrib/appengine.py) + * [Ntlmpool](https://github.com/TheAlgorithms/Python/blob/master/venv/lib/python3.7/site-packages/urllib3/contrib/ntlmpool.py) + * [Pyopenssl](https://github.com/TheAlgorithms/Python/blob/master/venv/lib/python3.7/site-packages/urllib3/contrib/pyopenssl.py) + * [Securetransport](https://github.com/TheAlgorithms/Python/blob/master/venv/lib/python3.7/site-packages/urllib3/contrib/securetransport.py) + * [Socks](https://github.com/TheAlgorithms/Python/blob/master/venv/lib/python3.7/site-packages/urllib3/contrib/socks.py) + * [Exceptions](https://github.com/TheAlgorithms/Python/blob/master/venv/lib/python3.7/site-packages/urllib3/exceptions.py) + * [Fields](https://github.com/TheAlgorithms/Python/blob/master/venv/lib/python3.7/site-packages/urllib3/fields.py) + * [Filepost](https://github.com/TheAlgorithms/Python/blob/master/venv/lib/python3.7/site-packages/urllib3/filepost.py) + * Packages + * Backports + * [Makefile](https://github.com/TheAlgorithms/Python/blob/master/venv/lib/python3.7/site-packages/urllib3/packages/backports/makefile.py) + * [Six](https://github.com/TheAlgorithms/Python/blob/master/venv/lib/python3.7/site-packages/urllib3/packages/six.py) + * Ssl Match Hostname + * [ Implementation](https://github.com/TheAlgorithms/Python/blob/master/venv/lib/python3.7/site-packages/urllib3/packages/ssl_match_hostname/_implementation.py) + * [Poolmanager](https://github.com/TheAlgorithms/Python/blob/master/venv/lib/python3.7/site-packages/urllib3/poolmanager.py) + * [Request](https://github.com/TheAlgorithms/Python/blob/master/venv/lib/python3.7/site-packages/urllib3/request.py) + * [Response](https://github.com/TheAlgorithms/Python/blob/master/venv/lib/python3.7/site-packages/urllib3/response.py) + * Util + * [Connection](https://github.com/TheAlgorithms/Python/blob/master/venv/lib/python3.7/site-packages/urllib3/util/connection.py) + * [Queue](https://github.com/TheAlgorithms/Python/blob/master/venv/lib/python3.7/site-packages/urllib3/util/queue.py) + * [Request](https://github.com/TheAlgorithms/Python/blob/master/venv/lib/python3.7/site-packages/urllib3/util/request.py) + * [Response](https://github.com/TheAlgorithms/Python/blob/master/venv/lib/python3.7/site-packages/urllib3/util/response.py) + * [Retry](https://github.com/TheAlgorithms/Python/blob/master/venv/lib/python3.7/site-packages/urllib3/util/retry.py) + * [Ssl ](https://github.com/TheAlgorithms/Python/blob/master/venv/lib/python3.7/site-packages/urllib3/util/ssl_.py) + * [Timeout](https://github.com/TheAlgorithms/Python/blob/master/venv/lib/python3.7/site-packages/urllib3/util/timeout.py) + * [Url](https://github.com/TheAlgorithms/Python/blob/master/venv/lib/python3.7/site-packages/urllib3/util/url.py) + * [Wait](https://github.com/TheAlgorithms/Python/blob/master/venv/lib/python3.7/site-packages/urllib3/util/wait.py) + ## Web Programming * [Crawl Google Results](https://github.com/TheAlgorithms/Python/blob/master/web_programming/crawl_google_results.py) + * [Fetch Bbc News](https://github.com/TheAlgorithms/Python/blob/master/web_programming/fetch_bbc_news.py) + * [Fetch Github Info](https://github.com/TheAlgorithms/Python/blob/master/web_programming/fetch_github_info.py) * [Get Imdbtop](https://github.com/TheAlgorithms/Python/blob/master/web_programming/get_imdbtop.py) + * [Slack Message](https://github.com/TheAlgorithms/Python/blob/master/web_programming/slack_message.py) diff --git a/strings/remove_duplicate.py b/strings/remove_duplicate.py new file mode 100644 index 000000000000..0462292b78d2 --- /dev/null +++ b/strings/remove_duplicate.py @@ -0,0 +1,20 @@ +# Created by sarathkaul on 14/11/19 + + +def remove_duplicates(sentence: str) -> str: + """ + Reomove duplicates from sentence + >>> remove_duplicates("Python is great and Java is also great") + 'Java Python also and great is' + """ + sen_list = sentence.split(" ") + check = set() + + for a_word in sen_list: + check.add(a_word) + + return " ".join(sorted(check)) + + +if __name__ == "__main__": + print(remove_duplicates("INPUT_SENTENCE")) From a7424cc115a3b5db388b0d599f7a01a7a33b9483 Mon Sep 17 00:00:00 2001 From: Ankur Chattopadhyay <39518771+chttrjeankr@users.noreply.github.com> Date: Fri, 15 Nov 2019 02:00:35 +0530 Subject: [PATCH 0361/1071] changed implementation of GitHub action to auto update DIRECTORY.md (#1571) * changed implementation of GitHub action to auto update DIRECTORY.md * updating DIRECTORY.md --- .github/workflows/directory_writer.yml | 17 ++-- DIRECTORY.md | 112 ------------------------- 2 files changed, 9 insertions(+), 120 deletions(-) diff --git a/.github/workflows/directory_writer.yml b/.github/workflows/directory_writer.yml index dcbb9c304e15..76c7a5cc1969 100644 --- a/.github/workflows/directory_writer.yml +++ b/.github/workflows/directory_writer.yml @@ -1,6 +1,7 @@ -# The objective of this GitHub Action is to add a new DIRECTORY.md file to a pull request if needed. +# The objective of this GitHub Action is to update the DIRECTORY.md file (if needed) +# when doing a git push name: directory_writer -on: [pull_request] +on: [push] jobs: build: runs-on: ubuntu-latest @@ -8,8 +9,8 @@ jobs: max-parallel: 1 matrix: python-version: [3.7] - steps: + steps: - uses: actions/checkout@v1 - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v1 @@ -18,8 +19,8 @@ jobs: - name: Update DIRECTORY.md run: | scripts/build_directory_md.py 2>&1 | tee DIRECTORY.md - git config --global user.name 'directory_writer' - git config --global user.email 'mrvnmchm@users.noreply.github.com' - git remote set-url origin https://x-access-token:${{ secrets.gh_token }}@github.com/$GITHUB_REPOSITORY - git checkout $GITHUB_HEAD_REF - if git diff-files --quiet; then echo 0; else git commit -am "fixup: DIRECTORY.md" && git push; fi + git config --global user.name github-actions + git config --global user.email '${GITHUB_ACTOR}@users.noreply.github.com' + git remote set-url origin https://x-access-token:${{ secrets.GITHUB_TOKEN }}@github.com/$GITHUB_REPOSITORY + git commit -am "updating DIRECTORY.md" + git push --force origin HEAD:$GITHUB_REF diff --git a/DIRECTORY.md b/DIRECTORY.md index e807644b67db..15a906332d22 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -525,118 +525,6 @@ ## Traversals * [Binary Tree Traversals](https://github.com/TheAlgorithms/Python/blob/master/traversals/binary_tree_traversals.py) -## Venv - * Lib - * Python3.7 - * Site-Packages - * Certifi - * [ Main ](https://github.com/TheAlgorithms/Python/blob/master/venv/lib/python3.7/site-packages/certifi/__main__.py) - * [Core](https://github.com/TheAlgorithms/Python/blob/master/venv/lib/python3.7/site-packages/certifi/core.py) - * Chardet - * [Big5Freq](https://github.com/TheAlgorithms/Python/blob/master/venv/lib/python3.7/site-packages/chardet/big5freq.py) - * [Big5Prober](https://github.com/TheAlgorithms/Python/blob/master/venv/lib/python3.7/site-packages/chardet/big5prober.py) - * [Chardistribution](https://github.com/TheAlgorithms/Python/blob/master/venv/lib/python3.7/site-packages/chardet/chardistribution.py) - * [Charsetgroupprober](https://github.com/TheAlgorithms/Python/blob/master/venv/lib/python3.7/site-packages/chardet/charsetgroupprober.py) - * [Charsetprober](https://github.com/TheAlgorithms/Python/blob/master/venv/lib/python3.7/site-packages/chardet/charsetprober.py) - * Cli - * [Chardetect](https://github.com/TheAlgorithms/Python/blob/master/venv/lib/python3.7/site-packages/chardet/cli/chardetect.py) - * [Codingstatemachine](https://github.com/TheAlgorithms/Python/blob/master/venv/lib/python3.7/site-packages/chardet/codingstatemachine.py) - * [Compat](https://github.com/TheAlgorithms/Python/blob/master/venv/lib/python3.7/site-packages/chardet/compat.py) - * [Cp949Prober](https://github.com/TheAlgorithms/Python/blob/master/venv/lib/python3.7/site-packages/chardet/cp949prober.py) - * [Enums](https://github.com/TheAlgorithms/Python/blob/master/venv/lib/python3.7/site-packages/chardet/enums.py) - * [Escprober](https://github.com/TheAlgorithms/Python/blob/master/venv/lib/python3.7/site-packages/chardet/escprober.py) - * [Escsm](https://github.com/TheAlgorithms/Python/blob/master/venv/lib/python3.7/site-packages/chardet/escsm.py) - * [Eucjpprober](https://github.com/TheAlgorithms/Python/blob/master/venv/lib/python3.7/site-packages/chardet/eucjpprober.py) - * [Euckrfreq](https://github.com/TheAlgorithms/Python/blob/master/venv/lib/python3.7/site-packages/chardet/euckrfreq.py) - * [Euckrprober](https://github.com/TheAlgorithms/Python/blob/master/venv/lib/python3.7/site-packages/chardet/euckrprober.py) - * [Euctwfreq](https://github.com/TheAlgorithms/Python/blob/master/venv/lib/python3.7/site-packages/chardet/euctwfreq.py) - * [Euctwprober](https://github.com/TheAlgorithms/Python/blob/master/venv/lib/python3.7/site-packages/chardet/euctwprober.py) - * [Gb2312Freq](https://github.com/TheAlgorithms/Python/blob/master/venv/lib/python3.7/site-packages/chardet/gb2312freq.py) - * [Gb2312Prober](https://github.com/TheAlgorithms/Python/blob/master/venv/lib/python3.7/site-packages/chardet/gb2312prober.py) - * [Hebrewprober](https://github.com/TheAlgorithms/Python/blob/master/venv/lib/python3.7/site-packages/chardet/hebrewprober.py) - * [Jisfreq](https://github.com/TheAlgorithms/Python/blob/master/venv/lib/python3.7/site-packages/chardet/jisfreq.py) - * [Jpcntx](https://github.com/TheAlgorithms/Python/blob/master/venv/lib/python3.7/site-packages/chardet/jpcntx.py) - * [Langbulgarianmodel](https://github.com/TheAlgorithms/Python/blob/master/venv/lib/python3.7/site-packages/chardet/langbulgarianmodel.py) - * [Langcyrillicmodel](https://github.com/TheAlgorithms/Python/blob/master/venv/lib/python3.7/site-packages/chardet/langcyrillicmodel.py) - * [Langgreekmodel](https://github.com/TheAlgorithms/Python/blob/master/venv/lib/python3.7/site-packages/chardet/langgreekmodel.py) - * [Langhebrewmodel](https://github.com/TheAlgorithms/Python/blob/master/venv/lib/python3.7/site-packages/chardet/langhebrewmodel.py) - * [Langhungarianmodel](https://github.com/TheAlgorithms/Python/blob/master/venv/lib/python3.7/site-packages/chardet/langhungarianmodel.py) - * [Langthaimodel](https://github.com/TheAlgorithms/Python/blob/master/venv/lib/python3.7/site-packages/chardet/langthaimodel.py) - * [Langturkishmodel](https://github.com/TheAlgorithms/Python/blob/master/venv/lib/python3.7/site-packages/chardet/langturkishmodel.py) - * [Latin1Prober](https://github.com/TheAlgorithms/Python/blob/master/venv/lib/python3.7/site-packages/chardet/latin1prober.py) - * [Mbcharsetprober](https://github.com/TheAlgorithms/Python/blob/master/venv/lib/python3.7/site-packages/chardet/mbcharsetprober.py) - * [Mbcsgroupprober](https://github.com/TheAlgorithms/Python/blob/master/venv/lib/python3.7/site-packages/chardet/mbcsgroupprober.py) - * [Mbcssm](https://github.com/TheAlgorithms/Python/blob/master/venv/lib/python3.7/site-packages/chardet/mbcssm.py) - * [Sbcharsetprober](https://github.com/TheAlgorithms/Python/blob/master/venv/lib/python3.7/site-packages/chardet/sbcharsetprober.py) - * [Sbcsgroupprober](https://github.com/TheAlgorithms/Python/blob/master/venv/lib/python3.7/site-packages/chardet/sbcsgroupprober.py) - * [Sjisprober](https://github.com/TheAlgorithms/Python/blob/master/venv/lib/python3.7/site-packages/chardet/sjisprober.py) - * [Universaldetector](https://github.com/TheAlgorithms/Python/blob/master/venv/lib/python3.7/site-packages/chardet/universaldetector.py) - * [Utf8Prober](https://github.com/TheAlgorithms/Python/blob/master/venv/lib/python3.7/site-packages/chardet/utf8prober.py) - * [Version](https://github.com/TheAlgorithms/Python/blob/master/venv/lib/python3.7/site-packages/chardet/version.py) - * Idna - * [Codec](https://github.com/TheAlgorithms/Python/blob/master/venv/lib/python3.7/site-packages/idna/codec.py) - * [Compat](https://github.com/TheAlgorithms/Python/blob/master/venv/lib/python3.7/site-packages/idna/compat.py) - * [Core](https://github.com/TheAlgorithms/Python/blob/master/venv/lib/python3.7/site-packages/idna/core.py) - * [Idnadata](https://github.com/TheAlgorithms/Python/blob/master/venv/lib/python3.7/site-packages/idna/idnadata.py) - * [Intranges](https://github.com/TheAlgorithms/Python/blob/master/venv/lib/python3.7/site-packages/idna/intranges.py) - * [Package Data](https://github.com/TheAlgorithms/Python/blob/master/venv/lib/python3.7/site-packages/idna/package_data.py) - * [Uts46Data](https://github.com/TheAlgorithms/Python/blob/master/venv/lib/python3.7/site-packages/idna/uts46data.py) - * [Ordered Set](https://github.com/TheAlgorithms/Python/blob/master/venv/lib/python3.7/site-packages/ordered_set.py) - * Pip-19.0.3-Py3.7.Egg - * Pip - * [ Main ](https://github.com/TheAlgorithms/Python/blob/master/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/__main__.py) - * Requests - * [ Version ](https://github.com/TheAlgorithms/Python/blob/master/venv/lib/python3.7/site-packages/requests/__version__.py) - * [ Internal Utils](https://github.com/TheAlgorithms/Python/blob/master/venv/lib/python3.7/site-packages/requests/_internal_utils.py) - * [Adapters](https://github.com/TheAlgorithms/Python/blob/master/venv/lib/python3.7/site-packages/requests/adapters.py) - * [Api](https://github.com/TheAlgorithms/Python/blob/master/venv/lib/python3.7/site-packages/requests/api.py) - * [Auth](https://github.com/TheAlgorithms/Python/blob/master/venv/lib/python3.7/site-packages/requests/auth.py) - * [Certs](https://github.com/TheAlgorithms/Python/blob/master/venv/lib/python3.7/site-packages/requests/certs.py) - * [Compat](https://github.com/TheAlgorithms/Python/blob/master/venv/lib/python3.7/site-packages/requests/compat.py) - * [Cookies](https://github.com/TheAlgorithms/Python/blob/master/venv/lib/python3.7/site-packages/requests/cookies.py) - * [Exceptions](https://github.com/TheAlgorithms/Python/blob/master/venv/lib/python3.7/site-packages/requests/exceptions.py) - * [Help](https://github.com/TheAlgorithms/Python/blob/master/venv/lib/python3.7/site-packages/requests/help.py) - * [Hooks](https://github.com/TheAlgorithms/Python/blob/master/venv/lib/python3.7/site-packages/requests/hooks.py) - * [Models](https://github.com/TheAlgorithms/Python/blob/master/venv/lib/python3.7/site-packages/requests/models.py) - * [Packages](https://github.com/TheAlgorithms/Python/blob/master/venv/lib/python3.7/site-packages/requests/packages.py) - * [Sessions](https://github.com/TheAlgorithms/Python/blob/master/venv/lib/python3.7/site-packages/requests/sessions.py) - * [Status Codes](https://github.com/TheAlgorithms/Python/blob/master/venv/lib/python3.7/site-packages/requests/status_codes.py) - * [Structures](https://github.com/TheAlgorithms/Python/blob/master/venv/lib/python3.7/site-packages/requests/structures.py) - * [Utils](https://github.com/TheAlgorithms/Python/blob/master/venv/lib/python3.7/site-packages/requests/utils.py) - * Urllib3 - * [ Collections](https://github.com/TheAlgorithms/Python/blob/master/venv/lib/python3.7/site-packages/urllib3/_collections.py) - * [Connection](https://github.com/TheAlgorithms/Python/blob/master/venv/lib/python3.7/site-packages/urllib3/connection.py) - * [Connectionpool](https://github.com/TheAlgorithms/Python/blob/master/venv/lib/python3.7/site-packages/urllib3/connectionpool.py) - * Contrib - * [ Appengine Environ](https://github.com/TheAlgorithms/Python/blob/master/venv/lib/python3.7/site-packages/urllib3/contrib/_appengine_environ.py) - * [Appengine](https://github.com/TheAlgorithms/Python/blob/master/venv/lib/python3.7/site-packages/urllib3/contrib/appengine.py) - * [Ntlmpool](https://github.com/TheAlgorithms/Python/blob/master/venv/lib/python3.7/site-packages/urllib3/contrib/ntlmpool.py) - * [Pyopenssl](https://github.com/TheAlgorithms/Python/blob/master/venv/lib/python3.7/site-packages/urllib3/contrib/pyopenssl.py) - * [Securetransport](https://github.com/TheAlgorithms/Python/blob/master/venv/lib/python3.7/site-packages/urllib3/contrib/securetransport.py) - * [Socks](https://github.com/TheAlgorithms/Python/blob/master/venv/lib/python3.7/site-packages/urllib3/contrib/socks.py) - * [Exceptions](https://github.com/TheAlgorithms/Python/blob/master/venv/lib/python3.7/site-packages/urllib3/exceptions.py) - * [Fields](https://github.com/TheAlgorithms/Python/blob/master/venv/lib/python3.7/site-packages/urllib3/fields.py) - * [Filepost](https://github.com/TheAlgorithms/Python/blob/master/venv/lib/python3.7/site-packages/urllib3/filepost.py) - * Packages - * Backports - * [Makefile](https://github.com/TheAlgorithms/Python/blob/master/venv/lib/python3.7/site-packages/urllib3/packages/backports/makefile.py) - * [Six](https://github.com/TheAlgorithms/Python/blob/master/venv/lib/python3.7/site-packages/urllib3/packages/six.py) - * Ssl Match Hostname - * [ Implementation](https://github.com/TheAlgorithms/Python/blob/master/venv/lib/python3.7/site-packages/urllib3/packages/ssl_match_hostname/_implementation.py) - * [Poolmanager](https://github.com/TheAlgorithms/Python/blob/master/venv/lib/python3.7/site-packages/urllib3/poolmanager.py) - * [Request](https://github.com/TheAlgorithms/Python/blob/master/venv/lib/python3.7/site-packages/urllib3/request.py) - * [Response](https://github.com/TheAlgorithms/Python/blob/master/venv/lib/python3.7/site-packages/urllib3/response.py) - * Util - * [Connection](https://github.com/TheAlgorithms/Python/blob/master/venv/lib/python3.7/site-packages/urllib3/util/connection.py) - * [Queue](https://github.com/TheAlgorithms/Python/blob/master/venv/lib/python3.7/site-packages/urllib3/util/queue.py) - * [Request](https://github.com/TheAlgorithms/Python/blob/master/venv/lib/python3.7/site-packages/urllib3/util/request.py) - * [Response](https://github.com/TheAlgorithms/Python/blob/master/venv/lib/python3.7/site-packages/urllib3/util/response.py) - * [Retry](https://github.com/TheAlgorithms/Python/blob/master/venv/lib/python3.7/site-packages/urllib3/util/retry.py) - * [Ssl ](https://github.com/TheAlgorithms/Python/blob/master/venv/lib/python3.7/site-packages/urllib3/util/ssl_.py) - * [Timeout](https://github.com/TheAlgorithms/Python/blob/master/venv/lib/python3.7/site-packages/urllib3/util/timeout.py) - * [Url](https://github.com/TheAlgorithms/Python/blob/master/venv/lib/python3.7/site-packages/urllib3/util/url.py) - * [Wait](https://github.com/TheAlgorithms/Python/blob/master/venv/lib/python3.7/site-packages/urllib3/util/wait.py) - ## Web Programming * [Crawl Google Results](https://github.com/TheAlgorithms/Python/blob/master/web_programming/crawl_google_results.py) * [Fetch Bbc News](https://github.com/TheAlgorithms/Python/blob/master/web_programming/fetch_bbc_news.py) From dbaedd4ed7f5302921177107d8010e557f28a52c Mon Sep 17 00:00:00 2001 From: Christian Clauss Date: Fri, 15 Nov 2019 03:22:23 +0100 Subject: [PATCH 0362/1071] || true (#1572) * || true * 3.8 * python: 3.x --- .github/workflows/directory_writer.yml | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/.github/workflows/directory_writer.yml b/.github/workflows/directory_writer.yml index 76c7a5cc1969..e021051fe564 100644 --- a/.github/workflows/directory_writer.yml +++ b/.github/workflows/directory_writer.yml @@ -5,22 +5,16 @@ on: [push] jobs: build: runs-on: ubuntu-latest - strategy: - max-parallel: 1 - matrix: - python-version: [3.7] - steps: - uses: actions/checkout@v1 - - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v1 + - uses: actions/setup-python@v1 with: - python-version: ${{ matrix.python-version }} + python-version: 3.x - name: Update DIRECTORY.md run: | scripts/build_directory_md.py 2>&1 | tee DIRECTORY.md git config --global user.name github-actions git config --global user.email '${GITHUB_ACTOR}@users.noreply.github.com' git remote set-url origin https://x-access-token:${{ secrets.GITHUB_TOKEN }}@github.com/$GITHUB_REPOSITORY - git commit -am "updating DIRECTORY.md" - git push --force origin HEAD:$GITHUB_REF + git commit -am "updating DIRECTORY.md" || true + git push --force origin HEAD:$GITHUB_REF || true From b838f1042c6d67110965da2a45ffaa69b0c4bfc4 Mon Sep 17 00:00:00 2001 From: Christian Clauss Date: Sat, 16 Nov 2019 08:05:00 +0100 Subject: [PATCH 0363/1071] Fix indentation contains tabs (flake8 E101,W191) (#1573) --- .travis.yml | 2 +- backtracking/all_combinations.py | 6 +- backtracking/all_permutations.py | 18 +- backtracking/all_subsequences.py | 18 +- backtracking/sum_of_subsets.py | 20 +- boolean_algebra/quine_mc_cluskey.py | 54 +++--- ciphers/xor_cipher.py | 110 +++++------ compression/peak_signal_to_noise_ratio.py | 2 +- .../stacks/infix_to_prefix_conversion.py | 2 +- data_structures/stacks/postfix_evaluation.py | 2 +- dynamic_programming/rod_cutting.py | 180 +++++++++--------- other/magicdiamondpattern.py | 18 +- other/nested_brackets.py | 6 +- project_euler/problem_27/problem_27_sol1.py | 22 +-- searches/interpolation_search.py | 4 +- 15 files changed, 232 insertions(+), 232 deletions(-) diff --git a/.travis.yml b/.travis.yml index 0c7c9fd0e1c7..6884d9addba0 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,7 +5,7 @@ before_install: pip install --upgrade pip setuptools install: pip install -r requirements.txt before_script: - black --check . || true - - flake8 . --count --select=E9,F4,F63,F7,F82 --show-source --statistics + - flake8 . --count --select=E101,E9,F4,F63,F7,F82,W191 --show-source --statistics script: - scripts/validate_filenames.py # no uppercase, no spaces, in a directory - mypy --ignore-missing-imports . diff --git a/backtracking/all_combinations.py b/backtracking/all_combinations.py index 23fe378f5462..60e9579f28ba 100644 --- a/backtracking/all_combinations.py +++ b/backtracking/all_combinations.py @@ -1,9 +1,9 @@ # -*- coding: utf-8 -*- """ - In this problem, we want to determine all possible combinations of k - numbers out of 1 ... n. We use backtracking to solve this problem. - Time complexity: O(C(n,k)) which is O(n choose k) = O((n!/(k! * (n - k)!))) + In this problem, we want to determine all possible combinations of k + numbers out of 1 ... n. We use backtracking to solve this problem. + Time complexity: O(C(n,k)) which is O(n choose k) = O((n!/(k! * (n - k)!))) """ diff --git a/backtracking/all_permutations.py b/backtracking/all_permutations.py index b0955bf53a31..d144436033de 100644 --- a/backtracking/all_permutations.py +++ b/backtracking/all_permutations.py @@ -1,9 +1,9 @@ """ - In this problem, we want to determine all possible permutations - of the given sequence. We use backtracking to solve this problem. + In this problem, we want to determine all possible permutations + of the given sequence. We use backtracking to solve this problem. - Time complexity: O(n! * n), - where n denotes the length of the given sequence. + Time complexity: O(n! * n), + where n denotes the length of the given sequence. """ @@ -13,10 +13,10 @@ def generate_all_permutations(sequence): def create_state_space_tree(sequence, current_sequence, index, index_used): """ - Creates a state space tree to iterate through each branch using DFS. - We know that each state has exactly len(sequence) - index children. - It terminates when it reaches the end of the given sequence. - """ + Creates a state space tree to iterate through each branch using DFS. + We know that each state has exactly len(sequence) - index children. + It terminates when it reaches the end of the given sequence. + """ if index == len(sequence): print(current_sequence) @@ -32,7 +32,7 @@ def create_state_space_tree(sequence, current_sequence, index, index_used): """ -remove the comment to take an input from the user +remove the comment to take an input from the user print("Enter the elements") sequence = list(map(int, input().split())) diff --git a/backtracking/all_subsequences.py b/backtracking/all_subsequences.py index 4a22c05d29a8..8283386991d9 100644 --- a/backtracking/all_subsequences.py +++ b/backtracking/all_subsequences.py @@ -1,9 +1,9 @@ """ - In this problem, we want to determine all possible subsequences - of the given sequence. We use backtracking to solve this problem. + In this problem, we want to determine all possible subsequences + of the given sequence. We use backtracking to solve this problem. - Time complexity: O(2^n), - where n denotes the length of the given sequence. + Time complexity: O(2^n), + where n denotes the length of the given sequence. """ @@ -13,10 +13,10 @@ def generate_all_subsequences(sequence): def create_state_space_tree(sequence, current_subsequence, index): """ - Creates a state space tree to iterate through each branch using DFS. - We know that each state has exactly two children. - It terminates when it reaches the end of the given sequence. - """ + Creates a state space tree to iterate through each branch using DFS. + We know that each state has exactly two children. + It terminates when it reaches the end of the given sequence. + """ if index == len(sequence): print(current_subsequence) @@ -29,7 +29,7 @@ def create_state_space_tree(sequence, current_subsequence, index): """ -remove the comment to take an input from the user +remove the comment to take an input from the user print("Enter the elements") sequence = list(map(int, input().split())) diff --git a/backtracking/sum_of_subsets.py b/backtracking/sum_of_subsets.py index d96552d39997..e765a1b69714 100644 --- a/backtracking/sum_of_subsets.py +++ b/backtracking/sum_of_subsets.py @@ -1,9 +1,9 @@ """ - The sum-of-subsetsproblem states that a set of non-negative integers, and a value M, - determine all possible subsets of the given set whose summation sum equal to given M. + The sum-of-subsetsproblem states that a set of non-negative integers, and a value M, + determine all possible subsets of the given set whose summation sum equal to given M. - Summation of the chosen numbers must be equal to given number M and one number can - be used only once. + Summation of the chosen numbers must be equal to given number M and one number can + be used only once. """ @@ -18,12 +18,12 @@ def generate_sum_of_subsets_soln(nums, max_sum): def create_state_space_tree(nums, max_sum, num_index, path, result, remaining_nums_sum): """ - Creates a state space tree to iterate through each branch using DFS. - It terminates the branching of a node when any of the two conditions - given below satisfy. - This algorithm follows depth-fist-search and backtracks when the node is not branchable. + Creates a state space tree to iterate through each branch using DFS. + It terminates the branching of a node when any of the two conditions + given below satisfy. + This algorithm follows depth-fist-search and backtracks when the node is not branchable. - """ + """ if sum(path) > max_sum or (remaining_nums_sum + sum(path)) < max_sum: return if sum(path) == max_sum: @@ -41,7 +41,7 @@ def create_state_space_tree(nums, max_sum, num_index, path, result, remaining_nu """ -remove the comment to take an input from the user +remove the comment to take an input from the user print("Enter the elements") nums = list(map(int, input().split())) diff --git a/boolean_algebra/quine_mc_cluskey.py b/boolean_algebra/quine_mc_cluskey.py index 7762d712a01a..a066982e53b4 100644 --- a/boolean_algebra/quine_mc_cluskey.py +++ b/boolean_algebra/quine_mc_cluskey.py @@ -1,11 +1,11 @@ def compare_string(string1, string2): """ - >>> compare_string('0010','0110') - '0_10' - - >>> compare_string('0110','1101') - -1 - """ + >>> compare_string('0010','0110') + '0_10' + + >>> compare_string('0110','1101') + -1 + """ l1 = list(string1) l2 = list(string2) count = 0 @@ -21,9 +21,9 @@ def compare_string(string1, string2): def check(binary): """ - >>> check(['0.00.01.5']) - ['0.00.01.5'] - """ + >>> check(['0.00.01.5']) + ['0.00.01.5'] + """ pi = [] while 1: check1 = ["$"] * len(binary) @@ -45,9 +45,9 @@ def check(binary): def decimal_to_binary(no_of_variable, minterms): """ - >>> decimal_to_binary(3,[1.5]) - ['0.00.01.5'] - """ + >>> decimal_to_binary(3,[1.5]) + ['0.00.01.5'] + """ temp = [] s = "" for m in minterms: @@ -61,12 +61,12 @@ def decimal_to_binary(no_of_variable, minterms): def is_for_table(string1, string2, count): """ - >>> is_for_table('__1','011',2) - True - - >>> is_for_table('01_','001',1) - False - """ + >>> is_for_table('__1','011',2) + True + + >>> is_for_table('01_','001',1) + False + """ l1 = list(string1) l2 = list(string2) count_n = 0 @@ -81,12 +81,12 @@ def is_for_table(string1, string2, count): def selection(chart, prime_implicants): """ - >>> selection([[1]],['0.00.01.5']) - ['0.00.01.5'] - - >>> selection([[1]],['0.00.01.5']) - ['0.00.01.5'] - """ + >>> selection([[1]],['0.00.01.5']) + ['0.00.01.5'] + + >>> selection([[1]],['0.00.01.5']) + ['0.00.01.5'] + """ temp = [] select = [0] * len(chart) for i in range(len(chart[0])): @@ -128,9 +128,9 @@ def selection(chart, prime_implicants): def prime_implicant_chart(prime_implicants, binary): """ - >>> prime_implicant_chart(['0.00.01.5'],['0.00.01.5']) - [[1]] - """ + >>> prime_implicant_chart(['0.00.01.5'],['0.00.01.5']) + [[1]] + """ chart = [[0 for x in range(len(binary))] for x in range(len(prime_implicants))] for i in range(len(prime_implicants)): count = prime_implicants[i].count("_") diff --git a/ciphers/xor_cipher.py b/ciphers/xor_cipher.py index 7d8dbe41fdea..17e45413acd5 100644 --- a/ciphers/xor_cipher.py +++ b/ciphers/xor_cipher.py @@ -1,40 +1,40 @@ """ - author: Christian Bender - date: 21.12.2017 - class: XORCipher - - This class implements the XOR-cipher algorithm and provides - some useful methods for encrypting and decrypting strings and - files. - - Overview about methods - - - encrypt : list of char - - decrypt : list of char - - encrypt_string : str - - decrypt_string : str - - encrypt_file : boolean - - decrypt_file : boolean + author: Christian Bender + date: 21.12.2017 + class: XORCipher + + This class implements the XOR-cipher algorithm and provides + some useful methods for encrypting and decrypting strings and + files. + + Overview about methods + + - encrypt : list of char + - decrypt : list of char + - encrypt_string : str + - decrypt_string : str + - encrypt_file : boolean + - decrypt_file : boolean """ class XORCipher(object): def __init__(self, key=0): """ - simple constructor that receives a key or uses - default key = 0 - """ + simple constructor that receives a key or uses + default key = 0 + """ # private field self.__key = key def encrypt(self, content, key): """ - input: 'content' of type string and 'key' of type int - output: encrypted string 'content' as a list of chars - if key not passed the method uses the key by the constructor. - otherwise key = 1 - """ + input: 'content' of type string and 'key' of type int + output: encrypted string 'content' as a list of chars + if key not passed the method uses the key by the constructor. + otherwise key = 1 + """ # precondition assert isinstance(key, int) and isinstance(content, str) @@ -55,11 +55,11 @@ def encrypt(self, content, key): def decrypt(self, content, key): """ - input: 'content' of type list and 'key' of type int - output: decrypted string 'content' as a list of chars - if key not passed the method uses the key by the constructor. - otherwise key = 1 - """ + input: 'content' of type list and 'key' of type int + output: decrypted string 'content' as a list of chars + if key not passed the method uses the key by the constructor. + otherwise key = 1 + """ # precondition assert isinstance(key, int) and isinstance(content, list) @@ -80,11 +80,11 @@ def decrypt(self, content, key): def encrypt_string(self, content, key=0): """ - input: 'content' of type string and 'key' of type int - output: encrypted string 'content' - if key not passed the method uses the key by the constructor. - otherwise key = 1 - """ + input: 'content' of type string and 'key' of type int + output: encrypted string 'content' + if key not passed the method uses the key by the constructor. + otherwise key = 1 + """ # precondition assert isinstance(key, int) and isinstance(content, str) @@ -105,11 +105,11 @@ def encrypt_string(self, content, key=0): def decrypt_string(self, content, key=0): """ - input: 'content' of type string and 'key' of type int - output: decrypted string 'content' - if key not passed the method uses the key by the constructor. - otherwise key = 1 - """ + input: 'content' of type string and 'key' of type int + output: decrypted string 'content' + if key not passed the method uses the key by the constructor. + otherwise key = 1 + """ # precondition assert isinstance(key, int) and isinstance(content, str) @@ -130,12 +130,12 @@ def decrypt_string(self, content, key=0): def encrypt_file(self, file, key=0): """ - input: filename (str) and a key (int) - output: returns true if encrypt process was - successful otherwise false - if key not passed the method uses the key by the constructor. - otherwise key = 1 - """ + input: filename (str) and a key (int) + output: returns true if encrypt process was + successful otherwise false + if key not passed the method uses the key by the constructor. + otherwise key = 1 + """ # precondition assert isinstance(file, str) and isinstance(key, int) @@ -155,12 +155,12 @@ def encrypt_file(self, file, key=0): def decrypt_file(self, file, key): """ - input: filename (str) and a key (int) - output: returns true if decrypt process was - successful otherwise false - if key not passed the method uses the key by the constructor. - otherwise key = 1 - """ + input: filename (str) and a key (int) + output: returns true if decrypt process was + successful otherwise false + if key not passed the method uses the key by the constructor. + otherwise key = 1 + """ # precondition assert isinstance(file, str) and isinstance(key, int) @@ -195,11 +195,11 @@ def decrypt_file(self, file, key): # print(crypt.decrypt_string(crypt.encrypt_string("hallo welt",key),key)) # if (crypt.encrypt_file("test.txt",key)): -# print("encrypt successful") +# print("encrypt successful") # else: -# print("encrypt unsuccessful") +# print("encrypt unsuccessful") # if (crypt.decrypt_file("encrypt.out",key)): -# print("decrypt successful") +# print("decrypt successful") # else: -# print("decrypt unsuccessful") +# print("decrypt unsuccessful") diff --git a/compression/peak_signal_to_noise_ratio.py b/compression/peak_signal_to_noise_ratio.py index 418832a8127c..5853aace8f96 100644 --- a/compression/peak_signal_to_noise_ratio.py +++ b/compression/peak_signal_to_noise_ratio.py @@ -1,5 +1,5 @@ """ - Peak signal-to-noise ratio - PSNR - https://en.wikipedia.org/wiki/Peak_signal-to-noise_ratio + Peak signal-to-noise ratio - PSNR - https://en.wikipedia.org/wiki/Peak_signal-to-noise_ratio Soruce: https://tutorials.techonical.com/how-to-calculate-psnr-value-of-two-images-using-python/ """ diff --git a/data_structures/stacks/infix_to_prefix_conversion.py b/data_structures/stacks/infix_to_prefix_conversion.py index 4f0e1ab8adfa..5aa41a119528 100644 --- a/data_structures/stacks/infix_to_prefix_conversion.py +++ b/data_structures/stacks/infix_to_prefix_conversion.py @@ -11,7 +11,7 @@ a | + | cb^a | | cb^a+ - a+b^c (Infix) -> +a^bc (Prefix) + a+b^c (Infix) -> +a^bc (Prefix) """ diff --git a/data_structures/stacks/postfix_evaluation.py b/data_structures/stacks/postfix_evaluation.py index 0f3d5c76d6a3..22836cdcfcb1 100644 --- a/data_structures/stacks/postfix_evaluation.py +++ b/data_structures/stacks/postfix_evaluation.py @@ -14,7 +14,7 @@ | pop(5) | + | push(5+54) | 59 - Result = 59 + Result = 59 """ import operator as op diff --git a/dynamic_programming/rod_cutting.py b/dynamic_programming/rod_cutting.py index 3a1d55320d7b..26af71915833 100644 --- a/dynamic_programming/rod_cutting.py +++ b/dynamic_programming/rod_cutting.py @@ -13,28 +13,28 @@ def naive_cut_rod_recursive(n: int, prices: list): """ - Solves the rod-cutting problem via naively without using the benefit of dynamic programming. - The results is the same sub-problems are solved several times leading to an exponential runtime + Solves the rod-cutting problem via naively without using the benefit of dynamic programming. + The results is the same sub-problems are solved several times leading to an exponential runtime - Runtime: O(2^n) + Runtime: O(2^n) - Arguments - ------- - n: int, the length of the rod - prices: list, the prices for each piece of rod. ``p[i-i]`` is the - price for a rod of length ``i`` + Arguments + ------- + n: int, the length of the rod + prices: list, the prices for each piece of rod. ``p[i-i]`` is the + price for a rod of length ``i`` - Returns - ------- - The maximum revenue obtainable for a rod of length n given the list of prices for each piece. + Returns + ------- + The maximum revenue obtainable for a rod of length n given the list of prices for each piece. - Examples - -------- - >>> naive_cut_rod_recursive(4, [1, 5, 8, 9]) - 10 - >>> naive_cut_rod_recursive(10, [1, 5, 8, 9, 10, 17, 17, 20, 24, 30]) - 30 - """ + Examples + -------- + >>> naive_cut_rod_recursive(4, [1, 5, 8, 9]) + 10 + >>> naive_cut_rod_recursive(10, [1, 5, 8, 9, 10, 17, 17, 20, 24, 30]) + 30 + """ _enforce_args(n, prices) if n == 0: @@ -50,33 +50,33 @@ def naive_cut_rod_recursive(n: int, prices: list): def top_down_cut_rod(n: int, prices: list): """ - Constructs a top-down dynamic programming solution for the rod-cutting problem - via memoization. This function serves as a wrapper for _top_down_cut_rod_recursive - - Runtime: O(n^2) - - Arguments - -------- - n: int, the length of the rod - prices: list, the prices for each piece of rod. ``p[i-i]`` is the - price for a rod of length ``i`` - - Note - ---- - For convenience and because Python's lists using 0-indexing, length(max_rev) = n + 1, - to accommodate for the revenue obtainable from a rod of length 0. - - Returns - ------- - The maximum revenue obtainable for a rod of length n given the list of prices for each piece. - - Examples - ------- - >>> top_down_cut_rod(4, [1, 5, 8, 9]) - 10 - >>> top_down_cut_rod(10, [1, 5, 8, 9, 10, 17, 17, 20, 24, 30]) - 30 - """ + Constructs a top-down dynamic programming solution for the rod-cutting problem + via memoization. This function serves as a wrapper for _top_down_cut_rod_recursive + + Runtime: O(n^2) + + Arguments + -------- + n: int, the length of the rod + prices: list, the prices for each piece of rod. ``p[i-i]`` is the + price for a rod of length ``i`` + + Note + ---- + For convenience and because Python's lists using 0-indexing, length(max_rev) = n + 1, + to accommodate for the revenue obtainable from a rod of length 0. + + Returns + ------- + The maximum revenue obtainable for a rod of length n given the list of prices for each piece. + + Examples + ------- + >>> top_down_cut_rod(4, [1, 5, 8, 9]) + 10 + >>> top_down_cut_rod(10, [1, 5, 8, 9, 10, 17, 17, 20, 24, 30]) + 30 + """ _enforce_args(n, prices) max_rev = [float("-inf") for _ in range(n + 1)] return _top_down_cut_rod_recursive(n, prices, max_rev) @@ -84,23 +84,23 @@ def top_down_cut_rod(n: int, prices: list): def _top_down_cut_rod_recursive(n: int, prices: list, max_rev: list): """ - Constructs a top-down dynamic programming solution for the rod-cutting problem - via memoization. - - Runtime: O(n^2) - - Arguments - -------- - n: int, the length of the rod - prices: list, the prices for each piece of rod. ``p[i-i]`` is the - price for a rod of length ``i`` - max_rev: list, the computed maximum revenue for a piece of rod. - ``max_rev[i]`` is the maximum revenue obtainable for a rod of length ``i`` - - Returns - ------- - The maximum revenue obtainable for a rod of length n given the list of prices for each piece. - """ + Constructs a top-down dynamic programming solution for the rod-cutting problem + via memoization. + + Runtime: O(n^2) + + Arguments + -------- + n: int, the length of the rod + prices: list, the prices for each piece of rod. ``p[i-i]`` is the + price for a rod of length ``i`` + max_rev: list, the computed maximum revenue for a piece of rod. + ``max_rev[i]`` is the maximum revenue obtainable for a rod of length ``i`` + + Returns + ------- + The maximum revenue obtainable for a rod of length n given the list of prices for each piece. + """ if max_rev[n] >= 0: return max_rev[n] elif n == 0: @@ -120,28 +120,28 @@ def _top_down_cut_rod_recursive(n: int, prices: list, max_rev: list): def bottom_up_cut_rod(n: int, prices: list): """ - Constructs a bottom-up dynamic programming solution for the rod-cutting problem - - Runtime: O(n^2) - - Arguments - ---------- - n: int, the maximum length of the rod. - prices: list, the prices for each piece of rod. ``p[i-i]`` is the - price for a rod of length ``i`` - - Returns - ------- - The maximum revenue obtainable from cutting a rod of length n given - the prices for each piece of rod p. - - Examples - ------- - >>> bottom_up_cut_rod(4, [1, 5, 8, 9]) - 10 - >>> bottom_up_cut_rod(10, [1, 5, 8, 9, 10, 17, 17, 20, 24, 30]) - 30 - """ + Constructs a bottom-up dynamic programming solution for the rod-cutting problem + + Runtime: O(n^2) + + Arguments + ---------- + n: int, the maximum length of the rod. + prices: list, the prices for each piece of rod. ``p[i-i]`` is the + price for a rod of length ``i`` + + Returns + ------- + The maximum revenue obtainable from cutting a rod of length n given + the prices for each piece of rod p. + + Examples + ------- + >>> bottom_up_cut_rod(4, [1, 5, 8, 9]) + 10 + >>> bottom_up_cut_rod(10, [1, 5, 8, 9, 10, 17, 17, 20, 24, 30]) + 30 + """ _enforce_args(n, prices) # length(max_rev) = n + 1, to accommodate for the revenue obtainable from a rod of length 0. @@ -160,15 +160,15 @@ def bottom_up_cut_rod(n: int, prices: list): def _enforce_args(n: int, prices: list): """ - Basic checks on the arguments to the rod-cutting algorithms + Basic checks on the arguments to the rod-cutting algorithms - n: int, the length of the rod - prices: list, the price list for each piece of rod. + n: int, the length of the rod + prices: list, the price list for each piece of rod. - Throws ValueError: + Throws ValueError: - if n is negative or there are fewer items in the price list than the length of the rod - """ + if n is negative or there are fewer items in the price list than the length of the rod + """ if n < 0: raise ValueError(f"n must be greater than or equal to 0. Got n = {n}") diff --git a/other/magicdiamondpattern.py b/other/magicdiamondpattern.py index 9b434a7b6e0b..6de5046c9f18 100644 --- a/other/magicdiamondpattern.py +++ b/other/magicdiamondpattern.py @@ -3,9 +3,9 @@ # Function to print upper half of diamond (pyramid) def floyd(n): """ - Parameters: - n : size of pattern - """ + Parameters: + n : size of pattern + """ for i in range(0, n): for j in range(0, n - i - 1): # printing spaces print(" ", end="") @@ -17,9 +17,9 @@ def floyd(n): # Function to print lower half of diamond (pyramid) def reverse_floyd(n): """ - Parameters: - n : size of pattern - """ + Parameters: + n : size of pattern + """ for i in range(n, 0, -1): for j in range(i, 0, -1): # printing stars print("* ", end="") @@ -31,9 +31,9 @@ def reverse_floyd(n): # Function to print complete diamond pattern of "*" def pretty_print(n): """ - Parameters: - n : size of pattern - """ + Parameters: + n : size of pattern + """ if n <= 0: print(" ... .... nothing printing :(") return diff --git a/other/nested_brackets.py b/other/nested_brackets.py index 011e94b92928..c712bc21b5ce 100644 --- a/other/nested_brackets.py +++ b/other/nested_brackets.py @@ -3,9 +3,9 @@ brackets are properly nested. A sequence of brackets s is considered properly nested if any of the following conditions are true: - - s is empty - - s has the form (U) or [U] or {U} where U is a properly nested string - - s has the form VW where V and W are properly nested strings + - s is empty + - s has the form (U) or [U] or {U} where U is a properly nested string + - s has the form VW where V and W are properly nested strings For example, the string "()()[()]" is properly nested but "[(()]" is not. diff --git a/project_euler/problem_27/problem_27_sol1.py b/project_euler/problem_27/problem_27_sol1.py index dbd07f81b713..84b007a0bc88 100644 --- a/project_euler/problem_27/problem_27_sol1.py +++ b/project_euler/problem_27/problem_27_sol1.py @@ -39,17 +39,17 @@ def is_prime(k: int) -> bool: def solution(a_limit: int, b_limit: int) -> int: """ - >>> solution(1000, 1000) - -59231 - >>> solution(200, 1000) - -59231 - >>> solution(200, 200) - -4925 - >>> solution(-1000, 1000) - 0 - >>> solution(-1000, -1000) - 0 - """ + >>> solution(1000, 1000) + -59231 + >>> solution(200, 1000) + -59231 + >>> solution(200, 200) + -4925 + >>> solution(-1000, 1000) + 0 + >>> solution(-1000, -1000) + 0 + """ longest = [0, 0, 0] # length, a, b for a in range((a_limit * -1) + 1, a_limit): for b in range(2, b_limit): diff --git a/searches/interpolation_search.py b/searches/interpolation_search.py index d1873083bf8a..dffaf8d26084 100644 --- a/searches/interpolation_search.py +++ b/searches/interpolation_search.py @@ -113,7 +113,7 @@ def __assert_sorted(collection): import sys """ - user_input = input('Enter numbers separated by comma:\n').strip() + user_input = input('Enter numbers separated by comma:\n').strip() collection = [int(item) for item in user_input.split(',')] try: __assert_sorted(collection) @@ -122,7 +122,7 @@ def __assert_sorted(collection): target_input = input('Enter a single number to be found in the list:\n') target = int(target_input) - """ + """ debug = 0 if debug == 1: From 9a894ebc521a8b09613737905608dbfd8be5faf4 Mon Sep 17 00:00:00 2001 From: Sarath Kaul Date: Sun, 17 Nov 2019 17:27:26 +0530 Subject: [PATCH 0364/1071] Word Occurence Script Added (#1576) * Word Occurence Script Added * Word Occurence Script Updated * Added doctest using collections.Counter https://docs.python.org/3/library/collections.html#collections.Counter --- strings/word_occurence.py | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 strings/word_occurence.py diff --git a/strings/word_occurence.py b/strings/word_occurence.py new file mode 100644 index 000000000000..c4eb923d6bc8 --- /dev/null +++ b/strings/word_occurence.py @@ -0,0 +1,23 @@ +# Created by sarathkaul on 17/11/19 +from collections import defaultdict + + +def word_occurence(sentence: str) -> dict: + """ + >>> from collections import Counter + >>> SENTENCE = "a b A b c b d b d e f e g e h e i e j e 0" + >>> occurence_dict = word_occurence(SENTENCE) + >>> all(occurence_dict[word] == count for word, count + ... in Counter(SENTENCE.split()).items()) + True + """ + occurence = defaultdict(int) + # Creating a dictionary containing count of each word + for word in sentence.split(" "): + occurence[word] += 1 + return occurence + + +if __name__ == "__main__": + for word, count in word_occurence("INPUT STRING").items(): + print(f"{word}: {count}") From 5616fa9e62a7fc85f2e195db55ee086b687cbb48 Mon Sep 17 00:00:00 2001 From: Mantas Zimnickas Date: Sun, 17 Nov 2019 20:37:58 +0200 Subject: [PATCH 0365/1071] Add pytest-cov (#1578) * Add pytest-cov Also added coverage report in .travis.yml file. * updating DIRECTORY.md * Sort by missing statements * sort = Cover --- .coveragerc | 4 ++++ .travis.yml | 2 +- DIRECTORY.md | 1 + requirements.txt | 1 + 4 files changed, 7 insertions(+), 1 deletion(-) create mode 100644 .coveragerc diff --git a/.coveragerc b/.coveragerc new file mode 100644 index 000000000000..f7e6eb212bc8 --- /dev/null +++ b/.coveragerc @@ -0,0 +1,4 @@ +[report] +sort = Cover +omit = + .env/* diff --git a/.travis.yml b/.travis.yml index 6884d9addba0..6852b84915f9 100644 --- a/.travis.yml +++ b/.travis.yml @@ -9,6 +9,6 @@ before_script: script: - scripts/validate_filenames.py # no uppercase, no spaces, in a directory - mypy --ignore-missing-imports . - - pytest . --doctest-modules + - pytest --doctest-modules --cov-report=term-missing:skip-covered --cov=. . after_success: - scripts/build_directory_md.py 2>&1 | tee DIRECTORY.md diff --git a/DIRECTORY.md b/DIRECTORY.md index 15a906332d22..6e64f034df62 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -521,6 +521,7 @@ * [Naive String Search](https://github.com/TheAlgorithms/Python/blob/master/strings/naive_string_search.py) * [Rabin Karp](https://github.com/TheAlgorithms/Python/blob/master/strings/rabin_karp.py) * [Remove Duplicate](https://github.com/TheAlgorithms/Python/blob/master/strings/remove_duplicate.py) + * [Word Occurence](https://github.com/TheAlgorithms/Python/blob/master/strings/word_occurence.py) ## Traversals * [Binary Tree Traversals](https://github.com/TheAlgorithms/Python/blob/master/traversals/binary_tree_traversals.py) diff --git a/requirements.txt b/requirements.txt index 824f534a245f..1f4b11fc3ea5 100644 --- a/requirements.txt +++ b/requirements.txt @@ -9,6 +9,7 @@ opencv-python pandas pillow pytest +pytest-cov requests scikit-fuzzy sklearn From 12f69a86f56845f6df96c9991c856f665169f8a6 Mon Sep 17 00:00:00 2001 From: Mantas Zimnickas Date: Sun, 17 Nov 2019 20:38:48 +0200 Subject: [PATCH 0366/1071] Remove code with side effects from main (#1577) * Remove code with side effects from main When running tests withy pytest, some modules execute code in main scope and open plot or browser windows. Moves such code under `if __name__ == "__main__"`. * fixup! Format Python code with psf/black push --- fuzzy_logic/fuzzy_operations.py | 182 ++--- machine_learning/polymonial_regression.py | 9 +- neural_network/gan.py | 792 +++++++++++----------- web_programming/crawl_google_results.py | 26 +- 4 files changed, 510 insertions(+), 499 deletions(-) diff --git a/fuzzy_logic/fuzzy_operations.py b/fuzzy_logic/fuzzy_operations.py index ba4a8a22a4d1..cb870e8d9e3b 100644 --- a/fuzzy_logic/fuzzy_operations.py +++ b/fuzzy_logic/fuzzy_operations.py @@ -6,97 +6,97 @@ Python: - 3.5 """ -# Create universe of discourse in python using linspace () import numpy as np - -X = np.linspace(start=0, stop=75, num=75, endpoint=True, retstep=False) - -# Create two fuzzy sets by defining any membership function (trapmf(), gbellmf(),gaussmf(), etc). import skfuzzy as fuzz -abc1 = [0, 25, 50] -abc2 = [25, 50, 75] -young = fuzz.membership.trimf(X, abc1) -middle_aged = fuzz.membership.trimf(X, abc2) - -# Compute the different operations using inbuilt functions. -one = np.ones(75) -zero = np.zeros((75,)) -# 1. Union = max(µA(x), µB(x)) -union = fuzz.fuzzy_or(X, young, X, middle_aged)[1] -# 2. Intersection = min(µA(x), µB(x)) -intersection = fuzz.fuzzy_and(X, young, X, middle_aged)[1] -# 3. Complement (A) = (1- min(µA(x)) -complement_a = fuzz.fuzzy_not(young) -# 4. Difference (A/B) = min(µA(x),(1- µB(x))) -difference = fuzz.fuzzy_and(X, young, X, fuzz.fuzzy_not(middle_aged)[1])[1] -# 5. Algebraic Sum = [µA(x) + µB(x) – (µA(x) * µB(x))] -alg_sum = young + middle_aged - (young * middle_aged) -# 6. Algebraic Product = (µA(x) * µB(x)) -alg_product = young * middle_aged -# 7. Bounded Sum = min[1,(µA(x), µB(x))] -bdd_sum = fuzz.fuzzy_and(X, one, X, young + middle_aged)[1] -# 8. Bounded difference = min[0,(µA(x), µB(x))] -bdd_difference = fuzz.fuzzy_or(X, zero, X, young - middle_aged)[1] - -# max-min composition -# max-product composition - - -# Plot each set A, set B and each operation result using plot() and subplot(). -import matplotlib.pyplot as plt - -plt.figure() - -plt.subplot(4, 3, 1) -plt.plot(X, young) -plt.title("Young") -plt.grid(True) - -plt.subplot(4, 3, 2) -plt.plot(X, middle_aged) -plt.title("Middle aged") -plt.grid(True) - -plt.subplot(4, 3, 3) -plt.plot(X, union) -plt.title("union") -plt.grid(True) - -plt.subplot(4, 3, 4) -plt.plot(X, intersection) -plt.title("intersection") -plt.grid(True) - -plt.subplot(4, 3, 5) -plt.plot(X, complement_a) -plt.title("complement_a") -plt.grid(True) - -plt.subplot(4, 3, 6) -plt.plot(X, difference) -plt.title("difference a/b") -plt.grid(True) - -plt.subplot(4, 3, 7) -plt.plot(X, alg_sum) -plt.title("alg_sum") -plt.grid(True) - -plt.subplot(4, 3, 8) -plt.plot(X, alg_product) -plt.title("alg_product") -plt.grid(True) - -plt.subplot(4, 3, 9) -plt.plot(X, bdd_sum) -plt.title("bdd_sum") -plt.grid(True) - -plt.subplot(4, 3, 10) -plt.plot(X, bdd_difference) -plt.title("bdd_difference") -plt.grid(True) - -plt.subplots_adjust(hspace=0.5) -plt.show() + +if __name__ == "__main__": + # Create universe of discourse in python using linspace () + X = np.linspace(start=0, stop=75, num=75, endpoint=True, retstep=False) + + # Create two fuzzy sets by defining any membership function (trapmf(), gbellmf(),gaussmf(), etc). + abc1 = [0, 25, 50] + abc2 = [25, 50, 75] + young = fuzz.membership.trimf(X, abc1) + middle_aged = fuzz.membership.trimf(X, abc2) + + # Compute the different operations using inbuilt functions. + one = np.ones(75) + zero = np.zeros((75,)) + # 1. Union = max(µA(x), µB(x)) + union = fuzz.fuzzy_or(X, young, X, middle_aged)[1] + # 2. Intersection = min(µA(x), µB(x)) + intersection = fuzz.fuzzy_and(X, young, X, middle_aged)[1] + # 3. Complement (A) = (1- min(µA(x)) + complement_a = fuzz.fuzzy_not(young) + # 4. Difference (A/B) = min(µA(x),(1- µB(x))) + difference = fuzz.fuzzy_and(X, young, X, fuzz.fuzzy_not(middle_aged)[1])[1] + # 5. Algebraic Sum = [µA(x) + µB(x) – (µA(x) * µB(x))] + alg_sum = young + middle_aged - (young * middle_aged) + # 6. Algebraic Product = (µA(x) * µB(x)) + alg_product = young * middle_aged + # 7. Bounded Sum = min[1,(µA(x), µB(x))] + bdd_sum = fuzz.fuzzy_and(X, one, X, young + middle_aged)[1] + # 8. Bounded difference = min[0,(µA(x), µB(x))] + bdd_difference = fuzz.fuzzy_or(X, zero, X, young - middle_aged)[1] + + # max-min composition + # max-product composition + + # Plot each set A, set B and each operation result using plot() and subplot(). + import matplotlib.pyplot as plt + + plt.figure() + + plt.subplot(4, 3, 1) + plt.plot(X, young) + plt.title("Young") + plt.grid(True) + + plt.subplot(4, 3, 2) + plt.plot(X, middle_aged) + plt.title("Middle aged") + plt.grid(True) + + plt.subplot(4, 3, 3) + plt.plot(X, union) + plt.title("union") + plt.grid(True) + + plt.subplot(4, 3, 4) + plt.plot(X, intersection) + plt.title("intersection") + plt.grid(True) + + plt.subplot(4, 3, 5) + plt.plot(X, complement_a) + plt.title("complement_a") + plt.grid(True) + + plt.subplot(4, 3, 6) + plt.plot(X, difference) + plt.title("difference a/b") + plt.grid(True) + + plt.subplot(4, 3, 7) + plt.plot(X, alg_sum) + plt.title("alg_sum") + plt.grid(True) + + plt.subplot(4, 3, 8) + plt.plot(X, alg_product) + plt.title("alg_product") + plt.grid(True) + + plt.subplot(4, 3, 9) + plt.plot(X, bdd_sum) + plt.title("bdd_sum") + plt.grid(True) + + plt.subplot(4, 3, 10) + plt.plot(X, bdd_difference) + plt.title("bdd_difference") + plt.grid(True) + + plt.subplots_adjust(hspace=0.5) + plt.show() diff --git a/machine_learning/polymonial_regression.py b/machine_learning/polymonial_regression.py index 0d9db0f7578a..7b080715b762 100644 --- a/machine_learning/polymonial_regression.py +++ b/machine_learning/polymonial_regression.py @@ -36,8 +36,9 @@ def viz_polymonial(): return -viz_polymonial() +if __name__ == "__main__": + viz_polymonial() -# Predicting a new result with Polymonial Regression -pol_reg.predict(poly_reg.fit_transform([[5.5]])) -# output should be 132148.43750003 + # Predicting a new result with Polymonial Regression + pol_reg.predict(poly_reg.fit_transform([[5.5]])) + # output should be 132148.43750003 diff --git a/neural_network/gan.py b/neural_network/gan.py index 76f46314c4ba..deb062c48dc7 100644 --- a/neural_network/gan.py +++ b/neural_network/gan.py @@ -59,440 +59,448 @@ def plot(samples): return fig -# 1. Load Data and declare hyper -print("--------- Load Data ----------") -mnist = input_data.read_data_sets("MNIST_data", one_hot=False) -temp = mnist.test -images, labels = temp.images, temp.labels -images, labels = shuffle(np.asarray(images), np.asarray(labels)) -num_epoch = 10 -learing_rate = 0.00009 -G_input = 100 -hidden_input, hidden_input2, hidden_input3 = 128, 256, 346 -hidden_input4, hidden_input5, hidden_input6 = 480, 560, 686 - - -print("--------- Declare Hyper Parameters ----------") -# 2. Declare Weights -D_W1 = ( - np.random.normal(size=(784, hidden_input), scale=(1.0 / np.sqrt(784 / 2.0))) * 0.002 -) -# D_b1 = np.random.normal(size=(128),scale=(1. / np.sqrt(128 / 2.))) *0.002 -D_b1 = np.zeros(hidden_input) - -D_W2 = ( - np.random.normal(size=(hidden_input, 1), scale=(1.0 / np.sqrt(hidden_input / 2.0))) - * 0.002 -) -# D_b2 = np.random.normal(size=(1),scale=(1. / np.sqrt(1 / 2.))) *0.002 -D_b2 = np.zeros(1) - - -G_W1 = ( - np.random.normal(size=(G_input, hidden_input), scale=(1.0 / np.sqrt(G_input / 2.0))) - * 0.002 -) -# G_b1 = np.random.normal(size=(128),scale=(1. / np.sqrt(128 / 2.))) *0.002 -G_b1 = np.zeros(hidden_input) - -G_W2 = ( - np.random.normal( - size=(hidden_input, hidden_input2), scale=(1.0 / np.sqrt(hidden_input / 2.0)) +if __name__ == "__main__": + # 1. Load Data and declare hyper + print("--------- Load Data ----------") + mnist = input_data.read_data_sets("MNIST_data", one_hot=False) + temp = mnist.test + images, labels = temp.images, temp.labels + images, labels = shuffle(np.asarray(images), np.asarray(labels)) + num_epoch = 10 + learing_rate = 0.00009 + G_input = 100 + hidden_input, hidden_input2, hidden_input3 = 128, 256, 346 + hidden_input4, hidden_input5, hidden_input6 = 480, 560, 686 + + print("--------- Declare Hyper Parameters ----------") + # 2. Declare Weights + D_W1 = ( + np.random.normal(size=(784, hidden_input), scale=(1.0 / np.sqrt(784 / 2.0))) + * 0.002 ) - * 0.002 -) -# G_b1 = np.random.normal(size=(128),scale=(1. / np.sqrt(128 / 2.))) *0.002 -G_b2 = np.zeros(hidden_input2) - -G_W3 = ( - np.random.normal( - size=(hidden_input2, hidden_input3), scale=(1.0 / np.sqrt(hidden_input2 / 2.0)) - ) - * 0.002 -) -# G_b1 = np.random.normal(size=(128),scale=(1. / np.sqrt(128 / 2.))) *0.002 -G_b3 = np.zeros(hidden_input3) - -G_W4 = ( - np.random.normal( - size=(hidden_input3, hidden_input4), scale=(1.0 / np.sqrt(hidden_input3 / 2.0)) - ) - * 0.002 -) -# G_b1 = np.random.normal(size=(128),scale=(1. / np.sqrt(128 / 2.))) *0.002 -G_b4 = np.zeros(hidden_input4) - -G_W5 = ( - np.random.normal( - size=(hidden_input4, hidden_input5), scale=(1.0 / np.sqrt(hidden_input4 / 2.0)) + # D_b1 = np.random.normal(size=(128),scale=(1. / np.sqrt(128 / 2.))) *0.002 + D_b1 = np.zeros(hidden_input) + + D_W2 = ( + np.random.normal( + size=(hidden_input, 1), scale=(1.0 / np.sqrt(hidden_input / 2.0)) + ) + * 0.002 ) - * 0.002 -) -# G_b1 = np.random.normal(size=(128),scale=(1. / np.sqrt(128 / 2.))) *0.002 -G_b5 = np.zeros(hidden_input5) - -G_W6 = ( - np.random.normal( - size=(hidden_input5, hidden_input6), scale=(1.0 / np.sqrt(hidden_input5 / 2.0)) + # D_b2 = np.random.normal(size=(1),scale=(1. / np.sqrt(1 / 2.))) *0.002 + D_b2 = np.zeros(1) + + G_W1 = ( + np.random.normal( + size=(G_input, hidden_input), scale=(1.0 / np.sqrt(G_input / 2.0)) + ) + * 0.002 ) - * 0.002 -) -# G_b1 = np.random.normal(size=(128),scale=(1. / np.sqrt(128 / 2.))) *0.002 -G_b6 = np.zeros(hidden_input6) - -G_W7 = ( - np.random.normal( - size=(hidden_input6, 784), scale=(1.0 / np.sqrt(hidden_input6 / 2.0)) + # G_b1 = np.random.normal(size=(128),scale=(1. / np.sqrt(128 / 2.))) *0.002 + G_b1 = np.zeros(hidden_input) + + G_W2 = ( + np.random.normal( + size=(hidden_input, hidden_input2), + scale=(1.0 / np.sqrt(hidden_input / 2.0)), + ) + * 0.002 ) - * 0.002 -) -# G_b2 = np.random.normal(size=(784),scale=(1. / np.sqrt(784 / 2.))) *0.002 -G_b7 = np.zeros(784) - -# 3. For Adam Optimzier -v1, m1 = 0, 0 -v2, m2 = 0, 0 -v3, m3 = 0, 0 -v4, m4 = 0, 0 - -v5, m5 = 0, 0 -v6, m6 = 0, 0 -v7, m7 = 0, 0 -v8, m8 = 0, 0 -v9, m9 = 0, 0 -v10, m10 = 0, 0 -v11, m11 = 0, 0 -v12, m12 = 0, 0 - -v13, m13 = 0, 0 -v14, m14 = 0, 0 - -v15, m15 = 0, 0 -v16, m16 = 0, 0 - -v17, m17 = 0, 0 -v18, m18 = 0, 0 - - -beta_1, beta_2, eps = 0.9, 0.999, 0.00000001 - -print("--------- Started Training ----------") -for iter in range(num_epoch): - - random_int = np.random.randint(len(images) - 5) - current_image = np.expand_dims(images[random_int], axis=0) - - # Func: Generate The first Fake Data - Z = np.random.uniform(-1.0, 1.0, size=[1, G_input]) - Gl1 = Z.dot(G_W1) + G_b1 - Gl1A = arctan(Gl1) - Gl2 = Gl1A.dot(G_W2) + G_b2 - Gl2A = ReLu(Gl2) - Gl3 = Gl2A.dot(G_W3) + G_b3 - Gl3A = arctan(Gl3) - - Gl4 = Gl3A.dot(G_W4) + G_b4 - Gl4A = ReLu(Gl4) - Gl5 = Gl4A.dot(G_W5) + G_b5 - Gl5A = tanh(Gl5) - Gl6 = Gl5A.dot(G_W6) + G_b6 - Gl6A = ReLu(Gl6) - Gl7 = Gl6A.dot(G_W7) + G_b7 - - current_fake_data = log(Gl7) - - # Func: Forward Feed for Real data - Dl1_r = current_image.dot(D_W1) + D_b1 - Dl1_rA = ReLu(Dl1_r) - Dl2_r = Dl1_rA.dot(D_W2) + D_b2 - Dl2_rA = log(Dl2_r) - - # Func: Forward Feed for Fake Data - Dl1_f = current_fake_data.dot(D_W1) + D_b1 - Dl1_fA = ReLu(Dl1_f) - Dl2_f = Dl1_fA.dot(D_W2) + D_b2 - Dl2_fA = log(Dl2_f) - - # Func: Cost D - D_cost = -np.log(Dl2_rA) + np.log(1.0 - Dl2_fA) - - # Func: Gradient - grad_f_w2_part_1 = 1 / (1.0 - Dl2_fA) - grad_f_w2_part_2 = d_log(Dl2_f) - grad_f_w2_part_3 = Dl1_fA - grad_f_w2 = grad_f_w2_part_3.T.dot(grad_f_w2_part_1 * grad_f_w2_part_2) - grad_f_b2 = grad_f_w2_part_1 * grad_f_w2_part_2 - - grad_f_w1_part_1 = (grad_f_w2_part_1 * grad_f_w2_part_2).dot(D_W2.T) - grad_f_w1_part_2 = d_ReLu(Dl1_f) - grad_f_w1_part_3 = current_fake_data - grad_f_w1 = grad_f_w1_part_3.T.dot(grad_f_w1_part_1 * grad_f_w1_part_2) - grad_f_b1 = grad_f_w1_part_1 * grad_f_w1_part_2 - - grad_r_w2_part_1 = -1 / Dl2_rA - grad_r_w2_part_2 = d_log(Dl2_r) - grad_r_w2_part_3 = Dl1_rA - grad_r_w2 = grad_r_w2_part_3.T.dot(grad_r_w2_part_1 * grad_r_w2_part_2) - grad_r_b2 = grad_r_w2_part_1 * grad_r_w2_part_2 - - grad_r_w1_part_1 = (grad_r_w2_part_1 * grad_r_w2_part_2).dot(D_W2.T) - grad_r_w1_part_2 = d_ReLu(Dl1_r) - grad_r_w1_part_3 = current_image - grad_r_w1 = grad_r_w1_part_3.T.dot(grad_r_w1_part_1 * grad_r_w1_part_2) - grad_r_b1 = grad_r_w1_part_1 * grad_r_w1_part_2 - - grad_w1 = grad_f_w1 + grad_r_w1 - grad_b1 = grad_f_b1 + grad_r_b1 - - grad_w2 = grad_f_w2 + grad_r_w2 - grad_b2 = grad_f_b2 + grad_r_b2 - - # ---- Update Gradient ---- - m1 = beta_1 * m1 + (1 - beta_1) * grad_w1 - v1 = beta_2 * v1 + (1 - beta_2) * grad_w1 ** 2 - - m2 = beta_1 * m2 + (1 - beta_1) * grad_b1 - v2 = beta_2 * v2 + (1 - beta_2) * grad_b1 ** 2 - - m3 = beta_1 * m3 + (1 - beta_1) * grad_w2 - v3 = beta_2 * v3 + (1 - beta_2) * grad_w2 ** 2 - - m4 = beta_1 * m4 + (1 - beta_1) * grad_b2 - v4 = beta_2 * v4 + (1 - beta_2) * grad_b2 ** 2 - - D_W1 = D_W1 - (learing_rate / (np.sqrt(v1 / (1 - beta_2)) + eps)) * ( - m1 / (1 - beta_1) + # G_b1 = np.random.normal(size=(128),scale=(1. / np.sqrt(128 / 2.))) *0.002 + G_b2 = np.zeros(hidden_input2) + + G_W3 = ( + np.random.normal( + size=(hidden_input2, hidden_input3), + scale=(1.0 / np.sqrt(hidden_input2 / 2.0)), + ) + * 0.002 ) - D_b1 = D_b1 - (learing_rate / (np.sqrt(v2 / (1 - beta_2)) + eps)) * ( - m2 / (1 - beta_1) + # G_b1 = np.random.normal(size=(128),scale=(1. / np.sqrt(128 / 2.))) *0.002 + G_b3 = np.zeros(hidden_input3) + + G_W4 = ( + np.random.normal( + size=(hidden_input3, hidden_input4), + scale=(1.0 / np.sqrt(hidden_input3 / 2.0)), + ) + * 0.002 ) + # G_b1 = np.random.normal(size=(128),scale=(1. / np.sqrt(128 / 2.))) *0.002 + G_b4 = np.zeros(hidden_input4) - D_W2 = D_W2 - (learing_rate / (np.sqrt(v3 / (1 - beta_2)) + eps)) * ( - m3 / (1 - beta_1) + G_W5 = ( + np.random.normal( + size=(hidden_input4, hidden_input5), + scale=(1.0 / np.sqrt(hidden_input4 / 2.0)), + ) + * 0.002 ) - D_b2 = D_b2 - (learing_rate / (np.sqrt(v4 / (1 - beta_2)) + eps)) * ( - m4 / (1 - beta_1) + # G_b1 = np.random.normal(size=(128),scale=(1. / np.sqrt(128 / 2.))) *0.002 + G_b5 = np.zeros(hidden_input5) + + G_W6 = ( + np.random.normal( + size=(hidden_input5, hidden_input6), + scale=(1.0 / np.sqrt(hidden_input5 / 2.0)), + ) + * 0.002 ) + # G_b1 = np.random.normal(size=(128),scale=(1. / np.sqrt(128 / 2.))) *0.002 + G_b6 = np.zeros(hidden_input6) - # Func: Forward Feed for G - Z = np.random.uniform(-1.0, 1.0, size=[1, G_input]) - Gl1 = Z.dot(G_W1) + G_b1 - Gl1A = arctan(Gl1) - Gl2 = Gl1A.dot(G_W2) + G_b2 - Gl2A = ReLu(Gl2) - Gl3 = Gl2A.dot(G_W3) + G_b3 - Gl3A = arctan(Gl3) - - Gl4 = Gl3A.dot(G_W4) + G_b4 - Gl4A = ReLu(Gl4) - Gl5 = Gl4A.dot(G_W5) + G_b5 - Gl5A = tanh(Gl5) - Gl6 = Gl5A.dot(G_W6) + G_b6 - Gl6A = ReLu(Gl6) - Gl7 = Gl6A.dot(G_W7) + G_b7 - - current_fake_data = log(Gl7) - - Dl1 = current_fake_data.dot(D_W1) + D_b1 - Dl1_A = ReLu(Dl1) - Dl2 = Dl1_A.dot(D_W2) + D_b2 - Dl2_A = log(Dl2) - - # Func: Cost G - G_cost = -np.log(Dl2_A) - - # Func: Gradient - grad_G_w7_part_1 = ((-1 / Dl2_A) * d_log(Dl2).dot(D_W2.T) * (d_ReLu(Dl1))).dot( - D_W1.T + G_W7 = ( + np.random.normal( + size=(hidden_input6, 784), scale=(1.0 / np.sqrt(hidden_input6 / 2.0)) + ) + * 0.002 ) - grad_G_w7_part_2 = d_log(Gl7) - grad_G_w7_part_3 = Gl6A - grad_G_w7 = grad_G_w7_part_3.T.dot(grad_G_w7_part_1 * grad_G_w7_part_1) - grad_G_b7 = grad_G_w7_part_1 * grad_G_w7_part_2 + # G_b2 = np.random.normal(size=(784),scale=(1. / np.sqrt(784 / 2.))) *0.002 + G_b7 = np.zeros(784) - grad_G_w6_part_1 = (grad_G_w7_part_1 * grad_G_w7_part_2).dot(G_W7.T) - grad_G_w6_part_2 = d_ReLu(Gl6) - grad_G_w6_part_3 = Gl5A - grad_G_w6 = grad_G_w6_part_3.T.dot(grad_G_w6_part_1 * grad_G_w6_part_2) - grad_G_b6 = grad_G_w6_part_1 * grad_G_w6_part_2 + # 3. For Adam Optimzier + v1, m1 = 0, 0 + v2, m2 = 0, 0 + v3, m3 = 0, 0 + v4, m4 = 0, 0 - grad_G_w5_part_1 = (grad_G_w6_part_1 * grad_G_w6_part_2).dot(G_W6.T) - grad_G_w5_part_2 = d_tanh(Gl5) - grad_G_w5_part_3 = Gl4A - grad_G_w5 = grad_G_w5_part_3.T.dot(grad_G_w5_part_1 * grad_G_w5_part_2) - grad_G_b5 = grad_G_w5_part_1 * grad_G_w5_part_2 + v5, m5 = 0, 0 + v6, m6 = 0, 0 + v7, m7 = 0, 0 + v8, m8 = 0, 0 + v9, m9 = 0, 0 + v10, m10 = 0, 0 + v11, m11 = 0, 0 + v12, m12 = 0, 0 - grad_G_w4_part_1 = (grad_G_w5_part_1 * grad_G_w5_part_2).dot(G_W5.T) - grad_G_w4_part_2 = d_ReLu(Gl4) - grad_G_w4_part_3 = Gl3A - grad_G_w4 = grad_G_w4_part_3.T.dot(grad_G_w4_part_1 * grad_G_w4_part_2) - grad_G_b4 = grad_G_w4_part_1 * grad_G_w4_part_2 + v13, m13 = 0, 0 + v14, m14 = 0, 0 - grad_G_w3_part_1 = (grad_G_w4_part_1 * grad_G_w4_part_2).dot(G_W4.T) - grad_G_w3_part_2 = d_arctan(Gl3) - grad_G_w3_part_3 = Gl2A - grad_G_w3 = grad_G_w3_part_3.T.dot(grad_G_w3_part_1 * grad_G_w3_part_2) - grad_G_b3 = grad_G_w3_part_1 * grad_G_w3_part_2 + v15, m15 = 0, 0 + v16, m16 = 0, 0 - grad_G_w2_part_1 = (grad_G_w3_part_1 * grad_G_w3_part_2).dot(G_W3.T) - grad_G_w2_part_2 = d_ReLu(Gl2) - grad_G_w2_part_3 = Gl1A - grad_G_w2 = grad_G_w2_part_3.T.dot(grad_G_w2_part_1 * grad_G_w2_part_2) - grad_G_b2 = grad_G_w2_part_1 * grad_G_w2_part_2 + v17, m17 = 0, 0 + v18, m18 = 0, 0 - grad_G_w1_part_1 = (grad_G_w2_part_1 * grad_G_w2_part_2).dot(G_W2.T) - grad_G_w1_part_2 = d_arctan(Gl1) - grad_G_w1_part_3 = Z - grad_G_w1 = grad_G_w1_part_3.T.dot(grad_G_w1_part_1 * grad_G_w1_part_2) - grad_G_b1 = grad_G_w1_part_1 * grad_G_w1_part_2 + beta_1, beta_2, eps = 0.9, 0.999, 0.00000001 - # ---- Update Gradient ---- - m5 = beta_1 * m5 + (1 - beta_1) * grad_G_w1 - v5 = beta_2 * v5 + (1 - beta_2) * grad_G_w1 ** 2 + print("--------- Started Training ----------") + for iter in range(num_epoch): - m6 = beta_1 * m6 + (1 - beta_1) * grad_G_b1 - v6 = beta_2 * v6 + (1 - beta_2) * grad_G_b1 ** 2 + random_int = np.random.randint(len(images) - 5) + current_image = np.expand_dims(images[random_int], axis=0) - m7 = beta_1 * m7 + (1 - beta_1) * grad_G_w2 - v7 = beta_2 * v7 + (1 - beta_2) * grad_G_w2 ** 2 + # Func: Generate The first Fake Data + Z = np.random.uniform(-1.0, 1.0, size=[1, G_input]) + Gl1 = Z.dot(G_W1) + G_b1 + Gl1A = arctan(Gl1) + Gl2 = Gl1A.dot(G_W2) + G_b2 + Gl2A = ReLu(Gl2) + Gl3 = Gl2A.dot(G_W3) + G_b3 + Gl3A = arctan(Gl3) - m8 = beta_1 * m8 + (1 - beta_1) * grad_G_b2 - v8 = beta_2 * v8 + (1 - beta_2) * grad_G_b2 ** 2 + Gl4 = Gl3A.dot(G_W4) + G_b4 + Gl4A = ReLu(Gl4) + Gl5 = Gl4A.dot(G_W5) + G_b5 + Gl5A = tanh(Gl5) + Gl6 = Gl5A.dot(G_W6) + G_b6 + Gl6A = ReLu(Gl6) + Gl7 = Gl6A.dot(G_W7) + G_b7 - m9 = beta_1 * m9 + (1 - beta_1) * grad_G_w3 - v9 = beta_2 * v9 + (1 - beta_2) * grad_G_w3 ** 2 + current_fake_data = log(Gl7) - m10 = beta_1 * m10 + (1 - beta_1) * grad_G_b3 - v10 = beta_2 * v10 + (1 - beta_2) * grad_G_b3 ** 2 + # Func: Forward Feed for Real data + Dl1_r = current_image.dot(D_W1) + D_b1 + Dl1_rA = ReLu(Dl1_r) + Dl2_r = Dl1_rA.dot(D_W2) + D_b2 + Dl2_rA = log(Dl2_r) + + # Func: Forward Feed for Fake Data + Dl1_f = current_fake_data.dot(D_W1) + D_b1 + Dl1_fA = ReLu(Dl1_f) + Dl2_f = Dl1_fA.dot(D_W2) + D_b2 + Dl2_fA = log(Dl2_f) + + # Func: Cost D + D_cost = -np.log(Dl2_rA) + np.log(1.0 - Dl2_fA) + + # Func: Gradient + grad_f_w2_part_1 = 1 / (1.0 - Dl2_fA) + grad_f_w2_part_2 = d_log(Dl2_f) + grad_f_w2_part_3 = Dl1_fA + grad_f_w2 = grad_f_w2_part_3.T.dot(grad_f_w2_part_1 * grad_f_w2_part_2) + grad_f_b2 = grad_f_w2_part_1 * grad_f_w2_part_2 + + grad_f_w1_part_1 = (grad_f_w2_part_1 * grad_f_w2_part_2).dot(D_W2.T) + grad_f_w1_part_2 = d_ReLu(Dl1_f) + grad_f_w1_part_3 = current_fake_data + grad_f_w1 = grad_f_w1_part_3.T.dot(grad_f_w1_part_1 * grad_f_w1_part_2) + grad_f_b1 = grad_f_w1_part_1 * grad_f_w1_part_2 + + grad_r_w2_part_1 = -1 / Dl2_rA + grad_r_w2_part_2 = d_log(Dl2_r) + grad_r_w2_part_3 = Dl1_rA + grad_r_w2 = grad_r_w2_part_3.T.dot(grad_r_w2_part_1 * grad_r_w2_part_2) + grad_r_b2 = grad_r_w2_part_1 * grad_r_w2_part_2 + + grad_r_w1_part_1 = (grad_r_w2_part_1 * grad_r_w2_part_2).dot(D_W2.T) + grad_r_w1_part_2 = d_ReLu(Dl1_r) + grad_r_w1_part_3 = current_image + grad_r_w1 = grad_r_w1_part_3.T.dot(grad_r_w1_part_1 * grad_r_w1_part_2) + grad_r_b1 = grad_r_w1_part_1 * grad_r_w1_part_2 + + grad_w1 = grad_f_w1 + grad_r_w1 + grad_b1 = grad_f_b1 + grad_r_b1 + + grad_w2 = grad_f_w2 + grad_r_w2 + grad_b2 = grad_f_b2 + grad_r_b2 + + # ---- Update Gradient ---- + m1 = beta_1 * m1 + (1 - beta_1) * grad_w1 + v1 = beta_2 * v1 + (1 - beta_2) * grad_w1 ** 2 + + m2 = beta_1 * m2 + (1 - beta_1) * grad_b1 + v2 = beta_2 * v2 + (1 - beta_2) * grad_b1 ** 2 + + m3 = beta_1 * m3 + (1 - beta_1) * grad_w2 + v3 = beta_2 * v3 + (1 - beta_2) * grad_w2 ** 2 + + m4 = beta_1 * m4 + (1 - beta_1) * grad_b2 + v4 = beta_2 * v4 + (1 - beta_2) * grad_b2 ** 2 + + D_W1 = D_W1 - (learing_rate / (np.sqrt(v1 / (1 - beta_2)) + eps)) * ( + m1 / (1 - beta_1) + ) + D_b1 = D_b1 - (learing_rate / (np.sqrt(v2 / (1 - beta_2)) + eps)) * ( + m2 / (1 - beta_1) + ) - m11 = beta_1 * m11 + (1 - beta_1) * grad_G_w4 - v11 = beta_2 * v11 + (1 - beta_2) * grad_G_w4 ** 2 + D_W2 = D_W2 - (learing_rate / (np.sqrt(v3 / (1 - beta_2)) + eps)) * ( + m3 / (1 - beta_1) + ) + D_b2 = D_b2 - (learing_rate / (np.sqrt(v4 / (1 - beta_2)) + eps)) * ( + m4 / (1 - beta_1) + ) - m12 = beta_1 * m12 + (1 - beta_1) * grad_G_b4 - v12 = beta_2 * v12 + (1 - beta_2) * grad_G_b4 ** 2 + # Func: Forward Feed for G + Z = np.random.uniform(-1.0, 1.0, size=[1, G_input]) + Gl1 = Z.dot(G_W1) + G_b1 + Gl1A = arctan(Gl1) + Gl2 = Gl1A.dot(G_W2) + G_b2 + Gl2A = ReLu(Gl2) + Gl3 = Gl2A.dot(G_W3) + G_b3 + Gl3A = arctan(Gl3) - m13 = beta_1 * m13 + (1 - beta_1) * grad_G_w5 - v13 = beta_2 * v13 + (1 - beta_2) * grad_G_w5 ** 2 + Gl4 = Gl3A.dot(G_W4) + G_b4 + Gl4A = ReLu(Gl4) + Gl5 = Gl4A.dot(G_W5) + G_b5 + Gl5A = tanh(Gl5) + Gl6 = Gl5A.dot(G_W6) + G_b6 + Gl6A = ReLu(Gl6) + Gl7 = Gl6A.dot(G_W7) + G_b7 - m14 = beta_1 * m14 + (1 - beta_1) * grad_G_b5 - v14 = beta_2 * v14 + (1 - beta_2) * grad_G_b5 ** 2 + current_fake_data = log(Gl7) - m15 = beta_1 * m15 + (1 - beta_1) * grad_G_w6 - v15 = beta_2 * v15 + (1 - beta_2) * grad_G_w6 ** 2 + Dl1 = current_fake_data.dot(D_W1) + D_b1 + Dl1_A = ReLu(Dl1) + Dl2 = Dl1_A.dot(D_W2) + D_b2 + Dl2_A = log(Dl2) - m16 = beta_1 * m16 + (1 - beta_1) * grad_G_b6 - v16 = beta_2 * v16 + (1 - beta_2) * grad_G_b6 ** 2 + # Func: Cost G + G_cost = -np.log(Dl2_A) - m17 = beta_1 * m17 + (1 - beta_1) * grad_G_w7 - v17 = beta_2 * v17 + (1 - beta_2) * grad_G_w7 ** 2 + # Func: Gradient + grad_G_w7_part_1 = ((-1 / Dl2_A) * d_log(Dl2).dot(D_W2.T) * (d_ReLu(Dl1))).dot( + D_W1.T + ) + grad_G_w7_part_2 = d_log(Gl7) + grad_G_w7_part_3 = Gl6A + grad_G_w7 = grad_G_w7_part_3.T.dot(grad_G_w7_part_1 * grad_G_w7_part_1) + grad_G_b7 = grad_G_w7_part_1 * grad_G_w7_part_2 - m18 = beta_1 * m18 + (1 - beta_1) * grad_G_b7 - v18 = beta_2 * v18 + (1 - beta_2) * grad_G_b7 ** 2 + grad_G_w6_part_1 = (grad_G_w7_part_1 * grad_G_w7_part_2).dot(G_W7.T) + grad_G_w6_part_2 = d_ReLu(Gl6) + grad_G_w6_part_3 = Gl5A + grad_G_w6 = grad_G_w6_part_3.T.dot(grad_G_w6_part_1 * grad_G_w6_part_2) + grad_G_b6 = grad_G_w6_part_1 * grad_G_w6_part_2 - G_W1 = G_W1 - (learing_rate / (np.sqrt(v5 / (1 - beta_2)) + eps)) * ( - m5 / (1 - beta_1) - ) - G_b1 = G_b1 - (learing_rate / (np.sqrt(v6 / (1 - beta_2)) + eps)) * ( - m6 / (1 - beta_1) - ) + grad_G_w5_part_1 = (grad_G_w6_part_1 * grad_G_w6_part_2).dot(G_W6.T) + grad_G_w5_part_2 = d_tanh(Gl5) + grad_G_w5_part_3 = Gl4A + grad_G_w5 = grad_G_w5_part_3.T.dot(grad_G_w5_part_1 * grad_G_w5_part_2) + grad_G_b5 = grad_G_w5_part_1 * grad_G_w5_part_2 - G_W2 = G_W2 - (learing_rate / (np.sqrt(v7 / (1 - beta_2)) + eps)) * ( - m7 / (1 - beta_1) - ) - G_b2 = G_b2 - (learing_rate / (np.sqrt(v8 / (1 - beta_2)) + eps)) * ( - m8 / (1 - beta_1) - ) + grad_G_w4_part_1 = (grad_G_w5_part_1 * grad_G_w5_part_2).dot(G_W5.T) + grad_G_w4_part_2 = d_ReLu(Gl4) + grad_G_w4_part_3 = Gl3A + grad_G_w4 = grad_G_w4_part_3.T.dot(grad_G_w4_part_1 * grad_G_w4_part_2) + grad_G_b4 = grad_G_w4_part_1 * grad_G_w4_part_2 - G_W3 = G_W3 - (learing_rate / (np.sqrt(v9 / (1 - beta_2)) + eps)) * ( - m9 / (1 - beta_1) - ) - G_b3 = G_b3 - (learing_rate / (np.sqrt(v10 / (1 - beta_2)) + eps)) * ( - m10 / (1 - beta_1) - ) + grad_G_w3_part_1 = (grad_G_w4_part_1 * grad_G_w4_part_2).dot(G_W4.T) + grad_G_w3_part_2 = d_arctan(Gl3) + grad_G_w3_part_3 = Gl2A + grad_G_w3 = grad_G_w3_part_3.T.dot(grad_G_w3_part_1 * grad_G_w3_part_2) + grad_G_b3 = grad_G_w3_part_1 * grad_G_w3_part_2 - G_W4 = G_W4 - (learing_rate / (np.sqrt(v11 / (1 - beta_2)) + eps)) * ( - m11 / (1 - beta_1) - ) - G_b4 = G_b4 - (learing_rate / (np.sqrt(v12 / (1 - beta_2)) + eps)) * ( - m12 / (1 - beta_1) - ) + grad_G_w2_part_1 = (grad_G_w3_part_1 * grad_G_w3_part_2).dot(G_W3.T) + grad_G_w2_part_2 = d_ReLu(Gl2) + grad_G_w2_part_3 = Gl1A + grad_G_w2 = grad_G_w2_part_3.T.dot(grad_G_w2_part_1 * grad_G_w2_part_2) + grad_G_b2 = grad_G_w2_part_1 * grad_G_w2_part_2 - G_W5 = G_W5 - (learing_rate / (np.sqrt(v13 / (1 - beta_2)) + eps)) * ( - m13 / (1 - beta_1) - ) - G_b5 = G_b5 - (learing_rate / (np.sqrt(v14 / (1 - beta_2)) + eps)) * ( - m14 / (1 - beta_1) - ) + grad_G_w1_part_1 = (grad_G_w2_part_1 * grad_G_w2_part_2).dot(G_W2.T) + grad_G_w1_part_2 = d_arctan(Gl1) + grad_G_w1_part_3 = Z + grad_G_w1 = grad_G_w1_part_3.T.dot(grad_G_w1_part_1 * grad_G_w1_part_2) + grad_G_b1 = grad_G_w1_part_1 * grad_G_w1_part_2 - G_W6 = G_W6 - (learing_rate / (np.sqrt(v15 / (1 - beta_2)) + eps)) * ( - m15 / (1 - beta_1) - ) - G_b6 = G_b6 - (learing_rate / (np.sqrt(v16 / (1 - beta_2)) + eps)) * ( - m16 / (1 - beta_1) - ) + # ---- Update Gradient ---- + m5 = beta_1 * m5 + (1 - beta_1) * grad_G_w1 + v5 = beta_2 * v5 + (1 - beta_2) * grad_G_w1 ** 2 - G_W7 = G_W7 - (learing_rate / (np.sqrt(v17 / (1 - beta_2)) + eps)) * ( - m17 / (1 - beta_1) - ) - G_b7 = G_b7 - (learing_rate / (np.sqrt(v18 / (1 - beta_2)) + eps)) * ( - m18 / (1 - beta_1) - ) + m6 = beta_1 * m6 + (1 - beta_1) * grad_G_b1 + v6 = beta_2 * v6 + (1 - beta_2) * grad_G_b1 ** 2 + + m7 = beta_1 * m7 + (1 - beta_1) * grad_G_w2 + v7 = beta_2 * v7 + (1 - beta_2) * grad_G_w2 ** 2 + + m8 = beta_1 * m8 + (1 - beta_1) * grad_G_b2 + v8 = beta_2 * v8 + (1 - beta_2) * grad_G_b2 ** 2 - # --- Print Error ---- - # print("Current Iter: ",iter, " Current D cost:",D_cost, " Current G cost: ", G_cost,end='\r') - - if iter == 0: - learing_rate = learing_rate * 0.01 - if iter == 40: - learing_rate = learing_rate * 0.01 - - # ---- Print to Out put ---- - if iter % 10 == 0: - - print( - "Current Iter: ", - iter, - " Current D cost:", - D_cost, - " Current G cost: ", - G_cost, - end="\r", + m9 = beta_1 * m9 + (1 - beta_1) * grad_G_w3 + v9 = beta_2 * v9 + (1 - beta_2) * grad_G_w3 ** 2 + + m10 = beta_1 * m10 + (1 - beta_1) * grad_G_b3 + v10 = beta_2 * v10 + (1 - beta_2) * grad_G_b3 ** 2 + + m11 = beta_1 * m11 + (1 - beta_1) * grad_G_w4 + v11 = beta_2 * v11 + (1 - beta_2) * grad_G_w4 ** 2 + + m12 = beta_1 * m12 + (1 - beta_1) * grad_G_b4 + v12 = beta_2 * v12 + (1 - beta_2) * grad_G_b4 ** 2 + + m13 = beta_1 * m13 + (1 - beta_1) * grad_G_w5 + v13 = beta_2 * v13 + (1 - beta_2) * grad_G_w5 ** 2 + + m14 = beta_1 * m14 + (1 - beta_1) * grad_G_b5 + v14 = beta_2 * v14 + (1 - beta_2) * grad_G_b5 ** 2 + + m15 = beta_1 * m15 + (1 - beta_1) * grad_G_w6 + v15 = beta_2 * v15 + (1 - beta_2) * grad_G_w6 ** 2 + + m16 = beta_1 * m16 + (1 - beta_1) * grad_G_b6 + v16 = beta_2 * v16 + (1 - beta_2) * grad_G_b6 ** 2 + + m17 = beta_1 * m17 + (1 - beta_1) * grad_G_w7 + v17 = beta_2 * v17 + (1 - beta_2) * grad_G_w7 ** 2 + + m18 = beta_1 * m18 + (1 - beta_1) * grad_G_b7 + v18 = beta_2 * v18 + (1 - beta_2) * grad_G_b7 ** 2 + + G_W1 = G_W1 - (learing_rate / (np.sqrt(v5 / (1 - beta_2)) + eps)) * ( + m5 / (1 - beta_1) + ) + G_b1 = G_b1 - (learing_rate / (np.sqrt(v6 / (1 - beta_2)) + eps)) * ( + m6 / (1 - beta_1) ) - print("--------- Show Example Result See Tab Above ----------") - print("--------- Wait for the image to load ---------") - Z = np.random.uniform(-1.0, 1.0, size=[16, G_input]) - Gl1 = Z.dot(G_W1) + G_b1 - Gl1A = arctan(Gl1) - Gl2 = Gl1A.dot(G_W2) + G_b2 - Gl2A = ReLu(Gl2) - Gl3 = Gl2A.dot(G_W3) + G_b3 - Gl3A = arctan(Gl3) + G_W2 = G_W2 - (learing_rate / (np.sqrt(v7 / (1 - beta_2)) + eps)) * ( + m7 / (1 - beta_1) + ) + G_b2 = G_b2 - (learing_rate / (np.sqrt(v8 / (1 - beta_2)) + eps)) * ( + m8 / (1 - beta_1) + ) - Gl4 = Gl3A.dot(G_W4) + G_b4 - Gl4A = ReLu(Gl4) - Gl5 = Gl4A.dot(G_W5) + G_b5 - Gl5A = tanh(Gl5) - Gl6 = Gl5A.dot(G_W6) + G_b6 - Gl6A = ReLu(Gl6) - Gl7 = Gl6A.dot(G_W7) + G_b7 + G_W3 = G_W3 - (learing_rate / (np.sqrt(v9 / (1 - beta_2)) + eps)) * ( + m9 / (1 - beta_1) + ) + G_b3 = G_b3 - (learing_rate / (np.sqrt(v10 / (1 - beta_2)) + eps)) * ( + m10 / (1 - beta_1) + ) - current_fake_data = log(Gl7) + G_W4 = G_W4 - (learing_rate / (np.sqrt(v11 / (1 - beta_2)) + eps)) * ( + m11 / (1 - beta_1) + ) + G_b4 = G_b4 - (learing_rate / (np.sqrt(v12 / (1 - beta_2)) + eps)) * ( + m12 / (1 - beta_1) + ) - fig = plot(current_fake_data) - fig.savefig( - "Click_Me_{}.png".format( - str(iter).zfill(3) - + "_Ginput_" - + str(G_input) - + "_hiddenone" - + str(hidden_input) - + "_hiddentwo" - + str(hidden_input2) - + "_LR_" - + str(learing_rate) - ), - bbox_inches="tight", + G_W5 = G_W5 - (learing_rate / (np.sqrt(v13 / (1 - beta_2)) + eps)) * ( + m13 / (1 - beta_1) + ) + G_b5 = G_b5 - (learing_rate / (np.sqrt(v14 / (1 - beta_2)) + eps)) * ( + m14 / (1 - beta_1) ) -# for complete explanation visit https://towardsdatascience.com/only-numpy-implementing-gan-general-adversarial-networks-and-adam-optimizer-using-numpy-with-2a7e4e032021 -# -- end code -- + + G_W6 = G_W6 - (learing_rate / (np.sqrt(v15 / (1 - beta_2)) + eps)) * ( + m15 / (1 - beta_1) + ) + G_b6 = G_b6 - (learing_rate / (np.sqrt(v16 / (1 - beta_2)) + eps)) * ( + m16 / (1 - beta_1) + ) + + G_W7 = G_W7 - (learing_rate / (np.sqrt(v17 / (1 - beta_2)) + eps)) * ( + m17 / (1 - beta_1) + ) + G_b7 = G_b7 - (learing_rate / (np.sqrt(v18 / (1 - beta_2)) + eps)) * ( + m18 / (1 - beta_1) + ) + + # --- Print Error ---- + # print("Current Iter: ",iter, " Current D cost:",D_cost, " Current G cost: ", G_cost,end='\r') + + if iter == 0: + learing_rate = learing_rate * 0.01 + if iter == 40: + learing_rate = learing_rate * 0.01 + + # ---- Print to Out put ---- + if iter % 10 == 0: + + print( + "Current Iter: ", + iter, + " Current D cost:", + D_cost, + " Current G cost: ", + G_cost, + end="\r", + ) + print("--------- Show Example Result See Tab Above ----------") + print("--------- Wait for the image to load ---------") + Z = np.random.uniform(-1.0, 1.0, size=[16, G_input]) + + Gl1 = Z.dot(G_W1) + G_b1 + Gl1A = arctan(Gl1) + Gl2 = Gl1A.dot(G_W2) + G_b2 + Gl2A = ReLu(Gl2) + Gl3 = Gl2A.dot(G_W3) + G_b3 + Gl3A = arctan(Gl3) + + Gl4 = Gl3A.dot(G_W4) + G_b4 + Gl4A = ReLu(Gl4) + Gl5 = Gl4A.dot(G_W5) + G_b5 + Gl5A = tanh(Gl5) + Gl6 = Gl5A.dot(G_W6) + G_b6 + Gl6A = ReLu(Gl6) + Gl7 = Gl6A.dot(G_W7) + G_b7 + + current_fake_data = log(Gl7) + + fig = plot(current_fake_data) + fig.savefig( + "Click_Me_{}.png".format( + str(iter).zfill(3) + + "_Ginput_" + + str(G_input) + + "_hiddenone" + + str(hidden_input) + + "_hiddentwo" + + str(hidden_input2) + + "_LR_" + + str(learing_rate) + ), + bbox_inches="tight", + ) + # for complete explanation visit https://towardsdatascience.com/only-numpy-implementing-gan-general-adversarial-networks-and-adam-optimizer-using-numpy-with-2a7e4e032021 + # -- end code -- diff --git a/web_programming/crawl_google_results.py b/web_programming/crawl_google_results.py index c31ec1526d3e..79b69e71c6b3 100644 --- a/web_programming/crawl_google_results.py +++ b/web_programming/crawl_google_results.py @@ -5,16 +5,18 @@ from fake_useragent import UserAgent import requests -print("Googling.....") -url = "https://www.google.com/search?q=" + " ".join(sys.argv[1:]) -res = requests.get(url, headers={"UserAgent": UserAgent().random}) -# res.raise_for_status() -with open("project1a.html", "wb") as out_file: # only for knowing the class - for data in res.iter_content(10000): - out_file.write(data) -soup = BeautifulSoup(res.text, "html.parser") -links = list(soup.select(".eZt8xd"))[:5] -print(len(links)) -for link in links: - webbrowser.open(f"http://google.com{link.get('href')}") +if __name__ == "__main__": + print("Googling.....") + url = "https://www.google.com/search?q=" + " ".join(sys.argv[1:]) + res = requests.get(url, headers={"UserAgent": UserAgent().random}) + # res.raise_for_status() + with open("project1a.html", "wb") as out_file: # only for knowing the class + for data in res.iter_content(10000): + out_file.write(data) + soup = BeautifulSoup(res.text, "html.parser") + links = list(soup.select(".eZt8xd"))[:5] + + print(len(links)) + for link in links: + webbrowser.open(f"http://google.com{link.get('href')}") From 0832e1ec583406a70a3d38bca11bb01a77299a23 Mon Sep 17 00:00:00 2001 From: Himanshu Bhatnagar <33115688+Himan10@users.noreply.github.com> Date: Mon, 18 Nov 2019 00:29:50 +0530 Subject: [PATCH 0367/1071] Adding circular_queue.py (#1574) * Create circular_queue.py Circular Queue implementation using python list with fixed range. * Update circular_queue.py * Update circular_queue.py * Update circular_queue.py * Update circular_queue.py * Update circular_queue.py * doctest: Catch "Exception: UNDERFLOW" * Deal with the fluent interface for cq.enqueue() * Test the fluent interface --- data_structures/queue/circular_queue.py | 93 +++++++++++++++++++++++++ 1 file changed, 93 insertions(+) create mode 100644 data_structures/queue/circular_queue.py diff --git a/data_structures/queue/circular_queue.py b/data_structures/queue/circular_queue.py new file mode 100644 index 000000000000..2ba3f891e253 --- /dev/null +++ b/data_structures/queue/circular_queue.py @@ -0,0 +1,93 @@ +# Implementation of Circular Queue (using Python lists) + +class CircularQueue: + """Circular FIFO queue with a fixed capacity""" + + def __init__(self, n: int): + self.n = n + self.array = [None] * self.n + self.front = 0 # index of the first element + self.rear = 0 + self.size = 0 + + def __len__(self) -> int: + """ + >>> cq = CircularQueue(5) + >>> len(cq) + 0 + >>> cq.enqueue("A") # doctest: +ELLIPSIS + >> len(cq) + 1 + """ + return self.size + + def is_empty(self) -> bool: + """ + >>> cq = CircularQueue(5) + >>> cq.is_empty() + True + >>> cq.enqueue("A").is_empty() + False + """ + return self.size == 0 + + def first(self): + """ + >>> cq = CircularQueue(5) + >>> cq.first() + False + >>> cq.enqueue("A").first() + 'A' + """ + return False if self.is_empty() else self.array[self.front] + + def enqueue(self, data): + """ + This function insert an element in the queue using self.rear value as an index + >>> cq = CircularQueue(5) + >>> cq.enqueue("A") # doctest: +ELLIPSIS + >> (cq.size, cq.first()) + (1, 'A') + >>> cq.enqueue("B") # doctest: +ELLIPSIS + >> (cq.size, cq.first()) + (2, 'A') + """ + if self.size >= self.n: + raise Exception("QUEUE IS FULL") + + self.array[self.rear] = data + self.rear = (self.rear+1)%self.n + self.size += 1 + return self + + def dequeue(self): + """ + This function removes an element from the queue using on self.front value as an + index + >>> cq = CircularQueue(5) + >>> cq.dequeue() + Traceback (most recent call last): + ... + Exception: UNDERFLOW + >>> cq.enqueue("A").enqueue("B").dequeue() + 'A' + >>> (cq.size, cq.first()) + (1, 'B') + >>> cq.dequeue() + 'B' + >>> cq.dequeue() + Traceback (most recent call last): + ... + Exception: UNDERFLOW + """ + if self.size == 0: + raise Exception("UNDERFLOW") + + temp = self.array[self.front] + self.array[self.front] = None + self.front = (self.front + 1)%self.n + self.size -= 1 + return temp From 2565797504ccf270c150d657ef815f4d8ea467f3 Mon Sep 17 00:00:00 2001 From: Sarath Kaul Date: Mon, 18 Nov 2019 17:17:26 +0530 Subject: [PATCH 0368/1071] Reverse Words (#1581) * Word Occurence Script Added * Word Occurence Script Updated * Added doctest using collections.Counter https://docs.python.org/3/library/collections.html#collections.Counter * Reverse Word Script Added * Reverse Word Script Added * Reverse Word Script Added * Reverse Word Script Added * Word Occurence Script Added * Reverse Word Script Added * Reverse Word Script Added * Reverse Words DocTest Updated * Word Occurence Updated * Doctest Updated * Doctest Updated * Doctest Updated --- strings/reverse_words.py | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 strings/reverse_words.py diff --git a/strings/reverse_words.py b/strings/reverse_words.py new file mode 100644 index 000000000000..123b074406c3 --- /dev/null +++ b/strings/reverse_words.py @@ -0,0 +1,23 @@ +# Created by sarathkaul on 18/11/19 + + +def reverse_words(input_str: str) -> str: + """ + Reverses words in a given string + >>> sentence = "I love Python" + >>> reverse_words(sentence) == " ".join(sentence.split()[::-1]) + True + >>> reverse_words(sentence) + 'Python love I' + """ + input_str = input_str.split(" ") + new_str = list() + + for a_word in input_str: + new_str.insert(0, a_word) + + return " ".join(new_str) + + +if __name__ == "__main__": + print(reverse_words("INPUT STRING")) From c57c4ca1a126871a2153cec6425cf19ed6ed1e49 Mon Sep 17 00:00:00 2001 From: onlinejudge95 <44158581+onlinejudge95@users.noreply.github.com> Date: Tue, 19 Nov 2019 15:23:44 +0530 Subject: [PATCH 0369/1071] Adds operations for circular linked list (#1584) * Adds, append, len, print operations for circular linked list * Adds, prepend support * Adds, delete from front of the list * Adds, delete_rear support * Adds, method documentations * Adds, type checking and doctests * Updates doctest for delete ops * Addressing requested changes * Removes unused import * Fixes failing doctests * Minor modifications... --- .../linked_list/circular_linked_list.py | 186 ++++++++++++++++++ 1 file changed, 186 insertions(+) create mode 100644 data_structures/linked_list/circular_linked_list.py diff --git a/data_structures/linked_list/circular_linked_list.py b/data_structures/linked_list/circular_linked_list.py new file mode 100644 index 000000000000..cf523f0a4380 --- /dev/null +++ b/data_structures/linked_list/circular_linked_list.py @@ -0,0 +1,186 @@ +from typing import Any + + +class Node: + """ + Class to represent a single node. + + Each node has following attributes + * data + * next_ptr + """ + + def __init__(self, data: Any): + self.data = data + self.next_ptr = None + + +class CircularLinkedList: + """ + Class to represent the CircularLinkedList. + + CircularLinkedList has following attributes. + * head + * length + """ + + def __init__(self): + self.head = None + self.length = 0 + + def __len__(self) -> int: + """ + Dunder method to return length of the CircularLinkedList + >>> cll = CircularLinkedList() + >>> len(cll) + 0 + >>> cll.append(1) + >>> len(cll) + 1 + """ + return self.length + + def __str__(self) -> str: + """ + Dunder method to represent the string representation of the CircularLinkedList + >>> cll = CircularLinkedList() + >>> print(cll) + Empty linked list + >>> cll.append(1) + >>> cll.append(2) + >>> print(cll) + => + """ + current_node = self.head + if not current_node: + return "Empty linked list" + + results = [current_node.data] + current_node = current_node.next_ptr + + while current_node != self.head: + results.append(current_node.data) + current_node = current_node.next_ptr + + return " => ".join(f"" for result in results) + + def append(self, data: Any) -> None: + """ + Adds a node with given data to the end of the CircularLinkedList + >>> cll = CircularLinkedList() + >>> cll.append(1) + >>> print(f"{len(cll)}: {cll}") + 1: + >>> cll.append(2) + >>> print(f"{len(cll)}: {cll}") + 2: => + """ + current_node = self.head + + new_node = Node(data) + new_node.next_ptr = new_node + + if current_node: + while current_node.next_ptr != self.head: + current_node = current_node.next_ptr + + current_node.next_ptr = new_node + new_node.next_ptr = self.head + else: + self.head = new_node + + self.length += 1 + + def prepend(self, data: Any) -> None: + """ + Adds a ndoe with given data to the front of the CircularLinkedList + >>> cll = CircularLinkedList() + >>> cll.prepend(1) + >>> cll.prepend(2) + >>> print(f"{len(cll)}: {cll}") + 2: => + """ + current_node = self.head + + new_node = Node(data) + new_node.next_ptr = new_node + + if current_node: + while current_node.next_ptr != self.head: + current_node = current_node.next_ptr + + current_node.next_ptr = new_node + new_node.next_ptr = self.head + + self.head = new_node + self.length += 1 + + def delete_front(self) -> None: + """ + Removes the 1st node from the CircularLinkedList + >>> cll = CircularLinkedList() + >>> cll.delete_front() + Traceback (most recent call last): + ... + IndexError: Deleting from an empty list + >>> cll.append(1) + >>> cll.append(2) + >>> print(f"{len(cll)}: {cll}") + 2: => + >>> cll.delete_front() + >>> print(f"{len(cll)}: {cll}") + 1: + """ + if not self.head: + raise IndexError("Deleting from an empty list") + + current_node = self.head + + if current_node.next_ptr == current_node: + self.head, self.length = None, 0 + else: + while current_node.next_ptr != self.head: + current_node = current_node.next_ptr + + current_node.next_ptr = self.head.next_ptr + self.head = self.head.next_ptr + + self.length -= 1 + + def delete_rear(self) -> None: + """ + Removes the last node from the CircularLinkedList + >>> cll = CircularLinkedList() + >>> cll.delete_rear() + Traceback (most recent call last): + ... + IndexError: Deleting from an empty list + >>> cll.append(1) + >>> cll.append(2) + >>> print(f"{len(cll)}: {cll}") + 2: => + >>> cll.delete_rear() + >>> print(f"{len(cll)}: {cll}") + 1: + """ + if not self.head: + raise IndexError("Deleting from an empty list") + + temp_node, current_node = self.head, self.head + + if current_node.next_ptr == current_node: + self.head, self.length = None, 0 + else: + while current_node.next_ptr != self.head: + temp_node = current_node + current_node = current_node.next_ptr + + temp_node.next_ptr = current_node.next_ptr + + self.length -= 1 + + +if __name__ == "__main__": + import doctest + + doctest.testmod() From 28c02a1f21b07627c98dd38e2cb933c1b9c14c6c Mon Sep 17 00:00:00 2001 From: John Law Date: Tue, 19 Nov 2019 13:52:55 -0800 Subject: [PATCH 0370/1071] Improve bellman_ford.py (#1575) * Fix out of range error in bellman_ford.py * Update bellman_ford.py * fixup! Format Python code with psf/black push * Enhance the print function * fixup! Format Python code with psf/black push --- graphs/bellman_ford.py | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/graphs/bellman_ford.py b/graphs/bellman_ford.py index 5c36468e79de..6b5e8b735c43 100644 --- a/graphs/bellman_ford.py +++ b/graphs/bellman_ford.py @@ -1,14 +1,17 @@ +from typing import Dict, List + + def printDist(dist, V): - print("\nVertex Distance") - for i in range(V): - if dist[i] != float("inf"): - print(i, "\t", int(dist[i]), end="\t") - else: - print(i, "\t", "INF", end="\t") - print() + print("Vertex Distance") + distances = ("INF" if d == float("inf") else d for d in dist) + print("\t".join(f"{i}\t{d}" for i, d in enumerate(distances))) -def BellmanFord(graph, V, E, src): +def BellmanFord(graph: List[Dict[str, int]], V: int, E: int, src: int) -> int: + r""" + Returns shortest paths from a vertex src to all + other vertices. + """ mdist = [float("inf") for i in range(V)] mdist[src] = 0.0 @@ -30,6 +33,7 @@ def BellmanFord(graph, V, E, src): return printDist(mdist, V) + return src if __name__ == "__main__": @@ -38,7 +42,7 @@ def BellmanFord(graph, V, E, src): graph = [dict() for j in range(E)] - for i in range(V): + for i in range(E): graph[i][i] = 0.0 for i in range(E): From e8aa81297a6291a7d0994d71605f07d654aae17c Mon Sep 17 00:00:00 2001 From: Fakher Mokadem Date: Wed, 20 Nov 2019 06:36:32 +0100 Subject: [PATCH 0371/1071] Update gaussian_filter.py (#1548) * Update gaussian_filter.py Changed embedded for loops with product. This way range(dst_height) is called only once, instead of being called $dst_height. * Update gaussian_filter.py fixed missing width --- digital_image_processing/filters/gaussian_filter.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/digital_image_processing/filters/gaussian_filter.py b/digital_image_processing/filters/gaussian_filter.py index b800f0a7edc8..b5e2ac00dd81 100644 --- a/digital_image_processing/filters/gaussian_filter.py +++ b/digital_image_processing/filters/gaussian_filter.py @@ -3,6 +3,7 @@ """ from cv2 import imread, cvtColor, COLOR_BGR2GRAY, imshow, waitKey from numpy import pi, mgrid, exp, square, zeros, ravel, dot, uint8 +from itertools import product def gen_gaussian_kernel(k_size, sigma): @@ -21,8 +22,7 @@ def gaussian_filter(image, k_size, sigma): # im2col, turn the k_size*k_size pixels into a row and np.vstack all rows image_array = zeros((dst_height * dst_width, k_size * k_size)) row = 0 - for i in range(0, dst_height): - for j in range(0, dst_width): + for i, j in product(range(dst_height), range(dst_width)): window = ravel(image[i : i + k_size, j : j + k_size]) image_array[row, :] = window row += 1 From ec7bc7c7cde95afbc8a7d7f826358cc221edfb6b Mon Sep 17 00:00:00 2001 From: Christian Clauss Date: Thu, 21 Nov 2019 15:21:40 +0100 Subject: [PATCH 0372/1071] Tabs --> spaces in quine_mc_cluskey.py (#1426) * Tabs --> spaces in quine_mc_cluskey.py * fixup! Format Python code with psf/black push --- boolean_algebra/quine_mc_cluskey.py | 48 +++++++++---------- data_structures/queue/circular_queue.py | 5 +- .../filters/gaussian_filter.py | 8 ++-- 3 files changed, 31 insertions(+), 30 deletions(-) diff --git a/boolean_algebra/quine_mc_cluskey.py b/boolean_algebra/quine_mc_cluskey.py index a066982e53b4..036cfbe63e79 100644 --- a/boolean_algebra/quine_mc_cluskey.py +++ b/boolean_algebra/quine_mc_cluskey.py @@ -1,11 +1,11 @@ def compare_string(string1, string2): """ - >>> compare_string('0010','0110') - '0_10' + >>> compare_string('0010','0110') + '0_10' - >>> compare_string('0110','1101') - -1 - """ + >>> compare_string('0110','1101') + -1 + """ l1 = list(string1) l2 = list(string2) count = 0 @@ -21,9 +21,9 @@ def compare_string(string1, string2): def check(binary): """ - >>> check(['0.00.01.5']) - ['0.00.01.5'] - """ + >>> check(['0.00.01.5']) + ['0.00.01.5'] + """ pi = [] while 1: check1 = ["$"] * len(binary) @@ -45,9 +45,9 @@ def check(binary): def decimal_to_binary(no_of_variable, minterms): """ - >>> decimal_to_binary(3,[1.5]) - ['0.00.01.5'] - """ + >>> decimal_to_binary(3,[1.5]) + ['0.00.01.5'] + """ temp = [] s = "" for m in minterms: @@ -61,12 +61,12 @@ def decimal_to_binary(no_of_variable, minterms): def is_for_table(string1, string2, count): """ - >>> is_for_table('__1','011',2) - True + >>> is_for_table('__1','011',2) + True - >>> is_for_table('01_','001',1) - False - """ + >>> is_for_table('01_','001',1) + False + """ l1 = list(string1) l2 = list(string2) count_n = 0 @@ -81,12 +81,12 @@ def is_for_table(string1, string2, count): def selection(chart, prime_implicants): """ - >>> selection([[1]],['0.00.01.5']) - ['0.00.01.5'] + >>> selection([[1]],['0.00.01.5']) + ['0.00.01.5'] - >>> selection([[1]],['0.00.01.5']) - ['0.00.01.5'] - """ + >>> selection([[1]],['0.00.01.5']) + ['0.00.01.5'] + """ temp = [] select = [0] * len(chart) for i in range(len(chart[0])): @@ -128,9 +128,9 @@ def selection(chart, prime_implicants): def prime_implicant_chart(prime_implicants, binary): """ - >>> prime_implicant_chart(['0.00.01.5'],['0.00.01.5']) - [[1]] - """ + >>> prime_implicant_chart(['0.00.01.5'],['0.00.01.5']) + [[1]] + """ chart = [[0 for x in range(len(binary))] for x in range(len(prime_implicants))] for i in range(len(prime_implicants)): count = prime_implicants[i].count("_") diff --git a/data_structures/queue/circular_queue.py b/data_structures/queue/circular_queue.py index 2ba3f891e253..229a67ebb8be 100644 --- a/data_structures/queue/circular_queue.py +++ b/data_structures/queue/circular_queue.py @@ -1,5 +1,6 @@ # Implementation of Circular Queue (using Python lists) + class CircularQueue: """Circular FIFO queue with a fixed capacity""" @@ -59,7 +60,7 @@ def enqueue(self, data): raise Exception("QUEUE IS FULL") self.array[self.rear] = data - self.rear = (self.rear+1)%self.n + self.rear = (self.rear + 1) % self.n self.size += 1 return self @@ -88,6 +89,6 @@ def dequeue(self): temp = self.array[self.front] self.array[self.front] = None - self.front = (self.front + 1)%self.n + self.front = (self.front + 1) % self.n self.size -= 1 return temp diff --git a/digital_image_processing/filters/gaussian_filter.py b/digital_image_processing/filters/gaussian_filter.py index b5e2ac00dd81..79026ddcc57a 100644 --- a/digital_image_processing/filters/gaussian_filter.py +++ b/digital_image_processing/filters/gaussian_filter.py @@ -22,10 +22,10 @@ def gaussian_filter(image, k_size, sigma): # im2col, turn the k_size*k_size pixels into a row and np.vstack all rows image_array = zeros((dst_height * dst_width, k_size * k_size)) row = 0 - for i, j in product(range(dst_height), range(dst_width)): - window = ravel(image[i : i + k_size, j : j + k_size]) - image_array[row, :] = window - row += 1 + for i, j in product(range(dst_height), range(dst_width)): + window = ravel(image[i : i + k_size, j : j + k_size]) + image_array[row, :] = window + row += 1 # turn the kernel into shape(k*k, 1) gaussian_kernel = gen_gaussian_kernel(k_size, sigma) From c5fd075f1ea9b6dc11cca2d36605aaddbd0ab5fa Mon Sep 17 00:00:00 2001 From: Arun Babu PT <36483987+ptarun@users.noreply.github.com> Date: Fri, 22 Nov 2019 20:25:19 +0530 Subject: [PATCH 0373/1071] Fractional knapsack (#1524) * Add files via upload * Added doctests, type hints, f-strings, URLs * Rename knapsack.py to fractional_knapsack.py * Rename graphs/fractional_knapsack.py to dynamic_programming/fractional_knapsack_2.py --- dynamic_programming/fractional_knapsack_2.py | 60 ++++++++++++++++++++ 1 file changed, 60 insertions(+) create mode 100644 dynamic_programming/fractional_knapsack_2.py diff --git a/dynamic_programming/fractional_knapsack_2.py b/dynamic_programming/fractional_knapsack_2.py new file mode 100644 index 000000000000..eadb73c61a39 --- /dev/null +++ b/dynamic_programming/fractional_knapsack_2.py @@ -0,0 +1,60 @@ +# 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 + +from typing import List, Tuple + + +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) From e09bf696488b83b090330c1baaf51ec938ed2d3a Mon Sep 17 00:00:00 2001 From: BryanChan777 <43082778+BryanChan777@users.noreply.github.com> Date: Fri, 22 Nov 2019 19:06:52 -0800 Subject: [PATCH 0374/1071] Update README.md (#1588) * Update README.md * python -m unittest -v --- linear_algebra/README.md | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/linear_algebra/README.md b/linear_algebra/README.md index 169cd074d396..540920744948 100644 --- a/linear_algebra/README.md +++ b/linear_algebra/README.md @@ -1,6 +1,6 @@ # Linear algebra library for Python -This module contains some useful classes and functions for dealing with linear algebra in python 2. +This module contains classes and functions for doing linear algebra. --- @@ -8,7 +8,7 @@ This module contains some useful classes and functions for dealing with linear a ### class Vector - - - This class represents a vector of arbitray size and operations on it. + - This class represents a vector of arbitray size and related operations. **Overview about the methods:** @@ -58,22 +58,18 @@ This module contains some useful classes and functions for dealing with linear a ## Documentation -The module is well documented. You can use the python in-built ```help(...)``` function. -For instance: ```help(Vector)``` gives you all information about the Vector-class. -Or ```help(unitBasisVector)``` gives you all information you needed about the -global function ```unitBasisVector(...)```. If you need informations about a certain -method you type ```help(CLASSNAME.METHODNAME)```. +This module uses docstrings to enable the use of Python's in-built `help(...)` function. +For instance, try `help(Vector)`, `help(unitBasisVector)`, and `help(CLASSNAME.METHODNAME)`. --- ## Usage -You will find the module in the **src** directory its called ```lib.py```. You need to -import this module in your project. Alternative you can also use the file ```lib.pyc``` in python-bytecode. +Import the module `lib.py` from the **src** directory into your project. +Alternatively, you can directly use the Python bytecode file `lib.pyc`. --- ## Tests -In the **src** directory you also find the test-suite, its called ```tests.py```. -The test-suite uses the built-in python-test-framework **unittest**. +`src/tests.py` contains Python unit tests which can be run with `python3 -m unittest -v`. From 4c75f863c84e049026135d5ae04e6969fc569add Mon Sep 17 00:00:00 2001 From: vansh bhardwaj <39709733+vansh1999@users.noreply.github.com> Date: Sat, 23 Nov 2019 18:24:06 +0530 Subject: [PATCH 0375/1071] added current stock price (#1590) * added current stock price * Ten lines or less --- web_programming/current_stock_price.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 web_programming/current_stock_price.py diff --git a/web_programming/current_stock_price.py b/web_programming/current_stock_price.py new file mode 100644 index 000000000000..df44da4ef351 --- /dev/null +++ b/web_programming/current_stock_price.py @@ -0,0 +1,14 @@ +import requests +from bs4 import BeautifulSoup + + +def stock_price(symbol: str = "AAPL") -> str: + url = f"https://in.finance.yahoo.com/quote/{symbol}?s={symbol}" + soup = BeautifulSoup(requests.get(url).text, "html.parser") + class_ = "My(6px) Pos(r) smartphone_Mt(6px)" + return soup.find("div", class_=class_).find("span").text + + +if __name__ == "__main__": + for symbol in "AAPL AMZN IBM GOOG MSFT ORCL".split(): + print(f"Current {symbol:<4} stock price is {stock_price(symbol):>8}") From 2ad5a1f0836f9d8b8da77457f163a183d98a0bda Mon Sep 17 00:00:00 2001 From: achance6 <45263295+achance6@users.noreply.github.com> Date: Sat, 23 Nov 2019 10:52:32 -0500 Subject: [PATCH 0376/1071] Implemented simple keyword cipher (#1589) * Implemented simple keyword cipher * Added documentation and improved input processing * Allow object's hash function to be called * added to string functionality * reverted * Revised according to pull request #1589 * Optimized imports * Update simple_keyword_cypher.py * Update hash_table.py --- ciphers/simple_keyword_cypher.py | 90 ++++++++++++++++++++++++++++++++ 1 file changed, 90 insertions(+) create mode 100644 ciphers/simple_keyword_cypher.py diff --git a/ciphers/simple_keyword_cypher.py b/ciphers/simple_keyword_cypher.py new file mode 100644 index 000000000000..71c3083e9dfc --- /dev/null +++ b/ciphers/simple_keyword_cypher.py @@ -0,0 +1,90 @@ +def remove_duplicates(key: str) -> str: + """ + Removes duplicate alphabetic characters in a keyword (letter is ignored after its + first appearance). + :param key: Keyword to use + :return: String with duplicates removed + >>> remove_duplicates('Hello World!!') + 'Helo Wrd' + """ + + key_no_dups = "" + for ch in key: + if ch == " " or ch not in key_no_dups and ch.isalpha(): + key_no_dups += ch + return key_no_dups + + +def create_cipher_map(key: str) -> dict: + """ + Returns a cipher map given a keyword. + :param key: keyword to use + :return: dictionary cipher map + """ + # Create alphabet list + alphabet = [chr(i + 65) for i in range(26)] + # Remove duplicate characters from key + key = remove_duplicates(key.upper()) + offset = len(key) + # First fill cipher with key characters + cipher_alphabet = {alphabet[i]: char for i, char in enumerate(key)} + # Then map remaining characters in alphabet to + # the alphabet from the beginning + for i in range(len(cipher_alphabet), 26): + char = alphabet[i - offset] + # Ensure we are not mapping letters to letters previously mapped + while char in key: + offset -= 1 + char = alphabet[i - offset] + cipher_alphabet[alphabet[i]] = char + return cipher_alphabet + + +def encipher(message: str, cipher_map: dict) -> str: + """ + Enciphers a message given a cipher map. + :param message: Message to encipher + :param cipher_map: Cipher map + :return: enciphered string + >>> encipher('Hello World!!', create_cipher_map('Goodbye!!')) + 'CYJJM VMQJB!!' + """ + return "".join(cipher_map.get(ch, ch) for ch in message.upper()) + + +def decipher(message: str, cipher_map: dict) -> str: + """ + Deciphers a message given a cipher map + :param message: Message to decipher + :param cipher_map: Dictionary mapping to use + :return: Deciphered string + >>> cipher_map = create_cipher_map('Goodbye!!') + >>> decipher(encipher('Hello World!!', cipher_map), cipher_map) + 'HELLO WORLD!!' + """ + # Reverse our cipher mappings + rev_cipher_map = {v: k for k, v in cipher_map.items()} + return "".join(rev_cipher_map.get(ch, ch) for ch in message.upper()) + + +def main(): + """ + Handles I/O + :return: void + """ + message = input("Enter message to encode or decode: ").strip() + key = input("Enter keyword: ").strip() + option = input("Encipher or decipher? E/D:").strip()[0].lower() + try: + func = {"e": encipher, "d": decipher}[option] + except KeyError: + raise KeyError("invalid input option") + cipher_map = create_cipher_map(key) + print(func(message, cipher_map)) + + +if __name__ == "__main__": + import doctest + + doctest.testmod() + main() From 0d3c9d586ca3e4642aa88e1bbf88a008993e0019 Mon Sep 17 00:00:00 2001 From: Vikas Kumar <54888022+vikasit12@users.noreply.github.com> Date: Tue, 26 Nov 2019 11:15:28 +0530 Subject: [PATCH 0377/1071] Update singly_linked_list.py (#1593) * Update singly_linked_list.py printing current.data rather than node address in __repr__ for a more readable print statement * eval(repr(c)) == c The output of `__repr__()` _should look like a valid Python expression that could be used to recreate an object with the same value_. https://docs.python.org/3.4/reference/datamodel.html#object.__repr__ * += --> + --- .../linked_list/singly_linked_list.py | 76 ++++++++----------- 1 file changed, 32 insertions(+), 44 deletions(-) diff --git a/data_structures/linked_list/singly_linked_list.py b/data_structures/linked_list/singly_linked_list.py index 7137f4e66deb..8730a6d2a9e8 100644 --- a/data_structures/linked_list/singly_linked_list.py +++ b/data_structures/linked_list/singly_linked_list.py @@ -3,30 +3,30 @@ def __init__(self, data): self.data = data # given data self.next = None # given next to None - def __repr__(self): # String Representation of a Node - return f"" + def __repr__(self): # string representation of a Node + return f"Node({self.data})" class LinkedList: def __init__(self): - self.head = None # Initialize head to None + self.head = None # initialize head to None - def insert_tail(self, data): + def insert_tail(self, data) -> None: if self.head is None: - self.insert_head(data) # If this is first node, call insert_head + self.insert_head(data) # if this is first node, call insert_head else: temp = self.head while temp.next: # traverse to last node temp = temp.next temp.next = Node(data) # create node & link to tail - def insert_head(self, data): + def insert_head(self, data) -> None: newNod = Node(data) # create a new node if self.head: newNod.next = self.head # link newNode to head self.head = newNod # make NewNode as head - def printList(self): # print every node data + def print_list(self) -> None: # print every node data temp = self.head while temp: print(temp.data) @@ -47,14 +47,12 @@ def delete_tail(self): # delete from tail else: while temp.next.next: # find the 2nd last element temp = temp.next - temp.next, temp = ( - None, - temp.next, - ) # (2nd last element).next = None and temp = last element + # (2nd last element).next = None and temp = last element + temp.next, temp = None, temp.next return temp - def isEmpty(self): - return self.head is None # Return if head is none + def is_empty(self) -> bool: + return self.head is None # return True if head is none def reverse(self): prev = None @@ -76,17 +74,16 @@ def __repr__(self): # String representation/visualization of a Linked Lists current = self.head string_repr = "" while current: - string_repr += f"{current} ---> " + string_repr += f"{current} --> " current = current.next # END represents end of the LinkedList - string_repr += "END" - return string_repr + return string_repr + "END" # Indexing Support. Used to get a node at particaular position def __getitem__(self, index): current = self.head - # If LinkedList is Empty + # If LinkedList is empty if current is None: raise IndexError("The Linked List is empty") @@ -113,39 +110,30 @@ def __setitem__(self, index, data): def main(): A = LinkedList() - print("Inserting 1st at head") - a1 = input() - A.insert_head(a1) - print("Inserting 2nd at head") - a2 = input() - A.insert_head(a2) - print("\nPrint List : ") - A.printList() - print("\nInserting 1st at Tail") - a3 = input() - A.insert_tail(a3) - print("Inserting 2nd at Tail") - a4 = input() - A.insert_tail(a4) - print("\nPrint List : ") - A.printList() + A.insert_head(input("Inserting 1st at head ").strip()) + A.insert_head(input("Inserting 2nd at head ").strip()) + print("\nPrint list:") + A.print_list() + A.insert_tail(input("\nInserting 1st at tail ").strip()) + A.insert_tail(input("Inserting 2nd at tail ").strip()) + print("\nPrint list:") + A.print_list() print("\nDelete head") A.delete_head() - print("Delete Tail") + print("Delete tail") A.delete_tail() - print("\nPrint List : ") - A.printList() - print("\nReverse Linked List") + print("\nPrint list:") + A.print_list() + print("\nReverse linked list") A.reverse() - print("\nPrint List : ") - A.printList() - print("\nString Representation of Linked List:") + print("\nPrint list:") + A.print_list() + print("\nString representation of linked list:") print(A) - print("\n Reading/Changing Node Data using Indexing:") + print("\nReading/changing Node data using indexing:") print(f"Element at Position 1: {A[1]}") - p1 = input("Enter New Value: ") - A[1] = p1 - print("New List:") + A[1] = input("Enter New Value: ").strip() + print("New list:") print(A) From 140b79b4b2a184e041ce2e50e503d7a87b235b68 Mon Sep 17 00:00:00 2001 From: ELNS <57490926+EverLookNeverSee@users.noreply.github.com> Date: Tue, 26 Nov 2019 15:27:53 +0330 Subject: [PATCH 0378/1071] Adding Linear Discriminant Analysis (#1592) * Adding new file to the machine_learning directory * Adding initial documentation * importing modules * Adding Normal_gen function * Adding Y_gen function * Adding mean_calc function * Adding prob_calc function * Adding var_calc function * Adding predict function * Adding accuracy function * Adding main function * Renaming LDA file * Adding requested changes * Renaming some of functions * Refactoring str.format() statements to f-string * Removing unnecessary list objects inside two functions * changing code style in some lines * Fixing y_generator function * Refactoring 'predict_y_values' function by using list comprehensions * Changing code style in import statements * Refactoring CLI code block * fixup! Format Python code with psf/black push * No lines longer than 88 characters --- DIRECTORY.md | 7 + .../linear_discriminant_analysis.py | 330 ++++++++++++++++++ 2 files changed, 337 insertions(+) create mode 100644 machine_learning/linear_discriminant_analysis.py diff --git a/DIRECTORY.md b/DIRECTORY.md index 6e64f034df62..74c63d144e40 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -52,6 +52,7 @@ * [Rsa Factorization](https://github.com/TheAlgorithms/Python/blob/master/ciphers/rsa_factorization.py) * [Rsa Key Generator](https://github.com/TheAlgorithms/Python/blob/master/ciphers/rsa_key_generator.py) * [Shuffled Shift Cipher](https://github.com/TheAlgorithms/Python/blob/master/ciphers/shuffled_shift_cipher.py) + * [Simple Keyword Cypher](https://github.com/TheAlgorithms/Python/blob/master/ciphers/simple_keyword_cypher.py) * [Simple Substitution Cipher](https://github.com/TheAlgorithms/Python/blob/master/ciphers/simple_substitution_cipher.py) * [Trafid Cipher](https://github.com/TheAlgorithms/Python/blob/master/ciphers/trafid_cipher.py) * [Transposition Cipher](https://github.com/TheAlgorithms/Python/blob/master/ciphers/transposition_cipher.py) @@ -95,6 +96,7 @@ * [Heap](https://github.com/TheAlgorithms/Python/blob/master/data_structures/heap/heap.py) * [Min Heap](https://github.com/TheAlgorithms/Python/blob/master/data_structures/heap/min_heap.py) * Linked List + * [Circular Linked List](https://github.com/TheAlgorithms/Python/blob/master/data_structures/linked_list/circular_linked_list.py) * [Doubly Linked List](https://github.com/TheAlgorithms/Python/blob/master/data_structures/linked_list/doubly_linked_list.py) * [From Sequence](https://github.com/TheAlgorithms/Python/blob/master/data_structures/linked_list/from_sequence.py) * [Is Palindrome](https://github.com/TheAlgorithms/Python/blob/master/data_structures/linked_list/is_palindrome.py) @@ -102,6 +104,7 @@ * [Singly Linked List](https://github.com/TheAlgorithms/Python/blob/master/data_structures/linked_list/singly_linked_list.py) * [Swap Nodes](https://github.com/TheAlgorithms/Python/blob/master/data_structures/linked_list/swap_nodes.py) * Queue + * [Circular Queue](https://github.com/TheAlgorithms/Python/blob/master/data_structures/queue/circular_queue.py) * [Double Ended Queue](https://github.com/TheAlgorithms/Python/blob/master/data_structures/queue/double_ended_queue.py) * [Linked Queue](https://github.com/TheAlgorithms/Python/blob/master/data_structures/queue/linked_queue.py) * [Queue On List](https://github.com/TheAlgorithms/Python/blob/master/data_structures/queue/queue_on_list.py) @@ -149,6 +152,7 @@ * [Fibonacci](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/fibonacci.py) * [Floyd Warshall](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/floyd_warshall.py) * [Fractional Knapsack](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/fractional_knapsack.py) + * [Fractional Knapsack 2](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/fractional_knapsack_2.py) * [Integer Partition](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/integer_partition.py) * [K Means Clustering Tensorflow](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/k_means_clustering_tensorflow.py) * [Knapsack](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/knapsack.py) @@ -224,6 +228,7 @@ * [K Means Clust](https://github.com/TheAlgorithms/Python/blob/master/machine_learning/k_means_clust.py) * [K Nearest Neighbours](https://github.com/TheAlgorithms/Python/blob/master/machine_learning/k_nearest_neighbours.py) * [Knn Sklearn](https://github.com/TheAlgorithms/Python/blob/master/machine_learning/knn_sklearn.py) + * [Linear Discriminant Analysis](https://github.com/TheAlgorithms/Python/blob/master/machine_learning/linear_discriminant_analysis.py) * [Linear Regression](https://github.com/TheAlgorithms/Python/blob/master/machine_learning/linear_regression.py) * [Logistic Regression](https://github.com/TheAlgorithms/Python/blob/master/machine_learning/logistic_regression.py) * [Polymonial Regression](https://github.com/TheAlgorithms/Python/blob/master/machine_learning/polymonial_regression.py) @@ -521,6 +526,7 @@ * [Naive String Search](https://github.com/TheAlgorithms/Python/blob/master/strings/naive_string_search.py) * [Rabin Karp](https://github.com/TheAlgorithms/Python/blob/master/strings/rabin_karp.py) * [Remove Duplicate](https://github.com/TheAlgorithms/Python/blob/master/strings/remove_duplicate.py) + * [Reverse Words](https://github.com/TheAlgorithms/Python/blob/master/strings/reverse_words.py) * [Word Occurence](https://github.com/TheAlgorithms/Python/blob/master/strings/word_occurence.py) ## Traversals @@ -528,6 +534,7 @@ ## Web Programming * [Crawl Google Results](https://github.com/TheAlgorithms/Python/blob/master/web_programming/crawl_google_results.py) + * [Current Stock Price](https://github.com/TheAlgorithms/Python/blob/master/web_programming/current_stock_price.py) * [Fetch Bbc News](https://github.com/TheAlgorithms/Python/blob/master/web_programming/fetch_bbc_news.py) * [Fetch Github Info](https://github.com/TheAlgorithms/Python/blob/master/web_programming/fetch_github_info.py) * [Get Imdbtop](https://github.com/TheAlgorithms/Python/blob/master/web_programming/get_imdbtop.py) diff --git a/machine_learning/linear_discriminant_analysis.py b/machine_learning/linear_discriminant_analysis.py new file mode 100644 index 000000000000..8a89f6f5922e --- /dev/null +++ b/machine_learning/linear_discriminant_analysis.py @@ -0,0 +1,330 @@ +""" + Linear Discriminant Analysis + + + Assumptions About Data : + 1. The input variables has a gaussian distribution. + 2. The variance calculated for each input variables by class grouping is the + same. + 3. The mix of classes in your training set is representative of the problem. + + + Learning The Model : + The LDA model requires the estimation of statistics from the training data : + 1. Mean of each input value for each class. + 2. Probability of an instance belong to each class. + 3. Covariance for the input data for each class + + Calculate the class means : + mean(x) = 1/n ( for i = 1 to i = n --> sum(xi)) + + Calculate the class probabilities : + P(y = 0) = count(y = 0) / (count(y = 0) + count(y = 1)) + P(y = 1) = count(y = 1) / (count(y = 0) + count(y = 1)) + + Calculate the variance : + We can calculate the variance for dataset in two steps : + 1. Calculate the squared difference for each input variable from the + group mean. + 2. Calculate the mean of the squared difference. + ------------------------------------------------ + Squared_Difference = (x - mean(k)) ** 2 + Variance = (1 / (count(x) - count(classes))) * + (for i = 1 to i = n --> sum(Squared_Difference(xi))) + + Making Predictions : + discriminant(x) = x * (mean / variance) - + ((mean ** 2) / (2 * variance)) + Ln(probability) + --------------------------------------------------------------------------- + After calculating the discriminant value for each class, the class with the + largest discriminant value is taken as the prediction. + + Author: @EverLookNeverSee +""" + +from math import log +from os import name, system +from random import gauss + + +# Make a training dataset drawn from a gaussian distribution +def gaussian_distribution(mean: float, std_dev: float, instance_count: int) -> list: + """ + Generate gaussian distribution instances based-on given mean and standard deviation + :param mean: mean value of class + :param std_dev: value of standard deviation entered by usr or default value of it + :param instance_count: instance number of class + :return: a list containing generated values based-on given mean, std_dev and + instance_count + """ + return [gauss(mean, std_dev) for _ in range(instance_count)] + + +# Make corresponding Y flags to detecting classes +def y_generator(class_count: int, instance_count: list) -> list: + """ + Generate y values for corresponding classes + :param class_count: Number of classes(data groupings) in dataset + :param instance_count: number of instances in class + :return: corresponding values for data groupings in dataset + """ + + return [k for k in range(class_count) for _ in range(instance_count[k])] + + +# Calculate the class means +def calculate_mean(instance_count: int, items: list) -> float: + """ + Calculate given class mean + :param instance_count: Number of instances in class + :param items: items that related to specific class(data grouping) + :return: calculated actual mean of considered class + """ + # the sum of all items divided by number of instances + return sum(items) / instance_count + + +# Calculate the class probabilities +def calculate_probabilities(instance_count: int, total_count: int) -> float: + """ + Calculate the probability that a given instance will belong to which class + :param instance_count: number of instances in class + :param total_count: the number of all instances + :return: value of probability for considered class + """ + # number of instances in specific class divided by number of all instances + return instance_count / total_count + + +# Calculate the variance +def calculate_variance(items: list, means: list, total_count: int) -> float: + """ + Calculate the variance + :param items: a list containing all items(gaussian distribution of all classes) + :param means: a list containing real mean values of each class + :param total_count: the number of all instances + :return: calculated variance for considered dataset + """ + squared_diff = [] # An empty list to store all squared differences + # iterate over number of elements in items + for i in range(len(items)): + # for loop iterates over number of elements in inner layer of items + for j in range(len(items[i])): + # appending squared differences to 'squared_diff' list + squared_diff.append((items[i][j] - means[i]) ** 2) + + # one divided by (the number of all instances - number of classes) multiplied by + # sum of all squared differences + n_classes = len(means) # Number of classes in dataset + return 1 / (total_count - n_classes) * sum(squared_diff) + + +# Making predictions +def predict_y_values( + x_items: list, means: list, variance: float, probabilities: list +) -> list: + """ This function predicts new indexes(groups for our data) + :param x_items: a list containing all items(gaussian distribution of all classes) + :param means: a list containing real mean values of each class + :param variance: calculated value of variance by calculate_variance function + :param probabilities: a list containing all probabilities of classes + :return: a list containing predicted Y values + """ + # An empty list to store generated discriminant values of all items in dataset for + # each class + results = [] + # for loop iterates over number of elements in list + for i in range(len(x_items)): + # for loop iterates over number of inner items of each element + for j in range(len(x_items[i])): + temp = [] # to store all discriminant values of each item as a list + # for loop iterates over number of classes we have in our dataset + for k in range(len(x_items)): + # appending values of discriminants for each class to 'temp' list + temp.append( + x_items[i][j] * (means[k] / variance) + - (means[k] ** 2 / (2 * variance)) + + log(probabilities[k]) + ) + # appending discriminant values of each item to 'results' list + results.append(temp) + print("Generated Discriminants: \n", results) + return [l.index(max(l)) for l in results] + + +# Calculating Accuracy +def accuracy(actual_y: list, predicted_y: list) -> float: + """ + Calculate the value of accuracy based-on predictions + :param actual_y:a list containing initial Y values generated by 'y_generator' + function + :param predicted_y: a list containing predicted Y values generated by + 'predict_y_values' function + :return: percentage of accuracy + """ + # iterate over one element of each list at a time (zip mode) + # prediction is correct if actual Y value equals to predicted Y value + correct = sum(1 for i, j in zip(actual_y, predicted_y) if i == j) + # percentage of accuracy equals to number of correct predictions divided by number + # of all data and multiplied by 100 + return (correct / len(actual_y)) * 100 + + +# Main Function +def main(): + """ This function starts execution phase """ + while True: + print(" Linear Discriminant Analysis ".center(100, "*")) + print("*" * 100, "\n") + print("First of all we should specify the number of classes that") + print("we want to generate as training dataset") + # Trying to get number of classes + n_classes = 0 + while True: + try: + user_input = int( + input("Enter the number of classes (Data Groupings): ").strip() + ) + if user_input > 0: + n_classes = user_input + break + else: + print( + f"Your entered value is {user_input} , Number of classes " + f"should be positive!" + ) + continue + except ValueError: + print("Your entered value is not numerical!") + + print("-" * 100) + + std_dev = 1.0 # Default value for standard deviation of dataset + # Trying to get the value of standard deviation + while True: + try: + user_sd = float( + input( + "Enter the value of standard deviation" + "(Default value is 1.0 for all classes): " + ).strip() + or "1.0" + ) + if user_sd >= 0.0: + std_dev = user_sd + break + else: + print( + f"Your entered value is {user_sd}, Standard deviation should " + f"not be negative!" + ) + continue + except ValueError: + print("Your entered value is not numerical!") + + print("-" * 100) + + # Trying to get number of instances in classes and theirs means to generate + # dataset + counts = [] # An empty list to store instance counts of classes in dataset + for i in range(n_classes): + while True: + try: + user_count = int( + input(f"Enter The number of instances for class_{i+1}: ") + ) + if user_count > 0: + counts.append(user_count) + break + else: + print( + f"Your entered value is {user_count}, Number of " + f"instances should be positive!" + ) + continue + except ValueError: + print("Your entered value is not numerical!") + print("-" * 100) + + # An empty list to store values of user-entered means of classes + user_means = [] + for a in range(n_classes): + while True: + try: + user_mean = float( + input(f"Enter the value of mean for class_{a+1}: ") + ) + if isinstance(user_mean, float): + user_means.append(user_mean) + break + print(f"You entered an invalid value: {user_mean}") + except ValueError: + print("Your entered value is not numerical!") + print("-" * 100) + + print("Standard deviation: ", std_dev) + # print out the number of instances in classes in separated line + for i, count in enumerate(counts, 1): + print(f"Number of instances in class_{i} is: {count}") + print("-" * 100) + + # print out mean values of classes separated line + for i, user_mean in enumerate(user_means, 1): + print(f"Mean of class_{i} is: {user_mean}") + print("-" * 100) + + # Generating training dataset drawn from gaussian distribution + x = [ + gaussian_distribution(user_means[j], std_dev, counts[j]) + for j in range(n_classes) + ] + print("Generated Normal Distribution: \n", x) + print("-" * 100) + + # Generating Ys to detecting corresponding classes + y = y_generator(n_classes, counts) + print("Generated Corresponding Ys: \n", y) + print("-" * 100) + + # Calculating the value of actual mean for each class + actual_means = [calculate_mean(counts[k], x[k]) for k in range(n_classes)] + # for loop iterates over number of elements in 'actual_means' list and print + # out them in separated line + for i, actual_mean in enumerate(actual_means, 1): + print(f"Actual(Real) mean of class_{i} is: {actual_mean}") + print("-" * 100) + + # Calculating the value of probabilities for each class + # An empty list to store values of probabilities for each class + probabilities = ( + calculate_probabilities(counts[i], sum(counts)) for i in range(n_classes) + ) + # for loop iterates over number of elements in 'probabilities' list and print + # out them in separated line + for i, probability in enumerate(probabilities, 1): + print("Probability of class_{} is: {}".format(i, probability)) + print("-" * 100) + + # Calculating the values of variance for each class + variance = calculate_variance(x, actual_means, sum(counts)) + print("Variance: ", variance) + print("-" * 100) + + # Predicting Y values + # storing predicted Y values in 'pre_indexes' variable + pre_indexes = predict_y_values(x, actual_means, variance, probabilities) + print("-" * 100) + + # Calculating Accuracy of the model + print(f"Accuracy: {accuracy(y, pre_indexes)}") + print("-" * 100) + print(" DONE ".center(100, "+")) + + if input("Press any key to restart or 'q' for quit: ").strip().lower() == "q": + print("\n" + "GoodBye!".center(100, "-") + "\n") + break + system("cls" if name == "nt" else "clear") + + +if __name__ == "__main__": + main() From 4baf3972e1fc8b8e366e509762e4731bb158f0f5 Mon Sep 17 00:00:00 2001 From: Christian Clauss Date: Wed, 27 Nov 2019 11:30:21 +0100 Subject: [PATCH 0379/1071] GitHub Action to mark stale issues and pull requests (#1594) --- .github/workflows/stale.yml | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 .github/workflows/stale.yml diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml new file mode 100644 index 000000000000..1d1d743fa832 --- /dev/null +++ b/.github/workflows/stale.yml @@ -0,0 +1,15 @@ +name: Mark stale issues and pull requests +on: + schedule: + - cron: "0 0 * * *" +jobs: + stale: + runs-on: ubuntu-latest + steps: + - uses: actions/stale@v1 + with: + repo-token: ${{ secrets.GITHUB_TOKEN }} + stale-issue-message: 'Stale issue message' + stale-pr-message: 'Stale pull request message' + stale-issue-label: 'no-issue-activity' + stale-pr-label: 'no-pr-activity' From f4a7c5066c1921842a158976e349b4c6a6955d72 Mon Sep 17 00:00:00 2001 From: ELNS <57490926+EverLookNeverSee@users.noreply.github.com> Date: Thu, 28 Nov 2019 19:51:34 +0330 Subject: [PATCH 0380/1071] converting generator object to a list object (#1602) * converting generator object to a list object * Refactor: converting generator object to a list object * fixup! Format Python code with psf/black push --- machine_learning/linear_discriminant_analysis.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/machine_learning/linear_discriminant_analysis.py b/machine_learning/linear_discriminant_analysis.py index 8a89f6f5922e..62dc34af6bbd 100644 --- a/machine_learning/linear_discriminant_analysis.py +++ b/machine_learning/linear_discriminant_analysis.py @@ -295,10 +295,10 @@ def main(): print("-" * 100) # Calculating the value of probabilities for each class - # An empty list to store values of probabilities for each class - probabilities = ( + probabilities = [ calculate_probabilities(counts[i], sum(counts)) for i in range(n_classes) - ) + ] + # for loop iterates over number of elements in 'probabilities' list and print # out them in separated line for i, probability in enumerate(probabilities, 1): From 2fb6f786ceff9fef94494d73d541a4e7e5feafed Mon Sep 17 00:00:00 2001 From: Christian Clauss Date: Thu, 28 Nov 2019 19:53:37 +0100 Subject: [PATCH 0381/1071] Typo in a comment (#1603) --- .github/workflows/autoblack.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/autoblack.yml b/.github/workflows/autoblack.yml index dc76d4cee068..cf578a14da95 100644 --- a/.github/workflows/autoblack.yml +++ b/.github/workflows/autoblack.yml @@ -1,5 +1,5 @@ # GitHub Action that uses Black to reformat Python code (if needed) when doing a git push. -# If all Python code in the repo is complient with Black then this Action does nothing. +# If all Python code in the repo is compliant with Black then this Action does nothing. # Otherwise, Black is run and its changes are committed to the repo. # https://github.com/cclauss/autoblack From 5d20dbfb98a19634db0961318f5378f50e94c428 Mon Sep 17 00:00:00 2001 From: Saurabh Goyal Date: Sat, 30 Nov 2019 10:47:13 +0530 Subject: [PATCH 0382/1071] add a generic heap (#906) * add a generic heap * Delete __init__.py * Rename data_structures/Heap/heap_generic.py to data_structures/heap/heap_generic.py * Add doctests * Fix doctests * Fix doctests again --- .../data_structures/heap/heap_generic.py | 162 ++++++++++++++++++ 1 file changed, 162 insertions(+) create mode 100644 data_structures/data_structures/heap/heap_generic.py diff --git a/data_structures/data_structures/heap/heap_generic.py b/data_structures/data_structures/heap/heap_generic.py new file mode 100644 index 000000000000..c41a434542e7 --- /dev/null +++ b/data_structures/data_structures/heap/heap_generic.py @@ -0,0 +1,162 @@ +class Heap(object): + """A generic Heap class, can be used as min or max by passing the key function accordingly. + """ + + def __init__(self, key=None): + # Stores actual heap items. + self.arr = list() + # Stores indexes of each item for supporting updates and deletion. + self.pos_map = {} + # Stores current size of heap. + self.size = 0 + # Stores function used to evaluate the score of an item on which basis ordering will be done. + self.key = key or (lambda x: x) + + def _parent(self, i): + """Returns parent index of given index if exists else None""" + return int((i - 1) / 2) if i > 0 else None + + def _left(self, i): + """Returns left-child-index of given index if exists else None""" + left = int(2 * i + 1) + return left if 0 < left < self.size else None + + def _right(self, i): + """Returns right-child-index of given index if exists else None""" + right = int(2 * i + 2) + return right if 0 < right < self.size else None + + def _swap(self, i, j): + """Performs changes required for swapping two elements in the heap""" + # First update the indexes of the items in index map. + self.pos_map[self.arr[i][0]], self.pos_map[self.arr[j][0]] = ( + self.pos_map[self.arr[j][0]], self.pos_map[self.arr[i][0]] + ) + # Then swap the items in the list. + self.arr[i], self.arr[j] = self.arr[j], self.arr[i] + + def _cmp(self, i, j): + """Compares the two items using default comparison""" + return self.arr[i][1] < self.arr[j][1] + + def _get_valid_parent(self, i): + """Returns index of valid parent as per desired ordering among given index and both it's children""" + left = self._left(i) + right = self._right(i) + valid_parent = i + + if left is not None and not self._cmp(left, valid_parent): + valid_parent = left + if right is not None and not self._cmp(right, valid_parent): + valid_parent = right + + return valid_parent + + def _heapify_up(self, index): + """Fixes the heap in upward direction of given index""" + parent = self._parent(index) + while parent is not None and not self._cmp(index, parent): + self._swap(index, parent) + index, parent = parent, self._parent(parent) + + def _heapify_down(self, index): + """Fixes the heap in downward direction of given index""" + valid_parent = self._get_valid_parent(index) + while valid_parent != index: + self._swap(index, valid_parent) + index, valid_parent = valid_parent, self._get_valid_parent(valid_parent) + + def update_item(self, item, item_value): + """Updates given item value in heap if present""" + if item not in self.pos_map: + return + index = self.pos_map[item] + self.arr[index] = [item, self.key(item_value)] + # Make sure heap is right in both up and down direction. + # Ideally only one of them will make any change. + self._heapify_up(index) + self._heapify_down(index) + + def delete_item(self, item): + """Deletes given item from heap if present""" + if item not in self.pos_map: + return + index = self.pos_map[item] + del self.pos_map[item] + self.arr[index] = self.arr[self.size - 1] + self.pos_map[self.arr[self.size - 1][0]] = index + self.size -= 1 + # Make sure heap is right in both up and down direction. + # Ideally only one of them will make any change- so no performance loss in calling both. + if self.size > index: + self._heapify_up(index) + self._heapify_down(index) + + def insert_item(self, item, item_value): + """Inserts given item with given value in heap""" + arr_len = len(self.arr) + if arr_len == self.size: + self.arr.append([item, self.key(item_value)]) + else: + self.arr[self.size] = [item, self.key(item_value)] + self.pos_map[item] = self.size + self.size += 1 + self._heapify_up(self.size - 1) + + def get_top(self): + """Returns top item tuple (Calculated value, item) from heap if present""" + return self.arr[0] if self.size else None + + def extract_top(self): + """Returns top item tuple (Calculated value, item) from heap and removes it as well if present""" + top_item_tuple = self.get_top() + if top_item_tuple: + self.delete_item(top_item_tuple[0]) + return top_item_tuple + + +def test_heap() -> None: + """ + >>> h = Heap() # Max-heap + >>> h.insert_item(5, 34) + >>> h.insert_item(6, 31) + >>> h.insert_item(7, 37) + >>> h.get_top() + [7, 37] + >>> h.extract_top() + [7, 37] + >>> h.extract_top() + [5, 34] + >>> h.extract_top() + [6, 31] + >>> h = Heap(key=lambda x: -x) # Min heap + >>> h.insert_item(5, 34) + >>> h.insert_item(6, 31) + >>> h.insert_item(7, 37) + >>> h.get_top() + [6, -31] + >>> h.extract_top() + [6, -31] + >>> h.extract_top() + [5, -34] + >>> h.extract_top() + [7, -37] + >>> h.insert_item(8, 45) + >>> h.insert_item(9, 40) + >>> h.insert_item(10, 50) + >>> h.get_top() + [9, -40] + >>> h.update_item(10, 30) + >>> h.get_top() + [10, -30] + >>> h.delete_item(10) + >>> h.get_top() + [9, -40] + """ + pass + + +if __name__ == "__main__": + import doctest + + doctest.testmod() From 415c9f5e6547457eb3546b467283cbd9e82e4eec Mon Sep 17 00:00:00 2001 From: Bruno Santos <7022432+dunderbruno@users.noreply.github.com> Date: Sun, 1 Dec 2019 02:13:28 -0300 Subject: [PATCH 0383/1071] Improve prim.py (#1226) * suiting PEP8 * create auxiliary function * running example * updating DIRECTORY.md --- DIRECTORY.md | 3 ++ graphs/prim.py | 76 +++++++++++++++++++++++++++++++++----------------- 2 files changed, 53 insertions(+), 26 deletions(-) diff --git a/DIRECTORY.md b/DIRECTORY.md index 74c63d144e40..a3db3636e34c 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -82,6 +82,9 @@ * [Red Black Tree](https://github.com/TheAlgorithms/Python/blob/master/data_structures/binary_tree/red_black_tree.py) * [Segment Tree](https://github.com/TheAlgorithms/Python/blob/master/data_structures/binary_tree/segment_tree.py) * [Treap](https://github.com/TheAlgorithms/Python/blob/master/data_structures/binary_tree/treap.py) + * Data Structures + * Heap + * [Heap Generic](https://github.com/TheAlgorithms/Python/blob/master/data_structures/data_structures/heap/heap_generic.py) * Disjoint Set * [Disjoint Set](https://github.com/TheAlgorithms/Python/blob/master/data_structures/disjoint_set/disjoint_set.py) * Hashing diff --git a/graphs/prim.py b/graphs/prim.py index 336424d2c3c1..16cfaee089cb 100644 --- a/graphs/prim.py +++ b/graphs/prim.py @@ -2,26 +2,12 @@ Prim's Algorithm. Determines the minimum spanning tree(MST) of a graph using the Prim's Algorithm - -Create a list to store x the vertices. -G = [vertex(n) for n in range(x)] - -For each vertex in G, add the neighbors: -G[x].addNeighbor(G[y]) -G[y].addNeighbor(G[x]) - -For each vertex in G, add the edges: -G[x].addEdge(G[y], w) -G[y].addEdge(G[x], w) - -To solve run: -MST = prim(G, G[0]) """ import math -class vertex: +class Vertex: """Class Vertex.""" def __init__(self, id): @@ -36,7 +22,7 @@ def __init__(self, id): self.key = None self.pi = None self.neighbors = [] - self.edges = {} # [vertex:distance] + self.edges = {} # {vertex:distance} def __lt__(self, other): """Comparison rule to < operator.""" @@ -46,34 +32,72 @@ def __repr__(self): """Return the vertex id.""" return self.id - def addNeighbor(self, vertex): + def add_neighbor(self, vertex): """Add a pointer to a vertex at neighbor's list.""" self.neighbors.append(vertex) - def addEdge(self, vertex, weight): + def add_edge(self, vertex, weight): """Destination vertex and weight.""" self.edges[vertex.id] = weight +def connect(graph, a, b, edge): + # add the neighbors: + graph[a - 1].add_neighbor(graph[b - 1]) + graph[b - 1].add_neighbor(graph[a - 1]) + # add the edges: + graph[a - 1].add_edge(graph[b - 1], edge) + graph[b - 1].add_edge(graph[a - 1], edge) + + def prim(graph, root): """ Prim's Algorithm. Return a list with the edges of a Minimum Spanning Tree prim(graph, graph[0]) """ - A = [] + a = [] for u in graph: u.key = math.inf u.pi = None root.key = 0 - Q = graph[:] - while Q: - u = min(Q) - Q.remove(u) + q = graph[:] + while q: + u = min(q) + q.remove(u) for v in u.neighbors: - if (v in Q) and (u.edges[v.id] < v.key): + if (v in q) and (u.edges[v.id] < v.key): v.pi = u v.key = u.edges[v.id] for i in range(1, len(graph)): - A.append([graph[i].id, graph[i].pi.id]) - return A + a.append((int(graph[i].id) + 1, int(graph[i].pi.id) + 1)) + return a + + +def test_vector() -> None: + """ + # Creates a list to store x vertices. + >>> x = 5 + >>> G = [Vertex(n) for n in range(x)] + + >>> connect(G, 1, 2, 15) + >>> connect(G, 1, 3, 12) + >>> connect(G, 2, 4, 13) + >>> connect(G, 2, 5, 5) + >>> connect(G, 3, 2, 6) + >>> connect(G, 3, 4, 6) + >>> connect(G, 0, 0, 0) # Generate the minimum spanning tree: + >>> MST = prim(G, G[0]) + >>> for i in MST: + ... print(i) + (2, 3) + (3, 1) + (4, 3) + (5, 2) + """ + + +if __name__ == "__main__": + import doctest + + doctest.testmod() From 4dca9571dba5b6d0ce31fe49e8928451573a34af Mon Sep 17 00:00:00 2001 From: Bruno Santos <7022432+dunderbruno@users.noreply.github.com> Date: Sun, 1 Dec 2019 02:29:23 -0300 Subject: [PATCH 0384/1071] Pythagoras (#1243) * add pythagoras.py * function distance * run as script * Update pythagoras.py --- maths/pythagoras.py | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 maths/pythagoras.py diff --git a/maths/pythagoras.py b/maths/pythagoras.py new file mode 100644 index 000000000000..2f59107cdfaa --- /dev/null +++ b/maths/pythagoras.py @@ -0,0 +1,33 @@ +"""Uses Pythagoras theorem to calculate the distance between two points in space.""" + +import math + + +class Point: + def __init__(self, x, y, z): + self.x = x + self.y = y + self.z = z + + def __repr__(self) -> str: + return f"Point({self.x}, {self.y}, {self.z})" + + +def distance(a: Point, b: Point) -> float: + return math.sqrt(abs(((b.x - a.x) ** 2 + (b.y - a.y) ** 2 + (b.z - a.z) ** 2))) + + +def test_distance() -> None: + """ + >>> point1 = Point(2, -1, 7) + >>> point2 = Point(1, -3, 5) + >>> print(f"Distance from {point1} to {point2} is {distance(point1, point2)}") + Distance from Point(2, -1, 7) to Point(1, -3, 5) is 3.0 + """ + pass + + +if __name__ == "__main__": + import doctest + + doctest.testmod() From 74aeaa333f81912b696e0cf069e4993bc94113cc Mon Sep 17 00:00:00 2001 From: Abhijit Patil Date: Sun, 1 Dec 2019 11:28:25 +0530 Subject: [PATCH 0385/1071] Code for Eulers Totient function (#1229) * Create eulersTotient.py * Rename eulersTotient.py to eulers_totient.py * Update eulers_totient.py --- maths/eulers_totient.py | 45 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100644 maths/eulers_totient.py diff --git a/maths/eulers_totient.py b/maths/eulers_totient.py new file mode 100644 index 000000000000..6a35e69bde0b --- /dev/null +++ b/maths/eulers_totient.py @@ -0,0 +1,45 @@ +# Eulers Totient function finds the number of relative primes of a number n from 1 to n +def totient(n: int) -> list: + is_prime = [True for i in range(n + 1)] + totients = [i - 1 for i in range(n + 1)] + primes = [] + for i in range(2, n + 1): + if is_prime[i]: + primes.append(i) + for j in range(0, len(primes)): + if i * primes[j] >= n: + break + is_prime[i * primes[j]] = False + + if i % primes[j] == 0: + totients[i * primes[j]] = totients[i] * primes[j] + break + + totients[i * primes[j]] = totients[i] * (primes[j] - 1) + + return totients + + +def test_totient() -> None: + """ + >>> n = 10 + >>> totient_calculation = totient(n) + >>> for i in range(1, n): + ... print(f"{i} has {totient_calculation[i]} relative primes.") + 1 has 0 relative primes. + 2 has 1 relative primes. + 3 has 2 relative primes. + 4 has 2 relative primes. + 5 has 4 relative primes. + 6 has 2 relative primes. + 7 has 6 relative primes. + 8 has 4 relative primes. + 9 has 6 relative primes. + """ + pass + + +if __name__ == "__main__": + import doctest + + doctest.testmod() From 8ffc4f8706dc5ecb7cd015839f1cb92997217c63 Mon Sep 17 00:00:00 2001 From: GeorgeChambi Date: Tue, 3 Dec 2019 11:14:30 +0000 Subject: [PATCH 0386/1071] fixed bug (#1610) Removed comma from print statement causing and error. --- ciphers/caesar_cipher.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ciphers/caesar_cipher.py b/ciphers/caesar_cipher.py index 200f868051d4..4427a5234d70 100644 --- a/ciphers/caesar_cipher.py +++ b/ciphers/caesar_cipher.py @@ -39,7 +39,7 @@ def brute_force(input_string: str) -> None: def main(): while True: - print(f'{"-" * 10}\n Menu\n{"-", * 10}') + print(f'{"-" * 10}\n Menu\n{"-" * 10}') print(*["1.Encrpyt", "2.Decrypt", "3.BruteForce", "4.Quit"], sep="\n") choice = input("What would you like to do?: ") if choice not in ["1", "2", "3", "4"]: From caad74466aaa6a98465e10bd7adf828482d2de63 Mon Sep 17 00:00:00 2001 From: QuantumNovice <43876848+QuantumNovice@users.noreply.github.com> Date: Tue, 3 Dec 2019 16:17:42 +0500 Subject: [PATCH 0387/1071] Added Multilayer Perceptron (sklearn) (#1609) * Added Multilayer Perceptron ( sklearn) * Rename MLPClassifier.py to multilayer_preceptron_classifier.py * Rename multilayer_preceptron_classifier.py to multilayer_perceptron_classifier.py * Update multilayer_perceptron_classifier.py --- .../multilayer_perceptron_classifier.py | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 machine_learning/multilayer_perceptron_classifier.py diff --git a/machine_learning/multilayer_perceptron_classifier.py b/machine_learning/multilayer_perceptron_classifier.py new file mode 100644 index 000000000000..d78d2a9ed8eb --- /dev/null +++ b/machine_learning/multilayer_perceptron_classifier.py @@ -0,0 +1,30 @@ +from sklearn.neural_network import MLPClassifier + + +X = [[0.0, 0.0], [1.0, 1.0], [1.0, 0.0], [0.0, 1.0]] +y = [0, 1, 0, 0] + + +clf = MLPClassifier( + solver="lbfgs", alpha=1e-5, hidden_layer_sizes=(5, 2), random_state=1 +) + +clf.fit(X, y) + + +test = [[0.0, 0.0], [0.0, 1.0], [1.0, 1.0]] +Y = clf.predict(test) + + +def wrapper(Y): + """ + >>> wrapper(Y) + [0, 0, 1] + """ + return list(Y) + + +if __name__ == "__main__": + import doctest + + doctest.testmod() From 494fb4fb490c49d46c693933c5583ac2fb4f665b Mon Sep 17 00:00:00 2001 From: Bardia Alavi Date: Wed, 4 Dec 2019 23:06:41 -0500 Subject: [PATCH 0388/1071] address merge_soft duplicate files (#1612) Here the old file merge_sort_fastest is renamed to unknown_sort. Because it is not merge sort algorithm. Comments are updated accordingly. --- sorts/{merge_sort_fastest.py => unknown_sort.py} | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) rename sorts/{merge_sort_fastest.py => unknown_sort.py} (88%) diff --git a/sorts/merge_sort_fastest.py b/sorts/unknown_sort.py similarity index 88% rename from sorts/merge_sort_fastest.py rename to sorts/unknown_sort.py index f3c067795dd5..087533b4a575 100644 --- a/sorts/merge_sort_fastest.py +++ b/sorts/unknown_sort.py @@ -1,6 +1,5 @@ """ -Python implementation of the fastest merge sort algorithm. -Takes an average of 0.6 microseconds to sort a list of length 1000 items. +Python implementation of a sort algorithm. Best Case Scenario : O(n) Worst Case Scenario : O(n^2) because native python functions:min, max and remove are already O(n) """ From 3cfca42f17e4e5f3d31da30eb80f7c0baa66eb4b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Gustavo=20A=2E=20Amorim?= Date: Fri, 6 Dec 2019 03:13:10 -0300 Subject: [PATCH 0389/1071] add the index calculation class at digital_image_processing and the hamming code algorithm at hashes (#1152) * add the index calculation at difital_image_processing file * make changes at index_calculation * update the variables to self variables at functions * update the word wrap in comments at index_calculation * add the hamming code algorithm * Wrap long lines --- digital_image_processing/index_calculation.py | 571 ++++++++++++++++++ hashes/hamming_code.py | 315 ++++++++++ 2 files changed, 886 insertions(+) create mode 100644 digital_image_processing/index_calculation.py create mode 100644 hashes/hamming_code.py diff --git a/digital_image_processing/index_calculation.py b/digital_image_processing/index_calculation.py new file mode 100644 index 000000000000..f4f8759ad010 --- /dev/null +++ b/digital_image_processing/index_calculation.py @@ -0,0 +1,571 @@ +# Author: João Gustavo A. Amorim +# Author email: joaogustavoamorim@gmail.com +# Coding date: jan 2019 +# python/black: True + +# Imports +import numpy as np + +# Class implemented to calculus the index +class IndexCalculation: + """ + # Class Summary + This algorithm consists in calculating vegetation indices, these indices + can be used for precision agriculture for example (or remote sensing). There are + functions to define the data and to calculate the implemented indices. + + # Vegetation index + https://en.wikipedia.org/wiki/Vegetation_Index + A Vegetation Index (VI) is a spectral transformation of two or more bands designed + to enhance the contribution of vegetation properties and allow reliable spatial and + temporal inter-comparisons of terrestrial photosynthetic activity and canopy + structural variations + + # Information about channels (Wavelength range for each) + * nir - near-infrared + https://www.malvernpanalytical.com/br/products/technology/near-infrared-spectroscopy + Wavelength Range 700 nm to 2500 nm + * Red Edge + https://en.wikipedia.org/wiki/Red_edge + Wavelength Range 680 nm to 730 nm + * red + https://en.wikipedia.org/wiki/Color + Wavelength Range 635 nm to 700 nm + * blue + https://en.wikipedia.org/wiki/Color + Wavelength Range 450 nm to 490 nm + * green + https://en.wikipedia.org/wiki/Color + Wavelength Range 520 nm to 560 nm + + + # Implemented index list + #"abbreviationOfIndexName" -- list of channels used + + #"ARVI2" -- red, nir + #"CCCI" -- red, redEdge, nir + #"CVI" -- red, green, nir + #"GLI" -- red, green, blue + #"NDVI" -- red, nir + #"BNDVI" -- blue, nir + #"redEdgeNDVI" -- red, redEdge + #"GNDVI" -- green, nir + #"GBNDVI" -- green, blue, nir + #"GRNDVI" -- red, green, nir + #"RBNDVI" -- red, blue, nir + #"PNDVI" -- red, green, blue, nir + #"ATSAVI" -- red, nir + #"BWDRVI" -- blue, nir + #"CIgreen" -- green, nir + #"CIrededge" -- redEdge, nir + #"CI" -- red, blue + #"CTVI" -- red, nir + #"GDVI" -- green, nir + #"EVI" -- red, blue, nir + #"GEMI" -- red, nir + #"GOSAVI" -- green, nir + #"GSAVI" -- green, nir + #"Hue" -- red, green, blue + #"IVI" -- red, nir + #"IPVI" -- red, nir + #"I" -- red, green, blue + #"RVI" -- red, nir + #"MRVI" -- red, nir + #"MSAVI" -- red, nir + #"NormG" -- red, green, nir + #"NormNIR" -- red, green, nir + #"NormR" -- red, green, nir + #"NGRDI" -- red, green + #"RI" -- red, green + #"S" -- red, green, blue + #"IF" -- red, green, blue + #"DVI" -- red, nir + #"TVI" -- red, nir + #"NDRE" -- redEdge, nir + + #list of all index implemented + #allIndex = ["ARVI2", "CCCI", "CVI", "GLI", "NDVI", "BNDVI", "redEdgeNDVI", "GNDVI", + "GBNDVI", "GRNDVI", "RBNDVI", "PNDVI", "ATSAVI", "BWDRVI", "CIgreen", + "CIrededge", "CI", "CTVI", "GDVI", "EVI", "GEMI", "GOSAVI", "GSAVI", + "Hue", "IVI", "IPVI", "I", "RVI", "MRVI", "MSAVI", "NormG", "NormNIR", + "NormR", "NGRDI", "RI", "S", "IF", "DVI", "TVI", "NDRE"] + + #list of index with not blue channel + #notBlueIndex = ["ARVI2", "CCCI", "CVI", "NDVI", "redEdgeNDVI", "GNDVI", "GRNDVI", + "ATSAVI", "CIgreen", "CIrededge", "CTVI", "GDVI", "GEMI", "GOSAVI", + "GSAVI", "IVI", "IPVI", "RVI", "MRVI", "MSAVI", "NormG", "NormNIR", + "NormR", "NGRDI", "RI", "DVI", "TVI", "NDRE"] + + #list of index just with RGB channels + #RGBIndex = ["GLI", "CI", "Hue", "I", "NGRDI", "RI", "S", "IF"] + """ + + def __init__(self, red=None, green=None, blue=None, redEdge=None, nir=None): + # print("Numpy version: " + np.__version__) + self.setMatrices(red=red, green=green, blue=blue, redEdge=redEdge, nir=nir) + + def setMatrices(self, red=None, green=None, blue=None, redEdge=None, nir=None): + if red is not None: + self.red = red + if green is not None: + self.green = green + if blue is not None: + self.blue = blue + if redEdge is not None: + self.redEdge = redEdge + if nir is not None: + self.nir = nir + return True + + def calculation( + self, index="", red=None, green=None, blue=None, redEdge=None, nir=None + ): + """ + performs the calculation of the index with the values instantiated in the class + :str index: abbreviation of index name to perform + """ + self.setMatrices(red=red, green=green, blue=blue, redEdge=redEdge, nir=nir) + funcs = { + "ARVI2": self.ARVI2, + "CCCI": self.CCCI, + "CVI": self.CVI, + "GLI": self.GLI, + "NDVI": self.NDVI, + "BNDVI": self.BNDVI, + "redEdgeNDVI": self.redEdgeNDVI, + "GNDVI": self.GNDVI, + "GBNDVI": self.GBNDVI, + "GRNDVI": self.GRNDVI, + "RBNDVI": self.RBNDVI, + "PNDVI": self.PNDVI, + "ATSAVI": self.ATSAVI, + "BWDRVI": self.BWDRVI, + "CIgreen": self.CIgreen, + "CIrededge": self.CIrededge, + "CI": self.CI, + "CTVI": self.CTVI, + "GDVI": self.GDVI, + "EVI": self.EVI, + "GEMI": self.GEMI, + "GOSAVI": self.GOSAVI, + "GSAVI": self.GSAVI, + "Hue": self.Hue, + "IVI": self.IVI, + "IPVI": self.IPVI, + "I": self.I, + "RVI": self.RVI, + "MRVI": self.MRVI, + "MSAVI": self.MSAVI, + "NormG": self.NormG, + "NormNIR": self.NormNIR, + "NormR": self.NormR, + "NGRDI": self.NGRDI, + "RI": self.RI, + "S": self.S, + "IF": self.IF, + "DVI": self.DVI, + "TVI": self.TVI, + "NDRE": self.NDRE, + } + + try: + return funcs[index]() + except KeyError: + print("Index not in the list!") + return False + + def ARVI2(self): + """ + Atmospherically Resistant Vegetation Index 2 + https://www.indexdatabase.de/db/i-single.php?id=396 + :return: index + −0.18+1.17*(self.nir−self.red)/(self.nir+self.red) + """ + return -0.18 + (1.17 * ((self.nir - self.red) / (self.nir + self.red))) + + def CCCI(self): + """ + Canopy Chlorophyll Content Index + https://www.indexdatabase.de/db/i-single.php?id=224 + :return: index + """ + return ((self.nir - self.redEdge) / (self.nir + self.redEdge)) / ( + (self.nir - self.red) / (self.nir + self.red) + ) + + def CVI(self): + """ + Chlorophyll vegetation index + https://www.indexdatabase.de/db/i-single.php?id=391 + :return: index + """ + return self.nir * (self.red / (self.green ** 2)) + + def GLI(self): + """ + self.green leaf index + https://www.indexdatabase.de/db/i-single.php?id=375 + :return: index + """ + return (2 * self.green - self.red - self.blue) / ( + 2 * self.green + self.red + self.blue + ) + + def NDVI(self): + """ + Normalized Difference self.nir/self.red Normalized Difference Vegetation Index, + Calibrated NDVI - CDVI + https://www.indexdatabase.de/db/i-single.php?id=58 + :return: index + """ + return (self.nir - self.red) / (self.nir + self.red) + + def BNDVI(self): + """ + Normalized Difference self.nir/self.blue self.blue-normalized difference + vegetation index + https://www.indexdatabase.de/db/i-single.php?id=135 + :return: index + """ + return (self.nir - self.blue) / (self.nir + self.blue) + + def redEdgeNDVI(self): + """ + Normalized Difference self.rededge/self.red + https://www.indexdatabase.de/db/i-single.php?id=235 + :return: index + """ + return (self.redEdge - self.red) / (self.redEdge + self.red) + + def GNDVI(self): + """ + Normalized Difference self.nir/self.green self.green NDVI + https://www.indexdatabase.de/db/i-single.php?id=401 + :return: index + """ + return (self.nir - self.green) / (self.nir + self.green) + + def GBNDVI(self): + """ + self.green-self.blue NDVI + https://www.indexdatabase.de/db/i-single.php?id=186 + :return: index + """ + return (self.nir - (self.green + self.blue)) / ( + self.nir + (self.green + self.blue) + ) + + def GRNDVI(self): + """ + self.green-self.red NDVI + https://www.indexdatabase.de/db/i-single.php?id=185 + :return: index + """ + return (self.nir - (self.green + self.red)) / ( + self.nir + (self.green + self.red) + ) + + def RBNDVI(self): + """ + self.red-self.blue NDVI + https://www.indexdatabase.de/db/i-single.php?id=187 + :return: index + """ + return (self.nir - (self.blue + self.red)) / (self.nir + (self.blue + self.red)) + + def PNDVI(self): + """ + Pan NDVI + https://www.indexdatabase.de/db/i-single.php?id=188 + :return: index + """ + return (self.nir - (self.green + self.red + self.blue)) / ( + self.nir + (self.green + self.red + self.blue) + ) + + def ATSAVI(self, X=0.08, a=1.22, b=0.03): + """ + Adjusted transformed soil-adjusted VI + https://www.indexdatabase.de/db/i-single.php?id=209 + :return: index + """ + return a * ( + (self.nir - a * self.red - b) + / (a * self.nir + self.red - a * b + X * (1 + a ** 2)) + ) + + def BWDRVI(self): + """ + self.blue-wide dynamic range vegetation index + https://www.indexdatabase.de/db/i-single.php?id=136 + :return: index + """ + return (0.1 * self.nir - self.blue) / (0.1 * self.nir + self.blue) + + def CIgreen(self): + """ + Chlorophyll Index self.green + https://www.indexdatabase.de/db/i-single.php?id=128 + :return: index + """ + return (self.nir / self.green) - 1 + + def CIrededge(self): + """ + Chlorophyll Index self.redEdge + https://www.indexdatabase.de/db/i-single.php?id=131 + :return: index + """ + return (self.nir / self.redEdge) - 1 + + def CI(self): + """ + Coloration Index + https://www.indexdatabase.de/db/i-single.php?id=11 + :return: index + """ + return (self.red - self.blue) / self.red + + def CTVI(self): + """ + Corrected Transformed Vegetation Index + https://www.indexdatabase.de/db/i-single.php?id=244 + :return: index + """ + ndvi = self.NDVI() + return ((ndvi + 0.5) / (abs(ndvi + 0.5))) * (abs(ndvi + 0.5) ** (1 / 2)) + + def GDVI(self): + """ + Difference self.nir/self.green self.green Difference Vegetation Index + https://www.indexdatabase.de/db/i-single.php?id=27 + :return: index + """ + return self.nir - self.green + + def EVI(self): + """ + Enhanced Vegetation Index + https://www.indexdatabase.de/db/i-single.php?id=16 + :return: index + """ + return 2.5 * ( + (self.nir - self.red) / (self.nir + 6 * self.red - 7.5 * self.blue + 1) + ) + + def GEMI(self): + """ + Global Environment Monitoring Index + https://www.indexdatabase.de/db/i-single.php?id=25 + :return: index + """ + n = (2 * (self.nir ** 2 - self.red ** 2) + 1.5 * self.nir + 0.5 * self.red) / ( + self.nir + self.red + 0.5 + ) + return n * (1 - 0.25 * n) - (self.red - 0.125) / (1 - self.red) + + def GOSAVI(self, Y=0.16): + """ + self.green Optimized Soil Adjusted Vegetation Index + https://www.indexdatabase.de/db/i-single.php?id=29 + mit Y = 0,16 + :return: index + """ + return (self.nir - self.green) / (self.nir + self.green + Y) + + def GSAVI(self, L=0.5): + """ + self.green Soil Adjusted Vegetation Index + https://www.indexdatabase.de/db/i-single.php?id=31 + mit L = 0,5 + :return: index + """ + return ((self.nir - self.green) / (self.nir + self.green + L)) * (1 + L) + + def Hue(self): + """ + Hue + https://www.indexdatabase.de/db/i-single.php?id=34 + :return: index + """ + return np.arctan( + ( + ((2 * self.red - self.green - self.blue) / 30.5) + * (self.green - self.blue) + ) + ) + + def IVI(self, a=None, b=None): + """ + Ideal vegetation index + https://www.indexdatabase.de/db/i-single.php?id=276 + b=intercept of vegetation line + a=soil line slope + :return: index + """ + return (self.nir - b) / (a * self.red) + + def IPVI(self): + """ + Infraself.red percentage vegetation index + https://www.indexdatabase.de/db/i-single.php?id=35 + :return: index + """ + return (self.nir / ((self.nir + self.red) / 2)) * (self.NDVI() + 1) + + def I(self): + """ + Intensity + https://www.indexdatabase.de/db/i-single.php?id=36 + :return: index + """ + return (self.red + self.green + self.blue) / 30.5 + + def RVI(self): + """ + Ratio-Vegetation-Index + http://www.seos-project.eu/modules/remotesensing/remotesensing-c03-s01-p01.html + :return: index + """ + return self.nir / self.red + + def MRVI(self): + """ + Modified Normalized Difference Vegetation Index RVI + https://www.indexdatabase.de/db/i-single.php?id=275 + :return: index + """ + return (self.RVI() - 1) / (self.RVI() + 1) + + def MSAVI(self): + """ + Modified Soil Adjusted Vegetation Index + https://www.indexdatabase.de/db/i-single.php?id=44 + :return: index + """ + return ( + (2 * self.nir + 1) + - ((2 * self.nir + 1) ** 2 - 8 * (self.nir - self.red)) ** (1 / 2) + ) / 2 + + def NormG(self): + """ + Norm G + https://www.indexdatabase.de/db/i-single.php?id=50 + :return: index + """ + return self.green / (self.nir + self.red + self.green) + + def NormNIR(self): + """ + Norm self.nir + https://www.indexdatabase.de/db/i-single.php?id=51 + :return: index + """ + return self.nir / (self.nir + self.red + self.green) + + def NormR(self): + """ + Norm R + https://www.indexdatabase.de/db/i-single.php?id=52 + :return: index + """ + return self.red / (self.nir + self.red + self.green) + + def NGRDI(self): + """ + Normalized Difference self.green/self.red Normalized self.green self.red + difference index, Visible Atmospherically Resistant Indices self.green (VIself.green) + https://www.indexdatabase.de/db/i-single.php?id=390 + :return: index + """ + return (self.green - self.red) / (self.green + self.red) + + def RI(self): + """ + Normalized Difference self.red/self.green self.redness Index + https://www.indexdatabase.de/db/i-single.php?id=74 + :return: index + """ + return (self.red - self.green) / (self.red + self.green) + + def S(self): + """ + Saturation + https://www.indexdatabase.de/db/i-single.php?id=77 + :return: index + """ + max = np.max([np.max(self.red), np.max(self.green), np.max(self.blue)]) + min = np.min([np.min(self.red), np.min(self.green), np.min(self.blue)]) + return (max - min) / max + + def IF(self): + """ + Shape Index + https://www.indexdatabase.de/db/i-single.php?id=79 + :return: index + """ + return (2 * self.red - self.green - self.blue) / (self.green - self.blue) + + def DVI(self): + """ + Simple Ratio self.nir/self.red Difference Vegetation Index, Vegetation Index + Number (VIN) + https://www.indexdatabase.de/db/i-single.php?id=12 + :return: index + """ + return self.nir / self.red + + def TVI(self): + """ + Transformed Vegetation Index + https://www.indexdatabase.de/db/i-single.php?id=98 + :return: index + """ + return (self.NDVI() + 0.5) ** (1 / 2) + + def NDRE(self): + return (self.nir - self.redEdge) / (self.nir + self.redEdge) + + +""" +# genering a random matrices to test this class +red = np.ones((1000,1000, 1),dtype="float64") * 46787 +green = np.ones((1000,1000, 1),dtype="float64") * 23487 +blue = np.ones((1000,1000, 1),dtype="float64") * 14578 +redEdge = np.ones((1000,1000, 1),dtype="float64") * 51045 +nir = np.ones((1000,1000, 1),dtype="float64") * 52200 + +# Examples of how to use the class + +# instantiating the class +cl = IndexCalculation() + +# instantiating the class with the values +#cl = indexCalculation(red=red, green=green, blue=blue, redEdge=redEdge, nir=nir) + +# how set the values after instantiate the class cl, (for update the data or when dont +# instantiating the class with the values) +cl.setMatrices(red=red, green=green, blue=blue, redEdge=redEdge, nir=nir) + +# calculating the indices for the instantiated values in the class + # Note: the CCCI index can be changed to any index implemented in the class. +indexValue_form1 = cl.calculation("CCCI", red=red, green=green, blue=blue, + redEdge=redEdge, nir=nir).astype(np.float64) +indexValue_form2 = cl.CCCI() + +# calculating the index with the values directly -- you can set just the values preferred -- +# note: the *calculation* fuction performs the function *setMatrices* +indexValue_form3 = cl.calculation("CCCI", red=red, green=green, blue=blue, + redEdge=redEdge, nir=nir).astype(np.float64) + +print("Form 1: "+np.array2string(indexValue_form1, precision=20, separator=', ', floatmode='maxprec_equal')) +print("Form 2: "+np.array2string(indexValue_form2, precision=20, separator=', ', floatmode='maxprec_equal')) +print("Form 3: "+np.array2string(indexValue_form3, precision=20, separator=', ', floatmode='maxprec_equal')) + +# A list of examples results for different type of data at NDVI +# float16 -> 0.31567383 #NDVI (red = 50, nir = 100) +# float32 -> 0.31578946 #NDVI (red = 50, nir = 100) +# float64 -> 0.3157894736842105 #NDVI (red = 50, nir = 100) +# longdouble -> 0.3157894736842105 #NDVI (red = 50, nir = 100) +""" diff --git a/hashes/hamming_code.py b/hashes/hamming_code.py new file mode 100644 index 000000000000..155c1b10a38d --- /dev/null +++ b/hashes/hamming_code.py @@ -0,0 +1,315 @@ +# -*- coding: utf-8 -*- +# Author: João Gustavo A. Amorim & Gabriel Kunz +# Author email: joaogustavoamorim@gmail.com and gabriel-kunz@uergs.edu.br +# Coding date: apr 2019 +# Black: True + +""" + * This code implement the Hamming code: + https://en.wikipedia.org/wiki/Hamming_code - In telecommunication, + Hamming codes are a family of linear error-correcting codes. Hamming + codes can detect up to two-bit errors or correct one-bit errors + without detection of uncorrected errors. By contrast, the simple + parity code cannot correct errors, and can detect only an odd number + of bits in error. Hamming codes are perfect codes, that is, they + achieve the highest possible rate for codes with their block length + and minimum distance of three. + + * the implemented code consists of: + * a function responsible for encoding the message (emitterConverter) + * return the encoded message + * a function responsible for decoding the message (receptorConverter) + * return the decoded message and a ack of data integrity + + * how to use: + to be used you must declare how many parity bits (sizePari) + you want to include in the message. + it is desired (for test purposes) to select a bit to be set + as an error. This serves to check whether the code is working correctly. + Lastly, the variable of the message/word that must be desired to be + encoded (text). + + * how this work: + declaration of variables (sizePari, be, text) + + converts the message/word (text) to binary using the + text_to_bits function + encodes the message using the rules of hamming encoding + decodes the message using the rules of hamming encoding + print the original message, the encoded message and the + decoded message + + forces an error in the coded text variable + decodes the message that was forced the error + print the original message, the encoded message, the bit changed + message and the decoded message +""" + +# Imports +import numpy as np + +# Functions of binary conversion-------------------------------------- +def text_to_bits(text, encoding="utf-8", errors="surrogatepass"): + """ + >>> text_to_bits("msg") + '011011010111001101100111' + """ + bits = bin(int.from_bytes(text.encode(encoding, errors), "big"))[2:] + return bits.zfill(8 * ((len(bits) + 7) // 8)) + + +def text_from_bits(bits, encoding="utf-8", errors="surrogatepass"): + """ + >>> text_from_bits('011011010111001101100111') + 'msg' + """ + n = int(bits, 2) + return n.to_bytes((n.bit_length() + 7) // 8, "big").decode(encoding, errors) or "\0" + + +# Functions of hamming code------------------------------------------- +def emitterConverter(sizePar, data): + """ + :param sizePar: how many parity bits the message must have + :param data: information bits + :return: message to be transmitted by unreliable medium + - bits of information merged with parity bits + + >>> emitterConverter(4, "101010111111") + ['1', '1', '1', '1', '0', '1', '0', '0', '1', '0', '1', '1', '1', '1', '1', '1'] + """ + if sizePar + len(data) <= 2 ** sizePar - (len(data) - 1): + print("ERROR - size of parity don't match with size of data") + exit(0) + + dataOut = [] + parity = [] + binPos = [bin(x)[2:] for x in range(1, sizePar + len(data) + 1)] + pos = [x for x in range(1, sizePar + len(data) + 1)] + + # sorted information data for the size of the output data + dataOrd = [] + # data position template + parity + dataOutGab = [] + # parity bit counter + qtdBP = 0 + # counter position of data bits + contData = 0 + + for x in range(1, sizePar + len(data) + 1): + # Performs a template of bit positions - who should be given, + # and who should be parity + if qtdBP < sizePar: + if ((np.log(x) / np.log(2))).is_integer(): + dataOutGab.append("P") + qtdBP = qtdBP + 1 + else: + dataOutGab.append("D") + else: + dataOutGab.append("D") + + # Sorts the data to the new output size + if dataOutGab[-1] == "D": + dataOrd.append(data[contData]) + contData += 1 + else: + dataOrd.append(None) + + # Calculates parity + qtdBP = 0 # parity bit counter + for bp in range(1, sizePar + 1): + # Bit counter one for a given parity + contBO = 0 + # counter to control the loop reading + contLoop = 0 + for x in dataOrd: + if x != None: + try: + aux = (binPos[contLoop])[-1 * (bp)] + except: + aux = "0" + if aux == "1": + if x == "1": + contBO += 1 + contLoop += 1 + if contBO % 2 == 0: + parity.append(0) + else: + parity.append(1) + + qtdBP += 1 + + # Mount the message + ContBP = 0 # parity bit counter + for x in range(0, sizePar + len(data)): + if dataOrd[x] == None: + dataOut.append(str(parity[ContBP])) + ContBP += 1 + else: + dataOut.append(dataOrd[x]) + + return dataOut + + +def receptorConverter(sizePar, data): + """ + >>> receptorConverter(4, "1111010010111111") + (['1', '0', '1', '0', '1', '0', '1', '1', '1', '1', '1', '1'], True) + """ + # data position template + parity + dataOutGab = [] + # Parity bit counter + qtdBP = 0 + # Counter p data bit reading + contData = 0 + # list of parity received + parityReceived = [] + dataOutput = [] + + for x in range(1, len(data) + 1): + # Performs a template of bit positions - who should be given, + # and who should be parity + if qtdBP < sizePar: + if ((np.log(x) / np.log(2))).is_integer(): + dataOutGab.append("P") + qtdBP = qtdBP + 1 + else: + dataOutGab.append("D") + else: + dataOutGab.append("D") + + # Sorts the data to the new output size + if dataOutGab[-1] == "D": + dataOutput.append(data[contData]) + else: + parityReceived.append(data[contData]) + contData += 1 + + # -----------calculates the parity with the data + dataOut = [] + parity = [] + binPos = [bin(x)[2:] for x in range(1, sizePar + len(dataOutput) + 1)] + pos = [x for x in range(1, sizePar + len(dataOutput) + 1)] + + # sorted information data for the size of the output data + dataOrd = [] + # Data position feedback + parity + dataOutGab = [] + # Parity bit counter + qtdBP = 0 + # Counter p data bit reading + contData = 0 + + for x in range(1, sizePar + len(dataOutput) + 1): + # Performs a template position of bits - who should be given, + # and who should be parity + if qtdBP < sizePar: + if ((np.log(x) / np.log(2))).is_integer(): + dataOutGab.append("P") + qtdBP = qtdBP + 1 + else: + dataOutGab.append("D") + else: + dataOutGab.append("D") + + # Sorts the data to the new output size + if dataOutGab[-1] == "D": + dataOrd.append(dataOutput[contData]) + contData += 1 + else: + dataOrd.append(None) + + # Calculates parity + qtdBP = 0 # parity bit counter + for bp in range(1, sizePar + 1): + # Bit counter one for a certain parity + contBO = 0 + # Counter to control loop reading + contLoop = 0 + for x in dataOrd: + if x != None: + try: + aux = (binPos[contLoop])[-1 * (bp)] + except: + aux = "0" + if aux == "1": + if x == "1": + contBO += 1 + contLoop += 1 + if contBO % 2 == 0: + parity.append("0") + else: + parity.append("1") + + qtdBP += 1 + + # Mount the message + ContBP = 0 # Parity bit counter + for x in range(0, sizePar + len(dataOutput)): + if dataOrd[x] == None: + dataOut.append(str(parity[ContBP])) + ContBP += 1 + else: + dataOut.append(dataOrd[x]) + + if parityReceived == parity: + ack = True + else: + ack = False + + return dataOutput, ack + + +# --------------------------------------------------------------------- +""" +# Example how to use + +# number of parity bits +sizePari = 4 + +# location of the bit that will be forced an error +be = 2 + +# Message/word to be encoded and decoded with hamming +# text = input("Enter the word to be read: ") +text = "Message01" + +# Convert the message to binary +binaryText = text_to_bits(text) + +# Prints the binary of the string +print("Text input in binary is '" + binaryText + "'") + +# total transmitted bits +totalBits = len(binaryText) + sizePari +print("Size of data is " + str(totalBits)) + +print("\n --Message exchange--") +print("Data to send ------------> " + binaryText) +dataOut = emitterConverter(sizePari, binaryText) +print("Data converted ----------> " + "".join(dataOut)) +dataReceiv, ack = receptorConverter(sizePari, dataOut) +print( + "Data receive ------------> " + + "".join(dataReceiv) + + "\t\t -- Data integrity: " + + str(ack) +) + + +print("\n --Force error--") +print("Data to send ------------> " + binaryText) +dataOut = emitterConverter(sizePari, binaryText) +print("Data converted ----------> " + "".join(dataOut)) + +# forces error +dataOut[-be] = "1" * (dataOut[-be] == "0") + "0" * (dataOut[-be] == "1") +print("Data after transmission -> " + "".join(dataOut)) +dataReceiv, ack = receptorConverter(sizePari, dataOut) +print( + "Data receive ------------> " + + "".join(dataReceiv) + + "\t\t -- Data integrity: " + + str(ack) +) +""" From ccc1ff2ce89af2a569f1fbfa16ff70ad22ed9e89 Mon Sep 17 00:00:00 2001 From: SHAKTI SINGH Date: Fri, 6 Dec 2019 12:04:21 +0530 Subject: [PATCH 0390/1071] pigeonhole sorting in python (#364) * pigeonhole sorting in python * variable name update in pigeonhole_sort.py * Add doctest --- sorts/pigeonhole_sort.py | 44 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 sorts/pigeonhole_sort.py diff --git a/sorts/pigeonhole_sort.py b/sorts/pigeonhole_sort.py new file mode 100644 index 000000000000..d8eb61fc0e08 --- /dev/null +++ b/sorts/pigeonhole_sort.py @@ -0,0 +1,44 @@ +# Python program to implement Pigeonhole Sorting in python + +# Algorithm for the pigeonhole sorting + + +def pigeonhole_sort(a): + """ + >>> a = [8, 3, 2, 7, 4, 6, 8] + >>> b = sorted(a) # a nondestructive sort + >>> pigeonhole_sort(a) # a distructive sort + >>> a == b + True + """ + # size of range of values in the list (ie, number of pigeonholes we need) + + min_val = min(a) # min() finds the minimum value + max_val = max(a) # max() finds the maximum value + + size = max_val - min_val + 1 # size is difference of max and min values plus one + + # list of pigeonholes of size equal to the variable size + holes = [0] * size + + # Populate the pigeonholes. + for x in a: + assert isinstance(x, int), "integers only please" + holes[x - min_val] += 1 + + # Putting the elements back into the array in an order. + i = 0 + for count in range(size): + while holes[count] > 0: + holes[count] -= 1 + a[i] = count + min_val + i += 1 + + +def main(): + pigeonhole_sort([8, 3, 2, 7, 4, 6, 8]) + print("Sorted order is: ", " ", join(a)) + + +if __name__ == "__main__": + main() From 938dd0bbb5145aa7c60127745ae0571cb20a2387 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?N=C3=ADkolas=20Vargas?= Date: Sat, 7 Dec 2019 02:39:08 -0300 Subject: [PATCH 0391/1071] improved prime numbers implementation (#1606) * improved prime numbers implementation * fixup! Format Python code with psf/black push * fix type hint * fixup! Format Python code with psf/black push * fix doctests * updating DIRECTORY.md * added prime tests with negative numbers * using for instead filter * updating DIRECTORY.md * Remove unused typing.List * Remove tab indentation * print("Sorted order is:", " ".join(a)) --- DIRECTORY.md | 8 +++- .../data_structures/heap/heap_generic.py | 3 +- digital_image_processing/index_calculation.py | 8 ++-- maths/prime_numbers.py | 44 +++++++++++-------- sorts/pigeonhole_sort.py | 5 ++- 5 files changed, 41 insertions(+), 27 deletions(-) diff --git a/DIRECTORY.md b/DIRECTORY.md index a3db3636e34c..468fe65298d9 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -133,6 +133,7 @@ * [Gaussian Filter](https://github.com/TheAlgorithms/Python/blob/master/digital_image_processing/filters/gaussian_filter.py) * [Median Filter](https://github.com/TheAlgorithms/Python/blob/master/digital_image_processing/filters/median_filter.py) * [Sobel Filter](https://github.com/TheAlgorithms/Python/blob/master/digital_image_processing/filters/sobel_filter.py) + * [Index Calculation](https://github.com/TheAlgorithms/Python/blob/master/digital_image_processing/index_calculation.py) * Rotation * [Rotation](https://github.com/TheAlgorithms/Python/blob/master/digital_image_processing/rotation/rotation.py) * [Test Digital Image Processing](https://github.com/TheAlgorithms/Python/blob/master/digital_image_processing/test_digital_image_processing.py) @@ -216,6 +217,7 @@ ## Hashes * [Chaos Machine](https://github.com/TheAlgorithms/Python/blob/master/hashes/chaos_machine.py) * [Enigma Machine](https://github.com/TheAlgorithms/Python/blob/master/hashes/enigma_machine.py) + * [Hamming Code](https://github.com/TheAlgorithms/Python/blob/master/hashes/hamming_code.py) * [Md5](https://github.com/TheAlgorithms/Python/blob/master/hashes/md5.py) * [Sha1](https://github.com/TheAlgorithms/Python/blob/master/hashes/sha1.py) @@ -234,6 +236,7 @@ * [Linear Discriminant Analysis](https://github.com/TheAlgorithms/Python/blob/master/machine_learning/linear_discriminant_analysis.py) * [Linear Regression](https://github.com/TheAlgorithms/Python/blob/master/machine_learning/linear_regression.py) * [Logistic Regression](https://github.com/TheAlgorithms/Python/blob/master/machine_learning/logistic_regression.py) + * [Multilayer Perceptron Classifier](https://github.com/TheAlgorithms/Python/blob/master/machine_learning/multilayer_perceptron_classifier.py) * [Polymonial Regression](https://github.com/TheAlgorithms/Python/blob/master/machine_learning/polymonial_regression.py) * [Scoring Functions](https://github.com/TheAlgorithms/Python/blob/master/machine_learning/scoring_functions.py) * [Sequential Minimum Optimization](https://github.com/TheAlgorithms/Python/blob/master/machine_learning/sequential_minimum_optimization.py) @@ -252,6 +255,7 @@ * [Binomial Coefficient](https://github.com/TheAlgorithms/Python/blob/master/maths/binomial_coefficient.py) * [Ceil](https://github.com/TheAlgorithms/Python/blob/master/maths/ceil.py) * [Collatz Sequence](https://github.com/TheAlgorithms/Python/blob/master/maths/collatz_sequence.py) + * [Eulers Totient](https://github.com/TheAlgorithms/Python/blob/master/maths/eulers_totient.py) * [Explicit Euler](https://github.com/TheAlgorithms/Python/blob/master/maths/explicit_euler.py) * [Extended Euclidean Algorithm](https://github.com/TheAlgorithms/Python/blob/master/maths/extended_euclidean_algorithm.py) * [Factorial Python](https://github.com/TheAlgorithms/Python/blob/master/maths/factorial_python.py) @@ -286,6 +290,7 @@ * [Prime Factors](https://github.com/TheAlgorithms/Python/blob/master/maths/prime_factors.py) * [Prime Numbers](https://github.com/TheAlgorithms/Python/blob/master/maths/prime_numbers.py) * [Prime Sieve Eratosthenes](https://github.com/TheAlgorithms/Python/blob/master/maths/prime_sieve_eratosthenes.py) + * [Pythagoras](https://github.com/TheAlgorithms/Python/blob/master/maths/pythagoras.py) * [Qr Decomposition](https://github.com/TheAlgorithms/Python/blob/master/maths/qr_decomposition.py) * [Quadratic Equations Complex Numbers](https://github.com/TheAlgorithms/Python/blob/master/maths/quadratic_equations_complex_numbers.py) * [Radix2 Fft](https://github.com/TheAlgorithms/Python/blob/master/maths/radix2_fft.py) @@ -499,11 +504,11 @@ * [I Sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/i_sort.py) * [Insertion Sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/insertion_sort.py) * [Merge Sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/merge_sort.py) - * [Merge Sort Fastest](https://github.com/TheAlgorithms/Python/blob/master/sorts/merge_sort_fastest.py) * [Odd Even Transposition Parallel](https://github.com/TheAlgorithms/Python/blob/master/sorts/odd_even_transposition_parallel.py) * [Odd Even Transposition Single Threaded](https://github.com/TheAlgorithms/Python/blob/master/sorts/odd_even_transposition_single_threaded.py) * [Pancake Sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/pancake_sort.py) * [Pigeon Sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/pigeon_sort.py) + * [Pigeonhole Sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/pigeonhole_sort.py) * [Quick Sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/quick_sort.py) * [Quick Sort 3 Partition](https://github.com/TheAlgorithms/Python/blob/master/sorts/quick_sort_3_partition.py) * [Radix Sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/radix_sort.py) @@ -516,6 +521,7 @@ * [Tim Sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/tim_sort.py) * [Topological Sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/topological_sort.py) * [Tree Sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/tree_sort.py) + * [Unknown Sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/unknown_sort.py) * [Wiggle Sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/wiggle_sort.py) ## Strings diff --git a/data_structures/data_structures/heap/heap_generic.py b/data_structures/data_structures/heap/heap_generic.py index c41a434542e7..fc17c1b1218e 100644 --- a/data_structures/data_structures/heap/heap_generic.py +++ b/data_structures/data_structures/heap/heap_generic.py @@ -30,7 +30,8 @@ def _swap(self, i, j): """Performs changes required for swapping two elements in the heap""" # First update the indexes of the items in index map. self.pos_map[self.arr[i][0]], self.pos_map[self.arr[j][0]] = ( - self.pos_map[self.arr[j][0]], self.pos_map[self.arr[i][0]] + self.pos_map[self.arr[j][0]], + self.pos_map[self.arr[i][0]], ) # Then swap the items in the list. self.arr[i], self.arr[j] = self.arr[j], self.arr[i] diff --git a/digital_image_processing/index_calculation.py b/digital_image_processing/index_calculation.py index f4f8759ad010..fc5b169650a2 100644 --- a/digital_image_processing/index_calculation.py +++ b/digital_image_processing/index_calculation.py @@ -176,10 +176,10 @@ def calculation( def ARVI2(self): """ - Atmospherically Resistant Vegetation Index 2 - https://www.indexdatabase.de/db/i-single.php?id=396 - :return: index - −0.18+1.17*(self.nir−self.red)/(self.nir+self.red) + Atmospherically Resistant Vegetation Index 2 + https://www.indexdatabase.de/db/i-single.php?id=396 + :return: index + −0.18+1.17*(self.nir−self.red)/(self.nir+self.red) """ return -0.18 + (1.17 * ((self.nir - self.red) / (self.nir + self.red))) diff --git a/maths/prime_numbers.py b/maths/prime_numbers.py index a29a95ea2280..f9325996500c 100644 --- a/maths/prime_numbers.py +++ b/maths/prime_numbers.py @@ -1,28 +1,34 @@ -from typing import List +from typing import Generator -def primes(max: int) -> List[int]: +def primes(max: int) -> Generator[int, None, None]: """ Return a list of all primes numbers up to max. - >>> primes(10) - [2, 3, 5, 7] - >>> primes(11) - [2, 3, 5, 7, 11] - >>> primes(25) + >>> list(primes(0)) + [] + >>> list(primes(-1)) + [] + >>> list(primes(-10)) + [] + >>> list(primes(25)) [2, 3, 5, 7, 11, 13, 17, 19, 23] - >>> primes(1_000_000)[-1] - 999983 + >>> list(primes(11)) + [2, 3, 5, 7, 11] + >>> list(primes(33)) + [2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31] + >>> list(primes(10000))[-1] + 9973 """ - max += 1 - numbers = [False] * max - ret = [] - for i in range(2, max): - if not numbers[i]: - for j in range(i, max, i): - numbers[j] = True - ret.append(i) - return ret + numbers: Generator = (i for i in range(1, (max + 1))) + for i in (n for n in numbers if n > 1): + for j in range(2, i): + if (i % j) == 0: + break + else: + yield i if __name__ == "__main__": - print(primes(int(input("Calculate primes up to:\n>> ")))) + number = int(input("Calculate primes up to:\n>> ").strip()) + for ret in primes(number): + print(ret) diff --git a/sorts/pigeonhole_sort.py b/sorts/pigeonhole_sort.py index d8eb61fc0e08..a91e1d054442 100644 --- a/sorts/pigeonhole_sort.py +++ b/sorts/pigeonhole_sort.py @@ -36,8 +36,9 @@ def pigeonhole_sort(a): def main(): - pigeonhole_sort([8, 3, 2, 7, 4, 6, 8]) - print("Sorted order is: ", " ", join(a)) + a = [8, 3, 2, 7, 4, 6, 8] + pigeonhole_sort(a) + print("Sorted order is:", " ".join(a)) if __name__ == "__main__": From 9eb50cc223f7a8da8d7299bf4db8e4d3313b8bff Mon Sep 17 00:00:00 2001 From: GeorgeChambi Date: Sat, 7 Dec 2019 05:39:59 +0000 Subject: [PATCH 0392/1071] Improved readability (#1615) * improved readability * further readability improvements * removed csv file and added f --- .../newton_forward_interpolation.py | 10 +++++----- ciphers/trafid_cipher.py | 2 +- data_structures/linked_list/doubly_linked_list.py | 2 +- divide_and_conquer/convex_hull.py | 14 +++++++------- dynamic_programming/knapsack.py | 4 ++-- graphs/dijkstra_algorithm.py | 10 +++++----- graphs/edmonds_karp_multiple_source_and_sink.py | 2 +- graphs/page_rank.py | 6 ++---- machine_learning/knn_sklearn.py | 4 ++-- machine_learning/linear_discriminant_analysis.py | 8 ++++---- .../sequential_minimum_optimization.py | 8 +++----- maths/binary_exponentiation.py | 2 +- maths/simpson_rule.py | 2 +- maths/trapezoidal_rule.py | 2 +- neural_network/input_data.py | 4 +--- searches/binary_search.py | 2 +- searches/interpolation_search.py | 2 +- searches/linear_search.py | 2 +- searches/sentinel_linear_search.py | 2 +- searches/tabu_search.py | 2 +- searches/ternary_search.py | 4 ++-- 21 files changed, 44 insertions(+), 50 deletions(-) diff --git a/arithmetic_analysis/newton_forward_interpolation.py b/arithmetic_analysis/newton_forward_interpolation.py index 09adb5113f82..d91b9709f3d6 100644 --- a/arithmetic_analysis/newton_forward_interpolation.py +++ b/arithmetic_analysis/newton_forward_interpolation.py @@ -19,7 +19,7 @@ def ucal(u, p): def main(): - n = int(input("enter the numbers of values")) + n = int(input("enter the numbers of values: ")) y = [] for i in range(n): y.append([]) @@ -28,14 +28,14 @@ def main(): y[i].append(j) y[i][j] = 0 - print("enter the values of parameters in a list") + print("enter the values of parameters in a list: ") x = list(map(int, input().split())) - print("enter the values of corresponding parameters") + print("enter the values of corresponding parameters: ") for i in range(n): y[i][0] = float(input()) - value = int(input("enter the value to interpolate")) + value = int(input("enter the value to interpolate: ")) u = (value - x[0]) / (x[1] - x[0]) # for calculating forward difference table @@ -48,7 +48,7 @@ def main(): for i in range(1, n): summ += (ucal(u, i) * y[0][i]) / math.factorial(i) - print("the value at {} is {}".format(value, summ)) + print(f"the value at {value} is {summ}") if __name__ == "__main__": diff --git a/ciphers/trafid_cipher.py b/ciphers/trafid_cipher.py index 0add9ee74beb..f1c954b5c34f 100644 --- a/ciphers/trafid_cipher.py +++ b/ciphers/trafid_cipher.py @@ -117,4 +117,4 @@ def decryptMessage(message, alphabet="ABCDEFGHIJKLMNOPQRSTUVWXYZ.", period=5): msg = "DEFEND THE EAST WALL OF THE CASTLE." encrypted = encryptMessage(msg, "EPSDUCVWYM.ZLKXNBTFGORIJHAQ") decrypted = decryptMessage(encrypted, "EPSDUCVWYM.ZLKXNBTFGORIJHAQ") - print("Encrypted: {}\nDecrypted: {}".format(encrypted, decrypted)) + print(f"Encrypted: {encrypted}\nDecrypted: {decrypted}") diff --git a/data_structures/linked_list/doubly_linked_list.py b/data_structures/linked_list/doubly_linked_list.py index 2a95a004587c..2864356c1d19 100644 --- a/data_structures/linked_list/doubly_linked_list.py +++ b/data_structures/linked_list/doubly_linked_list.py @@ -76,4 +76,4 @@ def __init__(self, x): self.value = x def displayLink(self): - print("{}".format(self.value), end=" ") + print(f"{self.value}", end=" ") diff --git a/divide_and_conquer/convex_hull.py b/divide_and_conquer/convex_hull.py index bd88256ab01c..21463e62197d 100644 --- a/divide_and_conquer/convex_hull.py +++ b/divide_and_conquer/convex_hull.py @@ -50,7 +50,7 @@ def __init__(self, x, y): except ValueError as e: e.args = ( "x and y must be both numeric types " - "but got {}, {} instead".format(type(x), type(y)), + f"but got {type(x)}, {type(y)} instead" ) raise @@ -88,7 +88,7 @@ def __le__(self, other): return False def __repr__(self): - return "({}, {})".format(self.x, self.y) + return f"({self.x}, {self.y})" def __hash__(self): return hash(self.x) @@ -136,8 +136,8 @@ def _construct_points(list_of_tuples): points.append(Point(p[0], p[1])) except (IndexError, TypeError): print( - "Ignoring deformed point {}. All points" - " must have at least 2 coordinates.".format(p) + f"Ignoring deformed point {p}. All points" + " must have at least 2 coordinates." ) return points @@ -184,7 +184,7 @@ def _validate_input(points): """ if not points: - raise ValueError("Expecting a list of points but got {}".format(points)) + raise ValueError(f"Expecting a list of points but got {points}") if isinstance(points, set): points = list(points) @@ -196,12 +196,12 @@ def _validate_input(points): else: raise ValueError( "Expecting an iterable of type Point, list or tuple. " - "Found objects of type {} instead".format(type(points[0])) + f"Found objects of type {type(points[0])} instead" ) elif not hasattr(points, "__iter__"): raise ValueError( "Expecting an iterable object " - "but got an non-iterable type {}".format(points) + f"but got an non-iterable type {points}" ) except TypeError as e: print("Expecting an iterable of type Point, list or tuple.") diff --git a/dynamic_programming/knapsack.py b/dynamic_programming/knapsack.py index e71e3892e8cc..aefaa6bade96 100644 --- a/dynamic_programming/knapsack.py +++ b/dynamic_programming/knapsack.py @@ -81,13 +81,13 @@ def knapsack_with_example_solution(W: int, wt: list, val: list): raise ValueError( "The number of weights must be the " "same as the number of values.\nBut " - "got {} weights and {} values".format(num_items, len(val)) + f"got {num_items} weights and {len(val)} values" ) for i in range(num_items): if not isinstance(wt[i], int): raise TypeError( "All weights must be integers but " - "got weight of type {} at index {}".format(type(wt[i]), i) + f"got weight of type {type(wt[i])} at index {i}" ) optimal_val, dp_table = knapsack(W, wt, val, num_items) diff --git a/graphs/dijkstra_algorithm.py b/graphs/dijkstra_algorithm.py index 9304a83148f3..57733eb5106d 100644 --- a/graphs/dijkstra_algorithm.py +++ b/graphs/dijkstra_algorithm.py @@ -106,7 +106,7 @@ def show_graph(self): print( u, "->", - " -> ".join(str("{}({})".format(v, w)) for v, w in self.adjList[u]), + " -> ".join(str(f"{v}({w})") for v, w in self.adjList[u]), ) def dijkstra(self, src): @@ -139,9 +139,9 @@ def dijkstra(self, src): self.show_distances(src) def show_distances(self, src): - print("Distance from node: {}".format(src)) + print(f"Distance from node: {src}") for u in range(self.num_nodes): - print("Node {} has distance: {}".format(u, self.dist[u])) + print(f"Node {u} has distance: {self.dist[u]}") def show_path(self, src, dest): # To show the shortest path from src to dest @@ -161,9 +161,9 @@ def show_path(self, src, dest): path.append(src) path.reverse() - print("----Path to reach {} from {}----".format(dest, src)) + print(f"----Path to reach {dest} from {src}----") for u in path: - print("{}".format(u), end=" ") + print(f"{u}", end=" ") if u != dest: print("-> ", end="") diff --git a/graphs/edmonds_karp_multiple_source_and_sink.py b/graphs/edmonds_karp_multiple_source_and_sink.py index 6334f05c50bd..eb6ec739ba00 100644 --- a/graphs/edmonds_karp_multiple_source_and_sink.py +++ b/graphs/edmonds_karp_multiple_source_and_sink.py @@ -190,4 +190,4 @@ def relabel(self, vertexIndex): # and calculate maximumFlow = flowNetwork.findMaximumFlow() - print("maximum flow is {}".format(maximumFlow)) + print(f"maximum flow is {maximumFlow}") diff --git a/graphs/page_rank.py b/graphs/page_rank.py index 1e2c7d9aeb48..0f5129146ddf 100644 --- a/graphs/page_rank.py +++ b/graphs/page_rank.py @@ -27,9 +27,7 @@ def add_outbound(self, node): self.outbound.append(node) def __repr__(self): - return "Node {}: Inbound: {} ; Outbound: {}".format( - self.name, self.inbound, self.outbound - ) + return f"Node {self.name}: Inbound: {self.inbound} ; Outbound: {self.outbound}" def page_rank(nodes, limit=3, d=0.85): @@ -42,7 +40,7 @@ def page_rank(nodes, limit=3, d=0.85): outbounds[node.name] = len(node.outbound) for i in range(limit): - print("======= Iteration {} =======".format(i + 1)) + print(f"======= Iteration {i + 1} =======") for j, node in enumerate(nodes): ranks[node.name] = (1 - d) + d * sum( [ranks[ib] / outbounds[ib] for ib in node.inbound] diff --git a/machine_learning/knn_sklearn.py b/machine_learning/knn_sklearn.py index a371e30f5403..c36a530736cd 100644 --- a/machine_learning/knn_sklearn.py +++ b/machine_learning/knn_sklearn.py @@ -7,8 +7,8 @@ iris.keys() -print("Target names: \n {} ".format(iris.target_names)) -print("\n Features: \n {}".format(iris.feature_names)) +print(f"Target names: \n {iris.target_names} ") +print(f"\n Features: \n {iris.feature_names}") # Train set e Test set X_train, X_test, y_train, y_test = train_test_split( diff --git a/machine_learning/linear_discriminant_analysis.py b/machine_learning/linear_discriminant_analysis.py index 62dc34af6bbd..cc2f1dac7237 100644 --- a/machine_learning/linear_discriminant_analysis.py +++ b/machine_learning/linear_discriminant_analysis.py @@ -174,8 +174,8 @@ def accuracy(actual_y: list, predicted_y: list) -> float: def main(): """ This function starts execution phase """ while True: - print(" Linear Discriminant Analysis ".center(100, "*")) - print("*" * 100, "\n") + print(" Linear Discriminant Analysis ".center(50, "*")) + print("*" * 50, "\n") print("First of all we should specify the number of classes that") print("we want to generate as training dataset") # Trying to get number of classes @@ -239,7 +239,7 @@ def main(): else: print( f"Your entered value is {user_count}, Number of " - f"instances should be positive!" + "instances should be positive!" ) continue except ValueError: @@ -302,7 +302,7 @@ def main(): # for loop iterates over number of elements in 'probabilities' list and print # out them in separated line for i, probability in enumerate(probabilities, 1): - print("Probability of class_{} is: {}".format(i, probability)) + print(f"Probability of class_{i} is: {probability}") print("-" * 100) # Calculating the values of variance for each class diff --git a/machine_learning/sequential_minimum_optimization.py b/machine_learning/sequential_minimum_optimization.py index 1d4e4a276bc1..cb859602b29f 100644 --- a/machine_learning/sequential_minimum_optimization.py +++ b/machine_learning/sequential_minimum_optimization.py @@ -446,7 +446,7 @@ def call_func(*args, **kwargs): start_time = time.time() func(*args, **kwargs) end_time = time.time() - print("smo algorithm cost {} seconds".format(end_time - start_time)) + print(f"smo algorithm cost {end_time - start_time} seconds") return call_func @@ -500,11 +500,9 @@ def test_cancel_data(): if test_tags[i] == predict[i]: score += 1 print( - "\r\nall: {}\r\nright: {}\r\nfalse: {}".format( - test_num, score, test_num - score - ) + f"\r\nall: {test_num}\r\nright: {score}\r\nfalse: {test_num - score}" ) - print("Rough Accuracy: {}".format(score / test_tags.shape[0])) + print(f"Rough Accuracy: {score / test_tags.shape[0]}") def test_demonstration(): diff --git a/maths/binary_exponentiation.py b/maths/binary_exponentiation.py index 57c4b8686f5c..8dda5245cf44 100644 --- a/maths/binary_exponentiation.py +++ b/maths/binary_exponentiation.py @@ -25,4 +25,4 @@ def binary_exponentiation(a, n): print("Invalid literal for integer") RESULT = binary_exponentiation(BASE, POWER) - print("{}^({}) : {}".format(BASE, POWER, RESULT)) + print(f"{BASE}^({POWER}) : {RESULT}") diff --git a/maths/simpson_rule.py b/maths/simpson_rule.py index f4620be8e70f..91098804395d 100644 --- a/maths/simpson_rule.py +++ b/maths/simpson_rule.py @@ -44,7 +44,7 @@ def main(): steps = 10.0 # define number of steps or resolution boundary = [a, b] # define boundary of integration y = method_2(boundary, steps) - print("y = {0}".format(y)) + print(f"y = {y}") if __name__ == "__main__": diff --git a/maths/trapezoidal_rule.py b/maths/trapezoidal_rule.py index 0f321317614d..0f7dea6bf888 100644 --- a/maths/trapezoidal_rule.py +++ b/maths/trapezoidal_rule.py @@ -43,7 +43,7 @@ def main(): steps = 10.0 # define number of steps or resolution boundary = [a, b] # define boundary of integration y = method_1(boundary, steps) - print("y = {0}".format(y)) + print(f"y = {y}") if __name__ == "__main__": diff --git a/neural_network/input_data.py b/neural_network/input_data.py index 5e6c433aa97d..ea826be6cd84 100644 --- a/neural_network/input_data.py +++ b/neural_network/input_data.py @@ -331,9 +331,7 @@ def fake(): if not 0 <= validation_size <= len(train_images): raise ValueError( - "Validation size should be between 0 and {}. Received: {}.".format( - len(train_images), validation_size - ) + f"Validation size should be between 0 and {len(train_images)}. Received: {validation_size}." ) validation_images = train_images[:validation_size] diff --git a/searches/binary_search.py b/searches/binary_search.py index 76a50560e943..ff959c6cf2e3 100644 --- a/searches/binary_search.py +++ b/searches/binary_search.py @@ -152,6 +152,6 @@ def __assert_sorted(collection): target = int(target_input) result = binary_search(collection, target) if result is not None: - print("{} found at positions: {}".format(target, result)) + print(f"{target} found at positions: {result}") else: print("Not found") diff --git a/searches/interpolation_search.py b/searches/interpolation_search.py index dffaf8d26084..fd26ae0c64ce 100644 --- a/searches/interpolation_search.py +++ b/searches/interpolation_search.py @@ -135,6 +135,6 @@ def __assert_sorted(collection): result = interpolation_search(collection, target) if result is not None: - print("{} found at positions: {}".format(target, result)) + print(f"{target} found at positions: {result}") else: print("Not found") diff --git a/searches/linear_search.py b/searches/linear_search.py index ab20f3527bb3..b6f52ca4857f 100644 --- a/searches/linear_search.py +++ b/searches/linear_search.py @@ -45,6 +45,6 @@ def linear_search(sequence, target): target = int(target_input) result = linear_search(sequence, target) if result is not None: - print("{} found at positions: {}".format(target, result)) + print(f"{target} found at positions: {result}") else: print("Not found") diff --git a/searches/sentinel_linear_search.py b/searches/sentinel_linear_search.py index 6c4da9b21189..5650151b1d2f 100644 --- a/searches/sentinel_linear_search.py +++ b/searches/sentinel_linear_search.py @@ -53,6 +53,6 @@ def sentinel_linear_search(sequence, target): target = int(target_input) result = sentinel_linear_search(sequence, target) if result is not None: - print("{} found at positions: {}".format(target, result)) + print(f"{target} found at positions: {result}") else: print("Not found") diff --git a/searches/tabu_search.py b/searches/tabu_search.py index 9a1478244503..52086f1235ab 100644 --- a/searches/tabu_search.py +++ b/searches/tabu_search.py @@ -254,7 +254,7 @@ def main(args=None): args.Size, ) - print("Best solution: {0}, with total distance: {1}.".format(best_sol, best_cost)) + print(f"Best solution: {best_sol}, with total distance: {best_cost}.") if __name__ == "__main__": diff --git a/searches/ternary_search.py b/searches/ternary_search.py index 43407b7e5538..5ecc47644248 100644 --- a/searches/ternary_search.py +++ b/searches/ternary_search.py @@ -97,7 +97,7 @@ def __assert_sorted(collection): result2 = rec_ternary_search(0, len(collection) - 1, collection, target) if result2 is not None: - print("Iterative search: {} found at positions: {}".format(target, result1)) - print("Recursive search: {} found at positions: {}".format(target, result2)) + print(f"Iterative search: {target} found at positions: {result1}") + print(f"Recursive search: {target} found at positions: {result2}") else: print("Not found") From 26b0803319b6cf14f623769356c79343e3d43d14 Mon Sep 17 00:00:00 2001 From: Christian Clauss Date: Sun, 8 Dec 2019 22:42:17 +0100 Subject: [PATCH 0393/1071] Simplify sudoku.is_completed() using builtin all() (#1608) * Simplify sudoku.is_completed() using builtin all() Simplify __sudoku.is_completed()__ using Python builtin function [__all()__](https://docs.python.org/3/library/functions.html#all). * fixup! Format Python code with psf/black push * Update sudoku.py * fixup! Format Python code with psf/black push * Old style exception -> new style for Python 3 * updating DIRECTORY.md * Update convex_hull.py * fixup! Format Python code with psf/black push * e.args[0] = "msg" * ValueError: could not convert string to float: 'pi' * Update convex_hull.py * fixup! Format Python code with psf/black push --- backtracking/sudoku.py | 36 +++++++++---------- divide_and_conquer/convex_hull.py | 7 ++-- graphs/dijkstra_algorithm.py | 4 +-- .../sequential_minimum_optimization.py | 4 +-- 4 files changed, 21 insertions(+), 30 deletions(-) diff --git a/backtracking/sudoku.py b/backtracking/sudoku.py index b33351fd4911..d864e2823a9b 100644 --- a/backtracking/sudoku.py +++ b/backtracking/sudoku.py @@ -1,5 +1,4 @@ """ - Given a partially filled 9×9 2D array, the objective is to fill a 9×9 square grid with digits numbered 1 to 9, so that every row, column, and and each of the nine 3×3 sub-grids contains all of the digits. @@ -9,9 +8,7 @@ function on the next column to see if it returns True. if yes, we have solved the puzzle. else, we backtrack and place another number in that cell and repeat this process. - """ - # assigning initial values to the grid initial_grid = [ [3, 0, 6, 5, 0, 8, 4, 0, 0], @@ -24,6 +21,7 @@ [0, 0, 0, 0, 0, 0, 0, 7, 4], [0, 0, 5, 2, 0, 6, 3, 0, 0], ] + # a grid with no solution no_solution = [ [5, 0, 6, 5, 0, 8, 4, 0, 3], @@ -44,9 +42,7 @@ def is_safe(grid, row, column, n): column, and the 3x3 subgrids contain the digit 'n'. It returns False if it is not 'safe' (a duplicate digit is found) else returns True if it is 'safe' - """ - for i in range(9): if grid[row][i] == n or grid[i][column] == n: return False @@ -62,26 +58,29 @@ def is_safe(grid, row, column, n): def is_completed(grid): """ This function checks if the puzzle is completed or not. - it is completed when all the cells are assigned with a number(not zero) - and There is no repeating number in any column, row or 3x3 subgrid. - + it is completed when all the cells are assigned with a non-zero number. + + >>> is_completed([[0]]) + False + >>> is_completed([[1]]) + True + >>> is_completed([[1, 2], [0, 4]]) + False + >>> is_completed([[1, 2], [3, 4]]) + True + >>> is_completed(initial_grid) + False + >>> is_completed(no_solution) + False """ - - for row in grid: - for cell in row: - if cell == 0: - return False - - return True + return all(all(cell != 0 for cell in row) for row in grid) def find_empty_location(grid): """ This function finds an empty location so that we can assign a number for that particular row and column. - """ - for i in range(9): for j in range(9): if grid[i][j] == 0: @@ -129,9 +128,7 @@ def print_solution(grid): """ A function to print the solution in the form of a 9x9 grid - """ - for row in grid: for cell in row: print(cell, end=" ") @@ -139,7 +136,6 @@ def print_solution(grid): if __name__ == "__main__": - # make a copy of grid so that you can compare with the unmodified grid for grid in (initial_grid, no_solution): grid = list(map(list, grid)) diff --git a/divide_and_conquer/convex_hull.py b/divide_and_conquer/convex_hull.py index 21463e62197d..f233e822c473 100644 --- a/divide_and_conquer/convex_hull.py +++ b/divide_and_conquer/convex_hull.py @@ -28,7 +28,7 @@ class Point: Examples -------- >>> Point(1, 2) - (1, 2) + (1.0, 2.0) >>> Point("1", "2") (1.0, 2.0) >>> Point(1, 2) > Point(0, 1) @@ -41,7 +41,7 @@ class Point: Traceback (most recent call last): ... ValueError: x and y must be both numeric types but got , instead - """ + """ def __init__(self, x, y): if not (isinstance(x, Number) and isinstance(y, Number)): @@ -200,8 +200,7 @@ def _validate_input(points): ) elif not hasattr(points, "__iter__"): raise ValueError( - "Expecting an iterable object " - f"but got an non-iterable type {points}" + "Expecting an iterable object " f"but got an non-iterable type {points}" ) except TypeError as e: print("Expecting an iterable of type Point, list or tuple.") diff --git a/graphs/dijkstra_algorithm.py b/graphs/dijkstra_algorithm.py index 57733eb5106d..7dfb5fb9df48 100644 --- a/graphs/dijkstra_algorithm.py +++ b/graphs/dijkstra_algorithm.py @@ -104,9 +104,7 @@ def show_graph(self): # u -> v(w) for u in self.adjList: print( - u, - "->", - " -> ".join(str(f"{v}({w})") for v, w in self.adjList[u]), + u, "->", " -> ".join(str(f"{v}({w})") for v, w in self.adjList[u]), ) def dijkstra(self, src): diff --git a/machine_learning/sequential_minimum_optimization.py b/machine_learning/sequential_minimum_optimization.py index cb859602b29f..a98bd93f7a06 100644 --- a/machine_learning/sequential_minimum_optimization.py +++ b/machine_learning/sequential_minimum_optimization.py @@ -499,9 +499,7 @@ def test_cancel_data(): for i in range(test_tags.shape[0]): if test_tags[i] == predict[i]: score += 1 - print( - f"\r\nall: {test_num}\r\nright: {score}\r\nfalse: {test_num - score}" - ) + print(f"\r\nall: {test_num}\r\nright: {score}\r\nfalse: {test_num - score}") print(f"Rough Accuracy: {score / test_tags.shape[0]}") From 43905efe298172e9e9280661d80af8f7e2105517 Mon Sep 17 00:00:00 2001 From: ELNS <57490926+EverLookNeverSee@users.noreply.github.com> Date: Mon, 9 Dec 2019 01:45:17 +0330 Subject: [PATCH 0394/1071] Adding doctests into LDA algorithm (#1621) * Adding doctests into function * Adding doctests into function * Adding doctests into function * Adding doctests into function * Adding doctests into function * Adding doctests into function * Adding doctests into function * fixup! Format Python code with psf/black push * Update convex_hull.py * Update convex_hull.py --- divide_and_conquer/convex_hull.py | 57 +++++--------- .../linear_discriminant_analysis.py | 78 ++++++++++++++++++- 2 files changed, 96 insertions(+), 39 deletions(-) diff --git a/divide_and_conquer/convex_hull.py b/divide_and_conquer/convex_hull.py index f233e822c473..76184524e266 100644 --- a/divide_and_conquer/convex_hull.py +++ b/divide_and_conquer/convex_hull.py @@ -1,5 +1,3 @@ -from numbers import Number - """ The convex hull problem is problem of finding all the vertices of convex polygon, P of a set of points in a plane such that all the points are either on the vertices of P or @@ -40,22 +38,11 @@ class Point: >>> Point("pi", "e") Traceback (most recent call last): ... - ValueError: x and y must be both numeric types but got , instead + ValueError: could not convert string to float: 'pi' """ def __init__(self, x, y): - if not (isinstance(x, Number) and isinstance(y, Number)): - try: - x, y = float(x), float(y) - except ValueError as e: - e.args = ( - "x and y must be both numeric types " - f"but got {type(x)}, {type(y)} instead" - ) - raise - - self.x = x - self.y = y + self.x, self.y = float(x), float(y) def __eq__(self, other): return self.x == other.x and self.y == other.y @@ -112,13 +99,7 @@ def _construct_points(list_of_tuples): Examples ------- >>> _construct_points([[1, 1], [2, -1], [0.3, 4]]) - [(1, 1), (2, -1), (0.3, 4)] - >>> _construct_points(([1, 1], [2, -1], [0.3, 4])) - [(1, 1), (2, -1), (0.3, 4)] - >>> _construct_points([(1, 1), (2, -1), (0.3, 4)]) - [(1, 1), (2, -1), (0.3, 4)] - >>> _construct_points([[1, 1], (2, -1), [0.3, 4]]) - [(1, 1), (2, -1), (0.3, 4)] + [(1.0, 1.0), (2.0, -1.0), (0.3, 4.0)] >>> _construct_points([1, 2]) Ignoring deformed point 1. All points must have at least 2 coordinates. Ignoring deformed point 2. All points must have at least 2 coordinates. @@ -168,11 +149,11 @@ def _validate_input(points): Examples ------- >>> _validate_input([[1, 2]]) - [(1, 2)] + [(1.0, 2.0)] >>> _validate_input([(1, 2)]) - [(1, 2)] + [(1.0, 2.0)] >>> _validate_input([Point(2, 1), Point(-1, 2)]) - [(2, 1), (-1, 2)] + [(2.0, 1.0), (-1.0, 2.0)] >>> _validate_input([]) Traceback (most recent call last): ... @@ -200,9 +181,9 @@ def _validate_input(points): ) elif not hasattr(points, "__iter__"): raise ValueError( - "Expecting an iterable object " f"but got an non-iterable type {points}" + f"Expecting an iterable object but got an non-iterable type {points}" ) - except TypeError as e: + except TypeError: print("Expecting an iterable of type Point, list or tuple.") raise @@ -233,11 +214,11 @@ def _det(a, b, c): Examples ---------- >>> _det(Point(1, 1), Point(1, 2), Point(1, 5)) - 0 + 0.0 >>> _det(Point(0, 0), Point(10, 0), Point(0, 10)) - 100 + 100.0 >>> _det(Point(0, 0), Point(10, 0), Point(0, -10)) - -100 + -100.0 """ det = (a.x * b.y + b.x * c.y + c.x * a.y) - (a.y * b.x + b.y * c.x + c.y * a.x) @@ -271,13 +252,13 @@ def convex_hull_bf(points): Examples --------- >>> convex_hull_bf([[0, 0], [1, 0], [10, 1]]) - [(0, 0), (1, 0), (10, 1)] + [(0.0, 0.0), (1.0, 0.0), (10.0, 1.0)] >>> convex_hull_bf([[0, 0], [1, 0], [10, 0]]) - [(0, 0), (10, 0)] + [(0.0, 0.0), (10.0, 0.0)] >>> convex_hull_bf([[-1, 1],[-1, -1], [0, 0], [0.5, 0.5], [1, -1], [1, 1], [-0.75, 1]]) - [(-1, -1), (-1, 1), (1, -1), (1, 1)] + [(-1.0, -1.0), (-1.0, 1.0), (1.0, -1.0), (1.0, 1.0)] >>> convex_hull_bf([(0, 3), (2, 2), (1, 1), (2, 1), (3, 0), (0, 0), (3, 3), (2, -1), (2, -4), (1, -3)]) - [(0, 0), (0, 3), (1, -3), (2, -4), (3, 0), (3, 3)] + [(0.0, 0.0), (0.0, 3.0), (1.0, -3.0), (2.0, -4.0), (3.0, 0.0), (3.0, 3.0)] """ points = sorted(_validate_input(points)) @@ -336,13 +317,13 @@ def convex_hull_recursive(points): Examples --------- >>> convex_hull_recursive([[0, 0], [1, 0], [10, 1]]) - [(0, 0), (1, 0), (10, 1)] + [(0.0, 0.0), (1.0, 0.0), (10.0, 1.0)] >>> convex_hull_recursive([[0, 0], [1, 0], [10, 0]]) - [(0, 0), (10, 0)] + [(0.0, 0.0), (10.0, 0.0)] >>> convex_hull_recursive([[-1, 1],[-1, -1], [0, 0], [0.5, 0.5], [1, -1], [1, 1], [-0.75, 1]]) - [(-1, -1), (-1, 1), (1, -1), (1, 1)] + [(-1.0, -1.0), (-1.0, 1.0), (1.0, -1.0), (1.0, 1.0)] >>> convex_hull_recursive([(0, 3), (2, 2), (1, 1), (2, 1), (3, 0), (0, 0), (3, 3), (2, -1), (2, -4), (1, -3)]) - [(0, 0), (0, 3), (1, -3), (2, -4), (3, 0), (3, 3)] + [(0.0, 0.0), (0.0, 3.0), (1.0, -3.0), (2.0, -4.0), (3.0, 0.0), (3.0, 3.0)] """ points = sorted(_validate_input(points)) diff --git a/machine_learning/linear_discriminant_analysis.py b/machine_learning/linear_discriminant_analysis.py index cc2f1dac7237..6998db1ce4a0 100644 --- a/machine_learning/linear_discriminant_analysis.py +++ b/machine_learning/linear_discriminant_analysis.py @@ -45,6 +45,7 @@ from math import log from os import name, system from random import gauss +from random import seed # Make a training dataset drawn from a gaussian distribution @@ -56,7 +57,15 @@ def gaussian_distribution(mean: float, std_dev: float, instance_count: int) -> l :param instance_count: instance number of class :return: a list containing generated values based-on given mean, std_dev and instance_count + + >>> gaussian_distribution(5.0, 1.0, 20) # doctest: +NORMALIZE_WHITESPACE + [6.288184753155463, 6.4494456086997705, 5.066335808938262, 4.235456349028368, + 3.9078267848958586, 5.031334516831717, 3.977896829989127, 3.56317055489747, + 5.199311976483754, 5.133374604658605, 5.546468300338232, 4.086029056264687, + 5.005005283626573, 4.935258239627312, 3.494170998739258, 5.537997178661033, + 5.320711100998849, 7.3891120432406865, 5.202969177309964, 4.855297691835079] """ + seed(1) return [gauss(mean, std_dev) for _ in range(instance_count)] @@ -67,6 +76,14 @@ def y_generator(class_count: int, instance_count: list) -> list: :param class_count: Number of classes(data groupings) in dataset :param instance_count: number of instances in class :return: corresponding values for data groupings in dataset + + >>> y_generator(1, [10]) + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + >>> y_generator(2, [5, 10]) + [0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1] + >>> y_generator(4, [10, 5, 15, 20]) # doctest: +NORMALIZE_WHITESPACE + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3] """ return [k for k in range(class_count) for _ in range(instance_count[k])] @@ -79,6 +96,10 @@ def calculate_mean(instance_count: int, items: list) -> float: :param instance_count: Number of instances in class :param items: items that related to specific class(data grouping) :return: calculated actual mean of considered class + + >>> items = gaussian_distribution(5.0, 1.0, 20) + >>> calculate_mean(len(items), items) + 5.011267842911003 """ # the sum of all items divided by number of instances return sum(items) / instance_count @@ -91,6 +112,11 @@ def calculate_probabilities(instance_count: int, total_count: int) -> float: :param instance_count: number of instances in class :param total_count: the number of all instances :return: value of probability for considered class + + >>> calculate_probabilities(20, 60) + 0.3333333333333333 + >>> calculate_probabilities(30, 100) + 0.3 """ # number of instances in specific class divided by number of all instances return instance_count / total_count @@ -104,6 +130,12 @@ def calculate_variance(items: list, means: list, total_count: int) -> float: :param means: a list containing real mean values of each class :param total_count: the number of all instances :return: calculated variance for considered dataset + + >>> items = gaussian_distribution(5.0, 1.0, 20) + >>> means = [5.011267842911003] + >>> total_count = 20 + >>> calculate_variance([items], means, total_count) + 0.9618530973487491 """ squared_diff = [] # An empty list to store all squared differences # iterate over number of elements in items @@ -129,6 +161,36 @@ def predict_y_values( :param variance: calculated value of variance by calculate_variance function :param probabilities: a list containing all probabilities of classes :return: a list containing predicted Y values + + >>> x_items = [[6.288184753155463, 6.4494456086997705, 5.066335808938262, + ... 4.235456349028368, 3.9078267848958586, 5.031334516831717, + ... 3.977896829989127, 3.56317055489747, 5.199311976483754, + ... 5.133374604658605, 5.546468300338232, 4.086029056264687, + ... 5.005005283626573, 4.935258239627312, 3.494170998739258, + ... 5.537997178661033, 5.320711100998849, 7.3891120432406865, + ... 5.202969177309964, 4.855297691835079], [11.288184753155463, + ... 11.44944560869977, 10.066335808938263, 9.235456349028368, + ... 8.907826784895859, 10.031334516831716, 8.977896829989128, + ... 8.56317055489747, 10.199311976483754, 10.133374604658606, + ... 10.546468300338232, 9.086029056264687, 10.005005283626572, + ... 9.935258239627313, 8.494170998739259, 10.537997178661033, + ... 10.320711100998848, 12.389112043240686, 10.202969177309964, + ... 9.85529769183508], [16.288184753155463, 16.449445608699772, + ... 15.066335808938263, 14.235456349028368, 13.907826784895859, + ... 15.031334516831716, 13.977896829989128, 13.56317055489747, + ... 15.199311976483754, 15.133374604658606, 15.546468300338232, + ... 14.086029056264687, 15.005005283626572, 14.935258239627313, + ... 13.494170998739259, 15.537997178661033, 15.320711100998848, + ... 17.389112043240686, 15.202969177309964, 14.85529769183508]] + + >>> means = [5.011267842911003, 10.011267842911003, 15.011267842911002] + >>> variance = 0.9618530973487494 + >>> probabilities = [0.3333333333333333, 0.3333333333333333, 0.3333333333333333] + >>> predict_y_values(x_items, means, variance, probabilities) # doctest: +NORMALIZE_WHITESPACE + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2] + """ # An empty list to store generated discriminant values of all items in dataset for # each class @@ -148,7 +210,7 @@ def predict_y_values( ) # appending discriminant values of each item to 'results' list results.append(temp) - print("Generated Discriminants: \n", results) + return [l.index(max(l)) for l in results] @@ -161,6 +223,20 @@ def accuracy(actual_y: list, predicted_y: list) -> float: :param predicted_y: a list containing predicted Y values generated by 'predict_y_values' function :return: percentage of accuracy + + >>> actual_y = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, + ... 1, 1 ,1 ,1 ,1 ,1 ,1] + >>> predicted_y = [0, 0, 0, 1, 1, 1, 0, 0, 1, 1, 0, 0, + ... 0, 0, 1, 1, 1, 0, 1, 1, 1] + >>> accuracy(actual_y, predicted_y) + 50.0 + + >>> actual_y = [0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, + ... 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2] + >>> predicted_y = [0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, + ... 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2] + >>> accuracy(actual_y, predicted_y) + 100.0 """ # iterate over one element of each list at a time (zip mode) # prediction is correct if actual Y value equals to predicted Y value From 74d96ab3558120b6810aa3f613e091774aefeca3 Mon Sep 17 00:00:00 2001 From: Jawpral <34590600+Jawpral@users.noreply.github.com> Date: Mon, 9 Dec 2019 03:57:42 +0530 Subject: [PATCH 0395/1071] Fixed issue #1368 (#1482) * Changed as suggested Now return in same format as oct() returns * Slight change * Fixed issue #1368, return values for large number now is fixed and does not return in scientific notation * Update decimal_to_octal.py --- conversions/decimal_to_octal.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/conversions/decimal_to_octal.py b/conversions/decimal_to_octal.py index 0b005429d9d7..b1829f1a3973 100644 --- a/conversions/decimal_to_octal.py +++ b/conversions/decimal_to_octal.py @@ -6,8 +6,12 @@ # https://github.com/TheAlgorithms/Javascript/blob/master/Conversions/DecimalToOctal.js -def decimal_to_octal(num): - """Convert a Decimal Number to an Octal Number.""" +def decimal_to_octal(num: int) -> str: + """Convert a Decimal Number to an Octal Number. + + >>> all(decimal_to_octal(i) == oct(i) for i in (0, 2, 8, 64, 65, 216, 255, 256, 512)) + True + """ octal = 0 counter = 0 while num > 0: @@ -16,7 +20,7 @@ def decimal_to_octal(num): counter += 1 num = math.floor(num / 8) # basically /= 8 without remainder if any # This formatting removes trailing '.0' from `octal`. - return "{0:g}".format(float(octal)) + return f"0o{int(octal)}" def main(): From 02b717e364cd6623e303d4cc2378d1448779dc2b Mon Sep 17 00:00:00 2001 From: Samarth Sehgal Date: Mon, 9 Dec 2019 09:43:56 +1100 Subject: [PATCH 0396/1071] Update odd_even_transposition_parallel.py (#1458) * Update odd_even_transposition_parallel.py * arr = OddEvenTransposition(arr) --- sorts/odd_even_transposition_parallel.py | 16 +++------------- 1 file changed, 3 insertions(+), 13 deletions(-) diff --git a/sorts/odd_even_transposition_parallel.py b/sorts/odd_even_transposition_parallel.py index 4d2f377024d2..080c86af5a8c 100644 --- a/sorts/odd_even_transposition_parallel.py +++ b/sorts/odd_even_transposition_parallel.py @@ -10,7 +10,7 @@ They are synchronized with locks and message passing but other forms of synchronization could be used. """ -from multiprocessing import Process, Pipe, Lock +from multiprocessing import Lock, Pipe, Process # lock used to ensure that two processes do not access a pipe at the same time processLock = Lock() @@ -73,15 +73,11 @@ def oeProcess(position, value, LSend, RSend, LRcv, RRcv, resultPipe): def OddEvenTransposition(arr): - processArray = [] - resultPipe = [] - # initialize the list of pipes where the values will be retrieved for _ in arr: resultPipe.append(Pipe()) - # creates the processes # the first and last process only have one neighbor so they are made outside # of the loop @@ -131,21 +127,15 @@ def OddEvenTransposition(arr): for p in range(0, len(resultPipe)): arr[p] = resultPipe[p][0].recv() processArray[p].join() - return arr # creates a reverse sorted list and sorts it def main(): - arr = [] - - for i in range(10, 0, -1): - arr.append(i) + arr = list(range(10, 0, -1)) print("Initial List") print(*arr) - - list = OddEvenTransposition(arr) - + arr = OddEvenTransposition(arr) print("Sorted List\n") print(*arr) From 9316618611967c26b98ce275fab238f735f2864e Mon Sep 17 00:00:00 2001 From: Shoaib Asgar Date: Mon, 9 Dec 2019 07:59:01 +0530 Subject: [PATCH 0397/1071] digital_image_processing/convert_to_negative (#1216) * digital_image_processing/convert_to_negative * added doc * added test code * Update convert_to_negative.py --- .../convert_to_negative.py | 30 +++++++++++++++++++ .../test_digital_image_processing.py | 8 +++++ 2 files changed, 38 insertions(+) create mode 100644 digital_image_processing/convert_to_negative.py diff --git a/digital_image_processing/convert_to_negative.py b/digital_image_processing/convert_to_negative.py new file mode 100644 index 000000000000..cba503938aec --- /dev/null +++ b/digital_image_processing/convert_to_negative.py @@ -0,0 +1,30 @@ +""" + Implemented an algorithm using opencv to convert a colored image into its negative +""" + +from cv2 import imread, imshow, waitKey, destroyAllWindows + + +def convert_to_negative(img): + # getting number of pixels in the image + pixel_h, pixel_v = img.shape[0], img.shape[1] + + # converting each pixel's color to its negative + for i in range(pixel_h): + for j in range(pixel_v): + img[i][j] = [255, 255, 255] - img[i][j] + + return img + + +if __name__ == "__main__": + # read original image + img = imread("image_data/lena.jpg", 1) + + # convert to its negative + neg = convert_to_negative(img) + + # show result image + imshow("negative of original image", img) + waitKey(0) + destroyAllWindows() diff --git a/digital_image_processing/test_digital_image_processing.py b/digital_image_processing/test_digital_image_processing.py index 02c1a2d3a663..1a730b39101b 100644 --- a/digital_image_processing/test_digital_image_processing.py +++ b/digital_image_processing/test_digital_image_processing.py @@ -8,6 +8,7 @@ import digital_image_processing.filters.sobel_filter as sob import digital_image_processing.filters.convolve as conv import digital_image_processing.change_contrast as cc +import digital_image_processing.convert_to_negative as cn from cv2 import imread, cvtColor, COLOR_BGR2GRAY from numpy import array, uint8 from PIL import Image @@ -15,6 +16,13 @@ img = imread(r"digital_image_processing/image_data/lena_small.jpg") gray = cvtColor(img, COLOR_BGR2GRAY) +# Test: convert_to_negative() +def test_convert_to_negative(): + negative_img = cn.convert_to_negative(img) + # assert negative_img array for at least one True + assert negative_img.any() + + # Test: change_contrast() def test_change_contrast(): with Image.open("digital_image_processing/image_data/lena_small.jpg") as img: From 1cbeaa252ad5822c9197b385a99e550f7aa2f897 Mon Sep 17 00:00:00 2001 From: Binish Manandhar <37204996+binish784@users.noreply.github.com> Date: Tue, 10 Dec 2019 12:37:40 +0545 Subject: [PATCH 0398/1071] Image processing algorithms added (#616) * Image processing algorithms added * Example images included * Issues resolved * class added * Naming issues fixes * Create file_path --- .../histogram_stretch.py | 65 ++++++++++++++++++ .../image_data/input.jpg | Bin 0 -> 60104 bytes .../output_data/output.jpg | Bin 0 -> 118463 bytes 3 files changed, 65 insertions(+) create mode 100644 digital_image_processing/histogram_equalization/histogram_stretch.py create mode 100644 digital_image_processing/histogram_equalization/image_data/input.jpg create mode 100644 digital_image_processing/histogram_equalization/output_data/output.jpg diff --git a/digital_image_processing/histogram_equalization/histogram_stretch.py b/digital_image_processing/histogram_equalization/histogram_stretch.py new file mode 100644 index 000000000000..b6557d6ef77d --- /dev/null +++ b/digital_image_processing/histogram_equalization/histogram_stretch.py @@ -0,0 +1,65 @@ +# -*- coding: utf-8 -*- +""" +Created on Fri Sep 28 15:22:29 2018 + +@author: Binish125 +""" +import copy +import os + +import numpy as np + +import cv2 +import matplotlib.pyplot as plt + + +class contrastStretch: + def __init__(self): + self.img = "" + self.original_image = "" + self.last_list = [] + self.rem = 0 + self.L = 256 + self.sk = 0 + self.k = 0 + self.number_of_rows = 0 + self.number_of_cols = 0 + + def stretch(self, input_image): + self.img = cv2.imread(input_image, 0) + self.original_image = copy.deepcopy(self.img) + x, _, _ = plt.hist(self.img.ravel(), 256, [0, 256], label="x") + self.k = np.sum(x) + for i in range(len(x)): + prk = x[i] / self.k + self.sk += prk + last = (self.L - 1) * self.sk + if self.rem != 0: + self.rem = int(last % last) + last = int(last + 1 if self.rem >= 0.5 else last) + self.last_list.append(last) + self.number_of_rows = int(np.ma.count(self.img) / self.img[1].size) + self.number_of_cols = self.img[1].size + for i in range(self.number_of_cols): + for j in range(self.number_of_rows): + num = self.img[j][i] + if num != self.last_list[num]: + self.img[j][i] = self.last_list[num] + cv2.imwrite("output_data/output.jpg", self.img) + + def plotHistogram(self): + plt.hist(self.img.ravel(), 256, [0, 256]) + + def showImage(self): + cv2.imshow("Output-Image", self.img) + cv2.imshow("Input-Image", self.original_image) + cv2.waitKey(5000) + cv2.destroyAllWindows() + + +if __name__ == "__main__": + file_path = os.path.join(os.path.basename(__file__), "image_data/input.jpg") + stretcher = contrastStretch() + stretcher.stretch(file_path) + stretcher.plotHistogram() + stretcher.showImage() diff --git a/digital_image_processing/histogram_equalization/image_data/input.jpg b/digital_image_processing/histogram_equalization/image_data/input.jpg new file mode 100644 index 0000000000000000000000000000000000000000..483da6fd97f44624598f9f501fddcd2987951677 GIT binary patch literal 60104 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

EY;=J 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 KfA@_5fB)G)6y%Qp literal 0 HcmV?d00001 diff --git a/digital_image_processing/histogram_equalization/output_data/output.jpg b/digital_image_processing/histogram_equalization/output_data/output.jpg new file mode 100644 index 0000000000000000000000000000000000000000..48c70d8ae17daa06ac182057374ffdb3b8556384 GIT binary patch literal 118463 zcmX`S3p|tkAOF8fNn#=7xJq%Ckenj6RFZ_s9Wv}L$*Eal46_wQIab1rSS2KTRGyLCe%hqjD+jmGyJ}WDE196Y! zoZGfZ&a_=}8p*q3C9i{|6t*iKFt**HbmfNh!O*=Y9z1)!Q|HXbR^_WdxQ9$`-iwpj zrJ}k|P5rR0-d{(KnwptgoV2t$d(Q5>{RM}Mu76*1bN9IJ>3hrXwtqliP*^xRA`%l7 z9slrALgM2mNzXH0WM*amo0I#d@NH3X$-C0`pQ>w!wRQCkjcwHSj?S*n-Cz0#28V`e zBco#sChO;~nc3fS^SnRHE35o9V0~kA47BsVVS|B$r`{y}_2$0J$-RFY2fKan_MD_v zEd;|>PldDy9lkkRCGjazzY#mdW|)kNJh@HK#5}B zS8+*&4bsrrlvw>y@)jW+V3YwNOji3Q2#j?pog(CiAXipA-d>VVJSsViX6_2=QS?$5 zUa3bpvxM5WXD;h^hC}T3-s*16hj&5ARpbj=#^1tx^5`hA(3zJ|7Fq(hGYUQtd?A#~ zn(nK{aIrCv&9h*vJvB028irY)}{o!9H^3Ts+F^OteO5zBQ2pll@ zZ#CQ@LVShUIMOugZm?T+(U|cj$SJs(Tf&j8nDp`)1G3L>SAUWE*Y5^3dTfHy5gTlA z3@!9EhH&u%_ty}uT@>R?!=AF(<8lihqfn(LdbbG*!jIw^qIUJ^RFY-sRM)GsGxq4c z_FuDKhHI{NU7|h4eV-p z+mkdi?rfFWTwV}@Wl{p@e~w#*#2OD%-l5{BHA&aFEPeoR?G+H?R5y5Vkmzl4F-u4M z!P8Mb5{%1S>1&5P!EFbm>B$5&VEk&`fT{lZQEe;L1!>@#jRmVJeig5LmL6rMdz~nE z4B=@SzjS6+t%7&tQ*E~KV#4SqC`wFQ@4(hmxUO)=s}n?Nf*ZS6JNz~ zpRt8jpXXHv#INq-ckxn9S|cdC_E&S$D&FrDKKM1Nb^|h}JVczPPTF5nj~cqeY$$+D zQfGIs6IgEH5LJx5bhK1#NQL!V0%`U;tAtW=}ceHqYm-QpW-=EYd>)l6fBD2 zy94f`M~tc@sK)SZ63_3U%MEPCo%D+hJ_qyOwI)MF`t&N`FMpxJI2BUk^6)J$y^Zo@ zN}UXpDyNTe42~ghr`benuF{v$t!rwBBBgnKojwY@O%Ro$Q6rXoF$rt}yHh9HuuVi;E;A~GB zyOgnAHR$ik(}#Rtx;t=b%uE_Jf1CG@-KhqPCAXn{;aMX`;?851mP6A+^m8iu5(!E! z4=a{ddflcL;g;o#91h0-vZYaOQhw@RA!en#PmUc~pf$uA4iC$n*+(aTZ|Up(Y`Ni%rh~^XE?+W-N>jMPx~Dnh zKDmjjDz|k&ckPein$ts*R;#qcgJKx~6;u2>x~3|8?UG&VhX$os!oHb*f8IKh0eoBKETpav?~= z{q5P=S=*vN%UjOw`^hXBfZah!&6B}2w2QpGC?`s;q2LGFe$0@2@^@@Zboi1F8Y%F` zHA_G?pgv=cNCN7&f7X#^oi7d zG-UZx)A@*HV1+g3UC<;55qcxf&;04%7&Yi9R)s(%fkEN0nH>EN;|pUq zskgeN>UiE0ip~z*bK}y#x>)dR1oujQz{F?w)?>C$>3Kq>zTzqUUtdaZW6zquy3z3I z(E%ypm(fEJU9j)ha_GmI{ff`Lk1nQ7G3*y(e7C)tH(A7Gj!p>w%4;L5Ci?JvJ?-wT z!G71`Fq7u?$BbX1R~N%B3!#7tSP=thiXV3C58!5}g5tB0OQ&zbJcfo>ajJlLgO09 zuGDMXinnm{psfUXiC zo^Cg%d;~wJaO)Dm;Q=4*6rP4duhNwO_e#K>FE2{Ms>Qz&f7RYrd*w$qRb_k=WRuy7 zL7W$5VDY>RFF@-R?u$0$%9|6Z&NJ*b^EuR^loOx~)Ynj-Qpv|QK_JorTs7cG?|ZoN zPDgspOC7(TKkZ1JmQr5l@%6FspGX1D;C!Z$&q z1Fajxs^?C5lt;+%ZQ`6?_{=cv1EM7SEgPK{Rh&Vi=w-NWL&4xC=(Vh^+L5{6`YkJc zfDV4)5f9Rmo1MHePEf-9<)h|k_h#PMRt<8dB)ya5*d-Zrlow8eTQg-1XbCO@Y0Gf( zQw6_ROO-SVC$-(EYAZPcSlR?hd5W5#DpY^oc~&PaY%EZW?kp_)z47l0C;3xxmJ2Z~ zP2UWJ8rDx#fwkw3yEpV>{kY3s0?%qYq;}Jr9z~a$6h^~Ip^bhk&KlQGJ_0NSZODnU z1N{TH|040=qh}i!Hai?xgDZ3#ISLt8JQftu_-I}Setq89F{0eKz{+1M$|nY=EK zZG!3`ZTLh=HIzU{D0+97v7=XOBqLO(kIMX@?Y?LW&8*`S0^YhAGV&=p_x^4 zm-!uB1V@(fkmo{;y$X-nnMjD*TlVXtb|wp^Dhw5v%|Q?1$Ly*2F~!L_f7Rm^&t|!1 zL&9B>XrvH5(A4F&qnvNn18JGu z#V0Ip&p-U3;pNNX!MgfQ(31xOho!ORbVgsY(23=ALeSC}QH}KSO zN%aBwK|4F(E^wQ0dNiO{NA(?7ejUoorb18jYqXE~WS~(@Cn?s#4)A2W(Yi=KS@iyn zHCU3bT66IU*f2&@GlT<~yEo5;`*&+6HdGZzDkAWcH8)T52ZCebF9;Pg4>U*SO&B_& zO(TYR-gfz({xA+U3ZOb6pKj-%1rb29K(9mn7~a3u{E*idm&yZt@wHB&64#|=X_rvB z2B4JzvsZa;C57K^afy!}x}LkGBaWthMG&VK3h5}|sA}qMURNU4(AtctaT3NvRF2#J z2{NIJXIu;NdDB|eh#gLn3HuvpVo;Q(Fc!>$Qe<(EXDl}j9^*0b$146g?A>fhRWnvg zlp(2)H=Mququ$uw@#`C#$#^~)_Hs5pqs!T)B1)AL%e83HR^TdktR<}MyFsdMxLVz{ zbIYd|Y>V$nHsJpCza0F(U0G}BMf5{mW*dJfl?1(0`B}3~{`lniQ`(}kcLV6jtws;m z)TJT_L5(r5)yMn7{)~gj@zyWttK#bv?x`!>%GZ^im>8y07Ge9Nvb={wezoJKsdbA5 zxXU(RFM^K+EU*WX*r+_51CBFoy5*|EF5{v9e0fWcj*DJp7Z80Jb@yusNf6m8B1#h< z99wpNhlXsPzf^a~tEchHdbJiHu=01{6dgvTJccUPlF8qRTB=8Ww&&q#_n~WFceZYT zdv-MHQ|{xX3Dgar%rbbRy>IBOlg@D^2X;Mc-B;_Qj~eeewXI+;&ulgxyIuYjKkMU* zhKBk(UlvT-Ufv7aNGTZoLQ;2{eDo>c`{$5<>Wpw2Pvu#0#@~G*skV$zQG7J|RE~J) zwpZ+@k^C~{da=Stv_Eg7mwBH1tGz_5;AB0=T_1J48Hx0o1&Mvl6up6~Up~**Ern|Y ze36&-Imw~*m$H{FhboCqiaUQktCY@_{pZv2x~8^>vWqM}^j-N&&)bV0kblb+eW4@D z($v142<&Mdp06}!r<4P7tgX>0zBlO{`XDVKVWZA0X=s(pS^ zm~)}arf=WUOZVTKpoQg4(2fy&tNA9#KV6}N#ffp?L6TSX?WbjEL^sE9XxZ*lr#C@5 zMX{rdbV;@YTPrXG-m0wydk4bHWbYOa50AB8K5uhh)2+v6j5yHx1QCQ?fF+8zT^{X0 z9<=5o8g19cJY(DWf9&|Jr3>c-ee7$MSy7b_XY2&W9A{97#-Rl05e#&`b0$3UhSXwZ zz1Vp0?<>H7_t45<4E$W01EQP|^^om0T&wM0U_70sKP6P8fWsl% z=frYcMAFX$E#2c!Jubj-EfJP8#XNAkoP|KmjG_XZVagoL>rU0VaiC8Sm&3C0r^>xO zoKO@R87lwIyrWq3@B4J_JFH&W1R(1iYvKBFpl0&0dvBaHY3lksMtF7;B!eSh&~V2l z^ds2!qFk2)yZ`0*_zP@teUe%P7EradI74t&s5G$pxzhbh?&0c`Zj{0%s36g@YamH@ zTN1&OBq6V0$A7~%D8zO$cf>#j#peW(} zo0VJ#t*;f$W%Ju-roj4&E0uK&BaQ(xJ!x9LFD-m2F&a7anL|{B*0;@^mgkd;}}U?R&h^v!ns+i_K_%zCIP&r??3sEVB{N09DEVNNPYH zc$82Qyd}L)>5u+v?vSK4a?+t@HD*5u8FzpFT@5wN65YC1U5fhw)s2B8U8<HLAXA zp2W@*`fmn>2wn)=A^Q@qGCer5XcGPhx!xxV?ZD`@8z}Lk;(iK)c;JLpHe$hTU+w)( zP^B=&9l=Q7f!W4y)!$i>GME5_|&ypk|LqjMPs|(LJs5qE3KP+z@|L?B^3XFo1 z{axcC>oZ3~?n(U_a3izv?x;9{Z2lh~`rlq5r`ORxcf>I3&atsc8n#6^a6!8EgtR1j zGf>F`02tnod}e@C8BQbWE)KZXEw}sbPkELW+LR{F9L7fTq^Sn3=RTsgj(X%(oVBp9 zsCA6nBDEz2eJG=^MCaKxp-zlp6ZcfR)zPsB1}P5w;8(9QcduK={6pxhrTD>;tr7@m zNE|WKSF58T`0{=UIEiSzElsvs?K9(4vn|%m$7=J$NvdCo4GH@r9OiuNSo(b$9@vP1 z6+CAqu5;lv{2p*!wO*&#L3&wpSW>u%RRgLdDF%^!3EqkhVlIw3!ffvC-TgfJmT%Qs z@RwsNlF^-qs4v!x=LOdv5ZFz~?t(x2nTA!HsqIN=MB3eE#+Wj}J1vXkouUnupcH%U! z3V=8A^<3y@kBX-Cij0Zw1IN1NogzGEnu~y-wq;LJy>$0uQfX7f_qYa^N1Gr`tSm3D z)qvRtwir8jeYcCYrBp;^lJ`}OCR=RnJ$TpN>v?HqmkDa)lb&7Zw_`cy2r)7q{qtgd znpxof!5U+~TZH-1!`~#eetFa9nx;udfdTfij#W$|<*`@z*#T(ggLl}|b*|=3C<(9< zw>-GpAH~{Y`j;XfJ*AP>rO^}zU5O=tjDd!;ER%9Rb zwg**rDN$g(UPNglhXD2r${3`LQggn9hhT{b?^5LhFFn1U{A?dz^chwZYvjvBB@nd2 zZ3F4}-9ajk@vNCYOTWYx=~Jh)P5TNaz$Bk-=#S%a(&*y#UR z7Vj#O6s#d!3devu`?si&tQi>J22nI!pCP~p-ahV7eQa~d=aT}ER#-sEfPG5sO2Tau z4~?M`gm-nnFI5kvX`yeWADz)SLS7P~E8huG zfD=!aiswEM-mGhE8m#dD!y#gKvVBUp zT}AlfmzYy9<1*gdCc`$VxW+e-Oc-YblN0WGl^RV&z-j65fYT@{3T zVA81;>AXS)B+(GL!hVA7J@e~EOJ7Bl(F2?*)}w!4uy`BKjyU0d7hhrW$h?UNU>9KA6`&``; zx-yy9*V2<%TU+0fq|#C^4E3&B7-IE0G7}$qA`-MUYPhW)u>NC4-)G1>eno(o4`x??&~mWfxwoW0(- z2|5!`{n9^vy(8n9p}g_ueL5-bAH>tSkajKoOI*rh>~>zFlhttIp@AvjjBNT!(c_mh zUfR*_`Zws^;{D>!kpv~Q%UtU^!TcE#tc-!0uT>bXZoGf ziXJr7Wra6Z!}cGOvEBsj;iUYD>Bss4r2;5>%;#sVRr8-OTV0e3k7##1h8RIt;dY`D zvKD|pQ`@gfr7iA}(lE%pzalmoq{;D=x+t>NzBBAl-~_*4d^Sh!ZJ{sZ>eiQH5Zdy+ zB%sz+Y=VB<{@4U94d8j#L{Bj+rcwJSRN5QpY4zOQTW+h%jy1pJ_d=VHi7t@ip^}9z z3_1O)*=OLV@*e9vpHXv;P&{D=r*XnB80e7)SO4lC>c(HU;=Cc|qD>2~Z`#uBZkk z>)JO^S$Qb;*R30x7hJ4E-BCgprnk$LChk;;TyxrUY1IJe<${8toY?Z9i1iQLx+DWi z^^GLEte-v4U4|P_8eW(&{yf!@o1J1f)&E!Z-5cl;`{DXNjd{G3X^ciij60BF_BD4| z7G4(Yu%(@1JJ5XZD)wN1Imw`O4#kWqt2nqwpFCTi)J?NE}+d{z3Z{`%?` z!m-`%Fs4y*?qWXj2DgWSD+xj_oEG59q!7*7Nv*@yv8o9uok5ZdGkIyRs%WTP_OFaD z4~Ou30ctBmf!LIBp#g<8z^yE7p(ufqYPN9&o$j7?kEX1<7omNf zVZ%k-qG@Wns@Ht{X&!0JC+B7x{K@Qo4T`IGA@*uE)zdBS9R}83t7^Gt-JGe3n;k}6 z{B24Z+jgE;w2Z;CUUd!4&RQVenfGwR8Z4kJJa(S87)T=Y}s-OMFq^MKqB>WCj zjt7G!6DNpSCt zW1+puou?nCUEBn1)K`5X8ttJ_jS`-VzZW_=CdZ^2^?A#ybY*_C6vpa5;kt0_R68JB z?U}HB0J=t`Pmg^E9XZ;&aLWBxb6-3*N?<1R8N?}JuMHml!BD;0UF6*>_z}9BFki?TZjNl- z=u9LmRJc1P4d#$O>m~FIH!EEL7|c8aPuf*p_5)LqTj%f=g?a&O<8u&7W6l%bZs0>} zmo#h%H?>t8PO5CgQkHJM*Eadc-Ro)4N1c8k?K%(=a_4pu^9D-XG)AJG5#<|LD{wR1 z>Vfaso{+3kr&ZWG#6>rZ8cT5_po2%9eUI& zbXSp=%cws8AuqA336{K;jR>scnTRU+^KgcMwA>q^o=_+-!!)%wW-p+Mf>f=X#CdzI z-b_xk9Zm{tv5uO)2$YF;jS93Kc|DBG5)P943yJm)zKE}$Zhc#PiFY=oo85=PaviGr zARwUvFwV33>;r&XuJ+!kgI=i5x+~#7gszI1>3BRn8N$76q>i~a-C>2gZ*5&$cX^%9 zy2`s&9^>LYGGZxhBX1+1*{TZnWQcbe@2+jNR-#jYJd3=WWvt{YGhCa7#e1>AV2S7~ zA^GD?tq}tgJ{VN%T~RLN#H`>{r(0EhH0#DDNS7q&f^c0RV@-@rP~uyja|3tDDXZ7( zQd!CTGg8z2Z#X~Db4;I43@zQexTvW2SI~wQr*KPuFqZIH5wCeOClD>S`pNsWGfnf> zKS|I3UsLseTdOLLSei!kfV+JdRQ%!VRCnA#`U~-E5cD|D=uuJKhwsAcB?z zeGTQ4_DmA)7wOcV>TW4`e)t<7b}e5A^|vInSW zC?Ju;l`z>|gzaL;SilA)xx%mRWt4@gx+l{k?Ua1x4DKioMI|J-4XdBeWW?l0&$!Nd zIoWh&{=%=$=K!O;gs#>|w+L;k??_}-iKO3h&M@7^LCmtf`Xs!CIh}0n>&KEqTv2s* z%MxGWx7^h?apg__r~L>13DA}bhTn&=#NdTZ&?=(nGKmLCBq^16j?N{_kGl?58@`$j z_$*m!jK9rbdxXZ>R}s5`;4b+PWM3Zk2fhauowUT-K3x3<4B7;Rc#Msm!%lOqzuE-B zJ=G(VIAqN?Yzo*6?j0>=L9wcut~GO}OA7NJCxhy<=I8O#bo~1YlmZ(sGk(oVaiUkb zykzS>=|xJPZVgmT7%_l__#4JEP+LTm!SM9Iau#8~Qlru7oi1`f-&q~{?4$0X==Juf zH)_p&Y)?-}Iu>2@n=m@PPkeN2WL#k;YT0A`JaiK@%f6(2f?Hcu3iqD#^d4>w+tZO& z_28F-1#P$%MLvwlYp+c_%Nu?X-qR$hZD#h4W?k&>#~lJ_4g*y+hq*rn<81SS=U&rk zCPl(?1K3lq=5a>`Niyh|Qqf~cz%^R@IwH>GtqJ$iY-<3vz-qn#6sy~dJ`%pZnk`qt zwUBENo&Ylt`7o~I(jEXsMiwJCc3U7 zFWA-sH9s+LzIMFm#hkeE0ByOF#SaCDeuA4=iy^E&ugfh<>)uFIXwVM@k0)AejN1ITDSxO}viF+iXOk(OLh@n{Bzqlf+9~g=} zboKEc*walAoAyZv0b=Mz(gF4@Q`?6s7eXrhMIEB9Mbf|~XuitjBd>y==x>pH%o-Jp z4ns<3he|2=e>Fo$w9wtqlO2(as>j)sk*E0b+Jc=}ifNeFG$&bLI7cBaQ4jwhnMB?) zL&!r;bh+w@Q16)JV?Zzu(GjiN9LmjRp1$wp;UZV_X212+DI9UB_BgP7DWGP04+g=B z_8y`v8oeg0c~huD=9{5aNQ&`1f7lbE{x@uL&bWf`grFi-u;)d9k5m@ThgiJ{dRnfk zDM_AXU&^5xKo5&To#jS$Olv(uy*szdc~<0XS>**>qeBvK$^hP%VH96If~jS8&RVFG z?5kdciSg-9QYLqDWRmU}ou`v(F9r^8T)+j9xvaYT(kgn zgZ-+yxGpW;75oMv?$AD_t@E8^h?cXsa@?Ur?%7%)$}p~~_H*o+$+#SN9Gu89(0{w|NL-cg3c8lxIVUM(NUIQ!h=A)en6EsAA#VI@mU>hljX{caRl zQwS&iU}g&6m7D1YzTLAjmj$|eYIegt1$#sE1ZsjkG%W5e1|pW*@8$?so4+I5lb^rc z^LIpNlp336_G=@RTwOS?bAG5MupH%U*;qrmD)|ndrLF74Z}^+O_-HaosUM=&ZS7#YLUz%!_mLFA$dFMIL|B$~h1=o0xCUn<-4=gjp z&kBQ}raum4sZq2Ue&UB)~@TdK3Ja}%nKr5!Wv7b)M4I{A0NK(B`l zu(Ax7-Sg%u92CEF>3Msbj&gPx*8cm?4@3Lo&(+rSGvh@NpS~sV`sXUkVUiP<%7Bl} z>9sc&Q_%0>vF~NtEfWl*hYnMQ@WfS{3;Tzv7A1YicY{SMdoRNe9HVq(-nIVnkjlck zmbyAZf5j-KOmNoAstl9&MJmeu`-GGDeZ4oX=TiOmkcw~vlq<0@4S(Frqi^_=_2I}; zj1u*E5LqvmVQD-gDm5>V#YPds%e;BLozhjzsl7@B=GV1wU;;#ZhqDJx~^t0SVL`T=MyX%F$& z0jG-I-qNqjUicnQvNZ}>I-wEDs7Kz~)>9y!0cXk~hmKrZqf7YZlAzYLHV$;n0>i^atW&){GDHK< zae;;;aJv1IB=2X&+ytqR&X8>YbYqC$$OJt7==GoKNQcUp@v`kce zQX}VUU!60&;ZZ(nEKUP|dvE`bt!O*$nNUSkB-COWC0QA@&0#I%inF1a%F90R6gAy0 z&rvcU{l1&mbOE5!!>#fwOYnIlTf$$nFKNv}M{db|p%YRebLdxg;Z_^3*2S|$>P;moES?70ISz6Pu&NhmH?G|*8aO(UkdhE_~eO+ z{5PM$D}<1vlt)kltU0YlgQ6OkmHRWI(Yql_Cv1oHNR&u_mN1^k|0kKL`hX>>5X+3f z*ZIl$KA*vc*N;bwf*%92Jn0UEg0&)6uNY_>iG7!||5N?T^y;v2vjr=cRNRT*1+q+(O#61qHV9{W z_gwVNc=0p&jO~|2*_7L&FgiaU{R%*Jg=FX6Uk2sk_0tI78kWWjCAE->5XMdGp<6em zzGXV58?s#EuwVY2-9d`+2H?D%^W?3=;geQKPrk+xWO3$=6SPgU25{t5b*rbALfLa zQ>X9k(H6S$&e@qPYt z;!wx8Lfih3V>^mUCZ_`_zE_ZDnJ-Qz3s)+Zz;hqw_|v0}abs+sfaN=N1MQfj%(uG~LHsfcQVgb6xIw z$ENqu$%KLa!t8d)h@0D3<=gGwno7m&l8tvZ!7sSK`1uUTx7vU;T^>O&H_eJn=&WoIzxgXH@F<*LqLSAq&m5D)4jelGI=Rt7lAnXn$Ufr!Q z^4%FSdZ=+RvF8Q&AlP+opu!3E(hZ4>e_6a4lfH$ zaL>mY;uP(j<#2^}3TW4V)c-EEG z(B;!Ly;?D;#LpMKG)pc+imftSUf`<=_~UxoB=;KZ0U)?o{5`3Mo7ZVR7H@b$r>+xL z5tE&Or@x>m3tjKfth$mGR$?MHL04uyo_uC;t4{YzUQ-#{t-^kfMMOPL-f_W{stToG zXrZrgE!vRpxzhs2jw5KfvZWLkrC(x-nI{c)6Si#vej)qJmt**@k1B=7f)BF!cLr9I zUo-S2T~I|FoZ5`m2Gs-RMm!M5 ztNzux5JN)>q%mtO-6qCu!0-Kfq=m0RaOhq9;Qg2l+WS$V8W4zjKZ!xHmPW?^ZmQRv z62R`p;E2DbAwoS~RU28aUuZ)2-mxgKRoz!heklHZ55p4jt`qi!Kp7Xg8%aYI(NDbottVEE#!q6}CJ~4)`-iPJ9VUMTq5T@90 zQSz$MsAGFA0b(up{%bDuW9YBghmzG;+holWH-!2;;4U!}lkSkItSU{7n6iMua=y|g zsAnkJY!j4RGiQdIL)^g?~JD_3*;ME5?1Ux!;CsVx6ZYSNr6O5FLsy zWfy4cZj;XY)%6lBEc*c9E9dTsKdVnuQE83H;llUxo^>NNHkbN7U1*FJ(9*?&u#Z_8 zf(zpA2p?UZ%JZ(y0`=t;LH z5m-?eoHf%nn;qcn3r$Q9TJct8_!VcD1x{IWXUgl`)|8?$?DkIU_zpK!7F#_cbFfk#dUt6GwaaE9S~1qMF-QhXgY`>^ACym z{CCPo?}+PyHKddv2M@p*3{tAAD8s#7ONS)_JaMn5TTUV7`TPXt`2o~1lc+sj_m8187-M~|-b@*(3>!O_F z|1X&!Xg1%H-*sz!OD_#NL8$JTIcutQ>;1F&DRrLT5d*dY-+*_ZTgUm46jjie3q|81OmfFxH`%lQ7sI3AWuQg6&$aQpQbff|QNWuVY^S=HFEi)E8l-FD}DWvHt)9R&Y;F zMtkm~vY?}g-b+eb*w1BJH}KE;#xQ3)3JeD_&3c^|V@gkF{WM$mE~(sd>F!aA#4;l> zOlZuj-|=+nEIc;gJU{&DtU2^s=wNh#!|H~6WFhXWyQklyx2_)Z7D{%=kFSX-J-;4! zqY_}g6lH+mChp=&ruLqB*9-E8oNTK1Rb4^>hx9nt&4{1o5WDuLf7O&Rdf}@XO+(Z1 zzyTEJ3Vvs7#Q4QpxY*Nl5Fe%b%O{yD5X-T6FUbcbdBOVD=bfPT?A;^UEib_pF;%U2 zz=A>tKO*L=BKEPZ(uzW+?+yLjrNmR_Pq$2u7Sd-IM2zuRr55tunvZ+hb*>xSpuz^% zDyi*cQ+4MaCy2Aa_N+jHF5DquykZA*R5(0Uk$$?gnJD`(R1=YEi{y<>1)oYrs@)jW ztIL@2dx3qnF^ous8ezb$Ty}8e)L*$%%#OlIDf?|-7mmug^5X{0yyeGTxm^j$KjFw8 zQ%(ZvRi!hFS@M{4WQm@VcZUwSR3%nmn|rSI$$+`!7H zBc7o5WYLDTy{7&9HY8|hc`$}XMeN3@U@wba;&g%$GUELnUO|lx&7H|n?|UBQ6%-c0 zHij0X5$%1C6$0y^DowHa3sd;f{V%mwU;8m3Nj~5oA*MDiEmq2X`)xz5b-tRh@Vg|i zWN(o60SPB(^*TEpWq&&XsY#!-_j$&nQ1PE~u?N$;Ad1-QsIVPxzpsCXd#zX7TQGc( zEXtU~?aQ%1aNk~FhI(4yS0v6Y)Z_*v{3 z`Pp#|3KMRD>eQw?^Lj^;9UCai(8<^M9>Y^JA1Z~YSCYsOAIL3iA?pfU;^SJ8J516~ ziSzIUL)s?%5NYND9p~(rkif}lM++gzjZY{8?xFT> zg8YKBAwi^LhXkjsqlfv`%vf`~<;6wA^F_ggg&ca`+O@LM;M`klym_4NfT43qm~{4& zuDsmjc8e(`Qga;_pLPdXH;&_LxosQ!WLCwg@L8O0g>lp2VEL`P$MRDted%*4y=HRb8;puP4dl8ybB~+w7=ZWTik1zCvB(G@qhx7Ov_s;2Ll}X7wh6k&bY$6wdZWfxaJv8dFd-15d|`$Wby$Z z@v4XL03GRaRdC_!DT7T=qz{eTBbLz-T*Z78z5rbCflny3DNd4g^q(++ves8#J7T3hlpWC@7c|6CPIDfFd zxBUZ4*Puji?j_h$q$bL`&AUc!(Cb9*SiiISS!kX0an9s}W!}pNOUnv`8wf{n-`FjP zdbk49{5RxPLU2r8n7CTV^R}m! z0OJg!vE%a>Lqf17@4Z+a^#$Nb`%Mt7$`vT&o}wdXpHzqqN3f2y@q!b(Cj#BtV~Y{& z+-2}q+>m6{B=@_HyK$ZK3rBvd4`zrrW|#`%Y)-+nC}@T97@`EbjgaXmu6sg4{Q|PS z;+S2W_0~%FHbU)eG7k1zokm?ssJ$Gxk(ysKj(U~2_UXY?17Xy8mIZz!JUs;ga^89e zv&+-XnX=Ca`n;+@phR$fUc3)@@K&N?2Xj-JhiZ)Rukqy|-=gTZ1xh?xdbj|hgh`t2 zt~nkR*@!j9msLVo?#V__8O-hRzeNQ8q(dl9XUO2OWHN#8>6Ze@TnR!Hy(P=c1#Q?h z6UWCB>qrJ`ejV7tN^uWC%kW%u$P-pkZ3B_KZ1m5|)x_aO9vunqh%lCOVWz%~w7O0= zfBj6a*%w%%LL@CqfIb*-#d8~x2E8Q~O0uZGAVKYjQG3RYRC*I45u${S=@~Hyk;%S1 z@J))4AJK@okK5U8o-L6OeLWps1)TlzX}NEya3hH=(G*fW-Q(0D3DE7_!&9Zj+r+7% zrZzKTZ@Vz4F%Q-O9!<8TG!B}!zQc$77gk56A*)y1^|<q{(>fZoi-cEDHQA$o)f)9H%%h!Ya1wB z|4ec6y$@TF^7f18mq_xV-w6m;hN*;Q{fz6MIh~zwIqwrT@hJWxy1Al*`Y1v1=L+cW za7hY*L$vWhF4EXz{gtG%Oo{u(Ic=v*UG6ZT6gHQFF9=?4Vo7w$@h-?7zrk>P{FJuG z;l_t{(4xqmXFgjPlmv+kp4x`M-vLO3&GZ@;1k6Z{Hr4qZ!R1wr*~(F z8#mk=u$H1H9AcAfEdCnz8fCO1?@LnWPD*s}oY6x9*Kt|_(|67!xNZ*+=Jb+z>fGhE z9guT2D$N57d!b@A{}vDm9$SUW_Tm=E>wZIihHp3M|F}0|Vi+ZHB(*|A!W-b;moI;v zSCozU=RIr-{3G0cFTUYnl#5Vb_2QkUvY#XLChf zLu((7g-rihq)@M+h%(1EL0Fzzfh(~VziY_%BsoQtJA1=@eNx|C;NG0TysBvQsT~?c z5@+@WHFm!I^W@v>{WaM?m$75Bq$}?Ml#4_8KeQ#~7r4bMUf|S4GtSlT#iYXoXV{;$ z+xp#RV9CA7Rz<@Zbc(Eqyb$&?7@7V_6bz$k>kM04l-&UcT++N-eEz)2UbA#o-n2T~ z=V;9!R+WB!z*J4DHI#%nWp^%>bLXZGd1=;C1n}^&-JptnVkFT5x9^ z@c%y)U5P)_{~uQ>N*Wc)wMwX5$rWb%N+n54ikwR-$70Df%vLGnXx~y4Wl64>CCA*$ zx%!IboO`y(a&L|qJNkWo{s25apU?aCe!ZU8>pX9Fns0%7V@6>iL;beW_RyTdRNgQD ziKdH!-%LOy2@`ad30y~Au05Ayl`OxQO1Qr4J8P24=C;RW3RRJHq!f%&exR|}&IseF z2DjGIL`ig9?7msOC6d%k(@}b^q_JHke+dN3cMgs_1^*U=wJ_Yh)(#q-5s8>8=MjX)IW<+1XK-`3w_P{JM8R zS7=5X0WIAjUE}G_j|z_opnRjWP24k9!qSfw2`xkvcP<)t}2n)fJNnAaJAI41P>6p=E^hw85lU zkimwY8>q^C!Acp6k(l+MUHDIX%#5~=V%)jZ7B%Gi%dX zwR^b6E9DNDs=ckUz7#$6Zlh7}!$5;wAu=kX?rwa(*G%REj)mM`DT5L$m1PRsDY8R; z!q^G5C7GZ_{{!7JK^0{fH?DH+XYU|Y!sDqvh;bllnunt2pW~YF(*;JnTjL^KCSs3w z{;xNlfh$fhV-BUtkq5S|72t|+EoQ>Ag1jq~%0(aBZpA(bn`DNVWGuL_ zz;c4*Q6{HjT-WHQzf0rcYbzb+rKGEEn@Plb+U4n&@IpD~%IYuRhLuZHOJYWESJ``U zagm0(V@RL>dK00$)T8?S9kVLQ+XBKu7&JtuCUG5I;t~127-?05QuQPpd-ry}U46NM- zLJY!r5xb4Mkp9uaVo2#V#z6>plBOZDq*u~ZeZ(afA5;B@f){r#PeBwH5LuK;{QO&z zNbSp4UNv5!zf!{^R3WQt8y<8IK5IEAh{HS{>AO;eo?kzID`Dt3Btif5rr&;i;Py71 z|1P+7>Qxq;mfpOUnt!?PQe9=7xn)FjLD;nYn_kD%V^sCXgIdKO^}Ns8^P_6cXIH%W z0#mFf`LLels@~7p$v~DX9*Qm4cy5gMqZit2_I*CxJAKE*<{RQGe)XG7=lj~(XsO%k zlJKDdo&M~|q#e$DK+-(=;>E`5f{WXNN@|n5=$^RHXhC!b+g02mQm!rsut}dPt{xxE zte z>OhGe&!|xG?7j7(a@g!i##7`XbdQ~qd>N94nsRgFNEyzx7I2eZr98=V%RR&={bfbLLQ!9~8@ zqxX_$)Mj8VuRKk=!Ht{WVJytHX?WqKoKHk&x96x!I=Dt@=U>@xichgdbsA_?kLu#Q znV!HVGp<5?-IHh+#ta9~D}A#jcDdeTh<}KQh>8%8ZyVinf=Ix_0_VUxJ3r3!sa@T# z=!eNO-XfaI;epyUJe})Ooaha5+XpBwre?{zL$f#-duT2be}FL)nRCDxeQU8(tT_SR z2!Os`hA+$rQE1>?#oyuPq!f91q7$!_KQ}nSP(Sl+5A2jl-}86$j=xe{O|>bNnQZL= z_H5QbchTkC(Ddn24RiMCIog-g8Yk!rM%<2xK6Yjz=D}d!4O^0jT4(lK86)j42JiTj zTNY9`>G_ftf=alPeH%H2G%Dw4a{t_uBIDP(kcTn&m5HX+Uu&>u_)pmcxmh>ku$1Tz zmMiCCL9J>)dWY_>h48q5A1R2n^?mDr75f?yc9*a_Kmz$9XlCM)TJ&DP-sAFdoxT!W zeq>IU zBCPyB(S8BG7&47 zU>D2ICZ+o5@_rYT=LM~7r2FIb01I0)f$IVquK1>DPGEwcLJvB`eH;Z>cm^4t*py3PD{dnV|F ziwgmwV?=Y1?##34x4JUTSNidA*BWd%uN4Lgmq)c>b$S~7f=fIvt=K~|kAZ7RguhaO zh{TblZ;XRpv)BXu&-x(tuE;5z{(9mN28;Jb73}=CAfx_+Z~xlO$P*;f6z!4E!9ovj31dyjLQBi^`fM+VfzP> zT6P=Y83=|h&(d9LS6J1*rTH7N{yv;;xiOmCC z#1xhyj^P=lO(@g}3c0lN;txdsml}R;G*ic75Uc<2Eb<3rOK^#Re7=7by#JH3RlVc% zu#*BUD^3rq$TaYn4_u*1y@#BG8Go?8(}B9^K53T z=3(x8;=G%B+XsKi$aD6r-p|TdL^7-b+o<^xb21aEQ002+qgV9HVf)m#*)a6Jn#tRn zF<>N@oVE%}Qa#4gc^ZYp_@FYT$G;vH*=r6Aw8xAXOm|sy>@c!;Ci<`EvE-oIf^Hk) z9{WJojXuLl+P*Uw`-q~>L#cnIisH@8rt>EC9JT2Hjv!ZC1L!?6R>XOY-oNs=)46wm z!Fn%R$4F;%Jt1~99xrLU(SHwOQT9h=Z&~*3eav)WL)nj(%UP!8qZi(M z4fv?A1+OIr+eC)W(F$iS7r^rwpPb*LElCXoOPH1l2^XKwepN4PpRc`r3ar}(M z@7{5w0i~*Es>Dnx`|?18je5e!-N-k=*$*ci!uSNg*wQdrul@4K3hpx zmAHol8Ik#%;Un(c`AX@Jwl|Yuw%*z~uk(kas|Q{GZBxJV^4Mcm&P?#VhrZ>E-jX2I z*HurTnCxM=!iKcLwKciX?78r)zAMj<^@(p$7zUC7ZAnS2bm-RV0mN%i*EJNx12SH1 zz5rQ_Y^=v^cd6s;s15Wx`gJ(Jy%kriNEs`wXQ+<4zd2uJ)8YVuH40C5g{@00dKUE? z-n+RB=mYHNm#?WPIDbZm9-;Tt+w%hN>E~f_%S(rqeQP8$fcHT$jS`@;HN38}>b$Fs z;x6JCCL^=*8to}V5rQhg9It5L8KkcGcv-wXodJMxb!$XqTI_X4ub!2C9_&;~Y$GRW<^)rR=aPBk z$08?Y^hX{wp~2C-78PXdS4hJ>@-ob06ZKfGrQz?-lAnY;+B+5vC^Ok!?S;uihz;wJ z&TAf6h+UkM+(7Wx#d=0>w@Y+EeZq(ZVBpQc@+@=|*e* zMPo{GzDxAVg#A4y3gL(JeHtl0&RO^XqumDy6`BaZuAU~o#ew8LtM=#R#q3psO}BW- zPwXk^p*I^@+Qj#zbf{Owg^ky5w!B1dhDnsxIq^*=1OQ;Xs|=oaV^-QBQKIei{4I1~ zp0EP~XHn87KxE#YjF1JVwgbQe#4}>)hj}WKhz{^DuHBJ`lI+t;NmQ;ZL@$d&5%QezQq>Kc}NtvM)PD|%Go}w#SCR{NK?<0 zY}$+aC*zC}hM0>2gwXvDVK4rfzL62^i@fCCZ6iaO{8(@sX(*2swXh|cIqphtTbIwz zMq|h8uYFI%OKQtL_c37)C0heyaIF^zR~kmQRM!e2693n<{6LY3I16^0uzSLbG3#cb z67!M9Csd5Jf1m#9C)i*&>ah>wTfuuO@<+xt%=x`T*u@)haV030TM{Jzm&#BGZ`5O^ zj0QwLFxs>W!2jMrxcKBTX4HuL?nhF8Lnu#F{z zIy!NZ)H;3@F1RK+Ibayx1-E;1!H5HK@{Y_6D+tUoEBYle4l5OBG5&kZtdJB=kdM-# zQfNeTt3(mE`&=+9Hh0>Gh6TM|401AQ&5<53i|7x>D|MR{A9`$b1dwp`6S!rbZHP+1 z|N7WR8?hDRRMW`Y{*%5HnNqUX@UpcX&EpTmkq1nnzqD)PWSr})-dEvADupJX&0?yk zyiA(u-v-}{9iM<|n)3i5#K#~XRkHGQ=XdC}Y7AjKLcDg4msCY<^>%7-&iDA^(dPP~ z)$h}A;MCchz)bj^GDE~r3Ilz#>$z=U1&CIWoRF_$-DU2au6{#|^mpnJ*+hIiSasl@ zY4C0M@)WLADe4AS@?lVU-2?l+FsN1RJ zCJ1YU*D4uVW_&Tcqv=F#A91nfvHZ!11Y#gox5vMd%;h`^*XcK{vCPJ=rW=XYl!&|o zYK)x-L1^Xg+S-OnWxhf?G$e+eE^y&rul_10YE6NXRAyyytUcP7{7qxIbPStM57r47S} z+2)f|s|P06=6(%C-wW?NMz}yc?k%^^&a`d-LAE z3bKnfk{TikbWy93!aLW__02s+)8U34AN%A7UfR97blne_l~c={LtGrH+8&<#D|Kl; zTC#u(Ur2nhEiAR@Pi!ybe(Dy8i3GRAADIy6#>z6Jl7Z=J?XlGk`k02ul+qm0P_CE;-J?(cJyx z4ifaABt|-`JqwjMqF%7n%6HvN@6ufiuhyCpfiAKohi0tc&-M3qQo$gLc78*vKlnJx zh@_PJVQ!Q2q>}cMsVdf62foMfJu*ttFu#ISVX1rQCi(#w z)e{&yuu)55JJ?+Do8UoY(^Le7z;IQ?Z6Zv18VN22CvKht^nDpQYg9DdhpUnB1_Ni) zw&LyvRp;7>aS`05 z7RHdp*4CdEa}VGn^=^QBrV6&eW74G1&ZcQ@?I*HU5%uKs1@KArRgL-;Adh5~2TL;Z zj=Gk~0w=AVE~~PMnZDm6hcgL~lDZs5{^g%zGl%apeSdz6792(N&ikjHbEM}V(J9Jc zQ;PUm=}oJ;Ik&n*kz<26`Q->J_}kXYaVKNKB(N)z5rXn+9EfTDnmN@tj*Of7w9jkL ze5B-E`n=v0ZY#P;Zl@Vc1FdOc@oOxu(4)NRp8VTek*iZ-8(5iDU4Af~*mMZ3eC%Gt zh5v#gp-8gd;XiSL>Bf}iEVs6$Cle}Pd$3^0;52d)wRkgL)5``qA_77e+SVOD2N4cn zRL#U{mjK-!E;N{MssXSzRN?1`g%^YB3Li_1MW(?=4oC6hWL#gQ7WBkzxS)=PRt)+T>2lI(I zQ&NAcQIEPlp(cm}&J02j&!w;)^0s%C?+Dgu;Uoc>qo#xk-Gx0^3z3=Cvn7u%d$p<)TmY{s6GGSsQfOKymclj$WKN{9dkQW^iCju!XBF`p4l4{&t zjIGq^m3)2?E)elli%J14U{B7PB-Xv#2~XdFQ(Zww3GnR%`3CDKGPSzb-J7?)2^Oo; zOK!0D<+k4F#Z`UaT@1=v8#{8oT$w|Rc6*31D4vBW;HpKAQDOOjk_WrYuqpZSe?iVL zsdc1-%)7YWo*_BfU#0^nzW++mio3!LxGQb|Av!H&btR;S1?P_ad0{qhf-Bj2=(Koy zxMc4j6pXpb#TNPt4Fn#GgUj@53COvZxVR)7CoO_}5}nSXwk=%2;0B1{MgqG9ruj4y zr?+r&x=Hz0xQ!bVzJO!VsxbET!Fo67=cZrhkB##7AG#@gx5kHg{8??3Zi|7|7R^j( zao>?oo~`kX*`sAymeQPKB!E6$4?aYjUGT-W2G3TBA=I8H&s}zu35cq0;LwyM44_oIFeIJ`7RSd8)c)3|& zp~=nGv0c9biN|#(m+ST1!X$`xXzuJFd55BZc%JIRwF5^L`~INIdV9|$UoyiFTh6($ zlUrEhW;^;{E>FYOPDZ9|2&0cgB8;3pEb&ob2 z?<7d%4vx^y_vrL;`VM#-wD}v|KW`7E9>t|$(X%<4<$V~t(1IX9TwMKurS``7B%gZ4 zQjD~^X7#kEBJSd%_UKCVeIEWwTLh9=~;yABIA6$0yQY_=lDJ9&zbLso?A-K zxmC_7H4GHMt2-kiwzQLnn~i@yu#$MG$A)J0^i~#9x=TtOgH_c*rzXKV`m5g$ib_tf z?aJ#1n!YvZ3Xj6(j=XCf{h}qK1fi?mPSStrRC-u5Ocpl?=D3zZjx)huTBqk?c&Ou1 ziUm73n)Mr-^mRFt8Q#y2p80Oszt3sR_+7a7;^UK$(G{$jw4>hsTH#mRZaoohqI7ib zf;RqpERu3KJGKsZSgvvN9lgZY-A{mbw_rTTW|*r zItAs6xB5-H&i`nChxKkdk7)QSHBlp3$fwBq-rN`I{@8@obu1zv5^pbi}h=tAM5r&b$?+BVp;EzSI9hk%J$be z6@JGRhR=`Mwa;WxdGh!+kSh8kLNTy!Xp~xb_qgwun*a9E3B9#)<1pq#t_C$N0;V~a zK?fjE-rb0;#%oIP!INSWuOvabEw8aZ^VG@~A@;WDb3P?aWX4^yPmovyE}K8q zPdDwvE)n&_P{AnkjiHR-zq9nd3)|J{q zOQ{Q|Y+Jw@VSEJE$Vk)EnEZjZt;cU?X%d(|GeRTmFM=#Ckd$y$Kxvy$ZR{)4&ueek zdp->B>w!FHgxE;Xy>cMyuup~QwaWo0`cY+3My2F>WwsKA zr{Rxo;gYQipBb1romg4V9PB&y)+rRMfI;ybsfbD&dOXB`7VZ2cT#VE*jq!857z=zaARk0KQ|%ru<$2#7$+9zb2?GZ#t}@x)%0_d2RY+ z+>iBDrIHVV$hfPMfxo0VFXA6Kv^T-7SOQuUyb-DqxRls%r?Gd2mrSoBh69MJT zAbu%NToUs8yjj46H{;yVx{!kUG0eYjYCVdr8I=V+WTH@avCKSf;IoAsTL`oGJ}`LM zSXCCfaBEgY0wXv|TKdDMqi<*L_5M1qcEZNdg*)ak#BJMQ8>Z2u6+jK*MBoy8JTfSMb#U+UfK& z?EJFtlf3@^eV7xxPa|vO4!GdrnH$4HnMoUzIg@TDOp3}vBhO=nUl-quJ;ifc4xqb1 zubn#bRMD~oWK=6YsSefJh-(Fh z03BLacP->#GCXK*4bY3y)?j8?ABSx3&<=r>V632m1mfmfW&z3z05ey6y24 z{>yOy_P!QM7fshL$%}jic}&JmMxY0&d1x`Dt#w}Evauv~P@eO==@@b@f;53SsqFSo z*p6SwPORRDkLU*X(xzKmHe&vm4%91;#x|Q$y+@J7-O;;YMubt#@4q6iIBh<9Mn!yc z1KwJdy=cED3*VOJa*dn$5MmqUC+#g8Wv8HbKlZ6cH~Pl`u{@?c3Q5A<`4Rdl$W6K2 zfd0lXM%J)=)|$O>)lBkvd%??)g)>vnT1N2`Etl_KkcQw_2A{h1OXwm0sZJrrs|!DgPW8Dt_b#yfG?xw=TAY7a zeu%@cBpCwM(`#9@w&?KjYYu$1Eg`q%;$KD|jr1DSi1zUcMF0%FC&2aVpXgdQkl!*(u?jJD@o&tJ9$Wlw=Og7M3GBcA|?C>o=ej1s^rXACGz zrBfR#=k1qdX++GLpYRO!Kkxyl{w#P4^r+~h2|A(-+Ep;y7qr76H*>Jjvvp8YLHxor zII0CEyQalDW(4dv;9J0EKP7JRyil!hJXTfwHT*rC34V@Il6m@3_#-g5_cTtG4}L;W z!f0?$dWsALcP7*9+8B4@J8R;gjJE(&DbFLbLehK#S>Rzd(-`kfK3Lm3;yi5+`gM)4 z1JGEF&{(%K=ymib1_sK*4fPvoZ^4;nGhk)OL9|IL-$K)v$H3<{k^5o4|GRKrZD!6V zlnuv&E0*T-KF@%6i!6^1?8yU-z`hbUUhSG~h*fD7`!zp6Q#%NXe%wJqnG;Clu;>e} zhY~o+%S)qdW>rQpH`|W4^k^ZW5XH0^qZvlgtGXA0)k-)VB9XFJ1vY z8dvxu=GKLfL(3}%CD&0bndc%)mfRfSp|ZrJs<%omcT3BC@s3y~RKSz<5*@hbCv{mP*4v@;K z8Z{PDorAeX4oKu-|8%t^GYwj5+H|e+P|g{g^W-{Jbud%(6u*6L(f~k>_}_we5j0ga z;FPi)<{vu@bg?+j>j(I&$m4E76JRJQn`zKg*p5$%EP{Q^wHiPB#Uyo&qu~-e@(Y z;O&~_wBMc2!3Ug&*dtY@d?0RN#?G0iE%@3_Ub2nbz={SC3PD(lDy|Tssh`HF#fz2Q z_oed7OqyK@it{23eZ;;sN2FZEgNL*CsR{qafquKYk)tLKexcAYT5Lc(g|AVX-ey@E zi%VuGSK(zxhg&B{t*cKhQMFU9{4hi6u}xp3vI@!`z@Vice= z+Y*l2MP<`816 zB~fx%(q&amjGe>{Gp z6&{Scg~p=x&198*KJp673jHgk54>>L>*zI3o3%W+^`+=SCRN3CAyt2!e(`7#W}j}td00Y16JcppDOHl$bv^VJGgfO z{@rq&(?^8zSKQoHug4sUflW>K=7`N6Go4bAP4Grz(ne$`&YIeuH!D>lw zup0c@d7Z3SqEDQivyL^#Z%YW|3;c4-{m|<>(h7b=qpWJLWC+tbCZ24@gzspaV^UKZ zfVhFk&i3-s67$TETd4=vl@1)7p<4qa+*lITlkIUh&pz) zwg!?uE&ftk8Rv2l*aHFq70$#&Hr_s*c$I3rn+?kk5bfe|+F(zc^aoABNwXuI$4(8s zp4y^`TVtCsbP`5dP!h&dajM7jT@xlcFZX@v`?H7}h?msR<#;PB!0Pjqpmca(btx%o zUDa@&#^go3`N$_BdmvX4?ho7rH!u0#o%9>JYsQ|8-anVz&X+)k)G?)LbWso5d8$c#>a=alPYac8C83+E}fu zb#N*J1nAX0Nr`k)0lIte!0)f8&gd`Xt=xgR_?QSmfSNKMC{8x(3tO0CxH~z6zc@h^ zo|)VWgSJiA&4Gt?n-R(wcm%9{_|UI4$5{NSj%B@*g#cWhA3b z?eG%hPrO1G6hkT`MpS3{Kg8%x9>9Wve6+~5)iu>Zqmfi;3rNf^fC+MIT|iATIsgOg zHgqQIMxQSk7D!I$0F68Brp#7&L}s;*J^5h^z*u+7v>q#tHJlsDlFH$=!iIGg?V0_6-dBaj&;)$I?TX8xFE{o zIzDBRh;{5dPIYZS}AM5xQH}JzJH<&0OuyoQs z4+X{KS`iYbx;_;OHaH@oTVNqNsttOAn@>rZAE66x^@3N~76?C2x2(l%T=bpKf2D4D zdU?viSV;MX{;UW|X0dihUcsz|t9b!#>>u*}@i4o7GdbA&g7w8d(+jp>Um-|Tl`Wbs zCz%GrFIGx^4n!O)rxJ__#M@4&Q(^-Ld zz_S#4p~0R@@>Z#H&Ivww2}%##J+!P>&m|jOFXtu`e#JFFG~&u^(zt>j%-fD1cW6tZxQVzsLlW`e!N5KZ-&c=Ivb#(dq} zBVy_8ZmxU!G@e&xg&=N-ql71hfOL%^NPyypZ5K zee%zq#m|=N_=`MPYL;vzkN7-2U}pcbTZf+0PqRa*Aq1vZ+VjGq_K%|h%2B+ym&do2 z*pTA6KM|ylPo%8 z$er<0r<*&}v>yGU8)}CYv_63S&ffaZ>F?^dUIse8Q2QotKa zp&2902i4SeN#@7RFEE{)SMC8E^X*eCo)0yPyj82%)IoftTBv-)H8i zxA$UUVVPc);5gT)8K|{uu>>j6X+bAaTtzhcH{U3!tqG7u9^$IBPH{$t0EVH;2Fs>e89ce_9(a-wor9t%QPFdNhz7>{D>WIFD2nBFwvnyw=A+79 zTlo+|*KI6Gj)Hih1Lms0g(v`jBkXN}nl-xIDDR@b{iah2_+(2XP{1GFk1+;parmd1 zo~XUq8vrSLwsJI5%qMUKaD z7&I^pDVczhSc>iI~igzLxGNyfUz07(#jAe-bo7U6K!{wp#h5YZN|P zZM+afwp+j3g2m=U1Tca;yx#x`+!(dIz%2h4?|s;njkwJ9B0IUZasNC=3qak)TPaQ*TKq~N$pO3id>>=k+MYy#I*)c$o=7P-X)}d zTxs2W+D_(IVsD#x#eLKBW3IaTsPT@jpt~iL9;axMbA8~s=@v^bD%J~-UA%~nDlsX!Ub6L}F)VFq24_euW%^*=mw^YU9wQg9)h zpPl=_QfN7KH)9>nZR6AB!#+DAfse6BW@hF_;`w0nUx7bZ+iO>F(I6OSM~U~4yTd1+ z8@Lw88EN4kd$)Cxek~Vh5tM_p+BFs5*l&9g_i^?cu4J9`4|g4}-*jk(F_Mvm+X>2! z_Sa?DdOna1d2$~J9;F?y404Vs>jA`xBQj5yc~V8KRVn-a}%Zr$koWI z?}66GdHWO5;OlYxGnA~@?KV7{5! zeQUb)X80oXW{^bBE)U5sIu<-br82O=AT#AV(5|;sK z?o~?dt2Z(_;dPnfdywn6{g^PpIt#Za0H$!SvbOB$&xz}j7e{Vc+G~#C*5>-0)>9*J z%A-M6k5sX5!}6zNkKr?Jnf;uYWpKafS0NHL)wwHyi5FMA5N~V#6#}+&{kqInlH5#8 zNFvKvTou6wpF$s&nu?)ol?vD`jJ;9bL1__ye>z^Qv*&H%L6U_ycL@{BQ(^Y2qM8ZM zJPKYPihrB27XnbM$s7obx6fPP$ZcX=5C~ZYr)H-{?e<4C-gy-(K2ew^(ZQS&;Fyj` z18}=GrD~6F%|rHoB3MVmIvdrY$Yr!-`Q#QN9Egkqu%30eFxSWWvt-;n%ivc(1jigd z)iYd<3REARdbLK?-3ars1gMjdl{Sg#V>S>S7C+hh@7Ud)M*k9jqE9cHjP^Ek!qeNK z2K6=c2%uFMAMd_xH0}KzQY|C7!L~PEn4Ve|v5pjXYLJ6M~Ov)Q@YM)OIehK4#k#8@HJYVG07_8>ChI(|WXT;ns zI$|N05+4d3NZk{b!#0a*^U-iRvcHNv=tt=)n0uM{6e7u6Q!-)P7zuKXAwK^%*g2$i zd*#$OXkqaty@#@^!6`58V(axj-9W|LhrDc|w$#SBbAX)&9Rybx(s_gog(iMJv+uea zXZ`^&pdeP3d`83Agkb=mC^aW2=26J~9uu_rBc7;@+g8(GT_$O*wA3b3pve!!Cu*8V zXw`B5IM~#8b}GcWc~4Syif#&gdHcM8J+`gw@#;r)A0oxmZtH?I(_(V}(BdG<{RVSJ?>GTo5 ziD%Z-SKJZ1V1blj3Y#dVJ;mL%xjfn$)8EMnsZ9yF1I$h6rE3lTcZ4T`z)FdaDFGT0 zw|Q^c?vJS#Wz=izYnSIheDO@ZQ!TN{3jDTziD{Wy(;&jm)-7n0!2a#;HfLAMJldP!%S;PHrDpX4sK?gY|DhqMflY4lx9S1)2_D zu3XEeCSlTO?+ro1N*i6!TNqgL9E^w=rbDE8bhcajxH04kFgdo^L`(`;bc0;fIMw?6 z0~tHvPsy1k6E39{rY_K8r!BQE?M2_^T+W%;T-FX;Jhj`#_y7D!KSb#hI`P}cuig-h z(Z{)o;I*T7_3qkn#_;k!8@|UCWQ+^E7# z|GqbP!J5|LgxINvIMq2QI3NnlO~@7)J^5xYEGMi*OkgbgAf6wGpVHY5tHOZP^7!65 zQsTtns(#41;82xE?St=NY=I|d#q2Z3a}Z-2frgkIFRv8OD}Xor7MyjHhT)nKj1T`A(r*bVY_D^{E$3*aK{$5$;%?K;-GCoTCF9LcOm*u(2F3Bk2{Nzpvpaz(?kW^$%<{0hBi^5!XbPXcN1 z)Kf9nea+u~3@9*?%|^ZjZEkSl#n*52REo`~O@JHr8P4tfn}@b(TiR%Jn!MUX{F&>(@t7 zb%?}m=J#zi1SB5;3RssN%srm&P|pMN8*9Z=#l={@smR2q0!SO$70dGM{57HXtCZE3 zF*;x1y|+_FlbTC9-vgU0)Rf~&-Xrr%v06aX${p%Rcx}PI>P`ZQ*#Jmz+^A)EEgP{n z$OkfLNU2N|IT5F_w~nBkoo5kbnWQ!NGrCpIGaje42OaoayWIHf(U;&hw;)&uDgMPJ zh*6&CVxJ#k{;w2N{tT10_>2nnSXoNOaj#MyX?@#VO1uY&=`2bIWb;n9ln>go(4?bW zgh|0s{lm@MgAJQw;_d%7wy-&CTQFpV@7HHZWK&hG#_C+ zv}g%S-Fty%*IOG5=XVOSBPK(+9EXHnPe^*B`c9dRuIMSme&7}Yay{u}6{~NRPp0Au zX3pD_T&S>o`~6ln(;p^|Q3gZytjQHV3u$Ou+ps@#K`STLwJGz%=orH&#j|zcXmy#d z3*q16Q@p;SvPbh}W;3WV)T;CLI(tddEz9qc*D-!d8QD@_f59*^wjK{GMmX)fm%E=I zQKbJ1EEHBi@+<0lDgz5L%ubIEU*J3!J=$=4;x$v~RC|o?Oc|@X^Ev;t~yiaHm?J5kzrmvAu_Lb8Xp4()GLY_Ykx@7S3U!o3}LupS~N;QPowKu_IVsN&uDR*?qt0L$Hd%4x(R6-L{oLtKDvy zinz}za_e(n1QC{nK>z`=$G6~L;1As@+q>~C0+>S~bS*-eqJo{r${_e*Y9kwwiUb=$OmDTz%9=; zkxiY&g@ujb9y0S(p&mwiu(S%E2$Y0{HG`XHptOR(m{d*QY)S0(S{4489+Hs+nB30{ zE@B)%G*2^V_jev$>53u%Ep#wE5nO2>9|Cv~c6g^bUcTawObHdz^qP=K;tH(REcPT< zGo4G9C~Cm$ynWaflsifGM{^iNzZ;U~uyINEVABr4Ggf@AZD0hy{%t$|8<$N985qQ9 z3rIqBBvuCp@|aOA_zKZL{kAQtI~#mkVKbbB7Qlqp+_Alyy9YP|Pv+ZTLsQ|Ws<&kw zs%I}3Kq5#g0GXL8GUdS%g2R_mB?sjNtbt(Y;zR0V8*; zy64EPl};PVspJ(>B0&y%0^4)END#&zBz+$7@R&834ITHwk9!_S1md}HTQEj_s|4x) z|9U1W^>|reu9nhlvc-btMqEC-mnWe1-Z+{_L-WzW)`98mnjR-Y~Z1o7pejwgs2=kylv()yu?kA#dlx=Jj!K8*F&9Ncuu;ZF$nNKg~3- zcQz*N=|GR72MaeQ&UcA&39-QbPLf@kb?1RIYsSE^mohFmiZTRtlpp3P;7eQ9%pYaP~eW(4_x8W`t zHC+Gf1m@rA{^;izDqI0=PKbllNHPT^=8wvyJ+oKUh=CPSjhcuI6L;X;Lk|XJbx^iS zD=dTO`{Iyx{7QizC?0cI4e{!wXT>yIl*|Pfm;l5KGIZzMvRl1KNq{lbGUV*hnercL zWWnN}oX-;;&5=10hUXwMak~~g-3KE|iuZ4k|CSmmI&2HZn+*qkIh#?_2q>MrAcSK$ z4J$UVS6~z_ej9KYjdk#o&c8jYY9J~3*o-U~e-m;D>QQYHFFAL0utC3iL7i?PTzGPN zWc>GJyucME`X5Kv9?$gt|8=^k6RlFNu}bNjgoMat>n4@UlCl_EB{7R7$uL$a_e*jR zVo6BMlKYtDx=IXlYc6xknoHYUW_IiM`TqXopY5^F_I|&f&)4&!%cXr;O8deX`%EWa z?i$d2$@0$D^;5rt%}ESXAXt*7j6_Q)@fe`ScHi{gUNk2apxIQOsinP$>O>r;hoA3>kWUU;1RRrL9fg6F&^Bx%yD&;Lzd zK-I?~g@EY&)m3>~gA>?~<8A&h_EL%8SrzfZVgX3kpK=6~;P@58nD^Dd@k-<_tJVA%3+e@v6!fE2d7 zHdfE=@8uqa0@~@L>1Z6bZMSboq;G0d_eHgMD>8~vWP-Yc-yT)&ms@wIKoCzL^yxQV_-VQxng$$))Kth zH0mlWnS||#>}GGRl9>pf@oi{vJ}Sh6-P_BwP)J+ghOZ>hK~GWYWgL-Li4?3V{1~}@ zZ*YcaPHbH%?U9emb!5cVd7=W)Z+I9RRES>F@&5T=Ovx10XcE*;i=owIMtf6%#1ris z&8BScs+u|rlVIVZt27*N)xm_%`Cs_%EDw=c6<1oSy}fJLFhv&JeiN@FOKdzPF_xZ< zn!={6VsboPSra1&wEjPJ%eUz$Q@EYPJrYo(Zw8XiGde%ZcZxP+!vjfp)-#?V+hpV$ zN3U2w0I~&_h2g+DZq+CXyvd+y5q3SQi+FioXw;nLS|MO4^Tp)=c)P9rqoB6gQ)l)D zOuU&v>>_)tGPtgI-3ctKcv`L9Ra7tNn~N}N&Nzti%Kx|RF1UD=#8#DFmJw-pauF?r zO^qu2M89lo!hwu6mqF6>*V+t2li!5&aP~sG#2QDKP)#~S+aCPBJRn$4ZHv`w>_w^t zi3~82J4{jWY``SGxi&WQ`penTijT<(Pc=?YkJF&?9dmLEeoUOdDMLlnAaOv$!S&4F zAXp1E#r+xnYQN#L?CVWYA+I7C&9PmJJddG{lznrkN~0&`!A3f`kn@AqFkzG`G59Va z4AK~Z`7ZUAxXiP6`J2L?MjrVXp=d8~iaUnX9UwJ-zb}>@1#NN%$_wFfD~_0nn1o=@ zke2b?pMdJ?`e!#gU{G0@h$CE1_?)*#R%V9Xi}T*x-p>Akf5w2Ej!mW4T#{nqMQ#E; z_S8dju~R{jW06zEHr>#kjsNKGPt?H-Sey9}0OZ=Z+jsmZJ9HD9>ZX{5jNy;CR11Em z1sCH|rZ%}!5;kam^o0%%*#+O40L9b_#Q}xW*JQu`C|m^lKskMJ){c_|CER1*Fds4g z#10%_5f-}^F0R(pe;CnragrD&Th!S=omQ`c_vW6Ea`V)gk)sdxjIMkxLSlxHT0>ps zCB9gBzjZhCXXZ~wYQ4lJuC|XMb{sl0KVIS|wu`nlDFE1#ZHM#gA(8$Q)`5Bd{dmY$ zVzE@ezWk%`_0^f17iDAcjLwelH6vBDPb13u-+ea+hR9VT?tT{)x6;4Y)e>KTpy_Ts zjabEaH~(M*tA#by*3R_IIv@+4l;Cd;%S2cv4cC}!aEXHrTvO@}$>i!lmcS^t1kf2u zd;#JT4mi08>0VMz%}2Qg-NmxEhDzr9oD{{ZWg;tK3ZNte#~|98PmWd+KCFFUZ+gGQ+vGOmt`lnOsPXdby#zaX8c}Wa5%~5%6AoR z&s_^xSoI`qv#7vL@R*W^zys#I9Z_rB7c4soF?m zB>Nl-zl_SV%`L{hbVD9TqLQvr2ks0Mn(wx6Ov||W<7+YcBX_OXGP+oJtCkm$l?*FV zygbGSWVxDkPw@7_CnZt7kA|l;wBxv6z`1Ae8h#f-Ja| z328^q{FXRo1SLtmpqz1{?L$r5v?EWdwNGE%8oKN6>7pH&*Yci}g>iVsNE5SiglYDR zy(9R#C;~$Ce?JF;3aLZ9R=58o#vAj(XC{0Eh!n7-#8wRo9N%3q zIE%m#|H%U)%X{(ad`<^6hW58gEAmu~e7E*xz2%n{THW(2^B#nD9#CN~4V?}WgwUZH z4{f&0v~CLGs?p}{=H18li#ARh#g3osTc!PV0z5JcWJj5Ss|2kk)?s!YyN2s*x0JvCCa~S+-6)jHRl%i(Nw+*|!oI(Zqu+93ZqE!?0fJV*q8!)rF|H^P1WKj>g&>emuJ7FhlW=!MM z#2!~{|0ukjFBAm@DGqxJoS$?(i--0))V;KmV&3-#M3(hp;0iNDaqzSE6OS+yc-)0edID4n8Gi4EAKtY_U`}P zK1y3+n`>{eDb?(~F~;K7LiS}qKXo5QPbd}!A|d>~R`ReY^FqxFqnyw5FzyPKa8+Yrr zQEXgG#Mw2gJ!|Brh64uUf93+DH|+_lX;_vM40s)SMm*6)gM+_d>1z-+hZ=F* zS|#*Jtp8TJqIaaK@8GAAALlqmzm+^xJlJtk+{+&fp91M@|3*u)^WtiGMtPQ+5MkqR zO7{KSGHF}nE?nld+9yq401{=1g}vueEV37DzXmwzJ1;tPuylVa^=HzQ@pc4EO=~N^ zx((lT3{01s1znU8vE@~~MT5&S|8#nmsbjxuI)FE+dL4b^o6}B#=V9V+!@Z5Fa&TW` zuF3APOVHzY0zFle_(7e7D&!bw(JHDPc*zXp>Mh9boQRW`R_$A7ZDW(DG=jUE_2v(T zPcU!MVL2n)e7cQ$uBY?MQ;X_rZoL>C_3(q1!g&slwOyS{e&>C5rrBU2f z|AUv*zAAI7EA}sTSUnVFSc-wva(>>uzPmQ-ZP!!O9yjESe!dh(s`<+^J6HiIOoAuWqFp=3daCEt;v48 z=cX`__V!Cb1+^@Pq$W|GnquPeFU4A&)e`)X`hkQLCB3r0mlh^3agu*%(=1m3%_w-U z;XV8r8w%_MBcKy3w3ktSfHi5f$!tVKXs(--ph7jY@nj-#47m$C8d4bl)u`Y zC2I)rS0V~{5lVj)sJc=yd>g-DLal=rttl4e2G=(5f3}J0DjO^wM5Zr7GOA-BIS zA}a-O;ch_)aB#7vy z@8WBUvRkX=T9;&{P%lutCW*1Q;R8-}j>WrBE9+{morSG?CsRW|p!QjZ8!c{_NQzvx z>-}w}4Qe@g#c^^dD^TC=q~d=BdJjASZzhN=6aEi;q82Tpx@zfY45g>q^C0Ikg=rl$ zS@N*fMwtQ(L#thSz3T1I z&A7YJqskY8Kfee|>udT))(~A_w1}S4*1FoLnt8Unf8lXrEAI=p11J&bGF*13=$&L* z>)zV4R(s^c<;Kva)d7mK!;jGY=d6uGLsLXYnPa(n;69G=(*7MfZyTfoM#WMBvSig$HkaoNPRvK`APma)_PUK#XJpBWCooE__wZjk zPWateC8(e}f^F`jJKpA$AGgdqMem7Bx28N_{2F!TBjni#EWxZfK?3ve#ZeT8-Xv?^L(d5m5^zW<2744KS@1)NyQy$yQ3RMMWZdfj>M75={!Rkr+SEhU7mMc zktsincmx+%)YnU3;@IiPny%G_OQ)>y z<8@rNSvM}`!hpQ1$o$##(uIr$oGuN|-{M5DtR(%4D=WJ>s?Iub|Iy4&g6wSOx!!`_ z#rQ2_y3^lpF_RyM|9!W*urr)c=s?}_rsQ$`{8QLuBG4xZh%bLs*__uk|F%!7LFxXE zZ8MT(_7K%zkBjL%ZlS>Vd%|(e6c$rg3!zS@4QAh)!q%9m8sKw$9)xx%!a9MY-E5M* zaAVuGquVwmok_WBQ^x>v;(K}whR3T?G-Q3L40SKMXfSc3OW*1_5&~eUc5hU*!GVXU z5l1A3c$ciI*s4-0?E6#0H#)0AVk=ljw3qCE-rW2Gb3u;%QG9CgD$eejpqo5Z`dl-; zy!iGIc%E^wl*Y8iOyxq|X+4HjI+5Lx46;FtNI2Dm4Xsvu;15I$4$ewh_u>=hIHGr zbMvw$<`Zu1ZJdnZK`&29uS+2E?|&4O@n&AdNYPlejV_Y9L@Fygz*#9TMhcdJ$mKGK z27W)^&x8BYBVl{Oj|GdR#ymxxfu&uR#jU6~3j2Ark&37FeZTQLYT)JX;-UOskx+qR zZ23<$q#lsCbZq>Z8nabGzOUci2u)+SN81~0!Kq_D99~w=%d`G6GyM)Z&?D~x z?}{{+B73CvUgSAvG{v?mhjhQCeX|IB+HLTKlZ`3)WlgTHa!)$44!>K=+reAe_JqENrGM%?EN${ck=2cC@l)y zn6Oe#X}#cY$&*;Mx);_2eB7kc>WVj*OxY!J1y|$P4*zYmpm)x4M&c_@)&s!EwOZ3d zbbHq3#GukhYal%f02xW{TH*fst;ijeT4ToNCF4Df14etG$=05wK;Z+I;?MYTx((1Z z$E-OAI(L~1Z>Bl%zerp_t5bnYR@$}rCGoN({96BQHRs{3ZBMs{DQA?v<+839#|6Vq z&d%rHo8_hKLOYwHoZE&0N9{ox2!q4}(5?5DCm8PvZ)vBQ0Gm z^Vk|;Nw5G#R%a9M^sC)d+t^)=HP^|dL?|PGcad-{cTvys%HM3f23-+M@*eNPQ4*uW zZ(lt#Tbc8Cll4OYlvKmNG)W$rlhbQ;kmEo#t;}=2Q4}tSf%D6*u;#uQ9QHl$xIaMj zOF10XN&{AYQD2^u@787q{&ctrU1APWv9^MtXkImHLE?E{mVaRRftt+!k-Ifgt$(_v zo@sV2$eaZwYyi)#lhz3zJBor25oMaXKM~X7fzC}#y~b00ogQC@zF22($(^L0bF8Dt zyS;*e@R`qe`D&rL@Kz7WbWCq(t>$xQV4#@r{7@~@sRT{F2pQUQe@B2tmuo+eJEsDu zSyYY-%|lG&6Vcr^rGR7`Z`-?)YqvPmjekEmO>xP%s&ce_TJ9#D-W(=wpq5#G~M50 z&KG{!7l|A&+iUYXC&(-6_+~`GztfO(NNOQHtSEGN!S%EA=qiU*1UxdVoJd##mo zIouriMDGjh!Qaznz0w1N&`DLndCRhjdo|1ZV-|+FTlWEywlyFg$4K<|>~`Nqn@QiX z!@w5A3;3C>fPe+*PN}&Ua2+UbO~CM~LTh*KuqZ|94_F5m%&~oYRuMTQqie*jr6Bs{ zU-HB9zHw!dvH#7y<4?-S0ac<~bqCoe*3F1~A#XD0YYC89r*k+5X|6hpNP-w1^$c;9 zWi@Hw9<)xa#mZG{qPqfrZu8zWi(OuHjZ~N35ot}cb=*Z!zz%13pYXSCg8yH$tXrJ$ zyH%0@$oo{&aIZuv@;!U2Se^{A0ly;pUMIKbI>}`MSwO-;n6|QPYqKKXTcFp$S}Cg) zt|VSFb}#-spIZDl@*v(_?|19k|1{NjM%W+|=$P3qxZ^kpJPG_|iIuPJ*O)KyXeA2cpXm>d_Hh{qgNn%2OyWuA_Do94tr)mug`R_BAv9V5!vhyh+V+G)2rnHjzO(h3z+h{ zF#>q>omi_ZOuO6|qyHXVns01DYZ!ZCKH3!^wSMxmqFT;K*sMFx%w8ir5Az)DIX#(p z+GEFgZQF=j{DhX8JkyW0%-?iYjrVdPH$d#r*!@DP(}e7ltBJOh4i zM=gItu;-B*)fSeIDnVkN*l&jgIibl|-U6*MZ=M$w4OuIju~`RzIwhYCzA01s9n%%m ziWAKdUiV3x`mzqbj+ItiyXzJB0E9A<-`LRQO3D1{5+uBOQA4MgzxqMYF0A#mb4zL; zR-X_gM+n@Tv;tBh_lyl_IF25gp79{xC7!!)zGaAU#i2;&sbvpYqg?+`DMILtq48Ga%R1h{(` zu+}7@kv5ciHOrUW784=Q3Si?q{ixwni$*t+y;YJ<&Ka#^@z6oI%@im>nC;zO^O_ah z{*|6COfr&r1pg#z0`9LIe>1(l72?jEtcSnWJ`dBgfU?FbqGBM(j5QjT-#@6X8^cM! zipP!PC;Yv$Px)Yex`6nyT&KXUlLh^zoo*_NwY{9}UynUdJW_qfG5=iXy{BeMEoUBW z{<-WGqFZ~0$LlMzdn<~!vDmJEosxQ6b)wS#sYWP1t1f52_eLdwpBd(*`D7iz4YR*-NPvpWU_w-MO%874zI+c}p7h{)r$Kg{+O&;KLS; z-pCT#3`)>EH1w6jiS_#{i|tqW^o2Nn!sIJJ!xK@0gq3iGDq4g}SYN=vm&h*gK7t== zkMwHV=W*jojFnxEOcjv>s$c918&BsSnFc?L-0vwWXv?#zlbCfea+RM~iOdPBbhdQa zbF>z!v|x4tK~tLZS|m{FB&VV2=eCK?Oh)*~cln!tjyxd$PKoNuEx%(S?uU=>;**o4 zCMD=c;u}2&YKUFXfJ~X;?1u5obE5P@ZuaLM)0Ak(Afe-tgP~u#dOjpg8@Qaw8<|42 z%Kp;#f5aM&ZG_%Gb*hXJk30f6d6n+)ca)-qj**;hT2t*3Ga%$K^q-h`VGyv*9RizrpNQ_LObjsrSb-|GYGnrkB08QIvzz{p= zct4i}(RHPmnK9oVINFqdSNifcdH>8^>}u#LSq&)kyrb+C{$-W%TpznHJ3>7ARqNC? z$kxe<6+omDQi4f(@26>4`X)ht(RADXnoCc1C=Mk)oUHvzjJ`jR|?vuo8{h;jgGq$(qF z65@Vuu^J$`&rc?2qJ@M?&v`W;nZv}BU1b`;=N*#05XOtE8tJ@+Run5I(Zo!%>(#vm z_im+K6+m)w?n48g;sI$=x4wxzvk{~@=I5SIlN_fDozj3q1pj-$eZCc`Zuhq-`P1k* z$w(}eGh2_+&V^pvbwe*J7!wQ}HQ*XMv)LfnyI)AX`W|dmmYi>LSy z;iRjv_5Cx3t$V5$*h*fSey0O)iLF1=vc)Qsh3RE~S&_$nUa)?Gx+?Dsxy^HdRdE)6 zY?<()MkHbLhu60)*{vz^!{NDdCWR4SBv>qpn;66WGPyCb&!m}R^*_WG1O_#!_<{M! zW1`Zj2Rlo!KWkZ>6A4e45cnCC75+9L*NVjdB`YWkR<`S`WoE^QZ90gOkFcJh7J>%g z^a0EpYE65F$8ag1GZhg`jl_&42|zl55WVF+XU6ULFChq*(Je z-}ON2?HL8rNY|)Oe|K$DZ6AKZvs)oaP^@SxdxOP$Yv;P@VhaNXvVY7= zSS5%MY_Yl+iCVSx>MR3J7C)mn%k&CN(rw|yV}~nFp@~3}=x;zY9%pWOin+3AyO{Fg zc-cjbMIg!cFV3|AvkR2G^=QY~<3mG6b}{ zE&?Gm5@Ac(x$w(%m@SH`f)mStcYbzUPxf5uR?Ko#eSRop>jmXT4o;d9P|ZP}lx0Co zaem+ICFHFhvW&g!&HN@+M2sT`q4$+3sxS z<>|Wm;wQ0p_`^h~~?SqivVMvH3vMn1eI734dZ7 z<%s!C2OQk_mjve)#y9ph9TX{k{y63b$NQPZv~O$P`;Q8@W%t#MLRKXSxS># zzt9P=_Ror&&osiOq6#C@>+5y*rcI|j1MJPt?Ndvrc%__hT@VxfP||?f<1Q-V1|v#u8V0Dc*PO$hCkcCScbQX3 z9l3A(PKcsz^e+k`ebnQJ2Al4Php;H5sT`kRwC;WE6xTB^bIQ?hn|EKZ zJ!+xr#Bb236O==yUJ*nWhp0VxR$|9dA%N?mFw``-Ij1O!L$&?$F_%WFO_Y)Xs_(}f;_S)sBivV{C<_3 z3OORR5Uf3Ebf!7i(xMiOx4w&4`$|lET|&2(a9Gui+N31Njm6G=veX+&fyu;fYT6__ zvH0nE@48)d;4r+UM`e6wEt2~7daGxayj!1=Fk2D{O(Y>@!4Dt0Z zkL9!DJF1Oh9+p1pDfdU%tJVxzGfbCfLP&%d%ilTgV1~Jqxf><01)4x`N<+HiXmPBU z#3K!VUU0g034o~1%5`*JCB3hny_*}B`~5bv6{1;RKVaw+jjDo005mN+(^WGUh$VZY z6&`9B@YdMLPFgnvExR$7NogN^2XG+z&OckT8sJtXjvqW6gG#t;fh^@{XvLZZP-qU% z!m-cwuPT~Tl0^2Ms79s6p;+8yCPOgLsZ}4aEgLp&9`G7;S-kP{gN10QC^IyblhuJ4 zZd9CV4UY7GwbS^E?t3zz6u#ra8BK4V?}h;RddF6CkN;@HN6AUy;jTq-li=NR8C~Wn z`zxq75D~At{Dc1OvMT69o%!ya2658&BKv5=h3e-jmnRi`HTeySl4^KB9wku*9rNt z>`#L}`m`EakcO~zcd3OR7YR%EPSM3kN}0HbCE- z@)jCW#7gc2cI6+1b{FrSJ1;gzpx<>8I&1bs*n|TptCC0HjD7IeIvp)WV6@JXZ0KD| z$Cb9>=H(Hki_jldLW>@}ej#`Kgec(_2l6HmbL*JhO!FMUNuLYdBsKINAop(%Fa-TR z5G6x7(KytH*kTdVZCaJp=o&q=Ydc}}=HLA^ek8fXoh&quHl3$J=84rlwR`4i$|A(M zk5QKgOdEWGJJ0>V>+sw41a&zhiR9adBYr++0L-HFkIVn-^9=mw<>1UO>iX*J*)?LfbNvnF7oi21 zY*Qy_8u*KvhOcPelxp>_*fEDwr4S98;Ow|xEv34;_~o873`cHSJ2Y<5AR&yHk|3D! z!Jg6Zp!PtN3ZPkW78SqeHvPlI164w-4u!g>gJKd&q}#4$?(UAjiTegw%v}gB020?~ zO@LGtkPid>y_#`bXiz zl3#61S)%hsYm&;!T zkWfyQpEgg**en1UEvAp6A7E4|%5qxqW1%==f&reB&G)T<)U4Wt#45!0EbEd@P~TFp z3LuZM5Y0~}kgVtlzE39Fu$sSJGkZ8>;5!UtL304{s3VYlb)e6!{FrXTmfnX`?<;g> zgQTYkAqvZE(p5e!aaCE%Sr?u(IB2)){4nn&u>Ri0XSQb;Gd8G=UK+NK7XlS*xTK%Y zxEVEVI~A6faOHISOyEdpncHIYPY_2#lu7j$!5nL7%yQy2fgW-9D3As|j^~Ug@I>dN z2a0SbsV?+L&-O1<%22s`zcU7@!C!bp!A1k^%%$w@DJLg9&ZW)(4B&v-5*K+uU|3Qz zk0}}b1}sVHK7ND>=2yFd;Gg{9VgL56Y%fb7k2xvRa*WbEph@FXM!OMsD1S2TMEk6R zHF;TXdkYso-4^0Jg7c&6T?8sHTjlOUuSDzLHEn*E5@thtv!MMctHD!jS@|CARl~zn33s^N;gbOvdT$t$4$%f#$^2k@(;De%teB4C6Fcl< z@*-4!%Dky-iwI9 zRjYKMvaZ&|{@!^Rh65fsCsgc}qBlV-ffa4l2;yvH7a{nrlL(-1@`m-Ac8Vkt6Z|>R zhH_=ycBJONKKD($-@iDm+Yiqr_z?P!tQJVD+h0eMp8a(v^W?Y>uTF8W zSPp!l-^1qZh65QwLOOAZaoJP?)jRfV6M=E_=#bE#CzX+9%P8B(Z!`s<5!v(T$kbYg zviY~Zw2clwo>Xcdx6xlI>m3zyE#K))SJ?YVSBa-K#akZYUeTyE1&d<^=)W<$G zM?n2>x8kk)Jk5LJ?r?Di(^}B}2jFvbUk`$lnl^J$J69MBT?oe+ z*;pOuip{WZDIHpk&ND$R>`6ht2`N z4s(|@U@7w~vsp(IQR?wWp?F=UkJN%~7q!|abFSAcBj-RXu`ukiJPZghEf#t*rEao( z)r1cSW&JC<_d*nPU{pM38aqsxvT<4pmmP8J!5U=VgByR}@NyqVL43CnPDgEQ^qI&q z$dH4oTKDTN_D-03JSx9yX8;+`gMe@!2+G55BXeJOKezo>w!Tq7{s`2| z1$^qiXHb=+3_{1QziZ_olgM~YKCHbVwZsmqUC2bN(|lh8S!F~1{G7?2Dt>-N|L;Y3 z=j%|l*_jQ;WEeme9*y^Uhj90cRPXUO{7oW+pIVjxJkWIyQAqw6uK2WkNC0n zERlGmS$d9$;Qmu;P>jEgjWgG6hv-0u0Z1*Hsx$-dlR`vk*g$^FWJr6o&Y`4Alj9Te zbd;R{+6t`HB6VQVsNGG57l5#CdC`W~M)gIago*L|KmpudL?YupBYcG)o3#^f0G!`2 zHyHp7I?d!WK_#_O)m|UwyKc=6pBb`_z%YJC8n}zBCc83QX{^pRxMlDx?c}ubV;MiX zD09NF0ElB6apdb{2?qvt?$dI}lRpZ2nZoSrlI zwmh2%*PlQel3D#YpNlp1UpqE|yBp65rw5Gut$~UyJNd4PTi8&AOwXNZlCRWIF+36d zuaI<7OW*4I*#OGz29WaH+7&l8_oO9dB)Tp22PYwZVPz2|!N8fTTXxx5{!f6e)Bp_Uz2 zSlcwzN>Dt}DPDC%!rrE)0ZXh=-KYmz*9O_hG^2wWyy8gQ=X$PD#a3QM*NYrxt zxxUOG>YEi8&bfOawJ3t)g3+2xdr8AigHN#a_MoPNLk3b^FF$`gz~H-Q0UD9zi3xdi z>W5jY98dFB^BboCZDLMeeXbV*IOC@7{lfMCu8owh$wTcuh{?Q0+s!G$S?cY>_5GKY z-&Flk$n+DS;?&${3*Y*=x^LaBexZYLdD2vQ$9nOzk6TYC%p=+Spq4dOM(G|OnHwE; zSyC+Y<~u(BHiE9T=h!Dt%dST(@)ai=-A|@&LZhGkMSbm?HXRG%c#P*yLSra8aY1{V zsFZ&Z_b%4P%!pk>Vyt!5o;|$KZ-{qIDrg9O>A?tk4f4q5r;KDy4?V*>LsBJX<8E_m ztdRJdXiopiyRn~T=fSi3i6XZtqo=5ks3<8Amx<0EFL{qEpP_4HD6Jp6v%Dk1Qdpia z4Fbyj7|j6PvtNY3PSkr5JuU`#(QsSTIp4@80`tZ2X;?HM_9OA*VbPIx(IkA9KQ99; zT3>FE6TVS_xVh%-1eSS=-R0g}wYAKETg(1D@61<4T+~VYIaw|#nD*4NIy0^@Jl|ss z2a(b#fvF=Yej@gCj8uit&SJD;QWC+64`o*|2p<$=kcId3xEb~8e==03;}Ml80}N|N zzLH+^&m1RO@-5Ag&aD<)R<=-eISqT$K?I{W=A3b1gG*0%tI6kxO zs+OlQ$u##7R3ukZ*YOd_t0w?Sf{fRZKWU)iBr>p(Em8*-di`pI1#+7dbwwuaCo)&E zL3&1%S%lN!{O^O8+7)pt+`k{}@otI@Egqx-+LKltbG~d%SoB<=dpf>jV(Pmkp-dX& zsrS5acuu~j;x(>g^f8T^#{g45SQ1;w%D4>g2z#dnb5)|?=PAzKpMW-s0H~=p;wsO_ zD;u(=w4IDBFKUi&kT6mEMJEZ#(8{4?a(qeN`0s5^Oy#bWU9%L%bNTW9RWu_$*eWOV zRz2}sUGxrX%7VeIz7EBBep2D}*eWkNEsk{nk}({dMaS~P!Wu>C3&J)XcE zR=e&usfzR`Mw0BE`MjsAuAor`_iG+YV*h zdvpZ+QQ%~3{i9$#^isr}ASU9sg<=75!IoEdy}w5JsQ*#WoYk$6ch{s`Mr_Y`p|tUS zTbd}3I!7K}Mc5A@tgijowI1c8%iMRHYSgR)5jZ9C9gEnu{_=%^6}x73(ok4LS!db& z;{9czO(~c76uP&p9DnE`O=a$6>X;VUa(L9J`r#l^qwHAqtEw&+q~y@2$0w)PbeTXx z+qqJ7K2kOHN^5YTALQO3bftCU zn%P%5leo*;C|q(%l-8!{k#PS={1%e;zeH)u}7d(NlxS3=V4u_bV#nL%a zCAnsEQvwaJDLvR75p;z@Imy~vRiig8dk9FVJNv4wZ%s#6)FiY}(uY_ikhG!MV|6G_ zy$h%&b*-s+%I?w&ik}D`^SMg>Sl_>@_n!_l@HSykB5?)Bj$Ert?&nU;w(X!7yHC$D zSD|XS0vMkc<6j=Em6P@Hh1=;nM=^lmBC<_QXRF9)kGMFddOJq)1;Vt*>$H+l>&STr zMP|VTu=}-0aDC12osj3i>vWmL5Q#k2+E0?GLLRj7V*y1J9@Pl z#c3b>?i^kr9t>G5m6-byt7@AsMQY#(baPg4bz7>ZZ3M>f(F8}ZH)KL_Vcv_6om`8l z@(e?OcluFm$!VBite@I0B5ave3#3RNW;TXTN2pBgsNl> z64@o+CMqoB0(yu>m%=V6PiWN;#jX^Z z-((|tf)@?m*tk7VV&exn+P@GCRq?y~N8x;3-ZasQ34M;MT?_&^kST#rd#=8dz%Uhq z@tTS?5CmgpQf2Rg`oo7fyyPY=AiBizCnH8`TQG`VyL9r1+q>qjn`6oW|123u*@qS& z&W3)~?YVO;dH(VA*)AsVsNC$eUlvZp_}w|z+z#1ys6_=E$kD*l#_UmVi4|(iS%B`X zhTazDBPEPMv;OJ05AD_;fSei~^^F7#H3m;aC|*gPwfeU*5W35DFkhDPyU}Zk_wGne!6cJPqA#Br>%tE-uv~eLB7dIG%?8CE?5!%bSI3K#h5?>t=%c(VaywJ5D%DJA z=DuZ=j_lX%{-mMI05CK4sEKvlzLSuxdC)N)pelr*GTlJaxr)~Zv|23*u`2+T;G5}n zj5Ul=iy~_XU;B;4Xc(1&cPrlYyO{?(Z z`(o#e+$9NQcuDz#x3*MmeG-S-RXD_y z>sz^5HPciZ;%Vogv^9GQWD?KAYY6NCLh#w#P%D(5G2)Im?~u7=eBTBSS{s7}o9<4Z zo^Jqc#$EmK&?Rto4cuQ~UtT5e{?^o;=eKgE@QR+`KHD7+g_3zYOOF1m*ml>{kw#l%&0 zVo)nwDG33ui;}@baF9Up41e&+BzY$qO?LN0%r6TFFRj0Z$%T#Nj?5*z0smrO;{kQr zfYICaY|y&5oItt~b;Av>dng^W%==0~ zJ0o@PM+x)?sct563D!~j@asc+xk0(H>1#Y)cC);RPPa^XHj{F`;XBb6IJajAA9Sj* zZ9SrpDW^p1Q`7#c<159|aMTEcXtY`mKPNg;Dmo!_ne@DT=^<|_-pg|!B_U}r?~w-r zao$eas8fw{Q5p4J_8ggS@*m0t0+nct>YMWreaIe4b%UOfC29;+9!Q^->9ij#ouPlc=4GpeZfB>b!w6aNA`UN{#Oair#nRR)3t5N&`I8tm64R4 z^X3qE8oK!Y!Hp$0&m%hl&fSJKkqrewz1WI$5-wXSBB&!1xKi7gaNpXa`($61PN=O9 z^+p~Rg-#mnMQHhB&qW8@n5di04r@=)j zg*iD}uQf81ZkwT6fry?FpXO(GFydQ^`CS+cK~ZX&cd$YLPHwdFDxb@^MCowL2OXwS zIE7fQW^zWx*YL9Q8GYaX`g#YRUu=&>m(Iny}d;)OnQPudhz$4a{0($~U?NF+Sj0W?J7k8fF>f zv)qqE!We=eQOu>j-_~br-g>|=+g@G_s?eKi+RF!SLUIStDiE?b_8zAfp(kmnG4Zg! z<+Bs=5D`YiZ$wtAJ+)Q=#uY`rVzm6QyM6V;@IMMwh^qT?p~+|vGLA3-+IJ*Ax?qOI zc*jCa*!Q`w^MviPcvE6BD~KCn>6)eU4jQ_7?FS#`q*T3*5YQ(GaexJnp^fAs|4BUd zGUbHHc0$1y8{0r?Dwht34l`vZ6N28YkzGobw}St;*kv{3RY-URqxW!*5dNBx5~ z$@_2Ql(oUWFjT88zn%JgJ?9%@65DoHvsIMIm)H#~R70a(-T#lHYmaC8|Ngp>+;XR| zic(3ETNta4BuVRrTo)=am*h6g7IMFRBq?Iu+!At`xo)|xl9uZ-m)RC_-(2R+E}!4~ z`}@aZ|9CuhdB4u9mf+u2+J^<0~+5-f@ z;%5lWq{da|Xi!&3@W>lMIrt@zGCWM5B8AElVLSU~BCl_3Q{V8AvO)M9Q*6sOIU4RZ_XE6zjb0yXB z%S2TgDyfsJlNRIn-RoGQUa;Flbqe(Mb_BQHF}qMl=&1^8TO-X87WyI~9eNogc|D>& zZ4TsMhAosW1!}KO8MA%BuH+mD9%FRpnteLqd;E&bgXD1vF0|M~NlUP~L))BIC<@rA(vVjN$ggTst@0_VpYhY z$_81ISKqV?X3zXU6Ed$XtkY9z@iwA3NJ`j2asS9OH2Yss9I&Q!=%v)oZ=cdo2d|kb z;Q(NtU15d%Q8YZl*BWWH`7kz)vScwc>!S3qLzDKrzQSmbEapHv zckb>vN7?FjV&^VGBwKBiUNL&nW8?7()Q;KBJQ%|x6>4AN0=yW;)(5@&0r|VR6TNm5 zFz+G%lNMf4jtv}>m}})pq+!=me;|^hd=grb;ACq2>f>R8=JzK?B>k<4kDoAl!=c4Z z1MyPV#wfgt3nBvgN(;3RGvIN&X!aVOxAPzpes0Uy@-_7Z_`B?X+0KR4V@ogSZ99tz zot;dX&Um{lRjW#?3b(xh=Th}dmP0>AQBp%Q9&9dAYqmB|tVY&g_k?WH#`Pk&5~d~E z%ice#>a3L<9nZ*PS4&~Kp;3<23?$Mn*^?S5nbSFqGuaG_uy4^-@@h=s*YV08s4s-8 z+uf)mh+Q2z#J~=L;PTbmlj@v?(jaYacxAa!>)ADvWn>9EuM6TWHvR-aYsL0 z#5jaG()WiUeU!FCg~3^yw_fVJ9e~iDgOMq}!6Rcb~ng z$wTnhs;O|`m`oz54WN%od^y1sw>&wwU7EU)yrZ=Zx8p5h`S_yFT$__{-$CA)hw_*6 zle*4b8vNPi%}T&VUpJ=Opbmv3M)wQ3Rvz#gu zZl<6G-bG0o=|?^2VUF|&BfTQ8i%b3+`%{0QspT+ZrY?cR*f(IB66FDb7stE|)04c~ zKOZg(DCT?b__LM!QP0G6c%NSbZ9g#=vKh4F6D5r#i;)ElJ*11lT!@t5z*9?HKVMn% zCzNZJcm_f=4DmaOk<@8%iv=RRP=k--V}=iIH|4(iqxW~mIngqpYJrusq|TA)D1rey zn5Li5`W7j^{vKCFwO@Z1W03JN2hcaB1PAC>*|?PDfKMCIZztVOJ(F(yl$ETkA^*#0 z5Botyj0G@ka>dy88@OZzJ+@^qzev(hC(eH&J=j{uZ1%*`x=O&ihQiZtJHB6TS6Q$2 zW|KSZs^c({@irh$aHwV=(Erc*01URy_V?P*1L9qzAine6g#ZQHeLu`|5N{4Rrre*>itj$avJMFd ziT^5Cv}hjbPE2AibZ-~-lrk!dL&*&RtNF!AIG|3Z>EFz+#b_pfvP9ktYW- z+9qqx7?i{Q_D3RCf2Z@Dh<-6qG5lmyzI-pnphvzTOQ`To#?%SvQ`<6JOU`-47wL>n^yJJ`qqs5{7A2!_VBbNH(G#X%a+vE}X4}P(dI7S5%il#{qNxw&CyRTE2K~D|F=3L&I zFMslOvx*dMpl6wor~iqlpe-!Zs^(1c?W|MYlj4|UfdiT|g^edEuOl)JB!0?IZfs~F z>K`ls%WePrZR!n<1j{59>w`;kq{~2Io=D{T@x7S-k5>NBmMSx9H6JwpW*;#?GD3>K zAwMD=cky6932W%0b0BTBh_A|l*bJj>nApq+xeSR*ncnyPAFK+g5UACHGgf9JO9V5J zKQRl47VYVt=a!@f_$HS5Mk+@e^8>=@m(A zBMHJdlx|qOr3qlcG;K9v=^B1Y8~M{?3>qq*{JwdscF?;<1O0D`=^EUKz*teDKWxa< z{qggeqx6Egdd~I6|3pd}h~WnAici?N8`LpS0ehWi-PKmI;*sRTx}{N5 z{QFp8wD#!X8MJU29JxB|KaS5i(ej>dn0p@HX&!Glo?r70Nb7XcxPIXEKO;I=dOL{4 zy6T4HVa{Ur`Z<|_h5%zXHDRx0UY~1H*NNvirENe|QU3yQE1P@iTE^ZV5Lae`))%w_ zE+O|^1}7;&Da_*T!+xZ3R8~&(;cV=5b^b~9a60&<9@= zDXPy>BA`Ba%&m1@UtgP7)#iBrEcyqee-c@#lMM~Plk9e*y!@+b@|nYJC8*6Fq8egJ z%F0Fryv0r&`7=UK5E|Mt&A}Df_Rq(o%V&^{d0m94qSrxt1a0^iSk|O`w{2L>%-i{S8f|w;DL?VD;I+la_2{Mg=qZjWIS()G%GUrA5q;PoAl^ zk)&BTzHEN?(sAr8ZgatfGBulab55ic(pI3lmgJJ>It=>V&)sGBWhGC8xWtR=y*Ka~W^Ah{GA8(wMN0tS7Rl=+6AMgro%NTCLPc+Gxk?*k4}d=!<|l z1Cp$LX^8L1Qq`^g^s4CR{N$r=X0|=j$j=$;Qksj7w5$ZtT3wc)?>~`e#RJ!HNeqve zkAE%Y*_5tXPGT(EWzufq!7REt-?i~X?1;F7f z9PyGMnW%&I1u|HIh^qjdp@VeIZOuLO@(xR^vAWm@mlSn$Gr>q*tVel}m{Zn1jS$UR ziAQJy1a86rQGtJX1m;sMinbi&pLpAleF9`|pQQ!W)8WF3K@d7%F{q0{p1ezFeJWp^ zgKXC=MaA1U$@YfGEN?vr)fny7KPl-T?1Kl-I#GWJeIiE1RpQ_r7zff4)@8C&K2xj| z4~}$vc~Lk=B^!OWffg(q*C~WFvq?*O<&&8<^3Lm(O2D|9mPy4p&yTTD+f6ss3vcU@`K>#Aa9ZSD^Yh zN2Uv&x=rml-fH;E{{FrR|J&)RS8Y{<)* z-n!L}I~`aQ@Ca59UmPywTLU2_uynL+so#w+V%fp|!80-)Bv7jPaOJmVkEI-Yoi1;A zz&Ag4`*xYe(g<>WC(eQzvk&$alq^9U`nQcB%*7v*)`_I@T`l-MXA+TA>63^A6#U zl`Kx24%8PmTw1yvhRwu4NLiS|4`ZLNgO`ggD0Dkc)R~KDn5s@f9a8faEqCMJv3t)A z@0Z(c6a`PwK%1|iEq#)xiZ~?G$}>Vx?)y$9dd9Ev13!Qhnim_UYo@|%-)jAWL|g%~ z%IEV5V+kw&zWGn29MbO6Ozz%-!rr^}a(hFw6K5I&^*4-u_`;N*g3xu>(iehW!ovcA zZ?)Ag%t^F<(gisK*Q+IJ@5hKXLyp))A`I{tb7;m7yseMyfs$q8JSo-1p&FV%?>ivG zw!3Xz-ac67*kNIn3$s;|BW{G1%Wzn}dlxJcFv^v&5#bpW2qAKyk9>1F{j=AEiI4U~ zE62i+>d5xyBXcy!%7(aR7eQ(~MyGff8&Iu^a_t0>pTva>n-$f>5I4kA;z_^!pPxaE zMKV#pr8ym9e9%gNbPkH?(PN?W5BWC7 zqoOz_r`@DKZuX%Qep1dXj_konO8DemH-vUB8HdjZH}0oNzh@gEWc_Y|tjY!DY>Ka8 z(Y<%DCTX1=Ntfa~l=yla=%-&#bMiWt-*wM=8JW$2FO{>aJ8<>V1w4~NOAuxt zL|WZwRK}h<+xrXRlEj2PA?{wFtF03T&LPal8D%+3?6dlCs5YPWUD+gYjKo;O(BXlg67Ft8e`7Ck&X|X`4&(y z*qFNh%(fGF5!cM7;`I_BYE?qDW3MeKRrXvHd6^ARB~V>vWD(sBQp)gySZ)^HHxJfa z1Udqzu3*G{ankEAtLjO6_}2AJ_02eeHXM9Jhc~L$y3F8-Mit14AeFH~>?O)Jbf*S- zk!w_{+;s=4KDDEi=6mm9SEM5koOdRF-;Lf_+H%t5#PxCmfVTH@SSjLZzCBVr=q}G8 zp=)g~X3!&r9Y?+nM6!i4yHUt5Hg4=na29He^=}>A&yS)$$I$3?xGqY%Be)P39-w3I z&b_!9Ftm;%Y+bNjnSMaT0r%M;HSl~t8U~tV^f=DY?FWgJV9`m*$tGB?_e1(#qLM4B z;sytv0NvkwtmQHEYY>^=f-BZrlI2CC7P#T;hUCy_8G?(S6ZWnvmvB`l>gx_L!;lH4 zAjCrC(6&h410bs_kE%b()ajKu7wO>-6Mo!gaqj`6rF=i&PS}bJ@EBEfO56C?D`~-p zXsWO1tLk|ZD@5TU{YE|da<(;|o6zV-T{QJysX{f*2nV4?RX7v zmS`hlFJ{D;img`z+3dsQ&X_9}=J101l|AERtvf#+ZxxDRCpM~H^m6k6AC3fb@-5ro z`oPTK%97~soSXQJ7wch`PTGMM3*E4X@OT1I5{UJA3mPDIzIPQpX7AHZxEGmO3wnVc zdTbV2b%oUqli-`PYLHV1^$Wf%m8sQFhIWI(oc}~Hpm3&2p8J$z(&1_sU1>b9;5Hbv zhTYSal^61dTF5^Gc$Z)XXi>hF=IohLsgZ0^rR}j;?hC=AT|H?Wk< zV(EYpf0>igx$_>99PK!muih?DN99{2-%P;Q`eSBza_KQTwIdh}6w;afi|ttIbHJrt zY#9oGrkwzc+X^;pUbdWQk38_^+^soq%4kf)O647ymQ}W1{S&kxfw{6Zs)NBsNICv~ znH99gt<&Us42)8FK~US!aH*H)uxyeE%}+e<-Ifjy)`pMs1b$1;^GbYak6vk}I}E?{ z%@$-G&+*<_hN|V6G?edXR1@-KeT=k|Hx7IKqIX6PSYATD7)D!_drXwcqM!1qJBTZU zw)^V0+&mL&{>`!qJ)Z7Iy^$D3dcNP}Q9Nu2Yc@2v=g$i}t!CuW5g3LbW*!wK&z~Rf zr~CeUn5TC4++Qgz6OSX$eknC%T4JBFPXyL)G}Vs=VHOyJtSxa3={bvD0~lGT19~-XqW(?)!lNWUep}h<6x;Kak z&@pwMc%0M_(pTcym>3W>nVG9h@UoJe4j)%N%vV!@L61M?bI( zL(X!3=DcyGb%%%*L?80sLSsGr$V+(~0t?o$l4|CjnZc+uV_l0Q-=p^3yPtV$ZpofQ zU+jY|IkES3AnAKf$Co*K2pT3=dsxCqa0!Tp^B!X;2e4n#bTyf*Ifuk}fhz~zyB->K-MUo;8wt5XJi&%13nfu67C@G{#BN#Nh*l40 zxG31eNP>GE17NHaeRpsPx{HCW|2;0b#f7c?e$bY`axwbYAYbPJly?(dkYft^NU=x_ z@F!RwdOGHC4G_e@%1aq^A9$GZ{`xszxc41|3Q=1)LaL*IvoQH7^m^bC-i=IV46JzE zSq&11Uk~_C7-cXwCi6z`{zZ?kSLsb1{R*m{ z!7+0*j~SSYtoj=lSsVv`anobCwJj?Cf2#>gIV5hMO^2QeO4Q!Oeh^(fq3$$4-+11se(4Ich^2Yw0y*sUPIe;lw9qy&F$fx=VY!MILOZHVvAOM@`daM-LfGXP&1<- zx%2n)Cv7e?%Z2dXL_gB^i04_Ly@oAyJ0rZAW!~0_0c^qZKc{@&9hh&0zXuU1@lqsY zziTJ=W@TkfuHB;l-%`=K#}b3M%!1d`?^)2^L9K!%IJRve<8(@^mXlbZoD&|e7BUW6 z09B76xsL@cm93FTB%?06YR++lT}k|y_f89Bn^T-WXoYN4!vRf>1f$Di7as8Dv@r2M zv#Vym6NSC;{@yuVwM>HL8MFcDw{BlL<~O38|J?>;EXq}e?}gG0=&AEUDR-16=xqLV zpw;%{{5@nqHq=q&89lRTc-#-n1;ZRf8xH7Jw>of~i>NmR(;4fWuqtJ43&%1~Z*e2i zr}UejKfV_YTJ*|ISx0K4v%V9ra`F}z*z(+efkPr3sSR>^fM;JxgFpNg%Hmk{6MhVP z7L1vOdcxvcZA*Ic(?cTt52yV>V<~36gY})KOHNh4j`_;JEr>42YS&F>DIUoLjFXMJ%}Lisdf`Fe~bQECgD8hHiSAf z9QD2o`OCNVd1GB8ZSPL&^Z>1Nwk^2)ESnlyo=tclmn(U8$KX9EM$+2%#&zfUhN$=E zF5{~E{&maMtctP0JFm6c3&61Z1K5p<4})Rw`mxA=)9*22A)$ly(S@!KBTKC?_2fd$ zEQ9ZRMT|wyt-Qv4Z}aIOp016z8`2oGLY5mIzsfnK@*^jVY_T2Xm=p`OtwJST!#8uf zoMgulpR*4=to2IIOj2KXljM*;WBVzhJ8;Vn^V+vT^<|* zcH4Wve#T-?05pK{L3xB6^P*B@wK%2iCH8bKyR-+kOGv|hHm-<}&SiiAQ!+y8?UV6H z?ox{3-^8FSBd~QoOV}wa4c4jbbl|~zrR>ns!ecyvKZ4~^UQP!`2bv>6^G4>l zI0-8QX{*?`Z92Tqd9FEq=VXwvYDvK+Ccu9JF)uVynmkdjvMrU}-=ya<#`md1z$gdC{qshl}Gj_fPc- zGeSk(9I^bvV7nW*9EM2NIMFf`@S{NXFk#0e4DDdpzmAZdpZ#Y*4&^DahJAQpX5U+8 zfEY7jGo$h_yQmCej~6vh3;S{vx8;jC06I6H65yZ_yvqP(N-v{TCN%$NvG<*Sxpo(!eR>9~uS{>+{LMZdK;+YqV%o^An4(6R_lGXo?g#*k=ARV(%%$e=LJ@L@x zh$gUL@)A~6E`)6;7qL3V_rrDK;&2kgU3_FK0OssvsTEV~ zUyFHu{$^Y&tisv%#@yLH3yy$aWjXsQr|%yB+F(6GZp;m=m#r3-x~a_6pAk-yk)VYa&d)>h*j$=8$u!yQ3>rwuj?-39jrboVbTR+f3;H zP{3G48?=1xuY7JJ^_?}tCA&;vOk6P(jS~RHCyePUNvnMB*fBXsGp&W)vUv*5@BfL& z`oYv^0r8J$m65{(hXR^0C7F^z8fLgF8({rG`C-++7b)aiYpuh_>7Vp__`cWY|Kt1+ zP;`VKNw^1SGJ-1r1)Y!J(S8LbXK;EHa+S^(>;oKahT6UTg2aQNHGzl_--?G(=oy-n zgFt79sE8V5mb@*Vw^RqSI`e7n!B@!_H8pNYhH|SF1@Ax`z3OCK{YpwGN!HH`A~`#mN)C@Rr+Lg69dfuZu#_A!P)=B=WQMO+4G;eq$7%z;DTr#WRtZHKV?aQlT) zK)x=FX?WAO*3ETS2}qOGf^LB=^4Jv$M~thTWH>=vu708=>a9BGW|NN5(i*uj_Sfgk zC2@n;*OFmXR!hx=e>I&SPOnS6mR-ruWTKWleW$hkG9T%H#T~x=_&<@aIDy}TVp#HG z_OyGF6!|>qYxb67m_j~18Dbm5tImf`z5Y{rVePU0w-5arhSV|2!@yB7u@f&ehcqsz zmfkiYj5MSFL4FHA5%6Y78`n8^%h70spE-iv04p8O$iLs(3-IoiN9LV%?b~;{YkXgv zdF*@)_6qG?wa0=6q`9uZ=6dP%>q@HzD$9>Ue#b8ry({Yn)8t&Q4ML6lCJ%AK->u>| zzVWnak-yt^3Pogk>OI+$Z8A@mtB5Loa$ji)<9(0r9ev^rY!?;o@gL>G+iIKRJ7zCE zvwfy_3~r{OSw-#Kq;ZQ&DIXq&@6XZRUU=)SrlG*OoUn6&BcgCOvS?TUS|$Cb!ra{7&C~yO!&=qM+EsDxOy}M=`B_K|*E$ zT7*P!t#pBNkQ>dp!A7L4Z?3(!vmV{uw=6bc35JRlhEixDEW~0b)0U`!85SxvHD-3W zr+xb7Gt+H}ohB4cw$a;Ra(q2j(7rKL0r-5}1Cxuj^(DamRp1`Zb;(L=g;X|XIV7qZ zqrBFjL^ADFP!&X`k}+gt*phU;?X2^p{Q!NzWjiQkW5Vh-foe$K?+OnPKY}#%>e(J( zlQS@ylYX?qqP(dzF@nR|r+{gdvpmR^R8V+tdAvV#$*P;A+NxJP*Bt8Dbjig0qBqg@ z6J#H5w{S#5EpDMx!+h9(YH)=VeRAUE%WPUJoi*}`cWWM2Uz9Qxl;qU>gx&X|=-5!; zkrpX6T-&TAP}Rb%8Ne8#dZ)sKNo=o4^MI&xo}geVM>`iv?!Uj}0cXO~NE+Dt;L^QF zEl4#j8Q*tFcsyvf5r%Bk+U^~AD=3EDi&5sLHi0n*)jKC#s|b(YxqhWA7kPT@;Dys4 zl4SUZw|r5KbfGr)MJWS6!PJz=&yKsinl!yl73XO^)KF*VCAT_%Fv(;N;Covh62Je} zCTXUwuZV%i|4Y#cs%uyCSB~*>5YJCr>k8{ehY1P|=}K;;$$a&|fqBOfrt7P^FPl|1 z483k^widTpyL&~VI}9mo0Lj-4X4{$|XWDQj(-HAIKQOm=?2gZO(omqt+C>A6VHOM` zv&dquD34*jn_bLnR@-)<=9DhTNPE59_THurfh)0H?x?Wx;yM!(v z&f$l6f@^U4qL;C4%2CRis%SB-EK%B?$bPCJV^zd#?h(8viuls12vb>m@3^@mql}*g zF>7B%aaI6KSPt*FaI~4cWcLAifS%z!h}JDfoeb6`)7~;KU$$&Btqx<8$XS3(@Z6*^ z0RP}?R#kx1PrHdKp1!}XSXt=~YTRj|X`KYXv)E0fL ztsI5_=d-_CPk)4IJd?Ir1=Tmi zb7<*7^gn1-Y`NWi?0vmaM$@w+XM*`Y1eQClD=UL8X({tJ3e(2IDtz11wvhZL``Gud zaE=3Ad<<|Ud#HnPrr|^*i?agskF#-QIuzj<;Wx}~YG_Ft+a@v8eV0pP!|4CUy}|&6 z197Eq9yP%-P})7X(a!E1zbH4%aiF|9Q_*!t3d*z)A49`Uh_8{Zu%fXCxemqIcMv zIJFH`e$qE;TaP`D`$;|r75;e~6SF{m_%3z&@4M8K#ESn!4z=L6H?e}U97ikab>0QfZzJk?fU-K`%etV4jQ&idllRG@& z+V){7=(;!_F-&`+#hhJKkfGN_t zizl8bt;*CXifAO6s;WVr=WoP7I;jMrc5U4V6XI2aUxg0R@K9AowSq5EJKA-3zJ!Xt z`fE*~N2ftU5kwIZOR?&HW(soGZ1R+_)1?+LmkxkjfvVa*=gEG6JNhgV)|N_H$%^WX zcLL%LUEr$2pF&pYG*T%n3ntQP%2{b&VXVkQOYZ!U`|Z}jJ0Pa6D&y@&SOl&1(?E#};3tSqxj7OedwTgOmw<{PIj8Nxa0g=@Z{#C$$-80(-t63%}7H+f>L-Tk4 zl6biiIamAm6869>PnMQb3kFQBr4YT1`XZ&a^tRK;V{E@N+aor^BUMTDx9L(7g89ny2)FsFn)ZC-vaB%jCU( zGz&u60v+=%m}KC6!X2ppXJq)oxx8z&PCZ&5Qx4p5tpG~DbG>3oLD{atPH*D{ZQlyJ1Gf>VaV*07z>@s7QxGn zG!ji|jGJH2F{*soP%riIZya`V+&UdoKVi;|1rBmLSwYFz2b%1EQCIAvC|~2)_1R7p{gtH&U7I96A86H{qASeZ0D=&lMUx1c z4JG)%Pgu-`;va~5Xwaxh_v$3yZOHP#B&7m%uiI$+WS1^E_bcTb)ajrxo(B=qA$xWC zk)F(RP-v0D1LNTkK0vGEur{eppvCpC&x<`JkG2R&h8)MYsnw zvo>J3`%5l6(!mRUuh~FQjy=Dn=bR!s>(_fEN06)_%&*9WEp- zuej*PlLM^-(IJ8gzCO-hNOslCS~uok!-?n*y{56AtJ!i7yszEc8fjj@sfHQ*p_2;QJWI3EwX z%exJyzwe|r*ce792pZ1hGnNjYkY{)U$*w_w^r=!w^neprlzhh!EU*0f9p8D?`Kj1>y1b#gw>54 z3F2$Av6P|$urz{|r~7t8^U-v@8T-;ZHMI> zxOAz|q#rk_%zde}cT^Yon*n*2fxZ>-yRhk;#bxa$!R)3Hr!pWo80KB41|7N-ud_exsS3@HL%$;snf`QJ#rv0`Li9j`t-^;6Bb}-;kC4fI`*H)NQS@ybljYz({#GAa;-Ps{9b_7RVRBjXI*Lm zJEg+U|0*?IH$5MH0RQnJN!ij!NO74>3N$KZFIPr*^^eV;{Ed2HWlL1|V(R7}0_VD=K<8pJ76PS-mA2vH2WGv?Q3VCZYBh%o? z=#&r``YAX6PI9hyli#<$Y1_fP&y~@=fiI&j>S0C;Up)J8>|%~EKiILf?U&&kxL6>h zWBikp>RdqHBYb<#(W4jsx!G=47N1>*EcHjXC7$Sh#0gR7arQ{#7Nt_n?UJlJ;s96}U;ku?s3 z!JC)n=8lzz1bnc6ynk}9rIif7_FQ!{I)*)ru|yxm-5A0LhXo;D%Yvb1id`r>ZWueM z1C}&+LArw5(o(_E?S2AV?X=K&2Gp@zIyKG=z!=9H z^@2ZrffI$=va=ZY^>7z#@~5XSz!m=CA9c8DRrQn9FRnq!^jed}g^wtm+nm!Wtl#TM z+oDMc*PKT$IxQsKgDF?S!6LC|FO{f;`JtiR)X(2ngCbuAC$;{YJ>PGgl&9|w&Z>MM z?9k8`lwke-H?``seY$aKboDC_(S@QG9==Cxc0ab=rRAfz2&v0TMF;av@ui8dgY~XR zl>dX=1pKG1qie6DLfmG%;QLXX+#i6AH|OBg{f@C2?AbM&=ze*V8p}4F5zGC+*-b09 zWrkSy(y>kl5}{Gh`(u|y6G(bYYKwZ&R19=F5MD@F1RmEt{r336Znx5JLrx%fw}#${ z%>+pXffY>Be!dlF{QxfEy>hL~1)ZRKoj#^ZUyNFDc(G0x&44jd!x5!0j7pQW*xg-^ zTz*#-Y5U&1e#S|&v5LTb&@fbOXnz&u&FHI6lv+P)=#&yP&AgU99g}3#%eRbY;t4$k zlwHC>;l5w@Yf)TZ%TxGd@zYtGmw!5en`}1(xD6bw?jlKr3Tzw!DK_g;{Y&xt_~tr7 zXOl(6pK97a3pY46i;F~UL9yJtd7tsTN`Ozzxz$zJY=$}4fFlz}H2Q0K4A{X~aBZIxCf@s}498j_n=6cg|SQISr)`Msc9?r1{IFtofdN21{?ze~=HccYBf zQ0lG5(Ox;D>SU31Ll~o@lzLL?ro{t0QDM(NKhq_G3*R@XPHC8PMVm;#1v~{jiVv}V z)}-*oWm8I3@M|8`=}z+}a7oQ^|4x?lXH?lRVR*>ZDdtlt_ao2acC6} ze?u;L+Zi)1g4Jk^fI74!-yFL=u=ML$&4*O+g(puOb8m69xeZ|XeTsxc<4ghzV}s^aiA~KWSsK837w0_;M_O$|q8Vu!EAQAgN%|Z0lXUnoblQt_4?KbpzTbUJyIR_-f=%suBic9t$`b^B> zncO@0yWn_GO{wxKkVu&pi)-W}s^G z${s2s-dO3ry-4?C=R{}|Th-?v3VfY6K;}w2LAfHJeD3pB$Zxm_q0$fJHbe!!FR?B4 z+w1>k`7oq)+bM(*1VMSOhxQumaJ zDS|uORJ{*qVNz2pkIWm7VD`2QLbXeJr+2r-i#|5XUUJVYX38X;L8c*n?uUR|5b`e( zK6umn$tc9+bT|A0wU0@Gh6T}>i>v8k8ekUDKWy!c>iX)%J{iH0aSj2tAXbb<@FLLC z!}z`mU@VFy=^QHiH z<&TlxPQRj4X;<+aGZp)KRnq5&(2rXY%;{WeCndw_Ku}WUasYZ{)aT`AewliQsvwS)to*K{`)?1}>3v1lKK z-z<-Sc0==4w>iz)X?&d!Z==3UpC2#kqSgkg01`u{M+(90!;#^MMMllP*2f;7#uwP} z(l6D7;L5-S|2SaI*6hSd5ltpA!!Ax)U=^FFu+zx6OY{egUW1$2NsF$WmCmJ)zvr?a zM!jlT3$mXYGtAzklo?IK_G6KK2H-%V>?y!uv;Ys59b1vIefm}9)VEV^VVkO804Z2V zccLP|M=Gg`?vCQh?EgD?H0uX`fT5bV82_3@a#<7}MNz&W_Cn<=(e{FT%L9Itj}sl5 zZBnXoG1sug0hsRyMW_Wxy%Y7tBZvC&ed^&8AMB@dwMolaJDc@VHKajtBTmE*CJpZ% z+k5&{4L5D<>xGP_WoeyR(6}@okzy%@`LQ;Ktt~1JV@ecu=q$Nf;f~HMdyyF)HN5>G z?-G52cMjYKp{)k>j4~W(z895W%Dtgj0n`{ar5mEs>cfnU-sxfUUU5weUwHmLZ0$Qd zYMGCGhcc$ND~IR%#A`L>3svtn;}g+3;@FV~ONF zoFD!%NYap@jOxOR{omdo-<^9(YRgpFPMyX2;y_>p@MS}8a>U~56*$_dpLFZ)`vwKQ zvh%6Z`O25)@VEs)3){{fTolk{KwQjk+r2%l%S@@%3mYB#)r0vN=ASj126{cz+aGkL zPP@ykxX8LKaNNPY;4=|3UOJO_BFPD8V|OyADIIVLzT!};QOSG==+i)>i1UorEubqV zkolN}rh@Z;6;GLeVG!zEit<`<;22l(TJ|1l5)2v0r*|)SOmKq0xd|eK@z1e`M^YHB zvF|)Oxmfu-*G$g}E$TcDgZ<4BK_XENl=X?fv15D~7-OE!KZ?w_CgEA|TYRh1RP1+* z#X0gHZ0c2^66f?=h5)1Ua=E@D0Y9?7RVki7@c2RlO{9I+a*G^g??_wxY{}x z=%20iQWCa#?NN-(*3#|MXgwwa1-)#vvi{i0|8ZC5BT+Ni^pNbWp~_;Tk;?nfpI>FO zdI>9HtV1Io=c2kloqDk;k$5QArX6-QQ^&X-ZBmR=1xkK;zRz0h{wGNOhFcx%&|S6F zGv@1l+sZfCl%%kL)9P}pJ9oSr6VJ)0PU`4^XG$7$9%s@CH2t{A9;?7ZNu_z@mHVt} zL?Bi-5^caPnI%WeeLyy>lm$q8Qhvm1s|v6u;o>O8RvTuDQ476Z*6--C!@TCYGSFv} z9(|uHC2^&-CcaNR`N$W+aOh`{$=Y+&B&zS*^RhMDmx5*RlVhe(2&UMQ1Y3f8JgaM) zn-$4~Da7RKtH;L2^la@FQ48xq>P$iST8M*0sRc(+NpXK-h<3l-`(rm&C4RB9{1(J> z#e)o*Pi0N1mp*m+g4<=(=u0JVnd1Jp{+v>Q5-ODb`QkTXcJTo4~U%t*W>o_}5YqXdVMG z>mKtv_c)>Y-G_$)mL+d}pEv!ZVS|sz;CM3}N;4S1uCR#E=88FtSNqWl25Syypdvup zw0j)*MO1%i1&?3q5Vw0_+jB8y!RdB$V%Ysm?)(WU;$I@10j}EoD8E5F;_j8bemE6$ z2-Y~E%Jmr~N zR<(k&yT$ZOp@I@-l|7>~heL{^I&U~Ay&2vVXbn2D(M&V*xWso7pue^?sG~nA#ggJyM{~`bjsN$;( zSsn$Yy;liJn@5f>TObQT0}t;mY~4&AxEImgOw4f^ta3aqsm%Hzu0Rrg+{8VBs0BfN zB1_QvY-)mG%f+F$FMV_7tTqwcF|beA0L`ScCQoyAp={vn{llld2R3xDpXcS~Agc?Y zS0LEY$b+&e#LjhllMBP(0y9~)b>TH?_I|%!&*$UHYh!K6(0og>+H!^I*_?K#2K}V3|2S?BLRFN9P<9!fM(hD& zNu2p#@(CBq^kw7|WM%*CIB4&DiLGz4b%yQ!8z%^F-x;n4w!adj${a}Yk>{{aJ7E9< zMN#$B)gh#P_6~deHD#d?v4t1%Sg6O}zl8CBW=GxY>nr0rd=vlq(8FSXb)kGWy2*7P zPwJ`RH3?WE8Ln(e5IjP&L6{^1i-jF8|Gt4DS0bv%PlsS!2GL z-~+H+uEoSJ_12bUqB8e#DYdibNaaEB#I?1`Qp{VGt5r3o(T~k4nQ}=gP z-PC*}O#A&U@?r_#A*<4w;7R!HS`dD7xA#Vd_pRsC4C{NbMNLF zN}#~XX{O=NTiCfQjI~~{#c>Aop-ayQy&9P|cO%pwLXwr03_5>_j+IeXH7}}uCzdOx z^Jxi48A=F8FNvzmCpY=;7zW9&HP;1P#MWU*cZ8=YJBw&CZmaH-Twp2V#yt zYD|BpGd^stRY@Esr3&@eRd9DiDJH7^`8L(??e5)G_^O-JDo;JUU&61b$2^}kvEbV@ zeQ}ko=NBhE3wLMN7%T?LXQ9>%n`wQNGh-p#Wj+ez?wtYQWV+D#YQcPXlxax<{&yZg z=&832$MHNnJfvmO42unGndeH5nQrNtyjuDQAWK8u7h;#Umi_G3Gt(o%ZB~UOzF|Xq zPF`9>xB()F-FiZ2!^ne+H>g=T%bfI;Fw2UX6MyQZhE}KC1VVc(i3cjnEkd^;M1ok|bQn zWbRFJUDofPsq~4Z_BYE}wI@)&4ppS_KoTmz-3S zwhAwv3HPW68FG3_LNuFuy)p5gFIWH2{ZZvs4KwylV8zAjafT5}lxsHtZy>bmr zJAVGuD?W(&lCaCG1>U@!(2p_3)WqTU8Jag~MyMvkzhXs*$%C=C6m?X`|`S%qPt6qSTA6@7w(N8?_7MMt;*8lY5@^kilBsV z(S+VBG{_L|aN6@!#f5QhK_TJg-M@iTF6ddHo3X%FaA3`NB`gR`-c;MAU{2jWgt{LI z(NQZAu!e_Ohijog{zYH2v0=o)?xCwm9T5}HJ?t|jgIm_WZ%h`g-gY%Lt#L92^)QvrAwtL@7Pt zkv)C>*{6%E+fzn$B_BI$CQHTmA-a5V1s78Oc=&0%nKuB6lp+5%Law^JFK4B*al=Bu z^_h)L4rfjDiV+0mRo(rI_{$IDK6wjNbUn(%Bd=svzdT%?D5qEi6gv+RcMSQQ|7CT1 zJXGIITtYj8_aY5;b1r7D|2uO!DLc&H#F+iPLZ>rrztrn@@La6c7gC}^Bpq34RPn*Z zN5Of@_-LQvm;1i00h0k}yU6dfO7+?t>?leONY35(k#CPFpuNJ1n^%vAWnqS?N;Bw+ zN$zHLz5i|tj(1SI{l#}y;kO0vBL4=g+WiUBiw02da&On-1CK290j$Lwg=nKaj#t2$ zsT=cm5w{+Ke1juCxS;Q)V?x*3p%9Wme~`6`zxuFHv5Rs_pV1O5FT1!h<=oy*BT(g` z{%ie0EtB-%#IjdM&*M7ust=S5jVW=fSk=4+dUUY$0cFGMg%Kp&dtxQr?D>2uhn-2I z6Xk?l3n>l>TOQFKBQ#-{(le>es^MIt{Sza>Q?3!~CnJBO6{)*%`r;;0nW0oE zrLGrO=yKg2-jJj|who&?TTS654KTjvnb&!fNzZ!LejfksjmLyfc>bI`Sf;+jMSU4P z23co2Yb=o+r#Z#}|13&>tmF&oS>Hb3qt$nH2Sxw#-&fnl=SE%gnP)TArutuCzeHqu zM*SpbDdQ>*(O9nf>xRHXJFK+E%_jtBWcY3m{XO&(dqB^}v&laSAq#RMZiMi)>CC-B zGyZa_Q@N5?Yv>tVM@76$q_)sQlzmU=>uZqS@$Av!J;QJ{s0+p(U3BZJg3n<_HPyN|=CW+n?^an` zyYqPL!)-R!`fMQ7>==w0yL%)T#Ar2M>bn{3^l1QA!GYvWYg+VXqt-2b&{g(Pq;YjpD@cW z_Kw#~d2qWfdQR`3i}*V82lKq>#V=X=kUMZ@{3AuWhL+}(HD9U@!>n^_!%X{A+A#L++R0H=l#Zv!*UrfINgGH>?ay-=Hk8a>AWZDaAjjlFT3`BpaH~IfQtNSd7N>TJT|qHBRvlJMOwfrvwMynnJ`C+RSo3YF zRr^R49Iu}?4gV9c)MCfKn9YuNijq&x zeNoGL9kO?2)84Vbq)>Ls6~09*$3lhhEM!h{gKaA;G8Flv5Z`FQfvqV5jlKdtF(`Pi z`k@FFE`VjMk`sToZap{q;zA?)jQxN{|wiw0cfX7x>10+v71s+hBM z4vAskBv#?UVuY67IU|4j=YtqQA?lPWA)n!#C;O@1(ahC-{YqqSV1R<0>1pZ9`Gl>8hMF+hrbY43RR}dCMxyO0RC;%(d9DxC8rcJtwpM2LMwX zsVE{DFd0g$>asjXN9SgXICVBFvH87-OK~e*nnIt`m?M1FX*_q^U%O4QYi7|xYrXd| z3oSK-)wGH)%)I!wB{X0HLEIg{h zfjBljRJd1Pwf7E$dyfDL0DY?Sj50$)jJhFjhK?z8a%}p9FEXyadS`TO{rRLvU2PRj zB`G(LE_m~w#6y^HNl>T~LQwoQtt}(nn(3=0Tq&QKw`3!CPb(eK5tBaEeJ5zoiY?X{ zi$qlmj8TH``)<#)uJCx=y8R@bJH=D%`n3>Yb+Mw7l8)K0^R3jx+yPmNQRhXorn55V=dM4j~Sf782e+DR5gcG z@T5X!V>vo~X+D0sw-+@_$;6#0>8nj+D_pfaYz4Z8=P%UW)Mc3AJ(0?oz~GmkP&gL+ z{uT$}xd$MbFk>fUu2A5H-%MWn7oF&bVQ$i`td9t>;@cFeeXorXNoASNeAPopPAR(X zZRnY;l&Q+crn=c5rd@P#9U*?*q@p77o%F7vGJh+uE0%+m@?2kK2H{M*`s<|!|4YcxsJ>eFqT5%eSeRY-b+m@?oK`_=nrH0n z)4ATtRPdv==b|Ri$x=cvVi9Lt5AwV1MTHbO{(NIPsj=AN8kv__S|0sljlmrMXLSTd z;<(0uSTGKfOXqd4MZ`pk+$ZOmFvSn0tEn7Rs(Cy~nZA2S7*;FFH@WC*;>kkE5i5QE zIWNoj_f$ea7ja!1OB3Y%rl3M1`1GcUnWp<~O6e<&&Q0`t%UraX4^fS(gk3?$e^q2i zP6=#2ww*}K7STG2aiVoE91QYbzy9|h;bC5N@u$DnrtH#oO zV;&j285Rg`oEhYfElkvUSM1U+m5CAB78 z?=gOd9@5u}L~z~TQ;cV+R8__5F{E&}LeYs4S{w{<>ogKJS0db=8YU!r%H)@CAqP%r zRh{EGp~`rk(SN#4_OoOnOg-Ing!cme{J<3PI^u@datUiDRFxRZG>^%LtJhcxU6i55 zl*kTbxqmab@1FKi_t$p^>%o28{@ozHdEm}J0h<3H=Fek#DK~P4Fl$e%$$fSy=DW#r zAEQkdwYs|~C8MbApfaoFS4CcNjU~QQ7~iq{vR{pje0-YOa%q_;kDY1u)r%onye*j0 z_d(VZhLWDp_9Dk}0Rw9cHhp)L(g^5SGAky>nkf3Ej(D>DXIEpc6Kt~rdbX_|h&ThP zlB#{mB680Vz@96#?=Vp_FdZB&N{q{nsatCQQk!`h_(zI}KcYw<7y$|nf z5Cb`ZoR80_>VRbf68ADxZdl>n6=z%lEJ!guo{M7lrxwNGDy zouJ^ece?gidnm5`?vNSoGv%G&8y)4i(x&6#qCPEmLeR4B$*%!ZkU!Sfgw43XS9(T> z)uZT^H3>*d*NLMbTEeSOeG8u%qZ>REZxxT9h0fzWVXThdY<#)b%Qw z)?>%Bekz#U=!7I28UD@6?zOy!PhPlo{A5n4-%KhJcQRp=;1Av^u9>X(_0(DhzA|r7 z_;b;Ii5bnP@a~)4b*mAaftLik*DZ_xijP7Lg(E^szCA&g)4kufy6#@c&fRdfkH3oW zBJVr|bA<}V3jBg~!sw_IGXJiBdHmuygL+K#1}DcS)6rF$M37%2INjUjQ{C zIB6R5<}BQCkrqYV2&5&|!oU?bS)!Yp_LuV>d z_lUoyr4c3P{0uWgF1_xPK16;omvpN1J`9~!6`{R;8fTlqrZqNxsi#rgnlyc^Qa;A=JdZ^IT)+TP$wLfj)T?YIzLhf8g$i*D$1=@B{bO+li@kqaA{ix@OmX6-9(%n4pNdiMkJA<3E|b)(FJ_cm{JV&Xd5qS#u7;4pVnWC-#)#OdE6Xv{Q1&Jp#~>D;^<6slvO9KqUim9 z5*AtPX2J=7ywBF{AO%fxL=Cn&8k#npR}GmbL;^I~0P37jaA{Z!?8 zUJLC>OySCv!u>~?S>-zzQGf7ex6^@G4 zpae|d6n>XUIJ@9L9j!E@qjMj81`E!gJ`g5P08cPz<&9kdxUf(|ZZzuC&@Z=S!-`~M z0Hxrsl26-}n;g%x29{^ReMA>(tz@_tsSIU+9F2~cFhPYcdrG$xPyc9d%&d3qeY~zL z?gY>TLI;cd*Pa?>S=SwGc||>USkUUU;T!SexN2!4PMpRH$&cj5N4 zb&$>tm@c>H6W8d(ao2gyM<9$aJh?P5S9B-&PIvat2klvZ`n&5;Eh91x zyy&zsW}mZL4O?Zsa0^cp0 zbSb6Pbx+q)Px8;|`?E=mA=`Cs$7Pya!)QKOfB8Cs6oV6T-}-k5yCcj)(Jk|vvFS}M zjVoV>GE10w7-|E-HM!)_^E;-GIJu$y;DmFZA8fPd*~gjau?VtL=}%??f21)Cr*_8X z!x#E{)R!S9$Q5XY?*iGf^KpR?s>LWZEj>b1Nk+ga6%!}le^Ix{?75qeby8S3nQ6z> z3;Hy9F5${41% z)irz#oFu;dGaPYJ7^5!oQ9G))-HaVEs!8CwcW+rkoPRtu5QcXR9r{Gl*-xzww|eyO z{|r$`8FC)2OeXRR_EWX%QrT3e+|2_5*xHj$xiZ@KeYA+uNG$i zXsKBBnz8z(UYxWbsGGHj50~CzJMW6;Aa`P;zPM^fw>8%7l{4uQPj`P~+hF=|rT|>s z0i{`!(F(YezkRc74p;;7JCyn?p?UqwMCRS%3Ox1jlt= zRd-B8JkVBc!B=s5Uw*5MJm9)3=D*jAR6zvn-RQ=e$8y-@r*R=5eA zj8g0#js+!Y$>@zh5b#Fv#^3va-G)o3*?NEog$O($e|MIijF0wbt6ibJ{xZVuw&8Gl zjdcfH1k^T$j#kRr2kPFo#9Yk^ka&4vQOcAj7ifl zE*G*Q;SuhA0Hm@8yFlsU27Nb(^H^6)e05IpTck&VMks!^fBg#XA7}+pHHLbYI;EJA zJzP~=d;76g4aq*Wb<(oC`C*0A$T9STtTp;6M6|!uYQa^fB1CJ{FlmCc#r&^i4?hG9T!cRc;iE zy6J`Iyl9w2<{78G-OI|$+A|X`1yhWzRQv7Q+No*Qod$~mAASbI{t=5$cC3AF?)_NR zTa*}0{?HV$XI1!W74wMS-{=c#Nk$=_nYKRRCq$&%Gj~SrUes#wkIHG}x%l*N(Glc} zZ|=QAGXyZ`1@pj&+nWW)ivFnGKfXL~#lOy;d&>)KdKkw? zeu;AE#g_;CA+82xAd7EdJHV^b%v8mu_dD^DUJFQ7Q;HA1+=5TDgrDc*DVqzM*uV24 z$lS0NXkl^_{xc^p%s#=A_iMfd2s?QeaY0>EY66o5WN)~AGE~TVJxVOWWU2MvlT9`H z6HWZiVN~EYu-iJG**~;XN2a=<8y8*Xs>_}rml2TRQcP1sC?%BN)ejVoxVyn>2lX!- zXvcx%tveu-gGh&M)Jin5_zV%pUUXNr!$q2-PT?<27Kc?{ea6P$BTwo&sU+Icz2vTx7B6 z#+CF*y1y)Fz#e$>JwJ}S;M=e|<)YWb)o(%^;pOHx!(l>4D-60c^ewrTt%v57#C)O3 z@zvhZcqv#d_Kx~)zjOO9fmpM7rx2RtGE?4()`C}r-nRs7U-Z2){FIYF=|~3U6`rx# z>c}4Pp@ELj1EQw;11ay8et8=(AEjWs7HyzJ9<$})cBdA3f!B5=bSvecfGqkPOdPZa z6@cfwSs5-5A5zi}2cL<>eb;fPvz(Q&(Z?1c8+Hbj%T|??J4fg0o6biH=&BwZN55AzaBB# zVp8{may$S(IhGnCqv~AqL$zWNd%aT`8h?zw-U&Wy@|Upo2@sPP0qd$OrbG zLBV4rJRFK$%ZjFSxbTX5hZK83EcW=|8--&QS|ppy&4E3(Hw^T)d)2-EhB&~R_)zz$ z&EZIcU%7A^fIrQ+2M52|~FC&;=97g3+M=Zja;$f}o9RKTx3$JzTR zFpJs{a3iNTtEmNf+ts}H@wMaKvNjPl?KgPUQKFO`HR4`l#2tyt#57Y7IgfYj&%NNi z_hQsBxoUd)u;D2m;`iDUv*ac@9|O5N zx-G6IF|bh__~4zPF_)+|XT5wiZ6*x^XPg4K;beCZ5fH+gD%<5mKQ)Zw=*_+Cer{?W)!=Q(b|c-~b0~eByLI#c{gm@0 zd3U!^UQ~^Dhb7^a43UF<{xBa~3%4UresOF|n|E?PsnI!o4dEQMSCNX4t?^TY8EnqiYRR;-vNmepW<=EalT^X)&w9NCQleoyF?Nb7 zgeOZPBlbWhdB?ZhG{itIu{BGg^vNhvh0=5_@?g?o=}qf*-m;N9AWVx`t)@%;tPmtdDT!ZdM|gZ0%Y0vny=o264NRQYwR5k@rhLRxoO;coDEZ9N@?-^K7IsB*!Md zuyTZ#_!7N1^U1e+(xKtagalb)D`j5LgH!@oKO>xF)g}lJmx-`uXhx6ac>WOakdJo% z{ZC>8#dzINs10SZkuuOhc8&{{FG2>Tg%f48Z%xl|H;fp|Qo>jRCB(z4rD-ouS>s;j;FVlU##Q z_1YNwUjIs387k=IK7jU5*UX^hD~4)a>`>*(TwUqvfT4p@#Gm#7ym)q6EMgayJ=Ba) z_jXAb+|gtVsGu2p=KrX-vG>#sJ$gk3nXoT?ak8EG=7A2_+Em^05{a~gjMZ}J)CopL zQ&kr>Anr9zCLyO@-@!%+u2g~obHXlYvPB>m(WqI}fKOJ166g)~?3Kq{Up(urVN|hs zZkxd6e0b<$EbX-$HikotiIn$oR4?SCE$i$1gEbZgzJw~MRBR@{)nGn13{?AA_eNj# zoU;K@y99BubM!K1{Qawg+)qcv;k<;<+&=)bmI!hNFm6lu2q-f2hsrzQc2f+7d;oR{CNvG!k)ugRQ_ zXIa}tpDWiBk!*Bq#+5qDZ;OF%VpiF;Bma9aQ9H3G)&(w*JAdwsud9A}=vc-n(@QG_ zmG~!OO;M^?b%rX-gEbeMHB&W{Mlr8Z29`?W?YG6>z@ATR#wU&*sDr%ui%lW(zFQZB z1s~~%r}kZnb%ds3mk)r%dN`q76l%7jeB8_lp$1^BC=(sFs*{=${m*y&@kJf+$O>{B z-yb?mZ-GBUm(M>n7y+I*xuj~GS>Yaymp2yMOVw^&wO(9^)A|<;{_kT}oYz-tF#T!f_pLt9%%ZYKq4eQ4l zLV59US#953Fq4dKabN##Li7>nJr!?_7kKgdQdk|bkjwm>)>HEWiwB`veIFqgUKY7 z@ql4_&aZ|K7lW+NPN%3^-gzEyQfiAj4B2n^I54N}*wu)ntGT{LF8}P|bOJF^v9oCR zFTA%iu-_nns@wvsMlNc*fD#8|4j8?6Xr7*EQ-6*4m#?5<=K|(d3(4ci)>|jut=rc? z8i@UxtEc;dedz(iy&Xv5Qs8$Hz*dFK1cmtwCz&LJzM@91+*Q;03`j=u^7BGkXNQXAKX zR&33lf8~e73zb-=;n#UCCGUfgJH9I(bG)6bR24ceKsFQB)O3-&J2Vd34R=vgD&B~L zcLwQg1bKGA569B#PfC`t{2cKHJ}N$0@6#Ud67fa+T;u6SXiI+*Zh%=h>8XfRCE}3Pi@zY4P)RP8nO1Z{#KrL$Nb6 zH)vx-s$FD6OOL*9Exf4?2liC0KIWM9Dgz!DCN4!DigT#FmFoj<$%W^OXsj;3o04fHuTd;ax{UI%oQ<5Th(+`%!LKOVsd|~K< zWzKhHnH_KbEM|E#tMP;-S`re5v*)1&=1>BXWzxk`Qpt_Z)Jh8^i$nJ>8wLtAAT
}`)`RaFBIvbfW!@1@6R7WcZU@lH(XGq9mIm2WxWY15XXqbn`- zBpC;+-~82nobB$B;WWvP&9vSyK6-)YbrY)iUm?B*v`N@qD$Cn?w*JDczX^BD?Yhcl%?UD+f`;p^lD_oswq5Dg?U7QMoI zHv>8S|SKyv@@eL1o7`4lR*x@M+J2_0%AX^)roi4efpT1#cBDzfv(m( z85Up^2FYMqO9*yxYBFB))7z@=)p|^@R3*}D}TTznUUDY3=+HW2(Jq$Uldtgz5qAoBFfS1laFmKx0SNvx5<^jiV z5Mb{&LHv$J!gFmtrnzh2oSP?-aaQ5ocdj^MX8Md8oVEMI@XV+T-?zxIx$RBR`w>)k zF^r1qPVbEcVuS`iV71s=39=VNp zVs^gwhdx43*Sr;LQ?6mQHYP!CvxZWr+pj5T8OLN?if`T(CB z#DFIPOTUMy>q<-H=Gwl^A4gqZ2X&5B-L_((3 zuB3u6(Q9V(AQb<>8#5*y#6@09yYvF@-M)hkloXCM2AH$GA% z_O?Od&MfD!Z!FUru_ZX~PmuD6Y@2U{AdAWBk&W%mer$Il_7yWRR86bv zc_7lpc0>}Ei8nBz18aI=^Ahc;W8_=Dl@s44pMCCAdxVL{kKmIGOAvx}4KW zCr|G>Rbs|r9@M>@hJqLJ+Lssqw^3skP?4Bj69~6P>kadO!e7GJ&-}hb*`?j#=e@ep7 zBi;ySdX_5dps2AnqkgfmY6SX{y8EK_;e<;8eWy-xM#;hWXjfIf=QD$LbTfWCr=hev z>96aeZIh*5ZJx965|qh%;fM|V>bfPu6IBzlVy}?+U8yIjH<68gir#?q5;@YFnUx#T znU3A=Y3?!a?mP-#jeuJ28;f;Bad>aE!jN!#L!Y6Q9(0jJsn>p^V)*{jSE;b)K* zd0zB9QdW2k?7+;p(^(C{CEoepJ3o-G{nn!7z$Zr=avEtnD0|qj&E!PH9>@qAEc3}^ zVBS#1xBkz<|NO{R3*_0~{0u>Rw-^aEu~)cA67wcj-vr>5KtQC{Vqk}J@4S-Gq@SvCE`URn!=|3wR89ZZRScih zzuQJHAOS6H3rbmt!0J)_^M=qqj&A`amM+-4wXh}95Ms7ItTXNVz;Jy-n^M2a{3|jo zTwD?S;kRaRtP#H&HxurWPkh_|6km9V&{U0?{I)a{cTiVq-%=q%rQvl$`Ecxm_mYp9 zykhX64UUX{if(I5lpUh4?jFG^(6jbK5rrbs<=VTtvJX~u#HIEOWZ4SW`2AGYMoL|m z5OcpS3Z{*E66uGV zR{@}^ZJ}j|TKiAFVJCGDm}(vWP+WMZ8=zsvH`G8|CewWGzFao>ul5fE!d7IQfoi5J zd}EWWpy^F^Jg+#@m>zjjL|jfpx^#W0tMtzMS$3AWza?BRu?ob)e5otz0Z~MB+uy-M z?oT7KrYBdf!JP%9PdKR@pH%e?)jSd*SbM8^F>Nh)4&S$!H5_JVRG$Z-FY}D!z-RYN z+~^By?yp`{*2{;_M2AlHmEzs-io!^~IQ|m^=iHdf6&2)kWOxr1)5|#NYiF(k<`>{I zgO21RgOzIj-tCc-vk!%BphG2K$Z$fDDUJ733u!S%oZuTbxm;YoS?f1_<*XQ<7_>G@ z&P6*iAWu6;bxWlnXFD2yTh?rK>2sDbpZLsV4^T`<=JMGPRVUsKp zSBC|wg6-Z!Q4hNM{gaEw21vc?@%kMc0#Y68hQ*2+&eC^;N9cYA3p+}=L2j?j`QZGzZ3QyJW5b!|f`CsV_WZ=l43C7vGw-9j;(N z+EE-6?~xX7C743yY0I$a3ApgFTEk3ZL5z4r#k%K2M)*t1g&4DT9C*z(|C4crpPI|c z?F!1l`}AuvM%TOo>W(F8gGHMmI~9tpp${&ulN=WvB-cz6P5&l=}>JC(OLjXd{zdgVgO zqmm5bV`e%~jLb`8oo3g9uDA|e-6x{lBJH8KP0cW;oPy&xawn|MPg-49-Gv#+NGq{9 zsSdUKUE&_BOnUIsQfu^?oyWG%mRb@U`lz*#Lv};{6N5q-7lc(te7vp6N%iIFEcM^_ zggPWA@49qn*WpncSeBM(_{JzqZi=ej=S$IJv;9jB5!=h2Y~M^uNy1If&QVp3p5-{t z)TJeHeIt8t^&2*c|8)))2dhW%@Z#N7MbyLUr|aryUtTgsnJcx1p68?3ezhzau!W>u zHe%p~6jT5#O{drGZ@ScTLz0pgCJ*a*CH>4>VUf*g`@X0zqel|9wq|DwDG|TLhWw7l zcpp>L)d=mcN|CZXCdQu_@0>EO-d#tr{zd#F?tv>&dVsa!YOcIPaS(0(&Asnr-d+iiCbaB#^T6MJQ%(172wSCu_3IB(Sp80%;vs) z_N%B2S|HbzpXWn^>mZb%+m)sP%SKhDU{2)MWTbFiXt4&wjkvQ=us<861`?HjZ_*D9 z9KO=1r!}NmCYJM|Rm1>KA{$(_gh$llQ!lMPOozr5UwK46B_;d7Jj7PqZ-kJ%&3n>x z!P;Z)Tj)jcna5S8`Yuwj!L*Io^0~&*p)cLlxt|;^9qQ2tSWn*f$5XgOB8nx%U((Qk z4l2ZQ=;GZ;3)7fYKZ7AG91$V{>fRZ9Z+?it8`9@bfCOYdou250^_r?!MZ48B#{V9` z7n3kh-~pj&*BzcGsCbt96|=iWCAeqh6fP{#)pk9=>pux~v2NrKu_hD(785z0zJh}D z`zy)+4o)OI2}=hRHD$`+AG>%p*Z{m-kv)l3|-IUw5*ZHpm_b1$EC)K!^ppi&76VWiwG%ALCH$0QmxS z*q|6t567%=EFeelpmy&kwpx|!Y=^y)n1nC=pl*VaIc-ceNWkV?wbg0(_@1sBJdDq@ zEv@?|<#EPfQ(r^Fiwi^d>7L#dj&(S=r}C2(kHwC;&*S5)=gqEW4=>HHm%Mgs#P0}( zZS_$U@9#9crmOSoMzf3d6m76Wu)wdHOe&h>@1_z9FG;<2_YVF~LI(hxskGv7+E?a~ zF-P|`KNGi`U(ZSeYFVG_v6c00#|fn^S)(5sICkCCJ(esu^L0}ug6tcj_nK|~%;t`Ij%lE4JpT+GEoCAV?v{tmZ_&)%|L*+ZLo zU;9reHJ}jOsLg^%6^2;JC_OL#l>Qg|I8kI&gi4@EssHgx_x+Wr*3#qF{*Hj+lMYdN z$ONGre;>A^F}2(Rqo8O;(0jgQ4HvwE?+iMHkQ92B)^xYNKi5_DO=}!4&u)kx^GM`d zvG7gI?WWvia(s!-rK(Pn?@-$`Qgj0eL&lUBk)|LXb+QJk^Q~<13)n*34CIDS3k!Pzbfa)H@;3;5drVbP(-`wg5AQ+F zVAW_UOZa%Cgdak40L7kHoVpJsS;d)r*YV%idT0rr>*Ihk;?Kuc1@Ud-^K{o`4&NqT zxl}6a+FD`a@_xQ9<=;NsImHan)A&4M=N>4HLB)#7fTTQwO=zWUB@R}v`rYcjyWpP< z|2t$8*jG27)Fpff+HI^za%vCc%&dQFEmW;?*XT)@2`1x5a2MWlUUJ$_rNYGx9rV~U zMr;~EdyVt_YzWq)+u?bmDY75&ZEh=^5*hk-Mmrk?ojdk~zsD-q^u?bb06_NDwxb)3 z1WG7>$zsa=JR|Nmi3k*2og&YnB2Ra83hxHbZvgS`hK=;5$1hBQZpGdrA5)f>uR)ByJbX;)?&T8?PPU#7TM=O=)A;RibzPgjz4dw* zm<4F6WkK&bzUHZq2iG<2?)^#8LU*SZuwe@HzyR+*Ru8N{$R!@%^aqcK8XD@q(}A`c z*|2=rGo)m8aQuE`8Ve0N&`8?v9DoeB_`00MJf4R+1v0mOGvPl*vfDs+_R6Bq&kf zSbQbK!xbX6ywz2BS&1Q@KrTXQZ8-G`=D3Em=3S)SX3eObX&l|Su2~R_eAO->tlk#m zUt98xFZM5e1&<~?31@~0!T84It(&(>@3t4*`7I3K=&9?)Eitz`%Bf!Yd3_*RTqtBW zv`)0OXil6bN}U`H5T^_oLb+qaA%zw;n|XzAlph+B_XXjCdgwXRQS-lBp+t+Wc2Mw) z(ru!hjjdXGOA7GaEnGR}aEd;EiFB85-H}uY3J9}Qc~gjkJwb7(A@_!c1@?BamXKY7 z;w19}$6lADaZm~)7N4g44brnxScz6YUxB~)O7wkgA`i($vyo9RnpmVLq>Rw4$UDux z^kb9#o>#yr)=m9Kcuqv59>8kZ@K#!waZ0XxsV`^RXAG}shO9`*tGVnbyMjifD~w6R0I$Y_)0m~LgpCOkgwlp$GwVQ`f+8g*eRRe zxX=Qhwaw&5;zK?Ujp{zZJ@wB%&_wiReu5L~LK372=$nIi41>+s({rm49J@n|GQ@M%y{jJ-9L_#cj1$}=haV( zaw9jn3{TbNH!~SW=~w!7vIjA_18BlBohs$X$IK7@Cn0#lm*Sp?G{!fbb?|cZ<&p-M z){jukjb`2)?=+k0CbTwu7-FY2N5F5^@yPL1)xiPiP9y9X_T}1;=m5HrdKF5J;5mt< z>7NPDtMvle{>oU=Reh(7>(oBgP?YQGehUth(E)yoMJq{uideDIRF8yt^((SnTo(ib zq1N(YDFfT4g%0dE%DGS9;RHjpv8CKZJXRMOZj8RX0NVxJ6y3f_87#TWL+i-BUpzoJ zQ9JH7sDC!|k0o>8dl`TC-MiOBzYVaIj+0GKx@_}YHa2RouY)@+JaESDo!FyhyFhFddr58pN3pH13pAHya#pFv4E|2!V(Ut}e?hwneuOH>EZQF4(d{+?D7L>tOvxTr9+36-mS zd~R&L9iVO#|C88msFxU`F7;ciEK0dh{j^zcbL~&FJ~vljqQb&#>$~k+sal#xDE~0g|IpG;GhtwDZM})P`%lz_z9}ub&~w z7Af56v%dPo{jc^^1ndI;(Av|m)PjP)4+h?yEF)(W-w~o|6aZd74n}Q+{G8D8E~Dqh zorWEq1Lo0sxC1~P7d9n^_0n7X6Va;IR+^0fudf}kFXGd;@YTWHU7%j|<*`Uzq1SJ! z&a}Eocd>z*CS!wKvZ%M`W4(*Y_TTW7 z^M>whU*_$qP{j{qqJjJdchxBQcx+2xfuEc)#pScw!&amc_Oq#lSZU)_h~htQ^2@4w zT4)USl_CsE7Pki~EVKzmZvW^gwD?&=PMthXHElq)Rv3=JST+RNy6QN#m6H=!cY_+Q zD_^pG`CEz2;#pqv8c+abUSrQ&^*V+VckZE+rx?_0qJkyWp-erSUPGCG>J(<8tze~r z&CVg>s)aW5+n=Bh5k6m7-(NIk^-kK;U|he*1J<0cFAt)RUQvhP+|IEh|4G=$s}4M}!E#o`C#H?PUXE1FK8$MHGs;Qr zpmo!HTKa^lYI7iOP9@6QFL`X%`R+1D86jG;*I~otu^+yxusO|M9UfyM!6Dp0hv_M` zh|hg7g^1T<`KO($KmX)BoPD`ruN(8ANx(CK;a~U$g9#{(L)`Y4|J`2-F3fli+8o&_2)u?H*3qGkA^xn%vpweRoeU# z1e?nR1Y=j-NpKM;u}v2-S=)MLL&O#gN_^@ z&JLnuh;aXDxsn21QXw|~Ul)swn8&vRCRzwLu_eZwy@<`76oiTGCrIJn+8^vunYjNX zaNYQB|GtL8F|8Pkz z=7CKAe;n6WrE(>qT&rA_P`S@_NfN8%W=kr^u-wB~x$hNU6rqwU$8z8ImBi#48)lm$ z_m*pReSe?d-}c8g`@G+;*X#LsJ)e5DMd@F^-n)B2IW5Vx4y|tY7>RKNq~pCXK1Z{} zM6*tXD!Xs*yc_$v_@LB6bkR44$=)DvC$nmQJM<)YRj9B_0Zt{{-zDOZjP6hvA4{K$ zhgC1;wEbH`HiHQ>Qr-4f>!1BK{eb6;AeU2ghC;D1ncc9Hs8FwBMkuv*qM)H+TV>t( z+6&<1T?^MksnCkzl$sdt3K)Fmc|FYTu-v&z?+33GCFPaB0=Sf`4D5Vf&l@s^AlxVE zDJj--cM>7=U1@!K>&*y$3@p6PJ%fbwFvDguW67f%0dJOu&_bW{W~c?um{=nTnoxTH zUCNqK)sz!UWX~#6D2(7ok{es1eI!u0=xT^0+r4&t!@EK_)ym)_*NU$b8=JouG77GC z=%LT@m!3a;0J{#>YlQ%l@l)aQZcO8hQt|X6NB25)N<5NA-5M)?Ic?!iPtwE?|Q_%VQssW7_ck$z^e+)G1xT|lt9ZYAq| zSm_rIpka98k^t9*w#%!JP^K3E9wCtHXfNG*fe9qm|5hCFKTBcVZT0%(@f?>OdVGVu zrA%nVf!H@D08u*hU%H%`QKTp8x6f1XM0i*Tei*9wyS?ekK<82UcbW**i@iZaq-d@*n@(~t#CJkM4?`%(u@!+u4D zm9%e!&exB&DKI|S75+O1%`@QLhe8Z5WW`STxu6jR&jtJd zQdO^%&P69dPtjc2a-ie>rOu^}d5mJCU*M1<&fz~v;=3wR^V_rBNu!HRb(OVvzP&K+ zCop8V5t5j9#^?-^x>r<3gBH<7$82jdgjib|!IUNv;nN=<2IT&vrW(Joj|&`} zc9oao$lYC{aTeN{XJEN5R`Lht%XOBJL z2ko8TI_4$|8m%yiL8<#60sf!&B=$+GYY1Z+G@aa>UqoUXN+u8e-p-mg&$|e0C@x#q z3`spO4|D=Qq)kKx;=!w`irE|(`Qch6QmX4(B04@KFulal)aZ(9a4ZwsSE~sVMulct z0sf;M8IzU_-zC!i9=%QyT=1xNtVNK9@8vTe3;3|JR@%+Y*4>2PwgKGjz?~c5WB?M2 z8ztfVSR|F#a+sSO_|dg4^pUT$a#C`O{5GeF>nzRm6h|YOE@JTRHJXIoZ&0tS3e7-E zG9@R~G&9qvuX+a|!X37cN%x_?pgGoMRpb^<{y2Me)R-tXFtbA9tKrv&=l$bh=2PK> zE+5$f+8hV2nAq?;`ZJbE^EgVKoM^~rlHw=cSVW}0hc6+BiNKyj&4fQDN>+$>Fa=%H z{T#gb&S>|qQWQ!(R4M7@_jbo9X`h+SBzfME^=y5`Ky_jc>&J?ON8_epJZ)aO38V|ex%g>=X3UvTQk3o zKh+1-SWpYwXLdp2%*D58#6U`9*(Cr_x6HI5?e>{C5 zg*o$`)MSWx-FSX2n7%dFTH4umo`y}%ujK;aGSl9}*TZq#dN%yqXq%LKNK9)nlCN75 zJB_FX&jBK3z+WbJsmTjWw#X#wZlE;3G3?i@nv&389TQX1o;C541Q_i%svAc~>#op-4DYG!}&y3Ki z7~%94DBKUAddhz#f?~I{V$Z*tt%l)%NBYm7Ht8{wGrT@J9ShV?8%!TNf9urMklYXM zFRQ+zri%UQV?2FLWNTQUWSb^bX|daJ3j7jf!QA`XoeqpIl4;Y1GUBuusWDv5^b9w8 zQ@&DzJoco*k`oNjTIIc@V2a4X(GjJk|#&F0AiA8mSF4iN6`R;vMEF9 z(Xp{DFdEA#1rbLG&kyxO0OxLYV5Tgrelcr=CR<4nj+fL!N>I1mw6e4^t^sAjv*K+( zuGsV%P^ufrB8AB>@}k?$tXlWcaEls}(e#9e%Eg>qgD|?NF)f|@fqt#*S#28tYw?2) z;6R04G2Sk*-XcmDC4T`ds|MlZTB&z>Q@utHzSGTUxLXWYnzk7tDpzbIz)fW#_WEr< zG`E;5_PG!l+o8U8odMYHYP6b}HAc8qod0+Lbl3)Xg?aJpuBJUeXJ_2pxyaU6_3}(Q zyRGmom4UxMD(i*i1=qUSAdOiTx?lF@3bJsKaM|JEvPHF42 z+;VB%NjuxSYM!r;v)VO!oix9i7Xxy_)blN-mGaQzy?IW;OIV=?Htjzib4xDsn;w=4 z{5j-P-Q>BTj|XxWRCCi4i`ze9-(O>G-YjzpS$DYaGkWk{J;28)j4UYAA`|>=KoR5jlX&f5QHQ&!gDnJ*Vl+|Bl(rRB zQC_ow>McL{tls^u$rccjHXqnb$B|=ucjC{TZJ)uLUj{ORCY!gU*>@SWbaa0v zl%MmdP0FZbelQa}?fb`C_R06bsmP%Kz|y6CI&Uw8ex#@?uErN-R?y@1bYj=-a4Lqm zcJUkO7|@KMo9WAmC~Yc2*bN@~&0XD;OcK+2M;hY%k|Sm%&YNsNC|;q|!$1^^0zWSrY_6L5oC0TTMayPlZ`5J^OzWv zK6ywDLhE)4Zqm-%ON``$qA$;DZbeuVRH8Bn9!_C}n}CHEDYx!F9!Os^Bo-#w0wFsb zq771_iwNz&SHB=?6oOfUE`FI|JOdx=Ej0PIYSW3v<6ihIrPMS@9GXeb@jCkcI5Fte&x{5`YRI&rW0*pI)-hDE<_@@qH1CW#}&8+ zwKpImvMsqGfuKiERCXYzax4(1k+F~a)Xtz?GZgHC`wjaoCbA0|vuqoRa8=ahx=CJL zUo)9*>>Afx4w-%bcR)7o!ePQOk-CpZh51IPXAPUJtsCZ4_yIlj`Ss@&@>6t{}bdUCE^p)tXBx6_v@i z)_(kYBwP?85yb}QuQl2-!I-|(KX~peHox)8eZrk`wgoW8J1I<49gP{MeL zUl@pSQ_3!|q1z;eUz}!qVk|@Gb^a>@O_}DA=H1kGvH$BF69$%FXBe41xsr(%F$9v{ zgTF(I2WcTTK$r$&c0LT<>tjX<9azpHf$2&yRL)C1pz@?+4d_9Af%NDfBlFc+eKSH= zqGnF~1&MobRVwjCseMcOz1w%!9I|u1`b@n>%Q;BOHU^j7^vlt~PLp^^EB1BU)8>BC zN+*Lerg?xyGSX}e;a`xad&!_OSI~b>vPsWtf#ee9$(%^GH0StrcIl67ehr8)cd`gx_Om|PtR&UUT$B8?g6>CC@Y3(0B~HrA)UN1rdmTh zc42ha3~Ao^{+4d6T&pt@3`Ey-Y(+9NH~8pjdm&ZhEnd&)#;W!3SKu*I!0BLQPji!W z2*}vsWxFsO_>#9!ro6-mZ||B)K6Xe}Gp&2?9mAMjZ#4no17NL`ZT?)g~ ze-juNCdY0!NT$5M_ROG4_(IE;BSWWtP`0$Vo*gRSJ|)8k``G{yvSsYgm(Mg|V`@zm zYn%Vyipc5cU||dgx>`5rR5`fXA`9rO3NWgGyt@s6GJqbvhDUfbCGEv@Kb*WUD%bQM zk0l_u{=ICsdxS1PGp2x!I>Pn58vZVCFUnfEc@P#-S4gxJ_7z5`mz#;~gX^~rLnkbI zC1ZQmvh@)s;H1;U`z&j|>96+!MRxn(IbN0&Tm(v~*cO@Hqm}1U$C=CU%H(+J%o{_) zXvUy|EfOkMU5+oQBk=*H{(GzXI|inBv*g z+w>Xa&!r{iuSh=^%r1bg7) z^@8_G`uok!)H4l-I#*PcwfjaGX|T~v4OqQHH&2{dS)889P%x$|N4aZGFxI-o>k;-> zu6v|XBRAc~CRN4=w{GupBeOnJUjZw@D5a{{Z&L)o{FITO(=%Q6d)q;G&cERaTp4GM zL)9urJx}4gZuKcTJP>o}On-QYH*c)fFIO?U_pL|orvHl!mDkEwJ_iqMqGbW`65~^h zH0w$=tGuIn^@KoLWMopVe!?)_i>T86fgEO92R|GuW#RfXsUg zBJ<{9Tb+LIsENX};HGeqFn#nh9+fvA7j_rI16++K*;b!s0N*t2L-k;F2m6X&e=T&B zN_Lod5}F2qopcw^Ju>0>w4UE6yS({znDt#gHo%V7giEq-O&DFMXJ25(eAt8M+~ykA zi}oMv>||9xts)@?)=kfgDj+8EQ{42xzuVp-4NLU6r-r zTm$BClTA*DF3Ir9asozhHSc9z4C1ZS*p;Kj7QRj#QKxUHuw@ap%RtGS{%3xyzWQ^WC4^l?hRI zLEVw7P$+|(LMi=bzwq=5Fm#^E{bZ!STtE7`Ire7de>^v|e{V(ZKf6t6M4!A37bkn> zzp7V1r`#defb+>(&=gd<2T%JPzVPX()pZFs98}Nup3<6?=!21(cGCOvm@8NR*1a-$ zAZ`FEuy3q3BkErG;3p+mhMfOMrBlJtwyc0HHWxUY(1L)`;SQi5uRzaY?z(9W$b5gu zvyp_(EBEj)5~e{vWk6->vXF~elgh8Z^+Nc@MfxGbU$Vwv6D?{1vT^ffC*J>l|NJP^ zx<01G>>IK7NXF+-T>5sf`ohaf)J=K>t2o?0FF*4jka9ce&^Z2uJJsB)X2df9 z-q;?{&hbotY>w!WOsd0whe9qGw7$q_5SC=z<-(-9DNg(*5N+?l@*6YnYJ)p?+wryj zlkcYQ?rnX0r@|%e{^lLndO%M}E4^9k&*FziYP-%`s$98`40mX*{j2!ePyBK(ju_B- z`RBgzY9)8uhxKp0C3JcPc}u~G*;=9^%iCp zbHM4ovKqkk6053G3;72pLKpjW!ERFpm!9<-2&&C9ueW6f7n&)SDzQ;BhsF~Al%wnl zJ(y`8H)cd7Hj`UKCqk!pW^Ep`Zmr;pud(u!xc1g237P?8Z$f8{mL_irn1Au1^I<@xQ)DZqyEnrX>uyFj?q$_<7~XAp_50rblj;(OeWVah+lwYd}FQjAc# zayKyTG!jB-PS{g>Gu9N?L!Bp6U-48}Qucd=d4ZF#iQvG<;7+d29XeNknI|LUU>Gp! zl5M1{WgCW@o%Hhha_WKn859Se!RV#pWZ4faCEjGoXeJeTKY1(M@MFlYO>10JSb$;6 z0Nc|ao1hI~t*K|*C#_#>Mfsz7*cXr}UF`AN(1|36t+OI0b#Pdn6}zm~y2kmu*sF6S zlEM%h7x{+O%T4cfg?^`b;o`Ou&eTYZEq#r$H> zj)Pg*B|d32Q0%fT^fKNg#z_J;K9iXFGbuIyz^5-X7rD2Hf*BW z)+H@mVuNvh>1Bm)IA4Zb(CYFs{2ygwvD+I~73ivc&^or}Aow7BEP3vL?f#B913UCA z`+WnkHVh(t!u0)(|9DoeNaO}-!}{8Q8#^Kx+1Vl-j3C1lU%(5u9h8h-y_A>aoMGIi z9}3`iDP9e+e$gcB6y4j{|1bIn){Azxgf7}Eq@f(9xVEvcMLAy#fKh6r^*iNf7)}*6 zIS}*l&CT-6;fV_L)E+_1A|CNBh8^JrP+S?C$y|F3if-5VGUve@prUrB9Nj5snoT&Y~Iu)Wm1_Hb65K%|rICg+aM)l<+ zr>Bv}X5Y9?b*_x>uz!@1q|C|L<%kFEHkd1U^y}PTay-kahB(78zVXGm`q-4>pO^r{ z+;xHZYcj*a%L$~>mV3gVBdVTCX>icf+&QFIs`Hcql;&WYm%M`lfSN4N4_;F zE)DtkaKWRmUshQ9X9eLL6iQ|(d*0?&nPKe{@BaoiSmC8QsQqki$U(brlZ<|6 zo_7MMZth6TF*Z)^*Vn2s6%7S|V9degMwoN0%n_yTSrRexc(t;H2#IVi>zh3(@d{t` zb^$4@j=8zEZ=L@cv*rk_@L7c&4$N};_x6KOUqLeTRqe2c@a5_LDxV;o`T31m?&%@^ z$1L-t%ngJ}#m%2{7a#Mr+ndXEg|q|87&%XP=*qTZNt#|jm$B}db)U2pHolAj+f!ta zN;!EpO$9|LyJizl2kmjK{9 z@AthCUIk5;!y7>4xz;hVF#B{2`kxgl%e4!yot0toF6_ehtK9U>dRV>o`Ye69qs|&E z;$0!j9JW+QvhYBu6C|a1%%$z91JTOS;3M$x6&ZV|d;PVTt>t+!6)lhg%q;KH&=FuBnua-e4o_u3=R?XNv)AqFq_&kDS)!FJ&6y|8_Ny^X9=+D+QRm(9>? zAI?AcS4Mwl6)roDvZ819EM+WP@_+s2{A_yrQa^T_d#lR8lpGf7$SCDCrO*wmFD*&g z^&OLcDytV6Ir*Kd^oOY($G+EgwXW$)Y@ zVZF_jnKwUn+2${iZ9ubydbqc*S0-Re#vboVZscW#PN*OrIaye}E8`;wg-;Cv{0CpX zOf&ao@x%`OsNh%dQ`^JdMw64$jv&jl++d@*2}sY|;WUNe+OM1#eir)r)wtm0dP7|t zK%TL*kkcqBy2;JSjg4R8)eg1gT^W7PVc9xcE5x%ME3gt^^C!;WMd_b3wJ2|&LPBS% z5I9&Hd$5c3qj{H85?inLcM+^hf;?3Gg327jzBJP*p=Mr(wlu;XI!!q_23sb+MoquO z#(@!rHZXc@vPQcQA$VwERY39#cw#^n*&BwSb@ylL?RUcj94|JdtP)MN>IHQG*OfJE z)+*uossDJsAY+!ctuj0vZlD)488*s+xnP!KJN?R7?3PqS*cvUUhYOzA(f{pH|5MZW z(NdF;=oIfqpo!+_hFSJxO*V9Rgd{fbp7G9M3734^w@;o}TxriW9~A|rn3p3GF7MX0 z7Ejw6eK;L;w`WtfRc9AS5F14usj6*ae9m{6y;$??uh@g<*9dnF8-Rfmb8S`d(R$52 zgAMn5ahxAFadGn2%3T%v&CprDtFQ+KpHlrPpBRHc?=Ob#YBSgFvU%Y89N4(i+CQWq zy*;qP4X@3d#~oR-=X+xVVSJ$YH#BFtR4Qi5{aja1$~A<;&3>c6uK9uzT^Ge|kEcFt z4;xs>Tk^mqvSo8sMxE84cvbhPHaqlxq^ooQq0GT*Ay zJ*X7l0*1rA`~q(Vz362H5 zdva#3<{7^-|Jlu@>*2bZd#)WI#`*eU=;+8(+s zO_41xbTamOt*QU!RTU`wkQhJ%rUP_V0jCn|$`NaU1QggK(~Q4o?wftBgVfzTD@F&$ zQCmml8!-BF?c`Zty2@Oj%qVfrlK zC)arq?7l_BSf|2{Fg&)q2^SNc=~|8Mi}r)Z`cFPR6-rM6qPZerrFm6bY^i>hG+T14 ztQHh+_6^w&i@7pql<#kxW|~rZY-8ONTWKK1(r3#vT)$MLkwGz%N3;L=kEb*Re?Lxm z#JYAXMuY6W5Vx_B7}n3yo(iBZsOSn*qK=e2favy=ik>iHj!&tJD6BlEo9>mS&9{AV@8)*FndP& z^V_NY1FI~=S9#2z#JEK2pS2e$Sd;Y5^MRk4Hz`UAgIk(%n*BqE?l{3og4lD-^(PPZ zC$GJI@gI-Q1~lIp*p$&{1Og}gULEF(hc(mPfPd}Ua(Hby;4oiAcH&k9+-(?lu2Xt_ z4n(zSu>xK$%iz&u(i>5tn7ceIW?_l|mel`;oCuyT? zM}i@VK(UHT0-^{M#NQdz?R`soiCf>BHx=giYp?*G&4CJu^W=gUMUU!pndVo^3eUDQ z%4M!G@WXi$U{`bksM`9IXNyUp(;8G1{C6+v^w@++yQ*Q+Yr{sWCJe-QX|DCtLH$`4 zDwUHNQ2LJq@0*nF*M`+&S5+R}69lS~Ol%w!tNK7vf zo@H=4OOq7mlCj4G6wW#D?2T>`mda7qHye>-gcdn5s-g6W`Np=SvLN-Lj~`#%Tc~7v zEMnqP>VaRK+n=T*MB*I_Q8{fsETRZDI9lodWCCSYe5#z2R*)#z&?-+)X`w2SQozt4 zr@T9oHIk3@9g_{5vEhVppZ*6sydMKs@S53I2KNg8R@K>E7<+Xn4p;)-sU31f{?uKdN1Xs4* zON$Jt{Kw6Iklrjj-`ilH`;X@%W)wCJ>*2~VqG|2-nrMN=UPZxo_N_N2x(~hC#eQwk zrjK<4ow6!d^unw$#qLuKq~=(AP{7IpH+{|n;rF%2vtp;=w$m9Uqyc7Ecf(N__~K%7 zFVmA_ib~x|#54We`3Re?THrN5u5k#gb1OE;8q8lKQ9n`fxkgI~w+-2X7?#$LwHW6! zf^6$BH%C;We}?~=F-HLQvmpV^i33GOIya!!&y0rkKjODpbWwAbcZ*hkf$~#Cv&1Lq zkGqEf?0G(1Y~XtOvoc-2qp9x{C(pONDT^10{J1Q=Tr2V;^;4?W55r>xU(+`9g{Q%j zE{nY(5*@5W<*j5%q2yxV$LRmp&NSQe*gvH%?pq|Fd0Pv6MCT{h)uZ#vBW;5-mTpNx zC(>jwFA1(w*jo~@oF{Fr_6uN-WVjP8Hp&xaMaI+|Q*pHnTA=C+%P@V7osolpX(FRQ zIEHOkU~gfNcDPHqG&$eB^W1CUU*Nu0_4~g){Wl(x>}_l3JUnW@+p@ z4yyP8L5d*gie$mkmjGaQ{kP8>w~e=vg?t76uU51(s^HxH5xais1<_-ma_CP`LK1~Wh+{44GBy-Oz! z?UUCY5(a~V_;FqW9hio+d zXFwGW_Q)voJpA4L_mE8Gr6mn1Ae-&C@FqTm{hV$R$8-2)}zhCu9f zy723kBeeZ_dA)+&8LjKTK3^#h(CJfrzatX42Fn5_uJvqDUjh}9NkPQaG&`wox-4mB zS-9kVq;tgpZ!Ra0BW<{dFS<-4Xl(OJMTT;s)cCnOuYGxB^o2)@8AJZFV&yN!u^W_s zppv&E$3AhC)q1Z&A(H5qy^S4S&2PVN;Kc^v1a_8dEZ#cds-&^w^W~Q|y1)W6_0H<`ys+JB36$I+Ju798a4-Vn0WqU?A+;C=M$Yn2#_PZ61*bY7vb6EGj-UG${=UVlV!aj?xMeUkJ6!dFm`x}?Y z2NN0$uOA}*Mlj~@-Wx`T;ro+#LH8mlOf}|eFQ39%dS>UGcrD$k|F!44jjf{pcpT*; zu^#I!60ECmS*hQ&1is zvRLUhWN(I(i1ML{+T>AUBT(X5{cjMvCDUq@nj>WvJXdnr&-ayzP^emXm~5tMLHRLs zjh8^Q{m_8USp{1<&SF`Y{)v|LfA=J_QrZ&2E z-0qsn2`AfZGbQog-FpvD@V<#K3=yi~*vpZBm$+-S$wcRrZSX^R0a@^Cv0>=*$tM!l zV)# zms6~t|E?+3;(8d+ks2fnwBR=pyX_k*(r5W&1-|L+Q43UJ8tSqX=Kj_A)V1X-5!&>0 znU|Obv*C8+$)awPonB}p4(`-MKIB_>Rtr)${`h=5Epo1nM&pa|CxXvnL{|06Yln*# z07K0}1|(i3ITRADI+R;25r=|&tN7+6F*GshE0AB$u+Hppf_LRA(S%w#arj3A-#U!? z4_}7p?g6PvhX5T%lMIg46UxSV_|BM%z3u>S6S={Db`nfd)UH`+j)Tbr_t!JohY=*B z)@Was`?T!jrCaL5{O=OjWRz3 z#a@xJGxg(Dz+3ThX}RjTK=_Zx<${fasKn409A@5*Z{57h$8bYs#I4DIa&^ZVx?y%p z<|B_+Fz9RI&E=0Azm&XQ*eC0zzH%z$2=ewLT1FAVzbW~hcHtDKGxw^ABhAk-qmrUH zhPvf%mVDyOX^`q8ZQ-RSgvm7L)3=QeLymNE_OpS#&DF2DQZ2OIq2j96RJSAtgW_bE zgtd*Y)J4A*0Jss6{kMKHLsGT`@nSi?g%Q#Ro|tfOI=9kxp5?C%yrR5z&`$c+h6QGJt*=(w&e`2V2<_gr)f})=~>04Ai z60EKLdPrPBnupJ0qH<)jT=SSWWl~-%@#Ih)GKjnDzASx3;zft{;pgZZH4MzRH52FeHa(%>A@$$xingzakz0A5x#r6 z^2(bRfByyVe@iJPbCe&!NOoP0M)R9_fh~K}Tt(n&yWSqxVg)aDldxQxfq*hqny4IT z$W^QXr+CvJ$TmjinbUwIcVdlx`g`+2WVVHgpJ>P{aE>)&t89(S!!T-+&CLi|jxQIm z1op2g`3*N3oR^=5P&FN3w7GcJLzK#EM*qz)CG(gO$7$H*H-3fYtA@w0ml)IY`mIvr zqtRu^3@NmLbNU;^KCQQZE}>Vm-bwAMPBg6F6z2neI+5&H1OBB}7@ZQ?1*+#9ML( z2cj}2tt-h=96z%=+ArrUwFx#SUL6N!eO$$=-R$yHtL7@@2HF8ypT)LsI=jFXNlmZ( z{v0}stB2O^6|KKuNx6l%#)8S$=!WTwV}0kX&0>|#oY@))J;4&~0`s!fUGl}p+6Gy; zUGamp{?@0cvw%mqE+e*=u$f<>Fp)<9O4geR3%Oy+zqkr0KKk;y6#~=emM`Ht{lOC=X z2cuOX%Dp^MpN;bTyq`z2KaYUAn)wmaPo|aj3R=Dp2tN*7SVkTFk37(onC|RE48yhw zA5{*}>83-iQ}Xd)^qFxyiMO0Q_IYAw!(!QN8@RRZ3A8GQ_s=huaRuXE?_2izEQfuR z4xU&x;^D+yVT-XIOrh@5gp*&^$J#~}){|LrMpk^qOq=m0P&bHw<&}e5A~{j4mK6ll zEsz~YCO#exta%G_N9F|S`yGod0Cv#O){^9ALknM>L>z3SP}=#?Za9HilsetIlGbiJ zrn*BMlCsH~P$>`qt(U$2zA`NBBHLke$%iaea&8QzfAo_ouY337YncRoS)Kh38IPhw z18^Y&Fg0}@Mcp`ak+QFQ?c?^x5YW%>0UKJIyYksx)|u9I+ls2U+HP(4BZ~M85`dJ# zC@;-Y6DMD$saAz}-%#c+gMj6F&&mBn13s)rR$~>dFQLgd*k!}p(YbjCXTf(yI?}!_ z#O}9O+5^v}^TR%*T$26I$rd%m0_T$1_v@Lg2g{HX!Sf5$lm_WNgGsObuJ%*iB{#qP z$q=ARU1_G*IJ&$QUQF6us?VGcF{9X$8}Qu0BIu>4KIs@2yxV$+Ya^AYiFZJ0j~R%| zd+|M~iRp;IcUcY3^93p;g=;Qk2Fl9hGRZscLXtaiWj~ZshH&J5Sh;@_I>JER+v4O^=5GIa+smt^;euxvX}dQp8AvyN6KOr_RYM%oniRan zyI8*S_gy=27yAWBrAUU^*5PYs;lLubd!%TGDmXwbZ5Nn2`C4=N5u)z)8$*>ire+K8 z{%y9tjR~A;t`NZQa9g_LlzD%yPyDK`foZ{61urGtZT9T!sv6@STMm%j2ij@!HjA72 z^U4#jYR!dk6ZW1+xMnwUpNx$g9Wg2J=oBKqo$Rc}Y^pE$(O+Ayi!4UfaS@o2bZFYNK<1C~jXj1*#D)iE5oA>*-5&4FY zS`S1&HN3xFEyD{jI-2ai29w;R9%%fGt8hRW^sVeA6M(4pEW+=o03FShy*h zPm^;9Xn~Hjh_J4+rGFaNTw#R? zPUMQuA^rM7TCSOWbI{P*ud5EEq;OaRYh0oM?>y-g11QLAO{sb@8GZM@m{!WSxa2)@G|Wq3OHs?EdM71g$~k#O z?s}2-9(7GI7x+7rl7r;Oz$(Y%$Evp&HhJMAP!LVN z?GhueoNYErV405<)Yox-|NUhbBic_f4u^BcVaIm>U&u)ZpxdVYT;pgF!GDKE32nag zwn%VN0KJfLchgPC_eD}tQeYu6t>YvSnL50O(4ww?*BV4Z{&9KJyoP~ULI+vE@+cv; zS6I@7lJiP!YUcy0-|ov|EGi7Xt2B@8aw{A?G7T{QsdK90O9Cc#z{I)zxw#p|q}T8| zOn_0e9{5_g=B5jH4fC@tdz8&ri1Z$w@%V>7<7Dv5odiq|RHzC6w4PR8A4_c?yT@TP z)|@gvhfQx&<^Dj6)NT#aF>}^yB(lC1}8OUZ$q>}w6}TJe_&>Ao$pK} zG)}9ipe#c3>5nhia9xO6`IihsSWC(>QVVhu8ep{!M31$hVZMp$RQq7KUQfa zYn-;69H4eWUE?~YI@js=*3K>vlj3Z{KBStd^>t3}RfUVd<4*OAc{)Ieg26~)Pq-3Y zyikN~A-bfG2s#aOToB6rh}H`)3ZjOV+wVCEJ&?I7=lptA;r-WitzU-D#*FPUTSN?ZzFE+8(ZC9mT zz4i=E!FsD zTr)Mrh6>IO7l_^u-)ODv_>bqp*DiZ$V7gYSxXszR-Ll#wKan~lh8mq zWj}SR%F%x6fbR@0eY7#zf{B`D{(qhLL=z(s*XbABeT^y$ z2mBB!u_QAMK3!0cFrw%_6SjAoQw;b88H!qrX(9x9u@YL{54CSAUfFpc-hLa3L6eHa>wBtiZ>R+CW2^=5E^R;N-=3KRDT*B6m5XN+brnaW+lSw|6nLO?Yeb!t zhs68GUTIM)9IBBns4_*N)SJGz$7K$(z*fVnWfAU^H?Kg1aeu3_mW(bkY&r{ElqLetEp<|KKd_M24EuhLU7$)qGltt z2U%fPFI6?A@;pB*;Ah1KF4$OrtE;fwi=hv{PlZbwr5bPSw&*%b{&Nq660o!b&lQ5fS3{$?Nqm zN4YS=XDp|}YfB~l=l(&KLxfPu40kdj+TJFjr|{zeVX>EZAah;x8^1 zB}ZK80ge0?(TY|+TUT57 z-Z{bRhVeK6e32sAmpRJF(zn< zW^_ouT5R2KcwkTV@#W0@R2IvombAj0%PH#AtouYm$XzqXEZa$dru9O9X!7IgJdPgX z#$Q{r@oW+ON-8L|h&Xk3;Io)eN#P%+%GTc9B$v8|<&)j_GL24;t}g=N7ZLFH;KfSJ z*8t7_7=LpU6Yc~9UB0oc@atlf9H_t6hjbd{HNW-Gs+OK>vc~%gCEbkUzuH`<*%f|j z1t@bs4SLGSF@Ug4xq~63UMbG~OaA2`NamB@{_|YZe@&8tP3m$S^SR`$f zYu4|2MtLGI67G-EdVP8z(D`h`SsiJYGo5^G!P48%c=CpK7v#vwC6~f2mf30L`ZGQs zbC2z;3c=x>fW}jEK+VOOhNhlRgCkxAgNmqch@;<8!E|rP3HWs+_(omS?0KjCRF_k! zdQXE^xH8H=1ER)ve=kE(eSgwFDdUq z2tFCGBMslfL?T9fvgqizaPbKnNNauV3zEF~F}|5+EmGY^@^nNuPE0ZXi^nO4LHD3K z=9v&Jp<{BSoi&&vo0kI&X}=E2X43DkdOd;GPk(BvkMC*@JtYJN+3%6~SKAITvIA7} z)#9$4QjFEQ3UHBEIN%c$Zv%d`%*0+P+DmB@7-eFm)?6OcjC+-zK1HU$B+dNfrUspBDG3IPox>ij z*j}PBT``gD)^fh2hgo+0p4rX6#Ax0aU23@{EgIW1i>4TrQ@|%zm0iC$0Jz?Yo=35w zQ|n8YO^a(A_F1`D%7^d^?zytxBkq+wKAO7u2RnFvJ%6s|`Q+|2%26ZH>(b=Fg;|9$ zf0sYX&JX<5J{*3XvP19xS>?BNFJU>&j+wdluGzs!%OBXE+toPkgE-4_c==T&dizD1 zTe0M$5Q9?FeRQaMF;uNiZHi1fW=>3RUn~z6?pB*#=8BOLcC!cD&*VUi$2H|%5dw$@ zbB@d3{t2~`G24r$!{Sh~ZXH zj~3zIyr;C95Zj5G?1D>Z|4IRo6zW_HYwzDG2DOMG@dy!-RhQ>lT=(%=)yRv)(EF`A zhAH3^2Skdk(J6)vIWIa38>^{u-K_MFYWO+38ikhWV`oJ~@+dnyVhccuc{k~>2dQc~ zUX6QpwO(@e?f7#Sv2}1uZQ>YywEapgB0=wEhPPsU7KT@c$~h@_45IKd!GL$yFieDx`9yTnVd$grssGtK=NXJ&YA0=Nv^SQ$ljA+~<~? z|i1YZ@{h=MWwQ2=|raH8Zev! z9iEw6*L^o_*TKp3#5}s7udtRnfuaCn_;$+_aE(y1fr>Xua+k@h;8RHDt0W)#Q74mn zPd5@e3e1nyCbQcRf6qcfzr=V0%Rm|Rd%vC=?a z-T!=Zr&ibUKiGdtUV6?YgqCVc-{mgpY?EV?n-CEgg7$&^IZB)}UXKDamXp&0LbeT) z#XY5lD`(;Xtd)fS!_a{+G*8NyjyeaBw|>4v-3$;}xY>OW;vF&FGg90lkNd5Mf7{VTY_l z>wAK;Mz{;o&v%7jZYnXz&7qTm1NGD3MohSp>I6`_LKXz z%i|GW!U@*5S__Ses}@k?##dSUEt%x}hd{$0{<(U1vs|OYCv7XDqTMFf87N^reAVHa z3%ze|Yf!h75oPG5OpYYc91zDrfjr6u1pQlSDF+t?%CfBX7V!6r?Y-KCTC#wQfW6U2 z>>c_9ibw{hO*B@txFU8dcNXxQqVt;%rlbz!>^iiLf#-OKkuL8xf#j4zxOmC!keM%_ zhH0SL0X*LdJ;Am34%;fFZ}bEPXX$AkI%)#NSNrw*+{L%=kru7c-v;t;XmmwxNYJhOqNAG5d9W1krhEG#jGVtyCNz}uyrBKG6 zeXpWaW+T`rKX1K+%>}NK3#b}&aLTZeCfjn9RgL(dhIs- z=(}=ajwvbviM;xruWh znHF@#Zj@kf>jUR=+|uGA_GeyXyVXpv%iCy!>|(r=`*&Ai)+4%kW*yv-5Pl)f`X>`(@hK- z7lsmbC(tkVz$b^6r-z=;c_n???Xj};1yV&mU}Q#s{ni?~u-fMLhKFc{TiLOgm;sUW zhKeHjm?E4ZF+|mBdmbVN*JNhdc)ZG68`N`=XbMZYBt~y z=oQ*C$+&sE;$uzgbK*u4^v#nK8w9(0z(zWD5yDpzTU(*}`v&&!c87ZZ`?CBIjoG{V zU7-fDx=vy=9OWR*r46h(47-Qxu^bQPYMQt5H+W+A6mY#W^Zj;<(c1NKC_ZI|n*;g3 z5gsSpIt(K|I}otr#Ym`{KnK$7KD|ANvUc}|95Vp@m6eNAlQj{gm6ia6qZ6i`AG-f> z?eyAaMNJZTS-y+pNpxp6MgmGKo4qNNS2T^>Yj|&?7{@sccN;;t4_q~(f?n@L27I5M zoOW`GqIEkRwBU(gmgvrWH+sQun%i#NZ$k{XZ=HBip=y?c3HVtFCKnG4N8o!mitNgO zI=`Gb5HRgqml<00nrSrBZQ9p6v@kr*_^m!5-Q@ng`DV+NJTo(tT;I@royBjc6Mv`N zer^-RAAEbxl=txTk!>KNElKv9i(mIX`gFZNmoX+KW<->^xi3B1M*J|?AL=1A^J~yV zh=(U^jUbEt=rc1JRu3)Z85qBR5W;dr-DCm6pZu8_LtmOjI_SbuBx((Sl}hX{w&+Za z>@DMLkUqh@SbZLShV3NyJD7FClJsqYdChOXs=I$~wVYitCg`rEB3bVb_$Dz1yq`8yT)O(+fYuAmQ-Xy5VX^neV z<#vkMj;!fAH{a{u_OooN$^Dp<)lM|6YjhMjb5Lw@5Nk8KSu?A4hco2t;&{;WUl_i~ z2#`+3nfWnSux|#W97oSbQ};k8TrZhw{opygZ5%cqf%8q-LrrgS2nfAl@~oe~3^rHZ zg~z~Ues!*{W)^y@W-K|iytC5KP;9<~!4a)~5=3?3CrOtl!bxWNUqnb`&S|tEC9XVJ z=F*^CY4Vi%@moUKnaRl>!yK7#9EqO6p)JcbjfRd!}u5Vg!QeD-cx#otmNTGd{ey{XF={n;E+r)9tXN?E(Z{AVgO( zGRA~y{P9peV@qXg{i9?fG9f%Q_A}k%4mG~>qd*C^4)bQJ&1?JQwRu-mp!3^*T*5(6 zXr7GRedMo3tdY{G4h9h){rpyA z44I{cXtlcc{Mr4w$|sQo-TsGOQ(<)}ud7cCv8>ORmfG2OEYHlu&RzDm+p+jObLUNH zeKzM)Zc(jnMNRDTpI8hz&uYfm>86igE&1Z;p?B&+SQjCR6fKWlMtw*`Cb3k$!FWh! z6kJTeXa2Ik-`x)fBgbtESjz0es8jGKRKbb`OCIlt%9^sibCEr#({(4nQ@pfP%IXGj zp#g45J>OR;u$$-WTYhF{Yxc#Zq1S+wtcxZkvLtmkh1crUo`%$uhC2C@aoZR#NcFc_ z=nufrpmfhT`@tCe-YEU9SwxI*BIxod=};Q_2?g6p;Dfu2X7BlbPedJ z*KA`sPjHlAChb-bmSTp0nN97bY{nxhQdh^)o>w2bs-o77s?DnQKoF(KVl@o+Mm>XP zV}D#XTmdOxmK+N@h@CI?_nJFG7g|)gMR>4Xw5heQ^{tt~U+QwT-+xIh$V4qe9N~KS z71RO_rP^G08RvNKA;L?}@6sPYd!47DYdW&Yf-FLgcZCJ=YE}Iz=#!1qR~po_V1hYA z93^^JG0PVZ2bI<*%Dv=2ORG{x?!i4I7VB72^N7U4o=}bsej&o_DBTR2vx6lh{vZ-g zc*|Z-v=r=dpSr8T+l&3c&6O=}>a>!xFnirJpV3LjfHxJYrO=Z8%d-f)?i7WV8Ce92 zV4hjGXFk%iL>{==XbN1lOfszhj-mQZR;bWhJQ2%9&n97k9aF!BpbLJI>dnzYGZKD* zG*9f>dxj0-O)dE~fse&k4$JOzIg@!G!uMwYA%7mB{=rore3xqZ5?sr>d4y@kIyVt@ z87Wkh(puDsn6%;N+fn^YRtc}H7fRj=K8-m;Sm)O}_e&{ZbF1ers{r6k(!KH}YN*I) zjLHl>p80SCuLIICvQ+i3Q-}?z{$)G z(MFxEjb66g@xY(l{`+TKaTPbedV{0zcRlGtd|BF3j~e$2*P=&+1;p)CkZ4&sTW@e-!NJC!Ewjoim?lPHA^qJoTRI2cIkV^BY9|A$bpJTM{w` z&b>h3>wp&1y+`?-b*Bh!A3xtdp~3x~yU0g#0#o2_ zOVV)GDGMldPcl~AmQ(wYd(UKT|?S@3ls zJ2YT%dq<~)IqoxvZ)IYPl)5qvs%KMe)zqiyb655&9ei!g<2+u|-Ph{WrRwti=dcNN zjYet4qZhoc)|xI6$8bqWjjpjtgc|1$wL@#>{3Ou-os zd}%QWBvKJyB{5_H{&WEam7i4S7eCQh%8uL7jNipTU= zmCB%yiIkD;inq<`(tSga(W=V53Z+NsHTnlt><$T4+cT>#P0$OE{4@owtS{&I++!-p z4+;F`{?T^OY7(VWV&^jJWCZms^`e&L@MYLOzO5&MazRLY>@ehL52y#O<}>~S;7M81B?Gk?(X*QOy(I;ya#>*xyq`Bry|G_83KI3Y zvNjO*)#a)gRGh4b%~Nr4c4fN2Lc75yK2t0^9-c2AzVq-=f}Gq2JA9?p-NLd%y>H2z z5!+;Wo-osbx?Jq!xE?a5)h`c&Y#ET7A8i%Kz~UHT=FlBk=vBw6_-==AFAfOrQODj_ zS?0X+upA}_K_zx)MIYxAzl{FBL9@r4p!*lV1h0Rz?Enkzk{n3~`HpSAr)|=a1o_nB zJRoWlBBbZ=IJ*ZY3{{^RREW7ZzS;SsY_&^&iv6RiM2p4{;#Af zDG%&k-XV)~$B+q{;7vnfg=IVO9@R2dbEujgOa7DzsjfNmcC4{4bC^3ds&=KxwiAT} zo!C(zNi8Sxa*9f5WuJ?0;Ab#yP zjbYUwB#55BYFv}+r#c$MrGZF!x?abT$$D7g0*fYERgKOwHFw3i!Jdo>q>R_Yy38WT znu~CqpRT|72i*=q@N)K=v&G;nk5uGE-}$il*NVJbCie9X$c_ks04cvymxe_DZf~_< zo?2FzE;h92RN2|F^eIH!YF!PagkA7Tko2q{sr5M8?2ol&{%6MlP{Nc%`qpPp?f^A^j1Ubsk} zKKaNsbY9B$`tO*V0PX(BSs5xP9KR9@XoGzN7x5b%M9b8CISRWnuXue_=-FZxIJ_-g`-nf3-)xZ%hk6zPBFu zA;P^Fesbjy&}BASTHN|&&zXpPez`>ZP?3c6v*<{FtAml*b@2+f;;^(Yx19Csh4Rh- zSyKLtT}{7^kTjR_sq!B;p;27ww_cci{OS6F#)Y?7AXym8xD4#Q)8v8N=!TIR&&2c|D`nY{Rg41ZJadl z{Cfn&Of3fTedyKah8UB2EVYH!7Od}J_SogmXOn6-*x05}1kz!M4`hSa%8r zT!S(l3TaQQH6bYo0X=nM9wN5BiJobfCDZ3Ft}KqLS9vn#njcu?nHH}PVNS@+_7qL? z_-5^&P}}-!%^a|cl5C-BT$x+vT)tg1*3+9_4ga&JsSz4(UGdG1t~FaJjUD_QvN`)m zImlzgXG={Ja>^oL8$+n#nc@9jY@U0hwp>!P_CtCGcy#K}j*7eKB3Z~#?p9sB-K|Ff z5w_HeVSc_vzp`y;8SrvLqh~P!X<<(YGObr_mGu(RAy$a$6F1zR3Xa~>O|6Z1I8xJk z{ZaJj#r&1=503qT=_{o&Zd}BIRdBq6VS)3yR{iyg{h|UMyIkpcj|K(~{@u}0ar)W# zmdHq;|1xMcjce#vVCu)l{jw}ZRf|ou#Z48Pp9fl$$PX9ZuIelW?#V>{ZPC?g4harx zG2;Exp{@;f7hAh<&E<*oq5Rp_Nc+HV7GXteznY#Tc3CNzNNf2 z?Kk&r>aQ=Liw$z0maG$M-aj)x-+Jnq^w!PH-DL}{Q+2sFX;mZWKu|!#eQZd zjVJ@cCc6=B;3zpdxQkE?iqCa+x$|6u_hR|$LCLcpJf=^x$^ew$2)(}}N-Yqv&_D0v z>YMRn?Q8`^n2vrl{5*D(W#(7ZB>^pcA35^qc~CQ0ny@yfO&Ln^&F>bjGE#=Qy%lNW z9c?nHX~BA~__~9>j8b?9B>biFn6I2fX8*HuHWdWY(wD4x4d5c}zWSp>eGDcU>=d zA~$1eZfjdK&#)>^NAw@pXaFj_A(-yFfISLJ^%I4u{e0o*on0M|X?exvK`hlL0UUq& z1^fn{dC!>zsOGP61{o`b8ri~K&~IzB{p;{p-ZKp3+wRzer>q&er{`4S^QXfVXMvc;hOu|fv z%O9YXp5mXOM(@M?Ru7FIA0ev6yPSiFk{t5$D!ukYnh5dk(X>1BI|@74<>bh4+d-@_ z=Pc_6{jnxU8gMRBsLcChOmJ%%72kI|+hYYmLTXI9Q{;egP^sphjXev>r1Phdm-W2M zu~o)l-CWk2gHRG;(xGRuqNmG}hkn;WUv5;@hcgmX4Z7%-iBcNaTfqR0WOO)O9?ubA zIZ%6IS*|4&?7^n0eh-hs9s90ew!h0iE+DG0GmjQYzihPHzV|xlBM;y?S zKI-BWOndeR!zw{U-k;5A`UPyer4WqS`H$=5Q%ccRyskz0G}PFlOgw5{$Z3poK57sm z2vDzN6x$sT8=Fj{zuHsCSM$-`o&}9UGQF<_n@!u$P^x&(Pz<5^wWMOKfx34{9p_B$ zOxP92*L-Tt44bWbs-HPMb{?ugA;+a`w$9>4e!zsxd$q&|d&Tb^!H%`RHI-`8a3bAHjXHT?NE&F<`9BgrQKR3>Wq9P?df@Dn2U zxyiqw43A6{Jf>q%lRb;s2~v=(t^Ps0r1tFb04CDXo5(rC zlBQ=9hu*koQwb@0Mp{Xf=ldOxJ3KV;Tcgr~9RyT2#ua#_#VlP4=rv;ggCx2G%mW>U zHo5J6k;G+wwaYkebwo#w+7$&dx@2);;G~lM!qlBpro2KQ{ zS4&t*bjx{1v~WcyWm^2U_0+G%onk-7HFtcT z?@c~VCgsl7VrDmZMbIx|3V^AM0<1n`raNeI5jKGd;$794^}iDT$l{!rTdf84HS_n} zyJZIYC-OKflcwo+EAZF*{))#=mY)Tl&cbgl zMucsZpA37ZSAw}TF`L@BcwOl&PSXxsy>i-I(HkA45vi)ACb%ZaI%jY2VCA0kkw@df zZ1Fa6{!eQnn#+Ui9p8gQX6^t^)A!!)?OdJ`h1vlMLTlU1XT!3{yt>?54k7BL{OEDL zG|GV^FZ<-FYEkoWNYCIZ5*k*hmjRaEnbr)bmCv)r1=-7?T~Bfz_I4XDrSwDPQ6PkpOoj`ZHo~Vd-!8arXvL*G0XF@C%<4|C=&Z zeoD1|`QrX%-W6$TKHGc7;rPBy9m%VryQ+{oz@BS{CvbPD{w~98J|1~4)7Z|%%J-DK zaco`=Oqx?mHjtvW<;YD(66z0DFyp|t%!xCK%NL+e@^-Mp=_UZL@}#NQM+Cp1_D&6_ zmeIB#^d`%}8BfznUr;c+o!4h?qsL1mvz2P8V(CzzwiWc2<;#5Nd1m~aXKwJpo5N4P zNAa`dzSEuOgX!LI!{9*q2fZh@T=%z4E9;q((0=aE4m0oR2dXA>n%r%!#nY`Rv}QGw zAJ4w^L5#u^$U292)W^8cZ}IQON|S+AqWx=^C%Y>Rs1yS=de;J(zaxwiW171WHyKc{ zlN>Bj2I0e8gl!G|q~QrCeLa4(HbdqRg{%=&L0L-4BIU~$*c@xByBFWg!ejyc1} z`6yb5x&ZRwhz_q$`A)D1?yc0Lb(v&$L8Yr*K$~KFOH%Ds`D1Kj-jeAO{4@e%;W)Be z5!ha5s+u0EFfew0wXQ*K89^+DO@U9+t2?sOy$mku8h^%B$@@uhSDC*6g!tO@vlJl+ z;FMjer%wb%^3G7*mlF>3xxS08dS6ys{lPV%uL1G|Ci3c{D5W)~EB|!9BU|!%&{>!D zvp>^<&NT|X24Gy46qNvQy<*FV0_TWkRGZ8&TI7Sd|6Eg6c69)VvC&^uYM9^a!Y`%<-HOVt+}iXaKC+M}uwK+X*O7X70C z7tRb2U!{Vr)0DaO#Fe|7hey}7{uAa1UYZT{Lt4ro+WUlzEueOuCp~CTLU<`> zYr8t_KeHC;+N~or0M2#i! zWYGfpn?QS4dngOy0u&zv5yX>h`Uth&fr?Lnn-qJIf(~cl$RQ6P+F!nOt+Anjv_87P zVgterEewjF=KpB!Xk2x0)p=&C{b03(8^x)+By%CmMV`s?v1HmprAlC|AFQ+VYAiozX&-%$ z?lDgmgB5mK{rUSaq4Ko_)a$s9Nsh)}&dryg$_>VUP{uvr7(0b%Q(s`e0p_dxes8eC zH5bq<#A37Oa`QxGw?}PlhNcovM8y8j`=(t{FV{kLyl{NSKVfW!vQJo**{xd&1{rhp z^{W#_k92iT*&wAI=Dze@f6OgBG+-Sp;Ad?o2jn^vJZJa+^z2Ls342c!m&%LTG&z?( zLwr4E+fjf2OKp7kfj0 z-AhkpfglBOI3F#x&VLOsBdgC1!|j3uVX>QtAFf#|qEp$ApWnaqp-HbP2r>GO@UW%u71&oE}5!bDGSBu zy`nYVj_8K@g)^kuCHs58v{=k1JzL?h0KQMzqD&z`+&99mt3jn=j*oFpuh;*WCh6KT zCZH{S8is{WF?}Po0%v`fs%xp5wfOuz!-Sijq$@z`B`V&xB^XLcdXNQcAsPA$*4X)E zKH*Ac>{liRYh%~AtzfKL_OC+rdK)!6!LLT(KMS)C4k#Cjy1rs8RgWJa)Yot1Em}@1 zwPB?2tgBQLj);um-Em@*qvQ8S(#Uzaw25_)Kn;NC$coSdi~nlrwpJD*e4_Pl%`X=y zj)zvJ>H{4c9F^c;y_;l?`Io_DwT+e3FM&2OGK;G>!u%h zx122rfcw~`l2n$OKUzSoC)XzDlXt$wZ`#s)R~KA?31Ay-HuZl`vQE&wN{g6pw9HDi zK9}mfA5v6|9s0-h4XasGVTF;04fdE)v5A$ci!F`k!xWpQ3SRx>dVC!J2XP2 z3q~Xwh~bxR`W2VFO&emqZ%-B|{Bdm+huLDkYvKRGGNN10qhxLpnP{y_66-;T->ifUnLBq+JyS^jGauNqY|7+^o3SfI0J#s-9Cpra%*I~f zySUA{$(J(5*Aw@!|Ix8`*q>`08-oPyuU(#;@r53BlX@jvi|B+1w3w=*OXi%RZ9%@e zC)}?HntscK;%!6v3oGlPexhv|{AQnKaQ7O&+=R|E$$SKe)_3#sSCs<;GNMFBJ*ULu zg9yhbJxvQ-%G$iSyQER<@C1C%f-m4@w}FG&Bk-L#dPV~hDJww#L`Ad-v-Bt?i9SKD z`oo;@?87t0n3&x%kWdtl7v~U#9UrI03aFRN`p6a0T)Mg($r@|`%91svI|)@i)LC(L zVJKQpwuvz9OH8j-X^?&HpQ`jz&ye{a+@3zbd|Kayl4SWh>I>aRF)bw2z>%j4K@GzB z?>W|E{Y-864XW*C7;vkb%_C)vH5@$`s>d`WR@D{rj&{#o_h*qhv7UrOu#@uy0OLwl zCmF~$Ot2mjp`~g*zMTL)6r~78twCOP z#5gg*QYmv;AMWxCQ)rDgY3Mn+zK1>WUO9_@!0;*Z-3`(|u1ITr+Xth1nk8AmxeKQ& z=RrEfa+=Gz0%u)c`_0V32)PwyeTcwPk4>C4olj*fF0NW%>7vC7U#w@Y*6Aoz-O&q@BUyest3JXbC^2; z@WyMJn-esAqmH=eOS3b$h2N&$SP!_|F)Y20F5&RAygAd~{|Fn8{FH0W;NI@M=O05x zwqxSxGE^XST&Ts-iz?M7drm9&GQN}7$J_^U-Vpp{QW7}E%o;bK|NrCq0 zEim4{@8OK?IG@)-O5knF!(NmeStaen2+|wQ3wqD0m1jtJ=qru)8Z^Aecx9G;US8=p z$)P07B?nktZ`%SiVpgFL>?uZ$OK1)zIZSmrm?YPV{TQ$C-OL)%e%~g9`Jv*w7opTp z*`g47vC6*AnOkj!@z5lWbHalwEM3*b6b)%AQN zS-&kx^1Dy@Zfof6ma)yVp-|ZVJ;A8(#a(bhlb)?lQ}6~&oK`^#E5Jb1hQKM|YOyUA zC6hz-CUuE7Ll6;%uJ>kwdaQvcQ(wi~HQ5fWW9PJQnH$Mq8CB3B48YI@%DHH(V82s_ zPZV0JjWpC-uQ?<%r3x*EPkz35FSEl|IF`X1j}t&hQ%R ziDc;7S}~Pb3aBoZGsrIdhPzm~g9vi3Lz;1w>Ga_ zUKz2RjJ?aewNEn{W0@=f+caiQz3$HDKd!NXl%gKSBjAuZMgU@CNsCvh%^j#CEX2H- zg+urB{kXnbi2T9i;MLT3YX&;(v?wLI2d!?@z<{OmE*L2=^ZrXy+yeEm9srnyGr~cl zoIwKR14>|$zB1@+!x-*Rc=PrSmo&!64KB&f#_+LZsl}!KltQaqf-TTTM}*8j5n4co zZ$`WaQ(mq?r%KJFn`YXmwO5SpJ-c@v}2D>(p^SkD;oo$c}epJ%CkeO57tAP zmh%*2H9tCd?cz)&H;K~Z&E0bTW(3c=gS0j-H+Z7`k2CcYwg*s~>aRQd-JKv~&Uo2q zU7nuw+E34!vNIu@%9MR?93w06j6+w1w3kzF=vq7GZQ8bFfA>-;x6m&qRhhabvF|Yk z5CU|UfvK^MZ1F&;gq8uz`yuQ_=1LWjN^ZN360w|j=IuN|3LTB{4?U?$K7)9{h*Sb~ zxrnh*dnmCr#A#NrUY)JYcN5=Y2WRj*(7+~X0RI1B{etZ#-=fg8WhY7oqcrF2g*iD) zzB3xs@8VmzdHKdR_KYPw7fJhoTtsflF>eE`MIgD&QKCi$)KLb6OuF!~P+EGg(vkgC z^l5f|u;nEA&e+yQ(B?hnBWf%zCRm}X^E)8GFxf?~V$|q66F}{|2=vG|D=yu6ewHB9 zDW3RilD{Tdo%7}K?tVqHiu_#)WUby-`v_HZud@yt1UQhA(le)9gRw^qWQ+i-=UlB6 zud3<$B>BgcD zOrrt{1Up{QZAT~$CWn(hAM_qXj0;700D>*w!4FP{8hY36cVd-|upt(PVI|Hk z7NLk2l$qY7I;a!8`5)J0d9(A`HfNWWiJ<6o7V^-+w-hW(PQd56&z|GQTV9IW&vCa2 z4FHt+jg4x{(i&CGEG0PhC>>n2pDF=5=@%hiRTW0I%H!ib@LQZNS7DgNFGSA}ajq26 zZa;6yICTzq9daB|mivg&O_~*cgSaVc%R+=f_kSc4D+c$S%+hydu3Pht)x?k8 zA7bb3I9w2aa3@eXM&_QSY~wD^+pM5fx>t>rk&$R?zx}5w9XIx3_hzkCK8@>H1+H+nr?a}U*Zy%Gx2=I+W`CBitxLq)wKng(>d74yQiW$C%@KRm zF8h>f+IxHpsBDssZ4dw5}arK(X>L! z#QaR*pWsprwBTP|v26b*i`rRR67PF&pLp@k(QyRsS4!9LLhuC3K@|m&FP4y(&~)*; zh-1^W^=}rk_F7tgr6(^o2?@}XOi!>&?vAQ%o_W2If2E--0Pr%wZyXfCT`1BFU~s?3 zx3T9KyraBTG1GlhbeX`@BEWnIJBCvNOSl-8{&DQ+S}r;6P3TgSf;%=-sADXpLi018 zA(2hHuKhrxc%V&7ri)TtU8?KyIytIM*_-zt&qNl!fi5DZ_~+lD;)D9qG`3$~X{q|H z8wmMG5N6#hDM=*HBOgN3{mvS>)7B3GtH^w$;ddM;M~nnB3au;*nE`*NuM{=r6i9F@ zQm*xsXzrmm_9)p7!y3&w8Yu>U#hTQPyfz6TO%tz(?b{Hli@-OSgCav?j~_v zd5*++~co#EeLf%n63oT-CXsk2{zBt7v= z2aX7Et2sCT-Uu^M8UPLH)W*tdSUl6Pzb38bwa38rmupMzn5HU7?Ed#2qQbb!X62%+ zb6axqtH0iHUpf5r$jemc{mzDSjw}2gDg!@{ZHI7c?tr^clStZEAbwS=fAQnPMaDGU zUhOvHS+3iNfbUTm_4UX|(qEbLq)rif)F8Q?qgs!t9^*}q`XO}n_s_i{hx!+fQ&w72 zX-9p?=l-0_BpHY-)o~&A{4*5-%;Dok<3JlTX7;77?8S4+wWhYHr!@<`i9U>l=~} zLW@uko3q47MOq?$w}i4h3fHIH&&RD?|7zf|4I5f@LLq^Run;WSV!CL_<()4?nF)7^ zUjzx8$$t56n3q#SxzS@VS!weB;P;IYVxAuW{QS%fDX!1Tm6=UQqzUr%u1;a|`w&Y? z9zZUZErKCZaVZgRRh<}i!t{2j4|dI@ffaXM_M`e}B|V&EHKtZ!Noz?ccKWus71h|w zts|Wbx)n&bU0Y>I6)^=Iq9(w#uMr}ez?s57#7{RIYM@Hjzdvub_F2~CFE4&&jlggn zt@*p#58KF{eVOc&v}P+aas%1_G}6R75m752Xa6kaEMJG_!?+cmMEiw_mQK#45)Xqk zu{*nsbJvj6ek+2;)TJGb{R}Wg;URWhQ$S@g@7A!q=Wi5d2D{$np;?KBYJ`_dB3N}{ z@^c8>^YV|Tn$8w~=-9T=e))_vlt=c(YOum>@}je>oC+@KxL6fD?F?97tDV`t8D(oZ zj{CK!0E8C_$ingEn-2huQNb<;|9ue80?zhl&(GZLe5&X-OPuxI3Fir%n4!vxc_WFw#TzTc;tYa!6BaTPu ze;qG&%p+X>S-nVHU1L@!Z?%!f2)V!%?8ON{(@v52~zd8|y_>`gC#+y?oJ` ze~pNS1m(Y>f*7U%ko6o0RhDOqu`T-_Wqm|y_v^TUAckYbn6)Ga$rkoY?IS?#EYXE}hD zH%Y%5#X4@aXd1*u_=dFf9}m+s>m(UuWX?`HOFVzxJuk;O)^o!9eoJW2gPb!U>fXw% zZ9hsB@ystNm%JGR2*)~dYcf0o%nk)>H{8=%quY*2&Xoo)Sh%ZI!)%nsz3MEkd=O!_ zOnl9BOq`POFm4f!zE<1gU{}-Nr)MQt=odoqzG|58475G`C^w|09=|i|6>?WE!C$i` zJy0{GNvmF~w3wTnBPqrn&jUT5%KFvjTOnUl{XI@6I%>n1WqXxqDdfd#oM~&N@G1Jw zL7I~(6Y#?H`Nx$4-(wni>bf)+Oxdq>Lu#gPx&Oya7{wfCqt9e^paeBxwc5;^%&W)} zO`rp@akS8XZkHiRQ}_v*J1|*Ss-YV$phcmK%ABuuM~Hid6E@hCO^pj% z*}t3W>y2y!XGBJuhNe#5XMfdu;xUhULlE>^&Ke~f#jRSWUVQfAyxe|E_)|<85Eyo{ z0|Ta5pp)?(R~=`jjjA}fMf`C~e{0T_s9^}N!7;iD%a)3=$TJ&#KJD8W_urU0R8e^&0oe1j^;AO~$qRUczrD>Q@hBp7@d zF%ULhQCRvpamdbhM^U+VLG+5sf*JO2S&u+1xKp9xq3mrdIDN!vJ*JKe_;_FHwbl5VS#I|R=&1rJliW1vDAo4((`~tm^!XR~Vi=E}i4tKX-P2XL>9?SSV7Sx|a`lM`I7qWB#upP|R%X?K5*mDyVML_vc zvfD!Q<*2`1q{n==JWThlL$3?SZD753Y{6Q9&xsIe7{6x=Z25=mH*hJYWK=f?G@`;% z(J(keJ(6q-unD#fNvE~M7?|#MBd%CPev~r*u045dFeX+2m*W>0a@Mb|{X22Xm$PZ2 zVUudmr6=^FN?V!G@o1VOvMHur70vf1^nj*df;f-#nza*UnM4d9{<#ZRcuwEGmgtK{P} zros;D7!z7=589`GI}JP|s5j)%FO~0))UlP!DN7Hqp$$x=UH%y*!IDd}dN$c!$L{zp zfSb1eaScL~8(T*VM$VH@wF~Z~XObX&;$5f%)m$taK8@Izc(n*VkggqK@(-}GN=E=N zzYap(ML8rU0tfJg3hbeOT)owPocVuT!$!fJqyM~z~DNf?7r2!4|-l*lOeHNHX0zhO)2IB54pp9FS za0A4pu$p5eP8(3yUZN6!CG-O4HSI9?7xp^BPv}6gse~QojQCrG?z94CrMf8g z>relp*PZSOx-#D~*d5Wee1N2qVf6*^2h6aBye4cPnm~|hYIpu|u|zF7rpB*0 z|G0KVYbg;N)ALDyu>c*LhEBssA%0oyp@Etgbs)*ac44>Gy|83N3KZ7QxhWaXj$STv z0Z3D6+VO}D;6GfINNNGfmos;;qwW~C2HE#+0ReAiuAGTFIIa?yOJJqo!FxT7@qFN0fCL~P=U8_VX#2kEKdzZ?{=mx_ z3j4?9a;`>W=s+<9Yg`HKfovZG9*4Ana9Gs?1h29Hff(~{XATPtGN%a)X68SxTblnS F{|Aochi3o) literal 0 HcmV?d00001 From b9bff8f3a72427b5d2ae3f2f174dc5db11df0d50 Mon Sep 17 00:00:00 2001 From: Christian Clauss Date: Tue, 10 Dec 2019 15:53:50 +0100 Subject: [PATCH 0399/1071] Remove \r from strings (#1622) * Remove \r from strings * Satisfy tensorflow with numpy>=1.17.4 --- machine_learning/sequential_minimum_optimization.py | 8 ++++---- requirements.txt | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/machine_learning/sequential_minimum_optimization.py b/machine_learning/sequential_minimum_optimization.py index a98bd93f7a06..aa997d88cac9 100644 --- a/machine_learning/sequential_minimum_optimization.py +++ b/machine_learning/sequential_minimum_optimization.py @@ -85,7 +85,7 @@ def fit(self): i1, i2 = self.choose_alpha.send(state) state = None except StopIteration: - print("Optimization done!\r\nEvery sample satisfy the KKT condition!") + print("Optimization done!\nEvery sample satisfy the KKT condition!") break # 2: calculate new alpha2 and new alpha1 @@ -453,7 +453,7 @@ def call_func(*args, **kwargs): @count_time def test_cancel_data(): - print("Hello!\r\nStart test svm by smo algorithm!") + print("Hello!\nStart test svm by smo algorithm!") # 0: download dataset and load into pandas' dataframe if not os.path.exists(r"cancel_data.csv"): request = urllib.request.Request( @@ -499,13 +499,13 @@ def test_cancel_data(): for i in range(test_tags.shape[0]): if test_tags[i] == predict[i]: score += 1 - print(f"\r\nall: {test_num}\r\nright: {score}\r\nfalse: {test_num - score}") + print(f"\nall: {test_num}\nright: {score}\nfalse: {test_num - score}") print(f"Rough Accuracy: {score / test_tags.shape[0]}") def test_demonstration(): # change stdout - print("\r\nStart plot,please wait!!!") + print("\nStart plot,please wait!!!") sys.stdout = open(os.devnull, "w") ax1 = plt.subplot2grid((2, 2), (0, 0)) diff --git a/requirements.txt b/requirements.txt index 1f4b11fc3ea5..2c4ac59d3e09 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,7 +4,7 @@ fake_useragent flake8 matplotlib mypy -numpy +numpy>=1.17.4 opencv-python pandas pillow From d385472c6fe5abda18759f89e81efe1f0bf6da0f Mon Sep 17 00:00:00 2001 From: heartsmoking <327899144@qq.com> Date: Wed, 11 Dec 2019 14:57:08 +0800 Subject: [PATCH 0400/1071] Update find_min.py (#1627) Line 5: :return: max number in list "max" must be "min" --- maths/find_min.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/maths/find_min.py b/maths/find_min.py index 4d721ce82194..2af2e44ba353 100644 --- a/maths/find_min.py +++ b/maths/find_min.py @@ -2,7 +2,7 @@ def find_min(nums): """ Find Minimum Number in a List :param nums: contains elements - :return: max number in list + :return: min number in list >>> for nums in ([3, 2, 1], [-3, -2, -1], [3, -3, 0], [3.0, 3.1, 2.9]): ... find_min(nums) == min(nums) From bc5b92f7f9de09bbaba96cd7fc8b2853dc0c080c Mon Sep 17 00:00:00 2001 From: Muhammad Ibtihaj Naeem Date: Sat, 14 Dec 2019 10:46:02 +0500 Subject: [PATCH 0401/1071] Harmonic Geometric and P-Series Added (#1633) * Harmonic Geometric and P-Series Added * Editing comments * Update and rename series/Geometric_Series.py to maths/series/geometric_series.py * Update and rename series/Harmonic_Series.py to maths/series/harmonic_series.py * Update and rename series/P_Series.py to maths/series/p_series.py --- maths/series/geometric_series.py | 63 ++++++++++++++++++++++++++++++++ maths/series/harmonic_series.py | 46 +++++++++++++++++++++++ maths/series/p_series.py | 48 ++++++++++++++++++++++++ 3 files changed, 157 insertions(+) create mode 100644 maths/series/geometric_series.py create mode 100644 maths/series/harmonic_series.py create mode 100644 maths/series/p_series.py diff --git a/maths/series/geometric_series.py b/maths/series/geometric_series.py new file mode 100644 index 000000000000..d12382e6d8c4 --- /dev/null +++ b/maths/series/geometric_series.py @@ -0,0 +1,63 @@ +""" +This is a pure Python implementation of the Geometric Series algorithm +https://en.wikipedia.org/wiki/Geometric_series + +Run the doctests with the following command: +python3 -m doctest -v geometric_series.py +or +python -m doctest -v geometric_series.py +For manual testing run: +python3 geometric_series.py +""" + + +def geometric_series(nth_term: int, start_term_a: int, common_ratio_r: int) -> list: + """Pure Python implementation of Geometric Series algorithm + :param nth_term: The last term (nth term of Geometric Series) + :param start_term_a : The first term of Geometric Series + :param common_ratio_r : The common ratio between all the terms + :return: The Geometric Series starting from first term a and multiple of common + ration with first term with increase in power till last term (nth term) + Examples: + >>> geometric_series(4, 2, 2) + [2, '4.0', '8.0', '16.0'] + >>> geometric_series(4.0, 2.0, 2.0) + [2.0, '4.0', '8.0', '16.0'] + >>> geometric_series(4.1, 2.1, 2.1) + [2.1, '4.41', '9.261000000000001', '19.448100000000004'] + >>> geometric_series(4, 2, -2) + [2, '-4.0', '8.0', '-16.0'] + >>> geometric_series(4, -2, 2) + [-2, '-4.0', '-8.0', '-16.0'] + >>> geometric_series(-4, 2, 2) + [] + >>> geometric_series(0, 100, 500) + [] + >>> geometric_series(1, 1, 1) + [1] + >>> geometric_series(0, 0, 0) + [] + """ + if "" in (nth_term, start_term_a, common_ratio_r): + return "" + series = [] + power = 1 + multiple = common_ratio_r + for _ in range(int(nth_term)): + if series == []: + series.append(start_term_a) + else: + power += 1 + series.append(str(float(start_term_a) * float(multiple))) + multiple = pow(float(common_ratio_r), power) + return series + + +if __name__ == "__main__": + nth_term = input("Enter the last number (n term) of the Geometric Series") + start_term_a = input("Enter the starting term (a) of the Geometric Series") + common_ratio_r = input( + "Enter the common ratio between two terms (r) of the Geometric Series" + ) + print("Formula of Geometric Series => a + ar + ar^2 ... +ar^n") + print(geometric_series(nth_term, start_term_a, common_ratio_r)) diff --git a/maths/series/harmonic_series.py b/maths/series/harmonic_series.py new file mode 100644 index 000000000000..91b5944583e4 --- /dev/null +++ b/maths/series/harmonic_series.py @@ -0,0 +1,46 @@ +""" +This is a pure Python implementation of the Harmonic Series algorithm +https://en.wikipedia.org/wiki/Harmonic_series_(mathematics) + +For doctests run following command: +python -m doctest -v harmonic_series.py +or +python3 -m doctest -v harmonic_series.py + +For manual testing run: +python3 harmonic_series.py +""" + + +def harmonic_series(n_term: str) -> list: + """Pure Python implementation of Harmonic Series algorithm + + :param n_term: The last (nth) term of Harmonic Series + :return: The Harmonic Series starting from 1 to last (nth) term + + Examples: + >>> harmonic_series(5) + ['1', '1/2', '1/3', '1/4', '1/5'] + >>> harmonic_series(5.0) + ['1', '1/2', '1/3', '1/4', '1/5'] + >>> harmonic_series(5.1) + ['1', '1/2', '1/3', '1/4', '1/5'] + >>> harmonic_series(-5) + [] + >>> harmonic_series(0) + [] + >>> harmonic_series(1) + ['1'] + """ + if n_term == "": + return n_term + series = [] + for temp in range(int(n_term)): + series.append(f"1/{temp + 1}" if series else "1") + return series + + +if __name__ == "__main__": + nth_term = input("Enter the last number (nth term) of the Harmonic Series") + print("Formula of Harmonic Series => 1+1/2+1/3 ..... 1/n") + print(harmonic_series(nth_term)) diff --git a/maths/series/p_series.py b/maths/series/p_series.py new file mode 100644 index 000000000000..04019aed5a85 --- /dev/null +++ b/maths/series/p_series.py @@ -0,0 +1,48 @@ +""" +This is a pure Python implementation of the P-Series algorithm +https://en.wikipedia.org/wiki/Harmonic_series_(mathematics)#P-series + +For doctests run following command: +python -m doctest -v p_series.py +or +python3 -m doctest -v p_series.py + +For manual testing run: +python3 p_series.py +""" + + +def p_series(nth_term: int, power: int) -> list: + """Pure Python implementation of P-Series algorithm + + :return: The P-Series starting from 1 to last (nth) term + + Examples: + >>> p_series(5, 2) + [1, '1/4', '1/9', '1/16', '1/25'] + >>> p_series(-5, 2) + [] + >>> p_series(5, -2) + [1, '1/0.25', '1/0.1111111111111111', '1/0.0625', '1/0.04'] + >>> p_series("", 1000) + '' + >>> p_series(0, 0) + [] + >>> p_series(1, 1) + [1] + """ + if nth_term == "": + return nth_term + nth_term = int(nth_term) + power = int(power) + series = [] + for temp in range(int(nth_term)): + series.append(f"1/{pow(temp + 1, int(power))}" if series else 1) + return series + + +if __name__ == "__main__": + nth_term = input("Enter the last number (nth term) of the P-Series") + power = input("Enter the power for P-Series") + print("Formula of P-Series => 1+1/2^p+1/3^p ..... 1/n^p") + print(p_series(nth_term, power)) From f4779bc04ae706b0c548e2fe6d7a59efe8b85524 Mon Sep 17 00:00:00 2001 From: Rohit Joshi <34398948+rohitjoshi21@users.noreply.github.com> Date: Sun, 15 Dec 2019 13:12:07 +0545 Subject: [PATCH 0402/1071] Bug Fixed in newton_raphson_method.py (#1634) * Bug Fixed * Fixed newton_raphson_method.py * Fixed newton_raphson_method.py 2 * Fixed newton_raphson_method.py 3 * Fixed newton_raphson_method.py 4 * Fixed newton_raphson_method.py 5 * Fixed newton_raphson_method.py 6 * Update newton_raphson_method.py * Update newton_raphson_method.py * # noqa: F401, F403 * newton_raphson * newton_raphson * precision: int=10 ** -10 * return float(x) * 3.1415926536808043 * Update newton_raphson_method.py * 2.23606797749979 * Update newton_raphson_method.py * Rename newton_raphson_method.py to newton_raphson.py --- arithmetic_analysis/newton_raphson.py | 40 ++++++++++++++++++++ arithmetic_analysis/newton_raphson_method.py | 34 ----------------- 2 files changed, 40 insertions(+), 34 deletions(-) create mode 100644 arithmetic_analysis/newton_raphson.py delete mode 100644 arithmetic_analysis/newton_raphson_method.py diff --git a/arithmetic_analysis/newton_raphson.py b/arithmetic_analysis/newton_raphson.py new file mode 100644 index 000000000000..8aa816cd0d04 --- /dev/null +++ b/arithmetic_analysis/newton_raphson.py @@ -0,0 +1,40 @@ +# Implementing Newton Raphson method in Python +# Author: Syed Haseeb Shah (github.com/QuantumNovice) +# The Newton-Raphson method (also known as Newton's method) is a way to +# quickly find a good approximation for the root of a real-valued function + +from decimal import Decimal +from math import * # noqa: F401, F403 +from sympy import diff + + +def newton_raphson(func: str, a: int, precision: int=10 ** -10) -> float: + """ Finds root from the point 'a' onwards by Newton-Raphson method + >>> newton_raphson("sin(x)", 2) + 3.1415926536808043 + >>> newton_raphson("x**2 - 5*x +2", 0.4) + 0.4384471871911695 + >>> newton_raphson("x**2 - 5", 0.1) + 2.23606797749979 + >>> newton_raphson("log(x)- 1", 2) + 2.718281828458938 + """ + x = a + while True: + x = Decimal(x) - (Decimal(eval(func)) / Decimal(eval(str(diff(func))))) + # This number dictates the accuracy of the answer + if abs(eval(func)) < precision: + return float(x) + + +# Let's Execute +if __name__ == "__main__": + # Find root of trigonometric function + # Find value of pi + print(f"The root of sin(x) = 0 is {newton_raphson('sin(x)', 2)}") + # Find root of polynomial + print(f"The root of x**2 - 5*x + 2 = 0 is {newton_raphson('x**2 - 5*x + 2', 0.4)}") + # Find Square Root of 5 + print(f"The root of log(x) - 1 = 0 is {newton_raphson('log(x) - 1', 2)}") + # Exponential Roots + print(f"The root of exp(x) - 1 = 0 is {newton_raphson('exp(x) - 1', 0)}") diff --git a/arithmetic_analysis/newton_raphson_method.py b/arithmetic_analysis/newton_raphson_method.py deleted file mode 100644 index 646b352a923c..000000000000 --- a/arithmetic_analysis/newton_raphson_method.py +++ /dev/null @@ -1,34 +0,0 @@ -# Implementing Newton Raphson method in Python -# Author: Syed Haseeb Shah (github.com/QuantumNovice) -# The Newton-Raphson method (also known as Newton's method) is a way to -# quickly find a good approximation for the root of a real-valued function -from sympy import diff -from decimal import Decimal - - -def NewtonRaphson(func, a): - """ Finds root from the point 'a' onwards by Newton-Raphson method """ - while True: - c = Decimal(a) - (Decimal(eval(func)) / Decimal(eval(str(diff(func))))) - - a = c - - # This number dictates the accuracy of the answer - if abs(eval(func)) < 10 ** -15: - return c - - -# Let's Execute -if __name__ == "__main__": - # Find root of trigonometric function - # Find value of pi - print("sin(x) = 0", NewtonRaphson("sin(x)", 2)) - - # Find root of polynomial - print("x**2 - 5*x +2 = 0", NewtonRaphson("x**2 - 5*x +2", 0.4)) - - # Find Square Root of 5 - print("x**2 - 5 = 0", NewtonRaphson("x**2 - 5", 0.1)) - - # Exponential Roots - print("exp(x) - 1 = 0", NewtonRaphson("exp(x) - 1", 0)) From 1af4c02ba6999dff2d9b8c3b3016a4774faed842 Mon Sep 17 00:00:00 2001 From: Iqrar Agalosi Nureyza Date: Wed, 18 Dec 2019 14:35:03 +0700 Subject: [PATCH 0403/1071] adding doctests on coin_change.py and fixed some typos (#1337) * adding doctests on coin_change.py * fixed some typos * Update lib.py --- dynamic_programming/coin_change.py | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/dynamic_programming/coin_change.py b/dynamic_programming/coin_change.py index a85d8e08dbb1..12ced411780f 100644 --- a/dynamic_programming/coin_change.py +++ b/dynamic_programming/coin_change.py @@ -8,6 +8,18 @@ def dp_count(S, m, n): + """ + >>> dp_count([1, 2, 3], 3, 4) + 4 + >>> dp_count([1, 2, 3], 3, 7) + 8 + >>> dp_count([2, 5, 3, 6], 4, 10) + 5 + >>> dp_count([10], 1, 99) + 0 + >>> dp_count([4, 5, 6], 3, 0) + 1 + """ # table[i] represents the number of ways to get to amount i table = [0] * (n + 1) @@ -24,7 +36,7 @@ def dp_count(S, m, n): return table[n] - if __name__ == "__main__": - print(dp_count([1, 2, 3], 3, 4)) # answer 4 - print(dp_count([2, 5, 3, 6], 4, 10)) # answer 5 + import doctest + + doctest.testmod() From 86dbf0a9d399c93280a9676ef3bf3cbc2fc2a284 Mon Sep 17 00:00:00 2001 From: faizan2700 <46817346+faizan2700@users.noreply.github.com> Date: Wed, 18 Dec 2019 19:55:12 +0530 Subject: [PATCH 0404/1071] file iterating_through_submasks.py for given mask is added in dynamic_programming (#1635) * new file *iterating_through_submasks* is added in dynamic_programming section * no changes * *iterating_through_submasks.py is added in dynamic_programming * iterating_through_submasks is added with doctests * iterating_through_submasks.py is added in dynamic_programming * changes made in *iterating_through_submasks.py * changes made in *iterating_through_submasks.py * updated --- .../iterating_through_submasks.py | 60 +++++++++++++++++++ 1 file changed, 60 insertions(+) create mode 100644 dynamic_programming/iterating_through_submasks.py diff --git a/dynamic_programming/iterating_through_submasks.py b/dynamic_programming/iterating_through_submasks.py new file mode 100644 index 000000000000..edeacc3124fa --- /dev/null +++ b/dynamic_programming/iterating_through_submasks.py @@ -0,0 +1,60 @@ +""" +Author : Syed Faizan (3rd Year Student IIIT Pune) +github : faizan2700 +You are given a bitmask m and you want to efficiently iterate through all of +its submasks. The mask s is submask of m if only bits that were included in +bitmask are set +""" +from typing import List + + +def list_of_submasks(mask: int) -> List[int]: + + """ + Args: + mask : number which shows mask ( always integer > 0, zero does not have any submasks ) + + Returns: + all_submasks : the list of submasks of mask (mask s is called submask of mask + m if only bits that were included in original mask are set + + Raises: + AssertionError: mask not positive integer + + >>> list_of_submasks(15) + [15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1] + >>> list_of_submasks(13) + [13, 12, 9, 8, 5, 4, 1] + >>> list_of_submasks(-7) # doctest: +ELLIPSIS + Traceback (most recent call last): + ... + AssertionError: mask needs to be positive integer, your input -7 + >>> list_of_submasks(0) # doctest: +ELLIPSIS + Traceback (most recent call last): + ... + AssertionError: mask needs to be positive integer, your input 0 + + """ + + fmt = "mask needs to be positive integer, your input {}" + assert isinstance(mask, int) and mask > 0, fmt.format(mask) + + """ + first submask iterated will be mask itself then operation will be performed + to get other submasks till we reach empty submask that is zero ( zero is not + included in final submasks list ) + """ + all_submasks = [] + submask = mask + + while submask: + all_submasks.append(submask) + submask = (submask - 1) & mask + + return all_submasks + + +if __name__ == "__main__": + import doctest + + doctest.testmod() From 5f57ac975f80bb79949d0dc222bb5fe124de6357 Mon Sep 17 00:00:00 2001 From: Kyle <40903431+kylepw@users.noreply.github.com> Date: Thu, 19 Dec 2019 18:40:16 +0900 Subject: [PATCH 0405/1071] Add docstr and algorithm to BFS shortest path module (#1637) * Add docs and type alias to bfs_shortest_path.py * Add bfs_shortest_path_distance algorithm * Make requested changes --- graphs/bfs_shortest_path.py | 71 +++++++++++++++++++++++++++++++++++-- 1 file changed, 69 insertions(+), 2 deletions(-) diff --git a/graphs/bfs_shortest_path.py b/graphs/bfs_shortest_path.py index ec82c13997e2..c3664796e677 100644 --- a/graphs/bfs_shortest_path.py +++ b/graphs/bfs_shortest_path.py @@ -1,3 +1,11 @@ +"""Breadth-first search shortest path implementations. + + doctest: + python -m doctest -v bfs_shortest_path.py + + Manual test: + python bfs_shortest_path.py +""" graph = { "A": ["B", "C", "E"], "B": ["A", "D", "E"], @@ -9,7 +17,22 @@ } -def bfs_shortest_path(graph, start, goal): +def bfs_shortest_path(graph: dict, start, goal) -> str: + """Find shortest path between `start` and `goal` nodes. + + Args: + graph (dict): node/list of neighboring nodes key/value pairs. + start: start node. + goal: target node. + + Returns: + Shortest path between `start` and `goal` nodes as a string of nodes. + 'Not found' string if no path found. + + Example: + >>> bfs_shortest_path(graph, "G", "D") + ['G', 'C', 'A', 'B', 'D'] + """ # keep track of explored nodes explored = [] # keep track of all the paths to be checked @@ -44,4 +67,48 @@ def bfs_shortest_path(graph, start, goal): return "So sorry, but a connecting path doesn't exist :(" -bfs_shortest_path(graph, "G", "D") # returns ['G', 'C', 'A', 'B', 'D'] +def bfs_shortest_path_distance(graph: dict, start, target) -> int: + """Find shortest path distance between `start` and `target` nodes. + + Args: + graph: node/list of neighboring nodes key/value pairs. + start: node to start search from. + target: node to search for. + + Returns: + Number of edges in shortest path between `start` and `target` nodes. + -1 if no path exists. + + Example: + >>> bfs_shortest_path_distance(graph, "G", "D") + 4 + >>> bfs_shortest_path_distance(graph, "A", "A") + 0 + >>> bfs_shortest_path_distance(graph, "A", "H") + -1 + """ + if not graph or start not in graph or target not in graph: + return -1 + if start == target: + return 0 + queue = [start] + visited = [start] + # Keep tab on distances from `start` node. + dist = {start: 0, target: -1} + while queue: + node = queue.pop(0) + if node == target: + dist[target] = ( + dist[node] if dist[target] == -1 else min(dist[target], dist[node]) + ) + for adjacent in graph[node]: + if adjacent not in visited: + visited.append(adjacent) + queue.append(adjacent) + dist[adjacent] = dist[node] + 1 + return dist[target] + + +if __name__ == "__main__": + print(bfs_shortest_path(graph, "G", "D")) # returns ['G', 'C', 'A', 'B', 'D'] + print(bfs_shortest_path_distance(graph, "G", "D")) # returns 4 From c67776da597aef98e27fd1f701e7de987fdd4bb2 Mon Sep 17 00:00:00 2001 From: faizan2700 <46817346+faizan2700@users.noreply.github.com> Date: Sat, 21 Dec 2019 00:57:32 +0530 Subject: [PATCH 0406/1071] other/integeration_by_simpson_approx.py is added for approximate integeration (#1638) * new file *iterating_through_submasks* is added in dynamic_programming section * no changes * *iterating_through_submasks.py is added in dynamic_programming * iterating_through_submasks is added with doctests * iterating_through_submasks.py is added in dynamic_programming * changes made in *iterating_through_submasks.py * changes made in *iterating_through_submasks.py * updated * *other/integeration_by_simpson_approx.py added * *other/integeration_by_simpson_approx.py Added for integeration * Delete iterating_through_submasks.py * Delete DIRECTORY.md * Revert "updated" This reverts commit 73456f85de03782b7d3c794eca8390a4fe87037c. * changes made *integeration_by_simpson_approx.py * update2 Co-authored-by: Christian Clauss --- other/integeration_by_simpson_approx.py | 123 ++++++++++++++++++++++++ 1 file changed, 123 insertions(+) create mode 100644 other/integeration_by_simpson_approx.py diff --git a/other/integeration_by_simpson_approx.py b/other/integeration_by_simpson_approx.py new file mode 100644 index 000000000000..2115ac9a5146 --- /dev/null +++ b/other/integeration_by_simpson_approx.py @@ -0,0 +1,123 @@ +""" +Author : Syed Faizan ( 3rd Year IIIT Pune ) +Github : faizan2700 + +Purpose : You have one function f(x) which takes float integer and returns +float you have to integrate the function in limits a to b. +The approximation proposed by Thomas Simpsons in 1743 is one way to calculate integration. + +( read article : https://cp-algorithms.com/num_methods/simpson-integration.html ) + +simpson_integration() takes function,lower_limit=a,upper_limit=b,precision and +returns the integration of function in given limit. +""" + +# constants +# the more the number of steps the more accurate +N_STEPS = 1000 + + +def f(x: float) -> float: + return x * x + + +""" +Summary of Simpson Approximation : + +By simpsons integration : +1.integration of fxdx with limit a to b is = f(x0) + 4 * f(x1) + 2 * f(x2) + 4 * f(x3) + 2 * f(x4)..... + f(xn) +where x0 = a +xi = a + i * h +xn = b +""" + + +def simpson_integration(function, a: float, b: float, precision: int = 4) -> float: + + """ + Args: + function : the function which's integration is desired + a : the lower limit of integration + b : upper limit of integraion + precision : precision of the result,error required default is 4 + + Returns: + result : the value of the approximated integration of function in range a to b + + Raises: + AssertionError: function is not callable + AssertionError: a is not float or integer + AssertionError: function should return float or integer + AssertionError: b is not float or integer + AssertionError: precision is not positive integer + + >>> simpson_integration(lambda x : x*x,1,2,3) + 2.333 + + >>> simpson_integration(lambda x : x*x,'wrong_input',2,3) + Traceback (most recent call last): + ... + AssertionError: a should be float or integer your input : wrong_input + + >>> simpson_integration(lambda x : x*x,1,'wrong_input',3) + Traceback (most recent call last): + ... + AssertionError: b should be float or integer your input : wrong_input + + >>> simpson_integration(lambda x : x*x,1,2,'wrong_input') + Traceback (most recent call last): + ... + AssertionError: precision should be positive integer your input : wrong_input + >>> simpson_integration('wrong_input',2,3,4) + Traceback (most recent call last): + ... + AssertionError: the function(object) passed should be callable your input : wrong_input + + >>> simpson_integration(lambda x : x*x,3.45,3.2,1) + -2.8 + + >>> simpson_integration(lambda x : x*x,3.45,3.2,0) + Traceback (most recent call last): + ... + AssertionError: precision should be positive integer your input : 0 + + >>> simpson_integration(lambda x : x*x,3.45,3.2,-1) + Traceback (most recent call last): + ... + AssertionError: precision should be positive integer your input : -1 + + """ + assert callable( + function + ), f"the function(object) passed should be callable your input : {function}" + assert isinstance(a, float) or isinstance( + a, int + ), f"a should be float or integer your input : {a}" + assert isinstance(function(a), float) or isinstance( + function(a), int + ), f"the function should return integer or float return type of your function, {type(a)}" + assert isinstance(b, float) or isinstance( + b, int + ), f"b should be float or integer your input : {b}" + assert ( + isinstance(precision, int) and precision > 0 + ), f"precision should be positive integer your input : {precision}" + + # just applying the formula of simpson for approximate integraion written in + # mentioned article in first comment of this file and above this function + + h = (b - a) / N_STEPS + result = function(a) + function(b) + + for i in range(1, N_STEPS): + a1 = a + h * i + result += function(a1) * (4 if i%2 else 2) + + result *= h / 3 + return round(result, precision) + + +if __name__ == "__main__": + import doctest + + doctest.testmod() From 1d9266eca08c4e58ba04a403168292edf4204b80 Mon Sep 17 00:00:00 2001 From: Saransh Gupta Date: Sat, 21 Dec 2019 04:22:43 +0530 Subject: [PATCH 0407/1071] Fixed warning string for Key B = 0 (#1639) * Fixed warning string for Key B = 0 * Update affine_cipher.py * Update affine_cipher.py * decrypt_message(encrypt_message()) Co-authored-by: Christian Clauss --- ciphers/affine_cipher.py | 93 +++++++++++++++++++++------------------- 1 file changed, 50 insertions(+), 43 deletions(-) diff --git a/ciphers/affine_cipher.py b/ciphers/affine_cipher.py index eb50acf8fc20..ad41feb32837 100644 --- a/ciphers/affine_cipher.py +++ b/ciphers/affine_cipher.py @@ -1,56 +1,63 @@ -import sys, random, cryptomath_module as cryptoMath +import random +import sys -SYMBOLS = r""" !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~""" +import cryptomath_module as cryptomath + +SYMBOLS = (r""" !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`""" + r"""abcdefghijklmnopqrstuvwxyz{|}~""") def main(): - message = input("Enter message: ") - key = int(input("Enter key [2000 - 9000]: ")) - mode = input("Encrypt/Decrypt [E/D]: ") + """ + >>> key = get_random_key() + >>> msg = "This is a test!" + >>> decrypt_message(key, encrypt_message(key, msg)) == msg + True + """ + message = input("Enter message: ").strip() + key = int(input("Enter key [2000 - 9000]: ").strip()) + mode = input("Encrypt/Decrypt [E/D]: ").strip().lower() - if mode.lower().startswith("e"): + if mode.startswith("e"): mode = "encrypt" - translated = encryptMessage(key, message) - elif mode.lower().startswith("d"): + translated = encrypt_message(key, message) + elif mode.startswith("d"): mode = "decrypt" - translated = decryptMessage(key, message) - print("\n%sed text: \n%s" % (mode.title(), translated)) - - -def getKeyParts(key): - keyA = key // len(SYMBOLS) - keyB = key % len(SYMBOLS) - return (keyA, keyB) - - -def checkKeys(keyA, keyB, mode): - if keyA == 1 and mode == "encrypt": - sys.exit( - "The affine cipher becomes weak when key A is set to 1. Choose different key" - ) - if keyB == 0 and mode == "encrypt": - sys.exit( - "The affine cipher becomes weak when key A is set to 1. Choose different key" - ) + translated = decrypt_message(key, message) + print(f"\n{mode.title()}ed text: \n{translated}") + + +def check_keys(keyA, keyB, mode): + if mode == "encrypt": + if keyA == 1: + sys.exit( + "The affine cipher becomes weak when key " + "A is set to 1. Choose different key" + ) + if keyB == 0: + sys.exit( + "The affine cipher becomes weak when key " + "B is set to 0. Choose different key" + ) if keyA < 0 or keyB < 0 or keyB > len(SYMBOLS) - 1: sys.exit( - "Key A must be greater than 0 and key B must be between 0 and %s." - % (len(SYMBOLS) - 1) + "Key A must be greater than 0 and key B must " + f"be between 0 and {len(SYMBOLS) - 1}." ) - if cryptoMath.gcd(keyA, len(SYMBOLS)) != 1: + if cryptomath.gcd(keyA, len(SYMBOLS)) != 1: sys.exit( - "Key A %s and the symbol set size %s are not relatively prime. Choose a different key." - % (keyA, len(SYMBOLS)) + f"Key A {keyA} and the symbol set size {len(SYMBOLS)} " + "are not relatively prime. Choose a different key." ) -def encryptMessage(key, message): +def encrypt_message(key: int, message: str) -> str: """ - >>> encryptMessage(4545, 'The affine cipher is a type of monoalphabetic substitution cipher.') + >>> encrypt_message(4545, 'The affine cipher is a type of monoalphabetic substitution cipher.') 'VL}p MM{I}p~{HL}Gp{vp pFsH}pxMpyxIx JHL O}F{~pvuOvF{FuF{xIp~{HL}Gi' """ - keyA, keyB = getKeyParts(key) - checkKeys(keyA, keyB, "encrypt") + keyA, keyB = divmod(key, len(SYMBOLS)) + check_keys(keyA, keyB, "encrypt") cipherText = "" for symbol in message: if symbol in SYMBOLS: @@ -61,15 +68,15 @@ def encryptMessage(key, message): return cipherText -def decryptMessage(key, message): +def decrypt_message(key: int, message: str) -> str: """ - >>> decryptMessage(4545, 'VL}p MM{I}p~{HL}Gp{vp pFsH}pxMpyxIx JHL O}F{~pvuOvF{FuF{xIp~{HL}Gi') + >>> decrypt_message(4545, 'VL}p MM{I}p~{HL}Gp{vp pFsH}pxMpyxIx JHL O}F{~pvuOvF{FuF{xIp~{HL}Gi') 'The affine cipher is a type of monoalphabetic substitution cipher.' """ - keyA, keyB = getKeyParts(key) - checkKeys(keyA, keyB, "decrypt") + keyA, keyB = divmod(key, len(SYMBOLS)) + check_keys(keyA, keyB, "decrypt") plainText = "" - modInverseOfkeyA = cryptoMath.findModInverse(keyA, len(SYMBOLS)) + modInverseOfkeyA = cryptomath.findModInverse(keyA, len(SYMBOLS)) for symbol in message: if symbol in SYMBOLS: symIndex = SYMBOLS.find(symbol) @@ -79,11 +86,11 @@ def decryptMessage(key, message): return plainText -def getRandomKey(): +def get_random_key(): while True: keyA = random.randint(2, len(SYMBOLS)) keyB = random.randint(2, len(SYMBOLS)) - if cryptoMath.gcd(keyA, len(SYMBOLS)) == 1: + if cryptomath.gcd(keyA, len(SYMBOLS)) == 1: return keyA * len(SYMBOLS) + keyB From 3242682473c58aebb1b8083443603487ea507589 Mon Sep 17 00:00:00 2001 From: Jasper <46252815+jasper256@users.noreply.github.com> Date: Fri, 20 Dec 2019 18:23:15 -0500 Subject: [PATCH 0408/1071] Create roman_to_integer.py (#1636) * added roman to integer conversion (LeetCode No. 13) * updated directory to include Roman to Integer * Delete DIRECTORY.md * Update roman_to_integer.py Co-authored-by: Christian Clauss --- DIRECTORY.md | 550 -------------------------------- conversions/roman_to_integer.py | 27 ++ 2 files changed, 27 insertions(+), 550 deletions(-) delete mode 100644 DIRECTORY.md create mode 100644 conversions/roman_to_integer.py diff --git a/DIRECTORY.md b/DIRECTORY.md deleted file mode 100644 index 468fe65298d9..000000000000 --- a/DIRECTORY.md +++ /dev/null @@ -1,550 +0,0 @@ - -## Arithmetic Analysis - * [Bisection](https://github.com/TheAlgorithms/Python/blob/master/arithmetic_analysis/bisection.py) - * [Gaussian Elimination](https://github.com/TheAlgorithms/Python/blob/master/arithmetic_analysis/gaussian_elimination.py) - * [In Static Equilibrium](https://github.com/TheAlgorithms/Python/blob/master/arithmetic_analysis/in_static_equilibrium.py) - * [Intersection](https://github.com/TheAlgorithms/Python/blob/master/arithmetic_analysis/intersection.py) - * [Lu Decomposition](https://github.com/TheAlgorithms/Python/blob/master/arithmetic_analysis/lu_decomposition.py) - * [Newton Forward Interpolation](https://github.com/TheAlgorithms/Python/blob/master/arithmetic_analysis/newton_forward_interpolation.py) - * [Newton Method](https://github.com/TheAlgorithms/Python/blob/master/arithmetic_analysis/newton_method.py) - * [Newton Raphson Method](https://github.com/TheAlgorithms/Python/blob/master/arithmetic_analysis/newton_raphson_method.py) - * [Secant Method](https://github.com/TheAlgorithms/Python/blob/master/arithmetic_analysis/secant_method.py) - -## Backtracking - * [All Combinations](https://github.com/TheAlgorithms/Python/blob/master/backtracking/all_combinations.py) - * [All Permutations](https://github.com/TheAlgorithms/Python/blob/master/backtracking/all_permutations.py) - * [All Subsequences](https://github.com/TheAlgorithms/Python/blob/master/backtracking/all_subsequences.py) - * [Minimax](https://github.com/TheAlgorithms/Python/blob/master/backtracking/minimax.py) - * [N Queens](https://github.com/TheAlgorithms/Python/blob/master/backtracking/n_queens.py) - * [Sudoku](https://github.com/TheAlgorithms/Python/blob/master/backtracking/sudoku.py) - * [Sum Of Subsets](https://github.com/TheAlgorithms/Python/blob/master/backtracking/sum_of_subsets.py) - -## Blockchain - * [Chinese Remainder Theorem](https://github.com/TheAlgorithms/Python/blob/master/blockchain/chinese_remainder_theorem.py) - * [Diophantine Equation](https://github.com/TheAlgorithms/Python/blob/master/blockchain/diophantine_equation.py) - * [Modular Division](https://github.com/TheAlgorithms/Python/blob/master/blockchain/modular_division.py) - -## Boolean Algebra - * [Quine Mc Cluskey](https://github.com/TheAlgorithms/Python/blob/master/boolean_algebra/quine_mc_cluskey.py) - -## Ciphers - * [Affine Cipher](https://github.com/TheAlgorithms/Python/blob/master/ciphers/affine_cipher.py) - * [Atbash](https://github.com/TheAlgorithms/Python/blob/master/ciphers/atbash.py) - * [Base16](https://github.com/TheAlgorithms/Python/blob/master/ciphers/base16.py) - * [Base32](https://github.com/TheAlgorithms/Python/blob/master/ciphers/base32.py) - * [Base64 Cipher](https://github.com/TheAlgorithms/Python/blob/master/ciphers/base64_cipher.py) - * [Base85](https://github.com/TheAlgorithms/Python/blob/master/ciphers/base85.py) - * [Brute Force Caesar Cipher](https://github.com/TheAlgorithms/Python/blob/master/ciphers/brute_force_caesar_cipher.py) - * [Caesar Cipher](https://github.com/TheAlgorithms/Python/blob/master/ciphers/caesar_cipher.py) - * [Cryptomath Module](https://github.com/TheAlgorithms/Python/blob/master/ciphers/cryptomath_module.py) - * [Deterministic Miller Rabin](https://github.com/TheAlgorithms/Python/blob/master/ciphers/deterministic_miller_rabin.py) - * [Diffie](https://github.com/TheAlgorithms/Python/blob/master/ciphers/diffie.py) - * [Elgamal Key Generator](https://github.com/TheAlgorithms/Python/blob/master/ciphers/elgamal_key_generator.py) - * [Hill Cipher](https://github.com/TheAlgorithms/Python/blob/master/ciphers/hill_cipher.py) - * [Mixed Keyword Cypher](https://github.com/TheAlgorithms/Python/blob/master/ciphers/mixed_keyword_cypher.py) - * [Morse Code Implementation](https://github.com/TheAlgorithms/Python/blob/master/ciphers/morse_code_implementation.py) - * [Onepad Cipher](https://github.com/TheAlgorithms/Python/blob/master/ciphers/onepad_cipher.py) - * [Playfair Cipher](https://github.com/TheAlgorithms/Python/blob/master/ciphers/playfair_cipher.py) - * [Porta Cipher](https://github.com/TheAlgorithms/Python/blob/master/ciphers/porta_cipher.py) - * [Rabin Miller](https://github.com/TheAlgorithms/Python/blob/master/ciphers/rabin_miller.py) - * [Rot13](https://github.com/TheAlgorithms/Python/blob/master/ciphers/rot13.py) - * [Rsa Cipher](https://github.com/TheAlgorithms/Python/blob/master/ciphers/rsa_cipher.py) - * [Rsa Factorization](https://github.com/TheAlgorithms/Python/blob/master/ciphers/rsa_factorization.py) - * [Rsa Key Generator](https://github.com/TheAlgorithms/Python/blob/master/ciphers/rsa_key_generator.py) - * [Shuffled Shift Cipher](https://github.com/TheAlgorithms/Python/blob/master/ciphers/shuffled_shift_cipher.py) - * [Simple Keyword Cypher](https://github.com/TheAlgorithms/Python/blob/master/ciphers/simple_keyword_cypher.py) - * [Simple Substitution Cipher](https://github.com/TheAlgorithms/Python/blob/master/ciphers/simple_substitution_cipher.py) - * [Trafid Cipher](https://github.com/TheAlgorithms/Python/blob/master/ciphers/trafid_cipher.py) - * [Transposition Cipher](https://github.com/TheAlgorithms/Python/blob/master/ciphers/transposition_cipher.py) - * [Transposition Cipher Encrypt Decrypt File](https://github.com/TheAlgorithms/Python/blob/master/ciphers/transposition_cipher_encrypt_decrypt_file.py) - * [Vigenere Cipher](https://github.com/TheAlgorithms/Python/blob/master/ciphers/vigenere_cipher.py) - * [Xor Cipher](https://github.com/TheAlgorithms/Python/blob/master/ciphers/xor_cipher.py) - -## Compression - * [Burrows Wheeler](https://github.com/TheAlgorithms/Python/blob/master/compression/burrows_wheeler.py) - * [Huffman](https://github.com/TheAlgorithms/Python/blob/master/compression/huffman.py) - * [Peak Signal To Noise Ratio](https://github.com/TheAlgorithms/Python/blob/master/compression/peak_signal_to_noise_ratio.py) - -## Conversions - * [Decimal To Binary](https://github.com/TheAlgorithms/Python/blob/master/conversions/decimal_to_binary.py) - * [Decimal To Hexadecimal](https://github.com/TheAlgorithms/Python/blob/master/conversions/decimal_to_hexadecimal.py) - * [Decimal To Octal](https://github.com/TheAlgorithms/Python/blob/master/conversions/decimal_to_octal.py) - -## Data Structures - * Binary Tree - * [Avl Tree](https://github.com/TheAlgorithms/Python/blob/master/data_structures/binary_tree/avl_tree.py) - * [Basic Binary Tree](https://github.com/TheAlgorithms/Python/blob/master/data_structures/binary_tree/basic_binary_tree.py) - * [Binary Search Tree](https://github.com/TheAlgorithms/Python/blob/master/data_structures/binary_tree/binary_search_tree.py) - * [Fenwick Tree](https://github.com/TheAlgorithms/Python/blob/master/data_structures/binary_tree/fenwick_tree.py) - * [Lazy Segment Tree](https://github.com/TheAlgorithms/Python/blob/master/data_structures/binary_tree/lazy_segment_tree.py) - * [Lca](https://github.com/TheAlgorithms/Python/blob/master/data_structures/binary_tree/lca.py) - * [Non Recursive Segment Tree](https://github.com/TheAlgorithms/Python/blob/master/data_structures/binary_tree/non_recursive_segment_tree.py) - * [Red Black Tree](https://github.com/TheAlgorithms/Python/blob/master/data_structures/binary_tree/red_black_tree.py) - * [Segment Tree](https://github.com/TheAlgorithms/Python/blob/master/data_structures/binary_tree/segment_tree.py) - * [Treap](https://github.com/TheAlgorithms/Python/blob/master/data_structures/binary_tree/treap.py) - * Data Structures - * Heap - * [Heap Generic](https://github.com/TheAlgorithms/Python/blob/master/data_structures/data_structures/heap/heap_generic.py) - * Disjoint Set - * [Disjoint Set](https://github.com/TheAlgorithms/Python/blob/master/data_structures/disjoint_set/disjoint_set.py) - * Hashing - * [Double Hash](https://github.com/TheAlgorithms/Python/blob/master/data_structures/hashing/double_hash.py) - * [Hash Table](https://github.com/TheAlgorithms/Python/blob/master/data_structures/hashing/hash_table.py) - * [Hash Table With Linked List](https://github.com/TheAlgorithms/Python/blob/master/data_structures/hashing/hash_table_with_linked_list.py) - * Number Theory - * [Prime Numbers](https://github.com/TheAlgorithms/Python/blob/master/data_structures/hashing/number_theory/prime_numbers.py) - * [Quadratic Probing](https://github.com/TheAlgorithms/Python/blob/master/data_structures/hashing/quadratic_probing.py) - * Heap - * [Binomial Heap](https://github.com/TheAlgorithms/Python/blob/master/data_structures/heap/binomial_heap.py) - * [Heap](https://github.com/TheAlgorithms/Python/blob/master/data_structures/heap/heap.py) - * [Min Heap](https://github.com/TheAlgorithms/Python/blob/master/data_structures/heap/min_heap.py) - * Linked List - * [Circular Linked List](https://github.com/TheAlgorithms/Python/blob/master/data_structures/linked_list/circular_linked_list.py) - * [Doubly Linked List](https://github.com/TheAlgorithms/Python/blob/master/data_structures/linked_list/doubly_linked_list.py) - * [From Sequence](https://github.com/TheAlgorithms/Python/blob/master/data_structures/linked_list/from_sequence.py) - * [Is Palindrome](https://github.com/TheAlgorithms/Python/blob/master/data_structures/linked_list/is_palindrome.py) - * [Print Reverse](https://github.com/TheAlgorithms/Python/blob/master/data_structures/linked_list/print_reverse.py) - * [Singly Linked List](https://github.com/TheAlgorithms/Python/blob/master/data_structures/linked_list/singly_linked_list.py) - * [Swap Nodes](https://github.com/TheAlgorithms/Python/blob/master/data_structures/linked_list/swap_nodes.py) - * Queue - * [Circular Queue](https://github.com/TheAlgorithms/Python/blob/master/data_structures/queue/circular_queue.py) - * [Double Ended Queue](https://github.com/TheAlgorithms/Python/blob/master/data_structures/queue/double_ended_queue.py) - * [Linked Queue](https://github.com/TheAlgorithms/Python/blob/master/data_structures/queue/linked_queue.py) - * [Queue On List](https://github.com/TheAlgorithms/Python/blob/master/data_structures/queue/queue_on_list.py) - * [Queue On Pseudo Stack](https://github.com/TheAlgorithms/Python/blob/master/data_structures/queue/queue_on_pseudo_stack.py) - * Stacks - * [Balanced Parentheses](https://github.com/TheAlgorithms/Python/blob/master/data_structures/stacks/balanced_parentheses.py) - * [Infix To Postfix Conversion](https://github.com/TheAlgorithms/Python/blob/master/data_structures/stacks/infix_to_postfix_conversion.py) - * [Infix To Prefix Conversion](https://github.com/TheAlgorithms/Python/blob/master/data_structures/stacks/infix_to_prefix_conversion.py) - * [Linked Stack](https://github.com/TheAlgorithms/Python/blob/master/data_structures/stacks/linked_stack.py) - * [Next Greater Element](https://github.com/TheAlgorithms/Python/blob/master/data_structures/stacks/next_greater_element.py) - * [Postfix Evaluation](https://github.com/TheAlgorithms/Python/blob/master/data_structures/stacks/postfix_evaluation.py) - * [Stack](https://github.com/TheAlgorithms/Python/blob/master/data_structures/stacks/stack.py) - * [Stock Span Problem](https://github.com/TheAlgorithms/Python/blob/master/data_structures/stacks/stock_span_problem.py) - * Trie - * [Trie](https://github.com/TheAlgorithms/Python/blob/master/data_structures/trie/trie.py) - -## Digital Image Processing - * [Change Contrast](https://github.com/TheAlgorithms/Python/blob/master/digital_image_processing/change_contrast.py) - * Edge Detection - * [Canny](https://github.com/TheAlgorithms/Python/blob/master/digital_image_processing/edge_detection/canny.py) - * Filters - * [Convolve](https://github.com/TheAlgorithms/Python/blob/master/digital_image_processing/filters/convolve.py) - * [Gaussian Filter](https://github.com/TheAlgorithms/Python/blob/master/digital_image_processing/filters/gaussian_filter.py) - * [Median Filter](https://github.com/TheAlgorithms/Python/blob/master/digital_image_processing/filters/median_filter.py) - * [Sobel Filter](https://github.com/TheAlgorithms/Python/blob/master/digital_image_processing/filters/sobel_filter.py) - * [Index Calculation](https://github.com/TheAlgorithms/Python/blob/master/digital_image_processing/index_calculation.py) - * Rotation - * [Rotation](https://github.com/TheAlgorithms/Python/blob/master/digital_image_processing/rotation/rotation.py) - * [Test Digital Image Processing](https://github.com/TheAlgorithms/Python/blob/master/digital_image_processing/test_digital_image_processing.py) - -## Divide And Conquer - * [Closest Pair Of Points](https://github.com/TheAlgorithms/Python/blob/master/divide_and_conquer/closest_pair_of_points.py) - * [Convex Hull](https://github.com/TheAlgorithms/Python/blob/master/divide_and_conquer/convex_hull.py) - * [Inversions](https://github.com/TheAlgorithms/Python/blob/master/divide_and_conquer/inversions.py) - * [Max Subarray Sum](https://github.com/TheAlgorithms/Python/blob/master/divide_and_conquer/max_subarray_sum.py) - * [Mergesort](https://github.com/TheAlgorithms/Python/blob/master/divide_and_conquer/mergesort.py) - -## Dynamic Programming - * [Abbreviation](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/abbreviation.py) - * [Bitmask](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/bitmask.py) - * [Climbing Stairs](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/climbing_stairs.py) - * [Coin Change](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/coin_change.py) - * [Edit Distance](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/edit_distance.py) - * [Factorial](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/factorial.py) - * [Fast Fibonacci](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/fast_fibonacci.py) - * [Fibonacci](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/fibonacci.py) - * [Floyd Warshall](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/floyd_warshall.py) - * [Fractional Knapsack](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/fractional_knapsack.py) - * [Fractional Knapsack 2](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/fractional_knapsack_2.py) - * [Integer Partition](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/integer_partition.py) - * [K Means Clustering Tensorflow](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/k_means_clustering_tensorflow.py) - * [Knapsack](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/knapsack.py) - * [Longest Common Subsequence](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/longest_common_subsequence.py) - * [Longest Increasing Subsequence](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/longest_increasing_subsequence.py) - * [Longest Increasing Subsequence O(Nlogn)](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/longest_increasing_subsequence_o(nlogn).py) - * [Longest Sub Array](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/longest_sub_array.py) - * [Matrix Chain Order](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/matrix_chain_order.py) - * [Max Sub Array](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/max_sub_array.py) - * [Max Sum Contigous Subsequence](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/max_sum_contigous_subsequence.py) - * [Minimum Partition](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/minimum_partition.py) - * [Rod Cutting](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/rod_cutting.py) - * [Subset Generation](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/subset_generation.py) - * [Sum Of Subset](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/sum_of_subset.py) - -## File Transfer - * [Recieve File](https://github.com/TheAlgorithms/Python/blob/master/file_transfer/recieve_file.py) - * [Send File](https://github.com/TheAlgorithms/Python/blob/master/file_transfer/send_file.py) - -## Fuzzy Logic - * [Fuzzy Operations](https://github.com/TheAlgorithms/Python/blob/master/fuzzy_logic/fuzzy_operations.py) - -## Graphs - * [A Star](https://github.com/TheAlgorithms/Python/blob/master/graphs/a_star.py) - * [Articulation Points](https://github.com/TheAlgorithms/Python/blob/master/graphs/articulation_points.py) - * [Basic Graphs](https://github.com/TheAlgorithms/Python/blob/master/graphs/basic_graphs.py) - * [Bellman Ford](https://github.com/TheAlgorithms/Python/blob/master/graphs/bellman_ford.py) - * [Bfs](https://github.com/TheAlgorithms/Python/blob/master/graphs/bfs.py) - * [Bfs Shortest Path](https://github.com/TheAlgorithms/Python/blob/master/graphs/bfs_shortest_path.py) - * [Breadth First Search](https://github.com/TheAlgorithms/Python/blob/master/graphs/breadth_first_search.py) - * [Check Bipartite Graph Bfs](https://github.com/TheAlgorithms/Python/blob/master/graphs/check_bipartite_graph_bfs.py) - * [Check Bipartite Graph Dfs](https://github.com/TheAlgorithms/Python/blob/master/graphs/check_bipartite_graph_dfs.py) - * [Depth First Search](https://github.com/TheAlgorithms/Python/blob/master/graphs/depth_first_search.py) - * [Dfs](https://github.com/TheAlgorithms/Python/blob/master/graphs/dfs.py) - * [Dijkstra](https://github.com/TheAlgorithms/Python/blob/master/graphs/dijkstra.py) - * [Dijkstra 2](https://github.com/TheAlgorithms/Python/blob/master/graphs/dijkstra_2.py) - * [Dijkstra Algorithm](https://github.com/TheAlgorithms/Python/blob/master/graphs/dijkstra_algorithm.py) - * [Dinic](https://github.com/TheAlgorithms/Python/blob/master/graphs/dinic.py) - * [Directed And Undirected (Weighted) Graph](https://github.com/TheAlgorithms/Python/blob/master/graphs/directed_and_undirected_(weighted)_graph.py) - * [Edmonds Karp Multiple Source And Sink](https://github.com/TheAlgorithms/Python/blob/master/graphs/edmonds_karp_multiple_source_and_sink.py) - * [Eulerian Path And Circuit For Undirected Graph](https://github.com/TheAlgorithms/Python/blob/master/graphs/eulerian_path_and_circuit_for_undirected_graph.py) - * [Even Tree](https://github.com/TheAlgorithms/Python/blob/master/graphs/even_tree.py) - * [Finding Bridges](https://github.com/TheAlgorithms/Python/blob/master/graphs/finding_bridges.py) - * [G Topological Sort](https://github.com/TheAlgorithms/Python/blob/master/graphs/g_topological_sort.py) - * [Graph List](https://github.com/TheAlgorithms/Python/blob/master/graphs/graph_list.py) - * [Graph Matrix](https://github.com/TheAlgorithms/Python/blob/master/graphs/graph_matrix.py) - * [Graphs Floyd Warshall](https://github.com/TheAlgorithms/Python/blob/master/graphs/graphs_floyd_warshall.py) - * [Kahns Algorithm Long](https://github.com/TheAlgorithms/Python/blob/master/graphs/kahns_algorithm_long.py) - * [Kahns Algorithm Topo](https://github.com/TheAlgorithms/Python/blob/master/graphs/kahns_algorithm_topo.py) - * [Minimum Spanning Tree Kruskal](https://github.com/TheAlgorithms/Python/blob/master/graphs/minimum_spanning_tree_kruskal.py) - * [Minimum Spanning Tree Prims](https://github.com/TheAlgorithms/Python/blob/master/graphs/minimum_spanning_tree_prims.py) - * [Multi Hueristic Astar](https://github.com/TheAlgorithms/Python/blob/master/graphs/multi_hueristic_astar.py) - * [Page Rank](https://github.com/TheAlgorithms/Python/blob/master/graphs/page_rank.py) - * [Prim](https://github.com/TheAlgorithms/Python/blob/master/graphs/prim.py) - * [Scc Kosaraju](https://github.com/TheAlgorithms/Python/blob/master/graphs/scc_kosaraju.py) - * [Tarjans Scc](https://github.com/TheAlgorithms/Python/blob/master/graphs/tarjans_scc.py) - -## Hashes - * [Chaos Machine](https://github.com/TheAlgorithms/Python/blob/master/hashes/chaos_machine.py) - * [Enigma Machine](https://github.com/TheAlgorithms/Python/blob/master/hashes/enigma_machine.py) - * [Hamming Code](https://github.com/TheAlgorithms/Python/blob/master/hashes/hamming_code.py) - * [Md5](https://github.com/TheAlgorithms/Python/blob/master/hashes/md5.py) - * [Sha1](https://github.com/TheAlgorithms/Python/blob/master/hashes/sha1.py) - -## Linear Algebra - * Src - * [Lib](https://github.com/TheAlgorithms/Python/blob/master/linear_algebra/src/lib.py) - * [Polynom-For-Points](https://github.com/TheAlgorithms/Python/blob/master/linear_algebra/src/polynom-for-points.py) - * [Test Linear Algebra](https://github.com/TheAlgorithms/Python/blob/master/linear_algebra/src/test_linear_algebra.py) - -## Machine Learning - * [Decision Tree](https://github.com/TheAlgorithms/Python/blob/master/machine_learning/decision_tree.py) - * [Gradient Descent](https://github.com/TheAlgorithms/Python/blob/master/machine_learning/gradient_descent.py) - * [K Means Clust](https://github.com/TheAlgorithms/Python/blob/master/machine_learning/k_means_clust.py) - * [K Nearest Neighbours](https://github.com/TheAlgorithms/Python/blob/master/machine_learning/k_nearest_neighbours.py) - * [Knn Sklearn](https://github.com/TheAlgorithms/Python/blob/master/machine_learning/knn_sklearn.py) - * [Linear Discriminant Analysis](https://github.com/TheAlgorithms/Python/blob/master/machine_learning/linear_discriminant_analysis.py) - * [Linear Regression](https://github.com/TheAlgorithms/Python/blob/master/machine_learning/linear_regression.py) - * [Logistic Regression](https://github.com/TheAlgorithms/Python/blob/master/machine_learning/logistic_regression.py) - * [Multilayer Perceptron Classifier](https://github.com/TheAlgorithms/Python/blob/master/machine_learning/multilayer_perceptron_classifier.py) - * [Polymonial Regression](https://github.com/TheAlgorithms/Python/blob/master/machine_learning/polymonial_regression.py) - * [Scoring Functions](https://github.com/TheAlgorithms/Python/blob/master/machine_learning/scoring_functions.py) - * [Sequential Minimum Optimization](https://github.com/TheAlgorithms/Python/blob/master/machine_learning/sequential_minimum_optimization.py) - * [Support Vector Machines](https://github.com/TheAlgorithms/Python/blob/master/machine_learning/support_vector_machines.py) - -## Maths - * [3N+1](https://github.com/TheAlgorithms/Python/blob/master/maths/3n+1.py) - * [Abs](https://github.com/TheAlgorithms/Python/blob/master/maths/abs.py) - * [Abs Max](https://github.com/TheAlgorithms/Python/blob/master/maths/abs_max.py) - * [Abs Min](https://github.com/TheAlgorithms/Python/blob/master/maths/abs_min.py) - * [Average Mean](https://github.com/TheAlgorithms/Python/blob/master/maths/average_mean.py) - * [Average Median](https://github.com/TheAlgorithms/Python/blob/master/maths/average_median.py) - * [Average Mode](https://github.com/TheAlgorithms/Python/blob/master/maths/average_mode.py) - * [Basic Maths](https://github.com/TheAlgorithms/Python/blob/master/maths/basic_maths.py) - * [Binary Exponentiation](https://github.com/TheAlgorithms/Python/blob/master/maths/binary_exponentiation.py) - * [Binomial Coefficient](https://github.com/TheAlgorithms/Python/blob/master/maths/binomial_coefficient.py) - * [Ceil](https://github.com/TheAlgorithms/Python/blob/master/maths/ceil.py) - * [Collatz Sequence](https://github.com/TheAlgorithms/Python/blob/master/maths/collatz_sequence.py) - * [Eulers Totient](https://github.com/TheAlgorithms/Python/blob/master/maths/eulers_totient.py) - * [Explicit Euler](https://github.com/TheAlgorithms/Python/blob/master/maths/explicit_euler.py) - * [Extended Euclidean Algorithm](https://github.com/TheAlgorithms/Python/blob/master/maths/extended_euclidean_algorithm.py) - * [Factorial Python](https://github.com/TheAlgorithms/Python/blob/master/maths/factorial_python.py) - * [Factorial Recursive](https://github.com/TheAlgorithms/Python/blob/master/maths/factorial_recursive.py) - * [Factors](https://github.com/TheAlgorithms/Python/blob/master/maths/factors.py) - * [Fermat Little Theorem](https://github.com/TheAlgorithms/Python/blob/master/maths/fermat_little_theorem.py) - * [Fibonacci](https://github.com/TheAlgorithms/Python/blob/master/maths/fibonacci.py) - * [Fibonacci Sequence Recursion](https://github.com/TheAlgorithms/Python/blob/master/maths/fibonacci_sequence_recursion.py) - * [Find Max](https://github.com/TheAlgorithms/Python/blob/master/maths/find_max.py) - * [Find Max Recursion](https://github.com/TheAlgorithms/Python/blob/master/maths/find_max_recursion.py) - * [Find Min](https://github.com/TheAlgorithms/Python/blob/master/maths/find_min.py) - * [Find Min Recursion](https://github.com/TheAlgorithms/Python/blob/master/maths/find_min_recursion.py) - * [Floor](https://github.com/TheAlgorithms/Python/blob/master/maths/floor.py) - * [Gaussian](https://github.com/TheAlgorithms/Python/blob/master/maths/gaussian.py) - * [Greatest Common Divisor](https://github.com/TheAlgorithms/Python/blob/master/maths/greatest_common_divisor.py) - * [Hardy Ramanujanalgo](https://github.com/TheAlgorithms/Python/blob/master/maths/hardy_ramanujanalgo.py) - * [Is Square Free](https://github.com/TheAlgorithms/Python/blob/master/maths/is_square_free.py) - * [Jaccard Similarity](https://github.com/TheAlgorithms/Python/blob/master/maths/jaccard_similarity.py) - * [Karatsuba](https://github.com/TheAlgorithms/Python/blob/master/maths/karatsuba.py) - * [Kth Lexicographic Permutation](https://github.com/TheAlgorithms/Python/blob/master/maths/kth_lexicographic_permutation.py) - * [Largest Of Very Large Numbers](https://github.com/TheAlgorithms/Python/blob/master/maths/largest_of_very_large_numbers.py) - * [Least Common Multiple](https://github.com/TheAlgorithms/Python/blob/master/maths/least_common_multiple.py) - * [Lucas Lehmer Primality Test](https://github.com/TheAlgorithms/Python/blob/master/maths/lucas_lehmer_primality_test.py) - * [Lucas Series](https://github.com/TheAlgorithms/Python/blob/master/maths/lucas_series.py) - * [Matrix Exponentiation](https://github.com/TheAlgorithms/Python/blob/master/maths/matrix_exponentiation.py) - * [Mobius Function](https://github.com/TheAlgorithms/Python/blob/master/maths/mobius_function.py) - * [Modular Exponential](https://github.com/TheAlgorithms/Python/blob/master/maths/modular_exponential.py) - * [Newton Raphson](https://github.com/TheAlgorithms/Python/blob/master/maths/newton_raphson.py) - * [Perfect Square](https://github.com/TheAlgorithms/Python/blob/master/maths/perfect_square.py) - * [Polynomial Evaluation](https://github.com/TheAlgorithms/Python/blob/master/maths/polynomial_evaluation.py) - * [Prime Check](https://github.com/TheAlgorithms/Python/blob/master/maths/prime_check.py) - * [Prime Factors](https://github.com/TheAlgorithms/Python/blob/master/maths/prime_factors.py) - * [Prime Numbers](https://github.com/TheAlgorithms/Python/blob/master/maths/prime_numbers.py) - * [Prime Sieve Eratosthenes](https://github.com/TheAlgorithms/Python/blob/master/maths/prime_sieve_eratosthenes.py) - * [Pythagoras](https://github.com/TheAlgorithms/Python/blob/master/maths/pythagoras.py) - * [Qr Decomposition](https://github.com/TheAlgorithms/Python/blob/master/maths/qr_decomposition.py) - * [Quadratic Equations Complex Numbers](https://github.com/TheAlgorithms/Python/blob/master/maths/quadratic_equations_complex_numbers.py) - * [Radix2 Fft](https://github.com/TheAlgorithms/Python/blob/master/maths/radix2_fft.py) - * [Runge Kutta](https://github.com/TheAlgorithms/Python/blob/master/maths/runge_kutta.py) - * [Segmented Sieve](https://github.com/TheAlgorithms/Python/blob/master/maths/segmented_sieve.py) - * [Sieve Of Eratosthenes](https://github.com/TheAlgorithms/Python/blob/master/maths/sieve_of_eratosthenes.py) - * [Simpson Rule](https://github.com/TheAlgorithms/Python/blob/master/maths/simpson_rule.py) - * [Softmax](https://github.com/TheAlgorithms/Python/blob/master/maths/softmax.py) - * [Sum Of Arithmetic Series](https://github.com/TheAlgorithms/Python/blob/master/maths/sum_of_arithmetic_series.py) - * [Test Prime Check](https://github.com/TheAlgorithms/Python/blob/master/maths/test_prime_check.py) - * [Trapezoidal Rule](https://github.com/TheAlgorithms/Python/blob/master/maths/trapezoidal_rule.py) - * [Volume](https://github.com/TheAlgorithms/Python/blob/master/maths/volume.py) - * [Zellers Congruence](https://github.com/TheAlgorithms/Python/blob/master/maths/zellers_congruence.py) - -## Matrix - * [Matrix Class](https://github.com/TheAlgorithms/Python/blob/master/matrix/matrix_class.py) - * [Matrix Operation](https://github.com/TheAlgorithms/Python/blob/master/matrix/matrix_operation.py) - * [Nth Fibonacci Using Matrix Exponentiation](https://github.com/TheAlgorithms/Python/blob/master/matrix/nth_fibonacci_using_matrix_exponentiation.py) - * [Rotate Matrix](https://github.com/TheAlgorithms/Python/blob/master/matrix/rotate_matrix.py) - * [Searching In Sorted Matrix](https://github.com/TheAlgorithms/Python/blob/master/matrix/searching_in_sorted_matrix.py) - * [Sherman Morrison](https://github.com/TheAlgorithms/Python/blob/master/matrix/sherman_morrison.py) - * [Spiral Print](https://github.com/TheAlgorithms/Python/blob/master/matrix/spiral_print.py) - * Tests - * [Test Matrix Operation](https://github.com/TheAlgorithms/Python/blob/master/matrix/tests/test_matrix_operation.py) - -## Networking Flow - * [Ford Fulkerson](https://github.com/TheAlgorithms/Python/blob/master/networking_flow/ford_fulkerson.py) - * [Minimum Cut](https://github.com/TheAlgorithms/Python/blob/master/networking_flow/minimum_cut.py) - -## Neural Network - * [Back Propagation Neural Network](https://github.com/TheAlgorithms/Python/blob/master/neural_network/back_propagation_neural_network.py) - * [Convolution Neural Network](https://github.com/TheAlgorithms/Python/blob/master/neural_network/convolution_neural_network.py) - * [Gan](https://github.com/TheAlgorithms/Python/blob/master/neural_network/gan.py) - * [Input Data](https://github.com/TheAlgorithms/Python/blob/master/neural_network/input_data.py) - * [Perceptron](https://github.com/TheAlgorithms/Python/blob/master/neural_network/perceptron.py) - -## Other - * [Activity Selection](https://github.com/TheAlgorithms/Python/blob/master/other/activity_selection.py) - * [Anagrams](https://github.com/TheAlgorithms/Python/blob/master/other/anagrams.py) - * [Autocomplete Using Trie](https://github.com/TheAlgorithms/Python/blob/master/other/autocomplete_using_trie.py) - * [Binary Exponentiation](https://github.com/TheAlgorithms/Python/blob/master/other/binary_exponentiation.py) - * [Binary Exponentiation 2](https://github.com/TheAlgorithms/Python/blob/master/other/binary_exponentiation_2.py) - * [Detecting English Programmatically](https://github.com/TheAlgorithms/Python/blob/master/other/detecting_english_programmatically.py) - * [Euclidean Gcd](https://github.com/TheAlgorithms/Python/blob/master/other/euclidean_gcd.py) - * [Fischer Yates Shuffle](https://github.com/TheAlgorithms/Python/blob/master/other/fischer_yates_shuffle.py) - * [Frequency Finder](https://github.com/TheAlgorithms/Python/blob/master/other/frequency_finder.py) - * [Game Of Life](https://github.com/TheAlgorithms/Python/blob/master/other/game_of_life.py) - * [Greedy](https://github.com/TheAlgorithms/Python/blob/master/other/greedy.py) - * [Largest Subarray Sum](https://github.com/TheAlgorithms/Python/blob/master/other/largest_subarray_sum.py) - * [Least Recently Used](https://github.com/TheAlgorithms/Python/blob/master/other/least_recently_used.py) - * [Linear Congruential Generator](https://github.com/TheAlgorithms/Python/blob/master/other/linear_congruential_generator.py) - * [Magicdiamondpattern](https://github.com/TheAlgorithms/Python/blob/master/other/magicdiamondpattern.py) - * [Nested Brackets](https://github.com/TheAlgorithms/Python/blob/master/other/nested_brackets.py) - * [Palindrome](https://github.com/TheAlgorithms/Python/blob/master/other/palindrome.py) - * [Password Generator](https://github.com/TheAlgorithms/Python/blob/master/other/password_generator.py) - * [Primelib](https://github.com/TheAlgorithms/Python/blob/master/other/primelib.py) - * [Sdes](https://github.com/TheAlgorithms/Python/blob/master/other/sdes.py) - * [Sierpinski Triangle](https://github.com/TheAlgorithms/Python/blob/master/other/sierpinski_triangle.py) - * [Tower Of Hanoi](https://github.com/TheAlgorithms/Python/blob/master/other/tower_of_hanoi.py) - * [Two Sum](https://github.com/TheAlgorithms/Python/blob/master/other/two_sum.py) - * [Word Patterns](https://github.com/TheAlgorithms/Python/blob/master/other/word_patterns.py) - -## Project Euler - * Problem 01 - * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_01/sol1.py) - * [Sol2](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_01/sol2.py) - * [Sol3](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_01/sol3.py) - * [Sol4](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_01/sol4.py) - * [Sol5](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_01/sol5.py) - * [Sol6](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_01/sol6.py) - * [Sol7](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_01/sol7.py) - * Problem 02 - * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_02/sol1.py) - * [Sol2](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_02/sol2.py) - * [Sol3](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_02/sol3.py) - * [Sol4](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_02/sol4.py) - * [Sol5](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_02/sol5.py) - * Problem 03 - * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_03/sol1.py) - * [Sol2](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_03/sol2.py) - * [Sol3](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_03/sol3.py) - * Problem 04 - * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_04/sol1.py) - * [Sol2](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_04/sol2.py) - * Problem 05 - * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_05/sol1.py) - * [Sol2](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_05/sol2.py) - * Problem 06 - * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_06/sol1.py) - * [Sol2](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_06/sol2.py) - * [Sol3](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_06/sol3.py) - * [Sol4](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_06/sol4.py) - * Problem 07 - * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_07/sol1.py) - * [Sol2](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_07/sol2.py) - * [Sol3](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_07/sol3.py) - * Problem 08 - * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_08/sol1.py) - * [Sol2](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_08/sol2.py) - * [Sol3](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_08/sol3.py) - * Problem 09 - * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_09/sol1.py) - * [Sol2](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_09/sol2.py) - * [Sol3](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_09/sol3.py) - * Problem 10 - * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_10/sol1.py) - * [Sol2](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_10/sol2.py) - * [Sol3](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_10/sol3.py) - * Problem 11 - * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_11/sol1.py) - * [Sol2](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_11/sol2.py) - * Problem 12 - * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_12/sol1.py) - * [Sol2](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_12/sol2.py) - * Problem 13 - * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_13/sol1.py) - * Problem 14 - * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_14/sol1.py) - * [Sol2](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_14/sol2.py) - * Problem 15 - * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_15/sol1.py) - * Problem 16 - * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_16/sol1.py) - * [Sol2](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_16/sol2.py) - * Problem 17 - * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_17/sol1.py) - * Problem 18 - * [Solution](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_18/solution.py) - * Problem 19 - * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_19/sol1.py) - * Problem 20 - * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_20/sol1.py) - * [Sol2](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_20/sol2.py) - * [Sol3](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_20/sol3.py) - * [Sol4](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_20/sol4.py) - * Problem 21 - * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_21/sol1.py) - * Problem 22 - * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_22/sol1.py) - * [Sol2](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_22/sol2.py) - * Problem 23 - * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_23/sol1.py) - * Problem 234 - * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_234/sol1.py) - * Problem 24 - * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_24/sol1.py) - * Problem 25 - * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_25/sol1.py) - * [Sol2](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_25/sol2.py) - * [Sol3](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_25/sol3.py) - * Problem 27 - * [Problem 27 Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_27/problem_27_sol1.py) - * Problem 28 - * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_28/sol1.py) - * Problem 29 - * [Solution](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_29/solution.py) - * Problem 31 - * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_31/sol1.py) - * Problem 32 - * [Sol32](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_32/sol32.py) - * Problem 33 - * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_33/sol1.py) - * Problem 36 - * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_36/sol1.py) - * Problem 40 - * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_40/sol1.py) - * Problem 42 - * [Solution42](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_42/solution42.py) - * Problem 48 - * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_48/sol1.py) - * Problem 52 - * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_52/sol1.py) - * Problem 53 - * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_53/sol1.py) - * Problem 551 - * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_551/sol1.py) - * Problem 56 - * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_56/sol1.py) - * Problem 67 - * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_67/sol1.py) - * Problem 76 - * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_76/sol1.py) - * Problem 99 - * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_99/sol1.py) - -## Searches - * [Binary Search](https://github.com/TheAlgorithms/Python/blob/master/searches/binary_search.py) - * [Fibonacci Search](https://github.com/TheAlgorithms/Python/blob/master/searches/fibonacci_search.py) - * [Interpolation Search](https://github.com/TheAlgorithms/Python/blob/master/searches/interpolation_search.py) - * [Jump Search](https://github.com/TheAlgorithms/Python/blob/master/searches/jump_search.py) - * [Linear Search](https://github.com/TheAlgorithms/Python/blob/master/searches/linear_search.py) - * [Quick Select](https://github.com/TheAlgorithms/Python/blob/master/searches/quick_select.py) - * [Sentinel Linear Search](https://github.com/TheAlgorithms/Python/blob/master/searches/sentinel_linear_search.py) - * [Simple-Binary-Search](https://github.com/TheAlgorithms/Python/blob/master/searches/simple-binary-search.py) - * [Tabu Search](https://github.com/TheAlgorithms/Python/blob/master/searches/tabu_search.py) - * [Ternary Search](https://github.com/TheAlgorithms/Python/blob/master/searches/ternary_search.py) - -## Sorts - * [Bitonic Sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/bitonic_sort.py) - * [Bogo Sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/bogo_sort.py) - * [Bubble Sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/bubble_sort.py) - * [Bucket Sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/bucket_sort.py) - * [Cocktail Shaker Sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/cocktail_shaker_sort.py) - * [Comb Sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/comb_sort.py) - * [Counting Sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/counting_sort.py) - * [Cycle Sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/cycle_sort.py) - * [Double Sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/double_sort.py) - * [External Sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/external_sort.py) - * [Gnome Sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/gnome_sort.py) - * [Heap Sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/heap_sort.py) - * [I Sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/i_sort.py) - * [Insertion Sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/insertion_sort.py) - * [Merge Sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/merge_sort.py) - * [Odd Even Transposition Parallel](https://github.com/TheAlgorithms/Python/blob/master/sorts/odd_even_transposition_parallel.py) - * [Odd Even Transposition Single Threaded](https://github.com/TheAlgorithms/Python/blob/master/sorts/odd_even_transposition_single_threaded.py) - * [Pancake Sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/pancake_sort.py) - * [Pigeon Sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/pigeon_sort.py) - * [Pigeonhole Sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/pigeonhole_sort.py) - * [Quick Sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/quick_sort.py) - * [Quick Sort 3 Partition](https://github.com/TheAlgorithms/Python/blob/master/sorts/quick_sort_3_partition.py) - * [Radix Sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/radix_sort.py) - * [Random Normal Distribution Quicksort](https://github.com/TheAlgorithms/Python/blob/master/sorts/random_normal_distribution_quicksort.py) - * [Random Pivot Quick Sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/random_pivot_quick_sort.py) - * [Recursive-Quick-Sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/recursive-quick-sort.py) - * [Selection Sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/selection_sort.py) - * [Shell Sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/shell_sort.py) - * [Stooge Sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/stooge_sort.py) - * [Tim Sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/tim_sort.py) - * [Topological Sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/topological_sort.py) - * [Tree Sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/tree_sort.py) - * [Unknown Sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/unknown_sort.py) - * [Wiggle Sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/wiggle_sort.py) - -## Strings - * [Aho-Corasick](https://github.com/TheAlgorithms/Python/blob/master/strings/aho-corasick.py) - * [Boyer Moore Search](https://github.com/TheAlgorithms/Python/blob/master/strings/boyer_moore_search.py) - * [Check Panagram](https://github.com/TheAlgorithms/Python/blob/master/strings/check_panagram.py) - * [Knuth Morris Pratt](https://github.com/TheAlgorithms/Python/blob/master/strings/knuth_morris_pratt.py) - * [Levenshtein Distance](https://github.com/TheAlgorithms/Python/blob/master/strings/levenshtein_distance.py) - * [Manacher](https://github.com/TheAlgorithms/Python/blob/master/strings/manacher.py) - * [Min Cost String Conversion](https://github.com/TheAlgorithms/Python/blob/master/strings/min_cost_string_conversion.py) - * [Naive String Search](https://github.com/TheAlgorithms/Python/blob/master/strings/naive_string_search.py) - * [Rabin Karp](https://github.com/TheAlgorithms/Python/blob/master/strings/rabin_karp.py) - * [Remove Duplicate](https://github.com/TheAlgorithms/Python/blob/master/strings/remove_duplicate.py) - * [Reverse Words](https://github.com/TheAlgorithms/Python/blob/master/strings/reverse_words.py) - * [Word Occurence](https://github.com/TheAlgorithms/Python/blob/master/strings/word_occurence.py) - -## Traversals - * [Binary Tree Traversals](https://github.com/TheAlgorithms/Python/blob/master/traversals/binary_tree_traversals.py) - -## Web Programming - * [Crawl Google Results](https://github.com/TheAlgorithms/Python/blob/master/web_programming/crawl_google_results.py) - * [Current Stock Price](https://github.com/TheAlgorithms/Python/blob/master/web_programming/current_stock_price.py) - * [Fetch Bbc News](https://github.com/TheAlgorithms/Python/blob/master/web_programming/fetch_bbc_news.py) - * [Fetch Github Info](https://github.com/TheAlgorithms/Python/blob/master/web_programming/fetch_github_info.py) - * [Get Imdbtop](https://github.com/TheAlgorithms/Python/blob/master/web_programming/get_imdbtop.py) - * [Slack Message](https://github.com/TheAlgorithms/Python/blob/master/web_programming/slack_message.py) diff --git a/conversions/roman_to_integer.py b/conversions/roman_to_integer.py new file mode 100644 index 000000000000..ce52b6fb7cbb --- /dev/null +++ b/conversions/roman_to_integer.py @@ -0,0 +1,27 @@ +def roman_to_int(roman: str) -> int: + """ + LeetCode No. 13 Roman to Integer + Given a roman numeral, convert it to an integer. + Input is guaranteed to be within the range from 1 to 3999. + https://en.wikipedia.org/wiki/Roman_numerals + >>> tests = {"III": 3, "CLIV": 154, "MIX": 1009, "MMD": 2500, "MMMCMXCIX": 3999} + >>> all(roman_to_int(key) == value for key, value in tests.items()) + True + """ + vals = {"I": 1, "V": 5, "X": 10, "L": 50, "C": 100, "D": 500, "M": 1000} + total = 0 + place = 0 + while place < len(roman): + if (place + 1 < len(roman)) and (vals[roman[place]] < vals[roman[place + 1]]): + total += vals[roman[place + 1]] - vals[roman[place]] + place += 2 + else: + total += vals[roman[place]] + place += 1 + return total + + +if __name__ == "__main__": + import doctest + + doctest.testmod() From 81ae5adcc8c7b5d7a348399bc43d21488facd205 Mon Sep 17 00:00:00 2001 From: Hocnonsense <48747984+Hocnonsense@users.noreply.github.com> Date: Sat, 21 Dec 2019 08:44:31 +0800 Subject: [PATCH 0409/1071] Update binary_search_tree.py (#1339) * Update binary_search_tree.py remove some bugs * Update binary_search_tree.py * Update binary_search_tree.py * Update binary_search_tree.py * Update binary_search_tree.py * Update binary_search_tree.py * Update binary_search_tree.py * testlist = (8, 3, 6, 1, 10, 14, 13, 4, 7) * Update .travis.yml Co-authored-by: Christian Clauss --- .travis.yml | 1 + .../binary_tree/binary_search_tree.py | 383 ++++++++---------- 2 files changed, 164 insertions(+), 220 deletions(-) diff --git a/.travis.yml b/.travis.yml index 6852b84915f9..80ea1302990d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,6 +6,7 @@ install: pip install -r requirements.txt before_script: - black --check . || true - flake8 . --count --select=E101,E9,F4,F63,F7,F82,W191 --show-source --statistics + - flake8 . --count --exit-zero --max-line-length=127 --statistics script: - scripts/validate_filenames.py # no uppercase, no spaces, in a directory - mypy --ignore-missing-imports . diff --git a/data_structures/binary_tree/binary_search_tree.py b/data_structures/binary_tree/binary_search_tree.py index 1e6c17112e81..4c687379e8c8 100644 --- a/data_structures/binary_tree/binary_search_tree.py +++ b/data_structures/binary_tree/binary_search_tree.py @@ -1,204 +1,157 @@ -""" +''' A binary search Tree -""" - - +''' class Node: - def __init__(self, label, parent): - self.label = label + def __init__(self, value, parent): + self.value = value + self.parent = parent # Added in order to delete a node easier self.left = None self.right = None - # Added in order to delete a node easier - self.parent = parent - - def getLabel(self): - return self.label - - def setLabel(self, label): - self.label = label - - def getLeft(self): - return self.left - - def setLeft(self, left): - self.left = left - def getRight(self): - return self.right - - def setRight(self, right): - self.right = right - - def getParent(self): - return self.parent - - def setParent(self, parent): - self.parent = parent + def __repr__(self): + from pprint import pformat + if self.left is None and self.right is None: + return str(self.value) + return pformat( + { + "%s" + % (self.value): (self.left, self.right) + }, + indent=1, + ) class BinarySearchTree: - def __init__(self): - self.root = None + def __init__(self, root = None): + self.root = root - # Insert a new node in Binary Search Tree with value label - def insert(self, label): - # Create a new Node - new_node = Node(label, None) - # If Tree is empty - if self.empty(): - self.root = new_node - else: - # If Tree is not empty - curr_node = self.root - # While we don't get to a leaf - while curr_node is not None: - # We keep reference of the parent node - parent_node = curr_node - # If node label is less than current node - if new_node.getLabel() < curr_node.getLabel(): - # We go left - curr_node = curr_node.getLeft() - else: - # Else we go right - curr_node = curr_node.getRight() - # We insert the new node in a leaf - if new_node.getLabel() < parent_node.getLabel(): - parent_node.setLeft(new_node) + def __str__(self): + """ + Return a string of all the Nodes using in order traversal + """ + return str(self.root) + + def __reassign_nodes(self, node, newChildren): + if(newChildren is not None): # reset its kids + newChildren.parent = node.parent + if(node.parent is not None): # reset its parent + if(self.is_right(node)): # If it is the right children + node.parent.right = newChildren else: - parent_node.setRight(new_node) - # Set parent to the new node - new_node.setParent(parent_node) + node.parent.left = newChildren + else: + self.root = newChildren - def delete(self, label): - if not self.empty(): - # Look for the node with that label - node = self.getNode(label) - # If the node exists - if node is not None: - # If it has no children - if node.getLeft() is None and node.getRight() is None: - self.__reassignNodes(node, None) - node = None - # Has only right children - elif node.getLeft() is None and node.getRight() is not None: - self.__reassignNodes(node, node.getRight()) - # Has only left children - elif node.getLeft() is not None and node.getRight() is None: - self.__reassignNodes(node, node.getLeft()) - # Has two children - else: - # Gets the max value of the left branch - tmpNode = self.getMax(node.getLeft()) - # Deletes the tmpNode - self.delete(tmpNode.getLabel()) - # Assigns the value to the node to delete and keesp tree structure - node.setLabel(tmpNode.getLabel()) + def is_right(self, node): + return node == node.parent.right - def getNode(self, label): - curr_node = None - # If the tree is not empty - if not self.empty(): - # Get tree root - curr_node = self.getRoot() - # While we don't find the node we look for - # I am using lazy evaluation here to avoid NoneType Attribute error - while curr_node is not None and curr_node.getLabel() is not label: - # If node label is less than current node - if label < curr_node.getLabel(): - # We go left - curr_node = curr_node.getLeft() + def empty(self): + return self.root is None + + def __insert(self, value): + """ + Insert a new node in Binary Search Tree with value label + """ + new_node = Node(value, None) # create a new Node + if self.empty(): # if Tree is empty + self.root = new_node # set its root + else: # Tree is not empty + parent_node = self.root # from root + while True: # While we don't get to a leaf + if value < parent_node.value: # We go left + if parent_node.left == None: + parent_node.left = new_node # We insert the new node in a leaf + break + else: + parent_node = parent_node.left else: - # Else we go right - curr_node = curr_node.getRight() - return curr_node - - def getMax(self, root=None): - if root is not None: - curr_node = root - else: - # We go deep on the right branch - curr_node = self.getRoot() - if not self.empty(): - while curr_node.getRight() is not None: - curr_node = curr_node.getRight() - return curr_node - - def getMin(self, root=None): - if root is not None: - curr_node = root + if parent_node.right == None: + parent_node.right = new_node + break + else: + parent_node = parent_node.right + new_node.parent = parent_node + + def insert(self, *values): + for value in values: + self.__insert(value) + return self + + def search(self, value): + if self.empty(): + raise IndexError("Warning: Tree is empty! please use another. ") else: - # We go deep on the left branch - curr_node = self.getRoot() + node = self.root + # use lazy evaluation here to avoid NoneType Attribute error + while node is not None and node.value is not value: + node = node.left if value < node.value else node.right + return node + + def get_max(self, node = None): + """ + We go deep on the right branch + """ + if node is None: + node = self.root if not self.empty(): - curr_node = self.getRoot() - while curr_node.getLeft() is not None: - curr_node = curr_node.getLeft() - return curr_node - - def empty(self): - if self.root is None: - return True - return False - - def __InOrderTraversal(self, curr_node): - nodeList = [] - if curr_node is not None: - nodeList.insert(0, curr_node) - nodeList = nodeList + self.__InOrderTraversal(curr_node.getLeft()) - nodeList = nodeList + self.__InOrderTraversal(curr_node.getRight()) - return nodeList - - def getRoot(self): - return self.root - - def __isRightChildren(self, node): - if node == node.getParent().getRight(): - return True - return False - - def __reassignNodes(self, node, newChildren): - if newChildren is not None: - newChildren.setParent(node.getParent()) - if node.getParent() is not None: - # If it is the Right Children - if self.__isRightChildren(node): - node.getParent().setRight(newChildren) + while(node.right is not None): + node = node.right + return node + + def get_min(self, node = None): + """ + We go deep on the left branch + """ + if(node is None): + node = self.root + if(not self.empty()): + node = self.root + while(node.left is not None): + node = node.left + return node + + def remove(self, value): + node = self.search(value) # Look for the node with that label + if(node is not None): + if(node.left is None and node.right is None): # If it has no children + self.__reassign_nodes(node, None) + node = None + elif(node.left is None): # Has only right children + self.__reassign_nodes(node, node.right) + elif(node.right is None): # Has only left children + self.__reassign_nodes(node, node.left) else: - # Else it is the left children - node.getParent().setLeft(newChildren) - - # This function traversal the tree. By default it returns an - # In order traversal list. You can pass a function to traversal - # The tree as needed by client code - def traversalTree(self, traversalFunction=None, root=None): - if traversalFunction is None: - # Returns a list of nodes in preOrder by default - return self.__InOrderTraversal(self.root) + tmpNode = self.get_max(node.left) # Gets the max value of the left branch + self.remove(tmpNode.value) + node.value = tmpNode.value # Assigns the value to the node to delete and keesp tree structure + + def preorder_traverse(self, node): + if node is not None: + yield node # Preorder Traversal + yield from self.preorder_traverse(node.left) + yield from self.preorder_traverse(node.right) + + def traversal_tree(self, traversalFunction = None): + """ + This function traversal the tree. + You can pass a function to traversal the tree as needed by client code + """ + if(traversalFunction is None): + return self.preorder_traverse(self.root) else: - # Returns a list of nodes in the order that the users wants to return traversalFunction(self.root) - # Returns an string of all the nodes labels in the list - # In Order Traversal - def __str__(self): - list = self.__InOrderTraversal(self.root) - str = "" - for x in list: - str = str + " " + x.getLabel().__str__() - return str - - -def InPreOrder(curr_node): - nodeList = [] +def postorder(curr_node): + """ + postOrder (left, right, self) + """ + nodeList = list() if curr_node is not None: - nodeList = nodeList + InPreOrder(curr_node.getLeft()) - nodeList.insert(0, curr_node.getLabel()) - nodeList = nodeList + InPreOrder(curr_node.getRight()) + nodeList = postorder(curr_node.left) + postorder(curr_node.right) + [curr_node] return nodeList - -def testBinarySearchTree(): - r""" +def binary_search_tree(): + r''' Example 8 / \ @@ -207,56 +160,46 @@ def testBinarySearchTree(): 1 6 14 / \ / 4 7 13 - """ - r""" - Example After Deletion - 7 - / \ - 1 4 - - """ + >>> t = BinarySearchTree().insert(8, 3, 6, 1, 10, 14, 13, 4, 7) + >>> print(" ".join(repr(i.value) for i in t.traversal_tree())) + 8 3 1 6 4 7 10 14 13 + >>> print(" ".join(repr(i.value) for i in t.traversal_tree(postorder))) + 1 4 7 6 3 13 14 10 8 + >>> BinarySearchTree().search(6) + Traceback (most recent call last): + ... + IndexError: Warning: Tree is empty! please use another. + ''' + testlist = (8, 3, 6, 1, 10, 14, 13, 4, 7) t = BinarySearchTree() - t.insert(8) - t.insert(3) - t.insert(6) - t.insert(1) - t.insert(10) - t.insert(14) - t.insert(13) - t.insert(4) - t.insert(7) + for i in testlist: + t.insert(i) # Prints all the elements of the list in order traversal - print(t.__str__()) - - if t.getNode(6) is not None: - print("The label 6 exists") + print(t) + + if(t.search(6) is not None): + print("The value 6 exists") else: - print("The label 6 doesn't exist") + print("The value 6 doesn't exist") - if t.getNode(-1) is not None: - print("The label -1 exists") + if(t.search(-1) is not None): + print("The value -1 exists") else: - print("The label -1 doesn't exist") - - if not t.empty(): - print(("Max Value: ", t.getMax().getLabel())) - print(("Min Value: ", t.getMin().getLabel())) + print("The value -1 doesn't exist") - t.delete(13) - t.delete(10) - t.delete(8) - t.delete(3) - t.delete(6) - t.delete(14) + if(not t.empty()): + print("Max Value: ", t.get_max().value) + print("Min Value: ", t.get_min().value) - # Gets all the elements of the tree In pre order - # And it prints them - list = t.traversalTree(InPreOrder, t.root) - for x in list: - print(x) + for i in testlist: + t.remove(i) + print(t) +二叉搜索树 = binary_search_tree if __name__ == "__main__": - testBinarySearchTree() + import doctest + doctest.testmod() + binary_search_tree() From aa18600e22ce323c59f2e1051ed53971196320c1 Mon Sep 17 00:00:00 2001 From: Dhakad9 <53108891+Dhakad9@users.noreply.github.com> Date: Sat, 21 Dec 2019 07:16:49 +0530 Subject: [PATCH 0410/1071] Stack using double linked list (#1413) * Stack using double linked list * Test with doctests * Update stack_using_dll.py * Update stack_using_dll.py * Update stack_using_dll.py Co-authored-by: Christian Clauss --- data_structures/stacks/stack_using_dll.py | 120 ++++++++++++++++++++++ 1 file changed, 120 insertions(+) create mode 100644 data_structures/stacks/stack_using_dll.py diff --git a/data_structures/stacks/stack_using_dll.py b/data_structures/stacks/stack_using_dll.py new file mode 100644 index 000000000000..10e4c067f6f3 --- /dev/null +++ b/data_structures/stacks/stack_using_dll.py @@ -0,0 +1,120 @@ +# A complete working Python program to demonstrate all +# stack operations using a doubly linked list + +class Node: + def __init__(self, data): + self.data = data # Assign data + self.next = None # Initialize next as null + self.prev = None # Initialize prev as null + +class Stack: + """ + >>> stack = Stack() + >>> stack.is_empty() + True + >>> stack.print_stack() + stack elements are: + >>> for i in range(4): + ... stack.push(i) + ... + >>> stack.is_empty() + False + >>> stack.print_stack() + stack elements are: + 3->2->1->0-> + >>> stack.top() + 3 + >>> len(stack) + 4 + >>> stack.pop() + 3 + >>> stack.print_stack() + stack elements are: + 2->1->0-> + """ + def __init__(self): + self.head = None + + def push(self, data): + """add a Node to the stack""" + if self.head is None: + self.head = Node(data) + else: + new_node = Node(data) + self.head.prev = new_node + new_node.next = self.head + new_node.prev = None + self.head = new_node + + def pop(self): + """pop the top element off the stack""" + if self.head is None: + return None + else: + temp = self.head.data + self.head = self.head.next + self.head.prev = None + return temp + + def top(self): + """return the top element of the stack""" + return self.head.data + + def __len__(self): + temp = self.head + count = 0 + while temp is not None: + count += 1 + temp = temp.next + return count + + def is_empty(self): + return self.head is None + + def print_stack(self): + print("stack elements are:") + temp = self.head + while temp is not None: + print(temp.data, end ="->") + temp = temp.next + + +# Code execution starts here +if __name__=='__main__': + + # Start with the empty stack + stack = Stack() + + # Insert 4 at the beginning. So stack becomes 4->None + print("Stack operations using Doubly LinkedList") + stack.push(4) + + # Insert 5 at the beginning. So stack becomes 4->5->None + stack.push(5) + + # Insert 6 at the beginning. So stack becomes 4->5->6->None + stack.push(6) + + # Insert 7 at the beginning. So stack becomes 4->5->6->7->None + stack.push(7) + + # Print the stack + stack.print_stack() + + # Print the top element + print("\nTop element is ", stack.top()) + + # Print the stack size + print("Size of the stack is ", len(stack)) + + # pop the top element + stack.pop() + + # pop the top element + stack.pop() + + # two elements have now been popped off + stack.print_stack() + + # Print True if the stack is empty else False + print("\nstack is empty:", stack.is_empty()) From 725834b9bca7c9cb1a76aa0ada0f4abf5a66f20b Mon Sep 17 00:00:00 2001 From: Anzo Teh Date: Tue, 24 Dec 2019 01:23:15 -0500 Subject: [PATCH 0411/1071] Added binary exponentiaion with respect to modulo (#1428) * Added binary exponentiaion with respect to modulo * Added miller rabin: the probabilistic primality test for large numbers * Removed unused import * Added test for miller_rabin * Add test to binary_exp_mod * Removed test parameter to make Travis CI happy * unittest.main() # doctest: +ELLIPSIS ... * Update binary_exp_mod.py * Update binary_exp_mod.py * Update miller_rabin.py * from .prime_check import prime_check Co-authored-by: Christian Clauss --- maths/binary_exp_mod.py | 28 +++++++++++++++++++++++ maths/miller_rabin.py | 50 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 78 insertions(+) create mode 100644 maths/binary_exp_mod.py create mode 100644 maths/miller_rabin.py diff --git a/maths/binary_exp_mod.py b/maths/binary_exp_mod.py new file mode 100644 index 000000000000..67dd1e728b18 --- /dev/null +++ b/maths/binary_exp_mod.py @@ -0,0 +1,28 @@ +def bin_exp_mod(a, n, b): + """ + >>> bin_exp_mod(3, 4, 5) + 1 + >>> bin_exp_mod(7, 13, 10) + 7 + """ + # mod b + assert not (b == 0), "This cannot accept modulo that is == 0" + if n == 0: + return 1 + + if n % 2 == 1: + return (bin_exp_mod(a, n - 1, b) * a) % b + + r = bin_exp_mod(a, n / 2, b) + return (r * r) % b + + +if __name__ == "__main__": + try: + BASE = int(input("Enter Base : ").strip()) + POWER = int(input("Enter Power : ").strip()) + MODULO = int(input("Enter Modulo : ").strip()) + except ValueError: + print("Invalid literal for integer") + + print(bin_exp_mod(BASE, POWER, MODULO)) diff --git a/maths/miller_rabin.py b/maths/miller_rabin.py new file mode 100644 index 000000000000..fe992027190b --- /dev/null +++ b/maths/miller_rabin.py @@ -0,0 +1,50 @@ +import random + +from .binary_exp_mod import bin_exp_mod + + +# This is a probabilistic check to test primality, useful for big numbers! +# if it's a prime, it will return true +# if it's not a prime, the chance of it returning true is at most 1/4**prec +def is_prime(n, prec=1000): + """ + >>> from .prime_check import prime_check + >>> all(is_prime(i) == prime_check(i) for i in range(1000)) + True + """ + if n < 2: + return False + + if n % 2 == 0: + return n == 2 + + # this means n is odd + d = n - 1 + exp = 0 + while d % 2 == 0: + d /= 2 + exp += 1 + + # n - 1=d*(2**exp) + count = 0 + while count < prec: + a = random.randint(2, n - 1) + b = bin_exp_mod(a, d, n) + if b != 1: + flag = True + for i in range(exp): + if b == n - 1: + flag = False + break + b = b * b + b %= n + if flag: + return False + count += 1 + return True + + +if __name__ == "__main__": + n = abs(int(input("Enter bound : ").strip())) + print("Here's the list of primes:") + print(", ".join(str(i) for i in range(n + 1) if is_prime(i))) From 1b3985837fac9b72c7d49d903282a219262f737b Mon Sep 17 00:00:00 2001 From: Yash Bhardwaj Date: Thu, 26 Dec 2019 08:44:47 +0530 Subject: [PATCH 0412/1071] Update back_propagation_neural_network.py (#1342) * Update back_propagation_neural_network.py Added comments below functions * Update back_propagation_neural_network.py Co-authored-by: Christian Clauss --- .../back_propagation_neural_network.py | 23 +++++-------------- 1 file changed, 6 insertions(+), 17 deletions(-) diff --git a/neural_network/back_propagation_neural_network.py b/neural_network/back_propagation_neural_network.py index 86797694bb0a..224fc85de066 100644 --- a/neural_network/back_propagation_neural_network.py +++ b/neural_network/back_propagation_neural_network.py @@ -31,7 +31,6 @@ class DenseLayer: """ Layers of BP neural network """ - def __init__( self, units, activation=None, learning_rate=None, is_input_layer=False ): @@ -58,6 +57,7 @@ def initializer(self, back_units): self.activation = sigmoid def cal_gradient(self): + # activation function may be sigmoid or linear if self.activation == sigmoid: gradient_mat = np.dot(self.output, (1 - self.output).T) gradient_activation = np.diag(np.diag(gradient_mat)) @@ -78,7 +78,6 @@ def forward_propagation(self, xdata): return self.output def back_propagation(self, gradient): - gradient_activation = self.cal_gradient() # i * i 维 gradient = np.asmatrix(np.dot(gradient.T, gradient_activation)) @@ -89,11 +88,10 @@ def back_propagation(self, gradient): self.gradient_weight = np.dot(gradient.T, self._gradient_weight.T) self.gradient_bias = gradient * self._gradient_bias self.gradient = np.dot(gradient, self._gradient_x).T - # ----------------------upgrade - # -----------the Negative gradient direction -------- + # upgrade: the Negative gradient direction self.weight = self.weight - self.learn_rate * self.gradient_weight self.bias = self.bias - self.learn_rate * self.gradient_bias.T - + # updates the weights and bias according to learning rate (0.3 if undefined) return self.gradient @@ -101,7 +99,6 @@ class BPNN: """ Back Propagation Neural Network model """ - def __init__(self): self.layers = [] self.train_mse = [] @@ -144,8 +141,7 @@ def train(self, xdata, ydata, train_round, accuracy): loss, gradient = self.cal_loss(_ydata, _xdata) all_loss = all_loss + loss - # back propagation - # the input_layer does not upgrade + # back propagation: the input_layer does not upgrade for layer in self.layers[:0:-1]: gradient = layer.back_propagation(gradient) @@ -176,7 +172,6 @@ def plot_loss(self): def example(): - x = np.random.randn(10, 10) y = np.asarray( [ @@ -192,17 +187,11 @@ def example(): [0.1, 0.5], ] ) - model = BPNN() - model.add_layer(DenseLayer(10)) - model.add_layer(DenseLayer(20)) - model.add_layer(DenseLayer(30)) - model.add_layer(DenseLayer(2)) - + for i in (10, 20, 30, 2): + model.add_layer(DenseLayer(i)) model.build() - model.summary() - model.train(xdata=x, ydata=y, train_round=100, accuracy=0.01) From 34c808b3751f3f9b0fe45e736b2f61abee1b91f6 Mon Sep 17 00:00:00 2001 From: Christian Clauss Date: Thu, 26 Dec 2019 12:50:12 +0100 Subject: [PATCH 0413/1071] actions/checkout@v2 (#1643) * actions/checkout@v2 https://github.com/actions/checkout/releases * fixup! Format Python code with psf/black push --- .github/workflows/directory_writer.yml | 2 +- arithmetic_analysis/newton_raphson.py | 2 +- ciphers/affine_cipher.py | 6 +- .../binary_tree/binary_search_tree.py | 81 +++++----- data_structures/stacks/stack_using_dll.py | 151 +++++++++--------- dynamic_programming/coin_change.py | 1 + other/integeration_by_simpson_approx.py | 2 +- 7 files changed, 128 insertions(+), 117 deletions(-) diff --git a/.github/workflows/directory_writer.yml b/.github/workflows/directory_writer.yml index e021051fe564..4a8ed6c9e509 100644 --- a/.github/workflows/directory_writer.yml +++ b/.github/workflows/directory_writer.yml @@ -6,7 +6,7 @@ jobs: build: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v1 + - uses: actions/checkout@v2 - uses: actions/setup-python@v1 with: python-version: 3.x diff --git a/arithmetic_analysis/newton_raphson.py b/arithmetic_analysis/newton_raphson.py index 8aa816cd0d04..a3e8bbf0fc21 100644 --- a/arithmetic_analysis/newton_raphson.py +++ b/arithmetic_analysis/newton_raphson.py @@ -8,7 +8,7 @@ from sympy import diff -def newton_raphson(func: str, a: int, precision: int=10 ** -10) -> float: +def newton_raphson(func: str, a: int, precision: int = 10 ** -10) -> float: """ Finds root from the point 'a' onwards by Newton-Raphson method >>> newton_raphson("sin(x)", 2) 3.1415926536808043 diff --git a/ciphers/affine_cipher.py b/ciphers/affine_cipher.py index ad41feb32837..21c92c6437e7 100644 --- a/ciphers/affine_cipher.py +++ b/ciphers/affine_cipher.py @@ -3,8 +3,10 @@ import cryptomath_module as cryptomath -SYMBOLS = (r""" !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`""" - r"""abcdefghijklmnopqrstuvwxyz{|}~""") +SYMBOLS = ( + r""" !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`""" + r"""abcdefghijklmnopqrstuvwxyz{|}~""" +) def main(): diff --git a/data_structures/binary_tree/binary_search_tree.py b/data_structures/binary_tree/binary_search_tree.py index 4c687379e8c8..c3c97bb02003 100644 --- a/data_structures/binary_tree/binary_search_tree.py +++ b/data_structures/binary_tree/binary_search_tree.py @@ -1,6 +1,8 @@ -''' +""" A binary search Tree -''' +""" + + class Node: def __init__(self, value, parent): self.value = value @@ -13,16 +15,11 @@ def __repr__(self): if self.left is None and self.right is None: return str(self.value) - return pformat( - { - "%s" - % (self.value): (self.left, self.right) - }, - indent=1, - ) + return pformat({"%s" % (self.value): (self.left, self.right)}, indent=1,) + class BinarySearchTree: - def __init__(self, root = None): + def __init__(self, root=None): self.root = root def __str__(self): @@ -32,10 +29,10 @@ def __str__(self): return str(self.root) def __reassign_nodes(self, node, newChildren): - if(newChildren is not None): # reset its kids + if newChildren is not None: # reset its kids newChildren.parent = node.parent - if(node.parent is not None): # reset its parent - if(self.is_right(node)): # If it is the right children + if node.parent is not None: # reset its parent + if self.is_right(node): # If it is the right children node.parent.right = newChildren else: node.parent.left = newChildren @@ -55,10 +52,10 @@ def __insert(self, value): new_node = Node(value, None) # create a new Node if self.empty(): # if Tree is empty self.root = new_node # set its root - else: # Tree is not empty - parent_node = self.root # from root + else: # Tree is not empty + parent_node = self.root # from root while True: # While we don't get to a leaf - if value < parent_node.value: # We go left + if value < parent_node.value: # We go left if parent_node.left == None: parent_node.left = new_node # We insert the new node in a leaf break @@ -87,60 +84,65 @@ def search(self, value): node = node.left if value < node.value else node.right return node - def get_max(self, node = None): + def get_max(self, node=None): """ We go deep on the right branch """ if node is None: node = self.root if not self.empty(): - while(node.right is not None): + while node.right is not None: node = node.right return node - def get_min(self, node = None): + def get_min(self, node=None): """ We go deep on the left branch """ - if(node is None): + if node is None: node = self.root - if(not self.empty()): + if not self.empty(): node = self.root - while(node.left is not None): + while node.left is not None: node = node.left return node def remove(self, value): - node = self.search(value) # Look for the node with that label - if(node is not None): - if(node.left is None and node.right is None): # If it has no children + node = self.search(value) # Look for the node with that label + if node is not None: + if node.left is None and node.right is None: # If it has no children self.__reassign_nodes(node, None) node = None - elif(node.left is None): # Has only right children + elif node.left is None: # Has only right children self.__reassign_nodes(node, node.right) - elif(node.right is None): # Has only left children + elif node.right is None: # Has only left children self.__reassign_nodes(node, node.left) else: - tmpNode = self.get_max(node.left) # Gets the max value of the left branch + tmpNode = self.get_max( + node.left + ) # Gets the max value of the left branch self.remove(tmpNode.value) - node.value = tmpNode.value # Assigns the value to the node to delete and keesp tree structure - + node.value = ( + tmpNode.value + ) # Assigns the value to the node to delete and keesp tree structure + def preorder_traverse(self, node): if node is not None: yield node # Preorder Traversal yield from self.preorder_traverse(node.left) yield from self.preorder_traverse(node.right) - def traversal_tree(self, traversalFunction = None): + def traversal_tree(self, traversalFunction=None): """ This function traversal the tree. You can pass a function to traversal the tree as needed by client code """ - if(traversalFunction is None): + if traversalFunction is None: return self.preorder_traverse(self.root) else: return traversalFunction(self.root) + def postorder(curr_node): """ postOrder (left, right, self) @@ -150,8 +152,9 @@ def postorder(curr_node): nodeList = postorder(curr_node.left) + postorder(curr_node.right) + [curr_node] return nodeList + def binary_search_tree(): - r''' + r""" Example 8 / \ @@ -170,7 +173,7 @@ def binary_search_tree(): Traceback (most recent call last): ... IndexError: Warning: Tree is empty! please use another. - ''' + """ testlist = (8, 3, 6, 1, 10, 14, 13, 4, 7) t = BinarySearchTree() for i in testlist: @@ -178,18 +181,18 @@ def binary_search_tree(): # Prints all the elements of the list in order traversal print(t) - - if(t.search(6) is not None): + + if t.search(6) is not None: print("The value 6 exists") else: print("The value 6 doesn't exist") - if(t.search(-1) is not None): + if t.search(-1) is not None: print("The value -1 exists") else: print("The value -1 doesn't exist") - if(not t.empty()): + if not t.empty(): print("Max Value: ", t.get_max().value) print("Min Value: ", t.get_min().value) @@ -197,9 +200,11 @@ def binary_search_tree(): t.remove(i) print(t) + 二叉搜索树 = binary_search_tree if __name__ == "__main__": import doctest + doctest.testmod() binary_search_tree() diff --git a/data_structures/stacks/stack_using_dll.py b/data_structures/stacks/stack_using_dll.py index 10e4c067f6f3..75e0cd20640d 100644 --- a/data_structures/stacks/stack_using_dll.py +++ b/data_structures/stacks/stack_using_dll.py @@ -1,12 +1,14 @@ -# A complete working Python program to demonstrate all -# stack operations using a doubly linked list - -class Node: - def __init__(self, data): - self.data = data # Assign data - self.next = None # Initialize next as null - self.prev = None # Initialize prev as null - +# A complete working Python program to demonstrate all +# stack operations using a doubly linked list + + +class Node: + def __init__(self, data): + self.data = data # Assign data + self.next = None # Initialize next as null + self.prev = None # Initialize prev as null + + class Stack: """ >>> stack = Stack() @@ -32,89 +34,90 @@ class Stack: stack elements are: 2->1->0-> """ - def __init__(self): + + def __init__(self): self.head = None - + def push(self, data): """add a Node to the stack""" - if self.head is None: - self.head = Node(data) - else: - new_node = Node(data) - self.head.prev = new_node - new_node.next = self.head + if self.head is None: + self.head = Node(data) + else: + new_node = Node(data) + self.head.prev = new_node + new_node.next = self.head new_node.prev = None - self.head = new_node - + self.head = new_node + def pop(self): """pop the top element off the stack""" - if self.head is None: + if self.head is None: return None - else: - temp = self.head.data + else: + temp = self.head.data self.head = self.head.next self.head.prev = None - return temp - + return temp + def top(self): """return the top element of the stack""" return self.head.data - def __len__(self): - temp = self.head + def __len__(self): + temp = self.head count = 0 - while temp is not None: + while temp is not None: count += 1 temp = temp.next - return count + return count def is_empty(self): return self.head is None - def print_stack(self): - print("stack elements are:") - temp = self.head - while temp is not None: - print(temp.data, end ="->") - temp = temp.next - - -# Code execution starts here -if __name__=='__main__': - - # Start with the empty stack - stack = Stack() - - # Insert 4 at the beginning. So stack becomes 4->None - print("Stack operations using Doubly LinkedList") - stack.push(4) - - # Insert 5 at the beginning. So stack becomes 4->5->None - stack.push(5) - - # Insert 6 at the beginning. So stack becomes 4->5->6->None - stack.push(6) - - # Insert 7 at the beginning. So stack becomes 4->5->6->7->None - stack.push(7) - - # Print the stack - stack.print_stack() - - # Print the top element - print("\nTop element is ", stack.top()) - - # Print the stack size - print("Size of the stack is ", len(stack)) - - # pop the top element - stack.pop() - - # pop the top element - stack.pop() - + def print_stack(self): + print("stack elements are:") + temp = self.head + while temp is not None: + print(temp.data, end="->") + temp = temp.next + + +# Code execution starts here +if __name__ == "__main__": + + # Start with the empty stack + stack = Stack() + + # Insert 4 at the beginning. So stack becomes 4->None + print("Stack operations using Doubly LinkedList") + stack.push(4) + + # Insert 5 at the beginning. So stack becomes 4->5->None + stack.push(5) + + # Insert 6 at the beginning. So stack becomes 4->5->6->None + stack.push(6) + + # Insert 7 at the beginning. So stack becomes 4->5->6->7->None + stack.push(7) + + # Print the stack + stack.print_stack() + + # Print the top element + print("\nTop element is ", stack.top()) + + # Print the stack size + print("Size of the stack is ", len(stack)) + + # pop the top element + stack.pop() + + # pop the top element + stack.pop() + # two elements have now been popped off - stack.print_stack() - - # Print True if the stack is empty else False - print("\nstack is empty:", stack.is_empty()) + stack.print_stack() + + # Print True if the stack is empty else False + print("\nstack is empty:", stack.is_empty()) diff --git a/dynamic_programming/coin_change.py b/dynamic_programming/coin_change.py index 12ced411780f..2d7106f0cc6f 100644 --- a/dynamic_programming/coin_change.py +++ b/dynamic_programming/coin_change.py @@ -36,6 +36,7 @@ def dp_count(S, m, n): return table[n] + if __name__ == "__main__": import doctest diff --git a/other/integeration_by_simpson_approx.py b/other/integeration_by_simpson_approx.py index 2115ac9a5146..0f7bfacf030a 100644 --- a/other/integeration_by_simpson_approx.py +++ b/other/integeration_by_simpson_approx.py @@ -111,7 +111,7 @@ def simpson_integration(function, a: float, b: float, precision: int = 4) -> flo for i in range(1, N_STEPS): a1 = a + h * i - result += function(a1) * (4 if i%2 else 2) + result += function(a1) * (4 if i % 2 else 2) result *= h / 3 return round(result, precision) From 28419cf8396bc390437a6c56a143a548ca8fa631 Mon Sep 17 00:00:00 2001 From: Christian Clauss Date: Fri, 3 Jan 2020 15:25:36 +0100 Subject: [PATCH 0414/1071] pyupgrade --py37-plus **/*.py (#1654) * pyupgrade --py37-plus **/*.py * fixup! Format Python code with psf/black push --- backtracking/all_combinations.py | 2 -- ciphers/brute_force_caesar_cipher.py | 2 +- ciphers/hill_cipher.py | 2 +- ciphers/rsa_cipher.py | 2 +- ciphers/rsa_key_generator.py | 4 ++-- ciphers/shuffled_shift_cipher.py | 2 +- ciphers/simple_substitution_cipher.py | 2 +- ciphers/xor_cipher.py | 2 +- compression/burrows_wheeler.py | 8 +++----- data_structures/binary_tree/avl_tree.py | 1 - data_structures/binary_tree/red_black_tree.py | 2 +- data_structures/binary_tree/treap.py | 7 +++---- data_structures/data_structures/heap/heap_generic.py | 2 +- data_structures/hashing/hash_table.py | 2 +- data_structures/stacks/stack.py | 2 +- .../histogram_equalization/histogram_stretch.py | 1 - digital_image_processing/index_calculation.py | 5 +---- dynamic_programming/fast_fibonacci.py | 1 - dynamic_programming/k_means_clustering_tensorflow.py | 2 +- dynamic_programming/matrix_chain_order.py | 2 +- graphs/basic_graphs.py | 6 +++--- graphs/breadth_first_search.py | 1 - graphs/depth_first_search.py | 1 - graphs/edmonds_karp_multiple_source_and_sink.py | 6 +++--- graphs/graph_list.py | 3 +-- hashes/hamming_code.py | 7 +++---- linear_algebra/src/lib.py | 5 ++--- linear_algebra/src/test_linear_algebra.py | 1 - machine_learning/k_means_clust.py | 4 ++-- machine_learning/logistic_regression.py | 1 - machine_learning/sequential_minimum_optimization.py | 6 ++---- maths/3n+1.py | 4 ++-- maths/__init__.py | 1 - maths/hardy_ramanujanalgo.py | 2 +- maths/lucas_lehmer_primality_test.py | 1 - maths/matrix_exponentiation.py | 2 +- maths/newton_raphson.py | 2 +- maths/pythagoras.py | 2 +- maths/sieve_of_eratosthenes.py | 2 -- matrix/matrix_operation.py | 6 +++--- matrix/rotate_matrix.py | 2 -- matrix/searching_in_sorted_matrix.py | 2 +- matrix/sherman_morrison.py | 10 +++++----- neural_network/back_propagation_neural_network.py | 3 ++- neural_network/convolution_neural_network.py | 4 +--- neural_network/input_data.py | 7 ++----- other/anagrams.py | 2 +- other/fischer_yates_shuffle.py | 1 - other/linear_congruential_generator.py | 2 +- other/primelib.py | 1 - other/sierpinski_triangle.py | 1 - project_euler/problem_06/sol1.py | 1 - project_euler/problem_06/sol2.py | 1 - project_euler/problem_06/sol3.py | 1 - project_euler/problem_06/sol4.py | 1 - project_euler/problem_07/sol1.py | 1 - project_euler/problem_07/sol2.py | 1 - project_euler/problem_07/sol3.py | 1 - project_euler/problem_08/sol1.py | 1 - project_euler/problem_08/sol2.py | 1 - project_euler/problem_08/sol3.py | 1 - project_euler/problem_14/sol1.py | 1 - project_euler/problem_14/sol2.py | 1 - project_euler/problem_21/sol1.py | 1 - project_euler/problem_22/sol1.py | 1 - project_euler/problem_22/sol2.py | 1 - project_euler/problem_25/sol1.py | 1 - project_euler/problem_25/sol2.py | 1 - project_euler/problem_25/sol3.py | 1 - project_euler/problem_31/sol1.py | 1 - project_euler/problem_32/sol32.py | 12 +++++------- project_euler/problem_33/__init__.py | 1 - project_euler/problem_33/sol1.py | 2 +- project_euler/problem_40/sol1.py | 1 - project_euler/problem_53/sol1.py | 1 - sorts/external_sort.py | 10 +++++----- strings/aho-corasick.py | 2 +- 77 files changed, 71 insertions(+), 128 deletions(-) diff --git a/backtracking/all_combinations.py b/backtracking/all_combinations.py index 60e9579f28ba..854dc5198422 100644 --- a/backtracking/all_combinations.py +++ b/backtracking/all_combinations.py @@ -1,5 +1,3 @@ -# -*- coding: utf-8 -*- - """ In this problem, we want to determine all possible combinations of k numbers out of 1 ... n. We use backtracking to solve this problem. diff --git a/ciphers/brute_force_caesar_cipher.py b/ciphers/brute_force_caesar_cipher.py index 2586803ba5ff..5f11cb848c41 100644 --- a/ciphers/brute_force_caesar_cipher.py +++ b/ciphers/brute_force_caesar_cipher.py @@ -40,7 +40,7 @@ def decrypt(message): translated = translated + LETTERS[num] else: translated = translated + symbol - print("Decryption using Key #%s: %s" % (key, translated)) + print(f"Decryption using Key #{key}: {translated}") def main(): diff --git a/ciphers/hill_cipher.py b/ciphers/hill_cipher.py index e01b6a3f48a8..05f716f8595e 100644 --- a/ciphers/hill_cipher.py +++ b/ciphers/hill_cipher.py @@ -78,7 +78,7 @@ def checkDeterminant(self): req_l = len(self.key_string) if gcd(det, len(self.key_string)) != 1: raise ValueError( - "discriminant modular {0} of encryption key({1}) is not co prime w.r.t {2}.\nTry another key.".format( + "discriminant modular {} of encryption key({}) is not co prime w.r.t {}.\nTry another key.".format( req_l, det, req_l ) ) diff --git a/ciphers/rsa_cipher.py b/ciphers/rsa_cipher.py index a9b2dcc55daa..da3778ae96f0 100644 --- a/ciphers/rsa_cipher.py +++ b/ciphers/rsa_cipher.py @@ -101,7 +101,7 @@ def encryptAndWriteToFile( for i in range(len(encryptedBlocks)): encryptedBlocks[i] = str(encryptedBlocks[i]) encryptedContent = ",".join(encryptedBlocks) - encryptedContent = "%s_%s_%s" % (len(message), blockSize, encryptedContent) + encryptedContent = "{}_{}_{}".format(len(message), blockSize, encryptedContent) with open(messageFilename, "w") as fo: fo.write(encryptedContent) return encryptedContent diff --git a/ciphers/rsa_key_generator.py b/ciphers/rsa_key_generator.py index ce7c1f3dd12b..729d31c08a02 100644 --- a/ciphers/rsa_key_generator.py +++ b/ciphers/rsa_key_generator.py @@ -43,11 +43,11 @@ def makeKeyFiles(name, keySize): publicKey, privateKey = generateKey(keySize) print("\nWriting public key to file %s_pubkey.txt..." % name) with open("%s_pubkey.txt" % name, "w") as fo: - fo.write("%s,%s,%s" % (keySize, publicKey[0], publicKey[1])) + fo.write("{},{},{}".format(keySize, publicKey[0], publicKey[1])) print("Writing private key to file %s_privkey.txt..." % name) with open("%s_privkey.txt" % name, "w") as fo: - fo.write("%s,%s,%s" % (keySize, privateKey[0], privateKey[1])) + fo.write("{},{},{}".format(keySize, privateKey[0], privateKey[1])) if __name__ == "__main__": diff --git a/ciphers/shuffled_shift_cipher.py b/ciphers/shuffled_shift_cipher.py index bbefe3305fa7..be5c6caf845b 100644 --- a/ciphers/shuffled_shift_cipher.py +++ b/ciphers/shuffled_shift_cipher.py @@ -2,7 +2,7 @@ import string -class ShuffledShiftCipher(object): +class ShuffledShiftCipher: """ This algorithm uses the Caesar Cipher algorithm but removes the option to use brute force to decrypt the message. diff --git a/ciphers/simple_substitution_cipher.py b/ciphers/simple_substitution_cipher.py index 12511cc39bbc..7da18482db8c 100644 --- a/ciphers/simple_substitution_cipher.py +++ b/ciphers/simple_substitution_cipher.py @@ -17,7 +17,7 @@ def main(): mode = "decrypt" translated = decryptMessage(key, message) - print("\n%sion: \n%s" % (mode.title(), translated)) + print("\n{}ion: \n{}".format(mode.title(), translated)) def checkValidKey(key): diff --git a/ciphers/xor_cipher.py b/ciphers/xor_cipher.py index 17e45413acd5..58b5352672ef 100644 --- a/ciphers/xor_cipher.py +++ b/ciphers/xor_cipher.py @@ -18,7 +18,7 @@ """ -class XORCipher(object): +class XORCipher: def __init__(self, key=0): """ simple constructor that receives a key or uses diff --git a/compression/burrows_wheeler.py b/compression/burrows_wheeler.py index 50ee62aa0cb3..1c7939b39038 100644 --- a/compression/burrows_wheeler.py +++ b/compression/burrows_wheeler.py @@ -135,16 +135,14 @@ def reverse_bwt(bwt_string: str, idx_original_string: int) -> str: idx_original_string = int(idx_original_string) except ValueError: raise TypeError( - ( - "The parameter idx_original_string type must be int or passive" - " of cast to int." - ) + "The parameter idx_original_string type must be int or passive" + " of cast to int." ) if idx_original_string < 0: raise ValueError("The parameter idx_original_string must not be lower than 0.") if idx_original_string >= len(bwt_string): raise ValueError( - ("The parameter idx_original_string must be lower than" " len(bwt_string).") + "The parameter idx_original_string must be lower than" " len(bwt_string)." ) ordered_rotations = [""] * len(bwt_string) diff --git a/data_structures/binary_tree/avl_tree.py b/data_structures/binary_tree/avl_tree.py index 31d12c811105..2df747c105ad 100644 --- a/data_structures/binary_tree/avl_tree.py +++ b/data_structures/binary_tree/avl_tree.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- """ An auto-balanced binary tree! """ diff --git a/data_structures/binary_tree/red_black_tree.py b/data_structures/binary_tree/red_black_tree.py index 908f13cd581e..0884766504de 100644 --- a/data_structures/binary_tree/red_black_tree.py +++ b/data_structures/binary_tree/red_black_tree.py @@ -467,7 +467,7 @@ def __repr__(self): from pprint import pformat if self.left is None and self.right is None: - return "'%s %s'" % (self.label, (self.color and "red") or "blk") + return "'{} {}'".format(self.label, (self.color and "red") or "blk") return pformat( { "%s %s" diff --git a/data_structures/binary_tree/treap.py b/data_structures/binary_tree/treap.py index 6bc2403f7102..d2e3fb88e8f7 100644 --- a/data_structures/binary_tree/treap.py +++ b/data_structures/binary_tree/treap.py @@ -2,7 +2,7 @@ from typing import Tuple -class Node(object): +class Node: """ Treap's node Treap is a binary tree by value and heap by priority @@ -18,11 +18,10 @@ def __repr__(self): from pprint import pformat if self.left is None and self.right is None: - return "'%s: %.5s'" % (self.value, self.prior) + return f"'{self.value}: {self.prior:.5}'" else: return pformat( - {"%s: %.5s" % (self.value, self.prior): (self.left, self.right)}, - indent=1, + {f"{self.value}: {self.prior:.5}": (self.left, self.right)}, indent=1, ) def __str__(self): diff --git a/data_structures/data_structures/heap/heap_generic.py b/data_structures/data_structures/heap/heap_generic.py index fc17c1b1218e..8993d501331b 100644 --- a/data_structures/data_structures/heap/heap_generic.py +++ b/data_structures/data_structures/heap/heap_generic.py @@ -1,4 +1,4 @@ -class Heap(object): +class Heap: """A generic Heap class, can be used as min or max by passing the key function accordingly. """ diff --git a/data_structures/hashing/hash_table.py b/data_structures/hashing/hash_table.py index ab473dc52324..69eaa65d8e57 100644 --- a/data_structures/hashing/hash_table.py +++ b/data_structures/hashing/hash_table.py @@ -28,7 +28,7 @@ def hash_function(self, key): def _step_by_step(self, step_ord): - print("step {0}".format(step_ord)) + print(f"step {step_ord}") print([i for i in range(len(self.values))]) print(self.values) diff --git a/data_structures/stacks/stack.py b/data_structures/stacks/stack.py index 9f5b279710c6..53a40a7b7ebc 100644 --- a/data_structures/stacks/stack.py +++ b/data_structures/stacks/stack.py @@ -1,7 +1,7 @@ __author__ = "Omkar Pathak" -class Stack(object): +class Stack: """ A stack is an abstract data type that serves as a collection of elements with two principal operations: push() and pop(). push() adds an element to the top of the stack, and pop() removes an element from the top diff --git a/digital_image_processing/histogram_equalization/histogram_stretch.py b/digital_image_processing/histogram_equalization/histogram_stretch.py index b6557d6ef77d..d4a6c08418ee 100644 --- a/digital_image_processing/histogram_equalization/histogram_stretch.py +++ b/digital_image_processing/histogram_equalization/histogram_stretch.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- """ Created on Fri Sep 28 15:22:29 2018 diff --git a/digital_image_processing/index_calculation.py b/digital_image_processing/index_calculation.py index fc5b169650a2..0786314e1223 100644 --- a/digital_image_processing/index_calculation.py +++ b/digital_image_processing/index_calculation.py @@ -389,10 +389,7 @@ def Hue(self): :return: index """ return np.arctan( - ( - ((2 * self.red - self.green - self.blue) / 30.5) - * (self.green - self.blue) - ) + ((2 * self.red - self.green - self.blue) / 30.5) * (self.green - self.blue) ) def IVI(self, a=None, b=None): diff --git a/dynamic_programming/fast_fibonacci.py b/dynamic_programming/fast_fibonacci.py index 47248078bd81..77094a40384b 100644 --- a/dynamic_programming/fast_fibonacci.py +++ b/dynamic_programming/fast_fibonacci.py @@ -1,5 +1,4 @@ #!/usr/bin/python -# encoding=utf8 """ This program calculates the nth Fibonacci number in O(log(n)). diff --git a/dynamic_programming/k_means_clustering_tensorflow.py b/dynamic_programming/k_means_clustering_tensorflow.py index 6b1eb628e5c3..4fbcedeaa0dc 100644 --- a/dynamic_programming/k_means_clustering_tensorflow.py +++ b/dynamic_programming/k_means_clustering_tensorflow.py @@ -40,7 +40,7 @@ def TFKMeansCluster(vectors, noofclusters): ##First lets ensure we have a Variable vector for each centroid, ##initialized to one of the vectors from the available data points centroids = [ - tf.Variable((vectors[vector_indices[i]])) for i in range(noofclusters) + tf.Variable(vectors[vector_indices[i]]) for i in range(noofclusters) ] ##These nodes will assign the centroid Variables the appropriate ##values diff --git a/dynamic_programming/matrix_chain_order.py b/dynamic_programming/matrix_chain_order.py index f88a9be8ac95..9411bc704f1c 100644 --- a/dynamic_programming/matrix_chain_order.py +++ b/dynamic_programming/matrix_chain_order.py @@ -46,7 +46,7 @@ def main(): # 30*35 35*15 15*5 5*10 10*20 20*25 Matrix, OptimalSolution = MatrixChainOrder(array) - print("No. of Operation required: " + str((Matrix[1][n - 1]))) + print("No. of Operation required: " + str(Matrix[1][n - 1])) PrintOptimalSolution(OptimalSolution, 1, n - 1) diff --git a/graphs/basic_graphs.py b/graphs/basic_graphs.py index 161bc0c09d3b..070af5f55f01 100644 --- a/graphs/basic_graphs.py +++ b/graphs/basic_graphs.py @@ -48,7 +48,7 @@ def dfs(G, s): - vis, S = set([s]), [s] + vis, S = {s}, [s] print(s) while S: flag = 0 @@ -76,7 +76,7 @@ def dfs(G, s): def bfs(G, s): - vis, Q = set([s]), deque([s]) + vis, Q = {s}, deque([s]) print(s) while Q: u = Q.popleft() @@ -255,7 +255,7 @@ def krusk(E_and_n): # Sort edges on the basis of distance (E, n) = E_and_n E.sort(reverse=True, key=lambda x: x[2]) - s = [set([i]) for i in range(1, n + 1)] + s = [{i} for i in range(1, n + 1)] while True: if len(s) == 1: break diff --git a/graphs/breadth_first_search.py b/graphs/breadth_first_search.py index 8516e60a59c4..faa166150c76 100644 --- a/graphs/breadth_first_search.py +++ b/graphs/breadth_first_search.py @@ -1,5 +1,4 @@ #!/usr/bin/python -# encoding=utf8 """ Author: OMKAR PATHAK """ diff --git a/graphs/depth_first_search.py b/graphs/depth_first_search.py index 5347c2fbcfa3..2fe9dd157d2d 100644 --- a/graphs/depth_first_search.py +++ b/graphs/depth_first_search.py @@ -1,5 +1,4 @@ #!/usr/bin/python -# encoding=utf8 """ Author: OMKAR PATHAK """ diff --git a/graphs/edmonds_karp_multiple_source_and_sink.py b/graphs/edmonds_karp_multiple_source_and_sink.py index eb6ec739ba00..0f359ff1aea3 100644 --- a/graphs/edmonds_karp_multiple_source_and_sink.py +++ b/graphs/edmonds_karp_multiple_source_and_sink.py @@ -57,7 +57,7 @@ def setMaximumFlowAlgorithm(self, Algorithm): self.maximumFlowAlgorithm = Algorithm(self) -class FlowNetworkAlgorithmExecutor(object): +class FlowNetworkAlgorithmExecutor: def __init__(self, flowNetwork): self.flowNetwork = flowNetwork self.verticesCount = flowNetwork.verticesCount @@ -80,7 +80,7 @@ def _algorithm(self): class MaximumFlowAlgorithmExecutor(FlowNetworkAlgorithmExecutor): def __init__(self, flowNetwork): - super(MaximumFlowAlgorithmExecutor, self).__init__(flowNetwork) + super().__init__(flowNetwork) # use this to save your result self.maximumFlow = -1 @@ -93,7 +93,7 @@ def getMaximumFlow(self): class PushRelabelExecutor(MaximumFlowAlgorithmExecutor): def __init__(self, flowNetwork): - super(PushRelabelExecutor, self).__init__(flowNetwork) + super().__init__(flowNetwork) self.preflow = [[0] * self.verticesCount for i in range(self.verticesCount)] diff --git a/graphs/graph_list.py b/graphs/graph_list.py index 4f0cbf15c033..a20940ab1598 100644 --- a/graphs/graph_list.py +++ b/graphs/graph_list.py @@ -1,12 +1,11 @@ #!/usr/bin/python -# encoding=utf8 # Author: OMKAR PATHAK # We can use Python's dictionary for constructing the graph. -class AdjacencyList(object): +class AdjacencyList: def __init__(self): self.List = {} diff --git a/hashes/hamming_code.py b/hashes/hamming_code.py index 155c1b10a38d..3e0424490781 100644 --- a/hashes/hamming_code.py +++ b/hashes/hamming_code.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Author: João Gustavo A. Amorim & Gabriel Kunz # Author email: joaogustavoamorim@gmail.com and gabriel-kunz@uergs.edu.br # Coding date: apr 2019 @@ -100,7 +99,7 @@ def emitterConverter(sizePar, data): # Performs a template of bit positions - who should be given, # and who should be parity if qtdBP < sizePar: - if ((np.log(x) / np.log(2))).is_integer(): + if (np.log(x) / np.log(2)).is_integer(): dataOutGab.append("P") qtdBP = qtdBP + 1 else: @@ -170,7 +169,7 @@ def receptorConverter(sizePar, data): # Performs a template of bit positions - who should be given, # and who should be parity if qtdBP < sizePar: - if ((np.log(x) / np.log(2))).is_integer(): + if (np.log(x) / np.log(2)).is_integer(): dataOutGab.append("P") qtdBP = qtdBP + 1 else: @@ -204,7 +203,7 @@ def receptorConverter(sizePar, data): # Performs a template position of bits - who should be given, # and who should be parity if qtdBP < sizePar: - if ((np.log(x) / np.log(2))).is_integer(): + if (np.log(x) / np.log(2)).is_integer(): dataOutGab.append("P") qtdBP = qtdBP + 1 else: diff --git a/linear_algebra/src/lib.py b/linear_algebra/src/lib.py index 15d176cc6392..f4628f1d964a 100644 --- a/linear_algebra/src/lib.py +++ b/linear_algebra/src/lib.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- """ Created on Mon Feb 26 14:29:11 2018 @@ -25,7 +24,7 @@ import random -class Vector(object): +class Vector: """ This class represents a vector of arbitrary size. You need to give the vector components. @@ -205,7 +204,7 @@ def randomVector(N, a, b): return Vector(ans) -class Matrix(object): +class Matrix: """ class: Matrix This class represents a arbitrary matrix. diff --git a/linear_algebra/src/test_linear_algebra.py b/linear_algebra/src/test_linear_algebra.py index f8e7db7de6cc..5e28910af86a 100644 --- a/linear_algebra/src/test_linear_algebra.py +++ b/linear_algebra/src/test_linear_algebra.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- """ Created on Mon Feb 26 15:40:07 2018 diff --git a/machine_learning/k_means_clust.py b/machine_learning/k_means_clust.py index 4c643226b213..7a4f69eb77ce 100644 --- a/machine_learning/k_means_clust.py +++ b/machine_learning/k_means_clust.py @@ -126,7 +126,7 @@ def plot_heterogeneity(heterogeneity, k): plt.plot(heterogeneity, linewidth=4) plt.xlabel("# Iterations") plt.ylabel("Heterogeneity") - plt.title("Heterogeneity of clustering over time, K={0:d}".format(k)) + plt.title(f"Heterogeneity of clustering over time, K={k:d}") plt.rcParams.update({"font.size": 16}) plt.show() @@ -164,7 +164,7 @@ def kmeans( num_changed = np.sum(prev_cluster_assignment != cluster_assignment) if verbose: print( - " {0:5d} elements changed their cluster assignment.".format( + " {:5d} elements changed their cluster assignment.".format( num_changed ) ) diff --git a/machine_learning/logistic_regression.py b/machine_learning/logistic_regression.py index f23d400ced55..f5edfb9c5d2c 100644 --- a/machine_learning/logistic_regression.py +++ b/machine_learning/logistic_regression.py @@ -1,5 +1,4 @@ #!/usr/bin/python -# -*- coding: utf-8 -*- ## Logistic Regression from scratch diff --git a/machine_learning/sequential_minimum_optimization.py b/machine_learning/sequential_minimum_optimization.py index aa997d88cac9..0612392c8dc2 100644 --- a/machine_learning/sequential_minimum_optimization.py +++ b/machine_learning/sequential_minimum_optimization.py @@ -1,4 +1,3 @@ -# coding: utf-8 """ Implementation of sequential minimal optimization(SMO) for support vector machines(SVM). @@ -29,7 +28,6 @@ http://web.cs.iastate.edu/~honavar/smo-svm.pdf """ -from __future__ import division import os import sys @@ -44,7 +42,7 @@ CANCER_DATASET_URL = "http://archive.ics.uci.edu/ml/machine-learning-databases/breast-cancer-wisconsin/wdbc.data" -class SmoSVM(object): +class SmoSVM: def __init__( self, train, @@ -405,7 +403,7 @@ def length(self): return self.samples.shape[0] -class Kernel(object): +class Kernel: def __init__(self, kernel, degree=1.0, coef0=0.0, gamma=1.0): self.degree = np.float64(degree) self.coef0 = np.float64(coef0) diff --git a/maths/3n+1.py b/maths/3n+1.py index f6fe77b2b3fe..64ff34fd2039 100644 --- a/maths/3n+1.py +++ b/maths/3n+1.py @@ -9,9 +9,9 @@ def n31(a: int) -> Tuple[List[int], int]: """ if not isinstance(a, int): - raise TypeError("Must be int, not {0}".format(type(a).__name__)) + raise TypeError("Must be int, not {}".format(type(a).__name__)) if a < 1: - raise ValueError("Given integer must be greater than 1, not {0}".format(a)) + raise ValueError(f"Given integer must be greater than 1, not {a}") path = [a] while a != 1: diff --git a/maths/__init__.py b/maths/__init__.py index 8b137891791f..e69de29bb2d1 100644 --- a/maths/__init__.py +++ b/maths/__init__.py @@ -1 +0,0 @@ - diff --git a/maths/hardy_ramanujanalgo.py b/maths/hardy_ramanujanalgo.py index bb31a1be49fb..90e4913c70a7 100644 --- a/maths/hardy_ramanujanalgo.py +++ b/maths/hardy_ramanujanalgo.py @@ -37,7 +37,7 @@ def exactPrimeFactorCount(n): if __name__ == "__main__": n = 51242183 print(f"The number of distinct prime factors is/are {exactPrimeFactorCount(n)}") - print("The value of log(log(n)) is {0:.4f}".format(math.log(math.log(n)))) + print("The value of log(log(n)) is {:.4f}".format(math.log(math.log(n)))) """ The number of distinct prime factors is/are 3 diff --git a/maths/lucas_lehmer_primality_test.py b/maths/lucas_lehmer_primality_test.py index 44e41ba58d93..8dac658f16d1 100644 --- a/maths/lucas_lehmer_primality_test.py +++ b/maths/lucas_lehmer_primality_test.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- """ In mathematics, the Lucas–Lehmer test (LLT) is a primality test for Mersenne numbers. https://en.wikipedia.org/wiki/Lucas%E2%80%93Lehmer_primality_test diff --git a/maths/matrix_exponentiation.py b/maths/matrix_exponentiation.py index c20292735a92..a8f11480378a 100644 --- a/maths/matrix_exponentiation.py +++ b/maths/matrix_exponentiation.py @@ -10,7 +10,7 @@ """ -class Matrix(object): +class Matrix: def __init__(self, arg): if isinstance(arg, list): # Initialzes a matrix identical to the one provided. self.t = arg diff --git a/maths/newton_raphson.py b/maths/newton_raphson.py index 093cc4438416..c4975c73e037 100644 --- a/maths/newton_raphson.py +++ b/maths/newton_raphson.py @@ -52,4 +52,4 @@ def newton_raphson(f, x0=0, maxiter=100, step=0.0001, maxerror=1e-6, logsteps=Fa plt.xlabel("step") plt.ylabel("error") plt.show() - print("solution = {%f}, error = {%f}" % (solution, error)) + print(f"solution = {{{solution:f}}}, error = {{{error:f}}}") diff --git a/maths/pythagoras.py b/maths/pythagoras.py index 2f59107cdfaa..69a17731a0fd 100644 --- a/maths/pythagoras.py +++ b/maths/pythagoras.py @@ -14,7 +14,7 @@ def __repr__(self) -> str: def distance(a: Point, b: Point) -> float: - return math.sqrt(abs(((b.x - a.x) ** 2 + (b.y - a.y) ** 2 + (b.z - a.z) ** 2))) + return math.sqrt(abs((b.x - a.x) ** 2 + (b.y - a.y) ** 2 + (b.z - a.z) ** 2)) def test_distance() -> None: diff --git a/maths/sieve_of_eratosthenes.py b/maths/sieve_of_eratosthenes.py index 44c7f8a02682..4761c9339ea0 100644 --- a/maths/sieve_of_eratosthenes.py +++ b/maths/sieve_of_eratosthenes.py @@ -1,5 +1,3 @@ -# -*- coding: utf-8 -*- - """ Sieve of Eratosthones diff --git a/matrix/matrix_operation.py b/matrix/matrix_operation.py index 5ca61b4ed023..26e21aafcbca 100644 --- a/matrix/matrix_operation.py +++ b/matrix/matrix_operation.py @@ -148,9 +148,9 @@ def main(): % (matrix_a, matrix_b, multiply(matrix_a, matrix_b)) ) print("Identity: %s \n" % identity(5)) - print("Minor of %s = %s \n" % (matrix_c, minor(matrix_c, 1, 2))) - print("Determinant of %s = %s \n" % (matrix_b, determinant(matrix_b))) - print("Inverse of %s = %s\n" % (matrix_d, inverse(matrix_d))) + print("Minor of {} = {} \n".format(matrix_c, minor(matrix_c, 1, 2))) + print("Determinant of {} = {} \n".format(matrix_b, determinant(matrix_b))) + print("Inverse of {} = {}\n".format(matrix_d, inverse(matrix_d))) if __name__ == "__main__": diff --git a/matrix/rotate_matrix.py b/matrix/rotate_matrix.py index 822851826121..14a9493cd12f 100644 --- a/matrix/rotate_matrix.py +++ b/matrix/rotate_matrix.py @@ -1,5 +1,3 @@ -# -*- coding: utf-8 -*- - """ In this problem, we want to rotate the matrix elements by 90, 180, 270 (counterclockwise) Discussion in stackoverflow: diff --git a/matrix/searching_in_sorted_matrix.py b/matrix/searching_in_sorted_matrix.py index 1b3eeedf3110..3897ffb1d9c6 100644 --- a/matrix/searching_in_sorted_matrix.py +++ b/matrix/searching_in_sorted_matrix.py @@ -2,7 +2,7 @@ def search_in_a_sorted_matrix(mat, m, n, key): i, j = m - 1, 0 while i >= 0 and j < n: if key == mat[i][j]: - print("Key %s found at row- %s column- %s" % (key, i + 1, j + 1)) + print("Key {} found at row- {} column- {}".format(key, i + 1, j + 1)) return if key < mat[i][j]: i -= 1 diff --git a/matrix/sherman_morrison.py b/matrix/sherman_morrison.py index 531b76cdeb94..257cf33712d5 100644 --- a/matrix/sherman_morrison.py +++ b/matrix/sherman_morrison.py @@ -176,7 +176,7 @@ def __mul__(self, another): return result else: raise TypeError( - "Unsupported type given for another (%s)" % (type(another),) + "Unsupported type given for another ({})".format(type(another)) ) def transpose(self): @@ -248,17 +248,17 @@ def test1(): ainv = Matrix(3, 3, 0) for i in range(3): ainv[i, i] = 1 - print("a^(-1) is %s" % (ainv,)) + print(f"a^(-1) is {ainv}") # u, v u = Matrix(3, 1, 0) u[0, 0], u[1, 0], u[2, 0] = 1, 2, -3 v = Matrix(3, 1, 0) v[0, 0], v[1, 0], v[2, 0] = 4, -2, 5 - print("u is %s" % (u,)) - print("v is %s" % (v,)) + print(f"u is {u}") + print(f"v is {v}") print("uv^T is %s" % (u * v.transpose())) # Sherman Morrison - print("(a + uv^T)^(-1) is %s" % (ainv.ShermanMorrison(u, v),)) + print("(a + uv^T)^(-1) is {}".format(ainv.ShermanMorrison(u, v))) def test2(): import doctest diff --git a/neural_network/back_propagation_neural_network.py b/neural_network/back_propagation_neural_network.py index 224fc85de066..c771dc46afc2 100644 --- a/neural_network/back_propagation_neural_network.py +++ b/neural_network/back_propagation_neural_network.py @@ -1,5 +1,4 @@ #!/usr/bin/python -# encoding=utf8 """ @@ -31,6 +30,7 @@ class DenseLayer: """ Layers of BP neural network """ + def __init__( self, units, activation=None, learning_rate=None, is_input_layer=False ): @@ -99,6 +99,7 @@ class BPNN: """ Back Propagation Neural Network model """ + def __init__(self): self.layers = [] self.train_mse = [] diff --git a/neural_network/convolution_neural_network.py b/neural_network/convolution_neural_network.py index 9448671abace..ecc8e3392b7f 100644 --- a/neural_network/convolution_neural_network.py +++ b/neural_network/convolution_neural_network.py @@ -1,5 +1,3 @@ -# -*- coding: utf-8 -*- - """ - - - - - -- - - - - - - - - - - - - - - - - - - - - - - Name - - CNN - Convolution Neural Network For Photo Recognizing @@ -286,7 +284,7 @@ def train( self.thre_bp3 = self.thre_bp3 - pd_k_all * self.rate_thre self.thre_bp2 = self.thre_bp2 - pd_j_all * self.rate_thre # calculate the sum error of all single image - errors = np.sum(abs((data_teach - bp_out3))) + errors = np.sum(abs(data_teach - bp_out3)) alle = alle + errors # print(' ----Teach ',data_teach) # print(' ----BP_output ',bp_out3) diff --git a/neural_network/input_data.py b/neural_network/input_data.py index ea826be6cd84..0e22ac0bcda5 100644 --- a/neural_network/input_data.py +++ b/neural_network/input_data.py @@ -17,9 +17,6 @@ This module and all its submodules are deprecated. """ -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function import collections import gzip @@ -115,7 +112,7 @@ def _extract_labels(f, one_hot=False, num_classes=10): return labels -class _DataSet(object): +class _DataSet: """Container class for a _DataSet (deprecated). THIS CLASS IS DEPRECATED. @@ -165,7 +162,7 @@ def __init__( else: assert ( images.shape[0] == labels.shape[0] - ), "images.shape: %s labels.shape: %s" % (images.shape, labels.shape) + ), f"images.shape: {images.shape} labels.shape: {labels.shape}" self._num_examples = images.shape[0] # Convert shape from [num examples, rows, columns, depth] diff --git a/other/anagrams.py b/other/anagrams.py index 9e103296b382..6e6806e92a0f 100644 --- a/other/anagrams.py +++ b/other/anagrams.py @@ -4,7 +4,7 @@ print("creating word list...") path = os.path.split(os.path.realpath(__file__)) with open(path[0] + "/words") as f: - word_list = sorted(list(set([word.strip().lower() for word in f]))) + word_list = sorted(list({word.strip().lower() for word in f})) def signature(word): diff --git a/other/fischer_yates_shuffle.py b/other/fischer_yates_shuffle.py index 977e5f131e4f..4217cb0bef67 100644 --- a/other/fischer_yates_shuffle.py +++ b/other/fischer_yates_shuffle.py @@ -1,5 +1,4 @@ #!/usr/bin/python -# encoding=utf8 """ The Fisher–Yates shuffle is an algorithm for generating a random permutation of a finite sequence. For more details visit diff --git a/other/linear_congruential_generator.py b/other/linear_congruential_generator.py index 3b150f422e4f..ea0adc7d027f 100644 --- a/other/linear_congruential_generator.py +++ b/other/linear_congruential_generator.py @@ -3,7 +3,7 @@ from time import time -class LinearCongruentialGenerator(object): +class LinearCongruentialGenerator: """ A pseudorandom number generator. """ diff --git a/other/primelib.py b/other/primelib.py index 6fc5eddeb257..ff438755ae6f 100644 --- a/other/primelib.py +++ b/other/primelib.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- """ Created on Thu Oct 5 16:44:23 2017 diff --git a/other/sierpinski_triangle.py b/other/sierpinski_triangle.py index 0e6ce43e35d3..a262900a84f9 100644 --- a/other/sierpinski_triangle.py +++ b/other/sierpinski_triangle.py @@ -1,5 +1,4 @@ #!/usr/bin/python -# encoding=utf8 """Author Anurag Kumar | anuragkumarak95@gmail.com | git/anuragkumarak95 diff --git a/project_euler/problem_06/sol1.py b/project_euler/problem_06/sol1.py index c69b6c89e35a..513b354679a9 100644 --- a/project_euler/problem_06/sol1.py +++ b/project_euler/problem_06/sol1.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- """ Problem: diff --git a/project_euler/problem_06/sol2.py b/project_euler/problem_06/sol2.py index 1698a3fb61fd..18cdb51752ea 100644 --- a/project_euler/problem_06/sol2.py +++ b/project_euler/problem_06/sol2.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- """ Problem: diff --git a/project_euler/problem_06/sol3.py b/project_euler/problem_06/sol3.py index f9c5dacb3777..ee739c9a1293 100644 --- a/project_euler/problem_06/sol3.py +++ b/project_euler/problem_06/sol3.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- """ Problem: diff --git a/project_euler/problem_06/sol4.py b/project_euler/problem_06/sol4.py index 1e1de5570e7d..07eed57ba9b5 100644 --- a/project_euler/problem_06/sol4.py +++ b/project_euler/problem_06/sol4.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- """ Problem: diff --git a/project_euler/problem_07/sol1.py b/project_euler/problem_07/sol1.py index d8d67e157860..f6b2584d9cdc 100644 --- a/project_euler/problem_07/sol1.py +++ b/project_euler/problem_07/sol1.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- """ By listing the first six prime numbers: diff --git a/project_euler/problem_07/sol2.py b/project_euler/problem_07/sol2.py index 5d30e540b3e7..6bfc5881f548 100644 --- a/project_euler/problem_07/sol2.py +++ b/project_euler/problem_07/sol2.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- """ By listing the first six prime numbers: diff --git a/project_euler/problem_07/sol3.py b/project_euler/problem_07/sol3.py index 3c28ecf7fb34..9b02ea87ec49 100644 --- a/project_euler/problem_07/sol3.py +++ b/project_euler/problem_07/sol3.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- """ By listing the first six prime numbers: diff --git a/project_euler/problem_08/sol1.py b/project_euler/problem_08/sol1.py index 6752fae3de60..e7582d46c351 100644 --- a/project_euler/problem_08/sol1.py +++ b/project_euler/problem_08/sol1.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- """ The four adjacent digits in the 1000-digit number that have the greatest product are 9 × 9 × 8 × 9 = 5832. diff --git a/project_euler/problem_08/sol2.py b/project_euler/problem_08/sol2.py index bae96e373d6c..bf8afa8379ee 100644 --- a/project_euler/problem_08/sol2.py +++ b/project_euler/problem_08/sol2.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- """ The four adjacent digits in the 1000-digit number that have the greatest product are 9 × 9 × 8 × 9 = 5832. diff --git a/project_euler/problem_08/sol3.py b/project_euler/problem_08/sol3.py index fe9901742201..dfbef5755dd7 100644 --- a/project_euler/problem_08/sol3.py +++ b/project_euler/problem_08/sol3.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- """ The four adjacent digits in the 1000-digit number that have the greatest product are 9 × 9 × 8 × 9 = 5832. diff --git a/project_euler/problem_14/sol1.py b/project_euler/problem_14/sol1.py index ab09937fb315..fda45bc94bb7 100644 --- a/project_euler/problem_14/sol1.py +++ b/project_euler/problem_14/sol1.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- """ Problem Statement: The following iterative sequence is defined for the set of positive integers: diff --git a/project_euler/problem_14/sol2.py b/project_euler/problem_14/sol2.py index 9b8857e710b4..375a34c72f57 100644 --- a/project_euler/problem_14/sol2.py +++ b/project_euler/problem_14/sol2.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- """ Collatz conjecture: start with any positive integer n. Next term obtained from the previous term as follows: diff --git a/project_euler/problem_21/sol1.py b/project_euler/problem_21/sol1.py index 49c2db964316..f01c9d0dad73 100644 --- a/project_euler/problem_21/sol1.py +++ b/project_euler/problem_21/sol1.py @@ -1,4 +1,3 @@ -# -.- coding: latin-1 -.- from math import sqrt """ diff --git a/project_euler/problem_22/sol1.py b/project_euler/problem_22/sol1.py index f6275e2138bb..982906245e87 100644 --- a/project_euler/problem_22/sol1.py +++ b/project_euler/problem_22/sol1.py @@ -1,4 +1,3 @@ -# -*- coding: latin-1 -*- """ Name scores Problem 22 diff --git a/project_euler/problem_22/sol2.py b/project_euler/problem_22/sol2.py index 69acd2fb8ef3..5ae41c84686e 100644 --- a/project_euler/problem_22/sol2.py +++ b/project_euler/problem_22/sol2.py @@ -1,4 +1,3 @@ -# -*- coding: latin-1 -*- """ Name scores Problem 22 diff --git a/project_euler/problem_25/sol1.py b/project_euler/problem_25/sol1.py index 8fce32285976..f0228915dc15 100644 --- a/project_euler/problem_25/sol1.py +++ b/project_euler/problem_25/sol1.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- """ The Fibonacci sequence is defined by the recurrence relation: diff --git a/project_euler/problem_25/sol2.py b/project_euler/problem_25/sol2.py index d754e2ddd722..c98f09b1d316 100644 --- a/project_euler/problem_25/sol2.py +++ b/project_euler/problem_25/sol2.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- """ The Fibonacci sequence is defined by the recurrence relation: diff --git a/project_euler/problem_25/sol3.py b/project_euler/problem_25/sol3.py index 4e3084ce5456..4a1d9da76bf7 100644 --- a/project_euler/problem_25/sol3.py +++ b/project_euler/problem_25/sol3.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- """ The Fibonacci sequence is defined by the recurrence relation: diff --git a/project_euler/problem_31/sol1.py b/project_euler/problem_31/sol1.py index 187fb9167a13..1c59658b81ee 100644 --- a/project_euler/problem_31/sol1.py +++ b/project_euler/problem_31/sol1.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- """ Coin sums Problem 31 diff --git a/project_euler/problem_32/sol32.py b/project_euler/problem_32/sol32.py index 0abc04829a9a..393218339e9f 100644 --- a/project_euler/problem_32/sol32.py +++ b/project_euler/problem_32/sol32.py @@ -46,13 +46,11 @@ def solution(): """ return sum( - set( - [ - int("".join(pandigital[5:9])) - for pandigital in itertools.permutations("123456789") - if isCombinationValid(pandigital) - ] - ) + { + int("".join(pandigital[5:9])) + for pandigital in itertools.permutations("123456789") + if isCombinationValid(pandigital) + } ) diff --git a/project_euler/problem_33/__init__.py b/project_euler/problem_33/__init__.py index 8b137891791f..e69de29bb2d1 100644 --- a/project_euler/problem_33/__init__.py +++ b/project_euler/problem_33/__init__.py @@ -1 +0,0 @@ - diff --git a/project_euler/problem_33/sol1.py b/project_euler/problem_33/sol1.py index 0992c96935f5..73a49023ae41 100644 --- a/project_euler/problem_33/sol1.py +++ b/project_euler/problem_33/sol1.py @@ -43,7 +43,7 @@ def solve(digit_len: int) -> str: while den <= 99: if (num != den) and (num % 10 == den // 10) and (den % 10 != 0): if isDigitCancelling(num, den): - solutions.append("{}/{}".format(num, den)) + solutions.append(f"{num}/{den}") den += 1 num += 1 den = 10 diff --git a/project_euler/problem_40/sol1.py b/project_euler/problem_40/sol1.py index 786725e274b1..69be377723a5 100644 --- a/project_euler/problem_40/sol1.py +++ b/project_euler/problem_40/sol1.py @@ -1,4 +1,3 @@ -# -.- coding: latin-1 -.- """ Champernowne's constant Problem 40 diff --git a/project_euler/problem_53/sol1.py b/project_euler/problem_53/sol1.py index f17508b005d1..0692bbe0ebb8 100644 --- a/project_euler/problem_53/sol1.py +++ b/project_euler/problem_53/sol1.py @@ -1,4 +1,3 @@ -# -.- coding: latin-1 -.- """ Combinatoric selections Problem 53 diff --git a/sorts/external_sort.py b/sorts/external_sort.py index abdcb29f95b2..e5b2c045fa95 100644 --- a/sorts/external_sort.py +++ b/sorts/external_sort.py @@ -7,7 +7,7 @@ import argparse -class FileSplitter(object): +class FileSplitter: BLOCK_FILENAME_FORMAT = "block_{0}.dat" def __init__(self, filename): @@ -44,7 +44,7 @@ def cleanup(self): map(lambda f: os.remove(f), self.block_filenames) -class NWayMerge(object): +class NWayMerge: def select(self, choices): min_index = -1 min_str = None @@ -56,7 +56,7 @@ def select(self, choices): return min_index -class FilesArray(object): +class FilesArray: def __init__(self, files): self.files = files self.empty = set() @@ -89,7 +89,7 @@ def unshift(self, index): return value -class FileMerger(object): +class FileMerger: def __init__(self, merge_strategy): self.merge_strategy = merge_strategy @@ -109,7 +109,7 @@ def get_file_handles(self, filenames, buffer_size): return files -class ExternalSort(object): +class ExternalSort: def __init__(self, block_size): self.block_size = block_size diff --git a/strings/aho-corasick.py b/strings/aho-corasick.py index b2f89450ee7a..d63dc94a03fc 100644 --- a/strings/aho-corasick.py +++ b/strings/aho-corasick.py @@ -82,7 +82,7 @@ def search_in(self, string): for key in self.adlist[current_state]["output"]: if not (key in result): result[key] = [] - result[key].append((i - len(key) + 1)) + result[key].append(i - len(key) + 1) return result From 1d606d877244159319229a123079cfa3630d779a Mon Sep 17 00:00:00 2001 From: Christian Clauss Date: Fri, 3 Jan 2020 15:26:16 +0100 Subject: [PATCH 0415/1071] Dijkstra's Bankers algorithm (#1650) * Dijkstra's Bankers algorithm @bluedistro, Your review please. A second shot at #1645 Implementation of the Dijkstra's Banker's algorithm with test examples and a comprehensible description. * fixup! Format Python code with psf/black push * Delete back_propagation_neural_network.py * Create back_propagation_neural_network.py * fixup! Format Python code with psf/black push --- other/dijkstra_bankers_algorithm.py | 222 ++++++++++++++++++++++++++++ 1 file changed, 222 insertions(+) create mode 100644 other/dijkstra_bankers_algorithm.py diff --git a/other/dijkstra_bankers_algorithm.py b/other/dijkstra_bankers_algorithm.py new file mode 100644 index 000000000000..1f78941d3afc --- /dev/null +++ b/other/dijkstra_bankers_algorithm.py @@ -0,0 +1,222 @@ +# A Python implementation of the Banker's Algorithm in Operating Systems using +# Processes and Resources +# { +# "Author: "Biney Kingsley (bluedistro@github.io), bineykingsley36@gmail.com", +# "Date": 28-10-2018 +# } +""" +The Banker's algorithm is a resource allocation and deadlock avoidance algorithm +developed by Edsger Dijkstra that tests for safety by simulating the allocation of +predetermined maximum possible amounts of all resources, and then makes a "s-state" +check to test for possible deadlock conditions for all other pending activities, +before deciding whether allocation should be allowed to continue. +[Source] Wikipedia +[Credit] Rosetta Code C implementation helped very much. + (https://rosettacode.org/wiki/Banker%27s_algorithm) +""" + +import time +from typing import Dict, List + +import numpy as np + +test_claim_vector = [8, 5, 9, 7] +test_allocated_res_table = [ + [2, 0, 1, 1], + [0, 1, 2, 1], + [4, 0, 0, 3], + [0, 2, 1, 0], + [1, 0, 3, 0], +] +test_maximum_claim_table = [ + [3, 2, 1, 4], + [0, 2, 5, 2], + [5, 1, 0, 5], + [1, 5, 3, 0], + [3, 0, 3, 3], +] + + +class BankersAlgorithm: + def __init__( + self, + claim_vector: List[int], + allocated_resources_table: List[List[int]], + maximum_claim_table: List[List[int]], + ) -> None: + """ + :param claim_vector: A nxn/nxm list depicting the amount of each resources + (eg. memory, interface, semaphores, etc.) available. + :param allocated_resources_table: A nxn/nxm list depicting the amount of each + resource each process is currently holding + :param maximum_claim_table: A nxn/nxm list depicting how much of each resource + the system currently has available + """ + self.__claim_vector = claim_vector + self.__allocated_resources_table = allocated_resources_table + self.__maximum_claim_table = maximum_claim_table + + def __processes_resource_summation(self) -> List[int]: + """ + Check for allocated resources in line with each resource in the claim vector + """ + return [ + sum(p_item[i] for p_item in self.__allocated_resources_table) + for i in range(len(self.__allocated_resources_table[0])) + ] + + def __available_resources(self) -> List[int]: + """ + Check for available resources in line with each resource in the claim vector + """ + return np.array(self.__claim_vector) - np.array( + self.__processes_resource_summation() + ) + + def __need(self) -> List[List[int]]: + """ + Implement safety checker that calculates the needs by ensuring that + max_claim[i][j] - alloc_table[i][j] <= avail[j] + """ + return [ + list(np.array(self.__maximum_claim_table[i]) - np.array(allocated_resource)) + for i, allocated_resource in enumerate(self.__allocated_resources_table) + ] + + def __need_index_manager(self) -> Dict[int, List[int]]: + """ + This function builds an index control dictionary to track original ids/indices + of processes when altered during execution of method "main" + Return: {0: [a: int, b: int], 1: [c: int, d: int]} + >>> BankersAlgorithm(test_claim_vector, test_allocated_res_table, + ... test_maximum_claim_table)._BankersAlgorithm__need_index_manager() + {0: [1, 2, 0, 3], 1: [0, 1, 3, 1], 2: [1, 1, 0, 2], 3: [1, 3, 2, 0], 4: [2, 0, 0, 3]} + """ + return {self.__need().index(i): i for i in self.__need()} + + def main(self, **kwargs) -> None: + """ + Utilize various methods in this class to simulate the Banker's algorithm + Return: None + >>> BankersAlgorithm(test_claim_vector, test_allocated_res_table, + ... test_maximum_claim_table).main(describe=True) + Allocated Resource Table + P1 2 0 1 1 + + P2 0 1 2 1 + + P3 4 0 0 3 + + P4 0 2 1 0 + + P5 1 0 3 0 + + System Resource Table + P1 3 2 1 4 + + P2 0 2 5 2 + + P3 5 1 0 5 + + P4 1 5 3 0 + + P5 3 0 3 3 + + Current Usage by Active Processes: 8 5 9 7 + Initial Available Resources: 1 2 2 2 + __________________________________________________ + + Process 3 is executing. + Updated available resource stack for processes: 5 2 2 5 + The process is in a safe state. + + Process 1 is executing. + Updated available resource stack for processes: 7 2 3 6 + The process is in a safe state. + + Process 2 is executing. + Updated available resource stack for processes: 7 3 5 7 + The process is in a safe state. + + Process 4 is executing. + Updated available resource stack for processes: 7 5 6 7 + The process is in a safe state. + + Process 5 is executing. + Updated available resource stack for processes: 8 5 9 7 + The process is in a safe state. + + """ + need_list = self.__need() + alloc_resources_table = self.__allocated_resources_table + available_resources = self.__available_resources() + need_index_manager = self.__need_index_manager() + for kw, val in kwargs.items(): + if kw and val is True: + self.__pretty_data() + print("_" * 50 + "\n") + while need_list: + safe = False + for each_need in need_list: + execution = True + for index, need in enumerate(each_need): + if need > available_resources[index]: + execution = False + break + if execution: + safe = True + # get the original index of the process from ind_ctrl db + for original_need_index, need_clone in need_index_manager.items(): + if each_need == need_clone: + process_number = original_need_index + print(f"Process {process_number + 1} is executing.") + # remove the process run from stack + need_list.remove(each_need) + # update available/freed resources stack + available_resources = np.array(available_resources) + np.array( + alloc_resources_table[process_number] + ) + print( + "Updated available resource stack for processes: " + + " ".join([str(x) for x in available_resources]) + ) + break + if safe: + print("The process is in a safe state.\n") + else: + print("System in unsafe state. Aborting...\n") + break + + def __pretty_data(self): + """ + Properly align display of the algorithm's solution + """ + print(" " * 9 + "Allocated Resource Table") + for item in self.__allocated_resources_table: + print( + f"P{self.__allocated_resources_table.index(item) + 1}" + + " ".join(f"{it:>8}" for it in item) + + "\n" + ) + print(" " * 9 + "System Resource Table") + for item in self.__maximum_claim_table: + print( + f"P{self.__maximum_claim_table.index(item) + 1}" + + " ".join(f"{it:>8}" for it in item) + + "\n" + ) + print( + "Current Usage by Active Processes: " + + " ".join(str(x) for x in self.__claim_vector) + ) + print( + "Initial Available Resources: " + + " ".join(str(x) for x in self.__available_resources()) + ) + time.sleep(1) + + +if __name__ == "__main__": + import doctest + + doctest.testmod() From d4fc55c5fc4859490584c5f023a18e1c514226c8 Mon Sep 17 00:00:00 2001 From: harsh patel Date: Sat, 4 Jan 2020 20:33:55 +0530 Subject: [PATCH 0416/1071] Add files via upload (#1657) --- sorts/recursive_bubble_sort.py | 40 ++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 sorts/recursive_bubble_sort.py diff --git a/sorts/recursive_bubble_sort.py b/sorts/recursive_bubble_sort.py new file mode 100644 index 000000000000..88c13cbb95b5 --- /dev/null +++ b/sorts/recursive_bubble_sort.py @@ -0,0 +1,40 @@ +def bubble_sort(list1): + """ + It is similar is bubble sort but recursive. + :param list1: mutable ordered sequence of elements + :return: the same list in ascending order + + >>> bubble_sort([0, 5, 2, 3, 2]) + [0, 2, 2, 3, 5] + + >>> bubble_sort([]) + [] + + >>> bubble_sort([-2, -45, -5]) + [-45, -5, -2] + + >>> bubble_sort([-23, 0, 6, -4, 34]) + [-23, -4, 0, 6, 34] + + >>> bubble_sort([-23, 0, 6, -4, 34]) == sorted([-23, 0, 6, -4, 34]) + True + + >>> bubble_sort(['z','a','y','b','x','c']) + ['a', 'b', 'c', 'x', 'y', 'z'] + + """ + + for i, num in enumerate(list1): + try: + if list1[i+1] < num: + list1[i] = list1[i+1] + list1[i+1] = num + bubble_sort(list1) + except IndexError: + pass + return list1 + +if __name__ == "__main__": + list1 = [33,99,22,11,66] + bubble_sort(list1) + print(list1) From 1cc817bcc961061941fddcd081ab2dc19da6b877 Mon Sep 17 00:00:00 2001 From: Yurii <33547678+yuriimchg@users.noreply.github.com> Date: Sun, 5 Jan 2020 08:19:29 +0200 Subject: [PATCH 0417/1071] update volumes with type hints + some refactoring (#1353) * update volumes with type hints + some refactoring * added docstrings * Use float instead of ints in doctest results Co-authored-by: Christian Clauss --- maths/volume.py | 98 +++++++++++++++++++++++++++++++++---------------- 1 file changed, 67 insertions(+), 31 deletions(-) diff --git a/maths/volume.py b/maths/volume.py index 38de7516d9b2..04283743d71f 100644 --- a/maths/volume.py +++ b/maths/volume.py @@ -3,80 +3,116 @@ Wikipedia reference: https://en.wikipedia.org/wiki/Volume """ +from typing import Union +from math import pi, pow -from math import pi +def vol_cube(side_length: Union[int, float]) -> float: + """ + Calculate the Volume of a Cube. -def vol_cube(side_length): - """Calculate the Volume of a Cube.""" - # Cube side_length. - return float(side_length ** 3) + >>> vol_cube(1) + 1.0 + >>> vol_cube(3) + 27.0 + """ + return pow(side_length, 3) -def vol_cuboid(width, height, length): - """Calculate the Volume of a Cuboid.""" - # Multiply lengths together. +def vol_cuboid(width: float, height: float, length: float) -> float: + """ + Calculate the Volume of a Cuboid. + :return multiple of width, length and height + + >>> vol_cuboid(1, 1, 1) + 1.0 + >>> vol_cuboid(1, 2, 3) + 6.0 + """ return float(width * height * length) -def vol_cone(area_of_base, height): +def vol_cone(area_of_base: float, height: float) -> float: """ Calculate the Volume of a Cone. Wikipedia reference: https://en.wikipedia.org/wiki/Cone - volume = (1/3) * area_of_base * height + :return (1/3) * area_of_base * height + + >>> vol_cone(10, 3) + 10.0 + >>> vol_cone(1, 1) + 0.3333333333333333 """ - return (float(1) / 3) * area_of_base * height + return area_of_base * height / 3.0 -def vol_right_circ_cone(radius, height): +def vol_right_circ_cone(radius: float, height: float) -> float: """ Calculate the Volume of a Right Circular Cone. Wikipedia reference: https://en.wikipedia.org/wiki/Cone - volume = (1/3) * pi * radius^2 * height - """ + :return (1/3) * pi * radius^2 * height - return (float(1) / 3) * pi * (radius ** 2) * height + >>> vol_right_circ_cone(2, 3) + 12.566370614359172 + """ + return pi * pow(radius, 2) * height / 3.0 -def vol_prism(area_of_base, height): +def vol_prism(area_of_base: float, height: float) -> float: """ Calculate the Volume of a Prism. - - V = Bh Wikipedia reference: https://en.wikipedia.org/wiki/Prism_(geometry) + :return V = Bh + + >>> vol_prism(10, 2) + 20.0 + >>> vol_prism(11, 1) + 11.0 """ return float(area_of_base * height) -def vol_pyramid(area_of_base, height): +def vol_pyramid(area_of_base: float, height: float) -> float: """ - Calculate the Volume of a Prism. - - V = (1/3) * Bh + Calculate the Volume of a Pyramid. Wikipedia reference: https://en.wikipedia.org/wiki/Pyramid_(geometry) + :return (1/3) * Bh + + >>> vol_pyramid(10, 3) + 10.0 + >>> vol_pyramid(1.5, 3) + 1.5 """ - return (float(1) / 3) * area_of_base * height + return area_of_base * height / 3.0 -def vol_sphere(radius): +def vol_sphere(radius: float) -> float: """ Calculate the Volume of a Sphere. - - V = (4/3) * pi * r^3 Wikipedia reference: https://en.wikipedia.org/wiki/Sphere + :return (4/3) * pi * r^3 + + >>> vol_sphere(5) + 523.5987755982989 + >>> vol_sphere(1) + 4.1887902047863905 """ - return (float(4) / 3) * pi * radius ** 3 + return 4 / 3 * pi * pow(radius, 3) -def vol_circular_cylinder(radius, height): +def vol_circular_cylinder(radius: float, height: float) -> float: """Calculate the Volume of a Circular Cylinder. - Wikipedia reference: https://en.wikipedia.org/wiki/Cylinder - volume = pi * radius^2 * height + :return pi * radius^2 * height + + >>> vol_circular_cylinder(1, 1) + 3.141592653589793 + >>> vol_circular_cylinder(4, 3) + 150.79644737231007 """ - return pi * radius ** 2 * height + return pi * pow(radius, 2) * height def main(): From b212a59754c8ac6c1293db402c263160a4bc76c0 Mon Sep 17 00:00:00 2001 From: Himanshu Bhatnagar <33115688+Himan10@users.noreply.github.com> Date: Sun, 5 Jan 2020 15:21:03 +0530 Subject: [PATCH 0418/1071] Create deque_doubly.py (#1652) * Create deque_doubly.py Implementing Deque ADT using Doubly Linked List.... * Update deque_doubly.py * Update deque_doubly.py Adding doctest * Update doctest of deque_doubly.py * Update deque_doubly.py * linked_list. Co-authored-by: Christian Clauss --- data_structures/linked_list/deque_doubly.py | 138 ++++++++++++++++++++ 1 file changed, 138 insertions(+) create mode 100644 data_structures/linked_list/deque_doubly.py diff --git a/data_structures/linked_list/deque_doubly.py b/data_structures/linked_list/deque_doubly.py new file mode 100644 index 000000000000..d858333220e9 --- /dev/null +++ b/data_structures/linked_list/deque_doubly.py @@ -0,0 +1,138 @@ +""" +Implementing Deque using DoublyLinkedList ... +Operations: + 1. insertion in the front -> O(1) + 2. insertion in the end -> O(1) + 3. remove fron the front -> O(1) + 4. remove from the end -> O(1) +""" + +class _DoublyLinkedBase: + """ A Private class (to be inherited) """ + class _Node: + __slots__ = '_prev', '_data', '_next' + def __init__(self, link_p, element, link_n): + self._prev = link_p + self._data = element + self._next = link_n + + def has_next_and_prev(self): + return " Prev -> {0}, Next -> {1}".format(self._prev != None, self._next != None) + + def __init__(self): + self._header = self._Node(None, None, None) + self._trailer = self._Node(None, None, None) + self._header._next = self._trailer + self._trailer._prev = self._header + self._size = 0 + + def __len__(self): + return self._size + + def is_empty(self): + return self.__len__() == 0 + + def _insert(self, predecessor, e, successor): + # Create new_node by setting it's prev.link -> header + # setting it's next.link -> trailer + new_node = self._Node(predecessor, e, successor) + predecessor._next = new_node + successor._prev = new_node + self._size += 1 + return self + + def _delete(self, node): + predecessor = node._prev + successor = node._next + + predecessor._next = successor + successor._prev = predecessor + self._size -= 1 + temp = node._data + node._prev = node._next = node._data = None + del node + return temp + +class LinkedDeque(_DoublyLinkedBase): + + def first(self): + """ return first element + >>> d = LinkedDeque() + >>> d.add_first('A').first() + 'A' + >>> d.add_first('B').first() + 'B' + """ + if self.is_empty(): + raise Exception('List is empty') + return self._header._next._data + + def last(self): + """ return last element + >>> d = LinkedDeque() + >>> d.add_last('A').last() + 'A' + >>> d.add_last('B').last() + 'B' + """ + if self.is_empty(): + raise Exception('List is empty') + return self._trailer._prev._data + + ### DEque Insert Operations (At the front, At the end) ### + + def add_first(self, element): + """ insertion in the front + >>> LinkedDeque().add_first('AV').first() + 'AV' + """ + return self._insert(self._header, element, self._header._next) + + def add_last(self, element): + """ insertion in the end + >>> LinkedDeque().add_last('B').last() + 'B' + """ + return self._insert(self._trailer._prev, element, self._trailer) + + ### DEqueu Remove Operations (At the front, At the end) ### + + def remove_first(self): + """ removal from the front + >>> d = LinkedDeque() + >>> d.is_empty() + True + >>> d.remove_first() + Traceback (most recent call last): + ... + IndexError: remove_first from empty list + >>> d.add_first('A') # doctest: +ELLIPSIS + >> d.remove_first() + 'A' + >>> d.is_empty() + True + """ + if self.is_empty(): + raise IndexError('remove_first from empty list') + return self._delete(self._header._next) + + def remove_last(self): + """ removal in the end + >>> d = LinkedDeque() + >>> d.is_empty() + True + >>> d.remove_last() + Traceback (most recent call last): + ... + IndexError: remove_first from empty list + >>> d.add_first('A') # doctest: +ELLIPSIS + >> d.remove_last() + 'A' + >>> d.is_empty() + True + """ + if self.is_empty(): + raise IndexError('remove_first from empty list') + return self._delete(self._trailer._prev) From 51b769095f9b5671147b71c412024009eba03220 Mon Sep 17 00:00:00 2001 From: nishithshowri006 <58651995+nishithshowri006@users.noreply.github.com> Date: Mon, 6 Jan 2020 02:58:36 +0530 Subject: [PATCH 0419/1071] Create get_imdb_top_250_movies_csv.py (#1659) * Create get_imdb_top_250_movies_csv.py * Update get_imdb_top_250_movies_csv.py * Update get_imdb_top_250_movies_csv.py * get_imdb_top_250_movies() Co-authored-by: Christian Clauss --- .../get_imdb_top_250_movies_csv.py | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 web_programming/get_imdb_top_250_movies_csv.py diff --git a/web_programming/get_imdb_top_250_movies_csv.py b/web_programming/get_imdb_top_250_movies_csv.py new file mode 100644 index 000000000000..811c21fb00e4 --- /dev/null +++ b/web_programming/get_imdb_top_250_movies_csv.py @@ -0,0 +1,29 @@ +import csv +from typing import Dict + +import requests +from bs4 import BeautifulSoup + + +def get_imdb_top_250_movies(url: str = "") -> Dict[str, float]: + url = url or "https://www.imdb.com/chart/top/?ref_=nv_mv_250" + soup = BeautifulSoup(requests.get(url).text, "html.parser") + titles = soup.find_all("td", attrs="titleColumn") + ratings = soup.find_all("td", class_="ratingColumn imdbRating") + return { + title.a.text: float(rating.strong.text) + for title, rating in zip(titles, ratings) + } + + +def write_movies(filename: str = "IMDb_Top_250_Movies.csv") -> None: + movies = get_imdb_top_250_movies() + with open(filename, "w", newline="") as out_file: + writer = csv.writer(out_file) + writer.writerow(["Movie title", "IMDb rating"]) + for title, rating in movies.items(): + writer.writerow([title, rating]) + + +if __name__ == "__main__": + write_movies() From 46df735cf48103ae0a400604762bf10208d5a6dc Mon Sep 17 00:00:00 2001 From: MadhavCode Date: Tue, 7 Jan 2020 14:47:35 +0530 Subject: [PATCH 0420/1071] New Code!!(Finding the N Possible Binary Search Tree and Binary Tree from Given N node Number) (#1663) * Code Upload * Code Upload * Delete n_possible_bst * Find the N Possible Binary Tree and Binary Tree from given Nth Number of Node. * Update in Test * Update and rename n_possible_bst.py to number_of_possible_binary_trees.py Co-authored-by: Christian Clauss --- .../number_of_possible_binary_trees.py | 102 ++++++++++++++++++ 1 file changed, 102 insertions(+) create mode 100644 data_structures/binary_tree/number_of_possible_binary_trees.py diff --git a/data_structures/binary_tree/number_of_possible_binary_trees.py b/data_structures/binary_tree/number_of_possible_binary_trees.py new file mode 100644 index 000000000000..71670d9691ac --- /dev/null +++ b/data_structures/binary_tree/number_of_possible_binary_trees.py @@ -0,0 +1,102 @@ +""" +Hey, we are going to find an exciting number called Catalan number which is use to find +the number of possible binary search trees from tree of a given number of nodes. + +We will use the formula: t(n) = SUMMATION(i = 1 to n)t(i-1)t(n-i) + +Further details at Wikipedia: https://en.wikipedia.org/wiki/Catalan_number +""" +""" +Our Contribution: +Basically we Create the 2 function: + 1. catalan_number(node_count: int) -> int + Returns the number of possible binary search trees for n nodes. + 2. binary_tree_count(node_count: int) -> int + Returns the number of possible binary trees for n nodes. +""" + + +def binomial_coefficient(n: int, k: int) -> int: + """ + Since Here we Find the Binomial Coefficient: + https://en.wikipedia.org/wiki/Binomial_coefficient + C(n,k) = n! / k!(n-k)! + :param n: 2 times of Number of nodes + :param k: Number of nodes + :return: Integer Value + + >>> binomial_coefficient(4, 2) + 6 + """ + result = 1 # To kept the Calculated Value + # Since C(n, k) = C(n, n-k) + if k > (n - k): + k = n - k + # Calculate C(n,k) + for i in range(k): + result *= n - i + result //= i + 1 + return result + + +def catalan_number(node_count: int) -> int: + """ + We can find Catalan number many ways but here we use Binomial Coefficent because it + does the job in O(n) + + return the Catalan number of n using 2nCn/(n+1). + :param n: number of nodes + :return: Catalan number of n nodes + + >>> catalan_number(5) + 42 + >>> catalan_number(6) + 132 + """ + return binomial_coefficient(2 * node_count, node_count) // (node_count + 1) + + +def factorial(n: int) -> int: + """ + Return the factorial of a number. + :param n: Number to find the Factorial of. + :return: Factorial of n. + + >>> import math + >>> all(factorial(i) == math.factorial(i) for i in range(10)) + True + >>> factorial(-5) # doctest: +ELLIPSIS + Traceback (most recent call last): + ... + ValueError: factorial() not defined for negative values + """ + if n < 0: + raise ValueError("factorial() not defined for negative values") + result = 1 + for i in range(1, n + 1): + result *= i + return result + + +def binary_tree_count(node_count: int) -> int: + """ + Return the number of possible of binary trees. + :param n: number of nodes + :return: Number of possilble binary trees + + >>> binary_tree_count(5) + 5040 + >>> binary_tree_count(6) + 95040 + """ + return catalan_number(node_count) * factorial(node_count) + + +if __name__ == "__main__": + node_count = int(input("Enter the number of nodes: ").strip() or 0) + if node_count <= 0: + raise ValueError("We need some nodes to work with.") + print( + f"Given {node_count} nodes, there are {binary_tree_count(node_count)} " + f"binary trees and {catalan_number(node_count)} binary search trees." + ) From cbab5f18f1d5f5434dc241c609085e6f1c0e469f Mon Sep 17 00:00:00 2001 From: Faraz Ahmed Khan <31242842+fk03983@users.noreply.github.com> Date: Tue, 7 Jan 2020 23:00:55 -0600 Subject: [PATCH 0421/1071] added hill climbing algorithm (#1666) * added hill climbing algorithm * Shorten long lines, streamline get_neighbors() * Update hill_climbing.py * Update and rename optimization/hill_climbing.py to searches/hill_climbing.py Co-authored-by: Christian Clauss --- optimization/requirements.txt | 1 + searches/hill_climbing.py | 187 ++++++++++++++++++++++++++++++++++ 2 files changed, 188 insertions(+) create mode 100644 optimization/requirements.txt create mode 100644 searches/hill_climbing.py diff --git a/optimization/requirements.txt b/optimization/requirements.txt new file mode 100644 index 000000000000..4b43f7e68658 --- /dev/null +++ b/optimization/requirements.txt @@ -0,0 +1 @@ +matplotlib \ No newline at end of file diff --git a/searches/hill_climbing.py b/searches/hill_climbing.py new file mode 100644 index 000000000000..7c4ba3fb84ab --- /dev/null +++ b/searches/hill_climbing.py @@ -0,0 +1,187 @@ +# https://en.wikipedia.org/wiki/Hill_climbing +import math + + +class SearchProblem: + """ + A interface to define search problems. The interface will be illustrated using + the example of mathematical function. + """ + + def __init__(self, x: int, y: int, step_size: int, function_to_optimize): + """ + The constructor of the search problem. + x: the x coordinate of the current search state. + y: the y coordinate of the current search state. + step_size: size of the step to take when looking for neighbors. + function_to_optimize: a function to optimize having the signature f(x, y). + """ + self.x = x + self.y = y + self.step_size = step_size + self.function = function_to_optimize + + def score(self) -> int: + """ + Returns the output for the function called with current x and y coordinates. + >>> def test_function(x, y): + ... return x + y + >>> SearchProblem(0, 0, 1, test_function).score() # 0 + 0 = 0 + 0 + >>> SearchProblem(5, 7, 1, test_function).score() # 5 + 7 = 12 + 12 + """ + return self.function(self.x, self.y) + + def get_neighbors(self): + """ + Returns a list of coordinates of neighbors adjacent to the current coordinates. + + Neighbors: + | 0 | 1 | 2 | + | 3 | _ | 4 | + | 5 | 6 | 7 | + """ + step_size = self.step_size + return [ + SearchProblem(x, y, step_size, self.function) + for x, y in ( + (self.x - step_size, self.y - step_size), + (self.x - step_size, self.y), + (self.x - step_size, self.y + step_size), + (self.x, self.y - step_size), + (self.x, self.y + step_size), + (self.x + step_size, self.y - step_size), + (self.x + step_size, self.y), + (self.x + step_size, self.y + step_size), + ) + ] + + def __hash__(self): + """ + hash the string represetation of the current search state. + """ + return hash(str(self)) + + def __str__(self): + """ + string representation of the current search state. + >>> str(SearchProblem(0, 0, 1, None)) + 'x: 0 y: 0' + >>> str(SearchProblem(2, 5, 1, None)) + 'x: 2 y: 5' + """ + return f"x: {self.x} y: {self.y}" + + +def hill_climbing( + search_prob, + find_max: bool = True, + max_x: float = math.inf, + min_x: float = -math.inf, + max_y: float = math.inf, + min_y: float = -math.inf, + visualization: bool = False, + max_iter: int = 10000, +) -> SearchProblem: + """ + implementation of the hill climbling algorithm. We start with a given state, find + all its neighbors, move towards the neighbor which provides the maximum (or + minimum) change. We keep doing this untill we are at a state where we do not + have any neighbors which can improve the solution. + Args: + search_prob: The search state at the start. + find_max: If True, the algorithm should find the minimum else the minimum. + max_x, min_x, max_y, min_y: the maximum and minimum bounds of x and y. + visualization: If True, a matplotlib graph is displayed. + max_iter: number of times to run the iteration. + Returns a search state having the maximum (or minimum) score. + """ + current_state = search_prob + scores = [] # list to store the current score at each iteration + iterations = 0 + solution_found = False + visited = set() + while not solution_found and iterations < max_iter: + visited.add(current_state) + iterations += 1 + current_score = current_state.score() + scores.append(current_score) + neighbors = current_state.get_neighbors() + max_change = -math.inf + min_change = math.inf + next_state = None # to hold the next best neighbor + for neighbor in neighbors: + if neighbor in visited: + continue # do not want to visit the same state again + if ( + neighbor.x > max_x + or neighbor.x < min_x + or neighbor.y > max_y + or neighbor.y < min_y + ): + continue # neighbor outside our bounds + change = neighbor.score() - current_score + if find_max: # finding max + # going to direction with greatest ascent + if change > max_change and change > 0: + max_change = change + next_state = neighbor + else: # finding min + # to direction with greatest descent + if change < min_change and change < 0: + min_change = change + next_state = neighbor + if next_state is not None: + # we found at least one neighbor which improved the current state + current_state = next_state + else: + # since we have no neighbor that improves the solution we stop the search + solution_found = True + + if visualization: + import matplotlib.pyplot as plt + + plt.plot(range(iterations), scores) + plt.xlabel("Iterations") + plt.ylabel("Function values") + plt.show() + + return current_state + + +if __name__ == "__main__": + import doctest + + doctest.testmod() + + def test_f1(x, y): + return (x ** 2) + (y ** 2) + + # starting the problem with initial coordinates (3, 4) + prob = SearchProblem(x=3, y=4, step_size=1, function_to_optimize=test_f1) + local_min = hill_climbing(prob, find_max=False) + print( + "The minimum score for f(x, y) = x^2 + y^2 found via hill climbing: " + f"{local_min.score()}" + ) + + # starting the problem with initial coordinates (12, 47) + prob = SearchProblem(x=12, y=47, step_size=1, function_to_optimize=test_f1) + local_min = hill_climbing( + prob, find_max=False, max_x=100, min_x=5, max_y=50, min_y=-5, visualization=True + ) + print( + "The minimum score for f(x, y) = x^2 + y^2 with the domain 100 > x > 5 " + f"and 50 > y > - 5 found via hill climbing: {local_min.score()}" + ) + + def test_f2(x, y): + return (3 * x ** 2) - (6 * y) + + prob = SearchProblem(x=3, y=4, step_size=1, function_to_optimize=test_f1) + local_min = hill_climbing(prob, find_max=True) + print( + "The maximum score for f(x, y) = x^2 + y^2 found via hill climbing: " + f"{local_min.score()}" + ) From 36d229f82a7b966419da7ea09f0077137d8714c1 Mon Sep 17 00:00:00 2001 From: MadhavCode Date: Wed, 8 Jan 2020 17:55:50 +0530 Subject: [PATCH 0422/1071] number of Possible Binary Search Tree and Binary Tree. (#1670) * Code Upload * Code Upload * Delete n_possible_bst * Find the N Possible Binary Tree and Binary Tree from given Nth Number of Node. * Update in Test * Update and rename n_possible_bst.py to number_of_possible_binary_trees.py Co-authored-by: Christian Clauss From 1f2b1a88ab726ea6ff7fbe382b9fea58c96deaed Mon Sep 17 00:00:00 2001 From: Christian Clauss Date: Wed, 8 Jan 2020 14:06:53 +0100 Subject: [PATCH 0423/1071] Typos in comments in hill_climbing.py (#1667) * Typos in comments in hill_climbing.py * fixup! Format Python code with psf/black push --- data_structures/linked_list/deque_doubly.py | 49 ++++++++++++--------- searches/hill_climbing.py | 4 +- sorts/recursive_bubble_sort.py | 23 +++++----- 3 files changed, 41 insertions(+), 35 deletions(-) diff --git a/data_structures/linked_list/deque_doubly.py b/data_structures/linked_list/deque_doubly.py index d858333220e9..abc7bc1f769f 100644 --- a/data_structures/linked_list/deque_doubly.py +++ b/data_structures/linked_list/deque_doubly.py @@ -7,31 +7,36 @@ 4. remove from the end -> O(1) """ + class _DoublyLinkedBase: """ A Private class (to be inherited) """ + class _Node: - __slots__ = '_prev', '_data', '_next' + __slots__ = "_prev", "_data", "_next" + def __init__(self, link_p, element, link_n): self._prev = link_p self._data = element self._next = link_n - + def has_next_and_prev(self): - return " Prev -> {0}, Next -> {1}".format(self._prev != None, self._next != None) - + return " Prev -> {0}, Next -> {1}".format( + self._prev != None, self._next != None + ) + def __init__(self): self._header = self._Node(None, None, None) self._trailer = self._Node(None, None, None) self._header._next = self._trailer self._trailer._prev = self._header self._size = 0 - + def __len__(self): return self._size - + def is_empty(self): return self.__len__() == 0 - + def _insert(self, predecessor, e, successor): # Create new_node by setting it's prev.link -> header # setting it's next.link -> trailer @@ -40,11 +45,11 @@ def _insert(self, predecessor, e, successor): successor._prev = new_node self._size += 1 return self - + def _delete(self, node): predecessor = node._prev successor = node._next - + predecessor._next = successor successor._prev = predecessor self._size -= 1 @@ -53,8 +58,8 @@ def _delete(self, node): del node return temp + class LinkedDeque(_DoublyLinkedBase): - def first(self): """ return first element >>> d = LinkedDeque() @@ -62,11 +67,11 @@ def first(self): 'A' >>> d.add_first('B').first() 'B' - """ + """ if self.is_empty(): - raise Exception('List is empty') + raise Exception("List is empty") return self._header._next._data - + def last(self): """ return last element >>> d = LinkedDeque() @@ -76,27 +81,27 @@ def last(self): 'B' """ if self.is_empty(): - raise Exception('List is empty') + raise Exception("List is empty") return self._trailer._prev._data - + ### DEque Insert Operations (At the front, At the end) ### - + def add_first(self, element): """ insertion in the front >>> LinkedDeque().add_first('AV').first() 'AV' """ return self._insert(self._header, element, self._header._next) - + def add_last(self, element): """ insertion in the end >>> LinkedDeque().add_last('B').last() 'B' """ return self._insert(self._trailer._prev, element, self._trailer) - + ### DEqueu Remove Operations (At the front, At the end) ### - + def remove_first(self): """ removal from the front >>> d = LinkedDeque() @@ -114,9 +119,9 @@ def remove_first(self): True """ if self.is_empty(): - raise IndexError('remove_first from empty list') + raise IndexError("remove_first from empty list") return self._delete(self._header._next) - + def remove_last(self): """ removal in the end >>> d = LinkedDeque() @@ -134,5 +139,5 @@ def remove_last(self): True """ if self.is_empty(): - raise IndexError('remove_first from empty list') + raise IndexError("remove_first from empty list") return self._delete(self._trailer._prev) diff --git a/searches/hill_climbing.py b/searches/hill_climbing.py index 7c4ba3fb84ab..8cabbd602bd1 100644 --- a/searches/hill_climbing.py +++ b/searches/hill_climbing.py @@ -23,7 +23,7 @@ def __init__(self, x: int, y: int, step_size: int, function_to_optimize): def score(self) -> int: """ - Returns the output for the function called with current x and y coordinates. + Returns the output of the function called with current x and y coordinates. >>> def test_function(x, y): ... return x + y >>> SearchProblem(0, 0, 1, test_function).score() # 0 + 0 = 0 @@ -91,7 +91,7 @@ def hill_climbing( have any neighbors which can improve the solution. Args: search_prob: The search state at the start. - find_max: If True, the algorithm should find the minimum else the minimum. + find_max: If True, the algorithm should find the maximum else the minimum. max_x, min_x, max_y, min_y: the maximum and minimum bounds of x and y. visualization: If True, a matplotlib graph is displayed. max_iter: number of times to run the iteration. diff --git a/sorts/recursive_bubble_sort.py b/sorts/recursive_bubble_sort.py index 88c13cbb95b5..616044778a4a 100644 --- a/sorts/recursive_bubble_sort.py +++ b/sorts/recursive_bubble_sort.py @@ -24,17 +24,18 @@ def bubble_sort(list1): """ - for i, num in enumerate(list1): - try: - if list1[i+1] < num: - list1[i] = list1[i+1] - list1[i+1] = num - bubble_sort(list1) - except IndexError: + for i, num in enumerate(list1): + try: + if list1[i + 1] < num: + list1[i] = list1[i + 1] + list1[i + 1] = num + bubble_sort(list1) + except IndexError: pass - return list1 + return list1 -if __name__ == "__main__": - list1 = [33,99,22,11,66] - bubble_sort(list1) + +if __name__ == "__main__": + list1 = [33, 99, 22, 11, 66] + bubble_sort(list1) print(list1) From e849578e59a733561537ef785852a1634e68faf2 Mon Sep 17 00:00:00 2001 From: Christian Clauss Date: Wed, 8 Jan 2020 14:15:41 +0100 Subject: [PATCH 0424/1071] Update and rename lca.py to lowest_common_ancestor.py (#1664) * Update and rename lca.py to lowest_common_ancestor.py * fixup! Format Python code with psf/black push --- .../binary_tree/{lca.py => lowest_common_ancestor.py} | 3 +++ 1 file changed, 3 insertions(+) rename data_structures/binary_tree/{lca.py => lowest_common_ancestor.py} (95%) diff --git a/data_structures/binary_tree/lca.py b/data_structures/binary_tree/lowest_common_ancestor.py similarity index 95% rename from data_structures/binary_tree/lca.py rename to data_structures/binary_tree/lowest_common_ancestor.py index c18f1e944bab..2109500a2581 100644 --- a/data_structures/binary_tree/lca.py +++ b/data_structures/binary_tree/lowest_common_ancestor.py @@ -1,3 +1,6 @@ +# https://en.wikipedia.org/wiki/Lowest_common_ancestor +# https://en.wikipedia.org/wiki/Breadth-first_search + import queue From a26ae00b2462490b9deb11f699efc3c886738e51 Mon Sep 17 00:00:00 2001 From: Cole Mollica <30614241+coleman2246@users.noreply.github.com> Date: Wed, 8 Jan 2020 08:18:17 -0500 Subject: [PATCH 0425/1071] Added to maths and strings (#1642) * Added to maths and strings * added changes suggest by cclauss --- maths/combinations.py | 19 ++++++++++++++ maths/gamma.py | 60 +++++++++++++++++++++++++++++++++++++++++++ maths/radians.py | 29 +++++++++++++++++++++ strings/lower.py | 29 +++++++++++++++++++++ strings/split.py | 33 ++++++++++++++++++++++++ strings/upper.py | 26 +++++++++++++++++++ 6 files changed, 196 insertions(+) create mode 100644 maths/combinations.py create mode 100644 maths/gamma.py create mode 100644 maths/radians.py create mode 100644 strings/lower.py create mode 100644 strings/split.py create mode 100644 strings/upper.py diff --git a/maths/combinations.py b/maths/combinations.py new file mode 100644 index 000000000000..fd98992e6c16 --- /dev/null +++ b/maths/combinations.py @@ -0,0 +1,19 @@ +from math import factorial + + +def combinations(n, k): + """ + >>> combinations(10,5) + 252 + >>> combinations(6,3) + 20 + >>> combinations(20,5) + 15504 + """ + return int(factorial(n) / ((factorial(k)) * (factorial(n - k)))) + + +if __name__ == "__main__": + from doctest import testmod + + testmod() diff --git a/maths/gamma.py b/maths/gamma.py new file mode 100644 index 000000000000..ef5e7dae6187 --- /dev/null +++ b/maths/gamma.py @@ -0,0 +1,60 @@ +import math +from scipy.integrate import quad +from numpy import inf + + +def gamma(num: float) -> float: + """ + https://en.wikipedia.org/wiki/Gamma_function + In mathematics, the gamma function is one commonly + used extension of the factorial function to complex numbers. + The gamma function is defined for all complex numbers except the non-positive integers + + + >>> gamma(-1) + Traceback (most recent call last): + ... + ValueError: math domain error + + + + >>> gamma(0) + Traceback (most recent call last): + ... + ValueError: math domain error + + + >>> gamma(9) + 40320.0 + + >>> from math import gamma as math_gamma + >>> all(gamma(i)/math_gamma(i) <= 1.000000001 and abs(gamma(i)/math_gamma(i)) > .99999999 for i in range(1, 50)) + True + + + >>> from math import gamma as math_gamma + >>> gamma(-1)/math_gamma(-1) <= 1.000000001 + Traceback (most recent call last): + ... + ValueError: math domain error + + + >>> from math import gamma as math_gamma + >>> gamma(3.3) - math_gamma(3.3) <= 0.00000001 + True + """ + + if num <= 0: + raise ValueError("math domain error") + + return quad(integrand, 0, inf, args=(num))[0] + + +def integrand(x: float, z: float) -> float: + return math.pow(x, z - 1) * math.exp(-x) + + +if __name__ == "__main__": + from doctest import testmod + + testmod() diff --git a/maths/radians.py b/maths/radians.py new file mode 100644 index 000000000000..3788b3e8a3a0 --- /dev/null +++ b/maths/radians.py @@ -0,0 +1,29 @@ +from math import pi + + +def radians(degree: float) -> float: + """ + Coverts the given angle from degrees to radians + https://en.wikipedia.org/wiki/Radian + + >>> radians(180) + 3.141592653589793 + >>> radians(92) + 1.6057029118347832 + >>> radians(274) + 4.782202150464463 + >>> radians(109.82) + 1.9167205845401725 + + >>> from math import radians as math_radians + >>> all(abs(radians(i)-math_radians(i)) <= 0.00000001 for i in range(-2, 361)) + True + """ + + return degree / (180 / pi) + + +if __name__ == "__main__": + from doctest import testmod + + testmod() diff --git a/strings/lower.py b/strings/lower.py new file mode 100644 index 000000000000..c3a6e598b9ea --- /dev/null +++ b/strings/lower.py @@ -0,0 +1,29 @@ +def lower(word: str) -> str: + + """ + Will convert the entire string to lowecase letters + + >>> lower("wow") + 'wow' + >>> lower("HellZo") + 'hellzo' + >>> lower("WHAT") + 'what' + + >>> lower("wh[]32") + 'wh[]32' + >>> lower("whAT") + 'what' + """ + + # converting to ascii value int value and checking to see if char is a capital letter + # if it is a capital letter it is getting shift by 32 which makes it a lower case letter + return "".join( + chr(ord(char) + 32) if 65 <= ord(char) <= 90 else char for char in word + ) + + +if __name__ == "__main__": + from doctest import testmod + + testmod() diff --git a/strings/split.py b/strings/split.py new file mode 100644 index 000000000000..727250fe6e9f --- /dev/null +++ b/strings/split.py @@ -0,0 +1,33 @@ +def split(string: str, seperator: str = " ") -> list: + """ + Will split the string up into all the values seperated by the seperator (defaults to spaces) + + >>> split("apple#banana#cherry#orange",seperator='#') + ['apple', 'banana', 'cherry', 'orange'] + + >>> split("Hello there") + ['Hello', 'there'] + + >>> split("11/22/63",seperator = '/') + ['11', '22', '63'] + + >>> split("12:43:39",seperator = ":") + ['12', '43', '39'] + """ + + split_words = [] + + last_index = 0 + for index, char in enumerate(string): + if char == seperator: + split_words.append(string[last_index:index]) + last_index = index + 1 + elif index + 1 == len(string): + split_words.append(string[last_index : index + 1]) + return split_words + + +if __name__ == "__main__": + from doctest import testmod + + testmod() diff --git a/strings/upper.py b/strings/upper.py new file mode 100644 index 000000000000..59b16096af0b --- /dev/null +++ b/strings/upper.py @@ -0,0 +1,26 @@ +def upper(word: str) -> str: + """ + Will convert the entire string to uppercase letters + + >>> upper("wow") + 'WOW' + >>> upper("Hello") + 'HELLO' + >>> upper("WHAT") + 'WHAT' + + >>> upper("wh[]32") + 'WH[]32' + """ + + # converting to ascii value int value and checking to see if char is a lower letter + # if it is a capital letter it is getting shift by 32 which makes it a capital case letter + return "".join( + chr(ord(char) - 32) if 97 <= ord(char) <= 122 else char for char in word + ) + + +if __name__ == "__main__": + from doctest import testmod + + testmod() From f9e1a16a98817b3076bdb31635a56732816cd18e Mon Sep 17 00:00:00 2001 From: Christian Clauss Date: Fri, 10 Jan 2020 02:08:49 +0100 Subject: [PATCH 0426/1071] git add DIRECTORY.md (#1674) * git add DIRECTORY.md * updating DIRECTORY.md --- .github/workflows/directory_writer.yml | 1 + DIRECTORY.md | 575 +++++++++++++++++++++++++ 2 files changed, 576 insertions(+) create mode 100644 DIRECTORY.md diff --git a/.github/workflows/directory_writer.yml b/.github/workflows/directory_writer.yml index 4a8ed6c9e509..f910ab33e00b 100644 --- a/.github/workflows/directory_writer.yml +++ b/.github/workflows/directory_writer.yml @@ -16,5 +16,6 @@ jobs: git config --global user.name github-actions git config --global user.email '${GITHUB_ACTOR}@users.noreply.github.com' git remote set-url origin https://x-access-token:${{ secrets.GITHUB_TOKEN }}@github.com/$GITHUB_REPOSITORY + git add DIRECTORY.md git commit -am "updating DIRECTORY.md" || true git push --force origin HEAD:$GITHUB_REF || true diff --git a/DIRECTORY.md b/DIRECTORY.md new file mode 100644 index 000000000000..73ecbc36fdc4 --- /dev/null +++ b/DIRECTORY.md @@ -0,0 +1,575 @@ + +## Arithmetic Analysis + * [Bisection](https://github.com/TheAlgorithms/Python/blob/master/arithmetic_analysis/bisection.py) + * [Gaussian Elimination](https://github.com/TheAlgorithms/Python/blob/master/arithmetic_analysis/gaussian_elimination.py) + * [In Static Equilibrium](https://github.com/TheAlgorithms/Python/blob/master/arithmetic_analysis/in_static_equilibrium.py) + * [Intersection](https://github.com/TheAlgorithms/Python/blob/master/arithmetic_analysis/intersection.py) + * [Lu Decomposition](https://github.com/TheAlgorithms/Python/blob/master/arithmetic_analysis/lu_decomposition.py) + * [Newton Forward Interpolation](https://github.com/TheAlgorithms/Python/blob/master/arithmetic_analysis/newton_forward_interpolation.py) + * [Newton Method](https://github.com/TheAlgorithms/Python/blob/master/arithmetic_analysis/newton_method.py) + * [Newton Raphson](https://github.com/TheAlgorithms/Python/blob/master/arithmetic_analysis/newton_raphson.py) + * [Secant Method](https://github.com/TheAlgorithms/Python/blob/master/arithmetic_analysis/secant_method.py) + +## Backtracking + * [All Combinations](https://github.com/TheAlgorithms/Python/blob/master/backtracking/all_combinations.py) + * [All Permutations](https://github.com/TheAlgorithms/Python/blob/master/backtracking/all_permutations.py) + * [All Subsequences](https://github.com/TheAlgorithms/Python/blob/master/backtracking/all_subsequences.py) + * [Minimax](https://github.com/TheAlgorithms/Python/blob/master/backtracking/minimax.py) + * [N Queens](https://github.com/TheAlgorithms/Python/blob/master/backtracking/n_queens.py) + * [Sudoku](https://github.com/TheAlgorithms/Python/blob/master/backtracking/sudoku.py) + * [Sum Of Subsets](https://github.com/TheAlgorithms/Python/blob/master/backtracking/sum_of_subsets.py) + +## Blockchain + * [Chinese Remainder Theorem](https://github.com/TheAlgorithms/Python/blob/master/blockchain/chinese_remainder_theorem.py) + * [Diophantine Equation](https://github.com/TheAlgorithms/Python/blob/master/blockchain/diophantine_equation.py) + * [Modular Division](https://github.com/TheAlgorithms/Python/blob/master/blockchain/modular_division.py) + +## Boolean Algebra + * [Quine Mc Cluskey](https://github.com/TheAlgorithms/Python/blob/master/boolean_algebra/quine_mc_cluskey.py) + +## Ciphers + * [Affine Cipher](https://github.com/TheAlgorithms/Python/blob/master/ciphers/affine_cipher.py) + * [Atbash](https://github.com/TheAlgorithms/Python/blob/master/ciphers/atbash.py) + * [Base16](https://github.com/TheAlgorithms/Python/blob/master/ciphers/base16.py) + * [Base32](https://github.com/TheAlgorithms/Python/blob/master/ciphers/base32.py) + * [Base64 Cipher](https://github.com/TheAlgorithms/Python/blob/master/ciphers/base64_cipher.py) + * [Base85](https://github.com/TheAlgorithms/Python/blob/master/ciphers/base85.py) + * [Brute Force Caesar Cipher](https://github.com/TheAlgorithms/Python/blob/master/ciphers/brute_force_caesar_cipher.py) + * [Caesar Cipher](https://github.com/TheAlgorithms/Python/blob/master/ciphers/caesar_cipher.py) + * [Cryptomath Module](https://github.com/TheAlgorithms/Python/blob/master/ciphers/cryptomath_module.py) + * [Deterministic Miller Rabin](https://github.com/TheAlgorithms/Python/blob/master/ciphers/deterministic_miller_rabin.py) + * [Diffie](https://github.com/TheAlgorithms/Python/blob/master/ciphers/diffie.py) + * [Elgamal Key Generator](https://github.com/TheAlgorithms/Python/blob/master/ciphers/elgamal_key_generator.py) + * [Hill Cipher](https://github.com/TheAlgorithms/Python/blob/master/ciphers/hill_cipher.py) + * [Mixed Keyword Cypher](https://github.com/TheAlgorithms/Python/blob/master/ciphers/mixed_keyword_cypher.py) + * [Morse Code Implementation](https://github.com/TheAlgorithms/Python/blob/master/ciphers/morse_code_implementation.py) + * [Onepad Cipher](https://github.com/TheAlgorithms/Python/blob/master/ciphers/onepad_cipher.py) + * [Playfair Cipher](https://github.com/TheAlgorithms/Python/blob/master/ciphers/playfair_cipher.py) + * [Porta Cipher](https://github.com/TheAlgorithms/Python/blob/master/ciphers/porta_cipher.py) + * [Rabin Miller](https://github.com/TheAlgorithms/Python/blob/master/ciphers/rabin_miller.py) + * [Rot13](https://github.com/TheAlgorithms/Python/blob/master/ciphers/rot13.py) + * [Rsa Cipher](https://github.com/TheAlgorithms/Python/blob/master/ciphers/rsa_cipher.py) + * [Rsa Factorization](https://github.com/TheAlgorithms/Python/blob/master/ciphers/rsa_factorization.py) + * [Rsa Key Generator](https://github.com/TheAlgorithms/Python/blob/master/ciphers/rsa_key_generator.py) + * [Shuffled Shift Cipher](https://github.com/TheAlgorithms/Python/blob/master/ciphers/shuffled_shift_cipher.py) + * [Simple Keyword Cypher](https://github.com/TheAlgorithms/Python/blob/master/ciphers/simple_keyword_cypher.py) + * [Simple Substitution Cipher](https://github.com/TheAlgorithms/Python/blob/master/ciphers/simple_substitution_cipher.py) + * [Trafid Cipher](https://github.com/TheAlgorithms/Python/blob/master/ciphers/trafid_cipher.py) + * [Transposition Cipher](https://github.com/TheAlgorithms/Python/blob/master/ciphers/transposition_cipher.py) + * [Transposition Cipher Encrypt Decrypt File](https://github.com/TheAlgorithms/Python/blob/master/ciphers/transposition_cipher_encrypt_decrypt_file.py) + * [Vigenere Cipher](https://github.com/TheAlgorithms/Python/blob/master/ciphers/vigenere_cipher.py) + * [Xor Cipher](https://github.com/TheAlgorithms/Python/blob/master/ciphers/xor_cipher.py) + +## Compression + * [Burrows Wheeler](https://github.com/TheAlgorithms/Python/blob/master/compression/burrows_wheeler.py) + * [Huffman](https://github.com/TheAlgorithms/Python/blob/master/compression/huffman.py) + * [Peak Signal To Noise Ratio](https://github.com/TheAlgorithms/Python/blob/master/compression/peak_signal_to_noise_ratio.py) + +## Conversions + * [Decimal To Binary](https://github.com/TheAlgorithms/Python/blob/master/conversions/decimal_to_binary.py) + * [Decimal To Hexadecimal](https://github.com/TheAlgorithms/Python/blob/master/conversions/decimal_to_hexadecimal.py) + * [Decimal To Octal](https://github.com/TheAlgorithms/Python/blob/master/conversions/decimal_to_octal.py) + * [Roman To Integer](https://github.com/TheAlgorithms/Python/blob/master/conversions/roman_to_integer.py) + +## Data Structures + * Binary Tree + * [Avl Tree](https://github.com/TheAlgorithms/Python/blob/master/data_structures/binary_tree/avl_tree.py) + * [Basic Binary Tree](https://github.com/TheAlgorithms/Python/blob/master/data_structures/binary_tree/basic_binary_tree.py) + * [Binary Search Tree](https://github.com/TheAlgorithms/Python/blob/master/data_structures/binary_tree/binary_search_tree.py) + * [Fenwick Tree](https://github.com/TheAlgorithms/Python/blob/master/data_structures/binary_tree/fenwick_tree.py) + * [Lazy Segment Tree](https://github.com/TheAlgorithms/Python/blob/master/data_structures/binary_tree/lazy_segment_tree.py) + * [Lowest Common Ancestor](https://github.com/TheAlgorithms/Python/blob/master/data_structures/binary_tree/lowest_common_ancestor.py) + * [Non Recursive Segment Tree](https://github.com/TheAlgorithms/Python/blob/master/data_structures/binary_tree/non_recursive_segment_tree.py) + * [Number Of Possible Binary Trees](https://github.com/TheAlgorithms/Python/blob/master/data_structures/binary_tree/number_of_possible_binary_trees.py) + * [Red Black Tree](https://github.com/TheAlgorithms/Python/blob/master/data_structures/binary_tree/red_black_tree.py) + * [Segment Tree](https://github.com/TheAlgorithms/Python/blob/master/data_structures/binary_tree/segment_tree.py) + * [Treap](https://github.com/TheAlgorithms/Python/blob/master/data_structures/binary_tree/treap.py) + * Data Structures + * Heap + * [Heap Generic](https://github.com/TheAlgorithms/Python/blob/master/data_structures/data_structures/heap/heap_generic.py) + * Disjoint Set + * [Disjoint Set](https://github.com/TheAlgorithms/Python/blob/master/data_structures/disjoint_set/disjoint_set.py) + * Hashing + * [Double Hash](https://github.com/TheAlgorithms/Python/blob/master/data_structures/hashing/double_hash.py) + * [Hash Table](https://github.com/TheAlgorithms/Python/blob/master/data_structures/hashing/hash_table.py) + * [Hash Table With Linked List](https://github.com/TheAlgorithms/Python/blob/master/data_structures/hashing/hash_table_with_linked_list.py) + * Number Theory + * [Prime Numbers](https://github.com/TheAlgorithms/Python/blob/master/data_structures/hashing/number_theory/prime_numbers.py) + * [Quadratic Probing](https://github.com/TheAlgorithms/Python/blob/master/data_structures/hashing/quadratic_probing.py) + * Heap + * [Binomial Heap](https://github.com/TheAlgorithms/Python/blob/master/data_structures/heap/binomial_heap.py) + * [Heap](https://github.com/TheAlgorithms/Python/blob/master/data_structures/heap/heap.py) + * [Min Heap](https://github.com/TheAlgorithms/Python/blob/master/data_structures/heap/min_heap.py) + * Linked List + * [Circular Linked List](https://github.com/TheAlgorithms/Python/blob/master/data_structures/linked_list/circular_linked_list.py) + * [Deque Doubly](https://github.com/TheAlgorithms/Python/blob/master/data_structures/linked_list/deque_doubly.py) + * [Doubly Linked List](https://github.com/TheAlgorithms/Python/blob/master/data_structures/linked_list/doubly_linked_list.py) + * [From Sequence](https://github.com/TheAlgorithms/Python/blob/master/data_structures/linked_list/from_sequence.py) + * [Is Palindrome](https://github.com/TheAlgorithms/Python/blob/master/data_structures/linked_list/is_palindrome.py) + * [Print Reverse](https://github.com/TheAlgorithms/Python/blob/master/data_structures/linked_list/print_reverse.py) + * [Singly Linked List](https://github.com/TheAlgorithms/Python/blob/master/data_structures/linked_list/singly_linked_list.py) + * [Swap Nodes](https://github.com/TheAlgorithms/Python/blob/master/data_structures/linked_list/swap_nodes.py) + * Queue + * [Circular Queue](https://github.com/TheAlgorithms/Python/blob/master/data_structures/queue/circular_queue.py) + * [Double Ended Queue](https://github.com/TheAlgorithms/Python/blob/master/data_structures/queue/double_ended_queue.py) + * [Linked Queue](https://github.com/TheAlgorithms/Python/blob/master/data_structures/queue/linked_queue.py) + * [Queue On List](https://github.com/TheAlgorithms/Python/blob/master/data_structures/queue/queue_on_list.py) + * [Queue On Pseudo Stack](https://github.com/TheAlgorithms/Python/blob/master/data_structures/queue/queue_on_pseudo_stack.py) + * Stacks + * [Balanced Parentheses](https://github.com/TheAlgorithms/Python/blob/master/data_structures/stacks/balanced_parentheses.py) + * [Infix To Postfix Conversion](https://github.com/TheAlgorithms/Python/blob/master/data_structures/stacks/infix_to_postfix_conversion.py) + * [Infix To Prefix Conversion](https://github.com/TheAlgorithms/Python/blob/master/data_structures/stacks/infix_to_prefix_conversion.py) + * [Linked Stack](https://github.com/TheAlgorithms/Python/blob/master/data_structures/stacks/linked_stack.py) + * [Next Greater Element](https://github.com/TheAlgorithms/Python/blob/master/data_structures/stacks/next_greater_element.py) + * [Postfix Evaluation](https://github.com/TheAlgorithms/Python/blob/master/data_structures/stacks/postfix_evaluation.py) + * [Stack](https://github.com/TheAlgorithms/Python/blob/master/data_structures/stacks/stack.py) + * [Stack Using Dll](https://github.com/TheAlgorithms/Python/blob/master/data_structures/stacks/stack_using_dll.py) + * [Stock Span Problem](https://github.com/TheAlgorithms/Python/blob/master/data_structures/stacks/stock_span_problem.py) + * Trie + * [Trie](https://github.com/TheAlgorithms/Python/blob/master/data_structures/trie/trie.py) + +## Digital Image Processing + * [Change Contrast](https://github.com/TheAlgorithms/Python/blob/master/digital_image_processing/change_contrast.py) + * [Convert To Negative](https://github.com/TheAlgorithms/Python/blob/master/digital_image_processing/convert_to_negative.py) + * Edge Detection + * [Canny](https://github.com/TheAlgorithms/Python/blob/master/digital_image_processing/edge_detection/canny.py) + * Filters + * [Convolve](https://github.com/TheAlgorithms/Python/blob/master/digital_image_processing/filters/convolve.py) + * [Gaussian Filter](https://github.com/TheAlgorithms/Python/blob/master/digital_image_processing/filters/gaussian_filter.py) + * [Median Filter](https://github.com/TheAlgorithms/Python/blob/master/digital_image_processing/filters/median_filter.py) + * [Sobel Filter](https://github.com/TheAlgorithms/Python/blob/master/digital_image_processing/filters/sobel_filter.py) + * Histogram Equalization + * [Histogram Stretch](https://github.com/TheAlgorithms/Python/blob/master/digital_image_processing/histogram_equalization/histogram_stretch.py) + * [Index Calculation](https://github.com/TheAlgorithms/Python/blob/master/digital_image_processing/index_calculation.py) + * Rotation + * [Rotation](https://github.com/TheAlgorithms/Python/blob/master/digital_image_processing/rotation/rotation.py) + * [Test Digital Image Processing](https://github.com/TheAlgorithms/Python/blob/master/digital_image_processing/test_digital_image_processing.py) + +## Divide And Conquer + * [Closest Pair Of Points](https://github.com/TheAlgorithms/Python/blob/master/divide_and_conquer/closest_pair_of_points.py) + * [Convex Hull](https://github.com/TheAlgorithms/Python/blob/master/divide_and_conquer/convex_hull.py) + * [Inversions](https://github.com/TheAlgorithms/Python/blob/master/divide_and_conquer/inversions.py) + * [Max Subarray Sum](https://github.com/TheAlgorithms/Python/blob/master/divide_and_conquer/max_subarray_sum.py) + * [Mergesort](https://github.com/TheAlgorithms/Python/blob/master/divide_and_conquer/mergesort.py) + +## Dynamic Programming + * [Abbreviation](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/abbreviation.py) + * [Bitmask](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/bitmask.py) + * [Climbing Stairs](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/climbing_stairs.py) + * [Coin Change](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/coin_change.py) + * [Edit Distance](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/edit_distance.py) + * [Factorial](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/factorial.py) + * [Fast Fibonacci](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/fast_fibonacci.py) + * [Fibonacci](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/fibonacci.py) + * [Floyd Warshall](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/floyd_warshall.py) + * [Fractional Knapsack](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/fractional_knapsack.py) + * [Fractional Knapsack 2](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/fractional_knapsack_2.py) + * [Integer Partition](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/integer_partition.py) + * [Iterating Through Submasks](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/iterating_through_submasks.py) + * [K Means Clustering Tensorflow](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/k_means_clustering_tensorflow.py) + * [Knapsack](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/knapsack.py) + * [Longest Common Subsequence](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/longest_common_subsequence.py) + * [Longest Increasing Subsequence](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/longest_increasing_subsequence.py) + * [Longest Increasing Subsequence O(Nlogn)](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/longest_increasing_subsequence_o(nlogn).py) + * [Longest Sub Array](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/longest_sub_array.py) + * [Matrix Chain Order](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/matrix_chain_order.py) + * [Max Sub Array](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/max_sub_array.py) + * [Max Sum Contigous Subsequence](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/max_sum_contigous_subsequence.py) + * [Minimum Partition](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/minimum_partition.py) + * [Rod Cutting](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/rod_cutting.py) + * [Subset Generation](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/subset_generation.py) + * [Sum Of Subset](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/sum_of_subset.py) + +## File Transfer + * [Recieve File](https://github.com/TheAlgorithms/Python/blob/master/file_transfer/recieve_file.py) + * [Send File](https://github.com/TheAlgorithms/Python/blob/master/file_transfer/send_file.py) + +## Fuzzy Logic + * [Fuzzy Operations](https://github.com/TheAlgorithms/Python/blob/master/fuzzy_logic/fuzzy_operations.py) + +## Graphs + * [A Star](https://github.com/TheAlgorithms/Python/blob/master/graphs/a_star.py) + * [Articulation Points](https://github.com/TheAlgorithms/Python/blob/master/graphs/articulation_points.py) + * [Basic Graphs](https://github.com/TheAlgorithms/Python/blob/master/graphs/basic_graphs.py) + * [Bellman Ford](https://github.com/TheAlgorithms/Python/blob/master/graphs/bellman_ford.py) + * [Bfs](https://github.com/TheAlgorithms/Python/blob/master/graphs/bfs.py) + * [Bfs Shortest Path](https://github.com/TheAlgorithms/Python/blob/master/graphs/bfs_shortest_path.py) + * [Breadth First Search](https://github.com/TheAlgorithms/Python/blob/master/graphs/breadth_first_search.py) + * [Check Bipartite Graph Bfs](https://github.com/TheAlgorithms/Python/blob/master/graphs/check_bipartite_graph_bfs.py) + * [Check Bipartite Graph Dfs](https://github.com/TheAlgorithms/Python/blob/master/graphs/check_bipartite_graph_dfs.py) + * [Depth First Search](https://github.com/TheAlgorithms/Python/blob/master/graphs/depth_first_search.py) + * [Dfs](https://github.com/TheAlgorithms/Python/blob/master/graphs/dfs.py) + * [Dijkstra](https://github.com/TheAlgorithms/Python/blob/master/graphs/dijkstra.py) + * [Dijkstra 2](https://github.com/TheAlgorithms/Python/blob/master/graphs/dijkstra_2.py) + * [Dijkstra Algorithm](https://github.com/TheAlgorithms/Python/blob/master/graphs/dijkstra_algorithm.py) + * [Dinic](https://github.com/TheAlgorithms/Python/blob/master/graphs/dinic.py) + * [Directed And Undirected (Weighted) Graph](https://github.com/TheAlgorithms/Python/blob/master/graphs/directed_and_undirected_(weighted)_graph.py) + * [Edmonds Karp Multiple Source And Sink](https://github.com/TheAlgorithms/Python/blob/master/graphs/edmonds_karp_multiple_source_and_sink.py) + * [Eulerian Path And Circuit For Undirected Graph](https://github.com/TheAlgorithms/Python/blob/master/graphs/eulerian_path_and_circuit_for_undirected_graph.py) + * [Even Tree](https://github.com/TheAlgorithms/Python/blob/master/graphs/even_tree.py) + * [Finding Bridges](https://github.com/TheAlgorithms/Python/blob/master/graphs/finding_bridges.py) + * [G Topological Sort](https://github.com/TheAlgorithms/Python/blob/master/graphs/g_topological_sort.py) + * [Graph List](https://github.com/TheAlgorithms/Python/blob/master/graphs/graph_list.py) + * [Graph Matrix](https://github.com/TheAlgorithms/Python/blob/master/graphs/graph_matrix.py) + * [Graphs Floyd Warshall](https://github.com/TheAlgorithms/Python/blob/master/graphs/graphs_floyd_warshall.py) + * [Kahns Algorithm Long](https://github.com/TheAlgorithms/Python/blob/master/graphs/kahns_algorithm_long.py) + * [Kahns Algorithm Topo](https://github.com/TheAlgorithms/Python/blob/master/graphs/kahns_algorithm_topo.py) + * [Minimum Spanning Tree Kruskal](https://github.com/TheAlgorithms/Python/blob/master/graphs/minimum_spanning_tree_kruskal.py) + * [Minimum Spanning Tree Prims](https://github.com/TheAlgorithms/Python/blob/master/graphs/minimum_spanning_tree_prims.py) + * [Multi Hueristic Astar](https://github.com/TheAlgorithms/Python/blob/master/graphs/multi_hueristic_astar.py) + * [Page Rank](https://github.com/TheAlgorithms/Python/blob/master/graphs/page_rank.py) + * [Prim](https://github.com/TheAlgorithms/Python/blob/master/graphs/prim.py) + * [Scc Kosaraju](https://github.com/TheAlgorithms/Python/blob/master/graphs/scc_kosaraju.py) + * [Tarjans Scc](https://github.com/TheAlgorithms/Python/blob/master/graphs/tarjans_scc.py) + +## Hashes + * [Chaos Machine](https://github.com/TheAlgorithms/Python/blob/master/hashes/chaos_machine.py) + * [Enigma Machine](https://github.com/TheAlgorithms/Python/blob/master/hashes/enigma_machine.py) + * [Hamming Code](https://github.com/TheAlgorithms/Python/blob/master/hashes/hamming_code.py) + * [Md5](https://github.com/TheAlgorithms/Python/blob/master/hashes/md5.py) + * [Sha1](https://github.com/TheAlgorithms/Python/blob/master/hashes/sha1.py) + +## Linear Algebra + * Src + * [Lib](https://github.com/TheAlgorithms/Python/blob/master/linear_algebra/src/lib.py) + * [Polynom-For-Points](https://github.com/TheAlgorithms/Python/blob/master/linear_algebra/src/polynom-for-points.py) + * [Test Linear Algebra](https://github.com/TheAlgorithms/Python/blob/master/linear_algebra/src/test_linear_algebra.py) + +## Machine Learning + * [Decision Tree](https://github.com/TheAlgorithms/Python/blob/master/machine_learning/decision_tree.py) + * [Gradient Descent](https://github.com/TheAlgorithms/Python/blob/master/machine_learning/gradient_descent.py) + * [K Means Clust](https://github.com/TheAlgorithms/Python/blob/master/machine_learning/k_means_clust.py) + * [K Nearest Neighbours](https://github.com/TheAlgorithms/Python/blob/master/machine_learning/k_nearest_neighbours.py) + * [Knn Sklearn](https://github.com/TheAlgorithms/Python/blob/master/machine_learning/knn_sklearn.py) + * [Linear Discriminant Analysis](https://github.com/TheAlgorithms/Python/blob/master/machine_learning/linear_discriminant_analysis.py) + * [Linear Regression](https://github.com/TheAlgorithms/Python/blob/master/machine_learning/linear_regression.py) + * [Logistic Regression](https://github.com/TheAlgorithms/Python/blob/master/machine_learning/logistic_regression.py) + * [Multilayer Perceptron Classifier](https://github.com/TheAlgorithms/Python/blob/master/machine_learning/multilayer_perceptron_classifier.py) + * [Polymonial Regression](https://github.com/TheAlgorithms/Python/blob/master/machine_learning/polymonial_regression.py) + * [Scoring Functions](https://github.com/TheAlgorithms/Python/blob/master/machine_learning/scoring_functions.py) + * [Sequential Minimum Optimization](https://github.com/TheAlgorithms/Python/blob/master/machine_learning/sequential_minimum_optimization.py) + * [Support Vector Machines](https://github.com/TheAlgorithms/Python/blob/master/machine_learning/support_vector_machines.py) + +## Maths + * [3N+1](https://github.com/TheAlgorithms/Python/blob/master/maths/3n+1.py) + * [Abs](https://github.com/TheAlgorithms/Python/blob/master/maths/abs.py) + * [Abs Max](https://github.com/TheAlgorithms/Python/blob/master/maths/abs_max.py) + * [Abs Min](https://github.com/TheAlgorithms/Python/blob/master/maths/abs_min.py) + * [Average Mean](https://github.com/TheAlgorithms/Python/blob/master/maths/average_mean.py) + * [Average Median](https://github.com/TheAlgorithms/Python/blob/master/maths/average_median.py) + * [Average Mode](https://github.com/TheAlgorithms/Python/blob/master/maths/average_mode.py) + * [Basic Maths](https://github.com/TheAlgorithms/Python/blob/master/maths/basic_maths.py) + * [Binary Exp Mod](https://github.com/TheAlgorithms/Python/blob/master/maths/binary_exp_mod.py) + * [Binary Exponentiation](https://github.com/TheAlgorithms/Python/blob/master/maths/binary_exponentiation.py) + * [Binomial Coefficient](https://github.com/TheAlgorithms/Python/blob/master/maths/binomial_coefficient.py) + * [Ceil](https://github.com/TheAlgorithms/Python/blob/master/maths/ceil.py) + * [Collatz Sequence](https://github.com/TheAlgorithms/Python/blob/master/maths/collatz_sequence.py) + * [Combinations](https://github.com/TheAlgorithms/Python/blob/master/maths/combinations.py) + * [Eulers Totient](https://github.com/TheAlgorithms/Python/blob/master/maths/eulers_totient.py) + * [Explicit Euler](https://github.com/TheAlgorithms/Python/blob/master/maths/explicit_euler.py) + * [Extended Euclidean Algorithm](https://github.com/TheAlgorithms/Python/blob/master/maths/extended_euclidean_algorithm.py) + * [Factorial Python](https://github.com/TheAlgorithms/Python/blob/master/maths/factorial_python.py) + * [Factorial Recursive](https://github.com/TheAlgorithms/Python/blob/master/maths/factorial_recursive.py) + * [Factors](https://github.com/TheAlgorithms/Python/blob/master/maths/factors.py) + * [Fermat Little Theorem](https://github.com/TheAlgorithms/Python/blob/master/maths/fermat_little_theorem.py) + * [Fibonacci](https://github.com/TheAlgorithms/Python/blob/master/maths/fibonacci.py) + * [Fibonacci Sequence Recursion](https://github.com/TheAlgorithms/Python/blob/master/maths/fibonacci_sequence_recursion.py) + * [Find Max](https://github.com/TheAlgorithms/Python/blob/master/maths/find_max.py) + * [Find Max Recursion](https://github.com/TheAlgorithms/Python/blob/master/maths/find_max_recursion.py) + * [Find Min](https://github.com/TheAlgorithms/Python/blob/master/maths/find_min.py) + * [Find Min Recursion](https://github.com/TheAlgorithms/Python/blob/master/maths/find_min_recursion.py) + * [Floor](https://github.com/TheAlgorithms/Python/blob/master/maths/floor.py) + * [Gamma](https://github.com/TheAlgorithms/Python/blob/master/maths/gamma.py) + * [Gaussian](https://github.com/TheAlgorithms/Python/blob/master/maths/gaussian.py) + * [Greatest Common Divisor](https://github.com/TheAlgorithms/Python/blob/master/maths/greatest_common_divisor.py) + * [Hardy Ramanujanalgo](https://github.com/TheAlgorithms/Python/blob/master/maths/hardy_ramanujanalgo.py) + * [Is Square Free](https://github.com/TheAlgorithms/Python/blob/master/maths/is_square_free.py) + * [Jaccard Similarity](https://github.com/TheAlgorithms/Python/blob/master/maths/jaccard_similarity.py) + * [Karatsuba](https://github.com/TheAlgorithms/Python/blob/master/maths/karatsuba.py) + * [Kth Lexicographic Permutation](https://github.com/TheAlgorithms/Python/blob/master/maths/kth_lexicographic_permutation.py) + * [Largest Of Very Large Numbers](https://github.com/TheAlgorithms/Python/blob/master/maths/largest_of_very_large_numbers.py) + * [Least Common Multiple](https://github.com/TheAlgorithms/Python/blob/master/maths/least_common_multiple.py) + * [Lucas Lehmer Primality Test](https://github.com/TheAlgorithms/Python/blob/master/maths/lucas_lehmer_primality_test.py) + * [Lucas Series](https://github.com/TheAlgorithms/Python/blob/master/maths/lucas_series.py) + * [Matrix Exponentiation](https://github.com/TheAlgorithms/Python/blob/master/maths/matrix_exponentiation.py) + * [Miller Rabin](https://github.com/TheAlgorithms/Python/blob/master/maths/miller_rabin.py) + * [Mobius Function](https://github.com/TheAlgorithms/Python/blob/master/maths/mobius_function.py) + * [Modular Exponential](https://github.com/TheAlgorithms/Python/blob/master/maths/modular_exponential.py) + * [Newton Raphson](https://github.com/TheAlgorithms/Python/blob/master/maths/newton_raphson.py) + * [Perfect Square](https://github.com/TheAlgorithms/Python/blob/master/maths/perfect_square.py) + * [Polynomial Evaluation](https://github.com/TheAlgorithms/Python/blob/master/maths/polynomial_evaluation.py) + * [Prime Check](https://github.com/TheAlgorithms/Python/blob/master/maths/prime_check.py) + * [Prime Factors](https://github.com/TheAlgorithms/Python/blob/master/maths/prime_factors.py) + * [Prime Numbers](https://github.com/TheAlgorithms/Python/blob/master/maths/prime_numbers.py) + * [Prime Sieve Eratosthenes](https://github.com/TheAlgorithms/Python/blob/master/maths/prime_sieve_eratosthenes.py) + * [Pythagoras](https://github.com/TheAlgorithms/Python/blob/master/maths/pythagoras.py) + * [Qr Decomposition](https://github.com/TheAlgorithms/Python/blob/master/maths/qr_decomposition.py) + * [Quadratic Equations Complex Numbers](https://github.com/TheAlgorithms/Python/blob/master/maths/quadratic_equations_complex_numbers.py) + * [Radians](https://github.com/TheAlgorithms/Python/blob/master/maths/radians.py) + * [Radix2 Fft](https://github.com/TheAlgorithms/Python/blob/master/maths/radix2_fft.py) + * [Runge Kutta](https://github.com/TheAlgorithms/Python/blob/master/maths/runge_kutta.py) + * [Segmented Sieve](https://github.com/TheAlgorithms/Python/blob/master/maths/segmented_sieve.py) + * Series + * [Geometric Series](https://github.com/TheAlgorithms/Python/blob/master/maths/series/geometric_series.py) + * [Harmonic Series](https://github.com/TheAlgorithms/Python/blob/master/maths/series/harmonic_series.py) + * [P Series](https://github.com/TheAlgorithms/Python/blob/master/maths/series/p_series.py) + * [Sieve Of Eratosthenes](https://github.com/TheAlgorithms/Python/blob/master/maths/sieve_of_eratosthenes.py) + * [Simpson Rule](https://github.com/TheAlgorithms/Python/blob/master/maths/simpson_rule.py) + * [Softmax](https://github.com/TheAlgorithms/Python/blob/master/maths/softmax.py) + * [Sum Of Arithmetic Series](https://github.com/TheAlgorithms/Python/blob/master/maths/sum_of_arithmetic_series.py) + * [Test Prime Check](https://github.com/TheAlgorithms/Python/blob/master/maths/test_prime_check.py) + * [Trapezoidal Rule](https://github.com/TheAlgorithms/Python/blob/master/maths/trapezoidal_rule.py) + * [Volume](https://github.com/TheAlgorithms/Python/blob/master/maths/volume.py) + * [Zellers Congruence](https://github.com/TheAlgorithms/Python/blob/master/maths/zellers_congruence.py) + +## Matrix + * [Matrix Class](https://github.com/TheAlgorithms/Python/blob/master/matrix/matrix_class.py) + * [Matrix Operation](https://github.com/TheAlgorithms/Python/blob/master/matrix/matrix_operation.py) + * [Nth Fibonacci Using Matrix Exponentiation](https://github.com/TheAlgorithms/Python/blob/master/matrix/nth_fibonacci_using_matrix_exponentiation.py) + * [Rotate Matrix](https://github.com/TheAlgorithms/Python/blob/master/matrix/rotate_matrix.py) + * [Searching In Sorted Matrix](https://github.com/TheAlgorithms/Python/blob/master/matrix/searching_in_sorted_matrix.py) + * [Sherman Morrison](https://github.com/TheAlgorithms/Python/blob/master/matrix/sherman_morrison.py) + * [Spiral Print](https://github.com/TheAlgorithms/Python/blob/master/matrix/spiral_print.py) + * Tests + * [Test Matrix Operation](https://github.com/TheAlgorithms/Python/blob/master/matrix/tests/test_matrix_operation.py) + +## Networking Flow + * [Ford Fulkerson](https://github.com/TheAlgorithms/Python/blob/master/networking_flow/ford_fulkerson.py) + * [Minimum Cut](https://github.com/TheAlgorithms/Python/blob/master/networking_flow/minimum_cut.py) + +## Neural Network + * [Back Propagation Neural Network](https://github.com/TheAlgorithms/Python/blob/master/neural_network/back_propagation_neural_network.py) + * [Convolution Neural Network](https://github.com/TheAlgorithms/Python/blob/master/neural_network/convolution_neural_network.py) + * [Gan](https://github.com/TheAlgorithms/Python/blob/master/neural_network/gan.py) + * [Input Data](https://github.com/TheAlgorithms/Python/blob/master/neural_network/input_data.py) + * [Perceptron](https://github.com/TheAlgorithms/Python/blob/master/neural_network/perceptron.py) + +## Other + * [Activity Selection](https://github.com/TheAlgorithms/Python/blob/master/other/activity_selection.py) + * [Anagrams](https://github.com/TheAlgorithms/Python/blob/master/other/anagrams.py) + * [Autocomplete Using Trie](https://github.com/TheAlgorithms/Python/blob/master/other/autocomplete_using_trie.py) + * [Binary Exponentiation](https://github.com/TheAlgorithms/Python/blob/master/other/binary_exponentiation.py) + * [Binary Exponentiation 2](https://github.com/TheAlgorithms/Python/blob/master/other/binary_exponentiation_2.py) + * [Detecting English Programmatically](https://github.com/TheAlgorithms/Python/blob/master/other/detecting_english_programmatically.py) + * [Dijkstra Bankers Algorithm](https://github.com/TheAlgorithms/Python/blob/master/other/dijkstra_bankers_algorithm.py) + * [Euclidean Gcd](https://github.com/TheAlgorithms/Python/blob/master/other/euclidean_gcd.py) + * [Fischer Yates Shuffle](https://github.com/TheAlgorithms/Python/blob/master/other/fischer_yates_shuffle.py) + * [Frequency Finder](https://github.com/TheAlgorithms/Python/blob/master/other/frequency_finder.py) + * [Game Of Life](https://github.com/TheAlgorithms/Python/blob/master/other/game_of_life.py) + * [Greedy](https://github.com/TheAlgorithms/Python/blob/master/other/greedy.py) + * [Integeration By Simpson Approx](https://github.com/TheAlgorithms/Python/blob/master/other/integeration_by_simpson_approx.py) + * [Largest Subarray Sum](https://github.com/TheAlgorithms/Python/blob/master/other/largest_subarray_sum.py) + * [Least Recently Used](https://github.com/TheAlgorithms/Python/blob/master/other/least_recently_used.py) + * [Linear Congruential Generator](https://github.com/TheAlgorithms/Python/blob/master/other/linear_congruential_generator.py) + * [Magicdiamondpattern](https://github.com/TheAlgorithms/Python/blob/master/other/magicdiamondpattern.py) + * [Nested Brackets](https://github.com/TheAlgorithms/Python/blob/master/other/nested_brackets.py) + * [Palindrome](https://github.com/TheAlgorithms/Python/blob/master/other/palindrome.py) + * [Password Generator](https://github.com/TheAlgorithms/Python/blob/master/other/password_generator.py) + * [Primelib](https://github.com/TheAlgorithms/Python/blob/master/other/primelib.py) + * [Sdes](https://github.com/TheAlgorithms/Python/blob/master/other/sdes.py) + * [Sierpinski Triangle](https://github.com/TheAlgorithms/Python/blob/master/other/sierpinski_triangle.py) + * [Tower Of Hanoi](https://github.com/TheAlgorithms/Python/blob/master/other/tower_of_hanoi.py) + * [Two Sum](https://github.com/TheAlgorithms/Python/blob/master/other/two_sum.py) + * [Word Patterns](https://github.com/TheAlgorithms/Python/blob/master/other/word_patterns.py) + +## Project Euler + * Problem 01 + * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_01/sol1.py) + * [Sol2](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_01/sol2.py) + * [Sol3](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_01/sol3.py) + * [Sol4](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_01/sol4.py) + * [Sol5](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_01/sol5.py) + * [Sol6](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_01/sol6.py) + * [Sol7](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_01/sol7.py) + * Problem 02 + * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_02/sol1.py) + * [Sol2](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_02/sol2.py) + * [Sol3](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_02/sol3.py) + * [Sol4](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_02/sol4.py) + * [Sol5](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_02/sol5.py) + * Problem 03 + * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_03/sol1.py) + * [Sol2](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_03/sol2.py) + * [Sol3](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_03/sol3.py) + * Problem 04 + * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_04/sol1.py) + * [Sol2](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_04/sol2.py) + * Problem 05 + * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_05/sol1.py) + * [Sol2](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_05/sol2.py) + * Problem 06 + * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_06/sol1.py) + * [Sol2](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_06/sol2.py) + * [Sol3](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_06/sol3.py) + * [Sol4](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_06/sol4.py) + * Problem 07 + * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_07/sol1.py) + * [Sol2](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_07/sol2.py) + * [Sol3](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_07/sol3.py) + * Problem 08 + * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_08/sol1.py) + * [Sol2](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_08/sol2.py) + * [Sol3](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_08/sol3.py) + * Problem 09 + * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_09/sol1.py) + * [Sol2](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_09/sol2.py) + * [Sol3](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_09/sol3.py) + * Problem 10 + * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_10/sol1.py) + * [Sol2](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_10/sol2.py) + * [Sol3](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_10/sol3.py) + * Problem 11 + * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_11/sol1.py) + * [Sol2](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_11/sol2.py) + * Problem 12 + * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_12/sol1.py) + * [Sol2](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_12/sol2.py) + * Problem 13 + * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_13/sol1.py) + * Problem 14 + * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_14/sol1.py) + * [Sol2](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_14/sol2.py) + * Problem 15 + * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_15/sol1.py) + * Problem 16 + * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_16/sol1.py) + * [Sol2](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_16/sol2.py) + * Problem 17 + * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_17/sol1.py) + * Problem 18 + * [Solution](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_18/solution.py) + * Problem 19 + * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_19/sol1.py) + * Problem 20 + * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_20/sol1.py) + * [Sol2](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_20/sol2.py) + * [Sol3](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_20/sol3.py) + * [Sol4](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_20/sol4.py) + * Problem 21 + * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_21/sol1.py) + * Problem 22 + * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_22/sol1.py) + * [Sol2](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_22/sol2.py) + * Problem 23 + * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_23/sol1.py) + * Problem 234 + * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_234/sol1.py) + * Problem 24 + * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_24/sol1.py) + * Problem 25 + * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_25/sol1.py) + * [Sol2](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_25/sol2.py) + * [Sol3](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_25/sol3.py) + * Problem 27 + * [Problem 27 Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_27/problem_27_sol1.py) + * Problem 28 + * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_28/sol1.py) + * Problem 29 + * [Solution](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_29/solution.py) + * Problem 31 + * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_31/sol1.py) + * Problem 32 + * [Sol32](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_32/sol32.py) + * Problem 33 + * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_33/sol1.py) + * Problem 36 + * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_36/sol1.py) + * Problem 40 + * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_40/sol1.py) + * Problem 42 + * [Solution42](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_42/solution42.py) + * Problem 48 + * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_48/sol1.py) + * Problem 52 + * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_52/sol1.py) + * Problem 53 + * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_53/sol1.py) + * Problem 551 + * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_551/sol1.py) + * Problem 56 + * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_56/sol1.py) + * Problem 67 + * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_67/sol1.py) + * Problem 76 + * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_76/sol1.py) + * Problem 99 + * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_99/sol1.py) + +## Searches + * [Binary Search](https://github.com/TheAlgorithms/Python/blob/master/searches/binary_search.py) + * [Fibonacci Search](https://github.com/TheAlgorithms/Python/blob/master/searches/fibonacci_search.py) + * [Hill Climbing](https://github.com/TheAlgorithms/Python/blob/master/searches/hill_climbing.py) + * [Interpolation Search](https://github.com/TheAlgorithms/Python/blob/master/searches/interpolation_search.py) + * [Jump Search](https://github.com/TheAlgorithms/Python/blob/master/searches/jump_search.py) + * [Linear Search](https://github.com/TheAlgorithms/Python/blob/master/searches/linear_search.py) + * [Quick Select](https://github.com/TheAlgorithms/Python/blob/master/searches/quick_select.py) + * [Sentinel Linear Search](https://github.com/TheAlgorithms/Python/blob/master/searches/sentinel_linear_search.py) + * [Simple-Binary-Search](https://github.com/TheAlgorithms/Python/blob/master/searches/simple-binary-search.py) + * [Tabu Search](https://github.com/TheAlgorithms/Python/blob/master/searches/tabu_search.py) + * [Ternary Search](https://github.com/TheAlgorithms/Python/blob/master/searches/ternary_search.py) + +## Sorts + * [Bitonic Sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/bitonic_sort.py) + * [Bogo Sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/bogo_sort.py) + * [Bubble Sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/bubble_sort.py) + * [Bucket Sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/bucket_sort.py) + * [Cocktail Shaker Sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/cocktail_shaker_sort.py) + * [Comb Sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/comb_sort.py) + * [Counting Sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/counting_sort.py) + * [Cycle Sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/cycle_sort.py) + * [Double Sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/double_sort.py) + * [External Sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/external_sort.py) + * [Gnome Sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/gnome_sort.py) + * [Heap Sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/heap_sort.py) + * [I Sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/i_sort.py) + * [Insertion Sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/insertion_sort.py) + * [Merge Sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/merge_sort.py) + * [Odd Even Transposition Parallel](https://github.com/TheAlgorithms/Python/blob/master/sorts/odd_even_transposition_parallel.py) + * [Odd Even Transposition Single Threaded](https://github.com/TheAlgorithms/Python/blob/master/sorts/odd_even_transposition_single_threaded.py) + * [Pancake Sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/pancake_sort.py) + * [Pigeon Sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/pigeon_sort.py) + * [Pigeonhole Sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/pigeonhole_sort.py) + * [Quick Sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/quick_sort.py) + * [Quick Sort 3 Partition](https://github.com/TheAlgorithms/Python/blob/master/sorts/quick_sort_3_partition.py) + * [Radix Sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/radix_sort.py) + * [Random Normal Distribution Quicksort](https://github.com/TheAlgorithms/Python/blob/master/sorts/random_normal_distribution_quicksort.py) + * [Random Pivot Quick Sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/random_pivot_quick_sort.py) + * [Recursive-Quick-Sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/recursive-quick-sort.py) + * [Recursive Bubble Sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/recursive_bubble_sort.py) + * [Selection Sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/selection_sort.py) + * [Shell Sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/shell_sort.py) + * [Stooge Sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/stooge_sort.py) + * [Tim Sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/tim_sort.py) + * [Topological Sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/topological_sort.py) + * [Tree Sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/tree_sort.py) + * [Unknown Sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/unknown_sort.py) + * [Wiggle Sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/wiggle_sort.py) + +## Strings + * [Aho-Corasick](https://github.com/TheAlgorithms/Python/blob/master/strings/aho-corasick.py) + * [Boyer Moore Search](https://github.com/TheAlgorithms/Python/blob/master/strings/boyer_moore_search.py) + * [Check Panagram](https://github.com/TheAlgorithms/Python/blob/master/strings/check_panagram.py) + * [Knuth Morris Pratt](https://github.com/TheAlgorithms/Python/blob/master/strings/knuth_morris_pratt.py) + * [Levenshtein Distance](https://github.com/TheAlgorithms/Python/blob/master/strings/levenshtein_distance.py) + * [Lower](https://github.com/TheAlgorithms/Python/blob/master/strings/lower.py) + * [Manacher](https://github.com/TheAlgorithms/Python/blob/master/strings/manacher.py) + * [Min Cost String Conversion](https://github.com/TheAlgorithms/Python/blob/master/strings/min_cost_string_conversion.py) + * [Naive String Search](https://github.com/TheAlgorithms/Python/blob/master/strings/naive_string_search.py) + * [Rabin Karp](https://github.com/TheAlgorithms/Python/blob/master/strings/rabin_karp.py) + * [Remove Duplicate](https://github.com/TheAlgorithms/Python/blob/master/strings/remove_duplicate.py) + * [Reverse Words](https://github.com/TheAlgorithms/Python/blob/master/strings/reverse_words.py) + * [Split](https://github.com/TheAlgorithms/Python/blob/master/strings/split.py) + * [Upper](https://github.com/TheAlgorithms/Python/blob/master/strings/upper.py) + * [Word Occurence](https://github.com/TheAlgorithms/Python/blob/master/strings/word_occurence.py) + +## Traversals + * [Binary Tree Traversals](https://github.com/TheAlgorithms/Python/blob/master/traversals/binary_tree_traversals.py) + +## Web Programming + * [Crawl Google Results](https://github.com/TheAlgorithms/Python/blob/master/web_programming/crawl_google_results.py) + * [Current Stock Price](https://github.com/TheAlgorithms/Python/blob/master/web_programming/current_stock_price.py) + * [Fetch Bbc News](https://github.com/TheAlgorithms/Python/blob/master/web_programming/fetch_bbc_news.py) + * [Fetch Github Info](https://github.com/TheAlgorithms/Python/blob/master/web_programming/fetch_github_info.py) + * [Get Imdb Top 250 Movies Csv](https://github.com/TheAlgorithms/Python/blob/master/web_programming/get_imdb_top_250_movies_csv.py) + * [Get Imdbtop](https://github.com/TheAlgorithms/Python/blob/master/web_programming/get_imdbtop.py) + * [Slack Message](https://github.com/TheAlgorithms/Python/blob/master/web_programming/slack_message.py) From 98733618e25db3d640093605a24be4faf9c69222 Mon Sep 17 00:00:00 2001 From: AlexLeka98 <32596824+AlexLeka98@users.noreply.github.com> Date: Sun, 12 Jan 2020 06:04:10 +0200 Subject: [PATCH 0427/1071] Added Strassen divide and conquer algorithm to multiply matrices. (#1648) * Added Strassen divide and conquer algorithm to multiply matrices * Divide and conquer algorith to calculate pow(a,b) or a raised to the power of b * Putting docstring inside the function. * Added doctests --- divide_and_conquer/power.py | 33 ++++ .../strassen_matrix_multiplication.py | 161 ++++++++++++++++++ 2 files changed, 194 insertions(+) create mode 100644 divide_and_conquer/power.py create mode 100644 divide_and_conquer/strassen_matrix_multiplication.py diff --git a/divide_and_conquer/power.py b/divide_and_conquer/power.py new file mode 100644 index 000000000000..f2e023afd536 --- /dev/null +++ b/divide_and_conquer/power.py @@ -0,0 +1,33 @@ +def actual_power(a: int, b: int): + """ + Function using divide and conquer to calculate a^b. + It only works for integer a,b. + """ + if b == 0: + return 1 + if (b % 2) == 0: + return actual_power(a, int(b / 2)) * actual_power(a, int(b / 2)) + else: + return a * actual_power(a, int(b / 2)) * actual_power(a, int(b / 2)) + + +def power(a: int, b: int) -> float: + """ + >>> power(4,6) + 4096 + >>> power(2,3) + 8 + >>> power(-2,3) + -8 + >>> power(2,-3) + 0.125 + >>> power(-2,-3) + -0.125 + """ + if b < 0: + return 1 / actual_power(a, b) + return actual_power(a, b) + + +if __name__ == "__main__": + print(power(-2, -3)) diff --git a/divide_and_conquer/strassen_matrix_multiplication.py b/divide_and_conquer/strassen_matrix_multiplication.py new file mode 100644 index 000000000000..c0725b1c951f --- /dev/null +++ b/divide_and_conquer/strassen_matrix_multiplication.py @@ -0,0 +1,161 @@ +import math +from typing import List, Tuple + + +def default_matrix_multiplication(a: List, b: List) -> List: + """ + Multiplication only for 2x2 matrices + """ + if len(a) != 2 or len(a[0]) != 2 or len(b) != 2 or len(b[0]) != 2: + raise Exception("Matrices are not 2x2") + new_matrix = [ + [a[0][0] * b[0][0] + a[0][1] * b[1][0], a[0][0] * b[0][1] + a[0][1] * b[1][1]], + [a[1][0] * b[0][0] + a[1][1] * b[1][0], a[1][0] * b[0][1] + a[1][1] * b[1][1]], + ] + return new_matrix + + +def matrix_addition(matrix_a: List, matrix_b: List): + return [ + [matrix_a[row][col] + matrix_b[row][col] for col in range(len(matrix_a[row]))] + for row in range(len(matrix_a)) + ] + + +def matrix_subtraction(matrix_a: List, matrix_b: List): + return [ + [matrix_a[row][col] - matrix_b[row][col] for col in range(len(matrix_a[row]))] + for row in range(len(matrix_a)) + ] + + +def split_matrix(a: List,) -> Tuple[List, List, List, List]: + """ + Given an even length matrix, returns the top_left, top_right, bot_left, bot_right quadrant. + + >>> split_matrix([[4,3,2,4],[2,3,1,1],[6,5,4,3],[8,4,1,6]]) + ([[4, 3], [2, 3]], [[2, 4], [1, 1]], [[6, 5], [8, 4]], [[4, 3], [1, 6]]) + >>> split_matrix([[4,3,2,4,4,3,2,4],[2,3,1,1,2,3,1,1],[6,5,4,3,6,5,4,3],[8,4,1,6,8,4,1,6],[4,3,2,4,4,3,2,4],[2,3,1,1,2,3,1,1],[6,5,4,3,6,5,4,3],[8,4,1,6,8,4,1,6]]) + ([[4, 3, 2, 4], [2, 3, 1, 1], [6, 5, 4, 3], [8, 4, 1, 6]], [[4, 3, 2, 4], [2, 3, 1, 1], [6, 5, 4, 3], [8, 4, 1, 6]], [[4, 3, 2, 4], [2, 3, 1, 1], [6, 5, 4, 3], [8, 4, 1, 6]], [[4, 3, 2, 4], [2, 3, 1, 1], [6, 5, 4, 3], [8, 4, 1, 6]]) + """ + if len(a) % 2 != 0 or len(a[0]) % 2 != 0: + raise Exception("Odd matrices are not supported!") + + matrix_length = len(a) + mid = matrix_length // 2 + + top_right = [[a[i][j] for j in range(mid, matrix_length)] for i in range(mid)] + bot_right = [ + [a[i][j] for j in range(mid, matrix_length)] for i in range(mid, matrix_length) + ] + + top_left = [[a[i][j] for j in range(mid)] for i in range(mid)] + bot_left = [[a[i][j] for j in range(mid)] for i in range(mid, matrix_length)] + + return top_left, top_right, bot_left, bot_right + + +def matrix_dimensions(matrix: List) -> Tuple[int, int]: + return len(matrix), len(matrix[0]) + + +def print_matrix(matrix: List) -> None: + for i in range(len(matrix)): + print(matrix[i]) + + +def actual_strassen(matrix_a: List, matrix_b: List) -> List: + """ + Recursive function to calculate the product of two matrices, using the Strassen Algorithm. + It only supports even length matrices. + """ + if matrix_dimensions(matrix_a) == (2, 2): + return default_matrix_multiplication(matrix_a, matrix_b) + + a, b, c, d = split_matrix(matrix_a) + e, f, g, h = split_matrix(matrix_b) + + t1 = actual_strassen(a, matrix_subtraction(f, h)) + t2 = actual_strassen(matrix_addition(a, b), h) + t3 = actual_strassen(matrix_addition(c, d), e) + t4 = actual_strassen(d, matrix_subtraction(g, e)) + t5 = actual_strassen(matrix_addition(a, d), matrix_addition(e, h)) + t6 = actual_strassen(matrix_subtraction(b, d), matrix_addition(g, h)) + t7 = actual_strassen(matrix_subtraction(a, c), matrix_addition(e, f)) + + top_left = matrix_addition(matrix_subtraction(matrix_addition(t5, t4), t2), t6) + top_right = matrix_addition(t1, t2) + bot_left = matrix_addition(t3, t4) + bot_right = matrix_subtraction(matrix_subtraction(matrix_addition(t1, t5), t3), t7) + + # construct the new matrix from our 4 quadrants + new_matrix = [] + for i in range(len(top_right)): + new_matrix.append(top_left[i] + top_right[i]) + for i in range(len(bot_right)): + new_matrix.append(bot_left[i] + bot_right[i]) + return new_matrix + + +def strassen(matrix1: List, matrix2: List) -> List: + """ + >>> strassen([[2,1,3],[3,4,6],[1,4,2],[7,6,7]], [[4,2,3,4],[2,1,1,1],[8,6,4,2]]) + [[34, 23, 19, 15], [68, 46, 37, 28], [28, 18, 15, 12], [96, 62, 55, 48]] + >>> strassen([[3,7,5,6,9],[1,5,3,7,8],[1,4,4,5,7]], [[2,4],[5,2],[1,7],[5,5],[7,8]]) + [[139, 163], [121, 134], [100, 121]] + """ + if matrix_dimensions(matrix1)[1] != matrix_dimensions(matrix2)[0]: + raise Exception( + f"Unable to multiply these matrices, please check the dimensions. \nMatrix A:{matrix1} \nMatrix B:{matrix2}" + ) + dimension1 = matrix_dimensions(matrix1) + dimension2 = matrix_dimensions(matrix2) + + if dimension1[0] == dimension1[1] and dimension2[0] == dimension2[1]: + return matrix1, matrix2 + + maximum = max(max(dimension1), max(dimension2)) + maxim = int(math.pow(2, math.ceil(math.log2(maximum)))) + new_matrix1 = matrix1 + new_matrix2 = matrix2 + + # Adding zeros to the matrices so that the arrays dimensions are the same and also power of 2 + for i in range(0, maxim): + if i < dimension1[0]: + for j in range(dimension1[1], maxim): + new_matrix1[i].append(0) + else: + new_matrix1.append([0] * maxim) + if i < dimension2[0]: + for j in range(dimension2[1], maxim): + new_matrix2[i].append(0) + else: + new_matrix2.append([0] * maxim) + + final_matrix = actual_strassen(new_matrix1, new_matrix2) + + # Removing the additional zeros + for i in range(0, maxim): + if i < dimension1[0]: + for j in range(dimension2[1], maxim): + final_matrix[i].pop() + else: + final_matrix.pop() + return final_matrix + + +if __name__ == "__main__": + matrix1= [ + [2, 3, 4, 5], + [6, 4, 3, 1], + [2, 3, 6, 7], + [3, 1, 2, 4], + [2, 3, 4, 5], + [6, 4, 3, 1], + [2, 3, 6, 7], + [3, 1, 2, 4], + [2, 3, 4, 5], + [6, 2, 3, 1], + ] + matrix2 = [[0, 2, 1, 1], [16, 2, 3, 3], [2, 2, 7, 7], [13, 11, 22, 4]] + print(strassen(matrix1, matrix2)) From 2cb6a6523e9cd716ece2559540019bdba2cc1295 Mon Sep 17 00:00:00 2001 From: onlinejudge95 <44158581+onlinejudge95@users.noreply.github.com> Date: Sun, 12 Jan 2020 14:58:47 +0530 Subject: [PATCH 0428/1071] Corrects failing check in master (#1676) --- divide_and_conquer/strassen_matrix_multiplication.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/divide_and_conquer/strassen_matrix_multiplication.py b/divide_and_conquer/strassen_matrix_multiplication.py index c0725b1c951f..bfced547d493 100644 --- a/divide_and_conquer/strassen_matrix_multiplication.py +++ b/divide_and_conquer/strassen_matrix_multiplication.py @@ -145,7 +145,7 @@ def strassen(matrix1: List, matrix2: List) -> List: if __name__ == "__main__": - matrix1= [ + matrix1 = [ [2, 3, 4, 5], [6, 4, 3, 1], [2, 3, 6, 7], From 4607cd48b6e3ded8be0c2d1915c9388367d79147 Mon Sep 17 00:00:00 2001 From: Leon Morten Richter <31622033+M0r13n@users.noreply.github.com> Date: Sun, 12 Jan 2020 10:30:40 +0100 Subject: [PATCH 0429/1071] Add a program to evaluate a string in prefix notation (Polish Notation) (#1675) * Create infix_evaluation.py * fix doctests * Rename infix_evaluation.py to prefix_evaluation.py * Add prefix_evaluation.py to directory --- DIRECTORY.md | 1 + data_structures/stacks/prefix_evaluation.py | 60 +++++++++++++++++++++ 2 files changed, 61 insertions(+) create mode 100644 data_structures/stacks/prefix_evaluation.py diff --git a/DIRECTORY.md b/DIRECTORY.md index 73ecbc36fdc4..1116e8539536 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -127,6 +127,7 @@ * [Stock Span Problem](https://github.com/TheAlgorithms/Python/blob/master/data_structures/stacks/stock_span_problem.py) * Trie * [Trie](https://github.com/TheAlgorithms/Python/blob/master/data_structures/trie/trie.py) + * [Prefix Evaluation](https://github.com/TheAlgorithms/Python/blob/master/data_structures/prefix_evaluation.py) ## Digital Image Processing * [Change Contrast](https://github.com/TheAlgorithms/Python/blob/master/digital_image_processing/change_contrast.py) diff --git a/data_structures/stacks/prefix_evaluation.py b/data_structures/stacks/prefix_evaluation.py new file mode 100644 index 000000000000..00df2c1e63b0 --- /dev/null +++ b/data_structures/stacks/prefix_evaluation.py @@ -0,0 +1,60 @@ +""" +Python3 program to evaluate a prefix expression. +""" + +calc = { + "+": lambda x, y: x + y, + "-": lambda x, y: x - y, + "*": lambda x, y: x * y, + "/": lambda x, y: x / y, +} + + +def is_operand(c): + """ + Return True if the given char c is an operand, e.g. it is a number + + >>> is_operand("1") + True + >>> is_operand("+") + False + """ + return c.isdigit() + + +def evaluate(expression): + """ + Evaluate a given expression in prefix notation. + Asserts that the given expression is valid. + + >>> evaluate("+ 9 * 2 6") + 21 + >>> evaluate("/ * 10 2 + 4 1 ") + 4.0 + """ + stack = [] + + # iterate over the string in reverse order + for c in expression.split()[::-1]: + + # push operand to stack + if is_operand(c): + stack.append(int(c)) + + else: + # pop values from stack can calculate the result + # push the result onto the stack again + o1 = stack.pop() + o2 = stack.pop() + stack.append(calc[c](o1, o2)) + + return stack.pop() + + +# Driver code +if __name__ == "__main__": + test_expression = "+ 9 * 2 6" + print(evaluate(test_expression)) + + test_expression = "/ * 10 2 + 4 1 " + print(evaluate(test_expression)) From 75523f9c1a9cc8e6aaa0b07ea652a693c7846f13 Mon Sep 17 00:00:00 2001 From: shrabian <58838321+shrabian@users.noreply.github.com> Date: Tue, 14 Jan 2020 03:22:18 +1100 Subject: [PATCH 0430/1071] A recursive insertion sort (#1683) * A recursive insertion sort * added doctests and typehints --- sorts/recursive_insertion_sort.py | 72 +++++++++++++++++++++++++++++++ 1 file changed, 72 insertions(+) create mode 100644 sorts/recursive_insertion_sort.py diff --git a/sorts/recursive_insertion_sort.py b/sorts/recursive_insertion_sort.py new file mode 100644 index 000000000000..a8bd2b9114ad --- /dev/null +++ b/sorts/recursive_insertion_sort.py @@ -0,0 +1,72 @@ +""" +A recursive implementation of the insertion sort algorithm +""" + +from typing import List + +def rec_insertion_sort(collection: List, n: int): + """ + Given a collection of numbers and its length, sorts the collections + in ascending order + + :param collection: A mutable collection of comparable elements + :param n: The length of collections + + >>> col = [1, 2, 1] + >>> rec_insertion_sort(col, len(col)) + >>> print(col) + [1, 1, 2] + + >>> col = [2, 1, 0, -1, -2] + >>> rec_insertion_sort(col, len(col)) + >>> print(col) + [-2, -1, 0, 1, 2] + + >>> col = [1] + >>> rec_insertion_sort(col, len(col)) + >>> print(col) + [1] + """ + #Checks if the entire collection has been sorted + if len(collection) <= 1 or n <= 1: + return + + + insert_next(collection, n-1) + rec_insertion_sort(collection, n-1) + +def insert_next(collection: List, index: int): + """ + Inserts the '(index-1)th' element into place + + >>> col = [3, 2, 4, 2] + >>> insert_next(col, 1) + >>> print(col) + [2, 3, 4, 2] + + >>> col = [3, 2, 3] + >>> insert_next(col, 2) + >>> print(col) + [3, 2, 3] + + >>> col = [] + >>> insert_next(col, 1) + >>> print(col) + [] + """ + #Checks order between adjacent elements + if index >= len(collection) or collection[index - 1] <= collection[index]: + return + + #Swaps adjacent elements since they are not in ascending order + collection[index - 1], collection[index] = ( + collection[index], collection[index - 1] + ) + + insert_next(collection, index + 1) + +if __name__ == "__main__": + numbers = input("Enter integers seperated by spaces: ") + numbers = [int(num) for num in numbers.split()] + rec_insertion_sort(numbers, len(numbers)) + print(numbers) From b492e6441708fc82e85d44cc87353178afd85342 Mon Sep 17 00:00:00 2001 From: Christian Clauss Date: Mon, 13 Jan 2020 19:56:06 +0100 Subject: [PATCH 0431/1071] Create pull_request_template.md (#1684) * Create pull_request_template.md * fixup! Format Python code with psf/black push * Update pull_request_template.md * updating DIRECTORY.md * Update pull_request_template.md * Update pull_request_template.md * Update pull_request_template.md * Update pull_request_template.md * Update pull_request_template.md * Typos and formatting Co-authored-by: John Law --- .github/pull_request_template.md | 19 +++++++++++++++++++ DIRECTORY.md | 5 ++++- sorts/recursive_insertion_sort.py | 15 +++++++++------ 3 files changed, 32 insertions(+), 7 deletions(-) create mode 100644 .github/pull_request_template.md diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 000000000000..2f130896ebe3 --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,19 @@ +### **Describe your change:** + + + +* [ ] Add an algorithm? +* [ ] Fix a bug or typo in an existing algorithm? +* [ ] Documentation change? + +### **Checklist:** +* [ ] I have read [CONTRIBUTING.md](https://github.com/TheAlgorithms/Python/blob/master/CONTRIBUTING.md). +* [ ] This pull request is all my own work -- I have not plagiarized. +* [ ] I know that pull requests will not be merged if they fail the automated tests. +* [ ] This PR only changes one algorithm file. To ease review, please open separate PRs for separate algorithms. +* [ ] All new Python files are placed inside an existing directory. +* [ ] All filenames are in all lowercase characters with no spaces or dashes. +* [ ] All functions and variable names follow Python naming conventions. +* [ ] All function parameters and return values are annotated with Python [type hints](https://docs.python.org/3/library/typing.html). +* [ ] All functions have [doctests](https://docs.python.org/3/library/doctest.html) that pass the automated testing. +* [ ] All new algorithms have a URL in its comments that points to Wikipedia or other similar explanation. diff --git a/DIRECTORY.md b/DIRECTORY.md index 1116e8539536..75dea10d34dd 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -122,12 +122,12 @@ * [Linked Stack](https://github.com/TheAlgorithms/Python/blob/master/data_structures/stacks/linked_stack.py) * [Next Greater Element](https://github.com/TheAlgorithms/Python/blob/master/data_structures/stacks/next_greater_element.py) * [Postfix Evaluation](https://github.com/TheAlgorithms/Python/blob/master/data_structures/stacks/postfix_evaluation.py) + * [Prefix Evaluation](https://github.com/TheAlgorithms/Python/blob/master/data_structures/stacks/prefix_evaluation.py) * [Stack](https://github.com/TheAlgorithms/Python/blob/master/data_structures/stacks/stack.py) * [Stack Using Dll](https://github.com/TheAlgorithms/Python/blob/master/data_structures/stacks/stack_using_dll.py) * [Stock Span Problem](https://github.com/TheAlgorithms/Python/blob/master/data_structures/stacks/stock_span_problem.py) * Trie * [Trie](https://github.com/TheAlgorithms/Python/blob/master/data_structures/trie/trie.py) - * [Prefix Evaluation](https://github.com/TheAlgorithms/Python/blob/master/data_structures/prefix_evaluation.py) ## Digital Image Processing * [Change Contrast](https://github.com/TheAlgorithms/Python/blob/master/digital_image_processing/change_contrast.py) @@ -152,6 +152,8 @@ * [Inversions](https://github.com/TheAlgorithms/Python/blob/master/divide_and_conquer/inversions.py) * [Max Subarray Sum](https://github.com/TheAlgorithms/Python/blob/master/divide_and_conquer/max_subarray_sum.py) * [Mergesort](https://github.com/TheAlgorithms/Python/blob/master/divide_and_conquer/mergesort.py) + * [Power](https://github.com/TheAlgorithms/Python/blob/master/divide_and_conquer/power.py) + * [Strassen Matrix Multiplication](https://github.com/TheAlgorithms/Python/blob/master/divide_and_conquer/strassen_matrix_multiplication.py) ## Dynamic Programming * [Abbreviation](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/abbreviation.py) @@ -537,6 +539,7 @@ * [Random Pivot Quick Sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/random_pivot_quick_sort.py) * [Recursive-Quick-Sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/recursive-quick-sort.py) * [Recursive Bubble Sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/recursive_bubble_sort.py) + * [Recursive Insertion Sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/recursive_insertion_sort.py) * [Selection Sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/selection_sort.py) * [Shell Sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/shell_sort.py) * [Stooge Sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/stooge_sort.py) diff --git a/sorts/recursive_insertion_sort.py b/sorts/recursive_insertion_sort.py index a8bd2b9114ad..39a903dd1bcb 100644 --- a/sorts/recursive_insertion_sort.py +++ b/sorts/recursive_insertion_sort.py @@ -4,6 +4,7 @@ from typing import List + def rec_insertion_sort(collection: List, n: int): """ Given a collection of numbers and its length, sorts the collections @@ -27,13 +28,13 @@ def rec_insertion_sort(collection: List, n: int): >>> print(col) [1] """ - #Checks if the entire collection has been sorted + # Checks if the entire collection has been sorted if len(collection) <= 1 or n <= 1: return + insert_next(collection, n - 1) + rec_insertion_sort(collection, n - 1) - insert_next(collection, n-1) - rec_insertion_sort(collection, n-1) def insert_next(collection: List, index: int): """ @@ -54,17 +55,19 @@ def insert_next(collection: List, index: int): >>> print(col) [] """ - #Checks order between adjacent elements + # Checks order between adjacent elements if index >= len(collection) or collection[index - 1] <= collection[index]: return - #Swaps adjacent elements since they are not in ascending order + # Swaps adjacent elements since they are not in ascending order collection[index - 1], collection[index] = ( - collection[index], collection[index - 1] + collection[index], + collection[index - 1], ) insert_next(collection, index + 1) + if __name__ == "__main__": numbers = input("Enter integers seperated by spaces: ") numbers = [int(num) for num in numbers.split()] From 56e7ae01d2ae04fa46f32937dd8077134193a792 Mon Sep 17 00:00:00 2001 From: lanzhiwang Date: Tue, 14 Jan 2020 17:02:15 +0800 Subject: [PATCH 0432/1071] enhance swapping code in link (#1660) * enhance swapping code in link * heapify do not recursive * fix * fix identifier and add test * typing.Any and LinkedList instead of Linkedlist * typing.Any and LinkedList instead of Linkedlist --- data_structures/linked_list/swap_nodes.py | 69 +++++++++-------------- 1 file changed, 26 insertions(+), 43 deletions(-) diff --git a/data_structures/linked_list/swap_nodes.py b/data_structures/linked_list/swap_nodes.py index a6a50091e3e0..bb177f419c30 100644 --- a/data_structures/linked_list/swap_nodes.py +++ b/data_structures/linked_list/swap_nodes.py @@ -1,72 +1,55 @@ +from typing import Any + + class Node: - def __init__(self, data): + def __init__(self, data: Any): self.data = data self.next = None -class Linkedlist: +class LinkedList: def __init__(self): self.head = None def print_list(self): temp = self.head while temp is not None: - print(temp.data) + print(temp.data, end=' ') temp = temp.next + print() # adding nodes - def push(self, new_data): + def push(self, new_data: Any): new_node = Node(new_data) new_node.next = self.head self.head = new_node # swapping nodes - def swapNodes(self, d1, d2): - prevD1 = None - prevD2 = None - if d1 == d2: + def swap_nodes(self, node_data_1, node_data_2): + if node_data_1 == node_data_2: return else: - # find d1 - D1 = self.head - while D1 is not None and D1.data != d1: - prevD1 = D1 - D1 = D1.next - # find d2 - D2 = self.head - while D2 is not None and D2.data != d2: - prevD2 = D2 - D2 = D2.next - if D1 is None and D2 is None: - return - # if D1 is head - if prevD1 is not None: - prevD1.next = D2 - else: - self.head = D2 - # if D2 is head - if prevD2 is not None: - prevD2.next = D1 - else: - self.head = D1 - temp = D1.next - D1.next = D2.next - D2.next = temp + node_1 = self.head + while node_1 is not None and node_1.data != node_data_1: + node_1 = node_1.next + node_2 = self.head + while node_2 is not None and node_2.data != node_data_2: + node_2 = node_2.next + + if node_1 is None or node_2 is None: + return -# swapping code ends here + node_1.data, node_2.data = node_2.data, node_1.data if __name__ == "__main__": - list = Linkedlist() - list.push(5) - list.push(4) - list.push(3) - list.push(2) - list.push(1) + ll = LinkedList() + for i in range(5, 0, -1): + ll.push(i) - list.print_list() + ll.print_list() - list.swapNodes(1, 4) + ll.swap_nodes(1, 4) print("After swapping") - list.print_list() + ll.print_list() From d09a805804641aabce64cf0fe97a5edc623b3380 Mon Sep 17 00:00:00 2001 From: Logan Lieou Date: Tue, 14 Jan 2020 03:21:02 -0600 Subject: [PATCH 0433/1071] Typo? (#1653) * Typo? newNod -> newNode * newNode -> new_node Co-authored-by: Christian Clauss --- data_structures/linked_list/singly_linked_list.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/data_structures/linked_list/singly_linked_list.py b/data_structures/linked_list/singly_linked_list.py index 8730a6d2a9e8..fb04ce10398e 100644 --- a/data_structures/linked_list/singly_linked_list.py +++ b/data_structures/linked_list/singly_linked_list.py @@ -21,10 +21,10 @@ def insert_tail(self, data) -> None: temp.next = Node(data) # create node & link to tail def insert_head(self, data) -> None: - newNod = Node(data) # create a new node + new_node = Node(data) # create a new node if self.head: - newNod.next = self.head # link newNode to head - self.head = newNod # make NewNode as head + new_node.next = self.head # link new_node to head + self.head = new_node # make NewNode as head def print_list(self) -> None: # print every node data temp = self.head From fc4c0f5bfda4fe90246d99767902dbf0a200c0d5 Mon Sep 17 00:00:00 2001 From: lanzhiwang Date: Tue, 14 Jan 2020 19:16:11 +0800 Subject: [PATCH 0434/1071] implement max heap and more pythonic (#1685) * implement max heap and more pythonic * add doctests for heap --- data_structures/heap/heap.py | 182 ++++++++++++++++++++++------------- 1 file changed, 115 insertions(+), 67 deletions(-) diff --git a/data_structures/heap/heap.py b/data_structures/heap/heap.py index b020ab067cc8..e4fe8f81154b 100644 --- a/data_structures/heap/heap.py +++ b/data_structures/heap/heap.py @@ -1,86 +1,134 @@ -#!/usr/bin/python +#!/usr/bin/python3 -# This heap class start from here. -class Heap: - def __init__(self): # Default constructor of heap class. + +class Heap(object): + """ + >>> unsorted = [103, 9, 1, 7, 11, 15, 25, 201, 209, 107, 5] + >>> h = Heap() + >>> h.build_heap(unsorted) + >>> h.display() + [209, 201, 25, 103, 107, 15, 1, 9, 7, 11, 5] + >>> + >>> h.get_max() + 209 + >>> h.display() + [201, 107, 25, 103, 11, 15, 1, 9, 7, 5] + >>> + >>> h.insert(100) + >>> h.display() + [201, 107, 25, 103, 100, 15, 1, 9, 7, 5, 11] + >>> + >>> h.heap_sort() + >>> h.display() + [1, 5, 7, 9, 11, 15, 25, 100, 103, 107, 201] + >>> + """ + def __init__(self): self.h = [] - self.currsize = 0 + self.curr_size = 0 - def leftChild(self, i): - if 2 * i + 1 < self.currsize: - return 2 * i + 1 + def get_left_child_index(self, i): + left_child_index = 2 * i + 1 + if left_child_index < self.curr_size: + return left_child_index return None - def rightChild(self, i): - if 2 * i + 2 < self.currsize: - return 2 * i + 2 + def get_right_child(self, i): + right_child_index = 2 * i + 2 + if right_child_index < self.curr_size: + return right_child_index return None - def maxHeapify(self, node): - if node < self.currsize: - m = node - lc = self.leftChild(node) - rc = self.rightChild(node) - if lc is not None and self.h[lc] > self.h[m]: - m = lc - if rc is not None and self.h[rc] > self.h[m]: - m = rc - if m != node: - temp = self.h[node] - self.h[node] = self.h[m] - self.h[m] = temp - self.maxHeapify(m) - - def buildHeap( - self, a - ): # This function is used to build the heap from the data container 'a'. - self.currsize = len(a) - self.h = list(a) - for i in range(self.currsize // 2, -1, -1): - self.maxHeapify(i) - - def getMax(self): # This function is used to get maximum value from the heap. - if self.currsize >= 1: + def max_heapify(self, index): + if index < self.curr_size: + largest = index + lc = self.get_left_child_index(index) + rc = self.get_right_child(index) + if lc is not None and self.h[lc] > self.h[largest]: + largest = lc + if rc is not None and self.h[rc] > self.h[largest]: + largest = rc + if largest != index: + self.h[largest], self.h[index] = self.h[index], self.h[largest] + self.max_heapify(largest) + + def build_heap(self, collection): + self.curr_size = len(collection) + self.h = list(collection) + if self.curr_size <= 1: + return + for i in range(self.curr_size // 2 - 1, -1, -1): + self.max_heapify(i) + + def get_max(self): + if self.curr_size >= 2: me = self.h[0] - temp = self.h[0] - self.h[0] = self.h[self.currsize - 1] - self.h[self.currsize - 1] = temp - self.currsize -= 1 - self.maxHeapify(0) + self.h[0] = self.h.pop(-1) + self.curr_size -= 1 + self.max_heapify(0) return me + elif self.curr_size == 1: + self.curr_size -= 1 + return self.h.pop(-1) return None - def heapSort(self): # This function is used to sort the heap. - size = self.currsize - while self.currsize - 1 >= 0: - temp = self.h[0] - self.h[0] = self.h[self.currsize - 1] - self.h[self.currsize - 1] = temp - self.currsize -= 1 - self.maxHeapify(0) - self.currsize = size - - def insert(self, data): # This function is used to insert data in the heap. + def heap_sort(self): + size = self.curr_size + for j in range(size - 1, 0, -1): + self.h[0], self.h[j] = self.h[j], self.h[0] + self.curr_size -= 1 + self.max_heapify(0) + self.curr_size = size + + def insert(self, data): self.h.append(data) - curr = self.currsize - self.currsize += 1 - while self.h[curr] > self.h[curr / 2]: - temp = self.h[curr / 2] - self.h[curr / 2] = self.h[curr] - self.h[curr] = temp - curr = curr / 2 - - def display(self): # This function is used to print the heap. + curr = (self.curr_size - 1) // 2 + self.curr_size += 1 + while curr >= 0: + self.max_heapify(curr) + curr = (curr - 1) // 2 + + def display(self): print(self.h) def main(): - l = list(map(int, input().split())) - h = Heap() - h.buildHeap(l) - h.heapSort() - h.display() + for unsorted in [ + [], + [0], + [2], + [3, 5], + [5, 3], + [5, 5], + [0, 0, 0, 0], + [1, 1, 1, 1], + [2, 2, 3, 5], + [0, 2, 2, 3, 5], + [2, 5, 3, 0, 2, 3, 0, 3], + [6, 1, 2, 7, 9, 3, 4, 5, 10, 8], + [103, 9, 1, 7, 11, 15, 25, 201, 209, 107, 5], + [-45, -2, -5] + ]: + print('source unsorted list: %s' % unsorted) + + h = Heap() + h.build_heap(unsorted) + print('after build heap: ', end=' ') + h.display() + + print('max value: %s' % h.get_max()) + print('delete max value: ', end=' ') + h.display() + + h.insert(100) + print('after insert new value 100: ', end=' ') + h.display() + + h.heap_sort() + print('heap sort: ', end=' ') + h.display() + print() -if __name__ == "__main__": +if __name__ == '__main__': main() From 38bad6b1e85e033d1367ce25b5c2000e7f790e7a Mon Sep 17 00:00:00 2001 From: Cole Mollica <30614241+coleman2246@users.noreply.github.com> Date: Wed, 15 Jan 2020 16:21:26 -0500 Subject: [PATCH 0435/1071] Implemented Square Root Algorithm (#1687) * Added to maths and strings * added changes suggest by cclauss * added square root function * Fixed type hinting * fixed type error * Fixed another type error --- maths/square_root.py | 63 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 63 insertions(+) create mode 100644 maths/square_root.py diff --git a/maths/square_root.py b/maths/square_root.py new file mode 100644 index 000000000000..46e791ab5662 --- /dev/null +++ b/maths/square_root.py @@ -0,0 +1,63 @@ +import math + + +def fx(x: float, a: float) -> float: + return math.pow(x, 2) - a + + +def fx_derivative(x: float) -> float: + return 2 * x + + +def get_initial_point(a: float) -> float: + start = 2.0 + + while start <= a: + start = math.pow(start, 2) + + return start + + +def square_root_iterative( + a: float, max_iter: int = 9999, tolerance: float = 0.00000000000001 +) -> float: + """ + Sqaure root is aproximated using Newtons method. + https://en.wikipedia.org/wiki/Newton%27s_method + + >>> all(abs(square_root_iterative(i)-math.sqrt(i)) <= .00000000000001 for i in range(0, 500)) + True + + >>> square_root_iterative(-1) + Traceback (most recent call last): + ... + ValueError: math domain error + + >>> square_root_iterative(4) + 2.0 + + >>> square_root_iterative(3.2) + 1.788854381999832 + + >>> square_root_iterative(140) + 11.832159566199232 + """ + + if a < 0: + raise ValueError("math domain error") + + value = get_initial_point(a) + + for i in range(max_iter): + prev_value = value + value = value - fx(value, a) / fx_derivative(value) + if abs(prev_value - value) < tolerance: + return value + + return value + + +if __name__ == "__main__": + from doctest import testmod + + testmod() From c5b376d52db78edf6cdaef05c84a991daece80ff Mon Sep 17 00:00:00 2001 From: Sombit Bose Date: Thu, 16 Jan 2020 19:49:02 +0530 Subject: [PATCH 0436/1071] Solution for problem 30 of Euler Project (#1690) * Create soln.py Solution for problem 30 of Euler Project * Update soln.py * update soln.py modified the changes * if __name__ == "__main__": Co-authored-by: Christian Clauss --- project_euler/problem_30/soln.py | 34 ++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 project_euler/problem_30/soln.py diff --git a/project_euler/problem_30/soln.py b/project_euler/problem_30/soln.py new file mode 100644 index 000000000000..0f4df66ae16a --- /dev/null +++ b/project_euler/problem_30/soln.py @@ -0,0 +1,34 @@ +""" Problem Statement (Digit Fifth Power ): https://projecteuler.net/problem=30 + +Surprisingly there are only three numbers that can be written as the sum of fourth powers of their digits: + +1634 = 1^4 + 6^4 + 3^4 + 4^4 +8208 = 8^4 + 2^4 + 0^4 + 8^4 +9474 = 9^4 + 4^4 + 7^4 + 4^4 +As 1 = 1^4 is not a sum it is not included. + +The sum of these numbers is 1634 + 8208 + 9474 = 19316. + +Find the sum of all the numbers that can be written as the sum of fifth powers of their digits. + +(9^5)=59,049‬ +59049*7=4,13,343 (which is only 6 digit number ) +So, number greater than 9,99,999 are rejected +and also 59049*3=1,77,147 (which exceeds the criteria of number being 3 digit) +So, n>999 +and hence a bound between (1000,1000000) +""" + + +def digitsum(s: str) -> int: + """ + >>> all(digitsum(str(i)) == (1 if i == 1 else 0) for i in range(100)) + True + """ + i = sum(pow(int(c), 5) for c in s) + return i if i == int(s) else 0 + + +if __name__ == "__main__": + count = sum(digitsum(str(i)) for i in range(1000,1000000)) + print(count) # --> 443839 From c01d1787985d99e274029d024c27b7aff91683d8 Mon Sep 17 00:00:00 2001 From: Faraz Ahmed Khan <31242842+fk03983@users.noreply.github.com> Date: Fri, 17 Jan 2020 16:32:06 -0600 Subject: [PATCH 0437/1071] Added implementation for simulated annealing (#1679) * added hill climbing algorithm * Shorten long lines, streamline get_neighbors() * Update hill_climbing.py * Update and rename optimization/hill_climbing.py to searches/hill_climbing.py * added hill climbing algorithm * Shorten long lines, streamline get_neighbors() * Update hill_climbing.py * Rebased * added simulated annealing.py * added final comments and test * black formatted * restricted search domain Co-authored-by: Christian Clauss --- searches/simulated_annealing.py | 134 ++++++++++++++++++++++++++++++++ 1 file changed, 134 insertions(+) create mode 100644 searches/simulated_annealing.py diff --git a/searches/simulated_annealing.py b/searches/simulated_annealing.py new file mode 100644 index 000000000000..5fec87bed321 --- /dev/null +++ b/searches/simulated_annealing.py @@ -0,0 +1,134 @@ +# https://en.wikipedia.org/wiki/Simulated_annealing +import math, random +from hill_climbing import SearchProblem + + +def simulated_annealing( + search_prob, + find_max: bool = True, + max_x: float = math.inf, + min_x: float = -math.inf, + max_y: float = math.inf, + min_y: float = -math.inf, + visualization: bool = False, + start_temperate: float = 100, + rate_of_decrease: float = 0.01, + threshold_temp: float = 1, +) -> SearchProblem: + """ + implementation of the simulated annealing algorithm. We start with a given state, find + all its neighbors. Pick a random neighbor, if that neighbor improves the solution, we move + in that direction, if that neighbor does not improve the solution, we generate a random + real number between 0 and 1, if the number is within a certain range (calculated using + temperature) we move in that direction, else we pick another neighbor randomly and repeat the process. + Args: + search_prob: The search state at the start. + find_max: If True, the algorithm should find the minimum else the minimum. + max_x, min_x, max_y, min_y: the maximum and minimum bounds of x and y. + visualization: If True, a matplotlib graph is displayed. + start_temperate: the initial temperate of the system when the program starts. + rate_of_decrease: the rate at which the temperate decreases in each iteration. + threshold_temp: the threshold temperature below which we end the search + Returns a search state having the maximum (or minimum) score. + """ + search_end = False + current_state = search_prob + current_temp = start_temperate + scores = [] + iterations = 0 + best_state = None + + while not search_end: + current_score = current_state.score() + if best_state is None or current_score > best_state.score(): + best_state = current_state + scores.append(current_score) + iterations += 1 + next_state = None + neighbors = current_state.get_neighbors() + while ( + next_state is None and neighbors + ): # till we do not find a neighbor that we can move to + index = random.randint(0, len(neighbors) - 1) # picking a random neighbor + picked_neighbor = neighbors.pop(index) + change = picked_neighbor.score() - current_score + + if ( + picked_neighbor.x > max_x + or picked_neighbor.x < min_x + or picked_neighbor.y > max_y + or picked_neighbor.y < min_y + ): + continue # neighbor outside our bounds + + if not find_max: + change = change * -1 # incase we are finding minimum + if change > 0: # improves the solution + next_state = picked_neighbor + else: + probabililty = (math.e) ** ( + change / current_temp + ) # probability generation function + if random.random() < probabililty: # random number within probability + next_state = picked_neighbor + current_temp = current_temp - (current_temp * rate_of_decrease) + + if ( + current_temp < threshold_temp or next_state is None + ): # temperature below threshold, or + # couldnt find a suitaable neighbor + search_end = True + else: + current_state = next_state + + if visualization: + import matplotlib.pyplot as plt + + plt.plot(range(iterations), scores) + plt.xlabel("Iterations") + plt.ylabel("Function values") + plt.show() + return best_state + + +if __name__ == "__main__": + + def test_f1(x, y): + return (x ** 2) + (y ** 2) + + # starting the problem with initial coordinates (12, 47) + prob = SearchProblem(x=12, y=47, step_size=1, function_to_optimize=test_f1) + local_min = simulated_annealing( + prob, find_max=False, max_x=100, min_x=5, max_y=50, min_y=-5, visualization=True + ) + print( + "The minimum score for f(x, y) = x^2 + y^2 with the domain 100 > x > 5 " + f"and 50 > y > - 5 found via hill climbing: {local_min.score()}" + ) + + # starting the problem with initial coordinates (12, 47) + prob = SearchProblem(x=12, y=47, step_size=1, function_to_optimize=test_f1) + local_min = simulated_annealing( + prob, find_max=True, max_x=100, min_x=5, max_y=50, min_y=-5, visualization=True + ) + print( + "The maximum score for f(x, y) = x^2 + y^2 with the domain 100 > x > 5 " + f"and 50 > y > - 5 found via hill climbing: {local_min.score()}" + ) + + def test_f2(x, y): + return (3 * x ** 2) - (6 * y) + + prob = SearchProblem(x=3, y=4, step_size=1, function_to_optimize=test_f1) + local_min = simulated_annealing(prob, find_max=False, visualization=True) + print( + "The minimum score for f(x, y) = 3*x^2 - 6*y found via hill climbing: " + f"{local_min.score()}" + ) + + prob = SearchProblem(x=3, y=4, step_size=1, function_to_optimize=test_f1) + local_min = simulated_annealing(prob, find_max=True, visualization=True) + print( + "The maximum score for f(x, y) = 3*x^2 - 6*y found via hill climbing: " + f"{local_min.score()}" + ) From bfcb95b297a2b28669203642ec63b2ef6fbd8146 Mon Sep 17 00:00:00 2001 From: Christian Clauss Date: Sat, 18 Jan 2020 13:24:33 +0100 Subject: [PATCH 0438/1071] Create codespell.yml (#1698) * fixup! Format Python code with psf/black push * Create codespell.yml * fixup! Format Python code with psf/black push --- .github/workflows/codespell.yml | 14 ++++++++ DIRECTORY.md | 12 ++++--- .../{2D_problems.JPG => 2D_problems.jpg} | Bin .../{2D_problems_1.JPG => 2D_problems_1.jpg} | Bin backtracking/n_queens.py | 8 ++--- ciphers/hill_cipher.py | 6 ++-- ciphers/rsa_cipher.py | 2 +- ciphers/rsa_key_generator.py | 8 ++--- compression/burrows_wheeler.py | 4 +-- compression/huffman.py | 2 +- compression/peak_signal_to_noise_ratio.py | 2 +- conversions/decimal_to_octal.py | 2 +- .../binary_tree/binary_search_tree.py | 2 +- .../binary_tree/lowest_common_ancestor.py | 2 +- .../number_of_possible_binary_trees.py | 2 +- data_structures/binary_tree/red_black_tree.py | 6 ++-- data_structures/binary_tree/treap.py | 4 +-- data_structures/heap/binomial_heap.py | 2 +- data_structures/heap/heap.py | 17 +++++----- data_structures/linked_list/deque_doubly.py | 2 +- .../linked_list/doubly_linked_list.py | 2 +- .../linked_list/singly_linked_list.py | 2 +- data_structures/linked_list/swap_nodes.py | 2 +- data_structures/stacks/postfix_evaluation.py | 2 +- data_structures/stacks/stock_span_problem.py | 2 +- digital_image_processing/index_calculation.py | 6 ++-- .../test_digital_image_processing.py | 2 +- dynamic_programming/bitmask.py | 2 +- dynamic_programming/knapsack.py | 4 +-- dynamic_programming/longest_sub_array.py | 2 +- ...e.py => max_sum_contiguous_subsequence.py} | 0 dynamic_programming/subset_generation.py | 23 ++++++------- .../{recieve_file.py => receive_file.py} | 0 graphs/a_star.py | 2 +- graphs/basic_graphs.py | 2 +- graphs/breadth_first_search.py | 8 ++--- graphs/depth_first_search.py | 6 ++-- graphs/dijkstra.py | 4 +-- graphs/dijkstra_algorithm.py | 6 ++-- ...irected_and_undirected_(weighted)_graph.py | 18 +++++----- ...stic_astar.py => multi_heuristic_astar.py} | 0 hashes/sha1.py | 4 +-- linear_algebra/README.md | 2 +- machine_learning/k_nearest_neighbours.py | 2 +- machine_learning/scoring_functions.py | 2 +- .../sequential_minimum_optimization.py | 31 ++++++++++-------- machine_learning/support_vector_machines.py | 2 +- maths/matrix_exponentiation.py | 4 +-- maths/mobius_function.py | 2 +- maths/prime_sieve_eratosthenes.py | 10 +++--- maths/simpson_rule.py | 2 +- maths/square_root.py | 2 +- maths/trapezoidal_rule.py | 2 +- maths/zellers_congruence.py | 16 ++++----- matrix/matrix_class.py | 2 +- matrix/spiral_print.py | 2 +- networking_flow/minimum_cut.py | 2 +- neural_network/convolution_neural_network.py | 24 +++++++------- neural_network/perceptron.py | 4 +-- other/linear_congruential_generator.py | 2 +- other/primelib.py | 6 ++-- project_euler/problem_04/sol1.py | 2 +- project_euler/problem_30/soln.py | 2 +- project_euler/problem_551/sol1.py | 4 +-- searches/hill_climbing.py | 2 +- searches/interpolation_search.py | 4 +-- searches/jump_search.py | 8 ++--- searches/simulated_annealing.py | 8 ++--- searches/tabu_search.py | 2 +- sorts/bitonic_sort.py | 2 +- sorts/double_sort.py | 6 ++-- sorts/pigeonhole_sort.py | 2 +- sorts/recursive_insertion_sort.py | 2 +- strings/aho-corasick.py | 2 +- strings/boyer_moore_search.py | 6 ++-- strings/manacher.py | 9 ++--- strings/split.py | 12 +++---- .../{word_occurence.py => word_occurrence.py} | 6 ++-- 78 files changed, 206 insertions(+), 188 deletions(-) create mode 100644 .github/workflows/codespell.yml rename arithmetic_analysis/image_data/{2D_problems.JPG => 2D_problems.jpg} (100%) rename arithmetic_analysis/image_data/{2D_problems_1.JPG => 2D_problems_1.jpg} (100%) rename dynamic_programming/{max_sum_contigous_subsequence.py => max_sum_contiguous_subsequence.py} (100%) rename file_transfer/{recieve_file.py => receive_file.py} (100%) rename graphs/{multi_hueristic_astar.py => multi_heuristic_astar.py} (100%) rename strings/{word_occurence.py => word_occurrence.py} (87%) diff --git a/.github/workflows/codespell.yml b/.github/workflows/codespell.yml new file mode 100644 index 000000000000..1e9b052cafe8 --- /dev/null +++ b/.github/workflows/codespell.yml @@ -0,0 +1,14 @@ +# GitHub Action to automate the identification of common misspellings in text files +# https://github.com/codespell-project/codespell +name: codespell +on: [push, pull_request] +jobs: + codespell: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: actions/setup-python@v1 + - run: pip install codespell flake8 + - run: | + SKIP="./.*,./other/dictionary.txt,./other/words,./project_euler/problem_22/p022_names.txt,*.bak,*.gif,*.jpeg,*.jpg,*.json,*.png,*.pyc" + codespell -L ans,fo,hist,iff,secant,tim --skip=$SKIP diff --git a/DIRECTORY.md b/DIRECTORY.md index 75dea10d34dd..eb17b3a7e78e 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -177,14 +177,14 @@ * [Longest Sub Array](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/longest_sub_array.py) * [Matrix Chain Order](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/matrix_chain_order.py) * [Max Sub Array](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/max_sub_array.py) - * [Max Sum Contigous Subsequence](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/max_sum_contigous_subsequence.py) + * [Max Sum Contiguous Subsequence](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/max_sum_contiguous_subsequence.py) * [Minimum Partition](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/minimum_partition.py) * [Rod Cutting](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/rod_cutting.py) * [Subset Generation](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/subset_generation.py) * [Sum Of Subset](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/sum_of_subset.py) ## File Transfer - * [Recieve File](https://github.com/TheAlgorithms/Python/blob/master/file_transfer/recieve_file.py) + * [Receive File](https://github.com/TheAlgorithms/Python/blob/master/file_transfer/receive_file.py) * [Send File](https://github.com/TheAlgorithms/Python/blob/master/file_transfer/send_file.py) ## Fuzzy Logic @@ -219,7 +219,7 @@ * [Kahns Algorithm Topo](https://github.com/TheAlgorithms/Python/blob/master/graphs/kahns_algorithm_topo.py) * [Minimum Spanning Tree Kruskal](https://github.com/TheAlgorithms/Python/blob/master/graphs/minimum_spanning_tree_kruskal.py) * [Minimum Spanning Tree Prims](https://github.com/TheAlgorithms/Python/blob/master/graphs/minimum_spanning_tree_prims.py) - * [Multi Hueristic Astar](https://github.com/TheAlgorithms/Python/blob/master/graphs/multi_hueristic_astar.py) + * [Multi Heuristic Astar](https://github.com/TheAlgorithms/Python/blob/master/graphs/multi_heuristic_astar.py) * [Page Rank](https://github.com/TheAlgorithms/Python/blob/master/graphs/page_rank.py) * [Prim](https://github.com/TheAlgorithms/Python/blob/master/graphs/prim.py) * [Scc Kosaraju](https://github.com/TheAlgorithms/Python/blob/master/graphs/scc_kosaraju.py) @@ -319,6 +319,7 @@ * [Sieve Of Eratosthenes](https://github.com/TheAlgorithms/Python/blob/master/maths/sieve_of_eratosthenes.py) * [Simpson Rule](https://github.com/TheAlgorithms/Python/blob/master/maths/simpson_rule.py) * [Softmax](https://github.com/TheAlgorithms/Python/blob/master/maths/softmax.py) + * [Square Root](https://github.com/TheAlgorithms/Python/blob/master/maths/square_root.py) * [Sum Of Arithmetic Series](https://github.com/TheAlgorithms/Python/blob/master/maths/sum_of_arithmetic_series.py) * [Test Prime Check](https://github.com/TheAlgorithms/Python/blob/master/maths/test_prime_check.py) * [Trapezoidal Rule](https://github.com/TheAlgorithms/Python/blob/master/maths/trapezoidal_rule.py) @@ -469,6 +470,8 @@ * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_28/sol1.py) * Problem 29 * [Solution](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_29/solution.py) + * Problem 30 + * [Soln](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_30/soln.py) * Problem 31 * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_31/sol1.py) * Problem 32 @@ -508,6 +511,7 @@ * [Quick Select](https://github.com/TheAlgorithms/Python/blob/master/searches/quick_select.py) * [Sentinel Linear Search](https://github.com/TheAlgorithms/Python/blob/master/searches/sentinel_linear_search.py) * [Simple-Binary-Search](https://github.com/TheAlgorithms/Python/blob/master/searches/simple-binary-search.py) + * [Simulated Annealing](https://github.com/TheAlgorithms/Python/blob/master/searches/simulated_annealing.py) * [Tabu Search](https://github.com/TheAlgorithms/Python/blob/master/searches/tabu_search.py) * [Ternary Search](https://github.com/TheAlgorithms/Python/blob/master/searches/ternary_search.py) @@ -564,7 +568,7 @@ * [Reverse Words](https://github.com/TheAlgorithms/Python/blob/master/strings/reverse_words.py) * [Split](https://github.com/TheAlgorithms/Python/blob/master/strings/split.py) * [Upper](https://github.com/TheAlgorithms/Python/blob/master/strings/upper.py) - * [Word Occurence](https://github.com/TheAlgorithms/Python/blob/master/strings/word_occurence.py) + * [Word Occurrence](https://github.com/TheAlgorithms/Python/blob/master/strings/word_occurrence.py) ## Traversals * [Binary Tree Traversals](https://github.com/TheAlgorithms/Python/blob/master/traversals/binary_tree_traversals.py) diff --git a/arithmetic_analysis/image_data/2D_problems.JPG b/arithmetic_analysis/image_data/2D_problems.jpg similarity index 100% rename from arithmetic_analysis/image_data/2D_problems.JPG rename to arithmetic_analysis/image_data/2D_problems.jpg diff --git a/arithmetic_analysis/image_data/2D_problems_1.JPG b/arithmetic_analysis/image_data/2D_problems_1.jpg similarity index 100% rename from arithmetic_analysis/image_data/2D_problems_1.JPG rename to arithmetic_analysis/image_data/2D_problems_1.jpg diff --git a/backtracking/n_queens.py b/backtracking/n_queens.py index f95357c82e21..c0db41496aee 100644 --- a/backtracking/n_queens.py +++ b/backtracking/n_queens.py @@ -40,8 +40,8 @@ def isSafe(board, row, column): def solve(board, row): """ - It creates a state space tree and calls the safe function untill it receives a - False Boolean and terminates that brach and backtracks to the next + It creates a state space tree and calls the safe function until it receives a + False Boolean and terminates that branch and backtracks to the next poosible solution branch. """ if row >= len(board): @@ -58,7 +58,7 @@ def solve(board, row): """ For every row it iterates through each column to check if it is feesible to place a queen there. - If all the combinations for that particaular branch are successfull the board is + If all the combinations for that particular branch are successful the board is reinitialized for the next possible combination. """ if isSafe(board, row, i): @@ -70,7 +70,7 @@ def solve(board, row): def printboard(board): """ - Prints the boards that have a successfull combination. + Prints the boards that have a successful combination. """ for i in range(len(board)): for j in range(len(board)): diff --git a/ciphers/hill_cipher.py b/ciphers/hill_cipher.py index 05f716f8595e..ffc1d9793bf2 100644 --- a/ciphers/hill_cipher.py +++ b/ciphers/hill_cipher.py @@ -3,15 +3,15 @@ Hill Cipher: The below defined class 'HillCipher' implements the Hill Cipher algorithm. The Hill Cipher is an algorithm that implements modern linear algebra techniques -In this algortihm, you have an encryption key matrix. This is what will be used +In this algorithm, you have an encryption key matrix. This is what will be used in encoding and decoding your text. -Algortihm: +Algorithm: Let the order of the encryption key be N (as it is a square matrix). Your text is divided into batches of length N and converted to numerical vectors by a simple mapping starting with A=0 and so on. -The key is then mulitplied with the newly created batch vector to obtain the +The key is then multiplied with the newly created batch vector to obtain the encoded vector. After each multiplication modular 36 calculations are performed on the vectors so as to bring the numbers between 0 and 36 and then mapped with their corresponding alphanumerics. diff --git a/ciphers/rsa_cipher.py b/ciphers/rsa_cipher.py index da3778ae96f0..3b9f5c143c85 100644 --- a/ciphers/rsa_cipher.py +++ b/ciphers/rsa_cipher.py @@ -6,7 +6,7 @@ def main(): filename = "encrypted_file.txt" - response = input(r"Encrypte\Decrypt [e\d]: ") + response = input(r"Encrypt\Decrypt [e\d]: ") if response.lower().startswith("e"): mode = "encrypt" diff --git a/ciphers/rsa_key_generator.py b/ciphers/rsa_key_generator.py index 729d31c08a02..0df31f0413c6 100644 --- a/ciphers/rsa_key_generator.py +++ b/ciphers/rsa_key_generator.py @@ -42,12 +42,12 @@ def makeKeyFiles(name, keySize): publicKey, privateKey = generateKey(keySize) print("\nWriting public key to file %s_pubkey.txt..." % name) - with open("%s_pubkey.txt" % name, "w") as fo: - fo.write("{},{},{}".format(keySize, publicKey[0], publicKey[1])) + with open("%s_pubkey.txt" % name, "w") as out_file: + out_file.write("{},{},{}".format(keySize, publicKey[0], publicKey[1])) print("Writing private key to file %s_privkey.txt..." % name) - with open("%s_privkey.txt" % name, "w") as fo: - fo.write("{},{},{}".format(keySize, privateKey[0], privateKey[1])) + with open("%s_privkey.txt" % name, "w") as out_file: + out_file.write("{},{},{}".format(keySize, privateKey[0], privateKey[1])) if __name__ == "__main__": diff --git a/compression/burrows_wheeler.py b/compression/burrows_wheeler.py index 1c7939b39038..2a08c6092cda 100644 --- a/compression/burrows_wheeler.py +++ b/compression/burrows_wheeler.py @@ -157,11 +157,11 @@ def reverse_bwt(bwt_string: str, idx_original_string: int) -> str: entry_msg = "Provide a string that I will generate its BWT transform: " s = input(entry_msg).strip() result = bwt_transform(s) - bwt_output_msg = "Burrows Wheeler tranform for string '{}' results in '{}'" + bwt_output_msg = "Burrows Wheeler transform for string '{}' results in '{}'" print(bwt_output_msg.format(s, result["bwt_string"])) original_string = reverse_bwt(result["bwt_string"], result["idx_original_string"]) fmt = ( - "Reversing Burrows Wheeler tranform for entry '{}' we get original" + "Reversing Burrows Wheeler transform for entry '{}' we get original" " string '{}'" ) print(fmt.format(result["bwt_string"], original_string)) diff --git a/compression/huffman.py b/compression/huffman.py index 73c084351c85..3a3cbfa4b0c6 100644 --- a/compression/huffman.py +++ b/compression/huffman.py @@ -21,7 +21,7 @@ def __init__(self, freq, left, right): def parse_file(file_path): """ Read the file and build a dict of all letters and their - frequences, then convert the dict into a list of Letters. + frequencies, then convert the dict into a list of Letters. """ chars = {} with open(file_path) as f: diff --git a/compression/peak_signal_to_noise_ratio.py b/compression/peak_signal_to_noise_ratio.py index 5853aace8f96..f4a1ca41e14d 100644 --- a/compression/peak_signal_to_noise_ratio.py +++ b/compression/peak_signal_to_noise_ratio.py @@ -1,6 +1,6 @@ """ Peak signal-to-noise ratio - PSNR - https://en.wikipedia.org/wiki/Peak_signal-to-noise_ratio - Soruce: https://tutorials.techonical.com/how-to-calculate-psnr-value-of-two-images-using-python/ + Source: https://tutorials.techonical.com/how-to-calculate-psnr-value-of-two-images-using-python/ """ import math diff --git a/conversions/decimal_to_octal.py b/conversions/decimal_to_octal.py index b1829f1a3973..a89a2be982b8 100644 --- a/conversions/decimal_to_octal.py +++ b/conversions/decimal_to_octal.py @@ -24,7 +24,7 @@ def decimal_to_octal(num: int) -> str: def main(): - """Print octal equivelents of decimal numbers.""" + """Print octal equivalents of decimal numbers.""" print("\n2 in octal is:") print(decimal_to_octal(2)) # = 2 print("\n8 in octal is:") diff --git a/data_structures/binary_tree/binary_search_tree.py b/data_structures/binary_tree/binary_search_tree.py index c3c97bb02003..fe163132cdf3 100644 --- a/data_structures/binary_tree/binary_search_tree.py +++ b/data_structures/binary_tree/binary_search_tree.py @@ -15,7 +15,7 @@ def __repr__(self): if self.left is None and self.right is None: return str(self.value) - return pformat({"%s" % (self.value): (self.left, self.right)}, indent=1,) + return pformat({"%s" % (self.value): (self.left, self.right)}, indent=1) class BinarySearchTree: diff --git a/data_structures/binary_tree/lowest_common_ancestor.py b/data_structures/binary_tree/lowest_common_ancestor.py index 2109500a2581..f560eaa5ef29 100644 --- a/data_structures/binary_tree/lowest_common_ancestor.py +++ b/data_structures/binary_tree/lowest_common_ancestor.py @@ -11,7 +11,7 @@ def swap(a, b): return a, b -# creating sparse table which saves each nodes 2^ith parent +# creating sparse table which saves each nodes 2^i-th parent def creatSparse(max_node, parent): j = 1 while (1 << j) < max_node: diff --git a/data_structures/binary_tree/number_of_possible_binary_trees.py b/data_structures/binary_tree/number_of_possible_binary_trees.py index 71670d9691ac..d053ba31171f 100644 --- a/data_structures/binary_tree/number_of_possible_binary_trees.py +++ b/data_structures/binary_tree/number_of_possible_binary_trees.py @@ -41,7 +41,7 @@ def binomial_coefficient(n: int, k: int) -> int: def catalan_number(node_count: int) -> int: """ - We can find Catalan number many ways but here we use Binomial Coefficent because it + We can find Catalan number many ways but here we use Binomial Coefficient because it does the job in O(n) return the Catalan number of n using 2nCn/(n+1). diff --git a/data_structures/binary_tree/red_black_tree.py b/data_structures/binary_tree/red_black_tree.py index 0884766504de..f038b587616d 100644 --- a/data_structures/binary_tree/red_black_tree.py +++ b/data_structures/binary_tree/red_black_tree.py @@ -12,7 +12,7 @@ class RedBlackTree: less strict, so it will perform faster for writing/deleting nodes and slower for reading in the average case, though, because they're both balanced binary search trees, both will get the same asymptotic - perfomance. + performance. To read more about them, https://en.wikipedia.org/wiki/Red–black_tree Unless otherwise specified, all asymptotic runtimes are specified in terms of the size of the tree. @@ -37,7 +37,7 @@ def __init__(self, label=None, color=0, parent=None, left=None, right=None): def rotate_left(self): """Rotate the subtree rooted at this node to the left and returns the new root to this subtree. - Perfoming one rotation can be done in O(1). + Performing one rotation can be done in O(1). """ parent = self.parent right = self.right @@ -656,7 +656,7 @@ def test_tree_traversal(): def test_tree_chaining(): - """Tests the three different tree chaning functions.""" + """Tests the three different tree chaining functions.""" tree = RedBlackTree(0) tree = tree.insert(-16).insert(16).insert(8).insert(24).insert(20).insert(22) if list(tree.inorder_traverse()) != [-16, 0, 8, 16, 20, 22, 24]: diff --git a/data_structures/binary_tree/treap.py b/data_structures/binary_tree/treap.py index d2e3fb88e8f7..26f021445ca4 100644 --- a/data_structures/binary_tree/treap.py +++ b/data_structures/binary_tree/treap.py @@ -21,7 +21,7 @@ def __repr__(self): return f"'{self.value}: {self.prior:.5}'" else: return pformat( - {f"{self.value}: {self.prior:.5}": (self.left, self.right)}, indent=1, + {f"{self.value}: {self.prior:.5}": (self.left, self.right)}, indent=1 ) def __str__(self): @@ -161,7 +161,7 @@ def main(): """After each command, program prints treap""" root = None print( - "enter numbers to creat a tree, + value to add value into treap, - value to erase all nodes with value. 'q' to quit. " + "enter numbers to create a tree, + value to add value into treap, - value to erase all nodes with value. 'q' to quit. " ) args = input() diff --git a/data_structures/heap/binomial_heap.py b/data_structures/heap/binomial_heap.py index 7f570f1c755b..ac244023082a 100644 --- a/data_structures/heap/binomial_heap.py +++ b/data_structures/heap/binomial_heap.py @@ -49,7 +49,7 @@ class BinomialHeap: r""" Min-oriented priority queue implemented with the Binomial Heap data structure implemented with the BinomialHeap class. It supports: - - Insert element in a heap with n elemnts: Guaranteed logn, amoratized 1 + - Insert element in a heap with n elements: Guaranteed logn, amoratized 1 - Merge (meld) heaps of size m and n: O(logn + logm) - Delete Min: O(logn) - Peek (return min without deleting it): O(1) diff --git a/data_structures/heap/heap.py b/data_structures/heap/heap.py index e4fe8f81154b..b901c54a4284 100644 --- a/data_structures/heap/heap.py +++ b/data_structures/heap/heap.py @@ -23,6 +23,7 @@ class Heap(object): [1, 5, 7, 9, 11, 15, 25, 100, 103, 107, 201] >>> """ + def __init__(self): self.h = [] self.curr_size = 0 @@ -107,28 +108,28 @@ def main(): [2, 5, 3, 0, 2, 3, 0, 3], [6, 1, 2, 7, 9, 3, 4, 5, 10, 8], [103, 9, 1, 7, 11, 15, 25, 201, 209, 107, 5], - [-45, -2, -5] + [-45, -2, -5], ]: - print('source unsorted list: %s' % unsorted) + print("source unsorted list: %s" % unsorted) h = Heap() h.build_heap(unsorted) - print('after build heap: ', end=' ') + print("after build heap: ", end=" ") h.display() - print('max value: %s' % h.get_max()) - print('delete max value: ', end=' ') + print("max value: %s" % h.get_max()) + print("delete max value: ", end=" ") h.display() h.insert(100) - print('after insert new value 100: ', end=' ') + print("after insert new value 100: ", end=" ") h.display() h.heap_sort() - print('heap sort: ', end=' ') + print("heap sort: ", end=" ") h.display() print() -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/data_structures/linked_list/deque_doubly.py b/data_structures/linked_list/deque_doubly.py index abc7bc1f769f..0898db679802 100644 --- a/data_structures/linked_list/deque_doubly.py +++ b/data_structures/linked_list/deque_doubly.py @@ -3,7 +3,7 @@ Operations: 1. insertion in the front -> O(1) 2. insertion in the end -> O(1) - 3. remove fron the front -> O(1) + 3. remove from the front -> O(1) 4. remove from the end -> O(1) """ diff --git a/data_structures/linked_list/doubly_linked_list.py b/data_structures/linked_list/doubly_linked_list.py index 2864356c1d19..27b04ed39ad2 100644 --- a/data_structures/linked_list/doubly_linked_list.py +++ b/data_structures/linked_list/doubly_linked_list.py @@ -3,7 +3,7 @@ - This is an example of a double ended, doubly linked list. - Each link references the next link and the previous one. - A Doubly Linked List (DLL) contains an extra pointer, typically called previous pointer, together with next pointer and data which are there in singly linked list. - - Advantages over SLL - IT can be traversed in both forward and backward direction.,Delete operation is more efficent""" + - Advantages over SLL - IT can be traversed in both forward and backward direction.,Delete operation is more efficient""" class LinkedList: # making main class named linked list diff --git a/data_structures/linked_list/singly_linked_list.py b/data_structures/linked_list/singly_linked_list.py index fb04ce10398e..c90cfb6e59a9 100644 --- a/data_structures/linked_list/singly_linked_list.py +++ b/data_structures/linked_list/singly_linked_list.py @@ -79,7 +79,7 @@ def __repr__(self): # String representation/visualization of a Linked Lists # END represents end of the LinkedList return string_repr + "END" - # Indexing Support. Used to get a node at particaular position + # Indexing Support. Used to get a node at particular position def __getitem__(self, index): current = self.head diff --git a/data_structures/linked_list/swap_nodes.py b/data_structures/linked_list/swap_nodes.py index bb177f419c30..3f825756b3d2 100644 --- a/data_structures/linked_list/swap_nodes.py +++ b/data_structures/linked_list/swap_nodes.py @@ -14,7 +14,7 @@ def __init__(self): def print_list(self): temp = self.head while temp is not None: - print(temp.data, end=' ') + print(temp.data, end=" ") temp = temp.next print() diff --git a/data_structures/stacks/postfix_evaluation.py b/data_structures/stacks/postfix_evaluation.py index 22836cdcfcb1..4cee0ba380b0 100644 --- a/data_structures/stacks/postfix_evaluation.py +++ b/data_structures/stacks/postfix_evaluation.py @@ -54,7 +54,7 @@ def Solve(Postfix): Stack.append( str(Opr[x](int(A), int(B))) - ) # evaluate the 2 values poped from stack & push result to stack + ) # evaluate the 2 values popped from stack & push result to stack print( x.rjust(8), ("push(" + A + x + B + ")").ljust(12), diff --git a/data_structures/stacks/stock_span_problem.py b/data_structures/stacks/stock_span_problem.py index 45cd6bae1282..cc2adfdd6c21 100644 --- a/data_structures/stacks/stock_span_problem.py +++ b/data_structures/stacks/stock_span_problem.py @@ -21,7 +21,7 @@ def calculateSpan(price, S): # Calculate span values for rest of the elements for i in range(1, n): - # Pop elements from stack whlie stack is not + # Pop elements from stack while stack is not # empty and top of stack is smaller than price[i] while len(st) > 0 and price[st[0]] <= price[i]: st.pop() diff --git a/digital_image_processing/index_calculation.py b/digital_image_processing/index_calculation.py index 0786314e1223..a34e51e56310 100644 --- a/digital_image_processing/index_calculation.py +++ b/digital_image_processing/index_calculation.py @@ -541,7 +541,7 @@ def NDRE(self): # instantiating the class with the values #cl = indexCalculation(red=red, green=green, blue=blue, redEdge=redEdge, nir=nir) -# how set the values after instantiate the class cl, (for update the data or when dont +# how set the values after instantiate the class cl, (for update the data or when don't # instantiating the class with the values) cl.setMatrices(red=red, green=green, blue=blue, redEdge=redEdge, nir=nir) @@ -551,8 +551,8 @@ def NDRE(self): redEdge=redEdge, nir=nir).astype(np.float64) indexValue_form2 = cl.CCCI() -# calculating the index with the values directly -- you can set just the values preferred -- -# note: the *calculation* fuction performs the function *setMatrices* +# calculating the index with the values directly -- you can set just the values +# preferred note: the *calculation* function performs the function *setMatrices* indexValue_form3 = cl.calculation("CCCI", red=red, green=green, blue=blue, redEdge=redEdge, nir=nir).astype(np.float64) diff --git a/digital_image_processing/test_digital_image_processing.py b/digital_image_processing/test_digital_image_processing.py index 1a730b39101b..b9a7211109a8 100644 --- a/digital_image_processing/test_digital_image_processing.py +++ b/digital_image_processing/test_digital_image_processing.py @@ -42,7 +42,7 @@ def test_gen_gaussian_kernel(): # canny.py def test_canny(): canny_img = imread("digital_image_processing/image_data/lena_small.jpg", 0) - # assert ambiguos array for all == True + # assert ambiguous array for all == True assert canny_img.all() canny_array = canny.canny(canny_img) # assert canny array for at least one True diff --git a/dynamic_programming/bitmask.py b/dynamic_programming/bitmask.py index 5c1ed36cb42a..1841f1747557 100644 --- a/dynamic_programming/bitmask.py +++ b/dynamic_programming/bitmask.py @@ -42,7 +42,7 @@ def CountWaysUtil(self, mask, taskno): if self.dp[mask][taskno] != -1: return self.dp[mask][taskno] - # Number of ways when we dont this task in the arrangement + # Number of ways when we don't this task in the arrangement total_ways_util = self.CountWaysUtil(mask, taskno + 1) # now assign the tasks one by one to all possible persons and recursively assign for the remaining tasks. diff --git a/dynamic_programming/knapsack.py b/dynamic_programming/knapsack.py index aefaa6bade96..1987dc35fd03 100644 --- a/dynamic_programming/knapsack.py +++ b/dynamic_programming/knapsack.py @@ -49,9 +49,9 @@ def knapsack_with_example_solution(W: int, wt: list, val: list): W: int, the total maximum weight for the given knapsack problem. wt: list, the vector of weights for all items where wt[i] is the weight - of the ith item. + of the i-th item. val: list, the vector of values for all items where val[i] is the value - of te ith item + of the i-th item Returns ------- diff --git a/dynamic_programming/longest_sub_array.py b/dynamic_programming/longest_sub_array.py index 65ce151c33d6..f3b5705b7de2 100644 --- a/dynamic_programming/longest_sub_array.py +++ b/dynamic_programming/longest_sub_array.py @@ -1,5 +1,5 @@ """ -Auther : Yvonne +Author : Yvonne This is a pure Python implementation of Dynamic Programming solution to the longest_sub_array problem. diff --git a/dynamic_programming/max_sum_contigous_subsequence.py b/dynamic_programming/max_sum_contiguous_subsequence.py similarity index 100% rename from dynamic_programming/max_sum_contigous_subsequence.py rename to dynamic_programming/max_sum_contiguous_subsequence.py diff --git a/dynamic_programming/subset_generation.py b/dynamic_programming/subset_generation.py index 2cca97fc3cbc..196b81c22045 100644 --- a/dynamic_programming/subset_generation.py +++ b/dynamic_programming/subset_generation.py @@ -1,10 +1,10 @@ -# python program to print all subset combination of n element in given set of r element . +# Python program to print all subset combinations of n element in given set of r element. # arr[] ---> Input Array # data[] ---> Temporary array to store current combination # start & end ---> Staring and Ending indexes in arr[] # index ---> Current index in data[] # r ---> Size of a combination to be printed -def combinationUtil(arr, n, r, index, data, i): +def combination_util(arr, n, r, index, data, i): # Current combination is ready to be printed, # print it if index == r: @@ -15,29 +15,26 @@ def combinationUtil(arr, n, r, index, data, i): # When no more elements are there to put in data[] if i >= n: return - # current is included, put next at next - # location + # current is included, put next at next location data[index] = arr[i] - combinationUtil(arr, n, r, index + 1, data, i + 1) + combination_util(arr, n, r, index + 1, data, i + 1) # current is excluded, replace it with # next (Note that i+1 is passed, but # index is not changed) - combinationUtil(arr, n, r, index, data, i + 1) + combination_util(arr, n, r, index, data, i + 1) # The main function that prints all combinations # of size r in arr[] of size n. This function # mainly uses combinationUtil() -def printcombination(arr, n, r): - # A temporary array to store all combination - # one by one +def print_combination(arr, n, r): + # A temporary array to store all combination one by one data = [0] * r - # Print all combination using temprary - # array 'data[]' - combinationUtil(arr, n, r, 0, data, 0) + # Print all combination using temporary array 'data[]' + combination_util(arr, n, r, 0, data, 0) # Driver function to check for above function arr = [10, 20, 30, 40, 50] -printcombination(arr, len(arr), 3) +print_combination(arr, len(arr), 3) # This code is contributed by Ambuj sahu diff --git a/file_transfer/recieve_file.py b/file_transfer/receive_file.py similarity index 100% rename from file_transfer/recieve_file.py rename to file_transfer/receive_file.py diff --git a/graphs/a_star.py b/graphs/a_star.py index e1d17fc55434..93ec26e7c496 100644 --- a/graphs/a_star.py +++ b/graphs/a_star.py @@ -35,7 +35,7 @@ def search(grid, init, goal, cost, heuristic): closed = [ [0 for col in range(len(grid[0]))] for row in range(len(grid)) - ] # the referrence grid + ] # the reference grid closed[init[0]][init[1]] = 1 action = [ [0 for col in range(len(grid[0]))] for row in range(len(grid)) diff --git a/graphs/basic_graphs.py b/graphs/basic_graphs.py index 070af5f55f01..8cdde6abc819 100644 --- a/graphs/basic_graphs.py +++ b/graphs/basic_graphs.py @@ -69,7 +69,7 @@ def dfs(G, s): Args : G - Dictionary of edges s - Starting Node Vars : vis - Set of visited nodes - Q - Traveral Stack + Q - Traversal Stack -------------------------------------------------------------------------------- """ from collections import deque diff --git a/graphs/breadth_first_search.py b/graphs/breadth_first_search.py index faa166150c76..bfb3c4e2c376 100644 --- a/graphs/breadth_first_search.py +++ b/graphs/breadth_first_search.py @@ -7,12 +7,12 @@ class Graph: def __init__(self): self.vertex = {} - # for printing the Graph vertexes + # for printing the Graph vertices def printGraph(self): for i in self.vertex.keys(): print(i, " -> ", " -> ".join([str(j) for j in self.vertex[i]])) - # for adding the edge beween two vertexes + # for adding the edge between two vertices def addEdge(self, fromVertex, toVertex): # check if vertex is already present, if fromVertex in self.vertex.keys(): @@ -22,10 +22,10 @@ def addEdge(self, fromVertex, toVertex): self.vertex[fromVertex] = [toVertex] def BFS(self, startVertex): - # Take a list for stoting already visited vertexes + # Take a list for stoting already visited vertices visited = [False] * len(self.vertex) - # create a list to store all the vertexes for BFS + # create a list to store all the vertices for BFS queue = [] # mark the source node as visited and enqueue it diff --git a/graphs/depth_first_search.py b/graphs/depth_first_search.py index 2fe9dd157d2d..0593e120b1da 100644 --- a/graphs/depth_first_search.py +++ b/graphs/depth_first_search.py @@ -7,13 +7,13 @@ class Graph: def __init__(self): self.vertex = {} - # for printing the Graph vertexes + # for printing the Graph vertices def printGraph(self): print(self.vertex) for i in self.vertex.keys(): print(i, " -> ", " -> ".join([str(j) for j in self.vertex[i]])) - # for adding the edge beween two vertexes + # for adding the edge between two vertices def addEdge(self, fromVertex, toVertex): # check if vertex is already present, if fromVertex in self.vertex.keys(): @@ -37,7 +37,7 @@ def DFSRec(self, startVertex, visited): print(startVertex, end=" ") - # Recur for all the vertexes that are adjacent to this node + # Recur for all the vertices that are adjacent to this node for i in self.vertex.keys(): if visited[i] == False: self.DFSRec(i, visited) diff --git a/graphs/dijkstra.py b/graphs/dijkstra.py index 195f4e02d409..f156602beb6e 100644 --- a/graphs/dijkstra.py +++ b/graphs/dijkstra.py @@ -22,7 +22,7 @@ 13 - add (total_cost,V) to H You can think at cost as a distance where Dijkstra finds the shortest distance -between vertexes s and v in a graph G. The use of a min heap as H guarantees +between vertices s and v in a graph G. The use of a min heap as H guarantees that if a vertex has already been explored there will be no other path with shortest distance, that happens because heapq.heappop will always return the next vertex with the shortest distance, considering that the heap stores not @@ -35,7 +35,7 @@ def dijkstra(graph, start, end): - """Return the cost of the shortest path between vertexes start and end. + """Return the cost of the shortest path between vertices start and end. >>> dijkstra(G, "E", "C") 6 diff --git a/graphs/dijkstra_algorithm.py b/graphs/dijkstra_algorithm.py index 7dfb5fb9df48..6b64834acd81 100644 --- a/graphs/dijkstra_algorithm.py +++ b/graphs/dijkstra_algorithm.py @@ -5,7 +5,7 @@ import math import sys -# For storing the vertex set to retreive node with the lowest distance +# For storing the vertex set to retrieve node with the lowest distance class PriorityQueue: @@ -103,9 +103,7 @@ def add_edge(self, u, v, w): def show_graph(self): # u -> v(w) for u in self.adjList: - print( - u, "->", " -> ".join(str(f"{v}({w})") for v, w in self.adjList[u]), - ) + print(u, "->", " -> ".join(str(f"{v}({w})") for v, w in self.adjList[u])) def dijkstra(self, src): # Flush old junk values in par[] diff --git a/graphs/directed_and_undirected_(weighted)_graph.py b/graphs/directed_and_undirected_(weighted)_graph.py index 883a8a00c6b1..15e2ce663594 100644 --- a/graphs/directed_and_undirected_(weighted)_graph.py +++ b/graphs/directed_and_undirected_(weighted)_graph.py @@ -3,7 +3,7 @@ import math as math import time -# the dfault weight is 1 if not assigend but all the implementation is weighted +# the dfault weight is 1 if not assigned but all the implementation is weighted class DirectedGraph: @@ -12,7 +12,7 @@ def __init__(self): # adding vertices and edges # adding the weight is optional - # handels repetition + # handles repetition def add_pair(self, u, v, w=1): if self.graph.get(u): if self.graph[u].count([w, v]) == 0: @@ -25,14 +25,14 @@ def add_pair(self, u, v, w=1): def all_nodes(self): return list(self.graph) - # handels if the input does not exist + # handles if the input does not exist def remove_pair(self, u, v): if self.graph.get(u): for _ in self.graph[u]: if _[1] == v: self.graph[u].remove(_) - # if no destination is meant the defaut value is -1 + # if no destination is meant the default value is -1 def dfs(self, s=-2, d=-1): if s == d: return [] @@ -71,7 +71,7 @@ def dfs(self, s=-2, d=-1): if len(stack) == 0: return visited - # c is the count of nodes you want and if you leave it or pass -1 to the funtion the count + # c is the count of nodes you want and if you leave it or pass -1 to the function the count # will be random from 10 to 10000 def fill_graph_randomly(self, c=-1): if c == -1: @@ -271,7 +271,7 @@ def __init__(self): # adding vertices and edges # adding the weight is optional - # handels repetition + # handles repetition def add_pair(self, u, v, w=1): # check if the u exists if self.graph.get(u): @@ -290,7 +290,7 @@ def add_pair(self, u, v, w=1): # if u does not exist self.graph[v] = [[w, u]] - # handels if the input does not exist + # handles if the input does not exist def remove_pair(self, u, v): if self.graph.get(u): for _ in self.graph[u]: @@ -302,7 +302,7 @@ def remove_pair(self, u, v): if _[1] == u: self.graph[v].remove(_) - # if no destination is meant the defaut value is -1 + # if no destination is meant the default value is -1 def dfs(self, s=-2, d=-1): if s == d: return [] @@ -341,7 +341,7 @@ def dfs(self, s=-2, d=-1): if len(stack) == 0: return visited - # c is the count of nodes you want and if you leave it or pass -1 to the funtion the count + # c is the count of nodes you want and if you leave it or pass -1 to the function the count # will be random from 10 to 10000 def fill_graph_randomly(self, c=-1): if c == -1: diff --git a/graphs/multi_hueristic_astar.py b/graphs/multi_heuristic_astar.py similarity index 100% rename from graphs/multi_hueristic_astar.py rename to graphs/multi_heuristic_astar.py diff --git a/hashes/sha1.py b/hashes/sha1.py index 3bf27af27582..c74ec0c853de 100644 --- a/hashes/sha1.py +++ b/hashes/sha1.py @@ -8,7 +8,7 @@ returned by the hashlib library SHA1 hash or SHA1 sum of a string is a crytpographic function which means it is easy -to calculate forwards but extemely difficult to calculate backwards. What this means +to calculate forwards but extremely difficult to calculate backwards. What this means is, you can easily calculate the hash of a string, but it is extremely difficult to know the original string if you have its hash. This property is useful to communicate securely, send encrypted messages and is very useful in payment systems, blockchain @@ -139,7 +139,7 @@ def testMatchHashes(self): def main(): """ Provides option 'string' or 'file' to take input and prints the calculated SHA1 hash. - unittest.main() has been commented because we probably dont want to run + unittest.main() has been commented because we probably don't want to run the test each time. """ # unittest.main() diff --git a/linear_algebra/README.md b/linear_algebra/README.md index 540920744948..9f8d150a72fa 100644 --- a/linear_algebra/README.md +++ b/linear_algebra/README.md @@ -8,7 +8,7 @@ This module contains classes and functions for doing linear algebra. ### class Vector - - - This class represents a vector of arbitray size and related operations. + - This class represents a vector of arbitrary size and related operations. **Overview about the methods:** diff --git a/machine_learning/k_nearest_neighbours.py b/machine_learning/k_nearest_neighbours.py index a60b744bc65e..481a8e1dbcd0 100644 --- a/machine_learning/k_nearest_neighbours.py +++ b/machine_learning/k_nearest_neighbours.py @@ -47,7 +47,7 @@ def classifier(train_data, train_target, classes, point, k=5): distances.append((distance, data_point[1])) # Choosing 'k' points with the least distances. votes = [i[1] for i in sorted(distances)[:k]] - # Most commonly occuring class among them + # Most commonly occurring class among them # is the class into which the point is classified result = Counter(votes).most_common(1)[0][0] return classes[result] diff --git a/machine_learning/scoring_functions.py b/machine_learning/scoring_functions.py index 5c84f7026e74..2b891d4eb9d5 100755 --- a/machine_learning/scoring_functions.py +++ b/machine_learning/scoring_functions.py @@ -10,7 +10,7 @@ even log is used. Using log and roots can be perceived as tools for penalizing big - erors. However, using appropriate metrics depends on the situations, + errors. However, using appropriate metrics depends on the situations, and types of data """ diff --git a/machine_learning/sequential_minimum_optimization.py b/machine_learning/sequential_minimum_optimization.py index 0612392c8dc2..a0b99a788cbd 100644 --- a/machine_learning/sequential_minimum_optimization.py +++ b/machine_learning/sequential_minimum_optimization.py @@ -1,8 +1,10 @@ """ - Implementation of sequential minimal optimization(SMO) for support vector machines(SVM). + Implementation of sequential minimal optimization (SMO) for support vector machines + (SVM). - Sequential minimal optimization (SMO) is an algorithm for solving the quadratic programming (QP) problem - that arises during the training of support vector machines. + Sequential minimal optimization (SMO) is an algorithm for solving the quadratic + programming (QP) problem that arises during the training of support vector + machines. It was invented by John Platt in 1998. Input: @@ -18,7 +20,8 @@ kernel = Kernel(kernel='poly', degree=3., coef0=1., gamma=0.5) init_alphas = np.zeros(train.shape[0]) - SVM = SmoSVM(train=train, alpha_list=init_alphas, kernel_func=kernel, cost=0.4, b=0.0, tolerance=0.001) + SVM = SmoSVM(train=train, alpha_list=init_alphas, kernel_func=kernel, cost=0.4, + b=0.0, tolerance=0.001) SVM.fit() predict = SVM.predict(test_samples) @@ -72,7 +75,7 @@ def __init__( self.choose_alpha = self._choose_alphas() - # Calculate alphas using SMO algorithsm + # Calculate alphas using SMO algorithm def fit(self): K = self._k state = None @@ -227,7 +230,7 @@ def _choose_alphas(self): def _choose_a1(self): """ Choose first alpha ;steps: - 1:Fisrt loop over all sample + 1:First loop over all sample 2:Second loop over all non-bound samples till all non-bound samples does not voilate kkt condition. 3:Repeat this two process endlessly,till all samples does not voilate kkt condition samples after first loop. """ @@ -261,9 +264,11 @@ def _choose_a1(self): def _choose_a2(self, i1): """ Choose the second alpha by using heuristic algorithm ;steps: - 1:Choosed alpha2 which get the maximum step size (|E1 - E2|). - 2:Start in a random point,loop over all non-bound samples till alpha1 and alpha2 are optimized. - 3:Start in a random point,loop over all samples till alpha1 and alpha2 are optimized. + 1: Choose alpha2 which gets the maximum step size (|E1 - E2|). + 2: Start in a random point,loop over all non-bound samples till alpha1 and + alpha2 are optimized. + 3: Start in a random point,loop over all samples till alpha1 and alpha2 are + optimized. """ self._unbound = [i for i in self._all_samples if self._is_unbound(i)] @@ -316,7 +321,7 @@ def _get_new_alpha(self, i1, i2, a1, a2, e1, e2, y1, y2): # select the new alpha2 which could get the minimal objectives if eta > 0.0: a2_new_unc = a2 + (y2 * (e1 - e2)) / eta - # a2_new has a boundry + # a2_new has a boundary if a2_new_unc >= H: a2_new = H elif a2_new_unc <= L: @@ -357,7 +362,7 @@ def _get_new_alpha(self, i1, i2, a1, a2, e1, e2, y1, y2): else: a2_new = a2 - # a1_new has a boundry too + # a1_new has a boundary too a1_new = a1 + s * (a2 - a2_new) if a1_new < 0: a2_new += s * a1_new @@ -471,7 +476,7 @@ def test_cancel_data(): data = data.replace({"M": np.float64(1), "B": np.float64(-1)}) samples = np.array(data)[:, :] - # 2: deviding data into train_data data and test_data data + # 2: dividing data into train_data data and test_data data train_data, test_data = samples[:328, :], samples[328:, :] test_tags, test_samples = test_data[:, 0], test_data[:, 1:] @@ -568,7 +573,7 @@ def plot_partition_boundary( ): """ We can not get the optimum w of our kernel svm model which is different from linear svm. - For this reason, we generate randomly destributed points with high desity and prediced values of these points are + For this reason, we generate randomly distributed points with high desity and prediced values of these points are calculated by using our tained model. Then we could use this prediced values to draw contour map. And this contour map can represent svm's partition boundary. diff --git a/machine_learning/support_vector_machines.py b/machine_learning/support_vector_machines.py index 92fa814c998f..d72e599eace4 100644 --- a/machine_learning/support_vector_machines.py +++ b/machine_learning/support_vector_machines.py @@ -18,7 +18,7 @@ def Linearsvc(train_x, train_y): def SVC(train_x, train_y): # svm.SVC(C=1.0, kernel='rbf', degree=3, gamma=0.0, coef0=0.0, shrinking=True, probability=False,tol=0.001, cache_size=200, class_weight=None, verbose=False, max_iter=-1, random_state=None) - # various parameters like "kernal","gamma","C" can effectively tuned for a given machine learning model. + # various parameters like "kernel","gamma","C" can effectively tuned for a given machine learning model. SVC = svm.SVC(gamma="auto") SVC.fit(train_x, train_y) return SVC diff --git a/maths/matrix_exponentiation.py b/maths/matrix_exponentiation.py index a8f11480378a..56d03b56fc1f 100644 --- a/maths/matrix_exponentiation.py +++ b/maths/matrix_exponentiation.py @@ -12,7 +12,7 @@ class Matrix: def __init__(self, arg): - if isinstance(arg, list): # Initialzes a matrix identical to the one provided. + if isinstance(arg, list): # Initializes a matrix identical to the one provided. self.t = arg self.n = len(arg) else: # Initializes a square matrix of the given size and set the values to zero. @@ -50,7 +50,7 @@ def fibonacci_with_matrix_exponentiation(n, f1, f2): def simple_fibonacci(n, f1, f2): - # Trival Cases + # Trivial Cases if n == 1: return f1 elif n == 2: diff --git a/maths/mobius_function.py b/maths/mobius_function.py index 15fb3d4380f4..df0f66177501 100644 --- a/maths/mobius_function.py +++ b/maths/mobius_function.py @@ -1,5 +1,5 @@ """ -Refrences: https://en.wikipedia.org/wiki/M%C3%B6bius_function +References: https://en.wikipedia.org/wiki/M%C3%B6bius_function References: wikipedia:square free number python/black : True flake8 : True diff --git a/maths/prime_sieve_eratosthenes.py b/maths/prime_sieve_eratosthenes.py index 4fa19d6db220..05363cf62953 100644 --- a/maths/prime_sieve_eratosthenes.py +++ b/maths/prime_sieve_eratosthenes.py @@ -4,18 +4,18 @@ Input : n =10 Output : 2 3 5 7 -Input : n = 20 +Input : n = 20 Output: 2 3 5 7 11 13 17 19 -you can read in detail about this at +you can read in detail about this at https://en.wikipedia.org/wiki/Sieve_of_Eratosthenes """ def prime_sieve_eratosthenes(num): """ - print the prime numbers upto n - + print the prime numbers up to n + >>> prime_sieve_eratosthenes(10) 2 3 5 7 >>> prime_sieve_eratosthenes(20) @@ -26,7 +26,7 @@ def prime_sieve_eratosthenes(num): p = 2 while p * p <= num: - if primes[p] == True: + if primes[p]: for i in range(p * p, num + 1, p): primes[i] = False p += 1 diff --git a/maths/simpson_rule.py b/maths/simpson_rule.py index 91098804395d..d66dc39a7171 100644 --- a/maths/simpson_rule.py +++ b/maths/simpson_rule.py @@ -1,7 +1,7 @@ """ Numerical integration or quadrature for a smooth function f with known values at x_i -This method is the classical approch of suming 'Equally Spaced Abscissas' +This method is the classical approach of suming 'Equally Spaced Abscissas' method 2: "Simpson Rule" diff --git a/maths/square_root.py b/maths/square_root.py index 46e791ab5662..d4c5e311b0b7 100644 --- a/maths/square_root.py +++ b/maths/square_root.py @@ -22,7 +22,7 @@ def square_root_iterative( a: float, max_iter: int = 9999, tolerance: float = 0.00000000000001 ) -> float: """ - Sqaure root is aproximated using Newtons method. + Square root is aproximated using Newtons method. https://en.wikipedia.org/wiki/Newton%27s_method >>> all(abs(square_root_iterative(i)-math.sqrt(i)) <= .00000000000001 for i in range(0, 500)) diff --git a/maths/trapezoidal_rule.py b/maths/trapezoidal_rule.py index 0f7dea6bf888..9a4ddc8af66b 100644 --- a/maths/trapezoidal_rule.py +++ b/maths/trapezoidal_rule.py @@ -1,7 +1,7 @@ """ Numerical integration or quadrature for a smooth function f with known values at x_i -This method is the classical approch of suming 'Equally Spaced Abscissas' +This method is the classical approach of suming 'Equally Spaced Abscissas' method 1: "extended trapezoidal rule" diff --git a/maths/zellers_congruence.py b/maths/zellers_congruence.py index 277ecfaf0da9..954a4643a9bb 100644 --- a/maths/zellers_congruence.py +++ b/maths/zellers_congruence.py @@ -31,17 +31,17 @@ def zeller(date_input: str) -> str: ... ValueError: invalid literal for int() with base 10: '.4' - Validate second seperator: + Validate second separator: >>> zeller('01-31*2010') Traceback (most recent call last): ... - ValueError: Date seperator must be '-' or '/' + ValueError: Date separator must be '-' or '/' - Validate first seperator: + Validate first separator: >>> zeller('01^31-2010') Traceback (most recent call last): ... - ValueError: Date seperator must be '-' or '/' + ValueError: Date separator must be '-' or '/' Validate out of range year: >>> zeller('01-31-8999') @@ -55,7 +55,7 @@ def zeller(date_input: str) -> str: ... TypeError: zeller() missing 1 required positional argument: 'date_input' - Test length fo date_input: + Test length of date_input: >>> zeller('') Traceback (most recent call last): ... @@ -92,7 +92,7 @@ def zeller(date_input: str) -> str: sep_1: str = date_input[2] # Validate if sep_1 not in ["-", "/"]: - raise ValueError("Date seperator must be '-' or '/'") + raise ValueError("Date separator must be '-' or '/'") # Get day d: int = int(date_input[3] + date_input[4]) @@ -100,11 +100,11 @@ def zeller(date_input: str) -> str: if not 0 < d < 32: raise ValueError("Date must be between 1 - 31") - # Get second seperator + # Get second separator sep_2: str = date_input[5] # Validate if sep_2 not in ["-", "/"]: - raise ValueError("Date seperator must be '-' or '/'") + raise ValueError("Date separator must be '-' or '/'") # Get year y: int = int(date_input[6] + date_input[7] + date_input[8] + date_input[9]) diff --git a/matrix/matrix_class.py b/matrix/matrix_class.py index a8066e319559..2a1977b5dbfe 100644 --- a/matrix/matrix_class.py +++ b/matrix/matrix_class.py @@ -1,4 +1,4 @@ -# An OOP aproach to representing and manipulating matrices +# An OOP approach to representing and manipulating matrices class Matrix: diff --git a/matrix/spiral_print.py b/matrix/spiral_print.py index 31d9fff84bfd..21dab76156e9 100644 --- a/matrix/spiral_print.py +++ b/matrix/spiral_print.py @@ -1,5 +1,5 @@ """ -This program print the matix in spiral form. +This program print the matrix in spiral form. This problem has been solved through recursive way. Matrix must satisfy below conditions diff --git a/networking_flow/minimum_cut.py b/networking_flow/minimum_cut.py index 7773df72f8f0..0f6781fbb88c 100644 --- a/networking_flow/minimum_cut.py +++ b/networking_flow/minimum_cut.py @@ -35,7 +35,7 @@ def mincut(graph, source, sink): parent = [-1] * (len(graph)) max_flow = 0 res = [] - temp = [i[:] for i in graph] # Record orignial cut, copy. + temp = [i[:] for i in graph] # Record original cut, copy. while BFS(graph, source, sink, parent): path_flow = float("Inf") s = sink diff --git a/neural_network/convolution_neural_network.py b/neural_network/convolution_neural_network.py index ecc8e3392b7f..ac0eddeb5cb6 100644 --- a/neural_network/convolution_neural_network.py +++ b/neural_network/convolution_neural_network.py @@ -1,12 +1,12 @@ """ - - - - - -- - - - - - - - - - - - - - - - - - - - - - - Name - - CNN - Convolution Neural Network For Photo Recognizing - Goal - - Recognize Handing Writting Word Photo + Goal - - Recognize Handing Writing Word Photo Detail:Total 5 layers neural network * Convolution layer * Pooling layer * Input layer layer of BP - * Hiden layer of BP + * Hidden layer of BP * Output layer of BP Author: Stephen Lee Github: 245885195@qq.com @@ -116,7 +116,7 @@ def convolute(self, data, convs, w_convs, thre_convs, conv_step): i_focus : i_focus + size_conv, j_focus : j_focus + size_conv ] data_focus.append(focus) - # caculate the feature map of every single kernel, and saved as list of matrix + # calculate the feature map of every single kernel, and saved as list of matrix data_featuremap = [] Size_FeatureMap = int((size_data - size_conv) / conv_step + 1) for i_map in range(num_conv): @@ -163,12 +163,12 @@ def pooling(self, featuremaps, size_pooling, type="average_pool"): featuremap_pooled.append(map_pooled) return featuremap_pooled - def _expand(self, datas): + def _expand(self, data): # expanding three dimension data to one dimension list data_expanded = [] - for i in range(len(datas)): - shapes = np.shape(datas[i]) - data_listed = datas[i].reshape(1, shapes[0] * shapes[1]) + for i in range(len(data)): + shapes = np.shape(data[i]) + data_listed = data[i].reshape(1, shapes[0] * shapes[1]) data_listed = data_listed.getA().tolist()[0] data_expanded.extend(data_listed) data_expanded = np.asarray(data_expanded) @@ -185,7 +185,7 @@ def _calculate_gradient_from_pool( self, out_map, pd_pool, num_map, size_map, size_pooling ): """ - calcluate the gradient from the data slice of pool layer + calculate the gradient from the data slice of pool layer pd_pool: list of matrix out_map: the shape of data slice(size_map*size_map) return: pd_all: list of matrix, [num, size_map, size_map] @@ -217,7 +217,7 @@ def train( all_mse = [] mse = 10000 while rp < n_repeat and mse >= error_accuracy: - alle = 0 + error_count = 0 print("-------------Learning Time %d--------------" % rp) for p in range(len(datas_train)): # print('------------Learning Image: %d--------------'%p) @@ -246,7 +246,7 @@ def train( bp_out3 = self.sig(bp_net_k) # --------------Model Leaning ------------------------ - # calcluate error and gradient--------------- + # calculate error and gradient--------------- pd_k_all = np.multiply( (data_teach - bp_out3), np.multiply(bp_out3, (1 - bp_out3)) ) @@ -285,11 +285,11 @@ def train( self.thre_bp2 = self.thre_bp2 - pd_j_all * self.rate_thre # calculate the sum error of all single image errors = np.sum(abs(data_teach - bp_out3)) - alle = alle + errors + error_count += errors # print(' ----Teach ',data_teach) # print(' ----BP_output ',bp_out3) rp = rp + 1 - mse = alle / patterns + mse = error_count / patterns all_mse.append(mse) def draw_error(): diff --git a/neural_network/perceptron.py b/neural_network/perceptron.py index 3610dd2ab227..2a1c46b359e6 100644 --- a/neural_network/perceptron.py +++ b/neural_network/perceptron.py @@ -76,11 +76,11 @@ def training(self) -> None: has_misclassified = True # print('Epoch: \n',epoch_count) epoch_count = epoch_count + 1 - # if you want controle the epoch or just by erro + # if you want control the epoch or just by error if not has_misclassified: print(("\nEpoch:\n", epoch_count)) print("------------------------\n") - # if epoch_count > self.epoch_number or not erro: + # if epoch_count > self.epoch_number or not error: break def sort(self, sample) -> None: diff --git a/other/linear_congruential_generator.py b/other/linear_congruential_generator.py index ea0adc7d027f..058f270d0584 100644 --- a/other/linear_congruential_generator.py +++ b/other/linear_congruential_generator.py @@ -13,7 +13,7 @@ def __init__(self, multiplier, increment, modulo, seed=int(time())): These parameters are saved and used when nextNumber() is called. modulo is the largest number that can be generated (exclusive). The most - efficent values are powers of 2. 2^32 is a common value. + efficient values are powers of 2. 2^32 is a common value. """ self.multiplier = multiplier self.increment = increment diff --git a/other/primelib.py b/other/primelib.py index ff438755ae6f..1b99819ce62a 100644 --- a/other/primelib.py +++ b/other/primelib.py @@ -88,7 +88,7 @@ def sieveEr(N): # precondition assert isinstance(N, int) and (N > 2), "'N' must been an int and > 2" - # beginList: conatins all natural numbers from 2 upt to N + # beginList: contains all natural numbers from 2 up to N beginList = [x for x in range(2, N + 1)] ans = [] # this list will be returns. @@ -480,8 +480,8 @@ def getPrimesBetween(pNumber1, pNumber2): """ input: prime numbers 'pNumber1' and 'pNumber2' pNumber1 < pNumber2 - returns a list of all prime numbers between 'pNumber1' (exclusiv) - and 'pNumber2' (exclusiv) + returns a list of all prime numbers between 'pNumber1' (exclusive) + and 'pNumber2' (exclusive) """ # precondition diff --git a/project_euler/problem_04/sol1.py b/project_euler/problem_04/sol1.py index 53fff8bed4d4..599345b5ab79 100644 --- a/project_euler/problem_04/sol1.py +++ b/project_euler/problem_04/sol1.py @@ -19,7 +19,7 @@ def solution(n): >>> solution(40000) 39893 """ - # fetchs the next number + # fetches the next number for number in range(n - 1, 10000, -1): # converts number into string. diff --git a/project_euler/problem_30/soln.py b/project_euler/problem_30/soln.py index 0f4df66ae16a..9d45739845a3 100644 --- a/project_euler/problem_30/soln.py +++ b/project_euler/problem_30/soln.py @@ -30,5 +30,5 @@ def digitsum(s: str) -> int: if __name__ == "__main__": - count = sum(digitsum(str(i)) for i in range(1000,1000000)) + count = sum(digitsum(str(i)) for i in range(1000, 1000000)) print(count) # --> 443839 diff --git a/project_euler/problem_551/sol1.py b/project_euler/problem_551/sol1.py index 307d644fbfc9..4775800693bc 100644 --- a/project_euler/problem_551/sol1.py +++ b/project_euler/problem_551/sol1.py @@ -2,7 +2,7 @@ Sum of digits sequence Problem 551 -Let a(0), a(1),... be an interger sequence defined by: +Let a(0), a(1),... be an integer sequence defined by: a(0) = 1 for n >= 1, a(n) is the sum of the digits of all preceding terms @@ -33,7 +33,7 @@ def next_term(a_i, k, i, n): k -- k when terms are written in the from a(i) = b*10^k + c. Term are calulcated until c > 10^k or the n-th term is reached. i -- position along the sequence - n -- term to caluclate up to if k is large enough + n -- term to calculate up to if k is large enough Return: a tuple of difference between ending term and starting term, and the number of terms calculated. ex. if starting term is a_0=1, and diff --git a/searches/hill_climbing.py b/searches/hill_climbing.py index 8cabbd602bd1..c1129514c04f 100644 --- a/searches/hill_climbing.py +++ b/searches/hill_climbing.py @@ -87,7 +87,7 @@ def hill_climbing( """ implementation of the hill climbling algorithm. We start with a given state, find all its neighbors, move towards the neighbor which provides the maximum (or - minimum) change. We keep doing this untill we are at a state where we do not + minimum) change. We keep doing this until we are at a state where we do not have any neighbors which can improve the solution. Args: search_prob: The search state at the start. diff --git a/searches/interpolation_search.py b/searches/interpolation_search.py index fd26ae0c64ce..419ec52c0f4e 100644 --- a/searches/interpolation_search.py +++ b/searches/interpolation_search.py @@ -15,7 +15,7 @@ def interpolation_search(sorted_collection, item): right = len(sorted_collection) - 1 while left <= right: - # avoid devided by 0 during interpolation + # avoid divided by 0 during interpolation if sorted_collection[left] == sorted_collection[right]: if sorted_collection[left] == item: return left @@ -59,7 +59,7 @@ def interpolation_search_by_recursion(sorted_collection, item, left, right): :return: index of found item or None if item is not found """ - # avoid devided by 0 during interpolation + # avoid divided by 0 during interpolation if sorted_collection[left] == sorted_collection[right]: if sorted_collection[left] == item: return left diff --git a/searches/jump_search.py b/searches/jump_search.py index e191cf2d4b27..5ba80e9d35be 100644 --- a/searches/jump_search.py +++ b/searches/jump_search.py @@ -20,7 +20,7 @@ def jump_search(arr, x): return -1 -arr = [0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610] -x = 55 -index = jump_search(arr, x) -print("\nNumber " + str(x) + " is at index " + str(index)) +if __name__ == "__main__": + arr = [0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610] + x = 55 + print(f"Number {x} is at index {jump_search(arr, x)}") diff --git a/searches/simulated_annealing.py b/searches/simulated_annealing.py index 5fec87bed321..d3542b00af45 100644 --- a/searches/simulated_annealing.py +++ b/searches/simulated_annealing.py @@ -62,7 +62,7 @@ def simulated_annealing( continue # neighbor outside our bounds if not find_max: - change = change * -1 # incase we are finding minimum + change = change * -1 # in case we are finding minimum if change > 0: # improves the solution next_state = picked_neighbor else: @@ -73,10 +73,8 @@ def simulated_annealing( next_state = picked_neighbor current_temp = current_temp - (current_temp * rate_of_decrease) - if ( - current_temp < threshold_temp or next_state is None - ): # temperature below threshold, or - # couldnt find a suitaable neighbor + if current_temp < threshold_temp or next_state is None: + # temperature below threshold, or could not find a suitaable neighbor search_end = True else: current_state = next_state diff --git a/searches/tabu_search.py b/searches/tabu_search.py index 52086f1235ab..04a0e5076912 100644 --- a/searches/tabu_search.py +++ b/searches/tabu_search.py @@ -188,7 +188,7 @@ def tabu_search( and the cost (distance) for each neighbor. :param iters: The number of iterations that Tabu search will execute. :param size: The size of Tabu List. - :return best_solution_ever: The solution with the lowest distance that occured during the execution of Tabu search. + :return best_solution_ever: The solution with the lowest distance that occurred during the execution of Tabu search. :return best_cost: The total distance that Travelling Salesman will travel, if he follows the path in best_solution ever. diff --git a/sorts/bitonic_sort.py b/sorts/bitonic_sort.py index 8780bb3259c5..131f97291fbb 100644 --- a/sorts/bitonic_sort.py +++ b/sorts/bitonic_sort.py @@ -22,7 +22,7 @@ def bitonicMerge(a, low, cnt, dire): bitonicMerge(a, low, k, dire) bitonicMerge(a, low + k, k, dire) - # This funcion first produces a bitonic sequence by recursively + # This function first produces a bitonic sequence by recursively # sorting its two halves in opposite sorting orders, and then diff --git a/sorts/double_sort.py b/sorts/double_sort.py index aca4b97ca775..04e18682017c 100644 --- a/sorts/double_sort.py +++ b/sorts/double_sort.py @@ -1,6 +1,6 @@ def double_sort(lst): - """this sorting algorithm sorts an array using the principle of bubble sort , - but does it both from left to right and right to left , + """this sorting algorithm sorts an array using the principle of bubble sort, + but does it both from left to right and right to left, hence i decided to call it "double sort" :param collection: mutable ordered sequence of elements :return: the same collection in ascending order @@ -17,7 +17,7 @@ def double_sort(lst): no_of_elements = len(lst) for i in range( 0, int(((no_of_elements - 1) / 2) + 1) - ): # we dont need to traverse to end of list as + ): # we don't need to traverse to end of list as for j in range(0, no_of_elements - 1): if ( lst[j + 1] < lst[j] diff --git a/sorts/pigeonhole_sort.py b/sorts/pigeonhole_sort.py index a91e1d054442..bfa9bb11b8a6 100644 --- a/sorts/pigeonhole_sort.py +++ b/sorts/pigeonhole_sort.py @@ -7,7 +7,7 @@ def pigeonhole_sort(a): """ >>> a = [8, 3, 2, 7, 4, 6, 8] >>> b = sorted(a) # a nondestructive sort - >>> pigeonhole_sort(a) # a distructive sort + >>> pigeonhole_sort(a) # a destructive sort >>> a == b True """ diff --git a/sorts/recursive_insertion_sort.py b/sorts/recursive_insertion_sort.py index 39a903dd1bcb..5b14c2a6c139 100644 --- a/sorts/recursive_insertion_sort.py +++ b/sorts/recursive_insertion_sort.py @@ -69,7 +69,7 @@ def insert_next(collection: List, index: int): if __name__ == "__main__": - numbers = input("Enter integers seperated by spaces: ") + numbers = input("Enter integers separated by spaces: ") numbers = [int(num) for num in numbers.split()] rec_insertion_sort(numbers, len(numbers)) print(numbers) diff --git a/strings/aho-corasick.py b/strings/aho-corasick.py index d63dc94a03fc..315f7793325e 100644 --- a/strings/aho-corasick.py +++ b/strings/aho-corasick.py @@ -67,7 +67,7 @@ def search_in(self, string): >>> A.search_in("whatever, err ... , wherever") {'what': [0], 'hat': [1], 'ver': [5, 25], 'er': [6, 10, 22, 26]} """ - result = dict() # returns a dict with keywords and list of its occurences + result = dict() # returns a dict with keywords and list of its occurrences current_state = 0 for i in range(len(string)): while ( diff --git a/strings/boyer_moore_search.py b/strings/boyer_moore_search.py index 59ee76b860d3..bd777c7c7e05 100644 --- a/strings/boyer_moore_search.py +++ b/strings/boyer_moore_search.py @@ -27,7 +27,7 @@ def __init__(self, text, pattern): def match_in_pattern(self, char): """ finds the index of char in pattern in reverse order - Paremeters : + Parameters : char (chr): character to be searched Returns : @@ -43,12 +43,12 @@ def match_in_pattern(self, char): def mismatch_in_text(self, currentPos): """ finds the index of mis-matched character in text when compared with pattern from last - Paremeters : + Parameters : currentPos (int): current index position of text Returns : i (int): index of mismatched char from last in text - -1 (int): if there is no mis-match between pattern and text block + -1 (int): if there is no mismatch between pattern and text block """ for i in range(self.patLen - 1, -1, -1): diff --git a/strings/manacher.py b/strings/manacher.py index ef8a724d027d..4193def2f71b 100644 --- a/strings/manacher.py +++ b/strings/manacher.py @@ -13,12 +13,13 @@ def palindromic_string(input_string): """ Manacher’s algorithm which finds Longest Palindromic Substring in linear time. - 1. first this conver input_string("xyx") into new_string("x|y|x") where odd positions are actual input - characters. + 1. first this convert input_string("xyx") into new_string("x|y|x") where odd + positions are actual input characters. 2. for each character in new_string it find corresponding length and store, a. max_length b. max_length's center - 3. return output_string from center - max_length to center + max_length and remove all "|" + 3. return output_string from center - max_length to center + max_length and remove + all "|" """ max_length = 0 @@ -35,7 +36,7 @@ def palindromic_string(input_string): # for each character in new_string find corresponding palindromic string for i in range(len(new_input_string)): - # get palindromic length from ith position + # get palindromic length from i-th position length = palindromic_length(i, 1, new_input_string) # update max_length and start position diff --git a/strings/split.py b/strings/split.py index 727250fe6e9f..d5bff316429f 100644 --- a/strings/split.py +++ b/strings/split.py @@ -1,17 +1,17 @@ -def split(string: str, seperator: str = " ") -> list: +def split(string: str, separator: str = " ") -> list: """ - Will split the string up into all the values seperated by the seperator (defaults to spaces) + Will split the string up into all the values separated by the separator (defaults to spaces) - >>> split("apple#banana#cherry#orange",seperator='#') + >>> split("apple#banana#cherry#orange",separator='#') ['apple', 'banana', 'cherry', 'orange'] >>> split("Hello there") ['Hello', 'there'] - >>> split("11/22/63",seperator = '/') + >>> split("11/22/63",separator = '/') ['11', '22', '63'] - >>> split("12:43:39",seperator = ":") + >>> split("12:43:39",separator = ":") ['12', '43', '39'] """ @@ -19,7 +19,7 @@ def split(string: str, seperator: str = " ") -> list: last_index = 0 for index, char in enumerate(string): - if char == seperator: + if char == separator: split_words.append(string[last_index:index]) last_index = index + 1 elif index + 1 == len(string): diff --git a/strings/word_occurence.py b/strings/word_occurrence.py similarity index 87% rename from strings/word_occurence.py rename to strings/word_occurrence.py index c4eb923d6bc8..7b8f9bee8146 100644 --- a/strings/word_occurence.py +++ b/strings/word_occurrence.py @@ -11,11 +11,11 @@ def word_occurence(sentence: str) -> dict: ... in Counter(SENTENCE.split()).items()) True """ - occurence = defaultdict(int) + occurrence = defaultdict(int) # Creating a dictionary containing count of each word for word in sentence.split(" "): - occurence[word] += 1 - return occurence + occurrence[word] += 1 + return occurrence if __name__ == "__main__": From 99ebd1a01820cefb7bdf169168131289d71158fc Mon Sep 17 00:00:00 2001 From: Pooja Date: Sat, 18 Jan 2020 18:36:48 +0530 Subject: [PATCH 0439/1071] Create factorial_iterative.py (#1693) * Create factorial_iterative.py * Update factorial_iterative.py * Update factorial_iterative.py * Update factorial_iterative.py * print(f"factorial{n} is {factorial(n)}") * Update factorial_recursive.py Co-authored-by: Christian Clauss --- maths/factorial_iterative.py | 30 ++++++++++++++++++++++++++++++ maths/factorial_recursive.py | 24 +++++++++++------------- 2 files changed, 41 insertions(+), 13 deletions(-) create mode 100644 maths/factorial_iterative.py diff --git a/maths/factorial_iterative.py b/maths/factorial_iterative.py new file mode 100644 index 000000000000..249408cb5b4e --- /dev/null +++ b/maths/factorial_iterative.py @@ -0,0 +1,30 @@ +# factorial of a positive integer -- https://en.wikipedia.org/wiki/Factorial + + +def factorial(n: int) -> int: + """ + >>> import math + >>> all(factorial(i) == math.factorial(i) for i in range(20)) + True + >>> factorial(0.1) + Traceback (most recent call last): + ... + ValueError: factorial() only accepts integral values + >>> factorial(-1) + Traceback (most recent call last): + ... + ValueError: factorial() not defined for negative values + """ + if n != int(n): + raise ValueError("factorial() only accepts integral values") + if n < 0: + raise ValueError("factorial() not defined for negative values") + value = 1 + for i in range(1, n + 1): + value *= i + return value + + +if __name__ == "__main__": + n = int(input("Enter a positivve integer: ").strip() or 0) + print(f"factorial{n} is {factorial(n)}") diff --git a/maths/factorial_recursive.py b/maths/factorial_recursive.py index 4f7074d16587..137112738905 100644 --- a/maths/factorial_recursive.py +++ b/maths/factorial_recursive.py @@ -1,26 +1,24 @@ def factorial(n: int) -> int: """ - Calculate the factorial of specified number + Calculate the factorial of a positive integer + https://en.wikipedia.org/wiki/Factorial - >>> factorial(1) - 1 - >>> factorial(6) - 720 - >>> factorial(0) - 1 - >>> factorial(-1) - Traceback (most recent call last): - ... - ValueError: factorial() not defined for negative values + >>> import math + >>> all(factorial(i) == math.factorial(i) for i in range(20)) + True >>> factorial(0.1) Traceback (most recent call last): ... ValueError: factorial() only accepts integral values + >>> factorial(-1) + Traceback (most recent call last): + ... + ValueError: factorial() not defined for negative values """ - if n < 0: - raise ValueError("factorial() not defined for negative values") if not isinstance(n, int): raise ValueError("factorial() only accepts integral values") + if n < 0: + raise ValueError("factorial() not defined for negative values") return 1 if n == 0 or n == 1 else n * factorial(n - 1) From e25d4248a31fc6346fdc2644e0f3ce86e1c9d412 Mon Sep 17 00:00:00 2001 From: Sharan Krishnan <58838321+shrabian@users.noreply.github.com> Date: Sun, 19 Jan 2020 04:25:27 +1100 Subject: [PATCH 0440/1071] Added an algorithm that approximates line lengths (#1692) * A recursive insertion sort * added doctests and typehints * Added arc length and numerical integration calculators * fixed doc test * Fixed some conversion errors * Fixed some commenting * Deleted numerical integration to allow 1 file per push * Changed string formatting method --- maths/line_length.py | 61 ++++++++++++++++++++++++++++++++ maths/numerical_integration.py | 63 ++++++++++++++++++++++++++++++++++ 2 files changed, 124 insertions(+) create mode 100644 maths/line_length.py create mode 100644 maths/numerical_integration.py diff --git a/maths/line_length.py b/maths/line_length.py new file mode 100644 index 000000000000..8737a863b902 --- /dev/null +++ b/maths/line_length.py @@ -0,0 +1,61 @@ +from typing import Callable, Union +import math as m + +def line_length(fnc: Callable[[Union[int, float]], Union[int, float]], + x_start: Union[int, float], + x_end: Union[int, float], + steps: int = 100) -> float: + + """ + Approximates the arc length of a line segment by treating the curve as a + sequence of linear lines and summing their lengths + :param fnc: a function which defines a curve + :param x_start: left end point to indicate the start of line segment + :param x_end: right end point to indicate end of line segment + :param steps: an accuracy gauge; more steps increases accuracy + :return: a float representing the length of the curve + + >>> def f(x): + ... return x + >>> f"{line_length(f, 0, 1, 10):.6f}" + '1.414214' + + >>> def f(x): + ... return 1 + >>> f"{line_length(f, -5.5, 4.5):.6f}" + '10.000000' + + >>> def f(x): + ... return m.sin(5 * x) + m.cos(10 * x) + x * x/10 + >>> f"{line_length(f, 0.0, 10.0, 10000):.6f}" + '69.534930' + """ + + x1 = x_start + fx1 = fnc(x_start) + length = 0.0 + + for i in range(steps): + + # Approximates curve as a sequence of linear lines and sums their length + x2 = (x_end - x_start) / steps + x1 + fx2 = fnc(x2) + length += m.hypot(x2 - x1, fx2 - fx1) + + # Increment step + x1 = x2 + fx1 = fx2 + + return length + +if __name__ == "__main__": + + def f(x): + return m.sin(10*x) + + print("f(x) = sin(10 * x)") + print("The length of the curve from x = -10 to x = 10 is:") + i = 10 + while i <= 100000: + print(f"With {i} steps: {line_length(f, -10, 10, i)}") + i *= 10 diff --git a/maths/numerical_integration.py b/maths/numerical_integration.py new file mode 100644 index 000000000000..55026f0d627f --- /dev/null +++ b/maths/numerical_integration.py @@ -0,0 +1,63 @@ +""" +Approximates the area under the curve using the trapezoidal rule +""" + +from typing import Callable, Union + +def trapezoidal_area(fnc: Callable[[Union[int, float]], Union[int, float]], + x_start: Union[int, float], + x_end: Union[int, float], + steps: int = 100) -> float: + + """ + Treats curve as a collection of linear lines and sums the area of the + trapezium shape they form + :param fnc: a function which defines a curve + :param x_start: left end point to indicate the start of line segment + :param x_end: right end point to indicate end of line segment + :param steps: an accuracy gauge; more steps increases the accuracy + :return: a float representing the length of the curve + + >>> def f(x): + ... return 5 + >>> '%.3f' % trapezoidal_area(f, 12.0, 14.0, 1000) + '10.000' + + >>> def f(x): + ... return 9*x**2 + >>> '%.4f' % trapezoidal_area(f, -4.0, 0, 10000) + '192.0000' + + >>> '%.4f' % trapezoidal_area(f, -4.0, 4.0, 10000) + '384.0000' + """ + x1 = x_start + fx1 = fnc(x_start) + area = 0.0 + + for i in range(steps): + + # Approximates small segments of curve as linear and solve + # for trapezoidal area + x2 = (x_end - x_start)/steps + x1 + fx2 = fnc(x2) + area += abs(fx2 + fx1) * (x2 - x1)/2 + + # Increment step + x1 = x2 + fx1 = fx2 + return area + + +if __name__ == "__main__": + + def f(x): + return x**3 + + print("f(x) = x^3") + print("The area between the curve, x = -10, x = 10 and the x axis is:") + i = 10 + while i <= 100000: + area = trapezoidal_area(f, -5, 5, i) + print("with {} steps: {}".format(i, area)) + i*=10 From 3042702d04eebfc9a1a545a076b2e6c764e17480 Mon Sep 17 00:00:00 2001 From: Sharan Krishnan <58838321+shrabian@users.noreply.github.com> Date: Sun, 19 Jan 2020 15:21:12 +1100 Subject: [PATCH 0441/1071] Area Under a Curve Algorithm (#1701) * A recursive insertion sort * added doctests and typehints * Added arc length and numerical integration calculators * fixed doc test * Fixed some conversion errors * Fixed some commenting * Deleted numerical integration to allow 1 file per push * Changed string formatting method * Added program to calculate trapezoidal area under curve * Deleted files ensure 1 pull request per file * file name changed * Update area_under_curve.py Co-authored-by: Christian Clauss --- maths/area_under_curve.py | 55 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) create mode 100644 maths/area_under_curve.py diff --git a/maths/area_under_curve.py b/maths/area_under_curve.py new file mode 100644 index 000000000000..d05e9e4ae201 --- /dev/null +++ b/maths/area_under_curve.py @@ -0,0 +1,55 @@ +""" +Approximates the area under the curve using the trapezoidal rule +""" + +from typing import Callable, Union + +def trapezoidal_area(fnc: Callable[[Union[int, float]], Union[int, float]], + x_start: Union[int, float], + x_end: Union[int, float], + steps: int = 100) -> float: + """ + Treats curve as a collection of linear lines and sums the area of the + trapezium shape they form + :param fnc: a function which defines a curve + :param x_start: left end point to indicate the start of line segment + :param x_end: right end point to indicate end of line segment + :param steps: an accuracy gauge; more steps increases the accuracy + :return: a float representing the length of the curve + + >>> def f(x): + ... return 5 + >>> f"{trapezoidal_area(f, 12.0, 14.0, 1000):.3f}" + '10.000' + >>> def f(x): + ... return 9*x**2 + >>> f"{trapezoidal_area(f, -4.0, 0, 10000):.4f}" + '192.0000' + >>> f"{trapezoidal_area(f, -4.0, 4.0, 10000):.4f}" + '384.0000' + """ + x1 = x_start + fx1 = fnc(x_start) + area = 0.0 + for i in range(steps): + # Approximates small segments of curve as linear and solve + # for trapezoidal area + x2 = (x_end - x_start)/steps + x1 + fx2 = fnc(x2) + area += abs(fx2 + fx1) * (x2 - x1)/2 + # Increment step + x1 = x2 + fx1 = fx2 + return area + + +if __name__ == "__main__": + def f(x): + return x**3 + x**2 + + print("f(x) = x^3 + x^2") + print("The area between the curve, x = -5, x = 5 and the x axis is:") + i = 10 + while i <= 100000: + print(f"with {i} steps: {trapezoidal_area(f, -5, 5, i)}") + i*=10 From 724b7d2198895b2cb88e9b506be25a8cd53ef87e Mon Sep 17 00:00:00 2001 From: Kyle <40903431+kylepw@users.noreply.github.com> Date: Wed, 22 Jan 2020 03:46:03 +0900 Subject: [PATCH 0442/1071] Add Prim's algorithm with min heap (#1704) --- graphs/prim.py | 65 +++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 57 insertions(+), 8 deletions(-) diff --git a/graphs/prim.py b/graphs/prim.py index 16cfaee089cb..a1d46a5a12a4 100644 --- a/graphs/prim.py +++ b/graphs/prim.py @@ -1,10 +1,13 @@ -""" -Prim's Algorithm. +"""Prim's Algorithm. + + Determines the minimum spanning tree(MST) of a graph using the Prim's Algorithm. -Determines the minimum spanning tree(MST) of a graph using the Prim's Algorithm + Details: https://en.wikipedia.org/wiki/Prim%27s_algorithm """ +import heapq as hq import math +from typing import Iterator class Vertex: @@ -50,11 +53,17 @@ def connect(graph, a, b, edge): graph[b - 1].add_edge(graph[a - 1], edge) -def prim(graph, root): - """ - Prim's Algorithm. - Return a list with the edges of a Minimum Spanning Tree - prim(graph, graph[0]) +def prim(graph: list, root: Vertex) -> list: + """Prim's Algorithm. + + Runtime: + O(mn) with `m` edges and `n` vertices + + Return: + List with the edges of a Minimum Spanning Tree + + Usage: + prim(graph, graph[0]) """ a = [] for u in graph: @@ -74,6 +83,38 @@ def prim(graph, root): return a +def prim_heap(graph: list, root: Vertex) -> Iterator[tuple]: + """Prim's Algorithm with min heap. + + Runtime: + O((m + n)log n) with `m` edges and `n` vertices + + Yield: + Edges of a Minimum Spanning Tree + + Usage: + prim(graph, graph[0]) + """ + for u in graph: + u.key = math.inf + u.pi = None + root.key = 0 + + h = [v for v in graph] + hq.heapify(h) + + while h: + u = hq.heappop(h) + for v in u.neighbors: + if (v in h) and (u.edges[v.id] < v.key): + v.pi = u + v.key = u.edges[v.id] + hq.heapify(h) + + for i in range(1, len(graph)): + yield (int(graph[i].id) + 1, int(graph[i].pi.id) + 1) + + def test_vector() -> None: """ # Creates a list to store x vertices. @@ -87,13 +128,21 @@ def test_vector() -> None: >>> connect(G, 3, 2, 6) >>> connect(G, 3, 4, 6) >>> connect(G, 0, 0, 0) # Generate the minimum spanning tree: + >>> G_heap = G[:] >>> MST = prim(G, G[0]) + >>> MST_heap = prim_heap(G, G[0]) >>> for i in MST: ... print(i) (2, 3) (3, 1) (4, 3) (5, 2) + >>> for i in MST_heap: + ... print(i) + (2, 3) + (3, 1) + (4, 3) + (5, 2) """ From 9a8e7de2dfb62110a92710c9be975c67510accb0 Mon Sep 17 00:00:00 2001 From: kostogls <38495639+kostogls@users.noreply.github.com> Date: Wed, 22 Jan 2020 17:35:30 +0200 Subject: [PATCH 0443/1071] Adding Armstrong number (#1708) * Adding Armstrong number * Update armstrong_numbers * Update armstrong_numbers.py * Update armstrong_numbers.py * Update armstrong_numbers.py Co-authored-by: Christian Clauss --- maths/armstrong_numbers.py | 55 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) create mode 100644 maths/armstrong_numbers.py diff --git a/maths/armstrong_numbers.py b/maths/armstrong_numbers.py new file mode 100644 index 000000000000..94acb35c33f6 --- /dev/null +++ b/maths/armstrong_numbers.py @@ -0,0 +1,55 @@ +""" +An Armstrong number is a number that is equal to the sum of the cubes of its digits. +For example, 370 is an Armstrong number because 3*3*3 + 7*7*7 + 0*0*0 = 370. +An Armstrong number is often called Narcissistic number. +""" + + +def armstrong_number(n: int) -> bool: + """ + This function checks if a number is Armstrong or not. + + >>> armstrong_number(153) + True + >>> armstrong_number(200) + False + >>> armstrong_number(1634) + True + >>> armstrong_number(0) + False + >>> armstrong_number(-1) + False + >>> armstrong_number(1.2) + False + """ + if not isinstance(n, int) or n < 1: + return False + + # Initialization of sum and number of digits. + sum = 0 + number_of_digits = 0 + temp = n + # Calculation of digits of the number + while temp > 0: + number_of_digits += 1 + temp //= 10 + # Dividing number into separate digits and find Armstrong number + temp = n + while temp > 0: + rem = temp % 10 + sum += (rem ** number_of_digits) + temp //= 10 + return n == sum + + +# In main function user inputs a number to find out if it's an Armstrong or not. Th function armstrong_number is called. +def main(): + num = int(input("Enter an integer number to check if it is Armstrong or not: ").strip()) + print(f"{num} is {'' if armstrong_number(num) else 'not '}an Armstrong number.") + + +if __name__ == '__main__': + import doctest + + doctest.testmod() + main() From 2cf7e8f99403f9823f4cbf6e9a85938a2e126473 Mon Sep 17 00:00:00 2001 From: kostogls <38495639+kostogls@users.noreply.github.com> Date: Wed, 22 Jan 2020 18:00:48 +0200 Subject: [PATCH 0444/1071] fix comment (#1710) * fix comment * Update armstrong_numbers.py Co-authored-by: Christian Clauss --- maths/armstrong_numbers.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/maths/armstrong_numbers.py b/maths/armstrong_numbers.py index 94acb35c33f6..8ce184b0cf44 100644 --- a/maths/armstrong_numbers.py +++ b/maths/armstrong_numbers.py @@ -1,5 +1,5 @@ """ -An Armstrong number is a number that is equal to the sum of the cubes of its digits. +An Armstrong number is equal to the sum of the cubes of its digits. For example, 370 is an Armstrong number because 3*3*3 + 7*7*7 + 0*0*0 = 370. An Armstrong number is often called Narcissistic number. """ @@ -7,7 +7,7 @@ def armstrong_number(n: int) -> bool: """ - This function checks if a number is Armstrong or not. + Return True if n is an Armstrong number or False if it is not. >>> armstrong_number(153) True @@ -42,9 +42,11 @@ def armstrong_number(n: int) -> bool: return n == sum -# In main function user inputs a number to find out if it's an Armstrong or not. Th function armstrong_number is called. def main(): - num = int(input("Enter an integer number to check if it is Armstrong or not: ").strip()) + """ + Request that user input an integer and tell them if it is Armstrong number. + """ + num = int(input("Enter an integer to see if it is an Armstrong number: ").strip()) print(f"{num} is {'' if armstrong_number(num) else 'not '}an Armstrong number.") From 46ac50a28e88037d965a7eea0b588493298b83eb Mon Sep 17 00:00:00 2001 From: Christian Clauss Date: Thu, 23 Jan 2020 17:21:51 +0100 Subject: [PATCH 0445/1071] codespell --quiet-level=2 (#1711) * codespell --quiet-level=2 Suppress the BINARY FILE warnings * fixup! Format Python code with psf/black push --- .github/workflows/codespell.yml | 4 ++-- maths/area_under_curve.py | 20 ++++++++++++-------- maths/armstrong_numbers.py | 6 +++--- maths/line_length.py | 14 +++++++++----- maths/numerical_integration.py | 19 +++++++++++-------- 5 files changed, 37 insertions(+), 26 deletions(-) diff --git a/.github/workflows/codespell.yml b/.github/workflows/codespell.yml index 1e9b052cafe8..188538775a2f 100644 --- a/.github/workflows/codespell.yml +++ b/.github/workflows/codespell.yml @@ -10,5 +10,5 @@ jobs: - uses: actions/setup-python@v1 - run: pip install codespell flake8 - run: | - SKIP="./.*,./other/dictionary.txt,./other/words,./project_euler/problem_22/p022_names.txt,*.bak,*.gif,*.jpeg,*.jpg,*.json,*.png,*.pyc" - codespell -L ans,fo,hist,iff,secant,tim --skip=$SKIP + SKIP="./.*,./other/dictionary.txt,./other/words,./project_euler/problem_22/p022_names.txt" + codespell -L ans,fo,hist,iff,secant,tim --skip=$SKIP --quiet-level=2 diff --git a/maths/area_under_curve.py b/maths/area_under_curve.py index d05e9e4ae201..2d01e414b63b 100644 --- a/maths/area_under_curve.py +++ b/maths/area_under_curve.py @@ -4,10 +4,13 @@ from typing import Callable, Union -def trapezoidal_area(fnc: Callable[[Union[int, float]], Union[int, float]], - x_start: Union[int, float], - x_end: Union[int, float], - steps: int = 100) -> float: + +def trapezoidal_area( + fnc: Callable[[Union[int, float]], Union[int, float]], + x_start: Union[int, float], + x_end: Union[int, float], + steps: int = 100, +) -> float: """ Treats curve as a collection of linear lines and sums the area of the trapezium shape they form @@ -34,9 +37,9 @@ def trapezoidal_area(fnc: Callable[[Union[int, float]], Union[int, float]], for i in range(steps): # Approximates small segments of curve as linear and solve # for trapezoidal area - x2 = (x_end - x_start)/steps + x1 + x2 = (x_end - x_start) / steps + x1 fx2 = fnc(x2) - area += abs(fx2 + fx1) * (x2 - x1)/2 + area += abs(fx2 + fx1) * (x2 - x1) / 2 # Increment step x1 = x2 fx1 = fx2 @@ -44,12 +47,13 @@ def trapezoidal_area(fnc: Callable[[Union[int, float]], Union[int, float]], if __name__ == "__main__": + def f(x): - return x**3 + x**2 + return x ** 3 + x ** 2 print("f(x) = x^3 + x^2") print("The area between the curve, x = -5, x = 5 and the x axis is:") i = 10 while i <= 100000: print(f"with {i} steps: {trapezoidal_area(f, -5, 5, i)}") - i*=10 + i *= 10 diff --git a/maths/armstrong_numbers.py b/maths/armstrong_numbers.py index 8ce184b0cf44..4ed23dd1d1d7 100644 --- a/maths/armstrong_numbers.py +++ b/maths/armstrong_numbers.py @@ -24,7 +24,7 @@ def armstrong_number(n: int) -> bool: """ if not isinstance(n, int) or n < 1: return False - + # Initialization of sum and number of digits. sum = 0 number_of_digits = 0 @@ -37,7 +37,7 @@ def armstrong_number(n: int) -> bool: temp = n while temp > 0: rem = temp % 10 - sum += (rem ** number_of_digits) + sum += rem ** number_of_digits temp //= 10 return n == sum @@ -50,7 +50,7 @@ def main(): print(f"{num} is {'' if armstrong_number(num) else 'not '}an Armstrong number.") -if __name__ == '__main__': +if __name__ == "__main__": import doctest doctest.testmod() diff --git a/maths/line_length.py b/maths/line_length.py index 8737a863b902..0b1ddb5b7866 100644 --- a/maths/line_length.py +++ b/maths/line_length.py @@ -1,10 +1,13 @@ from typing import Callable, Union import math as m -def line_length(fnc: Callable[[Union[int, float]], Union[int, float]], - x_start: Union[int, float], - x_end: Union[int, float], - steps: int = 100) -> float: + +def line_length( + fnc: Callable[[Union[int, float]], Union[int, float]], + x_start: Union[int, float], + x_end: Union[int, float], + steps: int = 100, +) -> float: """ Approximates the arc length of a line segment by treating the curve as a @@ -48,10 +51,11 @@ def line_length(fnc: Callable[[Union[int, float]], Union[int, float]], return length + if __name__ == "__main__": def f(x): - return m.sin(10*x) + return m.sin(10 * x) print("f(x) = sin(10 * x)") print("The length of the curve from x = -10 to x = 10 is:") diff --git a/maths/numerical_integration.py b/maths/numerical_integration.py index 55026f0d627f..67fbc0ddbf30 100644 --- a/maths/numerical_integration.py +++ b/maths/numerical_integration.py @@ -4,10 +4,13 @@ from typing import Callable, Union -def trapezoidal_area(fnc: Callable[[Union[int, float]], Union[int, float]], - x_start: Union[int, float], - x_end: Union[int, float], - steps: int = 100) -> float: + +def trapezoidal_area( + fnc: Callable[[Union[int, float]], Union[int, float]], + x_start: Union[int, float], + x_end: Union[int, float], + steps: int = 100, +) -> float: """ Treats curve as a collection of linear lines and sums the area of the @@ -39,9 +42,9 @@ def trapezoidal_area(fnc: Callable[[Union[int, float]], Union[int, float]], # Approximates small segments of curve as linear and solve # for trapezoidal area - x2 = (x_end - x_start)/steps + x1 + x2 = (x_end - x_start) / steps + x1 fx2 = fnc(x2) - area += abs(fx2 + fx1) * (x2 - x1)/2 + area += abs(fx2 + fx1) * (x2 - x1) / 2 # Increment step x1 = x2 @@ -52,7 +55,7 @@ def trapezoidal_area(fnc: Callable[[Union[int, float]], Union[int, float]], if __name__ == "__main__": def f(x): - return x**3 + return x ** 3 print("f(x) = x^3") print("The area between the curve, x = -10, x = 10 and the x axis is:") @@ -60,4 +63,4 @@ def f(x): while i <= 100000: area = trapezoidal_area(f, -5, 5, i) print("with {} steps: {}".format(i, area)) - i*=10 + i *= 10 From 63a1c4171a5a14f47b359d5439c279c03cef5c5f Mon Sep 17 00:00:00 2001 From: Faraz Ahmed Khan <31242842+fk03983@users.noreply.github.com> Date: Sat, 25 Jan 2020 00:18:43 -0600 Subject: [PATCH 0446/1071] Added implementation for Bezier Curve, under a new graphics directory. (#1713) * Added bezier curve * black formatted * corrected spell check * edited scipy import * updated documentation for readablitity * Update bezier_curve.py * Update bezier_curve.py Co-authored-by: Christian Clauss --- graphics/bezier_curve.py | 114 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 114 insertions(+) create mode 100644 graphics/bezier_curve.py diff --git a/graphics/bezier_curve.py b/graphics/bezier_curve.py new file mode 100644 index 000000000000..512efadf86ee --- /dev/null +++ b/graphics/bezier_curve.py @@ -0,0 +1,114 @@ +# https://en.wikipedia.org/wiki/B%C3%A9zier_curve +# https://www.tutorialspoint.com/computer_graphics/computer_graphics_curves.htm + +from typing import List, Tuple +from scipy.special import comb + + +class BezierCurve: + """ + Bezier curve is a weighted sum of a set of control points. + Generate Bezier curves from a given set of control points. + This implementation works only for 2d coordinates in the xy plane. + """ + + def __init__(self, list_of_points: List[Tuple[float, float]]): + """ + list_of_points: Control points in the xy plane on which to interpolate. These + points control the behavior (shape) of the Bezier curve. + """ + self.list_of_points = list_of_points + # Degree determines the flexibility of the curve. + # Degree = 1 will produce a straight line. + self.degree = len(list_of_points) - 1 + + def basis_function(self, t: float) -> List[float]: + """ + The basis function determines the weight of each control point at time t. + t: time value between 0 and 1 inclusive at which to evaluate the basis of + the curve. + returns the x, y values of basis function at time t + + >>> curve = BezierCurve([(1,1), (1,2)]) + >>> curve.basis_function(0) + [1.0, 0.0] + >>> curve.basis_function(1) + [0.0, 1.0] + """ + assert 0 <= t <= 1, "Time t must be between 0 and 1." + output_values: List[float] = [] + for i in range(len(self.list_of_points)): + # basis function for each i + output_values.append( + comb(self.degree, i) * ((1 - t) ** (self.degree - i)) * (t ** i) + ) + # the basis must sum up to 1 for it to produce a valid Bezier curve. + assert round(sum(output_values), 5) == 1 + return output_values + + def bezier_curve_function(self, t: float) -> Tuple[float, float]: + """ + The function to produce the values of the Bezier curve at time t. + t: the value of time t at which to evaluate the Bezier function + Returns the x, y coordinates of the Bezier curve at time t. + The first point in the curve is when t = 0. + The last point in the curve is when t = 1. + + >>> curve = BezierCurve([(1,1), (1,2)]) + >>> curve.bezier_curve_function(0) + (1.0, 1.0) + >>> curve.bezier_curve_function(1) + (1.0, 2.0) + """ + + assert 0 <= t <= 1, "Time t must be between 0 and 1." + + basis_function = self.basis_function(t) + x = 0.0 + y = 0.0 + for i in range(len(self.list_of_points)): + # For all points, sum up the product of i-th basis function and i-th point. + x += basis_function[i] * self.list_of_points[i][0] + y += basis_function[i] * self.list_of_points[i][1] + return (x, y) + + def plot_curve(self, step_size: float = 0.01): + """ + Plots the Bezier curve using matplotlib plotting capabilities. + step_size: defines the step(s) at which to evaluate the Bezier curve. + The smaller the step size, the finer the curve produced. + """ + import matplotlib.pyplot as plt + + to_plot_x: List[float] = [] # x coordinates of points to plot + to_plot_y: List[float] = [] # y coordinates of points to plot + + t = 0.0 + while t <= 1: + value = self.bezier_curve_function(t) + to_plot_x.append(value[0]) + to_plot_y.append(value[1]) + t += step_size + + x = [i[0] for i in self.list_of_points] + y = [i[1] for i in self.list_of_points] + + plt.plot( + to_plot_x, + to_plot_y, + color="blue", + label="Curve of Degree " + str(self.degree), + ) + plt.scatter(x, y, color="red", label="Control Points") + plt.legend() + plt.show() + + +if __name__ == "__main__": + import doctest + + doctest.testmod() + + BezierCurve([(1, 2), (3, 5)]).plot_curve() # degree 1 + BezierCurve([(0, 0), (5, 5), (5, 0)]).plot_curve() # degree 2 + BezierCurve([(0, 0), (5, 5), (5, 0), (2.5, -2.5)]).plot_curve() # degree 3 From 5c7d7782b0fd386b3fe205d34d73c24c241b3553 Mon Sep 17 00:00:00 2001 From: onlinejudge95 <44158581+onlinejudge95@users.noreply.github.com> Date: Tue, 28 Jan 2020 02:24:57 +0530 Subject: [PATCH 0447/1071] Mandates referencing issue in PR (#1717) * Mandates referencing issue in PR * Update CONTRIBUTING.md * Update pull_request_template.md * Update pull_request_template.md * Update pull_request_template.md * Update CONTRIBUTING.md Co-authored-by: John Law Co-authored-by: Christian Clauss --- .github/pull_request_template.md | 1 + CONTRIBUTING.md | 2 ++ 2 files changed, 3 insertions(+) diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 2f130896ebe3..3b7d70fed373 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -17,3 +17,4 @@ * [ ] All function parameters and return values are annotated with Python [type hints](https://docs.python.org/3/library/typing.html). * [ ] All functions have [doctests](https://docs.python.org/3/library/doctest.html) that pass the automated testing. * [ ] All new algorithms have a URL in its comments that points to Wikipedia or other similar explanation. +* [ ] If this issues resolves an open issue then the commit message contains `Fixes: #{$ISSUE_NO}` for auto cleanup. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index ce2f03886e01..11b956d73193 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -25,6 +25,8 @@ We appreciate any contribution, from fixing a grammar mistake in a comment to im Your contribution will be tested by our [automated testing on Travis CI](https://travis-ci.org/TheAlgorithms/Python/pull_requests) to save time and mental energy. After you have submitted your pull request, you should see the Travis tests start to run at the bottom of your submission page. If those tests fail, then click on the ___details___ button try to read through the Travis output to understand the failure. If you do not understand, please leave a comment on your submission page and a community member will try to help. +Please help us keep our issue list small by adding fixes: #{$ISSUE_NO} to the commit message of pull requests that resolve open issues. GitHub will use this tag to auto close the issue when the PR is merged. + #### Coding Style We want your work to be readable by others; therefore, we encourage you to note the following: From bef74d0ecc8132ffd629c1a066d5dad446775d64 Mon Sep 17 00:00:00 2001 From: Christian Clauss Date: Mon, 27 Jan 2020 22:09:47 +0100 Subject: [PATCH 0448/1071] Fix typo (#1718) * Fix typo * updating DIRECTORY.md --- .github/pull_request_template.md | 2 +- DIRECTORY.md | 8 ++++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 3b7d70fed373..103ecf7c288a 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -17,4 +17,4 @@ * [ ] All function parameters and return values are annotated with Python [type hints](https://docs.python.org/3/library/typing.html). * [ ] All functions have [doctests](https://docs.python.org/3/library/doctest.html) that pass the automated testing. * [ ] All new algorithms have a URL in its comments that points to Wikipedia or other similar explanation. -* [ ] If this issues resolves an open issue then the commit message contains `Fixes: #{$ISSUE_NO}` for auto cleanup. +* [ ] If this pull request resolves one or more open issues then the commit message contains `Fixes: #{$ISSUE_NO}`. diff --git a/DIRECTORY.md b/DIRECTORY.md index eb17b3a7e78e..ff98c21894c5 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -190,6 +190,9 @@ ## Fuzzy Logic * [Fuzzy Operations](https://github.com/TheAlgorithms/Python/blob/master/fuzzy_logic/fuzzy_operations.py) +## Graphics + * [Bezier Curve](https://github.com/TheAlgorithms/Python/blob/master/graphics/bezier_curve.py) + ## Graphs * [A Star](https://github.com/TheAlgorithms/Python/blob/master/graphs/a_star.py) * [Articulation Points](https://github.com/TheAlgorithms/Python/blob/master/graphs/articulation_points.py) @@ -258,6 +261,8 @@ * [Abs](https://github.com/TheAlgorithms/Python/blob/master/maths/abs.py) * [Abs Max](https://github.com/TheAlgorithms/Python/blob/master/maths/abs_max.py) * [Abs Min](https://github.com/TheAlgorithms/Python/blob/master/maths/abs_min.py) + * [Area Under Curve](https://github.com/TheAlgorithms/Python/blob/master/maths/area_under_curve.py) + * [Armstrong Numbers](https://github.com/TheAlgorithms/Python/blob/master/maths/armstrong_numbers.py) * [Average Mean](https://github.com/TheAlgorithms/Python/blob/master/maths/average_mean.py) * [Average Median](https://github.com/TheAlgorithms/Python/blob/master/maths/average_median.py) * [Average Mode](https://github.com/TheAlgorithms/Python/blob/master/maths/average_mode.py) @@ -271,6 +276,7 @@ * [Eulers Totient](https://github.com/TheAlgorithms/Python/blob/master/maths/eulers_totient.py) * [Explicit Euler](https://github.com/TheAlgorithms/Python/blob/master/maths/explicit_euler.py) * [Extended Euclidean Algorithm](https://github.com/TheAlgorithms/Python/blob/master/maths/extended_euclidean_algorithm.py) + * [Factorial Iterative](https://github.com/TheAlgorithms/Python/blob/master/maths/factorial_iterative.py) * [Factorial Python](https://github.com/TheAlgorithms/Python/blob/master/maths/factorial_python.py) * [Factorial Recursive](https://github.com/TheAlgorithms/Python/blob/master/maths/factorial_recursive.py) * [Factors](https://github.com/TheAlgorithms/Python/blob/master/maths/factors.py) @@ -292,6 +298,7 @@ * [Kth Lexicographic Permutation](https://github.com/TheAlgorithms/Python/blob/master/maths/kth_lexicographic_permutation.py) * [Largest Of Very Large Numbers](https://github.com/TheAlgorithms/Python/blob/master/maths/largest_of_very_large_numbers.py) * [Least Common Multiple](https://github.com/TheAlgorithms/Python/blob/master/maths/least_common_multiple.py) + * [Line Length](https://github.com/TheAlgorithms/Python/blob/master/maths/line_length.py) * [Lucas Lehmer Primality Test](https://github.com/TheAlgorithms/Python/blob/master/maths/lucas_lehmer_primality_test.py) * [Lucas Series](https://github.com/TheAlgorithms/Python/blob/master/maths/lucas_series.py) * [Matrix Exponentiation](https://github.com/TheAlgorithms/Python/blob/master/maths/matrix_exponentiation.py) @@ -299,6 +306,7 @@ * [Mobius Function](https://github.com/TheAlgorithms/Python/blob/master/maths/mobius_function.py) * [Modular Exponential](https://github.com/TheAlgorithms/Python/blob/master/maths/modular_exponential.py) * [Newton Raphson](https://github.com/TheAlgorithms/Python/blob/master/maths/newton_raphson.py) + * [Numerical Integration](https://github.com/TheAlgorithms/Python/blob/master/maths/numerical_integration.py) * [Perfect Square](https://github.com/TheAlgorithms/Python/blob/master/maths/perfect_square.py) * [Polynomial Evaluation](https://github.com/TheAlgorithms/Python/blob/master/maths/polynomial_evaluation.py) * [Prime Check](https://github.com/TheAlgorithms/Python/blob/master/maths/prime_check.py) From 81f077adfc685ef5dc40031d042a8e30735f8426 Mon Sep 17 00:00:00 2001 From: cschuerc <57899042+cschuerc@users.noreply.github.com> Date: Tue, 28 Jan 2020 18:03:59 +0100 Subject: [PATCH 0449/1071] Augment binary search algorithms (#1719) --- searches/binary_search.py | 164 +++++++++++++++++++++++++++++++++++++- 1 file changed, 163 insertions(+), 1 deletion(-) diff --git a/searches/binary_search.py b/searches/binary_search.py index ff959c6cf2e3..fe22e423a7d4 100644 --- a/searches/binary_search.py +++ b/searches/binary_search.py @@ -1,5 +1,5 @@ """ -This is pure python implementation of binary search algorithm +This is pure python implementation of binary search algorithms For doctests run following command: python -m doctest -v binary_search.py @@ -12,6 +12,168 @@ import bisect +def bisect_left(sorted_collection, item, lo=0, hi=None): + """ + Locates the first element in a sorted array that is larger or equal to a given value. + + It has the same interface as https://docs.python.org/3/library/bisect.html#bisect.bisect_left . + + :param sorted_collection: some ascending sorted collection with comparable items + :param item: item to bisect + :param lo: lowest index to consider (as in sorted_collection[lo:hi]) + :param hi: past the highest index to consider (as in sorted_collection[lo:hi]) + :return: index i such that all values in sorted_collection[lo:i] are < item and all values in sorted_collection[i:hi] are >= item. + + Examples: + >>> bisect_left([0, 5, 7, 10, 15], 0) + 0 + + >>> bisect_left([0, 5, 7, 10, 15], 6) + 2 + + >>> bisect_left([0, 5, 7, 10, 15], 20) + 5 + + >>> bisect_left([0, 5, 7, 10, 15], 15, 1, 3) + 3 + + >>> bisect_left([0, 5, 7, 10, 15], 6, 2) + 2 + """ + if hi is None: + hi = len(sorted_collection) + + while lo < hi: + mid = (lo + hi) // 2 + if sorted_collection[mid] < item: + lo = mid + 1 + else: + hi = mid + + return lo + + +def bisect_right(sorted_collection, item, lo=0, hi=None): + """ + Locates the first element in a sorted array that is larger than a given value. + + It has the same interface as https://docs.python.org/3/library/bisect.html#bisect.bisect_right . + + :param sorted_collection: some ascending sorted collection with comparable items + :param item: item to bisect + :param lo: lowest index to consider (as in sorted_collection[lo:hi]) + :param hi: past the highest index to consider (as in sorted_collection[lo:hi]) + :return: index i such that all values in sorted_collection[lo:i] are <= item and all values in sorted_collection[i:hi] are > item. + + Examples: + >>> bisect_right([0, 5, 7, 10, 15], 0) + 1 + + >>> bisect_right([0, 5, 7, 10, 15], 15) + 5 + + >>> bisect_right([0, 5, 7, 10, 15], 6) + 2 + + >>> bisect_right([0, 5, 7, 10, 15], 15, 1, 3) + 3 + + >>> bisect_right([0, 5, 7, 10, 15], 6, 2) + 2 + """ + if hi is None: + hi = len(sorted_collection) + + while lo < hi: + mid = (lo + hi) // 2 + if sorted_collection[mid] <= item: + lo = mid + 1 + else: + hi = mid + + return lo + + +def insort_left(sorted_collection, item, lo=0, hi=None): + """ + Inserts a given value into a sorted array before other values with the same value. + + It has the same interface as https://docs.python.org/3/library/bisect.html#bisect.insort_left . + + :param sorted_collection: some ascending sorted collection with comparable items + :param item: item to insert + :param lo: lowest index to consider (as in sorted_collection[lo:hi]) + :param hi: past the highest index to consider (as in sorted_collection[lo:hi]) + + Examples: + >>> sorted_collection = [0, 5, 7, 10, 15] + >>> insort_left(sorted_collection, 6) + >>> sorted_collection + [0, 5, 6, 7, 10, 15] + + >>> sorted_collection = [(0, 0), (5, 5), (7, 7), (10, 10), (15, 15)] + >>> item = (5, 5) + >>> insort_left(sorted_collection, item) + >>> sorted_collection + [(0, 0), (5, 5), (5, 5), (7, 7), (10, 10), (15, 15)] + >>> item is sorted_collection[1] + True + >>> item is sorted_collection[2] + False + + >>> sorted_collection = [0, 5, 7, 10, 15] + >>> insort_left(sorted_collection, 20) + >>> sorted_collection + [0, 5, 7, 10, 15, 20] + + >>> sorted_collection = [0, 5, 7, 10, 15] + >>> insort_left(sorted_collection, 15, 1, 3) + >>> sorted_collection + [0, 5, 7, 15, 10, 15] + """ + sorted_collection.insert(bisect_left(sorted_collection, item, lo, hi), item) + + +def insort_right(sorted_collection, item, lo=0, hi=None): + """ + Inserts a given value into a sorted array after other values with the same value. + + It has the same interface as https://docs.python.org/3/library/bisect.html#bisect.insort_right . + + :param sorted_collection: some ascending sorted collection with comparable items + :param item: item to insert + :param lo: lowest index to consider (as in sorted_collection[lo:hi]) + :param hi: past the highest index to consider (as in sorted_collection[lo:hi]) + + Examples: + >>> sorted_collection = [0, 5, 7, 10, 15] + >>> insort_right(sorted_collection, 6) + >>> sorted_collection + [0, 5, 6, 7, 10, 15] + + >>> sorted_collection = [(0, 0), (5, 5), (7, 7), (10, 10), (15, 15)] + >>> item = (5, 5) + >>> insort_right(sorted_collection, item) + >>> sorted_collection + [(0, 0), (5, 5), (5, 5), (7, 7), (10, 10), (15, 15)] + >>> item is sorted_collection[1] + False + >>> item is sorted_collection[2] + True + + >>> sorted_collection = [0, 5, 7, 10, 15] + >>> insort_right(sorted_collection, 20) + >>> sorted_collection + [0, 5, 7, 10, 15, 20] + + >>> sorted_collection = [0, 5, 7, 10, 15] + >>> insort_right(sorted_collection, 15, 1, 3) + >>> sorted_collection + [0, 5, 7, 15, 10, 15] + """ + sorted_collection.insert(bisect_right(sorted_collection, item, lo, hi), item) + + def binary_search(sorted_collection, item): """Pure implementation of binary search algorithm in Python From 74a7b5f799d7377f9bb536919d415c84eb906787 Mon Sep 17 00:00:00 2001 From: tania-cmyk <58653046+tania-cmyk@users.noreply.github.com> Date: Mon, 3 Feb 2020 14:30:58 +0530 Subject: [PATCH 0450/1071] relevant documentation added (#1725) --- ciphers/transposition_cipher.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/ciphers/transposition_cipher.py b/ciphers/transposition_cipher.py index b6c9195b5dee..3b69d6b99f67 100644 --- a/ciphers/transposition_cipher.py +++ b/ciphers/transposition_cipher.py @@ -1,6 +1,11 @@ import math - +''' +In cryptography, the TRANSPOSITION cipher is a method of encryption where the +positions of plaintext are shifted a certain number(determined by the key) that +follows a regular system that results in the permuted text, known as the encrypted +text. The type of transposition cipher demonstrated under is the ROUTE cipher. +''' def main(): message = input("Enter message: ") key = int(input("Enter key [2-%s]: " % (len(message) - 1))) From dacf1d0375dcfd4f8b294220c03df833a6d8ecac Mon Sep 17 00:00:00 2001 From: faizan2700 <46817346+faizan2700@users.noreply.github.com> Date: Wed, 5 Feb 2020 16:57:43 +0530 Subject: [PATCH 0451/1071] Implement Manacher's algorithm (#1721) * manacher's algorithm updated --- strings/manacher.py | 94 +++++++++++++++++++++++++++++++++------------ 1 file changed, 70 insertions(+), 24 deletions(-) diff --git a/strings/manacher.py b/strings/manacher.py index 4193def2f71b..95aba1fbe65d 100644 --- a/strings/manacher.py +++ b/strings/manacher.py @@ -1,25 +1,18 @@ -# calculate palindromic length from center with incrementing difference -def palindromic_length(center, diff, string): - if ( - center - diff == -1 - or center + diff == len(string) - or string[center - diff] != string[center + diff] - ): - return 0 - return 1 + palindromic_length(center, diff + 1, string) - - def palindromic_string(input_string): """ - Manacher’s algorithm which finds Longest Palindromic Substring in linear time. + >>> palindromic_string('abbbaba') + 'abbba' + >>> palindromic_string('ababa') + 'ababa' + + Manacher’s algorithm which finds Longest palindromic Substring in linear time. 1. first this convert input_string("xyx") into new_string("x|y|x") where odd positions are actual input characters. - 2. for each character in new_string it find corresponding length and store, - a. max_length - b. max_length's center - 3. return output_string from center - max_length to center + max_length and remove - all "|" + 2. for each character in new_string it find corresponding length and store the length + and l,r to store previously calculated info.(please look the explanation for details) + + 3. return corresponding output_string by removing all "|" """ max_length = 0 @@ -33,19 +26,38 @@ def palindromic_string(input_string): # append last character new_input_string += input_string[-1] + # we will store the starting and ending of previous furthest ending palindromic substring + l, r = 0, 0 + + # length[i] shows the length of palindromic substring with center i + length = [1 for i in range(len(new_input_string))] + # for each character in new_string find corresponding palindromic string for i in range(len(new_input_string)): + k = 1 if i > r else min(length[l + r - i] // 2, r - i + 1) + while ( + i - k >= 0 + and i + k < len(new_input_string) + and new_input_string[k + i] == new_input_string[i - k] + ): + k += 1 + + length[i] = 2 * k - 1 - # get palindromic length from i-th position - length = palindromic_length(i, 1, new_input_string) + # does this string is ending after the previously explored end (that is r) ? + # if yes the update the new r to the last index of this + if i + k - 1 > r: + l = i - k + 1 + r = i + k - 1 # update max_length and start position - if max_length < length: - max_length = length + if max_length < length[i]: + max_length = length[i] start = i # create that string - for i in new_input_string[start - max_length : start + max_length + 1]: + s = new_input_string[start - max_length // 2 : start + max_length // 2 + 1] + for i in s: if i != "|": output_string += i @@ -53,5 +65,39 @@ def palindromic_string(input_string): if __name__ == "__main__": - n = input() - print(palindromic_string(n)) + import doctest + + doctest.testmod() + +""" +...a0...a1...a2.....a3......a4...a5...a6.... + +consider the string for which we are calculating the longest palindromic substring is shown above where ... +are some characters in between and right now we are calculating the length of palindromic substring with +center at a5 with following conditions : +i) we have stored the length of palindromic substring which has center at a3 (starts at l ends at r) and it + is the furthest ending till now, and it has ending after a6 +ii) a2 and a4 are equally distant from a3 so char(a2) == char(a4) +iii) a0 and a6 are equally distant from a3 so char(a0) == char(a6) +iv) a1 is corresponding equal character of a5 in palindrome with center a3 (remember that in below derivation of a4==a6) + +now for a5 we will calculate the length of palindromic substring with center as a5 but can we use previously +calculated information in some way? +Yes, look the above string we know that a5 is inside the palindrome with center a3 and previously we have +have calculated that +a0==a2 (palindrome of center a1) +a2==a4 (palindrome of center a3) +a0==a6 (palindrome of center a3) +so a4==a6 + +so we can say that palindrome at center a5 is at least as long as palindrome at center a1 +but this only holds if a0 and a6 are inside the limits of palindrome centered at a3 so finally .. + +len_of_palindrome__at(a5) = min(len_of_palindrome_at(a1), r-a5) +where a3 lies from l to r and we have to keep updating that + +and if the a5 lies outside of l,r boundary we calculate length of palindrome with bruteforce and update +l,r. + +it gives the linear time complexity just like z-function +""" From e7041a8ecafd6df97e00b2c801457b508654c290 Mon Sep 17 00:00:00 2001 From: Ale3androsS <37119970+Ale3androsS@users.noreply.github.com> Date: Thu, 6 Feb 2020 21:48:58 +0200 Subject: [PATCH 0452/1071] Added first come first served scheduling (#1722) * Added FCFS * Fixed spelling error * Rename fcfs.py to first_come_first_served.py * Fixed FCFS and added tests. * Made changes requested * Use enumerate() instead of range(len()) Co-authored-by: Christian Clauss --- scheduling/first_come_first_served.py | 104 ++++++++++++++++++++++++++ 1 file changed, 104 insertions(+) create mode 100644 scheduling/first_come_first_served.py diff --git a/scheduling/first_come_first_served.py b/scheduling/first_come_first_served.py new file mode 100644 index 000000000000..d339273fe741 --- /dev/null +++ b/scheduling/first_come_first_served.py @@ -0,0 +1,104 @@ +# Implementation of First Come First Served scheduling algorithm +# In this Algorithm we just care about the order that the processes arrived +# without carring about their duration time +# https://en.wikipedia.org/wiki/Scheduling_(computing)#First_come,_first_served +from typing import List + + +def calculate_waiting_times(duration_times: List[int]) -> List[int]: + """ + This function calculates the waiting time of some processes that have a + specified duration time. + Return: The waiting time for each process. + >>> calculate_waiting_times([5, 10, 15]) + [0, 5, 15] + >>> calculate_waiting_times([1, 2, 3, 4, 5]) + [0, 1, 3, 6, 10] + >>> calculate_waiting_times([10, 3]) + [0, 10] + """ + waiting_times = [0] * len(duration_times) + for i in range(1, len(duration_times)): + waiting_times[i] = duration_times[i - 1] + waiting_times[i - 1] + return waiting_times + + +def calculate_turnaround_times( + duration_times: List[int], waiting_times: List[int] +) -> List[int]: + """ + This function calculates the turnaround time of some processes. + Return: The time difference between the completion time and the + arrival time. + Practically waiting_time + duration_time + >>> calculate_turnaround_times([5, 10, 15], [0, 5, 15]) + [5, 15, 30] + >>> calculate_turnaround_times([1, 2, 3, 4, 5], [0, 1, 3, 6, 10]) + [1, 3, 6, 10, 15] + >>> calculate_turnaround_times([10, 3], [0, 10]) + [10, 13] + """ + return [duration_time + waiting_times[i] for i, duration_time in enumerate(duration_times)] + + +def calculate_average_turnaround_time(turnaround_times: List[int]) -> float: + """ + This function calculates the average of the turnaround times + Return: The average of the turnaround times. + >>> calculate_average_turnaround_time([0, 5, 16]) + 7.0 + >>> calculate_average_turnaround_time([1, 5, 8, 12]) + 6.5 + >>> calculate_average_turnaround_time([10, 24]) + 17.0 + """ + return sum(turnaround_times) / len(turnaround_times) + + +def calculate_average_waiting_time(waiting_times: List[int]) -> float: + """ + This function calculates the average of the waiting times + Return: The average of the waiting times. + >>> calculate_average_waiting_time([0, 5, 16]) + 7.0 + >>> calculate_average_waiting_time([1, 5, 8, 12]) + 6.5 + >>> calculate_average_waiting_time([10, 24]) + 17.0 + """ + return sum(waiting_times) / len(waiting_times) + + +if __name__ == "__main__": + # process id's + processes = [1, 2, 3] + + # ensure that we actually have processes + if len(processes) == 0: + print("Zero amount of processes") + exit() + + # duration time of all processes + duration_times = [19, 8, 9] + + # ensure we can match each id to a duration time + if len(duration_times) != len(processes): + print("Unable to match all id's with their duration time") + exit() + + # get the waiting times and the turnaround times + waiting_times = calculate_waiting_times(duration_times) + turnaround_times = calculate_turnaround_times(duration_times, waiting_times) + + # get the average times + average_waiting_time = calculate_average_waiting_time(waiting_times) + average_turnaround_time = calculate_average_turnaround_time(turnaround_times) + + # print all the results + print("Process ID\tDuration Time\tWaiting Time\tTurnaround Time") + for i, process in enumerate(processes): + print( + f"{process}\t\t{duration_times[i]}\t\t{waiting_times[i]}\t\t{turnaround_times[i]}" + ) + print(f"Average waiting time = {average_waiting_time}") + print(f"Average turn around time = {average_turnaround_time}") From 1608d75351be2b11b0e1ce891e2f71296f0be24b Mon Sep 17 00:00:00 2001 From: TheSuperNoob Date: Thu, 6 Feb 2020 22:00:08 +0100 Subject: [PATCH 0453/1071] Improve collatz_sequence algorithm (#1726) - Add more doctests and type checking to make sure only natural numbers are used - Simplified the algorithm slightly This new verison is also between 10-15% faster for really long sequences --- maths/collatz_sequence.py | 34 ++++++++++++++++++++++++---------- 1 file changed, 24 insertions(+), 10 deletions(-) diff --git a/maths/collatz_sequence.py b/maths/collatz_sequence.py index a5f044a62b18..d3eb6e756dcd 100644 --- a/maths/collatz_sequence.py +++ b/maths/collatz_sequence.py @@ -1,19 +1,33 @@ -def collatz_sequence(n): +from typing import List + + +def collatz_sequence(n: int) -> List[int]: """ - Collatz conjecture: start with any positive integer n.Next term is obtained from the previous term as follows: - if the previous term is even, the next term is one half of the previous term. - If the previous term is odd, the next term is 3 times the previous term plus 1. - The conjecture states the sequence will always reach 1 regaardless of starting value n. + Collatz conjecture: start with any positive integer n. The next term is + obtained as follows: + If n term is even, the next term is: n / 2 . + If n is odd, the next term is: 3 * n + 1. + + The conjecture states the sequence will always reach 1 for any starting value n. Example: + >>> collatz_sequence(2.1) + Traceback (most recent call last): + ... + Exception: Sequence only defined for natural numbers + >>> collatz_sequence(0) + Traceback (most recent call last): + ... + Exception: Sequence only defined for natural numbers >>> collatz_sequence(43) [43, 130, 65, 196, 98, 49, 148, 74, 37, 112, 56, 28, 14, 7, 22, 11, 34, 17, 52, 26, 13, 40, 20, 10, 5, 16, 8, 4, 2, 1] """ + + if not isinstance(n, int) or n < 1: + raise Exception("Sequence only defined for natural numbers") + sequence = [n] while n != 1: - if n % 2 == 0: # even number condition - n //= 2 - else: - n = 3 * n + 1 + n = 3 * n + 1 if n & 1 else n // 2 sequence.append(n) return sequence @@ -22,7 +36,7 @@ def main(): n = 43 sequence = collatz_sequence(n) print(sequence) - print("collatz sequence from %d took %d steps." % (n, len(sequence))) + print(f"collatz sequence from {n} took {len(sequence)} steps.") if __name__ == "__main__": From f52b97f2c56aadfd30a7384cff62d8354157b9cb Mon Sep 17 00:00:00 2001 From: Miggelito Date: Fri, 7 Feb 2020 19:37:14 +0100 Subject: [PATCH 0454/1071] Added Random Forest Regressor and tested with flake8 (#1733) * Added Random Forest Regressor * Updated file to standard --- machine_learning/random_forest_regressor.py | 42 +++++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 machine_learning/random_forest_regressor.py diff --git a/machine_learning/random_forest_regressor.py b/machine_learning/random_forest_regressor.py new file mode 100644 index 000000000000..f6c470f0975a --- /dev/null +++ b/machine_learning/random_forest_regressor.py @@ -0,0 +1,42 @@ +# Random Forest Regressor Example + +from sklearn.datasets import load_boston +from sklearn.model_selection import train_test_split +from sklearn.ensemble import RandomForestRegressor +from sklearn.metrics import mean_absolute_error +from sklearn.metrics import mean_squared_error + + +def main(): + + """ + Random Tree Regressor Example using sklearn function. + Boston house price dataset is used to demonstrate algorithm. + """ + + # Load Boston house price dataset + boston = load_boston() + print(boston.keys()) + + # Split dataset into train and test data + X = boston["data"] # features + Y = boston["target"] + x_train, x_test, y_train, y_test = train_test_split( + X, Y, test_size=0.3, random_state=1 + ) + + # Random Forest Regressor + rand_for = RandomForestRegressor(random_state=42, n_estimators=300) + rand_for.fit(x_train, y_train) + + # Predict target for test data + predictions = rand_for.predict(x_test) + predictions = predictions.reshape(len(predictions), 1) + + # Error printing + print(f"Mean Absolute Error:\t {mean_absolute_error(y_test, predictions)}") + print(f"Mean Square Error :\t {mean_squared_error(y_test, predictions)}") + + +if __name__ == "__main__": + main() From 670f952aa680f5c141e4ad6efc42cd8d75fdc628 Mon Sep 17 00:00:00 2001 From: Christian Clauss Date: Fri, 7 Feb 2020 22:02:08 +0200 Subject: [PATCH 0455/1071] =?UTF-8?q?Travis=20CI:=20Don=E2=80=99t=20allow?= =?UTF-8?q?=20bare=20exceptions=20(#1734)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Travis CI: Don’t allow bare exceptions * fixup! Format Python code with psf/black push * except IOError: * except IOError: * Update hamming_code.py * IndexError * Get rid of the nonsense logic Co-authored-by: John Law --- .travis.yml | 2 +- ciphers/transposition_cipher.py | 6 ++++-- ciphers/xor_cipher.py | 4 ++-- hashes/hamming_code.py | 4 ++-- linear_algebra/src/test_linear_algebra.py | 6 +----- 5 files changed, 10 insertions(+), 12 deletions(-) diff --git a/.travis.yml b/.travis.yml index 80ea1302990d..bd2dfbbe4496 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,7 +5,7 @@ before_install: pip install --upgrade pip setuptools install: pip install -r requirements.txt before_script: - black --check . || true - - flake8 . --count --select=E101,E9,F4,F63,F7,F82,W191 --show-source --statistics + - flake8 . --count --select=E101,E722,E9,F4,F63,F7,F82,W191 --show-source --statistics - flake8 . --count --exit-zero --max-line-length=127 --statistics script: - scripts/validate_filenames.py # no uppercase, no spaces, in a directory diff --git a/ciphers/transposition_cipher.py b/ciphers/transposition_cipher.py index 3b69d6b99f67..4bba88955433 100644 --- a/ciphers/transposition_cipher.py +++ b/ciphers/transposition_cipher.py @@ -1,11 +1,13 @@ import math -''' +""" In cryptography, the TRANSPOSITION cipher is a method of encryption where the positions of plaintext are shifted a certain number(determined by the key) that follows a regular system that results in the permuted text, known as the encrypted text. The type of transposition cipher demonstrated under is the ROUTE cipher. -''' +""" + + def main(): message = input("Enter message: ") key = int(input("Enter key [2-%s]: " % (len(message) - 1))) diff --git a/ciphers/xor_cipher.py b/ciphers/xor_cipher.py index 58b5352672ef..0fcfbb0b9ae2 100644 --- a/ciphers/xor_cipher.py +++ b/ciphers/xor_cipher.py @@ -148,7 +148,7 @@ def encrypt_file(self, file, key=0): for line in fin: fout.write(self.encrypt_string(line, key)) - except: + except IOError: return False return True @@ -173,7 +173,7 @@ def decrypt_file(self, file, key): for line in fin: fout.write(self.decrypt_string(line, key)) - except: + except IOError: return False return True diff --git a/hashes/hamming_code.py b/hashes/hamming_code.py index 3e0424490781..1246e1817c76 100644 --- a/hashes/hamming_code.py +++ b/hashes/hamming_code.py @@ -125,7 +125,7 @@ def emitterConverter(sizePar, data): if x != None: try: aux = (binPos[contLoop])[-1 * (bp)] - except: + except IndexError: aux = "0" if aux == "1": if x == "1": @@ -229,7 +229,7 @@ def receptorConverter(sizePar, data): if x != None: try: aux = (binPos[contLoop])[-1 * (bp)] - except: + except IndexError: aux = "0" if aux == "1": if x == "1": diff --git a/linear_algebra/src/test_linear_algebra.py b/linear_algebra/src/test_linear_algebra.py index 5e28910af86a..8d2170e46da4 100644 --- a/linear_algebra/src/test_linear_algebra.py +++ b/linear_algebra/src/test_linear_algebra.py @@ -19,11 +19,7 @@ def test_component(self): x = Vector([1, 2, 3]) self.assertEqual(x.component(0), 1) self.assertEqual(x.component(2), 3) - try: - y = Vector() - self.assertTrue(False) - except: - self.assertTrue(True) + y = Vector() def test_str(self): """ From 32ceec550f085439404fe1bbdc8d6e952a269595 Mon Sep 17 00:00:00 2001 From: MatteoRaso <33975162+MatteoRaso@users.noreply.github.com> Date: Sat, 8 Feb 2020 15:47:11 -0500 Subject: [PATCH 0456/1071] Added a Monte Carlo simulation (#1723) * Added montecarlo.py This algorithm uses a Monte Carlo simulation to estimate the value of pi. * Rename montecarlo.py to maths/montecarlo.py * Add files via upload * Delete montecarlo.py * Rename montecarlo.py to maths/montecarlo.py * Update montecarlo.py --- maths/montecarlo.py | 43 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 maths/montecarlo.py diff --git a/maths/montecarlo.py b/maths/montecarlo.py new file mode 100644 index 000000000000..903012429c06 --- /dev/null +++ b/maths/montecarlo.py @@ -0,0 +1,43 @@ +""" +@author: MatteoRaso +""" +from numpy import pi, sqrt +from random import uniform + +def pi_estimator(iterations: int): + """An implementation of the Monte Carlo method used to find pi. + 1. Draw a 2x2 square centred at (0,0). + 2. Inscribe a circle within the square. + 3. For each iteration, place a dot anywhere in the square. + 3.1 Record the number of dots within the circle. + 4. After all the dots are placed, divide the dots in the circle by the total. + 5. Multiply this value by 4 to get your estimate of pi. + 6. Print the estimated and numpy value of pi + """ + + + circle_dots = 0 + + # A local function to see if a dot lands in the circle. + def circle(x: float, y: float): + distance_from_centre = sqrt((x ** 2) + (y ** 2)) + # Our circle has a radius of 1, so a distance greater than 1 would land outside the circle. + return distance_from_centre <= 1 + + circle_dots = sum( + int(circle(uniform(-1.0, 1.0), uniform(-1.0, 1.0))) for i in range(iterations) + ) + + # The proportion of guesses that landed within the circle + proportion = circle_dots / iterations + # The ratio of the area for circle to square is pi/4. + pi_estimate = proportion * 4 + print("The estimated value of pi is ", pi_estimate) + print("The numpy value of pi is ", pi) + print("The total error is ", abs(pi - pi_estimate)) + + +if __name__ == "__main__": + import doctest + + doctest.testmod() From 80718bd8802e9afd19c986b4cae18beb38d8058c Mon Sep 17 00:00:00 2001 From: onlinejudge95 <44158581+onlinejudge95@users.noreply.github.com> Date: Mon, 10 Feb 2020 16:13:57 +0530 Subject: [PATCH 0457/1071] Fixes black failures (#1742) --- maths/montecarlo.py | 2 +- scheduling/first_come_first_served.py | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/maths/montecarlo.py b/maths/montecarlo.py index 903012429c06..ce8f69f64a15 100644 --- a/maths/montecarlo.py +++ b/maths/montecarlo.py @@ -4,6 +4,7 @@ from numpy import pi, sqrt from random import uniform + def pi_estimator(iterations: int): """An implementation of the Monte Carlo method used to find pi. 1. Draw a 2x2 square centred at (0,0). @@ -15,7 +16,6 @@ def pi_estimator(iterations: int): 6. Print the estimated and numpy value of pi """ - circle_dots = 0 # A local function to see if a dot lands in the circle. diff --git a/scheduling/first_come_first_served.py b/scheduling/first_come_first_served.py index d339273fe741..f52c4243dec3 100644 --- a/scheduling/first_come_first_served.py +++ b/scheduling/first_come_first_served.py @@ -38,7 +38,10 @@ def calculate_turnaround_times( >>> calculate_turnaround_times([10, 3], [0, 10]) [10, 13] """ - return [duration_time + waiting_times[i] for i, duration_time in enumerate(duration_times)] + return [ + duration_time + waiting_times[i] + for i, duration_time in enumerate(duration_times) + ] def calculate_average_turnaround_time(turnaround_times: List[int]) -> float: From 6fdd53c6768b6b87b9bf7bc2203d0f5e7af9129c Mon Sep 17 00:00:00 2001 From: onlinejudge95 <44158581+onlinejudge95@users.noreply.github.com> Date: Tue, 11 Feb 2020 02:53:19 +0530 Subject: [PATCH 0458/1071] Fixes LGTM issues (#1745) * Fixes redefinition of a variable * Fixes implementing __eq__ * Updates docstring --- .../max_sum_contiguous_subsequence.py | 2 +- searches/hill_climbing.py | 30 ++++++++++++------- 2 files changed, 21 insertions(+), 11 deletions(-) diff --git a/dynamic_programming/max_sum_contiguous_subsequence.py b/dynamic_programming/max_sum_contiguous_subsequence.py index 2cbdb97a1759..bac592370c5d 100644 --- a/dynamic_programming/max_sum_contiguous_subsequence.py +++ b/dynamic_programming/max_sum_contiguous_subsequence.py @@ -6,7 +6,7 @@ def max_subarray_sum(nums: list) -> int: if not nums: return 0 n = len(nums) - s = [0] * n + res, s, s_pre = nums[0], nums[0], nums[0] for i in range(1, n): s = max(nums[i], s_pre + nums[i]) diff --git a/searches/hill_climbing.py b/searches/hill_climbing.py index c1129514c04f..324097ef5a24 100644 --- a/searches/hill_climbing.py +++ b/searches/hill_climbing.py @@ -4,17 +4,18 @@ class SearchProblem: """ - A interface to define search problems. The interface will be illustrated using - the example of mathematical function. + An interface to define search problems. + The interface will be illustrated using the example of mathematical function. """ def __init__(self, x: int, y: int, step_size: int, function_to_optimize): """ The constructor of the search problem. - x: the x coordinate of the current search state. - y: the y coordinate of the current search state. - step_size: size of the step to take when looking for neighbors. - function_to_optimize: a function to optimize having the signature f(x, y). + + x: the x coordinate of the current search state. + y: the y coordinate of the current search state. + step_size: size of the step to take when looking for neighbors. + function_to_optimize: a function to optimize having the signature f(x, y). """ self.x = x self.y = y @@ -63,6 +64,14 @@ def __hash__(self): """ return hash(str(self)) + def __eq__(self, obj): + """ + Check if the 2 objects are equal. + """ + if isinstance(obj, SearchProblem): + return hash(str(self)) == hash(str(obj)) + return False + def __str__(self): """ string representation of the current search state. @@ -85,10 +94,11 @@ def hill_climbing( max_iter: int = 10000, ) -> SearchProblem: """ - implementation of the hill climbling algorithm. We start with a given state, find - all its neighbors, move towards the neighbor which provides the maximum (or - minimum) change. We keep doing this until we are at a state where we do not - have any neighbors which can improve the solution. + Implementation of the hill climbling algorithm. + We start with a given state, find all its neighbors, + move towards the neighbor which provides the maximum (or minimum) change. + We keep doing this until we are at a state where we do not have any + neighbors which can improve the solution. Args: search_prob: The search state at the start. find_max: If True, the algorithm should find the maximum else the minimum. From abd320052f97262f44e3942ee061dea54ead4660 Mon Sep 17 00:00:00 2001 From: ayoub-edh <60881229+ayoub-edh@users.noreply.github.com> Date: Mon, 10 Feb 2020 22:26:59 +0100 Subject: [PATCH 0459/1071] Add gitpod config (#1744) * Add gitpod config * Add Gitpod to icon bar --- .gitpod.yml | 2 ++ README.md | 12 +++--------- 2 files changed, 5 insertions(+), 9 deletions(-) create mode 100644 .gitpod.yml diff --git a/.gitpod.yml b/.gitpod.yml new file mode 100644 index 000000000000..a5bc5751a3f6 --- /dev/null +++ b/.gitpod.yml @@ -0,0 +1,2 @@ +tasks: + - init: pip install -r ./requirements.txt diff --git a/README.md b/README.md index 51b2cf8c854c..7fc4f0f0b397 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,10 @@ # The Algorithms - Python - -[![Donate](https://img.shields.io/badge/Donate-PayPal-green.svg?logo=paypal&style=flat-square)](https://www.paypal.me/TheAlgorithms/100)  +[![Gitpod Ready-to-Code](https://img.shields.io/badge/Gitpod-Ready--to--Code-blue?logo=gitpod)](https://gitpod.io/#https://github.com/TheAlgorithms/Python) +[![Gitter chat](https://img.shields.io/badge/Chat-Gitter-ff69b4.svg?label=Chat&logo=gitter&style=flat-square)](https://gitter.im/TheAlgorithms)  [![Build Status](https://img.shields.io/travis/TheAlgorithms/Python.svg?label=Travis%20CI&logo=travis&style=flat-square)](https://travis-ci.com/TheAlgorithms/Python)  [![LGTM](https://img.shields.io/lgtm/alerts/github/TheAlgorithms/Python.svg?label=LGTM&logo=LGTM&style=flat-square)](https://lgtm.com/projects/g/TheAlgorithms/Python/alerts)  -[![Gitter chat](https://img.shields.io/badge/Chat-Gitter-ff69b4.svg?label=Chat&logo=gitter&style=flat-square)](https://gitter.im/TheAlgorithms)  [![contributions welcome](https://img.shields.io/static/v1.svg?label=Contributions&message=Welcome&color=0059b3&style=flat-square)](https://github.com/TheAlgorithms/Python/blob/master/CONTRIBUTING.md)  +[![Donate](https://img.shields.io/badge/Donate-PayPal-green.svg?logo=paypal&style=flat-square)](https://www.paypal.me/TheAlgorithms/100)  ![](https://img.shields.io/github/repo-size/TheAlgorithms/Python.svg?label=Repo%20size&style=flat-square)  @@ -23,9 +23,3 @@ We're on [Gitter](https://gitter.im/TheAlgorithms)! Please join us. ## List of Algorithms See our [directory](DIRECTORY.md). - - - - - -[![Open in Gitpod](https://gitpod.io/button/open-in-gitpod.svg?style=flat-square)](https://gitpod.io/#https://github.com/TheAlgorithms/Python) From 1096aa2336dae1350b75fcf395a38495a1d9b1e0 Mon Sep 17 00:00:00 2001 From: Jimmy Y Date: Mon, 10 Feb 2020 20:53:26 -0800 Subject: [PATCH 0460/1071] Added DP Solution for Optimal BST Problem (#1740) * Added code to dynamic_programming directory * Added doctest * Elaborated BST * Small tweaks * Update optimal_bst.py * Some touchups * Fixed doctest * Update optimal_bst.py * Update optimal_bst.py * Update optimal_bst.py * Rename optimal_bst.py to optimal_binary_search_tree.py Co-authored-by: Christian Clauss --- .../optimal_binary_search_tree.py | 144 ++++++++++++++++++ 1 file changed, 144 insertions(+) create mode 100644 dynamic_programming/optimal_binary_search_tree.py diff --git a/dynamic_programming/optimal_binary_search_tree.py b/dynamic_programming/optimal_binary_search_tree.py new file mode 100644 index 000000000000..b0f248acf35c --- /dev/null +++ b/dynamic_programming/optimal_binary_search_tree.py @@ -0,0 +1,144 @@ +#!/usr/bin/env python3 + +# This Python program implements an optimal binary search tree (abbreviated BST) +# building dynamic programming algorithm that delivers O(n^2) performance. +# +# The goal of the optimal BST problem is to build a low-cost BST for a +# given set of nodes, each with its own key and frequency. The frequency +# of the node is defined as how many time the node is being searched. +# The search cost of binary search tree is given by this formula: +# +# cost(1, n) = sum{i = 1 to n}((depth(node_i) + 1) * node_i_freq) +# +# where n is number of nodes in the BST. The characteristic of low-cost +# BSTs is having a faster overall search time than other implementations. +# The reason for their fast search time is that the nodes with high +# frequencies will be placed near the root of the tree while the nodes +# with low frequencies will be placed near the leaves of the tree thus +# reducing search time in the most frequent instances. + +import sys + +from random import randint + + +class Node: + """Binary Search Tree Node""" + def __init__(self, key, freq): + self.key = key + self.freq = freq + + def __str__(self): + """ + >>> str(Node(1, 2)) + 'Node(key=1, freq=2)' + """ + return f"Node(key={self.key}, freq={self.freq})" + + +def print_binary_search_tree(root, key, i, j, parent, is_left): + """ + Recursive function to print a BST from a root table. + + >>> key = [3, 8, 9, 10, 17, 21] + >>> root = [[0, 1, 1, 1, 1, 1], [0, 1, 1, 1, 1, 3], [0, 0, 2, 3, 3, 3], \ + [0, 0, 0, 3, 3, 3], [0, 0, 0, 0, 4, 5], [0, 0, 0, 0, 0, 5]] + >>> print_binary_search_tree(root, key, 0, 5, -1, False) + 8 is the root of the binary search tree. + 3 is the left child of key 8. + 10 is the right child of key 8. + 9 is the left child of key 10. + 21 is the right child of key 10. + 17 is the left child of key 21. + """ + if i > j or i < 0 or j > len(root) - 1: + return + + node = root[i][j] + if parent == -1: # root does not have a parent + print(f"{key[node]} is the root of the binary search tree.") + elif is_left: + print(f"{key[node]} is the left child of key {parent}.") + else: + print(f"{key[node]} is the right child of key {parent}.") + + print_binary_search_tree(root, key, i, node - 1, key[node], True) + print_binary_search_tree(root, key, node + 1, j, key[node], False) + + +def find_optimal_binary_search_tree(nodes): + """ + This function calculates and prints the optimal binary search tree. + The dynamic programming algorithm below runs in O(n^2) time. + Implemented from CLRS (Introduction to Algorithms) book. + https://en.wikipedia.org/wiki/Introduction_to_Algorithms + + >>> find_optimal_binary_search_tree([Node(12, 8), Node(10, 34), Node(20, 50), \ + Node(42, 3), Node(25, 40), Node(37, 30)]) + Binary search tree nodes: + Node(key=10, freq=34) + Node(key=12, freq=8) + Node(key=20, freq=50) + Node(key=25, freq=40) + Node(key=37, freq=30) + Node(key=42, freq=3) + + The cost of optimal BST for given tree nodes is 324. + 20 is the root of the binary search tree. + 10 is the left child of key 20. + 12 is the right child of key 10. + 25 is the right child of key 20. + 37 is the right child of key 25. + 42 is the right child of key 37. + """ + # Tree nodes must be sorted first, the code below sorts the keys in + # increasing order and rearrange its frequencies accordingly. + nodes.sort(key=lambda node: node.key) + + n = len(nodes) + + keys = [nodes[i].key for i in range(n)] + freqs = [nodes[i].freq for i in range(n)] + + # This 2D array stores the overall tree cost (which's as minimized as possible); + # for a single key, cost is equal to frequency of the key. + dp = [[freqs[i] if i == j else 0 for j in range(n)] for i in range(n)] + # sum[i][j] stores the sum of key frequencies between i and j inclusive in nodes array + sum = [[freqs[i] if i == j else 0 for j in range(n)] for i in range(n)] + # stores tree roots that will be used later for constructing binary search tree + root = [[i if i == j else 0 for j in range(n)] for i in range(n)] + + for l in range(2, n + 1): # l is an interval length + for i in range(n - l + 1): + j = i + l - 1 + + dp[i][j] = sys.maxsize # set the value to "infinity" + sum[i][j] = sum[i][j - 1] + freqs[j] + + # Apply Knuth's optimization + # Loop without optimization: for r in range(i, j + 1): + for r in range(root[i][j - 1], root[i + 1][j] + 1): # r is a temporal root + left = dp[i][r - 1] if r != i else 0 # optimal cost for left subtree + right = dp[r + 1][j] if r != j else 0 # optimal cost for right subtree + cost = left + sum[i][j] + right + + if dp[i][j] > cost: + dp[i][j] = cost + root[i][j] = r + + print("Binary search tree nodes:") + for node in nodes: + print(node) + + print(f"\nThe cost of optimal BST for given tree nodes is {dp[0][n - 1]}.") + print_binary_search_tree(root, keys, 0, n - 1, -1, False) + + +def main(): + # A sample binary search tree + nodes = [Node(i, randint(1, 50)) for i in range(10, 0, -1)] + find_optimal_binary_search_tree(nodes) + + +if __name__ == "__main__": + main() From fde31c93a3f7fc16547c217ae5cfbacef503c46d Mon Sep 17 00:00:00 2001 From: billpaps <37051006+billpaps@users.noreply.github.com> Date: Tue, 11 Feb 2020 10:20:24 +0200 Subject: [PATCH 0461/1071] Added Bisection algorithm (#1739) * Create Bisection.py Find root of * Update Bisection.py * Update Bisection.py i changed the given function with one that i could make the doctests. * Rename Bisection.py to bisection.py * Update bisection.py * Update bisection.py * Update bisection.py * Update bisection.py * Update bisection.py Made the changes that were requested * Update bisection.py * Update bisection.py * Add wiki url Co-authored-by: Christian Clauss --- maths/bisection.py | 61 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 61 insertions(+) create mode 100644 maths/bisection.py diff --git a/maths/bisection.py b/maths/bisection.py new file mode 100644 index 000000000000..a9df15b775b3 --- /dev/null +++ b/maths/bisection.py @@ -0,0 +1,61 @@ +""" +Given a function on floating number f(x) and two floating numbers ‘a’ and ‘b’ such that +f(a) * f(b) < 0 and f(x) is continuous in [a, b]. +Here f(x) represents algebraic or transcendental equation. +Find root of function in interval [a, b] (Or find a value of x such that f(x) is 0) + +https://en.wikipedia.org/wiki/Bisection_method +""" +def equation(x: float) -> float: + """ + >>> equation(5) + -15 + >>> equation(0) + 10 + >>> equation(-5) + -15 + >>> equation(0.1) + 9.99 + >>> equation(-0.1) + 9.99 + """ + return 10 - x * x + + +def bisection(a: float, b: float) -> float: + """ + >>> bisection(-2, 5) + 3.1611328125 + >>> bisection(0, 6) + 3.158203125 + >>> bisection(2, 3) + Traceback (most recent call last): + ... + ValueError: Wrong space! + """ + # Bolzano theory in order to find if there is a root between a and b + if equation(a) * equation(b) >= 0: + raise ValueError("Wrong space!") + + c = a + while (b - a) >= 0.01: + # Find middle point + c = (a + b) / 2 + # Check if middle point is root + if equation(c) == 0.0: + break + # Decide the side to repeat the steps + if equation(c) * equation(a) < 0: + b = c + else: + a = c + return c + + +if __name__ == "__main__": + import doctest + + doctest.testmod() + + print(bisection(-2, 5)) + print(bisection(0, 6)) From 7b7c1a0135580251990c7866aed39202f9928b1f Mon Sep 17 00:00:00 2001 From: onlinejudge95 <44158581+onlinejudge95@users.noreply.github.com> Date: Tue, 11 Feb 2020 13:59:09 +0530 Subject: [PATCH 0462/1071] Fixes unused variable errors in LGTM (#1746) * Fixes unsed variable errors in LGTM * Fixes integer check * Fixes failing tests --- ciphers/mixed_keyword_cypher.py | 3 --- .../binary_tree/binary_search_tree.py | 27 +++++++++---------- graphs/a_star.py | 2 -- hashes/hamming_code.py | 24 ++++++++--------- linear_algebra/src/polynom-for-points.py | 1 - matrix/matrix_operation.py | 7 ++--- 6 files changed, 25 insertions(+), 39 deletions(-) diff --git a/ciphers/mixed_keyword_cypher.py b/ciphers/mixed_keyword_cypher.py index c8d3ad6a535f..a546e4c781e6 100644 --- a/ciphers/mixed_keyword_cypher.py +++ b/ciphers/mixed_keyword_cypher.py @@ -29,9 +29,6 @@ def mixed_keyword(key="college", pt="UNIVERSITY"): # print(temp) alpha = [] modalpha = [] - # modalpha.append(temp) - dic = dict() - c = 0 for i in range(65, 91): t = chr(i) alpha.append(t) diff --git a/data_structures/binary_tree/binary_search_tree.py b/data_structures/binary_tree/binary_search_tree.py index fe163132cdf3..46c5ccca032c 100644 --- a/data_structures/binary_tree/binary_search_tree.py +++ b/data_structures/binary_tree/binary_search_tree.py @@ -76,7 +76,7 @@ def insert(self, *values): def search(self, value): if self.empty(): - raise IndexError("Warning: Tree is empty! please use another. ") + raise IndexError("Warning: Tree is empty! please use another.") else: node = self.root # use lazy evaluation here to avoid NoneType Attribute error @@ -112,7 +112,6 @@ def remove(self, value): if node is not None: if node.left is None and node.right is None: # If it has no children self.__reassign_nodes(node, None) - node = None elif node.left is None: # Has only right children self.__reassign_nodes(node, node.right) elif node.right is None: # Has only left children @@ -154,7 +153,7 @@ def postorder(curr_node): def binary_search_tree(): - r""" + """ Example 8 / \ @@ -164,15 +163,15 @@ def binary_search_tree(): / \ / 4 7 13 - >>> t = BinarySearchTree().insert(8, 3, 6, 1, 10, 14, 13, 4, 7) - >>> print(" ".join(repr(i.value) for i in t.traversal_tree())) - 8 3 1 6 4 7 10 14 13 - >>> print(" ".join(repr(i.value) for i in t.traversal_tree(postorder))) - 1 4 7 6 3 13 14 10 8 - >>> BinarySearchTree().search(6) - Traceback (most recent call last): - ... - IndexError: Warning: Tree is empty! please use another. + >>> t = BinarySearchTree().insert(8, 3, 6, 1, 10, 14, 13, 4, 7) + >>> print(" ".join(repr(i.value) for i in t.traversal_tree())) + 8 3 1 6 4 7 10 14 13 + >>> print(" ".join(repr(i.value) for i in t.traversal_tree(postorder))) + 1 4 7 6 3 13 14 10 8 + >>> BinarySearchTree().search(6) + Traceback (most recent call last): + ... + IndexError: Warning: Tree is empty! please use another. """ testlist = (8, 3, 6, 1, 10, 14, 13, 4, 7) t = BinarySearchTree() @@ -201,10 +200,8 @@ def binary_search_tree(): print(t) -二叉搜索树 = binary_search_tree - if __name__ == "__main__": import doctest doctest.testmod() - binary_search_tree() + # binary_search_tree() diff --git a/graphs/a_star.py b/graphs/a_star.py index 93ec26e7c496..a5d59626b0bc 100644 --- a/graphs/a_star.py +++ b/graphs/a_star.py @@ -52,7 +52,6 @@ def search(grid, init, goal, cost, heuristic): while not found and not resign: if len(cell) == 0: - resign = True return "FAIL" else: cell.sort() # to choose the least costliest action so as to move closer to the goal @@ -61,7 +60,6 @@ def search(grid, init, goal, cost, heuristic): x = next[2] y = next[3] g = next[1] - f = next[0] if x == goal[0] and y == goal[1]: found = True diff --git a/hashes/hamming_code.py b/hashes/hamming_code.py index 1246e1817c76..aae39ed9a06f 100644 --- a/hashes/hamming_code.py +++ b/hashes/hamming_code.py @@ -5,13 +5,13 @@ """ * This code implement the Hamming code: - https://en.wikipedia.org/wiki/Hamming_code - In telecommunication, + https://en.wikipedia.org/wiki/Hamming_code - In telecommunication, Hamming codes are a family of linear error-correcting codes. Hamming - codes can detect up to two-bit errors or correct one-bit errors - without detection of uncorrected errors. By contrast, the simple - parity code cannot correct errors, and can detect only an odd number - of bits in error. Hamming codes are perfect codes, that is, they - achieve the highest possible rate for codes with their block length + codes can detect up to two-bit errors or correct one-bit errors + without detection of uncorrected errors. By contrast, the simple + parity code cannot correct errors, and can detect only an odd number + of bits in error. Hamming codes are perfect codes, that is, they + achieve the highest possible rate for codes with their block length and minimum distance of three. * the implemented code consists of: @@ -19,15 +19,15 @@ * return the encoded message * a function responsible for decoding the message (receptorConverter) * return the decoded message and a ack of data integrity - + * how to use: - to be used you must declare how many parity bits (sizePari) + to be used you must declare how many parity bits (sizePari) you want to include in the message. it is desired (for test purposes) to select a bit to be set as an error. This serves to check whether the code is working correctly. - Lastly, the variable of the message/word that must be desired to be + Lastly, the variable of the message/word that must be desired to be encoded (text). - + * how this work: declaration of variables (sizePari, be, text) @@ -71,7 +71,7 @@ def emitterConverter(sizePar, data): """ :param sizePar: how many parity bits the message must have :param data: information bits - :return: message to be transmitted by unreliable medium + :return: message to be transmitted by unreliable medium - bits of information merged with parity bits >>> emitterConverter(4, "101010111111") @@ -84,7 +84,6 @@ def emitterConverter(sizePar, data): dataOut = [] parity = [] binPos = [bin(x)[2:] for x in range(1, sizePar + len(data) + 1)] - pos = [x for x in range(1, sizePar + len(data) + 1)] # sorted information data for the size of the output data dataOrd = [] @@ -188,7 +187,6 @@ def receptorConverter(sizePar, data): dataOut = [] parity = [] binPos = [bin(x)[2:] for x in range(1, sizePar + len(dataOutput) + 1)] - pos = [x for x in range(1, sizePar + len(dataOutput) + 1)] # sorted information data for the size of the output data dataOrd = [] diff --git a/linear_algebra/src/polynom-for-points.py b/linear_algebra/src/polynom-for-points.py index c884416b6dad..dc0c3d95102e 100644 --- a/linear_algebra/src/polynom-for-points.py +++ b/linear_algebra/src/polynom-for-points.py @@ -68,7 +68,6 @@ def points_to_polynomial(coordinates): # put the y values into a vector vector = [] while count_of_line < x: - count_in_line = 0 vector.append(coordinates[count_of_line][1]) count_of_line += 1 diff --git a/matrix/matrix_operation.py b/matrix/matrix_operation.py index 26e21aafcbca..307e8b6ba32e 100644 --- a/matrix/matrix_operation.py +++ b/matrix/matrix_operation.py @@ -111,12 +111,9 @@ def inverse(matrix): def _check_not_integer(matrix): - try: - rows = len(matrix) - cols = len(matrix[0]) + if not isinstance(matrix, int) and not isinstance(matrix[0], int): return True - except TypeError: - raise TypeError("Cannot input an integer value, it must be a matrix") + raise TypeError("Expected a matrix, got int/list instead") def _shape(matrix): From f0dfc4f46d47102bf34c09a3920138caf9cd9ae5 Mon Sep 17 00:00:00 2001 From: TheSuperNoob Date: Wed, 12 Feb 2020 15:04:59 +0100 Subject: [PATCH 0463/1071] Add Chudnovskys algorithm for calculating many digits of pi (#1752) * Add Chudnovskys algorithm for calculating many digits of pi * Update return value type hint * Initialize partial sum to be of type Decimal * Update chudnovsky_algorithm.py Co-authored-by: Christian Clauss --- maths/chudnovsky_algorithm.py | 61 +++++++++++++++++++++++++++++++++++ 1 file changed, 61 insertions(+) create mode 100644 maths/chudnovsky_algorithm.py diff --git a/maths/chudnovsky_algorithm.py b/maths/chudnovsky_algorithm.py new file mode 100644 index 000000000000..fb188cd6a3d8 --- /dev/null +++ b/maths/chudnovsky_algorithm.py @@ -0,0 +1,61 @@ +from decimal import Decimal, getcontext +from math import ceil, factorial + + +def pi(precision: int) -> str: + """ + The Chudnovsky algorithm is a fast method for calculating the digits of PI, + based on Ramanujan’s PI formulae. + + https://en.wikipedia.org/wiki/Chudnovsky_algorithm + + PI = constant_term / ((multinomial_term * linear_term) / exponential_term) + where constant_term = 426880 * sqrt(10005) + + The linear_term and the exponential_term can be defined iteratively as follows: + L_k+1 = L_k + 545140134 where L_0 = 13591409 + X_k+1 = X_k * -262537412640768000 where X_0 = 1 + + The multinomial_term is defined as follows: + 6k! / ((3k)! * (k!) ^ 3) + where k is the k_th iteration. + + This algorithm correctly calculates around 14 digits of PI per iteration + + >>> pi(10) + '3.14159265' + >>> pi(100) + '3.14159265358979323846264338327950288419716939937510582097494459230781640628620899862803482534211706' + >>> pi('hello') + Traceback (most recent call last): + ... + TypeError: Undefined for non-integers + >>> pi(-1) + Traceback (most recent call last): + ... + ValueError: Undefined for non-natural numbers + """ + + if not isinstance(precision, int): + raise TypeError("Undefined for non-integers") + elif precision < 1: + raise ValueError("Undefined for non-natural numbers") + + getcontext().prec = precision + num_iterations = ceil(precision / 14) + constant_term = 426880 * Decimal(10005).sqrt() + multinomial_term = 1 + exponential_term = 1 + linear_term = 13591409 + partial_sum = Decimal(linear_term) + for k in range(1, num_iterations): + multinomial_term = factorial(6 * k) // (factorial(3 * k) * factorial(k) ** 3) + linear_term += 545140134 + exponential_term *= -262537412640768000 + partial_sum += Decimal(multinomial_term * linear_term) / exponential_term + return str(constant_term / partial_sum)[:-1] + + +if __name__ == "__main__": + n = 50 + print(f"The first {n} digits of pi is: {pi(n)}") From 4866b1330bc7c77c0ed0e050e6b99efdeb026448 Mon Sep 17 00:00:00 2001 From: onlinejudge95 <44158581+onlinejudge95@users.noreply.github.com> Date: Thu, 13 Feb 2020 02:19:41 +0530 Subject: [PATCH 0464/1071] Fixes black failures from Previous PR (#1751) * Fixes black failures from Previous PR * Fixes equality testing alert * Fixes call to main() alert * Fixes unused import --- data_structures/binary_tree/binary_search_tree.py | 4 ++-- dynamic_programming/optimal_binary_search_tree.py | 1 + hashes/hamming_code.py | 4 ++-- maths/bisection.py | 2 ++ searches/tabu_search.py | 3 +-- strings/aho-corasick.py | 2 +- 6 files changed, 9 insertions(+), 7 deletions(-) diff --git a/data_structures/binary_tree/binary_search_tree.py b/data_structures/binary_tree/binary_search_tree.py index 46c5ccca032c..86dcd6489bd5 100644 --- a/data_structures/binary_tree/binary_search_tree.py +++ b/data_structures/binary_tree/binary_search_tree.py @@ -56,13 +56,13 @@ def __insert(self, value): parent_node = self.root # from root while True: # While we don't get to a leaf if value < parent_node.value: # We go left - if parent_node.left == None: + if parent_node.left is None: parent_node.left = new_node # We insert the new node in a leaf break else: parent_node = parent_node.left else: - if parent_node.right == None: + if parent_node.right is None: parent_node.right = new_node break else: diff --git a/dynamic_programming/optimal_binary_search_tree.py b/dynamic_programming/optimal_binary_search_tree.py index b0f248acf35c..f33ca01bd933 100644 --- a/dynamic_programming/optimal_binary_search_tree.py +++ b/dynamic_programming/optimal_binary_search_tree.py @@ -24,6 +24,7 @@ class Node: """Binary Search Tree Node""" + def __init__(self, key, freq): self.key = key self.freq = freq diff --git a/hashes/hamming_code.py b/hashes/hamming_code.py index aae39ed9a06f..756ba7c6670f 100644 --- a/hashes/hamming_code.py +++ b/hashes/hamming_code.py @@ -140,7 +140,7 @@ def emitterConverter(sizePar, data): # Mount the message ContBP = 0 # parity bit counter for x in range(0, sizePar + len(data)): - if dataOrd[x] == None: + if dataOrd[x] is None: dataOut.append(str(parity[ContBP])) ContBP += 1 else: @@ -243,7 +243,7 @@ def receptorConverter(sizePar, data): # Mount the message ContBP = 0 # Parity bit counter for x in range(0, sizePar + len(dataOutput)): - if dataOrd[x] == None: + if dataOrd[x] is None: dataOut.append(str(parity[ContBP])) ContBP += 1 else: diff --git a/maths/bisection.py b/maths/bisection.py index a9df15b775b3..93cc2247b64e 100644 --- a/maths/bisection.py +++ b/maths/bisection.py @@ -6,6 +6,8 @@ https://en.wikipedia.org/wiki/Bisection_method """ + + def equation(x: float) -> float: """ >>> equation(5) diff --git a/searches/tabu_search.py b/searches/tabu_search.py index 04a0e5076912..2847dca7acd7 100644 --- a/searches/tabu_search.py +++ b/searches/tabu_search.py @@ -25,7 +25,6 @@ import copy import argparse -import sys def generate_neighbours(path): @@ -278,4 +277,4 @@ def main(args=None): ) # Pass the arguments to main method - sys.exit(main(parser.parse_args())) + main(parser.parse_args()) diff --git a/strings/aho-corasick.py b/strings/aho-corasick.py index 315f7793325e..bb6955bdd423 100644 --- a/strings/aho-corasick.py +++ b/strings/aho-corasick.py @@ -47,7 +47,7 @@ def set_fail_transitions(self): q.append(child) state = self.adlist[r]["fail_state"] while ( - self.find_next_state(state, self.adlist[child]["value"]) == None + self.find_next_state(state, self.adlist[child]["value"]) is None and state != 0 ): state = self.adlist[state]["fail_state"] From 53042f0f45c7962d8601168f40c65c8f3e29dffe Mon Sep 17 00:00:00 2001 From: QuantumNovice <43876848+QuantumNovice@users.noreply.github.com> Date: Fri, 14 Feb 2020 16:56:56 +0500 Subject: [PATCH 0465/1071] Create RayleighQuotient.py (#1749) * Create RayleighQuotient.py https://en.wikipedia.org/wiki/Rayleigh_quotient * Update RayleighQuotient.py * Update and rename RayleighQuotient.py to rayleigh_quotient.py * Update rayleigh_quotient.py * Update rayleigh_quotient.py python/black * Update rayleigh_quotient.py --- linear_algebra/src/rayleigh_quotient.py | 65 +++++++++++++++++++++++++ 1 file changed, 65 insertions(+) create mode 100644 linear_algebra/src/rayleigh_quotient.py diff --git a/linear_algebra/src/rayleigh_quotient.py b/linear_algebra/src/rayleigh_quotient.py new file mode 100644 index 000000000000..46551749febd --- /dev/null +++ b/linear_algebra/src/rayleigh_quotient.py @@ -0,0 +1,65 @@ +""" +https://en.wikipedia.org/wiki/Rayleigh_quotient +""" +import numpy as np + + +def is_hermitian(matrix:np.matrix) -> bool: + """ + Checks if a matrix is Hermitian. + + >>> import numpy as np + >>> A = np.matrix([ + ... [2, 2+1j, 4], + ... [2-1j, 3, 1j], + ... [4, -1j, 1]]) + >>> is_hermitian(A) + True + >>> A = np.matrix([ + ... [2, 2+1j, 4+1j], + ... [2-1j, 3, 1j], + ... [4, -1j, 1]]) + >>> is_hermitian(A) + False + """ + return np.array_equal(matrix, matrix.H) + + +def rayleigh_quotient(A:np.matrix, v:np.matrix) -> float: + """ + Returns the Rayleigh quotient of a Hermitian matrix A and + vector v. + >>> import numpy as np + >>> A = np.matrix([ + ... [1, 2, 4], + ... [2, 3, -1], + ... [4, -1, 1] + ... ]) + >>> v = np.matrix([ + ... [1], + ... [2], + ... [3] + ... ]) + >>> rayleigh_quotient(A, v) + matrix([[3.]]) + """ + v_star = v.H + return (v_star * A * v) / (v_star * v) + + +def tests() -> None: + A = np.matrix([[2, 2 + 1j, 4], [2 - 1j, 3, 1j], [4, -1j, 1]]) + v = np.matrix([[1], [2], [3]]) + assert is_hermitian(A), f"{A} is not hermitian." + print(rayleigh_quotient(A, v)) + + A = np.matrix([[1, 2, 4], [2, 3, -1], [4, -1, 1]]) + assert is_hermitian(A), f"{A} is not hermitian." + assert rayleigh_quotient(A, v) == float(3) + + +if __name__ == "__main__": + import doctest + + doctest.testmod() + tests() From 2e405f397bbcefccc470f215c7ff024875ef16c5 Mon Sep 17 00:00:00 2001 From: eightysixth <25541207+eightysixth@users.noreply.github.com> Date: Sun, 16 Feb 2020 02:55:27 -0700 Subject: [PATCH 0466/1071] Created geodesy section with one algorithm (#1757) * implemented haversine * updated docstring Only calculate distance * added type hints * added type hints * improved docstring and math usage * f"{haversine_distance(*SAN_FRANCISCO, *YOSEMITE):0,.0f} meters" Co-authored-by: Christian Clauss --- geodesy/haversine_distance.py | 56 +++++++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) create mode 100644 geodesy/haversine_distance.py diff --git a/geodesy/haversine_distance.py b/geodesy/haversine_distance.py new file mode 100644 index 000000000000..de8ac7f88302 --- /dev/null +++ b/geodesy/haversine_distance.py @@ -0,0 +1,56 @@ +from math import asin, atan, cos, radians, sin, sqrt, tan + + +def haversine_distance(lat1: float, lon1: float, lat2: float, lon2: float) -> float: + """ + Calculate great circle distance between two points in a sphere, + given longitudes and latitudes https://en.wikipedia.org/wiki/Haversine_formula + + We know that the globe is "sort of" spherical, so a path between two points + isn't exactly a straight line. We need to account for the Earth's curvature + when calculating distance from point A to B. This effect is negligible for + small distances but adds up as distance increases. The Haversine method treats + the earth as a sphere which allows us to "project" the two points A and B + onto the surface of that sphere and approximate the spherical distance between + them. Since the Earth is not a perfect sphere, other methods which model the + Earth's ellipsoidal nature are more accurate but a quick and modifiable + computation like Haversine can be handy for shorter range distances. + + Args: + lat1, lon1: latitude and longitude of coordinate 1 + lat2, lon2: latitude and longitude of coordinate 2 + Returns: + geographical distance between two points in metres + >>> from collections import namedtuple + >>> point_2d = namedtuple("point_2d", "lat lon") + >>> SAN_FRANCISCO = point_2d(37.774856, -122.424227) + >>> YOSEMITE = point_2d(37.864742, -119.537521) + >>> f"{haversine_distance(*SAN_FRANCISCO, *YOSEMITE):0,.0f} meters" + '254,352 meters' + """ + # CONSTANTS per WGS84 https://en.wikipedia.org/wiki/World_Geodetic_System + # Distance in metres(m) + AXIS_A = 6378137.0 + AXIS_B = 6356752.314245 + RADIUS = 6378137 + # Equation parameters + # Equation https://en.wikipedia.org/wiki/Haversine_formula#Formulation + flattening = (AXIS_A - AXIS_B) / AXIS_A + phi_1 = atan((1 - flattening) * tan(radians(lat1))) + phi_2 = atan((1 - flattening) * tan(radians(lat2))) + lambda_1 = radians(lon1) + lambda_2 = radians(lon2) + # Equation + sin_sq_phi = sin((phi_2 - phi_1) / 2) + sin_sq_lambda = sin((lambda_2 - lambda_1) / 2) + # Square both values + sin_sq_phi *= sin_sq_phi + sin_sq_lambda *= sin_sq_lambda + h_value = sqrt(sin_sq_phi + (cos(phi_1) * cos(phi_2) * sin_sq_lambda)) + return 2 * RADIUS * asin(h_value) + + +if __name__ == "__main__": + import doctest + + doctest.testmod() From 748702b461c01e659d8f892cc127dab8fb279177 Mon Sep 17 00:00:00 2001 From: Naveen M V <30305957+naviji@users.noreply.github.com> Date: Mon, 17 Feb 2020 15:25:04 +0530 Subject: [PATCH 0467/1071] Add Monte Carlo dice simulation algorithm (#1759) * Implement basic dice simulation * Add tests to throw_dice * Fix comment * Add type hints * Add additional comments * Update monte_carlo_dice.py Co-authored-by: Christian Clauss --- maths/monte_carlo_dice.py | 45 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100644 maths/monte_carlo_dice.py diff --git a/maths/monte_carlo_dice.py b/maths/monte_carlo_dice.py new file mode 100644 index 000000000000..c045cc829213 --- /dev/null +++ b/maths/monte_carlo_dice.py @@ -0,0 +1,45 @@ +import random +from typing import List + +class Dice: + NUM_SIDES = 6 + + def __init__(self): + """ Initialize a six sided dice """ + self.sides = list(range(1, Dice.NUM_SIDES + 1)) + + def roll(self): + return random.choice(self.sides) + + def _str_(self): + return "Fair Dice" + + +def throw_dice(num_throws: int, num_dice: int=2) -> List[float]: + """ + Return probability list of all possible sums when throwing dice. + + >>> random.seed(0) + >>> throw_dice(10, 1) + [10.0, 0.0, 30.0, 50.0, 10.0, 0.0] + >>> throw_dice(100, 1) + [19.0, 17.0, 17.0, 11.0, 23.0, 13.0] + >>> throw_dice(1000, 1) + [18.8, 15.5, 16.3, 17.6, 14.2, 17.6] + >>> throw_dice(10000, 1) + [16.35, 16.89, 16.93, 16.6, 16.52, 16.71] + >>> throw_dice(10000, 2) + [2.74, 5.6, 7.99, 11.26, 13.92, 16.7, 14.44, 10.63, 8.05, 5.92, 2.75] + """ + dices = [Dice() for i in range(num_dice)] + count_of_sum = [0] * (len(dices) * Dice.NUM_SIDES + 1) + for i in range(num_throws): + count_of_sum[sum([dice.roll() for dice in dices])] += 1 + probability = [round((count * 100) / num_throws, 2) for count in count_of_sum] + return probability[num_dice:] # remove probability of sums that never appear + + +if __name__ == "__main__": + import doctest + + doctest.testmod() From d2f7982a4ee105ca980b2446ed8fc2e32139dd7d Mon Sep 17 00:00:00 2001 From: TheSuperNoob Date: Wed, 19 Feb 2020 19:45:55 +0100 Subject: [PATCH 0468/1071] Update quadratic equations solver (#1764) Use pythons complex number module cmath for the calculation of the roots --- maths/quadratic_equations_complex_numbers.py | 44 ++++++++++---------- 1 file changed, 21 insertions(+), 23 deletions(-) diff --git a/maths/quadratic_equations_complex_numbers.py b/maths/quadratic_equations_complex_numbers.py index 8f97508609bf..7c47bdef2297 100644 --- a/maths/quadratic_equations_complex_numbers.py +++ b/maths/quadratic_equations_complex_numbers.py @@ -1,38 +1,36 @@ -from math import sqrt +from cmath import sqrt from typing import Tuple -def QuadraticEquation(a: int, b: int, c: int) -> Tuple[str, str]: +def quadratic_roots(a: int, b: int, c: int) -> Tuple[complex, complex]: """ Given the numerical coefficients a, b and c, - prints the solutions for a quadratic equation, for a*x*x + b*x + c. + calculates the roots for any quadratic equation of the form ax^2 + bx + c - >>> QuadraticEquation(a=1, b=3, c=-4) - ('1.0', '-4.0') - >>> QuadraticEquation(5, 6, 1) - ('-0.2', '-1.0') + >>> quadratic_roots(a=1, b=3, c=-4) + (1.0, -4.0) + >>> quadratic_roots(5, 6, 1) + (-0.2, -1.0) + >>> quadratic_roots(1, -6, 25) + ((3+4j), (3-4j)) """ + if a == 0: - raise ValueError("Coefficient 'a' must not be zero for quadratic equations.") + raise ValueError("Coefficient 'a' must not be zero.") delta = b * b - 4 * a * c - if delta >= 0: - return str((-b + sqrt(delta)) / (2 * a)), str((-b - sqrt(delta)) / (2 * a)) - """ - Treats cases of Complexes Solutions(i = imaginary unit) - Ex.: a = 5, b = 2, c = 1 - Solution1 = (- 2 + 4.0 *i)/2 and Solution2 = (- 2 + 4.0 *i)/ 10 - """ - snd = sqrt(-delta) - if b == 0: - return f"({snd} * i) / 2", f"({snd} * i) / {2 * a}" - b = -abs(b) - return f"({b}+{snd} * i) / 2", f"({b}+{snd} * i) / {2 * a}" + + root_1 = (-b + sqrt(delta)) / (2 * a) + root_2 = (-b - sqrt(delta)) / (2 * a) + + return ( + root_1.real if not root_1.imag else root_1, + root_2.real if not root_2.imag else root_2, + ) def main(): - solutions = QuadraticEquation(a=5, b=6, c=1) - print("The equation solutions are: {} and {}".format(*solutions)) - # The equation solutions are: -0.2 and -1.0 + solutions = quadratic_roots(a=5, b=6, c=1) + print("The solutions are: {} and {}".format(*solutions)) if __name__ == "__main__": From 6b3bbc70a8388e6c03b62ef5843f79a493686fe3 Mon Sep 17 00:00:00 2001 From: Iqrar Agalosi Nureyza Date: Thu, 20 Feb 2020 18:29:01 +0700 Subject: [PATCH 0469/1071] Added doctests in modular_exponential.py (#1775) * added doctests in modular_exponential.py * added doctests in modular_exponential.py * added URL link --- maths/modular_exponential.py | 23 ++++++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/maths/modular_exponential.py b/maths/modular_exponential.py index 8715e17147ff..91fa0e462a49 100644 --- a/maths/modular_exponential.py +++ b/maths/modular_exponential.py @@ -1,8 +1,20 @@ -"""Modular Exponential.""" +""" + Modular Exponential. + Modular exponentiation is a type of exponentiation performed over a modulus. + For more explanation, please check https://en.wikipedia.org/wiki/Modular_exponentiation +""" +"""Calculate Modular Exponential.""" +def modular_exponential(base : int, power : int, mod : int): + """ + >>> modular_exponential(5, 0, 10) + 1 + >>> modular_exponential(2, 8, 7) + 4 + >>> modular_exponential(3, -2, 9) + -1 + """ -def modular_exponential(base, power, mod): - """Calculate Modular Exponential.""" if power < 0: return -1 base %= mod @@ -13,6 +25,7 @@ def modular_exponential(base, power, mod): result = (result * base) % mod power = power >> 1 base = (base * base) % mod + return result @@ -22,4 +35,8 @@ def main(): if __name__ == "__main__": + import doctest + + doctest.testmod() + main() From cb4795616cedb5cdd2340cf47f8c5fb3101dddb5 Mon Sep 17 00:00:00 2001 From: eightysixth <25541207+eightysixth@users.noreply.github.com> Date: Thu, 20 Feb 2020 06:34:43 -0700 Subject: [PATCH 0470/1071] Implemented geodesy - Lambert's ellipsoidal distance (#1763) * Implemented Lambert's long line * Update lamberts_ellipsoidal_distance.py Co-authored-by: John Law --- geodesy/lamberts_ellipsoidal_distance.py | 83 ++++++++++++++++++++++++ 1 file changed, 83 insertions(+) create mode 100644 geodesy/lamberts_ellipsoidal_distance.py diff --git a/geodesy/lamberts_ellipsoidal_distance.py b/geodesy/lamberts_ellipsoidal_distance.py new file mode 100644 index 000000000000..224b9404a5b7 --- /dev/null +++ b/geodesy/lamberts_ellipsoidal_distance.py @@ -0,0 +1,83 @@ +from math import atan, cos, radians, sin, tan +from haversine_distance import haversine_distance + + +def lamberts_ellipsoidal_distance( + lat1: float, lon1: float, lat2: float, lon2: float +) -> float: + + """ + Calculate the shortest distance along the surface of an ellipsoid between + two points on the surface of earth given longitudes and latitudes + https://en.wikipedia.org/wiki/Geographical_distance#Lambert's_formula_for_long_lines + + NOTE: This algorithm uses geodesy/haversine_distance.py to compute central angle, sigma + + Representing the earth as an ellipsoid allows us to approximate distances between points + on the surface much better than a sphere. Ellipsoidal formulas treat the Earth as an + oblate ellipsoid which means accounting for the flattening that happens at the North + and South poles. Lambert's formulae provide accuracy on the order of 10 meteres over + thousands of kilometeres. Other methods can provide millimeter-level accuracy but this + is a simpler method to calculate long range distances without increasing computational + intensity. + + Args: + lat1, lon1: latitude and longitude of coordinate 1 + lat2, lon2: latitude and longitude of coordinate 2 + Returns: + geographical distance between two points in metres + + >>> from collections import namedtuple + >>> point_2d = namedtuple("point_2d", "lat lon") + >>> SAN_FRANCISCO = point_2d(37.774856, -122.424227) + >>> YOSEMITE = point_2d(37.864742, -119.537521) + >>> NEW_YORK = point_2d(40.713019, -74.012647) + >>> VENICE = point_2d(45.443012, 12.313071) + >>> f"{lamberts_ellipsoidal_distance(*SAN_FRANCISCO, *YOSEMITE):0,.0f} meters" + '254,351 meters' + >>> f"{lamberts_ellipsoidal_distance(*SAN_FRANCISCO, *NEW_YORK):0,.0f} meters" + '4,138,992 meters' + >>> f"{lamberts_ellipsoidal_distance(*SAN_FRANCISCO, *VENICE):0,.0f} meters" + '9,737,326 meters' + """ + + # CONSTANTS per WGS84 https://en.wikipedia.org/wiki/World_Geodetic_System + # Distance in metres(m) + AXIS_A = 6378137.0 + AXIS_B = 6356752.314245 + EQUATORIAL_RADIUS = 6378137 + + # Equation Parameters + # https://en.wikipedia.org/wiki/Geographical_distance#Lambert's_formula_for_long_lines + flattening = (AXIS_A - AXIS_B) / AXIS_A + # Parametric latitudes https://en.wikipedia.org/wiki/Latitude#Parametric_(or_reduced)_latitude + b_lat1 = atan((1 - flattening) * tan(radians(lat1))) + b_lat2 = atan((1 - flattening) * tan(radians(lat2))) + + # Compute central angle between two points + # using haversine theta. sigma = haversine_distance / equatorial radius + sigma = haversine_distance(lat1, lon1, lat2, lon2) / EQUATORIAL_RADIUS + + # Intermediate P and Q values + P_value = (b_lat1 + b_lat2) / 2 + Q_value = (b_lat2 - b_lat1) / 2 + + # Intermediate X value + # X = (sigma - sin(sigma)) * sin^2Pcos^2Q / cos^2(sigma/2) + X_numerator = (sin(P_value) ** 2) * (cos(Q_value) ** 2) + X_demonimator = cos(sigma / 2) ** 2 + X_value = (sigma - sin(sigma)) * (X_numerator / X_demonimator) + + # Intermediate Y value + # Y = (sigma + sin(sigma)) * cos^2Psin^2Q / sin^2(sigma/2) + Y_numerator = (cos(P_value) ** 2) * (sin(Q_value) ** 2) + Y_denominator = sin(sigma / 2) ** 2 + Y_value = (sigma + sin(sigma)) * (Y_numerator / Y_denominator) + + return EQUATORIAL_RADIUS * (sigma - ((flattening / 2) * (X_value + Y_value))) + + +if __name__ == "__main__": + import doctest + + doctest.testmod() From 59bf115aa1b6203c51cc1a813d555e0a7c9d99e3 Mon Sep 17 00:00:00 2001 From: Christian Clauss Date: Fri, 21 Feb 2020 11:02:35 +0100 Subject: [PATCH 0471/1071] uses: actions/checkout@v2 (#1779) * uses: actions/checkout@v2 * fixup! Format Python code with psf/black push --- .github/workflows/autoblack.yml | 2 +- linear_algebra/src/rayleigh_quotient.py | 4 ++-- maths/modular_exponential.py | 4 +++- maths/monte_carlo_dice.py | 3 ++- 4 files changed, 8 insertions(+), 5 deletions(-) diff --git a/.github/workflows/autoblack.yml b/.github/workflows/autoblack.yml index cf578a14da95..95d2d3d64233 100644 --- a/.github/workflows/autoblack.yml +++ b/.github/workflows/autoblack.yml @@ -9,7 +9,7 @@ jobs: build: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v1 + - uses: actions/checkout@v2 - uses: actions/setup-python@v1 - run: pip install black - run: black --check . diff --git a/linear_algebra/src/rayleigh_quotient.py b/linear_algebra/src/rayleigh_quotient.py index 46551749febd..d0d5d6396d28 100644 --- a/linear_algebra/src/rayleigh_quotient.py +++ b/linear_algebra/src/rayleigh_quotient.py @@ -4,7 +4,7 @@ import numpy as np -def is_hermitian(matrix:np.matrix) -> bool: +def is_hermitian(matrix: np.matrix) -> bool: """ Checks if a matrix is Hermitian. @@ -25,7 +25,7 @@ def is_hermitian(matrix:np.matrix) -> bool: return np.array_equal(matrix, matrix.H) -def rayleigh_quotient(A:np.matrix, v:np.matrix) -> float: +def rayleigh_quotient(A: np.matrix, v: np.matrix) -> float: """ Returns the Rayleigh quotient of a Hermitian matrix A and vector v. diff --git a/maths/modular_exponential.py b/maths/modular_exponential.py index 91fa0e462a49..8b7b17575a33 100644 --- a/maths/modular_exponential.py +++ b/maths/modular_exponential.py @@ -5,7 +5,9 @@ """ """Calculate Modular Exponential.""" -def modular_exponential(base : int, power : int, mod : int): + + +def modular_exponential(base: int, power: int, mod: int): """ >>> modular_exponential(5, 0, 10) 1 diff --git a/maths/monte_carlo_dice.py b/maths/monte_carlo_dice.py index c045cc829213..c36c3e83e00b 100644 --- a/maths/monte_carlo_dice.py +++ b/maths/monte_carlo_dice.py @@ -1,6 +1,7 @@ import random from typing import List + class Dice: NUM_SIDES = 6 @@ -15,7 +16,7 @@ def _str_(self): return "Fair Dice" -def throw_dice(num_throws: int, num_dice: int=2) -> List[float]: +def throw_dice(num_throws: int, num_dice: int = 2) -> List[float]: """ Return probability list of all possible sums when throwing dice. From 6d7cbdacb192ccfbdd00c389ee4d6a17e2530d0f Mon Sep 17 00:00:00 2001 From: singlav <41392278+singlav@users.noreply.github.com> Date: Sat, 22 Feb 2020 23:36:47 +0530 Subject: [PATCH 0472/1071] add example to estimate area under line using montecarlo (#1782) * add example to estimate area under line using montecarlo * separate estimate func and print statements * use mean from stats package * avoid creating extra variable * min_value: float=0.0, max_value: float=1.0 * Update montecarlo.py * Update montecarlo.py * Rename montecarlo.py to monte_carlo.py * Update monte_carlo.py Co-authored-by: Christian Clauss --- maths/monte_carlo.py | 74 ++++++++++++++++++++++++++++++++++++++++++++ maths/montecarlo.py | 43 ------------------------- 2 files changed, 74 insertions(+), 43 deletions(-) create mode 100644 maths/monte_carlo.py delete mode 100644 maths/montecarlo.py diff --git a/maths/monte_carlo.py b/maths/monte_carlo.py new file mode 100644 index 000000000000..4980c5c55c8c --- /dev/null +++ b/maths/monte_carlo.py @@ -0,0 +1,74 @@ +""" +@author: MatteoRaso +""" +from numpy import pi, sqrt +from random import uniform +from statistics import mean + + +def pi_estimator(iterations: int): + """ + An implementation of the Monte Carlo method used to find pi. + 1. Draw a 2x2 square centred at (0,0). + 2. Inscribe a circle within the square. + 3. For each iteration, place a dot anywhere in the square. + a. Record the number of dots within the circle. + 4. After all the dots are placed, divide the dots in the circle by the total. + 5. Multiply this value by 4 to get your estimate of pi. + 6. Print the estimated and numpy value of pi + """ + # A local function to see if a dot lands in the circle. + def in_circle(x: float, y: float) -> bool: + distance_from_centre = sqrt((x ** 2) + (y ** 2)) + # Our circle has a radius of 1, so a distance + # greater than 1 would land outside the circle. + return distance_from_centre <= 1 + + # The proportion of guesses that landed in the circle + proportion = mean( + int(in_circle(uniform(-1.0, 1.0), uniform(-1.0, 1.0))) for _ in range(iterations) + ) + # The ratio of the area for circle to square is pi/4. + pi_estimate = proportion * 4 + print("The estimated value of pi is ", pi_estimate) + print("The numpy value of pi is ", pi) + print("The total error is ", abs(pi - pi_estimate)) + + +def area_under_line_estimator(iterations: int, + min_value: float=0.0, + max_value: float=1.0) -> float: + """ + An implementation of the Monte Carlo method to find area under + y = x where x lies between min_value to max_value + 1. Let x be a uniformly distributed random variable between min_value to max_value + 2. Expected value of x = integration of x from min_value to max_value + 3. Finding expected value of x: + a. Repeatedly draw x from uniform distribution + b. Expected value = average of those values + 4. Actual value = 1/2 + 5. Returns estimated value + """ + return mean(uniform(min_value, max_value) for _ in range(iterations)) + + +def area_under_line_estimator_check(iterations: int) -> None: + """ + Checks estimation error for area_under_line_estimator func + 1. Calls "area_under_line_estimator" function + 2. Compares with the expected value + 3. Prints estimated, expected and error value + """ + estimate = area_under_line_estimator(iterations) + print("******************") + print("Estimating area under y=x where x varies from 0 to 1") + print("Expected value is ", 0.5) + print("Estimated value is ", estimate) + print("Total error is ", abs(estimate - 0.5)) + print("******************") + + +if __name__ == "__main__": + import doctest + + doctest.testmod() diff --git a/maths/montecarlo.py b/maths/montecarlo.py deleted file mode 100644 index ce8f69f64a15..000000000000 --- a/maths/montecarlo.py +++ /dev/null @@ -1,43 +0,0 @@ -""" -@author: MatteoRaso -""" -from numpy import pi, sqrt -from random import uniform - - -def pi_estimator(iterations: int): - """An implementation of the Monte Carlo method used to find pi. - 1. Draw a 2x2 square centred at (0,0). - 2. Inscribe a circle within the square. - 3. For each iteration, place a dot anywhere in the square. - 3.1 Record the number of dots within the circle. - 4. After all the dots are placed, divide the dots in the circle by the total. - 5. Multiply this value by 4 to get your estimate of pi. - 6. Print the estimated and numpy value of pi - """ - - circle_dots = 0 - - # A local function to see if a dot lands in the circle. - def circle(x: float, y: float): - distance_from_centre = sqrt((x ** 2) + (y ** 2)) - # Our circle has a radius of 1, so a distance greater than 1 would land outside the circle. - return distance_from_centre <= 1 - - circle_dots = sum( - int(circle(uniform(-1.0, 1.0), uniform(-1.0, 1.0))) for i in range(iterations) - ) - - # The proportion of guesses that landed within the circle - proportion = circle_dots / iterations - # The ratio of the area for circle to square is pi/4. - pi_estimate = proportion * 4 - print("The estimated value of pi is ", pi_estimate) - print("The numpy value of pi is ", pi) - print("The total error is ", abs(pi - pi_estimate)) - - -if __name__ == "__main__": - import doctest - - doctest.testmod() From b36e46b9b205eedef6e112db476cbc40fdafcdb6 Mon Sep 17 00:00:00 2001 From: singlav <41392278+singlav@users.noreply.github.com> Date: Sun, 23 Feb 2020 04:03:12 +0530 Subject: [PATCH 0473/1071] extend estimation of area under curve of y=x using monte carlo simulation to any given lower and upper bound (#1784) * extend estimation of area under curve of y=x using monte carlo simulation to any given lower and upper bound * remove doctest --- maths/monte_carlo.py | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/maths/monte_carlo.py b/maths/monte_carlo.py index 4980c5c55c8c..6a407e98badd 100644 --- a/maths/monte_carlo.py +++ b/maths/monte_carlo.py @@ -42,29 +42,34 @@ def area_under_line_estimator(iterations: int, An implementation of the Monte Carlo method to find area under y = x where x lies between min_value to max_value 1. Let x be a uniformly distributed random variable between min_value to max_value - 2. Expected value of x = integration of x from min_value to max_value + 2. Expected value of x = (integration of x from min_value to max_value) / (max_value - min_value) 3. Finding expected value of x: a. Repeatedly draw x from uniform distribution b. Expected value = average of those values - 4. Actual value = 1/2 + 4. Actual value = (max_value^2 - min_value^2) / 2 5. Returns estimated value """ - return mean(uniform(min_value, max_value) for _ in range(iterations)) + return mean(uniform(min_value, max_value) for _ in range(iterations)) * (max_value - min_value) -def area_under_line_estimator_check(iterations: int) -> None: +def area_under_line_estimator_check(iterations: int, + min_value: float=0.0, + max_value: float=1.0) -> None: """ Checks estimation error for area_under_line_estimator func 1. Calls "area_under_line_estimator" function 2. Compares with the expected value 3. Prints estimated, expected and error value """ - estimate = area_under_line_estimator(iterations) + + estimated_value = area_under_line_estimator(iterations, min_value, max_value) + expected_value = (max_value*max_value - min_value*min_value) / 2 + print("******************") - print("Estimating area under y=x where x varies from 0 to 1") - print("Expected value is ", 0.5) - print("Estimated value is ", estimate) - print("Total error is ", abs(estimate - 0.5)) + print("Estimating area under y=x where x varies from ",min_value, " to ",max_value) + print("Estimated value is ", estimated_value) + print("Expected value is ", expected_value) + print("Total error is ", abs(estimated_value - expected_value)) print("******************") From 652b891a75d49cb82a76d39a5f74daf36727b116 Mon Sep 17 00:00:00 2001 From: Christian Clauss Date: Sun, 23 Feb 2020 04:23:00 +0100 Subject: [PATCH 0474/1071] Travis CI: Upgrade to Python 3.8 (#1783) * Travis CI: Upgrade to Python 3.8 * updating DIRECTORY.md * Tensorflow is not yet compatible with Python 3.8 * Disable k_means_clustering_tensorflow.py * updating DIRECTORY.md * Disable gan.py * updating DIRECTORY.md * Disable input_data.py * updating DIRECTORY.md * pip install a current version of six --- .travis.yml | 4 ++-- DIRECTORY.md | 17 ++++++++++++++--- ...w.py => k_means_clustering_tensorflow.py_tf} | 0 neural_network/{gan.py => gan.py_tf} | 0 .../{input_data.py => input_data.py_tf} | 0 requirements.txt | 2 +- 6 files changed, 17 insertions(+), 6 deletions(-) rename dynamic_programming/{k_means_clustering_tensorflow.py => k_means_clustering_tensorflow.py_tf} (100%) rename neural_network/{gan.py => gan.py_tf} (100%) rename neural_network/{input_data.py => input_data.py_tf} (100%) diff --git a/.travis.yml b/.travis.yml index bd2dfbbe4496..aec411c52507 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,7 +1,7 @@ language: python -python: 3.7 +python: 3.8 cache: pip -before_install: pip install --upgrade pip setuptools +before_install: pip install --upgrade pip setuptools six install: pip install -r requirements.txt before_script: - black --check . || true diff --git a/DIRECTORY.md b/DIRECTORY.md index ff98c21894c5..91a5ab7f6a99 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -169,7 +169,6 @@ * [Fractional Knapsack 2](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/fractional_knapsack_2.py) * [Integer Partition](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/integer_partition.py) * [Iterating Through Submasks](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/iterating_through_submasks.py) - * [K Means Clustering Tensorflow](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/k_means_clustering_tensorflow.py) * [Knapsack](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/knapsack.py) * [Longest Common Subsequence](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/longest_common_subsequence.py) * [Longest Increasing Subsequence](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/longest_increasing_subsequence.py) @@ -179,6 +178,7 @@ * [Max Sub Array](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/max_sub_array.py) * [Max Sum Contiguous Subsequence](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/max_sum_contiguous_subsequence.py) * [Minimum Partition](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/minimum_partition.py) + * [Optimal Binary Search Tree](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/optimal_binary_search_tree.py) * [Rod Cutting](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/rod_cutting.py) * [Subset Generation](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/subset_generation.py) * [Sum Of Subset](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/sum_of_subset.py) @@ -190,6 +190,10 @@ ## Fuzzy Logic * [Fuzzy Operations](https://github.com/TheAlgorithms/Python/blob/master/fuzzy_logic/fuzzy_operations.py) +## Geodesy + * [Haversine Distance](https://github.com/TheAlgorithms/Python/blob/master/geodesy/haversine_distance.py) + * [Lamberts Ellipsoidal Distance](https://github.com/TheAlgorithms/Python/blob/master/geodesy/lamberts_ellipsoidal_distance.py) + ## Graphics * [Bezier Curve](https://github.com/TheAlgorithms/Python/blob/master/graphics/bezier_curve.py) @@ -239,6 +243,7 @@ * Src * [Lib](https://github.com/TheAlgorithms/Python/blob/master/linear_algebra/src/lib.py) * [Polynom-For-Points](https://github.com/TheAlgorithms/Python/blob/master/linear_algebra/src/polynom-for-points.py) + * [Rayleigh Quotient](https://github.com/TheAlgorithms/Python/blob/master/linear_algebra/src/rayleigh_quotient.py) * [Test Linear Algebra](https://github.com/TheAlgorithms/Python/blob/master/linear_algebra/src/test_linear_algebra.py) ## Machine Learning @@ -252,6 +257,7 @@ * [Logistic Regression](https://github.com/TheAlgorithms/Python/blob/master/machine_learning/logistic_regression.py) * [Multilayer Perceptron Classifier](https://github.com/TheAlgorithms/Python/blob/master/machine_learning/multilayer_perceptron_classifier.py) * [Polymonial Regression](https://github.com/TheAlgorithms/Python/blob/master/machine_learning/polymonial_regression.py) + * [Random Forest Regressor](https://github.com/TheAlgorithms/Python/blob/master/machine_learning/random_forest_regressor.py) * [Scoring Functions](https://github.com/TheAlgorithms/Python/blob/master/machine_learning/scoring_functions.py) * [Sequential Minimum Optimization](https://github.com/TheAlgorithms/Python/blob/master/machine_learning/sequential_minimum_optimization.py) * [Support Vector Machines](https://github.com/TheAlgorithms/Python/blob/master/machine_learning/support_vector_machines.py) @@ -270,7 +276,9 @@ * [Binary Exp Mod](https://github.com/TheAlgorithms/Python/blob/master/maths/binary_exp_mod.py) * [Binary Exponentiation](https://github.com/TheAlgorithms/Python/blob/master/maths/binary_exponentiation.py) * [Binomial Coefficient](https://github.com/TheAlgorithms/Python/blob/master/maths/binomial_coefficient.py) + * [Bisection](https://github.com/TheAlgorithms/Python/blob/master/maths/bisection.py) * [Ceil](https://github.com/TheAlgorithms/Python/blob/master/maths/ceil.py) + * [Chudnovsky Algorithm](https://github.com/TheAlgorithms/Python/blob/master/maths/chudnovsky_algorithm.py) * [Collatz Sequence](https://github.com/TheAlgorithms/Python/blob/master/maths/collatz_sequence.py) * [Combinations](https://github.com/TheAlgorithms/Python/blob/master/maths/combinations.py) * [Eulers Totient](https://github.com/TheAlgorithms/Python/blob/master/maths/eulers_totient.py) @@ -305,6 +313,8 @@ * [Miller Rabin](https://github.com/TheAlgorithms/Python/blob/master/maths/miller_rabin.py) * [Mobius Function](https://github.com/TheAlgorithms/Python/blob/master/maths/mobius_function.py) * [Modular Exponential](https://github.com/TheAlgorithms/Python/blob/master/maths/modular_exponential.py) + * [Monte Carlo Dice](https://github.com/TheAlgorithms/Python/blob/master/maths/monte_carlo_dice.py) + * [Montecarlo](https://github.com/TheAlgorithms/Python/blob/master/maths/montecarlo.py) * [Newton Raphson](https://github.com/TheAlgorithms/Python/blob/master/maths/newton_raphson.py) * [Numerical Integration](https://github.com/TheAlgorithms/Python/blob/master/maths/numerical_integration.py) * [Perfect Square](https://github.com/TheAlgorithms/Python/blob/master/maths/perfect_square.py) @@ -352,8 +362,6 @@ ## Neural Network * [Back Propagation Neural Network](https://github.com/TheAlgorithms/Python/blob/master/neural_network/back_propagation_neural_network.py) * [Convolution Neural Network](https://github.com/TheAlgorithms/Python/blob/master/neural_network/convolution_neural_network.py) - * [Gan](https://github.com/TheAlgorithms/Python/blob/master/neural_network/gan.py) - * [Input Data](https://github.com/TheAlgorithms/Python/blob/master/neural_network/input_data.py) * [Perceptron](https://github.com/TheAlgorithms/Python/blob/master/neural_network/perceptron.py) ## Other @@ -509,6 +517,9 @@ * Problem 99 * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_99/sol1.py) +## Scheduling + * [First Come First Served](https://github.com/TheAlgorithms/Python/blob/master/scheduling/first_come_first_served.py) + ## Searches * [Binary Search](https://github.com/TheAlgorithms/Python/blob/master/searches/binary_search.py) * [Fibonacci Search](https://github.com/TheAlgorithms/Python/blob/master/searches/fibonacci_search.py) diff --git a/dynamic_programming/k_means_clustering_tensorflow.py b/dynamic_programming/k_means_clustering_tensorflow.py_tf similarity index 100% rename from dynamic_programming/k_means_clustering_tensorflow.py rename to dynamic_programming/k_means_clustering_tensorflow.py_tf diff --git a/neural_network/gan.py b/neural_network/gan.py_tf similarity index 100% rename from neural_network/gan.py rename to neural_network/gan.py_tf diff --git a/neural_network/input_data.py b/neural_network/input_data.py_tf similarity index 100% rename from neural_network/input_data.py rename to neural_network/input_data.py_tf diff --git a/requirements.txt b/requirements.txt index 2c4ac59d3e09..df5bcbafb2b6 100644 --- a/requirements.txt +++ b/requirements.txt @@ -14,4 +14,4 @@ requests scikit-fuzzy sklearn sympy -tensorflow +tensorflow; python_version < '3.8' From 5543d14b3f84eb4985f59c9f874f367e8980133f Mon Sep 17 00:00:00 2001 From: singlav <41392278+singlav@users.noreply.github.com> Date: Sun, 23 Feb 2020 12:40:51 +0530 Subject: [PATCH 0475/1071] estimate area under a curve defined by non-negative real-valued continuous function within a continuous interval using monte-carlo (#1785) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * estimate area under a curve defined by non-negative real-valued continuous function within a continuous interval using monte-carlo * run black; update comments * Use f”strings” and drop unnecessary returns Co-authored-by: Christian Clauss --- maths/monte_carlo.py | 108 +++++++++++++++++++++++++++++++------------ 1 file changed, 79 insertions(+), 29 deletions(-) diff --git a/maths/monte_carlo.py b/maths/monte_carlo.py index 6a407e98badd..dedca9f6cdf5 100644 --- a/maths/monte_carlo.py +++ b/maths/monte_carlo.py @@ -1,9 +1,10 @@ """ @author: MatteoRaso """ -from numpy import pi, sqrt +from math import pi, sqrt from random import uniform from statistics import mean +from typing import Callable def pi_estimator(iterations: int): @@ -18,7 +19,7 @@ def pi_estimator(iterations: int): 6. Print the estimated and numpy value of pi """ # A local function to see if a dot lands in the circle. - def in_circle(x: float, y: float) -> bool: + def is_in_circle(x: float, y: float) -> bool: distance_from_centre = sqrt((x ** 2) + (y ** 2)) # Our circle has a radius of 1, so a distance # greater than 1 would land outside the circle. @@ -26,50 +27,99 @@ def in_circle(x: float, y: float) -> bool: # The proportion of guesses that landed in the circle proportion = mean( - int(in_circle(uniform(-1.0, 1.0), uniform(-1.0, 1.0))) for _ in range(iterations) + int(is_in_circle(uniform(-1.0, 1.0), uniform(-1.0, 1.0))) + for _ in range(iterations) ) # The ratio of the area for circle to square is pi/4. pi_estimate = proportion * 4 - print("The estimated value of pi is ", pi_estimate) - print("The numpy value of pi is ", pi) - print("The total error is ", abs(pi - pi_estimate)) + print(f"The estimated value of pi is {pi_estimate}") + print(f"The numpy value of pi is {pi}") + print(f"The total error is {abs(pi - pi_estimate)}") -def area_under_line_estimator(iterations: int, - min_value: float=0.0, - max_value: float=1.0) -> float: +def area_under_curve_estimator( + iterations: int, + function_to_integrate: Callable[[float], float], + min_value: float = 0.0, + max_value: float = 1.0, +) -> float: """ An implementation of the Monte Carlo method to find area under - y = x where x lies between min_value to max_value - 1. Let x be a uniformly distributed random variable between min_value to max_value - 2. Expected value of x = (integration of x from min_value to max_value) / (max_value - min_value) - 3. Finding expected value of x: + a single variable non-negative real-valued continuous function, + say f(x), where x lies within a continuous bounded interval, + say [min_value, max_value], where min_value and max_value are + finite numbers + 1. Let x be a uniformly distributed random variable between min_value to + max_value + 2. Expected value of f(x) = + (integrate f(x) from min_value to max_value)/(max_value - min_value) + 3. Finding expected value of f(x): a. Repeatedly draw x from uniform distribution - b. Expected value = average of those values - 4. Actual value = (max_value^2 - min_value^2) / 2 + b. Evaluate f(x) at each of the drawn x values + c. Expected value = average of the function evaluations + 4. Estimated value of integral = Expected value * (max_value - min_value) 5. Returns estimated value """ - return mean(uniform(min_value, max_value) for _ in range(iterations)) * (max_value - min_value) + return mean( + function_to_integrate(uniform(min_value, max_value)) for _ in range(iterations) + ) * (max_value - min_value) -def area_under_line_estimator_check(iterations: int, - min_value: float=0.0, - max_value: float=1.0) -> None: + +def area_under_line_estimator_check( + iterations: int, min_value: float = 0.0, max_value: float = 1.0 +) -> None: """ - Checks estimation error for area_under_line_estimator func - 1. Calls "area_under_line_estimator" function + Checks estimation error for area_under_curve_estimator function + for f(x) = x where x lies within min_value to max_value + 1. Calls "area_under_curve_estimator" function 2. Compares with the expected value 3. Prints estimated, expected and error value """ - - estimated_value = area_under_line_estimator(iterations, min_value, max_value) - expected_value = (max_value*max_value - min_value*min_value) / 2 - + + def identity_function(x: float) -> float: + """ + Represents identity function + >>> [function_to_integrate(x) for x in [-2.0, -1.0, 0.0, 1.0, 2.0]] + [-2.0, -1.0, 0.0, 1.0, 2.0] + """ + return x + + estimated_value = area_under_curve_estimator( + iterations, identity_function, min_value, max_value + ) + expected_value = (max_value * max_value - min_value * min_value) / 2 + + print("******************") + print(f"Estimating area under y=x where x varies from {min_value} to {max_value}") + print(f"Estimated value is {estimated_value}") + print(f"Expected value is {expected_value}") + print(f"Total error is {abs(estimated_value - expected_value)}") + print("******************") + + +def pi_estimator_using_area_under_curve(iterations: int) -> None: + """ + Area under curve y = sqrt(4 - x^2) where x lies in 0 to 2 is equal to pi + """ + + def function_to_integrate(x: float) -> float: + """ + Represents semi-circle with radius 2 + >>> [function_to_integrate(x) for x in [-2.0, 0.0, 2.0]] + [0.0, 2.0, 0.0] + """ + return sqrt(4.0 - x * x) + + estimated_value = area_under_curve_estimator( + iterations, function_to_integrate, 0.0, 2.0 + ) + print("******************") - print("Estimating area under y=x where x varies from ",min_value, " to ",max_value) - print("Estimated value is ", estimated_value) - print("Expected value is ", expected_value) - print("Total error is ", abs(estimated_value - expected_value)) + print("Estimating pi using area_under_curve_estimator") + print(f"Estimated value is {estimated_value}") + print(f"Expected value is {pi}") + print(f"Total error is {abs(estimated_value - pi)}") print("******************") From c1a4cc96c8028d786af151f4177e2ef54250186e Mon Sep 17 00:00:00 2001 From: praveennadiminti Date: Wed, 26 Feb 2020 15:56:45 +0530 Subject: [PATCH 0476/1071] Add bilateral filter (#1786) * Added Bilateral filter * Added Bilateral filter * changed types of varS and varI * formatted with black * added type hints * changed variable names * Update bilateral_filter.py * Drop transitory variables, add parse_args() Co-authored-by: vinayak Co-authored-by: Christian Clauss --- .../filters/bilateral_filter.py | 88 +++++++++++++++++++ 1 file changed, 88 insertions(+) create mode 100644 digital_image_processing/filters/bilateral_filter.py diff --git a/digital_image_processing/filters/bilateral_filter.py b/digital_image_processing/filters/bilateral_filter.py new file mode 100644 index 000000000000..753d6ddb7a3f --- /dev/null +++ b/digital_image_processing/filters/bilateral_filter.py @@ -0,0 +1,88 @@ +""" +Implementation of Bilateral filter + +Inputs: + img: A 2d image with values in between 0 and 1 + varS: variance in space dimension. + varI: variance in Intensity. + N: Kernel size(Must be an odd number) +Output: + img:A 2d zero padded image with values in between 0 and 1 +""" + +import cv2 +import numpy as np +import math +import sys + + +def vec_gaussian(img: np.ndarray, variance: float) -> np.ndarray: + # For applying gaussian function for each element in matrix. + sigma = math.sqrt(variance) + cons = 1 / (sigma * math.sqrt(2 * math.pi)) + return cons * np.exp(-((img / sigma) ** 2) * 0.5) + + +def get_slice(img: np.ndarray, x: int, y: int, kernel_size: int) -> np.ndarray: + half = kernel_size // 2 + return img[x - half : x + half + 1, y - half : y + half + 1] + + +def get_gauss_kernel(kernel_size: int, spatial_variance: float) -> np.ndarray: + # Creates a gaussian kernel of given dimension. + arr = np.zeros((kernel_size, kernel_size)) + for i in range(0, kernel_size): + for j in range(0, kernel_size): + arr[i, j] = math.sqrt( + abs(i - kernel_size // 2) ** 2 + abs(j - kernel_size // 2) ** 2 + ) + return vec_gaussian(arr, spatial_variance) + + +def bilateral_filter( + img: np.ndarray, + spatial_variance: float, + intensity_variance: float, + kernel_size: int, +) -> np.ndarray: + img2 = np.zeros(img.shape) + gaussKer = get_gauss_kernel(kernel_size, spatial_variance) + sizeX, sizeY = img.shape + for i in range(kernel_size // 2, sizeX - kernel_size // 2): + for j in range(kernel_size // 2, sizeY - kernel_size // 2): + + imgS = get_slice(img, i, j, kernel_size) + imgI = imgS - imgS[kernel_size // 2, kernel_size // 2] + imgIG = vec_gaussian(imgI, intensity_variance) + weights = np.multiply(gaussKer, imgIG) + vals = np.multiply(imgS, weights) + val = np.sum(vals) / np.sum(weights) + img2[i, j] = val + return img2 + + +def parse_args(args: list) -> tuple: + filename = args[1] if args[1:] else "../image_data/lena.jpg" + spatial_variance = float(args[2]) if args[2:] else 1.0 + intensity_variance = float(args[3]) if args[3:] else 1.0 + if args[4:]: + kernel_size = int(args[4]) + kernel_size = kernel_size + abs(kernel_size % 2 - 1) + else: + kernel_size = 5 + return filename, spatial_variance, intensity_variance, kernel_size + + +if __name__ == "__main__": + filename, spatial_variance, intensity_variance, kernel_size = parse_args(sys.argv) + img = cv2.imread(filename, 0) + cv2.imshow("input image", img) + + out = img / 255 + out = out.astype("float32") + out = bilateral_filter(out, spatial_variance, intensity_variance, kernel_size) + out = out * 255 + out = np.uint8(out) + cv2.imshow("output image", out) + cv2.waitKey(0) + cv2.destroyAllWindows() From 2b19e8476732dab42354ba5a565d32ed4bd667ba Mon Sep 17 00:00:00 2001 From: Muhammad Umer Farooq Date: Wed, 26 Feb 2020 05:41:56 -0500 Subject: [PATCH 0477/1071] Create emails_from_url.py (#1756) * Create emails_from_url.py * Update emails_from_url.py * Update emails_from_url.py * 0 emails found: * Update emails_from_url.py * Use Python set() to remove duplicates * Update emails_from_url.py * Add type hints and doctests Co-authored-by: vinayak Co-authored-by: Christian Clauss --- web_programming/emails_from_url.py | 105 +++++++++++++++++++++++++++++ 1 file changed, 105 insertions(+) create mode 100644 web_programming/emails_from_url.py diff --git a/web_programming/emails_from_url.py b/web_programming/emails_from_url.py new file mode 100644 index 000000000000..fba9f769bace --- /dev/null +++ b/web_programming/emails_from_url.py @@ -0,0 +1,105 @@ +"""Get the site emails from URL.""" +__author__ = "Muhammad Umer Farooq" +__license__ = "MIT" +__version__ = "1.0.0" +__maintainer__ = "Muhammad Umer Farooq" +__email__ = "contact@muhammadumerfarooq.me" +__status__ = "Alpha" + +import re +from html.parser import HTMLParser +from urllib import parse + +import requests + + +class Parser(HTMLParser): + def __init__(self, domain: str): + HTMLParser.__init__(self) + self.data = [] + self.domain = domain + + def handle_starttag(self, tag: str, attrs: str) -> None: + """ + This function parse html to take takes url from tags + """ + # Only parse the 'anchor' tag. + if tag == "a": + # Check the list of defined attributes. + for name, value in attrs: + # If href is defined, and not empty nor # print it. + if name == "href" and value != "#" and value != "": + # If not already in data. + if value not in self.data: + url = parse.urljoin(self.domain, value) + self.data.append(url) + + +# Get main domain name (example.com) +def get_domain_name(url: str) -> str: + """ + This function get the main domain name + + >>> get_domain_name("https://a.b.c.d/e/f?g=h,i=j#k") + 'c.d' + >>> get_domain_name("Not a URL!") + '' + """ + return ".".join(get_sub_domain_name(url).split(".")[-2:]) + + +# Get sub domain name (sub.example.com) +def get_sub_domain_name(url: str) -> str: + """ + This function get sub domin name + + >>> get_sub_domain_name("https://a.b.c.d/e/f?g=h,i=j#k") + 'a.b.c.d' + >>> get_sub_domain_name("Not a URL!") + '' + """ + return parse.urlparse(url).netloc + + +def emails_from_url(url: str = "https://github.com") -> list: + """ + This function takes url and return all valid urls + """ + # Get the base domain from the url + domain = get_domain_name(url) + + # Initialize the parser + parser = Parser(domain) + + try: + # Open URL + r = requests.get(url) + + # pass the raw HTML to the parser to get links + parser.feed(r.text) + + # Get links and loop through + valid_emails = set() + for link in parser.data: + # open URL. + # read = requests.get(link) + try: + read = requests.get(link) + # Get the valid email. + emails = re.findall("[a-zA-Z0-9]+@" + domain, read.text) + # If not in list then append it. + for email in emails: + valid_emails.add(email) + except ValueError: + pass + except ValueError: + exit(-1) + + # Finally return a sorted list of email addresses with no duplicates. + return sorted(valid_emails) + + +if __name__ == "__main__": + emails = emails_from_url("https://github.com") + print(f"{len(emails)} emails found:") + print("\n".join(sorted(emails))) From 7f04e5cd3499c07f07ae94437b706254edb7ba39 Mon Sep 17 00:00:00 2001 From: matkosoric Date: Wed, 4 Mar 2020 13:40:28 +0100 Subject: [PATCH 0478/1071] contribution guidelines checks (#1787) * spelling corrections * review * improved documentation, removed redundant variables, added testing * added type hint * camel case to snake case * spelling fix * review * python --> Python # it is a brand name, not a snake * explicit cast to int * spaces in int list * "!= None" to "is not None" * Update comb_sort.py * various spelling corrections in documentation & several variables naming conventions fix * + char in file name * import dependency - bug fix Co-authored-by: John Law --- .gitignore | 2 +- DIRECTORY.md | 2 +- backtracking/n_queens.py | 4 +- ciphers/hill_cipher.py | 14 +++--- ciphers/onepad_cipher.py | 4 +- .../binary_tree/binary_search_tree.py | 36 +++++++------- .../number_of_possible_binary_trees.py | 2 +- data_structures/linked_list/deque_doubly.py | 2 +- .../linked_list/doubly_linked_list.py | 2 +- data_structures/queue/queue_on_list.py | 2 +- digital_image_processing/change_contrast.py | 2 +- divide_and_conquer/convex_hull.py | 12 ++--- dynamic_programming/bitmask.py | 28 +++++------ dynamic_programming/fibonacci.py | 2 +- fuzzy_logic/fuzzy_operations.py | 2 +- graphs/bellman_ford.py | 2 +- ...irected_and_undirected_(weighted)_graph.py | 2 +- graphs/minimum_spanning_tree_prims.py | 32 ++++++------- graphs/multi_heuristic_astar.py | 36 +++++--------- hashes/hamming_code.py | 4 +- machine_learning/linear_regression.py | 10 ++-- maths/{3n+1.py => 3n_plus_1.py} | 2 +- maths/average_mode.py | 2 +- maths/basic_maths.py | 2 +- maths/explicit_euler.py | 8 ++-- maths/factorial_iterative.py | 2 +- other/anagrams.py | 4 +- other/autocomplete_using_trie.py | 6 +-- other/fischer_yates_shuffle.py | 12 ++--- other/magicdiamondpattern.py | 2 +- other/primelib.py | 2 +- project_euler/problem_04/sol2.py | 2 +- project_euler/problem_07/sol1.py | 6 +-- project_euler/problem_551/sol1.py | 4 +- scripts/build_directory_md.py | 10 ++-- scripts/validate_filenames.py | 6 +-- searches/binary_search.py | 2 +- searches/interpolation_search.py | 2 +- searches/linear_search.py | 2 +- searches/sentinel_linear_search.py | 2 +- searches/simulated_annealing.py | 6 +-- searches/tabu_search.py | 2 +- sorts/bitonic_sort.py | 22 ++++----- sorts/bogo_sort.py | 11 +++-- sorts/comb_sort.py | 47 +++++++++++-------- sorts/counting_sort.py | 2 +- sorts/heap_sort.py | 2 +- sorts/insertion_sort.py | 2 +- sorts/merge_sort.py | 2 +- sorts/odd_even_transposition_parallel.py | 6 +-- sorts/pancake_sort.py | 2 +- sorts/quick_sort.py | 2 +- sorts/selection_sort.py | 2 +- sorts/shell_sort.py | 2 +- sorts/unknown_sort.py | 2 +- traversals/binary_tree_traversals.py | 2 +- web_programming/emails_from_url.py | 2 - 57 files changed, 198 insertions(+), 198 deletions(-) rename maths/{3n+1.py => 3n_plus_1.py} (97%) diff --git a/.gitignore b/.gitignore index b840d4ed0490..574cdf312836 100644 --- a/.gitignore +++ b/.gitignore @@ -26,7 +26,7 @@ wheels/ MANIFEST # PyInstaller -# Usually these files are written by a python script from a template +# Usually these files are written by a Python script from a template # before PyInstaller builds the exe, so as to inject date/other infos into it. *.manifest *.spec diff --git a/DIRECTORY.md b/DIRECTORY.md index 91a5ab7f6a99..48c1dde9ab67 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -263,7 +263,7 @@ * [Support Vector Machines](https://github.com/TheAlgorithms/Python/blob/master/machine_learning/support_vector_machines.py) ## Maths - * [3N+1](https://github.com/TheAlgorithms/Python/blob/master/maths/3n+1.py) + * [3N+1](https://github.com/TheAlgorithms/Python/blob/master/maths/3n_plus_1.py) * [Abs](https://github.com/TheAlgorithms/Python/blob/master/maths/abs.py) * [Abs Max](https://github.com/TheAlgorithms/Python/blob/master/maths/abs_max.py) * [Abs Min](https://github.com/TheAlgorithms/Python/blob/master/maths/abs_min.py) diff --git a/backtracking/n_queens.py b/backtracking/n_queens.py index c0db41496aee..58d9c4279a35 100644 --- a/backtracking/n_queens.py +++ b/backtracking/n_queens.py @@ -42,7 +42,7 @@ def solve(board, row): """ It creates a state space tree and calls the safe function until it receives a False Boolean and terminates that branch and backtracks to the next - poosible solution branch. + possible solution branch. """ if row >= len(board): """ @@ -56,7 +56,7 @@ def solve(board, row): return for i in range(len(board)): """ - For every row it iterates through each column to check if it is feesible to place a + For every row it iterates through each column to check if it is feasible to place a queen there. If all the combinations for that particular branch are successful the board is reinitialized for the next possible combination. diff --git a/ciphers/hill_cipher.py b/ciphers/hill_cipher.py index ffc1d9793bf2..47910e4ebdaa 100644 --- a/ciphers/hill_cipher.py +++ b/ciphers/hill_cipher.py @@ -65,11 +65,11 @@ def __init__(self, encrypt_key): encrypt_key is an NxN numpy matrix """ self.encrypt_key = self.modulus(encrypt_key) # mod36 calc's on the encrypt key - self.checkDeterminant() # validate the determinant of the encryption key + self.check_determinant() # validate the determinant of the encryption key self.decrypt_key = None self.break_key = encrypt_key.shape[0] - def checkDeterminant(self): + def check_determinant(self): det = round(numpy.linalg.det(self.encrypt_key)) if det < 0: @@ -83,7 +83,7 @@ def checkDeterminant(self): ) ) - def processText(self, text): + def process_text(self, text): text = list(text.upper()) text = [char for char in text if char in self.key_string] @@ -94,7 +94,7 @@ def processText(self, text): return "".join(text) def encrypt(self, text): - text = self.processText(text.upper()) + text = self.process_text(text.upper()) encrypted = "" for i in range(0, len(text) - self.break_key + 1, self.break_key): @@ -109,7 +109,7 @@ def encrypt(self, text): return encrypted - def makeDecryptKey(self): + def make_decrypt_key(self): det = round(numpy.linalg.det(self.encrypt_key)) if det < 0: @@ -129,8 +129,8 @@ def makeDecryptKey(self): return self.toInt(self.modulus(inv_key)) def decrypt(self, text): - self.decrypt_key = self.makeDecryptKey() - text = self.processText(text.upper()) + self.decrypt_key = self.make_decrypt_key() + text = self.process_text(text.upper()) decrypted = "" for i in range(0, len(text) - self.break_key + 1, self.break_key): diff --git a/ciphers/onepad_cipher.py b/ciphers/onepad_cipher.py index 5a410bfa638a..fe07908afff5 100644 --- a/ciphers/onepad_cipher.py +++ b/ciphers/onepad_cipher.py @@ -3,7 +3,7 @@ class Onepad: def encrypt(self, text): - """Function to encrypt text using psedo-random numbers""" + """Function to encrypt text using pseudo-random numbers""" plain = [ord(i) for i in text] key = [] cipher = [] @@ -15,7 +15,7 @@ def encrypt(self, text): return cipher, key def decrypt(self, cipher, key): - """Function to decrypt text using psedo-random numbers.""" + """Function to decrypt text using pseudo-random numbers.""" plain = [] for i in range(len(key)): p = int((cipher[i] - (key[i]) ** 2) / key[i]) diff --git a/data_structures/binary_tree/binary_search_tree.py b/data_structures/binary_tree/binary_search_tree.py index 86dcd6489bd5..40546875216b 100644 --- a/data_structures/binary_tree/binary_search_tree.py +++ b/data_structures/binary_tree/binary_search_tree.py @@ -28,16 +28,16 @@ def __str__(self): """ return str(self.root) - def __reassign_nodes(self, node, newChildren): - if newChildren is not None: # reset its kids - newChildren.parent = node.parent + def __reassign_nodes(self, node, new_children): + if new_children is not None: # reset its kids + new_children.parent = node.parent if node.parent is not None: # reset its parent if self.is_right(node): # If it is the right children - node.parent.right = newChildren + node.parent.right = new_children else: - node.parent.left = newChildren + node.parent.left = new_children else: - self.root = newChildren + self.root = new_children def is_right(self, node): return node == node.parent.right @@ -117,39 +117,39 @@ def remove(self, value): elif node.right is None: # Has only left children self.__reassign_nodes(node, node.left) else: - tmpNode = self.get_max( + tmp_node = self.get_max( node.left - ) # Gets the max value of the left branch - self.remove(tmpNode.value) + ) # Gets the max value of the left branch + self.remove(tmp_node.value) node.value = ( - tmpNode.value - ) # Assigns the value to the node to delete and keesp tree structure + tmp_node.value + ) # Assigns the value to the node to delete and keep tree structure def preorder_traverse(self, node): if node is not None: - yield node # Preorder Traversal + yield node # Preorder Traversal yield from self.preorder_traverse(node.left) yield from self.preorder_traverse(node.right) - def traversal_tree(self, traversalFunction=None): + def traversal_tree(self, traversal_function=None): """ This function traversal the tree. You can pass a function to traversal the tree as needed by client code """ - if traversalFunction is None: + if traversal_function is None: return self.preorder_traverse(self.root) else: - return traversalFunction(self.root) + return traversal_function(self.root) def postorder(curr_node): """ postOrder (left, right, self) """ - nodeList = list() + node_list = list() if curr_node is not None: - nodeList = postorder(curr_node.left) + postorder(curr_node.right) + [curr_node] - return nodeList + node_list = postorder(curr_node.left) + postorder(curr_node.right) + [curr_node] + return node_list def binary_search_tree(): diff --git a/data_structures/binary_tree/number_of_possible_binary_trees.py b/data_structures/binary_tree/number_of_possible_binary_trees.py index d053ba31171f..1ad8f2ed4287 100644 --- a/data_structures/binary_tree/number_of_possible_binary_trees.py +++ b/data_structures/binary_tree/number_of_possible_binary_trees.py @@ -82,7 +82,7 @@ def binary_tree_count(node_count: int) -> int: """ Return the number of possible of binary trees. :param n: number of nodes - :return: Number of possilble binary trees + :return: Number of possible binary trees >>> binary_tree_count(5) 5040 diff --git a/data_structures/linked_list/deque_doubly.py b/data_structures/linked_list/deque_doubly.py index 0898db679802..b2e73a8f789b 100644 --- a/data_structures/linked_list/deque_doubly.py +++ b/data_structures/linked_list/deque_doubly.py @@ -21,7 +21,7 @@ def __init__(self, link_p, element, link_n): def has_next_and_prev(self): return " Prev -> {0}, Next -> {1}".format( - self._prev != None, self._next != None + self._prev is not None, self._next is not None ) def __init__(self): diff --git a/data_structures/linked_list/doubly_linked_list.py b/data_structures/linked_list/doubly_linked_list.py index 27b04ed39ad2..f8f652be6d32 100644 --- a/data_structures/linked_list/doubly_linked_list.py +++ b/data_structures/linked_list/doubly_linked_list.py @@ -62,7 +62,7 @@ def isEmpty(self): # Will return True if the list is empty def display(self): # Prints contents of the list current = self.head - while current != None: + while current is not None: current.displayLink() current = current.next print() diff --git a/data_structures/queue/queue_on_list.py b/data_structures/queue/queue_on_list.py index bb44e08ad6c5..4d69461af66a 100644 --- a/data_structures/queue/queue_on_list.py +++ b/data_structures/queue/queue_on_list.py @@ -1,4 +1,4 @@ -"""Queue represented by a python list""" +"""Queue represented by a Python list""" class Queue: diff --git a/digital_image_processing/change_contrast.py b/digital_image_processing/change_contrast.py index 76f1a3e1fcd8..c7da52298ae2 100644 --- a/digital_image_processing/change_contrast.py +++ b/digital_image_processing/change_contrast.py @@ -2,7 +2,7 @@ Changing contrast with PIL This algorithm is used in -https://noivce.pythonanywhere.com/ python web app. +https://noivce.pythonanywhere.com/ Python web app. python/black: True flake8 : True diff --git a/divide_and_conquer/convex_hull.py b/divide_and_conquer/convex_hull.py index 76184524e266..11b16975c8b4 100644 --- a/divide_and_conquer/convex_hull.py +++ b/divide_and_conquer/convex_hull.py @@ -344,19 +344,19 @@ def convex_hull_recursive(points): right_most_point = points[n - 1] convex_set = {left_most_point, right_most_point} - upperhull = [] - lowerhull = [] + upper_hull = [] + lower_hull = [] for i in range(1, n - 1): det = _det(left_most_point, right_most_point, points[i]) if det > 0: - upperhull.append(points[i]) + upper_hull.append(points[i]) elif det < 0: - lowerhull.append(points[i]) + lower_hull.append(points[i]) - _construct_hull(upperhull, left_most_point, right_most_point, convex_set) - _construct_hull(lowerhull, right_most_point, left_most_point, convex_set) + _construct_hull(upper_hull, left_most_point, right_most_point, convex_set) + _construct_hull(lower_hull, right_most_point, left_most_point, convex_set) return sorted(convex_set) diff --git a/dynamic_programming/bitmask.py b/dynamic_programming/bitmask.py index 1841f1747557..625a0809c4b9 100644 --- a/dynamic_programming/bitmask.py +++ b/dynamic_programming/bitmask.py @@ -1,6 +1,6 @@ """ -This is a python implementation for questions involving task assignments between people. +This is a Python implementation for questions involving task assignments between people. Here Bitmasking and DP are used for solving this. Question :- @@ -25,41 +25,41 @@ def __init__(self, task_performed, total): self.task = defaultdict(list) # stores the list of persons for each task - # finalmask is used to check if all persons are included by setting all bits to 1 - self.finalmask = (1 << len(task_performed)) - 1 + # final_mask is used to check if all persons are included by setting all bits to 1 + self.final_mask = (1 << len(task_performed)) - 1 - def CountWaysUtil(self, mask, taskno): + def CountWaysUtil(self, mask, task_no): # if mask == self.finalmask all persons are distributed tasks, return 1 - if mask == self.finalmask: + if mask == self.final_mask: return 1 # if not everyone gets the task and no more tasks are available, return 0 - if taskno > self.total_tasks: + if task_no > self.total_tasks: return 0 # if case already considered - if self.dp[mask][taskno] != -1: - return self.dp[mask][taskno] + if self.dp[mask][task_no] != -1: + return self.dp[mask][task_no] # Number of ways when we don't this task in the arrangement - total_ways_util = self.CountWaysUtil(mask, taskno + 1) + total_ways_util = self.CountWaysUtil(mask, task_no + 1) # now assign the tasks one by one to all possible persons and recursively assign for the remaining tasks. - if taskno in self.task: - for p in self.task[taskno]: + if task_no in self.task: + for p in self.task[task_no]: # if p is already given a task if mask & (1 << p): continue # assign this task to p and change the mask value. And recursively assign tasks with the new mask value. - total_ways_util += self.CountWaysUtil(mask | (1 << p), taskno + 1) + total_ways_util += self.CountWaysUtil(mask | (1 << p), task_no + 1) # save the value. - self.dp[mask][taskno] = total_ways_util + self.dp[mask][task_no] = total_ways_util - return self.dp[mask][taskno] + return self.dp[mask][task_no] def countNoOfWays(self, task_performed): diff --git a/dynamic_programming/fibonacci.py b/dynamic_programming/fibonacci.py index 923560b54d30..45319269f5d4 100644 --- a/dynamic_programming/fibonacci.py +++ b/dynamic_programming/fibonacci.py @@ -28,7 +28,7 @@ def get(self, sequence_no=None): [0, 1, 1, 2, 3, 5] [] """ - if sequence_no != None: + if sequence_no is not None: if sequence_no < len(self.fib_array): return print(self.fib_array[: sequence_no + 1]) else: diff --git a/fuzzy_logic/fuzzy_operations.py b/fuzzy_logic/fuzzy_operations.py index cb870e8d9e3b..34dd9c029be7 100644 --- a/fuzzy_logic/fuzzy_operations.py +++ b/fuzzy_logic/fuzzy_operations.py @@ -11,7 +11,7 @@ if __name__ == "__main__": - # Create universe of discourse in python using linspace () + # Create universe of discourse in Python using linspace () X = np.linspace(start=0, stop=75, num=75, endpoint=True, retstep=False) # Create two fuzzy sets by defining any membership function (trapmf(), gbellmf(),gaussmf(), etc). diff --git a/graphs/bellman_ford.py b/graphs/bellman_ford.py index 6b5e8b735c43..807e0b0fcdb9 100644 --- a/graphs/bellman_ford.py +++ b/graphs/bellman_ford.py @@ -8,7 +8,7 @@ def printDist(dist, V): def BellmanFord(graph: List[Dict[str, int]], V: int, E: int, src: int) -> int: - r""" + """ Returns shortest paths from a vertex src to all other vertices. """ diff --git a/graphs/directed_and_undirected_(weighted)_graph.py b/graphs/directed_and_undirected_(weighted)_graph.py index 15e2ce663594..26c87cd8f4b2 100644 --- a/graphs/directed_and_undirected_(weighted)_graph.py +++ b/graphs/directed_and_undirected_(weighted)_graph.py @@ -3,7 +3,7 @@ import math as math import time -# the dfault weight is 1 if not assigned but all the implementation is weighted +# the default weight is 1 if not assigned but all the implementation is weighted class DirectedGraph: diff --git a/graphs/minimum_spanning_tree_prims.py b/graphs/minimum_spanning_tree_prims.py index 216d6a3f56de..6255b6af64ad 100644 --- a/graphs/minimum_spanning_tree_prims.py +++ b/graphs/minimum_spanning_tree_prims.py @@ -6,13 +6,13 @@ def PrimsAlgorithm(l): nodePosition = [] - def getPosition(vertex): + def get_position(vertex): return nodePosition[vertex] - def setPosition(vertex, pos): + def set_position(vertex, pos): nodePosition[vertex] = pos - def topToBottom(heap, start, size, positions): + def top_to_bottom(heap, start, size, positions): if start > size // 2 - 1: return else: @@ -28,14 +28,14 @@ def topToBottom(heap, start, size, positions): heap[m], positions[m] = heap[start], positions[start] heap[start], positions[start] = temp, temp1 - temp = getPosition(positions[m]) - setPosition(positions[m], getPosition(positions[start])) - setPosition(positions[start], temp) + temp = get_position(positions[m]) + set_position(positions[m], get_position(positions[start])) + set_position(positions[start], temp) - topToBottom(heap, m, size, positions) + top_to_bottom(heap, m, size, positions) # Update function if value of any node in min-heap decreases - def bottomToTop(val, index, heap, position): + def bottom_to_top(val, index, heap, position): temp = position[index] while index != 0: @@ -47,27 +47,27 @@ def bottomToTop(val, index, heap, position): if val < heap[parent]: heap[index] = heap[parent] position[index] = position[parent] - setPosition(position[parent], index) + set_position(position[parent], index) else: heap[index] = val position[index] = temp - setPosition(temp, index) + set_position(temp, index) break index = parent else: heap[0] = val position[0] = temp - setPosition(temp, 0) + set_position(temp, 0) def heapify(heap, positions): start = len(heap) // 2 - 1 for i in range(start, -1, -1): - topToBottom(heap, i, len(heap), positions) + top_to_bottom(heap, i, len(heap), positions) def deleteMinimum(heap, positions): temp = positions[0] heap[0] = sys.maxsize - topToBottom(heap, 0, len(heap), positions) + top_to_bottom(heap, 0, len(heap), positions) return temp visited = [0 for i in range(len(l))] @@ -96,9 +96,9 @@ def deleteMinimum(heap, positions): TreeEdges.append((Nbr_TV[vertex], vertex)) visited[vertex] = 1 for v in l[vertex]: - if visited[v[0]] == 0 and v[1] < Distance_TV[getPosition(v[0])]: - Distance_TV[getPosition(v[0])] = v[1] - bottomToTop(v[1], getPosition(v[0]), Distance_TV, Positions) + if visited[v[0]] == 0 and v[1] < Distance_TV[get_position(v[0])]: + Distance_TV[get_position(v[0])] = v[1] + bottom_to_top(v[1], get_position(v[0]), Distance_TV, Positions) Nbr_TV[v[0]] = vertex return TreeEdges diff --git a/graphs/multi_heuristic_astar.py b/graphs/multi_heuristic_astar.py index 56cfc727d338..386aab695bb0 100644 --- a/graphs/multi_heuristic_astar.py +++ b/graphs/multi_heuristic_astar.py @@ -52,25 +52,25 @@ def get(self): return (priority, item) -def consistent_hueristic(P, goal): +def consistent_heuristic(P, goal): # euclidean distance a = np.array(P) b = np.array(goal) return np.linalg.norm(a - b) -def hueristic_2(P, goal): +def heuristic_2(P, goal): # integer division by time variable - return consistent_hueristic(P, goal) // t + return consistent_heuristic(P, goal) // t -def hueristic_1(P, goal): +def heuristic_1(P, goal): # manhattan distance return abs(P[0] - goal[0]) + abs(P[1] - goal[1]) def key(start, i, goal, g_function): - ans = g_function[start] + W1 * hueristics[i](start, goal) + ans = g_function[start] + W1 * heuristics[i](start, goal) return ans @@ -134,7 +134,7 @@ def expand_state( open_list, back_pointer, ): - for itera in range(n_hueristic): + for itera in range(n_heuristic): open_list[itera].remove_element(s) # print("s", s) # print("j", j) @@ -158,30 +158,24 @@ def expand_state( if neighbours not in close_list_anchor: open_list[0].put(neighbours, key(neighbours, 0, goal, g_function)) if neighbours not in close_list_inad: - for var in range(1, n_hueristic): + for var in range(1, n_heuristic): if key(neighbours, var, goal, g_function) <= W2 * key( neighbours, 0, goal, g_function ): - # print("why not plssssssssss") open_list[j].put( neighbours, key(neighbours, var, goal, g_function) ) - # print - def make_common_ground(): some_list = [] - # block 1 for x in range(1, 5): for y in range(1, 6): some_list.append((x, y)) - # line for x in range(15, 20): some_list.append((x, 17)) - # block 2 big for x in range(10, 19): for y in range(1, 15): some_list.append((x, y)) @@ -196,7 +190,7 @@ def make_common_ground(): return some_list -hueristics = {0: consistent_hueristic, 1: hueristic_1, 2: hueristic_2} +heuristics = {0: consistent_heuristic, 1: heuristic_1, 2: heuristic_2} blocks_blk = [ (0, 1), @@ -229,7 +223,7 @@ def make_common_ground(): W1 = 1 W2 = 1 n = 20 -n_hueristic = 3 # one consistent and two other inconsistent +n_heuristic = 3 # one consistent and two other inconsistent # start and end destination start = (0, 0) @@ -238,26 +232,24 @@ def make_common_ground(): t = 1 -def multi_a_star(start, goal, n_hueristic): +def multi_a_star(start, goal, n_heuristic): g_function = {start: 0, goal: float("inf")} back_pointer = {start: -1, goal: -1} open_list = [] visited = set() - for i in range(n_hueristic): + for i in range(n_heuristic): open_list.append(PriorityQueue()) open_list[i].put(start, key(start, i, goal, g_function)) close_list_anchor = [] close_list_inad = [] while open_list[0].minkey() < float("inf"): - for i in range(1, n_hueristic): - # print("i", i) + for i in range(1, n_heuristic): # print(open_list[0].minkey(), open_list[i].minkey()) if open_list[i].minkey() <= W2 * open_list[0].minkey(): global t t += 1 - # print("less prio") if g_function[goal] <= open_list[i].minkey(): if g_function[goal] < float("inf"): do_something(back_pointer, goal, start) @@ -276,12 +268,10 @@ def multi_a_star(start, goal, n_hueristic): ) close_list_inad.append(get_s) else: - # print("more prio") if g_function[goal] <= open_list[0].minkey(): if g_function[goal] < float("inf"): do_something(back_pointer, goal, start) else: - # print("hoolla") get_s = open_list[0].top_show() visited.add(get_s) expand_state( @@ -319,4 +309,4 @@ def multi_a_star(start, goal, n_hueristic): if __name__ == "__main__": - multi_a_star(start, goal, n_hueristic) + multi_a_star(start, goal, n_heuristic) diff --git a/hashes/hamming_code.py b/hashes/hamming_code.py index 756ba7c6670f..c1ed7fe1d727 100644 --- a/hashes/hamming_code.py +++ b/hashes/hamming_code.py @@ -121,7 +121,7 @@ def emitterConverter(sizePar, data): # counter to control the loop reading contLoop = 0 for x in dataOrd: - if x != None: + if x is not None: try: aux = (binPos[contLoop])[-1 * (bp)] except IndexError: @@ -224,7 +224,7 @@ def receptorConverter(sizePar, data): # Counter to control loop reading contLoop = 0 for x in dataOrd: - if x != None: + if x is not None: try: aux = (binPos[contLoop])[-1 * (bp)] except IndexError: diff --git a/machine_learning/linear_regression.py b/machine_learning/linear_regression.py index b666feddccc7..29bb7c48589e 100644 --- a/machine_learning/linear_regression.py +++ b/machine_learning/linear_regression.py @@ -1,10 +1,10 @@ """ Linear regression is the most basic type of regression commonly used for -predictive analysis. The idea is pretty simple, we have a dataset and we have -a feature's associated with it. The Features should be choose very cautiously -as they determine, how much our model will be able to make future predictions. -We try to set these Feature weights, over many iterations, so that they best -fits our dataset. In this particular code, i had used a CSGO dataset (ADR vs +predictive analysis. The idea is pretty simple: we have a dataset and we have +features associated with it. Features should be chosen very cautiously +as they determine how much our model will be able to make future predictions. +We try to set the weight of these features, over many iterations, so that they best +fit our dataset. In this particular code, I had used a CSGO dataset (ADR vs Rating). We try to best fit a line through dataset and estimate the parameters. """ import requests diff --git a/maths/3n+1.py b/maths/3n_plus_1.py similarity index 97% rename from maths/3n+1.py rename to maths/3n_plus_1.py index 64ff34fd2039..d3684561827f 100644 --- a/maths/3n+1.py +++ b/maths/3n_plus_1.py @@ -3,7 +3,7 @@ def n31(a: int) -> Tuple[List[int], int]: """ - Returns the Collatz sequence and its length of any postiver integer. + Returns the Collatz sequence and its length of any positive integer. >>> n31(4) ([4, 2, 1], 3) """ diff --git a/maths/average_mode.py b/maths/average_mode.py index c1a4b3521448..d472dc04d4bf 100644 --- a/maths/average_mode.py +++ b/maths/average_mode.py @@ -14,7 +14,7 @@ def mode(input_list): # Defining function "mode." >>> mode(input_list) == statistics.mode(input_list) True """ - # Copying inputlist to check with the index number later. + # Copying input_list to check with the index number later. check_list = input_list.copy() result = list() # Empty list to store the counts of elements in input_list for x in input_list: diff --git a/maths/basic_maths.py b/maths/basic_maths.py index 5dbfd250d308..07ee3b3df296 100644 --- a/maths/basic_maths.py +++ b/maths/basic_maths.py @@ -63,7 +63,7 @@ def sum_of_divisors(n: int) -> int: def euler_phi(n: int) -> int: - """Calculte Euler's Phi Function. + """Calculate Euler's Phi Function. >>> euler_phi(100) 40 """ diff --git a/maths/explicit_euler.py b/maths/explicit_euler.py index 8a43d71fb432..7c780198602b 100644 --- a/maths/explicit_euler.py +++ b/maths/explicit_euler.py @@ -1,7 +1,7 @@ import numpy as np -def explicit_euler(ode_func, y0, x0, stepsize, x_end): +def explicit_euler(ode_func, y0, x0, step_size, x_end): """ Calculate numeric solution at each step to an ODE using Euler's Method @@ -22,14 +22,14 @@ def explicit_euler(ode_func, y0, x0, stepsize, x_end): >>> y[-1] 144.77277243257308 """ - N = int(np.ceil((x_end - x0) / stepsize)) + N = int(np.ceil((x_end - x0) / step_size)) y = np.zeros((N + 1,)) y[0] = y0 x = x0 for k in range(N): - y[k + 1] = y[k] + stepsize * ode_func(x, y[k]) - x += stepsize + y[k + 1] = y[k] + step_size * ode_func(x, y[k]) + x += step_size return y diff --git a/maths/factorial_iterative.py b/maths/factorial_iterative.py index 249408cb5b4e..64314790c11c 100644 --- a/maths/factorial_iterative.py +++ b/maths/factorial_iterative.py @@ -26,5 +26,5 @@ def factorial(n: int) -> int: if __name__ == "__main__": - n = int(input("Enter a positivve integer: ").strip() or 0) + n = int(input("Enter a positive integer: ").strip() or 0) print(f"factorial{n} is {factorial(n)}") diff --git a/other/anagrams.py b/other/anagrams.py index 6e6806e92a0f..471413194498 100644 --- a/other/anagrams.py +++ b/other/anagrams.py @@ -16,8 +16,8 @@ def signature(word): word_bysig[signature(word)].append(word) -def anagram(myword): - return word_bysig[signature(myword)] +def anagram(my_word): + return word_bysig[signature(my_word)] print("finding anagrams...") diff --git a/other/autocomplete_using_trie.py b/other/autocomplete_using_trie.py index eb906f8efa9a..8aa0dc223680 100644 --- a/other/autocomplete_using_trie.py +++ b/other/autocomplete_using_trie.py @@ -26,10 +26,10 @@ def _elements(self, d): result = [] for c, v in d.items(): if c == END: - subresult = [" "] + sub_result = [" "] else: - subresult = [c + s for s in self._elements(v)] - result.extend(subresult) + sub_result = [c + s for s in self._elements(v)] + result.extend(sub_result) return tuple(result) diff --git a/other/fischer_yates_shuffle.py b/other/fischer_yates_shuffle.py index 4217cb0bef67..99121ba632c7 100644 --- a/other/fischer_yates_shuffle.py +++ b/other/fischer_yates_shuffle.py @@ -7,12 +7,12 @@ import random -def FYshuffle(LIST): - for i in range(len(LIST)): - a = random.randint(0, len(LIST) - 1) - b = random.randint(0, len(LIST) - 1) - LIST[a], LIST[b] = LIST[b], LIST[a] - return LIST +def FYshuffle(list): + for i in range(len(list)): + a = random.randint(0, len(list) - 1) + b = random.randint(0, len(list) - 1) + list[a], list[b] = list[b], list[a] + return list if __name__ == "__main__": diff --git a/other/magicdiamondpattern.py b/other/magicdiamondpattern.py index 6de5046c9f18..4ca698d80c28 100644 --- a/other/magicdiamondpattern.py +++ b/other/magicdiamondpattern.py @@ -1,4 +1,4 @@ -# Python program for generating diamond pattern in python 3.7+ +# Python program for generating diamond pattern in Python 3.7+ # Function to print upper half of diamond (pyramid) def floyd(n): diff --git a/other/primelib.py b/other/primelib.py index 1b99819ce62a..a6d1d7dfb324 100644 --- a/other/primelib.py +++ b/other/primelib.py @@ -3,7 +3,7 @@ @author: Christian Bender -This python library contains some useful functions to deal with +This Python library contains some useful functions to deal with prime numbers and whole numbers. Overview: diff --git a/project_euler/problem_04/sol2.py b/project_euler/problem_04/sol2.py index ecc503912c34..0f185f1216ab 100644 --- a/project_euler/problem_04/sol2.py +++ b/project_euler/problem_04/sol2.py @@ -20,7 +20,7 @@ def solution(n): 39893 """ answer = 0 - for i in range(999, 99, -1): # 3 digit nimbers range from 999 down to 100 + for i in range(999, 99, -1): # 3 digit numbers range from 999 down to 100 for j in range(999, 99, -1): t = str(i * j) if t == t[::-1] and i * j < n: diff --git a/project_euler/problem_07/sol1.py b/project_euler/problem_07/sol1.py index f6b2584d9cdc..373915f886e1 100644 --- a/project_euler/problem_07/sol1.py +++ b/project_euler/problem_07/sol1.py @@ -8,7 +8,7 @@ from math import sqrt -def isprime(n): +def is_prime(n): if n == 2: return True elif n % 2 == 0: @@ -41,11 +41,11 @@ def solution(n): j = 1 while i != n and j < 3: j += 1 - if isprime(j): + if is_prime(j): i += 1 while i != n: j += 2 - if isprime(j): + if is_prime(j): i += 1 return j diff --git a/project_euler/problem_551/sol1.py b/project_euler/problem_551/sol1.py index 4775800693bc..873e520cc9b4 100644 --- a/project_euler/problem_551/sol1.py +++ b/project_euler/problem_551/sol1.py @@ -52,10 +52,10 @@ def next_term(a_i, k, i, n): sub_memo = memo.get(ds_b) - if sub_memo != None: + if sub_memo is not None: jumps = sub_memo.get(c) - if jumps != None and len(jumps) > 0: + if jumps is not None and len(jumps) > 0: # find and make the largest jump without going over max_jump = -1 for _k in range(len(jumps) - 1, -1, -1): diff --git a/scripts/build_directory_md.py b/scripts/build_directory_md.py index 1043e6ccb696..9e26ee81a323 100755 --- a/scripts/build_directory_md.py +++ b/scripts/build_directory_md.py @@ -6,14 +6,14 @@ URL_BASE = "https://github.com/TheAlgorithms/Python/blob/master" -def good_filepaths(top_dir: str = ".") -> Iterator[str]: - for dirpath, dirnames, filenames in os.walk(top_dir): - dirnames[:] = [d for d in dirnames if d != "scripts" and d[0] not in "._"] +def good_file_paths(top_dir: str = ".") -> Iterator[str]: + for dir_path, dir_names, filenames in os.walk(top_dir): + dir_names[:] = [d for d in dir_names if d != "scripts" and d[0] not in "._"] for filename in filenames: if filename == "__init__.py": continue if os.path.splitext(filename)[1] in (".py", ".ipynb"): - yield os.path.join(dirpath, filename).lstrip("./") + yield os.path.join(dir_path, filename).lstrip("./") def md_prefix(i): @@ -31,7 +31,7 @@ def print_path(old_path: str, new_path: str) -> str: def print_directory_md(top_dir: str = ".") -> None: old_path = "" - for filepath in sorted(good_filepaths()): + for filepath in sorted(good_file_paths()): filepath, filename = os.path.split(filepath) if filepath != old_path: old_path = print_path(old_path, filepath) diff --git a/scripts/validate_filenames.py b/scripts/validate_filenames.py index 51dd6a40cb41..f22fda4149f3 100755 --- a/scripts/validate_filenames.py +++ b/scripts/validate_filenames.py @@ -1,10 +1,10 @@ #!/usr/bin/env python3 import os -from build_directory_md import good_filepaths +from build_directory_md import good_file_paths -filepaths = list(good_filepaths()) -assert filepaths, "good_filepaths() failed!" +filepaths = list(good_file_paths()) +assert filepaths, "good_file_paths() failed!" upper_files = [file for file in filepaths if file != file.lower()] diff --git a/searches/binary_search.py b/searches/binary_search.py index fe22e423a7d4..8edf63136f9a 100644 --- a/searches/binary_search.py +++ b/searches/binary_search.py @@ -1,5 +1,5 @@ """ -This is pure python implementation of binary search algorithms +This is pure Python implementation of binary search algorithms For doctests run following command: python -m doctest -v binary_search.py diff --git a/searches/interpolation_search.py b/searches/interpolation_search.py index 419ec52c0f4e..f4fa8e1203df 100644 --- a/searches/interpolation_search.py +++ b/searches/interpolation_search.py @@ -1,5 +1,5 @@ """ -This is pure python implementation of interpolation search algorithm +This is pure Python implementation of interpolation search algorithm """ diff --git a/searches/linear_search.py b/searches/linear_search.py index b6f52ca4857f..76683dc6a6a8 100644 --- a/searches/linear_search.py +++ b/searches/linear_search.py @@ -1,5 +1,5 @@ """ -This is pure python implementation of linear search algorithm +This is pure Python implementation of linear search algorithm For doctests run following command: python -m doctest -v linear_search.py diff --git a/searches/sentinel_linear_search.py b/searches/sentinel_linear_search.py index 5650151b1d2f..69c1cf9f351a 100644 --- a/searches/sentinel_linear_search.py +++ b/searches/sentinel_linear_search.py @@ -1,5 +1,5 @@ """ -This is pure python implementation of sentinel linear search algorithm +This is pure Python implementation of sentinel linear search algorithm For doctests run following command: python -m doctest -v sentinel_linear_search.py diff --git a/searches/simulated_annealing.py b/searches/simulated_annealing.py index d3542b00af45..c24adc1ddb41 100644 --- a/searches/simulated_annealing.py +++ b/searches/simulated_annealing.py @@ -66,15 +66,15 @@ def simulated_annealing( if change > 0: # improves the solution next_state = picked_neighbor else: - probabililty = (math.e) ** ( + probability = (math.e) ** ( change / current_temp ) # probability generation function - if random.random() < probabililty: # random number within probability + if random.random() < probability: # random number within probability next_state = picked_neighbor current_temp = current_temp - (current_temp * rate_of_decrease) if current_temp < threshold_temp or next_state is None: - # temperature below threshold, or could not find a suitaable neighbor + # temperature below threshold, or could not find a suitable neighbor search_end = True else: current_state = next_state diff --git a/searches/tabu_search.py b/searches/tabu_search.py index 2847dca7acd7..9ddc5e8dee7f 100644 --- a/searches/tabu_search.py +++ b/searches/tabu_search.py @@ -1,5 +1,5 @@ """ -This is pure python implementation of Tabu search algorithm for a Travelling Salesman Problem, that the distances +This is pure Python implementation of Tabu search algorithm for a Travelling Salesman Problem, that the distances between the cities are symmetric (the distance between city 'a' and city 'b' is the same between city 'b' and city 'a'). The TSP can be represented into a graph. The cities are represented by nodes and the distance between them is represented by the weight of the ark between the nodes. diff --git a/sorts/bitonic_sort.py b/sorts/bitonic_sort.py index 131f97291fbb..ce80c6028729 100644 --- a/sorts/bitonic_sort.py +++ b/sorts/bitonic_sort.py @@ -14,36 +14,36 @@ def compAndSwap(a, i, j, dire): # if dir = 1, and in descending order otherwise (means dir=0). # The sequence to be sorted starts at index position low, # the parameter cnt is the number of elements to be sorted. -def bitonicMerge(a, low, cnt, dire): +def bitonic_merge(a, low, cnt, dire): if cnt > 1: k = int(cnt / 2) for i in range(low, low + k): compAndSwap(a, i, i + k, dire) - bitonicMerge(a, low, k, dire) - bitonicMerge(a, low + k, k, dire) + bitonic_merge(a, low, k, dire) + bitonic_merge(a, low + k, k, dire) # This function first produces a bitonic sequence by recursively # sorting its two halves in opposite sorting orders, and then -# calls bitonicMerge to make them in the same order -def bitonicSort(a, low, cnt, dire): +# calls bitonic_merge to make them in the same order +def bitonic_sort(a, low, cnt, dire): if cnt > 1: k = int(cnt / 2) - bitonicSort(a, low, k, 1) - bitonicSort(a, low + k, k, 0) - bitonicMerge(a, low, cnt, dire) + bitonic_sort(a, low, k, 1) + bitonic_sort(a, low + k, k, 0) + bitonic_merge(a, low, cnt, dire) - # Caller of bitonicSort for sorting the entire array of length N + # Caller of bitonic_sort for sorting the entire array of length N # in ASCENDING order def sort(a, N, up): - bitonicSort(a, 0, N, up) + bitonic_sort(a, 0, N, up) if __name__ == "__main__": - # Driver code to test above + a = [] n = int(input().strip()) diff --git a/sorts/bogo_sort.py b/sorts/bogo_sort.py index 0afa444e5b8e..b72f2089f3d2 100644 --- a/sorts/bogo_sort.py +++ b/sorts/bogo_sort.py @@ -1,5 +1,10 @@ """ -This is a pure python implementation of the bogosort algorithm +This is a pure Python implementation of the bogosort algorithm, +also known as permutation sort, stupid sort, slowsort, shotgun sort, or monkey sort. +Bogosort generates random permutations until it guesses the correct one. + +More info on: https://en.wikipedia.org/wiki/Bogosort + For doctests run following command: python -m doctest -v bogo_sort.py or @@ -25,7 +30,7 @@ def bogo_sort(collection): [-45, -5, -2] """ - def isSorted(collection): + def is_sorted(collection): if len(collection) < 2: return True for i in range(len(collection) - 1): @@ -33,7 +38,7 @@ def isSorted(collection): return False return True - while not isSorted(collection): + while not is_sorted(collection): random.shuffle(collection) return collection diff --git a/sorts/comb_sort.py b/sorts/comb_sort.py index 3c4c57483e3f..c36bb8e63748 100644 --- a/sorts/comb_sort.py +++ b/sorts/comb_sort.py @@ -1,8 +1,13 @@ """ +This is pure Python implementation of comb sort algorithm. Comb sort is a relatively simple sorting algorithm originally designed by Wlodzimierz Dobosiewicz in 1980. -Later it was rediscovered by Stephen Lacey and Richard Box in 1991. Comb sort improves on bubble sort. +It was rediscovered by Stephen Lacey and Richard Box in 1991. Comb sort improves on bubble sort algorithm. +In bubble sort, distance (or gap) between two compared elements is always one. +Comb sort improvement is that gap can be much more than 1, in order to prevent slowing down by small values +at the end of a list. + +More info on: https://en.wikipedia.org/wiki/Comb_sort -This is pure python implementation of comb sort algorithm For doctests run following command: python -m doctest -v comb_sort.py or @@ -13,42 +18,44 @@ """ -def comb_sort(data): +def comb_sort(data: list) -> list: """Pure implementation of comb sort algorithm in Python - :param collection: some mutable ordered collection with heterogeneous - comparable items inside - :return: the same collection ordered by ascending + :param data: mutable collection with comparable items + :return: the same collection in ascending order Examples: >>> comb_sort([0, 5, 3, 2, 2]) [0, 2, 2, 3, 5] >>> comb_sort([]) [] - >>> comb_sort([-2, -5, -45]) - [-45, -5, -2] + >>> comb_sort([99, 45, -7, 8, 2, 0, -15, 3]) + [-15, -7, 0, 2, 3, 8, 45, 99] """ shrink_factor = 1.3 gap = len(data) - swapped = True - i = 0 + completed = False - while gap > 1 or swapped: - # Update the gap value for a next comb - gap = int(float(gap) / shrink_factor) + while not completed: - swapped = False - i = 0 + # Update the gap value for a next comb + gap = int(gap / shrink_factor) + if gap <= 1: + completed = True - while gap + i < len(data): - if data[i] > data[i + gap]: + index = 0 + while index + gap < len(data): + if data[index] > data[index + gap]: # Swap values - data[i], data[i + gap] = data[i + gap], data[i] - swapped = True - i += 1 + data[index], data[index + gap] = data[index + gap], data[index] + completed = False + index += 1 return data if __name__ == "__main__": + import doctest + doctest.testmod() + user_input = input("Enter numbers separated by a comma:\n").strip() unsorted = [int(item) for item in user_input.split(",")] print(comb_sort(unsorted)) diff --git a/sorts/counting_sort.py b/sorts/counting_sort.py index b672d4af47cb..892ec5d5f344 100644 --- a/sorts/counting_sort.py +++ b/sorts/counting_sort.py @@ -1,5 +1,5 @@ """ -This is pure python implementation of counting sort algorithm +This is pure Python implementation of counting sort algorithm For doctests run following command: python -m doctest -v counting_sort.py or diff --git a/sorts/heap_sort.py b/sorts/heap_sort.py index a39ae2b88da2..4dca879bd89c 100644 --- a/sorts/heap_sort.py +++ b/sorts/heap_sort.py @@ -1,5 +1,5 @@ """ -This is a pure python implementation of the heap sort algorithm. +This is a pure Python implementation of the heap sort algorithm. For doctests run following command: python -m doctest -v heap_sort.py diff --git a/sorts/insertion_sort.py b/sorts/insertion_sort.py index b767018c3d57..ca678381b431 100644 --- a/sorts/insertion_sort.py +++ b/sorts/insertion_sort.py @@ -1,5 +1,5 @@ """ -This is a pure python implementation of the insertion sort algorithm +This is a pure Python implementation of the insertion sort algorithm For doctests run following command: python -m doctest -v insertion_sort.py diff --git a/sorts/merge_sort.py b/sorts/merge_sort.py index 13f1144d4ad3..e8031a1cb97c 100644 --- a/sorts/merge_sort.py +++ b/sorts/merge_sort.py @@ -1,5 +1,5 @@ """ -This is a pure python implementation of the merge sort algorithm +This is a pure Python implementation of the merge sort algorithm For doctests run following command: python -m doctest -v merge_sort.py diff --git a/sorts/odd_even_transposition_parallel.py b/sorts/odd_even_transposition_parallel.py index 080c86af5a8c..5de7a016c628 100644 --- a/sorts/odd_even_transposition_parallel.py +++ b/sorts/odd_even_transposition_parallel.py @@ -18,7 +18,7 @@ """ The function run by the processes that sorts the list -position = the position in the list the prcoess represents, used to know which +position = the position in the list the process represents, used to know which neighbor we pass our value to value = the initial value at list[position] LSend, RSend = the pipes we use to send to our left and right neighbors @@ -35,7 +35,7 @@ def oeProcess(position, value, LSend, RSend, LRcv, RRcv, resultPipe): # find out we are sorted as it does to sort the list with this algorithm for i in range(0, 10): - if (i + position) % 2 == 0 and RSend != None: + if (i + position) % 2 == 0 and RSend is not None: # send your value to your right neighbor processLock.acquire() RSend[1].send(value) @@ -48,7 +48,7 @@ def oeProcess(position, value, LSend, RSend, LRcv, RRcv, resultPipe): # take the lower value since you are on the left value = min(value, temp) - elif (i + position) % 2 != 0 and LSend != None: + elif (i + position) % 2 != 0 and LSend is not None: # send your value to your left neighbor processLock.acquire() LSend[1].send(value) diff --git a/sorts/pancake_sort.py b/sorts/pancake_sort.py index ee54e57f9e0f..e5d600738435 100644 --- a/sorts/pancake_sort.py +++ b/sorts/pancake_sort.py @@ -1,5 +1,5 @@ """ -This is a pure python implementation of the pancake sort algorithm +This is a pure Python implementation of the pancake sort algorithm For doctests run following command: python3 -m doctest -v pancake_sort.py or diff --git a/sorts/quick_sort.py b/sorts/quick_sort.py index 29e10206f720..f2a55c58b437 100644 --- a/sorts/quick_sort.py +++ b/sorts/quick_sort.py @@ -1,5 +1,5 @@ """ -This is a pure python implementation of the quick sort algorithm +This is a pure Python implementation of the quick sort algorithm For doctests run following command: python -m doctest -v quick_sort.py diff --git a/sorts/selection_sort.py b/sorts/selection_sort.py index 6a9c063d3364..f3beb31b7070 100644 --- a/sorts/selection_sort.py +++ b/sorts/selection_sort.py @@ -1,5 +1,5 @@ """ -This is a pure python implementation of the selection sort algorithm +This is a pure Python implementation of the selection sort algorithm For doctests run following command: python -m doctest -v selection_sort.py diff --git a/sorts/shell_sort.py b/sorts/shell_sort.py index ff9c2785b218..80d95870f95b 100644 --- a/sorts/shell_sort.py +++ b/sorts/shell_sort.py @@ -1,5 +1,5 @@ """ -This is a pure python implementation of the shell sort algorithm +This is a pure Python implementation of the shell sort algorithm For doctests run following command: python -m doctest -v shell_sort.py diff --git a/sorts/unknown_sort.py b/sorts/unknown_sort.py index 087533b4a575..5ecc55e9cf69 100644 --- a/sorts/unknown_sort.py +++ b/sorts/unknown_sort.py @@ -1,7 +1,7 @@ """ Python implementation of a sort algorithm. Best Case Scenario : O(n) -Worst Case Scenario : O(n^2) because native python functions:min, max and remove are already O(n) +Worst Case Scenario : O(n^2) because native Python functions:min, max and remove are already O(n) """ diff --git a/traversals/binary_tree_traversals.py b/traversals/binary_tree_traversals.py index 31a73ae0c6a4..c522ecebc0ff 100644 --- a/traversals/binary_tree_traversals.py +++ b/traversals/binary_tree_traversals.py @@ -1,5 +1,5 @@ """ -This is pure python implementation of tree traversal algorithms +This is pure Python implementation of tree traversal algorithms """ import queue from typing import List diff --git a/web_programming/emails_from_url.py b/web_programming/emails_from_url.py index fba9f769bace..01dee274f015 100644 --- a/web_programming/emails_from_url.py +++ b/web_programming/emails_from_url.py @@ -51,8 +51,6 @@ def get_domain_name(url: str) -> str: # Get sub domain name (sub.example.com) def get_sub_domain_name(url: str) -> str: """ - This function get sub domin name - >>> get_sub_domain_name("https://a.b.c.d/e/f?g=h,i=j#k") 'a.b.c.d' >>> get_sub_domain_name("Not a URL!") From a9f73e318cddf43769083614a3e1f9dab1ec50fc Mon Sep 17 00:00:00 2001 From: Christian Clauss Date: Thu, 5 Mar 2020 17:57:43 +0100 Subject: [PATCH 0479/1071] Added SkipList (#1781) * Added SkipList * Add missing type hints and doctests * Add missing doctest * Tighten up doctest Co-authored-by: Christian Clauss --- data_structures/linked_list/skip_list.py | 443 +++++++++++++++++++++++ 1 file changed, 443 insertions(+) create mode 100644 data_structures/linked_list/skip_list.py diff --git a/data_structures/linked_list/skip_list.py b/data_structures/linked_list/skip_list.py new file mode 100644 index 000000000000..195d7ffc6b91 --- /dev/null +++ b/data_structures/linked_list/skip_list.py @@ -0,0 +1,443 @@ +""" +Based on "Skip Lists: A Probabilistic Alternative to Balanced Trees" by William Pugh +https://epaperpress.com/sortsearch/download/skiplist.pdf +""" + +from random import random +from typing import Generic, List, Optional, Tuple, TypeVar + +KT = TypeVar("KT") +VT = TypeVar("VT") + + +class Node(Generic[KT, VT]): + def __init__(self, key: KT, value: VT): + self.key = key + self.value = value + self.forward: List[Node[KT, VT]] = [] + + def __repr__(self) -> str: + """ + :return: Visual representation of Node + + >>> node = Node("Key", 2) + >>> repr(node) + 'Node(Key: 2)' + """ + + return f"Node({self.key}: {self.value})" + + @property + def level(self) -> int: + """ + :return: Number of forward references + + >>> node = Node("Key", 2) + >>> node.level + 0 + >>> node.forward.append(Node("Key2", 4)) + >>> node.level + 1 + >>> node.forward.append(Node("Key3", 6)) + >>> node.level + 2 + """ + + return len(self.forward) + + +class SkipList(Generic[KT, VT]): + def __init__(self, p: float = 0.5, max_level: int = 16): + self.head = Node("root", None) + self.level = 0 + self.p = p + self.max_level = max_level + + def __str__(self) -> str: + """ + :return: Visual representation of SkipList + + >>> skip_list = SkipList() + >>> print(skip_list) + SkipList(level=0) + >>> skip_list.insert("Key1", "Value") + >>> print(skip_list) # doctest: +ELLIPSIS + SkipList(level=... + [root]--... + [Key1]--Key1... + None *... + >>> skip_list.insert("Key2", "OtherValue") + >>> print(skip_list) # doctest: +ELLIPSIS + SkipList(level=... + [root]--... + [Key1]--Key1... + [Key2]--Key2... + None *... + """ + + items = list(self) + + if len(items) == 0: + return f"SkipList(level={self.level})" + + label_size = max((len(str(item)) for item in items), default=4) + label_size = max(label_size, 4) + 4 + + node = self.head + lines = [] + + forwards = node.forward.copy() + lines.append(f"[{node.key}]".ljust(label_size, "-") + "* " * len(forwards)) + lines.append(" " * label_size + "| " * len(forwards)) + + while len(node.forward) != 0: + node = node.forward[0] + + lines.append( + f"[{node.key}]".ljust(label_size, "-") + + " ".join(str(n.key) if n.key == node.key else "|" for n in forwards) + ) + lines.append(" " * label_size + "| " * len(forwards)) + forwards[: node.level] = node.forward + + lines.append("None".ljust(label_size) + "* " * len(forwards)) + return f"SkipList(level={self.level})\n" + "\n".join(lines) + + def __iter__(self): + node = self.head + + while len(node.forward) != 0: + yield node.forward[0].key + node = node.forward[0] + + def random_level(self) -> int: + """ + :return: Random level from [1, self.max_level] interval. + Higher values are less likely. + """ + + level = 1 + while random() < self.p and level < self.max_level: + level += 1 + + return level + + def _locate_node(self, key) -> Tuple[Optional[Node[KT, VT]], List[Node[KT, VT]]]: + """ + :param key: Searched key, + :return: Tuple with searched node (or None if given key is not present) + and list of nodes that refer (if key is present) of should refer to given node. + """ + + # Nodes with refer or should refer to output node + update_vector = [] + + node = self.head + + for i in reversed(range(self.level)): + # i < node.level - When node level is lesser than `i` decrement `i`. + # node.forward[i].key < key - Jumping to node with key value higher + # or equal to searched key would result + # in skipping searched key. + while i < node.level and node.forward[i].key < key: + node = node.forward[i] + # Each leftmost node (relative to searched node) will potentially have to be updated. + update_vector.append(node) + + update_vector.reverse() # Note that we were inserting values in reverse order. + + # len(node.forward) != 0 - If current node doesn't contain any further + # references then searched key is not present. + # node.forward[0].key == key - Next node key should be equal to search key + # if key is present. + if len(node.forward) != 0 and node.forward[0].key == key: + return node.forward[0], update_vector + else: + return None, update_vector + + def delete(self, key: KT): + """ + :param key: Key to remove from list. + + >>> skip_list = SkipList() + >>> skip_list.insert(2, "Two") + >>> skip_list.insert(1, "One") + >>> skip_list.insert(3, "Three") + >>> list(skip_list) + [1, 2, 3] + >>> skip_list.delete(2) + >>> list(skip_list) + [1, 3] + """ + + node, update_vector = self._locate_node(key) + + if node is not None: + for i, update_node in enumerate(update_vector): + # Remove or replace all references to removed node. + if update_node.level > i and update_node.forward[i].key == key: + if node.level > i: + update_node.forward[i] = node.forward[i] + else: + update_node.forward = update_node.forward[:i] + + def insert(self, key: KT, value: VT): + """ + :param key: Key to insert. + :param value: Value associated with given key. + + >>> skip_list = SkipList() + >>> skip_list.insert(2, "Two") + >>> skip_list.find(2) + 'Two' + >>> list(skip_list) + [2] + """ + + node, update_vector = self._locate_node(key) + if node is not None: + node.value = value + else: + level = self.random_level() + + if level > self.level: + # After level increase we have to add additional nodes to head. + for i in range(self.level - 1, level): + update_vector.append(self.head) + self.level = level + + new_node = Node(key, value) + + for i, update_node in enumerate(update_vector[:level]): + # Change references to pass through new node. + if update_node.level > i: + new_node.forward.append(update_node.forward[i]) + + if update_node.level < i + 1: + update_node.forward.append(new_node) + else: + update_node.forward[i] = new_node + + def find(self, key: VT) -> Optional[VT]: + """ + :param key: Search key. + :return: Value associated with given key or None if given key is not present. + + >>> skip_list = SkipList() + >>> skip_list.find(2) + >>> skip_list.insert(2, "Two") + >>> skip_list.find(2) + 'Two' + >>> skip_list.insert(2, "Three") + >>> skip_list.find(2) + 'Three' + """ + + node, _ = self._locate_node(key) + + if node is not None: + return node.value + + return None + + +def test_insert(): + skip_list = SkipList() + skip_list.insert("Key1", 3) + skip_list.insert("Key2", 12) + skip_list.insert("Key3", 41) + skip_list.insert("Key4", -19) + + node = skip_list.head + all_values = {} + while node.level != 0: + node = node.forward[0] + all_values[node.key] = node.value + + assert len(all_values) == 4 + assert all_values["Key1"] == 3 + assert all_values["Key2"] == 12 + assert all_values["Key3"] == 41 + assert all_values["Key4"] == -19 + + +def test_insert_overrides_existing_value(): + skip_list = SkipList() + skip_list.insert("Key1", 10) + skip_list.insert("Key1", 12) + + skip_list.insert("Key5", 7) + skip_list.insert("Key7", 10) + skip_list.insert("Key10", 5) + + skip_list.insert("Key7", 7) + skip_list.insert("Key5", 5) + skip_list.insert("Key10", 10) + + node = skip_list.head + all_values = {} + while node.level != 0: + node = node.forward[0] + all_values[node.key] = node.value + + if len(all_values) != 4: + print() + assert len(all_values) == 4 + assert all_values["Key1"] == 12 + assert all_values["Key7"] == 7 + assert all_values["Key5"] == 5 + assert all_values["Key10"] == 10 + + +def test_searching_empty_list_returns_none(): + skip_list = SkipList() + assert skip_list.find("Some key") is None + + +def test_search(): + skip_list = SkipList() + + skip_list.insert("Key2", 20) + assert skip_list.find("Key2") == 20 + + skip_list.insert("Some Key", 10) + skip_list.insert("Key2", 8) + skip_list.insert("V", 13) + + assert skip_list.find("Y") is None + assert skip_list.find("Key2") == 8 + assert skip_list.find("Some Key") == 10 + assert skip_list.find("V") == 13 + + +def test_deleting_item_from_empty_list_do_nothing(): + skip_list = SkipList() + skip_list.delete("Some key") + + assert len(skip_list.head.forward) == 0 + + +def test_deleted_items_are_not_founded_by_find_method(): + skip_list = SkipList() + + skip_list.insert("Key1", 12) + skip_list.insert("V", 13) + skip_list.insert("X", 14) + skip_list.insert("Key2", 15) + + skip_list.delete("V") + skip_list.delete("Key2") + + assert skip_list.find("V") is None + assert skip_list.find("Key2") is None + + +def test_delete_removes_only_given_key(): + skip_list = SkipList() + + skip_list.insert("Key1", 12) + skip_list.insert("V", 13) + skip_list.insert("X", 14) + skip_list.insert("Key2", 15) + + skip_list.delete("V") + assert skip_list.find("V") is None + assert skip_list.find("X") == 14 + assert skip_list.find("Key1") == 12 + assert skip_list.find("Key2") == 15 + + skip_list.delete("X") + assert skip_list.find("V") is None + assert skip_list.find("X") is None + assert skip_list.find("Key1") == 12 + assert skip_list.find("Key2") == 15 + + skip_list.delete("Key1") + assert skip_list.find("V") is None + assert skip_list.find("X") is None + assert skip_list.find("Key1") is None + assert skip_list.find("Key2") == 15 + + skip_list.delete("Key2") + assert skip_list.find("V") is None + assert skip_list.find("X") is None + assert skip_list.find("Key1") is None + assert skip_list.find("Key2") is None + + +def test_delete_doesnt_leave_dead_nodes(): + skip_list = SkipList() + + skip_list.insert("Key1", 12) + skip_list.insert("V", 13) + skip_list.insert("X", 142) + skip_list.insert("Key2", 15) + + skip_list.delete("X") + + def traverse_keys(node): + yield node.key + for forward_node in node.forward: + yield from traverse_keys(forward_node) + + assert len(set(traverse_keys(skip_list.head))) == 4 + + +def test_iter_always_yields_sorted_values(): + def is_sorted(lst): + for item, next_item in zip(lst, lst[1:]): + if next_item < item: + return False + return True + + skip_list = SkipList() + for i in range(10): + skip_list.insert(i, i) + assert is_sorted(list(skip_list)) + skip_list.delete(5) + skip_list.delete(8) + skip_list.delete(2) + assert is_sorted(list(skip_list)) + skip_list.insert(-12, -12) + skip_list.insert(77, 77) + assert is_sorted(list(skip_list)) + + +def pytests(): + for i in range(100): + # Repeat test 100 times due to the probabilistic nature of skip list + # random values == random bugs + test_insert() + test_insert_overrides_existing_value() + + test_searching_empty_list_returns_none() + test_search() + + test_deleting_item_from_empty_list_do_nothing() + test_deleted_items_are_not_founded_by_find_method() + test_delete_removes_only_given_key() + test_delete_doesnt_leave_dead_nodes() + + test_iter_always_yields_sorted_values() + + +def main(): + """ + >>> pytests() + """ + + skip_list = SkipList() + skip_list.insert(2, "2") + skip_list.insert(4, "4") + skip_list.insert(6, "4") + skip_list.insert(4, "5") + skip_list.insert(8, "4") + skip_list.insert(9, "4") + + skip_list.delete(4) + + print(skip_list) + + +if __name__ == "__main__": + main() From 182e3042f83b083d68ac4bf08c11bddd12851282 Mon Sep 17 00:00:00 2001 From: Aakash Dinkar <35952953+aakashdinkar@users.noreply.github.com> Date: Sun, 8 Mar 2020 11:34:21 +0530 Subject: [PATCH 0480/1071] update rot13.py (#1790) * update rot13.py * Update rot13.py * Type hints, doctests, URL to Wikipedia Co-authored-by: Christian Clauss --- ciphers/rot13.py | 24 ++++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/ciphers/rot13.py b/ciphers/rot13.py index a7b546511967..7a5954f89dd3 100644 --- a/ciphers/rot13.py +++ b/ciphers/rot13.py @@ -1,9 +1,19 @@ -def dencrypt(s, n): +def dencrypt(s: str, n: int=13): + """ + https://en.wikipedia.org/wiki/ROT13 + + >>> msg = "My secret bank account number is 173-52946 so don't tell anyone!!" + >>> s = dencrypt(msg) + >>> s + "Zl frperg onax nppbhag ahzore vf 173-52946 fb qba'g gryy nalbar!!" + >>> dencrypt(s) == msg + True + """ out = "" for c in s: - if c >= "A" and c <= "Z": + if "A" <= c <= "Z": out += chr(ord("A") + (ord(c) - ord("A") + n) % 26) - elif c >= "a" and c <= "z": + elif "a" <= c <= "z": out += chr(ord("a") + (ord(c) - ord("a") + n) % 26) else: out += c @@ -11,14 +21,16 @@ def dencrypt(s, n): def main(): - s0 = "HELLO" + s0 = input("Enter message: ") s1 = dencrypt(s0, 13) - print(s1) # URYYB + print("Encryption:", s1) s2 = dencrypt(s1, 13) - print(s2) # HELLO + print("Decryption: ", s2) if __name__ == "__main__": + import doctest + doctest.testmod() main() From 5e3eb12a7b78092cd6bc31892b76e5ef976ad462 Mon Sep 17 00:00:00 2001 From: yoshitaka-i <8393063+inoue0426@users.noreply.github.com> Date: Fri, 13 Mar 2020 16:33:36 +0900 Subject: [PATCH 0481/1071] add relu function (#1795) --- maths/relu.py | 39 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 maths/relu.py diff --git a/maths/relu.py b/maths/relu.py new file mode 100644 index 000000000000..2c41d2e9dad9 --- /dev/null +++ b/maths/relu.py @@ -0,0 +1,39 @@ +""" +This script demonstrates the implementation of the ReLU function. + +It's a kind of activation function defined as the positive part of its argument in the context of neural network. +The function takes a vector of K real numbers as input and then argmax(x, 0). +After through ReLU, the element of the vector always 0 or real number. + +Script inspired from its corresponding Wikipedia article +https://en.wikipedia.org/wiki/Rectifier_(neural_networks) +""" + +import numpy as np +from typing import List + + +def relu(vector: List[float]): + """ + Implements the relu function + + Parameters: + vector (np.array,list,tuple): A numpy array of shape (1,n) + consisting of real values or a similar list,tuple + + + Returns: + relu_vec (np.array): The input numpy array, after applying + relu. + + >>> vec = np.array([-1, 0, 5]) + >>> relu(vec) + array([0, 0, 5]) + """ + + # compare two arrays and then return element-wise maxima. + return np.maximum(0, vector) + + +if __name__ == "__main__": + print(np.array(relu([-1, 0, 5]))) # --> [0, 0, 5] From 10fc90c7bd7d2350179cf7754f99a8a58d543664 Mon Sep 17 00:00:00 2001 From: Miggelito Date: Fri, 13 Mar 2020 09:13:43 +0100 Subject: [PATCH 0482/1071] Added Random Forest Classifier (#1738) * Added Random Forest Regressor * Updated file to standard * Added Random Forest Classifier (Iris dataset) and a Confusion Matrix for result visualization --- machine_learning/random_forest_classifier.py | 45 ++++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100644 machine_learning/random_forest_classifier.py diff --git a/machine_learning/random_forest_classifier.py b/machine_learning/random_forest_classifier.py new file mode 100644 index 000000000000..07bd33b340c5 --- /dev/null +++ b/machine_learning/random_forest_classifier.py @@ -0,0 +1,45 @@ +# Random Forest Classifier Example + +from sklearn.datasets import load_iris +from sklearn.model_selection import train_test_split +from sklearn.ensemble import RandomForestClassifier +from sklearn.metrics import plot_confusion_matrix +import matplotlib.pyplot as plt + + +def main(): + + """ + Random Tree Classifier Example using sklearn function. + Iris type dataset is used to demonstrate algorithm. + """ + + # Load Iris house price dataset + iris = load_iris() + + # Split dataset into train and test data + X = iris["data"] # features + Y = iris["target"] + x_train, x_test, y_train, y_test = train_test_split( + X, Y, test_size=0.3, random_state=1 + ) + + # Random Forest Classifier + rand_for = RandomForestClassifier(random_state=42, n_estimators=100) + rand_for.fit(x_train, y_train) + + # Display Confusion Matrix of Classifier + plot_confusion_matrix( + rand_for, + x_test, + y_test, + display_labels=iris["target_names"], + cmap="Blues", + normalize="true", + ) + plt.title("Normalized Confusion Matrix - IRIS Dataset") + plt.show() + + +if __name__ == "__main__": + main() From b1377f0e57a736df92d422720890ef16f6d12d5a Mon Sep 17 00:00:00 2001 From: Christian Clauss Date: Fri, 13 Mar 2020 09:23:38 +0100 Subject: [PATCH 0483/1071] autoblack: actions/checkout@v1 # Use v1, NOT v2 (#1796) * autoblack: actions/checkout@v1 # Use v1, NOT v2 * fixup! Format Python code with psf/black push Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> --- .github/workflows/autoblack.yml | 2 +- ciphers/rot13.py | 7 ++++--- sorts/comb_sort.py | 1 + 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/.github/workflows/autoblack.yml b/.github/workflows/autoblack.yml index 95d2d3d64233..99243f3ec7fc 100644 --- a/.github/workflows/autoblack.yml +++ b/.github/workflows/autoblack.yml @@ -9,7 +9,7 @@ jobs: build: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v1 # Use v1, NOT v2 - uses: actions/setup-python@v1 - run: pip install black - run: black --check . diff --git a/ciphers/rot13.py b/ciphers/rot13.py index 7a5954f89dd3..6bcb471d6e05 100644 --- a/ciphers/rot13.py +++ b/ciphers/rot13.py @@ -1,4 +1,4 @@ -def dencrypt(s: str, n: int=13): +def dencrypt(s: str, n: int = 13): """ https://en.wikipedia.org/wiki/ROT13 @@ -24,13 +24,14 @@ def main(): s0 = input("Enter message: ") s1 = dencrypt(s0, 13) - print("Encryption:", s1) + print("Encryption:", s1) s2 = dencrypt(s1, 13) - print("Decryption: ", s2) + print("Decryption: ", s2) if __name__ == "__main__": import doctest + doctest.testmod() main() diff --git a/sorts/comb_sort.py b/sorts/comb_sort.py index c36bb8e63748..416fd4552697 100644 --- a/sorts/comb_sort.py +++ b/sorts/comb_sort.py @@ -54,6 +54,7 @@ def comb_sort(data: list) -> list: if __name__ == "__main__": import doctest + doctest.testmod() user_input = input("Enter numbers separated by a comma:\n").strip() From cb5f8c6e4e77fdfefb1c7c9c7507e36fa058def9 Mon Sep 17 00:00:00 2001 From: KDH Date: Fri, 13 Mar 2020 21:46:52 +0900 Subject: [PATCH 0484/1071] Fix typo (#1797) colision => collision --- data_structures/hashing/hash_table.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/data_structures/hashing/hash_table.py b/data_structures/hashing/hash_table.py index 69eaa65d8e57..988f2ba0dfbf 100644 --- a/data_structures/hashing/hash_table.py +++ b/data_structures/hashing/hash_table.py @@ -44,7 +44,7 @@ def _set_value(self, key, data): self.values[key] = data self._keys[key] = data - def _colision_resolution(self, key, data=None): + def _collision_resolution(self, key, data=None): new_key = self.hash_function(key + 1) while self.values[new_key] is not None and self.values[new_key] != key: @@ -74,9 +74,9 @@ def insert_data(self, data): pass else: - colision_resolution = self._colision_resolution(key, data) - if colision_resolution is not None: - self._set_value(colision_resolution, data) + collision_resolution = self._collision_resolution(key, data) + if collision_resolution is not None: + self._set_value(collision_resolution, data) else: self.rehashing() self.insert_data(data) From 2da98db4a7c46e260b972556a39e20420fe4c0ec Mon Sep 17 00:00:00 2001 From: John Law Date: Sat, 14 Mar 2020 06:37:44 +0100 Subject: [PATCH 0485/1071] Effective directory writer (#1800) * updating DIRECTORY.md * Update directory_writer.yml Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> --- .github/workflows/directory_writer.yml | 15 ++++++++------- DIRECTORY.md | 9 +++++++-- 2 files changed, 15 insertions(+), 9 deletions(-) diff --git a/.github/workflows/directory_writer.yml b/.github/workflows/directory_writer.yml index f910ab33e00b..0b9793b5edfc 100644 --- a/.github/workflows/directory_writer.yml +++ b/.github/workflows/directory_writer.yml @@ -6,16 +6,17 @@ jobs: build: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v1 - uses: actions/setup-python@v1 with: python-version: 3.x - - name: Update DIRECTORY.md + - name: Write DIRECTORY.md run: | scripts/build_directory_md.py 2>&1 | tee DIRECTORY.md - git config --global user.name github-actions - git config --global user.email '${GITHUB_ACTOR}@users.noreply.github.com' - git remote set-url origin https://x-access-token:${{ secrets.GITHUB_TOKEN }}@github.com/$GITHUB_REPOSITORY - git add DIRECTORY.md + git config --global user.name "GitHub Actions" + git config --global user.email "actions@github.com" git commit -am "updating DIRECTORY.md" || true - git push --force origin HEAD:$GITHUB_REF || true + - name: Push DIRECTORY.md + if: github.ref == 'refs/heads/master' + run: | + git push "https://${GITHUB_ACTOR}:${{secrets.GITHUB_TOKEN}}@github.com/$GITHUB_REPOSITORY.git" ${master} --force diff --git a/DIRECTORY.md b/DIRECTORY.md index 48c1dde9ab67..8dd9fa929255 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -108,6 +108,7 @@ * [Is Palindrome](https://github.com/TheAlgorithms/Python/blob/master/data_structures/linked_list/is_palindrome.py) * [Print Reverse](https://github.com/TheAlgorithms/Python/blob/master/data_structures/linked_list/print_reverse.py) * [Singly Linked List](https://github.com/TheAlgorithms/Python/blob/master/data_structures/linked_list/singly_linked_list.py) + * [Skip List](https://github.com/TheAlgorithms/Python/blob/master/data_structures/linked_list/skip_list.py) * [Swap Nodes](https://github.com/TheAlgorithms/Python/blob/master/data_structures/linked_list/swap_nodes.py) * Queue * [Circular Queue](https://github.com/TheAlgorithms/Python/blob/master/data_structures/queue/circular_queue.py) @@ -135,6 +136,7 @@ * Edge Detection * [Canny](https://github.com/TheAlgorithms/Python/blob/master/digital_image_processing/edge_detection/canny.py) * Filters + * [Bilateral Filter](https://github.com/TheAlgorithms/Python/blob/master/digital_image_processing/filters/bilateral_filter.py) * [Convolve](https://github.com/TheAlgorithms/Python/blob/master/digital_image_processing/filters/convolve.py) * [Gaussian Filter](https://github.com/TheAlgorithms/Python/blob/master/digital_image_processing/filters/gaussian_filter.py) * [Median Filter](https://github.com/TheAlgorithms/Python/blob/master/digital_image_processing/filters/median_filter.py) @@ -257,13 +259,14 @@ * [Logistic Regression](https://github.com/TheAlgorithms/Python/blob/master/machine_learning/logistic_regression.py) * [Multilayer Perceptron Classifier](https://github.com/TheAlgorithms/Python/blob/master/machine_learning/multilayer_perceptron_classifier.py) * [Polymonial Regression](https://github.com/TheAlgorithms/Python/blob/master/machine_learning/polymonial_regression.py) + * [Random Forest Classifier](https://github.com/TheAlgorithms/Python/blob/master/machine_learning/random_forest_classifier.py) * [Random Forest Regressor](https://github.com/TheAlgorithms/Python/blob/master/machine_learning/random_forest_regressor.py) * [Scoring Functions](https://github.com/TheAlgorithms/Python/blob/master/machine_learning/scoring_functions.py) * [Sequential Minimum Optimization](https://github.com/TheAlgorithms/Python/blob/master/machine_learning/sequential_minimum_optimization.py) * [Support Vector Machines](https://github.com/TheAlgorithms/Python/blob/master/machine_learning/support_vector_machines.py) ## Maths - * [3N+1](https://github.com/TheAlgorithms/Python/blob/master/maths/3n_plus_1.py) + * [3N Plus 1](https://github.com/TheAlgorithms/Python/blob/master/maths/3n_plus_1.py) * [Abs](https://github.com/TheAlgorithms/Python/blob/master/maths/abs.py) * [Abs Max](https://github.com/TheAlgorithms/Python/blob/master/maths/abs_max.py) * [Abs Min](https://github.com/TheAlgorithms/Python/blob/master/maths/abs_min.py) @@ -313,8 +316,8 @@ * [Miller Rabin](https://github.com/TheAlgorithms/Python/blob/master/maths/miller_rabin.py) * [Mobius Function](https://github.com/TheAlgorithms/Python/blob/master/maths/mobius_function.py) * [Modular Exponential](https://github.com/TheAlgorithms/Python/blob/master/maths/modular_exponential.py) + * [Monte Carlo](https://github.com/TheAlgorithms/Python/blob/master/maths/monte_carlo.py) * [Monte Carlo Dice](https://github.com/TheAlgorithms/Python/blob/master/maths/monte_carlo_dice.py) - * [Montecarlo](https://github.com/TheAlgorithms/Python/blob/master/maths/montecarlo.py) * [Newton Raphson](https://github.com/TheAlgorithms/Python/blob/master/maths/newton_raphson.py) * [Numerical Integration](https://github.com/TheAlgorithms/Python/blob/master/maths/numerical_integration.py) * [Perfect Square](https://github.com/TheAlgorithms/Python/blob/master/maths/perfect_square.py) @@ -328,6 +331,7 @@ * [Quadratic Equations Complex Numbers](https://github.com/TheAlgorithms/Python/blob/master/maths/quadratic_equations_complex_numbers.py) * [Radians](https://github.com/TheAlgorithms/Python/blob/master/maths/radians.py) * [Radix2 Fft](https://github.com/TheAlgorithms/Python/blob/master/maths/radix2_fft.py) + * [Relu](https://github.com/TheAlgorithms/Python/blob/master/maths/relu.py) * [Runge Kutta](https://github.com/TheAlgorithms/Python/blob/master/maths/runge_kutta.py) * [Segmented Sieve](https://github.com/TheAlgorithms/Python/blob/master/maths/segmented_sieve.py) * Series @@ -595,6 +599,7 @@ ## Web Programming * [Crawl Google Results](https://github.com/TheAlgorithms/Python/blob/master/web_programming/crawl_google_results.py) * [Current Stock Price](https://github.com/TheAlgorithms/Python/blob/master/web_programming/current_stock_price.py) + * [Emails From Url](https://github.com/TheAlgorithms/Python/blob/master/web_programming/emails_from_url.py) * [Fetch Bbc News](https://github.com/TheAlgorithms/Python/blob/master/web_programming/fetch_bbc_news.py) * [Fetch Github Info](https://github.com/TheAlgorithms/Python/blob/master/web_programming/fetch_github_info.py) * [Get Imdb Top 250 Movies Csv](https://github.com/TheAlgorithms/Python/blob/master/web_programming/get_imdb_top_250_movies_csv.py) From d547d0347bec175a1b9886d4b00674363e6ae2db Mon Sep 17 00:00:00 2001 From: Christian Clauss Date: Sat, 14 Mar 2020 07:33:14 +0100 Subject: [PATCH 0486/1071] directory_writer: actions/checkout@v1 # Use v1, NOT v2 (#1799) * directory_writer: actions/checkout@v1 # Use v1, NOT v2 (#1796 * updating DIRECTORY.md --- .github/workflows/directory_writer.yml | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/.github/workflows/directory_writer.yml b/.github/workflows/directory_writer.yml index 0b9793b5edfc..77b0e1a262e1 100644 --- a/.github/workflows/directory_writer.yml +++ b/.github/workflows/directory_writer.yml @@ -6,17 +6,18 @@ jobs: build: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v1 + - uses: actions/checkout@v1 # v1, NOT v2 - uses: actions/setup-python@v1 with: python-version: 3.x - name: Write DIRECTORY.md run: | scripts/build_directory_md.py 2>&1 | tee DIRECTORY.md - git config --global user.name "GitHub Actions" - git config --global user.email "actions@github.com" - git commit -am "updating DIRECTORY.md" || true - - name: Push DIRECTORY.md - if: github.ref == 'refs/heads/master' + git config --global user.name github-actions + git config --global user.email '${GITHUB_ACTOR}@users.noreply.github.com' + git remote set-url origin https://x-access-token:${{ secrets.GITHUB_TOKEN }}@github.com/$GITHUB_REPOSITORY + - name: Update DIRECTORY.md run: | - git push "https://${GITHUB_ACTOR}:${{secrets.GITHUB_TOKEN}}@github.com/$GITHUB_REPOSITORY.git" ${master} --force + git add DIRECTORY.md + git commit -am "updating DIRECTORY.md" || true + git push --force origin HEAD:$GITHUB_REF || true \ No newline at end of file From 1bc84e1fa0b3bbd75c73b0b1b25f573c241e1c9b Mon Sep 17 00:00:00 2001 From: cschuerc <57899042+cschuerc@users.noreply.github.com> Date: Sat, 14 Mar 2020 07:51:30 +0100 Subject: [PATCH 0487/1071] Add Monte Carlo estimation of PI (#1712) * Add Monte Carlo estimation of PI * Add type annotations for Monte Carlo estimation of PI * Compare the PI estimate to PI from the math lib * accuracy -> error * Update pi_monte_carlo_estimation.py Co-authored-by: John Law --- maths/pi_monte_carlo_estimation.py | 61 ++++++++++++++++++++++++++++++ 1 file changed, 61 insertions(+) create mode 100644 maths/pi_monte_carlo_estimation.py diff --git a/maths/pi_monte_carlo_estimation.py b/maths/pi_monte_carlo_estimation.py new file mode 100644 index 000000000000..7f341ade94a4 --- /dev/null +++ b/maths/pi_monte_carlo_estimation.py @@ -0,0 +1,61 @@ +import random + + +class Point: + def __init__(self, x: float, y: float) -> None: + self.x = x + self.y = y + + def is_in_unit_circle(self) -> bool: + """ + True, if the point lies in the unit circle + False, otherwise + """ + return (self.x ** 2 + self.y ** 2) <= 1 + + @classmethod + def random_unit_square(cls): + """ + Generates a point randomly drawn from the unit square [0, 1) x [0, 1). + """ + return cls(x = random.random(), y = random.random()) + +def estimate_pi(number_of_simulations: int) -> float: + """ + Generates an estimate of the mathematical constant PI (see https://en.wikipedia.org/wiki/Monte_Carlo_method#Overview). + + The estimate is generated by Monte Carlo simulations. Let U be uniformly drawn from the unit square [0, 1) x [0, 1). The probability that U lies in the unit circle is: + + P[U in unit circle] = 1/4 PI + + and therefore + + PI = 4 * P[U in unit circle] + + We can get an estimate of the probability P[U in unit circle] (see https://en.wikipedia.org/wiki/Empirical_probability) by: + + 1. Draw a point uniformly from the unit square. + 2. Repeat the first step n times and count the number of points in the unit circle, which is called m. + 3. An estimate of P[U in unit circle] is m/n + """ + if number_of_simulations < 1: + raise ValueError("At least one simulation is necessary to estimate PI.") + + number_in_unit_circle = 0 + for simulation_index in range(number_of_simulations): + random_point = Point.random_unit_square() + + if random_point.is_in_unit_circle(): + number_in_unit_circle += 1 + + return 4 * number_in_unit_circle / number_of_simulations + + +if __name__ == "__main__": + # import doctest + + # doctest.testmod() + from math import pi + prompt = "Please enter the desired number of Monte Carlo simulations: " + my_pi = estimate_pi(int(input(prompt).strip())) + print(f"An estimate of PI is {my_pi} with an error of {abs(my_pi - pi)}") From ab3400bfad35f91756ef88e1b53f8865afc09a75 Mon Sep 17 00:00:00 2001 From: Christian Clauss Date: Sat, 14 Mar 2020 23:55:13 +0100 Subject: [PATCH 0488/1071] Travis CI: Fix Travis linter errors (#1802) * Travis CI: Fix Travis linter errors * fixup! Format Python code with psf/black push * Update .travis.yml * updating DIRECTORY.md Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> --- .travis.yml | 2 ++ DIRECTORY.md | 1 + maths/pi_monte_carlo_estimation.py | 4 +++- 3 files changed, 6 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index aec411c52507..b9c47c179dee 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,3 +1,5 @@ +os: linux +dist: bionic language: python python: 3.8 cache: pip diff --git a/DIRECTORY.md b/DIRECTORY.md index 8dd9fa929255..121b4df17c85 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -321,6 +321,7 @@ * [Newton Raphson](https://github.com/TheAlgorithms/Python/blob/master/maths/newton_raphson.py) * [Numerical Integration](https://github.com/TheAlgorithms/Python/blob/master/maths/numerical_integration.py) * [Perfect Square](https://github.com/TheAlgorithms/Python/blob/master/maths/perfect_square.py) + * [Pi Monte Carlo Estimation](https://github.com/TheAlgorithms/Python/blob/master/maths/pi_monte_carlo_estimation.py) * [Polynomial Evaluation](https://github.com/TheAlgorithms/Python/blob/master/maths/polynomial_evaluation.py) * [Prime Check](https://github.com/TheAlgorithms/Python/blob/master/maths/prime_check.py) * [Prime Factors](https://github.com/TheAlgorithms/Python/blob/master/maths/prime_factors.py) diff --git a/maths/pi_monte_carlo_estimation.py b/maths/pi_monte_carlo_estimation.py index 7f341ade94a4..d91c034cce12 100644 --- a/maths/pi_monte_carlo_estimation.py +++ b/maths/pi_monte_carlo_estimation.py @@ -18,7 +18,8 @@ def random_unit_square(cls): """ Generates a point randomly drawn from the unit square [0, 1) x [0, 1). """ - return cls(x = random.random(), y = random.random()) + return cls(x=random.random(), y=random.random()) + def estimate_pi(number_of_simulations: int) -> float: """ @@ -56,6 +57,7 @@ def estimate_pi(number_of_simulations: int) -> float: # doctest.testmod() from math import pi + prompt = "Please enter the desired number of Monte Carlo simulations: " my_pi = estimate_pi(int(input(prompt).strip())) print(f"An estimate of PI is {my_pi} with an error of {abs(my_pi - pi)}") From 45524dd6d3b9002d1488cf562e96b9179e86591b Mon Sep 17 00:00:00 2001 From: KDH Date: Mon, 16 Mar 2020 19:19:13 +0900 Subject: [PATCH 0489/1071] Fix rehashing function will not call insert_data function (#1803) * Fix rehashing function will not call insert_data function * Fix typo * Update loop syntax instead of allocating a list Co-Authored-By: Christian Clauss Co-authored-by: Christian Clauss --- data_structures/hashing/double_hash.py | 2 +- data_structures/hashing/hash_table.py | 3 ++- data_structures/hashing/hash_table_with_linked_list.py | 4 ++-- data_structures/hashing/quadratic_probing.py | 2 +- 4 files changed, 6 insertions(+), 5 deletions(-) diff --git a/data_structures/hashing/double_hash.py b/data_structures/hashing/double_hash.py index 6c3699cc9950..ce4454db0bef 100644 --- a/data_structures/hashing/double_hash.py +++ b/data_structures/hashing/double_hash.py @@ -24,7 +24,7 @@ def __hash_function_2(self, value, data): def __hash_double_function(self, key, data, increment): return (increment * self.__hash_function_2(key, data)) % self.size_table - def _colision_resolution(self, key, data=None): + def _collision_resolution(self, key, data=None): i = 1 new_key = self.hash_function(data) diff --git a/data_structures/hashing/hash_table.py b/data_structures/hashing/hash_table.py index 988f2ba0dfbf..3b39742f9d09 100644 --- a/data_structures/hashing/hash_table.py +++ b/data_structures/hashing/hash_table.py @@ -62,7 +62,8 @@ def rehashing(self): self.size_table = next_prime(self.size_table, factor=2) self._keys.clear() self.values = [None] * self.size_table # hell's pointers D: don't DRY ;/ - map(self.insert_data, survivor_values) + for value in survivor_values: + self.insert_data(value) def insert_data(self, data): key = self.hash_function(data) diff --git a/data_structures/hashing/hash_table_with_linked_list.py b/data_structures/hashing/hash_table_with_linked_list.py index 236985b69ac6..48d93bbc5cff 100644 --- a/data_structures/hashing/hash_table_with_linked_list.py +++ b/data_structures/hashing/hash_table_with_linked_list.py @@ -18,9 +18,9 @@ def balanced_factor(self): * self.charge_factor ) - def _colision_resolution(self, key, data=None): + def _collision_resolution(self, key, data=None): if not ( len(self.values[key]) == self.charge_factor and self.values.count(None) == 0 ): return key - return super()._colision_resolution(key, data) + return super()._collision_resolution(key, data) diff --git a/data_structures/hashing/quadratic_probing.py b/data_structures/hashing/quadratic_probing.py index ac966e1cd67e..0dd84a5d987c 100644 --- a/data_structures/hashing/quadratic_probing.py +++ b/data_structures/hashing/quadratic_probing.py @@ -11,7 +11,7 @@ class QuadraticProbing(HashTable): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) - def _colision_resolution(self, key, data=None): + def _collision_resolution(self, key, data=None): i = 1 new_key = self.hash_function(key + i * i) From ac664df6a05c064670af9aa85aaf8de3fafea90a Mon Sep 17 00:00:00 2001 From: wind-Lv <61381242+wind-Lv@users.noreply.github.com> Date: Fri, 20 Mar 2020 22:24:05 +0800 Subject: [PATCH 0490/1071] 'allocation_content_length' (#1808) * 'allocation_content_length' * 'allocation_number' * Delete allocation_content_length.py * Update allocation_number.py * Update allocation_number.py * number_of_bytes and partitions * Update allocation_number.py Co-authored-by: Christian Clauss --- maths/allocation_number.py | 56 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) create mode 100644 maths/allocation_number.py diff --git a/maths/allocation_number.py b/maths/allocation_number.py new file mode 100644 index 000000000000..04a8f1dac1f4 --- /dev/null +++ b/maths/allocation_number.py @@ -0,0 +1,56 @@ +from typing import List + + +def allocation_num(number_of_bytes: int, partitions: int) -> List[str]: + """ + Divide a number of bytes into x partitions. + + In a multi-threaded download, this algorithm could be used to provide + each worker thread with a block of non-overlapping bytes to download. + For example: + for i in allocation_list: + requests.get(url,headers={'Range':f'bytes={i}'}) + + parameter + ------------ + : param number_of_bytes + : param partitions + + return + ------------ + : return: list of bytes to be assigned to each worker thread + + Examples: + ------------ + >>> allocation_num(16647, 4) + ['0-4161', '4162-8322', '8323-12483', '12484-16647'] + >>> allocation_num(888, 888) + Traceback (most recent call last): + ... + ValueError: partitions can not >= number_of_bytes! + >>> allocation_num(888, 999) + Traceback (most recent call last): + ... + ValueError: partitions can not >= number_of_bytes! + >>> allocation_num(888, -4) + Traceback (most recent call last): + ... + ValueError: partitions must be a positive number! + """ + if partitions <= 0: + raise ValueError('partitions must be a positive number!') + if partitions >= number_of_bytes: + raise ValueError('partitions can not >= number_of_bytes!') + bytes_per_partition = number_of_bytes // partitions + allocation_list = [f'0-{bytes_per_partition}'] + for i in range(1, partitions - 1): + length = f'{bytes_per_partition * i + 1}-{bytes_per_partition * (i + 1)}' + allocation_list.append(length) + allocation_list.append(f'{(bytes_per_partition * (partitions - 1)) + 1}-' + f'{number_of_bytes}') + return allocation_list + + +if __name__ == '__main__': + import doctest + doctest.testmod() From 96df906e7a99fc6bcf20da97702a9642dde1d37c Mon Sep 17 00:00:00 2001 From: Vaibhav Singh <45447817+itsvaibhav01@users.noreply.github.com> Date: Fri, 27 Mar 2020 12:46:07 +0530 Subject: [PATCH 0491/1071] All suggeted changes within additional time limit tests (#1815) * With all suggested changes :white_check_mark: possibly covered all the recommended guidelines * Updated with both slow and faster algorithms possibally covered all the recomendations * removed the time comparision part! * Update data_structures/stacks/next_greater_element.py Co-Authored-By: Christian Clauss * Update data_structures/stacks/next_greater_element.py Co-Authored-By: Christian Clauss * Update data_structures/stacks/next_greater_element.py Co-Authored-By: Christian Clauss * Update data_structures/stacks/next_greater_element.py Co-Authored-By: Christian Clauss * Add benchmark using timeit https://docs.python.org/3/library/timeit.html The performance delta between these two implementation is quite small... ``` next_greatest_element_slow(): 1.843442126 next_greatest_element(): 1.828941414 ``` * Optimize slow() to create fast() - Three algorithms in the race Three algorithms in the race * Use a bigger test array with floats, negatives, zero * Setup import next_greatest_element_fast Co-authored-by: Christian Clauss --- .../stacks/next_greater_element.py | 88 ++++++++++++++++--- 1 file changed, 75 insertions(+), 13 deletions(-) diff --git a/data_structures/stacks/next_greater_element.py b/data_structures/stacks/next_greater_element.py index 29a039b9698b..4b400334e75e 100644 --- a/data_structures/stacks/next_greater_element.py +++ b/data_structures/stacks/next_greater_element.py @@ -1,24 +1,86 @@ -def printNGE(arr): +arr = [-10, -5, 0, 5, 5.1, 11, 13, 21, 3, 4, -21, -10, -5, -1, 0] + +def next_greatest_element_slow(arr): """ - Function to print element and Next Greatest Element (NGE) pair for all elements of list - NGE - Maximum element present afterwards the current one which is also greater than current one - >>> printNGE([11,13,21,3]) - 11 -- 13 - 13 -- 21 - 21 -- -1 - 3 -- -1 + Function to get Next Greatest Element (NGE) pair for all elements of list + Maximum element present afterwards the current one which is also greater than current one + >>> next_greatest_element_slow(arr) + [-5, 0, 5, 5.1, 11, 13, 21, -1, 4, -1, -10, -5, -1, 0, -1] """ + result = [] for i in range(0, len(arr), 1): - next = -1 for j in range(i + 1, len(arr), 1): if arr[i] < arr[j]: next = arr[j] break + result.append(next) + return result + + +def next_greatest_element_fast(arr): + """ + Like next_greatest_element_slow() but changes the loops to use + enumerate() instead of range(len()) for the outer loop and + for in a slice of arr for the inner loop. + >>> next_greatest_element_fast(arr) + [-5, 0, 5, 5.1, 11, 13, 21, -1, 4, -1, -10, -5, -1, 0, -1] + """ + result = [] + for i, outer in enumerate(arr): + next = -1 + for inner in arr[i + 1:]: + if outer < inner: + next = inner + break + result.append(next) + return result + + +def next_greatest_element(arr): + """ + Function to get Next Greatest Element (NGE) pair for all elements of list + Maximum element present afterwards the current one which is also greater than current one + + Naive way to solve this is to take two loops and check for the next bigger number but that will make the + time complexity as O(n^2). The better way to solve this would be to use a stack to keep track of maximum + number givig a linear time complex solution. + + >>> next_greatest_element(arr) + [-5, 0, 5, 5.1, 11, 13, 21, -1, 4, -1, -10, -5, -1, 0, -1] + """ + stack = [] + result = [-1]*len(arr) + + for index in reversed(range(len(arr))): + if len(stack): + while stack[-1] <= arr[index]: + stack.pop() + if len(stack) == 0: + break + + if len(stack) != 0: + result[index] = stack[-1] + + stack.append(arr[index]) + + return result + - print(str(arr[i]) + " -- " + str(next)) +if __name__ == "__main__": + from doctest import testmod + from timeit import timeit + testmod() + print(next_greatest_element_slow(arr)) + print(next_greatest_element_fast(arr)) + print(next_greatest_element(arr)) -# Driver program to test above function -arr = [11, 13, 21, 3] -printNGE(arr) + setup = ("from __main__ import arr, next_greatest_element_slow, " + "next_greatest_element_fast, next_greatest_element") + print("next_greatest_element_slow():", + timeit("next_greatest_element_slow(arr)", setup=setup)) + print("next_greatest_element_fast():", + timeit("next_greatest_element_fast(arr)", setup=setup)) + print(" next_greatest_element():", + timeit("next_greatest_element(arr)", setup=setup)) From f17e9822b08a0b1d811d1a638f3de1ac26c511d8 Mon Sep 17 00:00:00 2001 From: Christian Clauss Date: Sat, 28 Mar 2020 07:24:59 +0100 Subject: [PATCH 0492/1071] psf/black changes to next_greater_element.py (#1817) * psf/black changes to next_greater_element.py * fixup! Format Python code with psf/black push Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> --- .../stacks/next_greater_element.py | 72 +++++++++++-------- maths/allocation_number.py | 16 +++-- 2 files changed, 51 insertions(+), 37 deletions(-) diff --git a/data_structures/stacks/next_greater_element.py b/data_structures/stacks/next_greater_element.py index 4b400334e75e..d8c7ed17317b 100644 --- a/data_structures/stacks/next_greater_element.py +++ b/data_structures/stacks/next_greater_element.py @@ -1,11 +1,14 @@ arr = [-10, -5, 0, 5, 5.1, 11, 13, 21, 3, 4, -21, -10, -5, -1, 0] +expect = [-5, 0, 5, 5.1, 11, 13, 21, -1, 4, -1, -10, -5, -1, 0, -1] -def next_greatest_element_slow(arr): + +def next_greatest_element_slow(arr: list) -> list: """ - Function to get Next Greatest Element (NGE) pair for all elements of list - Maximum element present afterwards the current one which is also greater than current one - >>> next_greatest_element_slow(arr) - [-5, 0, 5, 5.1, 11, 13, 21, -1, 4, -1, -10, -5, -1, 0, -1] + Get the Next Greatest Element (NGE) for all elements in a list. + Maximum element present after the current one which is also greater than the + current one. + >>> next_greatest_element_slow(arr) == expect + True """ result = [] for i in range(0, len(arr), 1): @@ -18,18 +21,18 @@ def next_greatest_element_slow(arr): return result -def next_greatest_element_fast(arr): +def next_greatest_element_fast(arr: list) -> list: """ Like next_greatest_element_slow() but changes the loops to use enumerate() instead of range(len()) for the outer loop and for in a slice of arr for the inner loop. - >>> next_greatest_element_fast(arr) - [-5, 0, 5, 5.1, 11, 13, 21, -1, 4, -1, -10, -5, -1, 0, -1] + >>> next_greatest_element_fast(arr) == expect + True """ result = [] for i, outer in enumerate(arr): next = -1 - for inner in arr[i + 1:]: + for inner in arr[i + 1 :]: if outer < inner: next = inner break @@ -37,20 +40,21 @@ def next_greatest_element_fast(arr): return result -def next_greatest_element(arr): +def next_greatest_element(arr: list) -> list: """ - Function to get Next Greatest Element (NGE) pair for all elements of list - Maximum element present afterwards the current one which is also greater than current one - - Naive way to solve this is to take two loops and check for the next bigger number but that will make the - time complexity as O(n^2). The better way to solve this would be to use a stack to keep track of maximum - number givig a linear time complex solution. - - >>> next_greatest_element(arr) - [-5, 0, 5, 5.1, 11, 13, 21, -1, 4, -1, -10, -5, -1, 0, -1] + Get the Next Greatest Element (NGE) for all elements in a list. + Maximum element present after the current one which is also greater than the + current one. + + A naive way to solve this is to take two loops and check for the next bigger + number but that will make the time complexity as O(n^2). The better way to solve + this would be to use a stack to keep track of maximum number giving a linear time + solution. + >>> next_greatest_element(arr) == expect + True """ - stack = [] - result = [-1]*len(arr) + stack = [] + result = [-1] * len(arr) for index in reversed(range(len(arr))): if len(stack): @@ -63,7 +67,7 @@ def next_greatest_element(arr): result[index] = stack[-1] stack.append(arr[index]) - + return result @@ -76,11 +80,19 @@ def next_greatest_element(arr): print(next_greatest_element_fast(arr)) print(next_greatest_element(arr)) - setup = ("from __main__ import arr, next_greatest_element_slow, " - "next_greatest_element_fast, next_greatest_element") - print("next_greatest_element_slow():", - timeit("next_greatest_element_slow(arr)", setup=setup)) - print("next_greatest_element_fast():", - timeit("next_greatest_element_fast(arr)", setup=setup)) - print(" next_greatest_element():", - timeit("next_greatest_element(arr)", setup=setup)) + setup = ( + "from __main__ import arr, next_greatest_element_slow, " + "next_greatest_element_fast, next_greatest_element" + ) + print( + "next_greatest_element_slow():", + timeit("next_greatest_element_slow(arr)", setup=setup), + ) + print( + "next_greatest_element_fast():", + timeit("next_greatest_element_fast(arr)", setup=setup), + ) + print( + " next_greatest_element():", + timeit("next_greatest_element(arr)", setup=setup), + ) diff --git a/maths/allocation_number.py b/maths/allocation_number.py index 04a8f1dac1f4..fd002b0c4361 100644 --- a/maths/allocation_number.py +++ b/maths/allocation_number.py @@ -38,19 +38,21 @@ def allocation_num(number_of_bytes: int, partitions: int) -> List[str]: ValueError: partitions must be a positive number! """ if partitions <= 0: - raise ValueError('partitions must be a positive number!') + raise ValueError("partitions must be a positive number!") if partitions >= number_of_bytes: - raise ValueError('partitions can not >= number_of_bytes!') + raise ValueError("partitions can not >= number_of_bytes!") bytes_per_partition = number_of_bytes // partitions - allocation_list = [f'0-{bytes_per_partition}'] + allocation_list = [f"0-{bytes_per_partition}"] for i in range(1, partitions - 1): - length = f'{bytes_per_partition * i + 1}-{bytes_per_partition * (i + 1)}' + length = f"{bytes_per_partition * i + 1}-{bytes_per_partition * (i + 1)}" allocation_list.append(length) - allocation_list.append(f'{(bytes_per_partition * (partitions - 1)) + 1}-' - f'{number_of_bytes}') + allocation_list.append( + f"{(bytes_per_partition * (partitions - 1)) + 1}-" f"{number_of_bytes}" + ) return allocation_list -if __name__ == '__main__': +if __name__ == "__main__": import doctest + doctest.testmod() From 9b376a5bfb17fb2244a24d6041fc24fc5d4bd422 Mon Sep 17 00:00:00 2001 From: Nolan Emirot Date: Sun, 29 Mar 2020 01:19:19 -0700 Subject: [PATCH 0493/1071] Typo in comment rabin_karp.py (#1820) * Update rabin_karp.py fix: typo * Update rabin_karp.py Co-authored-by: Christian Clauss --- strings/rabin_karp.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/strings/rabin_karp.py b/strings/rabin_karp.py index 1fb145ec97fa..22da0de80f4c 100644 --- a/strings/rabin_karp.py +++ b/strings/rabin_karp.py @@ -40,7 +40,7 @@ def rabin_karp(pattern, text): return True if i == t_len - p_len: continue - # Calculating the ruling hash + # Calculate the https://en.wikipedia.org/wiki/Rolling_hash text_hash = ( (text_hash - ord(text[i]) * modulus_power) * alphabet_size + ord(text[i + p_len]) From 20c2db0de4bb4f73db3053b13280bed8dee30cf4 Mon Sep 17 00:00:00 2001 From: farnswj1 <54967482+farnswj1@users.noreply.github.com> Date: Sat, 4 Apr 2020 01:01:37 -0400 Subject: [PATCH 0494/1071] Update reverse_words.py (#1825) The following update results in less lines of code and faster performance while preserving functionality. --- strings/reverse_words.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/strings/reverse_words.py b/strings/reverse_words.py index 123b074406c3..547dda93d8d1 100644 --- a/strings/reverse_words.py +++ b/strings/reverse_words.py @@ -1,4 +1,5 @@ # Created by sarathkaul on 18/11/19 +# Edited by farnswj1 on 4/4/20 def reverse_words(input_str: str) -> str: @@ -13,10 +14,7 @@ def reverse_words(input_str: str) -> str: input_str = input_str.split(" ") new_str = list() - for a_word in input_str: - new_str.insert(0, a_word) - - return " ".join(new_str) + return ' '.join(reversed(input_str)) if __name__ == "__main__": From 6043a44ffbbbdd185e54177fd3a4f80fd46a0be0 Mon Sep 17 00:00:00 2001 From: Prince Gangurde <50592495+Prince326@users.noreply.github.com> Date: Tue, 7 Apr 2020 04:29:32 +0530 Subject: [PATCH 0495/1071] Update basic_binary_tree.py (#1833) fixed some grammar mistakes --- data_structures/binary_tree/basic_binary_tree.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/data_structures/binary_tree/basic_binary_tree.py b/data_structures/binary_tree/basic_binary_tree.py index 4257a8e3c5b3..3ed34fc6c68e 100644 --- a/data_structures/binary_tree/basic_binary_tree.py +++ b/data_structures/binary_tree/basic_binary_tree.py @@ -1,4 +1,4 @@ -class Node: # This is the Class Node with constructor that contains data variable to type data and left,right pointers. +class Node: # This is the Class Node with a constructor that contains data variable to type data and left, right pointers. def __init__(self, data): self.data = data self.left = None @@ -37,7 +37,7 @@ def depth_of_tree( def is_full_binary_tree( tree, -): # This functions returns that is it full binary tree or not? +): # This function returns that is it full binary tree or not? if tree is None: return True if (tree.left is None) and (tree.right is None): @@ -48,7 +48,7 @@ def is_full_binary_tree( return False -def main(): # Main func for testing. +def main(): # Main function for testing. tree = Node(1) tree.left = Node(2) tree.right = Node(3) From f35484baf69dd306afb19f56c51a367129d52758 Mon Sep 17 00:00:00 2001 From: Prince Gangurde <50592495+Prince326@users.noreply.github.com> Date: Tue, 7 Apr 2020 04:30:10 +0530 Subject: [PATCH 0496/1071] Update greedy.py (#1832) --- other/greedy.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/other/greedy.py b/other/greedy.py index d1bc156304b0..4b78bf1c0415 100644 --- a/other/greedy.py +++ b/other/greedy.py @@ -1,8 +1,8 @@ class things: - def __init__(self, n, v, w): - self.name = n - self.value = v - self.weight = w + def __init__(self, name, value, weight): + self.name = name + self.value = value + self.weight = weight def __repr__(self): return f"{self.__class__.__name__}({self.name}, {self.value}, {self.weight})" From 3d129a4964b335fe7977e6376935fc916b6df3c9 Mon Sep 17 00:00:00 2001 From: Christian Clauss Date: Tue, 7 Apr 2020 11:58:23 +0200 Subject: [PATCH 0497/1071] Create Python/quantum/README.md (#1834) * Create Python/quantum/README.md Started at #1831 * fixup! Format Python code with psf/black push Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> --- quantum/README.md | 8 ++++++++ strings/reverse_words.py | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) create mode 100644 quantum/README.md diff --git a/quantum/README.md b/quantum/README.md new file mode 100644 index 000000000000..8dfc76826787 --- /dev/null +++ b/quantum/README.md @@ -0,0 +1,8 @@ +# Welcome to Quatum Algorithms + +Started at https://github.com/TheAlgorithms/Python/issues/1831 + +* D-Wave: https://www.dwavesys.com and https://github.com/dwavesystems +* Google: https://research.google/teams/applied-science/quantum +* IBM: https://qiskit.org and https://github.com/Qiskit +* Rigetti: https://rigetti.com and https://github.com/rigetti diff --git a/strings/reverse_words.py b/strings/reverse_words.py index 547dda93d8d1..6b5cc6b04039 100644 --- a/strings/reverse_words.py +++ b/strings/reverse_words.py @@ -14,7 +14,7 @@ def reverse_words(input_str: str) -> str: input_str = input_str.split(" ") new_str = list() - return ' '.join(reversed(input_str)) + return " ".join(reversed(input_str)) if __name__ == "__main__": From e5f7fbcc9ec6f02c59f8e4ac2d7b7936c60afab0 Mon Sep 17 00:00:00 2001 From: Brian Larsen Date: Tue, 7 Apr 2020 05:20:08 -0500 Subject: [PATCH 0498/1071] Change gitpod configuration for python3. (#1827) --- .gitpod.yml | 2 +- CONTRIBUTING.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.gitpod.yml b/.gitpod.yml index a5bc5751a3f6..5975b8b8e983 100644 --- a/.gitpod.yml +++ b/.gitpod.yml @@ -1,2 +1,2 @@ tasks: - - init: pip install -r ./requirements.txt + - init: pip3 install -r ./requirements.txt diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 11b956d73193..d939108a471e 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -139,7 +139,7 @@ We want your work to be readable by others; therefore, we encourage you to note #### Other Standard While Submitting Your Work -- File extension for code should be `.py`. Jupiter notebook files are acceptable in machine learning algorithms. +- File extension for code should be `.py`. Jupyter notebook files are acceptable in machine learning algorithms. - Strictly use snake_case (underscore_separated) in your file_name, as it will be easy to parse in future using scripts. - Please avoid creating new directories if at all possible. Try to fit your work into the existing directory structure. - If possible, follow the standard *within* the folder you are submitting to. From c1a57e0353cd1612860915a93be5f8f7357c6376 Mon Sep 17 00:00:00 2001 From: Joaquin Cabezas Date: Tue, 7 Apr 2020 14:08:11 +0200 Subject: [PATCH 0499/1071] Fix typo "panagram" -> "pangram" (#1836) --- strings/{check_panagram.py => check_pangram.py} | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) rename strings/{check_panagram.py => check_pangram.py} (57%) diff --git a/strings/check_panagram.py b/strings/check_pangram.py similarity index 57% rename from strings/check_panagram.py rename to strings/check_pangram.py index 6f1991da2aa9..410afd8cc609 100644 --- a/strings/check_panagram.py +++ b/strings/check_pangram.py @@ -1,16 +1,16 @@ # Created by sarathkaul on 12/11/19 -def check_panagram( +def check_pangram( input_str: str = "The quick brown fox jumps over the lazy dog", ) -> bool: """ - A Panagram String contains all the alphabets at least once. - >>> check_panagram("The quick brown fox jumps over the lazy dog") + A Pangram String contains all the alphabets at least once. + >>> check_pangram("The quick brown fox jumps over the lazy dog") True - >>> check_panagram("My name is Unknown") + >>> check_pangram("My name is Unknown") False - >>> check_panagram("The quick brown fox jumps over the la_y dog") + >>> check_pangram("The quick brown fox jumps over the la_y dog") False """ frequency = set() @@ -26,5 +26,5 @@ def check_panagram( if __name__ == "main": check_str = "INPUT STRING" - status = check_panagram(check_str) - print(f"{check_str} is {'not ' if status else ''}a panagram string") + status = check_pangram(check_str) + print(f"{check_str} is {'not ' if status else ''}a pangram string") From a38e143cf8d546bc492116ae26000656797263ae Mon Sep 17 00:00:00 2001 From: Joan Martin Miralles Date: Tue, 7 Apr 2020 18:09:05 +0200 Subject: [PATCH 0500/1071] Binary search tree using recursion (#1839) * Binary search tree using recursion * updating DIRECTORY.md Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> --- DIRECTORY.md | 4 +- .../binary_search_tree_recursive.py | 613 ++++++++++++++++++ 2 files changed, 616 insertions(+), 1 deletion(-) create mode 100644 data_structures/binary_tree/binary_search_tree_recursive.py diff --git a/DIRECTORY.md b/DIRECTORY.md index 121b4df17c85..484c5e8fc153 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -76,6 +76,7 @@ * [Avl Tree](https://github.com/TheAlgorithms/Python/blob/master/data_structures/binary_tree/avl_tree.py) * [Basic Binary Tree](https://github.com/TheAlgorithms/Python/blob/master/data_structures/binary_tree/basic_binary_tree.py) * [Binary Search Tree](https://github.com/TheAlgorithms/Python/blob/master/data_structures/binary_tree/binary_search_tree.py) + * [Binary Search Tree Recursive](https://github.com/TheAlgorithms/Python/blob/master/data_structures/binary_tree/binary_search_tree_recursive.py) * [Fenwick Tree](https://github.com/TheAlgorithms/Python/blob/master/data_structures/binary_tree/fenwick_tree.py) * [Lazy Segment Tree](https://github.com/TheAlgorithms/Python/blob/master/data_structures/binary_tree/lazy_segment_tree.py) * [Lowest Common Ancestor](https://github.com/TheAlgorithms/Python/blob/master/data_structures/binary_tree/lowest_common_ancestor.py) @@ -270,6 +271,7 @@ * [Abs](https://github.com/TheAlgorithms/Python/blob/master/maths/abs.py) * [Abs Max](https://github.com/TheAlgorithms/Python/blob/master/maths/abs_max.py) * [Abs Min](https://github.com/TheAlgorithms/Python/blob/master/maths/abs_min.py) + * [Allocation Number](https://github.com/TheAlgorithms/Python/blob/master/maths/allocation_number.py) * [Area Under Curve](https://github.com/TheAlgorithms/Python/blob/master/maths/area_under_curve.py) * [Armstrong Numbers](https://github.com/TheAlgorithms/Python/blob/master/maths/armstrong_numbers.py) * [Average Mean](https://github.com/TheAlgorithms/Python/blob/master/maths/average_mean.py) @@ -580,7 +582,7 @@ ## Strings * [Aho-Corasick](https://github.com/TheAlgorithms/Python/blob/master/strings/aho-corasick.py) * [Boyer Moore Search](https://github.com/TheAlgorithms/Python/blob/master/strings/boyer_moore_search.py) - * [Check Panagram](https://github.com/TheAlgorithms/Python/blob/master/strings/check_panagram.py) + * [Check Pangram](https://github.com/TheAlgorithms/Python/blob/master/strings/check_pangram.py) * [Knuth Morris Pratt](https://github.com/TheAlgorithms/Python/blob/master/strings/knuth_morris_pratt.py) * [Levenshtein Distance](https://github.com/TheAlgorithms/Python/blob/master/strings/levenshtein_distance.py) * [Lower](https://github.com/TheAlgorithms/Python/blob/master/strings/lower.py) diff --git a/data_structures/binary_tree/binary_search_tree_recursive.py b/data_structures/binary_tree/binary_search_tree_recursive.py new file mode 100644 index 000000000000..f1e46e33cd24 --- /dev/null +++ b/data_structures/binary_tree/binary_search_tree_recursive.py @@ -0,0 +1,613 @@ +""" +This is a python3 implementation of binary search tree using recursion + +To run tests: +python -m unittest binary_search_tree_recursive.py + +To run an example: +python binary_search_tree_recursive.py +""" +import unittest + + +class Node: + def __init__(self, label: int, parent): + self.label = label + self.parent = parent + self.left = None + self.right = None + + +class BinarySearchTree: + def __init__(self): + self.root = None + + def empty(self): + """ + Empties the tree + + >>> t = BinarySearchTree() + >>> assert t.root is None + >>> t.put(8) + >>> assert t.root is not None + """ + self.root = None + + def is_empty(self) -> bool: + """ + Checks if the tree is empty + + >>> t = BinarySearchTree() + >>> t.is_empty() + True + >>> t.put(8) + >>> t.is_empty() + False + """ + return self.root is None + + def put(self, label: int): + """ + Put a new node in the tree + + >>> t = BinarySearchTree() + >>> t.put(8) + >>> assert t.root.parent is None + >>> assert t.root.label == 8 + + >>> t.put(10) + >>> assert t.root.right.parent == t.root + >>> assert t.root.right.label == 10 + + >>> t.put(3) + >>> assert t.root.left.parent == t.root + >>> assert t.root.left.label == 3 + """ + self.root = self._put(self.root, label) + + def _put(self, node: Node, label: int, parent: Node = None) -> Node: + if node is None: + node = Node(label, parent) + else: + if label < node.label: + node.left = self._put(node.left, label, node) + elif label > node.label: + node.right = self._put(node.right, label, node) + else: + raise Exception(f"Node with label {label} already exists") + + return node + + def search(self, label: int) -> Node: + """ + Searches a node in the tree + + >>> t = BinarySearchTree() + >>> t.put(8) + >>> t.put(10) + >>> node = t.search(8) + >>> assert node.label == 8 + + >>> node = t.search(3) + Traceback (most recent call last): + ... + Exception: Node with label 3 does not exist + """ + return self._search(self.root, label) + + def _search(self, node: Node, label: int) -> Node: + if node is None: + raise Exception(f"Node with label {label} does not exist") + else: + if label < node.label: + node = self._search(node.left, label) + elif label > node.label: + node = self._search(node.right, label) + + return node + + def remove(self, label: int): + """ + Removes a node in the tree + + >>> t = BinarySearchTree() + >>> t.put(8) + >>> t.put(10) + >>> t.remove(8) + >>> assert t.root.label == 10 + + >>> t.remove(3) + Traceback (most recent call last): + ... + Exception: Node with label 3 does not exist + """ + node = self.search(label) + if not node.right and not node.left: + self._reassign_nodes(node, None) + elif not node.right and node.left: + self._reassign_nodes(node, node.left) + elif node.right and not node.left: + self._reassign_nodes(node, node.right) + else: + lowest_node = self._get_lowest_node(node.right) + lowest_node.left = node.left + lowest_node.right = node.right + node.left.parent = lowest_node + if node.right: + node.right.parent = lowest_node + self._reassign_nodes(node, lowest_node) + + def _reassign_nodes(self, node: Node, new_children: Node): + if new_children: + new_children.parent = node.parent + + if node.parent: + if node.parent.right == node: + node.parent.right = new_children + else: + node.parent.left = new_children + else: + self.root = new_children + + def _get_lowest_node(self, node: Node) -> Node: + if node.left: + lowest_node = self._get_lowest_node(node.left) + else: + lowest_node = node + self._reassign_nodes(node, node.right) + + return lowest_node + + def exists(self, label: int) -> bool: + """ + Checks if a node exists in the tree + + >>> t = BinarySearchTree() + >>> t.put(8) + >>> t.put(10) + >>> t.exists(8) + True + + >>> t.exists(3) + False + """ + try: + self.search(label) + return True + except Exception: + return False + + def get_max_label(self) -> int: + """ + Gets the max label inserted in the tree + + >>> t = BinarySearchTree() + >>> t.get_max_label() + Traceback (most recent call last): + ... + Exception: Binary search tree is empty + + >>> t.put(8) + >>> t.put(10) + >>> t.get_max_label() + 10 + """ + if self.is_empty(): + raise Exception("Binary search tree is empty") + + node = self.root + while node.right is not None: + node = node.right + + return node.label + + def get_min_label(self) -> int: + """ + Gets the min label inserted in the tree + + >>> t = BinarySearchTree() + >>> t.get_min_label() + Traceback (most recent call last): + ... + Exception: Binary search tree is empty + + >>> t.put(8) + >>> t.put(10) + >>> t.get_min_label() + 8 + """ + if self.is_empty(): + raise Exception("Binary search tree is empty") + + node = self.root + while node.left is not None: + node = node.left + + return node.label + + def inorder_traversal(self) -> list: + """ + Return the inorder traversal of the tree + + >>> t = BinarySearchTree() + >>> [i.label for i in t.inorder_traversal()] + [] + + >>> t.put(8) + >>> t.put(10) + >>> t.put(9) + >>> [i.label for i in t.inorder_traversal()] + [8, 9, 10] + """ + return self._inorder_traversal(self.root) + + def _inorder_traversal(self, node: Node) -> list: + if node is not None: + yield from self._inorder_traversal(node.left) + yield node + yield from self._inorder_traversal(node.right) + + def preorder_traversal(self) -> list: + """ + Return the preorder traversal of the tree + + >>> t = BinarySearchTree() + >>> [i.label for i in t.preorder_traversal()] + [] + + >>> t.put(8) + >>> t.put(10) + >>> t.put(9) + >>> [i.label for i in t.preorder_traversal()] + [8, 10, 9] + """ + return self._preorder_traversal(self.root) + + def _preorder_traversal(self, node: Node) -> list: + if node is not None: + yield node + yield from self._preorder_traversal(node.left) + yield from self._preorder_traversal(node.right) + + +class BinarySearchTreeTest(unittest.TestCase): + @staticmethod + def _get_binary_search_tree(): + r""" + 8 + / \ + 3 10 + / \ \ + 1 6 14 + / \ / + 4 7 13 + \ + 5 + """ + t = BinarySearchTree() + t.put(8) + t.put(3) + t.put(6) + t.put(1) + t.put(10) + t.put(14) + t.put(13) + t.put(4) + t.put(7) + t.put(5) + + return t + + def test_put(self): + t = BinarySearchTree() + assert t.is_empty() + + t.put(8) + r""" + 8 + """ + assert t.root.parent is None + assert t.root.label == 8 + + t.put(10) + r""" + 8 + \ + 10 + """ + assert t.root.right.parent == t.root + assert t.root.right.label == 10 + + t.put(3) + r""" + 8 + / \ + 3 10 + """ + assert t.root.left.parent == t.root + assert t.root.left.label == 3 + + t.put(6) + r""" + 8 + / \ + 3 10 + \ + 6 + """ + assert t.root.left.right.parent == t.root.left + assert t.root.left.right.label == 6 + + t.put(1) + r""" + 8 + / \ + 3 10 + / \ + 1 6 + """ + assert t.root.left.left.parent == t.root.left + assert t.root.left.left.label == 1 + + with self.assertRaises(Exception): + t.put(1) + + def test_search(self): + t = self._get_binary_search_tree() + + node = t.search(6) + assert node.label == 6 + + node = t.search(13) + assert node.label == 13 + + with self.assertRaises(Exception): + t.search(2) + + def test_remove(self): + t = self._get_binary_search_tree() + + t.remove(13) + r""" + 8 + / \ + 3 10 + / \ \ + 1 6 14 + / \ + 4 7 + \ + 5 + """ + assert t.root.right.right.right is None + assert t.root.right.right.left is None + + t.remove(7) + r""" + 8 + / \ + 3 10 + / \ \ + 1 6 14 + / + 4 + \ + 5 + """ + assert t.root.left.right.right is None + assert t.root.left.right.left.label == 4 + + t.remove(6) + r""" + 8 + / \ + 3 10 + / \ \ + 1 4 14 + \ + 5 + """ + assert t.root.left.left.label == 1 + assert t.root.left.right.label == 4 + assert t.root.left.right.right.label == 5 + assert t.root.left.right.left is None + assert t.root.left.left.parent == t.root.left + assert t.root.left.right.parent == t.root.left + + t.remove(3) + r""" + 8 + / \ + 4 10 + / \ \ + 1 5 14 + """ + assert t.root.left.label == 4 + assert t.root.left.right.label == 5 + assert t.root.left.left.label == 1 + assert t.root.left.parent == t.root + assert t.root.left.left.parent == t.root.left + assert t.root.left.right.parent == t.root.left + + t.remove(4) + r""" + 8 + / \ + 5 10 + / \ + 1 14 + """ + assert t.root.left.label == 5 + assert t.root.left.right is None + assert t.root.left.left.label == 1 + assert t.root.left.parent == t.root + assert t.root.left.left.parent == t.root.left + + def test_remove_2(self): + t = self._get_binary_search_tree() + + t.remove(3) + r""" + 8 + / \ + 4 10 + / \ \ + 1 6 14 + / \ / + 5 7 13 + """ + assert t.root.left.label == 4 + assert t.root.left.right.label == 6 + assert t.root.left.left.label == 1 + assert t.root.left.right.right.label == 7 + assert t.root.left.right.left.label == 5 + assert t.root.left.parent == t.root + assert t.root.left.right.parent == t.root.left + assert t.root.left.left.parent == t.root.left + assert t.root.left.right.left.parent == t.root.left.right + + def test_empty(self): + t = self._get_binary_search_tree() + t.empty() + assert t.root is None + + def test_is_empty(self): + t = self._get_binary_search_tree() + assert not t.is_empty() + + t.empty() + assert t.is_empty() + + def test_exists(self): + t = self._get_binary_search_tree() + + assert t.exists(6) + assert not t.exists(-1) + + def test_get_max_label(self): + t = self._get_binary_search_tree() + + assert t.get_max_label() == 14 + + t.empty() + with self.assertRaises(Exception): + t.get_max_label() + + def test_get_min_label(self): + t = self._get_binary_search_tree() + + assert t.get_min_label() == 1 + + t.empty() + with self.assertRaises(Exception): + t.get_min_label() + + def test_inorder_traversal(self): + t = self._get_binary_search_tree() + + inorder_traversal_nodes = [i.label for i in t.inorder_traversal()] + assert inorder_traversal_nodes == [1, 3, 4, 5, 6, 7, 8, 10, 13, 14] + + def test_preorder_traversal(self): + t = self._get_binary_search_tree() + + preorder_traversal_nodes = [i.label for i in t.preorder_traversal()] + assert preorder_traversal_nodes == [8, 3, 1, 6, 4, 5, 7, 10, 14, 13] + + +def binary_search_tree_example(): + r""" + Example + 8 + / \ + 3 10 + / \ \ + 1 6 14 + / \ / + 4 7 13 + \ + 5 + + Example After Deletion + 4 + / \ + 1 7 + \ + 5 + + """ + + t = BinarySearchTree() + t.put(8) + t.put(3) + t.put(6) + t.put(1) + t.put(10) + t.put(14) + t.put(13) + t.put(4) + t.put(7) + t.put(5) + + print( + """ + 8 + / \\ + 3 10 + / \\ \\ + 1 6 14 + / \\ / + 4 7 13 + \\ + 5 + """ + ) + + print("Label 6 exists:", t.exists(6)) + print("Label 13 exists:", t.exists(13)) + print("Label -1 exists:", t.exists(-1)) + print("Label 12 exists:", t.exists(12)) + + # Prints all the elements of the list in inorder traversal + inorder_traversal_nodes = [i.label for i in t.inorder_traversal()] + print("Inorder traversal:", inorder_traversal_nodes) + + # Prints all the elements of the list in preorder traversal + preorder_traversal_nodes = [i.label for i in t.preorder_traversal()] + print("Preorder traversal:", preorder_traversal_nodes) + + print("Max. label:", t.get_max_label()) + print("Min. label:", t.get_min_label()) + + # Delete elements + print("\nDeleting elements 13, 10, 8, 3, 6, 14") + print( + """ + 4 + / \\ + 1 7 + \\ + 5 + """ + ) + t.remove(13) + t.remove(10) + t.remove(8) + t.remove(3) + t.remove(6) + t.remove(14) + + # Prints all the elements of the list in inorder traversal after delete + inorder_traversal_nodes = [i.label for i in t.inorder_traversal()] + print("Inorder traversal after delete:", inorder_traversal_nodes) + + # Prints all the elements of the list in preorder traversal after delete + preorder_traversal_nodes = [i.label for i in t.preorder_traversal()] + print("Preorder traversal after delete:", preorder_traversal_nodes) + + print("Max. label:", t.get_max_label()) + print("Min. label:", t.get_min_label()) + + +if __name__ == "__main__": + binary_search_tree_example() From 1b65309dca96fc0a64a3acc813565fb8ec9a3967 Mon Sep 17 00:00:00 2001 From: hackercyclops12 <63036550+hackercyclops12@users.noreply.github.com> Date: Wed, 8 Apr 2020 09:56:21 +1200 Subject: [PATCH 0501/1071] Update README.md (#1842) --- quantum/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/quantum/README.md b/quantum/README.md index 8dfc76826787..be5bd0843f4f 100644 --- a/quantum/README.md +++ b/quantum/README.md @@ -1,4 +1,4 @@ -# Welcome to Quatum Algorithms +# Welcome to Quantum Algorithms Started at https://github.com/TheAlgorithms/Python/issues/1831 From 8f2c9932e09948a046f8d11b18f7c634ee4161fe Mon Sep 17 00:00:00 2001 From: Christian Clauss Date: Wed, 8 Apr 2020 12:27:11 +0200 Subject: [PATCH 0502/1071] CONTRIBUTING.md: Fix comments about the black formatter (#1841) * CONTRIBUTING.md: Fix comments about the black formatter Fixes #1840 * Update CONTRIBUTING.md * Update CONTRIBUTING.md --- CONTRIBUTING.md | 39 +++++++++++---------------------------- 1 file changed, 11 insertions(+), 28 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index d939108a471e..2aa4be9e5324 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -37,13 +37,9 @@ We want your work to be readable by others; therefore, we encourage you to note - Expand acronyms because __gcd()__ is hard to understand but __greatest_common_divisor()__ is not. - Please follow the [Python Naming Conventions](https://pep8.org/#prescriptive-naming-conventions) so variable_names and function_names should be lower_case, CONSTANTS in UPPERCASE, ClassNames should be CamelCase, etc. - - - We encourage the use of Python [f-strings](https://realpython.com/python-f-strings/#f-strings-a-new-and-improved-way-to-format-strings-in-python) where the make the code easier to read. - - -- Please consider running [__psf/black__](https://github.com/python/black) on your Python file(s) before submitting your pull request. This is not yet a requirement but it does make your code more readable and automatically aligns it with much of [PEP 8](https://www.python.org/dev/peps/pep-0008/). There are other code formatters (autopep8, yapf) but the __black__ style is now the recommendation of the Python Core Team. To use it, +- Please consider running [__psf/black__](https://github.com/python/black) on your Python file(s) before submitting your pull request. This is not yet a requirement but it does make your code more readable and automatically aligns it with much of [PEP 8](https://www.python.org/dev/peps/pep-0008/). There are other code formatters (autopep8, yapf) but the __black__ formatter is now hosted by the Python Software Foundation. To use it, ```bash pip3 install black # only required the first time @@ -57,13 +53,11 @@ We want your work to be readable by others; therefore, we encourage you to note flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics ``` - - - Original code submission require docstrings or comments to describe your work. - More on docstrings and comments: - If you are using a Wikipedia article or some other source material to create your algorithm, please add the URL in a docstring or comment to help your reader. + If you used a Wikipedia article or some other source material to create your algorithm, please add the URL in a docstring or comment to help your reader. The following are considered to be bad and may be requested to be improved: @@ -73,13 +67,12 @@ We want your work to be readable by others; therefore, we encourage you to note This is too trivial. Comments are expected to be explanatory. For comments, you can write them above, on or below a line of code, as long as you are consistent within the same piece of code. - We encourage you to put docstrings inside your functions but please pay attention to indentation of docstrings. The following is acceptable in this case: + We encourage you to put docstrings inside your functions but please pay attention to indentation of docstrings. The following is a good example: ```python - def sumab(a, b): + def sum_ab(a, b): """ - This function returns the sum of two integers a and b - Return: a + b + Return the sum of two integers a and b. """ return a + b ``` @@ -87,15 +80,14 @@ We want your work to be readable by others; therefore, we encourage you to note - Write tests (especially [__doctests__](https://docs.python.org/3/library/doctest.html)) to illustrate and verify your work. We highly encourage the use of _doctests on all functions_. ```python - def sumab(a, b): + def sum_ab(a, b): """ - This function returns the sum of two integers a and b - Return: a + b - >>> sumab(2, 2) + Returns the sum of two integers a and b + >>> sum_ab(2, 2) 4 - >>> sumab(-2, 3) + >>> sum_ab(-2, 3) 1 - >>> sumab(4.9, 5.1) + >>> sum_ab(4.9, 5.1) 10.0 """ return a + b @@ -125,15 +117,11 @@ We want your work to be readable by others; therefore, we encourage you to note ```python def sumab(a: int, b: int) --> int: - pass + pass ``` - - - [__List comprehensions and generators__](https://docs.python.org/3/tutorial/datastructures.html#list-comprehensions) are preferred over the use of `lambda`, `map`, `filter`, `reduce` but the important thing is to demonstrate the power of Python in code that is easy to read and maintain. - - - Avoid importing external libraries for basic algorithms. Only use those libraries for complicated algorithms. - If you need a third party module that is not in the file __requirements.txt__, please add it to that file as part of your submission. @@ -143,17 +131,12 @@ We want your work to be readable by others; therefore, we encourage you to note - Strictly use snake_case (underscore_separated) in your file_name, as it will be easy to parse in future using scripts. - Please avoid creating new directories if at all possible. Try to fit your work into the existing directory structure. - If possible, follow the standard *within* the folder you are submitting to. - - - - If you have modified/added code work, make sure the code compiles before submitting. - If you have modified/added documentation work, ensure your language is concise and contains no grammar errors. - Do not update the README.md or DIRECTORY.md file which will be periodically autogenerated by our Travis CI processes. - Add a corresponding explanation to [Algorithms-Explanation](https://github.com/TheAlgorithms/Algorithms-Explanation) (Optional but recommended). - All submissions will be tested with [__mypy__](http://www.mypy-lang.org) so we encourage to add [__Python type hints__](https://docs.python.org/3/library/typing.html) where it makes sense to do so. - - - Most importantly, - **Be consistent in the use of these guidelines when submitting.** - **Join** [Gitter](https://gitter.im/TheAlgorithms) **now!** From 4103c9fde4e5e6003624cff1a7f85119c082ad62 Mon Sep 17 00:00:00 2001 From: Christian Clauss Date: Fri, 10 Apr 2020 11:25:58 +0200 Subject: [PATCH 0503/1071] Update FUNDING.yml (#1829) * Update FUNDING.yml * fixup! Format Python code with psf/black push * Update FUNDING.yml * updating DIRECTORY.md Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> Co-authored-by: Anup Kumar Panwar <1anuppanwar@gmail.com> --- .github/FUNDING.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml index 514c9327e231..9a63272be441 100644 --- a/.github/FUNDING.yml +++ b/.github/FUNDING.yml @@ -1,7 +1,7 @@ # These are supported funding model platforms -github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] -patreon: # Replace with a single Patreon username +github: [cclauss, anupkumarpanwar] +patreon: cclauss open_collective: # Replace with a single Open Collective username ko_fi: # Replace with a single Ko-fi username tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel From 728deeae82ce9f1b253d3964e179e78ac249be42 Mon Sep 17 00:00:00 2001 From: Anup Kumar Panwar <1anuppanwar@gmail.com> Date: Fri, 10 Apr 2020 14:58:03 +0530 Subject: [PATCH 0504/1071] Update FUNDING.yml --- .github/FUNDING.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml index 9a63272be441..209536812f75 100644 --- a/.github/FUNDING.yml +++ b/.github/FUNDING.yml @@ -1,6 +1,6 @@ # These are supported funding model platforms -github: [cclauss, anupkumarpanwar] +github: [cclauss] patreon: cclauss open_collective: # Replace with a single Open Collective username ko_fi: # Replace with a single Ko-fi username From c775baf55fda6d50c7e4fe743d3d68b8a4dce125 Mon Sep 17 00:00:00 2001 From: Sajied Shah Yousuf <40203390+meSajied@users.noreply.github.com> Date: Sun, 12 Apr 2020 20:45:07 +0600 Subject: [PATCH 0505/1071] Added new Algorithm to find middle element of Linked List (#1822) * Added new Algorithm to find middle element of Linked List * Rename MiddleElementOfLinkedList.py to middle_element_of_linked_list.py * changed "middle_element_of_linked_list.py" algorithm for taking input * Update middle_element_of_linked_list.py * Update middle_element_of_linked_list.py * Update middle_element_of_linked_list.py * Update middle_element_of_linked_list.py * Update middle_element_of_linked_list.py * Update middle_element_of_linked_list.py * Update middle_element_of_linked_list.py * Update middle_element_of_linked_list.py * Update middle_element_of_linked_list.py * Whack the trailing whitespace Co-authored-by: Christian Clauss --- .../middle_element_of_linked_list.py | 64 +++++++++++++++++++ 1 file changed, 64 insertions(+) create mode 100644 data_structures/linked_list/middle_element_of_linked_list.py diff --git a/data_structures/linked_list/middle_element_of_linked_list.py b/data_structures/linked_list/middle_element_of_linked_list.py new file mode 100644 index 000000000000..2903fe604dfa --- /dev/null +++ b/data_structures/linked_list/middle_element_of_linked_list.py @@ -0,0 +1,64 @@ +class Node: + def __init__(self, data: int) -> int: + self.data = data + self.next = None + + +class LinkedList: + def __init__(self): + self.head = None + + def push(self, new_data:int) -> int: + new_node = Node(new_data) + new_node.next = self.head + self.head = new_node + return self.head.data + + def middle_element(self) -> int: + ''' + >>> link = LinkedList() + >>> link.middle_element() + No element found. + >>> link.push(5) + 5 + >>> link.push(6) + 6 + >>> link.push(8) + 8 + >>> link.push(8) + 8 + >>> link.push(10) + 10 + >>> link.push(12) + 12 + >>> link.push(17) + 17 + >>> link.push(7) + 7 + >>> link.push(3) + 3 + >>> link.push(20) + 20 + >>> link.push(-20) + -20 + >>> link.middle_element() + 12 + >>> + ''' + slow_pointer = self.head + fast_pointer = self.head + if self.head: + while fast_pointer and fast_pointer.next: + fast_pointer = fast_pointer.next.next + slow_pointer = slow_pointer.next + return slow_pointer.data + else: + print("No element found.") + + +if __name__ == "__main__": + link = LinkedList() + for i in range(int(input().strip())): + data = int(input().strip()) + link.push(data) + print(link.middle_element()) From 8bf380ce7d010723d96a8658adc67eae8abc786c Mon Sep 17 00:00:00 2001 From: Christian Clauss Date: Sun, 12 Apr 2020 17:18:30 +0200 Subject: [PATCH 0506/1071] README.md: sumab() --> sum_ab() for consistancy (#1855) * README.md: sumab() --> sum_ab() for consistancy consistency * fixup! Format Python code with psf/black push Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> --- CONTRIBUTING.md | 6 +++--- .../linked_list/middle_element_of_linked_list.py | 14 +++++++------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 2aa4be9e5324..39d67c240e85 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -82,7 +82,7 @@ We want your work to be readable by others; therefore, we encourage you to note ```python def sum_ab(a, b): """ - Returns the sum of two integers a and b + Return the sum of two integers a and b >>> sum_ab(2, 2) 4 >>> sum_ab(-2, 3) @@ -116,8 +116,8 @@ We want your work to be readable by others; therefore, we encourage you to note The use of [Python type hints](https://docs.python.org/3/library/typing.html) is encouraged for function parameters and return values. Our automated testing will run [mypy](http://mypy-lang.org) so run that locally before making your submission. ```python - def sumab(a: int, b: int) --> int: - pass + def sum_ab(a: int, b: int) --> int: + return a + b ``` - [__List comprehensions and generators__](https://docs.python.org/3/tutorial/datastructures.html#list-comprehensions) are preferred over the use of `lambda`, `map`, `filter`, `reduce` but the important thing is to demonstrate the power of Python in code that is easy to read and maintain. diff --git a/data_structures/linked_list/middle_element_of_linked_list.py b/data_structures/linked_list/middle_element_of_linked_list.py index 2903fe604dfa..b845d2f19c20 100644 --- a/data_structures/linked_list/middle_element_of_linked_list.py +++ b/data_structures/linked_list/middle_element_of_linked_list.py @@ -8,14 +8,14 @@ class LinkedList: def __init__(self): self.head = None - def push(self, new_data:int) -> int: - new_node = Node(new_data) - new_node.next = self.head + def push(self, new_data: int) -> int: + new_node = Node(new_data) + new_node.next = self.head self.head = new_node return self.head.data def middle_element(self) -> int: - ''' + """ >>> link = LinkedList() >>> link.middle_element() No element found. @@ -44,11 +44,11 @@ def middle_element(self) -> int: >>> link.middle_element() 12 >>> - ''' + """ slow_pointer = self.head fast_pointer = self.head - if self.head: - while fast_pointer and fast_pointer.next: + if self.head: + while fast_pointer and fast_pointer.next: fast_pointer = fast_pointer.next.next slow_pointer = slow_pointer.next return slow_pointer.data From 3735e742967b63add1ce6878c77a35b084579d08 Mon Sep 17 00:00:00 2001 From: Shrutika Bansal Date: Sun, 12 Apr 2020 23:23:28 +0530 Subject: [PATCH 0507/1071] added add algorithm (#1856) * added add algorithm * Update and rename check/add.py to math/add.py Co-authored-by: Christian Clauss --- math/add.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 math/add.py diff --git a/math/add.py b/math/add.py new file mode 100644 index 000000000000..3b8b404658c0 --- /dev/null +++ b/math/add.py @@ -0,0 +1,17 @@ +""" +Just to check +""" +def add(a, b): + """ + >>> add(2, 2) + 4 + >>> add(2, -2) + 0 + """ + return a + b + + +if __name__ == "__main__": + a = 5 + b = 6 + print(f"The sum of {a} + {b} is {sum(a, b)}") From 7ebe2b9593725dd1ca431bab98e7f4307d976768 Mon Sep 17 00:00:00 2001 From: Christian Clauss Date: Mon, 13 Apr 2020 02:10:21 +0200 Subject: [PATCH 0508/1071] Test the exception conditions (#1853) * Text exception conditions These are ValueErrors, not AttributeErrors. * fixup! Format Python code with psf/black push * Update perceptron.py * Update perceptron.py * Update perceptron.py * Revert the test Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> --- neural_network/perceptron.py | 23 +++++++++++++++++------ 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/neural_network/perceptron.py b/neural_network/perceptron.py index 2a1c46b359e6..23b409b227c4 100644 --- a/neural_network/perceptron.py +++ b/neural_network/perceptron.py @@ -19,17 +19,28 @@ def __init__(self, sample, target, learning_rate=0.01, epoch_number=1000, bias=- :param learning_rate: learning rate used in optimizing. :param epoch_number: number of epochs to train network on. :param bias: bias value for the network. + + >>> p = Perceptron([], (0, 1, 2)) + Traceback (most recent call last): + ... + ValueError: Sample data can not be empty + >>> p = Perceptron(([0], 1, 2), []) + Traceback (most recent call last): + ... + ValueError: Target data can not be empty + >>> p = Perceptron(([0], 1, 2), (0, 1)) + Traceback (most recent call last): + ... + ValueError: Sample data and Target data do not have matching lengths """ self.sample = sample if len(self.sample) == 0: - raise AttributeError("Sample data can not be empty") + raise ValueError("Sample data can not be empty") self.target = target if len(self.target) == 0: - raise AttributeError("Target data can not be empty") + raise ValueError("Target data can not be empty") if len(self.sample) != len(self.target): - raise AttributeError( - "Sample data and Target data do not have matching lengths" - ) + raise ValueError("Sample data and Target data do not have matching lengths") self.learning_rate = learning_rate self.epoch_number = epoch_number self.bias = bias @@ -98,7 +109,7 @@ def sort(self, sample) -> None: classification: P... """ if len(self.sample) == 0: - raise AttributeError("Sample data can not be empty") + raise ValueError("Sample data can not be empty") sample.insert(0, self.bias) u = 0 for i in range(self.col_sample + 1): From 7ffdef2636e8368d72f50815fe8ced98294b1053 Mon Sep 17 00:00:00 2001 From: Prince Gangurde <50592495+Prince326@users.noreply.github.com> Date: Mon, 13 Apr 2020 05:45:48 +0530 Subject: [PATCH 0509/1071] Fix some typos in random forest classifier (#1858) --- machine_learning/random_forest_classifier.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/machine_learning/random_forest_classifier.py b/machine_learning/random_forest_classifier.py index 07bd33b340c5..e7acd91346a1 100644 --- a/machine_learning/random_forest_classifier.py +++ b/machine_learning/random_forest_classifier.py @@ -10,11 +10,11 @@ def main(): """ - Random Tree Classifier Example using sklearn function. + Random Forest Classifier Example using sklearn function. Iris type dataset is used to demonstrate algorithm. """ - # Load Iris house price dataset + # Load Iris dataset iris = load_iris() # Split dataset into train and test data From 2fc3f2e4a0c258c4da8ebd16d04d97d71190dda5 Mon Sep 17 00:00:00 2001 From: markaster <61535772+markaster@users.noreply.github.com> Date: Mon, 13 Apr 2020 08:17:29 +0800 Subject: [PATCH 0510/1071] Update year in LICENSE.md (#1848) --- LICENSE.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LICENSE.md b/LICENSE.md index a20869d96300..3b7951527ab3 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2019 The Algorithms +Copyright (c) 2020 The Algorithms Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal From f6ee518ee1262d4680e468cc8f1ea8fae5e72d68 Mon Sep 17 00:00:00 2001 From: Christian Clauss Date: Mon, 13 Apr 2020 07:50:46 +0200 Subject: [PATCH 0511/1071] Rename math/add.py to maths/add.py (#1857) * Rename math/add.py to maths/add.py * fixup! Format Python code with psf/black push * Fix sum to add * updating DIRECTORY.md Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> Co-authored-by: John Law --- DIRECTORY.md | 2 ++ {math => maths}/add.py | 4 +++- 2 files changed, 5 insertions(+), 1 deletion(-) rename {math => maths}/add.py (76%) diff --git a/DIRECTORY.md b/DIRECTORY.md index 484c5e8fc153..0f7363d7e9ab 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -107,6 +107,7 @@ * [Doubly Linked List](https://github.com/TheAlgorithms/Python/blob/master/data_structures/linked_list/doubly_linked_list.py) * [From Sequence](https://github.com/TheAlgorithms/Python/blob/master/data_structures/linked_list/from_sequence.py) * [Is Palindrome](https://github.com/TheAlgorithms/Python/blob/master/data_structures/linked_list/is_palindrome.py) + * [Middle Element Of Linked List](https://github.com/TheAlgorithms/Python/blob/master/data_structures/linked_list/middle_element_of_linked_list.py) * [Print Reverse](https://github.com/TheAlgorithms/Python/blob/master/data_structures/linked_list/print_reverse.py) * [Singly Linked List](https://github.com/TheAlgorithms/Python/blob/master/data_structures/linked_list/singly_linked_list.py) * [Skip List](https://github.com/TheAlgorithms/Python/blob/master/data_structures/linked_list/skip_list.py) @@ -271,6 +272,7 @@ * [Abs](https://github.com/TheAlgorithms/Python/blob/master/maths/abs.py) * [Abs Max](https://github.com/TheAlgorithms/Python/blob/master/maths/abs_max.py) * [Abs Min](https://github.com/TheAlgorithms/Python/blob/master/maths/abs_min.py) + * [Add](https://github.com/TheAlgorithms/Python/blob/master/maths/add.py) * [Allocation Number](https://github.com/TheAlgorithms/Python/blob/master/maths/allocation_number.py) * [Area Under Curve](https://github.com/TheAlgorithms/Python/blob/master/maths/area_under_curve.py) * [Armstrong Numbers](https://github.com/TheAlgorithms/Python/blob/master/maths/armstrong_numbers.py) diff --git a/math/add.py b/maths/add.py similarity index 76% rename from math/add.py rename to maths/add.py index 3b8b404658c0..0bc7da9697d3 100644 --- a/math/add.py +++ b/maths/add.py @@ -1,6 +1,8 @@ """ Just to check """ + + def add(a, b): """ >>> add(2, 2) @@ -14,4 +16,4 @@ def add(a, b): if __name__ == "__main__": a = 5 b = 6 - print(f"The sum of {a} + {b} is {sum(a, b)}") + print(f"The sum of {a} + {b} is {add(a, b)}") From d2e8e6215e81acdc2a7f7d10bdfcbfdf6d25b666 Mon Sep 17 00:00:00 2001 From: Sanders Lin <45224617+SandersLin@users.noreply.github.com> Date: Thu, 16 Apr 2020 18:34:14 +0800 Subject: [PATCH 0512/1071] Update g_topological_sort.py (#1873) --- graphs/g_topological_sort.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/graphs/g_topological_sort.py b/graphs/g_topological_sort.py index 1a2f4fa11d88..77543d51f61d 100644 --- a/graphs/g_topological_sort.py +++ b/graphs/g_topological_sort.py @@ -9,7 +9,7 @@ 5: "socks", 6: "shirt", 7: "tie", - 8: "clock", + 8: "watch", } graph = [[1, 4], [2, 4], [3], [], [], [4], [2, 7], [3], []] @@ -21,27 +21,27 @@ def print_stack(stack, clothes): order = 1 while stack: - cur_clothe = stack.pop() - print(order, clothes[cur_clothe]) + current_clothing = stack.pop() + print(order, clothes[current_clothing]) order += 1 -def dfs(u, visited, graph): +def depth_first_search(u, visited, graph): visited[u] = 1 for v in graph[u]: if not visited[v]: - dfs(v, visited, graph) + depth_first_search(v, visited, graph) stack.append(u) -def top_sort(graph, visited): +def topological_sort(graph, visited): for v in range(len(graph)): if not visited[v]: - dfs(v, visited, graph) + depth_first_search(v, visited, graph) if __name__ == "__main__": - top_sort(graph, visited) + topological_sort(graph, visited) print(stack) print_stack(stack, clothes) From b64c4af296e4873f68e14798e94a744663a4ae23 Mon Sep 17 00:00:00 2001 From: Prince Gangurde <50592495+Prince326@users.noreply.github.com> Date: Fri, 17 Apr 2020 07:08:44 +0530 Subject: [PATCH 0513/1071] Create gaussian_naive_bayes.py (#1861) * Create Gaussian_Naive_Bayes.py Added Gaussian Naive Bayes algorithm in the module machine learning * Rename Gaussian_Naive_Bayes.py to gaussian_naive_bayes.py * requirements.txt: pip install xgboost Co-authored-by: Christian Clauss --- machine_learning/gaussian_naive_bayes.py | 45 ++++++++++++++++++++++++ requirements.txt | 1 + 2 files changed, 46 insertions(+) create mode 100644 machine_learning/gaussian_naive_bayes.py diff --git a/machine_learning/gaussian_naive_bayes.py b/machine_learning/gaussian_naive_bayes.py new file mode 100644 index 000000000000..24c884adb98a --- /dev/null +++ b/machine_learning/gaussian_naive_bayes.py @@ -0,0 +1,45 @@ +# Gaussian Naive Bayes Example + +from sklearn.naive_bayes import GaussianNB +from sklearn.metrics import plot_confusion_matrix +from sklearn.datasets import load_iris +from sklearn.model_selection import train_test_split +import matplotlib.pyplot as plt + + +def main(): + + """ + Gaussian Naive Bayes Example using sklearn function. + Iris type dataset is used to demonstrate algorithm. + """ + + # Load Iris dataset + iris = load_iris() + + # Split dataset into train and test data + X = iris["data"] # features + Y = iris["target"] + x_train, x_test, y_train, y_test = train_test_split( + X, Y, test_size=0.3, random_state=1 + ) + + # Gaussian Naive Bayes + NB_model = GaussianNB() + NB_model.fit(x_train, y_train) + + # Display Confusion Matrix + plot_confusion_matrix( + NB_model, + x_test, + y_test, + display_labels=iris["target_names"], + cmap="Blues", + normalize="true", + ) + plt.title("Normalized Confusion Matrix - IRIS Dataset") + plt.show() + + +if __name__ == "__main__": + main() diff --git a/requirements.txt b/requirements.txt index df5bcbafb2b6..9d6cc502bde1 100644 --- a/requirements.txt +++ b/requirements.txt @@ -15,3 +15,4 @@ scikit-fuzzy sklearn sympy tensorflow; python_version < '3.8' +xgboost From d48661e35d6e7334cc1aea85faf45fc1c8d35525 Mon Sep 17 00:00:00 2001 From: Christian Clauss Date: Fri, 17 Apr 2020 12:42:00 +0200 Subject: [PATCH 0514/1071] CONTRIBUTING.md: What is an Algorithm? (#1885) * CONTRIBUTING.md: What is an Algorithm? We are seeing too many _how-to examples_ for using existing Python packages so we should define what we want algorithm contributions to be. * updating DIRECTORY.md Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> --- CONTRIBUTING.md | 22 ++++++++++++++++++++++ DIRECTORY.md | 1 + 2 files changed, 23 insertions(+) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 39d67c240e85..709a1130e625 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -27,6 +27,28 @@ Your contribution will be tested by our [automated testing on Travis CI](https:/ Please help us keep our issue list small by adding fixes: #{$ISSUE_NO} to the commit message of pull requests that resolve open issues. GitHub will use this tag to auto close the issue when the PR is merged. +#### What is an Algorithm? + +An Algorithm is one or more functions (or classes) that: +* take one or more inputs, +* perform some internal calculations or data manipulations, +* return one or more outputs, +* have minimal side effects (Ex. print(), plot(), read(), write()). + +Algorithms should be packaged in a way that would make it easy for readers to put them into larger programs. + +Algorithms should: +* have intuitive class and function names that make their purpose clear to readers +* use Python naming conventions and intuitive variable names to ease comprehension +* be flexible to take different input values +* have Python type hints for their input parameters and return values +* raise Python exceptions (ValueError, etc.) on erroneous input values +* have docstrings with clear explanations and/or URLs to source materials +* contain doctests that test both valid and erroneous input values +* return all calculation results instead of printing or plotting them + +Algorithms in this repo should not be how-to examples for existing Python packages. Instead, they should perform internal calculations or manipulations to convert input values into different output values. Those calculations or manipulations can use data types, classes, or functions of existing Python packages but each algorithm in this repo should add unique value. + #### Coding Style We want your work to be readable by others; therefore, we encourage you to note the following: diff --git a/DIRECTORY.md b/DIRECTORY.md index 0f7363d7e9ab..b582a8fb80a3 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -252,6 +252,7 @@ ## Machine Learning * [Decision Tree](https://github.com/TheAlgorithms/Python/blob/master/machine_learning/decision_tree.py) + * [Gaussian Naive Bayes](https://github.com/TheAlgorithms/Python/blob/master/machine_learning/gaussian_naive_bayes.py) * [Gradient Descent](https://github.com/TheAlgorithms/Python/blob/master/machine_learning/gradient_descent.py) * [K Means Clust](https://github.com/TheAlgorithms/Python/blob/master/machine_learning/k_means_clust.py) * [K Nearest Neighbours](https://github.com/TheAlgorithms/Python/blob/master/machine_learning/k_nearest_neighbours.py) From 8c01da20d6a5ab1cbd4cb251afae244ff61d60e4 Mon Sep 17 00:00:00 2001 From: Prince Gangurde <50592495+Prince326@users.noreply.github.com> Date: Fri, 17 Apr 2020 16:13:50 +0530 Subject: [PATCH 0515/1071] Update random_forest_regressor.py (#1880) --- machine_learning/random_forest_regressor.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/machine_learning/random_forest_regressor.py b/machine_learning/random_forest_regressor.py index f6c470f0975a..f78b6bbd0f42 100644 --- a/machine_learning/random_forest_regressor.py +++ b/machine_learning/random_forest_regressor.py @@ -10,8 +10,8 @@ def main(): """ - Random Tree Regressor Example using sklearn function. - Boston house price dataset is used to demonstrate algorithm. + Random Forest Regressor Example using sklearn function. + Boston house price dataset is used to demonstrate the algorithm. """ # Load Boston house price dataset From 0feed0bfb8c791a8ce81fb202b688009f7170030 Mon Sep 17 00:00:00 2001 From: Sanders Lin <45224617+SandersLin@users.noreply.github.com> Date: Sat, 18 Apr 2020 02:03:36 +0800 Subject: [PATCH 0516/1071] Update CONTRIBUTING.md (#1886) --- CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 709a1130e625..dd4295404f78 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -138,7 +138,7 @@ We want your work to be readable by others; therefore, we encourage you to note The use of [Python type hints](https://docs.python.org/3/library/typing.html) is encouraged for function parameters and return values. Our automated testing will run [mypy](http://mypy-lang.org) so run that locally before making your submission. ```python - def sum_ab(a: int, b: int) --> int: + def sum_ab(a: int, b: int) -> int: return a + b ``` From 1c9d4a39294b8dc1852717d89313d0f62744d125 Mon Sep 17 00:00:00 2001 From: Sanders Lin <45224617+SandersLin@users.noreply.github.com> Date: Sat, 18 Apr 2020 02:04:30 +0800 Subject: [PATCH 0517/1071] Update abbreviation.py (#1887) --- dynamic_programming/abbreviation.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/dynamic_programming/abbreviation.py b/dynamic_programming/abbreviation.py index 5432c24882e4..5175aa9ed92f 100644 --- a/dynamic_programming/abbreviation.py +++ b/dynamic_programming/abbreviation.py @@ -12,7 +12,7 @@ """ -def abbr(a, b): +def abbr(a: str, b: str) -> bool: """ >>> abbr("daBcd", "ABC") True @@ -34,7 +34,6 @@ def abbr(a, b): if __name__ == "__main__": - # print(abbr("daBcd", "ABC")) # expect True import doctest doctest.testmod() From 7aaf79cc23c7bac95d4da830605341b68529dd47 Mon Sep 17 00:00:00 2001 From: Sanders Lin <45224617+SandersLin@users.noreply.github.com> Date: Sat, 18 Apr 2020 02:05:29 +0800 Subject: [PATCH 0518/1071] Initialize set with source in DFS (#1872) * Update dfs.py * Add type hints, rearrange doc-strings and comments * fixup! Format Python code with psf/black push * dfs -> depth_first_search Co-Authored-By: Christian Clauss * dfs -> depth_first_search * Add doctest for DFS * fixup! Format Python code with psf/black push * Rename dfs.py to depth_first_search_dictionary.py * updating DIRECTORY.md * Rename depth_first_search_dictionary.py to depth_first_search_dfs.py * updating DIRECTORY.md * Rename depth_first_search.py to depth_first_search_2.py * updating DIRECTORY.md * Rename depth_first_search_dfs.py to depth_first_search.py * updating DIRECTORY.md Co-authored-by: John Law Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> Co-authored-by: Christian Clauss --- DIRECTORY.md | 2 +- graphs/depth_first_search.py | 118 ++++++++++++++++----------------- graphs/depth_first_search_2.py | 65 ++++++++++++++++++ graphs/dfs.py | 44 ------------ 4 files changed, 122 insertions(+), 107 deletions(-) create mode 100644 graphs/depth_first_search_2.py delete mode 100644 graphs/dfs.py diff --git a/DIRECTORY.md b/DIRECTORY.md index b582a8fb80a3..d1d10942f246 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -212,7 +212,7 @@ * [Check Bipartite Graph Bfs](https://github.com/TheAlgorithms/Python/blob/master/graphs/check_bipartite_graph_bfs.py) * [Check Bipartite Graph Dfs](https://github.com/TheAlgorithms/Python/blob/master/graphs/check_bipartite_graph_dfs.py) * [Depth First Search](https://github.com/TheAlgorithms/Python/blob/master/graphs/depth_first_search.py) - * [Dfs](https://github.com/TheAlgorithms/Python/blob/master/graphs/dfs.py) + * [Depth First Search 2](https://github.com/TheAlgorithms/Python/blob/master/graphs/depth_first_search_2.py) * [Dijkstra](https://github.com/TheAlgorithms/Python/blob/master/graphs/dijkstra.py) * [Dijkstra 2](https://github.com/TheAlgorithms/Python/blob/master/graphs/dijkstra_2.py) * [Dijkstra Algorithm](https://github.com/TheAlgorithms/Python/blob/master/graphs/dijkstra_algorithm.py) diff --git a/graphs/depth_first_search.py b/graphs/depth_first_search.py index 0593e120b1da..1206d5ae9252 100644 --- a/graphs/depth_first_search.py +++ b/graphs/depth_first_search.py @@ -1,65 +1,59 @@ -#!/usr/bin/python - -""" Author: OMKAR PATHAK """ - - -class Graph: - def __init__(self): - self.vertex = {} - - # for printing the Graph vertices - def printGraph(self): - print(self.vertex) - for i in self.vertex.keys(): - print(i, " -> ", " -> ".join([str(j) for j in self.vertex[i]])) - - # for adding the edge between two vertices - def addEdge(self, fromVertex, toVertex): - # check if vertex is already present, - if fromVertex in self.vertex.keys(): - self.vertex[fromVertex].append(toVertex) - else: - # else make a new vertex - self.vertex[fromVertex] = [toVertex] - - def DFS(self): - # visited array for storing already visited nodes - visited = [False] * len(self.vertex) - - # call the recursive helper function - for i in range(len(self.vertex)): - if visited[i] == False: - self.DFSRec(i, visited) - - def DFSRec(self, startVertex, visited): - # mark start vertex as visited - visited[startVertex] = True - - print(startVertex, end=" ") - - # Recur for all the vertices that are adjacent to this node - for i in self.vertex.keys(): - if visited[i] == False: - self.DFSRec(i, visited) - +"""The DFS function simply calls itself recursively for every unvisited child of +its argument. We can emulate that behaviour precisely using a stack of iterators. +Instead of recursively calling with a node, we'll push an iterator to the node's +children onto the iterator stack. When the iterator at the top of the stack +terminates, we'll pop it off the stack. + +Pseudocode: + all nodes initially unexplored + mark s as explored + for every edge (s, v): + if v unexplored: + DFS(G, v) +""" + +from typing import Set, Dict + + +def depth_first_search(graph: Dict, start: str) -> Set[int]: + """Depth First Search on Graph + + :param graph: directed graph in dictionary format + :param vertex: starting vectex as a string + :returns: the trace of the search + >>> G = { "A": ["B", "C", "D"], "B": ["A", "D", "E"], + ... "C": ["A", "F"], "D": ["B", "D"], "E": ["B", "F"], + ... "F": ["C", "E", "G"], "G": ["F"] } + >>> start = "A" + >>> output_G = list({'A', 'B', 'C', 'D', 'E', 'F', 'G'}) + >>> all(x in output_G for x in list(depth_first_search(G, "A"))) + True + >>> all(x in output_G for x in list(depth_first_search(G, "G"))) + True + """ + explored, stack = set(start), [start] + while stack: + v = stack.pop() + # one difference from BFS is to pop last element here instead of first one + for w in graph[v]: + if w not in explored: + explored.add(w) + stack.append(w) + return explored + + +G = { + "A": ["B", "C", "D"], + "B": ["A", "D", "E"], + "C": ["A", "F"], + "D": ["B", "D"], + "E": ["B", "F"], + "F": ["C", "E", "G"], + "G": ["F"], +} if __name__ == "__main__": - g = Graph() - g.addEdge(0, 1) - g.addEdge(0, 2) - g.addEdge(1, 2) - g.addEdge(2, 0) - g.addEdge(2, 3) - g.addEdge(3, 3) - - g.printGraph() - print("DFS:") - g.DFS() + import doctest - # OUTPUT: - # 0  ->  1 -> 2 - # 1  ->  2 - # 2  ->  0 -> 3 - # 3  ->  3 - # DFS: - #  0 1 2 3 + doctest.testmod() + print(depth_first_search(G, "A")) diff --git a/graphs/depth_first_search_2.py b/graphs/depth_first_search_2.py new file mode 100644 index 000000000000..0593e120b1da --- /dev/null +++ b/graphs/depth_first_search_2.py @@ -0,0 +1,65 @@ +#!/usr/bin/python + +""" Author: OMKAR PATHAK """ + + +class Graph: + def __init__(self): + self.vertex = {} + + # for printing the Graph vertices + def printGraph(self): + print(self.vertex) + for i in self.vertex.keys(): + print(i, " -> ", " -> ".join([str(j) for j in self.vertex[i]])) + + # for adding the edge between two vertices + def addEdge(self, fromVertex, toVertex): + # check if vertex is already present, + if fromVertex in self.vertex.keys(): + self.vertex[fromVertex].append(toVertex) + else: + # else make a new vertex + self.vertex[fromVertex] = [toVertex] + + def DFS(self): + # visited array for storing already visited nodes + visited = [False] * len(self.vertex) + + # call the recursive helper function + for i in range(len(self.vertex)): + if visited[i] == False: + self.DFSRec(i, visited) + + def DFSRec(self, startVertex, visited): + # mark start vertex as visited + visited[startVertex] = True + + print(startVertex, end=" ") + + # Recur for all the vertices that are adjacent to this node + for i in self.vertex.keys(): + if visited[i] == False: + self.DFSRec(i, visited) + + +if __name__ == "__main__": + g = Graph() + g.addEdge(0, 1) + g.addEdge(0, 2) + g.addEdge(1, 2) + g.addEdge(2, 0) + g.addEdge(2, 3) + g.addEdge(3, 3) + + g.printGraph() + print("DFS:") + g.DFS() + + # OUTPUT: + # 0  ->  1 -> 2 + # 1  ->  2 + # 2  ->  0 -> 3 + # 3  ->  3 + # DFS: + #  0 1 2 3 diff --git a/graphs/dfs.py b/graphs/dfs.py deleted file mode 100644 index f183eae73fef..000000000000 --- a/graphs/dfs.py +++ /dev/null @@ -1,44 +0,0 @@ -"""pseudo-code""" - -""" -DFS(graph G, start vertex s): -// all nodes initially unexplored -mark s as explored -for every edge (s, v): - if v unexplored: - DFS(G, v) -""" - - -def dfs(graph, start): - """The DFS function simply calls itself recursively for every unvisited child of its argument. We can emulate that - behaviour precisely using a stack of iterators. Instead of recursively calling with a node, we'll push an iterator - to the node's children onto the iterator stack. When the iterator at the top of the stack terminates, we'll pop - it off the stack.""" - explored, stack = set(), [start] - while stack: - v = ( - stack.pop() - ) # one difference from BFS is to pop last element here instead of first one - - if v in explored: - continue - - explored.add(v) - - for w in graph[v]: - if w not in explored: - stack.append(w) - return explored - - -G = { - "A": ["B", "C"], - "B": ["A", "D", "E"], - "C": ["A", "F"], - "D": ["B"], - "E": ["B", "F"], - "F": ["C", "E"], -} - -print(dfs(G, "A")) From c92a5209563747756181f84a5f83e610646d04dc Mon Sep 17 00:00:00 2001 From: Sanders Lin <45224617+SandersLin@users.noreply.github.com> Date: Sun, 19 Apr 2020 21:56:52 +0800 Subject: [PATCH 0519/1071] Update breadth_first_search.py (#1869) --- graphs/breadth_first_search.py | 54 +++++++++++++++------------------- 1 file changed, 24 insertions(+), 30 deletions(-) diff --git a/graphs/breadth_first_search.py b/graphs/breadth_first_search.py index bfb3c4e2c376..e40ec9d1d06d 100644 --- a/graphs/breadth_first_search.py +++ b/graphs/breadth_first_search.py @@ -5,42 +5,40 @@ class Graph: def __init__(self): - self.vertex = {} + self.vertices = {} - # for printing the Graph vertices def printGraph(self): - for i in self.vertex.keys(): - print(i, " -> ", " -> ".join([str(j) for j in self.vertex[i]])) + """prints adjacency list representation of graaph""" + for i in self.vertices.keys(): + print(i, " : ", " -> ".join([str(j) for j in self.vertices[i]])) - # for adding the edge between two vertices def addEdge(self, fromVertex, toVertex): - # check if vertex is already present, - if fromVertex in self.vertex.keys(): - self.vertex[fromVertex].append(toVertex) + """adding the edge between two vertices""" + if fromVertex in self.vertices.keys(): + self.vertices[fromVertex].append(toVertex) else: - # else make a new vertex - self.vertex[fromVertex] = [toVertex] + self.vertices[fromVertex] = [toVertex] def BFS(self, startVertex): - # Take a list for stoting already visited vertices - visited = [False] * len(self.vertex) + # initialize set for storing already visited vertices + visited = set() - # create a list to store all the vertices for BFS + # create a first in first out queue to store all the vertices for BFS queue = [] # mark the source node as visited and enqueue it - visited[startVertex] = True + visited.add(startVertex) queue.append(startVertex) while queue: - startVertex = queue.pop(0) - print(startVertex, end=" ") + vertex = queue.pop(0) - # mark all adjacent nodes as visited and print them - for i in self.vertex[startVertex]: - if visited[i] == False: - queue.append(i) - visited[i] = True + # loop through all adjacent vertex and enqueue it if not yet visited + for adjacent_vertex in self.vertices[vertex]: + if adjacent_vertex not in visited: + queue.append(adjacent_vertex) + visited.add(adjacent_vertex) + return visited if __name__ == "__main__": @@ -53,13 +51,9 @@ def BFS(self, startVertex): g.addEdge(3, 3) g.printGraph() - print("BFS:") - g.BFS(2) + # 0 : 1 -> 2 + # 1 : 2 + # 2 : 0 -> 3 + # 3 : 3 - # OUTPUT: - # 0  ->  1 -> 2 - # 1  ->  2 - # 2  ->  0 -> 3 - # 3  ->  3 - # BFS: - # 2 0 3 1 + assert sorted(g.BFS(2)) == [0, 1, 2, 3] From 4b78c6952d589e745b2ca5b4808835e1b496423b Mon Sep 17 00:00:00 2001 From: Muhammad Umer Farooq Date: Sun, 19 Apr 2020 12:35:36 -0400 Subject: [PATCH 0520/1071] Create is_palindrome.py (#1754) * Create is_palindrome.py * Update is_palindrome.py * Update is_palindrome.py Co-authored-by: Christian Clauss --- strings/is_palindrome.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 strings/is_palindrome.py diff --git a/strings/is_palindrome.py b/strings/is_palindrome.py new file mode 100644 index 000000000000..3070970ca6d0 --- /dev/null +++ b/strings/is_palindrome.py @@ -0,0 +1,19 @@ +def is_palindrome(s): + """ + Determine whether the string is palindrome + :param s: + :return: Boolean + >>> is_palindrome("a man a plan a canal panama".replace(" ", "")) + True + >>> is_palindrome("Hello") + False + """ + return s == s[::-1] + + +if __name__ == "__main__": + s = input("Enter string to determine whether its palindrome or not: ").strip() + if is_palindrome(s): + print("Given string is palindrome") + else: + print("Given string is not palindrome") From 24b2aecef3a4e7f5ec7ca01427283658a35fefaf Mon Sep 17 00:00:00 2001 From: Christian Clauss Date: Mon, 20 Apr 2020 20:19:27 +0200 Subject: [PATCH 0521/1071] Create Python/bit_manipulation/README.md (#1897) * Create Python/bit_manipulation/README.md To open up a new area of algorithms... @Shrutikabansal I hope that you will contribute some of your work here. * updating DIRECTORY.md Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> --- DIRECTORY.md | 1 + bit_manipulation/README.md | 7 +++++++ 2 files changed, 8 insertions(+) create mode 100644 bit_manipulation/README.md diff --git a/DIRECTORY.md b/DIRECTORY.md index d1d10942f246..5025fde4aad5 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -586,6 +586,7 @@ * [Aho-Corasick](https://github.com/TheAlgorithms/Python/blob/master/strings/aho-corasick.py) * [Boyer Moore Search](https://github.com/TheAlgorithms/Python/blob/master/strings/boyer_moore_search.py) * [Check Pangram](https://github.com/TheAlgorithms/Python/blob/master/strings/check_pangram.py) + * [Is Palindrome](https://github.com/TheAlgorithms/Python/blob/master/strings/is_palindrome.py) * [Knuth Morris Pratt](https://github.com/TheAlgorithms/Python/blob/master/strings/knuth_morris_pratt.py) * [Levenshtein Distance](https://github.com/TheAlgorithms/Python/blob/master/strings/levenshtein_distance.py) * [Lower](https://github.com/TheAlgorithms/Python/blob/master/strings/lower.py) diff --git a/bit_manipulation/README.md b/bit_manipulation/README.md new file mode 100644 index 000000000000..2ef1661524f2 --- /dev/null +++ b/bit_manipulation/README.md @@ -0,0 +1,7 @@ +https://docs.python.org/3/reference/expressions.html#binary-bitwise-operations +https://docs.python.org/3/reference/expressions.html#unary-arithmetic-and-bitwise-operations +https://docs.python.org/3/library/stdtypes.html#bitwise-operations-on-integer-types + +https://wiki.python.org/moin/BitManipulation +https://wiki.python.org/moin/BitwiseOperators +https://www.tutorialspoint.com/python3/bitwise_operators_example.htm From 098be3594bb65d3d19cb4bd1f3394ee522303b16 Mon Sep 17 00:00:00 2001 From: Arkadip Bhattacharya Date: Tue, 21 Apr 2020 20:58:54 +0530 Subject: [PATCH 0522/1071] fix: space count in strings/word_occurrence.py (#1896) * fix: space count in strings/word_occurrence.py * Update strings/word_occurrence.py Co-Authored-By: Christian Clauss * Update strings/word_occurrence.py Co-Authored-By: Christian Clauss * Update strings/word_occurrence.py Co-Authored-By: Christian Clauss * Update word_occurrence.py Seems like, there is no need o `occurrence.pop('', None)` Co-authored-by: Christian Clauss --- strings/word_occurrence.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/strings/word_occurrence.py b/strings/word_occurrence.py index 7b8f9bee8146..ef612e12dfa4 100644 --- a/strings/word_occurrence.py +++ b/strings/word_occurrence.py @@ -1,4 +1,5 @@ # Created by sarathkaul on 17/11/19 +# Modified by Arkadip Bhattacharya(@darkmatter18) on 20/04/2020 from collections import defaultdict @@ -10,10 +11,12 @@ def word_occurence(sentence: str) -> dict: >>> all(occurence_dict[word] == count for word, count ... in Counter(SENTENCE.split()).items()) True + >>> dict(word_occurence("Two spaces")) + {'Two': 1, 'spaces': 1} """ occurrence = defaultdict(int) # Creating a dictionary containing count of each word - for word in sentence.split(" "): + for word in sentence.split(): occurrence[word] += 1 return occurrence From b560b76002006e934533d47b2ea69c8360106d1d Mon Sep 17 00:00:00 2001 From: Christian Clauss Date: Thu, 23 Apr 2020 11:57:32 +0200 Subject: [PATCH 0523/1071] Add cellular_automata directory (#1902) Related to https://github.com/TheAlgorithms/Python/issues/1613#issuecomment-618175224 @8Dion8 Your review please. Once this land, this directory would be a great place for you to add your work. --- cellular_automata/README.md | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 cellular_automata/README.md diff --git a/cellular_automata/README.md b/cellular_automata/README.md new file mode 100644 index 000000000000..c3fa0516f5dd --- /dev/null +++ b/cellular_automata/README.md @@ -0,0 +1,4 @@ +# Cellular Automata + +* https://en.wikipedia.org/wiki/Cellular_automaton +* https://mathworld.wolfram.com/ElementaryCellularAutomaton.html From 58271c5851fe3866ec73d0f9130831bb74327e87 Mon Sep 17 00:00:00 2001 From: Prince Gangurde <50592495+Prince326@users.noreply.github.com> Date: Fri, 24 Apr 2020 16:04:18 +0530 Subject: [PATCH 0524/1071] Update linear_search.py (#1906) --- searches/linear_search.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/searches/linear_search.py b/searches/linear_search.py index 76683dc6a6a8..adf22056b575 100644 --- a/searches/linear_search.py +++ b/searches/linear_search.py @@ -45,6 +45,6 @@ def linear_search(sequence, target): target = int(target_input) result = linear_search(sequence, target) if result is not None: - print(f"{target} found at positions: {result}") + print(f"{target} found at position : {result}") else: print("Not found") From e5dd2b1eb74751e6f42cfc100c22792a799beedb Mon Sep 17 00:00:00 2001 From: Sanders Lin <45224617+SandersLin@users.noreply.github.com> Date: Sun, 26 Apr 2020 05:27:01 +0800 Subject: [PATCH 0525/1071] Fix typo in Project Euler sol1.py (#1875) --- project_euler/problem_31/sol1.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project_euler/problem_31/sol1.py b/project_euler/problem_31/sol1.py index 1c59658b81ee..09b60cdae89d 100644 --- a/project_euler/problem_31/sol1.py +++ b/project_euler/problem_31/sol1.py @@ -45,7 +45,7 @@ def two_pound(x): def solution(n): - """Returns the number of different ways can £n be made using any number of + """Returns the number of different ways can n pence be made using any number of coins? >>> solution(500) From 5933cd4d833b78b515ea24aaff57274726490d58 Mon Sep 17 00:00:00 2001 From: mateuszz0000 Date: Sun, 26 Apr 2020 11:59:11 +0200 Subject: [PATCH 0526/1071] Added sepia tone (#1877) * Add sepia tone * Add unit test * technic --> technique * Update digital_image_processing/sepia.py Co-Authored-By: Christian Clauss * Update digital_image_processing/sepia.py Co-Authored-By: Christian Clauss * Fixed errors after commit changes * Fixed errors Co-authored-by: Christian Clauss --- digital_image_processing/sepia.py | 48 +++++++++++++++++++ .../test_digital_image_processing.py | 6 +++ 2 files changed, 54 insertions(+) create mode 100644 digital_image_processing/sepia.py diff --git a/digital_image_processing/sepia.py b/digital_image_processing/sepia.py new file mode 100644 index 000000000000..e91d57d0379c --- /dev/null +++ b/digital_image_processing/sepia.py @@ -0,0 +1,48 @@ +""" + Implemented an algorithm using opencv to tone an image with sepia technique +""" + +from cv2 import imread, imshow, waitKey, destroyAllWindows + + +def make_sepia(img, factor: int): + """ Function create sepia tone. Source: https://en.wikipedia.org/wiki/Sepia_(color) """ + pixel_h, pixel_v = img.shape[0], img.shape[1] + + def to_grayscale(blue, green, red): + """ + Helper function to create pixel's greyscale representation + Src: https://pl.wikipedia.org/wiki/YUV + """ + return 0.2126 * red + 0.587 * green + 0.114 * blue + + def normalize(value): + """ Helper function to normalize R/G/B value -> return 255 if value > 255""" + return min(value, 255) + + for i in range(pixel_h): + for j in range(pixel_v): + greyscale = int(to_grayscale(*img[i][j])) + img[i][j] = [ + normalize(greyscale), + normalize(greyscale + factor), + normalize(greyscale + 2 * factor), + ] + + return img + + +if __name__ == "__main__": + # read original image + images = { + percentage: imread("image_data/lena.jpg", 1) for percentage in (10, 20, 30, 40) + } + + for percentage, img in images.items(): + make_sepia(img, percentage) + + for percentage, img in images.items(): + imshow(f"Original image with sepia (factor: {percentage})", img) + + waitKey(0) + destroyAllWindows() diff --git a/digital_image_processing/test_digital_image_processing.py b/digital_image_processing/test_digital_image_processing.py index b9a7211109a8..5c6127337599 100644 --- a/digital_image_processing/test_digital_image_processing.py +++ b/digital_image_processing/test_digital_image_processing.py @@ -9,6 +9,7 @@ import digital_image_processing.filters.convolve as conv import digital_image_processing.change_contrast as cc import digital_image_processing.convert_to_negative as cn +import digital_image_processing.sepia as sp from cv2 import imread, cvtColor, COLOR_BGR2GRAY from numpy import array, uint8 from PIL import Image @@ -68,3 +69,8 @@ def test_median_filter(): def test_sobel_filter(): grad, theta = sob.sobel_filter(gray) assert grad.any() and theta.any() + + +def test_sepia(): + sepia = sp.make_sepia(img, 20) + assert sepia.all() From 0ef9dd3977c9fd66060474bd4106f6d89a74930e Mon Sep 17 00:00:00 2001 From: 8Dion8 <62215043+8Dion8@users.noreply.github.com> Date: Mon, 27 Apr 2020 19:07:31 +0300 Subject: [PATCH 0527/1071] Create one_dimensional.py (#1905) * Create one_dimensional.py * Update cellular_automata/one_dimensional.py Co-Authored-By: Christian Clauss * Update cellular_automata/one_dimensional.py Co-Authored-By: Christian Clauss * Update one_dimensional.py Moved import to the top so that the type Image gets recognized * Update one_dimensional.py * Update cellular_automata/one_dimensional.py * Update cellular_automata/one_dimensional.py * Update one_dimensional.py * Update one_dimensional.py * Update one_dimensional.py Co-authored-by: Christian Clauss --- cellular_automata/one_dimensional.py | 73 ++++++++++++++++++++++++++++ 1 file changed, 73 insertions(+) create mode 100644 cellular_automata/one_dimensional.py diff --git a/cellular_automata/one_dimensional.py b/cellular_automata/one_dimensional.py new file mode 100644 index 000000000000..98d6fa25d7f5 --- /dev/null +++ b/cellular_automata/one_dimensional.py @@ -0,0 +1,73 @@ +""" +Return an image of 16 generations of one-dimensional cellular automata based on a given +ruleset number +https://mathworld.wolfram.com/ElementaryCellularAutomaton.html +""" + +from typing import List + +from PIL import Image + +# Define the first generation of cells +# fmt: off +CELLS = [[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]] +# fmt: on + + +def format_ruleset(ruleset: int) -> List[int]: + """ + >>> format_ruleset(11100) + [0, 0, 0, 1, 1, 1, 0, 0] + >>> format_ruleset(0) + [0, 0, 0, 0, 0, 0, 0, 0] + >>> format_ruleset(11111111) + [1, 1, 1, 1, 1, 1, 1, 1] + """ + return [int(c) for c in f"{ruleset:08}"[:8]] + + +def new_generation(cells: List[List[int]], rule: List[int], time: int) -> List[int]: + population = len(cells[0]) # 31 + next_generation = [] + for i in range(population): + # Get the neighbors of each cell + left_neighbor = 0 if i == 0 else cells[time][i - 1] # special: leftmost cell + right_neighbor = 0 if i == population - 1 else cells[time][i + 1] # rightmost + # Define a new cell and add it to the new generation + situation = 7 - int(f"{left_neighbor}{cells[time][i]}{right_neighbor}", 2) + next_generation.append(rule[situation]) + return next_generation + + +def generate_image(cells: List[List[int]]) -> Image.Image: + """ + Convert the cells into a greyscale PIL.Image.Image and return it to the caller. + >>> from random import random + >>> cells = [[random() for w in range(31)] for h in range(16)] + >>> img = generate_image(cells) + >>> isinstance(img, Image.Image) + True + >>> img.width, img.height + (31, 16) + """ + # Create the output image + img = Image.new("RGB", (len(cells[0]), len(cells))) + pixels = img.load() + # Generates image + for w in range(img.width): + for h in range(img.height): + color = 255 - int(255 * cells[h][w]) + pixels[w, h] = (color, color, color) + return img + + +if __name__ == "__main__": + rule_num = bin(int(input("Rule:\n").strip()))[2:] + rule = format_ruleset(int(rule_num)) + for time in range(16): + CELLS.append(new_generation(CELLS, rule, time)) + img = generate_image(CELLS) + # Uncomment to save the image + # img.save(f"rule_{rule_num}.png") + img.show() From 8cb957f893e88f4590377e42db371cbe859bb63d Mon Sep 17 00:00:00 2001 From: Christian Clauss Date: Mon, 27 Apr 2020 18:40:46 +0200 Subject: [PATCH 0528/1071] Blacken one_dimensional.py (#1911) * Blacken one_dimensional.py * updating DIRECTORY.md * Travis CI: Upgrade to Ubuntu 20.04 LTS Focal Ubuntu 20.04 LTS (Focal Fossa) https://releases.ubuntu.com/focal Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> --- .travis.yml | 2 +- DIRECTORY.md | 4 ++++ cellular_automata/one_dimensional.py | 2 +- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index b9c47c179dee..c19ae42ec0f8 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,5 +1,5 @@ os: linux -dist: bionic +dist: focal language: python python: 3.8 cache: pip diff --git a/DIRECTORY.md b/DIRECTORY.md index 5025fde4aad5..777f5d34f6a8 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -27,6 +27,9 @@ ## Boolean Algebra * [Quine Mc Cluskey](https://github.com/TheAlgorithms/Python/blob/master/boolean_algebra/quine_mc_cluskey.py) +## Cellular Automata + * [One Dimensional](https://github.com/TheAlgorithms/Python/blob/master/cellular_automata/one_dimensional.py) + ## Ciphers * [Affine Cipher](https://github.com/TheAlgorithms/Python/blob/master/ciphers/affine_cipher.py) * [Atbash](https://github.com/TheAlgorithms/Python/blob/master/ciphers/atbash.py) @@ -148,6 +151,7 @@ * [Index Calculation](https://github.com/TheAlgorithms/Python/blob/master/digital_image_processing/index_calculation.py) * Rotation * [Rotation](https://github.com/TheAlgorithms/Python/blob/master/digital_image_processing/rotation/rotation.py) + * [Sepia](https://github.com/TheAlgorithms/Python/blob/master/digital_image_processing/sepia.py) * [Test Digital Image Processing](https://github.com/TheAlgorithms/Python/blob/master/digital_image_processing/test_digital_image_processing.py) ## Divide And Conquer diff --git a/cellular_automata/one_dimensional.py b/cellular_automata/one_dimensional.py index 98d6fa25d7f5..7819088c8cff 100644 --- a/cellular_automata/one_dimensional.py +++ b/cellular_automata/one_dimensional.py @@ -33,7 +33,7 @@ def new_generation(cells: List[List[int]], rule: List[int], time: int) -> List[i for i in range(population): # Get the neighbors of each cell left_neighbor = 0 if i == 0 else cells[time][i - 1] # special: leftmost cell - right_neighbor = 0 if i == population - 1 else cells[time][i + 1] # rightmost + right_neighbor = 0 if i == population - 1 else cells[time][i + 1] # rightmost # Define a new cell and add it to the new generation situation = 7 - int(f"{left_neighbor}{cells[time][i]}{right_neighbor}", 2) next_generation.append(rule[situation]) From fbc038d5324b82b12bb686c84b9589fdac64a236 Mon Sep 17 00:00:00 2001 From: LethargicLeprechaun <64550669+LethargicLeprechaun@users.noreply.github.com> Date: Wed, 29 Apr 2020 23:23:51 +0100 Subject: [PATCH 0529/1071] Added A1Z26 Cipher (#1914) * A1Z26 Cipher * A1Z26 Cipher * Added type hints * Added Doctests * removed tabs, spaces instead * corrected doctest * corrected doctest * info URLs added * Condensed decode to one line * Condensed encode function to a single line * Nice one! Co-authored-by: Christian Clauss --- ciphers/a1z26.py | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 ciphers/a1z26.py diff --git a/ciphers/a1z26.py b/ciphers/a1z26.py new file mode 100644 index 000000000000..3684c8cb3294 --- /dev/null +++ b/ciphers/a1z26.py @@ -0,0 +1,29 @@ +""" +Convert a string of characters to a sequence of numbers +corresponding to the character's position in the alphabet. + +https://www.dcode.fr/letter-number-cipher +http://bestcodes.weebly.com/a1z26.html +""" + +def encode(plain: str) -> list: + """ + >>> encode("myname") + [13, 25, 14, 1, 13, 5] + """ + return [ord(elem) - 96 for elem in plain] + +def decode(encoded: list) -> str: + """ + >>> decode([13, 25, 14, 1, 13, 5]) + 'myname' + """ + return "".join(chr(elem + 96) for elem in encoded) + +def main(): + encoded = encode(input("->").strip().lower()) + print("Encoded: ", encoded) + print("Decoded:", decode(encoded)) + +if __name__ == "__main__": + main() From 3d0680eddf8bc94e2e57290aef978191162832d2 Mon Sep 17 00:00:00 2001 From: mateuszz0000 Date: Thu, 30 Apr 2020 11:54:20 +0200 Subject: [PATCH 0530/1071] Added Burkes dithering algorithm. (#1916) * Added Burkes dithering algorithm * Added unit tests for burkes algorithm * Fix burkes algorithm * Added some additional information * Fixed CI tests * Update digital_image_processing/dithering/burkes.py Co-Authored-By: Christian Clauss * Update digital_image_processing/dithering/burkes.py Co-Authored-By: Christian Clauss * Update digital_image_processing/dithering/burkes.py Co-Authored-By: Christian Clauss * Propogate the += and add a doctest * Fix doctest * @staticmethod --> @ classmethod to ease testing * def test_burkes(file_path): * Fix for mypy checks * Fix variable order in get_greyscale * Fix get_greyscale method * Fix get_greyscale method * 3.753 Co-authored-by: Christian Clauss --- .../dithering/__init__.py | 0 digital_image_processing/dithering/burkes.py | 87 +++++++++++++++++++ .../test_digital_image_processing.py | 8 ++ 3 files changed, 95 insertions(+) create mode 100644 digital_image_processing/dithering/__init__.py create mode 100644 digital_image_processing/dithering/burkes.py diff --git a/digital_image_processing/dithering/__init__.py b/digital_image_processing/dithering/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/digital_image_processing/dithering/burkes.py b/digital_image_processing/dithering/burkes.py new file mode 100644 index 000000000000..54a243bd255a --- /dev/null +++ b/digital_image_processing/dithering/burkes.py @@ -0,0 +1,87 @@ +""" +Implementation Burke's algorithm (dithering) +""" +from cv2 import destroyAllWindows, imread, imshow, waitKey +import numpy as np + + +class Burkes: + """ + Burke's algorithm is using for converting grayscale image to black and white version + Source: Source: https://en.wikipedia.org/wiki/Dither + + Note: + * Best results are given with threshold= ~1/2 * max greyscale value. + * This implementation get RGB image and converts it to greyscale in runtime. + """ + + def __init__(self, input_img, threshold: int): + self.min_threshold = 0 + # max greyscale value for #FFFFFF + self.max_threshold = int(self.get_greyscale(255, 255, 255)) + + if not self.min_threshold < threshold < self.max_threshold: + raise ValueError(f"Factor value should be from 0 to {self.max_threshold}") + + self.input_img = input_img + self.threshold = threshold + self.width, self.height = self.input_img.shape[1], self.input_img.shape[0] + + # error table size (+4 columns and +1 row) greater than input image because of + # lack of if statements + self.error_table = [ + [0 for _ in range(self.height + 4)] for __ in range(self.width + 1) + ] + self.output_img = np.ones((self.width, self.height, 3), np.uint8) * 255 + + @classmethod + def get_greyscale(cls, blue: int, green: int, red: int) -> float: + """ + >>> Burkes.get_greyscale(3, 4, 5) + 3.753 + """ + return 0.114 * blue + 0.587 * green + 0.2126 * red + + def process(self) -> None: + for y in range(self.height): + for x in range(self.width): + greyscale = int(self.get_greyscale(*self.input_img[y][x])) + if self.threshold > greyscale + self.error_table[y][x]: + self.output_img[y][x] = (0, 0, 0) + current_error = greyscale + self.error_table[x][y] + else: + self.output_img[y][x] = (255, 255, 255) + current_error = greyscale + self.error_table[x][y] - 255 + """ + Burkes error propagation (`*` is current pixel): + + * 8/32 4/32 + 2/32 4/32 8/32 4/32 2/32 + """ + self.error_table[y][x + 1] += int(8 / 32 * current_error) + self.error_table[y][x + 2] += int(4 / 32 * current_error) + self.error_table[y + 1][x] += int(8 / 32 * current_error) + self.error_table[y + 1][x + 1] += int(4 / 32 * current_error) + self.error_table[y + 1][x + 2] += int(2 / 32 * current_error) + self.error_table[y + 1][x - 1] += int(4 / 32 * current_error) + self.error_table[y + 1][x - 2] += int(2 / 32 * current_error) + + +if __name__ == "__main__": + # create Burke's instances with original images in greyscale + burkes_instances = [ + Burkes(imread("image_data/lena.jpg", 1), threshold) + for threshold in (1, 126, 130, 140) + ] + + for burkes in burkes_instances: + burkes.process() + + for burkes in burkes_instances: + imshow( + f"Original image with dithering threshold: {burkes.threshold}", + burkes.output_img, + ) + + waitKey(0) + destroyAllWindows() diff --git a/digital_image_processing/test_digital_image_processing.py b/digital_image_processing/test_digital_image_processing.py index 5c6127337599..1915f17e973e 100644 --- a/digital_image_processing/test_digital_image_processing.py +++ b/digital_image_processing/test_digital_image_processing.py @@ -10,6 +10,7 @@ import digital_image_processing.change_contrast as cc import digital_image_processing.convert_to_negative as cn import digital_image_processing.sepia as sp +import digital_image_processing.dithering.burkes as bs from cv2 import imread, cvtColor, COLOR_BGR2GRAY from numpy import array, uint8 from PIL import Image @@ -17,6 +18,7 @@ img = imread(r"digital_image_processing/image_data/lena_small.jpg") gray = cvtColor(img, COLOR_BGR2GRAY) + # Test: convert_to_negative() def test_convert_to_negative(): negative_img = cn.convert_to_negative(img) @@ -74,3 +76,9 @@ def test_sobel_filter(): def test_sepia(): sepia = sp.make_sepia(img, 20) assert sepia.all() + + +def test_burkes(file_path: str="digital_image_processing/image_data/lena_small.jpg"): + burkes = bs.Burkes(imread(file_path, 1), 120) + burkes.process() + assert burkes.output_img.any() From 1ad78b2663ac6ca9cd2ce76f78305a0ea6fc3b87 Mon Sep 17 00:00:00 2001 From: Christian Clauss Date: Thu, 30 Apr 2020 22:47:11 +0200 Subject: [PATCH 0531/1071] Fix invalid escape sequence in binary_search_tree.py (#1920) * Fix invalid escape sequence in binary_search_tree.py data_structures/binary_tree/binary_search_tree.py:156 /home/travis/build/TheAlgorithms/Python/data_structures/binary_tree/binary_search_tree.py:156: DeprecationWarning: invalid escape sequence \ * fixup! Format Python code with psf/black push Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> --- ciphers/a1z26.py | 4 ++++ data_structures/binary_tree/binary_search_tree.py | 2 +- digital_image_processing/test_digital_image_processing.py | 2 +- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/ciphers/a1z26.py b/ciphers/a1z26.py index 3684c8cb3294..92710ec44b0e 100644 --- a/ciphers/a1z26.py +++ b/ciphers/a1z26.py @@ -6,6 +6,7 @@ http://bestcodes.weebly.com/a1z26.html """ + def encode(plain: str) -> list: """ >>> encode("myname") @@ -13,6 +14,7 @@ def encode(plain: str) -> list: """ return [ord(elem) - 96 for elem in plain] + def decode(encoded: list) -> str: """ >>> decode([13, 25, 14, 1, 13, 5]) @@ -20,10 +22,12 @@ def decode(encoded: list) -> str: """ return "".join(chr(elem + 96) for elem in encoded) + def main(): encoded = encode(input("->").strip().lower()) print("Encoded: ", encoded) print("Decoded:", decode(encoded)) + if __name__ == "__main__": main() diff --git a/data_structures/binary_tree/binary_search_tree.py b/data_structures/binary_tree/binary_search_tree.py index 40546875216b..3868130357fb 100644 --- a/data_structures/binary_tree/binary_search_tree.py +++ b/data_structures/binary_tree/binary_search_tree.py @@ -153,7 +153,7 @@ def postorder(curr_node): def binary_search_tree(): - """ + r""" Example 8 / \ diff --git a/digital_image_processing/test_digital_image_processing.py b/digital_image_processing/test_digital_image_processing.py index 1915f17e973e..89cf6007af60 100644 --- a/digital_image_processing/test_digital_image_processing.py +++ b/digital_image_processing/test_digital_image_processing.py @@ -78,7 +78,7 @@ def test_sepia(): assert sepia.all() -def test_burkes(file_path: str="digital_image_processing/image_data/lena_small.jpg"): +def test_burkes(file_path: str = "digital_image_processing/image_data/lena_small.jpg"): burkes = bs.Burkes(imread(file_path, 1), 120) burkes.process() assert burkes.output_img.any() From b01e5b78a3db06f6bdd8c2bb8e20d54c3630ce91 Mon Sep 17 00:00:00 2001 From: Saba Pochkhua Date: Fri, 1 May 2020 01:23:52 +0400 Subject: [PATCH 0532/1071] Graph coloring (#1921) * add skeleton code * add doctests * add mainc function pseudo code and tests (ToDo: write Implementation) * typo fixes * implement algorithm * add type checking * add wikipedia link * typo fix * update range syntax Co-authored-by: Christian Clauss * change indexed iteration checking to any() Co-authored-by: Christian Clauss * fix: swap import and documentation sections * fix: change return none to return empty list * remove unnecessary import (Union) * change: remove returning boolean indicating problem was solved or not * remove unnecessary import (Tuple) Co-authored-by: Christian Clauss --- backtracking/coloring.py | 114 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 114 insertions(+) create mode 100644 backtracking/coloring.py diff --git a/backtracking/coloring.py b/backtracking/coloring.py new file mode 100644 index 000000000000..77beb5fc1956 --- /dev/null +++ b/backtracking/coloring.py @@ -0,0 +1,114 @@ +""" + Graph Coloring also called "m coloring problem" + consists of coloring given graph with at most m colors + such that no adjacent vertices are assigned same color + + Wikipedia: https://en.wikipedia.org/wiki/Graph_coloring +""" +from typing import List + + +def valid_coloring( + neighbours: List[int], colored_vertices: List[int], color: int +) -> bool: + """ + For each neighbour check if coloring constraint is satisfied + If any of the neighbours fail the constraint return False + If all neighbours validate constraint return True + + >>> neighbours = [0,1,0,1,0] + >>> colored_vertices = [0, 2, 1, 2, 0] + + >>> color = 1 + >>> valid_coloring(neighbours, colored_vertices, color) + True + + >>> color = 2 + >>> valid_coloring(neighbours, colored_vertices, color) + False + """ + # Does any neighbour not satisfy the constraints + return not any( + neighbour == 1 and colored_vertices[i] == color + for i, neighbour in enumerate(neighbours) + ) + + +def util_color( + graph: List[List[int]], max_colors: int, colored_vertices: List[int], index: int +) -> bool: + """ + Pseudo-Code + + Base Case: + 1. Check if coloring is complete + 1.1 If complete return True (meaning that we successfully colored graph) + + Recursive Step: + 2. Itterates over each color: + Check if current coloring is valid: + 2.1. Color given vertex + 2.2. Do recursive call check if this coloring leads to solving problem + 2.4. if current coloring leads to solution return + 2.5. Uncolor given vertex + + >>> graph = [[0, 1, 0, 0, 0], + ... [1, 0, 1, 0, 1], + ... [0, 1, 0, 1, 0], + ... [0, 1, 1, 0, 0], + ... [0, 1, 0, 0, 0]] + >>> max_colors = 3 + >>> colored_vertices = [0, 1, 0, 0, 0] + >>> index = 3 + + >>> util_color(graph, max_colors, colored_vertices, index) + True + + >>> max_colors = 2 + >>> util_color(graph, max_colors, colored_vertices, index) + False + """ + + # Base Case + if index == len(graph): + return True + + # Recursive Step + for i in range(max_colors): + if valid_coloring(graph[index], colored_vertices, i): + # Color current vertex + colored_vertices[index] = i + # Validate coloring + if util_color(graph, max_colors, colored_vertices, index + 1): + return True + # Backtrack + colored_vertices[index] = -1 + return False + + +def color(graph: List[List[int]], max_colors: int) -> List[int]: + """ + Wrapper function to call subroutine called util_color + which will either return True or False. + If True is returned colored_vertices list is filled with correct colorings + + >>> graph = [[0, 1, 0, 0, 0], + ... [1, 0, 1, 0, 1], + ... [0, 1, 0, 1, 0], + ... [0, 1, 1, 0, 0], + ... [0, 1, 0, 0, 0]] + + >>> max_colors = 3 + >>> color(graph, max_colors) + [0, 1, 0, 2, 0] + + >>> max_colors = 2 + >>> color(graph, max_colors) + [] + """ + colored_vertices = [-1] * len(graph) + + if util_color(graph, max_colors, colored_vertices, 0): + return colored_vertices + + return [] From 308505f18f71b793a2a2547717d3586ace6d99af Mon Sep 17 00:00:00 2001 From: Sanders Lin <45224617+SandersLin@users.noreply.github.com> Date: Fri, 1 May 2020 13:24:32 +0800 Subject: [PATCH 0533/1071] Add shortest path by BFS (#1870) * Create breadth_first_search_shortest_path.py * updating DIRECTORY.md * Reduce side effect of `shortest_path` For the sake of future testing and documentation - * fixup! Format Python code with psf/black push * Fix typo `separately` * Change to get() from dictionary Co-Authored-By: Christian Clauss * Move graph to the top * fixup! Format Python code with psf/black push * Add doctest for shortest path * Add doctest for BFS * fixup! Format Python code with psf/black push * Add typings for breadth_first_search_shortest_path * fixup! Format Python code with psf/black push * Remove assert from doctests * Add blank line to doctest Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> Co-authored-by: John Law Co-authored-by: Christian Clauss Co-authored-by: John Law --- DIRECTORY.md | 1 + graphs/breadth_first_search_shortest_path.py | 81 ++++++++++++++++++++ 2 files changed, 82 insertions(+) create mode 100644 graphs/breadth_first_search_shortest_path.py diff --git a/DIRECTORY.md b/DIRECTORY.md index 777f5d34f6a8..004545c9c632 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -213,6 +213,7 @@ * [Bfs](https://github.com/TheAlgorithms/Python/blob/master/graphs/bfs.py) * [Bfs Shortest Path](https://github.com/TheAlgorithms/Python/blob/master/graphs/bfs_shortest_path.py) * [Breadth First Search](https://github.com/TheAlgorithms/Python/blob/master/graphs/breadth_first_search.py) + * [Breadth First Search Shortest Path](https://github.com/TheAlgorithms/Python/blob/master/graphs/breadth_first_search_shortest_path.py) * [Check Bipartite Graph Bfs](https://github.com/TheAlgorithms/Python/blob/master/graphs/check_bipartite_graph_bfs.py) * [Check Bipartite Graph Dfs](https://github.com/TheAlgorithms/Python/blob/master/graphs/check_bipartite_graph_dfs.py) * [Depth First Search](https://github.com/TheAlgorithms/Python/blob/master/graphs/depth_first_search.py) diff --git a/graphs/breadth_first_search_shortest_path.py b/graphs/breadth_first_search_shortest_path.py new file mode 100644 index 000000000000..514aed6d7211 --- /dev/null +++ b/graphs/breadth_first_search_shortest_path.py @@ -0,0 +1,81 @@ +"""Breath First Search (BFS) can be used when finding the shortest path +from a given source node to a target node in an unweighted graph. +""" +graph = { + "A": ["B", "C", "E"], + "B": ["A", "D", "E"], + "C": ["A", "F", "G"], + "D": ["B"], + "E": ["A", "B", "D"], + "F": ["C"], + "G": ["C"], +} + +from typing import Dict + + +class Graph: + def __init__(self, graph: Dict[str, str], source_vertex: str) -> None: + """Graph is implemented as dictionary of adjancency lists. Also, + Source vertex have to be defined upon initialization. + """ + self.graph = graph + # mapping node to its parent in resulting breadth first tree + self.parent = {} + self.source_vertex = source_vertex + + def breath_first_search(self) -> None: + """This function is a helper for running breath first search on this graph. + >>> g = Graph(graph, "G") + >>> g.breath_first_search() + >>> g.parent + {'G': None, 'C': 'G', 'A': 'C', 'F': 'C', 'B': 'A', 'E': 'A', 'D': 'B'} + """ + visited = {self.source_vertex} + self.parent[self.source_vertex] = None + queue = [self.source_vertex] # first in first out queue + + while queue: + vertex = queue.pop(0) + for adjancent_vertex in self.graph[vertex]: + if adjancent_vertex not in visited: + visited.add(adjancent_vertex) + self.parent[adjancent_vertex] = vertex + queue.append(adjancent_vertex) + + def shortest_path(self, target_vertex: str) -> str: + """This shortest path function returns a string, describing the result: + 1.) No path is found. The string is a human readable message to indicate this. + 2.) The shortest path is found. The string is in the form `v1(->v2->v3->...->vn)`, + where v1 is the source vertex and vn is the target vertex, if it exists separately. + + >>> g = Graph(graph, "G") + >>> g.breath_first_search() + + Case 1 - No path is found. + >>> g.shortest_path("Foo") + 'No path from vertex:G to vertex:Foo' + + Case 2 - The path is found. + >>> g.shortest_path("D") + 'G->C->A->B->D' + >>> g.shortest_path("G") + 'G' + """ + if target_vertex == self.source_vertex: + return f"{self.source_vertex}" + elif not self.parent.get(target_vertex): + return f"No path from vertex:{self.source_vertex} to vertex:{target_vertex}" + else: + return self.shortest_path(self.parent[target_vertex]) + f"->{target_vertex}" + + +if __name__ == "__main__": + import doctest + + doctest.testmod() + g = Graph(graph, "G") + g.breath_first_search() + print(g.shortest_path("D")) + print(g.shortest_path("G")) + print(g.shortest_path("Foo")) From bcaa88b26c8012cb5f8cba75d10ccb44b801c72c Mon Sep 17 00:00:00 2001 From: QuantumNovice <43876848+QuantumNovice@users.noreply.github.com> Date: Fri, 1 May 2020 20:42:41 +0500 Subject: [PATCH 0534/1071] Changed the deprecated `np.matrix` to `np.ndarray` (#1923) --- linear_algebra/src/rayleigh_quotient.py | 27 ++++++++++++------------- 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/linear_algebra/src/rayleigh_quotient.py b/linear_algebra/src/rayleigh_quotient.py index d0d5d6396d28..69bbbac119e8 100644 --- a/linear_algebra/src/rayleigh_quotient.py +++ b/linear_algebra/src/rayleigh_quotient.py @@ -4,56 +4,55 @@ import numpy as np -def is_hermitian(matrix: np.matrix) -> bool: +def is_hermitian(matrix: np.array) -> bool: """ Checks if a matrix is Hermitian. - >>> import numpy as np - >>> A = np.matrix([ + >>> A = np.array([ ... [2, 2+1j, 4], ... [2-1j, 3, 1j], ... [4, -1j, 1]]) >>> is_hermitian(A) True - >>> A = np.matrix([ + >>> A = np.array([ ... [2, 2+1j, 4+1j], ... [2-1j, 3, 1j], ... [4, -1j, 1]]) >>> is_hermitian(A) False """ - return np.array_equal(matrix, matrix.H) + return np.array_equal(matrix, matrix.conjugate().T) -def rayleigh_quotient(A: np.matrix, v: np.matrix) -> float: +def rayleigh_quotient(A: np.array, v: np.array) -> float: """ Returns the Rayleigh quotient of a Hermitian matrix A and vector v. >>> import numpy as np - >>> A = np.matrix([ + >>> A = np.array([ ... [1, 2, 4], ... [2, 3, -1], ... [4, -1, 1] ... ]) - >>> v = np.matrix([ + >>> v = np.array([ ... [1], ... [2], ... [3] ... ]) >>> rayleigh_quotient(A, v) - matrix([[3.]]) + array([[3.]]) """ - v_star = v.H - return (v_star * A * v) / (v_star * v) + v_star = v.conjugate().T + return (v_star.dot(A).dot(v)) / (v_star.dot(v)) def tests() -> None: - A = np.matrix([[2, 2 + 1j, 4], [2 - 1j, 3, 1j], [4, -1j, 1]]) - v = np.matrix([[1], [2], [3]]) + A = np.array([[2, 2 + 1j, 4], [2 - 1j, 3, 1j], [4, -1j, 1]]) + v = np.array([[1], [2], [3]]) assert is_hermitian(A), f"{A} is not hermitian." print(rayleigh_quotient(A, v)) - A = np.matrix([[1, 2, 4], [2, 3, -1], [4, -1, 1]]) + A = np.array([[1, 2, 4], [2, 3, -1], [4, -1, 1]]) assert is_hermitian(A), f"{A} is not hermitian." assert rayleigh_quotient(A, v) == float(3) From 6acd7fb5ce89b99e6e4ecff09723ae775b520826 Mon Sep 17 00:00:00 2001 From: Christian Clauss Date: Fri, 1 May 2020 23:36:35 +0200 Subject: [PATCH 0535/1071] Wrap lines that go beyond GitHub Editor (#1925) * Wrap lines that go beyond GiHub Editor * flake8 --count --select=E501 --max-line-length=127 * updating DIRECTORY.md * Update strassen_matrix_multiplication.py * fixup! Format Python code with psf/black push * Update decision_tree.py Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> --- .travis.yml | 2 +- DIRECTORY.md | 4 +++ ciphers/rsa_cipher.py | 13 +++++++-- ciphers/rsa_key_generator.py | 11 +++++-- .../linked_list/doubly_linked_list.py | 15 ++++++---- .../strassen_matrix_multiplication.py | 23 ++++++++++----- dynamic_programming/bitmask.py | 17 ++++++----- dynamic_programming/edit_distance.py | 6 ++-- machine_learning/decision_tree.py | 29 ++++++++++++------- machine_learning/logistic_regression.py | 13 ++++++--- machine_learning/support_vector_machines.py | 12 +++++--- maths/pi_monte_carlo_estimation.py | 12 +++++--- maths/zellers_congruence.py | 5 +++- matrix/matrix_class.py | 29 ++++++++++--------- other/password_generator.py | 3 +- other/sierpinski_triangle.py | 16 ++++++---- project_euler/problem_99/sol1.py | 9 ++++-- searches/binary_search.py | 21 +++++++++----- web_programming/slack_message.py | 3 +- 19 files changed, 161 insertions(+), 82 deletions(-) diff --git a/.travis.yml b/.travis.yml index c19ae42ec0f8..10b91b2561ac 100644 --- a/.travis.yml +++ b/.travis.yml @@ -7,7 +7,7 @@ before_install: pip install --upgrade pip setuptools six install: pip install -r requirements.txt before_script: - black --check . || true - - flake8 . --count --select=E101,E722,E9,F4,F63,F7,F82,W191 --show-source --statistics + - flake8 . --count --select=E101,E501,E722,E9,F4,F63,F7,F82,W191 --max-line-length=127 --show-source --statistics - flake8 . --count --exit-zero --max-line-length=127 --statistics script: - scripts/validate_filenames.py # no uppercase, no spaces, in a directory diff --git a/DIRECTORY.md b/DIRECTORY.md index 004545c9c632..631b3a88a12c 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -14,6 +14,7 @@ * [All Combinations](https://github.com/TheAlgorithms/Python/blob/master/backtracking/all_combinations.py) * [All Permutations](https://github.com/TheAlgorithms/Python/blob/master/backtracking/all_permutations.py) * [All Subsequences](https://github.com/TheAlgorithms/Python/blob/master/backtracking/all_subsequences.py) + * [Coloring](https://github.com/TheAlgorithms/Python/blob/master/backtracking/coloring.py) * [Minimax](https://github.com/TheAlgorithms/Python/blob/master/backtracking/minimax.py) * [N Queens](https://github.com/TheAlgorithms/Python/blob/master/backtracking/n_queens.py) * [Sudoku](https://github.com/TheAlgorithms/Python/blob/master/backtracking/sudoku.py) @@ -31,6 +32,7 @@ * [One Dimensional](https://github.com/TheAlgorithms/Python/blob/master/cellular_automata/one_dimensional.py) ## Ciphers + * [A1Z26](https://github.com/TheAlgorithms/Python/blob/master/ciphers/a1z26.py) * [Affine Cipher](https://github.com/TheAlgorithms/Python/blob/master/ciphers/affine_cipher.py) * [Atbash](https://github.com/TheAlgorithms/Python/blob/master/ciphers/atbash.py) * [Base16](https://github.com/TheAlgorithms/Python/blob/master/ciphers/base16.py) @@ -138,6 +140,8 @@ ## Digital Image Processing * [Change Contrast](https://github.com/TheAlgorithms/Python/blob/master/digital_image_processing/change_contrast.py) * [Convert To Negative](https://github.com/TheAlgorithms/Python/blob/master/digital_image_processing/convert_to_negative.py) + * Dithering + * [Burkes](https://github.com/TheAlgorithms/Python/blob/master/digital_image_processing/dithering/burkes.py) * Edge Detection * [Canny](https://github.com/TheAlgorithms/Python/blob/master/digital_image_processing/edge_detection/canny.py) * Filters diff --git a/ciphers/rsa_cipher.py b/ciphers/rsa_cipher.py index 3b9f5c143c85..371966b8379b 100644 --- a/ciphers/rsa_cipher.py +++ b/ciphers/rsa_cipher.py @@ -1,4 +1,7 @@ -import sys, rsa_key_generator as rkg, os +import os +import sys + +import rsa_key_generator as rkg DEFAULT_BLOCK_SIZE = 128 BYTE_SIZE = 256 @@ -92,7 +95,9 @@ def encryptAndWriteToFile( keySize, n, e = readKeyFile(keyFilename) if keySize < blockSize * 8: sys.exit( - "ERROR: Block size is %s bits and key size is %s bits. The RSA cipher requires the block size to be equal to or greater than the key size. Either decrease the block size or use different keys." + "ERROR: Block size is %s bits and key size is %s bits. The RSA cipher " + "requires the block size to be equal to or greater than the key size. " + "Either decrease the block size or use different keys." % (blockSize * 8, keySize) ) @@ -117,7 +122,9 @@ def readFromFileAndDecrypt(messageFilename, keyFilename): if keySize < blockSize * 8: sys.exit( - "ERROR: Block size is %s bits and key size is %s bits. The RSA cipher requires the block size to be equal to or greater than the key size. Did you specify the correct key file and encrypted file?" + "ERROR: Block size is %s bits and key size is %s bits. The RSA cipher " + "requires the block size to be equal to or greater than the key size. " + "Did you specify the correct key file and encrypted file?" % (blockSize * 8, keySize) ) diff --git a/ciphers/rsa_key_generator.py b/ciphers/rsa_key_generator.py index 0df31f0413c6..5514c69917bf 100644 --- a/ciphers/rsa_key_generator.py +++ b/ciphers/rsa_key_generator.py @@ -1,5 +1,9 @@ -import random, sys, os -import rabin_miller as rabinMiller, cryptomath_module as cryptoMath +import os +import random +import sys + +import cryptomath_module as cryptoMath +import rabin_miller as rabinMiller def main(): @@ -35,7 +39,8 @@ def makeKeyFiles(name, keySize): ): print("\nWARNING:") print( - '"%s_pubkey.txt" or "%s_privkey.txt" already exists. \nUse a different name or delete these files and re-run this program.' + '"%s_pubkey.txt" or "%s_privkey.txt" already exists. \n' + "Use a different name or delete these files and re-run this program." % (name, name) ) sys.exit() diff --git a/data_structures/linked_list/doubly_linked_list.py b/data_structures/linked_list/doubly_linked_list.py index f8f652be6d32..e449ed6ec8ac 100644 --- a/data_structures/linked_list/doubly_linked_list.py +++ b/data_structures/linked_list/doubly_linked_list.py @@ -1,9 +1,12 @@ """ -- A linked list is similar to an array, it holds values. However, links in a linked list do not have indexes. +- A linked list is similar to an array, it holds values. However, links in a linked + list do not have indexes. - This is an example of a double ended, doubly linked list. - Each link references the next link and the previous one. -- A Doubly Linked List (DLL) contains an extra pointer, typically called previous pointer, together with next pointer and data which are there in singly linked list. - - Advantages over SLL - IT can be traversed in both forward and backward direction.,Delete operation is more efficient""" +- A Doubly Linked List (DLL) contains an extra pointer, typically called previous + pointer, together with next pointer and data which are there in singly linked list. + - Advantages over SLL - IT can be traversed in both forward and backward direction., + Delete operation is more efficient""" class LinkedList: # making main class named linked list @@ -13,7 +16,7 @@ def __init__(self): def insertHead(self, x): newLink = Link(x) # Create a new link with a value attached to it - if self.isEmpty() == True: # Set the first element added to be the tail + if self.isEmpty(): # Set the first element added to be the tail self.tail = newLink else: self.head.previous = newLink # newLink <-- currenthead(head) @@ -23,7 +26,9 @@ def insertHead(self, x): def deleteHead(self): temp = self.head self.head = self.head.next # oldHead <--> 2ndElement(head) - self.head.previous = None # oldHead --> 2ndElement(head) nothing pointing at it so the old head will be removed + # oldHead --> 2ndElement(head) nothing pointing at it so the old head will be + # removed + self.head.previous = None if self.head is None: self.tail = None # if empty linked list return temp diff --git a/divide_and_conquer/strassen_matrix_multiplication.py b/divide_and_conquer/strassen_matrix_multiplication.py index bfced547d493..ea54b0f52d29 100644 --- a/divide_and_conquer/strassen_matrix_multiplication.py +++ b/divide_and_conquer/strassen_matrix_multiplication.py @@ -31,12 +31,19 @@ def matrix_subtraction(matrix_a: List, matrix_b: List): def split_matrix(a: List,) -> Tuple[List, List, List, List]: """ - Given an even length matrix, returns the top_left, top_right, bot_left, bot_right quadrant. + Given an even length matrix, returns the top_left, top_right, bot_left, bot_right + quadrant. >>> split_matrix([[4,3,2,4],[2,3,1,1],[6,5,4,3],[8,4,1,6]]) ([[4, 3], [2, 3]], [[2, 4], [1, 1]], [[6, 5], [8, 4]], [[4, 3], [1, 6]]) - >>> split_matrix([[4,3,2,4,4,3,2,4],[2,3,1,1,2,3,1,1],[6,5,4,3,6,5,4,3],[8,4,1,6,8,4,1,6],[4,3,2,4,4,3,2,4],[2,3,1,1,2,3,1,1],[6,5,4,3,6,5,4,3],[8,4,1,6,8,4,1,6]]) - ([[4, 3, 2, 4], [2, 3, 1, 1], [6, 5, 4, 3], [8, 4, 1, 6]], [[4, 3, 2, 4], [2, 3, 1, 1], [6, 5, 4, 3], [8, 4, 1, 6]], [[4, 3, 2, 4], [2, 3, 1, 1], [6, 5, 4, 3], [8, 4, 1, 6]], [[4, 3, 2, 4], [2, 3, 1, 1], [6, 5, 4, 3], [8, 4, 1, 6]]) + >>> split_matrix([ + ... [4,3,2,4,4,3,2,4],[2,3,1,1,2,3,1,1],[6,5,4,3,6,5,4,3],[8,4,1,6,8,4,1,6], + ... [4,3,2,4,4,3,2,4],[2,3,1,1,2,3,1,1],[6,5,4,3,6,5,4,3],[8,4,1,6,8,4,1,6] + ... ]) # doctest: +NORMALIZE_WHITESPACE + ([[4, 3, 2, 4], [2, 3, 1, 1], [6, 5, 4, 3], [8, 4, 1, 6]], [[4, 3, 2, 4], + [2, 3, 1, 1], [6, 5, 4, 3], [8, 4, 1, 6]], [[4, 3, 2, 4], [2, 3, 1, 1], + [6, 5, 4, 3], [8, 4, 1, 6]], [[4, 3, 2, 4], [2, 3, 1, 1], [6, 5, 4, 3], + [8, 4, 1, 6]]) """ if len(a) % 2 != 0 or len(a[0]) % 2 != 0: raise Exception("Odd matrices are not supported!") @@ -66,8 +73,8 @@ def print_matrix(matrix: List) -> None: def actual_strassen(matrix_a: List, matrix_b: List) -> List: """ - Recursive function to calculate the product of two matrices, using the Strassen Algorithm. - It only supports even length matrices. + Recursive function to calculate the product of two matrices, using the Strassen + Algorithm. It only supports even length matrices. """ if matrix_dimensions(matrix_a) == (2, 2): return default_matrix_multiplication(matrix_a, matrix_b) @@ -106,7 +113,8 @@ def strassen(matrix1: List, matrix2: List) -> List: """ if matrix_dimensions(matrix1)[1] != matrix_dimensions(matrix2)[0]: raise Exception( - f"Unable to multiply these matrices, please check the dimensions. \nMatrix A:{matrix1} \nMatrix B:{matrix2}" + f"Unable to multiply these matrices, please check the dimensions. \n" + f"Matrix A:{matrix1} \nMatrix B:{matrix2}" ) dimension1 = matrix_dimensions(matrix1) dimension2 = matrix_dimensions(matrix2) @@ -119,7 +127,8 @@ def strassen(matrix1: List, matrix2: List) -> List: new_matrix1 = matrix1 new_matrix2 = matrix2 - # Adding zeros to the matrices so that the arrays dimensions are the same and also power of 2 + # Adding zeros to the matrices so that the arrays dimensions are the same and also + # power of 2 for i in range(0, maxim): if i < dimension1[0]: for j in range(dimension1[1], maxim): diff --git a/dynamic_programming/bitmask.py b/dynamic_programming/bitmask.py index 625a0809c4b9..2994db5b5e1e 100644 --- a/dynamic_programming/bitmask.py +++ b/dynamic_programming/bitmask.py @@ -4,10 +4,9 @@ Here Bitmasking and DP are used for solving this. Question :- -We have N tasks and M people. Each person in M can do only certain of these tasks. Also a person can do only one task and a task is performed only by one person. +We have N tasks and M people. Each person in M can do only certain of these tasks. Also +a person can do only one task and a task is performed only by one person. Find the total no of ways in which the tasks can be distributed. - - """ from collections import defaultdict @@ -25,7 +24,8 @@ def __init__(self, task_performed, total): self.task = defaultdict(list) # stores the list of persons for each task - # final_mask is used to check if all persons are included by setting all bits to 1 + # final_mask is used to check if all persons are included by setting all bits + # to 1 self.final_mask = (1 << len(task_performed)) - 1 def CountWaysUtil(self, mask, task_no): @@ -45,7 +45,8 @@ def CountWaysUtil(self, mask, task_no): # Number of ways when we don't this task in the arrangement total_ways_util = self.CountWaysUtil(mask, task_no + 1) - # now assign the tasks one by one to all possible persons and recursively assign for the remaining tasks. + # now assign the tasks one by one to all possible persons and recursively + # assign for the remaining tasks. if task_no in self.task: for p in self.task[task_no]: @@ -53,7 +54,8 @@ def CountWaysUtil(self, mask, task_no): if mask & (1 << p): continue - # assign this task to p and change the mask value. And recursively assign tasks with the new mask value. + # assign this task to p and change the mask value. And recursively + # assign tasks with the new mask value. total_ways_util += self.CountWaysUtil(mask | (1 << p), task_no + 1) # save the value. @@ -85,6 +87,7 @@ def countNoOfWays(self, task_performed): ) """ For the particular example the tasks can be distributed as - (1,2,3), (1,2,4), (1,5,3), (1,5,4), (3,1,4), (3,2,4), (3,5,4), (4,1,3), (4,2,3), (4,5,3) + (1,2,3), (1,2,4), (1,5,3), (1,5,4), (3,1,4), + (3,2,4), (3,5,4), (4,1,3), (4,2,3), (4,5,3) total 10 """ diff --git a/dynamic_programming/edit_distance.py b/dynamic_programming/edit_distance.py index 9df00eae96d7..56877e0c50a2 100644 --- a/dynamic_programming/edit_distance.py +++ b/dynamic_programming/edit_distance.py @@ -2,10 +2,12 @@ Author : Turfa Auliarachman Date : October 12, 2016 -This is a pure Python implementation of Dynamic Programming solution to the edit distance problem. +This is a pure Python implementation of Dynamic Programming solution to the edit +distance problem. The problem is : -Given two strings A and B. Find the minimum number of operations to string B such that A = B. The permitted operations are removal, insertion, and substitution. +Given two strings A and B. Find the minimum number of operations to string B such that +A = B. The permitted operations are removal, insertion, and substitution. """ diff --git a/machine_learning/decision_tree.py b/machine_learning/decision_tree.py index 6b121c73f3b4..fe1d54736563 100644 --- a/machine_learning/decision_tree.py +++ b/machine_learning/decision_tree.py @@ -20,15 +20,21 @@ def mean_squared_error(self, labels, prediction): mean_squared_error: @param labels: a one dimensional numpy array @param prediction: a floating point value - return value: mean_squared_error calculates the error if prediction is used to estimate the labels + return value: mean_squared_error calculates the error if prediction is used to + estimate the labels >>> tester = Decision_Tree() >>> test_labels = np.array([1,2,3,4,5,6,7,8,9,10]) >>> test_prediction = np.float(6) - >>> assert tester.mean_squared_error(test_labels, test_prediction) == Test_Decision_Tree.helper_mean_squared_error_test(test_labels, test_prediction) + >>> tester.mean_squared_error(test_labels, test_prediction) == ( + ... Test_Decision_Tree.helper_mean_squared_error_test(test_labels, + ... test_prediction)) + True >>> test_labels = np.array([1,2,3]) >>> test_prediction = np.float(2) - >>> assert tester.mean_squared_error(test_labels, test_prediction) == Test_Decision_Tree.helper_mean_squared_error_test(test_labels, test_prediction) - + >>> tester.mean_squared_error(test_labels, test_prediction) == ( + ... Test_Decision_Tree.helper_mean_squared_error_test(test_labels, + ... test_prediction)) + True """ if labels.ndim != 1: print("Error: Input labels must be one dimensional") @@ -46,7 +52,8 @@ def train(self, X, y): """ """ - this section is to check that the inputs conform to our dimensionality constraints + this section is to check that the inputs conform to our dimensionality + constraints """ if X.ndim != 1: print("Error: Input data set must be one dimensional") @@ -72,7 +79,8 @@ def train(self, X, y): """ loop over all possible splits for the decision tree. find the best split. if no split exists that is less than 2 * error for the entire array - then the data set is not split and the average for the entire array is used as the predictor + then the data set is not split and the average for the entire array is used as + the predictor """ for i in range(len(X)): if len(X[:i]) < self.min_leaf_size: @@ -136,7 +144,7 @@ def helper_mean_squared_error_test(labels, prediction): helper_mean_squared_error_test: @param labels: a one dimensional numpy array @param prediction: a floating point value - return value: helper_mean_squared_error_test calculates the mean squared error + return value: helper_mean_squared_error_test calculates the mean squared error """ squared_error_sum = np.float(0) for label in labels: @@ -147,9 +155,10 @@ def helper_mean_squared_error_test(labels, prediction): def main(): """ - In this demonstration we're generating a sample data set from the sin function in numpy. - We then train a decision tree on the data set and use the decision tree to predict the - label of 10 different test values. Then the mean squared error over this test is displayed. + In this demonstration we're generating a sample data set from the sin function in + numpy. We then train a decision tree on the data set and use the decision tree to + predict the label of 10 different test values. Then the mean squared error over + this test is displayed. """ X = np.arange(-1.0, 1.0, 0.005) y = np.sin(X) diff --git a/machine_learning/logistic_regression.py b/machine_learning/logistic_regression.py index f5edfb9c5d2c..1c1906e8e6b2 100644 --- a/machine_learning/logistic_regression.py +++ b/machine_learning/logistic_regression.py @@ -1,6 +1,6 @@ #!/usr/bin/python -## Logistic Regression from scratch +# Logistic Regression from scratch # In[62]: @@ -8,8 +8,12 @@ # importing all the required libraries -""" Implementing logistic regression for classification problem - Helpful resources : 1.Coursera ML course 2.https://medium.com/@martinpella/logistic-regression-from-scratch-in-python-124c5636b8ac""" +""" +Implementing logistic regression for classification problem +Helpful resources: +Coursera ML course +https://medium.com/@martinpella/logistic-regression-from-scratch-in-python-124c5636b8ac +""" import numpy as np import matplotlib.pyplot as plt @@ -21,7 +25,8 @@ # In[67]: -# sigmoid function or logistic function is used as a hypothesis function in classification problems +# sigmoid function or logistic function is used as a hypothesis function in +# classification problems def sigmoid_function(z): diff --git a/machine_learning/support_vector_machines.py b/machine_learning/support_vector_machines.py index d72e599eace4..53b446ef975d 100644 --- a/machine_learning/support_vector_machines.py +++ b/machine_learning/support_vector_machines.py @@ -3,6 +3,7 @@ from sklearn.model_selection import train_test_split import doctest + # different functions implementing different types of SVM's def NuSVC(train_x, train_y): svc_NuSVC = svm.NuSVC() @@ -17,8 +18,11 @@ def Linearsvc(train_x, train_y): def SVC(train_x, train_y): - # svm.SVC(C=1.0, kernel='rbf', degree=3, gamma=0.0, coef0=0.0, shrinking=True, probability=False,tol=0.001, cache_size=200, class_weight=None, verbose=False, max_iter=-1, random_state=None) - # various parameters like "kernel","gamma","C" can effectively tuned for a given machine learning model. + # svm.SVC(C=1.0, kernel='rbf', degree=3, gamma=0.0, coef0=0.0, shrinking=True, + # probability=False,tol=0.001, cache_size=200, class_weight=None, verbose=False, + # max_iter=-1, random_state=None) + # various parameters like "kernel","gamma","C" can effectively tuned for a given + # machine learning model. SVC = svm.SVC(gamma="auto") SVC.fit(train_x, train_y) return SVC @@ -27,8 +31,8 @@ def SVC(train_x, train_y): def test(X_new): """ 3 test cases to be passed - an array containing the sepal length (cm), sepal width (cm),petal length (cm),petal width (cm) - based on which the target name will be predicted + an array containing the sepal length (cm), sepal width (cm), petal length (cm), + petal width (cm) based on which the target name will be predicted >>> test([1,2,1,4]) 'virginica' >>> test([5, 2, 4, 1]) diff --git a/maths/pi_monte_carlo_estimation.py b/maths/pi_monte_carlo_estimation.py index d91c034cce12..20b46dddc6e5 100644 --- a/maths/pi_monte_carlo_estimation.py +++ b/maths/pi_monte_carlo_estimation.py @@ -23,9 +23,11 @@ def random_unit_square(cls): def estimate_pi(number_of_simulations: int) -> float: """ - Generates an estimate of the mathematical constant PI (see https://en.wikipedia.org/wiki/Monte_Carlo_method#Overview). + Generates an estimate of the mathematical constant PI. + See https://en.wikipedia.org/wiki/Monte_Carlo_method#Overview - The estimate is generated by Monte Carlo simulations. Let U be uniformly drawn from the unit square [0, 1) x [0, 1). The probability that U lies in the unit circle is: + The estimate is generated by Monte Carlo simulations. Let U be uniformly drawn from + the unit square [0, 1) x [0, 1). The probability that U lies in the unit circle is: P[U in unit circle] = 1/4 PI @@ -33,10 +35,12 @@ def estimate_pi(number_of_simulations: int) -> float: PI = 4 * P[U in unit circle] - We can get an estimate of the probability P[U in unit circle] (see https://en.wikipedia.org/wiki/Empirical_probability) by: + We can get an estimate of the probability P[U in unit circle]. + See https://en.wikipedia.org/wiki/Empirical_probability by: 1. Draw a point uniformly from the unit square. - 2. Repeat the first step n times and count the number of points in the unit circle, which is called m. + 2. Repeat the first step n times and count the number of points in the unit + circle, which is called m. 3. An estimate of P[U in unit circle] is m/n """ if number_of_simulations < 1: diff --git a/maths/zellers_congruence.py b/maths/zellers_congruence.py index 954a4643a9bb..9c13c29210b1 100644 --- a/maths/zellers_congruence.py +++ b/maths/zellers_congruence.py @@ -147,7 +147,10 @@ def zeller(date_input: str) -> str: doctest.testmod() parser = argparse.ArgumentParser( - description="Find out what day of the week nearly any date is or was. Enter date as a string in the mm-dd-yyyy or mm/dd/yyyy format" + description=( + "Find out what day of the week nearly any date is or was. Enter " + "date as a string in the mm-dd-yyyy or mm/dd/yyyy format" + ) ) parser.add_argument( "date_input", type=str, help="Date as a string (mm-dd-yyyy or mm/dd/yyyy)" diff --git a/matrix/matrix_class.py b/matrix/matrix_class.py index 2a1977b5dbfe..57a2fc45ffd1 100644 --- a/matrix/matrix_class.py +++ b/matrix/matrix_class.py @@ -3,7 +3,8 @@ class Matrix: """ - Matrix object generated from a 2D array where each element is an array representing a row. + Matrix object generated from a 2D array where each element is an array representing + a row. Rows can contain type int or float. Common operations and information available. >>> rows = [ @@ -16,13 +17,13 @@ class Matrix: [[1. 2. 3.] [4. 5. 6.] [7. 8. 9.]] - + Matrix rows and columns are available as 2D arrays >>> print(matrix.rows) [[1, 2, 3], [4, 5, 6], [7, 8, 9]] >>> print(matrix.columns()) [[1, 4, 7], [2, 5, 8], [3, 6, 9]] - + Order is returned as a tuple >>> matrix.order (3, 3) @@ -33,7 +34,8 @@ class Matrix: >>> matrix.is_invertable() False - Identity, Minors, Cofactors and Adjugate are returned as Matrices. Inverse can be a Matrix or Nonetype + Identity, Minors, Cofactors and Adjugate are returned as Matrices. Inverse can be + a Matrix or Nonetype >>> print(matrix.identity()) [[1. 0. 0.] [0. 1. 0.] @@ -46,7 +48,8 @@ class Matrix: [[-3. 6. -3.] [6. -12. 6.] [-3. 6. -3.]] - >>> print(matrix.adjugate()) # won't be apparent due to the nature of the cofactor matrix + >>> # won't be apparent due to the nature of the cofactor matrix + >>> print(matrix.adjugate()) [[-3. 6. -3.] [6. -12. 6.] [-3. 6. -3.]] @@ -57,7 +60,8 @@ class Matrix: >>> matrix.determinant() 0 - Negation, scalar multiplication, addition, subtraction, multiplication and exponentiation are available and all return a Matrix + Negation, scalar multiplication, addition, subtraction, multiplication and + exponentiation are available and all return a Matrix >>> print(-matrix) [[-1. -2. -3.] [-4. -5. -6.] @@ -102,8 +106,9 @@ class Matrix: def __init__(self, rows): error = TypeError( - "Matrices must be formed from a list of zero or more lists containing at least " - "one and the same number of values, each of which must be of type int or float." + "Matrices must be formed from a list of zero or more lists containing at " + "least one and the same number of values, each of which must be of type " + "int or float." ) if len(rows) != 0: cols = len(rows[0]) @@ -159,10 +164,8 @@ def determinant(self): ) else: return sum( - [ - self.rows[0][column] * self.cofactors().rows[0][column] - for column in range(self.num_columns) - ] + self.rows[0][column] * self.cofactors().rows[0][column] + for column in range(self.num_columns) ) def is_invertable(self): @@ -346,7 +349,7 @@ def __pow__(self, other): @classmethod def dot_product(cls, row, column): - return sum([row[i] * column[i] for i in range(len(row))]) + return sum(row[i] * column[i] for i in range(len(row))) if __name__ == "__main__": diff --git a/other/password_generator.py b/other/password_generator.py index 598f8d0eeade..35e11e4dfb78 100644 --- a/other/password_generator.py +++ b/other/password_generator.py @@ -30,7 +30,8 @@ def alternative_password_generator(ctbi, i): i = i - len(ctbi) quotient = int(i / 3) remainder = i % 3 - # chars = ctbi + random_letters(ascii_letters, i / 3 + remainder) + random_number(digits, i / 3) + random_characters(punctuation, i / 3) + # chars = ctbi + random_letters(ascii_letters, i / 3 + remainder) + + # random_number(digits, i / 3) + random_characters(punctuation, i / 3) chars = ( ctbi + random(ascii_letters, quotient + remainder) diff --git a/other/sierpinski_triangle.py b/other/sierpinski_triangle.py index a262900a84f9..e27db3a2e8c4 100644 --- a/other/sierpinski_triangle.py +++ b/other/sierpinski_triangle.py @@ -5,11 +5,14 @@ Simple example of Fractal generation using recursive function. What is Sierpinski Triangle? ->>The Sierpinski triangle (also with the original orthography Sierpinski), also called the Sierpinski gasket or the Sierpinski Sieve, -is a fractal and attractive fixed set with the overall shape of an equilateral triangle, subdivided recursively into smaller -equilateral triangles. Originally constructed as a curve, this is one of the basic examples of self-similar sets, i.e., -it is a mathematically generated pattern that can be reproducible at any magnification or reduction. It is named after -the Polish mathematician Wacław Sierpinski, but appeared as a decorative pattern many centuries prior to the work of Sierpinski. +>>The Sierpinski triangle (also with the original orthography Sierpinski), also called +the Sierpinski gasket or the Sierpinski Sieve, is a fractal and attractive fixed set +with the overall shape of an equilateral triangle, subdivided recursively into smaller +equilateral triangles. Originally constructed as a curve, this is one of the basic +examples of self-similar sets, i.e., it is a mathematically generated pattern that can +be reproducible at any magnification or reduction. It is named after the Polish +mathematician Wacław Sierpinski, but appeared as a decorative pattern many centuries +prior to the work of Sierpinski. Requirements(pip): - turtle @@ -20,7 +23,8 @@ Usage: - $python sierpinski_triangle.py -Credits: This code was written by editing the code from http://www.riannetrujillo.com/blog/python-fractal/ +Credits: This code was written by editing the code from +http://www.riannetrujillo.com/blog/python-fractal/ """ import turtle diff --git a/project_euler/problem_99/sol1.py b/project_euler/problem_99/sol1.py index 713bf65caab7..0148a80ef481 100644 --- a/project_euler/problem_99/sol1.py +++ b/project_euler/problem_99/sol1.py @@ -1,11 +1,14 @@ """ Problem: -Comparing two numbers written in index form like 2'11 and 3'7 is not difficult, as any calculator would confirm that 2^11 = 2048 < 3^7 = 2187. +Comparing two numbers written in index form like 2'11 and 3'7 is not difficult, as any +calculator would confirm that 2^11 = 2048 < 3^7 = 2187. -However, confirming that 632382^518061 > 519432^525806 would be much more difficult, as both numbers contain over three million digits. +However, confirming that 632382^518061 > 519432^525806 would be much more difficult, as +both numbers contain over three million digits. -Using base_exp.txt, a 22K text file containing one thousand lines with a base/exponent pair on each line, determine which line number has the greatest numerical value. +Using base_exp.txt, a 22K text file containing one thousand lines with a base/exponent +pair on each line, determine which line number has the greatest numerical value. NOTE: The first two lines in the file represent the numbers in the example given above. """ diff --git a/searches/binary_search.py b/searches/binary_search.py index 8edf63136f9a..0d9e730258d7 100644 --- a/searches/binary_search.py +++ b/searches/binary_search.py @@ -14,15 +14,18 @@ def bisect_left(sorted_collection, item, lo=0, hi=None): """ - Locates the first element in a sorted array that is larger or equal to a given value. + Locates the first element in a sorted array that is larger or equal to a given + value. - It has the same interface as https://docs.python.org/3/library/bisect.html#bisect.bisect_left . + It has the same interface as + https://docs.python.org/3/library/bisect.html#bisect.bisect_left . :param sorted_collection: some ascending sorted collection with comparable items :param item: item to bisect :param lo: lowest index to consider (as in sorted_collection[lo:hi]) :param hi: past the highest index to consider (as in sorted_collection[lo:hi]) - :return: index i such that all values in sorted_collection[lo:i] are < item and all values in sorted_collection[i:hi] are >= item. + :return: index i such that all values in sorted_collection[lo:i] are < item and all + values in sorted_collection[i:hi] are >= item. Examples: >>> bisect_left([0, 5, 7, 10, 15], 0) @@ -57,13 +60,15 @@ def bisect_right(sorted_collection, item, lo=0, hi=None): """ Locates the first element in a sorted array that is larger than a given value. - It has the same interface as https://docs.python.org/3/library/bisect.html#bisect.bisect_right . + It has the same interface as + https://docs.python.org/3/library/bisect.html#bisect.bisect_right . :param sorted_collection: some ascending sorted collection with comparable items :param item: item to bisect :param lo: lowest index to consider (as in sorted_collection[lo:hi]) :param hi: past the highest index to consider (as in sorted_collection[lo:hi]) - :return: index i such that all values in sorted_collection[lo:i] are <= item and all values in sorted_collection[i:hi] are > item. + :return: index i such that all values in sorted_collection[lo:i] are <= item and + all values in sorted_collection[i:hi] are > item. Examples: >>> bisect_right([0, 5, 7, 10, 15], 0) @@ -98,7 +103,8 @@ def insort_left(sorted_collection, item, lo=0, hi=None): """ Inserts a given value into a sorted array before other values with the same value. - It has the same interface as https://docs.python.org/3/library/bisect.html#bisect.insort_left . + It has the same interface as + https://docs.python.org/3/library/bisect.html#bisect.insort_left . :param sorted_collection: some ascending sorted collection with comparable items :param item: item to insert @@ -138,7 +144,8 @@ def insort_right(sorted_collection, item, lo=0, hi=None): """ Inserts a given value into a sorted array after other values with the same value. - It has the same interface as https://docs.python.org/3/library/bisect.html#bisect.insort_right . + It has the same interface as + https://docs.python.org/3/library/bisect.html#bisect.insort_right . :param sorted_collection: some ascending sorted collection with comparable items :param item: item to insert diff --git a/web_programming/slack_message.py b/web_programming/slack_message.py index 8dd0462d48e3..8ea9d5d0add2 100644 --- a/web_programming/slack_message.py +++ b/web_programming/slack_message.py @@ -14,5 +14,6 @@ def send_slack_message(message_body: str, slack_url: str) -> None: if __name__ == "main": - # Set the slack url to the one provided by Slack when you create the webhook at https://my.slack.com/services/new/incoming-webhook/ + # Set the slack url to the one provided by Slack when you create the webhook at + # https://my.slack.com/services/new/incoming-webhook/ send_slack_message("", "") From d62cc35268aaedad0ee616f97ad5ea7b60b182bf Mon Sep 17 00:00:00 2001 From: lkdmttg7 Date: Sat, 2 May 2020 16:34:25 +0530 Subject: [PATCH 0536/1071] Update stale comment (#1924) * Update close comment * Update stale.yml * Multiline strings in yaml files https://yaml-multiline.info/ Co-authored-by: John Law Co-authored-by: Christian Clauss --- .github/stale.yml | 4 +++- .github/workflows/stale.yml | 12 ++++++++++-- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/.github/stale.yml b/.github/stale.yml index 70032115fc2c..fe51e49e4707 100644 --- a/.github/stale.yml +++ b/.github/stale.yml @@ -15,4 +15,6 @@ markComment: > recent activity. It will be closed if no further activity occurs. Thank you for your contributions. # Comment to post when closing a stale issue. Set to `false` to disable -closeComment: true +closeComment: > + Please reopen this issue once you commit the changes requested or + make improvements on the code. Thank you for your contributions. diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml index 1d1d743fa832..4793f54f7af8 100644 --- a/.github/workflows/stale.yml +++ b/.github/workflows/stale.yml @@ -9,7 +9,15 @@ jobs: - uses: actions/stale@v1 with: repo-token: ${{ secrets.GITHUB_TOKEN }} - stale-issue-message: 'Stale issue message' - stale-pr-message: 'Stale pull request message' + stale-issue-message: > + Please reopen this issue once you add more information and updates here. + If this is not the case and you need some help, feel free to seek help + from our [Gitter](https://gitter.im/TheAlgorithms) or ping one of the + reviewers. Thank you for your contributions! + stale-pr-message: > + Please reopen this pull request once you commit the changes requested + or make improvements on the code. If this is not the case and you need + some help, feel free to seek help from our [Gitter](https://gitter.im/TheAlgorithms) + or ping one of the reviewers. Thank you for your contributions! stale-issue-label: 'no-issue-activity' stale-pr-label: 'no-pr-activity' From a859934105081c0653e029ed4e3ddf2dd5374103 Mon Sep 17 00:00:00 2001 From: Saba Pochkhua Date: Sat, 2 May 2020 23:13:56 +0400 Subject: [PATCH 0537/1071] Hamiltonian Cycle (#1930) * add skeleton code * add doctests * add tests for util function + implement wrapper * full implementation * add ability to add starting verex for algorithm * add static type checking * add doc tests to validation method * bug fix: doctests expected failing * Update hamiltonian_cycle.py * Update hamiltonian_cycle.py Co-authored-by: Christian Clauss --- backtracking/hamiltonian_cycle.py | 175 ++++++++++++++++++++++++++++++ 1 file changed, 175 insertions(+) create mode 100644 backtracking/hamiltonian_cycle.py diff --git a/backtracking/hamiltonian_cycle.py b/backtracking/hamiltonian_cycle.py new file mode 100644 index 000000000000..e4f2c62d2341 --- /dev/null +++ b/backtracking/hamiltonian_cycle.py @@ -0,0 +1,175 @@ +""" + A Hamiltonian cycle (Hamiltonian circuit) is a graph cycle + through a graph that visits each node exactly once. + Determining whether such paths and cycles exist in graphs + is the 'Hamiltonian path problem', which is NP-complete. + + Wikipedia: https://en.wikipedia.org/wiki/Hamiltonian_path +""" +from typing import List + + +def valid_connection( + graph: List[List[int]], next_ver: int, curr_ind: int, path: List[int] +) -> bool: + """ + Checks whether it is possible to add next into path by validating 2 statements + 1. There should be path between current and next vertex + 2. Next vertex should not be in path + If both validations succeeds we return true saying that it is possible to connect this vertices + either we return false + + Case 1:Use exact graph as in main function, with initialized values + >>> graph = [[0, 1, 0, 1, 0], + ... [1, 0, 1, 1, 1], + ... [0, 1, 0, 0, 1], + ... [1, 1, 0, 0, 1], + ... [0, 1, 1, 1, 0]] + >>> path = [0, -1, -1, -1, -1, 0] + >>> curr_ind = 1 + >>> next_ver = 1 + >>> valid_connection(graph, next_ver, curr_ind, path) + True + + Case 2: Same graph, but trying to connect to node that is already in path + >>> path = [0, 1, 2, 4, -1, 0] + >>> curr_ind = 4 + >>> next_ver = 1 + >>> valid_connection(graph, next_ver, curr_ind, path) + False + """ + + # 1. Validate that path exists between current and next vertices + if graph[path[curr_ind - 1]][next_ver] == 0: + return False + + # 2. Validate that next vertex is not already in path + return not any(vertex == next_ver for vertex in path) + + +def util_hamilton_cycle(graph: List[List[int]], path: List[int], curr_ind: int) -> bool: + """ + Pseudo-Code + Base Case: + 1. Chceck if we visited all of vertices + 1.1 If last visited vertex has path to starting vertex return True either return False + Recursive Step: + 2. Iterate over each vertex + Check if next vertex is valid for transiting from current vertex + 2.1 Remember next vertex as next transition + 2.2 Do recursive call and check if going to this vertex solves problem + 2.3 if next vertex leads to solution return True + 2.4 else backtrack, delete remembered vertex + + Case 1: Use exact graph as in main function, with initialized values + >>> graph = [[0, 1, 0, 1, 0], + ... [1, 0, 1, 1, 1], + ... [0, 1, 0, 0, 1], + ... [1, 1, 0, 0, 1], + ... [0, 1, 1, 1, 0]] + >>> path = [0, -1, -1, -1, -1, 0] + >>> curr_ind = 1 + >>> util_hamilton_cycle(graph, path, curr_ind) + True + >>> print(path) + [0, 1, 2, 4, 3, 0] + + Case 2: Use exact graph as in previous case, but in the properties taken from middle of calculation + >>> graph = [[0, 1, 0, 1, 0], + ... [1, 0, 1, 1, 1], + ... [0, 1, 0, 0, 1], + ... [1, 1, 0, 0, 1], + ... [0, 1, 1, 1, 0]] + >>> path = [0, 1, 2, -1, -1, 0] + >>> curr_ind = 3 + >>> util_hamilton_cycle(graph, path, curr_ind) + True + >>> print(path) + [0, 1, 2, 4, 3, 0] + """ + + # Base Case + if curr_ind == len(graph): + # return whether path exists between current and starting vertices + return graph[path[curr_ind - 1]][path[0]] == 1 + + # Recursive Step + for next in range(0, len(graph)): + if valid_connection(graph, next, curr_ind, path): + # Insert current vertex into path as next transition + path[curr_ind] = next + # Validate created path + if util_hamilton_cycle(graph, path, curr_ind + 1): + return True + # Backtrack + path[curr_ind] = -1 + return False + + +def hamilton_cycle(graph: List[List[int]], start_index: int = 0) -> List[int]: + r""" + Wrapper function to call subroutine called util_hamilton_cycle, + which will either return array of vertices indicating hamiltonian cycle + or an empty list indicating that hamiltonian cycle was not found. + Case 1: + Following graph consists of 5 edges. + If we look closely, we can see that there are multiple Hamiltonian cycles. + For example one result is when we iterate like: + (0)->(1)->(2)->(4)->(3)->(0) + + (0)---(1)---(2) + | / \ | + | / \ | + | / \ | + |/ \| + (3)---------(4) + >>> graph = [[0, 1, 0, 1, 0], + ... [1, 0, 1, 1, 1], + ... [0, 1, 0, 0, 1], + ... [1, 1, 0, 0, 1], + ... [0, 1, 1, 1, 0]] + >>> hamilton_cycle(graph) + [0, 1, 2, 4, 3, 0] + + Case 2: + Same Graph as it was in Case 1, changed starting index from default to 3 + + (0)---(1)---(2) + | / \ | + | / \ | + | / \ | + |/ \| + (3)---------(4) + >>> graph = [[0, 1, 0, 1, 0], + ... [1, 0, 1, 1, 1], + ... [0, 1, 0, 0, 1], + ... [1, 1, 0, 0, 1], + ... [0, 1, 1, 1, 0]] + >>> hamilton_cycle(graph, 3) + [3, 0, 1, 2, 4, 3] + + Case 3: + Following Graph is exactly what it was before, but edge 3-4 is removed. + Result is that there is no Hamiltonian Cycle anymore. + + (0)---(1)---(2) + | / \ | + | / \ | + | / \ | + |/ \| + (3) (4) + >>> graph = [[0, 1, 0, 1, 0], + ... [1, 0, 1, 1, 1], + ... [0, 1, 0, 0, 1], + ... [1, 1, 0, 0, 0], + ... [0, 1, 1, 0, 0]] + >>> hamilton_cycle(graph,4) + [] + """ + + # Initialize path with -1, indicating that we have not visited them yet + path = [-1] * (len(graph) + 1) + # initialize start and end of path with starting index + path[0] = path[-1] = start_index + # evaluate and if we find answer return path either return empty array + return path if util_hamilton_cycle(graph, path, 1) else [] From 9bb57fbbfef1ddf3420ef2da249f4e3fe63222ef Mon Sep 17 00:00:00 2001 From: QuantumNovice <43876848+QuantumNovice@users.noreply.github.com> Date: Sun, 3 May 2020 00:19:45 +0500 Subject: [PATCH 0538/1071] support_vector_machines.py increase error tolerance to suppress convergence warnings (#1929) * Update support_vector_machines.py * Update support_vector_machines.py Co-authored-by: Christian Clauss --- machine_learning/support_vector_machines.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/machine_learning/support_vector_machines.py b/machine_learning/support_vector_machines.py index 53b446ef975d..3bf54a69128d 100644 --- a/machine_learning/support_vector_machines.py +++ b/machine_learning/support_vector_machines.py @@ -1,7 +1,6 @@ from sklearn.datasets import load_iris from sklearn import svm from sklearn.model_selection import train_test_split -import doctest # different functions implementing different types of SVM's @@ -12,7 +11,7 @@ def NuSVC(train_x, train_y): def Linearsvc(train_x, train_y): - svc_linear = svm.LinearSVC() + svc_linear = svm.LinearSVC(tol=10e-2) svc_linear.fit(train_x, train_y) return svc_linear @@ -20,7 +19,7 @@ def Linearsvc(train_x, train_y): def SVC(train_x, train_y): # svm.SVC(C=1.0, kernel='rbf', degree=3, gamma=0.0, coef0=0.0, shrinking=True, # probability=False,tol=0.001, cache_size=200, class_weight=None, verbose=False, - # max_iter=-1, random_state=None) + # max_iter=1000, random_state=None) # various parameters like "kernel","gamma","C" can effectively tuned for a given # machine learning model. SVC = svm.SVC(gamma="auto") @@ -39,7 +38,6 @@ def test(X_new): 'versicolor' >>> test([6,3,4,1]) 'versicolor' - """ iris = load_iris() # splitting the dataset to test and train @@ -55,4 +53,6 @@ def test(X_new): if __name__ == "__main__": + import doctest + doctest.testmod() From 853741e518f6c5a121e24d3952ca1d9f018999b4 Mon Sep 17 00:00:00 2001 From: lanzhiwang Date: Sun, 3 May 2020 03:44:29 +0800 Subject: [PATCH 0539/1071] enhanced segment tree implementation and more pythonic (#1715) * enhanced segment tree implementation and more pythonic enhanced segment tree implementation and more pythonic * add doctests for segment tree * add type annotations * unified processing sum min max segment tre * delete source encoding in segment tree * use a generator function instead of returning * add doctests for methods * add doctests for methods * add doctests * fix doctest * fix doctest * fix doctest * fix function parameter and fix determine conditions --- .../binary_tree/segment_tree_other.py | 237 ++++++++++++++++++ 1 file changed, 237 insertions(+) create mode 100644 data_structures/binary_tree/segment_tree_other.py diff --git a/data_structures/binary_tree/segment_tree_other.py b/data_structures/binary_tree/segment_tree_other.py new file mode 100644 index 000000000000..93b603cdc7a2 --- /dev/null +++ b/data_structures/binary_tree/segment_tree_other.py @@ -0,0 +1,237 @@ +""" +Segment_tree creates a segment tree with a given array and function, +allowing queries to be done later in log(N) time +function takes 2 values and returns a same type value +""" + +from queue import Queue +from collections.abc import Sequence + + +class SegmentTreeNode(object): + def __init__(self, start, end, val, left=None, right=None): + self.start = start + self.end = end + self.val = val + self.mid = (start + end) // 2 + self.left = left + self.right = right + + def __str__(self): + return 'val: %s, start: %s, end: %s' % (self.val, self.start, self.end) + + +class SegmentTree(object): + """ + >>> import operator + >>> num_arr = SegmentTree([2, 1, 5, 3, 4], operator.add) + >>> for node in num_arr.traverse(): + ... print(node) + ... + val: 15, start: 0, end: 4 + val: 8, start: 0, end: 2 + val: 7, start: 3, end: 4 + val: 3, start: 0, end: 1 + val: 5, start: 2, end: 2 + val: 3, start: 3, end: 3 + val: 4, start: 4, end: 4 + val: 2, start: 0, end: 0 + val: 1, start: 1, end: 1 + >>> + >>> num_arr.update(1, 5) + >>> for node in num_arr.traverse(): + ... print(node) + ... + val: 19, start: 0, end: 4 + val: 12, start: 0, end: 2 + val: 7, start: 3, end: 4 + val: 7, start: 0, end: 1 + val: 5, start: 2, end: 2 + val: 3, start: 3, end: 3 + val: 4, start: 4, end: 4 + val: 2, start: 0, end: 0 + val: 5, start: 1, end: 1 + >>> + >>> num_arr.query_range(3, 4) + 7 + >>> num_arr.query_range(2, 2) + 5 + >>> num_arr.query_range(1, 3) + 13 + >>> + >>> max_arr = SegmentTree([2, 1, 5, 3, 4], max) + >>> for node in max_arr.traverse(): + ... print(node) + ... + val: 5, start: 0, end: 4 + val: 5, start: 0, end: 2 + val: 4, start: 3, end: 4 + val: 2, start: 0, end: 1 + val: 5, start: 2, end: 2 + val: 3, start: 3, end: 3 + val: 4, start: 4, end: 4 + val: 2, start: 0, end: 0 + val: 1, start: 1, end: 1 + >>> + >>> max_arr.update(1, 5) + >>> for node in max_arr.traverse(): + ... print(node) + ... + val: 5, start: 0, end: 4 + val: 5, start: 0, end: 2 + val: 4, start: 3, end: 4 + val: 5, start: 0, end: 1 + val: 5, start: 2, end: 2 + val: 3, start: 3, end: 3 + val: 4, start: 4, end: 4 + val: 2, start: 0, end: 0 + val: 5, start: 1, end: 1 + >>> + >>> max_arr.query_range(3, 4) + 4 + >>> max_arr.query_range(2, 2) + 5 + >>> max_arr.query_range(1, 3) + 5 + >>> + >>> min_arr = SegmentTree([2, 1, 5, 3, 4], min) + >>> for node in min_arr.traverse(): + ... print(node) + ... + val: 1, start: 0, end: 4 + val: 1, start: 0, end: 2 + val: 3, start: 3, end: 4 + val: 1, start: 0, end: 1 + val: 5, start: 2, end: 2 + val: 3, start: 3, end: 3 + val: 4, start: 4, end: 4 + val: 2, start: 0, end: 0 + val: 1, start: 1, end: 1 + >>> + >>> min_arr.update(1, 5) + >>> for node in min_arr.traverse(): + ... print(node) + ... + val: 2, start: 0, end: 4 + val: 2, start: 0, end: 2 + val: 3, start: 3, end: 4 + val: 2, start: 0, end: 1 + val: 5, start: 2, end: 2 + val: 3, start: 3, end: 3 + val: 4, start: 4, end: 4 + val: 2, start: 0, end: 0 + val: 5, start: 1, end: 1 + >>> + >>> min_arr.query_range(3, 4) + 3 + >>> min_arr.query_range(2, 2) + 5 + >>> min_arr.query_range(1, 3) + 3 + >>> + + """ + def __init__(self, collection: Sequence, function): + self.collection = collection + self.fn = function + if self.collection: + self.root = self._build_tree(0, len(collection) - 1) + + def update(self, i, val): + """ + Update an element in log(N) time + :param i: position to be update + :param val: new value + >>> import operator + >>> num_arr = SegmentTree([2, 1, 5, 3, 4], operator.add) + >>> num_arr.update(1, 5) + >>> num_arr.query_range(1, 3) + 13 + """ + self._update_tree(self.root, i, val) + + def query_range(self, i, j): + """ + Get range query value in log(N) time + :param i: left element index + :param j: right element index + :return: element combined in the range [i, j] + >>> import operator + >>> num_arr = SegmentTree([2, 1, 5, 3, 4], operator.add) + >>> num_arr.update(1, 5) + >>> num_arr.query_range(3, 4) + 7 + >>> num_arr.query_range(2, 2) + 5 + >>> num_arr.query_range(1, 3) + 13 + >>> + """ + return self._query_range(self.root, i, j) + + def _build_tree(self, start, end): + if start == end: + return SegmentTreeNode(start, end, self.collection[start]) + mid = (start + end) // 2 + left = self._build_tree(start, mid) + right = self._build_tree(mid + 1, end) + return SegmentTreeNode(start, end, self.fn(left.val, right.val), left, right) + + def _update_tree(self, node, i, val): + if node.start == i and node.end == i: + node.val = val + return + if i <= node.mid: + self._update_tree(node.left, i, val) + else: + self._update_tree(node.right, i, val) + node.val = self.fn(node.left.val, node.right.val) + + def _query_range(self, node, i, j): + if node.start == i and node.end == j: + return node.val + + if i <= node.mid: + if j <= node.mid: + # range in left child tree + return self._query_range(node.left, i, j) + else: + # range in left child tree and right child tree + return self.fn(self._query_range(node.left, i, node.mid), self._query_range(node.right, node.mid + 1, j)) + else: + # range in right child tree + return self._query_range(node.right, i, j) + + def traverse(self): + if self.root is not None: + queue = Queue() + queue.put(self.root) + while not queue.empty(): + node = queue.get() + yield node + + if node.left is not None: + queue.put(node.left) + + if node.right is not None: + queue.put(node.right) + + +if __name__ == '__main__': + import operator + for fn in [operator.add, max, min]: + print('*' * 50) + arr = SegmentTree([2, 1, 5, 3, 4], fn) + for node in arr.traverse(): + print(node) + print() + + arr.update(1, 5) + for node in arr.traverse(): + print(node) + print() + + print(arr.query_range(3, 4)) # 7 + print(arr.query_range(2, 2)) # 5 + print(arr.query_range(1, 3)) # 13 + print() From b6fcee311430d1bcf81253898b08f9e80d20064d Mon Sep 17 00:00:00 2001 From: Akash Date: Sun, 3 May 2020 19:45:31 +0530 Subject: [PATCH 0540/1071] Check if a item exist in stack or not (#1931) * Check if a item exist in stack or not * implemented __contains__ method in stack * made changes in __contains__ --- data_structures/stacks/stack.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/data_structures/stacks/stack.py b/data_structures/stacks/stack.py index 53a40a7b7ebc..a96275aa8df2 100644 --- a/data_structures/stacks/stack.py +++ b/data_structures/stacks/stack.py @@ -46,7 +46,11 @@ def is_empty(self): def size(self): """ Return the size of the stack.""" return len(self.stack) - + + def __contains__(self, item) -> bool: + """Check if item is in stack""" + return item in self.stack + class StackOverflowError(BaseException): pass @@ -66,3 +70,7 @@ class StackOverflowError(BaseException): print("After push(100), the stack is now: " + str(stack)) print("is_empty(): " + str(stack.is_empty())) print("size(): " + str(stack.size())) + num = 5 + if num in stack: + print(f"{num} is in stack") + From f80ffe1f54a0a3caacc14efb93b61393052d9f14 Mon Sep 17 00:00:00 2001 From: Akash Date: Sun, 3 May 2020 23:26:33 +0530 Subject: [PATCH 0541/1071] added method for checking armstrong number (#1936) * added method for checking armstrong number * Update comment Co-authored-by: Christian Clauss --- maths/armstrong_numbers.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/maths/armstrong_numbers.py b/maths/armstrong_numbers.py index 4ed23dd1d1d7..39a1464a9aa6 100644 --- a/maths/armstrong_numbers.py +++ b/maths/armstrong_numbers.py @@ -41,6 +41,14 @@ def armstrong_number(n: int) -> bool: temp //= 10 return n == sum +def narcissistic_number(n:int) -> bool: + """Return True if n is a narcissistic number or False if it is not""" + + expo = len(str(n)) #power, all number will be raised to + temp = [(int(i)**expo) for i in str(n)] # each digit will be multiplied expo times + + # check if sum of cube of each digit is equal to number + return n == sum(temp) def main(): """ @@ -48,6 +56,7 @@ def main(): """ num = int(input("Enter an integer to see if it is an Armstrong number: ").strip()) print(f"{num} is {'' if armstrong_number(num) else 'not '}an Armstrong number.") + print(f"{num} is {'' if narcissistic_number(num) else 'not '}an Armstrong number.") if __name__ == "__main__": From 9b2d65bac18e3a633e377dcc211fc4c127c6643f Mon Sep 17 00:00:00 2001 From: Alok Shukla <20066073+shuklalok@users.noreply.github.com> Date: Mon, 4 May 2020 02:18:16 +0530 Subject: [PATCH 0542/1071] Solution for Euler Problem 26 (#1939) * Solution for Euler Problem 26 * Update project_euler/problem_26/sol1.py typo error fix. Co-authored-by: Christian Clauss * Update project_euler/problem_26/sol1.py typo error fix Co-authored-by: Christian Clauss * Update project_euler/problem_26/sol1.py ok to remove, this comes from Pycharm automatically when docstring is added. Co-authored-by: Christian Clauss * Update project_euler/problem_26/sol1.py ok to remove. Co-authored-by: Christian Clauss * Update project_euler/problem_26/sol1.py Co-authored-by: Christian Clauss * Update project_euler/problem_26/sol1.py Co-authored-by: Christian Clauss * Update project_euler/problem_26/sol1.py Co-authored-by: Christian Clauss * now_divide = now_divide * 10 % divide_by_number Co-authored-by: Christian Clauss --- project_euler/problem_26/__init__.py | 0 project_euler/problem_26/sol1.py | 41 ++++++++++++++++++++++++++++ 2 files changed, 41 insertions(+) create mode 100644 project_euler/problem_26/__init__.py create mode 100644 project_euler/problem_26/sol1.py diff --git a/project_euler/problem_26/__init__.py b/project_euler/problem_26/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/project_euler/problem_26/sol1.py b/project_euler/problem_26/sol1.py new file mode 100644 index 000000000000..7b8c44c9c828 --- /dev/null +++ b/project_euler/problem_26/sol1.py @@ -0,0 +1,41 @@ +""" +Euler Problem 26 +https://projecteuler.net/problem=26 +Find the value of d < 1000 for which 1/d contains the longest recurring cycle +in its decimal fraction part. +""" + +def find_digit(numerator: int, digit: int) -> int: + """ + Considering any range can be provided, + because as per the problem, the digit d < 1000 + >>> find_digit(1, 10) + 7 + >>> find_digit(10, 100) + 97 + >>> find_digit(10, 1000) + 983 + """ + the_digit = 1 + longest_list_length = 0 + + for divide_by_number in range(numerator, digit + 1): + has_been_divided = [] + now_divide = numerator + for division_cycle in range(1, digit + 1): + if now_divide in has_been_divided: + if longest_list_length < len(has_been_divided): + longest_list_length = len(has_been_divided) + the_digit = divide_by_number + else: + has_been_divided.append(now_divide) + now_divide = now_divide * 10 % divide_by_number + + return the_digit + + +# Tests +if __name__ == "__main__": + import doctest + + doctest.testmod() From d1b25760bc92d41032a123cb0bbdbd0aff8caadb Mon Sep 17 00:00:00 2001 From: Christian Clauss Date: Sun, 3 May 2020 23:58:44 +0200 Subject: [PATCH 0543/1071] Fix psf/black issues than fail the build (#1935) * Fix psf/black issues than fail the build * fixup! Format Python code with psf/black push Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> --- README.md | 2 +- data_structures/binary_tree/segment_tree_other.py | 13 +++++++++---- data_structures/stacks/stack.py | 5 ++--- 3 files changed, 12 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 7fc4f0f0b397..106f5907eebf 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,7 @@ Read our [Contribution Guidelines](CONTRIBUTING.md) before you contribute. ## Community Channel -We're on [Gitter](https://gitter.im/TheAlgorithms)! Please join us. +We're on [Gitter](https://gitter.im/TheAlgorithms)! Please join us. ## List of Algorithms diff --git a/data_structures/binary_tree/segment_tree_other.py b/data_structures/binary_tree/segment_tree_other.py index 93b603cdc7a2..c3ab493d5f4f 100644 --- a/data_structures/binary_tree/segment_tree_other.py +++ b/data_structures/binary_tree/segment_tree_other.py @@ -18,7 +18,7 @@ def __init__(self, start, end, val, left=None, right=None): self.right = right def __str__(self): - return 'val: %s, start: %s, end: %s' % (self.val, self.start, self.end) + return "val: %s, start: %s, end: %s" % (self.val, self.start, self.end) class SegmentTree(object): @@ -131,6 +131,7 @@ class SegmentTree(object): >>> """ + def __init__(self, collection: Sequence, function): self.collection = collection self.fn = function @@ -197,7 +198,10 @@ def _query_range(self, node, i, j): return self._query_range(node.left, i, j) else: # range in left child tree and right child tree - return self.fn(self._query_range(node.left, i, node.mid), self._query_range(node.right, node.mid + 1, j)) + return self.fn( + self._query_range(node.left, i, node.mid), + self._query_range(node.right, node.mid + 1, j), + ) else: # range in right child tree return self._query_range(node.right, i, j) @@ -217,10 +221,11 @@ def traverse(self): queue.put(node.right) -if __name__ == '__main__': +if __name__ == "__main__": import operator + for fn in [operator.add, max, min]: - print('*' * 50) + print("*" * 50) arr = SegmentTree([2, 1, 5, 3, 4], fn) for node in arr.traverse(): print(node) diff --git a/data_structures/stacks/stack.py b/data_structures/stacks/stack.py index a96275aa8df2..baa0857eec0a 100644 --- a/data_structures/stacks/stack.py +++ b/data_structures/stacks/stack.py @@ -46,11 +46,11 @@ def is_empty(self): def size(self): """ Return the size of the stack.""" return len(self.stack) - + def __contains__(self, item) -> bool: """Check if item is in stack""" return item in self.stack - + class StackOverflowError(BaseException): pass @@ -73,4 +73,3 @@ class StackOverflowError(BaseException): num = 5 if num in stack: print(f"{num} is in stack") - From 540023ec3178c5de2837f661068756a5c4d616b0 Mon Sep 17 00:00:00 2001 From: Anup Kumar Panwar <1anuppanwar@gmail.com> Date: Mon, 4 May 2020 11:18:41 +0530 Subject: [PATCH 0544/1071] Delete FUNDING.yml --- .github/FUNDING.yml | 12 ------------ 1 file changed, 12 deletions(-) delete mode 100644 .github/FUNDING.yml diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml deleted file mode 100644 index 209536812f75..000000000000 --- a/.github/FUNDING.yml +++ /dev/null @@ -1,12 +0,0 @@ -# These are supported funding model platforms - -github: [cclauss] -patreon: cclauss -open_collective: # Replace with a single Open Collective username -ko_fi: # Replace with a single Ko-fi username -tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel -community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry -liberapay: TheAlgorithms -issuehunt: # Replace with a single IssueHunt username -otechie: # Replace with a single Otechie username -custom: ['http://paypal.me/TheAlgorithms/1000', 'https://donorbox.org/thealgorithms'] From 1e84aabaeaeba3c23e1ed194850ad0f33a7a1d4a Mon Sep 17 00:00:00 2001 From: Sanders Lin <45224617+SandersLin@users.noreply.github.com> Date: Tue, 5 May 2020 23:27:18 +0800 Subject: [PATCH 0545/1071] Create sol2.py (#1876) * Create sol2.py * updating DIRECTORY.md * Update DIRECTORY.md * updating DIRECTORY.md * Update sol2.py * Update DIRECTORY.md * updating DIRECTORY.md * Improve docstrings Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> Co-authored-by: vinayak Co-authored-by: John Law --- DIRECTORY.md | 1 + project_euler/problem_31/sol2.py | 56 ++++++++++++++++++++++++++++++++ 2 files changed, 57 insertions(+) create mode 100644 project_euler/problem_31/sol2.py diff --git a/DIRECTORY.md b/DIRECTORY.md index 631b3a88a12c..94d303afc0d5 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -509,6 +509,7 @@ * [Soln](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_30/soln.py) * Problem 31 * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_31/sol1.py) + * [Sol2](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_31/sol2.py) * Problem 32 * [Sol32](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_32/sol32.py) * Problem 33 diff --git a/project_euler/problem_31/sol2.py b/project_euler/problem_31/sol2.py new file mode 100644 index 000000000000..1f006f1a1824 --- /dev/null +++ b/project_euler/problem_31/sol2.py @@ -0,0 +1,56 @@ +"""Coin sums + +In England the currency is made up of pound, £, and pence, p, and there are +eight coins in general circulation: + +1p, 2p, 5p, 10p, 20p, 50p, £1 (100p) and £2 (200p). +It is possible to make £2 in the following way: + +1×£1 + 1×50p + 2×20p + 1×5p + 1×2p + 3×1p +How many different ways can £2 be made using any number of coins? + +Hint: + > There are 100 pence in a pound (£1 = 100p) + > There are coins(in pence) are available: 1, 2, 5, 10, 20, 50, 100 and 200. + > how many different ways you can combine these values to create 200 pence. + +Example: + to make 6p there are 5 ways + 1,1,1,1,1,1 + 1,1,1,1,2 + 1,1,2,2 + 2,2,2 + 1,5 + to make 5p there are 4 ways + 1,1,1,1,1 + 1,1,1,2 + 1,2,2 + 5 +""" + + +def solution(pence: int) -> int: + """Returns the number of different ways to make X pence using any number of coins. + The solution is based on dynamic programming paradigm in a bottom-up fashion. + + >>> solution(500) + 6295434 + >>> solution(200) + 73682 + >>> solution(50) + 451 + >>> solution(10) + 11 + """ + coins = [1, 2, 5, 10, 20, 50, 100, 200] + number_of_ways = [0] * (pence + 1) + number_of_ways[0] = 1 # base case: 1 way to make 0 pence + + for coin in coins: + for i in range(coin, pence + 1, 1): + number_of_ways[i] += number_of_ways[i - coin] + return number_of_ways[pence] + + +if __name__ == "__main__": + assert solution(200) == 73682 From 08c8bb5ad51e3b25f0662a3834f188b749be077e Mon Sep 17 00:00:00 2001 From: Christian Clauss Date: Wed, 6 May 2020 03:32:40 +0200 Subject: [PATCH 0546/1071] Deal with maps (#1945) * Deal with maps Try with the search term "pizza" to see why this was done in #1932 * fixup! Format Python code with psf/black push * Update armstrong_numbers.py * updating DIRECTORY.md * Update crawl_google_results.py Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> --- DIRECTORY.md | 4 ++++ maths/armstrong_numbers.py | 15 +++++++++------ project_euler/problem_26/sol1.py | 1 + web_programming/crawl_google_results.py | 5 ++++- 4 files changed, 18 insertions(+), 7 deletions(-) diff --git a/DIRECTORY.md b/DIRECTORY.md index 94d303afc0d5..61783b0e5bcf 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -15,6 +15,7 @@ * [All Permutations](https://github.com/TheAlgorithms/Python/blob/master/backtracking/all_permutations.py) * [All Subsequences](https://github.com/TheAlgorithms/Python/blob/master/backtracking/all_subsequences.py) * [Coloring](https://github.com/TheAlgorithms/Python/blob/master/backtracking/coloring.py) + * [Hamiltonian Cycle](https://github.com/TheAlgorithms/Python/blob/master/backtracking/hamiltonian_cycle.py) * [Minimax](https://github.com/TheAlgorithms/Python/blob/master/backtracking/minimax.py) * [N Queens](https://github.com/TheAlgorithms/Python/blob/master/backtracking/n_queens.py) * [Sudoku](https://github.com/TheAlgorithms/Python/blob/master/backtracking/sudoku.py) @@ -89,6 +90,7 @@ * [Number Of Possible Binary Trees](https://github.com/TheAlgorithms/Python/blob/master/data_structures/binary_tree/number_of_possible_binary_trees.py) * [Red Black Tree](https://github.com/TheAlgorithms/Python/blob/master/data_structures/binary_tree/red_black_tree.py) * [Segment Tree](https://github.com/TheAlgorithms/Python/blob/master/data_structures/binary_tree/segment_tree.py) + * [Segment Tree Other](https://github.com/TheAlgorithms/Python/blob/master/data_structures/binary_tree/segment_tree_other.py) * [Treap](https://github.com/TheAlgorithms/Python/blob/master/data_structures/binary_tree/treap.py) * Data Structures * Heap @@ -499,6 +501,8 @@ * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_25/sol1.py) * [Sol2](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_25/sol2.py) * [Sol3](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_25/sol3.py) + * Problem 26 + * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_26/sol1.py) * Problem 27 * [Problem 27 Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_27/problem_27_sol1.py) * Problem 28 diff --git a/maths/armstrong_numbers.py b/maths/armstrong_numbers.py index 39a1464a9aa6..d30ed2e430a0 100644 --- a/maths/armstrong_numbers.py +++ b/maths/armstrong_numbers.py @@ -41,15 +41,18 @@ def armstrong_number(n: int) -> bool: temp //= 10 return n == sum -def narcissistic_number(n:int) -> bool: + +def narcissistic_number(n: int) -> bool: """Return True if n is a narcissistic number or False if it is not""" - - expo = len(str(n)) #power, all number will be raised to - temp = [(int(i)**expo) for i in str(n)] # each digit will be multiplied expo times - - # check if sum of cube of each digit is equal to number + + expo = len(str(n)) # power, all number will be raised to + # each digit will be multiplied expo times + temp = [(int(i) ** expo) for i in str(n)] + + # check if sum of cube of each digit is equal to number return n == sum(temp) + def main(): """ Request that user input an integer and tell them if it is Armstrong number. diff --git a/project_euler/problem_26/sol1.py b/project_euler/problem_26/sol1.py index 7b8c44c9c828..cab8e0eb580b 100644 --- a/project_euler/problem_26/sol1.py +++ b/project_euler/problem_26/sol1.py @@ -5,6 +5,7 @@ in its decimal fraction part. """ + def find_digit(numerator: int, digit: int) -> int: """ Considering any range can be provided, diff --git a/web_programming/crawl_google_results.py b/web_programming/crawl_google_results.py index 79b69e71c6b3..7d2be7c03c22 100644 --- a/web_programming/crawl_google_results.py +++ b/web_programming/crawl_google_results.py @@ -19,4 +19,7 @@ print(len(links)) for link in links: - webbrowser.open(f"http://google.com{link.get('href')}") + if link.text == "Maps": + webbrowser.open(link.get("href")) + else: + webbrowser.open(f"http://google.com{link.get('href')}") From 3d4ccc383a32926c67633379fe629be1257d4daa Mon Sep 17 00:00:00 2001 From: Vipul Rai Date: Wed, 6 May 2020 11:19:44 +0530 Subject: [PATCH 0547/1071] change method name from front to get_front (#1943) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: “Vipul <“vipulrai8891@gmail.com”> --- data_structures/queue/queue_on_list.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data_structures/queue/queue_on_list.py b/data_structures/queue/queue_on_list.py index 4d69461af66a..485cf0b6f7a3 100644 --- a/data_structures/queue/queue_on_list.py +++ b/data_structures/queue/queue_on_list.py @@ -43,7 +43,7 @@ def rotate(self, rotation): """Enqueues {@code item} @return item at front of self.entries""" - def front(self): + def get_front(self): return self.entries[0] """Returns the length of this.entries""" From 4acc28ba55d6bd084dfefbb51bd8de6ac7e894ce Mon Sep 17 00:00:00 2001 From: Maxim R <49735721+mrmaxguns@users.noreply.github.com> Date: Wed, 6 May 2020 12:42:18 -0500 Subject: [PATCH 0548/1071] Added new algorithm: cracking caesar cipher with the chi-squared test (#1950) * added decrypt_caesar_with_chi_squared.py and ran all checks * Updated default parameters Removed mistake with mutable default arguments Co-authored-by: Christian Clauss * Updated handling for optional arguments Co-authored-by: Christian Clauss * Changed return statement to tuple Made function return a tuple instead of a list * Added more doctests * Fixed spelling mistakes * black . - reformatted decrypt_caesar_with_chi_squared.py * Updated if statements to fit the updated code * Minimized amount of lines in the code. Co-authored-by: Christian Clauss --- ciphers/decrypt_caesar_with_chi_squared.py | 228 +++++++++++++++++++++ 1 file changed, 228 insertions(+) create mode 100644 ciphers/decrypt_caesar_with_chi_squared.py diff --git a/ciphers/decrypt_caesar_with_chi_squared.py b/ciphers/decrypt_caesar_with_chi_squared.py new file mode 100644 index 000000000000..3c37631c7b35 --- /dev/null +++ b/ciphers/decrypt_caesar_with_chi_squared.py @@ -0,0 +1,228 @@ +def decrypt_caesar_with_chi_squared( + ciphertext: str, + cipher_alphabet=None, + frequencies_dict=None, + case_sensetive: bool = False, +) -> list: + """ + Basic Usage + =========== + Arguments: + * ciphertext (str): the text to decode (encoded with the caesar cipher) + + Optional Arguments: + * cipher_alphabet (list): the alphabet used for the cipher (each letter is + a string separated by commas) + * frequencies_dict (dict): a dictionary of word frequencies where keys are + the letters and values are a percentage representation of the frequency as + a decimal/float + * case_sensetive (bool): a boolean value: True if the case matters during + decryption, False if it doesn't + + Returns: + * A tuple in the form of: + ( + most_likely_cipher, + most_likely_cipher_chi_squared_value, + decoded_most_likely_cipher + ) + + where... + - most_likely_cipher is an integer representing the shift of the smallest + chi-squared statistic (most likely key) + - most_likely_cipher_chi_squared_value is a float representing the + chi-squared statistic of the most likely shift + - decoded_most_likely_cipher is a string with the decoded cipher + (decoded by the most_likely_cipher key) + + + The Chi-squared test + ==================== + + The caesar cipher + ----------------- + The caesar cipher is a very insecure encryption algorithm, however it has + been used since Julius Caesar. The cipher is a simple substitution cipher + where each character in the plain text is replaced by a character in the + alphabet a certain number of characters after the original character. The + number of characters away is called the shift or key. For example: + + Plain text: hello + Key: 1 + Cipher text: ifmmp + (each letter in hello has been shifted one to the right in the eng. alphabet) + + As you can imagine, this doesn't provide lots of security. In fact + decrypting ciphertext by brute-force is extremely easy even by hand. However + one way to do that is the chi-squared test. + + The chi-squared test + ------------------- + Each letter in the english alphabet has a frequency, or the amount of times + it shows up compared to other letters (usually expressed as a decimal + representing the percentage likelihood). The most common letter in the + english language is "e" with a frequency of 0.11162 or 11.162%. The test is + completed in the following fashion. + + 1. The ciphertext is decoded in a brute force way (every combination of the + 26 possible combinations) + 2. For every combination, for each letter in the combination, the average + amount of times the letter should appear the message is calculated by + multiplying the total number of characters by the frequency of the letter + + For example: + In a message of 100 characters, e should appear around 11.162 times. + + 3. Then, to calculate the margin of error (the amount of times the letter + SHOULD appear with the amount of times the letter DOES appear), we use + the chi-squared test. The following formula is used: + + Let: + - n be the number of times the letter actually appears + - p be the predicted value of the number of times the letter should + appear (see #2) + - let v be the chi-squared test result (referred to here as chi-squared + value/statistic) + + (n - p)^2 + --------- = v + p + + 4. Each chi squared value for each letter is then added up to the total. + The total is the chi-squared statistic for that encryption key. + 5. The encryption key with the lowest chi-squared value is the most likely + to be the decoded answer. + + Further Reading + ================ + + * http://practicalcryptography.com/cryptanalysis/text-characterisation/chi-squared-statistic/ + * https://en.wikipedia.org/wiki/Letter_frequency + * https://en.wikipedia.org/wiki/Chi-squared_test + * https://en.m.wikipedia.org/wiki/Caesar_cipher + + Doctests + ======== + >>> decrypt_caesar_with_chi_squared('dof pz aol jhlzhy jpwoly zv wvwbshy? pa pz avv lhzf av jyhjr!') + (7, 3129.228005747531, 'why is the caesar cipher so popular? it is too easy to crack!') + + >>> decrypt_caesar_with_chi_squared('crybd cdbsxq') + (10, 233.35343938980898, 'short string') + + >>> decrypt_caesar_with_chi_squared(12) + Traceback (most recent call last): + AttributeError: 'int' object has no attribute 'lower' + """ + alphabet_letters = cipher_alphabet or [chr(i) for i in range(97, 123)] + frequencies_dict = frequencies_dict or {} + + if frequencies_dict == {}: + # Frequencies of letters in the english language (how much they show up) + frequencies = { + "a": 0.08497, + "b": 0.01492, + "c": 0.02202, + "d": 0.04253, + "e": 0.11162, + "f": 0.02228, + "g": 0.02015, + "h": 0.06094, + "i": 0.07546, + "j": 0.00153, + "k": 0.01292, + "l": 0.04025, + "m": 0.02406, + "n": 0.06749, + "o": 0.07507, + "p": 0.01929, + "q": 0.00095, + "r": 0.07587, + "s": 0.06327, + "t": 0.09356, + "u": 0.02758, + "v": 0.00978, + "w": 0.02560, + "x": 0.00150, + "y": 0.01994, + "z": 0.00077, + } + else: + # Custom frequencies dictionary + frequencies = frequencies_dict + + if not case_sensetive: + ciphertext = ciphertext.lower() + + # Chi squared statistic values + chi_squared_statistic_values = {} + + # cycle through all of the shifts + for shift in range(len(alphabet_letters)): + decrypted_with_shift = "" + + # decrypt the message with the shift + for letter in ciphertext: + try: + # Try to index the letter in the alphabet + new_key = (alphabet_letters.index(letter) - shift) % len( + alphabet_letters + ) + decrypted_with_shift += alphabet_letters[new_key] + except ValueError: + # Append the character if it isn't in the alphabet + decrypted_with_shift += letter + + chi_squared_statistic = 0 + + # Loop through each letter in the decoded message with the shift + for letter in decrypted_with_shift: + if case_sensetive: + if letter in frequencies: + # Get the amount of times the letter occurs in the message + occurrences = decrypted_with_shift.count(letter) + + # Get the excepcted amount of times the letter should appear based on letter frequencies + expected = frequencies[letter] * occurrences + + # Complete the chi squared statistic formula + chi_letter_value = ((occurrences - expected) ** 2) / expected + + # Add the margin of error to the total chi squared statistic + chi_squared_statistic += chi_letter_value + else: + if letter.lower() in frequencies: + # Get the amount of times the letter occurs in the message + occurrences = decrypted_with_shift.count(letter) + + # Get the excepcted amount of times the letter should appear based on letter frequencies + expected = frequencies[letter] * occurrences + + # Complete the chi squared statistic formula + chi_letter_value = ((occurrences - expected) ** 2) / expected + + # Add the margin of error to the total chi squared statistic + chi_squared_statistic += chi_letter_value + + # Add the data to the chi_squared_statistic_values dictionary + chi_squared_statistic_values[shift] = [ + chi_squared_statistic, + decrypted_with_shift, + ] + + # Get the most likely cipher by finding the cipher with the smallest chi squared statistic + most_likely_cipher = min( + chi_squared_statistic_values, key=chi_squared_statistic_values.get + ) + + # Get all the data from the most likely cipher (key, decoded message) + most_likely_cipher_chi_squared_value = chi_squared_statistic_values[ + most_likely_cipher + ][0] + decoded_most_likely_cipher = chi_squared_statistic_values[most_likely_cipher][1] + + # Return the data on the most likely shift + return ( + most_likely_cipher, + most_likely_cipher_chi_squared_value, + decoded_most_likely_cipher, + ) From 8a8527f1bd0ec2641a1d09c6ace5a73d7f7675f0 Mon Sep 17 00:00:00 2001 From: Jeffin Francis Date: Thu, 7 May 2020 12:23:44 +0530 Subject: [PATCH 0549/1071] Added Lstm example for stock predection (#1908) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Added Lstm example for stock predection * Changes after review * changes after build failed * Add Kiera’s to requirements.txt * requirements.txt: Add keras and tensorflow * psf/black Co-authored-by: Christian Clauss --- machine_learning/lstm/lstm_prediction.py | 56 + machine_learning/lstm/sample_data.csv | 1259 ++++++++++++++++++++++ requirements.txt | 3 +- 3 files changed, 1317 insertions(+), 1 deletion(-) create mode 100644 machine_learning/lstm/lstm_prediction.py create mode 100644 machine_learning/lstm/sample_data.csv diff --git a/machine_learning/lstm/lstm_prediction.py b/machine_learning/lstm/lstm_prediction.py new file mode 100644 index 000000000000..fbf802f4d8ee --- /dev/null +++ b/machine_learning/lstm/lstm_prediction.py @@ -0,0 +1,56 @@ +""" + Create a Long Short Term Memory (LSTM) network model + An LSTM is a type of Recurrent Neural Network (RNN) as discussed at: + * http://colah.github.io/posts/2015-08-Understanding-LSTMs + * https://en.wikipedia.org/wiki/Long_short-term_memory +""" + +from keras.layers import Dense, LSTM +from keras.models import Sequential +import numpy as np +import pandas as pd +from sklearn.preprocessing import MinMaxScaler + + +if __name__ == "__main__": + """ + First part of building a model is to get the data and prepare + it for our model. You can use any dataset for stock prediction + make sure you set the price column on line number 21. Here we + use a dataset which have the price on 3rd column. + """ + df = pd.read_csv("sample_data.csv", header=None) + len_data = df.shape[:1][0] + # If you're using some other dataset input the target column + actual_data = df.iloc[:, 1:2] + actual_data = actual_data.values.reshape(len_data, 1) + actual_data = MinMaxScaler().fit_transform(actual_data) + look_back = 10 + forward_days = 5 + periods = 20 + division = len_data - periods * look_back + train_data = actual_data[:division] + test_data = actual_data[division - look_back :] + train_x, train_y = [], [] + test_x, test_y = [], [] + + for i in range(0, len(train_data) - forward_days - look_back + 1): + train_x.append(train_data[i : i + look_back]) + train_y.append(train_data[i + look_back : i + look_back + forward_days]) + for i in range(0, len(test_data) - forward_days - look_back + 1): + test_x.append(test_data[i : i + look_back]) + test_y.append(test_data[i + look_back : i + look_back + forward_days]) + x_train = np.array(train_x) + x_test = np.array(test_x) + y_train = np.array([list(i.ravel()) for i in train_y]) + y_test = np.array([list(i.ravel()) for i in test_y]) + + model = Sequential() + model.add(LSTM(128, input_shape=(look_back, 1), return_sequences=True)) + model.add(LSTM(64, input_shape=(128, 1))) + model.add(Dense(forward_days)) + model.compile(loss="mean_squared_error", optimizer="adam") + history = model.fit( + x_train, y_train, epochs=150, verbose=1, shuffle=True, batch_size=4 + ) + pred = model.predict(x_test) diff --git a/machine_learning/lstm/sample_data.csv b/machine_learning/lstm/sample_data.csv new file mode 100644 index 000000000000..f94db621f619 --- /dev/null +++ b/machine_learning/lstm/sample_data.csv @@ -0,0 +1,1259 @@ +04/24/2020, 1279.31, 1640394, 1261.17, 1280.4, 1249.45 +04/23/2020, 1276.31, 1566203, 1271.55, 1293.31, 1265.67 +04/22/2020, 1263.21, 2093140, 1245.54, 1285.6133, 1242 +04/21/2020, 1216.34, 2153003, 1247, 1254.27, 1209.71 +04/20/2020, 1266.61, 1695488, 1271, 1281.6, 1261.37 +04/17/2020, 1283.25, 1949042, 1284.85, 1294.43, 1271.23 +04/16/2020, 1263.47, 2518099, 1274.1, 1279, 1242.62 +04/15/2020, 1262.47, 1671703, 1245.61, 1280.46, 1240.4 +04/14/2020, 1269.23, 2470353, 1245.09, 1282.07, 1236.93 +04/13/2020, 1217.56, 1739828, 1209.18, 1220.51, 1187.5984 +04/09/2020, 1211.45, 2175421, 1224.08, 1225.57, 1196.7351 +04/08/2020, 1210.28, 1975135, 1206.5, 1219.07, 1188.16 +04/07/2020, 1186.51, 2387329, 1221, 1225, 1182.23 +04/06/2020, 1186.92, 2664723, 1138, 1194.66, 1130.94 +04/03/2020, 1097.88, 2313400, 1119.015, 1123.54, 1079.81 +04/02/2020, 1120.84, 1964881, 1098.26, 1126.86, 1096.4 +04/01/2020, 1105.62, 2344173, 1122, 1129.69, 1097.45 +03/31/2020, 1162.81, 2487983, 1147.3, 1175.31, 1138.14 +03/30/2020, 1146.82, 2574061, 1125.04, 1151.63, 1096.48 +03/27/2020, 1110.71, 3208495, 1125.67, 1150.6702, 1105.91 +03/26/2020, 1161.75, 3573755, 1111.8, 1169.97, 1093.53 +03/25/2020, 1102.49, 4081528, 1126.47, 1148.9, 1086.01 +03/24/2020, 1134.46, 3344450, 1103.77, 1135, 1090.62 +03/23/2020, 1056.62, 4044137, 1061.32, 1071.32, 1013.5361 +03/20/2020, 1072.32, 3601750, 1135.72, 1143.99, 1065.49 +03/19/2020, 1115.29, 3651106, 1093.05, 1157.9699, 1060.1075 +03/18/2020, 1096.8, 4233435, 1056.51, 1106.5, 1037.28 +03/17/2020, 1119.8, 3861489, 1093.11, 1130.86, 1056.01 +03/16/2020, 1084.33, 4252365, 1096, 1152.2665, 1074.44 +03/13/2020, 1219.73, 3700125, 1179, 1219.76, 1117.1432 +03/12/2020, 1114.91, 4226748, 1126, 1193.87, 1113.3 +03/11/2020, 1215.41, 2611229, 1249.7, 1260.96, 1196.07 +03/10/2020, 1280.39, 2611373, 1260, 1281.15, 1218.77 +03/09/2020, 1215.56, 3365365, 1205.3, 1254.7599, 1200 +03/06/2020, 1298.41, 2660628, 1277.06, 1306.22, 1261.05 +03/05/2020, 1319.04, 2561288, 1350.2, 1358.91, 1305.1 +03/04/2020, 1386.52, 1913315, 1359.23, 1388.09, 1343.11 +03/03/2020, 1341.39, 2402326, 1399.42, 1410.15, 1332 +03/02/2020, 1389.11, 2431468, 1351.61, 1390.87, 1326.815 +02/28/2020, 1339.33, 3790618, 1277.5, 1341.14, 1271 +02/27/2020, 1318.09, 2978300, 1362.06, 1371.7037, 1317.17 +02/26/2020, 1393.18, 2204037, 1396.14, 1415.7, 1379 +02/25/2020, 1388.45, 2478278, 1433, 1438.14, 1382.4 +02/24/2020, 1421.59, 2867053, 1426.11, 1436.97, 1411.39 +02/21/2020, 1485.11, 1732273, 1508.03, 1512.215, 1480.44 +02/20/2020, 1518.15, 1096552, 1522, 1529.64, 1506.82 +02/19/2020, 1526.69, 949268, 1525.07, 1532.1063, 1521.4 +02/18/2020, 1519.67, 1121140, 1515, 1531.63, 1512.59 +02/14/2020, 1520.74, 1197836, 1515.6, 1520.74, 1507.34 +02/13/2020, 1514.66, 929730, 1512.69, 1527.18, 1504.6 +02/12/2020, 1518.27, 1167565, 1514.48, 1520.695, 1508.11 +02/11/2020, 1508.79, 1344633, 1511.81, 1529.63, 1505.6378 +02/10/2020, 1508.68, 1419876, 1474.32, 1509.5, 1474.32 +02/07/2020, 1479.23, 1172270, 1467.3, 1485.84, 1466.35 +02/06/2020, 1476.23, 1679384, 1450.33, 1481.9997, 1449.57 +02/05/2020, 1448.23, 1986157, 1462.42, 1463.84, 1430.56 +02/04/2020, 1447.07, 3932954, 1457.07, 1469.5, 1426.3 +02/03/2020, 1485.94, 3055216, 1462, 1490, 1458.99 +01/31/2020, 1434.23, 2417214, 1468.9, 1470.13, 1428.53 +01/30/2020, 1455.84, 1339421, 1439.96, 1457.28, 1436.4 +01/29/2020, 1458.63, 1078667, 1458.8, 1465.43, 1446.74 +01/28/2020, 1452.56, 1577422, 1443, 1456, 1432.47 +01/27/2020, 1433.9, 1755201, 1431, 1438.07, 1421.2 +01/24/2020, 1466.71, 1784644, 1493.59, 1495.495, 1465.25 +01/23/2020, 1486.65, 1351354, 1487.64, 1495.52, 1482.1 +01/22/2020, 1485.95, 1610846, 1491, 1503.2143, 1484.93 +01/21/2020, 1484.4, 2036780, 1479.12, 1491.85, 1471.2 +01/17/2020, 1480.39, 2396215, 1462.91, 1481.2954, 1458.22 +01/16/2020, 1451.7, 1173688, 1447.44, 1451.99, 1440.92 +01/15/2020, 1439.2, 1282685, 1430.21, 1441.395, 1430.21 +01/14/2020, 1430.88, 1560453, 1439.01, 1441.8, 1428.37 +01/13/2020, 1439.23, 1653482, 1436.13, 1440.52, 1426.02 +01/10/2020, 1429.73, 1821566, 1427.56, 1434.9292, 1418.35 +01/09/2020, 1419.83, 1502664, 1420.57, 1427.33, 1410.27 +01/08/2020, 1404.32, 1529177, 1392.08, 1411.58, 1390.84 +01/07/2020, 1393.34, 1511693, 1397.94, 1402.99, 1390.38 +01/06/2020, 1394.21, 1733149, 1350, 1396.5, 1350 +01/03/2020, 1360.66, 1187006, 1347.86, 1372.5, 1345.5436 +01/02/2020, 1367.37, 1406731, 1341.55, 1368.14, 1341.55 +12/31/2019, 1337.02, 962468, 1330.11, 1338, 1329.085 +12/30/2019, 1336.14, 1051323, 1350, 1353, 1334.02 +12/27/2019, 1351.89, 1038718, 1362.99, 1364.53, 1349.31 +12/26/2019, 1360.4, 667754, 1346.17, 1361.3269, 1344.47 +12/24/2019, 1343.56, 347518, 1348.5, 1350.26, 1342.78 +12/23/2019, 1348.84, 883200, 1355.87, 1359.7999, 1346.51 +12/20/2019, 1349.59, 3316905, 1363.35, 1363.64, 1349 +12/19/2019, 1356.04, 1470112, 1351.82, 1358.1, 1348.985 +12/18/2019, 1352.62, 1657069, 1356.6, 1360.47, 1351 +12/17/2019, 1355.12, 1855259, 1362.89, 1365, 1351.3231 +12/16/2019, 1361.17, 1397451, 1356.5, 1364.68, 1352.67 +12/13/2019, 1347.83, 1550028, 1347.95, 1353.0931, 1343.87 +12/12/2019, 1350.27, 1281722, 1345.94, 1355.775, 1340.5 +12/11/2019, 1345.02, 850796, 1350.84, 1351.2, 1342.67 +12/10/2019, 1344.66, 1094653, 1341.5, 1349.975, 1336.04 +12/09/2019, 1343.56, 1355795, 1338.04, 1359.45, 1337.84 +12/06/2019, 1340.62, 1315510, 1333.44, 1344, 1333.44 +12/05/2019, 1328.13, 1212818, 1328, 1329.3579, 1316.44 +12/04/2019, 1320.54, 1538110, 1307.01, 1325.8, 1304.87 +12/03/2019, 1295.28, 1268647, 1279.57, 1298.461, 1279 +12/02/2019, 1289.92, 1511851, 1301, 1305.83, 1281 +11/29/2019, 1304.96, 586981, 1307.12, 1310.205, 1303.97 +11/27/2019, 1312.99, 996329, 1315, 1318.36, 1309.63 +11/26/2019, 1313.55, 1069795, 1309.86, 1314.8, 1305.09 +11/25/2019, 1306.69, 1036487, 1299.18, 1311.31, 1298.13 +11/22/2019, 1295.34, 1386506, 1305.62, 1308.73, 1291.41 +11/21/2019, 1301.35, 995499, 1301.48, 1312.59, 1293 +11/20/2019, 1303.05, 1309835, 1311.74, 1315, 1291.15 +11/19/2019, 1315.46, 1269372, 1327.7, 1327.7, 1312.8 +11/18/2019, 1320.7, 1488083, 1332.22, 1335.5288, 1317.5 +11/15/2019, 1334.87, 1782955, 1318.94, 1334.88, 1314.2796 +11/14/2019, 1311.46, 1194305, 1297.5, 1317, 1295.65 +11/13/2019, 1298, 853861, 1294.07, 1304.3, 1293.51 +11/12/2019, 1298.8, 1085859, 1300, 1310, 1295.77 +11/11/2019, 1299.19, 1012429, 1303.18, 1306.425, 1297.41 +11/08/2019, 1311.37, 1251916, 1305.28, 1318, 1304.365 +11/07/2019, 1308.86, 2029970, 1294.28, 1323.74, 1294.245 +11/06/2019, 1291.8, 1152977, 1289.46, 1293.73, 1282.5 +11/05/2019, 1292.03, 1282711, 1292.89, 1298.93, 1291.2289 +11/04/2019, 1291.37, 1500964, 1276.45, 1294.13, 1276.355 +11/01/2019, 1273.74, 1670072, 1265, 1274.62, 1260.5 +10/31/2019, 1260.11, 1455651, 1261.28, 1267.67, 1250.8428 +10/30/2019, 1261.29, 1408851, 1252.97, 1269.36, 1252 +10/29/2019, 1262.62, 1886380, 1276.23, 1281.59, 1257.2119 +10/28/2019, 1290, 2613237, 1275.45, 1299.31, 1272.54 +10/25/2019, 1265.13, 1213051, 1251.03, 1269.6, 1250.01 +10/24/2019, 1260.99, 1039868, 1260.9, 1264, 1253.715 +10/23/2019, 1259.13, 928595, 1242.36, 1259.89, 1242.36 +10/22/2019, 1242.8, 1047851, 1247.85, 1250.6, 1241.38 +10/21/2019, 1246.15, 1038042, 1252.26, 1254.6287, 1240.6 +10/18/2019, 1245.49, 1352839, 1253.46, 1258.89, 1241.08 +10/17/2019, 1253.07, 980510, 1250.93, 1263.325, 1249.94 +10/16/2019, 1243.64, 1168174, 1241.17, 1254.74, 1238.45 +10/15/2019, 1243.01, 1395259, 1220.4, 1247.33, 1220.4 +10/14/2019, 1217.14, 882039, 1212.34, 1226.33, 1211.76 +10/11/2019, 1215.45, 1277144, 1222.21, 1228.39, 1213.74 +10/10/2019, 1208.67, 932531, 1198.58, 1215, 1197.34 +10/09/2019, 1202.31, 876632, 1199.35, 1208.35, 1197.63 +10/08/2019, 1189.13, 1141784, 1197.59, 1206.08, 1189.01 +10/07/2019, 1207.68, 867149, 1204.4, 1218.2036, 1203.75 +10/04/2019, 1209, 1183264, 1191.89, 1211.44, 1189.17 +10/03/2019, 1187.83, 1663656, 1180, 1189.06, 1162.43 +10/02/2019, 1176.63, 1639237, 1196.98, 1196.99, 1171.29 +10/01/2019, 1205.1, 1358279, 1219, 1231.23, 1203.58 +09/30/2019, 1219, 1419676, 1220.97, 1226, 1212.3 +09/27/2019, 1225.09, 1354432, 1243.01, 1244.02, 1214.45 +09/26/2019, 1241.39, 1561882, 1241.96, 1245, 1232.268 +09/25/2019, 1246.52, 1593875, 1215.82, 1248.3, 1210.09 +09/24/2019, 1218.76, 1591786, 1240, 1246.74, 1210.68 +09/23/2019, 1234.03, 1075253, 1226, 1239.09, 1224.17 +09/20/2019, 1229.93, 2337269, 1233.12, 1243.32, 1223.08 +09/19/2019, 1238.71, 1000155, 1232.06, 1244.44, 1232.02 +09/18/2019, 1232.41, 1144333, 1227.51, 1235.61, 1216.53 +09/17/2019, 1229.15, 958112, 1230.4, 1235, 1223.69 +09/16/2019, 1231.3, 1053299, 1229.52, 1239.56, 1225.61 +09/13/2019, 1239.56, 1301350, 1231.35, 1240.88, 1227.01 +09/12/2019, 1234.25, 1725908, 1224.3, 1241.86, 1223.02 +09/11/2019, 1220.17, 1307033, 1203.41, 1222.6, 1202.2 +09/10/2019, 1206, 1260115, 1195.15, 1210, 1194.58 +09/09/2019, 1204.41, 1471880, 1204, 1220, 1192.62 +09/06/2019, 1204.93, 1072143, 1208.13, 1212.015, 1202.5222 +09/05/2019, 1211.38, 1408601, 1191.53, 1213.04, 1191.53 +09/04/2019, 1181.41, 1068968, 1176.71, 1183.48, 1171 +09/03/2019, 1168.39, 1480420, 1177.03, 1186.89, 1163.2 +08/30/2019, 1188.1, 1129959, 1198.5, 1198.5, 1183.8026 +08/29/2019, 1192.85, 1088858, 1181.12, 1196.06, 1181.12 +08/28/2019, 1171.02, 802243, 1161.71, 1176.4199, 1157.3 +08/27/2019, 1167.84, 1077452, 1180.53, 1182.4, 1161.45 +08/26/2019, 1168.89, 1226441, 1157.26, 1169.47, 1152.96 +08/23/2019, 1151.29, 1688271, 1181.99, 1194.08, 1147.75 +08/22/2019, 1189.53, 947906, 1194.07, 1198.0115, 1178.58 +08/21/2019, 1191.25, 741053, 1193.15, 1199, 1187.43 +08/20/2019, 1182.69, 915605, 1195.25, 1196.06, 1182.11 +08/19/2019, 1198.45, 1232517, 1190.09, 1206.99, 1190.09 +08/16/2019, 1177.6, 1349436, 1179.55, 1182.72, 1171.81 +08/15/2019, 1167.26, 1224739, 1163.5, 1175.84, 1162.11 +08/14/2019, 1164.29, 1578668, 1176.31, 1182.3, 1160.54 +08/13/2019, 1197.27, 1318009, 1171.46, 1204.78, 1171.46 +08/12/2019, 1174.71, 1003187, 1179.21, 1184.96, 1167.6723 +08/09/2019, 1188.01, 1065658, 1197.99, 1203.88, 1183.603 +08/08/2019, 1204.8, 1467997, 1182.83, 1205.01, 1173.02 +08/07/2019, 1173.99, 1444324, 1156, 1178.4451, 1149.6239 +08/06/2019, 1169.95, 1709374, 1163.31, 1179.96, 1160 +08/05/2019, 1152.32, 2597455, 1170.04, 1175.24, 1140.14 +08/02/2019, 1193.99, 1645067, 1200.74, 1206.9, 1188.94 +08/01/2019, 1209.01, 1698510, 1214.03, 1234.11, 1205.72 +07/31/2019, 1216.68, 1725454, 1223, 1234, 1207.7635 +07/30/2019, 1225.14, 1453263, 1225.41, 1234.87, 1223.3 +07/29/2019, 1239.41, 2223731, 1241.05, 1247.37, 1228.23 +07/26/2019, 1250.41, 4805752, 1224.04, 1265.5499, 1224 +07/25/2019, 1132.12, 2209823, 1137.82, 1141.7, 1120.92 +07/24/2019, 1137.81, 1590101, 1131.9, 1144, 1126.99 +07/23/2019, 1146.21, 1093688, 1144, 1146.9, 1131.8 +07/22/2019, 1138.07, 1301846, 1133.45, 1139.25, 1124.24 +07/19/2019, 1130.1, 1647245, 1148.19, 1151.14, 1129.62 +07/18/2019, 1146.33, 1291281, 1141.74, 1147.605, 1132.73 +07/17/2019, 1146.35, 1170047, 1150.97, 1158.36, 1145.77 +07/16/2019, 1153.58, 1238807, 1146, 1158.58, 1145 +07/15/2019, 1150.34, 903780, 1146.86, 1150.82, 1139.4 +07/12/2019, 1144.9, 863973, 1143.99, 1147.34, 1138.78 +07/11/2019, 1144.21, 1195569, 1143.25, 1153.07, 1139.58 +07/10/2019, 1140.48, 1209466, 1131.22, 1142.05, 1130.97 +07/09/2019, 1124.83, 1330370, 1111.8, 1128.025, 1107.17 +07/08/2019, 1116.35, 1236419, 1125.17, 1125.98, 1111.21 +07/05/2019, 1131.59, 1264540, 1117.8, 1132.88, 1116.14 +07/03/2019, 1121.58, 767011, 1117.41, 1126.76, 1113.86 +07/02/2019, 1111.25, 991755, 1102.24, 1111.77, 1098.17 +07/01/2019, 1097.95, 1438504, 1098, 1107.58, 1093.703 +06/28/2019, 1080.91, 1693450, 1076.39, 1081, 1073.37 +06/27/2019, 1076.01, 1004477, 1084, 1087.1, 1075.29 +06/26/2019, 1079.8, 1810869, 1086.5, 1092.97, 1072.24 +06/25/2019, 1086.35, 1546913, 1112.66, 1114.35, 1083.8 +06/24/2019, 1115.52, 1395696, 1119.61, 1122, 1111.01 +06/21/2019, 1121.88, 1947591, 1109.24, 1124.11, 1108.08 +06/20/2019, 1111.42, 1262011, 1119.99, 1120.12, 1104.74 +06/19/2019, 1102.33, 1339218, 1105.6, 1107, 1093.48 +06/18/2019, 1103.6, 1386684, 1109.69, 1116.39, 1098.99 +06/17/2019, 1092.5, 941602, 1086.28, 1099.18, 1086.28 +06/14/2019, 1085.35, 1111643, 1086.42, 1092.69, 1080.1721 +06/13/2019, 1088.77, 1058000, 1083.64, 1094.17, 1080.15 +06/12/2019, 1077.03, 1061255, 1078, 1080.93, 1067.54 +06/11/2019, 1078.72, 1437063, 1093.98, 1101.99, 1077.6025 +06/10/2019, 1080.38, 1464248, 1072.98, 1092.66, 1072.3216 +06/07/2019, 1066.04, 1802370, 1050.63, 1070.92, 1048.4 +06/06/2019, 1044.34, 1703244, 1044.99, 1047.49, 1033.7 +06/05/2019, 1042.22, 2168439, 1051.54, 1053.55, 1030.49 +06/04/2019, 1053.05, 2833483, 1042.9, 1056.05, 1033.69 +06/03/2019, 1036.23, 5130576, 1065.5, 1065.5, 1025 +05/31/2019, 1103.63, 1508203, 1101.29, 1109.6, 1100.18 +05/30/2019, 1117.95, 951873, 1115.54, 1123.13, 1112.12 +05/29/2019, 1116.46, 1538212, 1127.52, 1129.1, 1108.2201 +05/28/2019, 1134.15, 1365166, 1134, 1151.5871, 1133.12 +05/24/2019, 1133.47, 1112341, 1147.36, 1149.765, 1131.66 +05/23/2019, 1140.77, 1199300, 1140.5, 1145.9725, 1129.224 +05/22/2019, 1151.42, 914839, 1146.75, 1158.52, 1145.89 +05/21/2019, 1149.63, 1160158, 1148.49, 1152.7077, 1137.94 +05/20/2019, 1138.85, 1353292, 1144.5, 1146.7967, 1131.4425 +05/17/2019, 1162.3, 1208623, 1168.47, 1180.15, 1160.01 +05/16/2019, 1178.98, 1531404, 1164.51, 1188.16, 1162.84 +05/15/2019, 1164.21, 2289302, 1117.87, 1171.33, 1116.6657 +05/14/2019, 1120.44, 1836604, 1137.21, 1140.42, 1119.55 +05/13/2019, 1132.03, 1860648, 1141.96, 1147.94, 1122.11 +05/10/2019, 1164.27, 1314546, 1163.59, 1172.6, 1142.5 +05/09/2019, 1162.38, 1185973, 1159.03, 1169.66, 1150.85 +05/08/2019, 1166.27, 1309514, 1172.01, 1180.4243, 1165.74 +05/07/2019, 1174.1, 1551368, 1180.47, 1190.44, 1161.04 +05/06/2019, 1189.39, 1563943, 1166.26, 1190.85, 1166.26 +05/03/2019, 1185.4, 1980653, 1173.65, 1186.8, 1169 +05/02/2019, 1162.61, 1944817, 1167.76, 1174.1895, 1155.0018 +05/01/2019, 1168.08, 2642983, 1188.05, 1188.05, 1167.18 +04/30/2019, 1188.48, 6194691, 1185, 1192.81, 1175 +04/29/2019, 1287.58, 2412788, 1274, 1289.27, 1266.2949 +04/26/2019, 1272.18, 1228276, 1269, 1273.07, 1260.32 +04/25/2019, 1263.45, 1099614, 1264.77, 1267.4083, 1252.03 +04/24/2019, 1256, 1015006, 1264.12, 1268.01, 1255 +04/23/2019, 1264.55, 1271195, 1250.69, 1269, 1246.38 +04/22/2019, 1248.84, 806577, 1235.99, 1249.09, 1228.31 +04/18/2019, 1236.37, 1315676, 1239.18, 1242, 1234.61 +04/17/2019, 1236.34, 1211866, 1233, 1240.56, 1227.82 +04/16/2019, 1227.13, 855258, 1225, 1230.82, 1220.12 +04/15/2019, 1221.1, 1187353, 1218, 1224.2, 1209.1101 +04/12/2019, 1217.87, 926799, 1210, 1218.35, 1208.11 +04/11/2019, 1204.62, 709417, 1203.96, 1207.96, 1200.13 +04/10/2019, 1202.16, 724524, 1200.68, 1203.785, 1196.435 +04/09/2019, 1197.25, 865416, 1196, 1202.29, 1193.08 +04/08/2019, 1203.84, 859969, 1207.89, 1208.69, 1199.86 +04/05/2019, 1207.15, 900950, 1214.99, 1216.22, 1205.03 +04/04/2019, 1215, 949962, 1205.94, 1215.67, 1204.13 +04/03/2019, 1205.92, 1014195, 1207.48, 1216.3, 1200.5 +04/02/2019, 1200.49, 800820, 1195.32, 1201.35, 1185.71 +04/01/2019, 1194.43, 1188235, 1184.1, 1196.66, 1182 +03/29/2019, 1173.31, 1269573, 1174.9, 1178.99, 1162.88 +03/28/2019, 1168.49, 966843, 1171.54, 1171.565, 1159.4312 +03/27/2019, 1173.02, 1362217, 1185.5, 1187.559, 1159.37 +03/26/2019, 1184.62, 1894639, 1198.53, 1202.83, 1176.72 +03/25/2019, 1193, 1493841, 1196.93, 1206.3975, 1187.04 +03/22/2019, 1205.5, 1668910, 1226.32, 1230, 1202.825 +03/21/2019, 1231.54, 1195899, 1216, 1231.79, 1213.15 +03/20/2019, 1223.97, 2089367, 1197.35, 1227.14, 1196.17 +03/19/2019, 1198.85, 1404863, 1188.81, 1200, 1185.87 +03/18/2019, 1184.26, 1212506, 1183.3, 1190, 1177.4211 +03/15/2019, 1184.46, 2457597, 1193.38, 1196.57, 1182.61 +03/14/2019, 1185.55, 1150950, 1194.51, 1197.88, 1184.48 +03/13/2019, 1193.32, 1434816, 1200.645, 1200.93, 1191.94 +03/12/2019, 1193.2, 2012306, 1178.26, 1200, 1178.26 +03/11/2019, 1175.76, 1569332, 1144.45, 1176.19, 1144.45 +03/08/2019, 1142.32, 1212271, 1126.73, 1147.08, 1123.3 +03/07/2019, 1143.3, 1166076, 1155.72, 1156.755, 1134.91 +03/06/2019, 1157.86, 1094100, 1162.49, 1167.5658, 1155.49 +03/05/2019, 1162.03, 1422357, 1150.06, 1169.61, 1146.195 +03/04/2019, 1147.8, 1444774, 1146.99, 1158.2804, 1130.69 +03/01/2019, 1140.99, 1447454, 1124.9, 1142.97, 1124.75 +02/28/2019, 1119.92, 1541068, 1111.3, 1127.65, 1111.01 +02/27/2019, 1116.05, 968362, 1106.95, 1117.98, 1101 +02/26/2019, 1115.13, 1469761, 1105.75, 1119.51, 1099.92 +02/25/2019, 1109.4, 1395281, 1116, 1118.54, 1107.27 +02/22/2019, 1110.37, 1048361, 1100.9, 1111.24, 1095.6 +02/21/2019, 1096.97, 1414744, 1110.84, 1111.94, 1092.52 +02/20/2019, 1113.8, 1080144, 1119.99, 1123.41, 1105.28 +02/19/2019, 1118.56, 1046315, 1110, 1121.89, 1110 +02/15/2019, 1113.65, 1442461, 1130.08, 1131.67, 1110.65 +02/14/2019, 1121.67, 941678, 1118.05, 1128.23, 1110.445 +02/13/2019, 1120.16, 1048630, 1124.99, 1134.73, 1118.5 +02/12/2019, 1121.37, 1608658, 1106.8, 1125.295, 1105.85 +02/11/2019, 1095.01, 1063825, 1096.95, 1105.945, 1092.86 +02/08/2019, 1095.06, 1072031, 1087, 1098.91, 1086.55 +02/07/2019, 1098.71, 2040615, 1104.16, 1104.84, 1086 +02/06/2019, 1115.23, 2101674, 1139.57, 1147, 1112.77 +02/05/2019, 1145.99, 3529974, 1124.84, 1146.85, 1117.248 +02/04/2019, 1132.8, 2518184, 1112.66, 1132.8, 1109.02 +02/01/2019, 1110.75, 1455609, 1112.4, 1125, 1104.89 +01/31/2019, 1116.37, 1531463, 1103, 1117.33, 1095.41 +01/30/2019, 1089.06, 1241760, 1068.43, 1091, 1066.85 +01/29/2019, 1060.62, 1006731, 1072.68, 1075.15, 1055.8647 +01/28/2019, 1070.08, 1277745, 1080.11, 1083, 1063.8 +01/25/2019, 1090.99, 1114785, 1085, 1094, 1081.82 +01/24/2019, 1073.9, 1317718, 1076.48, 1079.475, 1060.7 +01/23/2019, 1075.57, 956526, 1077.35, 1084.93, 1059.75 +01/22/2019, 1070.52, 1607398, 1088, 1091.51, 1063.47 +01/18/2019, 1098.26, 1933754, 1100, 1108.352, 1090.9 +01/17/2019, 1089.9, 1223674, 1079.47, 1091.8, 1073.5 +01/16/2019, 1080.97, 1320530, 1080, 1092.375, 1079.34 +01/15/2019, 1077.15, 1452238, 1050.17, 1080.05, 1047.34 +01/14/2019, 1044.69, 1127417, 1046.92, 1051.53, 1041.255 +01/11/2019, 1057.19, 1512651, 1063.18, 1063.775, 1048.48 +01/10/2019, 1070.33, 1444976, 1067.66, 1071.15, 1057.71 +01/09/2019, 1074.66, 1198369, 1081.65, 1082.63, 1066.4 +01/08/2019, 1076.28, 1748371, 1076.11, 1084.56, 1060.53 +01/07/2019, 1068.39, 1978077, 1071.5, 1073.9999, 1054.76 +01/04/2019, 1070.71, 2080144, 1032.59, 1070.84, 1027.4179 +01/03/2019, 1016.06, 1829379, 1041, 1056.98, 1014.07 +01/02/2019, 1045.85, 1516681, 1016.57, 1052.32, 1015.71 +12/31/2018, 1035.61, 1492541, 1050.96, 1052.7, 1023.59 +12/28/2018, 1037.08, 1399218, 1049.62, 1055.56, 1033.1 +12/27/2018, 1043.88, 2102069, 1017.15, 1043.89, 997 +12/26/2018, 1039.46, 2337212, 989.01, 1040, 983 +12/24/2018, 976.22, 1590328, 973.9, 1003.54, 970.11 +12/21/2018, 979.54, 4560424, 1015.3, 1024.02, 973.69 +12/20/2018, 1009.41, 2659047, 1018.13, 1034.22, 996.36 +12/19/2018, 1023.01, 2419322, 1033.99, 1062, 1008.05 +12/18/2018, 1028.71, 2101854, 1026.09, 1049.48, 1021.44 +12/17/2018, 1016.53, 2337631, 1037.51, 1053.15, 1007.9 +12/14/2018, 1042.1, 1685802, 1049.98, 1062.6, 1040.79 +12/13/2018, 1061.9, 1329198, 1068.07, 1079.7597, 1053.93 +12/12/2018, 1063.68, 1523276, 1068, 1081.65, 1062.79 +12/11/2018, 1051.75, 1354751, 1056.49, 1060.6, 1039.84 +12/10/2018, 1039.55, 1793465, 1035.05, 1048.45, 1023.29 +12/07/2018, 1036.58, 2098526, 1060.01, 1075.26, 1028.5 +12/06/2018, 1068.73, 2758098, 1034.26, 1071.2, 1030.7701 +12/04/2018, 1050.82, 2278200, 1103.12, 1104.42, 1049.98 +12/03/2018, 1106.43, 1900355, 1123.14, 1124.65, 1103.6645 +11/30/2018, 1094.43, 2554416, 1089.07, 1095.57, 1077.88 +11/29/2018, 1088.3, 1403540, 1076.08, 1094.245, 1076 +11/28/2018, 1086.23, 2399374, 1048.76, 1086.84, 1035.76 +11/27/2018, 1044.41, 1801334, 1041, 1057.58, 1038.49 +11/26/2018, 1048.62, 1846430, 1038.35, 1049.31, 1033.91 +11/23/2018, 1023.88, 691462, 1030, 1037.59, 1022.3992 +11/21/2018, 1037.61, 1531676, 1036.76, 1048.56, 1033.47 +11/20/2018, 1025.76, 2447254, 1000, 1031.74, 996.02 +11/19/2018, 1020, 1837207, 1057.2, 1060.79, 1016.2601 +11/16/2018, 1061.49, 1641232, 1059.41, 1067, 1048.98 +11/15/2018, 1064.71, 1819132, 1044.71, 1071.85, 1031.78 +11/14/2018, 1043.66, 1561656, 1050, 1054.5643, 1031 +11/13/2018, 1036.05, 1496534, 1043.29, 1056.605, 1031.15 +11/12/2018, 1038.63, 1429319, 1061.39, 1062.12, 1031 +11/09/2018, 1066.15, 1343154, 1073.99, 1075.56, 1053.11 +11/08/2018, 1082.4, 1463022, 1091.38, 1093.27, 1072.2048 +11/07/2018, 1093.39, 2057155, 1069, 1095.46, 1065.9 +11/06/2018, 1055.81, 1225197, 1039.48, 1064.345, 1038.07 +11/05/2018, 1040.09, 2436742, 1055, 1058.47, 1021.24 +11/02/2018, 1057.79, 1829295, 1073.73, 1082.975, 1054.61 +11/01/2018, 1070, 1456222, 1075.8, 1083.975, 1062.46 +10/31/2018, 1076.77, 2528584, 1059.81, 1091.94, 1057 +10/30/2018, 1036.21, 3209126, 1008.46, 1037.49, 1000.75 +10/29/2018, 1020.08, 3873644, 1082.47, 1097.04, 995.83 +10/26/2018, 1071.47, 4185201, 1037.03, 1106.53, 1034.09 +10/25/2018, 1095.57, 2511884, 1071.79, 1110.98, 1069.55 +10/24/2018, 1050.71, 1910060, 1104.25, 1106.12, 1048.74 +10/23/2018, 1103.69, 1847798, 1080.89, 1107.89, 1070 +10/22/2018, 1101.16, 1494285, 1103.06, 1112.23, 1091 +10/19/2018, 1096.46, 1264605, 1093.37, 1110.36, 1087.75 +10/18/2018, 1087.97, 2056606, 1121.84, 1121.84, 1077.09 +10/17/2018, 1115.69, 1397613, 1126.46, 1128.99, 1102.19 +10/16/2018, 1121.28, 1845491, 1104.59, 1124.22, 1102.5 +10/15/2018, 1092.25, 1343231, 1108.91, 1113.4464, 1089 +10/12/2018, 1110.08, 2029872, 1108, 1115, 1086.402 +10/11/2018, 1079.32, 2939514, 1072.94, 1106.4, 1068.27 +10/10/2018, 1081.22, 2574985, 1131.08, 1132.17, 1081.13 +10/09/2018, 1138.82, 1308706, 1146.15, 1154.35, 1137.572 +10/08/2018, 1148.97, 1877142, 1150.11, 1168, 1127.3636 +10/05/2018, 1157.35, 1184245, 1167.5, 1173.4999, 1145.12 +10/04/2018, 1168.19, 2151762, 1195.33, 1197.51, 1155.576 +10/03/2018, 1202.95, 1207280, 1205, 1206.41, 1193.83 +10/02/2018, 1200.11, 1655602, 1190.96, 1209.96, 1186.63 +10/01/2018, 1195.31, 1345250, 1199.89, 1209.9, 1190.3 +09/28/2018, 1193.47, 1306822, 1191.87, 1195.41, 1184.5 +09/27/2018, 1194.64, 1244278, 1186.73, 1202.1, 1183.63 +09/26/2018, 1180.49, 1346434, 1185.15, 1194.23, 1174.765 +09/25/2018, 1184.65, 937577, 1176.15, 1186.88, 1168 +09/24/2018, 1173.37, 1218532, 1157.17, 1178, 1146.91 +09/21/2018, 1166.09, 4363929, 1192, 1192.21, 1166.04 +09/20/2018, 1186.87, 1209855, 1179.99, 1189.89, 1173.36 +09/19/2018, 1171.09, 1185321, 1164.98, 1173.21, 1154.58 +09/18/2018, 1161.22, 1184407, 1157.09, 1176.08, 1157.09 +09/17/2018, 1156.05, 1279147, 1170.14, 1177.24, 1154.03 +09/14/2018, 1172.53, 934300, 1179.1, 1180.425, 1168.3295 +09/13/2018, 1175.33, 1402005, 1170.74, 1178.61, 1162.85 +09/12/2018, 1162.82, 1291304, 1172.72, 1178.61, 1158.36 +09/11/2018, 1177.36, 1209171, 1161.63, 1178.68, 1156.24 +09/10/2018, 1164.64, 1115259, 1172.19, 1174.54, 1160.11 +09/07/2018, 1164.83, 1401034, 1158.67, 1175.26, 1157.215 +09/06/2018, 1171.44, 1886690, 1186.3, 1186.3, 1152 +09/05/2018, 1186.48, 2043732, 1193.8, 1199.0096, 1162 +09/04/2018, 1197, 1800509, 1204.27, 1212.99, 1192.5 +08/31/2018, 1218.19, 1812366, 1234.98, 1238.66, 1211.2854 +08/30/2018, 1239.12, 1320261, 1244.23, 1253.635, 1232.59 +08/29/2018, 1249.3, 1295939, 1237.45, 1250.66, 1236.3588 +08/28/2018, 1231.15, 1296532, 1241.29, 1242.545, 1228.69 +08/27/2018, 1241.82, 1154962, 1227.6, 1243.09, 1225.716 +08/24/2018, 1220.65, 946529, 1208.82, 1221.65, 1206.3588 +08/23/2018, 1205.38, 988509, 1207.14, 1221.28, 1204.24 +08/22/2018, 1207.33, 881463, 1200, 1211.84, 1199 +08/21/2018, 1201.62, 1187884, 1208, 1217.26, 1200.3537 +08/20/2018, 1207.77, 864462, 1205.02, 1211, 1194.6264 +08/17/2018, 1200.96, 1381724, 1202.03, 1209.02, 1188.24 +08/16/2018, 1206.49, 1319985, 1224.73, 1225.9999, 1202.55 +08/15/2018, 1214.38, 1815642, 1229.26, 1235.24, 1209.51 +08/14/2018, 1242.1, 1342534, 1235.19, 1245.8695, 1225.11 +08/13/2018, 1235.01, 957153, 1236.98, 1249.2728, 1233.6405 +08/10/2018, 1237.61, 1107323, 1243, 1245.695, 1232 +08/09/2018, 1249.1, 805227, 1249.9, 1255.542, 1246.01 +08/08/2018, 1245.61, 1369650, 1240.47, 1256.5, 1238.0083 +08/07/2018, 1242.22, 1493073, 1237, 1251.17, 1236.17 +08/06/2018, 1224.77, 1080923, 1225, 1226.0876, 1215.7965 +08/03/2018, 1223.71, 1072524, 1229.62, 1230, 1215.06 +08/02/2018, 1226.15, 1520488, 1205.9, 1229.88, 1204.79 +08/01/2018, 1220.01, 1567142, 1228, 1233.47, 1210.21 +07/31/2018, 1217.26, 1632823, 1220.01, 1227.5877, 1205.6 +07/30/2018, 1219.74, 1822782, 1228.01, 1234.916, 1211.47 +07/27/2018, 1238.5, 2115802, 1271, 1273.89, 1231 +07/26/2018, 1268.33, 2334881, 1251, 1269.7707, 1249.02 +07/25/2018, 1263.7, 2115890, 1239.13, 1265.86, 1239.13 +07/24/2018, 1248.08, 3303268, 1262.59, 1266, 1235.56 +07/23/2018, 1205.5, 2584034, 1181.01, 1206.49, 1181 +07/20/2018, 1184.91, 1246898, 1186.96, 1196.86, 1184.22 +07/19/2018, 1186.96, 1256113, 1191, 1200, 1183.32 +07/18/2018, 1195.88, 1391232, 1196.56, 1204.5, 1190.34 +07/17/2018, 1198.8, 1585091, 1172.22, 1203.04, 1170.6 +07/16/2018, 1183.86, 1049560, 1189.39, 1191, 1179.28 +07/13/2018, 1188.82, 1221687, 1185, 1195.4173, 1180 +07/12/2018, 1183.48, 1251083, 1159.89, 1184.41, 1155.935 +07/11/2018, 1153.9, 1094301, 1144.59, 1164.29, 1141.0003 +07/10/2018, 1152.84, 789249, 1156.98, 1159.59, 1149.59 +07/09/2018, 1154.05, 906073, 1148.48, 1154.67, 1143.42 +07/06/2018, 1140.17, 966155, 1123.58, 1140.93, 1120.7371 +07/05/2018, 1124.27, 1060752, 1110.53, 1127.5, 1108.48 +07/03/2018, 1102.89, 679034, 1135.82, 1135.82, 1100.02 +07/02/2018, 1127.46, 1188616, 1099, 1128, 1093.8 +06/29/2018, 1115.65, 1275979, 1120, 1128.2265, 1115 +06/28/2018, 1114.22, 1072438, 1102.09, 1122.31, 1096.01 +06/27/2018, 1103.98, 1287698, 1121.34, 1131.8362, 1103.62 +06/26/2018, 1118.46, 1559791, 1128, 1133.21, 1116.6589 +06/25/2018, 1124.81, 2155276, 1143.6, 1143.91, 1112.78 +06/22/2018, 1155.48, 1310164, 1159.14, 1162.4965, 1147.26 +06/21/2018, 1157.66, 1232352, 1174.85, 1177.295, 1152.232 +06/20/2018, 1169.84, 1648248, 1175.31, 1186.2856, 1169.16 +06/19/2018, 1168.06, 1616125, 1158.5, 1171.27, 1154.01 +06/18/2018, 1173.46, 1400641, 1143.65, 1174.31, 1143.59 +06/15/2018, 1152.26, 2119134, 1148.86, 1153.42, 1143.485 +06/14/2018, 1152.12, 1350085, 1143.85, 1155.47, 1140.64 +06/13/2018, 1134.79, 1490017, 1141.12, 1146.5, 1133.38 +06/12/2018, 1139.32, 899231, 1131.07, 1139.79, 1130.735 +06/11/2018, 1129.99, 1071114, 1118.6, 1137.26, 1118.6 +06/08/2018, 1120.87, 1289859, 1118.18, 1126.67, 1112.15 +06/07/2018, 1123.86, 1519860, 1131.32, 1135.82, 1116.52 +06/06/2018, 1136.88, 1697489, 1142.17, 1143, 1125.7429 +06/05/2018, 1139.66, 1538169, 1140.99, 1145.738, 1133.19 +06/04/2018, 1139.29, 1881046, 1122.33, 1141.89, 1122.005 +06/01/2018, 1119.5, 2416755, 1099.35, 1120, 1098.5 +05/31/2018, 1084.99, 3085325, 1067.56, 1097.19, 1067.56 +05/30/2018, 1067.8, 1129958, 1063.03, 1069.21, 1056.83 +05/29/2018, 1060.32, 1858676, 1064.89, 1073.37, 1055.22 +05/25/2018, 1075.66, 878903, 1079.02, 1082.56, 1073.775 +05/24/2018, 1079.24, 757752, 1079, 1080.47, 1066.15 +05/23/2018, 1079.69, 1057712, 1065.13, 1080.78, 1061.71 +05/22/2018, 1069.73, 1088700, 1083.56, 1086.59, 1066.69 +05/21/2018, 1079.58, 1012258, 1074.06, 1088, 1073.65 +05/18/2018, 1066.36, 1496448, 1061.86, 1069.94, 1060.68 +05/17/2018, 1078.59, 1031190, 1079.89, 1086.87, 1073.5 +05/16/2018, 1081.77, 989819, 1077.31, 1089.27, 1076.26 +05/15/2018, 1079.23, 1494306, 1090, 1090.05, 1073.47 +05/14/2018, 1100.2, 1450140, 1100, 1110.75, 1099.11 +05/11/2018, 1098.26, 1253205, 1093.6, 1101.3295, 1090.91 +05/10/2018, 1097.57, 1441456, 1086.03, 1100.44, 1085.64 +05/09/2018, 1082.76, 2032319, 1058.1, 1085.44, 1056.365 +05/08/2018, 1053.91, 1217260, 1058.54, 1060.55, 1047.145 +05/07/2018, 1054.79, 1464008, 1049.23, 1061.68, 1047.1 +05/04/2018, 1048.21, 1936797, 1016.9, 1048.51, 1016.9 +05/03/2018, 1023.72, 1813623, 1019, 1029.675, 1006.29 +05/02/2018, 1024.38, 1534094, 1028.1, 1040.389, 1022.87 +05/01/2018, 1037.31, 1427171, 1013.66, 1038.47, 1008.21 +04/30/2018, 1017.33, 1664084, 1030.01, 1037, 1016.85 +04/27/2018, 1030.05, 1617452, 1046, 1049.5, 1025.59 +04/26/2018, 1040.04, 1984448, 1029.51, 1047.98, 1018.19 +04/25/2018, 1021.18, 2225495, 1025.52, 1032.49, 1015.31 +04/24/2018, 1019.98, 4750851, 1052, 1057, 1010.59 +04/23/2018, 1067.45, 2278846, 1077.86, 1082.72, 1060.7 +04/20/2018, 1072.96, 1887698, 1082, 1092.35, 1069.57 +04/19/2018, 1087.7, 1741907, 1069.4, 1094.165, 1068.18 +04/18/2018, 1072.08, 1336678, 1077.43, 1077.43, 1066.225 +04/17/2018, 1074.16, 2311903, 1051.37, 1077.88, 1048.26 +04/16/2018, 1037.98, 1194144, 1037, 1043.24, 1026.74 +04/13/2018, 1029.27, 1175754, 1040.88, 1046.42, 1022.98 +04/12/2018, 1032.51, 1357599, 1025.04, 1040.69, 1021.4347 +04/11/2018, 1019.97, 1476133, 1027.99, 1031.3641, 1015.87 +04/10/2018, 1031.64, 1983510, 1026.44, 1036.28, 1011.34 +04/09/2018, 1015.45, 1738682, 1016.8, 1039.6, 1014.08 +04/06/2018, 1007.04, 1740896, 1020, 1031.42, 1003.03 +04/05/2018, 1027.81, 1345681, 1041.33, 1042.79, 1020.1311 +04/04/2018, 1025.14, 2464418, 993.41, 1028.7175, 993 +04/03/2018, 1013.41, 2271858, 1013.91, 1020.99, 994.07 +04/02/2018, 1006.47, 2679214, 1022.82, 1034.8, 990.37 +03/29/2018, 1031.79, 2714402, 1011.63, 1043, 1002.9 +03/28/2018, 1004.56, 3345046, 998, 1024.23, 980.64 +03/27/2018, 1005.1, 3081612, 1063, 1064.8393, 996.92 +03/26/2018, 1053.21, 2593808, 1046, 1055.63, 1008.4 +03/23/2018, 1021.57, 2147097, 1047.03, 1063.36, 1021.22 +03/22/2018, 1049.08, 2584639, 1081.88, 1082.9, 1045.91 +03/21/2018, 1090.88, 1878294, 1092.74, 1106.2999, 1085.15 +03/20/2018, 1097.71, 1802209, 1099, 1105.2, 1083.46 +03/19/2018, 1099.82, 2355186, 1120.01, 1121.99, 1089.01 +03/16/2018, 1135.73, 2614871, 1154.14, 1155.88, 1131.96 +03/15/2018, 1149.58, 1397767, 1149.96, 1161.08, 1134.54 +03/14/2018, 1149.49, 1290638, 1145.21, 1158.59, 1141.44 +03/13/2018, 1138.17, 1874176, 1170, 1176.76, 1133.33 +03/12/2018, 1164.5, 2106548, 1163.85, 1177.05, 1157.42 +03/09/2018, 1160.04, 2121425, 1136, 1160.8, 1132.4606 +03/08/2018, 1126, 1393529, 1115.32, 1127.6, 1112.8 +03/07/2018, 1109.64, 1277439, 1089.19, 1112.22, 1085.4823 +03/06/2018, 1095.06, 1497087, 1099.22, 1101.85, 1089.775 +03/05/2018, 1090.93, 1141932, 1075.14, 1097.1, 1069.0001 +03/02/2018, 1078.92, 2271394, 1053.08, 1081.9986, 1048.115 +03/01/2018, 1069.52, 2511872, 1107.87, 1110.12, 1067.001 +02/28/2018, 1104.73, 1873737, 1123.03, 1127.53, 1103.24 +02/27/2018, 1118.29, 1772866, 1141.24, 1144.04, 1118 +02/26/2018, 1143.75, 1514920, 1127.8, 1143.96, 1126.695 +02/23/2018, 1126.79, 1190432, 1112.64, 1127.28, 1104.7135 +02/22/2018, 1106.63, 1309536, 1116.19, 1122.82, 1102.59 +02/21/2018, 1111.34, 1507152, 1106.47, 1133.97, 1106.33 +02/20/2018, 1102.46, 1389491, 1090.57, 1113.95, 1088.52 +02/16/2018, 1094.8, 1680283, 1088.41, 1104.67, 1088.3134 +02/15/2018, 1089.52, 1785552, 1079.07, 1091.4794, 1064.34 +02/14/2018, 1069.7, 1547665, 1048.95, 1071.72, 1046.75 +02/13/2018, 1052.1, 1213800, 1045, 1058.37, 1044.0872 +02/12/2018, 1051.94, 2054002, 1048, 1061.5, 1040.928 +02/09/2018, 1037.78, 3503970, 1017.25, 1043.97, 992.56 +02/08/2018, 1001.52, 2809890, 1055.41, 1058.62, 1000.66 +02/07/2018, 1048.58, 2353003, 1081.54, 1081.78, 1048.26 +02/06/2018, 1080.6, 3432313, 1027.18, 1081.71, 1023.1367 +02/05/2018, 1055.8, 3769453, 1090.6, 1110, 1052.03 +02/02/2018, 1111.9, 4837979, 1122, 1123.07, 1107.2779 +02/01/2018, 1167.7, 2380221, 1162.61, 1174, 1157.52 +01/31/2018, 1169.94, 1523820, 1170.57, 1173, 1159.13 +01/30/2018, 1163.69, 1541771, 1167.83, 1176.52, 1163.52 +01/29/2018, 1175.58, 1337324, 1176.48, 1186.89, 1171.98 +01/26/2018, 1175.84, 1981173, 1175.08, 1175.84, 1158.11 +01/25/2018, 1170.37, 1461518, 1172.53, 1175.94, 1162.76 +01/24/2018, 1164.24, 1382904, 1177.33, 1179.86, 1161.05 +01/23/2018, 1169.97, 1309862, 1159.85, 1171.6266, 1158.75 +01/22/2018, 1155.81, 1616120, 1137.49, 1159.88, 1135.1101 +01/19/2018, 1137.51, 1390118, 1131.83, 1137.86, 1128.3 +01/18/2018, 1129.79, 1194943, 1131.41, 1132.51, 1117.5 +01/17/2018, 1131.98, 1200476, 1126.22, 1132.6, 1117.01 +01/16/2018, 1121.76, 1566662, 1132.51, 1139.91, 1117.8316 +01/12/2018, 1122.26, 1718491, 1102.41, 1124.29, 1101.15 +01/11/2018, 1105.52, 977727, 1106.3, 1106.525, 1099.59 +01/10/2018, 1102.61, 1042273, 1097.1, 1104.6, 1096.11 +01/09/2018, 1106.26, 900089, 1109.4, 1110.57, 1101.2307 +01/08/2018, 1106.94, 1046767, 1102.23, 1111.27, 1101.62 +01/05/2018, 1102.23, 1279990, 1094, 1104.25, 1092 +01/04/2018, 1086.4, 1002945, 1088, 1093.5699, 1084.0017 +01/03/2018, 1082.48, 1429757, 1064.31, 1086.29, 1063.21 +01/02/2018, 1065, 1236401, 1048.34, 1066.94, 1045.23 +12/29/2017, 1046.4, 886845, 1046.72, 1049.7, 1044.9 +12/28/2017, 1048.14, 833011, 1051.6, 1054.75, 1044.77 +12/27/2017, 1049.37, 1271780, 1057.39, 1058.37, 1048.05 +12/26/2017, 1056.74, 761097, 1058.07, 1060.12, 1050.2 +12/22/2017, 1060.12, 755089, 1061.11, 1064.2, 1059.44 +12/21/2017, 1063.63, 986548, 1064.95, 1069.33, 1061.7938 +12/20/2017, 1064.95, 1268285, 1071.78, 1073.38, 1061.52 +12/19/2017, 1070.68, 1307894, 1075.2, 1076.84, 1063.55 +12/18/2017, 1077.14, 1552016, 1066.08, 1078.49, 1062 +12/15/2017, 1064.19, 3275091, 1054.61, 1067.62, 1049.5 +12/14/2017, 1049.15, 1558684, 1045, 1058.5, 1043.11 +12/13/2017, 1040.61, 1220364, 1046.12, 1046.665, 1038.38 +12/12/2017, 1040.48, 1279511, 1039.63, 1050.31, 1033.6897 +12/11/2017, 1041.1, 1190527, 1035.5, 1043.8, 1032.0504 +12/08/2017, 1037.05, 1288419, 1037.49, 1042.05, 1032.5222 +12/07/2017, 1030.93, 1458145, 1020.43, 1034.24, 1018.071 +12/06/2017, 1018.38, 1258496, 1001.5, 1024.97, 1001.14 +12/05/2017, 1005.15, 2066247, 995.94, 1020.61, 988.28 +12/04/2017, 998.68, 1906058, 1012.66, 1016.1, 995.57 +12/01/2017, 1010.17, 1908962, 1015.8, 1022.4897, 1002.02 +11/30/2017, 1021.41, 1723003, 1022.37, 1028.4899, 1015 +11/29/2017, 1021.66, 2442974, 1042.68, 1044.08, 1015.65 +11/28/2017, 1047.41, 1421027, 1055.09, 1062.375, 1040 +11/27/2017, 1054.21, 1307471, 1040, 1055.46, 1038.44 +11/24/2017, 1040.61, 536996, 1035.87, 1043.178, 1035 +11/22/2017, 1035.96, 746351, 1035, 1039.706, 1031.43 +11/21/2017, 1034.49, 1096161, 1023.31, 1035.11, 1022.655 +11/20/2017, 1018.38, 898389, 1020.26, 1022.61, 1017.5 +11/17/2017, 1019.09, 1366936, 1034.01, 1034.42, 1017.75 +11/16/2017, 1032.5, 1129424, 1022.52, 1035.92, 1022.52 +11/15/2017, 1020.91, 847932, 1019.21, 1024.09, 1015.42 +11/14/2017, 1026, 958708, 1022.59, 1026.81, 1014.15 +11/13/2017, 1025.75, 885565, 1023.42, 1031.58, 1022.57 +11/10/2017, 1028.07, 720674, 1026.46, 1030.76, 1025.28 +11/09/2017, 1031.26, 1244701, 1033.99, 1033.99, 1019.6656 +11/08/2017, 1039.85, 1088395, 1030.52, 1043.522, 1028.45 +11/07/2017, 1033.33, 1112123, 1027.27, 1033.97, 1025.13 +11/06/2017, 1025.9, 1124757, 1028.99, 1034.87, 1025 +11/03/2017, 1032.48, 1075134, 1022.11, 1032.65, 1020.31 +11/02/2017, 1025.58, 1048584, 1021.76, 1028.09, 1013.01 +11/01/2017, 1025.5, 1371619, 1017.21, 1029.67, 1016.95 +10/31/2017, 1016.64, 1331265, 1015.22, 1024, 1010.42 +10/30/2017, 1017.11, 2083490, 1014, 1024.97, 1007.5 +10/27/2017, 1019.27, 5165922, 1009.19, 1048.39, 1008.2 +10/26/2017, 972.56, 2027218, 980, 987.6, 972.2 +10/25/2017, 973.33, 1210368, 968.37, 976.09, 960.5201 +10/24/2017, 970.54, 1206074, 970, 972.23, 961 +10/23/2017, 968.45, 1471544, 989.52, 989.52, 966.12 +10/20/2017, 988.2, 1176177, 989.44, 991, 984.58 +10/19/2017, 984.45, 1312706, 986, 988.88, 978.39 +10/18/2017, 992.81, 1057285, 991.77, 996.72, 986.9747 +10/17/2017, 992.18, 1290152, 990.29, 996.44, 988.59 +10/16/2017, 992, 910246, 992.1, 993.9065, 984 +10/13/2017, 989.68, 1169584, 992, 997.21, 989 +10/12/2017, 987.83, 1278357, 987.45, 994.12, 985 +10/11/2017, 989.25, 1692843, 973.72, 990.71, 972.25 +10/10/2017, 972.6, 968113, 980, 981.57, 966.0801 +10/09/2017, 977, 890620, 980, 985.425, 976.11 +10/06/2017, 978.89, 1146207, 966.7, 979.46, 963.36 +10/05/2017, 969.96, 1210427, 955.49, 970.91, 955.18 +10/04/2017, 951.68, 951766, 957, 960.39, 950.69 +10/03/2017, 957.79, 888303, 954, 958, 949.14 +10/02/2017, 953.27, 1282850, 959.98, 962.54, 947.84 +09/29/2017, 959.11, 1576365, 952, 959.7864, 951.51 +09/28/2017, 949.5, 997036, 941.36, 950.69, 940.55 +09/27/2017, 944.49, 2237538, 927.74, 949.9, 927.74 +09/26/2017, 924.86, 1666749, 923.72, 930.82, 921.14 +09/25/2017, 920.97, 1855742, 925.45, 926.4, 909.7 +09/22/2017, 928.53, 1052170, 927.75, 934.73, 926.48 +09/21/2017, 932.45, 1227059, 933, 936.53, 923.83 +09/20/2017, 931.58, 1535626, 922.98, 933.88, 922 +09/19/2017, 921.81, 912967, 917.42, 922.4199, 912.55 +09/18/2017, 915, 1300759, 920.01, 922.08, 910.6 +09/15/2017, 920.29, 2499466, 924.66, 926.49, 916.36 +09/14/2017, 925.11, 1395497, 931.25, 932.77, 924 +09/13/2017, 935.09, 1101145, 930.66, 937.25, 929.86 +09/12/2017, 932.07, 1133638, 932.59, 933.48, 923.861 +09/11/2017, 929.08, 1266020, 934.25, 938.38, 926.92 +09/08/2017, 926.5, 997699, 936.49, 936.99, 924.88 +09/07/2017, 935.95, 1211472, 931.73, 936.41, 923.62 +09/06/2017, 927.81, 1526209, 930.15, 930.915, 919.27 +09/05/2017, 928.45, 1346791, 933.08, 937, 921.96 +09/01/2017, 937.34, 943657, 941.13, 942.48, 935.15 +08/31/2017, 939.33, 1566888, 931.76, 941.98, 931.76 +08/30/2017, 929.57, 1300616, 920.05, 930.819, 919.65 +08/29/2017, 921.29, 1181391, 905.1, 923.33, 905 +08/28/2017, 913.81, 1085014, 916, 919.245, 911.87 +08/25/2017, 915.89, 1052764, 923.49, 925.555, 915.5 +08/24/2017, 921.28, 1266191, 928.66, 930.84, 915.5 +08/23/2017, 927, 1088575, 921.93, 929.93, 919.36 +08/22/2017, 924.69, 1166320, 912.72, 925.86, 911.4751 +08/21/2017, 906.66, 942328, 910, 913, 903.4 +08/18/2017, 910.67, 1341990, 910.31, 915.275, 907.1543 +08/17/2017, 910.98, 1241782, 925.78, 926.86, 910.98 +08/16/2017, 926.96, 1005261, 925.29, 932.7, 923.445 +08/15/2017, 922.22, 882479, 924.23, 926.5499, 919.82 +08/14/2017, 922.67, 1063404, 922.53, 924.668, 918.19 +08/11/2017, 914.39, 1205652, 907.97, 917.78, 905.58 +08/10/2017, 907.24, 1755521, 917.55, 919.26, 906.13 +08/09/2017, 922.9, 1191332, 920.61, 925.98, 917.2501 +08/08/2017, 926.79, 1057351, 927.09, 935.814, 925.6095 +08/07/2017, 929.36, 1031710, 929.06, 931.7, 926.5 +08/04/2017, 927.96, 1081814, 926.75, 930.3068, 923.03 +08/03/2017, 923.65, 1201519, 930.34, 932.24, 922.24 +08/02/2017, 930.39, 1822272, 928.61, 932.6, 916.68 +08/01/2017, 930.83, 1234612, 932.38, 937.447, 929.26 +07/31/2017, 930.5, 1964748, 941.89, 943.59, 926.04 +07/28/2017, 941.53, 1802343, 929.4, 943.83, 927.5 +07/27/2017, 934.09, 3128819, 951.78, 951.78, 920 +07/26/2017, 947.8, 2069349, 954.68, 955, 942.2788 +07/25/2017, 950.7, 4656609, 953.81, 959.7, 945.4 +07/24/2017, 980.34, 3205374, 972.22, 986.2, 970.77 +07/21/2017, 972.92, 1697190, 962.25, 973.23, 960.15 +07/20/2017, 968.15, 1620636, 975, 975.9, 961.51 +07/19/2017, 970.89, 1221155, 967.84, 973.04, 964.03 +07/18/2017, 965.4, 1152741, 953, 968.04, 950.6 +07/17/2017, 953.42, 1164141, 957, 960.74, 949.2407 +07/14/2017, 955.99, 1052855, 952, 956.91, 948.005 +07/13/2017, 947.16, 1294674, 946.29, 954.45, 943.01 +07/12/2017, 943.83, 1517168, 938.68, 946.3, 934.47 +07/11/2017, 930.09, 1112417, 929.54, 931.43, 922 +07/10/2017, 928.8, 1190237, 921.77, 930.38, 919.59 +07/07/2017, 918.59, 1590456, 908.85, 921.54, 908.85 +07/06/2017, 906.69, 1424290, 904.12, 914.9444, 899.7 +07/05/2017, 911.71, 1813309, 901.76, 914.51, 898.5 +07/03/2017, 898.7, 1710373, 912.18, 913.94, 894.79 +06/30/2017, 908.73, 2086340, 926.05, 926.05, 908.31 +06/29/2017, 917.79, 3287991, 929.92, 931.26, 910.62 +06/28/2017, 940.49, 2719213, 929, 942.75, 916 +06/27/2017, 927.33, 2566047, 942.46, 948.29, 926.85 +06/26/2017, 952.27, 1596664, 969.9, 973.31, 950.79 +06/23/2017, 965.59, 1527513, 956.83, 966, 954.2 +06/22/2017, 957.09, 941639, 958.7, 960.72, 954.55 +06/21/2017, 959.45, 1201971, 953.64, 960.1, 950.76 +06/20/2017, 950.63, 1125520, 957.52, 961.62, 950.01 +06/19/2017, 957.37, 1520715, 949.96, 959.99, 949.05 +06/16/2017, 939.78, 3061794, 940, 942.04, 931.595 +06/15/2017, 942.31, 2065271, 933.97, 943.339, 924.44 +06/14/2017, 950.76, 1487378, 959.92, 961.15, 942.25 +06/13/2017, 953.4, 2012980, 951.91, 959.98, 944.09 +06/12/2017, 942.9, 3762434, 939.56, 949.355, 915.2328 +06/09/2017, 949.83, 3305545, 984.5, 984.5, 935.63 +06/08/2017, 983.41, 1477151, 982.35, 984.57, 977.2 +06/07/2017, 981.08, 1447172, 979.65, 984.15, 975.77 +06/06/2017, 976.57, 1814323, 983.16, 988.25, 975.14 +06/05/2017, 983.68, 1251903, 976.55, 986.91, 975.1 +06/02/2017, 975.6, 1750723, 969.46, 975.88, 966 +06/01/2017, 966.95, 1408958, 968.95, 971.5, 960.01 +05/31/2017, 964.86, 2447176, 975.02, 979.27, 960.18 +05/30/2017, 975.88, 1466288, 970.31, 976.2, 969.49 +05/26/2017, 971.47, 1251425, 969.7, 974.98, 965.03 +05/25/2017, 969.54, 1659422, 957.33, 972.629, 955.47 +05/24/2017, 954.96, 1031408, 952.98, 955.09, 949.5 +05/23/2017, 948.82, 1269438, 947.92, 951.4666, 942.575 +05/22/2017, 941.86, 1118456, 935, 941.8828, 935 +05/19/2017, 934.01, 1389848, 931.47, 937.755, 931 +05/18/2017, 930.24, 1596058, 921, 933.17, 918.75 +05/17/2017, 919.62, 2357922, 935.67, 939.3325, 918.14 +05/16/2017, 943, 968288, 940, 943.11, 937.58 +05/15/2017, 937.08, 1104595, 932.95, 938.25, 929.34 +05/12/2017, 932.22, 1050377, 931.53, 933.44, 927.85 +05/11/2017, 930.6, 834997, 925.32, 932.53, 923.0301 +05/10/2017, 928.78, 1173887, 931.98, 932, 925.16 +05/09/2017, 932.17, 1581236, 936.95, 937.5, 929.53 +05/08/2017, 934.3, 1328885, 926.12, 936.925, 925.26 +05/05/2017, 927.13, 1910317, 933.54, 934.9, 925.2 +05/04/2017, 931.66, 1421938, 926.07, 935.93, 924.59 +05/03/2017, 927.04, 1497565, 914.86, 928.1, 912.5426 +05/02/2017, 916.44, 1543696, 909.62, 920.77, 909.4526 +05/01/2017, 912.57, 2114629, 901.94, 915.68, 901.45 +04/28/2017, 905.96, 3223850, 910.66, 916.85, 905.77 +04/27/2017, 874.25, 2009509, 873.6, 875.4, 870.38 +04/26/2017, 871.73, 1233724, 874.23, 876.05, 867.7481 +04/25/2017, 872.3, 1670095, 865, 875, 862.81 +04/24/2017, 862.76, 1371722, 851.2, 863.45, 849.86 +04/21/2017, 843.19, 1323364, 842.88, 843.88, 840.6 +04/20/2017, 841.65, 957994, 841.44, 845.2, 839.32 +04/19/2017, 838.21, 954324, 839.79, 842.22, 836.29 +04/18/2017, 836.82, 835433, 834.22, 838.93, 832.71 +04/17/2017, 837.17, 894540, 825.01, 837.75, 824.47 +04/13/2017, 823.56, 1118221, 822.14, 826.38, 821.44 +04/12/2017, 824.32, 900059, 821.93, 826.66, 821.02 +04/11/2017, 823.35, 1078951, 824.71, 827.4267, 817.0201 +04/10/2017, 824.73, 978825, 825.39, 829.35, 823.77 +04/07/2017, 824.67, 1056692, 827.96, 828.485, 820.5127 +04/06/2017, 827.88, 1254235, 832.4, 836.39, 826.46 +04/05/2017, 831.41, 1553163, 835.51, 842.45, 830.72 +04/04/2017, 834.57, 1044455, 831.36, 835.18, 829.0363 +04/03/2017, 838.55, 1670349, 829.22, 840.85, 829.22 +03/31/2017, 829.56, 1401756, 828.97, 831.64, 827.39 +03/30/2017, 831.5, 1055263, 833.5, 833.68, 829 +03/29/2017, 831.41, 1785006, 825, 832.765, 822.3801 +03/28/2017, 820.92, 1620532, 820.41, 825.99, 814.027 +03/27/2017, 819.51, 1894735, 806.95, 821.63, 803.37 +03/24/2017, 814.43, 1980415, 820.08, 821.93, 808.89 +03/23/2017, 817.58, 3485390, 821, 822.57, 812.257 +03/22/2017, 829.59, 1399409, 831.91, 835.55, 827.1801 +03/21/2017, 830.46, 2461375, 851.4, 853.5, 829.02 +03/20/2017, 848.4, 1217560, 850.01, 850.22, 845.15 +03/17/2017, 852.12, 1712397, 851.61, 853.4, 847.11 +03/16/2017, 848.78, 977384, 849.03, 850.85, 846.13 +03/15/2017, 847.2, 1381328, 847.59, 848.63, 840.77 +03/14/2017, 845.62, 779920, 843.64, 847.24, 840.8 +03/13/2017, 845.54, 1149928, 844, 848.685, 843.25 +03/10/2017, 843.25, 1702731, 843.28, 844.91, 839.5 +03/09/2017, 838.68, 1261393, 836, 842, 834.21 +03/08/2017, 835.37, 988900, 833.51, 838.15, 831.79 +03/07/2017, 831.91, 1037573, 827.4, 833.41, 826.52 +03/06/2017, 827.78, 1108799, 826.95, 828.88, 822.4 +03/03/2017, 829.08, 890640, 830.56, 831.36, 825.751 +03/02/2017, 830.63, 937824, 833.85, 834.51, 829.64 +03/01/2017, 835.24, 1495934, 828.85, 836.255, 827.26 +02/28/2017, 823.21, 2258695, 825.61, 828.54, 820.2 +02/27/2017, 829.28, 1101120, 824.55, 830.5, 824 +02/24/2017, 828.64, 1392039, 827.73, 829, 824.2 +02/23/2017, 831.33, 1471342, 830.12, 832.46, 822.88 +02/22/2017, 830.76, 983058, 828.66, 833.25, 828.64 +02/21/2017, 831.66, 1259841, 828.66, 833.45, 828.35 +02/17/2017, 828.07, 1602549, 823.02, 828.07, 821.655 +02/16/2017, 824.16, 1285919, 819.93, 824.4, 818.98 +02/15/2017, 818.98, 1311316, 819.36, 823, 818.47 +02/14/2017, 820.45, 1054472, 819, 823, 816 +02/13/2017, 819.24, 1205835, 816, 820.959, 815.49 +02/10/2017, 813.67, 1134701, 811.7, 815.25, 809.78 +02/09/2017, 809.56, 990260, 809.51, 810.66, 804.54 +02/08/2017, 808.38, 1155892, 807, 811.84, 803.1903 +02/07/2017, 806.97, 1240257, 803.99, 810.5, 801.78 +02/06/2017, 801.34, 1182882, 799.7, 801.67, 795.2501 +02/03/2017, 801.49, 1461217, 802.99, 806, 800.37 +02/02/2017, 798.53, 1530827, 793.8, 802.7, 792 +02/01/2017, 795.695, 2027708, 799.68, 801.19, 791.19 +01/31/2017, 796.79, 2153957, 796.86, 801.25, 790.52 +01/30/2017, 802.32, 3243568, 814.66, 815.84, 799.8 +01/27/2017, 823.31, 2964989, 834.71, 841.95, 820.44 +01/26/2017, 832.15, 2944642, 837.81, 838, 827.01 +01/25/2017, 835.67, 1612854, 829.62, 835.77, 825.06 +01/24/2017, 823.87, 1472228, 822.3, 825.9, 817.821 +01/23/2017, 819.31, 1962506, 807.25, 820.87, 803.74 +01/20/2017, 805.02, 1668638, 806.91, 806.91, 801.69 +01/19/2017, 802.175, 917085, 805.12, 809.48, 801.8 +01/18/2017, 806.07, 1293893, 805.81, 806.205, 800.99 +01/17/2017, 804.61, 1361935, 807.08, 807.14, 800.37 +01/13/2017, 807.88, 1098154, 807.48, 811.2244, 806.69 +01/12/2017, 806.36, 1352872, 807.14, 807.39, 799.17 +01/11/2017, 807.91, 1065360, 805, 808.15, 801.37 +01/10/2017, 804.79, 1176637, 807.86, 809.1299, 803.51 +01/09/2017, 806.65, 1274318, 806.4, 809.9664, 802.83 +01/06/2017, 806.15, 1639246, 795.26, 807.9, 792.2041 +01/05/2017, 794.02, 1334028, 786.08, 794.48, 785.02 +01/04/2017, 786.9, 1071198, 788.36, 791.34, 783.16 +01/03/2017, 786.14, 1657291, 778.81, 789.63, 775.8 +12/30/2016, 771.82, 1769809, 782.75, 782.78, 770.41 +12/29/2016, 782.79, 743808, 783.33, 785.93, 778.92 +12/28/2016, 785.05, 1142148, 793.7, 794.23, 783.2 +12/27/2016, 791.55, 789151, 790.68, 797.86, 787.657 +12/23/2016, 789.91, 623682, 790.9, 792.74, 787.28 +12/22/2016, 791.26, 972147, 792.36, 793.32, 788.58 +12/21/2016, 794.56, 1208770, 795.84, 796.6757, 787.1 +12/20/2016, 796.42, 950345, 796.76, 798.65, 793.27 +12/19/2016, 794.2, 1231966, 790.22, 797.66, 786.27 +12/16/2016, 790.8, 2435100, 800.4, 800.8558, 790.29 +12/15/2016, 797.85, 1623709, 797.34, 803, 792.92 +12/14/2016, 797.07, 1700875, 797.4, 804, 794.01 +12/13/2016, 796.1, 2122735, 793.9, 804.3799, 793.34 +12/12/2016, 789.27, 2102288, 785.04, 791.25, 784.3554 +12/09/2016, 789.29, 1821146, 780, 789.43, 779.021 +12/08/2016, 776.42, 1487517, 772.48, 778.18, 767.23 +12/07/2016, 771.19, 1757710, 761, 771.36, 755.8 +12/06/2016, 759.11, 1690365, 764.73, 768.83, 757.34 +12/05/2016, 762.52, 1393566, 757.71, 763.9, 752.9 +12/02/2016, 750.5, 1452181, 744.59, 754, 743.1 +12/01/2016, 747.92, 3017001, 757.44, 759.85, 737.0245 +11/30/2016, 758.04, 2386628, 770.07, 772.99, 754.83 +11/29/2016, 770.84, 1616427, 771.53, 778.5, 768.24 +11/28/2016, 768.24, 2177039, 760, 779.53, 759.8 +11/25/2016, 761.68, 587421, 764.26, 765, 760.52 +11/23/2016, 760.99, 1477501, 767.73, 768.2825, 755.25 +11/22/2016, 768.27, 1592372, 772.63, 776.96, 767 +11/21/2016, 769.2, 1324431, 762.61, 769.7, 760.6 +11/18/2016, 760.54, 1528555, 771.37, 775, 760 +11/17/2016, 771.23, 1298484, 766.92, 772.7, 764.23 +11/16/2016, 764.48, 1468196, 755.2, 766.36, 750.51 +11/15/2016, 758.49, 2375056, 746.97, 764.4162, 746.97 +11/14/2016, 736.08, 3644965, 755.6, 757.85, 727.54 +11/11/2016, 754.02, 2421889, 756.54, 760.78, 750.38 +11/10/2016, 762.56, 4733916, 791.17, 791.17, 752.18 +11/09/2016, 785.31, 2603860, 779.94, 791.2265, 771.67 +11/08/2016, 790.51, 1361472, 783.4, 795.633, 780.19 +11/07/2016, 782.52, 1574426, 774.5, 785.19, 772.55 +11/04/2016, 762.02, 2131948, 750.66, 770.36, 750.5611 +11/03/2016, 762.13, 1933937, 767.25, 769.95, 759.03 +11/02/2016, 768.7, 1905814, 778.2, 781.65, 763.4496 +11/01/2016, 783.61, 2404898, 782.89, 789.49, 775.54 +10/31/2016, 784.54, 2420892, 795.47, 796.86, 784 +10/28/2016, 795.37, 4261912, 808.35, 815.49, 793.59 +10/27/2016, 795.35, 2723097, 801, 803.49, 791.5 +10/26/2016, 799.07, 1645403, 806.34, 806.98, 796.32 +10/25/2016, 807.67, 1575020, 816.68, 816.68, 805.14 +10/24/2016, 813.11, 1693162, 804.9, 815.18, 804.82 +10/21/2016, 799.37, 1262042, 795, 799.5, 794 +10/20/2016, 796.97, 1755546, 803.3, 803.97, 796.03 +10/19/2016, 801.56, 1762990, 798.86, 804.63, 797.635 +10/18/2016, 795.26, 2046338, 787.85, 801.61, 785.565 +10/17/2016, 779.96, 1091524, 779.8, 785.85, 777.5 +10/14/2016, 778.53, 851512, 781.65, 783.95, 776 +10/13/2016, 778.19, 1360619, 781.22, 781.22, 773 +10/12/2016, 786.14, 935138, 783.76, 788.13, 782.06 +10/11/2016, 783.07, 1371461, 786.66, 792.28, 780.58 +10/10/2016, 785.94, 1161410, 777.71, 789.38, 775.87 +10/07/2016, 775.08, 932444, 779.66, 779.66, 770.75 +10/06/2016, 776.86, 1066910, 779, 780.48, 775.54 +10/05/2016, 776.47, 1457661, 779.31, 782.07, 775.65 +10/04/2016, 776.43, 1198361, 776.03, 778.71, 772.89 +10/03/2016, 772.56, 1276614, 774.25, 776.065, 769.5 +09/30/2016, 777.29, 1583293, 776.33, 780.94, 774.09 +09/29/2016, 775.01, 1310252, 781.44, 785.8, 774.232 +09/28/2016, 781.56, 1108249, 777.85, 781.81, 774.97 +09/27/2016, 783.01, 1152760, 775.5, 785.9899, 774.308 +09/26/2016, 774.21, 1531788, 782.74, 782.74, 773.07 +09/23/2016, 786.9, 1411439, 786.59, 788.93, 784.15 +09/22/2016, 787.21, 1483899, 780, 789.85, 778.44 +09/21/2016, 776.22, 1166290, 772.66, 777.16, 768.301 +09/20/2016, 771.41, 975434, 769, 773.33, 768.53 +09/19/2016, 765.7, 1171969, 772.42, 774, 764.4406 +09/16/2016, 768.88, 2047036, 769.75, 769.75, 764.66 +09/15/2016, 771.76, 1344945, 762.89, 773.8, 759.96 +09/14/2016, 762.49, 1093723, 759.61, 767.68, 759.11 +09/13/2016, 759.69, 1394158, 764.48, 766.2195, 755.8 +09/12/2016, 769.02, 1310493, 755.13, 770.29, 754.0001 +09/09/2016, 759.66, 1879903, 770.1, 773.245, 759.66 +09/08/2016, 775.32, 1268663, 778.59, 780.35, 773.58 +09/07/2016, 780.35, 893874, 780, 782.73, 776.2 +09/06/2016, 780.08, 1441864, 773.45, 782, 771 +09/02/2016, 771.46, 1070725, 773.01, 773.9199, 768.41 +09/01/2016, 768.78, 925019, 769.25, 771.02, 764.3 +08/31/2016, 767.05, 1247937, 767.01, 769.09, 765.38 +08/30/2016, 769.09, 1129932, 769.33, 774.466, 766.84 +08/29/2016, 772.15, 847537, 768.74, 774.99, 766.615 +08/26/2016, 769.54, 1164713, 769, 776.0799, 765.85 +08/25/2016, 769.41, 926856, 767, 771.89, 763.1846 +08/24/2016, 769.64, 1071569, 770.58, 774.5, 767.07 +08/23/2016, 772.08, 925356, 775.48, 776.44, 771.785 +08/22/2016, 772.15, 950417, 773.27, 774.54, 770.0502 +08/19/2016, 775.42, 860899, 775, 777.1, 773.13 +08/18/2016, 777.5, 718882, 780.01, 782.86, 777 +08/17/2016, 779.91, 921666, 777.32, 780.81, 773.53 +08/16/2016, 777.14, 1027836, 780.3, 780.98, 773.444 +08/15/2016, 782.44, 938183, 783.75, 787.49, 780.11 +08/12/2016, 783.22, 739761, 781.5, 783.395, 780.4 +08/11/2016, 784.85, 971742, 785, 789.75, 782.97 +08/10/2016, 784.68, 784559, 783.75, 786.8123, 782.778 +08/09/2016, 784.26, 1318457, 781.1, 788.94, 780.57 +08/08/2016, 781.76, 1106693, 782, 782.63, 778.091 +08/05/2016, 782.22, 1799478, 773.78, 783.04, 772.34 +08/04/2016, 771.61, 1139972, 772.22, 774.07, 768.795 +08/03/2016, 773.18, 1283186, 767.18, 773.21, 766.82 +08/02/2016, 771.07, 1782822, 768.69, 775.84, 767.85 +08/01/2016, 772.88, 2697699, 761.09, 780.43, 761.09 +07/29/2016, 768.79, 3830103, 772.71, 778.55, 766.77 +07/28/2016, 745.91, 3473040, 747.04, 748.65, 739.3 +07/27/2016, 741.77, 1509133, 738.28, 744.46, 737 +07/26/2016, 738.42, 1182993, 739.04, 741.69, 734.27 +07/25/2016, 739.77, 1031643, 740.67, 742.61, 737.5 +07/22/2016, 742.74, 1256741, 741.86, 743.24, 736.56 +07/21/2016, 738.63, 1022229, 740.36, 741.69, 735.831 +07/20/2016, 741.19, 1283931, 737.33, 742.13, 737.1 +07/19/2016, 736.96, 1225467, 729.89, 736.99, 729 +07/18/2016, 733.78, 1284740, 722.71, 736.13, 721.19 +07/15/2016, 719.85, 1277514, 725.73, 725.74, 719.055 +07/14/2016, 720.95, 949456, 721.58, 722.21, 718.03 +07/13/2016, 716.98, 933352, 723.62, 724, 716.85 +07/12/2016, 720.64, 1336112, 719.12, 722.94, 715.91 +07/11/2016, 715.09, 1107039, 708.05, 716.51, 707.24 +07/08/2016, 705.63, 1573909, 699.5, 705.71, 696.435 +07/07/2016, 695.36, 1303661, 698.08, 698.2, 688.215 +07/06/2016, 697.77, 1411080, 689.98, 701.68, 689.09 +07/05/2016, 694.49, 1462879, 696.06, 696.94, 688.88 +07/01/2016, 699.21, 1344387, 692.2, 700.65, 692.1301 +06/30/2016, 692.1, 1597298, 685.47, 692.32, 683.65 +06/29/2016, 684.11, 1931436, 683, 687.4292, 681.41 +06/28/2016, 680.04, 2169704, 678.97, 680.33, 673 +06/27/2016, 668.26, 2632011, 671, 672.3, 663.284 +06/24/2016, 675.22, 4442943, 675.17, 689.4, 673.45 +06/23/2016, 701.87, 2166183, 697.45, 701.95, 687 +06/22/2016, 697.46, 1182161, 699.06, 700.86, 693.0819 +06/21/2016, 695.94, 1464836, 698.4, 702.77, 692.01 +06/20/2016, 693.71, 2080645, 698.77, 702.48, 693.41 +06/17/2016, 691.72, 3397720, 708.65, 708.82, 688.4515 +06/16/2016, 710.36, 1981657, 714.91, 716.65, 703.26 +06/15/2016, 718.92, 1213386, 719, 722.98, 717.31 +06/14/2016, 718.27, 1303808, 716.48, 722.47, 713.12 +06/13/2016, 718.36, 1255199, 716.51, 725.44, 716.51 +06/10/2016, 719.41, 1213989, 719.47, 725.89, 716.43 +06/09/2016, 728.58, 987635, 722.87, 729.54, 722.3361 +06/08/2016, 728.28, 1583325, 723.96, 728.57, 720.58 +06/07/2016, 716.65, 1336348, 719.84, 721.98, 716.55 +06/06/2016, 716.55, 1565955, 724.91, 724.91, 714.61 +06/03/2016, 722.34, 1225924, 729.27, 729.49, 720.56 +06/02/2016, 730.4, 1340664, 732.5, 733.02, 724.17 +06/01/2016, 734.15, 1251468, 734.53, 737.21, 730.66 +05/31/2016, 735.72, 2128358, 731.74, 739.73, 731.26 +05/27/2016, 732.66, 1974425, 724.01, 733.936, 724 +05/26/2016, 724.12, 1573635, 722.87, 728.33, 720.28 +05/25/2016, 725.27, 1629790, 720.76, 727.51, 719.7047 +05/24/2016, 720.09, 1926828, 706.86, 720.97, 706.86 +05/23/2016, 704.24, 1326386, 706.53, 711.4781, 704.18 +05/20/2016, 709.74, 1825830, 701.62, 714.58, 700.52 +05/19/2016, 700.32, 1668887, 702.36, 706, 696.8 +05/18/2016, 706.63, 1765632, 703.67, 711.6, 700.63 +05/17/2016, 706.23, 1999883, 715.99, 721.52, 704.11 +05/16/2016, 716.49, 1316719, 709.13, 718.48, 705.65 +05/13/2016, 710.83, 1307559, 711.93, 716.6619, 709.26 +05/12/2016, 713.31, 1361170, 717.06, 719.25, 709 +05/11/2016, 715.29, 1690862, 723.41, 724.48, 712.8 +05/10/2016, 723.18, 1568621, 716.75, 723.5, 715.72 +05/09/2016, 712.9, 1509892, 712, 718.71, 710 +05/06/2016, 711.12, 1828508, 698.38, 711.86, 698.1067 +05/05/2016, 701.43, 1680220, 697.7, 702.3199, 695.72 +05/04/2016, 695.7, 1692757, 690.49, 699.75, 689.01 +05/03/2016, 692.36, 1541297, 696.87, 697.84, 692 +05/02/2016, 698.21, 1645013, 697.63, 700.64, 691 +04/29/2016, 693.01, 2486584, 690.7, 697.62, 689 +04/28/2016, 691.02, 2859790, 708.26, 714.17, 689.55 +04/27/2016, 705.84, 3094905, 707.29, 708.98, 692.3651 +04/26/2016, 708.14, 2739133, 725.42, 725.766, 703.0264 +04/25/2016, 723.15, 1956956, 716.1, 723.93, 715.59 +04/22/2016, 718.77, 5949699, 726.3, 736.12, 713.61 +04/21/2016, 759.14, 2995094, 755.38, 760.45, 749.55 +04/20/2016, 752.67, 1526776, 758, 758.1315, 750.01 +04/19/2016, 753.93, 2027962, 769.51, 769.9, 749.33 +04/18/2016, 766.61, 1557199, 760.46, 768.05, 757.3 +04/15/2016, 759, 1807062, 753.98, 761, 752.6938 +04/14/2016, 753.2, 1134056, 754.01, 757.31, 752.705 +04/13/2016, 751.72, 1707397, 749.16, 754.38, 744.261 +04/12/2016, 743.09, 1349780, 738, 743.83, 731.01 +04/11/2016, 736.1, 1218789, 743.02, 745, 736.05 +04/08/2016, 739.15, 1289869, 743.97, 745.45, 735.55 +04/07/2016, 740.28, 1452369, 745.37, 746.9999, 736.28 +04/06/2016, 745.69, 1052171, 735.77, 746.24, 735.56 +04/05/2016, 737.8, 1130817, 738, 742.8, 735.37 +04/04/2016, 745.29, 1134214, 750.06, 752.8, 742.43 +04/01/2016, 749.91, 1576240, 738.6, 750.34, 737 +03/31/2016, 744.95, 1718638, 749.25, 750.85, 740.94 +03/30/2016, 750.53, 1782278, 750.1, 757.88, 748.74 +03/29/2016, 744.77, 1902254, 734.59, 747.25, 728.76 +03/28/2016, 733.53, 1300817, 736.79, 738.99, 732.5 +03/24/2016, 735.3, 1570474, 732.01, 737.747, 731 +03/23/2016, 738.06, 1431130, 742.36, 745.7199, 736.15 +03/22/2016, 740.75, 1269263, 737.46, 745, 737.46 +03/21/2016, 742.09, 1835963, 736.5, 742.5, 733.5157 +03/18/2016, 737.6, 2982194, 741.86, 742, 731.83 +03/17/2016, 737.78, 1859562, 736.45, 743.07, 736 +03/16/2016, 736.09, 1621412, 726.37, 737.47, 724.51 +03/15/2016, 728.33, 1720790, 726.92, 732.29, 724.77 +03/14/2016, 730.49, 1717002, 726.81, 735.5, 725.15 +03/11/2016, 726.82, 1968164, 720, 726.92, 717.125 +03/10/2016, 712.82, 2830630, 708.12, 716.44, 703.36 +03/09/2016, 705.24, 1419661, 698.47, 705.68, 694 +03/08/2016, 693.97, 2075305, 688.59, 703.79, 685.34 +03/07/2016, 695.16, 2986064, 706.9, 708.0912, 686.9 +03/04/2016, 710.89, 1971379, 714.99, 716.49, 706.02 +03/03/2016, 712.42, 1956958, 718.68, 719.45, 706.02 +03/02/2016, 718.85, 1629501, 719, 720, 712 +03/01/2016, 718.81, 2148608, 703.62, 718.81, 699.77 +02/29/2016, 697.77, 2478214, 700.32, 710.89, 697.68 +02/26/2016, 705.07, 2241785, 708.58, 713.43, 700.86 +02/25/2016, 705.75, 1640430, 700.01, 705.98, 690.585 +02/24/2016, 699.56, 1961258, 688.92, 700, 680.78 +02/23/2016, 695.85, 2006572, 701.45, 708.4, 693.58 +02/22/2016, 706.46, 1949046, 707.45, 713.24, 702.51 +02/19/2016, 700.91, 1585152, 695.03, 703.0805, 694.05 +02/18/2016, 697.35, 1880306, 710, 712.35, 696.03 +02/17/2016, 708.4, 2490021, 699, 709.75, 691.38 +02/16/2016, 691, 2517324, 692.98, 698, 685.05 +02/12/2016, 682.4, 2138937, 690.26, 693.75, 678.6 +02/11/2016, 683.11, 3021587, 675, 689.35, 668.8675 +02/10/2016, 684.12, 2629130, 686.86, 701.31, 682.13 +02/09/2016, 678.11, 3605792, 672.32, 699.9, 668.77 +02/08/2016, 682.74, 4241416, 667.85, 684.03, 663.06 +02/05/2016, 683.57, 5098357, 703.87, 703.99, 680.15 +02/04/2016, 708.01, 5157988, 722.81, 727, 701.86 +02/03/2016, 726.95, 6166731, 770.22, 774.5, 720.5 +02/02/2016, 764.65, 6340548, 784.5, 789.8699, 764.65 +02/01/2016, 752, 5065235, 750.46, 757.86, 743.27 +01/29/2016, 742.95, 3464432, 731.53, 744.9899, 726.8 +01/28/2016, 730.96, 2664956, 722.22, 733.69, 712.35 +01/27/2016, 699.99, 2175913, 713.67, 718.235, 694.39 +01/26/2016, 713.04, 1329141, 713.85, 718.28, 706.48 +01/25/2016, 711.67, 1709777, 723.58, 729.68, 710.01 +01/22/2016, 725.25, 2009951, 723.6, 728.13, 720.121 +01/21/2016, 706.59, 2411079, 702.18, 719.19, 694.46 +01/20/2016, 698.45, 3441642, 688.61, 706.85, 673.26 +01/19/2016, 701.79, 2264747, 703.3, 709.98, 693.4101 +01/15/2016, 694.45, 3604137, 692.29, 706.74, 685.37 +01/14/2016, 714.72, 2225495, 705.38, 721.925, 689.1 +01/13/2016, 700.56, 2497086, 730.85, 734.74, 698.61 +01/12/2016, 726.07, 2010026, 721.68, 728.75, 717.3165 +01/11/2016, 716.03, 2089495, 716.61, 718.855, 703.54 +01/08/2016, 714.47, 2449420, 731.45, 733.23, 713 +01/07/2016, 726.39, 2960578, 730.31, 738.5, 719.06 +01/06/2016, 743.62, 1943685, 730, 747.18, 728.92 +01/05/2016, 742.58, 1949386, 746.45, 752, 738.64 +01/04/2016, 741.84, 3271348, 743, 744.06, 731.2577 +12/31/2015, 758.88, 1500129, 769.5, 769.5, 758.34 +12/30/2015, 771, 1293514, 776.6, 777.6, 766.9 +12/29/2015, 776.6, 1764044, 766.69, 779.98, 766.43 +12/28/2015, 762.51, 1515574, 752.92, 762.99, 749.52 +12/24/2015, 748.4, 527223, 749.55, 751.35, 746.62 +12/23/2015, 750.31, 1566723, 753.47, 754.21, 744 +12/22/2015, 750, 1365420, 751.65, 754.85, 745.53 +12/21/2015, 747.77, 1524535, 746.13, 750, 740 +12/18/2015, 739.31, 3140906, 746.51, 754.13, 738.15 +12/17/2015, 749.43, 1551087, 762.42, 762.68, 749 +12/16/2015, 758.09, 1986319, 750, 760.59, 739.435 +12/15/2015, 743.4, 2661199, 753, 758.08, 743.01 +12/14/2015, 747.77, 2417778, 741.79, 748.73, 724.17 +12/11/2015, 738.87, 2223284, 741.16, 745.71, 736.75 +12/10/2015, 749.46, 1988035, 752.85, 755.85, 743.83 +12/09/2015, 751.61, 2697978, 759.17, 764.23, 737.001 +12/08/2015, 762.37, 1829004, 757.89, 764.8, 754.2 +12/07/2015, 763.25, 1811336, 767.77, 768.73, 755.09 +12/04/2015, 766.81, 2756194, 753.1, 768.49, 750 +12/03/2015, 752.54, 2589641, 766.01, 768.995, 745.63 +12/02/2015, 762.38, 2196721, 768.9, 775.955, 758.96 +12/01/2015, 767.04, 2131827, 747.11, 768.95, 746.7 +11/30/2015, 742.6, 2045584, 748.81, 754.93, 741.27 +11/27/2015, 750.26, 838528, 748.46, 753.41, 747.49 +11/25/2015, 748.15, 1122224, 748.14, 752, 746.06 +11/24/2015, 748.28, 2333700, 752, 755.279, 737.63 +11/23/2015, 755.98, 1414640, 757.45, 762.7075, 751.82 +11/20/2015, 756.6, 2212934, 746.53, 757.92, 743 +11/19/2015, 738.41, 1327265, 738.74, 742, 737.43 +11/18/2015, 740, 1683978, 727.58, 741.41, 727 +11/17/2015, 725.3, 1507449, 729.29, 731.845, 723.027 +11/16/2015, 728.96, 1904395, 715.6, 729.49, 711.33 +11/13/2015, 717, 2072392, 729.17, 731.15, 716.73 +11/12/2015, 731.23, 1836567, 731, 737.8, 728.645 +11/11/2015, 735.4, 1366611, 732.46, 741, 730.23 +11/10/2015, 728.32, 1606499, 724.4, 730.59, 718.5001 +11/09/2015, 724.89, 2068920, 730.2, 734.71, 719.43 +11/06/2015, 733.76, 1510586, 731.5, 735.41, 727.01 +11/05/2015, 731.25, 1861100, 729.47, 739.48, 729.47 +11/04/2015, 728.11, 1705745, 722, 733.1, 721.9 +11/03/2015, 722.16, 1565355, 718.86, 724.65, 714.72 +11/02/2015, 721.11, 1885155, 711.06, 721.62, 705.85 +10/30/2015, 710.81, 1907732, 715.73, 718, 710.05 +10/29/2015, 716.92, 1455508, 710.5, 718.26, 710.01 +10/28/2015, 712.95, 2178841, 707.33, 712.98, 703.08 +10/27/2015, 708.49, 2232183, 707.38, 713.62, 704.55 +10/26/2015, 712.78, 2709292, 701.55, 719.15, 701.26 +10/23/2015, 702, 6651909, 727.5, 730, 701.5 +10/22/2015, 651.79, 3994360, 646.7, 657.8, 644.01 +10/21/2015, 642.61, 1792869, 654.15, 655.87, 641.73 +10/20/2015, 650.28, 2498077, 664.04, 664.7197, 644.195 +10/19/2015, 666.1, 1465691, 661.18, 666.82, 659.58 +10/16/2015, 662.2, 1610712, 664.11, 664.97, 657.2 +10/15/2015, 661.74, 1832832, 654.66, 663.13, 654.46 +10/14/2015, 651.16, 1413798, 653.21, 659.39, 648.85 +10/13/2015, 652.3, 1806003, 643.15, 657.8125, 643.15 +10/12/2015, 646.67, 1275565, 642.09, 648.5, 639.01 +10/09/2015, 643.61, 1648656, 640, 645.99, 635.318 +10/08/2015, 639.16, 2181990, 641.36, 644.45, 625.56 +10/07/2015, 642.36, 2092536, 649.24, 650.609, 632.15 +10/06/2015, 645.44, 2235078, 638.84, 649.25, 636.5295 +10/05/2015, 641.47, 1802263, 632, 643.01, 627 +10/02/2015, 626.91, 2681241, 607.2, 627.34, 603.13 +10/01/2015, 611.29, 1866223, 608.37, 612.09, 599.85 +09/30/2015, 608.42, 2412754, 603.28, 608.76, 600.73 +09/29/2015, 594.97, 2310065, 597.28, 605, 590.22 +09/28/2015, 594.89, 3118693, 610.34, 614.605, 589.38 +09/25/2015, 611.97, 2173134, 629.77, 629.77, 611 +09/24/2015, 625.8, 2238097, 616.64, 627.32, 612.4 +09/23/2015, 622.36, 1470633, 622.05, 628.93, 620 +09/22/2015, 622.69, 2561551, 627, 627.55, 615.43 +09/21/2015, 635.44, 1786543, 634.4, 636.49, 625.94 +09/18/2015, 629.25, 5123314, 636.79, 640, 627.02 +09/17/2015, 642.9, 2259404, 637.79, 650.9, 635.02 +09/16/2015, 635.98, 1276250, 635.47, 637.95, 632.32 +09/15/2015, 635.14, 2082426, 626.7, 638.7, 623.78 +09/14/2015, 623.24, 1701618, 625.7, 625.86, 619.43 +09/11/2015, 625.77, 1372803, 619.75, 625.78, 617.42 +09/10/2015, 621.35, 1903334, 613.1, 624.16, 611.43 +09/09/2015, 612.72, 1699686, 621.22, 626.52, 609.6 +09/08/2015, 614.66, 2277487, 612.49, 616.31, 604.12 +09/04/2015, 600.7, 2087028, 600, 603.47, 595.25 +09/03/2015, 606.25, 1757851, 617, 619.71, 602.8213 +09/02/2015, 614.34, 2573982, 605.59, 614.34, 599.71 +09/01/2015, 597.79, 3699844, 602.36, 612.86, 594.1 +08/31/2015, 618.25, 2172168, 627.54, 635.8, 617.68 +08/28/2015, 630.38, 1975818, 632.82, 636.88, 624.56 +08/27/2015, 637.61, 3485906, 639.4, 643.59, 622 +08/26/2015, 628.62, 4187276, 610.35, 631.71, 599.05 +08/25/2015, 582.06, 3521916, 614.91, 617.45, 581.11 +08/24/2015, 589.61, 5727282, 573, 614, 565.05 +08/21/2015, 612.48, 4261666, 639.78, 640.05, 612.33 +08/20/2015, 646.83, 2854028, 655.46, 662.99, 642.9 +08/19/2015, 660.9, 2132265, 656.6, 667, 654.19 +08/18/2015, 656.13, 1455664, 661.9, 664, 653.46 +08/17/2015, 660.87, 1050553, 656.8, 661.38, 651.24 +08/14/2015, 657.12, 1071333, 655.01, 659.855, 652.66 +08/13/2015, 656.45, 1807182, 659.323, 664.5, 651.661 +08/12/2015, 659.56, 2938651, 663.08, 665, 652.29 +08/11/2015, 660.78, 5016425, 669.2, 674.9, 654.27 +08/10/2015, 633.73, 1653836, 639.48, 643.44, 631.249 +08/07/2015, 635.3, 1403441, 640.23, 642.68, 629.71 +08/06/2015, 642.68, 1572150, 645, 645.379, 632.25 +08/05/2015, 643.78, 2331720, 634.33, 647.86, 633.16 +08/04/2015, 629.25, 1486858, 628.42, 634.81, 627.16 +08/03/2015, 631.21, 1301439, 625.34, 633.0556, 625.34 +07/31/2015, 625.61, 1705286, 631.38, 632.91, 625.5 +07/30/2015, 632.59, 1472286, 630, 635.22, 622.05 +07/29/2015, 631.93, 1573146, 628.8, 633.36, 622.65 +07/28/2015, 628, 1713684, 632.83, 632.83, 623.31 +07/27/2015, 627.26, 2673801, 621, 634.3, 620.5 +07/24/2015, 623.56, 3622089, 647, 648.17, 622.52 +07/23/2015, 644.28, 3014035, 661.27, 663.63, 641 +07/22/2015, 662.1, 3707818, 660.89, 678.64, 659 +07/21/2015, 662.3, 3363342, 655.21, 673, 654.3 +07/20/2015, 663.02, 5857092, 659.24, 668.88, 653.01 +07/17/2015, 672.93, 11153500, 649, 674.468, 645 +07/16/2015, 579.85, 4559712, 565.12, 580.68, 565 +07/15/2015, 560.22, 1782264, 560.13, 566.5029, 556.79 +07/14/2015, 561.1, 3231284, 546.76, 565.8487, 546.71 +07/13/2015, 546.55, 2204610, 532.88, 547.11, 532.4001 +07/10/2015, 530.13, 1954951, 526.29, 532.56, 525.55 +07/09/2015, 520.68, 1840155, 523.12, 523.77, 520.35 +07/08/2015, 516.83, 1293372, 521.05, 522.734, 516.11 +07/07/2015, 525.02, 1595672, 523.13, 526.18, 515.18 +07/06/2015, 522.86, 1278587, 519.5, 525.25, 519 +07/02/2015, 523.4, 1235773, 521.08, 524.65, 521.08 +07/01/2015, 521.84, 1961197, 524.73, 525.69, 518.2305 +06/30/2015, 520.51, 2234284, 526.02, 526.25, 520.5 +06/29/2015, 521.52, 1935361, 525.01, 528.61, 520.54 +06/26/2015, 531.69, 2108629, 537.26, 537.76, 531.35 +06/25/2015, 535.23, 1332412, 538.87, 540.9, 535.23 +06/24/2015, 537.84, 1286576, 540, 540, 535.66 +06/23/2015, 540.48, 1196115, 539.64, 541.499, 535.25 +06/22/2015, 538.19, 1243535, 539.59, 543.74, 537.53 +06/19/2015, 536.69, 1890916, 537.21, 538.25, 533.01 +06/18/2015, 536.73, 1832450, 531, 538.15, 530.79 +06/17/2015, 529.26, 1269113, 529.37, 530.98, 525.1 +06/16/2015, 528.15, 1071728, 528.4, 529.6399, 525.56 +06/15/2015, 527.2, 1632675, 528, 528.3, 524 +06/12/2015, 532.33, 955489, 531.6, 533.12, 530.16 +06/11/2015, 534.61, 1208632, 538.425, 538.98, 533.02 +06/10/2015, 536.69, 1813775, 529.36, 538.36, 529.35 +06/09/2015, 526.69, 1454172, 527.56, 529.2, 523.01 +06/08/2015, 526.83, 1523960, 533.31, 534.12, 526.24 +06/05/2015, 533.33, 1375008, 536.35, 537.2, 532.52 +06/04/2015, 536.7, 1346044, 537.76, 540.59, 534.32 +06/03/2015, 540.31, 1716836, 539.91, 543.5, 537.11 +06/02/2015, 539.18, 1936721, 532.93, 543, 531.33 +06/01/2015, 533.99, 1900257, 536.79, 536.79, 529.76 +05/29/2015, 532.11, 2590445, 537.37, 538.63, 531.45 +05/28/2015, 539.78, 1029764, 538.01, 540.61, 536.25 +05/27/2015, 539.79, 1524783, 532.8, 540.55, 531.71 +05/26/2015, 532.32, 2404462, 538.12, 539, 529.88 +05/22/2015, 540.11, 1175065, 540.15, 544.19, 539.51 +05/21/2015, 542.51, 1461431, 537.95, 543.8399, 535.98 +05/20/2015, 539.27, 1430565, 538.49, 542.92, 532.972 +05/19/2015, 537.36, 1964037, 533.98, 540.66, 533.04 +05/18/2015, 532.3, 2001117, 532.01, 534.82, 528.85 +05/15/2015, 533.85, 1965088, 539.18, 539.2743, 530.38 +05/14/2015, 538.4, 1401005, 533.77, 539, 532.41 +05/13/2015, 529.62, 1253005, 530.56, 534.3215, 528.655 +05/12/2015, 529.04, 1633180, 531.6, 533.2089, 525.26 +05/11/2015, 535.7, 904465, 538.37, 541.98, 535.4 +05/08/2015, 538.22, 1527181, 536.65, 541.15, 536 +05/07/2015, 530.7, 1543986, 523.99, 533.46, 521.75 +05/06/2015, 524.22, 1566865, 531.24, 532.38, 521.085 +05/05/2015, 530.8, 1380519, 538.21, 539.74, 530.3906 +05/04/2015, 540.78, 1303830, 538.53, 544.07, 535.06 +05/01/2015, 537.9, 1758085, 538.43, 539.54, 532.1 +04/30/2015, 537.34, 2080834, 547.87, 548.59, 535.05 +04/29/2015, 549.08, 1696886, 550.47, 553.68, 546.905 +04/28/2015, 553.68, 1490735, 554.64, 556.02, 550.366 +04/27/2015, 555.37, 2390696, 563.39, 565.95, 553.2001 diff --git a/requirements.txt b/requirements.txt index 9d6cc502bde1..d21c13a54f1c 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,6 +2,7 @@ beautifulsoup4 black fake_useragent flake8 +keras matplotlib mypy numpy>=1.17.4 @@ -14,5 +15,5 @@ requests scikit-fuzzy sklearn sympy -tensorflow; python_version < '3.8' +tensorflow xgboost From a52dd66ac678af83b79f7ade6642a5bad012991f Mon Sep 17 00:00:00 2001 From: pharshil1902 <57032836+pharshil1902@users.noreply.github.com> Date: Thu, 7 May 2020 22:57:44 +0530 Subject: [PATCH 0550/1071] Added Shortest Job First Algorithm (#1957) * Added Shortest Job First Algorithm It is in IPYNB format but the dataframes are really looking good. Please, take a look. * Delete Shortest_Job_First_Algorithm.ipynb * Added Shortest Job First Algorithm * Update Shortest_Job_First Algorithm.py * Update Shortest_Job_First Algorithm.py * Update Shortest_Job_first Algorithm * Added Shortest_Job_First Algorithm * Added Shortest Job First Algorithm * Update shortest_job_first_algorithm.py * Format code with psf/black Co-authored-by: Christian Clauss --- scheduling/shortest_job_first_algorithm.py | 149 +++++++++++++++++++++ 1 file changed, 149 insertions(+) create mode 100644 scheduling/shortest_job_first_algorithm.py diff --git a/scheduling/shortest_job_first_algorithm.py b/scheduling/shortest_job_first_algorithm.py new file mode 100644 index 000000000000..18aadca0032f --- /dev/null +++ b/scheduling/shortest_job_first_algorithm.py @@ -0,0 +1,149 @@ +""" +Shortest job remainig first +Please note arrival time and burst +Please use spaces to separate times entered. +""" + +import pandas as pd +from typing import List + + +def calculate_waitingtime( + arrival_time: List[int], burst_time: List[int], no_of_processes: int +) -> List[int]: + + """ + Calculate the waiting time of each processes + Return: list of waiting times. + >>> calculate_waitingtime([1,2,3,4],[3,3,5,1],4) + [0, 3, 5, 0] + >>> calculate_waitingtime([1,2,3],[2,5,1],3) + [0, 2, 0] + >>> calculate_waitingtime([2,3],[5,1],2) + [1, 0] + """ + remaining_time = [0] * no_of_processes + waiting_time = [0] * no_of_processes + # Copy the burst time into remaining_time[] + for i in range(no_of_processes): + remaining_time[i] = burst_time[i] + + complete = 0 + increment_time = 0 + minm = 999999999 + short = 0 + check = False + + # Process until all processes are completed + while complete != no_of_processes: + for j in range(no_of_processes): + if arrival_time[j] <= increment_time: + if remaining_time[j] > 0: + if remaining_time[j] < minm: + minm = remaining_time[j] + short = j + check = True + + if not check: + increment_time += 1 + continue + remaining_time[short] -= 1 + + minm = remaining_time[short] + if minm == 0: + minm = 999999999 + + if remaining_time[short] == 0: + complete += 1 + check = False + + # Find finish time of current process + finish_time = increment_time + 1 + + # Calculate waiting time + finar = finish_time - arrival_time[short] + waiting_time[short] = finar - burst_time[short] + + if waiting_time[short] < 0: + waiting_time[short] = 0 + + # Increment time + increment_time += 1 + return waiting_time + + +def calculate_turnaroundtime( + burst_time: List[int], no_of_processes: int, waiting_time: List[int] +) -> List[int]: + """ + Calculate the turn around time of each Processes + Return: list of turn around times. + >>> calculate_turnaroundtime([3,3,5,1], 4, [0,3,5,0]) + [3, 6, 10, 1] + >>> calculate_turnaroundtime([3,3], 2, [0,3]) + [3, 6] + >>> calculate_turnaroundtime([8,10,1], 3, [1,0,3]) + [9, 10, 4] + """ + turn_around_time = [0] * no_of_processes + for i in range(no_of_processes): + turn_around_time[i] = burst_time[i] + waiting_time[i] + return turn_around_time + + +def calculate_average_times( + waiting_time: List[int], turn_around_time: List[int], no_of_processes: int +): + """ + This function calculates the average of the waiting & turnaround times + Prints: Average Waiting time & Average Turn Around Time + >>> calculate_average_times([0,3,5,0],[3,6,10,1],4) + Average waiting time = 2.00000 + Average turn around time = 5.0 + >>> calculate_average_times([2,3],[3,6],2) + Average waiting time = 2.50000 + Average turn around time = 4.5 + >>> calculate_average_times([10,4,3],[2,7,6],3) + Average waiting time = 5.66667 + Average turn around time = 5.0 + """ + total_waiting_time = 0 + total_turn_around_time = 0 + for i in range(no_of_processes): + total_waiting_time = total_waiting_time + waiting_time[i] + total_turn_around_time = total_turn_around_time + turn_around_time[i] + print("Average waiting time = %.5f" % (total_waiting_time / no_of_processes)) + print("Average turn around time =", total_turn_around_time / no_of_processes) + + +if __name__ == "__main__": + print("Enter how many process you want to analyze") + no_of_processes = int(input()) + burst_time = [0] * no_of_processes + arrival_time = [0] * no_of_processes + processes = list(range(1, no_of_processes + 1)) + + for i in range(no_of_processes): + print("Enter the arrival time and brust time for process:--" + str(i + 1)) + arrival_time[i], burst_time[i] = map(int, input().split()) + waiting_time = calculate_waitingtime(arrival_time, burst_time, no_of_processes) + bt = burst_time + n = no_of_processes + wt = waiting_time + turn_around_time = calculate_turnaroundtime(bt, n, wt) + calculate_average_times(waiting_time, turn_around_time, no_of_processes) + processes = list(range(1, no_of_processes + 1)) + fcfs = pd.DataFrame( + list(zip(processes, burst_time, arrival_time, waiting_time, turn_around_time)), + columns=[ + "Process", + "BurstTime", + "ArrivalTime", + "WaitingTime", + "TurnAroundTime", + ], + ) + + # Printing the dataFrame + pd.set_option("display.max_rows", fcfs.shape[0] + 1) + print(fcfs) From 9e5f365fed73cd968b5488866be2bb6e22b5ca21 Mon Sep 17 00:00:00 2001 From: Arvind Krishna <54891738+ArvindAROO@users.noreply.github.com> Date: Fri, 8 May 2020 01:14:34 +0530 Subject: [PATCH 0551/1071] Add sleep-sort (#1867) * added sleepsort Adding sleepsort * Add doctest and typing for sleepsort * Use self-descriptive variable name * Update sleepsort.py * Update sorts/sleepsort.py Co-authored-by: John Law Co-authored-by: Christian Clauss --- sorts/sleepsort.py | 48 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) create mode 100644 sorts/sleepsort.py diff --git a/sorts/sleepsort.py b/sorts/sleepsort.py new file mode 100644 index 000000000000..5fa688d1bbd6 --- /dev/null +++ b/sorts/sleepsort.py @@ -0,0 +1,48 @@ +"""Sleepsort is probably the wierdest of all sorting functions +with time-complexity of O(max(input)+n) which is +quite different from almost all other sorting techniques. +If the number of inputs is small then the complexity +can be approximated to be O(max(input)) which is a constant + +If the number of inputs is large, the complexity is +approximately O(n). + +This function uses multithreading a kind of higher order programming +and calls n functions, each with a sleep time equal to its number. +Hence each of the functions wake in sorted form. + +This function is not stable for very large values. + +https://rosettacode.org/wiki/Sorting_algorithms/Sleep_sort +""" + +from time import sleep +from threading import Timer +from typing import List + + +def sleepsort(values: List[int]) -> List[int]: + """ + Sort the list using sleepsort. + >>> sleepsort([3, 2, 4, 7, 3, 6, 9, 1]) + [1, 2, 3, 3, 4, 6, 7, 9] + >>> sleepsort([3, 2, 1, 9, 8, 4, 2]) + [1, 2, 2, 3, 4, 8, 9] + """ + sleepsort.result = [] + def append_to_result(x): + sleepsort.result.append(x) + mx = values[0] + for v in values: + if mx < v: + mx = v + Timer(v, append_to_result, [v]).start() + sleep(mx+1) + return sleepsort.result + +if __name__ == '__main__': + import doctest + doctest.testmod() + x = [3, 2, 4, 7, 3, 6, 9, 1] + sorted_x = sleepsort(x) + print(sorted_x) From 7469fb6edd5570094295509e89c8c2c0af24d1b5 Mon Sep 17 00:00:00 2001 From: siva1098 <32545976+siva1098@users.noreply.github.com> Date: Fri, 8 May 2020 03:00:24 +0530 Subject: [PATCH 0552/1071] Add graphs/frequent_pattern_graph_miner.py (#1866) * Add files via upload * Update graphs/frequent_pattern_graph_miner.py Co-Authored-By: Christian Clauss * Update graphs/frequent_pattern_graph_miner.py Co-Authored-By: Christian Clauss * Update frequent_pattern_graph_miner.py * Update frequent_pattern_graph_miner.py * Update frequent_pattern_graph_miner.py * Update frequent_pattern_graph_miner.py * Update graphs/frequent_pattern_graph_miner.py Co-Authored-By: Christian Clauss * Update graphs/frequent_pattern_graph_miner.py Co-Authored-By: Christian Clauss * Update graphs/frequent_pattern_graph_miner.py Co-Authored-By: Christian Clauss * Update graphs/frequent_pattern_graph_miner.py Co-Authored-By: Christian Clauss * Update graphs/frequent_pattern_graph_miner.py Co-Authored-By: Christian Clauss * Update graphs/frequent_pattern_graph_miner.py Co-Authored-By: Christian Clauss * Update graphs/frequent_pattern_graph_miner.py Co-Authored-By: Christian Clauss * Update graphs/frequent_pattern_graph_miner.py Co-Authored-By: Christian Clauss * Update frequent_pattern_graph_miner.py * Update frequent_pattern_graph_miner.py * Update frequent_pattern_graph_miner.py * Update frequent_pattern_graph_miner.py * Update frequent_pattern_graph_miner.py * Update graphs/frequent_pattern_graph_miner.py Co-Authored-By: Christian Clauss * Update frequent_pattern_graph_miner.py * Update frequent_pattern_graph_miner.py * Update frequent_pattern_graph_miner.py * Update frequent_pattern_graph_miner.py * Update frequent_pattern_graph_miner.py * Update frequent_pattern_graph_miner.py * Update graphs/frequent_pattern_graph_miner.py Co-Authored-By: Christian Clauss * Update frequent_pattern_graph_miner.py * Update frequent_pattern_graph_miner.py * Update frequent_pattern_graph_miner.py * Update frequent_pattern_graph_miner.py * Update frequent_pattern_graph_miner.py * Update frequent_pattern_graph_miner.py * Update frequent_pattern_graph_miner.py * Whitespace changes * Format with psf/black Co-authored-by: Christian Clauss --- graphs/frequent_pattern_graph_miner.py | 232 +++++++++++++++++++++++++ 1 file changed, 232 insertions(+) create mode 100644 graphs/frequent_pattern_graph_miner.py diff --git a/graphs/frequent_pattern_graph_miner.py b/graphs/frequent_pattern_graph_miner.py new file mode 100644 index 000000000000..aa14fbdd3a3c --- /dev/null +++ b/graphs/frequent_pattern_graph_miner.py @@ -0,0 +1,232 @@ +""" +FP-GraphMiner - A Fast Frequent Pattern Mining Algorithm for Network Graphs + +A novel Frequent Pattern Graph Mining algorithm, FP-GraphMiner, that compactly +represents a set of network graphs as a Frequent Pattern Graph (or FP-Graph). +This graph can be used to efficiently mine frequent subgraphs including maximal +frequent subgraphs and maximum common subgraphs. + +URL: https://www.researchgate.net/publication/235255851 +""" +# fmt: off +edge_array = [ + ['ab-e1', 'ac-e3', 'ad-e5', 'bc-e4', 'bd-e2', 'be-e6', 'bh-e12', 'cd-e2', 'ce-e4', + 'de-e1', 'df-e8', 'dg-e5', 'dh-e10', 'ef-e3', 'eg-e2', 'fg-e6', 'gh-e6', 'hi-e3'], + ['ab-e1', 'ac-e3', 'ad-e5', 'bc-e4', 'bd-e2', 'be-e6', 'cd-e2', 'de-e1', 'df-e8', + 'ef-e3', 'eg-e2', 'fg-e6'], + ['ab-e1', 'ac-e3', 'bc-e4', 'bd-e2', 'de-e1', 'df-e8', 'dg-e5', 'ef-e3', 'eg-e2', + 'eh-e12', 'fg-e6', 'fh-e10', 'gh-e6'], + ['ab-e1', 'ac-e3', 'bc-e4', 'bd-e2', 'bh-e12', 'cd-e2', 'df-e8', 'dh-e10'], + ['ab-e1', 'ac-e3', 'ad-e5', 'bc-e4', 'bd-e2', 'cd-e2', 'ce-e4', 'de-e1', 'df-e8', + 'dg-e5', 'ef-e3', 'eg-e2', 'fg-e6'] + ] +# fmt: on + + +def get_distinct_edge(edge_array): + """ + Return Distinct edges from edge array of multiple graphs + >>> sorted(get_distinct_edge(edge_array)) + ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h'] + """ + distinct_edge = set() + for row in edge_array: + for item in row: + distinct_edge.add(item[0]) + return list(distinct_edge) + + +def get_bitcode(edge_array, distinct_edge): + """ + Return bitcode of distinct_edge + """ + bitcode = ["0"] * len(edge_array) + for i, row in enumerate(edge_array): + for item in row: + if distinct_edge in item[0]: + bitcode[i] = "1" + break + return "".join(bitcode) + + +def get_frequency_table(edge_array): + """ + Returns Frequency Table + """ + distinct_edge = get_distinct_edge(edge_array) + frequency_table = dict() + + for item in distinct_edge: + bit = get_bitcode(edge_array, item) + # print('bit',bit) + # bt=''.join(bit) + s = bit.count("1") + frequency_table[item] = [s, bit] + # Store [Distinct edge, WT(Bitcode), Bitcode] in descending order + sorted_frequency_table = [ + [k, v[0], v[1]] + for k, v in sorted(frequency_table.items(), key=lambda v: v[1][0], reverse=True) + ] + return sorted_frequency_table + + +def get_nodes(frequency_table): + """ + Returns nodes + format nodes={bitcode:edges that represent the bitcode} + >>> get_nodes([['ab', 5, '11111'], ['ac', 5, '11111'], ['df', 5, '11111'], + ... ['bd', 5, '11111'], ['bc', 5, '11111']]) + {'11111': ['ab', 'ac', 'df', 'bd', 'bc']} + """ + nodes = {} + for i, item in enumerate(frequency_table): + nodes.setdefault(item[2], []).append(item[0]) + return nodes + + +def get_cluster(nodes): + """ + Returns cluster + format cluster:{WT(bitcode):nodes with same WT} + """ + cluster = {} + for key, value in nodes.items(): + cluster.setdefault(key.count("1"), {})[key] = value + return cluster + + +def get_support(cluster): + """ + Returns support + >>> get_support({5: {'11111': ['ab', 'ac', 'df', 'bd', 'bc']}, + ... 4: {'11101': ['ef', 'eg', 'de', 'fg'], '11011': ['cd']}, + ... 3: {'11001': ['ad'], '10101': ['dg']}, + ... 2: {'10010': ['dh', 'bh'], '11000': ['be'], '10100': ['gh'], + ... '10001': ['ce']}, + ... 1: {'00100': ['fh', 'eh'], '10000': ['hi']}}) + [100.0, 80.0, 60.0, 40.0, 20.0] + """ + return [i * 100 / len(cluster) for i in cluster] + + +def print_all() -> None: + print("\nNodes\n") + for key, value in nodes.items(): + print(key, value) + print("\nSupport\n") + print(support) + print("\n Cluster \n") + for key, value in sorted(cluster.items(), reverse=True): + print(key, value) + print("\n Graph\n") + for key, value in graph.items(): + print(key, value) + print("\n Edge List of Frequent subgraphs \n") + for edge_list in freq_subgraph_edge_list: + print(edge_list) + + +def create_edge(nodes, graph, cluster, c1): + """ + create edge between the nodes + """ + for i in cluster[c1].keys(): + count = 0 + c2 = c1 + 1 + while c2 < max(cluster.keys()): + for j in cluster[c2].keys(): + """ + creates edge only if the condition satisfies + """ + if int(i, 2) & int(j, 2) == int(i, 2): + if tuple(nodes[i]) in graph: + graph[tuple(nodes[i])].append(nodes[j]) + else: + graph[tuple(nodes[i])] = [nodes[j]] + count += 1 + if count == 0: + c2 = c2 + 1 + else: + break + + +def construct_graph(cluster, nodes): + X = cluster[max(cluster.keys())] + cluster[max(cluster.keys()) + 1] = "Header" + graph = {} + for i in X: + if tuple(["Header"]) in graph: + graph[tuple(["Header"])].append(X[i]) + else: + graph[tuple(["Header"])] = [X[i]] + for i in X: + graph[tuple(X[i])] = [["Header"]] + i = 1 + while i < max(cluster) - 1: + create_edge(nodes, graph, cluster, i) + i = i + 1 + return graph + + +def myDFS(graph, start, end, path=[]): + """ + find different DFS walk from given node to Header node + """ + path = path + [start] + if start == end: + paths.append(path) + for node in graph[start]: + if tuple(node) not in path: + myDFS(graph, tuple(node), end, path) + + +def find_freq_subgraph_given_support(s, cluster, graph): + """ + find edges of multiple frequent subgraphs + """ + k = int(s / 100 * (len(cluster) - 1)) + for i in cluster[k].keys(): + myDFS(graph, tuple(cluster[k][i]), tuple(["Header"])) + + +def freq_subgraphs_edge_list(paths): + """ + returns Edge list for frequent subgraphs + """ + freq_sub_EL = [] + for edges in paths: + EL = [] + for j in range(len(edges) - 1): + temp = list(edges[j]) + for e in temp: + edge = (e[0], e[1]) + EL.append(edge) + freq_sub_EL.append(EL) + return freq_sub_EL + + +def preprocess(edge_array): + """ + Preprocess the edge array + >>> preprocess([['ab-e1', 'ac-e3', 'ad-e5', 'bc-e4', 'bd-e2', 'be-e6', 'bh-e12', + ... 'cd-e2', 'ce-e4', 'de-e1', 'df-e8', 'dg-e5', 'dh-e10', 'ef-e3', + ... 'eg-e2', 'fg-e6', 'gh-e6', 'hi-e3']]) + + """ + for i in range(len(edge_array)): + for j in range(len(edge_array[i])): + t = edge_array[i][j].split("-") + edge_array[i][j] = t + + +if __name__ == "__main__": + preprocess(edge_array) + frequency_table = get_frequency_table(edge_array) + nodes = get_nodes(frequency_table) + cluster = get_cluster(nodes) + support = get_support(cluster) + graph = construct_graph(cluster, nodes) + find_freq_subgraph_given_support(60, cluster, graph) + paths = [] + freq_subgraph_edge_list = freq_subgraphs_edge_list(paths) + print_all() From c18c677a38cfd24ae6eef39620e2fde98245f39f Mon Sep 17 00:00:00 2001 From: mateuszz0000 Date: Thu, 7 May 2020 23:47:28 +0200 Subject: [PATCH 0553/1071] Added Nearest neighbour algorithm (#1934) --- digital_image_processing/resize/__init__.py | 0 digital_image_processing/resize/resize.py | 69 +++++++++++++++++++ .../test_digital_image_processing.py | 8 +++ 3 files changed, 77 insertions(+) create mode 100644 digital_image_processing/resize/__init__.py create mode 100644 digital_image_processing/resize/resize.py diff --git a/digital_image_processing/resize/__init__.py b/digital_image_processing/resize/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/digital_image_processing/resize/resize.py b/digital_image_processing/resize/resize.py new file mode 100644 index 000000000000..b7d493e70b4b --- /dev/null +++ b/digital_image_processing/resize/resize.py @@ -0,0 +1,69 @@ +""" Multiple image resizing techniques """ +import numpy as np +from cv2 import imread, imshow, waitKey, destroyAllWindows + + +class NearestNeighbour: + """ + Simplest and fastest version of image resizing. + Source: https://en.wikipedia.org/wiki/Nearest-neighbor_interpolation + """ + + def __init__(self, img, dst_width: int, dst_height: int): + if dst_width < 0 or dst_height < 0: + raise ValueError(f"Destination width/height should be > 0") + + self.img = img + self.src_w = img.shape[1] + self.src_h = img.shape[0] + self.dst_w = dst_width + self.dst_h = dst_height + + self.ratio_x = self.src_w / self.dst_w + self.ratio_y = self.src_h / self.dst_h + + self.output = self.output_img = ( + np.ones((self.dst_h, self.dst_w, 3), np.uint8) * 255 + ) + + def process(self): + for i in range(self.dst_h): + for j in range(self.dst_w): + self.output[i][j] = self.img[self.get_y(i)][self.get_x(j)] + + def get_x(self, x: int) -> int: + """ + Get parent X coordinate for destination X + :param x: Destination X coordinate + :return: Parent X coordinate based on `x ratio` + >>> nn = NearestNeighbour(imread("digital_image_processing/image_data/lena.jpg", 1), 100, 100) + >>> nn.ratio_x = 0.5 + >>> nn.get_x(4) + 2 + """ + return int(self.ratio_x * x) + + def get_y(self, y: int) -> int: + """ + Get parent Y coordinate for destination Y + :param y: Destination X coordinate + :return: Parent X coordinate based on `y ratio` + >>> nn = NearestNeighbour(imread("digital_image_processing/image_data/lena.jpg", 1), 100, 100) + >>> nn.ratio_y = 0.5 + >>> nn.get_y(4) + 2 + """ + return int(self.ratio_y * y) + + +if __name__ == "__main__": + dst_w, dst_h = 800, 600 + im = imread("image_data/lena.jpg", 1) + n = NearestNeighbour(im, dst_w, dst_h) + n.process() + + imshow( + f"Image resized from: {im.shape[1]}x{im.shape[0]} to {dst_w}x{dst_h}", n.output + ) + waitKey(0) + destroyAllWindows() diff --git a/digital_image_processing/test_digital_image_processing.py b/digital_image_processing/test_digital_image_processing.py index 89cf6007af60..327e2c67f50f 100644 --- a/digital_image_processing/test_digital_image_processing.py +++ b/digital_image_processing/test_digital_image_processing.py @@ -11,6 +11,7 @@ import digital_image_processing.convert_to_negative as cn import digital_image_processing.sepia as sp import digital_image_processing.dithering.burkes as bs +import digital_image_processing.resize.resize as rs from cv2 import imread, cvtColor, COLOR_BGR2GRAY from numpy import array, uint8 from PIL import Image @@ -82,3 +83,10 @@ def test_burkes(file_path: str = "digital_image_processing/image_data/lena_small burkes = bs.Burkes(imread(file_path, 1), 120) burkes.process() assert burkes.output_img.any() + +def test_nearest_neighbour( + file_path: str = "digital_image_processing/image_data/lena_small.jpg", +): + nn = rs.NearestNeighbour(imread(file_path, 1), 400, 200) + nn.process() + assert nn.output.any() From 369562a1e8a6a794167496f2cb9e225d4cf42b10 Mon Sep 17 00:00:00 2001 From: Maxim R <49735721+mrmaxguns@users.noreply.github.com> Date: Fri, 8 May 2020 00:44:07 -0500 Subject: [PATCH 0554/1071] Upgrades to caesar_cipher.py (#1958) * Added more flexibility to functions, decreased amount of repeating code * Added docstrings * Updated input functions * Added doctests * removed test piece of code * black . * Updated caesar cipher standard alphabet to fit python 3.8 * Update and rename sleepsort.py to sleep_sort.py * Or 4 Co-authored-by: Christian Clauss --- ciphers/caesar_cipher.py | 263 +++++++++++++++--- .../test_digital_image_processing.py | 1 + sorts/sleep_sort.py | 49 ++++ sorts/sleepsort.py | 48 ---- 4 files changed, 275 insertions(+), 86 deletions(-) create mode 100644 sorts/sleep_sort.py delete mode 100644 sorts/sleepsort.py diff --git a/ciphers/caesar_cipher.py b/ciphers/caesar_cipher.py index 4427a5234d70..7bda519767a1 100644 --- a/ciphers/caesar_cipher.py +++ b/ciphers/caesar_cipher.py @@ -1,63 +1,250 @@ -def encrypt(input_string: str, key: int) -> str: - result = "" - for x in input_string: - if not x.isalpha(): - result += x - elif x.isupper(): - result += chr((ord(x) + key - 65) % 26 + 65) - elif x.islower(): - result += chr((ord(x) + key - 97) % 26 + 97) - return result +from string import ascii_letters + + +def encrypt(input_string: str, key: int, alphabet=None) -> str: + """ + encrypt + ======= + Encodes a given string with the caesar cipher and returns the encoded + message + + Parameters: + ----------- + * input_string: the plain-text that needs to be encoded + * key: the number of letters to shift the message by + + Optional: + * alphabet (None): the alphabet used to encode the cipher, if not + specified, the standard english alphabet with upper and lowercase + letters is used + + Returns: + * A string containing the encoded cipher-text + + More on the caesar cipher + ========================= + The caesar cipher is named after Julius Caesar who used it when sending + secret military messages to his troops. This is a simple substitution cipher + where very character in the plain-text is shifted by a certain number known + as the "key" or "shift". + + Example: + Say we have the following message: + "Hello, captain" + + And our alphabet is made up of lower and uppercase letters: + "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" + + And our shift is "2" + + We can then encode the message, one letter at a time. "H" would become "J", + since "J" is two letters away, and so on. If the shift is ever two large, or + our letter is at the end of the alphabet, we just start at the beginning + ("Z" would shift to "a" then "b" and so on). + + Our final message would be "Jgnnq, ecrvckp" + + Further reading + =============== + * https://en.m.wikipedia.org/wiki/Caesar_cipher + + Doctests + ======== + >>> encrypt('The quick brown fox jumps over the lazy dog', 8) + 'bpm yCqks jzwEv nwF rCuxA wDmz Bpm tiHG lwo' + >>> encrypt('A very large key', 8000) + 's nWjq dSjYW cWq' -def decrypt(input_string: str, key: int) -> str: + >>> encrypt('a lowercase alphabet', 5, 'abcdefghijklmnopqrstuvwxyz') + 'f qtbjwhfxj fqumfgjy' + """ + # Set default alphabet to lower and upper case english chars + alpha = alphabet or ascii_letters + + # The final result string result = "" - for x in input_string: - if not x.isalpha(): - result += x - elif x.isupper(): - result += chr((ord(x) - key - 65) % 26 + 65) - elif x.islower(): - result += chr((ord(x) - key - 97) % 26 + 97) + + for character in input_string: + if character not in alpha: + # Append without encryption if character is not in the alphabet + result += character + else: + # Get the index of the new key and make sure it isn't too large + new_key = (alpha.index(character) + key) % len(alpha) + + # Append the encoded character to the alphabet + result += alpha[new_key] + return result -def brute_force(input_string: str) -> None: +def decrypt(input_string: str, key: int, alphabet=None) -> str: + """ + decrypt + ======= + Decodes a given string of cipher-text and returns the decoded plain-text + + Parameters: + ----------- + * input_string: the cipher-text that needs to be decoded + * key: the number of letters to shift the message backwards by to decode + + Optional: + * alphabet (None): the alphabet used to decode the cipher, if not + specified, the standard english alphabet with upper and lowercase + letters is used + + Returns: + * A string containing the decoded plain-text + + More on the caesar cipher + ========================= + The caesar cipher is named after Julius Caesar who used it when sending + secret military messages to his troops. This is a simple substitution cipher + where very character in the plain-text is shifted by a certain number known + as the "key" or "shift". Please keep in mind, here we will be focused on + decryption. + + Example: + Say we have the following cipher-text: + "Jgnnq, ecrvckp" + + And our alphabet is made up of lower and uppercase letters: + "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" + + And our shift is "2" + + To decode the message, we would do the same thing as encoding, but in + reverse. The first letter, "J" would become "H" (remember: we are decoding) + because "H" is two letters in reverse (to the left) of "J". We would + continue doing this. A letter like "a" would shift back to the end of + the alphabet, and would become "Z" or "Y" and so on. + + Our final message would be "Hello, captain" + + Further reading + =============== + * https://en.m.wikipedia.org/wiki/Caesar_cipher + + Doctests + ======== + >>> decrypt('bpm yCqks jzwEv nwF rCuxA wDmz Bpm tiHG lwo', 8) + 'The quick brown fox jumps over the lazy dog' + + >>> decrypt('s nWjq dSjYW cWq', 8000) + 'A very large key' + + >>> decrypt('f qtbjwhfxj fqumfgjy', 5, 'abcdefghijklmnopqrstuvwxyz') + 'a lowercase alphabet' + """ + # Turn on decode mode by making the key negative + key *= -1 + + return encrypt(input_string, key, alphabet) + + +def brute_force(input_string: str, alphabet=None) -> dict: + """ + brute_force + =========== + Returns all the possible combinations of keys and the decoded strings in the + form of a dictionary + + Parameters: + ----------- + * input_string: the cipher-text that needs to be used during brute-force + + Optional: + * alphabet: (None): the alphabet used to decode the cipher, if not + specified, the standard english alphabet with upper and lowercase + letters is used + + More about brute force + ====================== + Brute force is when a person intercepts a message or password, not knowing + the key and tries every single combination. This is easy with the caesar + cipher since there are only all the letters in the alphabet. The more + complex the cipher, the larger amount of time it will take to do brute force + + Ex: + Say we have a 5 letter alphabet (abcde), for simplicity and we intercepted the + following message: + + "dbc" + + we could then just write out every combination: + ecd... and so on, until we reach a combination that makes sense: + "cab" + + Further reading + =============== + * https://en.wikipedia.org/wiki/Brute_force + + Doctests + ======== + >>> brute_force("jFyuMy xIH'N vLONy zILwy Gy!")[20] + "Please don't brute force me!" + + >>> brute_force(1) + Traceback (most recent call last): + TypeError: 'int' object is not iterable + """ + # Set default alphabet to lower and upper case english chars + alpha = alphabet or ascii_letters + + # The key during testing (will increase) key = 1 + + # The encoded result result = "" - while key <= 94: - for x in input_string: - indx = (ord(x) - key) % 256 - if indx < 32: - indx = indx + 95 - result = result + chr(indx) - print(f"Key: {key}\t| Message: {result}") + + # To store data on all the combinations + brute_force_data = {} + + # Cycle through each combination + while key <= len(alpha): + # Decrypt the message + result = decrypt(input_string, key, alpha) + + # Update the data + brute_force_data[key] = result + + # Reset result and increase the key result = "" key += 1 - return None + + return brute_force_data def main(): while True: - print(f'{"-" * 10}\n Menu\n{"-" * 10}') + print(f'\n{"-" * 10}\n Menu\n{"-" * 10}') print(*["1.Encrpyt", "2.Decrypt", "3.BruteForce", "4.Quit"], sep="\n") - choice = input("What would you like to do?: ") - if choice not in ["1", "2", "3", "4"]: + + # get user input + choice = input("\nWhat would you like to do?: ").strip() or "4" + + # run functions based on what the user chose + if choice not in ("1", "2", "3", "4"): print("Invalid choice, please enter a valid choice") elif choice == "1": input_string = input("Please enter the string to be encrypted: ") - key = int(input("Please enter off-set between 0-25: ")) - if key in range(1, 95): - print(encrypt(input_string.lower(), key)) + key = int(input("Please enter off-set: ").strip()) + + print(encrypt(input_string, key)) elif choice == "2": input_string = input("Please enter the string to be decrypted: ") - key = int(input("Please enter off-set between 1-94: ")) - if key in range(1, 95): - print(decrypt(input_string, key)) + key = int(input("Please enter off-set: ").strip()) + + print(decrypt(input_string, key)) elif choice == "3": input_string = input("Please enter the string to be decrypted: ") - brute_force(input_string) - main() + brute_force_data = brute_force(input_string) + + for key, value in brute_force_data.items(): + print(f"Key: {key} | Message: {value}") + elif choice == "4": print("Goodbye.") break diff --git a/digital_image_processing/test_digital_image_processing.py b/digital_image_processing/test_digital_image_processing.py index 327e2c67f50f..fe8890de9a31 100644 --- a/digital_image_processing/test_digital_image_processing.py +++ b/digital_image_processing/test_digital_image_processing.py @@ -84,6 +84,7 @@ def test_burkes(file_path: str = "digital_image_processing/image_data/lena_small burkes.process() assert burkes.output_img.any() + def test_nearest_neighbour( file_path: str = "digital_image_processing/image_data/lena_small.jpg", ): diff --git a/sorts/sleep_sort.py b/sorts/sleep_sort.py new file mode 100644 index 000000000000..0feda9c5e038 --- /dev/null +++ b/sorts/sleep_sort.py @@ -0,0 +1,49 @@ +""" +Sleep sort is probably the wierdest of all sorting functions with time-complexity of +O(max(input)+n) which is quite different from almost all other sorting techniques. +If the number of inputs is small then the complexity can be approximated to be +O(max(input)) which is a constant + +If the number of inputs is large, the complexity is approximately O(n). + +This function uses multithreading a kind of higher order programming and calls n +functions, each with a sleep time equal to its number. Hence each of function wakes +in sorted time. + +This function is not stable for very large values. + +https://rosettacode.org/wiki/Sorting_algorithms/Sleep_sort +""" +from threading import Timer +from time import sleep +from typing import List + + +def sleep_sort(values: List[int]) -> List[int]: + """ + Sort the list using sleepsort. + >>> sleep_sort([3, 2, 4, 7, 3, 6, 9, 1]) + [1, 2, 3, 3, 4, 6, 7, 9] + >>> sleep_sort([3, 2, 1, 9, 8, 4, 2]) + [1, 2, 2, 3, 4, 8, 9] + """ + sleep_sort.result = [] + + def append_to_result(x): + sleep_sort.result.append(x) + + mx = values[0] + for value in values: + if mx < value: + mx = value + Timer(value, append_to_result, [value]).start() + sleep(mx + 1) + return sleep_sort.result + + +if __name__ == "__main__": + import doctest + + doctest.testmod() + + print(sleep_sort([3, 2, 4, 7, 3, 6, 9, 1])) diff --git a/sorts/sleepsort.py b/sorts/sleepsort.py deleted file mode 100644 index 5fa688d1bbd6..000000000000 --- a/sorts/sleepsort.py +++ /dev/null @@ -1,48 +0,0 @@ -"""Sleepsort is probably the wierdest of all sorting functions -with time-complexity of O(max(input)+n) which is -quite different from almost all other sorting techniques. -If the number of inputs is small then the complexity -can be approximated to be O(max(input)) which is a constant - -If the number of inputs is large, the complexity is -approximately O(n). - -This function uses multithreading a kind of higher order programming -and calls n functions, each with a sleep time equal to its number. -Hence each of the functions wake in sorted form. - -This function is not stable for very large values. - -https://rosettacode.org/wiki/Sorting_algorithms/Sleep_sort -""" - -from time import sleep -from threading import Timer -from typing import List - - -def sleepsort(values: List[int]) -> List[int]: - """ - Sort the list using sleepsort. - >>> sleepsort([3, 2, 4, 7, 3, 6, 9, 1]) - [1, 2, 3, 3, 4, 6, 7, 9] - >>> sleepsort([3, 2, 1, 9, 8, 4, 2]) - [1, 2, 2, 3, 4, 8, 9] - """ - sleepsort.result = [] - def append_to_result(x): - sleepsort.result.append(x) - mx = values[0] - for v in values: - if mx < v: - mx = v - Timer(v, append_to_result, [v]).start() - sleep(mx+1) - return sleepsort.result - -if __name__ == '__main__': - import doctest - doctest.testmod() - x = [3, 2, 4, 7, 3, 6, 9, 1] - sorted_x = sleepsort(x) - print(sorted_x) From 8b55a143fa14ff4b3acbe38a91630c99c2f84367 Mon Sep 17 00:00:00 2001 From: Christian Clauss Date: Sat, 9 May 2020 13:24:25 +0200 Subject: [PATCH 0555/1071] Travis CI: lint for useless backslashes (#1961) * Travis CI: lint for useless backslashes * updating DIRECTORY.md * flake8 --max-complexity=25 Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> --- .travis.yml | 2 +- DIRECTORY.md | 8 ++++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 10b91b2561ac..b86a57460e56 100644 --- a/.travis.yml +++ b/.travis.yml @@ -7,7 +7,7 @@ before_install: pip install --upgrade pip setuptools six install: pip install -r requirements.txt before_script: - black --check . || true - - flake8 . --count --select=E101,E501,E722,E9,F4,F63,F7,F82,W191 --max-line-length=127 --show-source --statistics + - flake8 . --count --select=E101,E5,E722,E9,F4,F63,F7,F82,W191 --max-complexity=25 --max-line-length=127 --show-source --statistics - flake8 . --count --exit-zero --max-line-length=127 --statistics script: - scripts/validate_filenames.py # no uppercase, no spaces, in a directory diff --git a/DIRECTORY.md b/DIRECTORY.md index 61783b0e5bcf..27fb1a8988e3 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -43,6 +43,7 @@ * [Brute Force Caesar Cipher](https://github.com/TheAlgorithms/Python/blob/master/ciphers/brute_force_caesar_cipher.py) * [Caesar Cipher](https://github.com/TheAlgorithms/Python/blob/master/ciphers/caesar_cipher.py) * [Cryptomath Module](https://github.com/TheAlgorithms/Python/blob/master/ciphers/cryptomath_module.py) + * [Decrypt Caesar With Chi Squared](https://github.com/TheAlgorithms/Python/blob/master/ciphers/decrypt_caesar_with_chi_squared.py) * [Deterministic Miller Rabin](https://github.com/TheAlgorithms/Python/blob/master/ciphers/deterministic_miller_rabin.py) * [Diffie](https://github.com/TheAlgorithms/Python/blob/master/ciphers/diffie.py) * [Elgamal Key Generator](https://github.com/TheAlgorithms/Python/blob/master/ciphers/elgamal_key_generator.py) @@ -155,6 +156,8 @@ * Histogram Equalization * [Histogram Stretch](https://github.com/TheAlgorithms/Python/blob/master/digital_image_processing/histogram_equalization/histogram_stretch.py) * [Index Calculation](https://github.com/TheAlgorithms/Python/blob/master/digital_image_processing/index_calculation.py) + * Resize + * [Resize](https://github.com/TheAlgorithms/Python/blob/master/digital_image_processing/resize/resize.py) * Rotation * [Rotation](https://github.com/TheAlgorithms/Python/blob/master/digital_image_processing/rotation/rotation.py) * [Sepia](https://github.com/TheAlgorithms/Python/blob/master/digital_image_processing/sepia.py) @@ -233,6 +236,7 @@ * [Eulerian Path And Circuit For Undirected Graph](https://github.com/TheAlgorithms/Python/blob/master/graphs/eulerian_path_and_circuit_for_undirected_graph.py) * [Even Tree](https://github.com/TheAlgorithms/Python/blob/master/graphs/even_tree.py) * [Finding Bridges](https://github.com/TheAlgorithms/Python/blob/master/graphs/finding_bridges.py) + * [Frequent Pattern Graph Miner](https://github.com/TheAlgorithms/Python/blob/master/graphs/frequent_pattern_graph_miner.py) * [G Topological Sort](https://github.com/TheAlgorithms/Python/blob/master/graphs/g_topological_sort.py) * [Graph List](https://github.com/TheAlgorithms/Python/blob/master/graphs/graph_list.py) * [Graph Matrix](https://github.com/TheAlgorithms/Python/blob/master/graphs/graph_matrix.py) @@ -271,6 +275,8 @@ * [Linear Discriminant Analysis](https://github.com/TheAlgorithms/Python/blob/master/machine_learning/linear_discriminant_analysis.py) * [Linear Regression](https://github.com/TheAlgorithms/Python/blob/master/machine_learning/linear_regression.py) * [Logistic Regression](https://github.com/TheAlgorithms/Python/blob/master/machine_learning/logistic_regression.py) + * Lstm + * [Lstm Prediction](https://github.com/TheAlgorithms/Python/blob/master/machine_learning/lstm/lstm_prediction.py) * [Multilayer Perceptron Classifier](https://github.com/TheAlgorithms/Python/blob/master/machine_learning/multilayer_perceptron_classifier.py) * [Polymonial Regression](https://github.com/TheAlgorithms/Python/blob/master/machine_learning/polymonial_regression.py) * [Random Forest Classifier](https://github.com/TheAlgorithms/Python/blob/master/machine_learning/random_forest_classifier.py) @@ -543,6 +549,7 @@ ## Scheduling * [First Come First Served](https://github.com/TheAlgorithms/Python/blob/master/scheduling/first_come_first_served.py) + * [Shortest Job First Algorithm](https://github.com/TheAlgorithms/Python/blob/master/scheduling/shortest_job_first_algorithm.py) ## Searches * [Binary Search](https://github.com/TheAlgorithms/Python/blob/master/searches/binary_search.py) @@ -589,6 +596,7 @@ * [Recursive Insertion Sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/recursive_insertion_sort.py) * [Selection Sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/selection_sort.py) * [Shell Sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/shell_sort.py) + * [Sleep Sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/sleep_sort.py) * [Stooge Sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/stooge_sort.py) * [Tim Sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/tim_sort.py) * [Topological Sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/topological_sort.py) From eef63939353733ec722b7e2c16889200317d2f2c Mon Sep 17 00:00:00 2001 From: Raj-Parekh24 <54325945+Raj-Parekh24@users.noreply.github.com> Date: Sun, 10 May 2020 00:30:59 +0530 Subject: [PATCH 0556/1071] Kadanes_algorithm (#1959) * Add files via upload * Update Kadane's_algorithm.py * Update and rename Kadane's_algorithm.py to kadanes_algorithm.py * Update kadanes_algorithm.py * Update kadanes_algorithm.py * Update kadanes_algorithm.py * Update kadanes_algorithm.py * Update kadanes_algorithm.py Co-authored-by: Christian Clauss --- maths/kadanes_algorithm.py | 65 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 65 insertions(+) create mode 100644 maths/kadanes_algorithm.py diff --git a/maths/kadanes_algorithm.py b/maths/kadanes_algorithm.py new file mode 100644 index 000000000000..d02f238a0dc9 --- /dev/null +++ b/maths/kadanes_algorithm.py @@ -0,0 +1,65 @@ +""" +Kadane's algorithm to get maximum subarray sum +https://medium.com/@rsinghal757/kadanes-algorithm-dynamic-programming-how-and-why-does-it-work-3fd8849ed73d +https://en.wikipedia.org/wiki/Maximum_subarray_problem +""" +test_data = ([-2, -8, -9], [2, 8, 9], [-1, 0, 1], [0, 0], []) + + +def negative_exist(arr: list) -> int: + """ + >>> negative_exist([-2,-8,-9]) + -2 + >>> [negative_exist(arr) for arr in test_data] + [-2, 0, 0, 0, 0] + """ + arr = arr or [0] + max = arr[0] + for i in arr: + if i >= 0: + return 0 + elif max <= i: + max = i + return max + + +def kadanes(arr: list) -> int: + """ + If negative_exist() returns 0 than this function will execute + else it will return the value return by negative_exist function + + For example: arr = [2, 3, -9, 8, -2] + Initially we set value of max_sum to 0 and max_till_element to 0 than when + max_sum is less than max_till particular element it will assign that value to + max_sum and when value of max_till_sum is less than 0 it will assign 0 to i + and after that whole process, return the max_sum + So the output for above arr is 8 + + >>> kadanes([2, 3, -9, 8, -2]) + 8 + >>> [kadanes(arr) for arr in test_data] + [-2, 19, 1, 0, 0] + """ + max_sum = negative_exist(arr) + if max_sum < 0: + return max_sum + + max_sum = 0 + max_till_element = 0 + + for i in arr: + max_till_element += i + if max_sum <= max_till_element: + max_sum = max_till_element + if max_till_element < 0: + max_till_element = 0 + return max_sum + + +if __name__ == "__main__": + try: + print("Enter integer values sepatated by spaces") + arr = [int(x) for x in input().split()] + print(f"Maximum subarray sum of {arr} is {kadanes(arr)}") + except ValueError: + print("Please enter integer values.") From 77c3e5b74b679930b7d536423e7393b211888080 Mon Sep 17 00:00:00 2001 From: Jeffin Francis Date: Sun, 10 May 2020 00:37:36 +0530 Subject: [PATCH 0557/1071] Added A* algorithm (#1913) * a* algorithm * changes after build error * intent changes * fix after review * ImportMissmatchError * Build failed fix * doctest changes * doctest changes --- machine_learning/astar.py | 152 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 152 insertions(+) create mode 100644 machine_learning/astar.py diff --git a/machine_learning/astar.py b/machine_learning/astar.py new file mode 100644 index 000000000000..2dd10b1d5fa7 --- /dev/null +++ b/machine_learning/astar.py @@ -0,0 +1,152 @@ +import numpy as np + +''' +The A* algorithm combines features of uniform-cost search and pure +heuristic search to efficiently compute optimal solutions. +A* algorithm is a best-first search algorithm in which the cost +associated with a node is f(n) = g(n) + h(n), +where g(n) is the cost of the path from the initial state to node n and +h(n) is the heuristic estimate or the cost or a path +from node n to a goal.A* algorithm introduces a heuristic into a +regular graph-searching algorithm, +essentially planning ahead at each step so a more optimal decision +is made.A* also known as the algorithm with brains +''' + + +class Cell(object): + ''' + Class cell represents a cell in the world which have the property + position : The position of the represented by tupleof x and y + co-ordinates initially set to (0,0) + parent : This contains the parent cell object which we visited + before arrinving this cell + g,h,f : The parameters for constructing the heuristic function + which can be any function. for simplicity used line + distance + ''' + def __init__(self): + self.position = (0, 0) + self.parent = None + + self.g = 0 + self.h = 0 + self.f = 0 + ''' + overrides equals method because otherwise cell assign will give + wrong results + ''' + def __eq__(self, cell): + return self.position == cell.position + + def showcell(self): + print(self.position) + + +class Gridworld(object): + + ''' + Gridworld class represents the external world here a grid M*M + matrix + w : create a numpy array with the given world_size default is 5 + ''' + + def __init__(self, world_size=(5, 5)): + self.w = np.zeros(world_size) + self.world_x_limit = world_size[0] + self.world_y_limit = world_size[1] + + def show(self): + print(self.w) + + ''' + get_neighbours + As the name suggests this function will return the neighbours of + the a particular cell + ''' + def get_neigbours(self, cell): + neughbour_cord = [ + (-1, -1), (-1, 0), (-1, 1), (0, -1), + (0, 1), (1, -1), (1, 0), (1, 1)] + current_x = cell.position[0] + current_y = cell.position[1] + neighbours = [] + for n in neughbour_cord: + x = current_x + n[0] + y = current_y + n[1] + if ( + (x >= 0 and x < self.world_x_limit) and + (y >= 0 and y < self.world_y_limit)): + c = Cell() + c.position = (x, y) + c.parent = cell + neighbours.append(c) + return neighbours + +''' +Implementation of a start algorithm +world : Object of the world object +start : Object of the cell as start position +stop : Object of the cell as goal position +''' + + +def astar(world, start, goal): + ''' + >>> p = Gridworld() + >>> start = Cell() + >>> start.position = (0,0) + >>> goal = Cell() + >>> goal.position = (4,4) + >>> astar(p, start, goal) + [(0, 0), (1, 1), (2, 2), (3, 3), (4, 4)] + ''' + _open = [] + _closed = [] + _open.append(start) + + while _open: + min_f = np.argmin([n.f for n in _open]) + current = _open[min_f] + _closed.append(_open.pop(min_f)) + if current == goal: + break + for n in world.get_neigbours(current): + for c in _closed: + if c == n: + continue + n.g = current.g + 1 + x1, y1 = n.position + x2, y2 = goal.position + n.h = (y2 - y1)**2 + (x2 - x1)**2 + n.f = n.h + n.g + + for c in _open: + if c == n and c.f < n.f: + continue + _open.append(n) + path = [] + while current.parent is not None: + path.append(current.position) + current = current.parent + path.append(current.position) + path = path[::-1] + return path + +if __name__ == '__main__': + ''' + sample run + ''' +# object for the world + p = Gridworld() +# stat position and Goal + start = Cell() + start.position = (0, 0) + goal = Cell() + goal.position = (4, 4) + print("path from {} to {} ".format(start.position, goal.position)) + s = astar(p, start, goal) +# Just for visual Purpose + for i in s: + p.w[i] = 1 + print(p.w) From 3d9bb051a8f3d00fd3be724f3a84e90d3bcb06e5 Mon Sep 17 00:00:00 2001 From: Christian Clauss Date: Sun, 10 May 2020 08:06:42 +0200 Subject: [PATCH 0558/1071] Travis CI: Strict flake8 (#1962) * Travis CI: Strict flake8 Turn the screws all the way down. * updating DIRECTORY.md * Switch from flake8 --select to --ignore * Quotes * IGNORE=E123,E203,E265,E266,E302,E401,E402,E712,E731,E741,E743,F811,F841,W291,W293,W503 Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> --- .travis.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index b86a57460e56..22eea20c727e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -7,8 +7,8 @@ before_install: pip install --upgrade pip setuptools six install: pip install -r requirements.txt before_script: - black --check . || true - - flake8 . --count --select=E101,E5,E722,E9,F4,F63,F7,F82,W191 --max-complexity=25 --max-line-length=127 --show-source --statistics - - flake8 . --count --exit-zero --max-line-length=127 --statistics + - IGNORE=E123,E203,E265,E266,E302,E401,E402,E712,E731,E741,E743,F811,F841,W291,W293,W503 + - flake8 . --count --ignore=$IGNORE --max-complexity=25 --max-line-length=127 --show-source --statistics script: - scripts/validate_filenames.py # no uppercase, no spaces, in a directory - mypy --ignore-missing-imports . From a7cd633bb643cdeb38f83d89e921e3ac4dfe9623 Mon Sep 17 00:00:00 2001 From: Christian Clauss Date: Sun, 10 May 2020 17:19:40 +0200 Subject: [PATCH 0559/1071] Fix astar (#1966) * Fix astar Single character variable names are old school. * fixup! Format Python code with psf/black push * Tuple * updating DIRECTORY.md Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> --- DIRECTORY.md | 2 + machine_learning/astar.py | 92 +++++++++++----------- maths/{kadanes_algorithm.py => kadanes.py} | 2 +- 3 files changed, 48 insertions(+), 48 deletions(-) rename maths/{kadanes_algorithm.py => kadanes.py} (93%) diff --git a/DIRECTORY.md b/DIRECTORY.md index 27fb1a8988e3..e5ec48e27e7f 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -266,6 +266,7 @@ * [Test Linear Algebra](https://github.com/TheAlgorithms/Python/blob/master/linear_algebra/src/test_linear_algebra.py) ## Machine Learning + * [Astar](https://github.com/TheAlgorithms/Python/blob/master/machine_learning/astar.py) * [Decision Tree](https://github.com/TheAlgorithms/Python/blob/master/machine_learning/decision_tree.py) * [Gaussian Naive Bayes](https://github.com/TheAlgorithms/Python/blob/master/machine_learning/gaussian_naive_bayes.py) * [Gradient Descent](https://github.com/TheAlgorithms/Python/blob/master/machine_learning/gradient_descent.py) @@ -327,6 +328,7 @@ * [Hardy Ramanujanalgo](https://github.com/TheAlgorithms/Python/blob/master/maths/hardy_ramanujanalgo.py) * [Is Square Free](https://github.com/TheAlgorithms/Python/blob/master/maths/is_square_free.py) * [Jaccard Similarity](https://github.com/TheAlgorithms/Python/blob/master/maths/jaccard_similarity.py) + * [Kadanes](https://github.com/TheAlgorithms/Python/blob/master/maths/kadanes.py) * [Karatsuba](https://github.com/TheAlgorithms/Python/blob/master/maths/karatsuba.py) * [Kth Lexicographic Permutation](https://github.com/TheAlgorithms/Python/blob/master/maths/kth_lexicographic_permutation.py) * [Largest Of Very Large Numbers](https://github.com/TheAlgorithms/Python/blob/master/maths/largest_of_very_large_numbers.py) diff --git a/machine_learning/astar.py b/machine_learning/astar.py index 2dd10b1d5fa7..ec8f214ab082 100644 --- a/machine_learning/astar.py +++ b/machine_learning/astar.py @@ -1,6 +1,4 @@ -import numpy as np - -''' +""" The A* algorithm combines features of uniform-cost search and pure heuristic search to efficiently compute optimal solutions. A* algorithm is a best-first search algorithm in which the cost @@ -11,11 +9,12 @@ regular graph-searching algorithm, essentially planning ahead at each step so a more optimal decision is made.A* also known as the algorithm with brains -''' +""" +import numpy as np class Cell(object): - ''' + """ Class cell represents a cell in the world which have the property position : The position of the represented by tupleof x and y co-ordinates initially set to (0,0) @@ -24,7 +23,8 @@ class Cell(object): g,h,f : The parameters for constructing the heuristic function which can be any function. for simplicity used line distance - ''' + """ + def __init__(self): self.position = (0, 0) self.parent = None @@ -32,10 +32,12 @@ def __init__(self): self.g = 0 self.h = 0 self.f = 0 - ''' + + """ overrides equals method because otherwise cell assign will give wrong results - ''' + """ + def __eq__(self, cell): return self.position == cell.position @@ -44,12 +46,11 @@ def showcell(self): class Gridworld(object): - - ''' + """ Gridworld class represents the external world here a grid M*M matrix - w : create a numpy array with the given world_size default is 5 - ''' + world_size: create a numpy array with the given world_size default is 5 + """ def __init__(self, world_size=(5, 5)): self.w = np.zeros(world_size) @@ -59,40 +60,41 @@ def __init__(self, world_size=(5, 5)): def show(self): print(self.w) - ''' - get_neighbours - As the name suggests this function will return the neighbours of - the a particular cell - ''' def get_neigbours(self, cell): + """ + Return the neighbours of cell + """ neughbour_cord = [ - (-1, -1), (-1, 0), (-1, 1), (0, -1), - (0, 1), (1, -1), (1, 0), (1, 1)] + (-1, -1), + (-1, 0), + (-1, 1), + (0, -1), + (0, 1), + (1, -1), + (1, 0), + (1, 1), + ] current_x = cell.position[0] current_y = cell.position[1] neighbours = [] for n in neughbour_cord: x = current_x + n[0] y = current_y + n[1] - if ( - (x >= 0 and x < self.world_x_limit) and - (y >= 0 and y < self.world_y_limit)): + if 0 <= x < self.world_x_limit and 0 <= y < self.world_y_limit: c = Cell() c.position = (x, y) c.parent = cell neighbours.append(c) return neighbours -''' -Implementation of a start algorithm -world : Object of the world object -start : Object of the cell as start position -stop : Object of the cell as goal position -''' - def astar(world, start, goal): - ''' + """ + Implementation of a start algorithm + world : Object of the world object + start : Object of the cell as start position + stop : Object of the cell as goal position + >>> p = Gridworld() >>> start = Cell() >>> start.position = (0,0) @@ -100,7 +102,7 @@ def astar(world, start, goal): >>> goal.position = (4,4) >>> astar(p, start, goal) [(0, 0), (1, 1), (2, 2), (3, 3), (4, 4)] - ''' + """ _open = [] _closed = [] _open.append(start) @@ -118,7 +120,7 @@ def astar(world, start, goal): n.g = current.g + 1 x1, y1 = n.position x2, y2 = goal.position - n.h = (y2 - y1)**2 + (x2 - x1)**2 + n.h = (y2 - y1) ** 2 + (x2 - x1) ** 2 n.f = n.h + n.g for c in _open: @@ -130,23 +132,19 @@ def astar(world, start, goal): path.append(current.position) current = current.parent path.append(current.position) - path = path[::-1] - return path - -if __name__ == '__main__': - ''' - sample run - ''' -# object for the world - p = Gridworld() -# stat position and Goal + return path[::-1] + + +if __name__ == "__main__": + world = Gridworld() + # stat position and Goal start = Cell() start.position = (0, 0) goal = Cell() goal.position = (4, 4) - print("path from {} to {} ".format(start.position, goal.position)) - s = astar(p, start, goal) -# Just for visual Purpose + print(f"path from {start.position} to {goal.position}") + s = astar(world, start, goal) + # Just for visual reasons for i in s: - p.w[i] = 1 - print(p.w) + world.w[i] = 1 + print(world.w) diff --git a/maths/kadanes_algorithm.py b/maths/kadanes.py similarity index 93% rename from maths/kadanes_algorithm.py rename to maths/kadanes.py index d02f238a0dc9..d239d4a2589b 100644 --- a/maths/kadanes_algorithm.py +++ b/maths/kadanes.py @@ -3,7 +3,7 @@ https://medium.com/@rsinghal757/kadanes-algorithm-dynamic-programming-how-and-why-does-it-work-3fd8849ed73d https://en.wikipedia.org/wiki/Maximum_subarray_problem """ -test_data = ([-2, -8, -9], [2, 8, 9], [-1, 0, 1], [0, 0], []) +test_data: tuple = ([-2, -8, -9], [2, 8, 9], [-1, 0, 1], [0, 0], []) def negative_exist(arr: list) -> int: From 1151d8b1579155818ac51f9235f84ab0fbfe9019 Mon Sep 17 00:00:00 2001 From: halilylm <65048618+halilylm@users.noreply.github.com> Date: Mon, 11 May 2020 13:23:39 +0300 Subject: [PATCH 0560/1071] Add type hints to max_heap.py (#1960) * Max heap implementation * Update max_heap.py * Update max_heap.py * Update max_heap.py * __len__ method added * Update max_heap.py Co-authored-by: halilpython <65048618+halilpython@users.noreply.github.com> Co-authored-by: Christian Clauss --- data_structures/heap/max_heap.py | 87 ++++++++++++++++++++++++++++++++ 1 file changed, 87 insertions(+) create mode 100644 data_structures/heap/max_heap.py diff --git a/data_structures/heap/max_heap.py b/data_structures/heap/max_heap.py new file mode 100644 index 000000000000..2a08f8fa2cd1 --- /dev/null +++ b/data_structures/heap/max_heap.py @@ -0,0 +1,87 @@ +class BinaryHeap: + """ + A max-heap implementation in Python + >>> binary_heap = BinaryHeap() + >>> binary_heap.insert(6) + >>> binary_heap.insert(10) + >>> binary_heap.insert(15) + >>> binary_heap.insert(12) + >>> binary_heap.pop() + 15 + >>> binary_heap.pop() + 12 + >>> binary_heap.get_list + [10, 6] + >>> len(binary_heap) + 2 + """ + + def __init__(self): + self.__heap = [0] + self.__size = 0 + + def __swap_up(self, i: int) -> None: + """ Swap the element up """ + temporary = self.__heap[i] + while i // 2 > 0: + if self.__heap[i] > self.__heap[i // 2]: + self.__heap[i] = self.__heap[i // 2] + self.__heap[i // 2] = temporary + i //= 2 + + def insert(self, value: int) -> None: + """ Insert new element """ + self.__heap.append(value) + self.__size += 1 + self.__swap_up(self.__size) + + def __swap_down(self, i: int) -> None: + """ Swap the element down """ + while self.__size >= 2 * i: + if 2 * i + 1 > self.__size: + bigger_child = 2 * i + else: + if self.__heap[2 * i] > self.__heap[2 * i + 1]: + bigger_child = 2 * i + else: + bigger_child = 2 * i + 1 + temporary = self.__heap[i] + if self.__heap[i] < self.__heap[bigger_child]: + self.__heap[i] = self.__heap[bigger_child] + self.__heap[bigger_child] = temporary + i = bigger_child + + def pop(self) -> int: + """ Pop the root element """ + max_value = self.__heap[1] + self.__heap[1] = self.__heap[self.__size] + self.__size -= 1 + self.__heap.pop() + self.__swap_down(1) + return max_value + + @property + def get_list(self): + return self.__heap[1:] + + def __len__(self): + """ Length of the array """ + return self.__size + + +if __name__ == "__main__": + import doctest + + doctest.testmod() + # create an instance of BinaryHeap + binary_heap = BinaryHeap() + binary_heap.insert(6) + binary_heap.insert(10) + binary_heap.insert(15) + binary_heap.insert(12) + # pop root(max-values because it is max heap) + print(binary_heap.pop()) # 15 + print(binary_heap.pop()) # 12 + # get the list and size after operations + print(binary_heap.get_list) + print(len(binary_heap)) From ba8b156fdc1eb2c3d6c6bfc684c0d66563aa5932 Mon Sep 17 00:00:00 2001 From: Aman Gupta <53354515+amangupta0709@users.noreply.github.com> Date: Mon, 11 May 2020 20:10:02 +0530 Subject: [PATCH 0561/1071] Iterative merge sort implementation (#1972) * Added Iterative merge sort * Added iterative merge sorts * Update changes * Add the ability to sort strings Co-authored-by: Christian Clauss --- sorts/iterative_merge_sort.py | 73 +++++++++++++++++++++++++++++++++++ 1 file changed, 73 insertions(+) create mode 100644 sorts/iterative_merge_sort.py diff --git a/sorts/iterative_merge_sort.py b/sorts/iterative_merge_sort.py new file mode 100644 index 000000000000..e6e1513393e0 --- /dev/null +++ b/sorts/iterative_merge_sort.py @@ -0,0 +1,73 @@ +""" +Implementation of iterative merge sort in Python +Author: Aman Gupta + +For doctests run following command: +python3 -m doctest -v iterative_merge_sort.py + +For manual testing run: +python3 iterative_merge_sort.py +""" + +from typing import List + + +def merge(input_list: List, low: int, mid: int, high: int) -> List: + """ + sorting left-half and right-half individually + then merging them into result + """ + result = [] + left, right = input_list[low:mid], input_list[mid : high + 1] + while left and right: + result.append((left if left[0] <= right[0] else right).pop(0)) + input_list[low : high + 1] = result + left + right + return input_list + + +# iteration over the unsorted list +def iter_merge_sort(input_list: List) -> List: + """ + Return a sorted copy of the input list + + >>> iter_merge_sort([5, 9, 8, 7, 1, 2, 7]) + [1, 2, 5, 7, 7, 8, 9] + >>> iter_merge_sort([6]) + [6] + >>> iter_merge_sort([]) + [] + >>> iter_merge_sort([-2, -9, -1, -4]) + [-9, -4, -2, -1] + >>> iter_merge_sort([1.1, 1, 0.0, -1, -1.1]) + [-1.1, -1, 0.0, 1, 1.1] + >>> iter_merge_sort(['c', 'b', 'a']) + ['a', 'b', 'c'] + >>> iter_merge_sort('cba') + ['a', 'b', 'c'] + """ + if len(input_list) <= 1: + return input_list + input_list = list(input_list) + + # iteration for two-way merging + p = 2 + while p < len(input_list): + # getting low, high and middle value for merge-sort of single list + for i in range(0, len(input_list), p): + low = i + high = i + p - 1 + mid = (low + high + 1) // 2 + input_list = merge(input_list, low, mid, high) + # final merge of last two parts + if p * 2 >= len(input_list): + mid = i + input_list = merge(input_list, 0, mid, len(input_list) - 1) + p *= 2 + + return input_list + + +if __name__ == "__main__": + user_input = input("Enter numbers separated by a comma:\n").strip() + unsorted = [int(item.strip()) for item in user_input.split(",")] + print(iter_merge_sort(unsorted)) From 69171a9ac36bc01897c41bd5552d570a797c2e8b Mon Sep 17 00:00:00 2001 From: Christian Clauss Date: Wed, 13 May 2020 20:03:28 +0200 Subject: [PATCH 0562/1071] The new version of flake8 is linting f-strings (#1976) * The new version of flake8 is linting f-strings * updating DIRECTORY.md Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> --- DIRECTORY.md | 2 ++ digital_image_processing/resize/resize.py | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/DIRECTORY.md b/DIRECTORY.md index e5ec48e27e7f..fa20a2ae349e 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -108,6 +108,7 @@ * Heap * [Binomial Heap](https://github.com/TheAlgorithms/Python/blob/master/data_structures/heap/binomial_heap.py) * [Heap](https://github.com/TheAlgorithms/Python/blob/master/data_structures/heap/heap.py) + * [Max Heap](https://github.com/TheAlgorithms/Python/blob/master/data_structures/heap/max_heap.py) * [Min Heap](https://github.com/TheAlgorithms/Python/blob/master/data_structures/heap/min_heap.py) * Linked List * [Circular Linked List](https://github.com/TheAlgorithms/Python/blob/master/data_structures/linked_list/circular_linked_list.py) @@ -582,6 +583,7 @@ * [Heap Sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/heap_sort.py) * [I Sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/i_sort.py) * [Insertion Sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/insertion_sort.py) + * [Iterative Merge Sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/iterative_merge_sort.py) * [Merge Sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/merge_sort.py) * [Odd Even Transposition Parallel](https://github.com/TheAlgorithms/Python/blob/master/sorts/odd_even_transposition_parallel.py) * [Odd Even Transposition Single Threaded](https://github.com/TheAlgorithms/Python/blob/master/sorts/odd_even_transposition_single_threaded.py) diff --git a/digital_image_processing/resize/resize.py b/digital_image_processing/resize/resize.py index b7d493e70b4b..4fd222ccd6c6 100644 --- a/digital_image_processing/resize/resize.py +++ b/digital_image_processing/resize/resize.py @@ -11,7 +11,7 @@ class NearestNeighbour: def __init__(self, img, dst_width: int, dst_height: int): if dst_width < 0 or dst_height < 0: - raise ValueError(f"Destination width/height should be > 0") + raise ValueError("Destination width/height should be > 0") self.img = img self.src_w = img.shape[1] From 48bb14d4b2d73ac6c9b9ff430846156b90b101e8 Mon Sep 17 00:00:00 2001 From: Himanshu Airan <62210670+Himanshu-77@users.noreply.github.com> Date: Thu, 14 May 2020 20:22:43 +0530 Subject: [PATCH 0563/1071] Update linear_search.py (#1974) * Update linear_search.py Comment modified in line 17 as Sorting not required in Linear Search * Update linear_search.py Comment modified in line 17 --- searches/linear_search.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/searches/linear_search.py b/searches/linear_search.py index adf22056b575..155119bb4a43 100644 --- a/searches/linear_search.py +++ b/searches/linear_search.py @@ -14,7 +14,7 @@ def linear_search(sequence, target): """Pure implementation of linear search algorithm in Python - :param sequence: some sorted collection with comparable items + :param sequence: a collection with comparable items (as sorted items not required in Linear Search) :param target: item value to search :return: index of found item or None if item is not found From c8fbdee2294fcc14d002ee7ef9f6a3d8dcb66636 Mon Sep 17 00:00:00 2001 From: Steven Qu Date: Thu, 14 May 2020 18:33:50 -0400 Subject: [PATCH 0564/1071] improved prime number generator to check only up to sqrt(n) instead of n (#1984) * improved prime number generator to check only up to sqrt(n) instead of n * added old version as slow_primes() and named new, faster version primes() * fixed docstring in slow_primes * Add a timeit benchmark * Update prime_numbers.py Co-authored-by: Christian Clauss --- maths/prime_numbers.py | 37 ++++++++++++++++++++++++++++++++++++- 1 file changed, 36 insertions(+), 1 deletion(-) diff --git a/maths/prime_numbers.py b/maths/prime_numbers.py index f9325996500c..9aedf6864b0e 100644 --- a/maths/prime_numbers.py +++ b/maths/prime_numbers.py @@ -1,4 +1,32 @@ from typing import Generator +import math + + +def slow_primes(max: int) -> Generator[int, None, None]: + """ + Return a list of all primes numbers up to max. + >>> list(slow_primes(0)) + [] + >>> list(slow_primes(-1)) + [] + >>> list(slow_primes(-10)) + [] + >>> list(slow_primes(25)) + [2, 3, 5, 7, 11, 13, 17, 19, 23] + >>> list(slow_primes(11)) + [2, 3, 5, 7, 11] + >>> list(slow_primes(33)) + [2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31] + >>> list(slow_primes(10000))[-1] + 9973 + """ + numbers: Generator = (i for i in range(1, (max + 1))) + for i in (n for n in numbers if n > 1): + for j in range(2, i): + if (i % j) == 0: + break + else: + yield i def primes(max: int) -> Generator[int, None, None]: @@ -21,7 +49,9 @@ def primes(max: int) -> Generator[int, None, None]: """ numbers: Generator = (i for i in range(1, (max + 1))) for i in (n for n in numbers if n > 1): - for j in range(2, i): + # only need to check for factors up to sqrt(i) + bound = int(math.sqrt(i)) + 1 + for j in range(2, bound): if (i % j) == 0: break else: @@ -32,3 +62,8 @@ def primes(max: int) -> Generator[int, None, None]: number = int(input("Calculate primes up to:\n>> ").strip()) for ret in primes(number): print(ret) + + # Let's benchmark them side-by-side... + from timeit import timeit + print(timeit("slow_primes(1_000_000)", setup="from __main__ import slow_primes")) + print(timeit("primes(1_000_000)", setup="from __main__ import primes")) From a29a2a3a0698e936d7275e2c02d3c0c6e478cb4c Mon Sep 17 00:00:00 2001 From: Kenneth P <41343159+ken437@users.noreply.github.com> Date: Fri, 15 May 2020 02:06:51 -0400 Subject: [PATCH 0565/1071] Added file aliquot_sum.py (#1985) * Added file aliquot_sum.py containing a function to find theo * Added parameter type info to aliquot_sum.py * Update maths/aliquot_sum.py Co-authored-by: Christian Clauss * Update maths/aliquot_sum.py Co-authored-by: Christian Clauss * Updated code to raise ValueErrors if input is bad * Fixed logical operators * Removed unnecessary import * Updated aliquot_sum.py * fixed formatting * fixed documentation Co-authored-by: Christian Clauss --- maths/aliquot_sum.py | 46 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 maths/aliquot_sum.py diff --git a/maths/aliquot_sum.py b/maths/aliquot_sum.py new file mode 100644 index 000000000000..ac5fa58f41cf --- /dev/null +++ b/maths/aliquot_sum.py @@ -0,0 +1,46 @@ +def aliquot_sum(input_num: int) -> int: + """ + Finds the aliquot sum of an input integer, where the + aliquot sum of a number n is defined as the sum of all + natural numbers less than n that divide n evenly. For + example, the aliquot sum of 15 is 1 + 3 + 5 = 9. This is + a simple O(n) implementation. + @param input_num: a positive integer whose aliquot sum is to be found + @return: the aliquot sum of input_num, if input_num is positive. + Otherwise, raise a ValueError + Wikipedia Explanation: https://en.wikipedia.org/wiki/Aliquot_sum + + >>> aliquot_sum(15) + 9 + >>> aliquot_sum(6) + 6 + >>> aliquot_sum(-1) + Traceback (most recent call last): + ... + ValueError: Input must be positive + >>> aliquot_sum(0) + Traceback (most recent call last): + ... + ValueError: Input must be positive + >>> aliquot_sum(1.6) + Traceback (most recent call last): + ... + ValueError: Input must be an integer + >>> aliquot_sum(12) + 16 + >>> aliquot_sum(1) + 0 + >>> aliquot_sum(19) + 1 + """ + if not isinstance(input_num, int): + raise ValueError("Input must be an integer") + if input_num <= 0: + raise ValueError("Input must be positive") + return sum(divisor for divisor in range(1, input_num) if input_num % divisor == 0) + + +if __name__ == "__main__": + import doctest + + doctest.testmod() From 565060aa9973f23a68b4cf480a11c526c8d3830d Mon Sep 17 00:00:00 2001 From: Christian Clauss Date: Sat, 16 May 2020 08:49:56 +0200 Subject: [PATCH 0566/1071] actions/setup-python@v2 (#1986) * actions/setup-python@v2 * fixup! Format Python code with psf/black push * Update autoblack.yml * updating DIRECTORY.md * Update codespell.yml Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> --- .github/workflows/autoblack.yml | 2 +- .github/workflows/codespell.yml | 2 +- .github/workflows/directory_writer.yml | 4 ++-- DIRECTORY.md | 1 + maths/prime_numbers.py | 1 + 5 files changed, 6 insertions(+), 4 deletions(-) diff --git a/.github/workflows/autoblack.yml b/.github/workflows/autoblack.yml index 99243f3ec7fc..44249f78725c 100644 --- a/.github/workflows/autoblack.yml +++ b/.github/workflows/autoblack.yml @@ -10,7 +10,7 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v1 # Use v1, NOT v2 - - uses: actions/setup-python@v1 + - uses: actions/setup-python@v2 - run: pip install black - run: black --check . - name: If needed, commit black changes to a new pull request diff --git a/.github/workflows/codespell.yml b/.github/workflows/codespell.yml index 188538775a2f..010adffa9053 100644 --- a/.github/workflows/codespell.yml +++ b/.github/workflows/codespell.yml @@ -7,7 +7,7 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - - uses: actions/setup-python@v1 + - uses: actions/setup-python@v2 - run: pip install codespell flake8 - run: | SKIP="./.*,./other/dictionary.txt,./other/words,./project_euler/problem_22/p022_names.txt" diff --git a/.github/workflows/directory_writer.yml b/.github/workflows/directory_writer.yml index 77b0e1a262e1..6547d1c18c79 100644 --- a/.github/workflows/directory_writer.yml +++ b/.github/workflows/directory_writer.yml @@ -7,7 +7,7 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v1 # v1, NOT v2 - - uses: actions/setup-python@v1 + - uses: actions/setup-python@v2 with: python-version: 3.x - name: Write DIRECTORY.md @@ -20,4 +20,4 @@ jobs: run: | git add DIRECTORY.md git commit -am "updating DIRECTORY.md" || true - git push --force origin HEAD:$GITHUB_REF || true \ No newline at end of file + git push --force origin HEAD:$GITHUB_REF || true diff --git a/DIRECTORY.md b/DIRECTORY.md index fa20a2ae349e..f4499d8e07bd 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -293,6 +293,7 @@ * [Abs Max](https://github.com/TheAlgorithms/Python/blob/master/maths/abs_max.py) * [Abs Min](https://github.com/TheAlgorithms/Python/blob/master/maths/abs_min.py) * [Add](https://github.com/TheAlgorithms/Python/blob/master/maths/add.py) + * [Aliquot Sum](https://github.com/TheAlgorithms/Python/blob/master/maths/aliquot_sum.py) * [Allocation Number](https://github.com/TheAlgorithms/Python/blob/master/maths/allocation_number.py) * [Area Under Curve](https://github.com/TheAlgorithms/Python/blob/master/maths/area_under_curve.py) * [Armstrong Numbers](https://github.com/TheAlgorithms/Python/blob/master/maths/armstrong_numbers.py) diff --git a/maths/prime_numbers.py b/maths/prime_numbers.py index 9aedf6864b0e..1b2a29f1a203 100644 --- a/maths/prime_numbers.py +++ b/maths/prime_numbers.py @@ -65,5 +65,6 @@ def primes(max: int) -> Generator[int, None, None]: # Let's benchmark them side-by-side... from timeit import timeit + print(timeit("slow_primes(1_000_000)", setup="from __main__ import slow_primes")) print(timeit("primes(1_000_000)", setup="from __main__ import primes")) From bc8e8f03fda33788f77e85fa9fafe6e74ee30703 Mon Sep 17 00:00:00 2001 From: mateuszz0000 Date: Sun, 17 May 2020 22:48:39 +0200 Subject: [PATCH 0567/1071] Added strand sort (#1982) * Added strand sort * Review changes * Remove boilerplate code * Fixed flake error: E252 * Added missing return type hint --- sorts/strand_sort.py | 51 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) create mode 100644 sorts/strand_sort.py diff --git a/sorts/strand_sort.py b/sorts/strand_sort.py new file mode 100644 index 000000000000..a89135a0691f --- /dev/null +++ b/sorts/strand_sort.py @@ -0,0 +1,51 @@ +import operator + + +def strand_sort(arr: list, reverse: bool = False, solution: list = None) -> list: + """ + Strand sort implementation + source: https://en.wikipedia.org/wiki/Strand_sort + + :param arr: Unordered input list + :param reverse: Descent ordering flag + :param solution: Ordered items container + + Examples: + >>> strand_sort([4, 2, 5, 3, 0, 1]) + [0, 1, 2, 3, 4, 5] + + >>> strand_sort([4, 2, 5, 3, 0, 1], reverse=True) + [5, 4, 3, 2, 1, 0] + """ + _operator = operator.lt if reverse else operator.gt + solution = solution or [] + + if not arr: + return solution + + sublist = [arr.pop(0)] + for i, item in enumerate(arr): + if _operator(item, sublist[-1]): + sublist.append(item) + arr.pop(i) + + # merging sublist into solution list + if not solution: + solution.extend(sublist) + else: + while sublist: + item = sublist.pop(0) + for i, xx in enumerate(solution): + if not _operator(item, xx): + solution.insert(i, item) + break + else: + solution.append(item) + + strand_sort(arr, reverse, solution) + return solution + + +if __name__ == "__main__": + assert strand_sort([4, 3, 5, 1, 2]) == [1, 2, 3, 4, 5] + assert strand_sort([4, 3, 5, 1, 2], reverse=True) == [5, 4, 3, 2, 1] From aa120cea12589e4f7907dff95d2efe246f1da178 Mon Sep 17 00:00:00 2001 From: Bharath kumar Reddy Kotha Date: Mon, 18 May 2020 12:33:20 +0530 Subject: [PATCH 0568/1071] Add tests and type hints to hill cipher (#1991) * Added tests and type hints to hill cipher * Remove extra >>> * import doctest Co-authored-by: John Law --- ciphers/hill_cipher.py | 97 +++++++++++++++++++++++++++++++++--------- 1 file changed, 77 insertions(+), 20 deletions(-) diff --git a/ciphers/hill_cipher.py b/ciphers/hill_cipher.py index 47910e4ebdaa..9efada3e2b7d 100644 --- a/ciphers/hill_cipher.py +++ b/ciphers/hill_cipher.py @@ -41,7 +41,17 @@ import numpy -def gcd(a, b): +def gcd(a: int, b: int) -> int: + """ + >>> gcd(4, 8) + 4 + >>> gcd(8, 4) + 4 + >>> gcd(4, 7) + 1 + >>> gcd(0, 10) + 10 + """ if a == 0: return b return gcd(b % a, a) @@ -52,9 +62,6 @@ class HillCipher: # This cipher takes alphanumerics into account # i.e. a total of 36 characters - replaceLetters = lambda self, letter: self.key_string.index(letter) - replaceNumbers = lambda self, num: self.key_string[round(num)] - # take x and return x % len(key_string) modulus = numpy.vectorize(lambda x: x % 36) @@ -69,7 +76,31 @@ def __init__(self, encrypt_key): self.decrypt_key = None self.break_key = encrypt_key.shape[0] - def check_determinant(self): + def replaceLetters(self, letter: str) -> int: + """ + >>> hill_cipher = HillCipher(numpy.matrix([[2, 5], [1, 6]])) + >>> hill_cipher.replaceLetters('T') + 19 + >>> hill_cipher.replaceLetters('0') + 26 + """ + return self.key_string.index(letter) + + def replaceNumbers(self, num: int) -> str: + """ + >>> hill_cipher = HillCipher(numpy.matrix([[2, 5], [1, 6]])) + >>> hill_cipher.replaceNumbers(19) + 'T' + >>> hill_cipher.replaceNumbers(26) + '0' + """ + return self.key_string[round(num)] + + def check_determinant(self) -> None: + """ + >>> hill_cipher = HillCipher(numpy.matrix([[2, 5], [1, 6]])) + >>> hill_cipher.check_determinant() + """ det = round(numpy.linalg.det(self.encrypt_key)) if det < 0: @@ -78,38 +109,54 @@ def check_determinant(self): req_l = len(self.key_string) if gcd(det, len(self.key_string)) != 1: raise ValueError( - "discriminant modular {} of encryption key({}) is not co prime w.r.t {}.\nTry another key.".format( - req_l, det, req_l - ) + f"determinant modular {req_l} of encryption key({det}) is not co prime w.r.t {req_l}.\nTry another key." ) - def process_text(self, text): + def process_text(self, text: str) -> str: + """ + >>> hill_cipher = HillCipher(numpy.matrix([[2, 5], [1, 6]])) + >>> hill_cipher.process_text('Testing Hill Cipher') + 'TESTINGHILLCIPHERR' + """ text = list(text.upper()) - text = [char for char in text if char in self.key_string] + chars = [char for char in text if char in self.key_string] - last = text[-1] - while len(text) % self.break_key != 0: - text.append(last) + last = chars[-1] + while len(chars) % self.break_key != 0: + chars.append(last) - return "".join(text) + return "".join(chars) - def encrypt(self, text): + def encrypt(self, text: str) -> str: + """ + >>> hill_cipher = HillCipher(numpy.matrix([[2, 5], [1, 6]])) + >>> hill_cipher.encrypt('testing hill cipher') + 'WHXYJOLM9C6XT085LL' + """ text = self.process_text(text.upper()) encrypted = "" for i in range(0, len(text) - self.break_key + 1, self.break_key): batch = text[i : i + self.break_key] - batch_vec = list(map(self.replaceLetters, batch)) + batch_vec = [self.replaceLetters(char) for char in batch] batch_vec = numpy.matrix([batch_vec]).T batch_encrypted = self.modulus(self.encrypt_key.dot(batch_vec)).T.tolist()[ 0 ] - encrypted_batch = "".join(list(map(self.replaceNumbers, batch_encrypted))) + encrypted_batch = "".join( + self.replaceNumbers(num) for num in batch_encrypted + ) encrypted += encrypted_batch return encrypted def make_decrypt_key(self): + """ + >>> hill_cipher = HillCipher(numpy.matrix([[2, 5], [1, 6]])) + >>> hill_cipher.make_decrypt_key() + matrix([[ 6., 25.], + [ 5., 26.]]) + """ det = round(numpy.linalg.det(self.encrypt_key)) if det < 0: @@ -128,19 +175,26 @@ def make_decrypt_key(self): return self.toInt(self.modulus(inv_key)) - def decrypt(self, text): + def decrypt(self, text: str) -> str: + """ + >>> hill_cipher = HillCipher(numpy.matrix([[2, 5], [1, 6]])) + >>> hill_cipher.decrypt('WHXYJOLM9C6XT085LL') + 'TESTINGHILLCIPHERR' + """ self.decrypt_key = self.make_decrypt_key() text = self.process_text(text.upper()) decrypted = "" for i in range(0, len(text) - self.break_key + 1, self.break_key): batch = text[i : i + self.break_key] - batch_vec = list(map(self.replaceLetters, batch)) + batch_vec = [self.replaceLetters(char) for char in batch] batch_vec = numpy.matrix([batch_vec]).T batch_decrypted = self.modulus(self.decrypt_key.dot(batch_vec)).T.tolist()[ 0 ] - decrypted_batch = "".join(list(map(self.replaceNumbers, batch_decrypted))) + decrypted_batch = "".join( + self.replaceNumbers(num) for num in batch_decrypted + ) decrypted += decrypted_batch return decrypted @@ -176,4 +230,7 @@ def main(): if __name__ == "__main__": + import doctest + doctest.testmod() + main() From 38d2e98665db0df90ba1ef33d9ba8e5025c7e38d Mon Sep 17 00:00:00 2001 From: Christian Clauss Date: Mon, 18 May 2020 13:05:51 +0200 Subject: [PATCH 0569/1071] hill_cipher.py: gcd() -> greatest_common_divisor() (#1997) * hill_cipher.py: gcd() -> greatest_common_divisor() * fixup! Format Python code with psf/black push * import string * updating DIRECTORY.md * Change matrix to array Add more tests Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> Co-authored-by: John Law --- DIRECTORY.md | 1 + ciphers/hill_cipher.py | 107 ++++++++++++++++++++--------------------- 2 files changed, 53 insertions(+), 55 deletions(-) diff --git a/DIRECTORY.md b/DIRECTORY.md index f4499d8e07bd..711d0a9e972f 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -603,6 +603,7 @@ * [Shell Sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/shell_sort.py) * [Sleep Sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/sleep_sort.py) * [Stooge Sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/stooge_sort.py) + * [Strand Sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/strand_sort.py) * [Tim Sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/tim_sort.py) * [Topological Sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/topological_sort.py) * [Tree Sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/tree_sort.py) diff --git a/ciphers/hill_cipher.py b/ciphers/hill_cipher.py index 9efada3e2b7d..9cd4a73b4f44 100644 --- a/ciphers/hill_cipher.py +++ b/ciphers/hill_cipher.py @@ -1,10 +1,9 @@ """ Hill Cipher: -The below defined class 'HillCipher' implements the Hill Cipher algorithm. -The Hill Cipher is an algorithm that implements modern linear algebra techniques -In this algorithm, you have an encryption key matrix. This is what will be used -in encoding and decoding your text. +The 'HillCipher' class below implements the Hill Cipher algorithm which uses +modern linear algebra techniques to encode and decode text using an encryption +key matrix. Algorithm: Let the order of the encryption key be N (as it is a square matrix). @@ -24,12 +23,11 @@ The determinant of the encryption key matrix must be relatively prime w.r.t 36. Note: -The algorithm implemented in this code considers only alphanumerics in the text. -If the length of the text to be encrypted is not a multiple of the -break key(the length of one batch of letters),the last character of the text -is added to the text until the length of the text reaches a multiple of -the break_key. So the text after decrypting might be a little different than -the original text. +This implementation only considers alphanumerics in the text. If the length of +the text to be encrypted is not a multiple of the break key(the length of one +batch of letters), the last character of the text is added to the text until the +length of the text reaches a multiple of the break_key. So the text after +decrypting might be a little different than the original text. References: https://apprendre-en-ligne.net/crypto/hill/Hillciph.pdf @@ -38,67 +36,66 @@ """ +import string import numpy -def gcd(a: int, b: int) -> int: +def greatest_common_divisor(a: int, b: int) -> int: """ - >>> gcd(4, 8) + >>> greatest_common_divisor(4, 8) 4 - >>> gcd(8, 4) + >>> greatest_common_divisor(8, 4) 4 - >>> gcd(4, 7) + >>> greatest_common_divisor(4, 7) 1 - >>> gcd(0, 10) + >>> greatest_common_divisor(0, 10) 10 """ - if a == 0: - return b - return gcd(b % a, a) + return b if a == 0 else greatest_common_divisor(b % a, a) class HillCipher: - key_string = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" + key_string = string.ascii_uppercase + string.digits # This cipher takes alphanumerics into account # i.e. a total of 36 characters # take x and return x % len(key_string) modulus = numpy.vectorize(lambda x: x % 36) - toInt = numpy.vectorize(lambda x: round(x)) + to_int = numpy.vectorize(lambda x: round(x)) def __init__(self, encrypt_key): """ - encrypt_key is an NxN numpy matrix + encrypt_key is an NxN numpy array """ self.encrypt_key = self.modulus(encrypt_key) # mod36 calc's on the encrypt key self.check_determinant() # validate the determinant of the encryption key self.decrypt_key = None self.break_key = encrypt_key.shape[0] - def replaceLetters(self, letter: str) -> int: + def replace_letters(self, letter: str) -> int: """ - >>> hill_cipher = HillCipher(numpy.matrix([[2, 5], [1, 6]])) - >>> hill_cipher.replaceLetters('T') + >>> hill_cipher = HillCipher(numpy.array([[2, 5], [1, 6]])) + >>> hill_cipher.replace_letters('T') 19 - >>> hill_cipher.replaceLetters('0') + >>> hill_cipher.replace_letters('0') 26 """ return self.key_string.index(letter) - def replaceNumbers(self, num: int) -> str: + def replace_digits(self, num: int) -> str: """ - >>> hill_cipher = HillCipher(numpy.matrix([[2, 5], [1, 6]])) - >>> hill_cipher.replaceNumbers(19) + >>> hill_cipher = HillCipher(numpy.array([[2, 5], [1, 6]])) + >>> hill_cipher.replace_digits(19) 'T' - >>> hill_cipher.replaceNumbers(26) + >>> hill_cipher.replace_digits(26) '0' """ return self.key_string[round(num)] def check_determinant(self) -> None: """ - >>> hill_cipher = HillCipher(numpy.matrix([[2, 5], [1, 6]])) + >>> hill_cipher = HillCipher(numpy.array([[2, 5], [1, 6]])) >>> hill_cipher.check_determinant() """ det = round(numpy.linalg.det(self.encrypt_key)) @@ -107,19 +104,20 @@ def check_determinant(self) -> None: det = det % len(self.key_string) req_l = len(self.key_string) - if gcd(det, len(self.key_string)) != 1: + if greatest_common_divisor(det, len(self.key_string)) != 1: raise ValueError( f"determinant modular {req_l} of encryption key({det}) is not co prime w.r.t {req_l}.\nTry another key." ) def process_text(self, text: str) -> str: """ - >>> hill_cipher = HillCipher(numpy.matrix([[2, 5], [1, 6]])) + >>> hill_cipher = HillCipher(numpy.array([[2, 5], [1, 6]])) >>> hill_cipher.process_text('Testing Hill Cipher') 'TESTINGHILLCIPHERR' + >>> hill_cipher.process_text('hello') + 'HELLOO' """ - text = list(text.upper()) - chars = [char for char in text if char in self.key_string] + chars = [char for char in text.upper() if char in self.key_string] last = chars[-1] while len(chars) % self.break_key != 0: @@ -129,22 +127,24 @@ def process_text(self, text: str) -> str: def encrypt(self, text: str) -> str: """ - >>> hill_cipher = HillCipher(numpy.matrix([[2, 5], [1, 6]])) + >>> hill_cipher = HillCipher(numpy.array([[2, 5], [1, 6]])) >>> hill_cipher.encrypt('testing hill cipher') 'WHXYJOLM9C6XT085LL' + >>> hill_cipher.encrypt('hello') + '85FF00' """ text = self.process_text(text.upper()) encrypted = "" for i in range(0, len(text) - self.break_key + 1, self.break_key): batch = text[i : i + self.break_key] - batch_vec = [self.replaceLetters(char) for char in batch] - batch_vec = numpy.matrix([batch_vec]).T + batch_vec = [self.replace_letters(char) for char in batch] + batch_vec = numpy.array([batch_vec]).T batch_encrypted = self.modulus(self.encrypt_key.dot(batch_vec)).T.tolist()[ 0 ] encrypted_batch = "".join( - self.replaceNumbers(num) for num in batch_encrypted + self.replace_digits(num) for num in batch_encrypted ) encrypted += encrypted_batch @@ -152,10 +152,10 @@ def encrypt(self, text: str) -> str: def make_decrypt_key(self): """ - >>> hill_cipher = HillCipher(numpy.matrix([[2, 5], [1, 6]])) + >>> hill_cipher = HillCipher(numpy.array([[2, 5], [1, 6]])) >>> hill_cipher.make_decrypt_key() - matrix([[ 6., 25.], - [ 5., 26.]]) + array([[ 6., 25.], + [ 5., 26.]]) """ det = round(numpy.linalg.det(self.encrypt_key)) @@ -173,13 +173,15 @@ def make_decrypt_key(self): * numpy.linalg.inv(self.encrypt_key) ) - return self.toInt(self.modulus(inv_key)) + return self.to_int(self.modulus(inv_key)) def decrypt(self, text: str) -> str: """ - >>> hill_cipher = HillCipher(numpy.matrix([[2, 5], [1, 6]])) + >>> hill_cipher = HillCipher(numpy.array([[2, 5], [1, 6]])) >>> hill_cipher.decrypt('WHXYJOLM9C6XT085LL') 'TESTINGHILLCIPHERR' + >>> hill_cipher.decrypt('85FF00') + 'HELLOO' """ self.decrypt_key = self.make_decrypt_key() text = self.process_text(text.upper()) @@ -187,13 +189,13 @@ def decrypt(self, text: str) -> str: for i in range(0, len(text) - self.break_key + 1, self.break_key): batch = text[i : i + self.break_key] - batch_vec = [self.replaceLetters(char) for char in batch] - batch_vec = numpy.matrix([batch_vec]).T + batch_vec = [self.replace_letters(char) for char in batch] + batch_vec = numpy.array([batch_vec]).T batch_decrypted = self.modulus(self.decrypt_key.dot(batch_vec)).T.tolist()[ 0 ] decrypted_batch = "".join( - self.replaceNumbers(num) for num in batch_decrypted + self.replace_digits(num) for num in batch_decrypted ) decrypted += decrypted_batch @@ -206,19 +208,13 @@ def main(): print("Enter each row of the encryption key with space separated integers") for i in range(N): - row = list(map(int, input().split())) + row = [int(x) for x in input().split()] hill_matrix.append(row) - hc = HillCipher(numpy.matrix(hill_matrix)) + hc = HillCipher(numpy.array(hill_matrix)) print("Would you like to encrypt or decrypt some text? (1 or 2)") - option = input( - """ -1. Encrypt -2. Decrypt -""" - ) - + option = input("\n1. Encrypt\n2. Decrypt\n") if option == "1": text_e = input("What text would you like to encrypt?: ") print("Your encrypted text is:") @@ -231,6 +227,7 @@ def main(): if __name__ == "__main__": import doctest + doctest.testmod() main() From f9e0dd94d6040cd41b1272e8a6ece34dc767152e Mon Sep 17 00:00:00 2001 From: Akash Date: Mon, 18 May 2020 21:40:55 +0530 Subject: [PATCH 0570/1071] added __len__ function (#1812) * added __len__ function Added a function to count number of nodes in linked list * Updated __len__ method used snake_case instead of camel case * Add tests to __len__() Co-authored-by: Christian Clauss --- .../linked_list/singly_linked_list.py | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/data_structures/linked_list/singly_linked_list.py b/data_structures/linked_list/singly_linked_list.py index c90cfb6e59a9..516facc613eb 100644 --- a/data_structures/linked_list/singly_linked_list.py +++ b/data_structures/linked_list/singly_linked_list.py @@ -106,6 +106,35 @@ def __setitem__(self, index, data): raise IndexError("Index out of range.") current = current.next current.data = data + + def __len__(self): + """ + Return length of linked list i.e. number of nodes + >>> linked_list = LinkedList() + >>> len(linked_list) + 0 + >>> linked_list.insert_tail("head") + >>> len(linked_list) + 1 + >>> linked_list.insert_head("head") + >>> len(linked_list) + 2 + >>> _ = linked_list.delete_tail() + >>> len(linked_list) + 1 + >>> _ = linked_list.delete_head() + >>> len(linked_list) + 0 + """ + if not self.head: + return 0 + + count = 0 + cur_node = self.head + while cur_node.next: + count += 1 + cur_node = cur_node.next + return count + 1 def main(): @@ -135,6 +164,7 @@ def main(): A[1] = input("Enter New Value: ").strip() print("New list:") print(A) + print(f"length of A is : {len(A)}") if __name__ == "__main__": From 7a8696cd6d5611a7e2f929ca13f54e684126b6f2 Mon Sep 17 00:00:00 2001 From: Jie Han Date: Tue, 19 May 2020 03:44:27 +0800 Subject: [PATCH 0571/1071] change doctest line (#2007) * change doctest line import doctest is not relevant with algorithms. move it under main section. * from doctest import testmod Co-authored-by: Christian Clauss --- blockchain/chinese_remainder_theorem.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/blockchain/chinese_remainder_theorem.py b/blockchain/chinese_remainder_theorem.py index 8c3eb9b4b01e..2b1e66ebea02 100644 --- a/blockchain/chinese_remainder_theorem.py +++ b/blockchain/chinese_remainder_theorem.py @@ -81,10 +81,9 @@ def chinese_remainder_theorem2(n1, r1, n2, r2): return (n % m + m) % m -# import testmod for testing our function -from doctest import testmod - if __name__ == "__main__": + from doctest import testmod + testmod(name="chinese_remainder_theorem", verbose=True) testmod(name="chinese_remainder_theorem2", verbose=True) testmod(name="invert_modulo", verbose=True) From 77f3888b71a73fb3d65820940ae18da850b2a1de Mon Sep 17 00:00:00 2001 From: Kenneth P <41343159+ken437@users.noreply.github.com> Date: Mon, 18 May 2020 16:54:08 -0400 Subject: [PATCH 0572/1071] Pi digit extraction algorithm (#1996) * added pi digit extraction formula * updating DIRECTORY.md * fixed typo in a comment * updated bbp_formula.py * Update maths/bbp_formula.py Co-authored-by: Christian Clauss * Update maths/bbp_formula.py Co-authored-by: Christian Clauss * Update bbp_formula.py * Update and rename bbp_formula.py to bailey_borwein_plouffe.py * updating DIRECTORY.md * calculate * "".join(bailey_borwein_plouffe(i) for i in range(1, 12)) * Update bailey_borwein_plouffe.py * Update bailey_borwein_plouffe.py Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> Co-authored-by: Christian Clauss --- DIRECTORY.md | 1 + maths/bailey_borwein_plouffe.py | 87 +++++++++++++++++++++++++++++++++ 2 files changed, 88 insertions(+) create mode 100644 maths/bailey_borwein_plouffe.py diff --git a/DIRECTORY.md b/DIRECTORY.md index 711d0a9e972f..bef7bca86cc3 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -300,6 +300,7 @@ * [Average Mean](https://github.com/TheAlgorithms/Python/blob/master/maths/average_mean.py) * [Average Median](https://github.com/TheAlgorithms/Python/blob/master/maths/average_median.py) * [Average Mode](https://github.com/TheAlgorithms/Python/blob/master/maths/average_mode.py) + * [Bailey Borwein Plouffe](https://github.com/TheAlgorithms/Python/blob/master/maths/bailey_borwein_plouffe.py) * [Basic Maths](https://github.com/TheAlgorithms/Python/blob/master/maths/basic_maths.py) * [Binary Exp Mod](https://github.com/TheAlgorithms/Python/blob/master/maths/binary_exp_mod.py) * [Binary Exponentiation](https://github.com/TheAlgorithms/Python/blob/master/maths/binary_exponentiation.py) diff --git a/maths/bailey_borwein_plouffe.py b/maths/bailey_borwein_plouffe.py new file mode 100644 index 000000000000..7834668864af --- /dev/null +++ b/maths/bailey_borwein_plouffe.py @@ -0,0 +1,87 @@ +def bailey_borwein_plouffe(digit_position: int, precision: int = 1000) -> str: + """ + Implement a popular pi-digit-extraction algorithm known as the + Bailey-Borwein-Plouffe (BBP) formula to calculate the nth hex digit of pi. + Wikipedia page: + https://en.wikipedia.org/wiki/Bailey%E2%80%93Borwein%E2%80%93Plouffe_formula + @param digit_position: a positive integer representing the position of the digit to extract. + The digit immediately after the decimal point is located at position 1. + @param precision: number of terms in the second summation to calculate. + A higher number reduces the chance of an error but increases the runtime. + @return: a hexadecimal digit representing the digit at the nth position + in pi's decimal expansion. + + >>> "".join(bailey_borwein_plouffe(i) for i in range(1, 11)) + '243f6a8885' + >>> bailey_borwein_plouffe(5, 10000) + '6' + >>> bailey_borwein_plouffe(-10) + Traceback (most recent call last): + ... + ValueError: Digit position must be a positive integer + >>> bailey_borwein_plouffe(0) + Traceback (most recent call last): + ... + ValueError: Digit position must be a positive integer + >>> bailey_borwein_plouffe(1.7) + Traceback (most recent call last): + ... + ValueError: Digit position must be a positive integer + >>> bailey_borwein_plouffe(2, -10) + Traceback (most recent call last): + ... + ValueError: Precision must be a nonnegative integer + >>> bailey_borwein_plouffe(2, 1.6) + Traceback (most recent call last): + ... + ValueError: Precision must be a nonnegative integer + """ + if (not isinstance(digit_position, int)) or (digit_position <= 0): + raise ValueError("Digit position must be a positive integer") + elif (not isinstance(precision, int)) or (precision < 0): + raise ValueError("Please input a nonnegative integer for the precision") + + # compute an approximation of (16 ** (n - 1)) * pi whose fractional part is mostly accurate + sum_result = ( + 4 * _subsum(digit_position, 1, precision) + - 2 * _subsum(digit_position, 4, precision) + - _subsum(digit_position, 5, precision) + - _subsum(digit_position, 6, precision) + ) + + # return the first hex digit of the fractional part of the result + return hex(int((sum_result % 1) * 16))[2:] + + +def _subsum( + digit_pos_to_extract: int, denominator_addend: int, precision: int +) -> float: + # only care about first digit of fractional part; don't need decimal + """ + Private helper function to implement the summation + functionality. + @param digit_pos_to_extract: digit position to extract + @param denominator_addend: added to denominator of fractions in the formula + @param precision: same as precision in main function + @return: floating-point number whose integer part is not important + """ + sum = 0.0 + for sum_index in range(digit_pos_to_extract + precision): + denominator = 8 * sum_index + denominator_addend + exponential_term = 0.0 + if sum_index < digit_pos_to_extract: + # if the exponential term is an integer and we mod it by the denominator before + # dividing, only the integer part of the sum will change; the fractional part will not + exponential_term = pow( + 16, digit_pos_to_extract - 1 - sum_index, denominator + ) + else: + exponential_term = pow(16, digit_pos_to_extract - 1 - sum_index) + sum += exponential_term / denominator + return sum + + +if __name__ == "__main__": + import doctest + + doctest.testmod() From e6fdcc90fdceeac3375c644790f9c7476d46768d Mon Sep 17 00:00:00 2001 From: Adityanagraj <42292430+Adityanagraj@users.noreply.github.com> Date: Tue, 19 May 2020 02:36:19 +0530 Subject: [PATCH 0573/1071] consists of area of various geometrical shapes (#2002) * consists of area of various geometrical shapes In this program it consists of various area calculation of different geometrical shapes such as (square,rectangle) and many other shapes. * print(f'Rectangle: {area_rectangle(10, 20)=}') * Update area.py * Areas of various geometric shapes: Co-authored-by: Christian Clauss --- maths/area.py | 79 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 79 insertions(+) create mode 100644 maths/area.py diff --git a/maths/area.py b/maths/area.py new file mode 100644 index 000000000000..0621bf5dd809 --- /dev/null +++ b/maths/area.py @@ -0,0 +1,79 @@ +""" +Find the area of various geometric shapes +""" + +import math + + +def area_rectangle(base, height): + """ + Calculate the area of a rectangle + + >> area_rectangle(10,20) + 200 + """ + return base * height + + +def area_square(side_length): + """ + Calculate the area of a square + + >>> area_square(10) + 100 + """ + return side_length * side_length + + +def area_triangle(length, breadth): + """ + Calculate the area of a triangle + + >>> area_triangle(10,10) + 50.0 + """ + return 1 / 2 * length * breadth + + +def area_parallelogram(base, height): + """ + Calculate the area of a parallelogram + + >> area_parallelogram(10,20) + 200 + """ + return base * height + + +def area_trapezium(base1, base2, height): + """ + Calculate the area of a trapezium + + >> area_trapezium(10,20,30) + 450 + """ + return 1 / 2 * (base1 + base2) * height + + +def area_circle(radius): + """ + Calculate the area of a circle + + >> area_circle(20) + 1256.6370614359173 + """ + return math.pi * radius * radius + + +def main(): + print("Areas of various geometric shapes: \n") + print(f"Rectangle: {area_rectangle(10, 20)=}") + print(f"Square: {area_square(10)=}") + print(f"Triangle: {area_triangle(10, 10)=}") + print(f"Parallelogram: {area_parallelogram(10, 20)=}") + print(f"Trapezium: {area_trapezium(10, 20, 30)=}") + print(f"Circle: {area_circle(20)=}") + + +if __name__ == "__main__": + main() From 1c62bd10c19f6cf962159bd8f840ce2466309db0 Mon Sep 17 00:00:00 2001 From: Christian Clauss Date: Tue, 19 May 2020 12:16:20 +0200 Subject: [PATCH 0574/1071] Precision must be a nonnegative integer (#2013) * Precision must be a nonnegative integer * fixup! Format Python code with psf/black push Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> --- data_structures/linked_list/singly_linked_list.py | 2 +- maths/bailey_borwein_plouffe.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/data_structures/linked_list/singly_linked_list.py b/data_structures/linked_list/singly_linked_list.py index 516facc613eb..4377fc28022a 100644 --- a/data_structures/linked_list/singly_linked_list.py +++ b/data_structures/linked_list/singly_linked_list.py @@ -106,7 +106,7 @@ def __setitem__(self, index, data): raise IndexError("Index out of range.") current = current.next current.data = data - + def __len__(self): """ Return length of linked list i.e. number of nodes diff --git a/maths/bailey_borwein_plouffe.py b/maths/bailey_borwein_plouffe.py index 7834668864af..50a53c793867 100644 --- a/maths/bailey_borwein_plouffe.py +++ b/maths/bailey_borwein_plouffe.py @@ -39,7 +39,7 @@ def bailey_borwein_plouffe(digit_position: int, precision: int = 1000) -> str: if (not isinstance(digit_position, int)) or (digit_position <= 0): raise ValueError("Digit position must be a positive integer") elif (not isinstance(precision, int)) or (precision < 0): - raise ValueError("Please input a nonnegative integer for the precision") + raise ValueError("Precision must be a nonnegative integer") # compute an approximation of (16 ** (n - 1)) * pi whose fractional part is mostly accurate sum_result = ( From c906ba82f3772173a7e90bd0918c846530439a8b Mon Sep 17 00:00:00 2001 From: Jie Han Date: Tue, 19 May 2020 18:56:16 +0800 Subject: [PATCH 0575/1071] refactor: move import pytest line of blockchain algs under "main" section. (#2012) * change doctest line import doctest is not relevant with algorithms. move it under main section. * from doctest import testmod * refactor: move doctest under "main" section * Update diophantine_equation.py * Update modular_division.py Co-authored-by: Christian Clauss --- blockchain/diophantine_equation.py | 5 ++--- blockchain/modular_division.py | 5 ++--- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/blockchain/diophantine_equation.py b/blockchain/diophantine_equation.py index ec2ed26e40ec..dab4b3a65fb2 100644 --- a/blockchain/diophantine_equation.py +++ b/blockchain/diophantine_equation.py @@ -118,10 +118,9 @@ def extended_gcd(a, b): return (d, x, y) -# import testmod for testing our function -from doctest import testmod - if __name__ == "__main__": + from doctest import testmod + testmod(name="diophantine", verbose=True) testmod(name="diophantine_all_soln", verbose=True) testmod(name="extended_gcd", verbose=True) diff --git a/blockchain/modular_division.py b/blockchain/modular_division.py index 1255f04328d5..c81c2138d1a8 100644 --- a/blockchain/modular_division.py +++ b/blockchain/modular_division.py @@ -139,10 +139,9 @@ def greatest_common_divisor(a, b): return b -# Import testmod for testing our function -from doctest import testmod - if __name__ == "__main__": + from doctest import testmod + testmod(name="modular_division", verbose=True) testmod(name="modular_division2", verbose=True) testmod(name="invert_modulo", verbose=True) From 243100165800d5f4be46b6234c4888e34eb63e60 Mon Sep 17 00:00:00 2001 From: mateuszz0000 Date: Tue, 19 May 2020 13:44:45 +0200 Subject: [PATCH 0576/1071] Easter date gauss algorithm (#2010) * Added gauss easter algorithm * Fixes in easter algorithm * Commit suggestions --- other/gauss_easter.py | 59 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 59 insertions(+) create mode 100644 other/gauss_easter.py diff --git a/other/gauss_easter.py b/other/gauss_easter.py new file mode 100644 index 000000000000..4447d4ab86af --- /dev/null +++ b/other/gauss_easter.py @@ -0,0 +1,59 @@ +""" +https://en.wikipedia.org/wiki/Computus#Gauss'_Easter_algorithm +""" +import math +from datetime import datetime, timedelta + + +def gauss_easter(year: int) -> datetime: + """ + Calculation Gregorian easter date for given year + + >>> gauss_easter(2007) + datetime.datetime(2007, 4, 8, 0, 0) + + >>> gauss_easter(2008) + datetime.datetime(2008, 3, 23, 0, 0) + + >>> gauss_easter(2020) + datetime.datetime(2020, 4, 12, 0, 0) + + >>> gauss_easter(2021) + datetime.datetime(2021, 4, 4, 0, 0) + """ + metonic_cycle = year % 19 + julian_leap_year = year % 4 + non_leap_year = year % 7 + leap_day_inhibits = math.floor(year / 100) + lunar_orbit_correction = math.floor((13 + 8 * leap_day_inhibits) / 25) + leap_day_reinstall_number = leap_day_inhibits / 4 + secular_moon_shift = ( + 15 - lunar_orbit_correction + leap_day_inhibits - leap_day_reinstall_number + ) % 30 + century_starting_point = (4 + leap_day_inhibits - leap_day_reinstall_number) % 7 + + # days to be added to March 21 + days_to_add = (19 * metonic_cycle + secular_moon_shift) % 30 + + # PHM -> Paschal Full Moon + days_from_phm_to_sunday = ( + 2 * julian_leap_year + + 4 * non_leap_year + + 6 * days_to_add + + century_starting_point + ) % 7 + + if days_to_add == 29 and days_from_phm_to_sunday == 6: + return datetime(year, 4, 19) + elif days_to_add == 28 and days_from_phm_to_sunday == 6: + return datetime(year, 4, 18) + else: + return datetime(year, 3, 22) + timedelta( + days=int(days_to_add + days_from_phm_to_sunday) + ) + + +if __name__ == "__main__": + for year in (1994, 2000, 2010, 2021, 2023): + tense = "will be" if year > datetime.now().year else "was" + print(f"Easter in {year} {tense} {gauss_easter(year)}") From 0e6e5056b3fede1705c6081543bb129b97ff4d35 Mon Sep 17 00:00:00 2001 From: Christian Clauss Date: Tue, 19 May 2020 13:54:25 +0200 Subject: [PATCH 0577/1071] singly_linked_list.py: psf/black (#2008) * singly_linked_list.py: psf/black * updating DIRECTORY.md * Update singly_linked_list.py Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> --- DIRECTORY.md | 1 + data_structures/linked_list/singly_linked_list.py | 8 ++++---- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/DIRECTORY.md b/DIRECTORY.md index bef7bca86cc3..aea74f9255f9 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -295,6 +295,7 @@ * [Add](https://github.com/TheAlgorithms/Python/blob/master/maths/add.py) * [Aliquot Sum](https://github.com/TheAlgorithms/Python/blob/master/maths/aliquot_sum.py) * [Allocation Number](https://github.com/TheAlgorithms/Python/blob/master/maths/allocation_number.py) + * [Area](https://github.com/TheAlgorithms/Python/blob/master/maths/area.py) * [Area Under Curve](https://github.com/TheAlgorithms/Python/blob/master/maths/area_under_curve.py) * [Armstrong Numbers](https://github.com/TheAlgorithms/Python/blob/master/maths/armstrong_numbers.py) * [Average Mean](https://github.com/TheAlgorithms/Python/blob/master/maths/average_mean.py) diff --git a/data_structures/linked_list/singly_linked_list.py b/data_structures/linked_list/singly_linked_list.py index 4377fc28022a..1f3e03a31b7e 100644 --- a/data_structures/linked_list/singly_linked_list.py +++ b/data_structures/linked_list/singly_linked_list.py @@ -1,9 +1,9 @@ -class Node: # create a Node +class Node: def __init__(self, data): - self.data = data # given data - self.next = None # given next to None + self.data = data + self.next = None - def __repr__(self): # string representation of a Node + def __repr__(self): return f"Node({self.data})" From 777ddca2e9b63c294c49883518fc0723de2c11e0 Mon Sep 17 00:00:00 2001 From: Sanders Lin <45224617+SandersLin@users.noreply.github.com> Date: Wed, 20 May 2020 01:31:52 +0800 Subject: [PATCH 0578/1071] Update fast_fibonacci.py (#1889) * Update fast_fibonacci.py * Update fast_fibonacci.py Co-authored-by: Christian Clauss --- dynamic_programming/fast_fibonacci.py | 47 ++++++++++++--------------- 1 file changed, 20 insertions(+), 27 deletions(-) diff --git a/dynamic_programming/fast_fibonacci.py b/dynamic_programming/fast_fibonacci.py index 77094a40384b..63481fe70a92 100644 --- a/dynamic_programming/fast_fibonacci.py +++ b/dynamic_programming/fast_fibonacci.py @@ -1,44 +1,37 @@ -#!/usr/bin/python +#!/usr/bin/env python3 """ This program calculates the nth Fibonacci number in O(log(n)). -It's possible to calculate F(1000000) in less than a second. +It's possible to calculate F(1_000_000) in less than a second. """ import sys +from typing import Tuple -# returns F(n) -def fibonacci(n: int): # noqa: E999 This syntax is Python 3 only +def fibonacci(n: int) -> int: + """ + return F(n) + >>> [fibonacci(i) for i in range(13)] + [0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144] + """ if n < 0: raise ValueError("Negative arguments are not supported") return _fib(n)[0] # returns (F(n), F(n-1)) -def _fib(n: int): # noqa: E999 This syntax is Python 3 only - if n == 0: - # (F(0), F(1)) +def _fib(n: int) -> Tuple[int, int]: + if n == 0: # (F(0), F(1)) return (0, 1) - else: - # F(2n) = F(n)[2F(n+1) − F(n)] - # F(2n+1) = F(n+1)^2+F(n)^2 - a, b = _fib(n // 2) - c = a * (b * 2 - a) - d = a * a + b * b - if n % 2 == 0: - return (c, d) - else: - return (d, c + d) + + # F(2n) = F(n)[2F(n+1) − F(n)] + # F(2n+1) = F(n+1)^2+F(n)^2 + a, b = _fib(n // 2) + c = a * (b * 2 - a) + d = a * a + b * b + return (d, c + d) if n % 2 else (c, d) if __name__ == "__main__": - args = sys.argv[1:] - if len(args) != 1: - print("Too few or too much parameters given.") - exit(1) - try: - n = int(args[0]) - except ValueError: - print("Could not convert data to an integer.") - exit(1) - print("F(%d) = %d" % (n, fibonacci(n))) + n = int(sys.argv[1]) + print(f"fibonacci({n}) is {fibonacci(n)}") From 965d02ad41a5269e0b858a7e983c7d7e24e1ee33 Mon Sep 17 00:00:00 2001 From: mateuszz0000 Date: Wed, 20 May 2020 08:23:17 +0200 Subject: [PATCH 0579/1071] Update atbash cipher (doc, doctest, performance) (#2017) * Update atbash * Add benchmark() to quantify the performance improvement Co-authored-by: Christian Clauss --- ciphers/atbash.py | 59 +++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 55 insertions(+), 4 deletions(-) diff --git a/ciphers/atbash.py b/ciphers/atbash.py index 4cf003859856..c17d1e34f37a 100644 --- a/ciphers/atbash.py +++ b/ciphers/atbash.py @@ -1,6 +1,17 @@ -def atbash(): +""" https://en.wikipedia.org/wiki/Atbash """ +import string + + +def atbash_slow(sequence: str) -> str: + """ + >>> atbash_slow("ABCDEFG") + 'ZYXWVUT' + + >>> atbash_slow("aW;;123BX") + 'zD;;123YC' + """ output = "" - for i in input("Enter the sentence to be encrypted ").strip(): + for i in sequence: extract = ord(i) if 65 <= extract <= 90: output += chr(155 - extract) @@ -8,8 +19,48 @@ def atbash(): output += chr(219 - extract) else: output += i - print(output) + return output + + +def atbash(sequence: str) -> str: + """ + >>> atbash("ABCDEFG") + 'ZYXWVUT' + + >>> atbash("aW;;123BX") + 'zD;;123YC' + """ + letters = string.ascii_letters + letters_reversed = string.ascii_lowercase[::-1] + string.ascii_uppercase[::-1] + return "".join( + letters_reversed[letters.index(c)] if c in letters else c for c in sequence + ) + + +def benchmark() -> None: + """Let's benchmark them side-by-side...""" + from timeit import timeit + + print("Running performance benchmarks...") + print( + "> atbash_slow()", + timeit( + "atbash_slow(printable)", + setup="from string import printable ; from __main__ import atbash_slow", + ), + "seconds", + ) + print( + "> atbash()", + timeit( + "atbash(printable)", + setup="from string import printable ; from __main__ import atbash", + ), + "seconds", + ) if __name__ == "__main__": - atbash() + for sequence in ("ABCDEFGH", "123GGjj", "testStringtest", "with space"): + print(f"{sequence} encrypted in atbash: {atbash(sequence)}") + benchmark() From 1f2d607e5650aff922ab74be0747e7364eaa9b79 Mon Sep 17 00:00:00 2001 From: Erwin Lejeune Date: Wed, 20 May 2020 08:50:22 +0200 Subject: [PATCH 0580/1071] Graphs : Bidirectional A* (#2015) * implement bidirectional astar * add type hints * add wikipedia url * format with black * changes from review --- graphs/bidirectional_a_star.py | 218 +++++++++++++++++++++++++++++++++ 1 file changed, 218 insertions(+) create mode 100644 graphs/bidirectional_a_star.py diff --git a/graphs/bidirectional_a_star.py b/graphs/bidirectional_a_star.py new file mode 100644 index 000000000000..91479cc3b357 --- /dev/null +++ b/graphs/bidirectional_a_star.py @@ -0,0 +1,218 @@ +""" +https://en.wikipedia.org/wiki/Bidirectional_search +""" + +import time +from typing import List, Tuple + +grid = [ + [0, 0, 0, 0, 0, 0, 0], + [0, 1, 0, 0, 0, 0, 0], # 0 are free path whereas 1's are obstacles + [0, 0, 0, 0, 0, 0, 0], + [0, 0, 1, 0, 0, 0, 0], + [1, 0, 1, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 1, 0, 0], +] + +delta = [[-1, 0], [0, -1], [1, 0], [0, 1]] # up, left, down, right + + +class Node: + """ + >>> k = Node(0, 0, 4, 5, 0, None) + >>> k.calculate_heuristic() + 9 + >>> n = Node(1, 4, 3, 4, 2, None) + >>> n.calculate_heuristic() + 2 + >>> l = [k, n] + >>> n == l[0] + False + >>> l.sort() + >>> n == l[0] + True + """ + + def __init__(self, pos_x, pos_y, goal_x, goal_y, g_cost, parent): + self.pos_x = pos_x + self.pos_y = pos_y + self.pos = (pos_y, pos_x) + self.goal_x = goal_x + self.goal_y = goal_y + self.g_cost = g_cost + self.parent = parent + self.h_cost = self.calculate_heuristic() + self.f_cost = self.g_cost + self.h_cost + + def calculate_heuristic(self) -> float: + """ + The heuristic here is the Manhattan Distance + Could elaborate to offer more than one choice + """ + dy = abs(self.pos_x - self.goal_x) + dx = abs(self.pos_y - self.goal_y) + return dx + dy + + def __lt__(self, other): + return self.f_cost < other.f_cost + + +class AStar: + def __init__(self, start, goal): + self.start = Node(start[1], start[0], goal[1], goal[0], 0, None) + self.target = Node(goal[1], goal[0], goal[1], goal[0], 99999, None) + + self.open_nodes = [self.start] + self.closed_nodes = [] + + self.reached = False + + self.path = [(self.start.pos_y, self.start.pos_x)] + self.costs = [0] + + def search(self): + while self.open_nodes: + # Open Nodes are sorted using __lt__ + self.open_nodes.sort() + current_node = self.open_nodes.pop(0) + + if current_node.pos == self.target.pos: + self.reached = True + self.path = self.retrace_path(current_node) + break + + self.closed_nodes.append(current_node) + successors = self.get_successors(current_node) + + for child_node in successors: + if child_node in self.closed_nodes: + continue + + if child_node not in self.open_nodes: + self.open_nodes.append(child_node) + else: + # retrieve the best current path + better_node = self.open_nodes.pop(self.open_nodes.index(child_node)) + + if child_node.g_cost < better_node.g_cost: + self.open_nodes.append(child_node) + else: + self.open_nodes.append(better_node) + + if not (self.reached): + print("No path found") + + def get_successors(self, parent: Node) -> List[Node]: + """ + Returns a list of successors (both in the grid and free spaces) + """ + successors = [] + for action in delta: + pos_x = parent.pos_x + action[1] + pos_y = parent.pos_y + action[0] + if not (0 < pos_x < len(grid[0]) - 1 and 0 < pos_y < len(grid) - 1): + continue + + if grid[pos_y][pos_x] != 0: + continue + + node_ = Node( + pos_x, + pos_y, + self.target.pos_y, + self.target.pos_x, + parent.g_cost + 1, + parent, + ) + successors.append(node_) + return successors + + def retrace_path(self, node: Node) -> List[Tuple[int]]: + """ + Retrace the path from parents to parents until start node + """ + current_node = node + path = [] + while current_node is not None: + path.append((current_node.pos_y, current_node.pos_x)) + current_node = current_node.parent + path.reverse() + return path + + +class BidirectionalAStar: + def __init__(self, start, goal): + self.fwd_astar = AStar(start, goal) + self.bwd_astar = AStar(goal, start) + self.reached = False + self.path = self.fwd_astar.path + + def search(self): + while self.fwd_astar.open_nodes or self.bwd_astar.open_nodes: + self.fwd_astar.open_nodes.sort() + self.bwd_astar.open_nodes.sort() + current_fwd_node = self.fwd_astar.open_nodes.pop(0) + current_bwd_node = self.bwd_astar.open_nodes.pop(0) + + if current_bwd_node.pos == current_fwd_node.pos: + self.reached = True + self.retrace_bidirectional_path(current_fwd_node, current_bwd_node) + break + + self.fwd_astar.closed_nodes.append(current_fwd_node) + self.bwd_astar.closed_nodes.append(current_bwd_node) + + self.fwd_astar.target = current_bwd_node + self.bwd_astar.target = current_fwd_node + + successors = { + self.fwd_astar: self.fwd_astar.get_successors(current_fwd_node), + self.bwd_astar: self.bwd_astar.get_successors(current_bwd_node), + } + + for astar in [self.fwd_astar, self.bwd_astar]: + for child_node in successors[astar]: + if child_node in astar.closed_nodes: + continue + + if child_node not in astar.open_nodes: + astar.open_nodes.append(child_node) + else: + # retrieve the best current path + better_node = astar.open_nodes.pop( + astar.open_nodes.index(child_node) + ) + + if child_node.g_cost < better_node.g_cost: + astar.open_nodes.append(child_node) + else: + astar.open_nodes.append(better_node) + + def retrace_bidirectional_path( + self, fwd_node: Node, bwd_node: Node + ) -> List[Tuple[int]]: + fwd_path = self.fwd_astar.retrace_path(fwd_node) + bwd_path = self.bwd_astar.retrace_path(bwd_node) + fwd_path.reverse() + path = fwd_path + bwd_path + return path + + +# all coordinates are given in format [y,x] +init = (0, 0) +goal = (len(grid) - 1, len(grid[0]) - 1) +for elem in grid: + print(elem) + +start_time = time.time() +a_star = AStar(init, goal) +a_star.search() +end_time = time.time() - start_time +print(f"AStar execution time = {end_time:f} seconds") + +bd_start_time = time.time() +bidir_astar = BidirectionalAStar(init, goal) +bidir_astar.search() +bd_end_time = time.time() - bd_start_time +print(f"BidirectionalAStar execution time = {bd_end_time:f} seconds") From dc596d23a9c87d13085791207086cca3d5d835d1 Mon Sep 17 00:00:00 2001 From: Erwin Lejeune Date: Wed, 20 May 2020 23:25:48 +0200 Subject: [PATCH 0581/1071] Graphs : Greedy Best First (#2018) * implement greedy best first * implement Greedy Best First Search * review changes * add doctests * >>> gbf.search() # doctest: +NORMALIZE_WHITESPACE Co-authored-by: Christian Clauss --- graphs/greedy_best_first.py | 174 ++++++++++++++++++++++++++++++++++++ 1 file changed, 174 insertions(+) create mode 100644 graphs/greedy_best_first.py diff --git a/graphs/greedy_best_first.py b/graphs/greedy_best_first.py new file mode 100644 index 000000000000..2e63a50ce30a --- /dev/null +++ b/graphs/greedy_best_first.py @@ -0,0 +1,174 @@ +""" +https://en.wikipedia.org/wiki/Best-first_search#Greedy_BFS +""" + +from typing import List, Tuple + +grid = [ + [0, 0, 0, 0, 0, 0, 0], + [0, 1, 0, 0, 0, 0, 0], # 0 are free path whereas 1's are obstacles + [0, 0, 0, 0, 0, 0, 0], + [0, 0, 1, 0, 0, 0, 0], + [1, 0, 1, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 1, 0, 0], +] + +delta = ([-1, 0], [0, -1], [1, 0], [0, 1]) # up, left, down, right + + +class Node: + """ + >>> k = Node(0, 0, 4, 5, 0, None) + >>> k.calculate_heuristic() + 9 + >>> n = Node(1, 4, 3, 4, 2, None) + >>> n.calculate_heuristic() + 2 + >>> l = [k, n] + >>> n == l[0] + False + >>> l.sort() + >>> n == l[0] + True + """ + + def __init__(self, pos_x, pos_y, goal_x, goal_y, g_cost, parent): + self.pos_x = pos_x + self.pos_y = pos_y + self.pos = (pos_y, pos_x) + self.goal_x = goal_x + self.goal_y = goal_y + self.g_cost = g_cost + self.parent = parent + self.f_cost = self.calculate_heuristic() + + def calculate_heuristic(self) -> float: + """ + The heuristic here is the Manhattan Distance + Could elaborate to offer more than one choice + """ + dy = abs(self.pos_x - self.goal_x) + dx = abs(self.pos_y - self.goal_y) + return dx + dy + + def __lt__(self, other) -> bool: + return self.f_cost < other.f_cost + + +class GreedyBestFirst: + """ + >>> gbf = GreedyBestFirst((0, 0), (len(grid) - 1, len(grid[0]) - 1)) + >>> [x.pos for x in gbf.get_successors(gbf.start)] + [(1, 0), (0, 1)] + >>> (gbf.start.pos_y + delta[3][0], gbf.start.pos_x + delta[3][1]) + (0, 1) + >>> (gbf.start.pos_y + delta[2][0], gbf.start.pos_x + delta[2][1]) + (1, 0) + >>> gbf.retrace_path(gbf.start) + [(0, 0)] + >>> gbf.search() # doctest: +NORMALIZE_WHITESPACE + [(0, 0), (1, 0), (2, 0), (3, 0), (3, 1), (4, 1), (5, 1), (6, 1), + (6, 2), (6, 3), (5, 3), (5, 4), (5, 5), (6, 5), (6, 6)] + """ + + def __init__(self, start, goal): + self.start = Node(start[1], start[0], goal[1], goal[0], 0, None) + self.target = Node(goal[1], goal[0], goal[1], goal[0], 99999, None) + + self.open_nodes = [self.start] + self.closed_nodes = [] + + self.reached = False + + def search(self) -> List[Tuple[int]]: + """ + Search for the path, + if a path is not found, only the starting position is returned + """ + while self.open_nodes: + # Open Nodes are sorted using __lt__ + self.open_nodes.sort() + current_node = self.open_nodes.pop(0) + + if current_node.pos == self.target.pos: + self.reached = True + return self.retrace_path(current_node) + + self.closed_nodes.append(current_node) + successors = self.get_successors(current_node) + + for child_node in successors: + if child_node in self.closed_nodes: + continue + + if child_node not in self.open_nodes: + self.open_nodes.append(child_node) + else: + # retrieve the best current path + better_node = self.open_nodes.pop(self.open_nodes.index(child_node)) + + if child_node.g_cost < better_node.g_cost: + self.open_nodes.append(child_node) + else: + self.open_nodes.append(better_node) + + if not (self.reached): + return [self.start.pos] + + def get_successors(self, parent: Node) -> List[Node]: + """ + Returns a list of successors (both in the grid and free spaces) + """ + successors = [] + for action in delta: + pos_x = parent.pos_x + action[1] + pos_y = parent.pos_y + action[0] + + if not (0 <= pos_x <= len(grid[0]) - 1 and 0 <= pos_y <= len(grid) - 1): + continue + + if grid[pos_y][pos_x] != 0: + continue + + successors.append( + Node( + pos_x, + pos_y, + self.target.pos_y, + self.target.pos_x, + parent.g_cost + 1, + parent, + ) + ) + return successors + + def retrace_path(self, node: Node) -> List[Tuple[int]]: + """ + Retrace the path from parents to parents until start node + """ + current_node = node + path = [] + while current_node is not None: + path.append((current_node.pos_y, current_node.pos_x)) + current_node = current_node.parent + path.reverse() + return path + + +if __name__ == "__main__": + init = (0, 0) + goal = (len(grid) - 1, len(grid[0]) - 1) + for elem in grid: + print(elem) + + print("------") + + greedy_bf = GreedyBestFirst(init, goal) + path = greedy_bf.search() + + for elem in path: + grid[elem[0]][elem[1]] = 2 + + for elem in grid: + print(elem) From 21ed8968c08c67cf0552845b57e0cd244f9b47e9 Mon Sep 17 00:00:00 2001 From: Erwin Lejeune Date: Thu, 21 May 2020 21:50:52 +0200 Subject: [PATCH 0582/1071] Fixes in Bidirectional A* (#2020) * implement bidirectional astar * add type hints * add wikipedia url * format with black * changes from review * fix collision check * Add testmod() * # doctest: +NORMALIZE_WHITESPACE * Codespell: euclidean * Codespell: coordinates * Codespell: traversal * Codespell: remaining Co-authored-by: John Law Co-authored-by: Christian Clauss --- graphs/bidirectional_a_star.py | 130 +++++++++++++-------- machine_learning/astar.py | 2 +- scheduling/shortest_job_first_algorithm.py | 2 +- sorts/tree_sort.py | 2 +- 4 files changed, 87 insertions(+), 49 deletions(-) diff --git a/graphs/bidirectional_a_star.py b/graphs/bidirectional_a_star.py index 91479cc3b357..76313af769be 100644 --- a/graphs/bidirectional_a_star.py +++ b/graphs/bidirectional_a_star.py @@ -3,8 +3,12 @@ """ import time +from math import sqrt from typing import List, Tuple +# 1 for manhattan, 0 for euclidean +HEURISTIC = 0 + grid = [ [0, 0, 0, 0, 0, 0, 0], [0, 1, 0, 0, 0, 0, 0], # 0 are free path whereas 1's are obstacles @@ -20,12 +24,12 @@ class Node: """ - >>> k = Node(0, 0, 4, 5, 0, None) + >>> k = Node(0, 0, 4, 3, 0, None) >>> k.calculate_heuristic() - 9 + 5.0 >>> n = Node(1, 4, 3, 4, 2, None) >>> n.calculate_heuristic() - 2 + 2.0 >>> l = [k, n] >>> n == l[0] False @@ -47,18 +51,35 @@ def __init__(self, pos_x, pos_y, goal_x, goal_y, g_cost, parent): def calculate_heuristic(self) -> float: """ - The heuristic here is the Manhattan Distance - Could elaborate to offer more than one choice + Heuristic for the A* """ - dy = abs(self.pos_x - self.goal_x) - dx = abs(self.pos_y - self.goal_y) - return dx + dy - - def __lt__(self, other): + dy = self.pos_x - self.goal_x + dx = self.pos_y - self.goal_y + if HEURISTIC == 1: + return abs(dx) + abs(dy) + else: + return sqrt(dy ** 2 + dx ** 2) + + def __lt__(self, other) -> bool: return self.f_cost < other.f_cost class AStar: + """ + >>> astar = AStar((0, 0), (len(grid) - 1, len(grid[0]) - 1)) + >>> (astar.start.pos_y + delta[3][0], astar.start.pos_x + delta[3][1]) + (0, 1) + >>> [x.pos for x in astar.get_successors(astar.start)] + [(1, 0), (0, 1)] + >>> (astar.start.pos_y + delta[2][0], astar.start.pos_x + delta[2][1]) + (1, 0) + >>> astar.retrace_path(astar.start) + [(0, 0)] + >>> astar.search() # doctest: +NORMALIZE_WHITESPACE + [(0, 0), (1, 0), (2, 0), (2, 1), (2, 2), (2, 3), (3, 3), + (4, 3), (4, 4), (5, 4), (5, 5), (6, 5), (6, 6)] + """ + def __init__(self, start, goal): self.start = Node(start[1], start[0], goal[1], goal[0], 0, None) self.target = Node(goal[1], goal[0], goal[1], goal[0], 99999, None) @@ -68,10 +89,7 @@ def __init__(self, start, goal): self.reached = False - self.path = [(self.start.pos_y, self.start.pos_x)] - self.costs = [0] - - def search(self): + def search(self) -> List[Tuple[int]]: while self.open_nodes: # Open Nodes are sorted using __lt__ self.open_nodes.sort() @@ -79,8 +97,7 @@ def search(self): if current_node.pos == self.target.pos: self.reached = True - self.path = self.retrace_path(current_node) - break + return self.retrace_path(current_node) self.closed_nodes.append(current_node) successors = self.get_successors(current_node) @@ -101,7 +118,7 @@ def search(self): self.open_nodes.append(better_node) if not (self.reached): - print("No path found") + return [(self.start.pos)] def get_successors(self, parent: Node) -> List[Node]: """ @@ -111,21 +128,22 @@ def get_successors(self, parent: Node) -> List[Node]: for action in delta: pos_x = parent.pos_x + action[1] pos_y = parent.pos_y + action[0] - if not (0 < pos_x < len(grid[0]) - 1 and 0 < pos_y < len(grid) - 1): + if not (0 <= pos_x <= len(grid[0]) - 1 and 0 <= pos_y <= len(grid) - 1): continue if grid[pos_y][pos_x] != 0: continue - node_ = Node( - pos_x, - pos_y, - self.target.pos_y, - self.target.pos_x, - parent.g_cost + 1, - parent, + successors.append( + Node( + pos_x, + pos_y, + self.target.pos_y, + self.target.pos_x, + parent.g_cost + 1, + parent, + ) ) - successors.append(node_) return successors def retrace_path(self, node: Node) -> List[Tuple[int]]: @@ -142,13 +160,24 @@ def retrace_path(self, node: Node) -> List[Tuple[int]]: class BidirectionalAStar: + """ + >>> bd_astar = BidirectionalAStar((0, 0), (len(grid) - 1, len(grid[0]) - 1)) + >>> bd_astar.fwd_astar.start.pos == bd_astar.bwd_astar.target.pos + True + >>> bd_astar.retrace_bidirectional_path(bd_astar.fwd_astar.start, + ... bd_astar.bwd_astar.start) + [(0, 0)] + >>> bd_astar.search() # doctest: +NORMALIZE_WHITESPACE + [(0, 0), (0, 1), (0, 2), (1, 2), (1, 3), (2, 3), (2, 4), + (2, 5), (3, 5), (4, 5), (5, 5), (5, 6), (6, 6)] + """ + def __init__(self, start, goal): self.fwd_astar = AStar(start, goal) self.bwd_astar = AStar(goal, start) self.reached = False - self.path = self.fwd_astar.path - def search(self): + def search(self) -> List[Tuple[int]]: while self.fwd_astar.open_nodes or self.bwd_astar.open_nodes: self.fwd_astar.open_nodes.sort() self.bwd_astar.open_nodes.sort() @@ -157,8 +186,9 @@ def search(self): if current_bwd_node.pos == current_fwd_node.pos: self.reached = True - self.retrace_bidirectional_path(current_fwd_node, current_bwd_node) - break + return self.retrace_bidirectional_path( + current_fwd_node, current_bwd_node + ) self.fwd_astar.closed_nodes.append(current_fwd_node) self.bwd_astar.closed_nodes.append(current_bwd_node) @@ -189,30 +219,38 @@ def search(self): else: astar.open_nodes.append(better_node) + if not self.reached: + return [self.fwd_astar.start.pos] + def retrace_bidirectional_path( self, fwd_node: Node, bwd_node: Node ) -> List[Tuple[int]]: fwd_path = self.fwd_astar.retrace_path(fwd_node) bwd_path = self.bwd_astar.retrace_path(bwd_node) - fwd_path.reverse() + bwd_path.pop() + bwd_path.reverse() path = fwd_path + bwd_path return path -# all coordinates are given in format [y,x] -init = (0, 0) -goal = (len(grid) - 1, len(grid[0]) - 1) -for elem in grid: - print(elem) +if __name__ == "__main__": + # all coordinates are given in format [y,x] + import doctest + + doctest.testmod() + init = (0, 0) + goal = (len(grid) - 1, len(grid[0]) - 1) + for elem in grid: + print(elem) -start_time = time.time() -a_star = AStar(init, goal) -a_star.search() -end_time = time.time() - start_time -print(f"AStar execution time = {end_time:f} seconds") + start_time = time.time() + a_star = AStar(init, goal) + path = a_star.search() + end_time = time.time() - start_time + print(f"AStar execution time = {end_time:f} seconds") -bd_start_time = time.time() -bidir_astar = BidirectionalAStar(init, goal) -bidir_astar.search() -bd_end_time = time.time() - bd_start_time -print(f"BidirectionalAStar execution time = {bd_end_time:f} seconds") + bd_start_time = time.time() + bidir_astar = BidirectionalAStar(init, goal) + path = bidir_astar.search() + bd_end_time = time.time() - bd_start_time + print(f"BidirectionalAStar execution time = {bd_end_time:f} seconds") diff --git a/machine_learning/astar.py b/machine_learning/astar.py index ec8f214ab082..2f5c21a2bd5f 100644 --- a/machine_learning/astar.py +++ b/machine_learning/astar.py @@ -17,7 +17,7 @@ class Cell(object): """ Class cell represents a cell in the world which have the property position : The position of the represented by tupleof x and y - co-ordinates initially set to (0,0) + coordinates initially set to (0,0) parent : This contains the parent cell object which we visited before arrinving this cell g,h,f : The parameters for constructing the heuristic function diff --git a/scheduling/shortest_job_first_algorithm.py b/scheduling/shortest_job_first_algorithm.py index 18aadca0032f..8aa642a8b25b 100644 --- a/scheduling/shortest_job_first_algorithm.py +++ b/scheduling/shortest_job_first_algorithm.py @@ -1,5 +1,5 @@ """ -Shortest job remainig first +Shortest job remaining first Please note arrival time and burst Please use spaces to separate times entered. """ diff --git a/sorts/tree_sort.py b/sorts/tree_sort.py index 716170a94fd1..e445fb4520aa 100644 --- a/sorts/tree_sort.py +++ b/sorts/tree_sort.py @@ -29,7 +29,7 @@ def insert(self, val): def inorder(root, res): - # Recursive travesal + # Recursive traversal if root: inorder(root.left, res) res.append(root.val) From 1f8a21d7276a05442e02570dbbfe27cd4f0365a5 Mon Sep 17 00:00:00 2001 From: Christian Clauss Date: Fri, 22 May 2020 08:10:11 +0200 Subject: [PATCH 0583/1071] Tighten up psf/black and flake8 (#2024) * Tighten up psf/black and flake8 * Fix some tests * Fix some E741 * Fix some E741 * updating DIRECTORY.md Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> --- .travis.yml | 10 +-- DIRECTORY.md | 3 + .../newton_forward_interpolation.py | 1 + backtracking/coloring.py | 12 ++-- backtracking/hamiltonian_cycle.py | 30 ++++---- backtracking/minimax.py | 4 +- backtracking/n_queens.py | 16 ++--- ciphers/decrypt_caesar_with_chi_squared.py | 26 ++++--- ciphers/elgamal_key_generator.py | 4 +- ciphers/mixed_keyword_cypher.py | 8 +-- ciphers/simple_substitution_cipher.py | 3 +- ...ansposition_cipher_encrypt_decrypt_file.py | 5 +- conversions/decimal_to_octal.py | 2 +- data_structures/binary_tree/avl_tree.py | 18 ++--- .../binary_tree/basic_binary_tree.py | 7 +- .../binary_tree/lazy_segment_tree.py | 33 +++++---- .../binary_tree/non_recursive_segment_tree.py | 15 ++-- data_structures/binary_tree/segment_tree.py | 22 +++--- data_structures/binary_tree/treap.py | 5 +- data_structures/hashing/quadratic_probing.py | 2 +- data_structures/heap/binomial_heap.py | 2 + data_structures/heap/min_heap.py | 4 +- data_structures/linked_list/deque_doubly.py | 8 +-- .../middle_element_of_linked_list.py | 2 +- data_structures/stacks/postfix_evaluation.py | 20 +++--- data_structures/trie/trie.py | 2 +- digital_image_processing/dithering/burkes.py | 6 +- .../edge_detection/canny.py | 4 +- digital_image_processing/index_calculation.py | 72 ++++++++++--------- divide_and_conquer/max_subarray_sum.py | 28 ++++---- divide_and_conquer/mergesort.py | 2 +- dynamic_programming/factorial.py | 6 +- .../iterating_through_submasks.py | 4 +- .../longest_common_subsequence.py | 2 +- .../longest_increasing_subsequence.py | 17 +++-- ...longest_increasing_subsequence_o(nlogn).py | 11 +-- dynamic_programming/max_sub_array.py | 6 +- dynamic_programming/minimum_partition.py | 2 +- .../optimal_binary_search_tree.py | 13 ++-- dynamic_programming/subset_generation.py | 17 +++-- geodesy/lamberts_ellipsoidal_distance.py | 4 +- graphs/articulation_points.py | 8 +-- graphs/basic_graphs.py | 11 +-- graphs/bellman_ford.py | 2 +- graphs/breadth_first_search_shortest_path.py | 11 +-- graphs/check_bipartite_graph_bfs.py | 19 ++--- graphs/check_bipartite_graph_dfs.py | 19 ++--- graphs/depth_first_search.py | 8 +-- graphs/depth_first_search_2.py | 4 +- graphs/dijkstra.py | 5 +- graphs/dinic.py | 2 +- ...irected_and_undirected_(weighted)_graph.py | 48 ++++++------- ...n_path_and_circuit_for_undirected_graph.py | 2 +- graphs/finding_bridges.py | 10 +-- graphs/frequent_pattern_graph_miner.py | 2 +- graphs/kahns_algorithm_long.py | 14 ++-- graphs/kahns_algorithm_topo.py | 19 ++--- graphs/minimum_spanning_tree_prims.py | 4 +- hashes/chaos_machine.py | 41 ++++++----- hashes/hamming_code.py | 1 + linear_algebra/src/lib.py | 18 ++--- linear_algebra/src/test_linear_algebra.py | 2 +- machine_learning/k_means_clust.py | 27 ++++--- .../linear_discriminant_analysis.py | 5 +- machine_learning/polymonial_regression.py | 14 ++-- machine_learning/scoring_functions.py | 1 + maths/aliquot_sum.py | 2 +- maths/allocation_number.py | 4 +- maths/bailey_borwein_plouffe.py | 10 +-- maths/collatz_sequence.py | 5 +- maths/find_max_recursion.py | 2 +- maths/gamma.py | 12 ++-- maths/gaussian.py | 4 +- maths/is_square_free.py | 4 +- maths/kth_lexicographic_permutation.py | 8 +-- maths/lucas_lehmer_primality_test.py | 10 +-- maths/matrix_exponentiation.py | 2 +- maths/modular_exponential.py | 2 +- maths/monte_carlo.py | 10 +-- maths/newton_raphson.py | 4 +- maths/prime_factors.py | 2 +- maths/prime_sieve_eratosthenes.py | 6 +- maths/radians.py | 4 +- maths/radix2_fft.py | 30 ++++---- maths/sieve_of_eratosthenes.py | 4 +- maths/square_root.py | 4 +- matrix/sherman_morrison.py | 12 ++-- networking_flow/ford_fulkerson.py | 2 +- networking_flow/minimum_cut.py | 2 +- other/activity_selection.py | 10 +-- other/anagrams.py | 5 +- other/detecting_english_programmatically.py | 5 +- other/dijkstra_bankers_algorithm.py | 2 +- other/game_of_life.py | 8 ++- other/integeration_by_simpson_approx.py | 8 +-- other/magicdiamondpattern.py | 1 + other/sdes.py | 4 +- project_euler/problem_02/sol4.py | 2 +- project_euler/problem_03/sol1.py | 2 +- project_euler/problem_03/sol2.py | 2 +- project_euler/problem_05/sol1.py | 2 +- project_euler/problem_07/sol2.py | 2 +- project_euler/problem_08/sol1.py | 2 +- project_euler/problem_08/sol2.py | 2 +- project_euler/problem_08/sol3.py | 2 +- project_euler/problem_11/sol2.py | 2 +- project_euler/problem_16/sol2.py | 2 +- project_euler/problem_234/sol1.py | 2 +- project_euler/problem_30/soln.py | 2 +- project_euler/problem_31/sol2.py | 2 +- project_euler/problem_551/sol1.py | 2 +- scheduling/first_come_first_served.py | 2 +- searches/simulated_annealing.py | 4 +- searches/ternary_search.py | 1 + sorts/bitonic_sort.py | 3 +- sorts/pigeon_sort.py | 4 +- sorts/recursive_bubble_sort.py | 2 +- strings/boyer_moore_search.py | 24 +++---- strings/lower.py | 8 +-- strings/manacher.py | 42 ++++++----- strings/reverse_words.py | 5 +- strings/split.py | 8 +-- strings/upper.py | 8 +-- traversals/binary_tree_traversals.py | 4 +- 124 files changed, 587 insertions(+), 499 deletions(-) diff --git a/.travis.yml b/.travis.yml index 22eea20c727e..c5c032c290b8 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,13 +4,13 @@ language: python python: 3.8 cache: pip before_install: pip install --upgrade pip setuptools six -install: pip install -r requirements.txt +install: pip install black flake8 before_script: - - black --check . || true - - IGNORE=E123,E203,E265,E266,E302,E401,E402,E712,E731,E741,E743,F811,F841,W291,W293,W503 - - flake8 . --count --ignore=$IGNORE --max-complexity=25 --max-line-length=127 --show-source --statistics -script: + - black --check . + - flake8 --ignore=E203,W503 --max-complexity=25 --max-line-length=120 --statistics --count . - scripts/validate_filenames.py # no uppercase, no spaces, in a directory + - pip install -r requirements.txt # fast fail on black, flake8, validate_filenames +script: - mypy --ignore-missing-imports . - pytest --doctest-modules --cov-report=term-missing:skip-covered --cov=. . after_success: diff --git a/DIRECTORY.md b/DIRECTORY.md index aea74f9255f9..2bb18897044f 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -222,6 +222,7 @@ * [Bellman Ford](https://github.com/TheAlgorithms/Python/blob/master/graphs/bellman_ford.py) * [Bfs](https://github.com/TheAlgorithms/Python/blob/master/graphs/bfs.py) * [Bfs Shortest Path](https://github.com/TheAlgorithms/Python/blob/master/graphs/bfs_shortest_path.py) + * [Bidirectional A Star](https://github.com/TheAlgorithms/Python/blob/master/graphs/bidirectional_a_star.py) * [Breadth First Search](https://github.com/TheAlgorithms/Python/blob/master/graphs/breadth_first_search.py) * [Breadth First Search Shortest Path](https://github.com/TheAlgorithms/Python/blob/master/graphs/breadth_first_search_shortest_path.py) * [Check Bipartite Graph Bfs](https://github.com/TheAlgorithms/Python/blob/master/graphs/check_bipartite_graph_bfs.py) @@ -242,6 +243,7 @@ * [Graph List](https://github.com/TheAlgorithms/Python/blob/master/graphs/graph_list.py) * [Graph Matrix](https://github.com/TheAlgorithms/Python/blob/master/graphs/graph_matrix.py) * [Graphs Floyd Warshall](https://github.com/TheAlgorithms/Python/blob/master/graphs/graphs_floyd_warshall.py) + * [Greedy Best First](https://github.com/TheAlgorithms/Python/blob/master/graphs/greedy_best_first.py) * [Kahns Algorithm Long](https://github.com/TheAlgorithms/Python/blob/master/graphs/kahns_algorithm_long.py) * [Kahns Algorithm Topo](https://github.com/TheAlgorithms/Python/blob/master/graphs/kahns_algorithm_topo.py) * [Minimum Spanning Tree Kruskal](https://github.com/TheAlgorithms/Python/blob/master/graphs/minimum_spanning_tree_kruskal.py) @@ -409,6 +411,7 @@ * [Fischer Yates Shuffle](https://github.com/TheAlgorithms/Python/blob/master/other/fischer_yates_shuffle.py) * [Frequency Finder](https://github.com/TheAlgorithms/Python/blob/master/other/frequency_finder.py) * [Game Of Life](https://github.com/TheAlgorithms/Python/blob/master/other/game_of_life.py) + * [Gauss Easter](https://github.com/TheAlgorithms/Python/blob/master/other/gauss_easter.py) * [Greedy](https://github.com/TheAlgorithms/Python/blob/master/other/greedy.py) * [Integeration By Simpson Approx](https://github.com/TheAlgorithms/Python/blob/master/other/integeration_by_simpson_approx.py) * [Largest Subarray Sum](https://github.com/TheAlgorithms/Python/blob/master/other/largest_subarray_sum.py) diff --git a/arithmetic_analysis/newton_forward_interpolation.py b/arithmetic_analysis/newton_forward_interpolation.py index d91b9709f3d6..d32e3efbd1f2 100644 --- a/arithmetic_analysis/newton_forward_interpolation.py +++ b/arithmetic_analysis/newton_forward_interpolation.py @@ -2,6 +2,7 @@ import math + # for calculating u value def ucal(u, p): """ diff --git a/backtracking/coloring.py b/backtracking/coloring.py index 77beb5fc1956..3956b21a9182 100644 --- a/backtracking/coloring.py +++ b/backtracking/coloring.py @@ -18,7 +18,7 @@ def valid_coloring( >>> neighbours = [0,1,0,1,0] >>> colored_vertices = [0, 2, 1, 2, 0] - + >>> color = 1 >>> valid_coloring(neighbours, colored_vertices, color) True @@ -37,11 +37,11 @@ def valid_coloring( def util_color( graph: List[List[int]], max_colors: int, colored_vertices: List[int], index: int ) -> bool: - """ + """ Pseudo-Code Base Case: - 1. Check if coloring is complete + 1. Check if coloring is complete 1.1 If complete return True (meaning that we successfully colored graph) Recursive Step: @@ -60,7 +60,7 @@ def util_color( >>> max_colors = 3 >>> colored_vertices = [0, 1, 0, 0, 0] >>> index = 3 - + >>> util_color(graph, max_colors, colored_vertices, index) True @@ -87,11 +87,11 @@ def util_color( def color(graph: List[List[int]], max_colors: int) -> List[int]: - """ + """ Wrapper function to call subroutine called util_color which will either return True or False. If True is returned colored_vertices list is filled with correct colorings - + >>> graph = [[0, 1, 0, 0, 0], ... [1, 0, 1, 0, 1], ... [0, 1, 0, 1, 0], diff --git a/backtracking/hamiltonian_cycle.py b/backtracking/hamiltonian_cycle.py index e4f2c62d2341..bc85e36b583f 100644 --- a/backtracking/hamiltonian_cycle.py +++ b/backtracking/hamiltonian_cycle.py @@ -1,9 +1,9 @@ """ - A Hamiltonian cycle (Hamiltonian circuit) is a graph cycle + A Hamiltonian cycle (Hamiltonian circuit) is a graph cycle through a graph that visits each node exactly once. - Determining whether such paths and cycles exist in graphs + Determining whether such paths and cycles exist in graphs is the 'Hamiltonian path problem', which is NP-complete. - + Wikipedia: https://en.wikipedia.org/wiki/Hamiltonian_path """ from typing import List @@ -18,7 +18,7 @@ def valid_connection( 2. Next vertex should not be in path If both validations succeeds we return true saying that it is possible to connect this vertices either we return false - + Case 1:Use exact graph as in main function, with initialized values >>> graph = [[0, 1, 0, 1, 0], ... [1, 0, 1, 1, 1], @@ -56,11 +56,11 @@ def util_hamilton_cycle(graph: List[List[int]], path: List[int], curr_ind: int) Recursive Step: 2. Iterate over each vertex Check if next vertex is valid for transiting from current vertex - 2.1 Remember next vertex as next transition + 2.1 Remember next vertex as next transition 2.2 Do recursive call and check if going to this vertex solves problem 2.3 if next vertex leads to solution return True 2.4 else backtrack, delete remembered vertex - + Case 1: Use exact graph as in main function, with initialized values >>> graph = [[0, 1, 0, 1, 0], ... [1, 0, 1, 1, 1], @@ -111,12 +111,12 @@ def hamilton_cycle(graph: List[List[int]], start_index: int = 0) -> List[int]: Wrapper function to call subroutine called util_hamilton_cycle, which will either return array of vertices indicating hamiltonian cycle or an empty list indicating that hamiltonian cycle was not found. - Case 1: - Following graph consists of 5 edges. + Case 1: + Following graph consists of 5 edges. If we look closely, we can see that there are multiple Hamiltonian cycles. - For example one result is when we iterate like: + For example one result is when we iterate like: (0)->(1)->(2)->(4)->(3)->(0) - + (0)---(1)---(2) | / \ | | / \ | @@ -130,10 +130,10 @@ def hamilton_cycle(graph: List[List[int]], start_index: int = 0) -> List[int]: ... [0, 1, 1, 1, 0]] >>> hamilton_cycle(graph) [0, 1, 2, 4, 3, 0] - - Case 2: + + Case 2: Same Graph as it was in Case 1, changed starting index from default to 3 - + (0)---(1)---(2) | / \ | | / \ | @@ -147,11 +147,11 @@ def hamilton_cycle(graph: List[List[int]], start_index: int = 0) -> List[int]: ... [0, 1, 1, 1, 0]] >>> hamilton_cycle(graph, 3) [3, 0, 1, 2, 4, 3] - + Case 3: Following Graph is exactly what it was before, but edge 3-4 is removed. Result is that there is no Hamiltonian Cycle anymore. - + (0)---(1)---(2) | / \ | | / \ | diff --git a/backtracking/minimax.py b/backtracking/minimax.py index af07b8d8171a..4cec0e403ddf 100644 --- a/backtracking/minimax.py +++ b/backtracking/minimax.py @@ -1,10 +1,10 @@ import math """ Minimax helps to achieve maximum score in a game by checking all possible moves - depth is current depth in game tree. + depth is current depth in game tree. nodeIndex is index of current node in scores[]. if move is of maximizer return true else false - leaves of game tree is stored in scores[] + leaves of game tree is stored in scores[] height is maximum height of Game tree """ diff --git a/backtracking/n_queens.py b/backtracking/n_queens.py index 58d9c4279a35..5d95c0970121 100644 --- a/backtracking/n_queens.py +++ b/backtracking/n_queens.py @@ -1,9 +1,9 @@ """ - The nqueens problem is of placing N queens on a N * N + The nqueens problem is of placing N queens on a N * N chess board such that no queen can attack any other queens placed on that chess board. - This means that one queen cannot have any other queen on its horizontal, vertical and + This means that one queen cannot have any other queen on its horizontal, vertical and diagonal lines. """ @@ -12,7 +12,7 @@ def isSafe(board, row, column): """ - This function returns a boolean value True if it is safe to place a queen there considering + This function returns a boolean value True if it is safe to place a queen there considering the current state of the board. Parameters : @@ -40,13 +40,13 @@ def isSafe(board, row, column): def solve(board, row): """ - It creates a state space tree and calls the safe function until it receives a - False Boolean and terminates that branch and backtracks to the next + It creates a state space tree and calls the safe function until it receives a + False Boolean and terminates that branch and backtracks to the next possible solution branch. """ if row >= len(board): """ - If the row number exceeds N we have board with a successful combination + If the row number exceeds N we have board with a successful combination and that combination is appended to the solution list and the board is printed. """ @@ -56,9 +56,9 @@ def solve(board, row): return for i in range(len(board)): """ - For every row it iterates through each column to check if it is feasible to place a + For every row it iterates through each column to check if it is feasible to place a queen there. - If all the combinations for that particular branch are successful the board is + If all the combinations for that particular branch are successful the board is reinitialized for the next possible combination. """ if isSafe(board, row, i): diff --git a/ciphers/decrypt_caesar_with_chi_squared.py b/ciphers/decrypt_caesar_with_chi_squared.py index 3c37631c7b35..4036f9bdc43a 100644 --- a/ciphers/decrypt_caesar_with_chi_squared.py +++ b/ciphers/decrypt_caesar_with_chi_squared.py @@ -1,9 +1,12 @@ +#!/usr/bin/env python3 + + def decrypt_caesar_with_chi_squared( ciphertext: str, cipher_alphabet=None, frequencies_dict=None, case_sensetive: bool = False, -) -> list: +) -> tuple: """ Basic Usage =========== @@ -96,15 +99,19 @@ def decrypt_caesar_with_chi_squared( Further Reading ================ - * http://practicalcryptography.com/cryptanalysis/text-characterisation/chi-squared-statistic/ + * http://practicalcryptography.com/cryptanalysis/text-characterisation/chi-squared- + statistic/ * https://en.wikipedia.org/wiki/Letter_frequency * https://en.wikipedia.org/wiki/Chi-squared_test * https://en.m.wikipedia.org/wiki/Caesar_cipher Doctests ======== - >>> decrypt_caesar_with_chi_squared('dof pz aol jhlzhy jpwoly zv wvwbshy? pa pz avv lhzf av jyhjr!') - (7, 3129.228005747531, 'why is the caesar cipher so popular? it is too easy to crack!') + >>> decrypt_caesar_with_chi_squared( + ... 'dof pz aol jhlzhy jpwoly zv wvwbshy? pa pz avv lhzf av jyhjr!' + ... ) # doctest: +NORMALIZE_WHITESPACE + (7, 3129.228005747531, + 'why is the caesar cipher so popular? it is too easy to crack!') >>> decrypt_caesar_with_chi_squared('crybd cdbsxq') (10, 233.35343938980898, 'short string') @@ -172,7 +179,7 @@ def decrypt_caesar_with_chi_squared( # Append the character if it isn't in the alphabet decrypted_with_shift += letter - chi_squared_statistic = 0 + chi_squared_statistic = 0.0 # Loop through each letter in the decoded message with the shift for letter in decrypted_with_shift: @@ -181,7 +188,8 @@ def decrypt_caesar_with_chi_squared( # Get the amount of times the letter occurs in the message occurrences = decrypted_with_shift.count(letter) - # Get the excepcted amount of times the letter should appear based on letter frequencies + # Get the excepcted amount of times the letter should appear based + # on letter frequencies expected = frequencies[letter] * occurrences # Complete the chi squared statistic formula @@ -194,7 +202,8 @@ def decrypt_caesar_with_chi_squared( # Get the amount of times the letter occurs in the message occurrences = decrypted_with_shift.count(letter) - # Get the excepcted amount of times the letter should appear based on letter frequencies + # Get the excepcted amount of times the letter should appear based + # on letter frequencies expected = frequencies[letter] * occurrences # Complete the chi squared statistic formula @@ -209,7 +218,8 @@ def decrypt_caesar_with_chi_squared( decrypted_with_shift, ] - # Get the most likely cipher by finding the cipher with the smallest chi squared statistic + # Get the most likely cipher by finding the cipher with the smallest chi squared + # statistic most_likely_cipher = min( chi_squared_statistic_values, key=chi_squared_statistic_values.get ) diff --git a/ciphers/elgamal_key_generator.py b/ciphers/elgamal_key_generator.py index cc6b297f2daf..bade678ad201 100644 --- a/ciphers/elgamal_key_generator.py +++ b/ciphers/elgamal_key_generator.py @@ -1,7 +1,9 @@ import os import random import sys -import rabin_miller as rabinMiller, cryptomath_module as cryptoMath + +import cryptomath_module as cryptoMath +import rabin_miller as rabinMiller min_primitive_root = 3 diff --git a/ciphers/mixed_keyword_cypher.py b/ciphers/mixed_keyword_cypher.py index a546e4c781e6..6c5d6dc1d210 100644 --- a/ciphers/mixed_keyword_cypher.py +++ b/ciphers/mixed_keyword_cypher.py @@ -25,7 +25,7 @@ def mixed_keyword(key="college", pt="UNIVERSITY"): for i in key: if i not in temp: temp.append(i) - l = len(temp) + len_temp = len(temp) # print(temp) alpha = [] modalpha = [] @@ -40,17 +40,17 @@ def mixed_keyword(key="college", pt="UNIVERSITY"): k = 0 for i in range(r): t = [] - for j in range(l): + for j in range(len_temp): t.append(temp[k]) if not (k < 25): break k += 1 modalpha.append(t) # print(modalpha) - d = dict() + d = {} j = 0 k = 0 - for j in range(l): + for j in range(len_temp): for i in modalpha: if not (len(i) - 1 >= j): break diff --git a/ciphers/simple_substitution_cipher.py b/ciphers/simple_substitution_cipher.py index 7da18482db8c..4c6d58ceca46 100644 --- a/ciphers/simple_substitution_cipher.py +++ b/ciphers/simple_substitution_cipher.py @@ -1,4 +1,5 @@ -import sys, random +import random +import sys LETTERS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" diff --git a/ciphers/transposition_cipher_encrypt_decrypt_file.py b/ciphers/transposition_cipher_encrypt_decrypt_file.py index 775df354e117..71e7c4608fdd 100644 --- a/ciphers/transposition_cipher_encrypt_decrypt_file.py +++ b/ciphers/transposition_cipher_encrypt_decrypt_file.py @@ -1,4 +1,7 @@ -import time, os, sys +import os +import sys +import time + import transposition_cipher as transCipher diff --git a/conversions/decimal_to_octal.py b/conversions/decimal_to_octal.py index a89a2be982b8..5341ca3569bb 100644 --- a/conversions/decimal_to_octal.py +++ b/conversions/decimal_to_octal.py @@ -8,7 +8,7 @@ def decimal_to_octal(num: int) -> str: """Convert a Decimal Number to an Octal Number. - + >>> all(decimal_to_octal(i) == oct(i) for i in (0, 2, 8, 64, 65, 216, 255, 256, 512)) True """ diff --git a/data_structures/binary_tree/avl_tree.py b/data_structures/binary_tree/avl_tree.py index 2df747c105ad..cb043cf188b7 100644 --- a/data_structures/binary_tree/avl_tree.py +++ b/data_structures/binary_tree/avl_tree.py @@ -89,8 +89,8 @@ def leftrotation(node): Bl Br UB Br C / UB - - UB = unbalanced node + + UB = unbalanced node """ print("left rotation node:", node.getdata()) ret = node.getleft() @@ -120,11 +120,11 @@ def rightrotation(node): def rlrotation(node): r""" - A A Br + A A Br / \ / \ / \ B C RR Br C LR B A / \ --> / \ --> / / \ - Bl Br B UB Bl UB C + Bl Br B UB Bl UB C \ / UB Bl RR = rightrotation LR = leftrotation @@ -276,13 +276,13 @@ def test(self): if __name__ == "__main__": t = AVLtree() t.traversale() - l = list(range(10)) - random.shuffle(l) - for i in l: + lst = list(range(10)) + random.shuffle(lst) + for i in lst: t.insert(i) t.traversale() - random.shuffle(l) - for i in l: + random.shuffle(lst) + for i in lst: t.del_node(i) t.traversale() diff --git a/data_structures/binary_tree/basic_binary_tree.py b/data_structures/binary_tree/basic_binary_tree.py index 3ed34fc6c68e..9b6e25d5ec56 100644 --- a/data_structures/binary_tree/basic_binary_tree.py +++ b/data_structures/binary_tree/basic_binary_tree.py @@ -1,4 +1,9 @@ -class Node: # This is the Class Node with a constructor that contains data variable to type data and left, right pointers. +class Node: + """ + This is the Class Node with a constructor that contains data variable to type data + and left, right pointers. + """ + def __init__(self, data): self.data = data self.left = None diff --git a/data_structures/binary_tree/lazy_segment_tree.py b/data_structures/binary_tree/lazy_segment_tree.py index acd551b41b96..461996b87c26 100644 --- a/data_structures/binary_tree/lazy_segment_tree.py +++ b/data_structures/binary_tree/lazy_segment_tree.py @@ -16,8 +16,8 @@ def left(self, idx): def right(self, idx): return idx * 2 + 1 - def build(self, idx, l, r, A): - if l == r: + def build(self, idx, l, r, A): # noqa: E741 + if l == r: # noqa: E741 self.st[idx] = A[l - 1] else: mid = (l + r) // 2 @@ -25,14 +25,16 @@ def build(self, idx, l, r, A): self.build(self.right(idx), mid + 1, r, A) self.st[idx] = max(self.st[self.left(idx)], self.st[self.right(idx)]) - # update with O(lg N) (Normal segment tree without lazy update will take O(Nlg N) for each update) - def update( - self, idx, l, r, a, b, val - ): # update(1, 1, N, a, b, v) for update val v to [a,b] - if self.flag[idx] == True: + # update with O(lg N) (Normal segment tree without lazy update will take O(Nlg N) + # for each update) + def update(self, idx, l, r, a, b, val): # noqa: E741 + """ + update(1, 1, N, a, b, v) for update val v to [a,b] + """ + if self.flag[idx] is True: self.st[idx] = self.lazy[idx] self.flag[idx] = False - if l != r: + if l != r: # noqa: E741 self.lazy[self.left(idx)] = self.lazy[idx] self.lazy[self.right(idx)] = self.lazy[idx] self.flag[self.left(idx)] = True @@ -40,9 +42,9 @@ def update( if r < a or l > b: return True - if l >= a and r <= b: + if l >= a and r <= b: # noqa: E741 self.st[idx] = val - if l != r: + if l != r: # noqa: E741 self.lazy[self.left(idx)] = val self.lazy[self.right(idx)] = val self.flag[self.left(idx)] = True @@ -55,18 +57,21 @@ def update( return True # query with O(lg N) - def query(self, idx, l, r, a, b): # query(1, 1, N, a, b) for query max of [a,b] - if self.flag[idx] == True: + def query(self, idx, l, r, a, b): # noqa: E741 + """ + query(1, 1, N, a, b) for query max of [a,b] + """ + if self.flag[idx] is True: self.st[idx] = self.lazy[idx] self.flag[idx] = False - if l != r: + if l != r: # noqa: E741 self.lazy[self.left(idx)] = self.lazy[idx] self.lazy[self.right(idx)] = self.lazy[idx] self.flag[self.left(idx)] = True self.flag[self.right(idx)] = True if r < a or l > b: return -math.inf - if l >= a and r <= b: + if l >= a and r <= b: # noqa: E741 return self.st[idx] mid = (l + r) // 2 q1 = self.query(self.left(idx), l, mid, a, b) diff --git a/data_structures/binary_tree/non_recursive_segment_tree.py b/data_structures/binary_tree/non_recursive_segment_tree.py index 877ee45b5baa..97851af937d9 100644 --- a/data_structures/binary_tree/non_recursive_segment_tree.py +++ b/data_structures/binary_tree/non_recursive_segment_tree.py @@ -1,6 +1,7 @@ """ A non-recursive Segment Tree implementation with range query and single element update, -works virtually with any list of the same type of elements with a "commutative" combiner. +works virtually with any list of the same type of elements with a "commutative" +combiner. Explanation: https://www.geeksforgeeks.org/iterative-segment-tree-range-minimum-query/ @@ -22,7 +23,8 @@ >>> st.update(4, 1) >>> st.query(3, 4) 0 ->>> st = SegmentTree([[1, 2, 3], [3, 2, 1], [1, 1, 1]], lambda a, b: [a[i] + b[i] for i in range(len(a))]) +>>> st = SegmentTree([[1, 2, 3], [3, 2, 1], [1, 1, 1]], lambda a, b: [a[i] + b[i] for i +... in range(len(a))]) >>> st.query(0, 1) [4, 4, 4] >>> st.query(1, 2) @@ -47,7 +49,8 @@ def __init__(self, arr: List[T], fnc: Callable[[T, T], T]) -> None: >>> SegmentTree(['a', 'b', 'c'], lambda a, b: '{}{}'.format(a, b)).query(0, 2) 'abc' - >>> SegmentTree([(1, 2), (2, 3), (3, 4)], lambda a, b: (a[0] + b[0], a[1] + b[1])).query(0, 2) + >>> SegmentTree([(1, 2), (2, 3), (3, 4)], + ... lambda a, b: (a[0] + b[0], a[1] + b[1])).query(0, 2) (6, 9) """ self.N = len(arr) @@ -78,7 +81,7 @@ def update(self, p: int, v: T) -> None: p = p // 2 self.st[p] = self.fn(self.st[p * 2], self.st[p * 2 + 1]) - def query(self, l: int, r: int) -> T: + def query(self, l: int, r: int) -> T: # noqa: E741 """ Get range query value in log(N) time :param l: left element index @@ -95,9 +98,9 @@ def query(self, l: int, r: int) -> T: >>> st.query(2, 3) 7 """ - l, r = l + self.N, r + self.N + l, r = l + self.N, r + self.N # noqa: E741 res = None - while l <= r: + while l <= r: # noqa: E741 if l % 2 == 1: res = self.st[l] if res is None else self.fn(res, self.st[l]) if r % 2 == 0: diff --git a/data_structures/binary_tree/segment_tree.py b/data_structures/binary_tree/segment_tree.py index ad9476b4514b..10451ae68bb2 100644 --- a/data_structures/binary_tree/segment_tree.py +++ b/data_structures/binary_tree/segment_tree.py @@ -15,8 +15,8 @@ def left(self, idx): def right(self, idx): return idx * 2 + 1 - def build(self, idx, l, r): - if l == r: + def build(self, idx, l, r): # noqa: E741 + if l == r: # noqa: E741 self.st[idx] = A[l] else: mid = (l + r) // 2 @@ -27,12 +27,13 @@ def build(self, idx, l, r): def update(self, a, b, val): return self.update_recursive(1, 0, self.N - 1, a - 1, b - 1, val) - def update_recursive( - self, idx, l, r, a, b, val - ): # update(1, 1, N, a, b, v) for update val v to [a,b] + def update_recursive(self, idx, l, r, a, b, val): # noqa: E741 + """ + update(1, 1, N, a, b, v) for update val v to [a,b] + """ if r < a or l > b: return True - if l == r: + if l == r: # noqa: E741 self.st[idx] = val return True mid = (l + r) // 2 @@ -44,12 +45,13 @@ def update_recursive( def query(self, a, b): return self.query_recursive(1, 0, self.N - 1, a - 1, b - 1) - def query_recursive( - self, idx, l, r, a, b - ): # query(1, 1, N, a, b) for query max of [a,b] + def query_recursive(self, idx, l, r, a, b): # noqa: E741 + """ + query(1, 1, N, a, b) for query max of [a,b] + """ if r < a or l > b: return -math.inf - if l >= a and r <= b: + if l >= a and r <= b: # noqa: E741 return self.st[idx] mid = (l + r) // 2 q1 = self.query_recursive(self.left(idx), l, mid, a, b) diff --git a/data_structures/binary_tree/treap.py b/data_structures/binary_tree/treap.py index 26f021445ca4..52b757d584c3 100644 --- a/data_structures/binary_tree/treap.py +++ b/data_structures/binary_tree/treap.py @@ -1,3 +1,5 @@ +# flake8: noqa + from random import random from typing import Tuple @@ -161,7 +163,8 @@ def main(): """After each command, program prints treap""" root = None print( - "enter numbers to create a tree, + value to add value into treap, - value to erase all nodes with value. 'q' to quit. " + "enter numbers to create a tree, + value to add value into treap, " + "- value to erase all nodes with value. 'q' to quit. " ) args = input() diff --git a/data_structures/hashing/quadratic_probing.py b/data_structures/hashing/quadratic_probing.py index 0dd84a5d987c..668ddaa85048 100644 --- a/data_structures/hashing/quadratic_probing.py +++ b/data_structures/hashing/quadratic_probing.py @@ -5,7 +5,7 @@ class QuadraticProbing(HashTable): """ - Basic Hash Table example with open addressing using Quadratic Probing + Basic Hash Table example with open addressing using Quadratic Probing """ def __init__(self, *args, **kwargs): diff --git a/data_structures/heap/binomial_heap.py b/data_structures/heap/binomial_heap.py index ac244023082a..334b444eaaff 100644 --- a/data_structures/heap/binomial_heap.py +++ b/data_structures/heap/binomial_heap.py @@ -1,3 +1,5 @@ +# flake8: noqa + """ Binomial Heap Reference: Advanced Data Structures, Peter Brass diff --git a/data_structures/heap/min_heap.py b/data_structures/heap/min_heap.py index e68853837faa..5b96319197ec 100644 --- a/data_structures/heap/min_heap.py +++ b/data_structures/heap/min_heap.py @@ -66,7 +66,7 @@ def build_heap(self, array): # this is min-heapify method def sift_down(self, idx, array): while True: - l = self.get_left_child_idx(idx) + l = self.get_left_child_idx(idx) # noqa: E741 r = self.get_right_child_idx(idx) smallest = idx @@ -132,7 +132,7 @@ def decrease_key(self, node, newValue): self.sift_up(self.idx_of_element[node]) -## USAGE +# USAGE r = Node("R", -1) b = Node("B", 6) diff --git a/data_structures/linked_list/deque_doubly.py b/data_structures/linked_list/deque_doubly.py index b2e73a8f789b..7025a7ea22f9 100644 --- a/data_structures/linked_list/deque_doubly.py +++ b/data_structures/linked_list/deque_doubly.py @@ -1,5 +1,5 @@ """ -Implementing Deque using DoublyLinkedList ... +Implementing Deque using DoublyLinkedList ... Operations: 1. insertion in the front -> O(1) 2. insertion in the end -> O(1) @@ -61,7 +61,7 @@ def _delete(self, node): class LinkedDeque(_DoublyLinkedBase): def first(self): - """ return first element + """ return first element >>> d = LinkedDeque() >>> d.add_first('A').first() 'A' @@ -84,7 +84,7 @@ def last(self): raise Exception("List is empty") return self._trailer._prev._data - ### DEque Insert Operations (At the front, At the end) ### + # DEque Insert Operations (At the front, At the end) def add_first(self, element): """ insertion in the front @@ -100,7 +100,7 @@ def add_last(self, element): """ return self._insert(self._trailer._prev, element, self._trailer) - ### DEqueu Remove Operations (At the front, At the end) ### + # DEqueu Remove Operations (At the front, At the end) def remove_first(self): """ removal from the front diff --git a/data_structures/linked_list/middle_element_of_linked_list.py b/data_structures/linked_list/middle_element_of_linked_list.py index b845d2f19c20..185c4ccbbb0a 100644 --- a/data_structures/linked_list/middle_element_of_linked_list.py +++ b/data_structures/linked_list/middle_element_of_linked_list.py @@ -43,7 +43,7 @@ def middle_element(self) -> int: -20 >>> link.middle_element() 12 - >>> + >>> """ slow_pointer = self.head fast_pointer = self.head diff --git a/data_structures/stacks/postfix_evaluation.py b/data_structures/stacks/postfix_evaluation.py index 4cee0ba380b0..574acac71c43 100644 --- a/data_structures/stacks/postfix_evaluation.py +++ b/data_structures/stacks/postfix_evaluation.py @@ -22,7 +22,7 @@ def Solve(Postfix): Stack = [] - Div = lambda x, y: int(x / y) # integer division operation + Div = lambda x, y: int(x / y) # noqa: E731 integer division operation Opr = { "^": op.pow, "*": op.mul, @@ -38,29 +38,27 @@ def Solve(Postfix): for x in Postfix: if x.isdigit(): # if x in digit Stack.append(x) # append x to stack - print( - x.rjust(8), ("push(" + x + ")").ljust(12), ",".join(Stack), sep=" | " - ) # output in tabular format + # output in tabular format + print(x.rjust(8), ("push(" + x + ")").ljust(12), ",".join(Stack), sep=" | ") else: B = Stack.pop() # pop stack - print( - "".rjust(8), ("pop(" + B + ")").ljust(12), ",".join(Stack), sep=" | " - ) # output in tabular format + # output in tabular format + print("".rjust(8), ("pop(" + B + ")").ljust(12), ",".join(Stack), sep=" | ") A = Stack.pop() # pop stack - print( - "".rjust(8), ("pop(" + A + ")").ljust(12), ",".join(Stack), sep=" | " - ) # output in tabular format + # output in tabular format + print("".rjust(8), ("pop(" + A + ")").ljust(12), ",".join(Stack), sep=" | ") Stack.append( str(Opr[x](int(A), int(B))) ) # evaluate the 2 values popped from stack & push result to stack + # output in tabular format print( x.rjust(8), ("push(" + A + x + B + ")").ljust(12), ",".join(Stack), sep=" | ", - ) # output in tabular format + ) return int(Stack[0]) diff --git a/data_structures/trie/trie.py b/data_structures/trie/trie.py index 5a560b97c293..6947d97fc642 100644 --- a/data_structures/trie/trie.py +++ b/data_structures/trie/trie.py @@ -1,7 +1,7 @@ """ A Trie/Prefix Tree is a kind of search tree used to provide quick lookup of words/patterns in a set of words. A basic Trie however has O(n^2) space complexity -making it impractical in practice. It however provides O(max(search_string, length of longest word)) +making it impractical in practice. It however provides O(max(search_string, length of longest word)) lookup time making it an optimal approach when space is not an issue. """ diff --git a/digital_image_processing/dithering/burkes.py b/digital_image_processing/dithering/burkes.py index 54a243bd255a..0de21f4c009b 100644 --- a/digital_image_processing/dithering/burkes.py +++ b/digital_image_processing/dithering/burkes.py @@ -54,9 +54,9 @@ def process(self) -> None: current_error = greyscale + self.error_table[x][y] - 255 """ Burkes error propagation (`*` is current pixel): - - * 8/32 4/32 - 2/32 4/32 8/32 4/32 2/32 + + * 8/32 4/32 + 2/32 4/32 8/32 4/32 2/32 """ self.error_table[y][x + 1] += int(8 / 32 * current_error) self.error_table[y][x + 2] += int(4 / 32 * current_error) diff --git a/digital_image_processing/edge_detection/canny.py b/digital_image_processing/edge_detection/canny.py index 6f98fee6308e..6ee3ac5a22ea 100644 --- a/digital_image_processing/edge_detection/canny.py +++ b/digital_image_processing/edge_detection/canny.py @@ -29,8 +29,8 @@ def canny(image, threshold_low=15, threshold_high=30, weak=128, strong=255): dst = np.zeros((image_row, image_col)) """ - Non-maximum suppression. If the edge strength of the current pixel is the largest compared to the other pixels - in the mask with the same direction, the value will be preserved. Otherwise, the value will be suppressed. + Non-maximum suppression. If the edge strength of the current pixel is the largest compared to the other pixels + in the mask with the same direction, the value will be preserved. Otherwise, the value will be suppressed. """ for row in range(1, image_row - 1): for col in range(1, image_col - 1): diff --git a/digital_image_processing/index_calculation.py b/digital_image_processing/index_calculation.py index a34e51e56310..d55815a6e15e 100644 --- a/digital_image_processing/index_calculation.py +++ b/digital_image_processing/index_calculation.py @@ -6,26 +6,28 @@ # Imports import numpy as np + # Class implemented to calculus the index class IndexCalculation: """ # Class Summary - This algorithm consists in calculating vegetation indices, these indices - can be used for precision agriculture for example (or remote sensing). There are - functions to define the data and to calculate the implemented indices. + This algorithm consists in calculating vegetation indices, these + indices can be used for precision agriculture for example (or remote + sensing). There are functions to define the data and to calculate the + implemented indices. # Vegetation index https://en.wikipedia.org/wiki/Vegetation_Index - A Vegetation Index (VI) is a spectral transformation of two or more bands designed - to enhance the contribution of vegetation properties and allow reliable spatial and - temporal inter-comparisons of terrestrial photosynthetic activity and canopy - structural variations - + A Vegetation Index (VI) is a spectral transformation of two or more bands + designed to enhance the contribution of vegetation properties and allow + reliable spatial and temporal inter-comparisons of terrestrial + photosynthetic activity and canopy structural variations + # Information about channels (Wavelength range for each) * nir - near-infrared https://www.malvernpanalytical.com/br/products/technology/near-infrared-spectroscopy Wavelength Range 700 nm to 2500 nm - * Red Edge + * Red Edge https://en.wikipedia.org/wiki/Red_edge Wavelength Range 680 nm to 730 nm * red @@ -38,7 +40,7 @@ class IndexCalculation: https://en.wikipedia.org/wiki/Color Wavelength Range 520 nm to 560 nm - + # Implemented index list #"abbreviationOfIndexName" -- list of channels used @@ -84,17 +86,19 @@ class IndexCalculation: #"NDRE" -- redEdge, nir #list of all index implemented - #allIndex = ["ARVI2", "CCCI", "CVI", "GLI", "NDVI", "BNDVI", "redEdgeNDVI", "GNDVI", - "GBNDVI", "GRNDVI", "RBNDVI", "PNDVI", "ATSAVI", "BWDRVI", "CIgreen", - "CIrededge", "CI", "CTVI", "GDVI", "EVI", "GEMI", "GOSAVI", "GSAVI", - "Hue", "IVI", "IPVI", "I", "RVI", "MRVI", "MSAVI", "NormG", "NormNIR", - "NormR", "NGRDI", "RI", "S", "IF", "DVI", "TVI", "NDRE"] + #allIndex = ["ARVI2", "CCCI", "CVI", "GLI", "NDVI", "BNDVI", "redEdgeNDVI", + "GNDVI", "GBNDVI", "GRNDVI", "RBNDVI", "PNDVI", "ATSAVI", + "BWDRVI", "CIgreen", "CIrededge", "CI", "CTVI", "GDVI", "EVI", + "GEMI", "GOSAVI", "GSAVI", "Hue", "IVI", "IPVI", "I", "RVI", + "MRVI", "MSAVI", "NormG", "NormNIR", "NormR", "NGRDI", "RI", + "S", "IF", "DVI", "TVI", "NDRE"] #list of index with not blue channel - #notBlueIndex = ["ARVI2", "CCCI", "CVI", "NDVI", "redEdgeNDVI", "GNDVI", "GRNDVI", - "ATSAVI", "CIgreen", "CIrededge", "CTVI", "GDVI", "GEMI", "GOSAVI", - "GSAVI", "IVI", "IPVI", "RVI", "MRVI", "MSAVI", "NormG", "NormNIR", - "NormR", "NGRDI", "RI", "DVI", "TVI", "NDRE"] + #notBlueIndex = ["ARVI2", "CCCI", "CVI", "NDVI", "redEdgeNDVI", "GNDVI", + "GRNDVI", "ATSAVI", "CIgreen", "CIrededge", "CTVI", "GDVI", + "GEMI", "GOSAVI", "GSAVI", "IVI", "IPVI", "RVI", "MRVI", + "MSAVI", "NormG", "NormNIR", "NormR", "NGRDI", "RI", "DVI", + "TVI", "NDRE"] #list of index just with RGB channels #RGBIndex = ["GLI", "CI", "Hue", "I", "NGRDI", "RI", "S", "IF"] @@ -121,8 +125,8 @@ def calculation( self, index="", red=None, green=None, blue=None, redEdge=None, nir=None ): """ - performs the calculation of the index with the values instantiated in the class - :str index: abbreviation of index name to perform + performs the calculation of the index with the values instantiated in the class + :str index: abbreviation of index name to perform """ self.setMatrices(red=red, green=green, blue=blue, redEdge=redEdge, nir=nir) funcs = { @@ -213,8 +217,8 @@ def GLI(self): def NDVI(self): """ - Normalized Difference self.nir/self.red Normalized Difference Vegetation Index, - Calibrated NDVI - CDVI + Normalized Difference self.nir/self.red Normalized Difference Vegetation + Index, Calibrated NDVI - CDVI https://www.indexdatabase.de/db/i-single.php?id=58 :return: index """ @@ -222,7 +226,7 @@ def NDVI(self): def BNDVI(self): """ - Normalized Difference self.nir/self.blue self.blue-normalized difference + Normalized Difference self.nir/self.blue self.blue-normalized difference vegetation index https://www.indexdatabase.de/db/i-single.php?id=135 :return: index @@ -410,7 +414,7 @@ def IPVI(self): """ return (self.nir / ((self.nir + self.red) / 2)) * (self.NDVI() + 1) - def I(self): + def I(self): # noqa: E741,E743 """ Intensity https://www.indexdatabase.de/db/i-single.php?id=36 @@ -471,8 +475,9 @@ def NormR(self): def NGRDI(self): """ - Normalized Difference self.green/self.red Normalized self.green self.red - difference index, Visible Atmospherically Resistant Indices self.green (VIself.green) + Normalized Difference self.green/self.red Normalized self.green self.red + difference index, Visible Atmospherically Resistant Indices self.green + (VIself.green) https://www.indexdatabase.de/db/i-single.php?id=390 :return: index """ @@ -506,7 +511,7 @@ def IF(self): def DVI(self): """ - Simple Ratio self.nir/self.red Difference Vegetation Index, Vegetation Index + Simple Ratio self.nir/self.red Difference Vegetation Index, Vegetation Index Number (VIN) https://www.indexdatabase.de/db/i-single.php?id=12 :return: index @@ -535,7 +540,7 @@ def NDRE(self): # Examples of how to use the class -# instantiating the class +# instantiating the class cl = IndexCalculation() # instantiating the class with the values @@ -556,9 +561,12 @@ def NDRE(self): indexValue_form3 = cl.calculation("CCCI", red=red, green=green, blue=blue, redEdge=redEdge, nir=nir).astype(np.float64) -print("Form 1: "+np.array2string(indexValue_form1, precision=20, separator=', ', floatmode='maxprec_equal')) -print("Form 2: "+np.array2string(indexValue_form2, precision=20, separator=', ', floatmode='maxprec_equal')) -print("Form 3: "+np.array2string(indexValue_form3, precision=20, separator=', ', floatmode='maxprec_equal')) +print("Form 1: "+np.array2string(indexValue_form1, precision=20, separator=', ', + floatmode='maxprec_equal')) +print("Form 2: "+np.array2string(indexValue_form2, precision=20, separator=', ', + floatmode='maxprec_equal')) +print("Form 3: "+np.array2string(indexValue_form3, precision=20, separator=', ', + floatmode='maxprec_equal')) # A list of examples results for different type of data at NDVI # float16 -> 0.31567383 #NDVI (red = 50, nir = 100) diff --git a/divide_and_conquer/max_subarray_sum.py b/divide_and_conquer/max_subarray_sum.py index 9e81c83649a6..03bf9d25cb8a 100644 --- a/divide_and_conquer/max_subarray_sum.py +++ b/divide_and_conquer/max_subarray_sum.py @@ -1,10 +1,10 @@ -""" -Given a array of length n, max_subarray_sum() finds +""" +Given a array of length n, max_subarray_sum() finds the maximum of sum of contiguous sub-array using divide and conquer method. Time complexity : O(n log n) -Ref : INTRODUCTION TO ALGORITHMS THIRD EDITION +Ref : INTRODUCTION TO ALGORITHMS THIRD EDITION (section : 4, sub-section : 4.1, page : 70) """ @@ -13,10 +13,10 @@ def max_sum_from_start(array): """ This function finds the maximum contiguous sum of array from 0 index - Parameters : + Parameters : array (list[int]) : given array - - Returns : + + Returns : max_sum (int) : maximum contiguous sum of array from 0 index """ @@ -32,10 +32,10 @@ def max_sum_from_start(array): def max_cross_array_sum(array, left, mid, right): """ This function finds the maximum contiguous sum of left and right arrays - Parameters : - array, left, mid, right (list[int], int, int, int) - - Returns : + Parameters : + array, left, mid, right (list[int], int, int, int) + + Returns : (int) : maximum of sum of contiguous sum of left and right arrays """ @@ -48,11 +48,11 @@ def max_cross_array_sum(array, left, mid, right): def max_subarray_sum(array, left, right): """ Maximum contiguous sub-array sum, using divide and conquer method - Parameters : - array, left, right (list[int], int, int) : + Parameters : + array, left, right (list[int], int, int) : given array, current left index and current right index - - Returns : + + Returns : int : maximum of sum of contiguous sub-array """ diff --git a/divide_and_conquer/mergesort.py b/divide_and_conquer/mergesort.py index d6693eb36a0a..328e3dca316f 100644 --- a/divide_and_conquer/mergesort.py +++ b/divide_and_conquer/mergesort.py @@ -1,5 +1,5 @@ def merge(a, b, m, e): - l = a[b : m + 1] + l = a[b : m + 1] # noqa: E741 r = a[m + 1 : e + 1] k = b i = 0 diff --git a/dynamic_programming/factorial.py b/dynamic_programming/factorial.py index 0269014e7a18..546478441f31 100644 --- a/dynamic_programming/factorial.py +++ b/dynamic_programming/factorial.py @@ -26,9 +26,9 @@ def factorial(num): # factorial of num # uncomment the following to see how recalculations are avoided -##result=[-1]*10 -##result[0]=result[1]=1 -##print(factorial(5)) +# result=[-1]*10 +# result[0]=result[1]=1 +# print(factorial(5)) # print(factorial(3)) # print(factorial(7)) diff --git a/dynamic_programming/iterating_through_submasks.py b/dynamic_programming/iterating_through_submasks.py index edeacc3124fa..28c4ded66495 100644 --- a/dynamic_programming/iterating_through_submasks.py +++ b/dynamic_programming/iterating_through_submasks.py @@ -1,5 +1,5 @@ """ -Author : Syed Faizan (3rd Year Student IIIT Pune) +Author : Syed Faizan (3rd Year Student IIIT Pune) github : faizan2700 You are given a bitmask m and you want to efficiently iterate through all of its submasks. The mask s is submask of m if only bits that were included in @@ -33,7 +33,7 @@ def list_of_submasks(mask: int) -> List[int]: Traceback (most recent call last): ... AssertionError: mask needs to be positive integer, your input 0 - + """ fmt = "mask needs to be positive integer, your input {}" diff --git a/dynamic_programming/longest_common_subsequence.py b/dynamic_programming/longest_common_subsequence.py index a7206b221d96..b319421b9aa2 100644 --- a/dynamic_programming/longest_common_subsequence.py +++ b/dynamic_programming/longest_common_subsequence.py @@ -76,7 +76,7 @@ def longest_common_subsequence(x: str, y: str): expected_subseq = "GTAB" ln, subseq = longest_common_subsequence(a, b) - ## print("len =", ln, ", sub-sequence =", subseq) + print("len =", ln, ", sub-sequence =", subseq) import doctest doctest.testmod() diff --git a/dynamic_programming/longest_increasing_subsequence.py b/dynamic_programming/longest_increasing_subsequence.py index 81b7f8f8ff17..48d5e8e8fade 100644 --- a/dynamic_programming/longest_increasing_subsequence.py +++ b/dynamic_programming/longest_increasing_subsequence.py @@ -1,11 +1,14 @@ """ Author : Mehdi ALAOUI -This is a pure Python implementation of Dynamic Programming solution to the longest increasing subsequence of a given sequence. +This is a pure Python implementation of Dynamic Programming solution to the longest +increasing subsequence of a given sequence. The problem is : -Given an array, to find the longest and increasing sub-array in that given array and return it. -Example: [10, 22, 9, 33, 21, 50, 41, 60, 80] as input will return [10, 22, 33, 41, 60, 80] as output +Given an array, to find the longest and increasing sub-array in that given array and +return it. +Example: [10, 22, 9, 33, 21, 50, 41, 60, 80] as input will return + [10, 22, 33, 41, 60, 80] as output """ from typing import List @@ -21,11 +24,13 @@ def longest_subsequence(array: List[int]) -> List[int]: # This function is recu [8] >>> longest_subsequence([1, 1, 1]) [1, 1, 1] + >>> longest_subsequence([]) + [] """ array_length = len(array) - if ( - array_length <= 1 - ): # If the array contains only one element, we return it (it's the stop condition of recursion) + # If the array contains only one element, we return it (it's the stop condition of + # recursion) + if array_length <= 1: return array # Else pivot = array[0] diff --git a/dynamic_programming/longest_increasing_subsequence_o(nlogn).py b/dynamic_programming/longest_increasing_subsequence_o(nlogn).py index 46790a5a8d41..b33774057db3 100644 --- a/dynamic_programming/longest_increasing_subsequence_o(nlogn).py +++ b/dynamic_programming/longest_increasing_subsequence_o(nlogn).py @@ -1,19 +1,19 @@ ############################# # Author: Aravind Kashyap # File: lis.py -# comments: This programme outputs the Longest Strictly Increasing Subsequence in O(NLogN) -# Where N is the Number of elements in the list +# comments: This programme outputs the Longest Strictly Increasing Subsequence in +# O(NLogN) Where N is the Number of elements in the list ############################# from typing import List -def CeilIndex(v, l, r, key): +def CeilIndex(v, l, r, key): # noqa: E741 while r - l > 1: m = (l + r) // 2 if v[m] >= key: r = m else: - l = m + l = m # noqa: E741 return r @@ -23,7 +23,8 @@ def LongestIncreasingSubsequenceLength(v: List[int]) -> int: 6 >>> LongestIncreasingSubsequenceLength([]) 0 - >>> LongestIncreasingSubsequenceLength([0, 8, 4, 12, 2, 10, 6, 14, 1, 9, 5, 13, 3, 11, 7, 15]) + >>> LongestIncreasingSubsequenceLength([0, 8, 4, 12, 2, 10, 6, 14, 1, 9, 5, 13, 3, + ... 11, 7, 15]) 6 >>> LongestIncreasingSubsequenceLength([5, 4, 3, 2, 1]) 1 diff --git a/dynamic_programming/max_sub_array.py b/dynamic_programming/max_sub_array.py index 7350eaf373cb..284edb5841e4 100644 --- a/dynamic_programming/max_sub_array.py +++ b/dynamic_programming/max_sub_array.py @@ -44,12 +44,12 @@ def max_sub_array(nums: List[int]) -> int: >>> max_sub_array([-2, 1, -3, 4, -1, 2, 1, -5, 4]) 6 - + An empty (sub)array has sum 0. >>> max_sub_array([]) 0 - - If all elements are negative, the largest subarray would be the empty array, + + If all elements are negative, the largest subarray would be the empty array, having the sum 0. >>> max_sub_array([-1, -2, -3]) 0 diff --git a/dynamic_programming/minimum_partition.py b/dynamic_programming/minimum_partition.py index d5750326fea4..8fad4ef3072f 100644 --- a/dynamic_programming/minimum_partition.py +++ b/dynamic_programming/minimum_partition.py @@ -23,7 +23,7 @@ def findMin(arr): dp[i][j] = dp[i][j] or dp[i - 1][j - arr[i - 1]] for j in range(int(s / 2), -1, -1): - if dp[n][j] == True: + if dp[n][j] is True: diff = s - 2 * j break diff --git a/dynamic_programming/optimal_binary_search_tree.py b/dynamic_programming/optimal_binary_search_tree.py index f33ca01bd933..e6f93f85ef0f 100644 --- a/dynamic_programming/optimal_binary_search_tree.py +++ b/dynamic_programming/optimal_binary_search_tree.py @@ -40,7 +40,7 @@ def __str__(self): def print_binary_search_tree(root, key, i, j, parent, is_left): """ Recursive function to print a BST from a root table. - + >>> key = [3, 8, 9, 10, 17, 21] >>> root = [[0, 1, 1, 1, 1, 1], [0, 1, 1, 1, 1, 3], [0, 0, 2, 3, 3, 3], \ [0, 0, 0, 3, 3, 3], [0, 0, 0, 0, 4, 5], [0, 0, 0, 0, 0, 5]] @@ -73,7 +73,7 @@ def find_optimal_binary_search_tree(nodes): The dynamic programming algorithm below runs in O(n^2) time. Implemented from CLRS (Introduction to Algorithms) book. https://en.wikipedia.org/wiki/Introduction_to_Algorithms - + >>> find_optimal_binary_search_tree([Node(12, 8), Node(10, 34), Node(20, 50), \ Node(42, 3), Node(25, 40), Node(37, 30)]) Binary search tree nodes: @@ -104,14 +104,15 @@ def find_optimal_binary_search_tree(nodes): # This 2D array stores the overall tree cost (which's as minimized as possible); # for a single key, cost is equal to frequency of the key. dp = [[freqs[i] if i == j else 0 for j in range(n)] for i in range(n)] - # sum[i][j] stores the sum of key frequencies between i and j inclusive in nodes array + # sum[i][j] stores the sum of key frequencies between i and j inclusive in nodes + # array sum = [[freqs[i] if i == j else 0 for j in range(n)] for i in range(n)] # stores tree roots that will be used later for constructing binary search tree root = [[i if i == j else 0 for j in range(n)] for i in range(n)] - for l in range(2, n + 1): # l is an interval length - for i in range(n - l + 1): - j = i + l - 1 + for interval_length in range(2, n + 1): + for i in range(n - interval_length + 1): + j = i + interval_length - 1 dp[i][j] = sys.maxsize # set the value to "infinity" sum[i][j] = sum[i][j - 1] + freqs[j] diff --git a/dynamic_programming/subset_generation.py b/dynamic_programming/subset_generation.py index 196b81c22045..7d99727dd900 100644 --- a/dynamic_programming/subset_generation.py +++ b/dynamic_programming/subset_generation.py @@ -1,12 +1,15 @@ # Python program to print all subset combinations of n element in given set of r element. -# arr[] ---> Input Array -# data[] ---> Temporary array to store current combination -# start & end ---> Staring and Ending indexes in arr[] -# index ---> Current index in data[] -# r ---> Size of a combination to be printed + + def combination_util(arr, n, r, index, data, i): - # Current combination is ready to be printed, - # print it + """ + Current combination is ready to be printed, print it + arr[] ---> Input Array + data[] ---> Temporary array to store current combination + start & end ---> Staring and Ending indexes in arr[] + index ---> Current index in data[] + r ---> Size of a combination to be printed + """ if index == r: for j in range(r): print(data[j], end=" ") diff --git a/geodesy/lamberts_ellipsoidal_distance.py b/geodesy/lamberts_ellipsoidal_distance.py index 224b9404a5b7..613c779a1b3e 100644 --- a/geodesy/lamberts_ellipsoidal_distance.py +++ b/geodesy/lamberts_ellipsoidal_distance.py @@ -15,8 +15,8 @@ def lamberts_ellipsoidal_distance( Representing the earth as an ellipsoid allows us to approximate distances between points on the surface much better than a sphere. Ellipsoidal formulas treat the Earth as an - oblate ellipsoid which means accounting for the flattening that happens at the North - and South poles. Lambert's formulae provide accuracy on the order of 10 meteres over + oblate ellipsoid which means accounting for the flattening that happens at the North + and South poles. Lambert's formulae provide accuracy on the order of 10 meteres over thousands of kilometeres. Other methods can provide millimeter-level accuracy but this is a simpler method to calculate long range distances without increasing computational intensity. diff --git a/graphs/articulation_points.py b/graphs/articulation_points.py index 3ecc829946e8..7197369de090 100644 --- a/graphs/articulation_points.py +++ b/graphs/articulation_points.py @@ -1,5 +1,5 @@ # Finding Articulation Points in Undirected Graph -def computeAP(l): +def computeAP(l): # noqa: E741 n = len(l) outEdgeCount = 0 low = [0] * n @@ -36,12 +36,12 @@ def dfs(root, at, parent, outEdgeCount): isArt[i] = outEdgeCount > 1 for x in range(len(isArt)): - if isArt[x] == True: + if isArt[x] is True: print(x) # Adjacency list of graph -l = { +data = { 0: [1, 2], 1: [0, 2], 2: [0, 1, 3, 5], @@ -52,4 +52,4 @@ def dfs(root, at, parent, outEdgeCount): 7: [6, 8], 8: [5, 7], } -computeAP(l) +computeAP(data) diff --git a/graphs/basic_graphs.py b/graphs/basic_graphs.py index 8cdde6abc819..1cbd82a2bd08 100644 --- a/graphs/basic_graphs.py +++ b/graphs/basic_graphs.py @@ -1,3 +1,6 @@ +from collections import deque + + if __name__ == "__main__": # Accept No. of Nodes and edges n, m = map(int, input().split(" ")) @@ -72,7 +75,6 @@ def dfs(G, s): Q - Traversal Stack -------------------------------------------------------------------------------- """ -from collections import deque def bfs(G, s): @@ -125,7 +127,6 @@ def dijk(G, s): Topological Sort -------------------------------------------------------------------------------- """ -from collections import deque def topo(G, ind=None, Q=None): @@ -235,10 +236,10 @@ def prim(G, s): def edglist(): n, m = map(int, input().split(" ")) - l = [] + edges = [] for i in range(m): - l.append(map(int, input().split(" "))) - return l, n + edges.append(map(int, input().split(" "))) + return edges, n """ diff --git a/graphs/bellman_ford.py b/graphs/bellman_ford.py index 807e0b0fcdb9..d4d37a365e03 100644 --- a/graphs/bellman_ford.py +++ b/graphs/bellman_ford.py @@ -9,7 +9,7 @@ def printDist(dist, V): def BellmanFord(graph: List[Dict[str, int]], V: int, E: int, src: int) -> int: """ - Returns shortest paths from a vertex src to all + Returns shortest paths from a vertex src to all other vertices. """ mdist = [float("inf") for i in range(V)] diff --git a/graphs/breadth_first_search_shortest_path.py b/graphs/breadth_first_search_shortest_path.py index 514aed6d7211..e556d7966fa3 100644 --- a/graphs/breadth_first_search_shortest_path.py +++ b/graphs/breadth_first_search_shortest_path.py @@ -1,6 +1,8 @@ -"""Breath First Search (BFS) can be used when finding the shortest path +"""Breath First Search (BFS) can be used when finding the shortest path from a given source node to a target node in an unweighted graph. """ +from typing import Dict + graph = { "A": ["B", "C", "E"], "B": ["A", "D", "E"], @@ -11,8 +13,6 @@ "G": ["C"], } -from typing import Dict - class Graph: def __init__(self, graph: Dict[str, str], source_vertex: str) -> None: @@ -46,8 +46,9 @@ def breath_first_search(self) -> None: def shortest_path(self, target_vertex: str) -> str: """This shortest path function returns a string, describing the result: 1.) No path is found. The string is a human readable message to indicate this. - 2.) The shortest path is found. The string is in the form `v1(->v2->v3->...->vn)`, - where v1 is the source vertex and vn is the target vertex, if it exists separately. + 2.) The shortest path is found. The string is in the form + `v1(->v2->v3->...->vn)`, where v1 is the source vertex and vn is the target + vertex, if it exists separately. >>> g = Graph(graph, "G") >>> g.breath_first_search() diff --git a/graphs/check_bipartite_graph_bfs.py b/graphs/check_bipartite_graph_bfs.py index 1ec3e3d1d45f..00b771649b5d 100644 --- a/graphs/check_bipartite_graph_bfs.py +++ b/graphs/check_bipartite_graph_bfs.py @@ -1,21 +1,22 @@ # Check whether Graph is Bipartite or Not using BFS + # A Bipartite Graph is a graph whose vertices can be divided into two independent sets, # U and V such that every edge (u, v) either connects a vertex from U to V or a vertex # from V to U. In other words, for every edge (u, v), either u belongs to U and v to V, # or u belongs to V and v to U. We can also say that there is no edge that connects # vertices of same set. -def checkBipartite(l): +def checkBipartite(graph): queue = [] - visited = [False] * len(l) - color = [-1] * len(l) + visited = [False] * len(graph) + color = [-1] * len(graph) def bfs(): while queue: u = queue.pop(0) visited[u] = True - for neighbour in l[u]: + for neighbour in graph[u]: if neighbour == u: return False @@ -29,16 +30,16 @@ def bfs(): return True - for i in range(len(l)): + for i in range(len(graph)): if not visited[i]: queue.append(i) color[i] = 0 - if bfs() == False: + if bfs() is False: return False return True -# Adjacency List of graph -l = {0: [1, 3], 1: [0, 2], 2: [1, 3], 3: [0, 2]} -print(checkBipartite(l)) +if __name__ == "__main__": + # Adjacency List of graph + print(checkBipartite({0: [1, 3], 1: [0, 2], 2: [1, 3], 3: [0, 2]})) diff --git a/graphs/check_bipartite_graph_dfs.py b/graphs/check_bipartite_graph_dfs.py index 6fe54a6723c5..fd644230449c 100644 --- a/graphs/check_bipartite_graph_dfs.py +++ b/graphs/check_bipartite_graph_dfs.py @@ -1,27 +1,28 @@ # Check whether Graph is Bipartite or Not using DFS + # A Bipartite Graph is a graph whose vertices can be divided into two independent sets, # U and V such that every edge (u, v) either connects a vertex from U to V or a vertex # from V to U. In other words, for every edge (u, v), either u belongs to U and v to V, # or u belongs to V and v to U. We can also say that there is no edge that connects # vertices of same set. -def check_bipartite_dfs(l): - visited = [False] * len(l) - color = [-1] * len(l) +def check_bipartite_dfs(graph): + visited = [False] * len(graph) + color = [-1] * len(graph) def dfs(v, c): visited[v] = True color[v] = c - for u in l[v]: + for u in graph[v]: if not visited[u]: dfs(u, 1 - c) - for i in range(len(l)): + for i in range(len(graph)): if not visited[i]: dfs(i, 0) - for i in range(len(l)): - for j in l[i]: + for i in range(len(graph)): + for j in graph[i]: if color[i] == color[j]: return False @@ -29,5 +30,5 @@ def dfs(v, c): # Adjacency list of graph -l = {0: [1, 3], 1: [0, 2], 2: [1, 3], 3: [0, 2], 4: []} -print(check_bipartite_dfs(l)) +graph = {0: [1, 3], 1: [0, 2], 2: [1, 3], 3: [0, 2], 4: []} +print(check_bipartite_dfs(graph)) diff --git a/graphs/depth_first_search.py b/graphs/depth_first_search.py index 1206d5ae9252..f3e0ed4ebaa6 100644 --- a/graphs/depth_first_search.py +++ b/graphs/depth_first_search.py @@ -1,6 +1,6 @@ -"""The DFS function simply calls itself recursively for every unvisited child of -its argument. We can emulate that behaviour precisely using a stack of iterators. -Instead of recursively calling with a node, we'll push an iterator to the node's +"""The DFS function simply calls itself recursively for every unvisited child of +its argument. We can emulate that behaviour precisely using a stack of iterators. +Instead of recursively calling with a node, we'll push an iterator to the node's children onto the iterator stack. When the iterator at the top of the stack terminates, we'll pop it off the stack. @@ -21,7 +21,7 @@ def depth_first_search(graph: Dict, start: str) -> Set[int]: :param graph: directed graph in dictionary format :param vertex: starting vectex as a string :returns: the trace of the search - >>> G = { "A": ["B", "C", "D"], "B": ["A", "D", "E"], + >>> G = { "A": ["B", "C", "D"], "B": ["A", "D", "E"], ... "C": ["A", "F"], "D": ["B", "D"], "E": ["B", "F"], ... "F": ["C", "E", "G"], "G": ["F"] } >>> start = "A" diff --git a/graphs/depth_first_search_2.py b/graphs/depth_first_search_2.py index 0593e120b1da..c932e76293ed 100644 --- a/graphs/depth_first_search_2.py +++ b/graphs/depth_first_search_2.py @@ -28,7 +28,7 @@ def DFS(self): # call the recursive helper function for i in range(len(self.vertex)): - if visited[i] == False: + if visited[i] is False: self.DFSRec(i, visited) def DFSRec(self, startVertex, visited): @@ -39,7 +39,7 @@ def DFSRec(self, startVertex, visited): # Recur for all the vertices that are adjacent to this node for i in self.vertex.keys(): - if visited[i] == False: + if visited[i] is False: self.DFSRec(i, visited) diff --git a/graphs/dijkstra.py b/graphs/dijkstra.py index f156602beb6e..d15fcbbfeef0 100644 --- a/graphs/dijkstra.py +++ b/graphs/dijkstra.py @@ -1,6 +1,6 @@ -"""pseudo-code""" - """ +pseudo-code + DIJKSTRA(graph G, start vertex s, destination vertex d): //all nodes initially unexplored @@ -30,7 +30,6 @@ distance between each vertex that makes up the path from start vertex to target vertex. """ - import heapq diff --git a/graphs/dinic.py b/graphs/dinic.py index 4f5e81236984..aaf3a119525c 100644 --- a/graphs/dinic.py +++ b/graphs/dinic.py @@ -37,7 +37,7 @@ def depth_first_search(self, vertex, sink, flow): # Here we calculate the flow that reaches the sink def max_flow(self, source, sink): flow, self.q[0] = 0, source - for l in range(31): # l = 30 maybe faster for random data + for l in range(31): # noqa: E741 l = 30 maybe faster for random data while True: self.lvl, self.ptr = [0] * len(self.q), [0] * len(self.q) qi, qe, self.lvl[source] = 0, 1, 1 diff --git a/graphs/directed_and_undirected_(weighted)_graph.py b/graphs/directed_and_undirected_(weighted)_graph.py index 26c87cd8f4b2..0312e982a9e0 100644 --- a/graphs/directed_and_undirected_(weighted)_graph.py +++ b/graphs/directed_and_undirected_(weighted)_graph.py @@ -71,8 +71,8 @@ def dfs(self, s=-2, d=-1): if len(stack) == 0: return visited - # c is the count of nodes you want and if you leave it or pass -1 to the function the count - # will be random from 10 to 10000 + # c is the count of nodes you want and if you leave it or pass -1 to the function + # the count will be random from 10 to 10000 def fill_graph_randomly(self, c=-1): if c == -1: c = (math.floor(rand.random() * 10000)) + 10 @@ -168,14 +168,14 @@ def cycle_nodes(self): and indirect_parents.count(__[1]) > 0 and not on_the_way_back ): - l = len(stack) - 1 - while True and l >= 0: - if stack[l] == __[1]: + len_stack = len(stack) - 1 + while True and len_stack >= 0: + if stack[len_stack] == __[1]: anticipating_nodes.add(__[1]) break else: - anticipating_nodes.add(stack[l]) - l -= 1 + anticipating_nodes.add(stack[len_stack]) + len_stack -= 1 if visited.count(__[1]) < 1: stack.append(__[1]) visited.append(__[1]) @@ -221,15 +221,15 @@ def has_cycle(self): and indirect_parents.count(__[1]) > 0 and not on_the_way_back ): - l = len(stack) - 1 - while True and l >= 0: - if stack[l] == __[1]: + len_stack_minus_one = len(stack) - 1 + while True and len_stack_minus_one >= 0: + if stack[len_stack_minus_one] == __[1]: anticipating_nodes.add(__[1]) break else: return True - anticipating_nodes.add(stack[l]) - l -= 1 + anticipating_nodes.add(stack[len_stack_minus_one]) + len_stack_minus_one -= 1 if visited.count(__[1]) < 1: stack.append(__[1]) visited.append(__[1]) @@ -341,8 +341,8 @@ def dfs(self, s=-2, d=-1): if len(stack) == 0: return visited - # c is the count of nodes you want and if you leave it or pass -1 to the function the count - # will be random from 10 to 10000 + # c is the count of nodes you want and if you leave it or pass -1 to the function + # the count will be random from 10 to 10000 def fill_graph_randomly(self, c=-1): if c == -1: c = (math.floor(rand.random() * 10000)) + 10 @@ -397,14 +397,14 @@ def cycle_nodes(self): and indirect_parents.count(__[1]) > 0 and not on_the_way_back ): - l = len(stack) - 1 - while True and l >= 0: - if stack[l] == __[1]: + len_stack = len(stack) - 1 + while True and len_stack >= 0: + if stack[len_stack] == __[1]: anticipating_nodes.add(__[1]) break else: - anticipating_nodes.add(stack[l]) - l -= 1 + anticipating_nodes.add(stack[len_stack]) + len_stack -= 1 if visited.count(__[1]) < 1: stack.append(__[1]) visited.append(__[1]) @@ -450,15 +450,15 @@ def has_cycle(self): and indirect_parents.count(__[1]) > 0 and not on_the_way_back ): - l = len(stack) - 1 - while True and l >= 0: - if stack[l] == __[1]: + len_stack_minus_one = len(stack) - 1 + while True and len_stack_minus_one >= 0: + if stack[len_stack_minus_one] == __[1]: anticipating_nodes.add(__[1]) break else: return True - anticipating_nodes.add(stack[l]) - l -= 1 + anticipating_nodes.add(stack[len_stack_minus_one]) + len_stack_minus_one -= 1 if visited.count(__[1]) < 1: stack.append(__[1]) visited.append(__[1]) diff --git a/graphs/eulerian_path_and_circuit_for_undirected_graph.py b/graphs/eulerian_path_and_circuit_for_undirected_graph.py index a2e5cf4da26a..7850933b0201 100644 --- a/graphs/eulerian_path_and_circuit_for_undirected_graph.py +++ b/graphs/eulerian_path_and_circuit_for_undirected_graph.py @@ -9,7 +9,7 @@ def dfs(u, graph, visited_edge, path=[]): path = path + [u] for v in graph[u]: - if visited_edge[u][v] == False: + if visited_edge[u][v] is False: visited_edge[u][v], visited_edge[v][u] = True, True path = dfs(v, graph, visited_edge, path) return path diff --git a/graphs/finding_bridges.py b/graphs/finding_bridges.py index e18a3bafa9c0..6555dd7bc29e 100644 --- a/graphs/finding_bridges.py +++ b/graphs/finding_bridges.py @@ -1,7 +1,7 @@ # Finding Bridges in Undirected Graph -def computeBridges(l): +def computeBridges(graph): id = 0 - n = len(l) # No of vertices in graph + n = len(graph) # No of vertices in graph low = [0] * n visited = [False] * n @@ -9,7 +9,7 @@ def dfs(at, parent, bridges, id): visited[at] = True low[at] = id id += 1 - for to in l[at]: + for to in graph[at]: if to == parent: pass elif not visited[to]: @@ -28,7 +28,7 @@ def dfs(at, parent, bridges, id): print(bridges) -l = { +graph = { 0: [1, 2], 1: [0, 2], 2: [0, 1, 3, 5], @@ -39,4 +39,4 @@ def dfs(at, parent, bridges, id): 7: [6, 8], 8: [5, 7], } -computeBridges(l) +computeBridges(graph) diff --git a/graphs/frequent_pattern_graph_miner.py b/graphs/frequent_pattern_graph_miner.py index aa14fbdd3a3c..ff7063082267 100644 --- a/graphs/frequent_pattern_graph_miner.py +++ b/graphs/frequent_pattern_graph_miner.py @@ -19,7 +19,7 @@ ['ab-e1', 'ac-e3', 'bc-e4', 'bd-e2', 'bh-e12', 'cd-e2', 'df-e8', 'dh-e10'], ['ab-e1', 'ac-e3', 'ad-e5', 'bc-e4', 'bd-e2', 'cd-e2', 'ce-e4', 'de-e1', 'df-e8', 'dg-e5', 'ef-e3', 'eg-e2', 'fg-e6'] - ] +] # fmt: on diff --git a/graphs/kahns_algorithm_long.py b/graphs/kahns_algorithm_long.py index 0651040365d0..fed7517a21e2 100644 --- a/graphs/kahns_algorithm_long.py +++ b/graphs/kahns_algorithm_long.py @@ -1,10 +1,10 @@ # Finding longest distance in Directed Acyclic Graph using KahnsAlgorithm -def longestDistance(l): - indegree = [0] * len(l) +def longestDistance(graph): + indegree = [0] * len(graph) queue = [] - longDist = [1] * len(l) + longDist = [1] * len(graph) - for key, values in l.items(): + for key, values in graph.items(): for i in values: indegree[i] += 1 @@ -14,7 +14,7 @@ def longestDistance(l): while queue: vertex = queue.pop(0) - for x in l[vertex]: + for x in graph[vertex]: indegree[x] -= 1 if longDist[vertex] + 1 > longDist[x]: @@ -27,5 +27,5 @@ def longestDistance(l): # Adjacency list of Graph -l = {0: [2, 3, 4], 1: [2, 7], 2: [5], 3: [5, 7], 4: [7], 5: [6], 6: [7], 7: []} -longestDistance(l) +graph = {0: [2, 3, 4], 1: [2, 7], 2: [5], 3: [5, 7], 4: [7], 5: [6], 6: [7], 7: []} +longestDistance(graph) diff --git a/graphs/kahns_algorithm_topo.py b/graphs/kahns_algorithm_topo.py index d50bc9a43d19..bf9f90299361 100644 --- a/graphs/kahns_algorithm_topo.py +++ b/graphs/kahns_algorithm_topo.py @@ -1,11 +1,14 @@ -# Kahn's Algorithm is used to find Topological ordering of Directed Acyclic Graph using BFS -def topologicalSort(l): - indegree = [0] * len(l) +def topologicalSort(graph): + """ + Kahn's Algorithm is used to find Topological ordering of Directed Acyclic Graph + using BFS + """ + indegree = [0] * len(graph) queue = [] topo = [] cnt = 0 - for key, values in l.items(): + for key, values in graph.items(): for i in values: indegree[i] += 1 @@ -17,17 +20,17 @@ def topologicalSort(l): vertex = queue.pop(0) cnt += 1 topo.append(vertex) - for x in l[vertex]: + for x in graph[vertex]: indegree[x] -= 1 if indegree[x] == 0: queue.append(x) - if cnt != len(l): + if cnt != len(graph): print("Cycle exists") else: print(topo) # Adjacency List of Graph -l = {0: [1, 2], 1: [3], 2: [3], 3: [4, 5], 4: [], 5: []} -topologicalSort(l) +graph = {0: [1, 2], 1: [3], 2: [3], 3: [4, 5], 4: [], 5: []} +topologicalSort(graph) diff --git a/graphs/minimum_spanning_tree_prims.py b/graphs/minimum_spanning_tree_prims.py index 6255b6af64ad..77ff149e2a38 100644 --- a/graphs/minimum_spanning_tree_prims.py +++ b/graphs/minimum_spanning_tree_prims.py @@ -2,7 +2,7 @@ from collections import defaultdict -def PrimsAlgorithm(l): +def PrimsAlgorithm(l): # noqa: E741 nodePosition = [] @@ -109,7 +109,7 @@ def deleteMinimum(heap, positions): e = int(input("Enter number of edges: ").strip()) adjlist = defaultdict(list) for x in range(e): - l = [int(x) for x in input().strip().split()] + l = [int(x) for x in input().strip().split()] # noqa: E741 adjlist[l[0]].append([l[1], l[2]]) adjlist[l[1]].append([l[0], l[2]]) print(PrimsAlgorithm(adjlist)) diff --git a/hashes/chaos_machine.py b/hashes/chaos_machine.py index 8d3bbd4c0251..1bdf984b68de 100644 --- a/hashes/chaos_machine.py +++ b/hashes/chaos_machine.py @@ -79,24 +79,23 @@ def reset(): machine_time = 0 -####################################### - -# Initialization -reset() - -# Pushing Data (Input) -import random - -message = random.sample(range(0xFFFFFFFF), 100) -for chunk in message: - push(chunk) - -# for controlling -inp = "" - -# Pulling Data (Output) -while inp in ("e", "E"): - print("%s" % format(pull(), "#04x")) - print(buffer_space) - print(params_space) - inp = input("(e)exit? ").strip() +if __name__ == "__main__": + # Initialization + reset() + + # Pushing Data (Input) + import random + + message = random.sample(range(0xFFFFFFFF), 100) + for chunk in message: + push(chunk) + + # for controlling + inp = "" + + # Pulling Data (Output) + while inp in ("e", "E"): + print("%s" % format(pull(), "#04x")) + print(buffer_space) + print(params_space) + inp = input("(e)exit? ").strip() diff --git a/hashes/hamming_code.py b/hashes/hamming_code.py index c1ed7fe1d727..14d23ef3cef4 100644 --- a/hashes/hamming_code.py +++ b/hashes/hamming_code.py @@ -47,6 +47,7 @@ # Imports import numpy as np + # Functions of binary conversion-------------------------------------- def text_to_bits(text, encoding="utf-8", errors="surrogatepass"): """ diff --git a/linear_algebra/src/lib.py b/linear_algebra/src/lib.py index f4628f1d964a..10b9da65863f 100644 --- a/linear_algebra/src/lib.py +++ b/linear_algebra/src/lib.py @@ -27,10 +27,10 @@ class Vector: """ This class represents a vector of arbitrary size. - You need to give the vector components. - + You need to give the vector components. + Overview about the methods: - + constructor(components : list) : init the vector set(components : list) : changes the vector components. __str__() : toString method @@ -124,7 +124,7 @@ def __sub__(self, other): def __mul__(self, other): """ - mul implements the scalar multiplication + mul implements the scalar multiplication and the dot-product """ if isinstance(other, float) or isinstance(other, int): @@ -167,7 +167,7 @@ def zeroVector(dimension): def unitBasisVector(dimension, pos): """ - returns a unit basis vector with a One + returns a unit basis vector with a One at index 'pos' (indexing at 0) """ # precondition @@ -196,7 +196,7 @@ def randomVector(N, a, b): """ input: size (N) of the vector. random range (a,b) - output: returns a random vector of size N, with + output: returns a random vector of size N, with random integer components between 'a' and 'b'. """ random.seed(None) @@ -208,10 +208,10 @@ class Matrix: """ class: Matrix This class represents a arbitrary matrix. - + Overview about the methods: - - __str__() : returns a string representation + + __str__() : returns a string representation operator * : implements the matrix vector multiplication implements the matrix-scalar multiplication. changeComponent(x,y,value) : changes the specified component. diff --git a/linear_algebra/src/test_linear_algebra.py b/linear_algebra/src/test_linear_algebra.py index 8d2170e46da4..21fed9529ac0 100644 --- a/linear_algebra/src/test_linear_algebra.py +++ b/linear_algebra/src/test_linear_algebra.py @@ -19,7 +19,7 @@ def test_component(self): x = Vector([1, 2, 3]) self.assertEqual(x.component(0), 1) self.assertEqual(x.component(2), 3) - y = Vector() + _ = Vector() def test_str(self): """ diff --git a/machine_learning/k_means_clust.py b/machine_learning/k_means_clust.py index 7a4f69eb77ce..86a5dd968779 100644 --- a/machine_learning/k_means_clust.py +++ b/machine_learning/k_means_clust.py @@ -11,9 +11,11 @@ Inputs: - X , a 2D numpy array of features. - k , number of clusters to create. - - initial_centroids , initial centroid values generated by utility function(mentioned in usage). + - initial_centroids , initial centroid values generated by utility function(mentioned + in usage). - maxiter , maximum number of iterations to process. - - heterogeneity , empty list that will be filled with hetrogeneity values if passed to kmeans func. + - heterogeneity , empty list that will be filled with hetrogeneity values if passed + to kmeans func. Usage: 1. define 'k' value, 'X' features array and 'hetrogeneity' empty list @@ -22,7 +24,8 @@ initial_centroids = get_initial_centroids( X, k, - seed=0 # seed value for initial centroid generation, None for randomness(default=None) + seed=0 # seed value for initial centroid generation, + # None for randomness(default=None) ) 3. find centroids and clusters using kmeans function. @@ -37,7 +40,8 @@ ) - 4. Plot the loss function, hetrogeneity values for every iteration saved in hetrogeneity list. + 4. Plot the loss function, hetrogeneity values for every iteration saved in + hetrogeneity list. plot_heterogeneity( heterogeneity, k @@ -46,8 +50,9 @@ 5. Have fun.. """ -from sklearn.metrics import pairwise_distances import numpy as np +from matplotlib import pyplot as plt +from sklearn.metrics import pairwise_distances TAG = "K-MEANS-CLUST/ " @@ -118,9 +123,6 @@ def compute_heterogeneity(data, k, centroids, cluster_assignment): return heterogeneity -from matplotlib import pyplot as plt - - def plot_heterogeneity(heterogeneity, k): plt.figure(figsize=(7, 4)) plt.plot(heterogeneity, linewidth=4) @@ -136,9 +138,11 @@ def kmeans( ): """This function runs k-means on given data and initial set of centroids. maxiter: maximum number of iterations to run.(default=500) - record_heterogeneity: (optional) a list, to store the history of heterogeneity as function of iterations + record_heterogeneity: (optional) a list, to store the history of heterogeneity + as function of iterations if None, do not store the history. - verbose: if True, print how many data points changed their cluster labels in each iteration""" + verbose: if True, print how many data points changed their cluster labels in + each iteration""" centroids = initial_centroids[:] prev_cluster_assignment = None @@ -149,7 +153,8 @@ def kmeans( # 1. Make cluster assignments using nearest centroids cluster_assignment = assign_clusters(data, centroids) - # 2. Compute a new centroid for each of the k clusters, averaging all data points assigned to that cluster. + # 2. Compute a new centroid for each of the k clusters, averaging all data + # points assigned to that cluster. centroids = revise_centroids(data, k, cluster_assignment) # Check for convergence: if none of the assignments changed, stop diff --git a/machine_learning/linear_discriminant_analysis.py b/machine_learning/linear_discriminant_analysis.py index 6998db1ce4a0..01be288ea64a 100644 --- a/machine_learning/linear_discriminant_analysis.py +++ b/machine_learning/linear_discriminant_analysis.py @@ -186,7 +186,8 @@ def predict_y_values( >>> means = [5.011267842911003, 10.011267842911003, 15.011267842911002] >>> variance = 0.9618530973487494 >>> probabilities = [0.3333333333333333, 0.3333333333333333, 0.3333333333333333] - >>> predict_y_values(x_items, means, variance, probabilities) # doctest: +NORMALIZE_WHITESPACE + >>> predict_y_values(x_items, means, variance, + ... probabilities) # doctest: +NORMALIZE_WHITESPACE [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2] @@ -211,7 +212,7 @@ def predict_y_values( # appending discriminant values of each item to 'results' list results.append(temp) - return [l.index(max(l)) for l in results] + return [result.index(max(result)) for result in results] # Calculating Accuracy diff --git a/machine_learning/polymonial_regression.py b/machine_learning/polymonial_regression.py index 7b080715b762..cdcb90b8fd21 100644 --- a/machine_learning/polymonial_regression.py +++ b/machine_learning/polymonial_regression.py @@ -1,5 +1,12 @@ import matplotlib.pyplot as plt import pandas as pd +from sklearn.linear_model import LinearRegression + +# Splitting the dataset into the Training set and Test set +from sklearn.model_selection import train_test_split + +# Fitting Polynomial Regression to the dataset +from sklearn.preprocessing import PolynomialFeatures # Importing the dataset dataset = pd.read_csv( @@ -9,16 +16,9 @@ y = dataset.iloc[:, 2].values -# Splitting the dataset into the Training set and Test set -from sklearn.model_selection import train_test_split - X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=0) -# Fitting Polynomial Regression to the dataset -from sklearn.preprocessing import PolynomialFeatures -from sklearn.linear_model import LinearRegression - poly_reg = PolynomialFeatures(degree=4) X_poly = poly_reg.fit_transform(X) pol_reg = LinearRegression() diff --git a/machine_learning/scoring_functions.py b/machine_learning/scoring_functions.py index 2b891d4eb9d5..a401df139748 100755 --- a/machine_learning/scoring_functions.py +++ b/machine_learning/scoring_functions.py @@ -14,6 +14,7 @@ and types of data """ + # Mean Absolute Error def mae(predict, actual): """ diff --git a/maths/aliquot_sum.py b/maths/aliquot_sum.py index ac5fa58f41cf..c8635bd61237 100644 --- a/maths/aliquot_sum.py +++ b/maths/aliquot_sum.py @@ -9,7 +9,7 @@ def aliquot_sum(input_num: int) -> int: @return: the aliquot sum of input_num, if input_num is positive. Otherwise, raise a ValueError Wikipedia Explanation: https://en.wikipedia.org/wiki/Aliquot_sum - + >>> aliquot_sum(15) 9 >>> aliquot_sum(6) diff --git a/maths/allocation_number.py b/maths/allocation_number.py index fd002b0c4361..c6f1e562f878 100644 --- a/maths/allocation_number.py +++ b/maths/allocation_number.py @@ -4,8 +4,8 @@ def allocation_num(number_of_bytes: int, partitions: int) -> List[str]: """ Divide a number of bytes into x partitions. - - In a multi-threaded download, this algorithm could be used to provide + + In a multi-threaded download, this algorithm could be used to provide each worker thread with a block of non-overlapping bytes to download. For example: for i in allocation_list: diff --git a/maths/bailey_borwein_plouffe.py b/maths/bailey_borwein_plouffe.py index 50a53c793867..be97acfd063e 100644 --- a/maths/bailey_borwein_plouffe.py +++ b/maths/bailey_borwein_plouffe.py @@ -1,16 +1,16 @@ def bailey_borwein_plouffe(digit_position: int, precision: int = 1000) -> str: """ - Implement a popular pi-digit-extraction algorithm known as the + Implement a popular pi-digit-extraction algorithm known as the Bailey-Borwein-Plouffe (BBP) formula to calculate the nth hex digit of pi. Wikipedia page: https://en.wikipedia.org/wiki/Bailey%E2%80%93Borwein%E2%80%93Plouffe_formula - @param digit_position: a positive integer representing the position of the digit to extract. + @param digit_position: a positive integer representing the position of the digit to extract. The digit immediately after the decimal point is located at position 1. @param precision: number of terms in the second summation to calculate. A higher number reduces the chance of an error but increases the runtime. @return: a hexadecimal digit representing the digit at the nth position in pi's decimal expansion. - + >>> "".join(bailey_borwein_plouffe(i) for i in range(1, 11)) '243f6a8885' >>> bailey_borwein_plouffe(5, 10000) @@ -59,11 +59,11 @@ def _subsum( # only care about first digit of fractional part; don't need decimal """ Private helper function to implement the summation - functionality. + functionality. @param digit_pos_to_extract: digit position to extract @param denominator_addend: added to denominator of fractions in the formula @param precision: same as precision in main function - @return: floating-point number whose integer part is not important + @return: floating-point number whose integer part is not important """ sum = 0.0 for sum_index in range(digit_pos_to_extract + precision): diff --git a/maths/collatz_sequence.py b/maths/collatz_sequence.py index d3eb6e756dcd..6ace77312732 100644 --- a/maths/collatz_sequence.py +++ b/maths/collatz_sequence.py @@ -18,8 +18,9 @@ def collatz_sequence(n: int) -> List[int]: Traceback (most recent call last): ... Exception: Sequence only defined for natural numbers - >>> collatz_sequence(43) - [43, 130, 65, 196, 98, 49, 148, 74, 37, 112, 56, 28, 14, 7, 22, 11, 34, 17, 52, 26, 13, 40, 20, 10, 5, 16, 8, 4, 2, 1] + >>> collatz_sequence(43) # doctest: +NORMALIZE_WHITESPACE + [43, 130, 65, 196, 98, 49, 148, 74, 37, 112, 56, 28, 14, 7, + 22, 11, 34, 17, 52, 26, 13, 40, 20, 10, 5, 16, 8, 4, 2, 1] """ if not isinstance(n, int) or n < 1: diff --git a/maths/find_max_recursion.py b/maths/find_max_recursion.py index fc10ecf3757a..03fb81950dcb 100644 --- a/maths/find_max_recursion.py +++ b/maths/find_max_recursion.py @@ -6,7 +6,7 @@ def find_max(nums, left, right): :param left: index of first element :param right: index of last element :return: max in nums - + >>> nums = [1, 3, 5, 7, 9, 2, 4, 6, 8, 10] >>> find_max(nums, 0, len(nums) - 1) == max(nums) True diff --git a/maths/gamma.py b/maths/gamma.py index ef5e7dae6187..98b327fa2f99 100644 --- a/maths/gamma.py +++ b/maths/gamma.py @@ -6,8 +6,8 @@ def gamma(num: float) -> float: """ https://en.wikipedia.org/wiki/Gamma_function - In mathematics, the gamma function is one commonly - used extension of the factorial function to complex numbers. + In mathematics, the gamma function is one commonly + used extension of the factorial function to complex numbers. The gamma function is defined for all complex numbers except the non-positive integers @@ -16,7 +16,7 @@ def gamma(num: float) -> float: ... ValueError: math domain error - + >>> gamma(0) Traceback (most recent call last): @@ -27,12 +27,12 @@ def gamma(num: float) -> float: >>> gamma(9) 40320.0 - >>> from math import gamma as math_gamma + >>> from math import gamma as math_gamma >>> all(gamma(i)/math_gamma(i) <= 1.000000001 and abs(gamma(i)/math_gamma(i)) > .99999999 for i in range(1, 50)) True - >>> from math import gamma as math_gamma + >>> from math import gamma as math_gamma >>> gamma(-1)/math_gamma(-1) <= 1.000000001 Traceback (most recent call last): ... @@ -40,7 +40,7 @@ def gamma(num: float) -> float: >>> from math import gamma as math_gamma - >>> gamma(3.3) - math_gamma(3.3) <= 0.00000001 + >>> gamma(3.3) - math_gamma(3.3) <= 0.00000001 True """ diff --git a/maths/gaussian.py b/maths/gaussian.py index ffea20fb2ba1..edd52d1a4b2c 100644 --- a/maths/gaussian.py +++ b/maths/gaussian.py @@ -12,7 +12,7 @@ def gaussian(x, mu: float = 0.0, sigma: float = 1.0) -> int: """ >>> gaussian(1) 0.24197072451914337 - + >>> gaussian(24) 3.342714441794458e-126 @@ -25,7 +25,7 @@ def gaussian(x, mu: float = 0.0, sigma: float = 1.0) -> int: 1.33830226e-04, 1.48671951e-06, 6.07588285e-09, 9.13472041e-12, 5.05227108e-15, 1.02797736e-18, 7.69459863e-23, 2.11881925e-27, 2.14638374e-32, 7.99882776e-38, 1.09660656e-43]) - + >>> gaussian(15) 5.530709549844416e-50 diff --git a/maths/is_square_free.py b/maths/is_square_free.py index acc13fa5f833..6d27d0af3387 100644 --- a/maths/is_square_free.py +++ b/maths/is_square_free.py @@ -13,12 +13,12 @@ def is_square_free(factors: List[int]) -> bool: returns True if the factors are square free. >>> is_square_free([1, 1, 2, 3, 4]) False - + These are wrong but should return some value it simply checks for repition in the numbers. >>> is_square_free([1, 3, 4, 'sd', 0.0]) True - + >>> is_square_free([1, 0.5, 2, 0.0]) True >>> is_square_free([1, 2, 2, 5]) diff --git a/maths/kth_lexicographic_permutation.py b/maths/kth_lexicographic_permutation.py index 1820be7274e3..491c1c84fa85 100644 --- a/maths/kth_lexicographic_permutation.py +++ b/maths/kth_lexicographic_permutation.py @@ -1,15 +1,15 @@ def kthPermutation(k, n): """ - Finds k'th lexicographic permutation (in increasing order) of + Finds k'th lexicographic permutation (in increasing order) of 0,1,2,...n-1 in O(n^2) time. - + Examples: First permutation is always 0,1,2,...n >>> kthPermutation(0,5) [0, 1, 2, 3, 4] - + The order of permutation of 0,1,2,3 is [0,1,2,3], [0,1,3,2], [0,2,1,3], - [0,2,3,1], [0,3,1,2], [0,3,2,1], [1,0,2,3], [1,0,3,2], [1,2,0,3], + [0,2,3,1], [0,3,1,2], [0,3,2,1], [1,0,2,3], [1,0,3,2], [1,2,0,3], [1,2,3,0], [1,3,0,2] >>> kthPermutation(10,4) [1, 3, 0, 2] diff --git a/maths/lucas_lehmer_primality_test.py b/maths/lucas_lehmer_primality_test.py index 8dac658f16d1..33e4a2141efc 100644 --- a/maths/lucas_lehmer_primality_test.py +++ b/maths/lucas_lehmer_primality_test.py @@ -1,12 +1,12 @@ """ In mathematics, the Lucas–Lehmer test (LLT) is a primality test for Mersenne numbers. https://en.wikipedia.org/wiki/Lucas%E2%80%93Lehmer_primality_test - + A Mersenne number is a number that is one less than a power of two. That is M_p = 2^p - 1 https://en.wikipedia.org/wiki/Mersenne_prime - - The Lucas–Lehmer test is the primality test used by the + + The Lucas–Lehmer test is the primality test used by the Great Internet Mersenne Prime Search (GIMPS) to locate large primes. """ @@ -17,10 +17,10 @@ def lucas_lehmer_test(p: int) -> bool: """ >>> lucas_lehmer_test(p=7) True - + >>> lucas_lehmer_test(p=11) False - + # M_11 = 2^11 - 1 = 2047 = 23 * 89 """ diff --git a/maths/matrix_exponentiation.py b/maths/matrix_exponentiation.py index 56d03b56fc1f..574269050fd8 100644 --- a/maths/matrix_exponentiation.py +++ b/maths/matrix_exponentiation.py @@ -4,7 +4,7 @@ """ Matrix Exponentiation is a technique to solve linear recurrences in logarithmic time. -You read more about it here: +You read more about it here: http://zobayer.blogspot.com/2010/11/matrix-exponentiation.html https://www.hackerearth.com/practice/notes/matrix-exponentiation-1/ """ diff --git a/maths/modular_exponential.py b/maths/modular_exponential.py index 8b7b17575a33..9cb171477ff0 100644 --- a/maths/modular_exponential.py +++ b/maths/modular_exponential.py @@ -1,6 +1,6 @@ """ Modular Exponential. - Modular exponentiation is a type of exponentiation performed over a modulus. + Modular exponentiation is a type of exponentiation performed over a modulus. For more explanation, please check https://en.wikipedia.org/wiki/Modular_exponentiation """ diff --git a/maths/monte_carlo.py b/maths/monte_carlo.py index dedca9f6cdf5..28027cbe4178 100644 --- a/maths/monte_carlo.py +++ b/maths/monte_carlo.py @@ -45,13 +45,13 @@ def area_under_curve_estimator( ) -> float: """ An implementation of the Monte Carlo method to find area under - a single variable non-negative real-valued continuous function, - say f(x), where x lies within a continuous bounded interval, - say [min_value, max_value], where min_value and max_value are + a single variable non-negative real-valued continuous function, + say f(x), where x lies within a continuous bounded interval, + say [min_value, max_value], where min_value and max_value are finite numbers - 1. Let x be a uniformly distributed random variable between min_value to + 1. Let x be a uniformly distributed random variable between min_value to max_value - 2. Expected value of f(x) = + 2. Expected value of f(x) = (integrate f(x) from min_value to max_value)/(max_value - min_value) 3. Finding expected value of f(x): a. Repeatedly draw x from uniform distribution diff --git a/maths/newton_raphson.py b/maths/newton_raphson.py index c4975c73e037..7b16d2dd9b2e 100644 --- a/maths/newton_raphson.py +++ b/maths/newton_raphson.py @@ -24,7 +24,7 @@ def newton_raphson(f, x0=0, maxiter=100, step=0.0001, maxerror=1e-6, logsteps=Fa a = x0 # set the initial guess steps = [a] error = abs(f(a)) - f1 = lambda x: calc_derivative(f, x, h=step) # Derivative of f(x) + f1 = lambda x: calc_derivative(f, x, h=step) # noqa: E731 Derivative of f(x) for _ in range(maxiter): if f1(a) == 0: raise ValueError("No converging solution found") @@ -44,7 +44,7 @@ def newton_raphson(f, x0=0, maxiter=100, step=0.0001, maxerror=1e-6, logsteps=Fa if __name__ == "__main__": import matplotlib.pyplot as plt - f = lambda x: m.tanh(x) ** 2 - m.exp(3 * x) + f = lambda x: m.tanh(x) ** 2 - m.exp(3 * x) # noqa: E731 solution, error, steps = newton_raphson( f, x0=10, maxiter=1000, step=1e-6, logsteps=True ) diff --git a/maths/prime_factors.py b/maths/prime_factors.py index eb3de00de6a7..34795dd98d1a 100644 --- a/maths/prime_factors.py +++ b/maths/prime_factors.py @@ -7,7 +7,7 @@ def prime_factors(n: int) -> List[int]: """ Returns prime factors of n as a list. - + >>> prime_factors(0) [] >>> prime_factors(100) diff --git a/maths/prime_sieve_eratosthenes.py b/maths/prime_sieve_eratosthenes.py index 05363cf62953..0ebdfdb94e15 100644 --- a/maths/prime_sieve_eratosthenes.py +++ b/maths/prime_sieve_eratosthenes.py @@ -1,11 +1,13 @@ +# flake8: noqa + """ Sieve of Eratosthenes Input : n =10 -Output : 2 3 5 7 +Output: 2 3 5 7 Input : n = 20 -Output: 2 3 5 7 11 13 17 19 +Output: 2 3 5 7 11 13 17 19 you can read in detail about this at https://en.wikipedia.org/wiki/Sieve_of_Eratosthenes diff --git a/maths/radians.py b/maths/radians.py index 3788b3e8a3a0..465467a3ba08 100644 --- a/maths/radians.py +++ b/maths/radians.py @@ -14,8 +14,8 @@ def radians(degree: float) -> float: 4.782202150464463 >>> radians(109.82) 1.9167205845401725 - - >>> from math import radians as math_radians + + >>> from math import radians as math_radians >>> all(abs(radians(i)-math_radians(i)) <= 0.00000001 for i in range(-2, 361)) True """ diff --git a/maths/radix2_fft.py b/maths/radix2_fft.py index 3911fea1d04d..de87071e5440 100644 --- a/maths/radix2_fft.py +++ b/maths/radix2_fft.py @@ -12,36 +12,36 @@ class FFT: Reference: https://en.wikipedia.org/wiki/Cooley%E2%80%93Tukey_FFT_algorithm#The_radix-2_DIT_case - - For polynomials of degree m and n the algorithms has complexity + + For polynomials of degree m and n the algorithms has complexity O(n*logn + m*logm) - + The main part of the algorithm is split in two parts: - 1) __DFT: We compute the discrete fourier transform (DFT) of A and B using a - bottom-up dynamic approach - + 1) __DFT: We compute the discrete fourier transform (DFT) of A and B using a + bottom-up dynamic approach - 2) __multiply: Once we obtain the DFT of A*B, we can similarly invert it to obtain A*B - The class FFT takes two polynomials A and B with complex coefficients as arguments; + The class FFT takes two polynomials A and B with complex coefficients as arguments; The two polynomials should be represented as a sequence of coefficients starting - from the free term. Thus, for instance x + 2*x^3 could be represented as - [0,1,0,2] or (0,1,0,2). The constructor adds some zeros at the end so that the - polynomials have the same length which is a power of 2 at least the length of - their product. - + from the free term. Thus, for instance x + 2*x^3 could be represented as + [0,1,0,2] or (0,1,0,2). The constructor adds some zeros at the end so that the + polynomials have the same length which is a power of 2 at least the length of + their product. + Example: - + Create two polynomials as sequences >>> A = [0, 1, 0, 2] # x+2x^3 >>> B = (2, 3, 4, 0) # 2+3x+4x^2 - + Create an FFT object with them >>> x = FFT(A, B) - + Print product >>> print(x.product) # 2x + 3x^2 + 8x^3 + 4x^4 + 6x^5 [(-0+0j), (2+0j), (3+0j), (8+0j), (6+0j), (8+0j)] - + __str__ test >>> print(x) A = 0*x^0 + 1*x^1 + 2*x^0 + 3*x^2 diff --git a/maths/sieve_of_eratosthenes.py b/maths/sieve_of_eratosthenes.py index 4761c9339ea0..9f2960dc134a 100644 --- a/maths/sieve_of_eratosthenes.py +++ b/maths/sieve_of_eratosthenes.py @@ -16,7 +16,7 @@ def sieve(n): """ Returns a list with all prime numbers up to n. - + >>> sieve(50) [2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47] >>> sieve(25) @@ -31,7 +31,7 @@ def sieve(n): [] """ - l = [True] * (n + 1) + l = [True] * (n + 1) # noqa: E741 prime = [] start = 2 end = int(math.sqrt(n)) diff --git a/maths/square_root.py b/maths/square_root.py index d4c5e311b0b7..fe775828c8c5 100644 --- a/maths/square_root.py +++ b/maths/square_root.py @@ -24,10 +24,10 @@ def square_root_iterative( """ Square root is aproximated using Newtons method. https://en.wikipedia.org/wiki/Newton%27s_method - + >>> all(abs(square_root_iterative(i)-math.sqrt(i)) <= .00000000000001 for i in range(0, 500)) True - + >>> square_root_iterative(-1) Traceback (most recent call last): ... diff --git a/matrix/sherman_morrison.py b/matrix/sherman_morrison.py index 257cf33712d5..91a70d189fc1 100644 --- a/matrix/sherman_morrison.py +++ b/matrix/sherman_morrison.py @@ -11,7 +11,7 @@ def __init__(self, row: int, column: int, default_value: float = 0): Example: >>> a = Matrix(2, 3, 1) - >>> a + >>> a Matrix consist of 2 rows and 3 columns [1, 1, 1] [1, 1, 1] @@ -186,10 +186,10 @@ def transpose(self): Example: >>> a = Matrix(2, 3) - >>> for r in range(2): + >>> for r in range(2): ... for c in range(3): ... a[r,c] = r*c - ... + ... >>> a.transpose() Matrix consist of 3 rows and 2 columns [0, 0] @@ -209,14 +209,14 @@ def ShermanMorrison(self, u, v): Apply Sherman-Morrison formula in O(n^2). To learn this formula, please look this: https://en.wikipedia.org/wiki/Sherman%E2%80%93Morrison_formula This method returns (A + uv^T)^(-1) where A^(-1) is self. Returns None if it's impossible to calculate. - Warning: This method doesn't check if self is invertible. + Warning: This method doesn't check if self is invertible. Make sure self is invertible before execute this method. Example: >>> ainv = Matrix(3, 3, 0) >>> for i in range(3): ainv[i,i] = 1 - ... - >>> u = Matrix(3, 1, 0) + ... + >>> u = Matrix(3, 1, 0) >>> u[0,0], u[1,0], u[2,0] = 1, 2, -3 >>> v = Matrix(3, 1, 0) >>> v[0,0], v[1,0], v[2,0] = 4, -2, 5 diff --git a/networking_flow/ford_fulkerson.py b/networking_flow/ford_fulkerson.py index 0028c7cc577f..96b782649774 100644 --- a/networking_flow/ford_fulkerson.py +++ b/networking_flow/ford_fulkerson.py @@ -16,7 +16,7 @@ def BFS(graph, s, t, parent): while queue: u = queue.pop(0) for ind in range(len(graph[u])): - if visited[ind] == False and graph[u][ind] > 0: + if visited[ind] is False and graph[u][ind] > 0: queue.append(ind) visited[ind] = True parent[ind] = u diff --git a/networking_flow/minimum_cut.py b/networking_flow/minimum_cut.py index 0f6781fbb88c..d79f3619caf1 100644 --- a/networking_flow/minimum_cut.py +++ b/networking_flow/minimum_cut.py @@ -19,7 +19,7 @@ def BFS(graph, s, t, parent): while queue: u = queue.pop(0) for ind in range(len(graph[u])): - if visited[ind] == False and graph[u][ind] > 0: + if visited[ind] is False and graph[u][ind] > 0: queue.append(ind) visited[ind] = True parent[ind] = u diff --git a/other/activity_selection.py b/other/activity_selection.py index 4e8e6c78e3f5..8876eb2930fc 100644 --- a/other/activity_selection.py +++ b/other/activity_selection.py @@ -1,7 +1,9 @@ -"""The following implementation assumes that the activities +# flake8: noqa + +"""The following implementation assumes that the activities are already sorted according to their finish time""" -"""Prints a maximum set of activities that can be done by a +"""Prints a maximum set of activities that can be done by a single person, one at a time""" # n --> Total number of activities # start[]--> An array that contains start time of all activities @@ -10,8 +12,8 @@ def printMaxActivities(start, finish): """ - >>> start = [1, 3, 0, 5, 8, 5] - >>> finish = [2, 4, 6, 7, 9, 9] + >>> start = [1, 3, 0, 5, 8, 5] + >>> finish = [2, 4, 6, 7, 9, 9] >>> printMaxActivities(start, finish) The following activities are selected: 0 1 3 4 diff --git a/other/anagrams.py b/other/anagrams.py index 471413194498..0be013d5bc47 100644 --- a/other/anagrams.py +++ b/other/anagrams.py @@ -1,4 +1,7 @@ -import collections, pprint, time, os +import collections +import os +import pprint +import time start_time = time.time() print("creating word list...") diff --git a/other/detecting_english_programmatically.py b/other/detecting_english_programmatically.py index 4b0bb37ce520..44fb7191866b 100644 --- a/other/detecting_english_programmatically.py +++ b/other/detecting_english_programmatically.py @@ -55,6 +55,7 @@ def isEnglish(message, wordPercentage=20, letterPercentage=85): return wordsMatch and lettersMatch -import doctest +if __name__ == "__main__": + import doctest -doctest.testmod() + doctest.testmod() diff --git a/other/dijkstra_bankers_algorithm.py b/other/dijkstra_bankers_algorithm.py index 1f78941d3afc..ab4fba4c3bd1 100644 --- a/other/dijkstra_bankers_algorithm.py +++ b/other/dijkstra_bankers_algorithm.py @@ -145,7 +145,7 @@ def main(self, **kwargs) -> None: Process 5 is executing. Updated available resource stack for processes: 8 5 9 7 The process is in a safe state. - + """ need_list = self.__need() alloc_resources_table = self.__allocated_resources_table diff --git a/other/game_of_life.py b/other/game_of_life.py index 2b4d1116fa8c..688ee1f282b3 100644 --- a/other/game_of_life.py +++ b/other/game_of_life.py @@ -1,4 +1,4 @@ -"""Conway's Game Of Life, Author Anurag Kumar(mailto:anuragkumarak95@gmail.com) +"""Conway's Game Of Life, Author Anurag Kumar(mailto:anuragkumarak95@gmail.com) Requirements: - numpy @@ -13,7 +13,7 @@ - $python3 game_o_life Game-Of-Life Rules: - + 1. Any live cell with fewer than two live neighbours dies, as if caused by under-population. @@ -27,8 +27,10 @@ Any dead cell with exactly three live neighbours be- comes a live cell, as if by reproduction. """ +import random +import sys + import numpy as np -import random, sys from matplotlib import pyplot as plt from matplotlib.colors import ListedColormap diff --git a/other/integeration_by_simpson_approx.py b/other/integeration_by_simpson_approx.py index 0f7bfacf030a..f88d3a0f0173 100644 --- a/other/integeration_by_simpson_approx.py +++ b/other/integeration_by_simpson_approx.py @@ -43,14 +43,14 @@ def simpson_integration(function, a: float, b: float, precision: int = 4) -> flo Returns: result : the value of the approximated integration of function in range a to b - + Raises: AssertionError: function is not callable AssertionError: a is not float or integer AssertionError: function should return float or integer AssertionError: b is not float or integer AssertionError: precision is not positive integer - + >>> simpson_integration(lambda x : x*x,1,2,3) 2.333 @@ -72,7 +72,7 @@ def simpson_integration(function, a: float, b: float, precision: int = 4) -> flo Traceback (most recent call last): ... AssertionError: the function(object) passed should be callable your input : wrong_input - + >>> simpson_integration(lambda x : x*x,3.45,3.2,1) -2.8 @@ -85,7 +85,7 @@ def simpson_integration(function, a: float, b: float, precision: int = 4) -> flo Traceback (most recent call last): ... AssertionError: precision should be positive integer your input : -1 - + """ assert callable( function diff --git a/other/magicdiamondpattern.py b/other/magicdiamondpattern.py index 4ca698d80c28..37b5e4809f47 100644 --- a/other/magicdiamondpattern.py +++ b/other/magicdiamondpattern.py @@ -1,5 +1,6 @@ # Python program for generating diamond pattern in Python 3.7+ + # Function to print upper half of diamond (pyramid) def floyd(n): """ diff --git a/other/sdes.py b/other/sdes.py index 3038ff193ae9..cfc5a53df2b2 100644 --- a/other/sdes.py +++ b/other/sdes.py @@ -44,9 +44,9 @@ def function(expansion, s0, s1, key, message): right = message[4:] temp = apply_table(right, expansion) temp = XOR(temp, key) - l = apply_sbox(s0, temp[:4]) + l = apply_sbox(s0, temp[:4]) # noqa: E741 r = apply_sbox(s1, temp[4:]) - l = "0" * (2 - len(l)) + l + l = "0" * (2 - len(l)) + l # noqa: E741 r = "0" * (2 - len(r)) + r temp = apply_table(l + r, p4_table) temp = XOR(left, temp) diff --git a/project_euler/problem_02/sol4.py b/project_euler/problem_02/sol4.py index 92ea0a51e026..be4328941aa3 100644 --- a/project_euler/problem_02/sol4.py +++ b/project_euler/problem_02/sol4.py @@ -48,7 +48,7 @@ def solution(n): """ try: n = int(n) - except (TypeError, ValueError) as e: + except (TypeError, ValueError): raise TypeError("Parameter n must be int or passive of cast to int.") if n <= 0: raise ValueError("Parameter n must be greater or equal to one.") diff --git a/project_euler/problem_03/sol1.py b/project_euler/problem_03/sol1.py index 9f8ecc5e6565..347f8a53f5b4 100644 --- a/project_euler/problem_03/sol1.py +++ b/project_euler/problem_03/sol1.py @@ -50,7 +50,7 @@ def solution(n): """ try: n = int(n) - except (TypeError, ValueError) as e: + except (TypeError, ValueError): raise TypeError("Parameter n must be int or passive of cast to int.") if n <= 0: raise ValueError("Parameter n must be greater or equal to one.") diff --git a/project_euler/problem_03/sol2.py b/project_euler/problem_03/sol2.py index b6fad079fa31..daac041d4bd9 100644 --- a/project_euler/problem_03/sol2.py +++ b/project_euler/problem_03/sol2.py @@ -37,7 +37,7 @@ def solution(n): """ try: n = int(n) - except (TypeError, ValueError) as e: + except (TypeError, ValueError): raise TypeError("Parameter n must be int or passive of cast to int.") if n <= 0: raise ValueError("Parameter n must be greater or equal to one.") diff --git a/project_euler/problem_05/sol1.py b/project_euler/problem_05/sol1.py index b3a231f4dcf5..f8d83fc12b71 100644 --- a/project_euler/problem_05/sol1.py +++ b/project_euler/problem_05/sol1.py @@ -41,7 +41,7 @@ def solution(n): """ try: n = int(n) - except (TypeError, ValueError) as e: + except (TypeError, ValueError): raise TypeError("Parameter n must be int or passive of cast to int.") if n <= 0: raise ValueError("Parameter n must be greater or equal to one.") diff --git a/project_euler/problem_07/sol2.py b/project_euler/problem_07/sol2.py index 6bfc5881f548..ec182b835c84 100644 --- a/project_euler/problem_07/sol2.py +++ b/project_euler/problem_07/sol2.py @@ -50,7 +50,7 @@ def solution(n): """ try: n = int(n) - except (TypeError, ValueError) as e: + except (TypeError, ValueError): raise TypeError("Parameter n must be int or passive of cast to int.") if n <= 0: raise ValueError("Parameter n must be greater or equal to one.") diff --git a/project_euler/problem_08/sol1.py b/project_euler/problem_08/sol1.py index e7582d46c351..1cccdb8c85d6 100644 --- a/project_euler/problem_08/sol1.py +++ b/project_euler/problem_08/sol1.py @@ -53,7 +53,7 @@ def solution(n): """Find the thirteen adjacent digits in the 1000-digit number n that have the greatest product and returns it. - + >>> solution(N) 23514624000 """ diff --git a/project_euler/problem_08/sol2.py b/project_euler/problem_08/sol2.py index bf8afa8379ee..60bd8254f2c3 100644 --- a/project_euler/problem_08/sol2.py +++ b/project_euler/problem_08/sol2.py @@ -56,7 +56,7 @@ def solution(n): """Find the thirteen adjacent digits in the 1000-digit number n that have the greatest product and returns it. - + >>> solution(N) 23514624000 """ diff --git a/project_euler/problem_08/sol3.py b/project_euler/problem_08/sol3.py index dfbef5755dd7..f3e87c6d3436 100644 --- a/project_euler/problem_08/sol3.py +++ b/project_euler/problem_08/sol3.py @@ -60,7 +60,7 @@ def streval(s: str) -> int: def solution(n: str) -> int: """Find the thirteen adjacent digits in the 1000-digit number n that have the greatest product and returns it. - + >>> solution(N) 23514624000 """ diff --git a/project_euler/problem_11/sol2.py b/project_euler/problem_11/sol2.py index 64702e852b0f..1482fc7d3b04 100644 --- a/project_euler/problem_11/sol2.py +++ b/project_euler/problem_11/sol2.py @@ -34,7 +34,7 @@ def solution(): 70600674 """ with open(os.path.dirname(__file__) + "/grid.txt") as f: - l = [] + l = [] # noqa: E741 for i in range(20): l.append([int(x) for x in f.readline().split()]) diff --git a/project_euler/problem_16/sol2.py b/project_euler/problem_16/sol2.py index 88672e9a9e54..cd724d89a9e3 100644 --- a/project_euler/problem_16/sol2.py +++ b/project_euler/problem_16/sol2.py @@ -7,7 +7,7 @@ def solution(power): """Returns the sum of the digits of the number 2^power. - + >>> solution(1000) 1366 >>> solution(50) diff --git a/project_euler/problem_234/sol1.py b/project_euler/problem_234/sol1.py index 28d82b550c85..b65a506d1def 100644 --- a/project_euler/problem_234/sol1.py +++ b/project_euler/problem_234/sol1.py @@ -40,7 +40,7 @@ def solution(n): """Returns the sum of all semidivisible numbers not exceeding n.""" semidivisible = [] for x in range(n): - l = [i for i in input().split()] + l = [i for i in input().split()] # noqa: E741 c2 = 1 while 1: if len(fib(l[0], l[1], c2)) < int(l[2]): diff --git a/project_euler/problem_30/soln.py b/project_euler/problem_30/soln.py index 9d45739845a3..829ddb0fb9cc 100644 --- a/project_euler/problem_30/soln.py +++ b/project_euler/problem_30/soln.py @@ -10,7 +10,7 @@ The sum of these numbers is 1634 + 8208 + 9474 = 19316. Find the sum of all the numbers that can be written as the sum of fifth powers of their digits. - + (9^5)=59,049‬ 59049*7=4,13,343 (which is only 6 digit number ) So, number greater than 9,99,999 are rejected diff --git a/project_euler/problem_31/sol2.py b/project_euler/problem_31/sol2.py index 1f006f1a1824..b390b5b1efe5 100644 --- a/project_euler/problem_31/sol2.py +++ b/project_euler/problem_31/sol2.py @@ -30,7 +30,7 @@ def solution(pence: int) -> int: - """Returns the number of different ways to make X pence using any number of coins. + """Returns the number of different ways to make X pence using any number of coins. The solution is based on dynamic programming paradigm in a bottom-up fashion. >>> solution(500) diff --git a/project_euler/problem_551/sol1.py b/project_euler/problem_551/sol1.py index 873e520cc9b4..bbdd4d6b039d 100644 --- a/project_euler/problem_551/sol1.py +++ b/project_euler/problem_551/sol1.py @@ -28,7 +28,7 @@ def next_term(a_i, k, i, n): is cached to greatly speed up the computation. Arguments: - a_i -- array of digits starting from the one's place that represent + a_i -- array of digits starting from the one's place that represent the i-th term in the sequence k -- k when terms are written in the from a(i) = b*10^k + c. Term are calulcated until c > 10^k or the n-th term is reached. diff --git a/scheduling/first_come_first_served.py b/scheduling/first_come_first_served.py index f52c4243dec3..163f5257f361 100644 --- a/scheduling/first_come_first_served.py +++ b/scheduling/first_come_first_served.py @@ -28,7 +28,7 @@ def calculate_turnaround_times( ) -> List[int]: """ This function calculates the turnaround time of some processes. - Return: The time difference between the completion time and the + Return: The time difference between the completion time and the arrival time. Practically waiting_time + duration_time >>> calculate_turnaround_times([5, 10, 15], [0, 5, 15]) diff --git a/searches/simulated_annealing.py b/searches/simulated_annealing.py index c24adc1ddb41..6a4a8638632d 100644 --- a/searches/simulated_annealing.py +++ b/searches/simulated_annealing.py @@ -1,5 +1,7 @@ # https://en.wikipedia.org/wiki/Simulated_annealing -import math, random +import math +import random + from hill_climbing import SearchProblem diff --git a/searches/ternary_search.py b/searches/ternary_search.py index 5ecc47644248..6fdee58cf5dc 100644 --- a/searches/ternary_search.py +++ b/searches/ternary_search.py @@ -12,6 +12,7 @@ # It is recommended for users to keep this number greater than or equal to 10. precision = 10 + # This is the linear search that will occur after the search space has become smaller. def lin_search(left, right, A, target): for i in range(left, right + 1): diff --git a/sorts/bitonic_sort.py b/sorts/bitonic_sort.py index ce80c6028729..be3499de13cd 100644 --- a/sorts/bitonic_sort.py +++ b/sorts/bitonic_sort.py @@ -1,9 +1,10 @@ # Python program for Bitonic Sort. Note that this program # works only when size of input is a power of 2. + # The parameter dir indicates the sorting direction, ASCENDING # or DESCENDING; if (a[i] > a[j]) agrees with the direction, -# then a[i] and a[j] are interchanged.*/ +# then a[i] and a[j] are interchanged. def compAndSwap(a, i, j, dire): if (dire == 1 and a[i] > a[j]) or (dire == 0 and a[i] < a[j]): a[i], a[j] = a[j], a[i] diff --git a/sorts/pigeon_sort.py b/sorts/pigeon_sort.py index cf900699bc8d..cc6205f804dc 100644 --- a/sorts/pigeon_sort.py +++ b/sorts/pigeon_sort.py @@ -1,11 +1,11 @@ """ This is an implementation of Pigeon Hole Sort. For doctests run following command: - + python3 -m doctest -v pigeon_sort.py or python -m doctest -v pigeon_sort.py - + For manual testing run: python pigeon_sort.py """ diff --git a/sorts/recursive_bubble_sort.py b/sorts/recursive_bubble_sort.py index 616044778a4a..79d706e6164d 100644 --- a/sorts/recursive_bubble_sort.py +++ b/sorts/recursive_bubble_sort.py @@ -21,7 +21,7 @@ def bubble_sort(list1): >>> bubble_sort(['z','a','y','b','x','c']) ['a', 'b', 'c', 'x', 'y', 'z'] - + """ for i, num in enumerate(list1): diff --git a/strings/boyer_moore_search.py b/strings/boyer_moore_search.py index bd777c7c7e05..4bd6aff27bf3 100644 --- a/strings/boyer_moore_search.py +++ b/strings/boyer_moore_search.py @@ -1,15 +1,15 @@ """ The algorithm finds the pattern in given text using following rule. -The bad-character rule considers the mismatched character in Text. -The next occurrence of that character to the left in Pattern is found, +The bad-character rule considers the mismatched character in Text. +The next occurrence of that character to the left in Pattern is found, -If the mismatched character occurs to the left in Pattern, -a shift is proposed that aligns text block and pattern. +If the mismatched character occurs to the left in Pattern, +a shift is proposed that aligns text block and pattern. -If the mismatched character does not occur to the left in Pattern, -a shift is proposed that moves the entirety of Pattern past -the point of mismatch in the text. +If the mismatched character does not occur to the left in Pattern, +a shift is proposed that moves the entirety of Pattern past +the point of mismatch in the text. If there no mismatch then the pattern matches with text block. @@ -27,12 +27,12 @@ def __init__(self, text, pattern): def match_in_pattern(self, char): """ finds the index of char in pattern in reverse order - Parameters : + Parameters : char (chr): character to be searched - + Returns : i (int): index of char from last in pattern - -1 (int): if char is not found in pattern + -1 (int): if char is not found in pattern """ for i in range(self.patLen - 1, -1, -1): @@ -43,9 +43,9 @@ def match_in_pattern(self, char): def mismatch_in_text(self, currentPos): """ finds the index of mis-matched character in text when compared with pattern from last - Parameters : + Parameters : currentPos (int): current index position of text - + Returns : i (int): index of mismatched char from last in text -1 (int): if there is no mismatch between pattern and text block diff --git a/strings/lower.py b/strings/lower.py index c3a6e598b9ea..222b8d443289 100644 --- a/strings/lower.py +++ b/strings/lower.py @@ -1,15 +1,15 @@ def lower(word: str) -> str: - """ - Will convert the entire string to lowecase letters - + """ + Will convert the entire string to lowecase letters + >>> lower("wow") 'wow' >>> lower("HellZo") 'hellzo' >>> lower("WHAT") 'what' - + >>> lower("wh[]32") 'wh[]32' >>> lower("whAT") diff --git a/strings/manacher.py b/strings/manacher.py index 95aba1fbe65d..73b31a7bea9f 100644 --- a/strings/manacher.py +++ b/strings/manacher.py @@ -9,9 +9,10 @@ def palindromic_string(input_string): 1. first this convert input_string("xyx") into new_string("x|y|x") where odd positions are actual input characters. - 2. for each character in new_string it find corresponding length and store the length - and l,r to store previously calculated info.(please look the explanation for details) - + 2. for each character in new_string it find corresponding length and store the + length and l,r to store previously calculated info.(please look the explanation + for details) + 3. return corresponding output_string by removing all "|" """ max_length = 0 @@ -26,7 +27,8 @@ def palindromic_string(input_string): # append last character new_input_string += input_string[-1] - # we will store the starting and ending of previous furthest ending palindromic substring + # we will store the starting and ending of previous furthest ending palindromic + # substring l, r = 0, 0 # length[i] shows the length of palindromic substring with center i @@ -47,7 +49,7 @@ def palindromic_string(input_string): # does this string is ending after the previously explored end (that is r) ? # if yes the update the new r to the last index of this if i + k - 1 > r: - l = i - k + 1 + l = i - k + 1 # noqa: E741 r = i + k - 1 # update max_length and start position @@ -72,32 +74,34 @@ def palindromic_string(input_string): """ ...a0...a1...a2.....a3......a4...a5...a6.... -consider the string for which we are calculating the longest palindromic substring is shown above where ... -are some characters in between and right now we are calculating the length of palindromic substring with -center at a5 with following conditions : -i) we have stored the length of palindromic substring which has center at a3 (starts at l ends at r) and it - is the furthest ending till now, and it has ending after a6 +consider the string for which we are calculating the longest palindromic substring is +shown above where ... are some characters in between and right now we are calculating +the length of palindromic substring with center at a5 with following conditions : +i) we have stored the length of palindromic substring which has center at a3 (starts at + l ends at r) and it is the furthest ending till now, and it has ending after a6 ii) a2 and a4 are equally distant from a3 so char(a2) == char(a4) iii) a0 and a6 are equally distant from a3 so char(a0) == char(a6) -iv) a1 is corresponding equal character of a5 in palindrome with center a3 (remember that in below derivation of a4==a6) +iv) a1 is corresponding equal character of a5 in palindrome with center a3 (remember + that in below derivation of a4==a6) -now for a5 we will calculate the length of palindromic substring with center as a5 but can we use previously -calculated information in some way? -Yes, look the above string we know that a5 is inside the palindrome with center a3 and previously we have -have calculated that +now for a5 we will calculate the length of palindromic substring with center as a5 but +can we use previously calculated information in some way? +Yes, look the above string we know that a5 is inside the palindrome with center a3 and +previously we have have calculated that a0==a2 (palindrome of center a1) a2==a4 (palindrome of center a3) a0==a6 (palindrome of center a3) so a4==a6 -so we can say that palindrome at center a5 is at least as long as palindrome at center a1 -but this only holds if a0 and a6 are inside the limits of palindrome centered at a3 so finally .. +so we can say that palindrome at center a5 is at least as long as palindrome at center +a1 but this only holds if a0 and a6 are inside the limits of palindrome centered at a3 +so finally .. len_of_palindrome__at(a5) = min(len_of_palindrome_at(a1), r-a5) where a3 lies from l to r and we have to keep updating that -and if the a5 lies outside of l,r boundary we calculate length of palindrome with bruteforce and update -l,r. +and if the a5 lies outside of l,r boundary we calculate length of palindrome with +bruteforce and update l,r. it gives the linear time complexity just like z-function """ diff --git a/strings/reverse_words.py b/strings/reverse_words.py index 6b5cc6b04039..8ab060fe1d24 100644 --- a/strings/reverse_words.py +++ b/strings/reverse_words.py @@ -11,10 +11,7 @@ def reverse_words(input_str: str) -> str: >>> reverse_words(sentence) 'Python love I' """ - input_str = input_str.split(" ") - new_str = list() - - return " ".join(reversed(input_str)) + return " ".join(reversed(input_str.split(" "))) if __name__ == "__main__": diff --git a/strings/split.py b/strings/split.py index d5bff316429f..d614bd88478f 100644 --- a/strings/split.py +++ b/strings/split.py @@ -1,16 +1,16 @@ def split(string: str, separator: str = " ") -> list: """ Will split the string up into all the values separated by the separator (defaults to spaces) - + >>> split("apple#banana#cherry#orange",separator='#') ['apple', 'banana', 'cherry', 'orange'] - + >>> split("Hello there") ['Hello', 'there'] - + >>> split("11/22/63",separator = '/') ['11', '22', '63'] - + >>> split("12:43:39",separator = ":") ['12', '43', '39'] """ diff --git a/strings/upper.py b/strings/upper.py index 59b16096af0b..96b52878e05e 100644 --- a/strings/upper.py +++ b/strings/upper.py @@ -1,14 +1,14 @@ def upper(word: str) -> str: - """ - Will convert the entire string to uppercase letters - + """ + Will convert the entire string to uppercase letters + >>> upper("wow") 'WOW' >>> upper("Hello") 'HELLO' >>> upper("WHAT") 'WHAT' - + >>> upper("wh[]32") 'WH[]32' """ diff --git a/traversals/binary_tree_traversals.py b/traversals/binary_tree_traversals.py index c522ecebc0ff..7f100e66f200 100644 --- a/traversals/binary_tree_traversals.py +++ b/traversals/binary_tree_traversals.py @@ -1,3 +1,5 @@ +# flake8: noqa + """ This is pure Python implementation of tree traversal algorithms """ @@ -144,7 +146,7 @@ def level_order_actual(node: TreeNode) -> None: >>> root.left, root.right = tree_node2, tree_node3 >>> tree_node2.left, tree_node2.right = tree_node4 , tree_node5 >>> tree_node3.left, tree_node3.right = tree_node6 , tree_node7 - >>> level_order_actual(root) + >>> level_order_actual(root) 1 2 3 4 5 6 7 From a15f82579d150f5e5678375b3cf1df3e7c12f223 Mon Sep 17 00:00:00 2001 From: mateuszz0000 Date: Fri, 22 May 2020 09:41:40 +0200 Subject: [PATCH 0584/1071] Added bead sort (#2022) * Added bead sort * Commit suggestion * Added checking before sort * Bead sort only works for sequences of nonegative integers Co-authored-by: Christian Clauss --- sorts/bead_sort.py | 43 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 sorts/bead_sort.py diff --git a/sorts/bead_sort.py b/sorts/bead_sort.py new file mode 100644 index 000000000000..3767e842d8c2 --- /dev/null +++ b/sorts/bead_sort.py @@ -0,0 +1,43 @@ +""" +Bead sort only works for sequences of nonegative integers. +https://en.wikipedia.org/wiki/Bead_sort +""" + + +def bead_sort(sequence: list) -> list: + """ + >>> bead_sort([6, 11, 12, 4, 1, 5]) + [1, 4, 5, 6, 11, 12] + + >>> bead_sort([9, 8, 7, 6, 5, 4 ,3, 2, 1]) + [1, 2, 3, 4, 5, 6, 7, 8, 9] + + >>> bead_sort([5, 0, 4, 3]) + [0, 3, 4, 5] + + >>> bead_sort([8, 2, 1]) + [1, 2, 8] + + >>> bead_sort([1, .9, 0.0, 0, -1, -.9]) + Traceback (most recent call last): + ... + TypeError: Sequence must be list of nonnegative integers + + >>> bead_sort("Hello world") + Traceback (most recent call last): + ... + TypeError: Sequence must be list of nonnegative integers + """ + if any(not isinstance(x, int) or x < 0 for x in sequence): + raise TypeError("Sequence must be list of nonnegative integers") + for _ in range(len(sequence)): + for i, (rod_upper, rod_lower) in enumerate(zip(sequence, sequence[1:])): + if rod_upper > rod_lower: + sequence[i] -= rod_upper - rod_lower + sequence[i + 1] += rod_upper - rod_lower + return sequence + + +if __name__ == "__main__": + assert bead_sort([5, 4, 3, 2, 1]) == [1, 2, 3, 4, 5] + assert bead_sort([7, 9, 4, 3, 5]) == [3, 4, 5, 7, 9] From d8a4faf96d940737ddc488aa8817bc3563ab1a82 Mon Sep 17 00:00:00 2001 From: Ashwin Das Date: Fri, 22 May 2020 15:27:23 +0530 Subject: [PATCH 0585/1071] Update is_palindrome.py (#2025) * Update is_palindrome.py * Update is_palindrome.py * Reuse s Co-authored-by: Christian Clauss --- strings/is_palindrome.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/strings/is_palindrome.py b/strings/is_palindrome.py index 3070970ca6d0..a0795b7b3783 100644 --- a/strings/is_palindrome.py +++ b/strings/is_palindrome.py @@ -1,4 +1,4 @@ -def is_palindrome(s): +def is_palindrome(s: str) -> bool: """ Determine whether the string is palindrome :param s: @@ -7,7 +7,16 @@ def is_palindrome(s): True >>> is_palindrome("Hello") False + >>> is_palindrome("Able was I ere I saw Elba") + True + >>> is_palindrome("racecar") + True + >>> is_palindrome("Mr. Owl ate my metal worm?") + True """ + # Since Punctuation, capitalization, and spaces are usually ignored while checking Palindrome, + # we first remove them from our string. + s = "".join([character for character in s.lower() if character.isalnum()]) return s == s[::-1] From 025b1a69896f00438d9d2bc0082ed4e4ec31fad9 Mon Sep 17 00:00:00 2001 From: Ekansh Mangal <43078195+EkanshMangal@users.noreply.github.com> Date: Sun, 24 May 2020 12:08:43 +0530 Subject: [PATCH 0586/1071] Merge sort Update variable names (#2032) * Update Merge sort variable names * Update mergesort.py * Update mergesort.py * black * Update mergesort.py Co-authored-by: Christian Clauss --- divide_and_conquer/mergesort.py | 47 ++++++++++++++++++--------------- 1 file changed, 25 insertions(+), 22 deletions(-) diff --git a/divide_and_conquer/mergesort.py b/divide_and_conquer/mergesort.py index 328e3dca316f..f31a57251ce6 100644 --- a/divide_and_conquer/mergesort.py +++ b/divide_and_conquer/mergesort.py @@ -1,45 +1,48 @@ -def merge(a, b, m, e): - l = a[b : m + 1] # noqa: E741 - r = a[m + 1 : e + 1] - k = b +def merge(arr, left, mid, right): + # overall array will divided into 2 array + # left_arr contains the left portion of array from left to mid + # right_arr contains the right portion of array from mid + 1 to right + left_arr = arr[left : mid + 1] + right_arr = arr[mid + 1 : right + 1] + k = left i = 0 j = 0 - while i < len(l) and j < len(r): + while i < len(left_arr) and j < len(right_arr): # change sign for Descending order - if l[i] < r[j]: - a[k] = l[i] + if left_arr[i] < right_arr[j]: + arr[k] = left_arr[i] i += 1 else: - a[k] = r[j] + arr[k] = right_arr[j] j += 1 k += 1 - while i < len(l): - a[k] = l[i] + while i < len(left_arr): + arr[k] = left_arr[i] i += 1 k += 1 - while j < len(r): - a[k] = r[j] + while j < len(right_arr): + arr[k] = right_arr[j] j += 1 k += 1 - return a + return arr -def mergesort(a, b, e): +def mergesort(arr, left, right): """ - >>> mergesort([3,2,1],0,2) + >>> mergesort([3, 2, 1], 0, 2) [1, 2, 3] - >>> mergesort([3,2,1,0,1,2,3,5,4],0,8) + >>> mergesort([3, 2, 1, 0, 1, 2, 3, 5, 4], 0, 8) [0, 1, 1, 2, 2, 3, 3, 4, 5] """ - if b < e: - m = (b + e) // 2 + if left < right: + mid = (left + right) // 2 # print("ms1",a,b,m) - mergesort(a, b, m) + mergesort(arr, left, mid) # print("ms2",a,m+1,e) - mergesort(a, m + 1, e) + mergesort(arr, mid + 1, right) # print("m",a,b,m,e) - merge(a, b, m, e) - return a + merge(arr, left, mid, right) + return arr if __name__ == "__main__": From dc4049ee289a7e3f1d0f895b1d877f0b7a016395 Mon Sep 17 00:00:00 2001 From: Christian Clauss Date: Sun, 24 May 2020 17:08:28 +0200 Subject: [PATCH 0587/1071] .travis.yml: Revert to using autoblack (#2033) * .travis.yml: Revert to using autoblack Our autoblack GitHub Action will get us to black compliance without forcing each contributor to learn about, install, and use psf/black on every pull request. * updating DIRECTORY.md Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> --- .travis.yml | 2 +- DIRECTORY.md | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index c5c032c290b8..24140a5d0cfa 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,7 +6,7 @@ cache: pip before_install: pip install --upgrade pip setuptools six install: pip install black flake8 before_script: - - black --check . + - black --check . || true - flake8 --ignore=E203,W503 --max-complexity=25 --max-line-length=120 --statistics --count . - scripts/validate_filenames.py # no uppercase, no spaces, in a directory - pip install -r requirements.txt # fast fail on black, flake8, validate_filenames diff --git a/DIRECTORY.md b/DIRECTORY.md index 2bb18897044f..38fa2430351f 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -575,6 +575,7 @@ * [Ternary Search](https://github.com/TheAlgorithms/Python/blob/master/searches/ternary_search.py) ## Sorts + * [Bead Sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/bead_sort.py) * [Bitonic Sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/bitonic_sort.py) * [Bogo Sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/bogo_sort.py) * [Bubble Sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/bubble_sort.py) From bb5552efd0fea2a8e83aef4321d9686acb150b59 Mon Sep 17 00:00:00 2001 From: mateuszz0000 Date: Mon, 25 May 2020 12:32:57 +0200 Subject: [PATCH 0588/1071] Euclidean recursive method + doctests + type hints (#1999) * Recursive euclidean algorithm + doctests and type hints * Fix doctests in recursive method * Added commit suggestions --- other/euclidean_gcd.py | 46 +++++++++++++++++++++++++++++++++--------- 1 file changed, 36 insertions(+), 10 deletions(-) diff --git a/other/euclidean_gcd.py b/other/euclidean_gcd.py index c6c11f947a08..de4b250243db 100644 --- a/other/euclidean_gcd.py +++ b/other/euclidean_gcd.py @@ -1,20 +1,46 @@ -# https://en.wikipedia.org/wiki/Euclidean_algorithm +""" https://en.wikipedia.org/wiki/Euclidean_algorithm """ -def euclidean_gcd(a, b): +def euclidean_gcd(a: int, b: int) -> int: + """ + Examples: + >>> euclidean_gcd(3, 5) + 1 + + >>> euclidean_gcd(6, 3) + 3 + """ while b: - t = b - b = a % b - a = t + a, b = b, a % b return a +def euclidean_gcd_recursive(a: int, b: int) -> int: + """ + Recursive method for euclicedan gcd algorithm + + Examples: + >>> euclidean_gcd_recursive(3, 5) + 1 + + >>> euclidean_gcd_recursive(6, 3) + 3 + """ + return a if b == 0 else euclidean_gcd_recursive(b, a % b) + + def main(): - print("GCD(3, 5) = " + str(euclidean_gcd(3, 5))) - print("GCD(5, 3) = " + str(euclidean_gcd(5, 3))) - print("GCD(1, 3) = " + str(euclidean_gcd(1, 3))) - print("GCD(3, 6) = " + str(euclidean_gcd(3, 6))) - print("GCD(6, 3) = " + str(euclidean_gcd(6, 3))) + print(f"euclidean_gcd(3, 5) = {euclidean_gcd(3, 5)}") + print(f"euclidean_gcd(5, 3) = {euclidean_gcd(5, 3)}") + print(f"euclidean_gcd(1, 3) = {euclidean_gcd(1, 3)}") + print(f"euclidean_gcd(3, 6) = {euclidean_gcd(3, 6)}") + print(f"euclidean_gcd(6, 3) = {euclidean_gcd(6, 3)}") + + print(f"euclidean_gcd_recursive(3, 5) = {euclidean_gcd_recursive(3, 5)}") + print(f"euclidean_gcd_recursive(5, 3) = {euclidean_gcd_recursive(5, 3)}") + print(f"euclidean_gcd_recursive(1, 3) = {euclidean_gcd_recursive(1, 3)}") + print(f"euclidean_gcd_recursive(3, 6) = {euclidean_gcd_recursive(3, 6)}") + print(f"euclidean_gcd_recursive(6, 3) = {euclidean_gcd_recursive(6, 3)}") if __name__ == "__main__": From 0e619065e72a7eb0647547ac50def5e7766563d1 Mon Sep 17 00:00:00 2001 From: Nitisha Bharathi <30657775+nitishabharathi@users.noreply.github.com> Date: Mon, 25 May 2020 18:40:54 +0530 Subject: [PATCH 0589/1071] added Boruvka's MST algorithm (#2026) * added Boruvka's MST algorithm * Add files via upload * fixup! Format Python code with psf/black push * Updated Boruvka with doctest * updating DIRECTORY.md * Update minimum_spanning_tree_boruvka.py * No blank line in doctest * * Avoid mutable default values https://docs.python-guide.org/writing/gotchas/ * Update minimum_spanning_tree_boruvka.py * Avoid mutable default values * fixup! Format Python code with psf/black push * Update minimum_spanning_tree_boruvka.py * Update minimum_spanning_tree_boruvka.py Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> Co-authored-by: Christian Clauss --- DIRECTORY.md | 1 + graphs/minimum_spanning_tree_boruvka.py | 195 ++++++++++++++++++++++++ 2 files changed, 196 insertions(+) create mode 100644 graphs/minimum_spanning_tree_boruvka.py diff --git a/DIRECTORY.md b/DIRECTORY.md index 38fa2430351f..935755de6ff5 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -246,6 +246,7 @@ * [Greedy Best First](https://github.com/TheAlgorithms/Python/blob/master/graphs/greedy_best_first.py) * [Kahns Algorithm Long](https://github.com/TheAlgorithms/Python/blob/master/graphs/kahns_algorithm_long.py) * [Kahns Algorithm Topo](https://github.com/TheAlgorithms/Python/blob/master/graphs/kahns_algorithm_topo.py) + * [Minimum Spanning Tree Boruvka](https://github.com/TheAlgorithms/Python/blob/master/graphs/minimum_spanning_tree_boruvka.py) * [Minimum Spanning Tree Kruskal](https://github.com/TheAlgorithms/Python/blob/master/graphs/minimum_spanning_tree_kruskal.py) * [Minimum Spanning Tree Prims](https://github.com/TheAlgorithms/Python/blob/master/graphs/minimum_spanning_tree_prims.py) * [Multi Heuristic Astar](https://github.com/TheAlgorithms/Python/blob/master/graphs/multi_heuristic_astar.py) diff --git a/graphs/minimum_spanning_tree_boruvka.py b/graphs/minimum_spanning_tree_boruvka.py new file mode 100644 index 000000000000..f65aa7cef031 --- /dev/null +++ b/graphs/minimum_spanning_tree_boruvka.py @@ -0,0 +1,195 @@ +class Graph: + """ + Data structure to store graphs (based on adjacency lists) + """ + + def __init__(self): + + self.num_vertices = 0 + self.num_edges = 0 + self.adjacency = {} + + def add_vertex(self, vertex): + """ + Adds a vertex to the graph + + """ + if vertex not in self.adjacency: + self.adjacency[vertex] = {} + self.num_vertices += 1 + + def add_edge(self, head, tail, weight): + """ + Adds an edge to the graph + + """ + + self.add_vertex(head) + self.add_vertex(tail) + + if head == tail: + return + + self.adjacency[head][tail] = weight + self.adjacency[tail][head] = weight + + def distinct_weight(self): + """ + For Boruvks's algorithm the weights should be distinct + Converts the weights to be distinct + + """ + edges = self.get_edges() + for edge in edges: + head, tail, weight = edge + edges.remove((tail, head, weight)) + for i in range(len(edges)): + edges[i] = list(edges[i]) + + edges.sort(key=lambda e: e[2]) + for i in range(len(edges) - 1): + if edges[i][2] >= edges[i + 1][2]: + edges[i + 1][2] = edges[i][2] + 1 + for edge in edges: + head, tail, weight = edge + self.adjacency[head][tail] = weight + self.adjacency[tail][head] = weight + + def __str__(self): + """ + Returns string representation of the graph + """ + string = "" + for tail in self.adjacency: + for head in self.adjacency[tail]: + weight = self.adjacency[head][tail] + string += "%d -> %d == %d\n" % (head, tail, weight) + return string.rstrip("\n") + + def get_edges(self): + """ + Returna all edges in the graph + """ + output = [] + for tail in self.adjacency: + for head in self.adjacency[tail]: + output.append((tail, head, self.adjacency[head][tail])) + return output + + def get_vertices(self): + """ + Returns all vertices in the graph + """ + return self.adjacency.keys() + + @staticmethod + def build(vertices=None, edges=None): + """ + Builds a graph from the given set of vertices and edges + + """ + g = Graph() + if vertices is None: + vertices = [] + if edges is None: + edge = [] + for vertex in vertices: + g.add_vertex(vertex) + for edge in edges: + g.add_edge(*edge) + return g + + class UnionFind(object): + """ + Disjoint set Union and Find for Boruvka's algorithm + """ + + def __init__(self): + self.parent = {} + self.rank = {} + + def __len__(self): + return len(self.parent) + + def make_set(self, item): + if item in self.parent: + return self.find(item) + + self.parent[item] = item + self.rank[item] = 0 + return item + + def find(self, item): + if item not in self.parent: + return self.make_set(item) + if item != self.parent[item]: + self.parent[item] = self.find(self.parent[item]) + return self.parent[item] + + def union(self, item1, item2): + root1 = self.find(item1) + root2 = self.find(item2) + + if root1 == root2: + return root1 + + if self.rank[root1] > self.rank[root2]: + self.parent[root2] = root1 + return root1 + + if self.rank[root1] < self.rank[root2]: + self.parent[root1] = root2 + return root2 + + if self.rank[root1] == self.rank[root2]: + self.rank[root1] += 1 + self.parent[root2] = root1 + return root1 + + def boruvka_mst(graph): + """ + Implementation of Boruvka's algorithm + >>> g = Graph() + >>> g = Graph.build([0, 1, 2, 3], [[0, 1, 1], [0, 2, 1],[2, 3, 1]]) + >>> g.distinct_weight() + >>> bg = Graph.boruvka_mst(g) + >>> print(bg) + 1 -> 0 == 1 + 2 -> 0 == 2 + 0 -> 1 == 1 + 0 -> 2 == 2 + 3 -> 2 == 3 + 2 -> 3 == 3 + """ + num_components = graph.num_vertices + + union_find = Graph.UnionFind() + mst_edges = [] + while num_components > 1: + cheap_edge = {} + for vertex in graph.get_vertices(): + cheap_edge[vertex] = -1 + + edges = graph.get_edges() + for edge in edges: + head, tail, weight = edge + edges.remove((tail, head, weight)) + for edge in edges: + head, tail, weight = edge + set1 = union_find.find(head) + set2 = union_find.find(tail) + if set1 != set2: + if cheap_edge[set1] == -1 or cheap_edge[set1][2] > weight: + cheap_edge[set1] = [head, tail, weight] + + if cheap_edge[set2] == -1 or cheap_edge[set2][2] > weight: + cheap_edge[set2] = [head, tail, weight] + for vertex in cheap_edge: + if cheap_edge[vertex] != -1: + head, tail, weight = cheap_edge[vertex] + if union_find.find(head) != union_find.find(tail): + union_find.union(head, tail) + mst_edges.append(cheap_edge[vertex]) + num_components = num_components - 1 + mst = Graph.build(edges=mst_edges) + return mst From 47687356689615317d4f5f9cb846abec320f84bf Mon Sep 17 00:00:00 2001 From: KDH Date: Tue, 26 May 2020 11:18:03 +0900 Subject: [PATCH 0590/1071] Enhance shell sort syntax (#2035) --- sorts/shell_sort.py | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/sorts/shell_sort.py b/sorts/shell_sort.py index 80d95870f95b..bf3c2c7f9cc6 100644 --- a/sorts/shell_sort.py +++ b/sorts/shell_sort.py @@ -30,16 +30,11 @@ def shell_sort(collection): gaps = [701, 301, 132, 57, 23, 10, 4, 1] for gap in gaps: - i = gap - while i < len(collection): - temp = collection[i] + for i in range(gap, len(collection)): j = i - while j >= gap and collection[j - gap] > temp: - collection[j] = collection[j - gap] + while j >= gap and collection[j] < collection[j - gap]: + collection[j], collection[j - gap] = collection[j - gap], collection[j] j -= gap - collection[j] = temp - i += 1 - return collection From f8bfd0244d54563ae93e1d7b6083128465c1006d Mon Sep 17 00:00:00 2001 From: Swapnanil Dutta <47251193+swapnanildutta@users.noreply.github.com> Date: Sat, 30 May 2020 20:55:06 +0530 Subject: [PATCH 0591/1071] Created weatherforecast.py (#2037) * Created weatherforecast.py Added weatherforecast.py to retrieve weather information of a location and return dictionary values. * Update weatherforecast.py * Update and rename weatherforecast.py to current_weather.py Co-authored-by: Christian Clauss --- web_programming/current_weather.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 web_programming/current_weather.py diff --git a/web_programming/current_weather.py b/web_programming/current_weather.py new file mode 100644 index 000000000000..a35fff2d4615 --- /dev/null +++ b/web_programming/current_weather.py @@ -0,0 +1,19 @@ +from pprint import pprint + +import requests + +APPID = "" # <-- Put your OpenWeatherMap appid here! +URL_BASE = "http://api.openweathermap.org/data/2.5/weather" + + +def current_weather(location: str = "Chicago", appid: str = APPID) -> dict: + return requests.get(URL_BASE, params={"appid": appid, "q": location}).json() + + +if __name__ == "__main__": + while True: + location = input("Enter a location:").strip() + if location: + pprint(current_weather(location)) + else: + break From fa358d614a2ee0ad624fd3175fed3d06e7a9a434 Mon Sep 17 00:00:00 2001 From: Christian Clauss Date: Sat, 30 May 2020 20:17:26 +0200 Subject: [PATCH 0592/1071] current_weather, weather_forecast, weather_onecall (#2048) * current_weather, weather_forecast, weather_onecall * updating DIRECTORY.md * weather_forecast("Kolkata, India") Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> --- DIRECTORY.md | 1 + web_programming/current_weather.py | 21 ++++++++++++++++----- 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/DIRECTORY.md b/DIRECTORY.md index 935755de6ff5..78095b2645a4 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -641,6 +641,7 @@ ## Web Programming * [Crawl Google Results](https://github.com/TheAlgorithms/Python/blob/master/web_programming/crawl_google_results.py) * [Current Stock Price](https://github.com/TheAlgorithms/Python/blob/master/web_programming/current_stock_price.py) + * [Current Weather](https://github.com/TheAlgorithms/Python/blob/master/web_programming/current_weather.py) * [Emails From Url](https://github.com/TheAlgorithms/Python/blob/master/web_programming/emails_from_url.py) * [Fetch Bbc News](https://github.com/TheAlgorithms/Python/blob/master/web_programming/fetch_bbc_news.py) * [Fetch Github Info](https://github.com/TheAlgorithms/Python/blob/master/web_programming/fetch_github_info.py) diff --git a/web_programming/current_weather.py b/web_programming/current_weather.py index a35fff2d4615..e043b438473f 100644 --- a/web_programming/current_weather.py +++ b/web_programming/current_weather.py @@ -1,16 +1,27 @@ -from pprint import pprint - import requests APPID = "" # <-- Put your OpenWeatherMap appid here! -URL_BASE = "http://api.openweathermap.org/data/2.5/weather" +URL_BASE = "http://api.openweathermap.org/data/2.5/" + + +def current_weather(q: str = "Chicago", appid: str = APPID) -> dict: + """https://openweathermap.org/api""" + return requests.get(URL_BASE + "weather", params=locals()).json() -def current_weather(location: str = "Chicago", appid: str = APPID) -> dict: - return requests.get(URL_BASE, params={"appid": appid, "q": location}).json() +def weather_forecast(q: str = "Kolkata, India", appid: str = APPID) -> dict: + """https://openweathermap.org/forecast5""" + return requests.get(URL_BASE + "forecast", params=locals()).json() + + +def weather_onecall(lat: float = 55.68, lon: float = 12.57, appid: str = APPID) -> dict: + """https://openweathermap.org/api/one-call-api""" + return requests.get(URL_BASE + "onecall", params=locals()).json() if __name__ == "__main__": + from pprint import pprint + while True: location = input("Enter a location:").strip() if location: From 3357768fc394c27e6d9c364198db0fef8d87f649 Mon Sep 17 00:00:00 2001 From: mateuszz0000 Date: Sat, 30 May 2020 20:44:55 +0200 Subject: [PATCH 0593/1071] Jaro winkler (#2041) * Added jaro_winkler first version * Added doctests * Fix flake warnings * Refactor * Fixes bug in jaro winkler implementation * Commit suggestions * Missing comming suggestions * Remove unused math module * Import doctest Co-authored-by: John Law --- strings/jaro_winkler.py | 71 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 71 insertions(+) create mode 100644 strings/jaro_winkler.py diff --git a/strings/jaro_winkler.py b/strings/jaro_winkler.py new file mode 100644 index 000000000000..73827c2330c0 --- /dev/null +++ b/strings/jaro_winkler.py @@ -0,0 +1,71 @@ +"""https://en.wikipedia.org/wiki/Jaro%E2%80%93Winkler_distance""" + + +def jaro_winkler(str1: str, str2: str) -> float: + """ + Jaro–Winkler distance is a string metric measuring an edit distance between two sequences. + Output value is between 0.0 and 1.0. + + >>> jaro_winkler("martha", "marhta") + 0.9611111111111111 + >>> jaro_winkler("CRATE", "TRACE") + 0.7333333333333334 + >>> jaro_winkler("test", "dbdbdbdb") + 0.0 + >>> jaro_winkler("test", "test") + 1.0 + >>> jaro_winkler("hello world", "HeLLo W0rlD") + 0.6363636363636364 + >>> jaro_winkler("test", "") + 0.0 + >>> jaro_winkler("hello", "world") + 0.4666666666666666 + >>> jaro_winkler("hell**o", "*world") + 0.4365079365079365 + """ + + def get_matched_characters(_str1: str, _str2: str) -> str: + matched = [] + limit = min(len(_str1), len(_str2)) // 2 + for i, l in enumerate(_str1): + left = int(max(0, i - limit)) + right = int(min(i + limit + 1, len(_str2))) + if l in _str2[left:right]: + matched.append(l) + _str2 = f"{_str2[0:_str2.index(l)]} {_str2[_str2.index(l) + 1:]}" + + return ''.join(matched) + + # matching characters + matching_1 = get_matched_characters(str1, str2) + matching_2 = get_matched_characters(str2, str1) + match_count = len(matching_1) + + # transposition + transpositions = len( + [(c1, c2) for c1, c2 in zip(matching_1, matching_2) if c1 != c2] + ) // 2 + + if not match_count: + jaro = 0.0 + else: + jaro = 1 / 3 * ( + match_count / len(str1) + + match_count / len(str2) + + (match_count - transpositions) / match_count) + + # common prefix up to 4 characters + prefix_len = 0 + for c1, c2 in zip(str1[:4], str2[:4]): + if c1 == c2: + prefix_len += 1 + else: + break + + return jaro + 0.1 * prefix_len * (1 - jaro) + + +if __name__ == '__main__': + import doctest + doctest.testmod() + print(jaro_winkler("hello", "world")) From 1e8fe8efcfc499c85079659708f4afefe92ec66b Mon Sep 17 00:00:00 2001 From: Christian Clauss Date: Sun, 31 May 2020 09:36:57 +0200 Subject: [PATCH 0594/1071] circular_linked_list: Add more len() tests (#2051) * circular_linked_list: Add more len() tests * fixup! Format Python code with psf/black push * prepend() * updating DIRECTORY.md * Fix decrementation of self.length * Add empty list tests Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> --- DIRECTORY.md | 1 + .../linked_list/circular_linked_list.py | 23 ++++++++++++++++-- strings/jaro_winkler.py | 24 ++++++++++++------- 3 files changed, 37 insertions(+), 11 deletions(-) diff --git a/DIRECTORY.md b/DIRECTORY.md index 78095b2645a4..78afe07ec21f 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -622,6 +622,7 @@ * [Boyer Moore Search](https://github.com/TheAlgorithms/Python/blob/master/strings/boyer_moore_search.py) * [Check Pangram](https://github.com/TheAlgorithms/Python/blob/master/strings/check_pangram.py) * [Is Palindrome](https://github.com/TheAlgorithms/Python/blob/master/strings/is_palindrome.py) + * [Jaro Winkler](https://github.com/TheAlgorithms/Python/blob/master/strings/jaro_winkler.py) * [Knuth Morris Pratt](https://github.com/TheAlgorithms/Python/blob/master/strings/knuth_morris_pratt.py) * [Levenshtein Distance](https://github.com/TheAlgorithms/Python/blob/master/strings/levenshtein_distance.py) * [Lower](https://github.com/TheAlgorithms/Python/blob/master/strings/lower.py) diff --git a/data_structures/linked_list/circular_linked_list.py b/data_structures/linked_list/circular_linked_list.py index cf523f0a4380..290e30ebfad6 100644 --- a/data_structures/linked_list/circular_linked_list.py +++ b/data_structures/linked_list/circular_linked_list.py @@ -37,6 +37,15 @@ def __len__(self) -> int: >>> cll.append(1) >>> len(cll) 1 + >>> cll.prepend(0) + >>> len(cll) + 2 + >>> cll.delete_front() + >>> len(cll) + 1 + >>> cll.delete_rear() + >>> len(cll) + 0 """ return self.length @@ -130,6 +139,9 @@ def delete_front(self) -> None: >>> cll.delete_front() >>> print(f"{len(cll)}: {cll}") 1: + >>> cll.delete_front() + >>> print(f"{len(cll)}: {cll}") + 0: Empty linked list """ if not self.head: raise IndexError("Deleting from an empty list") @@ -137,7 +149,7 @@ def delete_front(self) -> None: current_node = self.head if current_node.next_ptr == current_node: - self.head, self.length = None, 0 + self.head = None else: while current_node.next_ptr != self.head: current_node = current_node.next_ptr @@ -146,6 +158,8 @@ def delete_front(self) -> None: self.head = self.head.next_ptr self.length -= 1 + if not self.head: + assert self.length == 0 def delete_rear(self) -> None: """ @@ -162,6 +176,9 @@ def delete_rear(self) -> None: >>> cll.delete_rear() >>> print(f"{len(cll)}: {cll}") 1: + >>> cll.delete_rear() + >>> print(f"{len(cll)}: {cll}") + 0: Empty linked list """ if not self.head: raise IndexError("Deleting from an empty list") @@ -169,7 +186,7 @@ def delete_rear(self) -> None: temp_node, current_node = self.head, self.head if current_node.next_ptr == current_node: - self.head, self.length = None, 0 + self.head = None else: while current_node.next_ptr != self.head: temp_node = current_node @@ -178,6 +195,8 @@ def delete_rear(self) -> None: temp_node.next_ptr = current_node.next_ptr self.length -= 1 + if not self.head: + assert self.length == 0 if __name__ == "__main__": diff --git a/strings/jaro_winkler.py b/strings/jaro_winkler.py index 73827c2330c0..de09538542e4 100644 --- a/strings/jaro_winkler.py +++ b/strings/jaro_winkler.py @@ -34,7 +34,7 @@ def get_matched_characters(_str1: str, _str2: str) -> str: matched.append(l) _str2 = f"{_str2[0:_str2.index(l)]} {_str2[_str2.index(l) + 1:]}" - return ''.join(matched) + return "".join(matched) # matching characters matching_1 = get_matched_characters(str1, str2) @@ -42,17 +42,22 @@ def get_matched_characters(_str1: str, _str2: str) -> str: match_count = len(matching_1) # transposition - transpositions = len( - [(c1, c2) for c1, c2 in zip(matching_1, matching_2) if c1 != c2] - ) // 2 + transpositions = ( + len([(c1, c2) for c1, c2 in zip(matching_1, matching_2) if c1 != c2]) // 2 + ) if not match_count: jaro = 0.0 else: - jaro = 1 / 3 * ( - match_count / len(str1) - + match_count / len(str2) - + (match_count - transpositions) / match_count) + jaro = ( + 1 + / 3 + * ( + match_count / len(str1) + + match_count / len(str2) + + (match_count - transpositions) / match_count + ) + ) # common prefix up to 4 characters prefix_len = 0 @@ -65,7 +70,8 @@ def get_matched_characters(_str1: str, _str2: str) -> str: return jaro + 0.1 * prefix_len * (1 - jaro) -if __name__ == '__main__': +if __name__ == "__main__": import doctest + doctest.testmod() print(jaro_winkler("hello", "world")) From 321b1425e34d850cca84ca94ce64dae3f52c3d9a Mon Sep 17 00:00:00 2001 From: Lakshmikanth2001 <52835045+Lakshmikanth2001@users.noreply.github.com> Date: Sun, 31 May 2020 15:07:45 +0530 Subject: [PATCH 0595/1071] data_structures/linked_list: Add __len__() function and tests (#2047) * Update __init__.py please add a function to get length of linked list * Update __init__.py * Update doubly_linked_list.py all size function lo doubly linked list class * prime number _better method * comments * Updated init.py 2 made it more pythonic * updated length function * commnet in linked_list construtor * Update data_structures/linked_list/__init__.py accepecting changes Co-authored-by: Christian Clauss * Update data_structures/linked_list/__init__.py Co-authored-by: Christian Clauss * Update __init__.py * Revert changes to doubly_linked_list.py * Revert changes to prime_check.py Co-authored-by: Christian Clauss --- data_structures/linked_list/__init__.py | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/data_structures/linked_list/__init__.py b/data_structures/linked_list/__init__.py index a050adba42b2..3ddfea5c5abf 100644 --- a/data_structures/linked_list/__init__.py +++ b/data_structures/linked_list/__init__.py @@ -7,9 +7,11 @@ def __init__(self, item, next): class LinkedList: def __init__(self): self.head = None + self.size = 0 def add(self, item): self.head = Node(item, self.head) + self.size += 1 def remove(self): if self.is_empty(): @@ -17,7 +19,28 @@ def remove(self): else: item = self.head.item self.head = self.head.next + self.size -= 1 return item def is_empty(self): return self.head is None + + def __len__(self): + """ + >>> linked_list = LinkedList() + >>> len(linked_list) + 0 + >>> linked_list.add("a") + >>> len(linked_list) + 1 + >>> linked_list.add("b") + >>> len(linked_list) + 2 + >>> _ = linked_list.remove() + >>> len(linked_list) + 1 + >>> _ = linked_list.remove() + >>> len(linked_list) + 0 + """ + return self.size From 1a254465e38b45ce3ed6919054b1113cf51137d9 Mon Sep 17 00:00:00 2001 From: mateuszz0000 Date: Mon, 1 Jun 2020 15:40:40 +0200 Subject: [PATCH 0596/1071] Naive string doctests + typehints (#2054) * Added doctests * Added __main__ * Commit suggestion * Undo changes to keep only doctests and typehints * Reundo function name with params * Update naive_string_search.py * Update naive_string_search.py * Update naive_string_search.py * Update naive_string_search.py Co-authored-by: Christian Clauss --- strings/naive_string_search.py | 34 ++++++++++++++++++++++------------ 1 file changed, 22 insertions(+), 12 deletions(-) diff --git a/strings/naive_string_search.py b/strings/naive_string_search.py index a8c2ea584399..f28950264121 100644 --- a/strings/naive_string_search.py +++ b/strings/naive_string_search.py @@ -1,4 +1,6 @@ """ +https://en.wikipedia.org/wiki/String-searching_algorithm#Na%C3%AFve_string_search + this algorithm tries to find the pattern from every position of the mainString if pattern is found from position i it add it to the answer and does the same for position i+1 @@ -9,14 +11,25 @@ """ -def naivePatternSearch(mainString, pattern): - patLen = len(pattern) - strLen = len(mainString) +def naive_pattern_search(s: str, pattern: str) -> list: + """ + >>> naive_pattern_search("ABAAABCDBBABCDDEBCABC", "ABC") + [4, 10, 18] + >>> naive_pattern_search("ABC", "ABAAABCDBBABCDDEBCABC") + [] + >>> naive_pattern_search("", "ABC") + [] + >>> naive_pattern_search("TEST", "TEST") + [0] + >>> naive_pattern_search("ABCDEGFTEST", "TEST") + [7] + """ + pat_len = len(pattern) position = [] - for i in range(strLen - patLen + 1): + for i in range(len(s) - pat_len + 1): match_found = True - for j in range(patLen): - if mainString[i + j] != pattern[j]: + for j in range(pat_len): + if s[i + j] != pattern[j]: match_found = False break if match_found: @@ -24,9 +37,6 @@ def naivePatternSearch(mainString, pattern): return position -mainString = "ABAAABCDBBABCDDEBCABC" -pattern = "ABC" -position = naivePatternSearch(mainString, pattern) -print("Pattern found in position ") -for x in position: - print(x) +if __name__ == "__main__": + assert naive_pattern_search("ABCDEFG", "DE") == [3] + print(f"{naive_pattern_search('ABAAABCDBBABCDDEBCABC', 'ABC') = }") From dc720a83d77a81d526da08705055a1fb9626b577 Mon Sep 17 00:00:00 2001 From: Vignesh Date: Mon, 1 Jun 2020 20:53:15 +0530 Subject: [PATCH 0597/1071] Create number_of_digits.py (#1975) * Create number_of_digits.py A python program to find the number of digits in a number. * Update number_of_digits.py * Update number_of_digits.py * Add #1976 to get Travis CI to pass #1976 * Add type hints as discussed in CONTRIBUTING.md Co-authored-by: Christian Clauss --- maths/number_of_digits.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 maths/number_of_digits.py diff --git a/maths/number_of_digits.py b/maths/number_of_digits.py new file mode 100644 index 000000000000..12717065149e --- /dev/null +++ b/maths/number_of_digits.py @@ -0,0 +1,18 @@ +def num_digits(n: int) -> int: + """ + Find the number of digits in a number. + + >>> num_digits(12345) + 5 + >>> num_digits(123) + 3 + """ + digits = 0 + while n > 0: + n = n // 10 + digits += 1 + return digits + + +if __name__ == "__main__": + print(num_digits(12345)) # ===> 5 From b080a5e027666919207b215933aa3306e1c57587 Mon Sep 17 00:00:00 2001 From: mateuszz0000 Date: Tue, 2 Jun 2020 11:51:22 +0200 Subject: [PATCH 0598/1071] Doctests + typehints in cocktail shaker sort (#2061) * Doctests in cocktail shaker sort * import doctest * print(f"{cocktail_shaker_sort(unsorted) = }") Co-authored-by: John Law Co-authored-by: Christian Clauss --- sorts/cocktail_shaker_sort.py | 25 ++++++++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) diff --git a/sorts/cocktail_shaker_sort.py b/sorts/cocktail_shaker_sort.py index ab624421a3d6..42015abc5f97 100644 --- a/sorts/cocktail_shaker_sort.py +++ b/sorts/cocktail_shaker_sort.py @@ -1,6 +1,23 @@ -def cocktail_shaker_sort(unsorted): +""" https://en.wikipedia.org/wiki/Cocktail_shaker_sort """ + + +def cocktail_shaker_sort(unsorted: list) -> list: """ Pure implementation of the cocktail shaker sort algorithm in Python. + >>> cocktail_shaker_sort([4, 5, 2, 1, 2]) + [1, 2, 2, 4, 5] + + >>> cocktail_shaker_sort([-4, 5, 0, 1, 2, 11]) + [-4, 0, 1, 2, 5, 11] + + >>> cocktail_shaker_sort([0.1, -2.4, 4.4, 2.2]) + [-2.4, 0.1, 2.2, 4.4] + + >>> cocktail_shaker_sort([1, 2, 3, 4, 5]) + [1, 2, 3, 4, 5] + + >>> cocktail_shaker_sort([-4, -5, -24, -7, -11]) + [-24, -11, -7, -5, -4] """ for i in range(len(unsorted) - 1, 0, -1): swapped = False @@ -20,7 +37,9 @@ def cocktail_shaker_sort(unsorted): if __name__ == "__main__": + import doctest + + doctest.testmod() user_input = input("Enter numbers separated by a comma:\n").strip() unsorted = [int(item) for item in user_input.split(",")] - cocktail_shaker_sort(unsorted) - print(unsorted) + print(f"{cocktail_shaker_sort(unsorted) = }") From d7cc778092ab406cd9269845262e4fc4952c51bc Mon Sep 17 00:00:00 2001 From: Shivam Verma <50954641+sarcastic-verma@users.noreply.github.com> Date: Tue, 2 Jun 2020 20:17:10 +0530 Subject: [PATCH 0599/1071] conversions/decimal_to_binary.py: Add type hints (#2001) * A .py file to covert a base to any new base(2-18) * Update base_changer.py * Added type-hints. * Delete base_changer.py --- conversions/decimal_to_binary.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/conversions/decimal_to_binary.py b/conversions/decimal_to_binary.py index ad4ba166745d..e6821c09b4a2 100644 --- a/conversions/decimal_to_binary.py +++ b/conversions/decimal_to_binary.py @@ -1,7 +1,7 @@ """Convert a Decimal Number to a Binary Number.""" -def decimal_to_binary(num): +def decimal_to_binary(num: int) -> str: """ Convert a Integer Decimal Number to a Binary Number as str. From 35319a2a2ad278a37e33bb1ed15f9afa44e5b09d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20R=C3=B6bke?= Date: Tue, 2 Jun 2020 21:14:12 +0200 Subject: [PATCH 0600/1071] Update build_directory_md.py (#2066) Propagate argument `top_dir` to good_file_paths. Previously this argument did not get passed to the helper function when calling print_directory_md. --- scripts/build_directory_md.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/build_directory_md.py b/scripts/build_directory_md.py index 9e26ee81a323..7a4bc3a4b258 100755 --- a/scripts/build_directory_md.py +++ b/scripts/build_directory_md.py @@ -31,7 +31,7 @@ def print_path(old_path: str, new_path: str) -> str: def print_directory_md(top_dir: str = ".") -> None: old_path = "" - for filepath in sorted(good_file_paths()): + for filepath in sorted(good_file_paths(top_dir)): filepath, filename = os.path.split(filepath) if filepath != old_path: old_path = print_path(old_path, filepath) From 0904610a7671e52b164157ef5d7e0cc54d75399b Mon Sep 17 00:00:00 2001 From: Vignesh Date: Wed, 3 Jun 2020 03:08:32 +0530 Subject: [PATCH 0601/1071] create sum_of_digits.py (#2065) * create sum_of_digits.py create sum_of_digits.py to find the sum of digits of a number digit_sum(12345) ---> 15 digit_sum(12345) ---> 10 * Update sum_of_digits.py * Update maths/sum_of_digits.py Co-authored-by: Christian Clauss * Update maths/sum_of_digits.py Co-authored-by: Christian Clauss * Update sum_of_digits.py Co-authored-by: Christian Clauss --- maths/sum_of_digits.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 maths/sum_of_digits.py diff --git a/maths/sum_of_digits.py b/maths/sum_of_digits.py new file mode 100644 index 000000000000..88baf2ca2ccc --- /dev/null +++ b/maths/sum_of_digits.py @@ -0,0 +1,18 @@ +def sum_of_digits(n: int) -> int: + """ + Find the sum of digits of a number. + + >>> sum_of_digits(12345) + 15 + >>> sum_of_digits(123) + 6 + """ + res = 0 + while n > 0: + res += n % 10 + n = n // 10 + return res + + +if __name__ == "__main__": + print(sum_of_digits(12345)) # ===> 15 From 7a14285cb684698ad5a4e0196444e76fdb7e4176 Mon Sep 17 00:00:00 2001 From: Jeffin Francis Date: Thu, 4 Jun 2020 19:32:51 +0530 Subject: [PATCH 0602/1071] Harris corner detection (#2064) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Added Lstm example for stock predection * Changes after review * changes after build failed * Add Kiera’s to requirements.txt * requirements.txt: Add keras and tensorflow * psf/black * haris corner detection * fixup! Format Python code with psf/black push * changes after review * changes after review * fixup! Format Python code with psf/black push Co-authored-by: Christian Clauss Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> --- computer_vision/harriscorner.py | 75 +++++++++++++++++++++++++++++++++ maths/number_of_digits.py | 2 +- 2 files changed, 76 insertions(+), 1 deletion(-) create mode 100644 computer_vision/harriscorner.py diff --git a/computer_vision/harriscorner.py b/computer_vision/harriscorner.py new file mode 100644 index 000000000000..35302f01411b --- /dev/null +++ b/computer_vision/harriscorner.py @@ -0,0 +1,75 @@ +import numpy as np +import cv2 + +""" +Harris Corner Detector +https://en.wikipedia.org/wiki/Harris_Corner_Detector +""" + + +class Harris_Corner: + def __init__(self, k: float, window_size: int): + + """ + k : is an empirically determined constant in [0.04,0.06] + window_size : neighbourhoods considered + """ + + if k in (0.04, 0.06): + self.k = k + self.window_size = window_size + else: + raise ValueError("invalid k value") + + def __str__(self): + + return f"Harris Corner detection with k : {self.k}" + + def detect(self, img_path: str): + + """ + Returns the image with corners identified + img_path : path of the image + output : list of the corner positions, image + """ + + img = cv2.imread(img_path, 0) + h, w = img.shape + corner_list = [] + color_img = img.copy() + color_img = cv2.cvtColor(color_img, cv2.COLOR_GRAY2RGB) + dy, dx = np.gradient(img) + ixx = dx ** 2 + iyy = dy ** 2 + ixy = dx * dy + k = 0.04 + offset = self.window_size // 2 + for y in range(offset, h - offset): + for x in range(offset, w - offset): + wxx = ixx[ + y - offset : y + offset + 1, x - offset : x + offset + 1 + ].sum() + wyy = iyy[ + y - offset : y + offset + 1, x - offset : x + offset + 1 + ].sum() + wxy = ixy[ + y - offset : y + offset + 1, x - offset : x + offset + 1 + ].sum() + + det = (wxx * wyy) - (wxy ** 2) + trace = wxx + wyy + r = det - k * (trace ** 2) + # Can change the value + if r > 0.5: + corner_list.append([x, y, r]) + color_img.itemset((y, x, 0), 0) + color_img.itemset((y, x, 1), 0) + color_img.itemset((y, x, 2), 255) + return color_img, corner_list + + +if __name__ == "__main__": + + edge_detect = Harris_Corner(0.04, 3) + color_img, _ = edge_detect.detect("path_to_image") + cv2.imwrite("detect.png", color_img) diff --git a/maths/number_of_digits.py b/maths/number_of_digits.py index 12717065149e..30e82f60fadc 100644 --- a/maths/number_of_digits.py +++ b/maths/number_of_digits.py @@ -15,4 +15,4 @@ def num_digits(n: int) -> int: if __name__ == "__main__": - print(num_digits(12345)) # ===> 5 + print(num_digits(12345)) # ===> 5 From 20b21e5ec9d4ed28ede8bd67d4b5bc924454d4ac Mon Sep 17 00:00:00 2001 From: mateuszz0000 Date: Fri, 5 Jun 2020 09:13:43 +0200 Subject: [PATCH 0603/1071] Refactor cycle_sort (#2072) * Refactor cycle_sort * Undo changes to keep only doctests --- sorts/cycle_sort.py | 35 +++++++++++++++++++++++------------ 1 file changed, 23 insertions(+), 12 deletions(-) diff --git a/sorts/cycle_sort.py b/sorts/cycle_sort.py index 4ce6a2a0e757..d731ea838425 100644 --- a/sorts/cycle_sort.py +++ b/sorts/cycle_sort.py @@ -1,5 +1,23 @@ -# Code contributed by Honey Sharma -def cycle_sort(array): +""" +Code contributed by Honey Sharma +Source: https://en.wikipedia.org/wiki/Cycle_sort +""" + + +def cycle_sort(array: list) -> list: + """ + >>> cycle_sort([4, 3, 2, 1]) + [1, 2, 3, 4] + + >>> cycle_sort([-4, 20, 0, -50, 100, -1]) + [-50, -4, -1, 0, 20, 100] + + >>> cycle_sort([-.1, -.2, 1.3, -.8]) + [-0.8, -0.2, -0.1, 1.3] + + >>> cycle_sort([]) + [] + """ ans = 0 # Pass through the array to find cycles to rotate. @@ -37,16 +55,9 @@ def cycle_sort(array): array[pos], item = item, array[pos] ans += 1 - return ans + return array -# Main Code starts here if __name__ == "__main__": - user_input = input("Enter numbers separated by a comma:\n") - unsorted = [int(item) for item in user_input.split(",")] - n = len(unsorted) - cycle_sort(unsorted) - - print("After sort : ") - for i in range(0, n): - print(unsorted[i], end=" ") + assert cycle_sort([4, 5, 3, 2, 1]) == [1, 2, 3, 4, 5] + assert cycle_sort([0, 1, -10, 15, 2, -2]) == [-10, -2, 0, 1, 2, 15] From 6752e9c737f87d196c10dc9742d6d8a18dbe14f3 Mon Sep 17 00:00:00 2001 From: mateuszz0000 Date: Sun, 7 Jun 2020 23:05:22 +0200 Subject: [PATCH 0604/1071] Remove boilerplate comments and unused variables (#2073) --- sorts/cycle_sort.py | 32 +++++++++++--------------------- 1 file changed, 11 insertions(+), 21 deletions(-) diff --git a/sorts/cycle_sort.py b/sorts/cycle_sort.py index d731ea838425..806f40441d79 100644 --- a/sorts/cycle_sort.py +++ b/sorts/cycle_sort.py @@ -18,42 +18,32 @@ def cycle_sort(array: list) -> list: >>> cycle_sort([]) [] """ - ans = 0 + array_len = len(array) + for cycle_start in range(0, array_len - 1): + item = array[cycle_start] - # Pass through the array to find cycles to rotate. - for cycleStart in range(0, len(array) - 1): - item = array[cycleStart] - - # finding the position for putting the item. - pos = cycleStart - for i in range(cycleStart + 1, len(array)): + pos = cycle_start + for i in range(cycle_start + 1, array_len): if array[i] < item: pos += 1 - # If the item is already present-not a cycle. - if pos == cycleStart: + if pos == cycle_start: continue - # Otherwise, put the item there or right after any duplicates. while item == array[pos]: pos += 1 - array[pos], item = item, array[pos] - ans += 1 - - # Rotate the rest of the cycle. - while pos != cycleStart: - # Find where to put the item. - pos = cycleStart - for i in range(cycleStart + 1, len(array)): + array[pos], item = item, array[pos] + while pos != cycle_start: + pos = cycle_start + for i in range(cycle_start + 1, array_len): if array[i] < item: pos += 1 - # Put the item there or right after any duplicates. while item == array[pos]: pos += 1 + array[pos], item = item, array[pos] - ans += 1 return array From 1e7df7f77aa7ef75b8651f8676baf2b27ebd146e Mon Sep 17 00:00:00 2001 From: mateuszz0000 Date: Mon, 8 Jun 2020 14:11:01 +0200 Subject: [PATCH 0605/1071] Errors notifications under pull requests (#2081) * Added error comments under pull requests * updating DIRECTORY.md Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> --- .github/workflows/codespell.yml | 3 +++ .travis.yml | 2 ++ DIRECTORY.md | 5 +++++ 3 files changed, 10 insertions(+) diff --git a/.github/workflows/codespell.yml b/.github/workflows/codespell.yml index 010adffa9053..30f2f34b47ce 100644 --- a/.github/workflows/codespell.yml +++ b/.github/workflows/codespell.yml @@ -12,3 +12,6 @@ jobs: - run: | SKIP="./.*,./other/dictionary.txt,./other/words,./project_euler/problem_22/p022_names.txt" codespell -L ans,fo,hist,iff,secant,tim --skip=$SKIP --quiet-level=2 + - name: Codespell comment + if: ${{ failure() }} + uses: plettich/python_codespell_action@master diff --git a/.travis.yml b/.travis.yml index 24140a5d0cfa..2c9f0f0dfd01 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,6 +5,8 @@ python: 3.8 cache: pip before_install: pip install --upgrade pip setuptools six install: pip install black flake8 +notifications: + webhooks: https://www.travisbuddy.com/ before_script: - black --check . || true - flake8 --ignore=E203,W503 --max-complexity=25 --max-line-length=120 --statistics --count . diff --git a/DIRECTORY.md b/DIRECTORY.md index 78afe07ec21f..cc73b18db50a 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -72,6 +72,9 @@ * [Huffman](https://github.com/TheAlgorithms/Python/blob/master/compression/huffman.py) * [Peak Signal To Noise Ratio](https://github.com/TheAlgorithms/Python/blob/master/compression/peak_signal_to_noise_ratio.py) +## Computer Vision + * [Harriscorner](https://github.com/TheAlgorithms/Python/blob/master/computer_vision/harriscorner.py) + ## Conversions * [Decimal To Binary](https://github.com/TheAlgorithms/Python/blob/master/conversions/decimal_to_binary.py) * [Decimal To Hexadecimal](https://github.com/TheAlgorithms/Python/blob/master/conversions/decimal_to_hexadecimal.py) @@ -350,6 +353,7 @@ * [Monte Carlo](https://github.com/TheAlgorithms/Python/blob/master/maths/monte_carlo.py) * [Monte Carlo Dice](https://github.com/TheAlgorithms/Python/blob/master/maths/monte_carlo_dice.py) * [Newton Raphson](https://github.com/TheAlgorithms/Python/blob/master/maths/newton_raphson.py) + * [Number Of Digits](https://github.com/TheAlgorithms/Python/blob/master/maths/number_of_digits.py) * [Numerical Integration](https://github.com/TheAlgorithms/Python/blob/master/maths/numerical_integration.py) * [Perfect Square](https://github.com/TheAlgorithms/Python/blob/master/maths/perfect_square.py) * [Pi Monte Carlo Estimation](https://github.com/TheAlgorithms/Python/blob/master/maths/pi_monte_carlo_estimation.py) @@ -375,6 +379,7 @@ * [Softmax](https://github.com/TheAlgorithms/Python/blob/master/maths/softmax.py) * [Square Root](https://github.com/TheAlgorithms/Python/blob/master/maths/square_root.py) * [Sum Of Arithmetic Series](https://github.com/TheAlgorithms/Python/blob/master/maths/sum_of_arithmetic_series.py) + * [Sum Of Digits](https://github.com/TheAlgorithms/Python/blob/master/maths/sum_of_digits.py) * [Test Prime Check](https://github.com/TheAlgorithms/Python/blob/master/maths/test_prime_check.py) * [Trapezoidal Rule](https://github.com/TheAlgorithms/Python/blob/master/maths/trapezoidal_rule.py) * [Volume](https://github.com/TheAlgorithms/Python/blob/master/maths/volume.py) From 7be3d0f6673f5bfbf76664d7a6586000a437ab63 Mon Sep 17 00:00:00 2001 From: Apoorve Date: Tue, 9 Jun 2020 21:29:19 +0530 Subject: [PATCH 0606/1071] Create-Add files to greedy_method directory (#2082) * Add Greedy Method Approach * Update Filename * Update Variable and Links * Fixed flake8 bugs * Update unittest filename * Update unittest filename * Final unittest filename update * Pythonic Code formatting * flake8 fixes * lowercase function name * Add zip function * Add zip function * params lowercase * Travis CI fixes * Update and rename knapsack_problem.py to knapsack.py * Update test_knapsack.py * Fix bugs * Rename knapsack.py to greedy_knapsack.py * Update test_knapsack.py Co-authored-by: Christian Clauss --- greedy_method/greedy_knapsack.py | 99 ++++++++++++++++++++++++++++++++ greedy_method/test_knapsack.py | 74 ++++++++++++++++++++++++ 2 files changed, 173 insertions(+) create mode 100644 greedy_method/greedy_knapsack.py create mode 100644 greedy_method/test_knapsack.py diff --git a/greedy_method/greedy_knapsack.py b/greedy_method/greedy_knapsack.py new file mode 100644 index 000000000000..92dd81aaaa82 --- /dev/null +++ b/greedy_method/greedy_knapsack.py @@ -0,0 +1,99 @@ +# To get an insight into Greedy Algorithm through the Knapsack problem + + +""" +A shopkeeper has bags of wheat that each have different weights and different profits. +eg. +profit 5 8 7 1 12 3 4 +weight 2 7 1 6 4 2 5 +max_weight 100 + +Constraints: +max_weight > 0 +profit[i] >= 0 +weight[i] >= 0 +Calculate the maximum profit that the shopkeeper can make given maxmum weight that can +be carried. +""" +from typing import Union + + +def calc_profit(profit: list, weight: list, max_weight: int) -> Union[str, int]: + """ + Function description is as follows- + :param profit: Take a list of profits + :param weight: Take a list of weight if bags corresponding to the profits + :param max_weight: Maximum weight that could be carried + :return: Maximum expected gain + + >>> calc_profit([1, 2, 3], [3, 4, 5], 15) + 6 + >>> calc_profit([10, 9 , 8], [3 ,4 , 5], 25) + 27 + """ + if len(profit) != len(weight): + raise ValueError("The length of profit and weight must be same.") + if max_weight <= 0: + raise ValueError("max_weight must greater than zero.") + if any(p < 0 for p in profit): + raise ValueError("Profit can not be negative.") + if any(w < 0 for w in weight): + raise ValueError("Weight can not be negative.") + + # List created to store profit gained for the 1kg in case of each weight + # respectively. Calculate and append profit/weight for each element. + profit_by_weight = [p / w for p, w in zip(profit, weight)] + + # Creating a copy of the list and sorting profit/weight in ascending order + sorted_profit_by_weight = sorted(profit_by_weight) + + # declaring useful variables + length = len(sorted_profit_by_weight) + limit = 0 + gain = 0 + i = 0 + + # loop till the total weight do not reach max limit e.g. 15 kg and till i= weight[index]: + limit += weight[index] + # Adding profit gained for the given weight 1 === + # weight[index]/weight[index] + gain += 1 * profit[index] + else: + # Since the weight encountered is greater than limit, therefore take the + # required number of remaining kgs and calculate profit for it. + # weight remaining / weight[index] + gain += (max_weight - limit) / weight[index] * profit[index] + break + i += 1 + return gain + + +if __name__ == "__main__": + print( + "Input profits, weights, and then max_weight (all positive ints) separated by " + "spaces." + ) + + profit = [int(x) for x in input("Input profits separated by spaces: ").split()] + weight = [int(x) for x in input("Input weights separated by spaces: ").split()] + max_weight = int(input("Max weight allowed: ")) + + # Function Call + calc_profit(profit, weight, max_weight) diff --git a/greedy_method/test_knapsack.py b/greedy_method/test_knapsack.py new file mode 100644 index 000000000000..8f8107bc7009 --- /dev/null +++ b/greedy_method/test_knapsack.py @@ -0,0 +1,74 @@ +import unittest +import greedy_knapsack as kp + + +class TestClass(unittest.TestCase): + """ + Test cases for knapsack + """ + + def test_sorted(self): + """ + kp.calc_profit takes the required argument (profit, weight, max_weight) + and returns whether the answer matches to the expected ones + """ + profit = [10, 20, 30, 40, 50, 60] + weight = [2, 4, 6, 8, 10, 12] + max_weight = 100 + self.assertEqual(kp.calc_profit(profit, weight, max_weight), 210) + + def test_negative_max_weight(self): + """ + Returns ValueError for any negative max_weight value + :return: ValueError + """ + # profit = [10, 20, 30, 40, 50, 60] + # weight = [2, 4, 6, 8, 10, 12] + # max_weight = -15 + self.assertRaisesRegex(ValueError, "max_weight must greater than zero.") + + def test_negative_profit_value(self): + """ + Returns ValueError for any negative profit value in the list + :return: ValueError + """ + # profit = [10, -20, 30, 40, 50, 60] + # weight = [2, 4, 6, 8, 10, 12] + # max_weight = 15 + self.assertRaisesRegex( + ValueError, "Weight can not be negative.", + ) + + def test_negative_weight_value(self): + """ + Returns ValueError for any negative weight value in the list + :return: ValueError + """ + # profit = [10, 20, 30, 40, 50, 60] + # weight = [2, -4, 6, -8, 10, 12] + # max_weight = 15 + self.assertRaisesRegex(ValueError, "Profit can not be negative.") + + def test_null_max_weight(self): + """ + Returns ValueError for any zero max_weight value + :return: ValueError + """ + # profit = [10, 20, 30, 40, 50, 60] + # weight = [2, 4, 6, 8, 10, 12] + # max_weight = null + self.assertRaisesRegex(ValueError, "max_weight must greater than zero.") + + def test_unequal_list_length(self): + """ + Returns IndexError if length of lists (profit and weight) are unequal. + :return: IndexError + """ + # profit = [10, 20, 30, 40, 50] + # weight = [2, 4, 6, 8, 10, 12] + # max_weight = 100 + self.assertRaisesRegex(IndexError, "The length of profit and weight must be same.") + + +if __name__ == "__main__": + unittest.main() From e553f4bf11d44a59dc4d8eb02c8b7fbb08488fe0 Mon Sep 17 00:00:00 2001 From: mateuszz0000 Date: Wed, 10 Jun 2020 12:40:52 +0200 Subject: [PATCH 0607/1071] Added travis notifications only on fail (#2091) * Added travis notifications only on fail * fixup! Format Python code with psf/black push Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> --- .travis.yml | 1 + greedy_method/test_knapsack.py | 4 +++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 2c9f0f0dfd01..c9f601144418 100644 --- a/.travis.yml +++ b/.travis.yml @@ -7,6 +7,7 @@ before_install: pip install --upgrade pip setuptools six install: pip install black flake8 notifications: webhooks: https://www.travisbuddy.com/ + on_success: never before_script: - black --check . || true - flake8 --ignore=E203,W503 --max-complexity=25 --max-line-length=120 --statistics --count . diff --git a/greedy_method/test_knapsack.py b/greedy_method/test_knapsack.py index 8f8107bc7009..9d556d2d22f4 100644 --- a/greedy_method/test_knapsack.py +++ b/greedy_method/test_knapsack.py @@ -67,7 +67,9 @@ def test_unequal_list_length(self): # profit = [10, 20, 30, 40, 50] # weight = [2, 4, 6, 8, 10, 12] # max_weight = 100 - self.assertRaisesRegex(IndexError, "The length of profit and weight must be same.") + self.assertRaisesRegex( + IndexError, "The length of profit and weight must be same." + ) if __name__ == "__main__": From bf0da25e4f867a280669f76c2535ebc23aca7192 Mon Sep 17 00:00:00 2001 From: Jeffin Francis Date: Wed, 10 Jun 2020 20:40:47 +0530 Subject: [PATCH 0608/1071] Added Readme for computer vision (#2075) * Create README.md * updating DIRECTORY.md Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> --- computer_vision/README.md | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 computer_vision/README.md diff --git a/computer_vision/README.md b/computer_vision/README.md new file mode 100644 index 000000000000..3a561d2f1f24 --- /dev/null +++ b/computer_vision/README.md @@ -0,0 +1,8 @@ +### Computer Vision + +Computer vision is a field of computer science that works on enabling computers to see, +identify and process images in the same way that human vision does, and then provide appropriate output. +It is like imparting human intelligence and instincts to a computer. +Image processing and computer vision and little different from each other.Image processing means applying some algorithms for transforming image from one form to other like smoothing,contrasting, stretching etc +While in computer vision comes from modelling image processing using the techniques of machine learning.Computer vision applies machine learning to recognize patterns for interpretation of images. +Much like the process of visual reasoning of human vision From 3de6f010c364b4d1942a31204e6a8dd348e904f0 Mon Sep 17 00:00:00 2001 From: mateuszz0000 Date: Thu, 11 Jun 2020 06:13:40 +0200 Subject: [PATCH 0609/1071] Refactor remove duplicates to more pythonic (#2093) * Refactor strings/remove_duplicate to more pythonic * updating DIRECTORY.md Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> --- DIRECTORY.md | 4 ++++ strings/remove_duplicate.py | 10 ++-------- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/DIRECTORY.md b/DIRECTORY.md index cc73b18db50a..da849b6fc98f 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -258,6 +258,10 @@ * [Scc Kosaraju](https://github.com/TheAlgorithms/Python/blob/master/graphs/scc_kosaraju.py) * [Tarjans Scc](https://github.com/TheAlgorithms/Python/blob/master/graphs/tarjans_scc.py) +## Greedy Method + * [Greedy Knapsack](https://github.com/TheAlgorithms/Python/blob/master/greedy_method/greedy_knapsack.py) + * [Test Knapsack](https://github.com/TheAlgorithms/Python/blob/master/greedy_method/test_knapsack.py) + ## Hashes * [Chaos Machine](https://github.com/TheAlgorithms/Python/blob/master/hashes/chaos_machine.py) * [Enigma Machine](https://github.com/TheAlgorithms/Python/blob/master/hashes/enigma_machine.py) diff --git a/strings/remove_duplicate.py b/strings/remove_duplicate.py index 0462292b78d2..6357050ac17d 100644 --- a/strings/remove_duplicate.py +++ b/strings/remove_duplicate.py @@ -1,4 +1,4 @@ -# Created by sarathkaul on 14/11/19 +""" Created by sarathkaul on 14/11/19 """ def remove_duplicates(sentence: str) -> str: @@ -7,13 +7,7 @@ def remove_duplicates(sentence: str) -> str: >>> remove_duplicates("Python is great and Java is also great") 'Java Python also and great is' """ - sen_list = sentence.split(" ") - check = set() - - for a_word in sen_list: - check.add(a_word) - - return " ".join(sorted(check)) + return " ".join(sorted(set(sentence.split(" ")))) if __name__ == "__main__": From 657d46101d6073895fad1fcb13a1d6b340bb6679 Mon Sep 17 00:00:00 2001 From: Christian Clauss Date: Thu, 11 Jun 2020 16:36:09 +0200 Subject: [PATCH 0610/1071] calc_profit always returns an int (#2090) * calc_profit always returns an int * fixup! Format Python code with psf/black push Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> --- greedy_method/greedy_knapsack.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/greedy_method/greedy_knapsack.py b/greedy_method/greedy_knapsack.py index 92dd81aaaa82..ed6399c740c8 100644 --- a/greedy_method/greedy_knapsack.py +++ b/greedy_method/greedy_knapsack.py @@ -15,10 +15,9 @@ Calculate the maximum profit that the shopkeeper can make given maxmum weight that can be carried. """ -from typing import Union -def calc_profit(profit: list, weight: list, max_weight: int) -> Union[str, int]: +def calc_profit(profit: list, weight: list, max_weight: int) -> int: """ Function description is as follows- :param profit: Take a list of profits From a7b431137801230050eee114fa205540a06d6eac Mon Sep 17 00:00:00 2001 From: ocivo <57896941+ocivo@users.noreply.github.com> Date: Thu, 11 Jun 2020 22:38:43 +0800 Subject: [PATCH 0611/1071] fix fetch_github_info __main__ bug (#2080) * fix fetch_github_info __main__ bug * Algorithms should not print * Update fetch_github_info.py * Update fetch_github_info.py Co-authored-by: Christian Clauss Co-authored-by: John Law --- web_programming/fetch_github_info.py | 27 ++++++++++++++++++--------- 1 file changed, 18 insertions(+), 9 deletions(-) diff --git a/web_programming/fetch_github_info.py b/web_programming/fetch_github_info.py index f6626770e833..227598bb20ab 100644 --- a/web_programming/fetch_github_info.py +++ b/web_programming/fetch_github_info.py @@ -1,17 +1,26 @@ -# Created by sarathkaul on 14/11/19 +#!/usr/bin/env python3 + +""" +Created by sarathkaul on 14/11/19 + +Basic authentication using an API password is deprecated and will soon no longer work. +Visit https://developer.github.com/changes/2020-02-14-deprecating-password-auth +for more information around suggested workarounds and removal dates. +""" + import requests _GITHUB_API = "https://api.github.com/user" -def fetch_github_info(auth_user: str, auth_pass: str) -> None: - # fetching github info using requests - info = requests.get(_GITHUB_API, auth=(auth_user, auth_pass)) - - for a_info, a_detail in info.json().items(): - print(f"{a_info}: {a_detail}") +def fetch_github_info(auth_user: str, auth_pass: str) -> dict: + """ + Fetch GitHub info of a user using the requests module + """ + return requests.get(_GITHUB_API, auth=(auth_user, auth_pass)).json() -if __name__ == "main": - fetch_github_info("", "") +if __name__ == "__main__": + for key, value in fetch_github_info("", "").items(): + print(f"{key}: {value}") From 19c3871b21df5f9936438b332a50daac81313f90 Mon Sep 17 00:00:00 2001 From: "Kevin C. Escobedo" Date: Thu, 11 Jun 2020 08:59:42 -0700 Subject: [PATCH 0612/1071] Added function to convert from decimal to another base (#2087) * Added function to convert from decimal to another base * Update conversions/decimal_to_any.py Changed type() to isinstance() Co-authored-by: Christian Clauss * Update conversions/decimal_to_any.py Changed to base in (0, 1) Co-authored-by: Christian Clauss * Update conversions/decimal_to_any.py Updated to div not in (0, 1) Co-authored-by: Christian Clauss * Update conversions/decimal_to_any.py Updated to make condition clearer Co-authored-by: Christian Clauss * Update conversions/decimal_to_any.py Using divmod() instead of % operator Co-authored-by: Christian Clauss * Update conversions/decimal_to_any.py Improved readability on a docstring test Co-authored-by: Christian Clauss * Update conversions/decimal_to_any.py Changed use of type() to isinstance() Co-authored-by: Christian Clauss * Update conversions/decimal_to_any.py Changed from use of type() to isinstance() Co-authored-by: Christian Clauss * Made changes and improved function * Update conversions/decimal_to_any.py Added space to docstring test Co-authored-by: Christian Clauss * Changed action for bad input * Added support for conversions up to base 36 (#2087) * Added support for conversions up to base 36 and renamed HEXADECIMAL dict (#2087) * Fixed whitespace issue (#2087) * Fixed issue with line length (#2087) * Fixed issue with conversions past base-10 failing (#2087) * Added more robust testing (#2087) Co-authored-by: Christian Clauss --- conversions/decimal_to_any.py | 104 ++++++++++++++++++++++++++++++++++ 1 file changed, 104 insertions(+) create mode 100644 conversions/decimal_to_any.py diff --git a/conversions/decimal_to_any.py b/conversions/decimal_to_any.py new file mode 100644 index 000000000000..b91d84b9db98 --- /dev/null +++ b/conversions/decimal_to_any.py @@ -0,0 +1,104 @@ +"""Convert a positive Decimal Number to Any Other Representation""" + + +def decimal_to_any(num: int, base: int) -> str: + + """ + Convert a positive integer to another base as str. + >>> decimal_to_any(0, 2) + '0' + >>> decimal_to_any(5, 4) + '11' + >>> decimal_to_any(20, 3) + '202' + >>> decimal_to_any(58, 16) + '3A' + >>> decimal_to_any(243, 17) + 'E5' + >>> decimal_to_any(34923, 36) + 'QY3' + >>> decimal_to_any(10, 11) + 'A' + >>> decimal_to_any(16, 16) + '10' + >>> decimal_to_any(36, 36) + '10' + >>> # negatives will error + >>> decimal_to_any(-45, 8) # doctest: +ELLIPSIS + Traceback (most recent call last): + ... + ValueError: parameter must be positive int + >>> # floats will error + >>> decimal_to_any(34.4, 6) # doctest: +ELLIPSIS + Traceback (most recent call last): + ... + TypeError: int() can't convert non-string with explicit base + >>> # a float base will error + >>> decimal_to_any(5, 2.5) # doctest: +ELLIPSIS + Traceback (most recent call last): + ... + TypeError: 'float' object cannot be interpreted as an integer + >>> # a str base will error + >>> decimal_to_any(10, '16') # doctest: +ELLIPSIS + Traceback (most recent call last): + ... + TypeError: 'str' object cannot be interpreted as an integer + >>> # a base less than 2 will error + >>> decimal_to_any(7, 0) # doctest: +ELLIPSIS + Traceback (most recent call last): + ... + ValueError: base must be >= 2 + >>> # a base greater than 36 will error + >>> decimal_to_any(34, 37) # doctest: +ELLIPSIS + Traceback (most recent call last): + ... + ValueError: base must be <= 36 + """ + if isinstance(num, float): + raise TypeError("int() can't convert non-string with explicit base") + if num < 0: + raise ValueError("parameter must be positive int") + if isinstance(base, str): + raise TypeError("'str' object cannot be interpreted as an integer") + if isinstance(base, float): + raise TypeError("'float' object cannot be interpreted as an integer") + if base in (0, 1): + raise ValueError("base must be >= 2") + if base > 36: + raise ValueError("base must be <= 36") + + ALPHABET_VALUES = {'10': 'A', '11': 'B', '12': 'C', '13': 'D', '14': 'E', '15': 'F', '16': 'G', '17': 'H', + '18': 'I', '19': 'J', '20': 'K', '21': 'L', '22': 'M', '23': 'N', '24': 'O', '25': 'P', + '26': 'Q', '27': 'R', '28': 'S', '29': 'T', '30': 'U', '31': 'V', '32': 'W', '33': 'X', + '34': 'Y', '35': 'Z'} + new_value = "" + mod = 0 + div = 0 + while div != 1: + div, mod = divmod(num, base) + if base >= 11 and 9 < mod < 36: + actual_value = ALPHABET_VALUES[str(mod)] + mod = actual_value + new_value += str(mod) + div = num // base + num = div + if div == 0: + return str(new_value[::-1]) + elif div == 1: + new_value += str(div) + return str(new_value[::-1]) + + return new_value[::-1] + + +if __name__ == "__main__": + import doctest + + doctest.testmod() + + for base in range(2, 37): + for num in range(1000): + assert int(decimal_to_any(num, base), base) == num, ( + num, base, decimal_to_any(num, base), + int(decimal_to_any(num, base), base) + ) From 3893ed30cafd03265991f3209b5ef21ddd62b4c0 Mon Sep 17 00:00:00 2001 From: Vignesh Date: Thu, 11 Jun 2020 23:06:53 +0530 Subject: [PATCH 0613/1071] created perfect_cube.py (#2076) * created perfect_cube.py To find whether a number is a perfect cube or not. * Update perfect_cube.py * Update perfect_cube.py * Update perfect_cube.py --- maths/perfect_cube.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 maths/perfect_cube.py diff --git a/maths/perfect_cube.py b/maths/perfect_cube.py new file mode 100644 index 000000000000..f65795ba8686 --- /dev/null +++ b/maths/perfect_cube.py @@ -0,0 +1,16 @@ +def perfect_cube(n: int) -> bool: + """ + Check if a number is a perfect cube or not. + + >>> perfect_cube(27) + True + >>> perfect_cube(4) + False + """ + val = n ** (1 / 3) + return (val * val * val) == n + + +if(__name__ == '__main__'): + print(perfect_cube(27)) + print(perfect_cube(4)) From 2264244a341a7d8e41fa1ad90cea1b9bd6880f99 Mon Sep 17 00:00:00 2001 From: Nika Losaberidze Date: Thu, 11 Jun 2020 21:43:05 +0400 Subject: [PATCH 0614/1071] Add Z-function algorithm implementation (#2067) * Add Z-function algorithm implementation * Spelling correction * Reference url correction * Add additional function as an example of z-function usage, change docstrings for functions * Fix flake8 errors * Update z_function.py Co-authored-by: Christian Clauss --- strings/z_function.py | 89 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 89 insertions(+) create mode 100644 strings/z_function.py diff --git a/strings/z_function.py b/strings/z_function.py new file mode 100644 index 000000000000..55be81935cc5 --- /dev/null +++ b/strings/z_function.py @@ -0,0 +1,89 @@ +""" +https://cp-algorithms.com/string/z-function.html + +Z-function or Z algorithm + +Efficient algorithm for pattern occurrence in a string + +Time Complexity: O(n) - where n is the length of the string + +""" + + +def z_function(input_str: str) -> list: + """ + For the given string this function computes value for each index, + which represents the maximal length substring starting from the index + and is the same as the prefix of the same size + + e.x. for string 'abab' for second index value would be 2 + + For the value of the first element the algorithm always returns 0 + + >>> z_function("abracadabra") + [0, 0, 0, 1, 0, 1, 0, 4, 0, 0, 1] + >>> z_function("aaaa") + [0, 3, 2, 1] + >>> z_function("zxxzxxz") + [0, 0, 0, 4, 0, 0, 1] + """ + z_result = [0] * len(input_str) + + # initialize interval's left pointer and right pointer + left_pointer, right_pointer = 0, 0 + + for i in range(1, len(input_str)): + # case when current index is inside the interval + if i <= right_pointer: + min_edge = min(right_pointer - i + 1, z_result[i - left_pointer]) + z_result[i] = min_edge + + while go_next(i, z_result, input_str): + z_result[i] += 1 + + # if new index's result gives us more right interval, + # we've to update left_pointer and right_pointer + if i + z_result[i] - 1 > right_pointer: + left_pointer, right_pointer = i, i + z_result[i] - 1 + + return z_result + + +def go_next(i, z_result, s): + """ + Check if we have to move forward to the next characters or not + """ + return i + z_result[i] < len(s) and s[z_result[i]] == s[i + z_result[i]] + + +def find_pattern(pattern: str, input_str: str) -> int: + """ + Example of using z-function for pattern occurrence + Given function returns the number of times 'pattern' + appears in 'input_str' as a substring + + >>> find_pattern("abr", "abracadabra") + 2 + >>> find_pattern("a", "aaaa") + 4 + >>> find_pattern("xz", "zxxzxxz") + 2 + """ + answer = 0 + # concatenate 'pattern' and 'input_str' and call z_function + # with concatenated string + z_result = z_function(pattern + input_str) + + for val in z_result: + # if value is greater then length of the pattern string + # that means this index is starting position of substring + # which is equal to pattern string + if val >= len(pattern): + answer += 1 + + return answer + + +if __name__ == "__main__": + import doctest + doctest.testmod() From ec2d900b03dbc511504819caf353310b0a997fa6 Mon Sep 17 00:00:00 2001 From: bnMikheili <39998190+bnMikheili@users.noreply.github.com> Date: Fri, 12 Jun 2020 00:22:16 +0400 Subject: [PATCH 0615/1071] implement sdbm hash algorithm (#2094) * implement sdbm hash algorithm * fix bug: styling * fix styling for decimal_to_any --- conversions/decimal_to_any.py | 8 +++++--- hashes/sdbm.py | 32 ++++++++++++++++++++++++++++++++ 2 files changed, 37 insertions(+), 3 deletions(-) create mode 100644 hashes/sdbm.py diff --git a/conversions/decimal_to_any.py b/conversions/decimal_to_any.py index b91d84b9db98..e3fb4e5d3f08 100644 --- a/conversions/decimal_to_any.py +++ b/conversions/decimal_to_any.py @@ -99,6 +99,8 @@ def decimal_to_any(num: int, base: int) -> str: for base in range(2, 37): for num in range(1000): assert int(decimal_to_any(num, base), base) == num, ( - num, base, decimal_to_any(num, base), - int(decimal_to_any(num, base), base) - ) + num, + base, + decimal_to_any(num, base), + int(decimal_to_any(num, base), base), + ) diff --git a/hashes/sdbm.py b/hashes/sdbm.py new file mode 100644 index 000000000000..f80941306a74 --- /dev/null +++ b/hashes/sdbm.py @@ -0,0 +1,32 @@ +""" + This algorithm was created for sdbm (a public-domain reimplementation of ndbm) database library. + It was found to do well in scrambling bits, causing better distribution of the keys and fewer splits. + It also happens to be a good general hashing function with good distribution. + The actual function (pseudo code) is: + for i in i..len(str): + hash(i) = hash(i - 1) * 65599 + str[i]; + + What is included below is the faster version used in gawk. [there is even a faster, duff-device version] + The magic constant 65599 was picked out of thin air while experimenting with different constants. + It turns out to be a prime. + This is one of the algorithms used in berkeley db (see sleepycat) and elsewhere. + + source: http://www.cse.yorku.ca/~oz/hash.html +""" + + +def sdbm(plain_text: str) -> str: + """ + Function implements sdbm hash, easy to use, great for bits scrambling. + iterates over each character in the given string and applies function to each of them. + + >>> sdbm('Algorithms') + 1462174910723540325254304520539387479031000036 + + >>> sdbm('scramble bits') + 730247649148944819640658295400555317318720608290373040936089 + """ + hash = 0 + for plain_chr in plain_text: + hash = ord(plain_chr) + (hash << 6) + (hash << 16) - hash + return hash From 8bb7b8f457df31991f6c8f06312b76e6cc4ceb81 Mon Sep 17 00:00:00 2001 From: Nika Losaberidze Date: Fri, 12 Jun 2020 08:51:47 +0400 Subject: [PATCH 0616/1071] Fix syntax for flake8 passing (#2096) * Fix syntax for flake8 passing * fixup! Format Python code with psf/black push * # fmt: off / # fmt: on * updating DIRECTORY.md Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> Co-authored-by: Christian Clauss --- DIRECTORY.md | 4 ++++ conversions/decimal_to_any.py | 10 +++++----- maths/perfect_cube.py | 2 +- strings/z_function.py | 1 + 4 files changed, 11 insertions(+), 6 deletions(-) diff --git a/DIRECTORY.md b/DIRECTORY.md index da849b6fc98f..c90043aa734f 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -76,6 +76,7 @@ * [Harriscorner](https://github.com/TheAlgorithms/Python/blob/master/computer_vision/harriscorner.py) ## Conversions + * [Decimal To Any](https://github.com/TheAlgorithms/Python/blob/master/conversions/decimal_to_any.py) * [Decimal To Binary](https://github.com/TheAlgorithms/Python/blob/master/conversions/decimal_to_binary.py) * [Decimal To Hexadecimal](https://github.com/TheAlgorithms/Python/blob/master/conversions/decimal_to_hexadecimal.py) * [Decimal To Octal](https://github.com/TheAlgorithms/Python/blob/master/conversions/decimal_to_octal.py) @@ -267,6 +268,7 @@ * [Enigma Machine](https://github.com/TheAlgorithms/Python/blob/master/hashes/enigma_machine.py) * [Hamming Code](https://github.com/TheAlgorithms/Python/blob/master/hashes/hamming_code.py) * [Md5](https://github.com/TheAlgorithms/Python/blob/master/hashes/md5.py) + * [Sdbm](https://github.com/TheAlgorithms/Python/blob/master/hashes/sdbm.py) * [Sha1](https://github.com/TheAlgorithms/Python/blob/master/hashes/sha1.py) ## Linear Algebra @@ -359,6 +361,7 @@ * [Newton Raphson](https://github.com/TheAlgorithms/Python/blob/master/maths/newton_raphson.py) * [Number Of Digits](https://github.com/TheAlgorithms/Python/blob/master/maths/number_of_digits.py) * [Numerical Integration](https://github.com/TheAlgorithms/Python/blob/master/maths/numerical_integration.py) + * [Perfect Cube](https://github.com/TheAlgorithms/Python/blob/master/maths/perfect_cube.py) * [Perfect Square](https://github.com/TheAlgorithms/Python/blob/master/maths/perfect_square.py) * [Pi Monte Carlo Estimation](https://github.com/TheAlgorithms/Python/blob/master/maths/pi_monte_carlo_estimation.py) * [Polynomial Evaluation](https://github.com/TheAlgorithms/Python/blob/master/maths/polynomial_evaluation.py) @@ -644,6 +647,7 @@ * [Split](https://github.com/TheAlgorithms/Python/blob/master/strings/split.py) * [Upper](https://github.com/TheAlgorithms/Python/blob/master/strings/upper.py) * [Word Occurrence](https://github.com/TheAlgorithms/Python/blob/master/strings/word_occurrence.py) + * [Z Function](https://github.com/TheAlgorithms/Python/blob/master/strings/z_function.py) ## Traversals * [Binary Tree Traversals](https://github.com/TheAlgorithms/Python/blob/master/traversals/binary_tree_traversals.py) diff --git a/conversions/decimal_to_any.py b/conversions/decimal_to_any.py index e3fb4e5d3f08..d3acac3bd41e 100644 --- a/conversions/decimal_to_any.py +++ b/conversions/decimal_to_any.py @@ -2,7 +2,6 @@ def decimal_to_any(num: int, base: int) -> str: - """ Convert a positive integer to another base as str. >>> decimal_to_any(0, 2) @@ -66,11 +65,12 @@ def decimal_to_any(num: int, base: int) -> str: raise ValueError("base must be >= 2") if base > 36: raise ValueError("base must be <= 36") - + # fmt: off ALPHABET_VALUES = {'10': 'A', '11': 'B', '12': 'C', '13': 'D', '14': 'E', '15': 'F', '16': 'G', '17': 'H', - '18': 'I', '19': 'J', '20': 'K', '21': 'L', '22': 'M', '23': 'N', '24': 'O', '25': 'P', - '26': 'Q', '27': 'R', '28': 'S', '29': 'T', '30': 'U', '31': 'V', '32': 'W', '33': 'X', - '34': 'Y', '35': 'Z'} + '18': 'I', '19': 'J', '20': 'K', '21': 'L', '22': 'M', '23': 'N', '24': 'O', '25': 'P', + '26': 'Q', '27': 'R', '28': 'S', '29': 'T', '30': 'U', '31': 'V', '32': 'W', '33': 'X', + '34': 'Y', '35': 'Z'} + # fmt: on new_value = "" mod = 0 div = 0 diff --git a/maths/perfect_cube.py b/maths/perfect_cube.py index f65795ba8686..9ad287e41e75 100644 --- a/maths/perfect_cube.py +++ b/maths/perfect_cube.py @@ -11,6 +11,6 @@ def perfect_cube(n: int) -> bool: return (val * val * val) == n -if(__name__ == '__main__'): +if __name__ == "__main__": print(perfect_cube(27)) print(perfect_cube(4)) diff --git a/strings/z_function.py b/strings/z_function.py index 55be81935cc5..d8d823a37efb 100644 --- a/strings/z_function.py +++ b/strings/z_function.py @@ -86,4 +86,5 @@ def find_pattern(pattern: str, input_str: str) -> int: if __name__ == "__main__": import doctest + doctest.testmod() From ae9d0f91960a3f6857e040333eb3b68abda10c3b Mon Sep 17 00:00:00 2001 From: Christian Clauss Date: Sun, 14 Jun 2020 08:28:00 +0200 Subject: [PATCH 0617/1071] Fix indentation (#2097) --- conversions/decimal_to_any.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/conversions/decimal_to_any.py b/conversions/decimal_to_any.py index d3acac3bd41e..5abcb8e6549f 100644 --- a/conversions/decimal_to_any.py +++ b/conversions/decimal_to_any.py @@ -67,9 +67,9 @@ def decimal_to_any(num: int, base: int) -> str: raise ValueError("base must be <= 36") # fmt: off ALPHABET_VALUES = {'10': 'A', '11': 'B', '12': 'C', '13': 'D', '14': 'E', '15': 'F', '16': 'G', '17': 'H', - '18': 'I', '19': 'J', '20': 'K', '21': 'L', '22': 'M', '23': 'N', '24': 'O', '25': 'P', - '26': 'Q', '27': 'R', '28': 'S', '29': 'T', '30': 'U', '31': 'V', '32': 'W', '33': 'X', - '34': 'Y', '35': 'Z'} + '18': 'I', '19': 'J', '20': 'K', '21': 'L', '22': 'M', '23': 'N', '24': 'O', '25': 'P', + '26': 'Q', '27': 'R', '28': 'S', '29': 'T', '30': 'U', '31': 'V', '32': 'W', '33': 'X', + '34': 'Y', '35': 'Z'} # fmt: on new_value = "" mod = 0 From 55b3088e47690f2a8a152ceb44bba786810abb28 Mon Sep 17 00:00:00 2001 From: bnMikheili <39998190+bnMikheili@users.noreply.github.com> Date: Sun, 14 Jun 2020 11:49:39 +0400 Subject: [PATCH 0618/1071] Hash adler32 (#2111) * implement hash * fix indentation --- hashes/adler32.py | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 hashes/adler32.py diff --git a/hashes/adler32.py b/hashes/adler32.py new file mode 100644 index 000000000000..8b82fff477fd --- /dev/null +++ b/hashes/adler32.py @@ -0,0 +1,27 @@ +""" + Adler-32 is a checksum algorithm which was invented by Mark Adler in 1995. + Compared to a cyclic redundancy check of the same length, it trades reliability for speed (preferring the latter). + Adler-32 is more reliable than Fletcher-16, and slightly less reliable than Fletcher-32.[2] + + source: https://en.wikipedia.org/wiki/Adler-32 +""" + + +def adler32(plain_text: str) -> str: + """ + Function implements adler-32 hash. + Itterates and evaluates new value for each character + + >>> adler32('Algorithms') + 363791387 + + >>> adler32('go adler em all') + 708642122 + """ + MOD_ADLER = 65521 + a = 1 + b = 0 + for plain_chr in plain_text: + a = (a + ord(plain_chr)) % MOD_ADLER + b = (b + a) % MOD_ADLER + return (b << 16) | a From b9185bebd6784f5402c1632d1749887c89fdd232 Mon Sep 17 00:00:00 2001 From: Christian Clauss Date: Mon, 15 Jun 2020 09:55:41 +0200 Subject: [PATCH 0619/1071] Create natural_language_processing (#2116) * Create natural_language_processing Closes #2115 * updating DIRECTORY.md Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> --- DIRECTORY.md | 1 + natural_language_processing | 3 +++ 2 files changed, 4 insertions(+) create mode 100644 natural_language_processing diff --git a/DIRECTORY.md b/DIRECTORY.md index c90043aa734f..5f9046cc7108 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -264,6 +264,7 @@ * [Test Knapsack](https://github.com/TheAlgorithms/Python/blob/master/greedy_method/test_knapsack.py) ## Hashes + * [Adler32](https://github.com/TheAlgorithms/Python/blob/master/hashes/adler32.py) * [Chaos Machine](https://github.com/TheAlgorithms/Python/blob/master/hashes/chaos_machine.py) * [Enigma Machine](https://github.com/TheAlgorithms/Python/blob/master/hashes/enigma_machine.py) * [Hamming Code](https://github.com/TheAlgorithms/Python/blob/master/hashes/hamming_code.py) diff --git a/natural_language_processing b/natural_language_processing new file mode 100644 index 000000000000..b864d9e08f70 --- /dev/null +++ b/natural_language_processing @@ -0,0 +1,3 @@ +# Natural Language Processing + +https://en.wikipedia.org/wiki/Natural_language_processing From 23dae9ceabfd8135598b67ca89dc8f199879d88d Mon Sep 17 00:00:00 2001 From: beqakd <39763019+beqakd@users.noreply.github.com> Date: Mon, 15 Jun 2020 17:03:30 +0400 Subject: [PATCH 0620/1071] implement rat in maze algorithm. (#2106) * implement rat in maze algorithm. * style changes * fix trailing whitespace * add test, fix style * fix style * method change * minor changes * style changes * return solved Co-authored-by: Christian Clauss --- backtracking/rat_in_maze.py | 113 ++++++++++++++++++++++++++++++++++++ 1 file changed, 113 insertions(+) create mode 100644 backtracking/rat_in_maze.py diff --git a/backtracking/rat_in_maze.py b/backtracking/rat_in_maze.py new file mode 100644 index 000000000000..c533713a8933 --- /dev/null +++ b/backtracking/rat_in_maze.py @@ -0,0 +1,113 @@ +def solve_maze(maze: list) -> bool: + """ + This method solves rat in maze algorithm. + In this problem we have n by n matrix and we have start point and end point + we want to go from source to distination. In this matrix 0 are block paths + 1 are open paths we can use. + Parameters : + maze(2D matrix) : maze + Returns: + Return: True is maze has a solution or False if it does not. + >>> maze = [[0, 1, 0, 1, 1], + ... [0, 0, 0, 0, 0], + ... [1, 0, 1, 0, 1], + ... [0, 0, 1, 0, 0], + ... [1, 0, 0, 1, 0]] + >>> solve_maze(maze) + [1, 0, 0, 0, 0] + [1, 1, 1, 1, 0] + [0, 0, 0, 1, 0] + [0, 0, 0, 1, 1] + [0, 0, 0, 0, 1] + True + + >>> maze = [[0, 1, 0, 1, 1], + ... [0, 0, 0, 0, 0], + ... [0, 0, 0, 0, 1], + ... [0, 0, 0, 0, 0], + ... [0, 0, 0, 0, 0]] + >>> solve_maze(maze) + [1, 0, 0, 0, 0] + [1, 0, 0, 0, 0] + [1, 0, 0, 0, 0] + [1, 0, 0, 0, 0] + [1, 1, 1, 1, 1] + True + + >>> maze = [[0, 0, 0], + ... [0, 1, 0], + ... [1, 0, 0]] + >>> solve_maze(maze) + [1, 1, 1] + [0, 0, 1] + [0, 0, 1] + True + + >>> maze = [[0, 1, 0], + ... [0, 1, 0], + ... [1, 0, 0]] + >>> solve_maze(maze) + Solution does not exists! + False + + >>> maze = [[0, 1], + ... [1, 0]] + >>> solve_maze(maze) + Solution does not exists! + False + """ + size = len(maze) + # We need to create solution object to save path. + solutions = [[0 for _ in range(size)] for _ in range(size)] + solved = run_maze(maze, 0, 0, solutions) + if solved: + print("\n".join(str(row) for row in solutions)) + else: + print("Solution does not exists!") + return solved + + +def run_maze(maze, i, j, solutions): + """ + This method is recursive method which starts from i and j + and goes with 4 direction option up, down, left, right + if path found to destination it breaks and return True + otherwise False + Parameters: + maze(2D matrix) : maze + i, j : coordinates of matrix + solutions(2D matrix) : solutions + Returns: + Boolean if path is found True, Otherwise False. + """ + size = len(maze) + # Final check point. + if i == j == (size - 1): + solutions[i][j] = 1 + return True + + lower_flag = (not (i < 0)) and (not (j < 0)) # Check lower bounds + upper_flag = (i < size) and (j < size) # Check upper bounds + + if lower_flag and upper_flag: + # check for already visited and block points. + block_flag = (not (solutions[i][j])) and (not (maze[i][j])) + if block_flag: + # check visited + solutions[i][j] = 1 + + # check for directions + if (run_maze(maze, i + 1, j, solutions) or + run_maze(maze, i, j + 1, solutions) or + run_maze(maze, i - 1, j, solutions) or + run_maze(maze, i, j - 1, solutions)): + return True + + solutions[i][j] = 0 + return False + + +if __name__ == "__main__": + import doctest + + doctest.testmod() From 0b028aa32a71ee478664ad70db7ab154e19435e6 Mon Sep 17 00:00:00 2001 From: Christian Clauss Date: Mon, 15 Jun 2020 15:47:02 +0200 Subject: [PATCH 0621/1071] Fix line break after binary operator (#2119) * Fix line break after binary operator * fixup! Format Python code with psf/black push Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> --- backtracking/rat_in_maze.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/backtracking/rat_in_maze.py b/backtracking/rat_in_maze.py index c533713a8933..ba96d6a52214 100644 --- a/backtracking/rat_in_maze.py +++ b/backtracking/rat_in_maze.py @@ -97,10 +97,12 @@ def run_maze(maze, i, j, solutions): solutions[i][j] = 1 # check for directions - if (run_maze(maze, i + 1, j, solutions) or - run_maze(maze, i, j + 1, solutions) or - run_maze(maze, i - 1, j, solutions) or - run_maze(maze, i, j - 1, solutions)): + if ( + run_maze(maze, i + 1, j, solutions) + or run_maze(maze, i, j + 1, solutions) + or run_maze(maze, i - 1, j, solutions) + or run_maze(maze, i, j - 1, solutions) + ): return True solutions[i][j] = 0 From 9438c6bf0b583e2b56f19d8fa28f2e95d7146d42 Mon Sep 17 00:00:00 2001 From: Michael Quevillon Date: Mon, 15 Jun 2020 12:09:32 -0400 Subject: [PATCH 0622/1071] Add pytest init file to define custom mark mat_ops (#2120) Fixes #1917. --- pytest.ini | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 pytest.ini diff --git a/pytest.ini b/pytest.ini new file mode 100644 index 000000000000..a26de5e638dc --- /dev/null +++ b/pytest.ini @@ -0,0 +1,4 @@ +# Setup for pytest +[pytest] +markers = + mat_ops: mark a test as utilizing matrix operations. From 9316e7c0147a84b9b549094a5b8c70f95a0cd3a1 Mon Sep 17 00:00:00 2001 From: Christian Clauss Date: Tue, 16 Jun 2020 10:09:19 +0200 Subject: [PATCH 0623/1071] Set the Python file maximum line length to 88 characters (#2122) * flake8 --max-line-length=88 * fixup! Format Python code with psf/black push Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> --- .travis.yml | 2 +- arithmetic_analysis/intersection.py | 8 +- backtracking/hamiltonian_cycle.py | 10 +- backtracking/n_queens.py | 8 +- backtracking/sum_of_subsets.py | 12 ++- blockchain/chinese_remainder_theorem.py | 5 +- blockchain/diophantine_equation.py | 18 ++-- blockchain/modular_division.py | 12 ++- ciphers/affine_cipher.py | 6 +- ciphers/base64_cipher.py | 3 +- ciphers/elgamal_key_generator.py | 3 +- ciphers/hill_cipher.py | 3 +- ciphers/shuffled_shift_cipher.py | 21 ++-- compression/peak_signal_to_noise_ratio.py | 6 +- conversions/decimal_to_any.py | 7 +- conversions/decimal_to_hexadecimal.py | 3 +- conversions/decimal_to_octal.py | 3 +- .../data_structures/heap/heap_generic.py | 21 ++-- data_structures/linked_list/skip_list.py | 6 +- .../stacks/infix_to_prefix_conversion.py | 6 +- data_structures/trie/trie.py | 4 +- .../edge_detection/canny.py | 22 +++-- digital_image_processing/resize/resize.py | 6 +- digital_image_processing/sepia.py | 5 +- divide_and_conquer/convex_hull.py | 46 +++++---- dynamic_programming/fibonacci.py | 3 +- dynamic_programming/integer_partition.py | 7 +- .../iterating_through_submasks.py | 3 +- dynamic_programming/knapsack.py | 4 +- .../longest_common_subsequence.py | 5 +- dynamic_programming/longest_sub_array.py | 6 +- dynamic_programming/rod_cutting.py | 29 +++--- dynamic_programming/subset_generation.py | 2 +- dynamic_programming/sum_of_subset.py | 3 +- fuzzy_logic/fuzzy_operations.py | 3 +- geodesy/lamberts_ellipsoidal_distance.py | 20 ++-- graphs/a_star.py | 4 +- graphs/graphs_floyd_warshall.py | 11 ++- graphs/minimum_spanning_tree_prims.py | 3 +- graphs/tarjans_scc.py | 17 ++-- hashes/adler32.py | 6 +- hashes/sdbm.py | 15 ++- hashes/sha1.py | 25 ++--- machine_learning/gradient_descent.py | 9 +- machine_learning/polymonial_regression.py | 3 +- .../sequential_minimum_optimization.py | 26 +++-- maths/bailey_borwein_plouffe.py | 11 ++- maths/ceil.py | 3 +- maths/fermat_little_theorem.py | 3 +- maths/fibonacci.py | 9 +- maths/floor.py | 3 +- maths/gamma.py | 3 +- maths/greatest_common_divisor.py | 9 +- maths/lucas_lehmer_primality_test.py | 4 +- maths/matrix_exponentiation.py | 2 +- maths/modular_exponential.py | 3 +- maths/polynomial_evaluation.py | 3 +- maths/relu.py | 3 +- maths/sieve_of_eratosthenes.py | 6 +- maths/square_root.py | 3 +- ...h_fibonacci_using_matrix_exponentiation.py | 8 +- matrix/rotate_matrix.py | 3 +- matrix/sherman_morrison.py | 6 +- matrix/tests/test_matrix_operation.py | 3 +- other/dijkstra_bankers_algorithm.py | 6 +- other/fischer_yates_shuffle.py | 3 +- other/game_of_life.py | 3 +- other/integeration_by_simpson_approx.py | 15 +-- other/linear_congruential_generator.py | 3 +- other/nested_brackets.py | 16 ++-- other/two_sum.py | 6 +- project_euler/problem_30/soln.py | 6 +- project_euler/problem_56/sol1.py | 3 +- scheduling/first_come_first_served.py | 3 +- searches/linear_search.py | 3 +- searches/simulated_annealing.py | 32 ++++--- searches/tabu_search.py | 96 ++++++++++--------- sorts/bucket_sort.py | 5 +- sorts/comb_sort.py | 8 +- sorts/random_normal_distribution_quicksort.py | 3 +- sorts/unknown_sort.py | 3 +- strings/boyer_moore_search.py | 4 +- strings/is_palindrome.py | 4 +- strings/jaro_winkler.py | 3 +- strings/knuth_morris_pratt.py | 8 +- strings/lower.py | 5 +- strings/min_cost_string_conversion.py | 3 +- strings/rabin_karp.py | 13 ++- strings/split.py | 3 +- strings/upper.py | 3 +- 90 files changed, 474 insertions(+), 321 deletions(-) diff --git a/.travis.yml b/.travis.yml index c9f601144418..21103c3570d3 100644 --- a/.travis.yml +++ b/.travis.yml @@ -10,7 +10,7 @@ notifications: on_success: never before_script: - black --check . || true - - flake8 --ignore=E203,W503 --max-complexity=25 --max-line-length=120 --statistics --count . + - flake8 --ignore=E203,W503 --max-complexity=25 --max-line-length=88 --statistics --count . - scripts/validate_filenames.py # no uppercase, no spaces, in a directory - pip install -r requirements.txt # fast fail on black, flake8, validate_filenames script: diff --git a/arithmetic_analysis/intersection.py b/arithmetic_analysis/intersection.py index 0fdcfbf1943e..8d14555b366f 100644 --- a/arithmetic_analysis/intersection.py +++ b/arithmetic_analysis/intersection.py @@ -1,9 +1,11 @@ import math -def intersection( - function, x0, x1 -): # function is the f we want to find its root and x0 and x1 are two random starting points +def intersection(function, x0, x1): + """ + function is the f we want to find its root + x0 and x1 are two random starting points + """ x_n = x0 x_n1 = x1 while True: diff --git a/backtracking/hamiltonian_cycle.py b/backtracking/hamiltonian_cycle.py index bc85e36b583f..3bd61fc667d9 100644 --- a/backtracking/hamiltonian_cycle.py +++ b/backtracking/hamiltonian_cycle.py @@ -16,8 +16,8 @@ def valid_connection( Checks whether it is possible to add next into path by validating 2 statements 1. There should be path between current and next vertex 2. Next vertex should not be in path - If both validations succeeds we return true saying that it is possible to connect this vertices - either we return false + If both validations succeeds we return True saying that it is possible to connect + this vertices either we return False Case 1:Use exact graph as in main function, with initialized values >>> graph = [[0, 1, 0, 1, 0], @@ -52,7 +52,8 @@ def util_hamilton_cycle(graph: List[List[int]], path: List[int], curr_ind: int) Pseudo-Code Base Case: 1. Chceck if we visited all of vertices - 1.1 If last visited vertex has path to starting vertex return True either return False + 1.1 If last visited vertex has path to starting vertex return True either + return False Recursive Step: 2. Iterate over each vertex Check if next vertex is valid for transiting from current vertex @@ -74,7 +75,8 @@ def util_hamilton_cycle(graph: List[List[int]], path: List[int], curr_ind: int) >>> print(path) [0, 1, 2, 4, 3, 0] - Case 2: Use exact graph as in previous case, but in the properties taken from middle of calculation + Case 2: Use exact graph as in previous case, but in the properties taken from + middle of calculation >>> graph = [[0, 1, 0, 1, 0], ... [1, 0, 1, 1, 1], ... [0, 1, 0, 0, 1], diff --git a/backtracking/n_queens.py b/backtracking/n_queens.py index 5d95c0970121..ca7beb830bba 100644 --- a/backtracking/n_queens.py +++ b/backtracking/n_queens.py @@ -12,8 +12,8 @@ def isSafe(board, row, column): """ - This function returns a boolean value True if it is safe to place a queen there considering - the current state of the board. + This function returns a boolean value True if it is safe to place a queen there + considering the current state of the board. Parameters : board(2D matrix) : board @@ -56,8 +56,8 @@ def solve(board, row): return for i in range(len(board)): """ - For every row it iterates through each column to check if it is feasible to place a - queen there. + For every row it iterates through each column to check if it is feasible to + place a queen there. If all the combinations for that particular branch are successful the board is reinitialized for the next possible combination. """ diff --git a/backtracking/sum_of_subsets.py b/backtracking/sum_of_subsets.py index e765a1b69714..c03df18ae743 100644 --- a/backtracking/sum_of_subsets.py +++ b/backtracking/sum_of_subsets.py @@ -1,9 +1,10 @@ """ - The sum-of-subsetsproblem states that a set of non-negative integers, and a value M, - determine all possible subsets of the given set whose summation sum equal to given M. + The sum-of-subsetsproblem states that a set of non-negative integers, and a + value M, determine all possible subsets of the given set whose summation sum + equal to given M. - Summation of the chosen numbers must be equal to given number M and one number can - be used only once. + Summation of the chosen numbers must be equal to given number M and one number + can be used only once. """ @@ -21,7 +22,8 @@ def create_state_space_tree(nums, max_sum, num_index, path, result, remaining_nu Creates a state space tree to iterate through each branch using DFS. It terminates the branching of a node when any of the two conditions given below satisfy. - This algorithm follows depth-fist-search and backtracks when the node is not branchable. + This algorithm follows depth-fist-search and backtracks when the node is not + branchable. """ if sum(path) > max_sum or (remaining_nums_sum + sum(path)) < max_sum: diff --git a/blockchain/chinese_remainder_theorem.py b/blockchain/chinese_remainder_theorem.py index 2b1e66ebea02..b6a486f0b1ed 100644 --- a/blockchain/chinese_remainder_theorem.py +++ b/blockchain/chinese_remainder_theorem.py @@ -1,8 +1,9 @@ # Chinese Remainder Theorem: # GCD ( Greatest Common Divisor ) or HCF ( Highest Common Factor ) -# If GCD(a,b) = 1, then for any remainder ra modulo a and any remainder rb modulo b there exists integer n, -# such that n = ra (mod a) and n = ra(mod b). If n1 and n2 are two such integers, then n1=n2(mod ab) +# If GCD(a,b) = 1, then for any remainder ra modulo a and any remainder rb modulo b +# there exists integer n, such that n = ra (mod a) and n = ra(mod b). If n1 and n2 are +# two such integers, then n1=n2(mod ab) # Algorithm : diff --git a/blockchain/diophantine_equation.py b/blockchain/diophantine_equation.py index dab4b3a65fb2..751b0efb7227 100644 --- a/blockchain/diophantine_equation.py +++ b/blockchain/diophantine_equation.py @@ -1,5 +1,6 @@ -# Diophantine Equation : Given integers a,b,c ( at least one of a and b != 0), the diophantine equation -# a*x + b*y = c has a solution (where x and y are integers) iff gcd(a,b) divides c. +# Diophantine Equation : Given integers a,b,c ( at least one of a and b != 0), the +# diophantine equation a*x + b*y = c has a solution (where x and y are integers) +# iff gcd(a,b) divides c. # GCD ( Greatest Common Divisor ) or HCF ( Highest Common Factor ) @@ -29,8 +30,9 @@ def diophantine(a, b, c): # Finding All solutions of Diophantine Equations: -# Theorem : Let gcd(a,b) = d, a = d*p, b = d*q. If (x0,y0) is a solution of Diophantine Equation a*x + b*y = c. -# a*x0 + b*y0 = c, then all the solutions have the form a(x0 + t*q) + b(y0 - t*p) = c, where t is an arbitrary integer. +# Theorem : Let gcd(a,b) = d, a = d*p, b = d*q. If (x0,y0) is a solution of Diophantine +# Equation a*x + b*y = c. a*x0 + b*y0 = c, then all the solutions have the form +# a(x0 + t*q) + b(y0 - t*p) = c, where t is an arbitrary integer. # n is the number of solution you want, n = 2 by default @@ -75,8 +77,9 @@ def greatest_common_divisor(a, b): >>> greatest_common_divisor(7,5) 1 - Note : In number theory, two integers a and b are said to be relatively prime, mutually prime, or co-prime - if the only positive integer (factor) that divides both of them is 1 i.e., gcd(a,b) = 1. + Note : In number theory, two integers a and b are said to be relatively prime, + mutually prime, or co-prime if the only positive integer (factor) that + divides both of them is 1 i.e., gcd(a,b) = 1. >>> greatest_common_divisor(121, 11) 11 @@ -91,7 +94,8 @@ def greatest_common_divisor(a, b): return b -# Extended Euclid's Algorithm : If d divides a and b and d = a*x + b*y for integers x and y, then d = gcd(a,b) +# Extended Euclid's Algorithm : If d divides a and b and d = a*x + b*y for integers +# x and y, then d = gcd(a,b) def extended_gcd(a, b): diff --git a/blockchain/modular_division.py b/blockchain/modular_division.py index c81c2138d1a8..c09863a3c5f0 100644 --- a/blockchain/modular_division.py +++ b/blockchain/modular_division.py @@ -3,8 +3,8 @@ # GCD ( Greatest Common Divisor ) or HCF ( Highest Common Factor ) -# Given three integers a, b, and n, such that gcd(a,n)=1 and n>1, the algorithm should return an integer x such that -# 0≤x≤n−1, and b/a=x(modn) (that is, b=ax(modn)). +# Given three integers a, b, and n, such that gcd(a,n)=1 and n>1, the algorithm should +# return an integer x such that 0≤x≤n−1, and b/a=x(modn) (that is, b=ax(modn)). # Theorem: # a has a multiplicative inverse modulo n iff gcd(a,n) = 1 @@ -68,7 +68,8 @@ def modular_division2(a, b, n): return x -# Extended Euclid's Algorithm : If d divides a and b and d = a*x + b*y for integers x and y, then d = gcd(a,b) +# Extended Euclid's Algorithm : If d divides a and b and d = a*x + b*y for integers x +# and y, then d = gcd(a,b) def extended_gcd(a, b): @@ -123,8 +124,9 @@ def greatest_common_divisor(a, b): >>> greatest_common_divisor(7,5) 1 - Note : In number theory, two integers a and b are said to be relatively prime, mutually prime, or co-prime - if the only positive integer (factor) that divides both of them is 1 i.e., gcd(a,b) = 1. + Note : In number theory, two integers a and b are said to be relatively prime, + mutually prime, or co-prime if the only positive integer (factor) that divides + both of them is 1 i.e., gcd(a,b) = 1. >>> greatest_common_divisor(121, 11) 11 diff --git a/ciphers/affine_cipher.py b/ciphers/affine_cipher.py index 21c92c6437e7..bcf8b6500a1a 100644 --- a/ciphers/affine_cipher.py +++ b/ciphers/affine_cipher.py @@ -55,7 +55,8 @@ def check_keys(keyA, keyB, mode): def encrypt_message(key: int, message: str) -> str: """ - >>> encrypt_message(4545, 'The affine cipher is a type of monoalphabetic substitution cipher.') + >>> encrypt_message(4545, 'The affine cipher is a type of monoalphabetic ' + ... 'substitution cipher.') 'VL}p MM{I}p~{HL}Gp{vp pFsH}pxMpyxIx JHL O}F{~pvuOvF{FuF{xIp~{HL}Gi' """ keyA, keyB = divmod(key, len(SYMBOLS)) @@ -72,7 +73,8 @@ def encrypt_message(key: int, message: str) -> str: def decrypt_message(key: int, message: str) -> str: """ - >>> decrypt_message(4545, 'VL}p MM{I}p~{HL}Gp{vp pFsH}pxMpyxIx JHL O}F{~pvuOvF{FuF{xIp~{HL}Gi') + >>> decrypt_message(4545, 'VL}p MM{I}p~{HL}Gp{vp pFsH}pxMpyxIx JHL O}F{~pvuOvF{FuF' + ... '{xIp~{HL}Gi') 'The affine cipher is a type of monoalphabetic substitution cipher.' """ keyA, keyB = divmod(key, len(SYMBOLS)) diff --git a/ciphers/base64_cipher.py b/ciphers/base64_cipher.py index f95403c7b426..338476934f28 100644 --- a/ciphers/base64_cipher.py +++ b/ciphers/base64_cipher.py @@ -39,7 +39,8 @@ def decode_base64(text): 'WELCOME to base64 encoding 😁' >>> decode_base64('QcOF4ZCD8JCAj/CfpJM=') 'AÅᐃ𐀏🤓' - >>> decode_base64("QUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFB\r\nQUFB") + >>> decode_base64("QUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUF" + ... "BQUFBQUFBQUFB\r\nQUFB") 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' """ base64_chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/" diff --git a/ciphers/elgamal_key_generator.py b/ciphers/elgamal_key_generator.py index bade678ad201..1b387751be27 100644 --- a/ciphers/elgamal_key_generator.py +++ b/ciphers/elgamal_key_generator.py @@ -16,7 +16,8 @@ def main(): # I have written my code naively same as definition of primitive root # however every time I run this program, memory exceeded... -# so I used 4.80 Algorithm in Handbook of Applied Cryptography(CRC Press, ISBN : 0-8493-8523-7, October 1996) +# so I used 4.80 Algorithm in +# Handbook of Applied Cryptography(CRC Press, ISBN : 0-8493-8523-7, October 1996) # and it seems to run nicely! def primitiveRoot(p_val): print("Generating primitive root of p") diff --git a/ciphers/hill_cipher.py b/ciphers/hill_cipher.py index 9cd4a73b4f44..82382d873baf 100644 --- a/ciphers/hill_cipher.py +++ b/ciphers/hill_cipher.py @@ -106,7 +106,8 @@ def check_determinant(self) -> None: req_l = len(self.key_string) if greatest_common_divisor(det, len(self.key_string)) != 1: raise ValueError( - f"determinant modular {req_l} of encryption key({det}) is not co prime w.r.t {req_l}.\nTry another key." + f"determinant modular {req_l} of encryption key({det}) is not co prime " + f"w.r.t {req_l}.\nTry another key." ) def process_text(self, text: str) -> str: diff --git a/ciphers/shuffled_shift_cipher.py b/ciphers/shuffled_shift_cipher.py index be5c6caf845b..22628f3c9d9e 100644 --- a/ciphers/shuffled_shift_cipher.py +++ b/ciphers/shuffled_shift_cipher.py @@ -83,19 +83,21 @@ def __make_key_list(self) -> list: Shuffling only 26 letters of the english alphabet can generate 26! combinations for the shuffled list. In the program we consider, a set of 97 characters (including letters, digits, punctuation and whitespaces), - thereby creating a possibility of 97! combinations (which is a 152 digit number in itself), - thus diminishing the possibility of a brute force approach. Moreover, - shift keys even introduce a multiple of 26 for a brute force approach + thereby creating a possibility of 97! combinations (which is a 152 digit number + in itself), thus diminishing the possibility of a brute force approach. + Moreover, shift keys even introduce a multiple of 26 for a brute force approach for each of the already 97! combinations. """ - # key_list_options contain nearly all printable except few elements from string.whitespace + # key_list_options contain nearly all printable except few elements from + # string.whitespace key_list_options = ( string.ascii_letters + string.digits + string.punctuation + " \t\n" ) keys_l = [] - # creates points known as breakpoints to break the key_list_options at those points and pivot each substring + # creates points known as breakpoints to break the key_list_options at those + # points and pivot each substring breakpoints = sorted(set(self.__passcode)) temp_list = [] @@ -103,7 +105,8 @@ def __make_key_list(self) -> list: for i in key_list_options: temp_list.extend(i) - # checking breakpoints at which to pivot temporary sublist and add it into keys_l + # checking breakpoints at which to pivot temporary sublist and add it into + # keys_l if i in breakpoints or i == key_list_options[-1]: keys_l.extend(temp_list[::-1]) temp_list = [] @@ -131,7 +134,8 @@ def decrypt(self, encoded_message: str) -> str: """ decoded_message = "" - # decoding shift like Caesar cipher algorithm implementing negative shift or reverse shift or left shift + # decoding shift like Caesar cipher algorithm implementing negative shift or + # reverse shift or left shift for i in encoded_message: position = self.__key_list.index(i) decoded_message += self.__key_list[ @@ -152,7 +156,8 @@ def encrypt(self, plaintext: str) -> str: """ encoded_message = "" - # encoding shift like Caesar cipher algorithm implementing positive shift or forward shift or right shift + # encoding shift like Caesar cipher algorithm implementing positive shift or + # forward shift or right shift for i in plaintext: position = self.__key_list.index(i) encoded_message += self.__key_list[ diff --git a/compression/peak_signal_to_noise_ratio.py b/compression/peak_signal_to_noise_ratio.py index f4a1ca41e14d..6c6c4c38a12a 100644 --- a/compression/peak_signal_to_noise_ratio.py +++ b/compression/peak_signal_to_noise_ratio.py @@ -1,6 +1,8 @@ """ - Peak signal-to-noise ratio - PSNR - https://en.wikipedia.org/wiki/Peak_signal-to-noise_ratio - Source: https://tutorials.techonical.com/how-to-calculate-psnr-value-of-two-images-using-python/ +Peak signal-to-noise ratio - PSNR + https://en.wikipedia.org/wiki/Peak_signal-to-noise_ratio +Source: +https://tutorials.techonical.com/how-to-calculate-psnr-value-of-two-images-using-python """ import math diff --git a/conversions/decimal_to_any.py b/conversions/decimal_to_any.py index 5abcb8e6549f..cbed1ac12214 100644 --- a/conversions/decimal_to_any.py +++ b/conversions/decimal_to_any.py @@ -66,9 +66,10 @@ def decimal_to_any(num: int, base: int) -> str: if base > 36: raise ValueError("base must be <= 36") # fmt: off - ALPHABET_VALUES = {'10': 'A', '11': 'B', '12': 'C', '13': 'D', '14': 'E', '15': 'F', '16': 'G', '17': 'H', - '18': 'I', '19': 'J', '20': 'K', '21': 'L', '22': 'M', '23': 'N', '24': 'O', '25': 'P', - '26': 'Q', '27': 'R', '28': 'S', '29': 'T', '30': 'U', '31': 'V', '32': 'W', '33': 'X', + ALPHABET_VALUES = {'10': 'A', '11': 'B', '12': 'C', '13': 'D', '14': 'E', '15': 'F', + '16': 'G', '17': 'H', '18': 'I', '19': 'J', '20': 'K', '21': 'L', + '22': 'M', '23': 'N', '24': 'O', '25': 'P', '26': 'Q', '27': 'R', + '28': 'S', '29': 'T', '30': 'U', '31': 'V', '32': 'W', '33': 'X', '34': 'Y', '35': 'Z'} # fmt: on new_value = "" diff --git a/conversions/decimal_to_hexadecimal.py b/conversions/decimal_to_hexadecimal.py index a70e3c7b97bf..6bd9533ab390 100644 --- a/conversions/decimal_to_hexadecimal.py +++ b/conversions/decimal_to_hexadecimal.py @@ -23,7 +23,8 @@ def decimal_to_hexadecimal(decimal): """ - take integer decimal value, return hexadecimal representation as str beginning with 0x + take integer decimal value, return hexadecimal representation as str beginning + with 0x >>> decimal_to_hexadecimal(5) '0x5' >>> decimal_to_hexadecimal(15) diff --git a/conversions/decimal_to_octal.py b/conversions/decimal_to_octal.py index 5341ca3569bb..8dc04830ad87 100644 --- a/conversions/decimal_to_octal.py +++ b/conversions/decimal_to_octal.py @@ -9,7 +9,8 @@ def decimal_to_octal(num: int) -> str: """Convert a Decimal Number to an Octal Number. - >>> all(decimal_to_octal(i) == oct(i) for i in (0, 2, 8, 64, 65, 216, 255, 256, 512)) + >>> all(decimal_to_octal(i) == oct(i) for i + ... in (0, 2, 8, 64, 65, 216, 255, 256, 512)) True """ octal = 0 diff --git a/data_structures/data_structures/heap/heap_generic.py b/data_structures/data_structures/heap/heap_generic.py index 8993d501331b..553cb94518c4 100644 --- a/data_structures/data_structures/heap/heap_generic.py +++ b/data_structures/data_structures/heap/heap_generic.py @@ -1,5 +1,7 @@ class Heap: - """A generic Heap class, can be used as min or max by passing the key function accordingly. + """ + A generic Heap class, can be used as min or max by passing the key function + accordingly. """ def __init__(self, key=None): @@ -9,7 +11,8 @@ def __init__(self, key=None): self.pos_map = {} # Stores current size of heap. self.size = 0 - # Stores function used to evaluate the score of an item on which basis ordering will be done. + # Stores function used to evaluate the score of an item on which basis ordering + # will be done. self.key = key or (lambda x: x) def _parent(self, i): @@ -41,7 +44,10 @@ def _cmp(self, i, j): return self.arr[i][1] < self.arr[j][1] def _get_valid_parent(self, i): - """Returns index of valid parent as per desired ordering among given index and both it's children""" + """ + Returns index of valid parent as per desired ordering among given index and + both it's children + """ left = self._left(i) right = self._right(i) valid_parent = i @@ -87,8 +93,8 @@ def delete_item(self, item): self.arr[index] = self.arr[self.size - 1] self.pos_map[self.arr[self.size - 1][0]] = index self.size -= 1 - # Make sure heap is right in both up and down direction. - # Ideally only one of them will make any change- so no performance loss in calling both. + # Make sure heap is right in both up and down direction. Ideally only one + # of them will make any change- so no performance loss in calling both. if self.size > index: self._heapify_up(index) self._heapify_down(index) @@ -109,7 +115,10 @@ def get_top(self): return self.arr[0] if self.size else None def extract_top(self): - """Returns top item tuple (Calculated value, item) from heap and removes it as well if present""" + """ + Return top item tuple (Calculated value, item) from heap and removes it as well + if present + """ top_item_tuple = self.get_top() if top_item_tuple: self.delete_item(top_item_tuple[0]) diff --git a/data_structures/linked_list/skip_list.py b/data_structures/linked_list/skip_list.py index 195d7ffc6b91..ee572cd3ed19 100644 --- a/data_structures/linked_list/skip_list.py +++ b/data_structures/linked_list/skip_list.py @@ -126,7 +126,8 @@ def _locate_node(self, key) -> Tuple[Optional[Node[KT, VT]], List[Node[KT, VT]]] """ :param key: Searched key, :return: Tuple with searched node (or None if given key is not present) - and list of nodes that refer (if key is present) of should refer to given node. + and list of nodes that refer (if key is present) of should refer to + given node. """ # Nodes with refer or should refer to output node @@ -141,7 +142,8 @@ def _locate_node(self, key) -> Tuple[Optional[Node[KT, VT]], List[Node[KT, VT]]] # in skipping searched key. while i < node.level and node.forward[i].key < key: node = node.forward[i] - # Each leftmost node (relative to searched node) will potentially have to be updated. + # Each leftmost node (relative to searched node) will potentially have to + # be updated. update_vector.append(node) update_vector.reverse() # Note that we were inserting values in reverse order. diff --git a/data_structures/stacks/infix_to_prefix_conversion.py b/data_structures/stacks/infix_to_prefix_conversion.py index 5aa41a119528..d3dc9e3e9c73 100644 --- a/data_structures/stacks/infix_to_prefix_conversion.py +++ b/data_structures/stacks/infix_to_prefix_conversion.py @@ -49,10 +49,8 @@ def infix_2_postfix(Infix): else: if len(Stack) == 0: Stack.append(x) # If stack is empty, push x to stack - else: - while ( - len(Stack) > 0 and priority[x] <= priority[Stack[-1]] - ): # while priority of x is not greater than priority of element in the stack + else: # while priority of x is not > priority of element in the stack + while len(Stack) > 0 and priority[x] <= priority[Stack[-1]]: Postfix.append(Stack.pop()) # pop stack & add to Postfix Stack.append(x) # push x to stack diff --git a/data_structures/trie/trie.py b/data_structures/trie/trie.py index 6947d97fc642..6582be24fd0c 100644 --- a/data_structures/trie/trie.py +++ b/data_structures/trie/trie.py @@ -1,8 +1,8 @@ """ A Trie/Prefix Tree is a kind of search tree used to provide quick lookup of words/patterns in a set of words. A basic Trie however has O(n^2) space complexity -making it impractical in practice. It however provides O(max(search_string, length of longest word)) -lookup time making it an optimal approach when space is not an issue. +making it impractical in practice. It however provides O(max(search_string, length of +longest word)) lookup time making it an optimal approach when space is not an issue. """ diff --git a/digital_image_processing/edge_detection/canny.py b/digital_image_processing/edge_detection/canny.py index 6ee3ac5a22ea..477ffcb9bbb1 100644 --- a/digital_image_processing/edge_detection/canny.py +++ b/digital_image_processing/edge_detection/canny.py @@ -29,8 +29,9 @@ def canny(image, threshold_low=15, threshold_high=30, weak=128, strong=255): dst = np.zeros((image_row, image_col)) """ - Non-maximum suppression. If the edge strength of the current pixel is the largest compared to the other pixels - in the mask with the same direction, the value will be preserved. Otherwise, the value will be suppressed. + Non-maximum suppression. If the edge strength of the current pixel is the largest + compared to the other pixels in the mask with the same direction, the value will be + preserved. Otherwise, the value will be suppressed. """ for row in range(1, image_row - 1): for col in range(1, image_col - 1): @@ -71,10 +72,12 @@ def canny(image, threshold_low=15, threshold_high=30, weak=128, strong=255): dst[row, col] = sobel_grad[row, col] """ - High-Low threshold detection. If an edge pixel’s gradient value is higher than the high threshold - value, it is marked as a strong edge pixel. If an edge pixel’s gradient value is smaller than the high - threshold value and larger than the low threshold value, it is marked as a weak edge pixel. If an edge - pixel's value is smaller than the low threshold value, it will be suppressed. + High-Low threshold detection. If an edge pixel’s gradient value is higher + than the high threshold value, it is marked as a strong edge pixel. If an + edge pixel’s gradient value is smaller than the high threshold value and + larger than the low threshold value, it is marked as a weak edge pixel. If + an edge pixel's value is smaller than the low threshold value, it will be + suppressed. """ if dst[row, col] >= threshold_high: dst[row, col] = strong @@ -84,9 +87,10 @@ def canny(image, threshold_low=15, threshold_high=30, weak=128, strong=255): dst[row, col] = weak """ - Edge tracking. Usually a weak edge pixel caused from true edges will be connected to a strong edge pixel while - noise responses are unconnected. As long as there is one strong edge pixel that is involved in its 8-connected - neighborhood, that weak edge point can be identified as one that should be preserved. + Edge tracking. Usually a weak edge pixel caused from true edges will be connected + to a strong edge pixel while noise responses are unconnected. As long as there is + one strong edge pixel that is involved in its 8-connected neighborhood, that weak + edge point can be identified as one that should be preserved. """ for row in range(1, image_row): for col in range(1, image_col): diff --git a/digital_image_processing/resize/resize.py b/digital_image_processing/resize/resize.py index 4fd222ccd6c6..afcacda4bd86 100644 --- a/digital_image_processing/resize/resize.py +++ b/digital_image_processing/resize/resize.py @@ -36,7 +36,8 @@ def get_x(self, x: int) -> int: Get parent X coordinate for destination X :param x: Destination X coordinate :return: Parent X coordinate based on `x ratio` - >>> nn = NearestNeighbour(imread("digital_image_processing/image_data/lena.jpg", 1), 100, 100) + >>> nn = NearestNeighbour(imread("digital_image_processing/image_data/lena.jpg", + ... 1), 100, 100) >>> nn.ratio_x = 0.5 >>> nn.get_x(4) 2 @@ -48,7 +49,8 @@ def get_y(self, y: int) -> int: Get parent Y coordinate for destination Y :param y: Destination X coordinate :return: Parent X coordinate based on `y ratio` - >>> nn = NearestNeighbour(imread("digital_image_processing/image_data/lena.jpg", 1), 100, 100) + >>> nn = NearestNeighbour(imread("digital_image_processing/image_data/lena.jpg", + 1), 100, 100) >>> nn.ratio_y = 0.5 >>> nn.get_y(4) 2 diff --git a/digital_image_processing/sepia.py b/digital_image_processing/sepia.py index e91d57d0379c..b97b4c0ae1bc 100644 --- a/digital_image_processing/sepia.py +++ b/digital_image_processing/sepia.py @@ -6,7 +6,10 @@ def make_sepia(img, factor: int): - """ Function create sepia tone. Source: https://en.wikipedia.org/wiki/Sepia_(color) """ + """ + Function create sepia tone. + Source: https://en.wikipedia.org/wiki/Sepia_(color) + """ pixel_h, pixel_v = img.shape[0], img.shape[1] def to_grayscale(blue, green, red): diff --git a/divide_and_conquer/convex_hull.py b/divide_and_conquer/convex_hull.py index 11b16975c8b4..de67cb1e06df 100644 --- a/divide_and_conquer/convex_hull.py +++ b/divide_and_conquer/convex_hull.py @@ -140,7 +140,8 @@ def _validate_input(points): Exception --------- - ValueError: if points is empty or None, or if a wrong data structure like a scalar is passed + ValueError: if points is empty or None, or if a wrong data structure like a scalar + is passed TypeError: if an iterable but non-indexable object (eg. dictionary) is passed. The exception to this a set which we'll convert to a list before using @@ -229,10 +230,10 @@ def convex_hull_bf(points): """ Constructs the convex hull of a set of 2D points using a brute force algorithm. The algorithm basically considers all combinations of points (i, j) and uses the - definition of convexity to determine whether (i, j) is part of the convex hull or not. - (i, j) is part of the convex hull if and only iff there are no points on both sides - of the line segment connecting the ij, and there is no point k such that k is on either end - of the ij. + definition of convexity to determine whether (i, j) is part of the convex hull or + not. (i, j) is part of the convex hull if and only iff there are no points on both + sides of the line segment connecting the ij, and there is no point k such that k is + on either end of the ij. Runtime: O(n^3) - definitely horrible @@ -255,9 +256,11 @@ def convex_hull_bf(points): [(0.0, 0.0), (1.0, 0.0), (10.0, 1.0)] >>> convex_hull_bf([[0, 0], [1, 0], [10, 0]]) [(0.0, 0.0), (10.0, 0.0)] - >>> convex_hull_bf([[-1, 1],[-1, -1], [0, 0], [0.5, 0.5], [1, -1], [1, 1], [-0.75, 1]]) + >>> convex_hull_bf([[-1, 1],[-1, -1], [0, 0], [0.5, 0.5], [1, -1], [1, 1], + ... [-0.75, 1]]) [(-1.0, -1.0), (-1.0, 1.0), (1.0, -1.0), (1.0, 1.0)] - >>> convex_hull_bf([(0, 3), (2, 2), (1, 1), (2, 1), (3, 0), (0, 0), (3, 3), (2, -1), (2, -4), (1, -3)]) + >>> convex_hull_bf([(0, 3), (2, 2), (1, 1), (2, 1), (3, 0), (0, 0), (3, 3), + ... (2, -1), (2, -4), (1, -3)]) [(0.0, 0.0), (0.0, 3.0), (1.0, -3.0), (2.0, -4.0), (3.0, 0.0), (3.0, 3.0)] """ @@ -299,9 +302,10 @@ def convex_hull_bf(points): def convex_hull_recursive(points): """ Constructs the convex hull of a set of 2D points using a divide-and-conquer strategy - The algorithm exploits the geometric properties of the problem by repeatedly partitioning - the set of points into smaller hulls, and finding the convex hull of these smaller hulls. - The union of the convex hull from smaller hulls is the solution to the convex hull of the larger problem. + The algorithm exploits the geometric properties of the problem by repeatedly + partitioning the set of points into smaller hulls, and finding the convex hull of + these smaller hulls. The union of the convex hull from smaller hulls is the + solution to the convex hull of the larger problem. Parameter --------- @@ -320,9 +324,11 @@ def convex_hull_recursive(points): [(0.0, 0.0), (1.0, 0.0), (10.0, 1.0)] >>> convex_hull_recursive([[0, 0], [1, 0], [10, 0]]) [(0.0, 0.0), (10.0, 0.0)] - >>> convex_hull_recursive([[-1, 1],[-1, -1], [0, 0], [0.5, 0.5], [1, -1], [1, 1], [-0.75, 1]]) + >>> convex_hull_recursive([[-1, 1],[-1, -1], [0, 0], [0.5, 0.5], [1, -1], [1, 1], + [-0.75, 1]]) [(-1.0, -1.0), (-1.0, 1.0), (1.0, -1.0), (1.0, 1.0)] - >>> convex_hull_recursive([(0, 3), (2, 2), (1, 1), (2, 1), (3, 0), (0, 0), (3, 3), (2, -1), (2, -4), (1, -3)]) + >>> convex_hull_recursive([(0, 3), (2, 2), (1, 1), (2, 1), (3, 0), (0, 0), (3, 3), + (2, -1), (2, -4), (1, -3)]) [(0.0, 0.0), (0.0, 3.0), (1.0, -3.0), (2.0, -4.0), (3.0, 0.0), (3.0, 3.0)] """ @@ -335,10 +341,12 @@ def convex_hull_recursive(points): # use these two anchors to divide all the points into two hulls, # an upper hull and a lower hull. - # all points to the left (above) the line joining the extreme points belong to the upper hull - # all points to the right (below) the line joining the extreme points below to the lower hull - # ignore all points on the line joining the extreme points since they cannot be part of the - # convex hull + # all points to the left (above) the line joining the extreme points belong to the + # upper hull + # all points to the right (below) the line joining the extreme points below to the + # lower hull + # ignore all points on the line joining the extreme points since they cannot be + # part of the convex hull left_most_point = points[0] right_most_point = points[n - 1] @@ -366,10 +374,12 @@ def _construct_hull(points, left, right, convex_set): Parameters --------- - points: list or None, the hull of points from which to choose the next convex-hull point + points: list or None, the hull of points from which to choose the next convex-hull + point left: Point, the point to the left of line segment joining left and right right: The point to the right of the line segment joining left and right - convex_set: set, the current convex-hull. The state of convex-set gets updated by this function + convex_set: set, the current convex-hull. The state of convex-set gets updated by + this function Note ---- diff --git a/dynamic_programming/fibonacci.py b/dynamic_programming/fibonacci.py index 45319269f5d4..cab1358ddea1 100644 --- a/dynamic_programming/fibonacci.py +++ b/dynamic_programming/fibonacci.py @@ -1,5 +1,6 @@ """ -This is a pure Python implementation of Dynamic Programming solution to the fibonacci sequence problem. +This is a pure Python implementation of Dynamic Programming solution to the fibonacci +sequence problem. """ diff --git a/dynamic_programming/integer_partition.py b/dynamic_programming/integer_partition.py index ec8c5bf62d7d..4eb06348ce84 100644 --- a/dynamic_programming/integer_partition.py +++ b/dynamic_programming/integer_partition.py @@ -1,7 +1,8 @@ """ -The number of partitions of a number n into at least k parts equals the number of partitions into exactly k parts -plus the number of partitions into at least k-1 parts. Subtracting 1 from each part of a partition of n into k parts -gives a partition of n-k into k parts. These two facts together are used for this algorithm. +The number of partitions of a number n into at least k parts equals the number of +partitions into exactly k parts plus the number of partitions into at least k-1 parts. +Subtracting 1 from each part of a partition of n into k parts gives a partition of n-k +into k parts. These two facts together are used for this algorithm. """ diff --git a/dynamic_programming/iterating_through_submasks.py b/dynamic_programming/iterating_through_submasks.py index 28c4ded66495..cb27a5b884bd 100644 --- a/dynamic_programming/iterating_through_submasks.py +++ b/dynamic_programming/iterating_through_submasks.py @@ -12,7 +12,8 @@ def list_of_submasks(mask: int) -> List[int]: """ Args: - mask : number which shows mask ( always integer > 0, zero does not have any submasks ) + mask : number which shows mask ( always integer > 0, zero does not have any + submasks ) Returns: all_submasks : the list of submasks of mask (mask s is called submask of mask diff --git a/dynamic_programming/knapsack.py b/dynamic_programming/knapsack.py index 1987dc35fd03..69e54c00aa4e 100644 --- a/dynamic_programming/knapsack.py +++ b/dynamic_programming/knapsack.py @@ -9,8 +9,8 @@ def MF_knapsack(i, wt, val, j): """ - This code involves the concept of memory functions. Here we solve the subproblems which are needed - unlike the below example + This code involves the concept of memory functions. Here we solve the subproblems + which are needed unlike the below example F is a 2D array with -1s filled up """ global F # a global dp table for knapsack diff --git a/dynamic_programming/longest_common_subsequence.py b/dynamic_programming/longest_common_subsequence.py index b319421b9aa2..fdcf3311a017 100644 --- a/dynamic_programming/longest_common_subsequence.py +++ b/dynamic_programming/longest_common_subsequence.py @@ -1,6 +1,7 @@ """ -LCS Problem Statement: Given two sequences, find the length of longest subsequence present in both of them. -A subsequence is a sequence that appears in the same relative order, but not necessarily continuous. +LCS Problem Statement: Given two sequences, find the length of longest subsequence +present in both of them. A subsequence is a sequence that appears in the same relative +order, but not necessarily continuous. Example:"abc", "abg" are subsequences of "abcdefgh". """ diff --git a/dynamic_programming/longest_sub_array.py b/dynamic_programming/longest_sub_array.py index f3b5705b7de2..30159a1386c3 100644 --- a/dynamic_programming/longest_sub_array.py +++ b/dynamic_programming/longest_sub_array.py @@ -1,10 +1,12 @@ """ Author : Yvonne -This is a pure Python implementation of Dynamic Programming solution to the longest_sub_array problem. +This is a pure Python implementation of Dynamic Programming solution to the + longest_sub_array problem. The problem is : -Given an array, to find the longest and continuous sub array and get the max sum of the sub array in the given array. +Given an array, to find the longest and continuous sub array and get the max sum of the + sub array in the given array. """ diff --git a/dynamic_programming/rod_cutting.py b/dynamic_programming/rod_cutting.py index 26af71915833..a4919742e739 100644 --- a/dynamic_programming/rod_cutting.py +++ b/dynamic_programming/rod_cutting.py @@ -13,8 +13,9 @@ def naive_cut_rod_recursive(n: int, prices: list): """ - Solves the rod-cutting problem via naively without using the benefit of dynamic programming. - The results is the same sub-problems are solved several times leading to an exponential runtime + Solves the rod-cutting problem via naively without using the benefit of dynamic + programming. The results is the same sub-problems are solved several times + leading to an exponential runtime Runtime: O(2^n) @@ -26,7 +27,8 @@ def naive_cut_rod_recursive(n: int, prices: list): Returns ------- - The maximum revenue obtainable for a rod of length n given the list of prices for each piece. + The maximum revenue obtainable for a rod of length n given the list of prices + for each piece. Examples -------- @@ -50,8 +52,9 @@ def naive_cut_rod_recursive(n: int, prices: list): def top_down_cut_rod(n: int, prices: list): """ - Constructs a top-down dynamic programming solution for the rod-cutting problem - via memoization. This function serves as a wrapper for _top_down_cut_rod_recursive + Constructs a top-down dynamic programming solution for the rod-cutting + problem via memoization. This function serves as a wrapper for + _top_down_cut_rod_recursive Runtime: O(n^2) @@ -63,12 +66,13 @@ def top_down_cut_rod(n: int, prices: list): Note ---- - For convenience and because Python's lists using 0-indexing, length(max_rev) = n + 1, - to accommodate for the revenue obtainable from a rod of length 0. + For convenience and because Python's lists using 0-indexing, length(max_rev) = + n + 1, to accommodate for the revenue obtainable from a rod of length 0. Returns ------- - The maximum revenue obtainable for a rod of length n given the list of prices for each piece. + The maximum revenue obtainable for a rod of length n given the list of prices + for each piece. Examples ------- @@ -99,7 +103,8 @@ def _top_down_cut_rod_recursive(n: int, prices: list, max_rev: list): Returns ------- - The maximum revenue obtainable for a rod of length n given the list of prices for each piece. + The maximum revenue obtainable for a rod of length n given the list of prices + for each piece. """ if max_rev[n] >= 0: return max_rev[n] @@ -144,7 +149,8 @@ def bottom_up_cut_rod(n: int, prices: list): """ _enforce_args(n, prices) - # length(max_rev) = n + 1, to accommodate for the revenue obtainable from a rod of length 0. + # length(max_rev) = n + 1, to accommodate for the revenue obtainable from a rod of + # length 0. max_rev = [float("-inf") for _ in range(n + 1)] max_rev[0] = 0 @@ -167,7 +173,8 @@ def _enforce_args(n: int, prices: list): Throws ValueError: - if n is negative or there are fewer items in the price list than the length of the rod + if n is negative or there are fewer items in the price list than the length of + the rod """ if n < 0: raise ValueError(f"n must be greater than or equal to 0. Got n = {n}") diff --git a/dynamic_programming/subset_generation.py b/dynamic_programming/subset_generation.py index 7d99727dd900..4781b23b32eb 100644 --- a/dynamic_programming/subset_generation.py +++ b/dynamic_programming/subset_generation.py @@ -1,4 +1,4 @@ -# Python program to print all subset combinations of n element in given set of r element. +# Print all subset combinations of n element in given set of r element. def combination_util(arr, n, r, index, data, i): diff --git a/dynamic_programming/sum_of_subset.py b/dynamic_programming/sum_of_subset.py index 9394d29dabc0..a12177b57c74 100644 --- a/dynamic_programming/sum_of_subset.py +++ b/dynamic_programming/sum_of_subset.py @@ -9,7 +9,8 @@ def isSumSubset(arr, arrLen, requiredSum): # initially no subsets can be formed hence False/0 subset = [[False for i in range(requiredSum + 1)] for i in range(arrLen + 1)] - # for each arr value, a sum of zero(0) can be formed by not taking any element hence True/1 + # for each arr value, a sum of zero(0) can be formed by not taking any element + # hence True/1 for i in range(arrLen + 1): subset[i][0] = True diff --git a/fuzzy_logic/fuzzy_operations.py b/fuzzy_logic/fuzzy_operations.py index 34dd9c029be7..795a8cde653f 100644 --- a/fuzzy_logic/fuzzy_operations.py +++ b/fuzzy_logic/fuzzy_operations.py @@ -14,7 +14,8 @@ # Create universe of discourse in Python using linspace () X = np.linspace(start=0, stop=75, num=75, endpoint=True, retstep=False) - # Create two fuzzy sets by defining any membership function (trapmf(), gbellmf(),gaussmf(), etc). + # Create two fuzzy sets by defining any membership function + # (trapmf(), gbellmf(), gaussmf(), etc). abc1 = [0, 25, 50] abc2 = [25, 50, 75] young = fuzz.membership.trimf(X, abc1) diff --git a/geodesy/lamberts_ellipsoidal_distance.py b/geodesy/lamberts_ellipsoidal_distance.py index 613c779a1b3e..969e70befd0b 100644 --- a/geodesy/lamberts_ellipsoidal_distance.py +++ b/geodesy/lamberts_ellipsoidal_distance.py @@ -11,15 +11,16 @@ def lamberts_ellipsoidal_distance( two points on the surface of earth given longitudes and latitudes https://en.wikipedia.org/wiki/Geographical_distance#Lambert's_formula_for_long_lines - NOTE: This algorithm uses geodesy/haversine_distance.py to compute central angle, sigma + NOTE: This algorithm uses geodesy/haversine_distance.py to compute central angle, + sigma - Representing the earth as an ellipsoid allows us to approximate distances between points - on the surface much better than a sphere. Ellipsoidal formulas treat the Earth as an - oblate ellipsoid which means accounting for the flattening that happens at the North - and South poles. Lambert's formulae provide accuracy on the order of 10 meteres over - thousands of kilometeres. Other methods can provide millimeter-level accuracy but this - is a simpler method to calculate long range distances without increasing computational - intensity. + Representing the earth as an ellipsoid allows us to approximate distances between + points on the surface much better than a sphere. Ellipsoidal formulas treat the + Earth as an oblate ellipsoid which means accounting for the flattening that happens + at the North and South poles. Lambert's formulae provide accuracy on the order of + 10 meteres over thousands of kilometeres. Other methods can provide + millimeter-level accuracy but this is a simpler method to calculate long range + distances without increasing computational intensity. Args: lat1, lon1: latitude and longitude of coordinate 1 @@ -50,7 +51,8 @@ def lamberts_ellipsoidal_distance( # Equation Parameters # https://en.wikipedia.org/wiki/Geographical_distance#Lambert's_formula_for_long_lines flattening = (AXIS_A - AXIS_B) / AXIS_A - # Parametric latitudes https://en.wikipedia.org/wiki/Latitude#Parametric_(or_reduced)_latitude + # Parametric latitudes + # https://en.wikipedia.org/wiki/Latitude#Parametric_(or_reduced)_latitude b_lat1 = atan((1 - flattening) * tan(radians(lat1))) b_lat2 = atan((1 - flattening) * tan(radians(lat2))) diff --git a/graphs/a_star.py b/graphs/a_star.py index a5d59626b0bc..cb5b2fcd16e8 100644 --- a/graphs/a_star.py +++ b/graphs/a_star.py @@ -53,8 +53,8 @@ def search(grid, init, goal, cost, heuristic): while not found and not resign: if len(cell) == 0: return "FAIL" - else: - cell.sort() # to choose the least costliest action so as to move closer to the goal + else: # to choose the least costliest action so as to move closer to the goal + cell.sort() cell.reverse() next = cell.pop() x = next[2] diff --git a/graphs/graphs_floyd_warshall.py b/graphs/graphs_floyd_warshall.py index 5727a2f21d89..56cf8b9e382b 100644 --- a/graphs/graphs_floyd_warshall.py +++ b/graphs/graphs_floyd_warshall.py @@ -1,7 +1,7 @@ # floyd_warshall.py """ - The problem is to find the shortest distance between all pairs of vertices in a weighted directed graph that can - have negative edge weights. + The problem is to find the shortest distance between all pairs of vertices in a + weighted directed graph that can have negative edge weights. """ @@ -26,10 +26,11 @@ def floyd_warshall(graph, v): distance[u][v] will contain the shortest distance from vertex u to v. 1. For all edges from v to n, distance[i][j] = weight(edge(i, j)). - 3. The algorithm then performs distance[i][j] = min(distance[i][j], distance[i][k] + distance[k][j]) for each - possible pair i, j of vertices. + 3. The algorithm then performs distance[i][j] = min(distance[i][j], distance[i][k] + + distance[k][j]) for each possible pair i, j of vertices. 4. The above is repeated for each vertex k in the graph. - 5. Whenever distance[i][j] is given a new minimum value, next vertex[i][j] is updated to the next vertex[i][k]. + 5. Whenever distance[i][j] is given a new minimum value, next vertex[i][j] is + updated to the next vertex[i][k]. """ dist = [[float("inf") for _ in range(v)] for _ in range(v)] diff --git a/graphs/minimum_spanning_tree_prims.py b/graphs/minimum_spanning_tree_prims.py index 77ff149e2a38..527f3cf98c20 100644 --- a/graphs/minimum_spanning_tree_prims.py +++ b/graphs/minimum_spanning_tree_prims.py @@ -72,7 +72,8 @@ def deleteMinimum(heap, positions): visited = [0 for i in range(len(l))] Nbr_TV = [-1 for i in range(len(l))] # Neighboring Tree Vertex of selected vertex - # Minimum Distance of explored vertex with neighboring vertex of partial tree formed in graph + # Minimum Distance of explored vertex with neighboring vertex of partial tree + # formed in graph Distance_TV = [] # Heap of Distance of vertices from their neighboring vertex Positions = [] diff --git a/graphs/tarjans_scc.py b/graphs/tarjans_scc.py index 4b0a689ea3c0..30f8ca8a204f 100644 --- a/graphs/tarjans_scc.py +++ b/graphs/tarjans_scc.py @@ -5,19 +5,20 @@ def tarjan(g): """ Tarjan's algo for finding strongly connected components in a directed graph - Uses two main attributes of each node to track reachability, the index of that node within a component(index), - and the lowest index reachable from that node(lowlink). + Uses two main attributes of each node to track reachability, the index of that node + within a component(index), and the lowest index reachable from that node(lowlink). - We then perform a dfs of the each component making sure to update these parameters for each node and saving the - nodes we visit on the way. + We then perform a dfs of the each component making sure to update these parameters + for each node and saving the nodes we visit on the way. - If ever we find that the lowest reachable node from a current node is equal to the index of the current node then it - must be the root of a strongly connected component and so we save it and it's equireachable vertices as a strongly + If ever we find that the lowest reachable node from a current node is equal to the + index of the current node then it must be the root of a strongly connected + component and so we save it and it's equireachable vertices as a strongly connected component. - Complexity: strong_connect() is called at most once for each node and has a complexity of O(|E|) as it is DFS. + Complexity: strong_connect() is called at most once for each node and has a + complexity of O(|E|) as it is DFS. Therefore this has complexity O(|V| + |E|) for a graph G = (V, E) - """ n = len(g) diff --git a/hashes/adler32.py b/hashes/adler32.py index 8b82fff477fd..fad747abe3c3 100644 --- a/hashes/adler32.py +++ b/hashes/adler32.py @@ -1,7 +1,9 @@ """ Adler-32 is a checksum algorithm which was invented by Mark Adler in 1995. - Compared to a cyclic redundancy check of the same length, it trades reliability for speed (preferring the latter). - Adler-32 is more reliable than Fletcher-16, and slightly less reliable than Fletcher-32.[2] + Compared to a cyclic redundancy check of the same length, it trades reliability for + speed (preferring the latter). + Adler-32 is more reliable than Fletcher-16, and slightly less reliable than + Fletcher-32.[2] source: https://en.wikipedia.org/wiki/Adler-32 """ diff --git a/hashes/sdbm.py b/hashes/sdbm.py index f80941306a74..86d47a1d9967 100644 --- a/hashes/sdbm.py +++ b/hashes/sdbm.py @@ -1,13 +1,17 @@ """ - This algorithm was created for sdbm (a public-domain reimplementation of ndbm) database library. - It was found to do well in scrambling bits, causing better distribution of the keys and fewer splits. + This algorithm was created for sdbm (a public-domain reimplementation of ndbm) + database library. + It was found to do well in scrambling bits, causing better distribution of the keys + and fewer splits. It also happens to be a good general hashing function with good distribution. The actual function (pseudo code) is: for i in i..len(str): hash(i) = hash(i - 1) * 65599 + str[i]; - What is included below is the faster version used in gawk. [there is even a faster, duff-device version] - The magic constant 65599 was picked out of thin air while experimenting with different constants. + What is included below is the faster version used in gawk. [there is even a faster, + duff-device version] + The magic constant 65599 was picked out of thin air while experimenting with + different constants. It turns out to be a prime. This is one of the algorithms used in berkeley db (see sleepycat) and elsewhere. @@ -18,7 +22,8 @@ def sdbm(plain_text: str) -> str: """ Function implements sdbm hash, easy to use, great for bits scrambling. - iterates over each character in the given string and applies function to each of them. + iterates over each character in the given string and applies function to each of + them. >>> sdbm('Algorithms') 1462174910723540325254304520539387479031000036 diff --git a/hashes/sha1.py b/hashes/sha1.py index c74ec0c853de..26069fb9b7fb 100644 --- a/hashes/sha1.py +++ b/hashes/sha1.py @@ -3,7 +3,8 @@ to find hash of string or hash of text from a file. Usage: python sha1.py --string "Hello World!!" python sha1.py --file "hello_world.txt" - When run without any arguments, it prints the hash of the string "Hello World!! Welcome to Cryptography" + When run without any arguments, it prints the hash of the string "Hello World!! + Welcome to Cryptography" Also contains a Test class to verify that the generated Hash is same as that returned by the hashlib library @@ -39,7 +40,8 @@ class SHA1Hash: def __init__(self, data): """ Inititates the variables data and h. h is a list of 5 8-digit Hexadecimal - numbers corresponding to (1732584193, 4023233417, 2562383102, 271733878, 3285377520) + numbers corresponding to + (1732584193, 4023233417, 2562383102, 271733878, 3285377520) respectively. We will start with this as a message digest. 0x is how you write Hexadecimal numbers in Python """ @@ -74,8 +76,8 @@ def split_blocks(self): # @staticmethod def expand_block(self, block): """ - Takes a bytestring-block of length 64, unpacks it to a list of integers and returns a - list of 80 integers after some bit operations + Takes a bytestring-block of length 64, unpacks it to a list of integers and + returns a list of 80 integers after some bit operations """ w = list(struct.unpack(">16L", block)) + [0] * 64 for i in range(16, 80): @@ -84,12 +86,13 @@ def expand_block(self, block): def final_hash(self): """ - Calls all the other methods to process the input. Pads the data, then splits into - blocks and then does a series of operations for each block (including expansion). + Calls all the other methods to process the input. Pads the data, then splits + into blocks and then does a series of operations for each block (including + expansion). For each block, the variable h that was initialized is copied to a,b,c,d,e - and these 5 variables a,b,c,d,e undergo several changes. After all the blocks are - processed, these 5 variables are pairwise added to h ie a to h[0], b to h[1] and so on. - This h becomes our final hash which is returned. + and these 5 variables a,b,c,d,e undergo several changes. After all the blocks + are processed, these 5 variables are pairwise added to h ie a to h[0], b to h[1] + and so on. This h becomes our final hash which is returned. """ self.padded_data = self.padding() self.blocks = self.split_blocks() @@ -138,8 +141,8 @@ def testMatchHashes(self): def main(): """ - Provides option 'string' or 'file' to take input and prints the calculated SHA1 hash. - unittest.main() has been commented because we probably don't want to run + Provides option 'string' or 'file' to take input and prints the calculated SHA1 + hash. unittest.main() has been commented because we probably don't want to run the test each time. """ # unittest.main() diff --git a/machine_learning/gradient_descent.py b/machine_learning/gradient_descent.py index 811cc68467f9..9fa460a07562 100644 --- a/machine_learning/gradient_descent.py +++ b/machine_learning/gradient_descent.py @@ -1,5 +1,6 @@ """ -Implementation of gradient descent algorithm for minimizing cost of a linear hypothesis function. +Implementation of gradient descent algorithm for minimizing cost of a linear hypothesis +function. """ import numpy @@ -75,7 +76,8 @@ def summation_of_cost_derivative(index, end=m): :param index: index wrt derivative is being calculated :param end: value where summation ends, default is m, number of examples :return: Returns the summation of cost derivative - Note: If index is -1, this means we are calculating summation wrt to biased parameter. + Note: If index is -1, this means we are calculating summation wrt to biased + parameter. """ summation_value = 0 for i in range(end): @@ -90,7 +92,8 @@ def get_cost_derivative(index): """ :param index: index of the parameter vector wrt to derivative is to be calculated :return: derivative wrt to that index - Note: If index is -1, this means we are calculating summation wrt to biased parameter. + Note: If index is -1, this means we are calculating summation wrt to biased + parameter. """ cost_derivative_value = summation_of_cost_derivative(index, m) / m return cost_derivative_value diff --git a/machine_learning/polymonial_regression.py b/machine_learning/polymonial_regression.py index cdcb90b8fd21..f781141f169c 100644 --- a/machine_learning/polymonial_regression.py +++ b/machine_learning/polymonial_regression.py @@ -10,7 +10,8 @@ # Importing the dataset dataset = pd.read_csv( - "https://s3.us-west-2.amazonaws.com/public.gamelab.fun/dataset/position_salaries.csv" + "https://s3.us-west-2.amazonaws.com/public.gamelab.fun/dataset/" + "position_salaries.csv" ) X = dataset.iloc[:, 1:2].values y = dataset.iloc[:, 2].values diff --git a/machine_learning/sequential_minimum_optimization.py b/machine_learning/sequential_minimum_optimization.py index a0b99a788cbd..3583b18ab2e6 100644 --- a/machine_learning/sequential_minimum_optimization.py +++ b/machine_learning/sequential_minimum_optimization.py @@ -42,7 +42,10 @@ from sklearn.datasets import make_blobs, make_circles from sklearn.preprocessing import StandardScaler -CANCER_DATASET_URL = "http://archive.ics.uci.edu/ml/machine-learning-databases/breast-cancer-wisconsin/wdbc.data" +CANCER_DATASET_URL = ( + "http://archive.ics.uci.edu/ml/machine-learning-databases/" + "breast-cancer-wisconsin/wdbc.data" +) class SmoSVM: @@ -124,7 +127,8 @@ def fit(self): b_old = self._b self._b = b - # 4: update error value,here we only calculate those non-bound samples' error + # 4: update error value,here we only calculate those non-bound samples' + # error self._unbound = [i for i in self._all_samples if self._is_unbound(i)] for s in self.unbound: if s == i1 or s == i2: @@ -231,8 +235,10 @@ def _choose_a1(self): """ Choose first alpha ;steps: 1:First loop over all sample - 2:Second loop over all non-bound samples till all non-bound samples does not voilate kkt condition. - 3:Repeat this two process endlessly,till all samples does not voilate kkt condition samples after first loop. + 2:Second loop over all non-bound samples till all non-bound samples does not + voilate kkt condition. + 3:Repeat this two process endlessly,till all samples does not voilate kkt + condition samples after first loop. """ while True: all_not_obey = True @@ -352,8 +358,8 @@ def _get_new_alpha(self, i1, i2, a1, a2, e1, e2, y1, y2): ) """ # way 2 - Use objective function check which alpha2 new could get the minimal objectives - + Use objective function check which alpha2 new could get the minimal + objectives """ if ol < (oh - self._eps): a2_new = L @@ -572,11 +578,11 @@ def plot_partition_boundary( model, train_data, ax, resolution=100, colors=("b", "k", "r") ): """ - We can not get the optimum w of our kernel svm model which is different from linear svm. - For this reason, we generate randomly distributed points with high desity and prediced values of these points are - calculated by using our tained model. Then we could use this prediced values to draw contour map. + We can not get the optimum w of our kernel svm model which is different from linear + svm. For this reason, we generate randomly distributed points with high desity and + prediced values of these points are calculated by using our tained model. Then we + could use this prediced values to draw contour map. And this contour map can represent svm's partition boundary. - """ train_data_x = train_data[:, 1] train_data_y = train_data[:, 2] diff --git a/maths/bailey_borwein_plouffe.py b/maths/bailey_borwein_plouffe.py index be97acfd063e..febf7e975516 100644 --- a/maths/bailey_borwein_plouffe.py +++ b/maths/bailey_borwein_plouffe.py @@ -4,7 +4,8 @@ def bailey_borwein_plouffe(digit_position: int, precision: int = 1000) -> str: Bailey-Borwein-Plouffe (BBP) formula to calculate the nth hex digit of pi. Wikipedia page: https://en.wikipedia.org/wiki/Bailey%E2%80%93Borwein%E2%80%93Plouffe_formula - @param digit_position: a positive integer representing the position of the digit to extract. + @param digit_position: a positive integer representing the position of the digit to + extract. The digit immediately after the decimal point is located at position 1. @param precision: number of terms in the second summation to calculate. A higher number reduces the chance of an error but increases the runtime. @@ -41,7 +42,8 @@ def bailey_borwein_plouffe(digit_position: int, precision: int = 1000) -> str: elif (not isinstance(precision, int)) or (precision < 0): raise ValueError("Precision must be a nonnegative integer") - # compute an approximation of (16 ** (n - 1)) * pi whose fractional part is mostly accurate + # compute an approximation of (16 ** (n - 1)) * pi whose fractional part is mostly + # accurate sum_result = ( 4 * _subsum(digit_position, 1, precision) - 2 * _subsum(digit_position, 4, precision) @@ -70,8 +72,9 @@ def _subsum( denominator = 8 * sum_index + denominator_addend exponential_term = 0.0 if sum_index < digit_pos_to_extract: - # if the exponential term is an integer and we mod it by the denominator before - # dividing, only the integer part of the sum will change; the fractional part will not + # if the exponential term is an integer and we mod it by the denominator + # before dividing, only the integer part of the sum will change; + # the fractional part will not exponential_term = pow( 16, digit_pos_to_extract - 1 - sum_index, denominator ) diff --git a/maths/ceil.py b/maths/ceil.py index ff136f685524..ac86798a357f 100644 --- a/maths/ceil.py +++ b/maths/ceil.py @@ -6,7 +6,8 @@ def ceil(x) -> int: :return: the smallest integer >= x. >>> import math - >>> all(ceil(n) == math.ceil(n) for n in (1, -1, 0, -0, 1.1, -1.1, 1.0, -1.0, 1_000_000_000)) + >>> all(ceil(n) == math.ceil(n) for n + ... in (1, -1, 0, -0, 1.1, -1.1, 1.0, -1.0, 1_000_000_000)) True """ return ( diff --git a/maths/fermat_little_theorem.py b/maths/fermat_little_theorem.py index 24d558115795..73af3e28c618 100644 --- a/maths/fermat_little_theorem.py +++ b/maths/fermat_little_theorem.py @@ -1,5 +1,6 @@ # Python program to show the usage of Fermat's little theorem in a division -# According to Fermat's little theorem, (a / b) mod p always equals a * (b ^ (p - 2)) mod p +# According to Fermat's little theorem, (a / b) mod p always equals +# a * (b ^ (p - 2)) mod p # Here we assume that p is a prime number, b divides a, and p doesn't divide b # Wikipedia reference: https://en.wikipedia.org/wiki/Fermat%27s_little_theorem diff --git a/maths/fibonacci.py b/maths/fibonacci.py index 5ba9f6636364..d1e8cf7775fd 100644 --- a/maths/fibonacci.py +++ b/maths/fibonacci.py @@ -4,7 +4,8 @@ 2. Calculates the fibonacci sequence with a formula an = [ Phin - (phi)n ]/Sqrt[5] - reference-->Su, Francis E., et al. "Fibonacci Number Formula." Math Fun Facts. + reference-->Su, Francis E., et al. "Fibonacci Number Formula." Math Fun Facts. + """ import math import functools @@ -71,11 +72,13 @@ def _check_number_input(n, min_thresh, max_thresh=None): print("Incorrect Input: number must not be less than 0") except ValueTooSmallError: print( - f"Incorrect Input: input number must be > {min_thresh} for the recursive calculation" + f"Incorrect Input: input number must be > {min_thresh} for the recursive " + "calculation" ) except ValueTooLargeError: print( - f"Incorrect Input: input number must be < {max_thresh} for the recursive calculation" + f"Incorrect Input: input number must be < {max_thresh} for the recursive " + "calculation" ) return False diff --git a/maths/floor.py b/maths/floor.py index ae6e5129a6ff..41bd5ecb3cd7 100644 --- a/maths/floor.py +++ b/maths/floor.py @@ -6,7 +6,8 @@ def floor(x) -> int: :return: the largest integer <= x. >>> import math - >>> all(floor(n) == math.floor(n) for n in (1, -1, 0, -0, 1.1, -1.1, 1.0, -1.0, 1_000_000_000)) + >>> all(floor(n) == math.floor(n) for n + ... in (1, -1, 0, -0, 1.1, -1.1, 1.0, -1.0, 1_000_000_000)) True """ return ( diff --git a/maths/gamma.py b/maths/gamma.py index 98b327fa2f99..febbee9a5888 100644 --- a/maths/gamma.py +++ b/maths/gamma.py @@ -8,7 +8,8 @@ def gamma(num: float) -> float: https://en.wikipedia.org/wiki/Gamma_function In mathematics, the gamma function is one commonly used extension of the factorial function to complex numbers. - The gamma function is defined for all complex numbers except the non-positive integers + The gamma function is defined for all complex numbers except the non-positive + integers >>> gamma(-1) diff --git a/maths/greatest_common_divisor.py b/maths/greatest_common_divisor.py index 21c427d5b227..0926ade5dec2 100644 --- a/maths/greatest_common_divisor.py +++ b/maths/greatest_common_divisor.py @@ -25,9 +25,9 @@ def greatest_common_divisor(a, b): """ -Below method is more memory efficient because it does not use the stack (chunk of memory). -While above method is good, uses more memory for huge numbers because of the recursive calls -required to calculate the greatest common divisor. +Below method is more memory efficient because it does not use the stack (chunk of +memory). While above method is good, uses more memory for huge numbers because of the +recursive calls required to calculate the greatest common divisor. """ @@ -50,7 +50,8 @@ def main(): num_1 = int(nums[0]) num_2 = int(nums[1]) print( - f"greatest_common_divisor({num_1}, {num_2}) = {greatest_common_divisor(num_1, num_2)}" + f"greatest_common_divisor({num_1}, {num_2}) = " + f"{greatest_common_divisor(num_1, num_2)}" ) print(f"By iterative gcd({num_1}, {num_2}) = {gcd_by_iterative(num_1, num_2)}") except (IndexError, UnboundLocalError, ValueError): diff --git a/maths/lucas_lehmer_primality_test.py b/maths/lucas_lehmer_primality_test.py index 33e4a2141efc..15e25cbfe996 100644 --- a/maths/lucas_lehmer_primality_test.py +++ b/maths/lucas_lehmer_primality_test.py @@ -1,6 +1,6 @@ """ - In mathematics, the Lucas–Lehmer test (LLT) is a primality test for Mersenne numbers. - https://en.wikipedia.org/wiki/Lucas%E2%80%93Lehmer_primality_test + In mathematics, the Lucas–Lehmer test (LLT) is a primality test for Mersenne + numbers. https://en.wikipedia.org/wiki/Lucas%E2%80%93Lehmer_primality_test A Mersenne number is a number that is one less than a power of two. That is M_p = 2^p - 1 diff --git a/maths/matrix_exponentiation.py b/maths/matrix_exponentiation.py index 574269050fd8..033ceb3f28a0 100644 --- a/maths/matrix_exponentiation.py +++ b/maths/matrix_exponentiation.py @@ -15,7 +15,7 @@ def __init__(self, arg): if isinstance(arg, list): # Initializes a matrix identical to the one provided. self.t = arg self.n = len(arg) - else: # Initializes a square matrix of the given size and set the values to zero. + else: # Initializes a square matrix of the given size and set values to zero. self.n = arg self.t = [[0 for _ in range(self.n)] for _ in range(self.n)] diff --git a/maths/modular_exponential.py b/maths/modular_exponential.py index 9cb171477ff0..42987dbf3a24 100644 --- a/maths/modular_exponential.py +++ b/maths/modular_exponential.py @@ -1,7 +1,8 @@ """ Modular Exponential. Modular exponentiation is a type of exponentiation performed over a modulus. - For more explanation, please check https://en.wikipedia.org/wiki/Modular_exponentiation + For more explanation, please check + https://en.wikipedia.org/wiki/Modular_exponentiation """ """Calculate Modular Exponential.""" diff --git a/maths/polynomial_evaluation.py b/maths/polynomial_evaluation.py index d2394f398c36..e929a2d02972 100644 --- a/maths/polynomial_evaluation.py +++ b/maths/polynomial_evaluation.py @@ -44,7 +44,8 @@ def horner(poly: Sequence[float], x: float) -> float: Example: >>> poly = (0.0, 0.0, 5.0, 9.3, 7.0) # f(x) = 7.0x^4 + 9.3x^3 + 5.0x^2 >>> x = -13.0 - >>> print(evaluate_poly(poly, x)) # f(-13) = 7.0(-13)^4 + 9.3(-13)^3 + 5.0(-13)^2 = 180339.9 + >>> # f(-13) = 7.0(-13)^4 + 9.3(-13)^3 + 5.0(-13)^2 = 180339.9 + >>> print(evaluate_poly(poly, x)) 180339.9 """ poly = (0.0, 0.0, 5.0, 9.3, 7.0) diff --git a/maths/relu.py b/maths/relu.py index 2c41d2e9dad9..38a937decb0c 100644 --- a/maths/relu.py +++ b/maths/relu.py @@ -1,7 +1,8 @@ """ This script demonstrates the implementation of the ReLU function. -It's a kind of activation function defined as the positive part of its argument in the context of neural network. +It's a kind of activation function defined as the positive part of its argument in the +context of neural network. The function takes a vector of K real numbers as input and then argmax(x, 0). After through ReLU, the element of the vector always 0 or real number. diff --git a/maths/sieve_of_eratosthenes.py b/maths/sieve_of_eratosthenes.py index 9f2960dc134a..faf6fc0f9a98 100644 --- a/maths/sieve_of_eratosthenes.py +++ b/maths/sieve_of_eratosthenes.py @@ -1,8 +1,10 @@ """ Sieve of Eratosthones -The sieve of Eratosthenes is an algorithm used to find prime numbers, less than or equal to a given value. -Illustration: https://upload.wikimedia.org/wikipedia/commons/b/b9/Sieve_of_Eratosthenes_animation.gif +The sieve of Eratosthenes is an algorithm used to find prime numbers, less than or +equal to a given value. +Illustration: +https://upload.wikimedia.org/wikipedia/commons/b/b9/Sieve_of_Eratosthenes_animation.gif Reference: https://en.wikipedia.org/wiki/Sieve_of_Eratosthenes doctest provider: Bruno Simas Hadlich (https://github.com/brunohadlich) diff --git a/maths/square_root.py b/maths/square_root.py index fe775828c8c5..b324c723037c 100644 --- a/maths/square_root.py +++ b/maths/square_root.py @@ -25,7 +25,8 @@ def square_root_iterative( Square root is aproximated using Newtons method. https://en.wikipedia.org/wiki/Newton%27s_method - >>> all(abs(square_root_iterative(i)-math.sqrt(i)) <= .00000000000001 for i in range(0, 500)) + >>> all(abs(square_root_iterative(i)-math.sqrt(i)) <= .00000000000001 + ... for i in range(500)) True >>> square_root_iterative(-1) diff --git a/matrix/nth_fibonacci_using_matrix_exponentiation.py b/matrix/nth_fibonacci_using_matrix_exponentiation.py index 222779f454f9..296c36e88691 100644 --- a/matrix/nth_fibonacci_using_matrix_exponentiation.py +++ b/matrix/nth_fibonacci_using_matrix_exponentiation.py @@ -1,6 +1,7 @@ """ Implementation of finding nth fibonacci number using matrix exponentiation. -Time Complexity is about O(log(n)*8), where 8 is the complexity of matrix multiplication of size 2 by 2. +Time Complexity is about O(log(n)*8), where 8 is the complexity of matrix +multiplication of size 2 by 2. And on the other hand complexity of bruteforce solution is O(n). As we know f[n] = f[n-1] + f[n-1] @@ -70,7 +71,10 @@ def nth_fibonacci_bruteforce(n): def main(): - fmt = "{} fibonacci number using matrix exponentiation is {} and using bruteforce is {}\n" + fmt = ( + "{} fibonacci number using matrix exponentiation is {} and using bruteforce " + "is {}\n" + ) for ordinal in "0th 1st 2nd 3rd 10th 100th 1000th".split(): n = int("".join(c for c in ordinal if c in "0123456789")) # 1000th --> 1000 print(fmt.format(ordinal, nth_fibonacci_matrix(n), nth_fibonacci_bruteforce(n))) diff --git a/matrix/rotate_matrix.py b/matrix/rotate_matrix.py index 14a9493cd12f..6daf7e0cf2c5 100644 --- a/matrix/rotate_matrix.py +++ b/matrix/rotate_matrix.py @@ -1,5 +1,6 @@ """ -In this problem, we want to rotate the matrix elements by 90, 180, 270 (counterclockwise) +In this problem, we want to rotate the matrix elements by 90, 180, 270 +(counterclockwise) Discussion in stackoverflow: https://stackoverflow.com/questions/42519/how-do-you-rotate-a-two-dimensional-array """ diff --git a/matrix/sherman_morrison.py b/matrix/sherman_morrison.py index 91a70d189fc1..4920ec6c13db 100644 --- a/matrix/sherman_morrison.py +++ b/matrix/sherman_morrison.py @@ -207,8 +207,10 @@ def ShermanMorrison(self, u, v): """ Apply Sherman-Morrison formula in O(n^2). - To learn this formula, please look this: https://en.wikipedia.org/wiki/Sherman%E2%80%93Morrison_formula - This method returns (A + uv^T)^(-1) where A^(-1) is self. Returns None if it's impossible to calculate. + To learn this formula, please look this: + https://en.wikipedia.org/wiki/Sherman%E2%80%93Morrison_formula + This method returns (A + uv^T)^(-1) where A^(-1) is self. Returns None if it's + impossible to calculate. Warning: This method doesn't check if self is invertible. Make sure self is invertible before execute this method. diff --git a/matrix/tests/test_matrix_operation.py b/matrix/tests/test_matrix_operation.py index f9f72cf59af8..bf049b32116e 100644 --- a/matrix/tests/test_matrix_operation.py +++ b/matrix/tests/test_matrix_operation.py @@ -1,7 +1,8 @@ """ Testing here assumes that numpy and linalg is ALWAYS correct!!!! -If running from PyCharm you can place the following line in "Additional Arguments" for the pytest run configuration +If running from PyCharm you can place the following line in "Additional Arguments" for +the pytest run configuration -vv -m mat_ops -p no:cacheprovider """ diff --git a/other/dijkstra_bankers_algorithm.py b/other/dijkstra_bankers_algorithm.py index ab4fba4c3bd1..405c10b88495 100644 --- a/other/dijkstra_bankers_algorithm.py +++ b/other/dijkstra_bankers_algorithm.py @@ -88,9 +88,11 @@ def __need_index_manager(self) -> Dict[int, List[int]]: This function builds an index control dictionary to track original ids/indices of processes when altered during execution of method "main" Return: {0: [a: int, b: int], 1: [c: int, d: int]} - >>> BankersAlgorithm(test_claim_vector, test_allocated_res_table, + >>> (BankersAlgorithm(test_claim_vector, test_allocated_res_table, ... test_maximum_claim_table)._BankersAlgorithm__need_index_manager() - {0: [1, 2, 0, 3], 1: [0, 1, 3, 1], 2: [1, 1, 0, 2], 3: [1, 3, 2, 0], 4: [2, 0, 0, 3]} + ... ) # doctest: +NORMALIZE_WHITESPACE + {0: [1, 2, 0, 3], 1: [0, 1, 3, 1], 2: [1, 1, 0, 2], 3: [1, 3, 2, 0], + 4: [2, 0, 0, 3]} """ return {self.__need().index(i): i for i in self.__need()} diff --git a/other/fischer_yates_shuffle.py b/other/fischer_yates_shuffle.py index 99121ba632c7..6eec738c02e1 100644 --- a/other/fischer_yates_shuffle.py +++ b/other/fischer_yates_shuffle.py @@ -1,6 +1,7 @@ #!/usr/bin/python """ -The Fisher–Yates shuffle is an algorithm for generating a random permutation of a finite sequence. +The Fisher–Yates shuffle is an algorithm for generating a random permutation of a +finite sequence. For more details visit wikipedia/Fischer-Yates-Shuffle. """ diff --git a/other/game_of_life.py b/other/game_of_life.py index 688ee1f282b3..651467969fad 100644 --- a/other/game_of_life.py +++ b/other/game_of_life.py @@ -52,7 +52,8 @@ def seed(canvas): def run(canvas): - """ This function runs the rules of game through all points, and changes their status accordingly.(in the same canvas) + """ This function runs the rules of game through all points, and changes their + status accordingly.(in the same canvas) @Args: -- canvas : canvas of population to run the rules on. diff --git a/other/integeration_by_simpson_approx.py b/other/integeration_by_simpson_approx.py index f88d3a0f0173..da0e1cffde02 100644 --- a/other/integeration_by_simpson_approx.py +++ b/other/integeration_by_simpson_approx.py @@ -4,7 +4,8 @@ Purpose : You have one function f(x) which takes float integer and returns float you have to integrate the function in limits a to b. -The approximation proposed by Thomas Simpsons in 1743 is one way to calculate integration. +The approximation proposed by Thomas Simpsons in 1743 is one way to calculate +integration. ( read article : https://cp-algorithms.com/num_methods/simpson-integration.html ) @@ -25,7 +26,8 @@ def f(x: float) -> float: Summary of Simpson Approximation : By simpsons integration : -1.integration of fxdx with limit a to b is = f(x0) + 4 * f(x1) + 2 * f(x2) + 4 * f(x3) + 2 * f(x4)..... + f(xn) +1. integration of fxdx with limit a to b is = + f(x0) + 4 * f(x1) + 2 * f(x2) + 4 * f(x3) + 2 * f(x4)..... + f(xn) where x0 = a xi = a + i * h xn = b @@ -71,7 +73,7 @@ def simpson_integration(function, a: float, b: float, precision: int = 4) -> flo >>> simpson_integration('wrong_input',2,3,4) Traceback (most recent call last): ... - AssertionError: the function(object) passed should be callable your input : wrong_input + AssertionError: the function(object) passed should be callable your input : ... >>> simpson_integration(lambda x : x*x,3.45,3.2,1) -2.8 @@ -93,9 +95,10 @@ def simpson_integration(function, a: float, b: float, precision: int = 4) -> flo assert isinstance(a, float) or isinstance( a, int ), f"a should be float or integer your input : {a}" - assert isinstance(function(a), float) or isinstance( - function(a), int - ), f"the function should return integer or float return type of your function, {type(a)}" + assert isinstance(function(a), float) or isinstance(function(a), int), ( + "the function should return integer or float return type of your function, " + f"{type(a)}" + ) assert isinstance(b, float) or isinstance( b, int ), f"b should be float or integer your input : {b}" diff --git a/other/linear_congruential_generator.py b/other/linear_congruential_generator.py index 058f270d0584..f8b604b8562d 100644 --- a/other/linear_congruential_generator.py +++ b/other/linear_congruential_generator.py @@ -23,7 +23,8 @@ def __init__(self, multiplier, increment, modulo, seed=int(time())): def next_number(self): """ The smallest number that can be generated is zero. - The largest number that can be generated is modulo-1. modulo is set in the constructor. + The largest number that can be generated is modulo-1. modulo is set in the + constructor. """ self.seed = (self.multiplier * self.seed + self.increment) % self.modulo return self.seed diff --git a/other/nested_brackets.py b/other/nested_brackets.py index c712bc21b5ce..99e2f3a38797 100644 --- a/other/nested_brackets.py +++ b/other/nested_brackets.py @@ -9,9 +9,8 @@ For example, the string "()()[()]" is properly nested but "[(()]" is not. -The function called is_balanced takes as input a string S which is a sequence of brackets and -returns true if S is nested and false otherwise. - +The function called is_balanced takes as input a string S which is a sequence of +brackets and returns true if S is nested and false otherwise. """ @@ -37,14 +36,11 @@ def is_balanced(S): def main(): - - S = input("Enter sequence of brackets: ") - - if is_balanced(S): - print((S, "is balanced")) - + s = input("Enter sequence of brackets: ") + if is_balanced(s): + print(s, "is balanced") else: - print((S, "is not balanced")) + print(s, "is not balanced") if __name__ == "__main__": diff --git a/other/two_sum.py b/other/two_sum.py index 70d5c5375026..8ac7a18ba9a4 100644 --- a/other/two_sum.py +++ b/other/two_sum.py @@ -1,7 +1,9 @@ """ -Given an array of integers, return indices of the two numbers such that they add up to a specific target. +Given an array of integers, return indices of the two numbers such that they add up to +a specific target. -You may assume that each input would have exactly one solution, and you may not use the same element twice. +You may assume that each input would have exactly one solution, and you may not use the +same element twice. Example: Given nums = [2, 7, 11, 15], target = 9, diff --git a/project_euler/problem_30/soln.py b/project_euler/problem_30/soln.py index 829ddb0fb9cc..3ade82208344 100644 --- a/project_euler/problem_30/soln.py +++ b/project_euler/problem_30/soln.py @@ -1,6 +1,7 @@ """ Problem Statement (Digit Fifth Power ): https://projecteuler.net/problem=30 -Surprisingly there are only three numbers that can be written as the sum of fourth powers of their digits: +Surprisingly there are only three numbers that can be written as the sum of fourth +powers of their digits: 1634 = 1^4 + 6^4 + 3^4 + 4^4 8208 = 8^4 + 2^4 + 0^4 + 8^4 @@ -9,7 +10,8 @@ The sum of these numbers is 1634 + 8208 + 9474 = 19316. -Find the sum of all the numbers that can be written as the sum of fifth powers of their digits. +Find the sum of all the numbers that can be written as the sum of fifth powers of their +digits. (9^5)=59,049‬ 59049*7=4,13,343 (which is only 6 digit number ) diff --git a/project_euler/problem_56/sol1.py b/project_euler/problem_56/sol1.py index d5225efcb9e5..2a00fa6a195d 100644 --- a/project_euler/problem_56/sol1.py +++ b/project_euler/problem_56/sol1.py @@ -15,7 +15,8 @@ def maximum_digital_sum(a: int, b: int) -> int: 1872 """ - # RETURN the MAXIMUM from the list of SUMs of the list of INT converted from STR of BASE raised to the POWER + # RETURN the MAXIMUM from the list of SUMs of the list of INT converted from STR of + # BASE raised to the POWER return max( [ sum([int(x) for x in str(base ** power)]) diff --git a/scheduling/first_come_first_served.py b/scheduling/first_come_first_served.py index 163f5257f361..b51fc9fe0c04 100644 --- a/scheduling/first_come_first_served.py +++ b/scheduling/first_come_first_served.py @@ -101,7 +101,8 @@ def calculate_average_waiting_time(waiting_times: List[int]) -> float: print("Process ID\tDuration Time\tWaiting Time\tTurnaround Time") for i, process in enumerate(processes): print( - f"{process}\t\t{duration_times[i]}\t\t{waiting_times[i]}\t\t{turnaround_times[i]}" + f"{process}\t\t{duration_times[i]}\t\t{waiting_times[i]}\t\t" + f"{turnaround_times[i]}" ) print(f"Average waiting time = {average_waiting_time}") print(f"Average turn around time = {average_turnaround_time}") diff --git a/searches/linear_search.py b/searches/linear_search.py index 155119bb4a43..2056bd7a4916 100644 --- a/searches/linear_search.py +++ b/searches/linear_search.py @@ -14,7 +14,8 @@ def linear_search(sequence, target): """Pure implementation of linear search algorithm in Python - :param sequence: a collection with comparable items (as sorted items not required in Linear Search) + :param sequence: a collection with comparable items (as sorted items not required + in Linear Search) :param target: item value to search :return: index of found item or None if item is not found diff --git a/searches/simulated_annealing.py b/searches/simulated_annealing.py index 6a4a8638632d..b12303274b14 100644 --- a/searches/simulated_annealing.py +++ b/searches/simulated_annealing.py @@ -18,21 +18,23 @@ def simulated_annealing( threshold_temp: float = 1, ) -> SearchProblem: """ - implementation of the simulated annealing algorithm. We start with a given state, find - all its neighbors. Pick a random neighbor, if that neighbor improves the solution, we move - in that direction, if that neighbor does not improve the solution, we generate a random - real number between 0 and 1, if the number is within a certain range (calculated using - temperature) we move in that direction, else we pick another neighbor randomly and repeat the process. - Args: - search_prob: The search state at the start. - find_max: If True, the algorithm should find the minimum else the minimum. - max_x, min_x, max_y, min_y: the maximum and minimum bounds of x and y. - visualization: If True, a matplotlib graph is displayed. - start_temperate: the initial temperate of the system when the program starts. - rate_of_decrease: the rate at which the temperate decreases in each iteration. - threshold_temp: the threshold temperature below which we end the search - Returns a search state having the maximum (or minimum) score. - """ + Implementation of the simulated annealing algorithm. We start with a given state, + find all its neighbors. Pick a random neighbor, if that neighbor improves the + solution, we move in that direction, if that neighbor does not improve the solution, + we generate a random real number between 0 and 1, if the number is within a certain + range (calculated using temperature) we move in that direction, else we pick + another neighbor randomly and repeat the process. + + Args: + search_prob: The search state at the start. + find_max: If True, the algorithm should find the minimum else the minimum. + max_x, min_x, max_y, min_y: the maximum and minimum bounds of x and y. + visualization: If True, a matplotlib graph is displayed. + start_temperate: the initial temperate of the system when the program starts. + rate_of_decrease: the rate at which the temperate decreases in each iteration. + threshold_temp: the threshold temperature below which we end the search + Returns a search state having the maximum (or minimum) score. + """ search_end = False current_state = search_prob current_temp = start_temperate diff --git a/searches/tabu_search.py b/searches/tabu_search.py index 9ddc5e8dee7f..cc4c95ead615 100644 --- a/searches/tabu_search.py +++ b/searches/tabu_search.py @@ -1,8 +1,9 @@ """ -This is pure Python implementation of Tabu search algorithm for a Travelling Salesman Problem, that the distances -between the cities are symmetric (the distance between city 'a' and city 'b' is the same between city 'b' and city 'a'). -The TSP can be represented into a graph. The cities are represented by nodes and the distance between them is -represented by the weight of the ark between the nodes. +This is pure Python implementation of Tabu search algorithm for a Travelling Salesman +Problem, that the distances between the cities are symmetric (the distance between city +'a' and city 'b' is the same between city 'b' and city 'a'). +The TSP can be represented into a graph. The cities are represented by nodes and the +distance between them is represented by the weight of the ark between the nodes. The .txt file with the graph has the form: @@ -10,8 +11,8 @@ node1 node3 distance_between_node1_and_node3 ... -Be careful node1, node2 and the distance between them, must exist only once. This means in the .txt file -should not exist: +Be careful node1, node2 and the distance between them, must exist only once. This means +in the .txt file should not exist: node1 node2 distance_between_node1_and_node2 node2 node1 distance_between_node2_and_node1 @@ -19,7 +20,8 @@ pytest For manual testing run: -python tabu_search.py -f your_file_name.txt -number_of_iterations_of_tabu_search -s size_of_tabu_search +python tabu_search.py -f your_file_name.txt -number_of_iterations_of_tabu_search \ + -s size_of_tabu_search e.g. python tabu_search.py -f tabudata2.txt -i 4 -s 3 """ @@ -33,16 +35,16 @@ def generate_neighbours(path): neighbor, given a path file that includes a graph. :param path: The path to the .txt file that includes the graph (e.g.tabudata2.txt) - :return dict_of_neighbours: Dictionary with key each node and value a list of lists with the neighbors of the node - and the cost (distance) for each neighbor. + :return dict_of_neighbours: Dictionary with key each node and value a list of lists + with the neighbors of the node and the cost (distance) for each neighbor. Example of dict_of_neighbours: >>) dict_of_neighbours[a] [[b,20],[c,18],[d,22],[e,26]] - This indicates the neighbors of node (city) 'a', which has neighbor the node 'b' with distance 20, - the node 'c' with distance 18, the node 'd' with distance 22 and the node 'e' with distance 26. - + This indicates the neighbors of node (city) 'a', which has neighbor the node 'b' + with distance 20, the node 'c' with distance 18, the node 'd' with distance 22 and + the node 'e' with distance 26. """ dict_of_neighbours = {} @@ -71,19 +73,19 @@ def generate_neighbours(path): def generate_first_solution(path, dict_of_neighbours): """ - Pure implementation of generating the first solution for the Tabu search to start, with the redundant resolution - strategy. That means that we start from the starting node (e.g. node 'a'), then we go to the city nearest (lowest - distance) to this node (let's assume is node 'c'), then we go to the nearest city of the node 'c', etc + Pure implementation of generating the first solution for the Tabu search to start, + with the redundant resolution strategy. That means that we start from the starting + node (e.g. node 'a'), then we go to the city nearest (lowest distance) to this node + (let's assume is node 'c'), then we go to the nearest city of the node 'c', etc. till we have visited all cities and return to the starting node. :param path: The path to the .txt file that includes the graph (e.g.tabudata2.txt) - :param dict_of_neighbours: Dictionary with key each node and value a list of lists with the neighbors of the node - and the cost (distance) for each neighbor. - :return first_solution: The solution for the first iteration of Tabu search using the redundant resolution strategy - in a list. - :return distance_of_first_solution: The total distance that Travelling Salesman will travel, if he follows the path - in first_solution. - + :param dict_of_neighbours: Dictionary with key each node and value a list of lists + with the neighbors of the node and the cost (distance) for each neighbor. + :return first_solution: The solution for the first iteration of Tabu search using + the redundant resolution strategy in a list. + :return distance_of_first_solution: The total distance that Travelling Salesman + will travel, if he follows the path in first_solution. """ with open(path) as f: @@ -124,22 +126,23 @@ def generate_first_solution(path, dict_of_neighbours): def find_neighborhood(solution, dict_of_neighbours): """ - Pure implementation of generating the neighborhood (sorted by total distance of each solution from - lowest to highest) of a solution with 1-1 exchange method, that means we exchange each node in a solution with each - other node and generating a number of solution named neighborhood. + Pure implementation of generating the neighborhood (sorted by total distance of + each solution from lowest to highest) of a solution with 1-1 exchange method, that + means we exchange each node in a solution with each other node and generating a + number of solution named neighborhood. :param solution: The solution in which we want to find the neighborhood. - :param dict_of_neighbours: Dictionary with key each node and value a list of lists with the neighbors of the node - and the cost (distance) for each neighbor. - :return neighborhood_of_solution: A list that includes the solutions and the total distance of each solution - (in form of list) that are produced with 1-1 exchange from the solution that the method took as an input - + :param dict_of_neighbours: Dictionary with key each node and value a list of lists + with the neighbors of the node and the cost (distance) for each neighbor. + :return neighborhood_of_solution: A list that includes the solutions and the total + distance of each solution (in form of list) that are produced with 1-1 exchange + from the solution that the method took as an input Example: - >>) find_neighborhood(['a','c','b','d','e','a']) - [['a','e','b','d','c','a',90], [['a','c','d','b','e','a',90],['a','d','b','c','e','a',93], - ['a','c','b','e','d','a',102], ['a','c','e','d','b','a',113], ['a','b','c','d','e','a',93]] - + >>> find_neighborhood(['a','c','b','d','e','a']) # doctest: +NORMALIZE_WHITESPACE + [['a','e','b','d','c','a',90], [['a','c','d','b','e','a',90], + ['a','d','b','c','e','a',93], ['a','c','b','e','d','a',102], + ['a','c','e','d','b','a',113], ['a','b','c','d','e','a',93]] """ neighborhood_of_solution = [] @@ -177,20 +180,21 @@ def tabu_search( first_solution, distance_of_first_solution, dict_of_neighbours, iters, size ): """ - Pure implementation of Tabu search algorithm for a Travelling Salesman Problem in Python. - - :param first_solution: The solution for the first iteration of Tabu search using the redundant resolution strategy - in a list. - :param distance_of_first_solution: The total distance that Travelling Salesman will travel, if he follows the path - in first_solution. - :param dict_of_neighbours: Dictionary with key each node and value a list of lists with the neighbors of the node - and the cost (distance) for each neighbor. + Pure implementation of Tabu search algorithm for a Travelling Salesman Problem in + Python. + + :param first_solution: The solution for the first iteration of Tabu search using + the redundant resolution strategy in a list. + :param distance_of_first_solution: The total distance that Travelling Salesman will + travel, if he follows the path in first_solution. + :param dict_of_neighbours: Dictionary with key each node and value a list of lists + with the neighbors of the node and the cost (distance) for each neighbor. :param iters: The number of iterations that Tabu search will execute. :param size: The size of Tabu List. - :return best_solution_ever: The solution with the lowest distance that occurred during the execution of Tabu search. - :return best_cost: The total distance that Travelling Salesman will travel, if he follows the path in best_solution - ever. - + :return best_solution_ever: The solution with the lowest distance that occurred + during the execution of Tabu search. + :return best_cost: The total distance that Travelling Salesman will travel, if he + follows the path in best_solution ever. """ count = 1 solution = first_solution diff --git a/sorts/bucket_sort.py b/sorts/bucket_sort.py index 217ee5893c4b..a2d1096ece6a 100644 --- a/sorts/bucket_sort.py +++ b/sorts/bucket_sort.py @@ -17,8 +17,9 @@ # number of buckets. # Time Complexity of Solution: -# Worst case scenario occurs when all the elements are placed in a single bucket. The overall performance -# would then be dominated by the algorithm used to sort each bucket. In this case, O(n log n), because of TimSort +# Worst case scenario occurs when all the elements are placed in a single bucket. The +# overall performance would then be dominated by the algorithm used to sort each bucket. +# In this case, O(n log n), because of TimSort # # Average Case O(n + (n^2)/k + k), where k is the number of buckets # diff --git a/sorts/comb_sort.py b/sorts/comb_sort.py index 416fd4552697..16bd10c78fe5 100644 --- a/sorts/comb_sort.py +++ b/sorts/comb_sort.py @@ -1,9 +1,11 @@ """ This is pure Python implementation of comb sort algorithm. -Comb sort is a relatively simple sorting algorithm originally designed by Wlodzimierz Dobosiewicz in 1980. -It was rediscovered by Stephen Lacey and Richard Box in 1991. Comb sort improves on bubble sort algorithm. +Comb sort is a relatively simple sorting algorithm originally designed by Wlodzimierz +Dobosiewicz in 1980. It was rediscovered by Stephen Lacey and Richard Box in 1991. +Comb sort improves on bubble sort algorithm. In bubble sort, distance (or gap) between two compared elements is always one. -Comb sort improvement is that gap can be much more than 1, in order to prevent slowing down by small values +Comb sort improvement is that gap can be much more than 1, in order to prevent slowing +down by small values at the end of a list. More info on: https://en.wikipedia.org/wiki/Comb_sort diff --git a/sorts/random_normal_distribution_quicksort.py b/sorts/random_normal_distribution_quicksort.py index be3b90190407..5da9337ee080 100644 --- a/sorts/random_normal_distribution_quicksort.py +++ b/sorts/random_normal_distribution_quicksort.py @@ -57,6 +57,7 @@ def _inPlacePartition(A, start, end): z = _inPlaceQuickSort(M, 0, r) print( - "No of Comparisons for 100 elements selected from a standard normal distribution is :" + "No of Comparisons for 100 elements selected from a standard normal distribution" + "is :" ) print(z) diff --git a/sorts/unknown_sort.py b/sorts/unknown_sort.py index 5ecc55e9cf69..9fa9d22fb5e0 100644 --- a/sorts/unknown_sort.py +++ b/sorts/unknown_sort.py @@ -1,7 +1,8 @@ """ Python implementation of a sort algorithm. Best Case Scenario : O(n) -Worst Case Scenario : O(n^2) because native Python functions:min, max and remove are already O(n) +Worst Case Scenario : O(n^2) because native Python functions:min, max and remove are +already O(n) """ diff --git a/strings/boyer_moore_search.py b/strings/boyer_moore_search.py index 4bd6aff27bf3..f340855d7a05 100644 --- a/strings/boyer_moore_search.py +++ b/strings/boyer_moore_search.py @@ -41,7 +41,9 @@ def match_in_pattern(self, char): return -1 def mismatch_in_text(self, currentPos): - """ finds the index of mis-matched character in text when compared with pattern from last + """ + find the index of mis-matched character in text when compared with pattern + from last Parameters : currentPos (int): current index position of text diff --git a/strings/is_palindrome.py b/strings/is_palindrome.py index a0795b7b3783..4776a5fc29c4 100644 --- a/strings/is_palindrome.py +++ b/strings/is_palindrome.py @@ -14,8 +14,8 @@ def is_palindrome(s: str) -> bool: >>> is_palindrome("Mr. Owl ate my metal worm?") True """ - # Since Punctuation, capitalization, and spaces are usually ignored while checking Palindrome, - # we first remove them from our string. + # Since Punctuation, capitalization, and spaces are usually ignored while checking + # Palindrome, we first remove them from our string. s = "".join([character for character in s.lower() if character.isalnum()]) return s == s[::-1] diff --git a/strings/jaro_winkler.py b/strings/jaro_winkler.py index de09538542e4..f4a8fbad3ac8 100644 --- a/strings/jaro_winkler.py +++ b/strings/jaro_winkler.py @@ -3,7 +3,8 @@ def jaro_winkler(str1: str, str2: str) -> float: """ - Jaro–Winkler distance is a string metric measuring an edit distance between two sequences. + Jaro–Winkler distance is a string metric measuring an edit distance between two + sequences. Output value is between 0.0 and 1.0. >>> jaro_winkler("martha", "marhta") diff --git a/strings/knuth_morris_pratt.py b/strings/knuth_morris_pratt.py index c7e96887c387..2e5e0c7e73f1 100644 --- a/strings/knuth_morris_pratt.py +++ b/strings/knuth_morris_pratt.py @@ -5,11 +5,11 @@ def kmp(pattern, text): 1) Preprocess pattern to identify any suffixes that are identical to prefixes - This tells us where to continue from if we get a mismatch between a character in our pattern - and the text. + This tells us where to continue from if we get a mismatch between a character + in our pattern and the text. - 2) Step through the text one character at a time and compare it to a character in the pattern - updating our location within the pattern if necessary + 2) Step through the text one character at a time and compare it to a character in + the pattern updating our location within the pattern if necessary """ diff --git a/strings/lower.py b/strings/lower.py index 222b8d443289..a1bad44c7403 100644 --- a/strings/lower.py +++ b/strings/lower.py @@ -16,8 +16,9 @@ def lower(word: str) -> str: 'what' """ - # converting to ascii value int value and checking to see if char is a capital letter - # if it is a capital letter it is getting shift by 32 which makes it a lower case letter + # converting to ascii value int value and checking to see if char is a capital + # letter if it is a capital letter it is getting shift by 32 which makes it a lower + # case letter return "".join( chr(ord(char) + 32) if 65 <= ord(char) <= 90 else char for char in word ) diff --git a/strings/min_cost_string_conversion.py b/strings/min_cost_string_conversion.py index abc9d2c65158..34b42f3f0f64 100644 --- a/strings/min_cost_string_conversion.py +++ b/strings/min_cost_string_conversion.py @@ -1,5 +1,6 @@ """ -Algorithm for calculating the most cost-efficient sequence for converting one string into another. +Algorithm for calculating the most cost-efficient sequence for converting one string +into another. The only allowed operations are ---Copy character with cost cC ---Replace character with cost cR diff --git a/strings/rabin_karp.py b/strings/rabin_karp.py index 22da0de80f4c..d866b1397277 100644 --- a/strings/rabin_karp.py +++ b/strings/rabin_karp.py @@ -8,15 +8,18 @@ def rabin_karp(pattern, text): """ The Rabin-Karp Algorithm for finding a pattern within a piece of text with complexity O(nm), most efficient when it is used with multiple patterns - as it is able to check if any of a set of patterns match a section of text in o(1) given the precomputed hashes. + as it is able to check if any of a set of patterns match a section of text in o(1) + given the precomputed hashes. - This will be the simple version which only assumes one pattern is being searched for but it's not hard to modify + This will be the simple version which only assumes one pattern is being searched + for but it's not hard to modify 1) Calculate pattern hash - 2) Step through the text one character at a time passing a window with the same length as the pattern - calculating the hash of the text within the window compare it with the hash of the pattern. Only testing - equality if the hashes match + 2) Step through the text one character at a time passing a window with the same + length as the pattern + calculating the hash of the text within the window compare it with the hash + of the pattern. Only testing equality if the hashes match """ p_len = len(pattern) t_len = len(text) diff --git a/strings/split.py b/strings/split.py index d614bd88478f..b62b86d2401f 100644 --- a/strings/split.py +++ b/strings/split.py @@ -1,6 +1,7 @@ def split(string: str, separator: str = " ") -> list: """ - Will split the string up into all the values separated by the separator (defaults to spaces) + Will split the string up into all the values separated by the separator + (defaults to spaces) >>> split("apple#banana#cherry#orange",separator='#') ['apple', 'banana', 'cherry', 'orange'] diff --git a/strings/upper.py b/strings/upper.py index 96b52878e05e..2c264c641b12 100644 --- a/strings/upper.py +++ b/strings/upper.py @@ -14,7 +14,8 @@ def upper(word: str) -> str: """ # converting to ascii value int value and checking to see if char is a lower letter - # if it is a capital letter it is getting shift by 32 which makes it a capital case letter + # if it is a capital letter it is getting shift by 32 which makes it a capital case + # letter return "".join( chr(ord(char) - 32) if 97 <= ord(char) <= 122 else char for char in word ) From b9e5259aeb28f59c9735f340581a8b1d2f45d307 Mon Sep 17 00:00:00 2001 From: Christian Clauss Date: Tue, 16 Jun 2020 14:29:13 +0200 Subject: [PATCH 0624/1071] Fix long line, tests (#2123) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Fix long line * updating DIRECTORY.md * Add doctest * ... * ... * Update tabu_search.py * space * Fix doctest >>> find_neighborhood(['a','c','b','d','e','a']) # doctest: +NORMALIZE_WHITESPACE [['a','e','b','d','c','a',90], [['a','c','d','b','e','a',90], ['a','d','b','c','e','a',93], ['a','c','b','e','d','a',102], ['a','c','e','d','b','a',113], ['a','b','c','d','e','a',93]] Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> Co-authored-by: John Law --- DIRECTORY.md | 1 + digital_image_processing/resize/resize.py | 2 +- divide_and_conquer/convex_hull.py | 4 ++-- maths/gamma.py | 3 ++- searches/tabu_search.py | 19 ++++++++++++++----- 5 files changed, 20 insertions(+), 9 deletions(-) diff --git a/DIRECTORY.md b/DIRECTORY.md index 5f9046cc7108..b34658b32065 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -18,6 +18,7 @@ * [Hamiltonian Cycle](https://github.com/TheAlgorithms/Python/blob/master/backtracking/hamiltonian_cycle.py) * [Minimax](https://github.com/TheAlgorithms/Python/blob/master/backtracking/minimax.py) * [N Queens](https://github.com/TheAlgorithms/Python/blob/master/backtracking/n_queens.py) + * [Rat In Maze](https://github.com/TheAlgorithms/Python/blob/master/backtracking/rat_in_maze.py) * [Sudoku](https://github.com/TheAlgorithms/Python/blob/master/backtracking/sudoku.py) * [Sum Of Subsets](https://github.com/TheAlgorithms/Python/blob/master/backtracking/sum_of_subsets.py) diff --git a/digital_image_processing/resize/resize.py b/digital_image_processing/resize/resize.py index afcacda4bd86..f33e80e580de 100644 --- a/digital_image_processing/resize/resize.py +++ b/digital_image_processing/resize/resize.py @@ -50,7 +50,7 @@ def get_y(self, y: int) -> int: :param y: Destination X coordinate :return: Parent X coordinate based on `y ratio` >>> nn = NearestNeighbour(imread("digital_image_processing/image_data/lena.jpg", - 1), 100, 100) + ... 1), 100, 100) >>> nn.ratio_y = 0.5 >>> nn.get_y(4) 2 diff --git a/divide_and_conquer/convex_hull.py b/divide_and_conquer/convex_hull.py index de67cb1e06df..cf2c7f835798 100644 --- a/divide_and_conquer/convex_hull.py +++ b/divide_and_conquer/convex_hull.py @@ -325,10 +325,10 @@ def convex_hull_recursive(points): >>> convex_hull_recursive([[0, 0], [1, 0], [10, 0]]) [(0.0, 0.0), (10.0, 0.0)] >>> convex_hull_recursive([[-1, 1],[-1, -1], [0, 0], [0.5, 0.5], [1, -1], [1, 1], - [-0.75, 1]]) + ... [-0.75, 1]]) [(-1.0, -1.0), (-1.0, 1.0), (1.0, -1.0), (1.0, 1.0)] >>> convex_hull_recursive([(0, 3), (2, 2), (1, 1), (2, 1), (3, 0), (0, 0), (3, 3), - (2, -1), (2, -4), (1, -3)]) + ... (2, -1), (2, -4), (1, -3)]) [(0.0, 0.0), (0.0, 3.0), (1.0, -3.0), (2.0, -4.0), (3.0, 0.0), (3.0, 3.0)] """ diff --git a/maths/gamma.py b/maths/gamma.py index febbee9a5888..9193929baa28 100644 --- a/maths/gamma.py +++ b/maths/gamma.py @@ -29,7 +29,8 @@ def gamma(num: float) -> float: 40320.0 >>> from math import gamma as math_gamma - >>> all(gamma(i)/math_gamma(i) <= 1.000000001 and abs(gamma(i)/math_gamma(i)) > .99999999 for i in range(1, 50)) + >>> all(.99999999 < gamma(i) / math_gamma(i) <= 1.000000001 + ... for i in range(1, 50)) True diff --git a/searches/tabu_search.py b/searches/tabu_search.py index cc4c95ead615..f0c3241dbe19 100644 --- a/searches/tabu_search.py +++ b/searches/tabu_search.py @@ -139,10 +139,19 @@ def find_neighborhood(solution, dict_of_neighbours): from the solution that the method took as an input Example: - >>> find_neighborhood(['a','c','b','d','e','a']) # doctest: +NORMALIZE_WHITESPACE - [['a','e','b','d','c','a',90], [['a','c','d','b','e','a',90], - ['a','d','b','c','e','a',93], ['a','c','b','e','d','a',102], - ['a','c','e','d','b','a',113], ['a','b','c','d','e','a',93]] + >>> find_neighborhood(['a', 'c', 'b', 'd', 'e', 'a'], + ... {'a': [['b', '20'], ['c', '18'], ['d', '22'], ['e', '26']], + ... 'c': [['a', '18'], ['b', '10'], ['d', '23'], ['e', '24']], + ... 'b': [['a', '20'], ['c', '10'], ['d', '11'], ['e', '12']], + ... 'e': [['a', '26'], ['b', '12'], ['c', '24'], ['d', '40']], + ... 'd': [['a', '22'], ['b', '11'], ['c', '23'], ['e', '40']]} + ... ) # doctest: +NORMALIZE_WHITESPACE + [['a', 'e', 'b', 'd', 'c', 'a', 90], + ['a', 'c', 'd', 'b', 'e', 'a', 90], + ['a', 'd', 'b', 'c', 'e', 'a', 93], + ['a', 'c', 'b', 'e', 'd', 'a', 102], + ['a', 'c', 'e', 'd', 'b', 'a', 113], + ['a', 'b', 'c', 'd', 'e', 'a', 119]] """ neighborhood_of_solution = [] @@ -209,7 +218,7 @@ def tabu_search( best_cost_index = len(best_solution) - 1 found = False - while found is False: + while not found: i = 0 while i < len(best_solution): From 4a2d457747dd5429c6f9ef06b58362f2e90017af Mon Sep 17 00:00:00 2001 From: Muhammadrasul <64916997+Muhammadrasul446@users.noreply.github.com> Date: Tue, 16 Jun 2020 17:36:09 +0500 Subject: [PATCH 0625/1071] Count (#2084) * Add files via upload * Update and rename count_islands.py to count_islands_in_matrix.py * Update matrix/count_islands_in_matrix.py Co-authored-by: Christian Clauss * Update matrix/count_islands_in_matrix.py Co-authored-by: Christian Clauss * Update matrix/count_islands_in_matrix.py Co-authored-by: Christian Clauss * Reformat count islands.py * Indent Python code with 4 spaces, not tabs * Update count_islands_in_matrix.py * Add type hints for return values Co-authored-by: Christian Clauss --- matrix/count_islands_in_matrix.py | 36 +++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) create mode 100644 matrix/count_islands_in_matrix.py diff --git a/matrix/count_islands_in_matrix.py b/matrix/count_islands_in_matrix.py new file mode 100644 index 000000000000..ad9c67fb8c1b --- /dev/null +++ b/matrix/count_islands_in_matrix.py @@ -0,0 +1,36 @@ +# An island in matrix is a group of linked areas, all having the same value. +# This code counts number of islands in a given matrix, with including diagonal +# connections. + + +class matrix: # Public class to implement a graph + def __init__(self, row: int, col: int, graph: list): + self.ROW = row + self.COL = col + self.graph = graph + + def is_safe(self, i, j, visited) -> bool: + return ( + 0 <= i < self.ROW + and 0 <= j < self.COL + and not visited[i][j] + and self.graph[i][j] + ) + + def diffs(self, i, j, visited): # Checking all 8 elements surrounding nth element + rowNbr = [-1, -1, -1, 0, 0, 1, 1, 1] # Coordinate order + colNbr = [-1, 0, 1, -1, 1, -1, 0, 1] + visited[i][j] = True # Make those cells visited + for k in range(8): + if self.is_safe(i + rowNbr[k], j + colNbr[k], visited): + self.diffs(i + rowNbr[k], j + colNbr[k], visited) + + def count_islands(self) -> int: # And finally, count all islands. + visited = [[False for j in range(self.COL)] for i in range(self.ROW)] + count = 0 + for i in range(self.ROW): + for j in range(self.COL): + if visited[i][j] is False and self.graph[i][j] == 1: + self.diffs(i, j, visited) + count += 1 + return count From 4e8a0d924eafc3f15dff202980cf24f99221eeb3 Mon Sep 17 00:00:00 2001 From: Christian Clauss Date: Tue, 16 Jun 2020 16:33:17 +0200 Subject: [PATCH 0626/1071] CONTRIBUTING.md: Update flake8 command (#2124) * CONTRIBUTING.md: Update flake8 command * updating DIRECTORY.md Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> --- CONTRIBUTING.md | 4 ++-- DIRECTORY.md | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index dd4295404f78..2761be61aa8b 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -68,11 +68,11 @@ We want your work to be readable by others; therefore, we encourage you to note black . ``` -- All submissions will need to pass the test __flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics__ before they will be accepted so if possible, try this test locally on your Python file(s) before submitting your pull request. +- All submissions will need to pass the test __flake8 . --ignore=E203,W503 --max-line-length=88__ before they will be accepted so if possible, try this test locally on your Python file(s) before submitting your pull request. ```bash pip3 install flake8 # only required the first time - flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics + flake8 . --ignore=E203,W503 --max-line-length=88 --show-source ``` - Original code submission require docstrings or comments to describe your work. diff --git a/DIRECTORY.md b/DIRECTORY.md index b34658b32065..23cd7f79931a 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -395,6 +395,7 @@ * [Zellers Congruence](https://github.com/TheAlgorithms/Python/blob/master/maths/zellers_congruence.py) ## Matrix + * [Count Islands In Matrix](https://github.com/TheAlgorithms/Python/blob/master/matrix/count_islands_in_matrix.py) * [Matrix Class](https://github.com/TheAlgorithms/Python/blob/master/matrix/matrix_class.py) * [Matrix Operation](https://github.com/TheAlgorithms/Python/blob/master/matrix/matrix_operation.py) * [Nth Fibonacci Using Matrix Exponentiation](https://github.com/TheAlgorithms/Python/blob/master/matrix/nth_fibonacci_using_matrix_exponentiation.py) From a5c246793cd2352f0037a76d9949ffebd49261ea Mon Sep 17 00:00:00 2001 From: Erwin Lejeune Date: Tue, 16 Jun 2020 17:10:22 +0200 Subject: [PATCH 0627/1071] Graphs : Bidirectional Breadth-First Search (#2057) * implement bidirectional breadth first * remove useless import * remove trailing whitespaces --- graphs/bidirectional_breadth_first_search.py | 177 +++++++++++++++++++ 1 file changed, 177 insertions(+) create mode 100644 graphs/bidirectional_breadth_first_search.py diff --git a/graphs/bidirectional_breadth_first_search.py b/graphs/bidirectional_breadth_first_search.py new file mode 100644 index 000000000000..d567f6ae6a28 --- /dev/null +++ b/graphs/bidirectional_breadth_first_search.py @@ -0,0 +1,177 @@ +""" +https://en.wikipedia.org/wiki/Bidirectional_search +""" + +import time +from typing import List, Tuple + +grid = [ + [0, 0, 0, 0, 0, 0, 0], + [0, 1, 0, 0, 0, 0, 0], # 0 are free path whereas 1's are obstacles + [0, 0, 0, 0, 0, 0, 0], + [0, 0, 1, 0, 0, 0, 0], + [1, 0, 1, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 1, 0, 0], +] + +delta = [[-1, 0], [0, -1], [1, 0], [0, 1]] # up, left, down, right + + +class Node: + def __init__(self, pos_x, pos_y, goal_x, goal_y, parent): + self.pos_x = pos_x + self.pos_y = pos_y + self.pos = (pos_y, pos_x) + self.goal_x = goal_x + self.goal_y = goal_y + self.parent = parent + + +class BreadthFirstSearch: + """ + >>> bfs = BreadthFirstSearch((0, 0), (len(grid) - 1, len(grid[0]) - 1)) + >>> (bfs.start.pos_y + delta[3][0], bfs.start.pos_x + delta[3][1]) + (0, 1) + >>> [x.pos for x in bfs.get_successors(bfs.start)] + [(1, 0), (0, 1)] + >>> (bfs.start.pos_y + delta[2][0], bfs.start.pos_x + delta[2][1]) + (1, 0) + >>> bfs.retrace_path(bfs.start) + [(0, 0)] + >>> bfs.search() # doctest: +NORMALIZE_WHITESPACE + [(0, 0), (1, 0), (2, 0), (3, 0), (3, 1), (4, 1), + (5, 1), (5, 2), (5, 3), (5, 4), (5, 5), (6, 5), (6, 6)] + """ + def __init__(self, start, goal): + self.start = Node(start[1], start[0], goal[1], goal[0], None) + self.target = Node(goal[1], goal[0], goal[1], goal[0], None) + + self.node_queue = [self.start] + self.reached = False + + def search(self) -> List[Tuple[int]]: + while self.node_queue: + current_node = self.node_queue.pop(0) + + if current_node.pos == self.target.pos: + self.reached = True + return self.retrace_path(current_node) + + successors = self.get_successors(current_node) + + for node in successors: + self.node_queue.append(node) + + if not (self.reached): + return [(self.start.pos)] + + def get_successors(self, parent: Node) -> List[Node]: + """ + Returns a list of successors (both in the grid and free spaces) + """ + successors = [] + for action in delta: + pos_x = parent.pos_x + action[1] + pos_y = parent.pos_y + action[0] + if not (0 <= pos_x <= len(grid[0]) - 1 and 0 <= pos_y <= len(grid) - 1): + continue + + if grid[pos_y][pos_x] != 0: + continue + + successors.append( + Node(pos_x, pos_y, self.target.pos_y, self.target.pos_x, parent) + ) + return successors + + def retrace_path(self, node: Node) -> List[Tuple[int]]: + """ + Retrace the path from parents to parents until start node + """ + current_node = node + path = [] + while current_node is not None: + path.append((current_node.pos_y, current_node.pos_x)) + current_node = current_node.parent + path.reverse() + return path + + +class BidirectionalBreadthFirstSearch: + """ + >>> bd_bfs = BidirectionalBreadthFirstSearch((0, 0), (len(grid) - 1, len(grid[0]) - 1)) + >>> bd_bfs.fwd_bfs.start.pos == bd_bfs.bwd_bfs.target.pos + True + >>> bd_bfs.retrace_bidirectional_path(bd_bfs.fwd_bfs.start, + ... bd_bfs.bwd_bfs.start) + [(0, 0)] + >>> bd_bfs.search() # doctest: +NORMALIZE_WHITESPACE + [(0, 0), (0, 1), (0, 2), (1, 2), (2, 2), (2, 3), + (2, 4), (3, 4), (3, 5), (3, 6), (4, 6), (5, 6), (6, 6)] + """ + def __init__(self, start, goal): + self.fwd_bfs = BreadthFirstSearch(start, goal) + self.bwd_bfs = BreadthFirstSearch(goal, start) + self.reached = False + + def search(self) -> List[Tuple[int]]: + while self.fwd_bfs.node_queue or self.bwd_bfs.node_queue: + current_fwd_node = self.fwd_bfs.node_queue.pop(0) + current_bwd_node = self.bwd_bfs.node_queue.pop(0) + + if current_bwd_node.pos == current_fwd_node.pos: + self.reached = True + return self.retrace_bidirectional_path( + current_fwd_node, current_bwd_node + ) + + self.fwd_bfs.target = current_bwd_node + self.bwd_bfs.target = current_fwd_node + + successors = { + self.fwd_bfs: self.fwd_bfs.get_successors(current_fwd_node), + self.bwd_bfs: self.bwd_bfs.get_successors(current_bwd_node), + } + + for bfs in [self.fwd_bfs, self.bwd_bfs]: + for node in successors[bfs]: + bfs.node_queue.append(node) + + if not self.reached: + return [self.fwd_bfs.start.pos] + + def retrace_bidirectional_path( + self, fwd_node: Node, bwd_node: Node + ) -> List[Tuple[int]]: + fwd_path = self.fwd_bfs.retrace_path(fwd_node) + bwd_path = self.bwd_bfs.retrace_path(bwd_node) + bwd_path.pop() + bwd_path.reverse() + path = fwd_path + bwd_path + return path + + +if __name__ == "__main__": + # all coordinates are given in format [y,x] + import doctest + + doctest.testmod() + init = (0, 0) + goal = (len(grid) - 1, len(grid[0]) - 1) + for elem in grid: + print(elem) + + start_bfs_time = time.time() + bfs = BreadthFirstSearch(init, goal) + path = bfs.search() + bfs_time = time.time() - start_bfs_time + + print("Unidirectional BFS computation time : ", bfs_time) + + start_bd_bfs_time = time.time() + bd_bfs = BidirectionalBreadthFirstSearch(init, goal) + bd_path = bd_bfs.search() + bd_bfs_time = time.time() - start_bd_bfs_time + + print("Bidirectional BFS computation time : ", bd_bfs_time) From 924ef9b7d88cb6c911c95fff370a91bd615005f2 Mon Sep 17 00:00:00 2001 From: beqakd <39763019+beqakd@users.noreply.github.com> Date: Tue, 16 Jun 2020 23:36:57 +0400 Subject: [PATCH 0628/1071] implementation of entropy algorithm. (#2110) * implementation of entropy algorithm. * add tests, fix requested changes * open_file() --> analyze_text() * Create bidirectional_breadth_first_search.py * # type: ignore Co-authored-by: Christian Clauss --- graphs/bidirectional_breadth_first_search.py | 3 +- maths/entropy.py | 131 +++++++++++++++++++ 2 files changed, 133 insertions(+), 1 deletion(-) create mode 100644 maths/entropy.py diff --git a/graphs/bidirectional_breadth_first_search.py b/graphs/bidirectional_breadth_first_search.py index d567f6ae6a28..d6e3ebd64697 100644 --- a/graphs/bidirectional_breadth_first_search.py +++ b/graphs/bidirectional_breadth_first_search.py @@ -100,7 +100,8 @@ def retrace_path(self, node: Node) -> List[Tuple[int]]: class BidirectionalBreadthFirstSearch: """ - >>> bd_bfs = BidirectionalBreadthFirstSearch((0, 0), (len(grid) - 1, len(grid[0]) - 1)) + >>> bd_bfs = BidirectionalBreadthFirstSearch((0, 0), (len(grid) - 1, + ... len(grid[0]) - 1)) >>> bd_bfs.fwd_bfs.start.pos == bd_bfs.bwd_bfs.target.pos True >>> bd_bfs.retrace_bidirectional_path(bd_bfs.fwd_bfs.start, diff --git a/maths/entropy.py b/maths/entropy.py new file mode 100644 index 000000000000..eb6bf1a5a3ec --- /dev/null +++ b/maths/entropy.py @@ -0,0 +1,131 @@ +#!/usr/bin/env python3 + +""" +Implementation of entropy of information +https://en.wikipedia.org/wiki/Entropy_(information_theory) +""" + +import math +from collections import Counter +from string import ascii_lowercase +from typing import Tuple + + +def calculate_prob(text: str) -> None: + """ + This method takes path and two dict as argument + and than calculates entropy of them. + :param dict: + :param dict: + :return: Prints + 1) Entropy of information based on 1 alphabet + 2) Entropy of information based on couples of 2 alphabet + 3) print Entropy of H(X n∣Xn−1) + + Text from random books. Also, random quotes. + >>> text = ("Behind Winston’s back the voice " + ... "from the telescreen was still " + ... "babbling and the overfulfilment") + >>> calculate_prob(text) + 4.0 + 6.0 + 2.0 + + >>> text = ("The Ministry of Truth—Minitrue, in Newspeak [Newspeak was the official" + ... "face in elegant lettering, the three") + >>> calculate_prob(text) + 4.0 + 5.0 + 1.0 + >>> text = ("Had repulsive dashwoods suspicion sincerity but advantage now him. " + ... "Remark easily garret nor nay. Civil those mrs enjoy shy fat merry. " + ... "You greatest jointure saw horrible. He private he on be imagine " + ... "suppose. Fertile beloved evident through no service elderly is. Blind " + ... "there if every no so at. Own neglected you preferred way sincerity " + ... "delivered his attempted. To of message cottage windows do besides " + ... "against uncivil. Delightful unreserved impossible few estimating " + ... "men favourable see entreaties. She propriety immediate was improving. " + ... "He or entrance humoured likewise moderate. Much nor game son say " + ... "feel. Fat make met can must form into gate. Me we offending prevailed " + ... "discovery.") + >>> calculate_prob(text) + 4.0 + 7.0 + 3.0 + """ + single_char_strings, two_char_strings = analyze_text(text) + my_alphas = list(' ' + ascii_lowercase) + # what is our total sum of probabilities. + all_sum = sum(single_char_strings.values()) + + # one length string + my_fir_sum = 0 + # for each alpha we go in our dict and if it is in it we calculate entropy + for ch in my_alphas: + if ch in single_char_strings: + my_str = single_char_strings[ch] + prob = my_str / all_sum + my_fir_sum += prob * math.log2(prob) # entropy formula. + + # print entropy + print("{0:.1f}".format(round(-1 * my_fir_sum))) + + # two len string + all_sum = sum(two_char_strings.values()) + my_sec_sum = 0 + # for each alpha (two in size) calculate entropy. + for ch0 in my_alphas: + for ch1 in my_alphas: + sequence = ch0 + ch1 + if sequence in two_char_strings: + my_str = two_char_strings[sequence] + prob = int(my_str) / all_sum + my_sec_sum += prob * math.log2(prob) + + # print second entropy + print("{0:.1f}".format(round(-1 * my_sec_sum))) + + # print the difference between them + print("{0:.1f}".format(round(((-1 * my_sec_sum) - (-1 * my_fir_sum))))) + + +def analyze_text(text: str) -> Tuple[dict, dict]: + """ + Convert text input into two dicts of counts. + The first dictionary stores the frequency of single character strings. + The second dictionary stores the frequency of two character strings. + """ + single_char_strings = Counter() # type: ignore + two_char_strings = Counter() # type: ignore + single_char_strings[text[-1]] += 1 + + # first case when we have space at start. + two_char_strings[" " + text[0]] += 1 + for i in range(0, len(text) - 1): + single_char_strings[text[i]] += 1 + two_char_strings[text[i : i + 2]] += 1 + return single_char_strings, two_char_strings + + +def main(): + import doctest + + doctest.testmod() + # text = ( + # "Had repulsive dashwoods suspicion sincerity but advantage now him. Remark " + # "easily garret nor nay. Civil those mrs enjoy shy fat merry. You greatest " + # "jointure saw horrible. He private he on be imagine suppose. Fertile " + # "beloved evident through no service elderly is. Blind there if every no so " + # "at. Own neglected you preferred way sincerity delivered his attempted. To " + # "of message cottage windows do besides against uncivil. Delightful " + # "unreserved impossible few estimating men favourable see entreaties. She " + # "propriety immediate was improving. He or entrance humoured likewise " + # "moderate. Much nor game son say feel. Fat make met can must form into " + # "gate. Me we offending prevailed discovery. " + # ) + + # calculate_prob(text) + + +if __name__ == "__main__": + main() From 62f7561428ccbd05abc89b0b674f61ef32c85fd4 Mon Sep 17 00:00:00 2001 From: bnMikheili <39998190+bnMikheili@users.noreply.github.com> Date: Wed, 17 Jun 2020 00:22:47 +0400 Subject: [PATCH 0629/1071] Hash djb2 (#2098) * implement hash * fix flake8 error * Update hashes/djb2.py * Update hashes/djb2.py * Long lines * def djb2(s: str) -> int: Co-authored-by: Christian Clauss --- hashes/djb2.py | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 hashes/djb2.py diff --git a/hashes/djb2.py b/hashes/djb2.py new file mode 100644 index 000000000000..2d1c9aabb1fb --- /dev/null +++ b/hashes/djb2.py @@ -0,0 +1,35 @@ +""" +This algorithm (k=33) was first reported by Dan Bernstein many years ago in comp.lang.c +Another version of this algorithm (now favored by Bernstein) uses xor: + hash(i) = hash(i - 1) * 33 ^ str[i]; + + First Magic constant 33: + It has never been adequately explained. + It's magic because it works better than many other constants, prime or not. + + Second Magic Constant 5381: + + 1. odd number + 2. prime number + 3. deficient number + 4. 001/010/100/000/101 b + + source: http://www.cse.yorku.ca/~oz/hash.html +""" + + +def djb2(s: str) -> int: + """ + Implementation of djb2 hash algorithm that + is popular because of it's magic constants. + + >>> djb2('Algorithms') + 3782405311 + + >>> djb2('scramble bits') + 1609059040 + """ + hash = 5381 + for x in s: + hash = ((hash << 5) + hash) + ord(x) + return hash & 0xFFFFFFFF From f97af65579c78686d812da2e8c42c3a6d1258169 Mon Sep 17 00:00:00 2001 From: Christian Clauss Date: Wed, 17 Jun 2020 00:59:38 +0200 Subject: [PATCH 0630/1071] Blacken our code (#2125) * Blacken Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> --- DIRECTORY.md | 2 ++ graphs/bidirectional_breadth_first_search.py | 2 ++ maths/entropy.py | 2 +- 3 files changed, 5 insertions(+), 1 deletion(-) diff --git a/DIRECTORY.md b/DIRECTORY.md index 23cd7f79931a..54de20c0e13f 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -228,6 +228,7 @@ * [Bfs](https://github.com/TheAlgorithms/Python/blob/master/graphs/bfs.py) * [Bfs Shortest Path](https://github.com/TheAlgorithms/Python/blob/master/graphs/bfs_shortest_path.py) * [Bidirectional A Star](https://github.com/TheAlgorithms/Python/blob/master/graphs/bidirectional_a_star.py) + * [Bidirectional Breadth First Search](https://github.com/TheAlgorithms/Python/blob/master/graphs/bidirectional_breadth_first_search.py) * [Breadth First Search](https://github.com/TheAlgorithms/Python/blob/master/graphs/breadth_first_search.py) * [Breadth First Search Shortest Path](https://github.com/TheAlgorithms/Python/blob/master/graphs/breadth_first_search_shortest_path.py) * [Check Bipartite Graph Bfs](https://github.com/TheAlgorithms/Python/blob/master/graphs/check_bipartite_graph_bfs.py) @@ -325,6 +326,7 @@ * [Chudnovsky Algorithm](https://github.com/TheAlgorithms/Python/blob/master/maths/chudnovsky_algorithm.py) * [Collatz Sequence](https://github.com/TheAlgorithms/Python/blob/master/maths/collatz_sequence.py) * [Combinations](https://github.com/TheAlgorithms/Python/blob/master/maths/combinations.py) + * [Entropy](https://github.com/TheAlgorithms/Python/blob/master/maths/entropy.py) * [Eulers Totient](https://github.com/TheAlgorithms/Python/blob/master/maths/eulers_totient.py) * [Explicit Euler](https://github.com/TheAlgorithms/Python/blob/master/maths/explicit_euler.py) * [Extended Euclidean Algorithm](https://github.com/TheAlgorithms/Python/blob/master/maths/extended_euclidean_algorithm.py) diff --git a/graphs/bidirectional_breadth_first_search.py b/graphs/bidirectional_breadth_first_search.py index d6e3ebd64697..d941c0db5893 100644 --- a/graphs/bidirectional_breadth_first_search.py +++ b/graphs/bidirectional_breadth_first_search.py @@ -43,6 +43,7 @@ class BreadthFirstSearch: [(0, 0), (1, 0), (2, 0), (3, 0), (3, 1), (4, 1), (5, 1), (5, 2), (5, 3), (5, 4), (5, 5), (6, 5), (6, 6)] """ + def __init__(self, start, goal): self.start = Node(start[1], start[0], goal[1], goal[0], None) self.target = Node(goal[1], goal[0], goal[1], goal[0], None) @@ -111,6 +112,7 @@ class BidirectionalBreadthFirstSearch: [(0, 0), (0, 1), (0, 2), (1, 2), (2, 2), (2, 3), (2, 4), (3, 4), (3, 5), (3, 6), (4, 6), (5, 6), (6, 6)] """ + def __init__(self, start, goal): self.fwd_bfs = BreadthFirstSearch(start, goal) self.bwd_bfs = BreadthFirstSearch(goal, start) diff --git a/maths/entropy.py b/maths/entropy.py index eb6bf1a5a3ec..c380afd3b5c9 100644 --- a/maths/entropy.py +++ b/maths/entropy.py @@ -54,7 +54,7 @@ def calculate_prob(text: str) -> None: 3.0 """ single_char_strings, two_char_strings = analyze_text(text) - my_alphas = list(' ' + ascii_lowercase) + my_alphas = list(" " + ascii_lowercase) # what is our total sum of probabilities. all_sum = sum(single_char_strings.values()) From 6f80ca821c2adc02196962a6682104321ba82566 Mon Sep 17 00:00:00 2001 From: Furkan Atesli <31884209+furkanatesli@users.noreply.github.com> Date: Wed, 17 Jun 2020 08:49:20 +0300 Subject: [PATCH 0631/1071] Create change_brightness.py (#2126) * Create change_brightness.py * Update change_brightness.py * Update change_brightness.py * Update change_brightness.py * Update change_brightness.py Co-authored-by: Christian Clauss --- digital_image_processing/change_brightness.py | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 digital_image_processing/change_brightness.py diff --git a/digital_image_processing/change_brightness.py b/digital_image_processing/change_brightness.py new file mode 100644 index 000000000000..97493f1a399e --- /dev/null +++ b/digital_image_processing/change_brightness.py @@ -0,0 +1,26 @@ +from PIL import Image + + +def change_brightness(img: Image, level: float) -> Image: + """ + Change the brightness of a PIL Image to a given level. + """ + + def brightness(c: int) -> float: + """ + Fundamental Transformation/Operation that'll be performed on + every bit. + """ + return 128 + level + (c - 128) + + if not -255.0 <= level <= 255.0: + raise ValueError("level must be between -255.0 (black) and 255.0 (white)") + return img.point(brightness) + + +if __name__ == "__main__": + # Load image + with Image.open("image_data/lena.jpg") as img: + # Change brightness to 100 + brigt_img = change_brightness(img, 100) + brigt_img.save("image_data/lena_brightness.png", format="png") From d7a75da8ef070235c89c619a819516f4601d9c7c Mon Sep 17 00:00:00 2001 From: mateuszz0000 Date: Wed, 17 Jun 2020 09:42:44 +0200 Subject: [PATCH 0632/1071] Added doctests to bucket sort (#2079) * Added doctests to bucket sort * Missing typehint * Wrap long lines * updating DIRECTORY.md * Update bucket_sort.py * updating DIRECTORY.md * Update bucket_sort.py Co-authored-by: Christian Clauss Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> --- DIRECTORY.md | 2 ++ sorts/bucket_sort.py | 81 +++++++++++++++++++++++++++----------------- 2 files changed, 52 insertions(+), 31 deletions(-) diff --git a/DIRECTORY.md b/DIRECTORY.md index 54de20c0e13f..940b039f99d1 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -147,6 +147,7 @@ * [Trie](https://github.com/TheAlgorithms/Python/blob/master/data_structures/trie/trie.py) ## Digital Image Processing + * [Change Brightness](https://github.com/TheAlgorithms/Python/blob/master/digital_image_processing/change_brightness.py) * [Change Contrast](https://github.com/TheAlgorithms/Python/blob/master/digital_image_processing/change_contrast.py) * [Convert To Negative](https://github.com/TheAlgorithms/Python/blob/master/digital_image_processing/convert_to_negative.py) * Dithering @@ -268,6 +269,7 @@ ## Hashes * [Adler32](https://github.com/TheAlgorithms/Python/blob/master/hashes/adler32.py) * [Chaos Machine](https://github.com/TheAlgorithms/Python/blob/master/hashes/chaos_machine.py) + * [Djb2](https://github.com/TheAlgorithms/Python/blob/master/hashes/djb2.py) * [Enigma Machine](https://github.com/TheAlgorithms/Python/blob/master/hashes/enigma_machine.py) * [Hamming Code](https://github.com/TheAlgorithms/Python/blob/master/hashes/hamming_code.py) * [Md5](https://github.com/TheAlgorithms/Python/blob/master/hashes/md5.py) diff --git a/sorts/bucket_sort.py b/sorts/bucket_sort.py index a2d1096ece6a..178b4f664480 100644 --- a/sorts/bucket_sort.py +++ b/sorts/bucket_sort.py @@ -1,34 +1,54 @@ -#!/usr/bin/env python - -"""Illustrate how to implement bucket sort algorithm.""" - -# Author: OMKAR PATHAK -# This program will illustrate how to implement bucket sort algorithm - -# Wikipedia says: Bucket sort, or bin sort, is a sorting algorithm that works -# by distributing the elements of an array into a number of buckets. -# Each bucket is then sorted individually, either using a different sorting -# algorithm, or by recursively applying the bucket sorting algorithm. It is a -# distribution sort, and is a cousin of radix sort in the most to least -# significant digit flavour. -# Bucket sort is a generalization of pigeonhole sort. Bucket sort can be -# implemented with comparisons and therefore can also be considered a -# comparison sort algorithm. The computational complexity estimates involve the -# number of buckets. - -# Time Complexity of Solution: -# Worst case scenario occurs when all the elements are placed in a single bucket. The -# overall performance would then be dominated by the algorithm used to sort each bucket. -# In this case, O(n log n), because of TimSort -# -# Average Case O(n + (n^2)/k + k), where k is the number of buckets -# -# If k = O(n), time complexity is O(n) +#!/usr/bin/env python3 +""" +Illustrate how to implement bucket sort algorithm. +Author: OMKAR PATHAK +This program will illustrate how to implement bucket sort algorithm + +Wikipedia says: Bucket sort, or bin sort, is a sorting algorithm that works +by distributing the elements of an array into a number of buckets. +Each bucket is then sorted individually, either using a different sorting +algorithm, or by recursively applying the bucket sorting algorithm. It is a +distribution sort, and is a cousin of radix sort in the most to least +significant digit flavour. +Bucket sort is a generalization of pigeonhole sort. Bucket sort can be +implemented with comparisons and therefore can also be considered a +comparison sort algorithm. The computational complexity estimates involve the +number of buckets. + +Time Complexity of Solution: +Worst case scenario occurs when all the elements are placed in a single bucket. +The overall performance would then be dominated by the algorithm used to sort each +bucket. In this case, O(n log n), because of TimSort + +Average Case O(n + (n^2)/k + k), where k is the number of buckets + +If k = O(n), time complexity is O(n) + +Source: https://en.wikipedia.org/wiki/Bucket_sort +""" DEFAULT_BUCKET_SIZE = 5 -def bucket_sort(my_list, bucket_size=DEFAULT_BUCKET_SIZE): +def bucket_sort(my_list: list, bucket_size: int = DEFAULT_BUCKET_SIZE) -> list: + """ + >>> data = [-1, 2, -5, 0] + >>> bucket_sort(data) == sorted(data) + True + + >>> data = [9, 8, 7, 6, -12] + >>> bucket_sort(data) == sorted(data) + True + + >>> data = [.4, 1.2, .1, .2, -.9] + >>> bucket_sort(data) == sorted(data) + True + + >>> bucket_sort([]) + Traceback (most recent call last): + ... + Exception: Please add some elements in the array. + """ if len(my_list) == 0: raise Exception("Please add some elements in the array.") @@ -40,11 +60,10 @@ def bucket_sort(my_list, bucket_size=DEFAULT_BUCKET_SIZE): buckets[int((my_list[i] - min_value) // bucket_size)].append(my_list[i]) return sorted( - [buckets[i][j] for i in range(len(buckets)) for j in range(len(buckets[i]))] + buckets[i][j] for i in range(len(buckets)) for j in range(len(buckets[i])) ) if __name__ == "__main__": - user_input = input("Enter numbers separated by a comma:").strip() - unsorted = [float(n) for n in user_input.split(",") if len(user_input) > 0] - print(bucket_sort(unsorted)) + assert bucket_sort([4, 5, 3, 2, 1]) == [1, 2, 3, 4, 5] + assert bucket_sort([0, 1, -10, 15, 2, -2]) == [-10, -2, 0, 1, 2, 15] From 23484efdad9afc33259adb5753fe2d378e61c37a Mon Sep 17 00:00:00 2001 From: Tapajyoti Bose <44058757+ruppysuppy@users.noreply.github.com> Date: Wed, 17 Jun 2020 20:39:29 +0530 Subject: [PATCH 0633/1071] Added maximum non-adjacent sum (#2130) * Added maximum non-adjacent sum * Bugfix: flake8 test * Implemented changes (broke tuple unpacking into 2 lines due to flake8 tests) * Implemented changes v2.0 * Update max_non_adjacent_sum.py Co-authored-by: Christian Clauss --- dynamic_programming/max_non_adjacent_sum.py | 33 +++++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 dynamic_programming/max_non_adjacent_sum.py diff --git a/dynamic_programming/max_non_adjacent_sum.py b/dynamic_programming/max_non_adjacent_sum.py new file mode 100644 index 000000000000..1d771f21f3fb --- /dev/null +++ b/dynamic_programming/max_non_adjacent_sum.py @@ -0,0 +1,33 @@ +# Video Explaination: https://www.youtube.com/watch?v=6w60Zi1NtL8&feature=emb_logo + +from typing import List + + +def maximum_non_adjacent_sum(nums: List[int]) -> int: + ''' + Find the maximum non-adjacent sum of the integers in the nums input list + + >>> print(maximum_non_adjacent_sum([1, 2, 3])) + 4 + >>> maximum_non_adjacent_sum([1, 5, 3, 7, 2, 2, 6]) + 18 + >>> maximum_non_adjacent_sum([-1, -5, -3, -7, -2, -2, -6]) + 0 + >>> maximum_non_adjacent_sum([499, 500, -3, -7, -2, -2, -6]) + 500 + ''' + if not nums: + return 0 + max_including = nums[0] + max_excluding = 0 + for num in nums[1:]: + max_including, max_excluding = ( + max_excluding + num, max(max_including, max_excluding) + ) + return max(max_excluding, max_including) + + +if __name__ == "__main__": + import doctest + + doctest.testmod() From 2bbdc3bfe75298ca26459e391035670ac3f9cacb Mon Sep 17 00:00:00 2001 From: Nika Losaberidze Date: Wed, 17 Jun 2020 20:15:24 +0400 Subject: [PATCH 0634/1071] Implement connected components algorithm for graphs (#2113) * Implement connected components algorithm for graphs * fixup! Format Python code with psf/black push * Add parameters and return values annotations with Python type hints * updating DIRECTORY.md * Add doctests and typehints * Remove unnecessary comments, change variable names * fixup! Format Python code with psf/black push Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> --- DIRECTORY.md | 1 + graphs/connected_components.py | 73 ++++++++++++++++++++++++++++++++++ 2 files changed, 74 insertions(+) create mode 100644 graphs/connected_components.py diff --git a/DIRECTORY.md b/DIRECTORY.md index 940b039f99d1..9ab6a457e545 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -234,6 +234,7 @@ * [Breadth First Search Shortest Path](https://github.com/TheAlgorithms/Python/blob/master/graphs/breadth_first_search_shortest_path.py) * [Check Bipartite Graph Bfs](https://github.com/TheAlgorithms/Python/blob/master/graphs/check_bipartite_graph_bfs.py) * [Check Bipartite Graph Dfs](https://github.com/TheAlgorithms/Python/blob/master/graphs/check_bipartite_graph_dfs.py) + * [Connected Components](https://github.com/TheAlgorithms/Python/blob/master/graphs/connected_components.py) * [Depth First Search](https://github.com/TheAlgorithms/Python/blob/master/graphs/depth_first_search.py) * [Depth First Search 2](https://github.com/TheAlgorithms/Python/blob/master/graphs/depth_first_search_2.py) * [Dijkstra](https://github.com/TheAlgorithms/Python/blob/master/graphs/dijkstra.py) diff --git a/graphs/connected_components.py b/graphs/connected_components.py new file mode 100644 index 000000000000..b5ef8c292b29 --- /dev/null +++ b/graphs/connected_components.py @@ -0,0 +1,73 @@ +""" +https://en.wikipedia.org/wiki/Component_(graph_theory) + +Finding connected components in graph + +""" + +test_graph_1 = { + 0: [1, 2], + 1: [0, 3], + 2: [0], + 3: [1], + 4: [5, 6], + 5: [4, 6], + 6: [4, 5], +} + +test_graph_2 = { + 0: [1, 2, 3], + 1: [0, 3], + 2: [0], + 3: [0, 1], + 4: [], + 5: [], +} + + +def dfs(graph: dict, vert: int, visited: list) -> list: + """ + Use depth first search to find all vertexes + being in the same component as initial vertex + >>> dfs(test_graph_1, 0, 5 * [False]) + [0, 1, 3, 2] + >>> dfs(test_graph_2, 0, 6 * [False]) + [0, 1, 3, 2] + """ + + visited[vert] = True + connected_verts = [] + + for neighbour in graph[vert]: + if not visited[neighbour]: + connected_verts += dfs(graph, neighbour, visited) + + return [vert] + connected_verts + + +def connected_components(graph: dict) -> list: + """ + This function takes graph as a parameter + and then returns the list of connected components + >>> connected_components(test_graph_1) + [[0, 1, 3, 2], [4, 5, 6]] + >>> connected_components(test_graph_2) + [[0, 1, 3, 2], [4], [5]] + """ + + graph_size = len(graph) + visited = graph_size * [False] + components_list = [] + + for i in range(graph_size): + if not visited[i]: + i_connected = dfs(graph, i, visited) + components_list.append(i_connected) + + return components_list + + +if __name__ == "__main__": + import doctest + + doctest.testmod() From fb3a228d2695d083208ecac26ff1e7b5a9f463b0 Mon Sep 17 00:00:00 2001 From: Nika Losaberidze Date: Wed, 17 Jun 2020 20:16:54 +0400 Subject: [PATCH 0635/1071] Strongly connected components (#2114) * Implement strongly connected components for graph algorithms * fixup! Format Python code with psf/black push * Delete trailing whitespace * updating DIRECTORY.md * Add doctests and typehints * Remove unnecessary comments, change variable names * fixup! Format Python code with psf/black push * Change undefined variable's name * Apply suggestions from code review Co-authored-by: Christian Clauss Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> Co-authored-by: Christian Clauss --- DIRECTORY.md | 1 + graphs/strongly_connected_components.py | 105 ++++++++++++++++++++++++ 2 files changed, 106 insertions(+) create mode 100644 graphs/strongly_connected_components.py diff --git a/DIRECTORY.md b/DIRECTORY.md index 9ab6a457e545..01f421613c35 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -261,6 +261,7 @@ * [Page Rank](https://github.com/TheAlgorithms/Python/blob/master/graphs/page_rank.py) * [Prim](https://github.com/TheAlgorithms/Python/blob/master/graphs/prim.py) * [Scc Kosaraju](https://github.com/TheAlgorithms/Python/blob/master/graphs/scc_kosaraju.py) + * [Strongly Connected Components](https://github.com/TheAlgorithms/Python/blob/master/graphs/strongly_connected_components.py) * [Tarjans Scc](https://github.com/TheAlgorithms/Python/blob/master/graphs/tarjans_scc.py) ## Greedy Method diff --git a/graphs/strongly_connected_components.py b/graphs/strongly_connected_components.py new file mode 100644 index 000000000000..283545c9a618 --- /dev/null +++ b/graphs/strongly_connected_components.py @@ -0,0 +1,105 @@ +""" +https://en.wikipedia.org/wiki/Strongly_connected_component + +Finding strongly connected components in directed graph + +""" + +test_graph_1 = { + 0: [2, 3], + 1: [0], + 2: [1], + 3: [4], + 4: [], +} + +test_graph_2 = { + 0: [1, 2, 3], + 1: [2], + 2: [0], + 3: [4], + 4: [5], + 5: [3], +} + + +def topology_sort(graph: dict, vert: int, visited: list) -> list: + """ + Use depth first search to sort graph + At this time graph is the same as input + >>> topology_sort(test_graph_1, 0, 5 * [False]) + [1, 2, 4, 3, 0] + >>> topology_sort(test_graph_2, 0, 6 * [False]) + [2, 1, 5, 4, 3, 0] + """ + + visited[vert] = True + order = [] + + for neighbour in graph[vert]: + if not visited[neighbour]: + order += topology_sort(graph, neighbour, visited) + + order.append(vert) + + return order + + +def find_components(reversed_graph: dict, vert: int, visited: list) -> list: + """ + Use depth first search to find strongliy connected + vertices. Now graph is reversed + >>> find_components({0: [1], 1: [2], 2: [0]}, 0, 5 * [False]) + [0, 1, 2] + >>> find_components({0: [2], 1: [0], 2: [0, 1]}, 0, 6 * [False]) + [0, 2, 1] + """ + + visited[vert] = True + component = [vert] + + for neighbour in reversed_graph[vert]: + if not visited[neighbour]: + component += find_components(reversed_graph, neighbour, visited) + + return component + + +def strongly_connected_components(graph: dict) -> list: + """ + This function takes graph as a parameter + and then returns the list of strongly connected components + >>> strongly_connected_components(test_graph_1) + [[0, 1, 2], [3], [4]] + >>> strongly_connected_components(test_graph_2) + [[0, 2, 1], [3, 5, 4]] + """ + + visited = len(graph) * [False] + reversed_graph = {vert: [] for vert in range(len(graph))} + + for vert, neighbours in graph.items(): + for neighbour in neighbours: + reversed_graph[neighbour].append(vert) + + order = [] + for i, was_visited in enumerate(visited): + if not was_visited: + order += topology_sort(graph, i, visited) + + components_list = [] + visited = len(graph) * [False] + + for i in range(len(graph)): + vert = order[len(graph) - i - 1] + if not visited[vert]: + component = find_components(reversed_graph, vert, visited) + components_list.append(component) + + return components_list + + +if __name__ == "__main__": + import doctest + + doctest.testmod() From 671e570c35772141f16068e1c7c98f62e2bf478b Mon Sep 17 00:00:00 2001 From: Nika Losaberidze Date: Wed, 17 Jun 2020 20:27:05 +0400 Subject: [PATCH 0636/1071] Implement prefix function, knuth-morris-pratt another usage (#2099) * Implement prefix function, knuth-morris-pratt another usage * fixup! Format Python code with psf/black push * Fix style * updating DIRECTORY.md * Update prefix_function.py Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> Co-authored-by: Christian Clauss --- DIRECTORY.md | 1 + strings/prefix_function.py | 65 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 66 insertions(+) create mode 100644 strings/prefix_function.py diff --git a/DIRECTORY.md b/DIRECTORY.md index 01f421613c35..4ffd20da58de 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -650,6 +650,7 @@ * [Manacher](https://github.com/TheAlgorithms/Python/blob/master/strings/manacher.py) * [Min Cost String Conversion](https://github.com/TheAlgorithms/Python/blob/master/strings/min_cost_string_conversion.py) * [Naive String Search](https://github.com/TheAlgorithms/Python/blob/master/strings/naive_string_search.py) + * [Prefix Function](https://github.com/TheAlgorithms/Python/blob/master/strings/prefix_function.py) * [Rabin Karp](https://github.com/TheAlgorithms/Python/blob/master/strings/rabin_karp.py) * [Remove Duplicate](https://github.com/TheAlgorithms/Python/blob/master/strings/remove_duplicate.py) * [Reverse Words](https://github.com/TheAlgorithms/Python/blob/master/strings/reverse_words.py) diff --git a/strings/prefix_function.py b/strings/prefix_function.py new file mode 100644 index 000000000000..9e6dbbf5408f --- /dev/null +++ b/strings/prefix_function.py @@ -0,0 +1,65 @@ +""" +https://cp-algorithms.com/string/prefix-function.html + +Prefix function Knuth–Morris–Pratt algorithm + +Different algorithm than Knuth-Morris-Pratt pattern finding + +E.x. Finding longest prefix which is also suffix + +Time Complexity: O(n) - where n is the length of the string +""" + + +def prefix_function(input_string: str) -> list: + """ + For the given string this function computes value for each index(i), + which represents the longest coincidence of prefix and sufix + for given substring (input_str[0...i]) + + For the value of the first element the algorithm always returns 0 + + >>> prefix_function("aabcdaabc") + [0, 1, 0, 0, 0, 1, 2, 3, 4] + >>> prefix_function("asdasdad") + [0, 0, 0, 1, 2, 3, 4, 0] + """ + + # list for the result values + prefix_result = [0] * len(input_string) + + for i in range(1, len(input_string)): + + # use last results for better performance - dynamic programming + j = prefix_result[i - 1] + while j > 0 and input_string[i] != input_string[j]: + j = prefix_result[j - 1] + + if input_string[i] == input_string[j]: + j += 1 + prefix_result[i] = j + + return prefix_result + + +def longest_prefix(input_str: str) -> int: + """ + Prefix-function use case + Finding longest prefix which is sufix as well + + >>> longest_prefix("aabcdaabc") + 4 + >>> longest_prefix("asdasdad") + 4 + >>> longest_prefix("abcab") + 2 + """ + + # just returning maximum value of the array gives us answer + return max(prefix_function(input_str)) + + +if __name__ == "__main__": + import doctest + + doctest.testmod() From 19b713aecbaa3995d1b6fd9f766f7139d5a3def4 Mon Sep 17 00:00:00 2001 From: Ioane Margiani Date: Wed, 17 Jun 2020 23:12:48 +0400 Subject: [PATCH 0637/1071] Add lempel ziv compression (#2107) * Added lempel-ziv compression algorithm implementation * Added lempel-ziv decompression algorithm implementation * Reformatted lempel-ziv compress/decompress files using black * Added type hints and some other modifications (Doctests coming up) * Shortened several lines to comply with the standards --- compression/lempel_ziv.py | 125 +++++++++++++++++++++++++++ compression/lempel_ziv_decompress.py | 111 ++++++++++++++++++++++++ 2 files changed, 236 insertions(+) create mode 100644 compression/lempel_ziv.py create mode 100644 compression/lempel_ziv_decompress.py diff --git a/compression/lempel_ziv.py b/compression/lempel_ziv.py new file mode 100644 index 000000000000..3ac8573c43d8 --- /dev/null +++ b/compression/lempel_ziv.py @@ -0,0 +1,125 @@ +""" + One of the several implementations of Lempel–Ziv–Welch compression algorithm + https://en.wikipedia.org/wiki/Lempel%E2%80%93Ziv%E2%80%93Welch +""" + +import math +import os +import sys + + +def read_file_binary(file_path: str) -> str: + """ + Reads given file as bytes and returns them as a long string + """ + result = "" + try: + with open(file_path, "rb") as binary_file: + data = binary_file.read() + for dat in data: + curr_byte = "{0:08b}".format(dat) + result += curr_byte + return result + except IOError: + print("File not accessible") + sys.exit() + + +def add_key_to_lexicon( + lexicon: dict, curr_string: str, index: int, last_match_id: int +) -> None: + """ + Adds new strings (curr_string + "0", curr_string + "1") to the lexicon + """ + lexicon.pop(curr_string) + lexicon[curr_string + "0"] = last_match_id + + if math.log2(index).is_integer(): + for curr_key in lexicon: + lexicon[curr_key] = "0" + lexicon[curr_key] + + lexicon[curr_string + "1"] = bin(index)[2:] + + +def compress_data(data_bits: str) -> str: + """ + Compresses given data_bits using Lempel–Ziv–Welch compression algorithm + and returns the result as a string + """ + lexicon = {"0": "0", "1": "1"} + result, curr_string = "", "" + index = len(lexicon) + + for i in range(len(data_bits)): + curr_string += data_bits[i] + if curr_string not in lexicon: + continue + + last_match_id = lexicon[curr_string] + result += last_match_id + add_key_to_lexicon(lexicon, curr_string, index, last_match_id) + index += 1 + curr_string = "" + + while curr_string != "" and curr_string not in lexicon: + curr_string += "0" + + if curr_string != "": + last_match_id = lexicon[curr_string] + result += last_match_id + + return result + + +def add_file_length(source_path: str, compressed: str) -> str: + """ + Adds given file's length in front (using Elias gamma coding) of the compressed + string + """ + file_length = os.path.getsize(source_path) + file_length_binary = bin(file_length)[2:] + length_length = len(file_length_binary) + + return "0" * (length_length - 1) + file_length_binary + compressed + + +def write_file_binary(file_path: str, to_write: str) -> None: + """ + Writes given to_write string (should only consist of 0's and 1's) as bytes in the + file + """ + byte_length = 8 + try: + with open(file_path, "wb") as opened_file: + result_byte_array = [ + to_write[i : i + byte_length] + for i in range(0, len(to_write), byte_length) + ] + + if len(result_byte_array[-1]) % byte_length == 0: + result_byte_array.append("10000000") + else: + result_byte_array[-1] += "1" + "0" * ( + byte_length - len(result_byte_array[-1]) - 1 + ) + + for elem in result_byte_array: + opened_file.write(int(elem, 2).to_bytes(1, byteorder="big")) + except IOError: + print("File not accessible") + sys.exit() + + +def compress(source_path, destination_path: str) -> None: + """ + Reads source file, compresses it and writes the compressed result in destination + file + """ + data_bits = read_file_binary(source_path) + compressed = compress_data(data_bits) + compressed = add_file_length(source_path, compressed) + write_file_binary(destination_path, compressed) + + +if __name__ == "__main__": + compress(sys.argv[1], sys.argv[2]) diff --git a/compression/lempel_ziv_decompress.py b/compression/lempel_ziv_decompress.py new file mode 100644 index 000000000000..05c26740bf62 --- /dev/null +++ b/compression/lempel_ziv_decompress.py @@ -0,0 +1,111 @@ +""" + One of the several implementations of Lempel–Ziv–Welch decompression algorithm + https://en.wikipedia.org/wiki/Lempel%E2%80%93Ziv%E2%80%93Welch +""" + +import math +import sys + + +def read_file_binary(file_path: str) -> str: + """ + Reads given file as bytes and returns them as a long string + """ + result = "" + try: + with open(file_path, "rb") as binary_file: + data = binary_file.read() + for dat in data: + curr_byte = "{0:08b}".format(dat) + result += curr_byte + return result + except IOError: + print("File not accessible") + sys.exit() + + +def decompress_data(data_bits: str) -> str: + """ + Decompresses given data_bits using Lempel–Ziv–Welch compression algorithm + and returns the result as a string + """ + lexicon = {"0": "0", "1": "1"} + result, curr_string = "", "" + index = len(lexicon) + + for i in range(len(data_bits)): + curr_string += data_bits[i] + if curr_string not in lexicon: + continue + + last_match_id = lexicon[curr_string] + result += last_match_id + lexicon[curr_string] = last_match_id + "0" + + if math.log2(index).is_integer(): + newLex = {} + for curr_key in list(lexicon): + newLex["0" + curr_key] = lexicon.pop(curr_key) + lexicon = newLex + + lexicon[bin(index)[2:]] = last_match_id + "1" + index += 1 + curr_string = "" + return result + + +def write_file_binary(file_path: str, to_write: str) -> None: + """ + Writes given to_write string (should only consist of 0's and 1's) as bytes in the + file + """ + byte_length = 8 + try: + with open(file_path, "wb") as opened_file: + result_byte_array = [ + to_write[i : i + byte_length] + for i in range(0, len(to_write), byte_length) + ] + + if len(result_byte_array[-1]) % byte_length == 0: + result_byte_array.append("10000000") + else: + result_byte_array[-1] += "1" + "0" * ( + byte_length - len(result_byte_array[-1]) - 1 + ) + + for elem in result_byte_array[:-1]: + opened_file.write(int(elem, 2).to_bytes(1, byteorder="big")) + except IOError: + print("File not accessible") + sys.exit() + + +def remove_prefix(data_bits: str) -> str: + """ + Removes size prefix, that compressed file should have + Returns the result + """ + counter = 0 + for letter in data_bits: + if letter == "1": + break + counter += 1 + + data_bits = data_bits[counter:] + data_bits = data_bits[counter + 1 :] + return data_bits + + +def compress(source_path: str, destination_path: str) -> None: + """ + Reads source file, decompresses it and writes the result in destination file + """ + data_bits = read_file_binary(source_path) + data_bits = remove_prefix(data_bits) + decompressed = decompress_data(data_bits) + write_file_binary(destination_path, decompressed) + + +if __name__ == "__main__": + compress(sys.argv[1], sys.argv[2]) From b9e7c891e2465a89997281b69e1d5de93cb1d11a Mon Sep 17 00:00:00 2001 From: Tapajyoti Bose <44058757+ruppysuppy@users.noreply.github.com> Date: Thu, 18 Jun 2020 15:00:24 +0530 Subject: [PATCH 0638/1071] Added (Open) Knight Tour Algorithm (#2132) * Added (Open) Knight Tour Algorithm * Implemented Suggestions --- backtracking/knight_tour.py | 98 +++++++++++++++++++++++++++++++++++++ 1 file changed, 98 insertions(+) create mode 100644 backtracking/knight_tour.py diff --git a/backtracking/knight_tour.py b/backtracking/knight_tour.py new file mode 100644 index 000000000000..7d1e03b837e9 --- /dev/null +++ b/backtracking/knight_tour.py @@ -0,0 +1,98 @@ +# Knight Tour Intro: https://www.youtube.com/watch?v=ab_dY3dZFHM + +from typing import List, Tuple + + +def get_valid_pos(position: Tuple[int], n: int) -> List[Tuple[int]]: + ''' + Find all the valid positions a knight can move to from the current position. + + >>> get_valid_pos((1, 3), 4) + [(2, 1), (0, 1), (3, 2)] + ''' + + y, x = position + positions = [ + (y + 1, x + 2), + (y - 1, x + 2), + (y + 1, x - 2), + (y - 1, x - 2), + (y + 2, x + 1), + (y + 2, x - 1), + (y - 2, x + 1), + (y - 2, x - 1) + ] + permissible_positions = [] + + for position in positions: + y_test, x_test = position + if 0 <= y_test < n and 0 <= x_test < n: + permissible_positions.append(position) + + return permissible_positions + + +def is_complete(board: List[List[int]]) -> bool: + ''' + Check if the board (matrix) has been completely filled with non-zero values. + + >>> is_complete([[1]]) + True + + >>> is_complete([[1, 2], [3, 0]]) + False + ''' + + return not any(elem == 0 for row in board for elem in row) + + +def open_knight_tour_helper(board: List[List[int]], pos: Tuple[int], curr: int) -> bool: + ''' + Helper function to solve knight tour problem. + ''' + + if is_complete(board): + return True + + for position in get_valid_pos(pos, len(board)): + y, x = position + + if board[y][x] == 0: + board[y][x] = curr + 1 + if open_knight_tour_helper(board, position, curr + 1): + return True + board[y][x] = 0 + + return False + + +def open_knight_tour(n: int) -> List[List[int]]: + ''' + Find the solution for the knight tour problem for a board of size n. Raises + ValueError if the tour cannot be performed for the given size. + + >>> open_knight_tour(1) + [[1]] + + >>> open_knight_tour(2) + Traceback (most recent call last): + ... + ValueError: Open Kight Tour cannot be performed on a board of size 2 + ''' + + board = [[0 for i in range(n)] for j in range(n)] + + for i in range(n): + for j in range(n): + board[i][j] = 1 + if open_knight_tour_helper(board, (i, j), 1): + return board + board[i][j] = 0 + + raise ValueError(f"Open Kight Tour cannot be performed on a board of size {n}") + + +if __name__ == "__main__": + import doctest + + doctest.testmod() From d034add61f4fee07169cfe6aaf9f9a610b593cf1 Mon Sep 17 00:00:00 2001 From: beqakd <39763019+beqakd@users.noreply.github.com> Date: Fri, 19 Jun 2020 19:55:13 +0400 Subject: [PATCH 0639/1071] add visualization of k means clustering as excel format (#2104) * add visualization of kmneas clust as excel format * style changes * style changes * Add doctest and typehint! * style change * Update machine_learning/k_means_clust.py Co-authored-by: Christian Clauss * Update machine_learning/k_means_clust.py Co-authored-by: Christian Clauss Co-authored-by: Christian Clauss --- machine_learning/k_means_clust.py | 163 +++++++++++++++++++++++++++++- 1 file changed, 162 insertions(+), 1 deletion(-) diff --git a/machine_learning/k_means_clust.py b/machine_learning/k_means_clust.py index 86a5dd968779..d5fa31135073 100644 --- a/machine_learning/k_means_clust.py +++ b/machine_learning/k_means_clust.py @@ -47,12 +47,18 @@ k ) - 5. Have fun.. + 5. Transfers Dataframe into excel format it must have feature called + 'Clust' with k means clustering numbers in it. + """ import numpy as np +import pandas as pd from matplotlib import pyplot as plt from sklearn.metrics import pairwise_distances +import warnings + +warnings.filterwarnings("ignore") TAG = "K-MEANS-CLUST/ " @@ -202,3 +208,158 @@ def kmeans( verbose=True, ) plot_heterogeneity(heterogeneity, k) + + +def ReportGenerator( + df: pd.DataFrame, ClusteringVariables: np.array, FillMissingReport=None +) -> pd.DataFrame: + """ + Function generates easy-erading clustering report. It takes 2 arguments as an input: + DataFrame - dataframe with predicted cluester column; + FillMissingReport - dictionary of rules how we are going to fill missing + values of for final report generate (not included in modeling); + in order to run the function following libraries must be imported: + import pandas as pd + import numpy as np + + >>> data = pd.DataFrame() + >>> data['numbers'] = [1, 2, 3] + >>> data['col1'] = [0.5, 2.5, 4.5] + >>> data['col2'] = [100, 200, 300] + >>> data['col3'] = [10, 20, 30] + >>> data['Cluster'] = [1, 1, 2] + >>> ReportGenerator(data, ['col1', 'col2'], 0) + Features Type Mark 1 2 + 0 # of Customers ClusterSize False 2.000000 1.000000 + 1 % of Customers ClusterProportion False 0.666667 0.333333 + 2 col1 mean_with_zeros True 1.500000 4.500000 + 3 col2 mean_with_zeros True 150.000000 300.000000 + 4 numbers mean_with_zeros False 1.500000 3.000000 + .. ... ... ... ... ... + 99 dummy 5% False 1.000000 1.000000 + 100 dummy 95% False 1.000000 1.000000 + 101 dummy stdev False 0.000000 NaN + 102 dummy mode False 1.000000 1.000000 + 103 dummy median False 1.000000 1.000000 + + [104 rows x 5 columns] + """ + # Fill missing values with given rules + if FillMissingReport: + df.fillna(value=FillMissingReport, inplace=True) + df["dummy"] = 1 + numeric_cols = df.select_dtypes(np.number).columns + report = ( + df.groupby(["Cluster"])[ # constract report dataframe + numeric_cols + ] # group by cluster number + .agg( + [ + ("sum", np.sum), + ("mean_with_zeros", lambda x: np.mean(np.nan_to_num(x))), + ("mean_without_zeros", lambda x: x.replace(0, np.NaN).mean()), + ( + "mean_25-75", + lambda x: np.mean( + np.nan_to_num( + sorted(x)[ + round((len(x) * 25 / 100)) : round(len(x) * 75 / 100) + ] + ) + ), + ), + ("mean_with_na", np.mean), + ("min", lambda x: x.min()), + ("5%", lambda x: x.quantile(0.05)), + ("25%", lambda x: x.quantile(0.25)), + ("50%", lambda x: x.quantile(0.50)), + ("75%", lambda x: x.quantile(0.75)), + ("95%", lambda x: x.quantile(0.95)), + ("max", lambda x: x.max()), + ("count", lambda x: x.count()), + ("stdev", lambda x: x.std()), + ("mode", lambda x: x.mode()[0]), + ("median", lambda x: x.median()), + ("# > 0", lambda x: (x > 0).sum()), + ] + ) + .T.reset_index() + .rename(index=str, columns={"level_0": "Features", "level_1": "Type"}) + ) # rename columns + + clustersize = report[ + (report["Features"] == "dummy") & (report["Type"] == "count") + ] # caclulating size of cluster(count of clientID's) + clustersize.Type = ( + "ClusterSize" # rename created cluster df to match report column names + ) + clustersize.Features = "# of Customers" + clusterproportion = pd.DataFrame( + clustersize.iloc[:, 2:].values + / clustersize.iloc[:, 2:].values.sum() # caclulating proportion of cluster + ) + clusterproportion[ + "Type" + ] = "% of Customers" # rename created cluster df to match report column names + clusterproportion["Features"] = "ClusterProportion" + cols = clusterproportion.columns.tolist() + cols = cols[-2:] + cols[:-2] + clusterproportion = clusterproportion[cols] # rearrange columns to match report + clusterproportion.columns = report.columns + a = pd.DataFrame( + abs( + report[report["Type"] == "count"].iloc[:, 2:].values + - clustersize.iloc[:, 2:].values + ) + ) # generating df with count of nan values + a["Features"] = 0 + a["Type"] = "# of nan" + a.Features = report[ + report["Type"] == "count" + ].Features.tolist() # filling values in order to match report + cols = a.columns.tolist() + cols = cols[-2:] + cols[:-2] + a = a[cols] # rearrange columns to match report + a.columns = report.columns # rename columns to match report + report = report.drop( + report[report.Type == "count"].index + ) # drop count values except cluster size + report = pd.concat( + [report, a, clustersize, clusterproportion], axis=0 + ) # concat report with clustert size and nan values + report["Mark"] = report["Features"].isin(ClusteringVariables) + cols = report.columns.tolist() + cols = cols[0:2] + cols[-1:] + cols[2:-1] + report = report[cols] + sorter1 = { + "ClusterSize": 9, + "ClusterProportion": 8, + "mean_with_zeros": 7, + "mean_with_na": 6, + "max": 5, + "50%": 4, + "min": 3, + "25%": 2, + "75%": 1, + "# of nan": 0, + "# > 0": -1, + "sum_with_na": -2, + } + report = ( + report.assign( + Sorter1=lambda x: x.Type.map(sorter1), + Sorter2=lambda x: list(reversed(range(len(x)))), + ) + .sort_values(["Sorter1", "Mark", "Sorter2"], ascending=False) + .drop(["Sorter1", "Sorter2"], axis=1) + ) + report.columns.name = "" + report = report.reset_index() + report.drop(columns=["index"], inplace=True) + return report + + +if __name__ == "__main__": + import doctest + + doctest.testmod() From fdc5bee7af0310f5c18a69c909529923150a91e3 Mon Sep 17 00:00:00 2001 From: Christian Clauss Date: Mon, 22 Jun 2020 14:16:12 +0200 Subject: [PATCH 0640/1071] Euler problem 551 sol 1: Reduce McCabe code complexity (#2141) * Euler problem 551 sol 1: Reduce McCabe code complexity As discussed in #2128 * fixup! Format Python code with psf/black push Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> --- backtracking/knight_tour.py | 18 +++++++++--------- dynamic_programming/max_non_adjacent_sum.py | 7 ++++--- project_euler/problem_551/sol1.py | 8 ++------ 3 files changed, 15 insertions(+), 18 deletions(-) diff --git a/backtracking/knight_tour.py b/backtracking/knight_tour.py index 7d1e03b837e9..e4a93fbc2105 100644 --- a/backtracking/knight_tour.py +++ b/backtracking/knight_tour.py @@ -4,12 +4,12 @@ def get_valid_pos(position: Tuple[int], n: int) -> List[Tuple[int]]: - ''' + """ Find all the valid positions a knight can move to from the current position. >>> get_valid_pos((1, 3), 4) [(2, 1), (0, 1), (3, 2)] - ''' + """ y, x = position positions = [ @@ -20,7 +20,7 @@ def get_valid_pos(position: Tuple[int], n: int) -> List[Tuple[int]]: (y + 2, x + 1), (y + 2, x - 1), (y - 2, x + 1), - (y - 2, x - 1) + (y - 2, x - 1), ] permissible_positions = [] @@ -33,7 +33,7 @@ def get_valid_pos(position: Tuple[int], n: int) -> List[Tuple[int]]: def is_complete(board: List[List[int]]) -> bool: - ''' + """ Check if the board (matrix) has been completely filled with non-zero values. >>> is_complete([[1]]) @@ -41,15 +41,15 @@ def is_complete(board: List[List[int]]) -> bool: >>> is_complete([[1, 2], [3, 0]]) False - ''' + """ return not any(elem == 0 for row in board for elem in row) def open_knight_tour_helper(board: List[List[int]], pos: Tuple[int], curr: int) -> bool: - ''' + """ Helper function to solve knight tour problem. - ''' + """ if is_complete(board): return True @@ -67,7 +67,7 @@ def open_knight_tour_helper(board: List[List[int]], pos: Tuple[int], curr: int) def open_knight_tour(n: int) -> List[List[int]]: - ''' + """ Find the solution for the knight tour problem for a board of size n. Raises ValueError if the tour cannot be performed for the given size. @@ -78,7 +78,7 @@ def open_knight_tour(n: int) -> List[List[int]]: Traceback (most recent call last): ... ValueError: Open Kight Tour cannot be performed on a board of size 2 - ''' + """ board = [[0 for i in range(n)] for j in range(n)] diff --git a/dynamic_programming/max_non_adjacent_sum.py b/dynamic_programming/max_non_adjacent_sum.py index 1d771f21f3fb..b9f99a226bd9 100644 --- a/dynamic_programming/max_non_adjacent_sum.py +++ b/dynamic_programming/max_non_adjacent_sum.py @@ -4,7 +4,7 @@ def maximum_non_adjacent_sum(nums: List[int]) -> int: - ''' + """ Find the maximum non-adjacent sum of the integers in the nums input list >>> print(maximum_non_adjacent_sum([1, 2, 3])) @@ -15,14 +15,15 @@ def maximum_non_adjacent_sum(nums: List[int]) -> int: 0 >>> maximum_non_adjacent_sum([499, 500, -3, -7, -2, -2, -6]) 500 - ''' + """ if not nums: return 0 max_including = nums[0] max_excluding = 0 for num in nums[1:]: max_including, max_excluding = ( - max_excluding + num, max(max_including, max_excluding) + max_excluding + num, + max(max_including, max_excluding), ) return max(max_excluding, max_including) diff --git a/project_euler/problem_551/sol1.py b/project_euler/problem_551/sol1.py index bbdd4d6b039d..817474b3578b 100644 --- a/project_euler/problem_551/sol1.py +++ b/project_euler/problem_551/sol1.py @@ -40,12 +40,8 @@ def next_term(a_i, k, i, n): ending term is a_10=62, then (61, 9) is returned. """ # ds_b - digitsum(b) - ds_b = 0 - for j in range(k, len(a_i)): - ds_b += a_i[j] - c = 0 - for j in range(min(len(a_i), k)): - c += a_i[j] * base[j] + ds_b = sum(a_i[j] for j in range(k, len(a_i))) + c = sum(a_i[j] * base[j] for j in range(min(len(a_i), k))) diff, dn = 0, 0 max_dn = n - i From cbbaa98684d6f66adf209f342933ba924e974ac6 Mon Sep 17 00:00:00 2001 From: Christian Clauss Date: Mon, 22 Jun 2020 14:18:57 +0200 Subject: [PATCH 0641/1071] hamming_code.py: Reduce McCabe code complexity (#2140) * hamming_code.py: Reduce McCabe code complexity As discussed in #2128 * fixup! Format Python code with psf/black push Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> --- hashes/hamming_code.py | 39 +++++++++++---------------------------- 1 file changed, 11 insertions(+), 28 deletions(-) diff --git a/hashes/hamming_code.py b/hashes/hamming_code.py index 14d23ef3cef4..4a32bae1a51c 100644 --- a/hashes/hamming_code.py +++ b/hashes/hamming_code.py @@ -131,10 +131,7 @@ def emitterConverter(sizePar, data): if x == "1": contBO += 1 contLoop += 1 - if contBO % 2 == 0: - parity.append(0) - else: - parity.append(1) + parity.append(contBO % 2) qtdBP += 1 @@ -168,12 +165,9 @@ def receptorConverter(sizePar, data): for x in range(1, len(data) + 1): # Performs a template of bit positions - who should be given, # and who should be parity - if qtdBP < sizePar: - if (np.log(x) / np.log(2)).is_integer(): - dataOutGab.append("P") - qtdBP = qtdBP + 1 - else: - dataOutGab.append("D") + if qtdBP < sizePar and (np.log(x) / np.log(2)).is_integer(): + dataOutGab.append("P") + qtdBP = qtdBP + 1 else: dataOutGab.append("D") @@ -201,12 +195,9 @@ def receptorConverter(sizePar, data): for x in range(1, sizePar + len(dataOutput) + 1): # Performs a template position of bits - who should be given, # and who should be parity - if qtdBP < sizePar: - if (np.log(x) / np.log(2)).is_integer(): - dataOutGab.append("P") - qtdBP = qtdBP + 1 - else: - dataOutGab.append("D") + if qtdBP < sizePar and (np.log(x) / np.log(2)).is_integer(): + dataOutGab.append("P") + qtdBP = qtdBP + 1 else: dataOutGab.append("D") @@ -230,14 +221,10 @@ def receptorConverter(sizePar, data): aux = (binPos[contLoop])[-1 * (bp)] except IndexError: aux = "0" - if aux == "1": - if x == "1": - contBO += 1 + if aux == "1" and x == "1": + contBO += 1 contLoop += 1 - if contBO % 2 == 0: - parity.append("0") - else: - parity.append("1") + parity.append(str(contBO % 2)) qtdBP += 1 @@ -250,11 +237,7 @@ def receptorConverter(sizePar, data): else: dataOut.append(dataOrd[x]) - if parityReceived == parity: - ack = True - else: - ack = False - + ack = parityReceived == parity return dataOutput, ack From f1ce2d6e80c0bd5242182170e5b4434177dcb444 Mon Sep 17 00:00:00 2001 From: Tapajyoti Bose <44058757+ruppysuppy@users.noreply.github.com> Date: Tue, 23 Jun 2020 16:26:08 +0530 Subject: [PATCH 0642/1071] Added Markov Chain (#2146) * Added Markov Chain * Implemented suggestions --- other/markov_chain.py | 82 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 82 insertions(+) create mode 100644 other/markov_chain.py diff --git a/other/markov_chain.py b/other/markov_chain.py new file mode 100644 index 000000000000..d5893d849471 --- /dev/null +++ b/other/markov_chain.py @@ -0,0 +1,82 @@ +from collections import Counter +from random import random +from typing import Dict, List, Tuple + + +class MarkovChainGraphUndirectedUnweighted: + ''' + Undirected Unweighted Graph for running Markov Chain Algorithm + ''' + + def __init__(self): + self.connections = {} + + def add_node(self, node: str) -> None: + self.connections[node] = {} + + def add_transition_probability(self, node1: str, + node2: str, + probability: float) -> None: + if node1 not in self.connections: + self.add_node(node1) + if node2 not in self.connections: + self.add_node(node2) + self.connections[node1][node2] = probability + + def get_nodes(self) -> List[str]: + return list(self.connections) + + def transition(self, node: str) -> str: + current_probability = 0 + random_value = random() + + for dest in self.connections[node]: + current_probability += self.connections[node][dest] + if current_probability > random_value: + return dest + + +def get_transitions(start: str, + transitions: List[Tuple[str, str, float]], + steps: int) -> Dict[str, int]: + ''' + Running Markov Chain algorithm and calculating the number of times each node is + visited + + >>> transitions = [ + ... ('a', 'a', 0.9), + ... ('a', 'b', 0.075), + ... ('a', 'c', 0.025), + ... ('b', 'a', 0.15), + ... ('b', 'b', 0.8), + ... ('b', 'c', 0.05), + ... ('c', 'a', 0.25), + ... ('c', 'b', 0.25), + ... ('c', 'c', 0.5) + ... ] + + >>> result = get_transitions('a', transitions, 5000) + + >>> result['a'] > result['b'] > result['c'] + True + ''' + + graph = MarkovChainGraphUndirectedUnweighted() + + for node1, node2, probability in transitions: + graph.add_transition_probability(node1, node2, probability) + + visited = Counter(graph.get_nodes()) + node = start + + for _ in range(steps): + node = graph.transition(node) + visited[node] += 1 + + return visited + + +if __name__ == "__main__": + import doctest + + doctest.testmod() From 5b6ebf8f12fa56f710c4d5fa254d069c0052f520 Mon Sep 17 00:00:00 2001 From: Christian Clauss Date: Tue, 23 Jun 2020 15:37:24 +0200 Subject: [PATCH 0643/1071] Add doctests to radix_sort() (#2148) * Add doctests to radix_sort() * fixup! Format Python code with psf/black push * Update radix_sort.py * updating DIRECTORY.md Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> --- DIRECTORY.md | 5 +++++ other/markov_chain.py | 20 ++++++++++---------- sorts/radix_sort.py | 33 ++++++++++++++++++--------------- 3 files changed, 33 insertions(+), 25 deletions(-) diff --git a/DIRECTORY.md b/DIRECTORY.md index 4ffd20da58de..984744ad7800 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -16,6 +16,7 @@ * [All Subsequences](https://github.com/TheAlgorithms/Python/blob/master/backtracking/all_subsequences.py) * [Coloring](https://github.com/TheAlgorithms/Python/blob/master/backtracking/coloring.py) * [Hamiltonian Cycle](https://github.com/TheAlgorithms/Python/blob/master/backtracking/hamiltonian_cycle.py) + * [Knight Tour](https://github.com/TheAlgorithms/Python/blob/master/backtracking/knight_tour.py) * [Minimax](https://github.com/TheAlgorithms/Python/blob/master/backtracking/minimax.py) * [N Queens](https://github.com/TheAlgorithms/Python/blob/master/backtracking/n_queens.py) * [Rat In Maze](https://github.com/TheAlgorithms/Python/blob/master/backtracking/rat_in_maze.py) @@ -71,6 +72,8 @@ ## Compression * [Burrows Wheeler](https://github.com/TheAlgorithms/Python/blob/master/compression/burrows_wheeler.py) * [Huffman](https://github.com/TheAlgorithms/Python/blob/master/compression/huffman.py) + * [Lempel Ziv](https://github.com/TheAlgorithms/Python/blob/master/compression/lempel_ziv.py) + * [Lempel Ziv Decompress](https://github.com/TheAlgorithms/Python/blob/master/compression/lempel_ziv_decompress.py) * [Peak Signal To Noise Ratio](https://github.com/TheAlgorithms/Python/blob/master/compression/peak_signal_to_noise_ratio.py) ## Computer Vision @@ -199,6 +202,7 @@ * [Longest Increasing Subsequence O(Nlogn)](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/longest_increasing_subsequence_o(nlogn).py) * [Longest Sub Array](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/longest_sub_array.py) * [Matrix Chain Order](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/matrix_chain_order.py) + * [Max Non Adjacent Sum](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/max_non_adjacent_sum.py) * [Max Sub Array](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/max_sub_array.py) * [Max Sum Contiguous Subsequence](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/max_sum_contiguous_subsequence.py) * [Minimum Partition](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/minimum_partition.py) @@ -440,6 +444,7 @@ * [Least Recently Used](https://github.com/TheAlgorithms/Python/blob/master/other/least_recently_used.py) * [Linear Congruential Generator](https://github.com/TheAlgorithms/Python/blob/master/other/linear_congruential_generator.py) * [Magicdiamondpattern](https://github.com/TheAlgorithms/Python/blob/master/other/magicdiamondpattern.py) + * [Markov Chain](https://github.com/TheAlgorithms/Python/blob/master/other/markov_chain.py) * [Nested Brackets](https://github.com/TheAlgorithms/Python/blob/master/other/nested_brackets.py) * [Palindrome](https://github.com/TheAlgorithms/Python/blob/master/other/palindrome.py) * [Password Generator](https://github.com/TheAlgorithms/Python/blob/master/other/password_generator.py) diff --git a/other/markov_chain.py b/other/markov_chain.py index d5893d849471..9b13fa515709 100644 --- a/other/markov_chain.py +++ b/other/markov_chain.py @@ -4,9 +4,9 @@ class MarkovChainGraphUndirectedUnweighted: - ''' + """ Undirected Unweighted Graph for running Markov Chain Algorithm - ''' + """ def __init__(self): self.connections = {} @@ -14,9 +14,9 @@ def __init__(self): def add_node(self, node: str) -> None: self.connections[node] = {} - def add_transition_probability(self, node1: str, - node2: str, - probability: float) -> None: + def add_transition_probability( + self, node1: str, node2: str, probability: float + ) -> None: if node1 not in self.connections: self.add_node(node1) if node2 not in self.connections: @@ -36,10 +36,10 @@ def transition(self, node: str) -> str: return dest -def get_transitions(start: str, - transitions: List[Tuple[str, str, float]], - steps: int) -> Dict[str, int]: - ''' +def get_transitions( + start: str, transitions: List[Tuple[str, str, float]], steps: int +) -> Dict[str, int]: + """ Running Markov Chain algorithm and calculating the number of times each node is visited @@ -59,7 +59,7 @@ def get_transitions(start: str, >>> result['a'] > result['b'] > result['c'] True - ''' + """ graph = MarkovChainGraphUndirectedUnweighted() diff --git a/sorts/radix_sort.py b/sorts/radix_sort.py index 2990247a0ac0..c379c679787f 100644 --- a/sorts/radix_sort.py +++ b/sorts/radix_sort.py @@ -1,26 +1,29 @@ -def radix_sort(lst): - RADIX = 10 - placement = 1 +from typing import List - # get the maximum number - max_digit = max(lst) +def radix_sort(list_of_ints: List[int]) -> List[int]: + """ + radix_sort(range(15)) == sorted(range(15)) + True + radix_sort(reversed(range(15))) == sorted(range(15)) + True + """ + RADIX = 10 + placement = 1 + max_digit = max(list_of_ints) while placement < max_digit: - # declare and initialize buckets + # declare and initialize empty buckets buckets = [list() for _ in range(RADIX)] - - # split lst between lists - for i in lst: + # split list_of_ints between the buckets + for i in list_of_ints: tmp = int((i / placement) % RADIX) buckets[tmp].append(i) - - # empty lists into lst array + # put each buckets' contents into list_of_ints a = 0 for b in range(RADIX): - buck = buckets[b] - for i in buck: - lst[a] = i + for i in buckets[b]: + list_of_ints[a] = i a += 1 - # move to next placement *= RADIX + return list_of_ints From 9e2206e5fb5d8570ee1d8b357df38ab61da92672 Mon Sep 17 00:00:00 2001 From: mateuszz0000 Date: Thu, 25 Jun 2020 08:56:57 +0200 Subject: [PATCH 0644/1071] Added doctests to OddEvenTraposition (#2152) * Added doctests * Change __main__ content --- .../odd_even_transposition_single_threaded.py | 29 ++++++++----------- 1 file changed, 12 insertions(+), 17 deletions(-) diff --git a/sorts/odd_even_transposition_single_threaded.py b/sorts/odd_even_transposition_single_threaded.py index ec045d9dd08d..f776dc8d7cd4 100644 --- a/sorts/odd_even_transposition_single_threaded.py +++ b/sorts/odd_even_transposition_single_threaded.py @@ -7,29 +7,24 @@ def OddEvenTransposition(arr): + """ + >>> OddEvenTransposition([5, 4, 3, 2, 1]) + [1, 2, 3, 4, 5] + + >>> OddEvenTransposition([13, 11, 18, 0, -1]) + [-1, 0, 11, 13, 18] + + >>> OddEvenTransposition([-.1, 1.1, .1, -2.9]) + [-2.9, -0.1, 0.1, 1.1] + """ for i in range(0, len(arr)): for i in range(i % 2, len(arr) - 1, 2): if arr[i + 1] < arr[i]: arr[i], arr[i + 1] = arr[i + 1], arr[i] - print(*arr) return arr -# creates a list and sorts it -def main(): - list = [] - - for i in range(10, 0, -1): - list.append(i) - print("Initial List") - print(*list) - - list = OddEvenTransposition(list) - - print("Sorted List\n") - print(*list) - - if __name__ == "__main__": - main() + arr = list(range(10, 0, -1)) + print(f"Original: {arr}. Sorted: {OddEvenTransposition(arr)}") From b0c3c0fbf6820a7f44b2709c4a7896b08b9aadaa Mon Sep 17 00:00:00 2001 From: mateuszz0000 Date: Thu, 25 Jun 2020 09:48:52 +0200 Subject: [PATCH 0645/1071] Typehints + refactor (#2154) --- sorts/odd_even_transposition_single_threaded.py | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/sorts/odd_even_transposition_single_threaded.py b/sorts/odd_even_transposition_single_threaded.py index f776dc8d7cd4..fe06459e8dd1 100644 --- a/sorts/odd_even_transposition_single_threaded.py +++ b/sorts/odd_even_transposition_single_threaded.py @@ -1,4 +1,6 @@ """ +Source: https://en.wikipedia.org/wiki/Odd%E2%80%93even_sort + This is a non-parallelized implementation of odd-even transpostiion sort. Normally the swaps in each set happen simultaneously, without that the algorithm @@ -6,19 +8,20 @@ """ -def OddEvenTransposition(arr): +def odd_even_transposition(arr: list) -> list: """ - >>> OddEvenTransposition([5, 4, 3, 2, 1]) + >>> odd_even_transposition([5, 4, 3, 2, 1]) [1, 2, 3, 4, 5] - >>> OddEvenTransposition([13, 11, 18, 0, -1]) + >>> odd_even_transposition([13, 11, 18, 0, -1]) [-1, 0, 11, 13, 18] - >>> OddEvenTransposition([-.1, 1.1, .1, -2.9]) + >>> odd_even_transposition([-.1, 1.1, .1, -2.9]) [-2.9, -0.1, 0.1, 1.1] """ - for i in range(0, len(arr)): - for i in range(i % 2, len(arr) - 1, 2): + arr_size = len(arr) + for _ in range(arr_size): + for i in range(_ % 2, arr_size - 1, 2): if arr[i + 1] < arr[i]: arr[i], arr[i + 1] = arr[i + 1], arr[i] @@ -27,4 +30,4 @@ def OddEvenTransposition(arr): if __name__ == "__main__": arr = list(range(10, 0, -1)) - print(f"Original: {arr}. Sorted: {OddEvenTransposition(arr)}") + print(f"Original: {arr}. Sorted: {odd_even_transposition(arr)}") From c7ca9cf0df7fe257310b2f2e81083782a947882d Mon Sep 17 00:00:00 2001 From: Markgolzh <1134386961@qq.com> Date: Thu, 25 Jun 2020 02:55:13 -0500 Subject: [PATCH 0646/1071] Update avl_tree.py (#2145) * Update avl_tree.py it's true definition of AVL tree,change left and right rotation,and add avl_tree doctest * Update avl_tree.py * Update data_structures/binary_tree/avl_tree.py Co-authored-by: Christian Clauss * Update data_structures/binary_tree/avl_tree.py Co-authored-by: Christian Clauss * Update data_structures/binary_tree/avl_tree.py Co-authored-by: Christian Clauss * Update data_structures/binary_tree/avl_tree.py Co-authored-by: Christian Clauss * Update data_structures/binary_tree/avl_tree.py Co-authored-by: Christian Clauss * Update data_structures/binary_tree/avl_tree.py Co-authored-by: Christian Clauss * Update data_structures/binary_tree/avl_tree.py Co-authored-by: Christian Clauss * Update data_structures/binary_tree/avl_tree.py Co-authored-by: Christian Clauss * Update data_structures/binary_tree/avl_tree.py Co-authored-by: Christian Clauss * Update data_structures/binary_tree/avl_tree.py Co-authored-by: Christian Clauss * Update data_structures/binary_tree/avl_tree.py Co-authored-by: Christian Clauss * Update avl_tree.py update some function name and update doctest * Update avl_tree.py change some code format to fit flake8 review Co-authored-by: Christian Clauss --- data_structures/binary_tree/avl_tree.py | 260 ++++++++++++++---------- 1 file changed, 148 insertions(+), 112 deletions(-) diff --git a/data_structures/binary_tree/avl_tree.py b/data_structures/binary_tree/avl_tree.py index cb043cf188b7..71dede2ccacc 100644 --- a/data_structures/binary_tree/avl_tree.py +++ b/data_structures/binary_tree/avl_tree.py @@ -1,6 +1,11 @@ """ -An auto-balanced binary tree! +Implementation of an auto-balanced binary tree! +For doctests run following command: +python3 -m doctest -v avl_tree.py +For testing run: +python avl_tree.py """ + import math import random @@ -11,7 +16,7 @@ def __init__(self): self.head = 0 self.tail = 0 - def isEmpty(self): + def is_empty(self): return self.head == self.tail def push(self, data): @@ -39,39 +44,39 @@ def __init__(self, data): self.right = None self.height = 1 - def getdata(self): + def get_data(self): return self.data - def getleft(self): + def get_left(self): return self.left - def getright(self): + def get_right(self): return self.right - def getheight(self): + def get_height(self): return self.height - def setdata(self, data): + def set_data(self, data): self.data = data return - def setleft(self, node): + def set_left(self, node): self.left = node return - def setright(self, node): + def set_right(self, node): self.right = node return - def setheight(self, height): + def set_height(self, height): self.height = height return -def getheight(node): +def get_height(node): if node is None: return 0 - return node.getheight() + return node.get_height() def my_max(a, b): @@ -80,7 +85,7 @@ def my_max(a, b): return b -def leftrotation(node): +def right_rotation(node): r""" A B / \ / \ @@ -89,138 +94,171 @@ def leftrotation(node): Bl Br UB Br C / UB - UB = unbalanced node """ - print("left rotation node:", node.getdata()) - ret = node.getleft() - node.setleft(ret.getright()) - ret.setright(node) - h1 = my_max(getheight(node.getright()), getheight(node.getleft())) + 1 - node.setheight(h1) - h2 = my_max(getheight(ret.getright()), getheight(ret.getleft())) + 1 - ret.setheight(h2) + print("left rotation node:", node.get_data()) + ret = node.get_left() + node.set_left(ret.get_right()) + ret.set_right(node) + h1 = my_max(get_height(node.get_right()), get_height(node.get_left())) + 1 + node.set_height(h1) + h2 = my_max(get_height(ret.get_right()), get_height(ret.get_left())) + 1 + ret.set_height(h2) return ret -def rightrotation(node): +def left_rotation(node): """ - a mirror symmetry rotation of the leftrotation + a mirror symmetry rotation of the left_rotation """ - print("right rotation node:", node.getdata()) - ret = node.getright() - node.setright(ret.getleft()) - ret.setleft(node) - h1 = my_max(getheight(node.getright()), getheight(node.getleft())) + 1 - node.setheight(h1) - h2 = my_max(getheight(ret.getright()), getheight(ret.getleft())) + 1 - ret.setheight(h2) + print("right rotation node:", node.get_data()) + ret = node.get_right() + node.set_right(ret.get_left()) + ret.set_left(node) + h1 = my_max(get_height(node.get_right()), get_height(node.get_left())) + 1 + node.set_height(h1) + h2 = my_max(get_height(ret.get_right()), get_height(ret.get_left())) + 1 + ret.set_height(h2) return ret -def rlrotation(node): +def lr_rotation(node): r""" A A Br / \ / \ / \ - B C RR Br C LR B A + B C LR Br C RR B A / \ --> / \ --> / / \ Bl Br B UB Bl UB C \ / UB Bl - RR = rightrotation LR = leftrotation + RR = right_rotation LR = left_rotation """ - node.setleft(rightrotation(node.getleft())) - return leftrotation(node) + node.set_left(left_rotation(node.get_left())) + return right_rotation(node) -def lrrotation(node): - node.setright(leftrotation(node.getright())) - return rightrotation(node) +def rl_rotation(node): + node.set_right(right_rotation(node.get_right())) + return left_rotation(node) def insert_node(node, data): if node is None: return my_node(data) - if data < node.getdata(): - node.setleft(insert_node(node.getleft(), data)) + if data < node.get_data(): + node.set_left(insert_node(node.get_left(), data)) if ( - getheight(node.getleft()) - getheight(node.getright()) == 2 + get_height(node.get_left()) - get_height(node.get_right()) == 2 ): # an unbalance detected if ( - data < node.getleft().getdata() + data < node.get_left().get_data() ): # new node is the left child of the left child - node = leftrotation(node) + node = right_rotation(node) else: - node = rlrotation(node) # new node is the right child of the left child + node = lr_rotation(node) else: - node.setright(insert_node(node.getright(), data)) - if getheight(node.getright()) - getheight(node.getleft()) == 2: - if data < node.getright().getdata(): - node = lrrotation(node) + node.set_right(insert_node(node.get_right(), data)) + if get_height(node.get_right()) - get_height(node.get_left()) == 2: + if data < node.get_right().get_data(): + node = rl_rotation(node) else: - node = rightrotation(node) - h1 = my_max(getheight(node.getright()), getheight(node.getleft())) + 1 - node.setheight(h1) + node = left_rotation(node) + h1 = my_max(get_height(node.get_right()), get_height(node.get_left())) + 1 + node.set_height(h1) return node -def getRightMost(root): - while root.getright() is not None: - root = root.getright() - return root.getdata() +def get_rightMost(root): + while root.get_right() is not None: + root = root.get_right() + return root.get_data() -def getLeftMost(root): - while root.getleft() is not None: - root = root.getleft() - return root.getdata() +def get_leftMost(root): + while root.get_left() is not None: + root = root.get_left() + return root.get_data() def del_node(root, data): - if root.getdata() == data: - if root.getleft() is not None and root.getright() is not None: - temp_data = getLeftMost(root.getright()) - root.setdata(temp_data) - root.setright(del_node(root.getright(), temp_data)) - elif root.getleft() is not None: - root = root.getleft() + if root.get_data() == data: + if root.get_left() is not None and root.get_right() is not None: + temp_data = get_leftMost(root.get_right()) + root.set_data(temp_data) + root.set_right(del_node(root.get_right(), temp_data)) + elif root.get_left() is not None: + root = root.get_left() else: - root = root.getright() - elif root.getdata() > data: - if root.getleft() is None: + root = root.get_right() + elif root.get_data() > data: + if root.get_left() is None: print("No such data") return root else: - root.setleft(del_node(root.getleft(), data)) - elif root.getdata() < data: - if root.getright() is None: + root.set_left(del_node(root.get_left(), data)) + elif root.get_data() < data: + if root.get_right() is None: return root else: - root.setright(del_node(root.getright(), data)) + root.set_right(del_node(root.get_right(), data)) if root is None: return root - if getheight(root.getright()) - getheight(root.getleft()) == 2: - if getheight(root.getright().getright()) > getheight(root.getright().getleft()): - root = rightrotation(root) + if get_height(root.get_right()) - get_height(root.get_left()) == 2: + if get_height(root.get_right().get_right()) > \ + get_height(root.get_right().get_left()): + root = left_rotation(root) else: - root = lrrotation(root) - elif getheight(root.getright()) - getheight(root.getleft()) == -2: - if getheight(root.getleft().getleft()) > getheight(root.getleft().getright()): - root = leftrotation(root) + root = rl_rotation(root) + elif get_height(root.get_right()) - get_height(root.get_left()) == -2: + if get_height(root.get_left().get_left()) > \ + get_height(root.get_left().get_right()): + root = right_rotation(root) else: - root = rlrotation(root) - height = my_max(getheight(root.getright()), getheight(root.getleft())) + 1 - root.setheight(height) + root = lr_rotation(root) + height = my_max(get_height(root.get_right()), get_height(root.get_left())) + 1 + root.set_height(height) return root class AVLtree: + """ + An AVL tree doctest + Examples: + >>> t = AVLtree() + >>> t.insert(4) + insert:4 + >>> print(str(t).replace(" \\n","\\n")) + 4 + ************************************* + >>> t.insert(2) + insert:2 + >>> print(str(t).replace(" \\n","\\n").replace(" \\n","\\n")) + 4 + 2 * + ************************************* + >>> t.insert(3) + insert:3 + right rotation node: 2 + left rotation node: 4 + >>> print(str(t).replace(" \\n","\\n").replace(" \\n","\\n")) + 3 + 2 4 + ************************************* + >>> t.get_height() + 2 + >>> t.del_node(3) + delete:3 + >>> print(str(t).replace(" \\n","\\n").replace(" \\n","\\n")) + 4 + 2 * + ************************************* + """ def __init__(self): self.root = None - def getheight(self): + def get_height(self): # print("yyy") - return getheight(self.root) + return get_height(self.root) def insert(self, data): print("insert:" + str(data)) @@ -233,56 +271,54 @@ def del_node(self, data): return self.root = del_node(self.root, data) - def traversale(self): # a level traversale, gives a more intuitive look on the tree + def __str__(self): # a level traversale, gives a more intuitive look on the tree + output = "" q = my_queue() q.push(self.root) - layer = self.getheight() + layer = self.get_height() if layer == 0: - return + return output cnt = 0 - while not q.isEmpty(): + while not q.is_empty(): node = q.pop() space = " " * int(math.pow(2, layer - 1)) - print(space, end="") + output += space if node is None: - print("*", end="") + output += "*" q.push(None) q.push(None) else: - print(node.getdata(), end="") - q.push(node.getleft()) - q.push(node.getright()) - print(space, end="") + output += str(node.get_data()) + q.push(node.get_left()) + q.push(node.get_right()) + output += space cnt = cnt + 1 for i in range(100): if cnt == math.pow(2, i) - 1: layer = layer - 1 if layer == 0: - print() - print("*************************************") - return - print() + output += "\n*************************************" + return output + output += "\n" break - print() - print("*************************************") - return + output += "\n*************************************" + return output - def test(self): - getheight(None) - print("****") - self.getheight() + +def _test(): + import doctest + doctest.testmod() if __name__ == "__main__": + _test() t = AVLtree() - t.traversale() lst = list(range(10)) random.shuffle(lst) for i in lst: t.insert(i) - t.traversale() - + print(str(t)) random.shuffle(lst) for i in lst: t.del_node(i) - t.traversale() + print(str(t)) From b368b1ecfd870da333974a1650116d66ffbfa121 Mon Sep 17 00:00:00 2001 From: Dan Murphy Date: Thu, 25 Jun 2020 04:00:43 -0400 Subject: [PATCH 0647/1071] NLP Word Frequency Algorithms (#2142) * NLP Word Frequency Algorithms * Added type hints and Wikipedia link to tf-idf * Update machine_learning/word_frequency_functions.py Co-authored-by: Christian Clauss * Update machine_learning/word_frequency_functions.py Co-authored-by: Christian Clauss * Update machine_learning/word_frequency_functions.py Co-authored-by: Christian Clauss * Update machine_learning/word_frequency_functions.py Co-authored-by: Christian Clauss * Fix line length for flake8 * Fix line length for flake8 V2 * Add line escapes and change int to float * Corrected doctests * Fix for TravisCI * Fix for TravisCI V2 * Tests passing locally * Tests passing locally * Update machine_learning/word_frequency_functions.py Co-authored-by: Christian Clauss * Update machine_learning/word_frequency_functions.py Co-authored-by: Christian Clauss * Update machine_learning/word_frequency_functions.py Co-authored-by: Christian Clauss * Update machine_learning/word_frequency_functions.py Co-authored-by: Christian Clauss * Add doctest examples and clean up docstrings Co-authored-by: Christian Clauss --- machine_learning/word_frequency_functions.py | 133 +++++++++++++++++++ 1 file changed, 133 insertions(+) create mode 100644 machine_learning/word_frequency_functions.py diff --git a/machine_learning/word_frequency_functions.py b/machine_learning/word_frequency_functions.py new file mode 100644 index 000000000000..a105e30f5d3b --- /dev/null +++ b/machine_learning/word_frequency_functions.py @@ -0,0 +1,133 @@ +import string +from math import log10 + +""" + tf-idf Wikipedia: https://en.wikipedia.org/wiki/Tf%E2%80%93idf + tf-idf and other word frequency algorithms are often used + as a weighting factor in information retrieval and text + mining. 83% of text-based recommender systems use + tf-idf for term weighting. In Layman's terms, tf-idf + is a statistic intended to reflect how important a word + is to a document in a corpus (a collection of documents) + + + Here I've implemented several word frequency algorithms + that are commonly used in information retrieval: Term Frequency, + Document Frequency, and TF-IDF (Term-Frequency*Inverse-Document-Frequency) + are included. + + Term Frequency is a statistical function that + returns a number representing how frequently + an expression occurs in a document. This + indicates how significant a particular term is in + a given document. + + Document Frequency is a statistical function that returns + an integer representing the number of documents in a + corpus that a term occurs in (where the max number returned + would be the number of documents in the corpus). + + Inverse Document Frequency is mathematically written as + log10(N/df), where N is the number of documents in your + corpus and df is the Document Frequency. If df is 0, a + ZeroDivisionError will be thrown. + + Term-Frequency*Inverse-Document-Frequency is a measure + of the originality of a term. It is mathematically written + as tf*log10(N/df). It compares the number of times + a term appears in a document with the number of documents + the term appears in. If df is 0, a ZeroDivisionError will be thrown. +""" + + +def term_frequency(term : str, document : str) -> int: + """ + Return the number of times a term occurs within + a given document. + @params: term, the term to search a document for, and document, + the document to search within + @returns: an integer representing the number of times a term is + found within the document + + @examples: + >>> term_frequency("to", "To be, or not to be") + 2 + """ + # strip all punctuation and newlines and replace it with '' + document_without_punctuation = document.translate( + str.maketrans("", "", string.punctuation) + ).replace("\n", "") + tokenize_document = document_without_punctuation.split(" ") # word tokenization + return len( + [word for word in tokenize_document if word.lower() == term.lower()] + ) + + +def document_frequency(term: str, corpus: str) -> int: + """ + Calculate the number of documents in a corpus that contain a + given term + @params : term, the term to search each document for, and corpus, a collection of + documents. Each document should be separated by a newline. + @returns : the number of documents in the corpus that contain the term you are + searching for and the number of documents in the corpus + @examples : + >>> document_frequency("first", "This is the first document in the corpus.\\nThIs\ +is the second document in the corpus.\\nTHIS is \ +the third document in the corpus.") + (1, 3) + """ + corpus_without_punctuation = corpus.translate( + str.maketrans("", "", string.punctuation) + ) # strip all punctuation and replace it with '' + documents = corpus_without_punctuation.split("\n") + lowercase_documents = [document.lower() for document in documents] + return len( + [document for document in lowercase_documents if term.lower() in document] + ), len(documents) + + +def inverse_document_frequency(df : int, N: int) -> float: + """ + Return an integer denoting the importance + of a word. This measure of importance is + calculated by log10(N/df), where N is the + number of documents and df is + the Document Frequency. + @params : df, the Document Frequency, and N, + the number of documents in the corpus. + @returns : log10(N/df) + @examples : + >>> inverse_document_frequency(3, 0) + Traceback (most recent call last): + ... + ValueError: log10(0) is undefined. + >>> inverse_document_frequency(1, 3) + 0.477 + >>> inverse_document_frequency(0, 3) + Traceback (most recent call last): + ... + ZeroDivisionError: df must be > 0 + """ + if df == 0: + raise ZeroDivisionError("df must be > 0") + elif N == 0: + raise ValueError("log10(0) is undefined.") + return round(log10(N / df), 3) + + +def tf_idf(tf : int, idf: int) -> float: + """ + Combine the term frequency + and inverse document frequency functions to + calculate the originality of a term. This + 'originality' is calculated by multiplying + the term frequency and the inverse document + frequency : tf-idf = TF * IDF + @params : tf, the term frequency, and idf, the inverse document + frequency + @examples : + >>> tf_idf(2, 0.477) + 0.954 + """ + return round(tf * idf, 3) From 27dde06dfa86d00d82522ed1d72fcfe9cf11a44e Mon Sep 17 00:00:00 2001 From: Tapajyoti Bose <44058757+ruppysuppy@users.noreply.github.com> Date: Thu, 25 Jun 2020 15:10:03 +0530 Subject: [PATCH 0648/1071] Added LRU Cache (#2138) * Added LRU Cache * Optimized the program * Added Cache as Decorator + Implemented suggestions * Implemented suggestions --- other/lru_cache.py | 192 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 192 insertions(+) create mode 100644 other/lru_cache.py diff --git a/other/lru_cache.py b/other/lru_cache.py new file mode 100644 index 000000000000..7b5d16be66e5 --- /dev/null +++ b/other/lru_cache.py @@ -0,0 +1,192 @@ +from typing import Callable, Optional + + +class DoubleLinkedListNode: + ''' + Double Linked List Node built specifically for LRU Cache + ''' + + def __init__(self, key: int, val: int): + self.key = key + self.val = val + self.next = None + self.prev = None + + +class DoubleLinkedList: + ''' + Double Linked List built specifically for LRU Cache + ''' + + def __init__(self): + self.head = DoubleLinkedListNode(None, None) + self.rear = DoubleLinkedListNode(None, None) + self.head.next, self.rear.prev = self.rear, self.head + + def add(self, node: DoubleLinkedListNode) -> None: + ''' + Adds the given node to the end of the list (before rear) + ''' + + temp = self.rear.prev + temp.next, node.prev = node, temp + self.rear.prev, node.next = node, self.rear + + def remove(self, node: DoubleLinkedListNode) -> DoubleLinkedListNode: + ''' + Removes and returns the given node from the list + ''' + + temp_last, temp_next = node.prev, node.next + node.prev, node.next = None, None + temp_last.next, temp_next.prev = temp_next, temp_last + + return node + + +class LRUCache: + ''' + LRU Cache to store a given capacity of data. Can be used as a stand-alone object + or as a function decorator. + + >>> cache = LRUCache(2) + + >>> cache.set(1, 1) + + >>> cache.set(2, 2) + + >>> cache.get(1) + 1 + + >>> cache.set(3, 3) + + >>> cache.get(2) # None returned + + >>> cache.set(4, 4) + + >>> cache.get(1) # None returned + + >>> cache.get(3) + 3 + + >>> cache.get(4) + 4 + + >>> cache + CacheInfo(hits=3, misses=2, capacity=2, current size=2) + + >>> @LRUCache.decorator(100) + ... def fib(num): + ... if num in (1, 2): + ... return 1 + ... return fib(num - 1) + fib(num - 2) + + >>> for i in range(1, 100): + ... res = fib(i) + + >>> fib.cache_info() + CacheInfo(hits=194, misses=99, capacity=100, current size=99) + ''' + + # class variable to map the decorator functions to their respective instance + decorator_function_to_instance_map = {} + + def __init__(self, capacity: int): + self.list = DoubleLinkedList() + self.capacity = capacity + self.num_keys = 0 + self.hits = 0 + self.miss = 0 + self.cache = {} + + def __repr__(self) -> str: + ''' + Return the details for the cache instance + [hits, misses, capacity, current_size] + ''' + + return (f'CacheInfo(hits={self.hits}, misses={self.miss}, ' + f'capacity={self.capacity}, current size={self.num_keys})') + + def __contains__(self, key: int) -> bool: + ''' + >>> cache = LRUCache(1) + + >>> 1 in cache + False + + >>> cache.set(1, 1) + + >>> 1 in cache + True + ''' + + return key in self.cache + + def get(self, key: int) -> Optional[int]: + ''' + Returns the value for the input key and updates the Double Linked List. Returns + None if key is not present in cache + ''' + + if key in self.cache: + self.hits += 1 + self.list.add(self.list.remove(self.cache[key])) + return self.cache[key].val + self.miss += 1 + return None + + def set(self, key: int, value: int) -> None: + ''' + Sets the value for the input key and updates the Double Linked List + ''' + + if key not in self.cache: + if self.num_keys >= self.capacity: + key_to_delete = self.list.head.next.key + self.list.remove(self.cache[key_to_delete]) + del self.cache[key_to_delete] + self.num_keys -= 1 + self.cache[key] = DoubleLinkedListNode(key, value) + self.list.add(self.cache[key]) + self.num_keys += 1 + + else: + node = self.list.remove(self.cache[key]) + node.val = value + self.list.add(node) + + @staticmethod + def decorator(size: int = 128): + ''' + Decorator version of LRU Cache + ''' + + def cache_decorator_inner(func: Callable): + + def cache_decorator_wrapper(*args, **kwargs): + if func not in LRUCache.decorator_function_to_instance_map: + LRUCache.decorator_function_to_instance_map[func] = LRUCache(size) + + result = LRUCache.decorator_function_to_instance_map[func].get(args[0]) + if result is None: + result = func(*args, **kwargs) + LRUCache.decorator_function_to_instance_map[func].set( + args[0], result + ) + return result + + def cache_info(): + return LRUCache.decorator_function_to_instance_map[func] + + cache_decorator_wrapper.cache_info = cache_info + + return cache_decorator_wrapper + + return cache_decorator_inner + + +if __name__ == "__main__": + import doctest + + doctest.testmod() From 9eb3138b817854d37e1e48991f9174714ca04f33 Mon Sep 17 00:00:00 2001 From: Tapajyoti Bose <44058757+ruppysuppy@users.noreply.github.com> Date: Thu, 25 Jun 2020 15:10:50 +0530 Subject: [PATCH 0649/1071] Added LFU Cache (#2151) * Added LFU Cache * Update lfu_cache.py * None is returned * Add type hints Co-authored-by: Christian Clauss --- other/lfu_cache.py | 186 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 186 insertions(+) create mode 100644 other/lfu_cache.py diff --git a/other/lfu_cache.py b/other/lfu_cache.py new file mode 100644 index 000000000000..0f128646d7a2 --- /dev/null +++ b/other/lfu_cache.py @@ -0,0 +1,186 @@ +from typing import Callable, Optional + + +class DoubleLinkedListNode: + ''' + Double Linked List Node built specifically for LFU Cache + ''' + + def __init__(self, key: int, val: int): + self.key = key + self.val = val + self.freq = 0 + self.next = None + self.prev = None + + +class DoubleLinkedList: + ''' + Double Linked List built specifically for LFU Cache + ''' + + def __init__(self): + self.head = DoubleLinkedListNode(None, None) + self.rear = DoubleLinkedListNode(None, None) + self.head.next, self.rear.prev = self.rear, self.head + + def add(self, node: DoubleLinkedListNode) -> None: + ''' + Adds the given node at the head of the list and shifting it to proper position + ''' + + temp = self.rear.prev + + self.rear.prev, node.next = node, self.rear + temp.next, node.prev = node, temp + node.freq += 1 + self._position_node(node) + + def _position_node(self, node: DoubleLinkedListNode) -> None: + while node.prev.key and node.prev.freq > node.freq: + node1, node2 = node, node.prev + node1.prev, node2.next = node2.prev, node1.prev + node1.next, node2.prev = node2, node1 + + def remove(self, node: DoubleLinkedListNode) -> DoubleLinkedListNode: + ''' + Removes and returns the given node from the list + ''' + + temp_last, temp_next = node.prev, node.next + node.prev, node.next = None, None + temp_last.next, temp_next.prev = temp_next, temp_last + return node + + +class LFUCache: + ''' + LFU Cache to store a given capacity of data. Can be used as a stand-alone object + or as a function decorator. + + >>> cache = LFUCache(2) + >>> cache.set(1, 1) + >>> cache.set(2, 2) + >>> cache.get(1) + 1 + >>> cache.set(3, 3) + >>> cache.get(2) # None is returned + >>> cache.set(4, 4) + >>> cache.get(1) # None is returned + >>> cache.get(3) + 3 + >>> cache.get(4) + 4 + >>> cache + CacheInfo(hits=3, misses=2, capacity=2, current size=2) + >>> @LFUCache.decorator(100) + ... def fib(num): + ... if num in (1, 2): + ... return 1 + ... return fib(num - 1) + fib(num - 2) + + >>> for i in range(1, 101): + ... res = fib(i) + + >>> fib.cache_info() + CacheInfo(hits=196, misses=100, capacity=100, current size=100) + ''' + + # class variable to map the decorator functions to their respective instance + decorator_function_to_instance_map = {} + + def __init__(self, capacity: int): + self.list = DoubleLinkedList() + self.capacity = capacity + self.num_keys = 0 + self.hits = 0 + self.miss = 0 + self.cache = {} + + def __repr__(self) -> str: + ''' + Return the details for the cache instance + [hits, misses, capacity, current_size] + ''' + + return (f'CacheInfo(hits={self.hits}, misses={self.miss}, ' + f'capacity={self.capacity}, current size={self.num_keys})') + + def __contains__(self, key: int) -> bool: + ''' + >>> cache = LFUCache(1) + >>> 1 in cache + False + >>> cache.set(1, 1) + >>> 1 in cache + True + ''' + return key in self.cache + + def get(self, key: int) -> Optional[int]: + ''' + Returns the value for the input key and updates the Double Linked List. Returns + None if key is not present in cache + ''' + + if key in self.cache: + self.hits += 1 + self.list.add(self.list.remove(self.cache[key])) + return self.cache[key].val + self.miss += 1 + return None + + def set(self, key: int, value: int) -> None: + ''' + Sets the value for the input key and updates the Double Linked List + ''' + + if key not in self.cache: + if self.num_keys >= self.capacity: + key_to_delete = self.list.head.next.key + self.list.remove(self.cache[key_to_delete]) + del self.cache[key_to_delete] + self.num_keys -= 1 + self.cache[key] = DoubleLinkedListNode(key, value) + self.list.add(self.cache[key]) + self.num_keys += 1 + + else: + node = self.list.remove(self.cache[key]) + node.val = value + self.list.add(node) + + @staticmethod + def decorator(size: int = 128): + ''' + Decorator version of LFU Cache + ''' + + def cache_decorator_inner(func: Callable): + + def cache_decorator_wrapper(*args, **kwargs): + if func not in LFUCache.decorator_function_to_instance_map: + LFUCache.decorator_function_to_instance_map[func] = LFUCache(size) + + result = LFUCache.decorator_function_to_instance_map[func].get(args[0]) + if result is None: + result = func(*args, **kwargs) + LFUCache.decorator_function_to_instance_map[func].set( + args[0], result + ) + return result + + def cache_info(): + return LFUCache.decorator_function_to_instance_map[func] + + cache_decorator_wrapper.cache_info = cache_info + + return cache_decorator_wrapper + + return cache_decorator_inner + + +if __name__ == "__main__": + import doctest + + doctest.testmod() From 3d4172307f9a99217744c9fb1f40b87a3b5d8285 Mon Sep 17 00:00:00 2001 From: Mark Moretto Date: Thu, 25 Jun 2020 06:25:19 -0400 Subject: [PATCH 0650/1071] project_euler/problem_47/sol1.py (#2150) * Create __init__.py * Initial commit Not sure if this should be formatted differently. I'm open to ideas! * Completing testing/updates Ran code through `black`, `flake8`, and `doctest`. Added some type hints. `doctest` is finicky on sets, so I had to sort and reformat as set to pass those tests. * Update project_euler/problem_47/sol1.py Nice. Co-authored-by: Christian Clauss * Update project_euler/problem_47/sol1.py Looks good Co-authored-by: Christian Clauss * Update project_euler/problem_47/sol1.py Okay, this should work. Thank you for the reminder on map(), filter(), reduce(). Co-authored-by: Christian Clauss * Update project_euler/problem_47/sol1.py My IDE needs a spellchecker. Or, lighter comment font. Co-authored-by: Christian Clauss * Update project_euler/problem_47/sol1.py Co-authored-by: Christian Clauss * Update project_euler/problem_47/sol1.py Co-authored-by: Christian Clauss * Update project_euler/problem_47/sol1.py This means that `results = run(N)` should be updated to `results = run(n)`, correct? Co-authored-by: Christian Clauss * Update project_euler/problem_47/sol1.py Co-authored-by: Christian Clauss * Update project_euler/problem_47/sol1.py Looks good! Co-authored-by: Christian Clauss * Update project_euler/problem_47/sol1.py Co-authored-by: Christian Clauss * Update project_euler/problem_47/sol1.py Co-authored-by: Christian Clauss * Update project_euler/problem_47/sol1.py Works for me! I spent way too much time getting this to pass doctest, so any improvement is welcome. Co-authored-by: Christian Clauss * Update sol1.py Added some suggested changes from the pull request: * Updated tests outputs in `unique_prime_factors` function. * Changed `@lru_cache(maxsize=5)` to `@lru_cache(maxsize=None)` * Removed duplicate `return` line in `equality` function * Changed `i` to `base` in run function. * Added some commentary to `run()` function. * Replaced `group = list(map(lambda x: base + x, [i for i in range(n)]))` with `group = [base + i for i in range(n)]` * Update sol1.py * Trailing whitespace * Update sol1.py * Update __init__.py * Update sol1.py * Update __init__.py Co-authored-by: Christian Clauss --- project_euler/problem_47/__init__.py | 1 + project_euler/problem_47/sol1.py | 112 +++++++++++++++++++++++++++ 2 files changed, 113 insertions(+) create mode 100644 project_euler/problem_47/__init__.py create mode 100644 project_euler/problem_47/sol1.py diff --git a/project_euler/problem_47/__init__.py b/project_euler/problem_47/__init__.py new file mode 100644 index 000000000000..8b137891791f --- /dev/null +++ b/project_euler/problem_47/__init__.py @@ -0,0 +1 @@ + diff --git a/project_euler/problem_47/sol1.py b/project_euler/problem_47/sol1.py new file mode 100644 index 000000000000..fab8ffde9052 --- /dev/null +++ b/project_euler/problem_47/sol1.py @@ -0,0 +1,112 @@ +""" +Combinatoric selections + +Problem 47 + +The first two consecutive numbers to have two distinct prime factors are: + +14 = 2 × 7 +15 = 3 × 5 + +The first three consecutive numbers to have three distinct prime factors are: + +644 = 2² × 7 × 23 +645 = 3 × 5 × 43 +646 = 2 × 17 × 19. + +Find the first four consecutive integers to have four distinct prime factors each. +What is the first of these numbers? +""" + +from functools import lru_cache + + +def unique_prime_factors(n: int) -> set: + """ + Find unique prime factors of an integer. + Tests include sorting because only the set really matters, + not the order in which it is produced. + >>> sorted(set(unique_prime_factors(14))) + [2, 7] + >>> set(sorted(unique_prime_factors(644))) + [2, 7, 23] + >>> set(sorted(unique_prime_factors(646))) + [2, 17, 19] + """ + i = 2 + factors = set() + while i * i <= n: + if n % i: + i += 1 + else: + n //= i + factors.add(i) + if n > 1: + factors.add(n) + return factors + + +@lru_cache +def upf_len(num: int) -> int: + """ + Memoize upf() length results for a given value. + >>> upf_len(14) + 2 + """ + return len(unique_prime_factors(num)) + + +def equality(iterable: list) -> bool: + """ + Check equality of ALL elements in an interable. + >>> equality([1, 2, 3, 4]) + False + >>> equality([2, 2, 2, 2]) + True + >>> equality([1, 2, 3, 2, 1]) + True + """ + return len(set(iterable)) in (0, 1) + + +def run(n: int) -> list: + """ + Runs core process to find problem solution. + >>> run(3) + [644, 645, 646] + """ + + # Incrementor variable for our group list comprehension. + # This serves as the first number in each list of values + # to test. + base = 2 + + while True: + # Increment each value of a generated range + group = [base + i for i in range(n)] + + # Run elements through out unique_prime_factors function + # Append our target number to the end. + checker = [upf_len(x) for x in group] + checker.append(n) + + # If all numbers in the list are equal, return the group variable. + if equality(checker): + return group + + # Increment our base variable by 1 + base += 1 + + +def solution(n: int = 4) -> int: + """Return the first value of the first four consecutive integers to have four + distinct prime factors each. + >>> solution() + 134043 + """ + results = run(n) + return results[0] if len(results) else None + + +if __name__ == "__main__": + print(solution()) From d2fa91b18e4f87976a67f99a57929d12fe48cfd9 Mon Sep 17 00:00:00 2001 From: wuyudi Date: Thu, 25 Jun 2020 23:54:41 +0800 Subject: [PATCH 0651/1071] Add url and typing hint for BFS (#2156) * Add typing for bfs * Add url for BFS * rename the function Co-authored-by: Christian Clauss * Update graphs/bfs.py Co-authored-by: Christian Clauss * Change the return value type of bfs * change the function name. change all instances of bfs() to breadth_first_search(). * change the function name in annotate * Add one more blank line. * Delete one blank line. * Delete one blank line. I've read the https://www.flake8rules.com/rules/W391.html, and still don't know how to do it. I've tried using 0 ,1,2 blank lines... * Update graphs/bfs.py Co-authored-by: Christian Clauss * Update graphs/bfs.py Co-authored-by: Christian Clauss * Rename bfs.py to breadth_first_search_2.py Co-authored-by: Christian Clauss --- graphs/{bfs.py => breadth_first_search_2.py} | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) rename graphs/{bfs.py => breadth_first_search_2.py} (69%) diff --git a/graphs/bfs.py b/graphs/breadth_first_search_2.py similarity index 69% rename from graphs/bfs.py rename to graphs/breadth_first_search_2.py index 9d9b1ac037d9..0c87b5d8bf3d 100644 --- a/graphs/bfs.py +++ b/graphs/breadth_first_search_2.py @@ -1,9 +1,7 @@ """ -BFS. - +https://en.wikipedia.org/wiki/Breadth-first_search pseudo-code: - -BFS(graph G, start vertex s): +breadth_first_search(graph G, start vertex s): // all nodes initially unexplored mark s as explored let Q = queue data structure, initialized with s @@ -13,9 +11,10 @@ if w unexplored: mark w as explored add w to Q (at the end) - """ +from typing import Set, Dict + G = { "A": ["B", "C"], "B": ["A", "D", "E"], @@ -26,13 +25,13 @@ } -def bfs(graph, start): +def breadth_first_search(graph: Dict, start: str) -> Set[str]: """ - >>> ''.join(sorted(bfs(G, 'A'))) + >>> ''.join(sorted(breadth_first_search(G, 'A'))) 'ABCDEF' """ - explored, queue = set(), [start] # collections.deque([start]) - explored.add(start) + explored = {start} + queue = [start] while queue: v = queue.pop(0) # queue.popleft() for w in graph[v]: @@ -43,4 +42,4 @@ def bfs(graph, start): if __name__ == "__main__": - print(bfs(G, "A")) + print(breadth_first_search(G, "A")) From 8ab84fd7940911b81980c4b387decf85d454064b Mon Sep 17 00:00:00 2001 From: Christian Clauss Date: Thu, 25 Jun 2020 19:15:30 +0200 Subject: [PATCH 0652/1071] Only one carriage return (#2155) * updating DIRECTORY.md * touch * fixup! Format Python code with psf/black push * Update word_frequency_functions.py * updating DIRECTORY.md * Update word_frequency_functions.py * Update lfu_cache.py * Update sol1.py Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> --- DIRECTORY.md | 5 ++ data_structures/binary_tree/avl_tree.py | 12 +++-- machine_learning/word_frequency_functions.py | 23 +++++---- other/lfu_cache.py | 51 ++++++++++---------- other/lru_cache.py | 47 +++++++++--------- project_euler/problem_47/__init__.py | 1 - project_euler/problem_47/sol1.py | 6 +-- 7 files changed, 77 insertions(+), 68 deletions(-) diff --git a/DIRECTORY.md b/DIRECTORY.md index 984744ad7800..f35f3906bff6 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -309,6 +309,7 @@ * [Scoring Functions](https://github.com/TheAlgorithms/Python/blob/master/machine_learning/scoring_functions.py) * [Sequential Minimum Optimization](https://github.com/TheAlgorithms/Python/blob/master/machine_learning/sequential_minimum_optimization.py) * [Support Vector Machines](https://github.com/TheAlgorithms/Python/blob/master/machine_learning/support_vector_machines.py) + * [Word Frequency Functions](https://github.com/TheAlgorithms/Python/blob/master/machine_learning/word_frequency_functions.py) ## Maths * [3N Plus 1](https://github.com/TheAlgorithms/Python/blob/master/maths/3n_plus_1.py) @@ -442,7 +443,9 @@ * [Integeration By Simpson Approx](https://github.com/TheAlgorithms/Python/blob/master/other/integeration_by_simpson_approx.py) * [Largest Subarray Sum](https://github.com/TheAlgorithms/Python/blob/master/other/largest_subarray_sum.py) * [Least Recently Used](https://github.com/TheAlgorithms/Python/blob/master/other/least_recently_used.py) + * [Lfu Cache](https://github.com/TheAlgorithms/Python/blob/master/other/lfu_cache.py) * [Linear Congruential Generator](https://github.com/TheAlgorithms/Python/blob/master/other/linear_congruential_generator.py) + * [Lru Cache](https://github.com/TheAlgorithms/Python/blob/master/other/lru_cache.py) * [Magicdiamondpattern](https://github.com/TheAlgorithms/Python/blob/master/other/magicdiamondpattern.py) * [Markov Chain](https://github.com/TheAlgorithms/Python/blob/master/other/markov_chain.py) * [Nested Brackets](https://github.com/TheAlgorithms/Python/blob/master/other/nested_brackets.py) @@ -566,6 +569,8 @@ * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_40/sol1.py) * Problem 42 * [Solution42](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_42/solution42.py) + * Problem 47 + * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_47/sol1.py) * Problem 48 * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_48/sol1.py) * Problem 52 diff --git a/data_structures/binary_tree/avl_tree.py b/data_structures/binary_tree/avl_tree.py index 71dede2ccacc..c6a45f1cbeb7 100644 --- a/data_structures/binary_tree/avl_tree.py +++ b/data_structures/binary_tree/avl_tree.py @@ -204,14 +204,16 @@ def del_node(root, data): if root is None: return root if get_height(root.get_right()) - get_height(root.get_left()) == 2: - if get_height(root.get_right().get_right()) > \ - get_height(root.get_right().get_left()): + if get_height(root.get_right().get_right()) > get_height( + root.get_right().get_left() + ): root = left_rotation(root) else: root = rl_rotation(root) elif get_height(root.get_right()) - get_height(root.get_left()) == -2: - if get_height(root.get_left().get_left()) > \ - get_height(root.get_left().get_right()): + if get_height(root.get_left().get_left()) > get_height( + root.get_left().get_right() + ): root = right_rotation(root) else: root = lr_rotation(root) @@ -253,6 +255,7 @@ class AVLtree: 2 * ************************************* """ + def __init__(self): self.root = None @@ -307,6 +310,7 @@ def __str__(self): # a level traversale, gives a more intuitive look on the tre def _test(): import doctest + doctest.testmod() diff --git a/machine_learning/word_frequency_functions.py b/machine_learning/word_frequency_functions.py index a105e30f5d3b..09c6d269ef0c 100644 --- a/machine_learning/word_frequency_functions.py +++ b/machine_learning/word_frequency_functions.py @@ -40,7 +40,7 @@ """ -def term_frequency(term : str, document : str) -> int: +def term_frequency(term: str, document: str) -> int: """ Return the number of times a term occurs within a given document. @@ -58,9 +58,7 @@ def term_frequency(term : str, document : str) -> int: str.maketrans("", "", string.punctuation) ).replace("\n", "") tokenize_document = document_without_punctuation.split(" ") # word tokenization - return len( - [word for word in tokenize_document if word.lower() == term.lower()] - ) + return len([word for word in tokenize_document if word.lower() == term.lower()]) def document_frequency(term: str, corpus: str) -> int: @@ -77,17 +75,18 @@ def document_frequency(term: str, corpus: str) -> int: the third document in the corpus.") (1, 3) """ - corpus_without_punctuation = corpus.translate( + corpus_without_punctuation = corpus.lower().translate( str.maketrans("", "", string.punctuation) ) # strip all punctuation and replace it with '' - documents = corpus_without_punctuation.split("\n") - lowercase_documents = [document.lower() for document in documents] - return len( - [document for document in lowercase_documents if term.lower() in document] - ), len(documents) + docs = corpus_without_punctuation.split("\n") + term = term.lower() + return ( + len([doc for doc in docs if term in doc]), + len(docs), + ) -def inverse_document_frequency(df : int, N: int) -> float: +def inverse_document_frequency(df: int, N: int) -> float: """ Return an integer denoting the importance of a word. This measure of importance is @@ -116,7 +115,7 @@ def inverse_document_frequency(df : int, N: int) -> float: return round(log10(N / df), 3) -def tf_idf(tf : int, idf: int) -> float: +def tf_idf(tf: int, idf: int) -> float: """ Combine the term frequency and inverse document frequency functions to diff --git a/other/lfu_cache.py b/other/lfu_cache.py index 0f128646d7a2..40268242f564 100644 --- a/other/lfu_cache.py +++ b/other/lfu_cache.py @@ -2,9 +2,9 @@ class DoubleLinkedListNode: - ''' + """ Double Linked List Node built specifically for LFU Cache - ''' + """ def __init__(self, key: int, val: int): self.key = key @@ -15,9 +15,9 @@ def __init__(self, key: int, val: int): class DoubleLinkedList: - ''' + """ Double Linked List built specifically for LFU Cache - ''' + """ def __init__(self): self.head = DoubleLinkedListNode(None, None) @@ -25,9 +25,9 @@ def __init__(self): self.head.next, self.rear.prev = self.rear, self.head def add(self, node: DoubleLinkedListNode) -> None: - ''' + """ Adds the given node at the head of the list and shifting it to proper position - ''' + """ temp = self.rear.prev @@ -43,9 +43,9 @@ def _position_node(self, node: DoubleLinkedListNode) -> None: node1.next, node2.prev = node2, node1 def remove(self, node: DoubleLinkedListNode) -> DoubleLinkedListNode: - ''' + """ Removes and returns the given node from the list - ''' + """ temp_last, temp_next = node.prev, node.next node.prev, node.next = None, None @@ -54,7 +54,7 @@ def remove(self, node: DoubleLinkedListNode) -> DoubleLinkedListNode: class LFUCache: - ''' + """ LFU Cache to store a given capacity of data. Can be used as a stand-alone object or as a function decorator. @@ -72,7 +72,7 @@ class LFUCache: >>> cache.get(4) 4 >>> cache - CacheInfo(hits=3, misses=2, capacity=2, current size=2) + CacheInfo(hits=3, misses=2, capacity=2, current_size=2) >>> @LFUCache.decorator(100) ... def fib(num): ... if num in (1, 2): @@ -83,8 +83,8 @@ class LFUCache: ... res = fib(i) >>> fib.cache_info() - CacheInfo(hits=196, misses=100, capacity=100, current size=100) - ''' + CacheInfo(hits=196, misses=100, capacity=100, current_size=100) + """ # class variable to map the decorator functions to their respective instance decorator_function_to_instance_map = {} @@ -98,30 +98,32 @@ def __init__(self, capacity: int): self.cache = {} def __repr__(self) -> str: - ''' + """ Return the details for the cache instance [hits, misses, capacity, current_size] - ''' + """ - return (f'CacheInfo(hits={self.hits}, misses={self.miss}, ' - f'capacity={self.capacity}, current size={self.num_keys})') + return ( + f"CacheInfo(hits={self.hits}, misses={self.miss}, " + f"capacity={self.capacity}, current_size={self.num_keys})" + ) def __contains__(self, key: int) -> bool: - ''' + """ >>> cache = LFUCache(1) >>> 1 in cache False >>> cache.set(1, 1) >>> 1 in cache True - ''' + """ return key in self.cache def get(self, key: int) -> Optional[int]: - ''' + """ Returns the value for the input key and updates the Double Linked List. Returns None if key is not present in cache - ''' + """ if key in self.cache: self.hits += 1 @@ -131,9 +133,9 @@ def get(self, key: int) -> Optional[int]: return None def set(self, key: int, value: int) -> None: - ''' + """ Sets the value for the input key and updates the Double Linked List - ''' + """ if key not in self.cache: if self.num_keys >= self.capacity: @@ -152,12 +154,11 @@ def set(self, key: int, value: int) -> None: @staticmethod def decorator(size: int = 128): - ''' + """ Decorator version of LFU Cache - ''' + """ def cache_decorator_inner(func: Callable): - def cache_decorator_wrapper(*args, **kwargs): if func not in LFUCache.decorator_function_to_instance_map: LFUCache.decorator_function_to_instance_map[func] = LFUCache(size) diff --git a/other/lru_cache.py b/other/lru_cache.py index 7b5d16be66e5..2a9d7e49b279 100644 --- a/other/lru_cache.py +++ b/other/lru_cache.py @@ -2,9 +2,9 @@ class DoubleLinkedListNode: - ''' + """ Double Linked List Node built specifically for LRU Cache - ''' + """ def __init__(self, key: int, val: int): self.key = key @@ -14,9 +14,9 @@ def __init__(self, key: int, val: int): class DoubleLinkedList: - ''' + """ Double Linked List built specifically for LRU Cache - ''' + """ def __init__(self): self.head = DoubleLinkedListNode(None, None) @@ -24,18 +24,18 @@ def __init__(self): self.head.next, self.rear.prev = self.rear, self.head def add(self, node: DoubleLinkedListNode) -> None: - ''' + """ Adds the given node to the end of the list (before rear) - ''' + """ temp = self.rear.prev temp.next, node.prev = node, temp self.rear.prev, node.next = node, self.rear def remove(self, node: DoubleLinkedListNode) -> DoubleLinkedListNode: - ''' + """ Removes and returns the given node from the list - ''' + """ temp_last, temp_next = node.prev, node.next node.prev, node.next = None, None @@ -45,7 +45,7 @@ def remove(self, node: DoubleLinkedListNode) -> DoubleLinkedListNode: class LRUCache: - ''' + """ LRU Cache to store a given capacity of data. Can be used as a stand-alone object or as a function decorator. @@ -86,7 +86,7 @@ class LRUCache: >>> fib.cache_info() CacheInfo(hits=194, misses=99, capacity=100, current size=99) - ''' + """ # class variable to map the decorator functions to their respective instance decorator_function_to_instance_map = {} @@ -100,16 +100,18 @@ def __init__(self, capacity: int): self.cache = {} def __repr__(self) -> str: - ''' + """ Return the details for the cache instance [hits, misses, capacity, current_size] - ''' + """ - return (f'CacheInfo(hits={self.hits}, misses={self.miss}, ' - f'capacity={self.capacity}, current size={self.num_keys})') + return ( + f"CacheInfo(hits={self.hits}, misses={self.miss}, " + f"capacity={self.capacity}, current size={self.num_keys})" + ) def __contains__(self, key: int) -> bool: - ''' + """ >>> cache = LRUCache(1) >>> 1 in cache @@ -119,15 +121,15 @@ def __contains__(self, key: int) -> bool: >>> 1 in cache True - ''' + """ return key in self.cache def get(self, key: int) -> Optional[int]: - ''' + """ Returns the value for the input key and updates the Double Linked List. Returns None if key is not present in cache - ''' + """ if key in self.cache: self.hits += 1 @@ -137,9 +139,9 @@ def get(self, key: int) -> Optional[int]: return None def set(self, key: int, value: int) -> None: - ''' + """ Sets the value for the input key and updates the Double Linked List - ''' + """ if key not in self.cache: if self.num_keys >= self.capacity: @@ -158,12 +160,11 @@ def set(self, key: int, value: int) -> None: @staticmethod def decorator(size: int = 128): - ''' + """ Decorator version of LRU Cache - ''' + """ def cache_decorator_inner(func: Callable): - def cache_decorator_wrapper(*args, **kwargs): if func not in LRUCache.decorator_function_to_instance_map: LRUCache.decorator_function_to_instance_map[func] = LRUCache(size) diff --git a/project_euler/problem_47/__init__.py b/project_euler/problem_47/__init__.py index 8b137891791f..e69de29bb2d1 100644 --- a/project_euler/problem_47/__init__.py +++ b/project_euler/problem_47/__init__.py @@ -1 +0,0 @@ - diff --git a/project_euler/problem_47/sol1.py b/project_euler/problem_47/sol1.py index fab8ffde9052..1287e0d9e107 100644 --- a/project_euler/problem_47/sol1.py +++ b/project_euler/problem_47/sol1.py @@ -28,9 +28,9 @@ def unique_prime_factors(n: int) -> set: not the order in which it is produced. >>> sorted(set(unique_prime_factors(14))) [2, 7] - >>> set(sorted(unique_prime_factors(644))) + >>> sorted(set(unique_prime_factors(644))) [2, 7, 23] - >>> set(sorted(unique_prime_factors(646))) + >>> sorted(set(unique_prime_factors(646))) [2, 17, 19] """ i = 2 @@ -64,7 +64,7 @@ def equality(iterable: list) -> bool: >>> equality([2, 2, 2, 2]) True >>> equality([1, 2, 3, 2, 1]) - True + False """ return len(set(iterable)) in (0, 1) From c534e77cb1bf2029d6dcc578fa19363ecd40df3d Mon Sep 17 00:00:00 2001 From: Tapajyoti Bose <44058757+ruppysuppy@users.noreply.github.com> Date: Thu, 2 Jul 2020 14:43:29 +0530 Subject: [PATCH 0653/1071] Added minimum cost path algorithm (#2135) * Added maximum path sum for matrix (top left to bottom right) * Changed maximum cost path to minimum cost path + added video explaination --- dynamic_programming/minimum_cost_path.py | 37 ++++++++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 dynamic_programming/minimum_cost_path.py diff --git a/dynamic_programming/minimum_cost_path.py b/dynamic_programming/minimum_cost_path.py new file mode 100644 index 000000000000..93d936656294 --- /dev/null +++ b/dynamic_programming/minimum_cost_path.py @@ -0,0 +1,37 @@ +# Youtube Explaination: https://www.youtube.com/watch?v=lBRtnuxg-gU + +from typing import List + + +def minimum_cost_path(matrix: List[List[int]]) -> int: + ''' + Find the minimum cost traced by all possible paths from top left to bottom right in + a given matrix + + >>> minimum_cost_path([[2, 1], [3, 1], [4, 2]]) + 6 + + >>> minimum_cost_path([[2, 1, 4], [2, 1, 3], [3, 2, 1]]) + 7 + ''' + + # preprocessing the first row + for i in range(1, len(matrix[0])): + matrix[0][i] += matrix[0][i - 1] + + # preprocessing the first column + for i in range(1, len(matrix)): + matrix[i][0] += matrix[i - 1][0] + + # updating the path cost for current position + for i in range(1, len(matrix)): + for j in range(1, len(matrix[0])): + matrix[i][j] += min(matrix[i - 1][j], matrix[i][j - 1]) + + return matrix[-1][-1] + + +if __name__ == "__main__": + import doctest + + doctest.testmod() From 2d3d660155241113b23e4ed810e05479b2fc4bba Mon Sep 17 00:00:00 2001 From: vinayak Date: Thu, 2 Jul 2020 20:02:15 +0530 Subject: [PATCH 0654/1071] black fixes and Travis CI fixes (#2160) * black format * updating DIRECTORY.md * fixes * fixup! Format Python code with psf/black push Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> --- DIRECTORY.md | 3 ++- ciphers/affine_cipher.py | 2 +- dynamic_programming/minimum_cost_path.py | 4 ++-- graphs/connected_components.py | 21 +++----------------- graphs/strongly_connected_components.py | 19 +++--------------- greedy_method/test_knapsack.py | 4 +--- machine_learning/word_frequency_functions.py | 5 +---- 7 files changed, 13 insertions(+), 45 deletions(-) diff --git a/DIRECTORY.md b/DIRECTORY.md index f35f3906bff6..f93f082d8a18 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -205,6 +205,7 @@ * [Max Non Adjacent Sum](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/max_non_adjacent_sum.py) * [Max Sub Array](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/max_sub_array.py) * [Max Sum Contiguous Subsequence](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/max_sum_contiguous_subsequence.py) + * [Minimum Cost Path](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/minimum_cost_path.py) * [Minimum Partition](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/minimum_partition.py) * [Optimal Binary Search Tree](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/optimal_binary_search_tree.py) * [Rod Cutting](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/rod_cutting.py) @@ -230,11 +231,11 @@ * [Articulation Points](https://github.com/TheAlgorithms/Python/blob/master/graphs/articulation_points.py) * [Basic Graphs](https://github.com/TheAlgorithms/Python/blob/master/graphs/basic_graphs.py) * [Bellman Ford](https://github.com/TheAlgorithms/Python/blob/master/graphs/bellman_ford.py) - * [Bfs](https://github.com/TheAlgorithms/Python/blob/master/graphs/bfs.py) * [Bfs Shortest Path](https://github.com/TheAlgorithms/Python/blob/master/graphs/bfs_shortest_path.py) * [Bidirectional A Star](https://github.com/TheAlgorithms/Python/blob/master/graphs/bidirectional_a_star.py) * [Bidirectional Breadth First Search](https://github.com/TheAlgorithms/Python/blob/master/graphs/bidirectional_breadth_first_search.py) * [Breadth First Search](https://github.com/TheAlgorithms/Python/blob/master/graphs/breadth_first_search.py) + * [Breadth First Search 2](https://github.com/TheAlgorithms/Python/blob/master/graphs/breadth_first_search_2.py) * [Breadth First Search Shortest Path](https://github.com/TheAlgorithms/Python/blob/master/graphs/breadth_first_search_shortest_path.py) * [Check Bipartite Graph Bfs](https://github.com/TheAlgorithms/Python/blob/master/graphs/check_bipartite_graph_bfs.py) * [Check Bipartite Graph Dfs](https://github.com/TheAlgorithms/Python/blob/master/graphs/check_bipartite_graph_dfs.py) diff --git a/ciphers/affine_cipher.py b/ciphers/affine_cipher.py index bcf8b6500a1a..5d77cfef33f7 100644 --- a/ciphers/affine_cipher.py +++ b/ciphers/affine_cipher.py @@ -102,4 +102,4 @@ def get_random_key(): import doctest doctest.testmod() - main() + # main() diff --git a/dynamic_programming/minimum_cost_path.py b/dynamic_programming/minimum_cost_path.py index 93d936656294..a8d424eb2790 100644 --- a/dynamic_programming/minimum_cost_path.py +++ b/dynamic_programming/minimum_cost_path.py @@ -4,7 +4,7 @@ def minimum_cost_path(matrix: List[List[int]]) -> int: - ''' + """ Find the minimum cost traced by all possible paths from top left to bottom right in a given matrix @@ -13,7 +13,7 @@ def minimum_cost_path(matrix: List[List[int]]) -> int: >>> minimum_cost_path([[2, 1, 4], [2, 1, 3], [3, 2, 1]]) 7 - ''' + """ # preprocessing the first row for i in range(1, len(matrix[0])): diff --git a/graphs/connected_components.py b/graphs/connected_components.py index b5ef8c292b29..6bcc160a9ab7 100644 --- a/graphs/connected_components.py +++ b/graphs/connected_components.py @@ -5,24 +5,9 @@ """ -test_graph_1 = { - 0: [1, 2], - 1: [0, 3], - 2: [0], - 3: [1], - 4: [5, 6], - 5: [4, 6], - 6: [4, 5], -} - -test_graph_2 = { - 0: [1, 2, 3], - 1: [0, 3], - 2: [0], - 3: [0, 1], - 4: [], - 5: [], -} +test_graph_1 = {0: [1, 2], 1: [0, 3], 2: [0], 3: [1], 4: [5, 6], 5: [4, 6], 6: [4, 5]} + +test_graph_2 = {0: [1, 2, 3], 1: [0, 3], 2: [0], 3: [0, 1], 4: [], 5: []} def dfs(graph: dict, vert: int, visited: list) -> list: diff --git a/graphs/strongly_connected_components.py b/graphs/strongly_connected_components.py index 283545c9a618..d469df0c625b 100644 --- a/graphs/strongly_connected_components.py +++ b/graphs/strongly_connected_components.py @@ -5,22 +5,9 @@ """ -test_graph_1 = { - 0: [2, 3], - 1: [0], - 2: [1], - 3: [4], - 4: [], -} - -test_graph_2 = { - 0: [1, 2, 3], - 1: [2], - 2: [0], - 3: [4], - 4: [5], - 5: [3], -} +test_graph_1 = {0: [2, 3], 1: [0], 2: [1], 3: [4], 4: []} + +test_graph_2 = {0: [1, 2, 3], 1: [2], 2: [0], 3: [4], 4: [5], 5: [3]} def topology_sort(graph: dict, vert: int, visited: list) -> list: diff --git a/greedy_method/test_knapsack.py b/greedy_method/test_knapsack.py index 9d556d2d22f4..71a259a1ec46 100644 --- a/greedy_method/test_knapsack.py +++ b/greedy_method/test_knapsack.py @@ -35,9 +35,7 @@ def test_negative_profit_value(self): # profit = [10, -20, 30, 40, 50, 60] # weight = [2, 4, 6, 8, 10, 12] # max_weight = 15 - self.assertRaisesRegex( - ValueError, "Weight can not be negative.", - ) + self.assertRaisesRegex(ValueError, "Weight can not be negative.") def test_negative_weight_value(self): """ diff --git a/machine_learning/word_frequency_functions.py b/machine_learning/word_frequency_functions.py index 09c6d269ef0c..e9e9e644b7d8 100644 --- a/machine_learning/word_frequency_functions.py +++ b/machine_learning/word_frequency_functions.py @@ -80,10 +80,7 @@ def document_frequency(term: str, corpus: str) -> int: ) # strip all punctuation and replace it with '' docs = corpus_without_punctuation.split("\n") term = term.lower() - return ( - len([doc for doc in docs if term in doc]), - len(docs), - ) + return (len([doc for doc in docs if term in doc]), len(docs)) def inverse_document_frequency(df: int, N: int) -> float: From e274863cda5514b22196942d3a55887499166fcf Mon Sep 17 00:00:00 2001 From: Pawan Sundargiri <67560186+pawanbuddy2000@users.noreply.github.com> Date: Fri, 3 Jul 2020 18:41:07 +0530 Subject: [PATCH 0655/1071] Add round_robin scheduling algorithm (#2158) * round_robin and priority cpu scheduling algorithms * Delete priority_cpu_scheduling.py * Delete round_robin_algorithm.py * [add] cpu_scheduling_algorithms * [add] Round robin cpu scheduling algorithm * Update scheduling/round_robin_scheduling_algorithm.py Co-authored-by: Christian Clauss * Update scheduling/round_robin.py Co-authored-by: Christian Clauss * Update scheduling/round_robin_scheduling.py Co-authored-by: Christian Clauss * Update scheduling/round_robin_scheduling.py Co-authored-by: Christian Clauss * Update scheduling/round_robin.py Co-authored-by: Christian Clauss * Round_Robin * Update round_robin.py * Update round_robin.py * Update round_robin.py * Update round_robin.py Co-authored-by: pawanbuddy <46370996+pawanbuddy@users.noreply.github.com> Co-authored-by: Christian Clauss --- scheduling/round_robin.py | 65 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 65 insertions(+) create mode 100755 scheduling/round_robin.py diff --git a/scheduling/round_robin.py b/scheduling/round_robin.py new file mode 100755 index 000000000000..10aa3ecab31f --- /dev/null +++ b/scheduling/round_robin.py @@ -0,0 +1,65 @@ +""" +Round Robin is a scheduling algorithm. +In Round Robin each process is assigned a fixed time slot in a cyclic way. +https://en.wikipedia.org/wiki/Round-robin_scheduling +""" +from statistics import mean +from typing import List + + +def calculate_waiting_times(burst_times: List[int]) -> List[int]: + """ + Calculate the waiting times of a list of processes that have a specified duration. + + Return: The waiting time for each process. + >>> calculate_waiting_times([10, 5, 8]) + [13, 10, 13] + >>> calculate_waiting_times([4, 6, 3, 1]) + [5, 8, 9, 6] + >>> calculate_waiting_times([12, 2, 10]) + [12, 2, 12] + """ + quantum = 2 + rem_burst_times = list(burst_times) + waiting_times = [0] * len(burst_times) + t = 0 + while 1: + done = True + for i, burst_time in enumerate(burst_times): + if rem_burst_times[i] > 0: + done = False + if rem_burst_times[i] > quantum: + t += quantum + rem_burst_times[i] -= quantum + else: + t += rem_burst_times[i] + waiting_times[i] = t - burst_times[i] + rem_burst_times[i] = 0 + if done is True: + return waiting_times + + +def calculate_turn_around_times( + burst_times: List[int], waiting_times: List[int] +) -> List[int]: + """ + >>> calculate_turn_around_times([1, 2, 3, 4], [0, 1, 3]) + [1, 3, 6] + >>> calculate_turn_around_times([10, 3, 7], [10, 6, 11]) + [20, 9, 18] + """ + return [burst + waiting for burst, waiting in zip(burst_times, waiting_times)] + + +if __name__ == "__main__": + burst_times = [3, 5, 7] + waiting_times = calculate_waiting_times(burst_times) + turn_around_times = calculate_turn_around_times(burst_times, waiting_times) + print("Process ID \tBurst Time \tWaiting Time \tTurnaround Time") + for i, burst_time in enumerate(burst_times): + print( + f" {i + 1}\t\t {burst_time}\t\t {waiting_times[i]}\t\t " + f"{turn_around_times[i]}" + ) + print(f"\nAverage waiting time = {mean(waiting_times):.5f}") + print(f"Average turn around time = {mean(turn_around_times):.5f}") From 2c98dce0573a6282e7d45176d7a10da58e9f5260 Mon Sep 17 00:00:00 2001 From: Christian Clauss Date: Fri, 3 Jul 2020 21:26:40 +0200 Subject: [PATCH 0656/1071] Rename shortest_job_first_algorithm.py to shortest_job_first.py (#2164) * Rename shortest_job_first_algorithm.py to shortest_job_first.py * updating DIRECTORY.md * Minor tweek to round_robin.py Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> --- DIRECTORY.md | 3 ++- scheduling/round_robin.py | 4 ++-- ...{shortest_job_first_algorithm.py => shortest_job_first.py} | 0 3 files changed, 4 insertions(+), 3 deletions(-) rename scheduling/{shortest_job_first_algorithm.py => shortest_job_first.py} (100%) diff --git a/DIRECTORY.md b/DIRECTORY.md index f93f082d8a18..34ee376be1c3 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -591,7 +591,8 @@ ## Scheduling * [First Come First Served](https://github.com/TheAlgorithms/Python/blob/master/scheduling/first_come_first_served.py) - * [Shortest Job First Algorithm](https://github.com/TheAlgorithms/Python/blob/master/scheduling/shortest_job_first_algorithm.py) + * [Round Robin](https://github.com/TheAlgorithms/Python/blob/master/scheduling/round_robin.py) + * [Shortest Job First](https://github.com/TheAlgorithms/Python/blob/master/scheduling/shortest_job_first.py) ## Searches * [Binary Search](https://github.com/TheAlgorithms/Python/blob/master/searches/binary_search.py) diff --git a/scheduling/round_robin.py b/scheduling/round_robin.py index 10aa3ecab31f..4a79301c1816 100755 --- a/scheduling/round_robin.py +++ b/scheduling/round_robin.py @@ -23,7 +23,7 @@ def calculate_waiting_times(burst_times: List[int]) -> List[int]: rem_burst_times = list(burst_times) waiting_times = [0] * len(burst_times) t = 0 - while 1: + while True: done = True for i, burst_time in enumerate(burst_times): if rem_burst_times[i] > 0: @@ -33,7 +33,7 @@ def calculate_waiting_times(burst_times: List[int]) -> List[int]: rem_burst_times[i] -= quantum else: t += rem_burst_times[i] - waiting_times[i] = t - burst_times[i] + waiting_times[i] = t - burst_time rem_burst_times[i] = 0 if done is True: return waiting_times diff --git a/scheduling/shortest_job_first_algorithm.py b/scheduling/shortest_job_first.py similarity index 100% rename from scheduling/shortest_job_first_algorithm.py rename to scheduling/shortest_job_first.py From f70a0a2980188a57195d17dd28ccb29b1fc6962b Mon Sep 17 00:00:00 2001 From: wuyudi Date: Sat, 4 Jul 2020 03:28:16 +0800 Subject: [PATCH 0657/1071] Update matrix_operation.py (#2159) * Update matrix_operation.py * Update matrix_operation.py * Update matrix_operation.py * use yapf to format the code * recover the error check * add typing hint * Update matrix/matrix_operation.py Co-authored-by: Christian Clauss * Update matrix/matrix_operation.py Co-authored-by: Christian Clauss * Update matrix/matrix_operation.py Co-authored-by: Christian Clauss * Update matrix/matrix_operation.py Co-authored-by: Christian Clauss * add float doctest * black formated * Update searching_in_sorted_matrix.py * recover this file * f-string, typing hint , doctest * Update matrix_operation.py * Update searching_in_sorted_matrix.py * Update matrix_operation.py * Update matrix/matrix_operation.py Co-authored-by: Christian Clauss Co-authored-by: Christian Clauss Co-authored-by: vinayak --- matrix/matrix_operation.py | 134 +++++++++++++++++---------- matrix/searching_in_sorted_matrix.py | 26 +++++- 2 files changed, 110 insertions(+), 50 deletions(-) diff --git a/matrix/matrix_operation.py b/matrix/matrix_operation.py index 307e8b6ba32e..2580ef85dcc6 100644 --- a/matrix/matrix_operation.py +++ b/matrix/matrix_operation.py @@ -1,39 +1,55 @@ """ -function based version of matrix operations, which are just 2D arrays +Functions for 2D matrix operations """ +from typing import List, Tuple -def add(matrix_a, matrix_b): + +def add(matrix_a: List[list], matrix_b: List[list]) -> List[list]: + """ + >>> add([[1,2],[3,4]],[[2,3],[4,5]]) + [[3, 5], [7, 9]] + >>> add([[1.2,2.4],[3,4]],[[2,3],[4,5]]) + [[3.2, 5.4], [7, 9]] + """ if _check_not_integer(matrix_a) and _check_not_integer(matrix_b): - rows, cols = _verify_matrix_sizes(matrix_a, matrix_b) - matrix_c = [] - for i in range(rows[0]): - list_1 = [] - for j in range(cols[0]): - val = matrix_a[i][j] + matrix_b[i][j] - list_1.append(val) - matrix_c.append(list_1) + _verify_matrix_sizes(matrix_a, matrix_b) + matrix_c = [[i + j for i, j in zip(m, n)] + for m, n in zip(matrix_a, matrix_b)] return matrix_c -def subtract(matrix_a, matrix_b): +def subtract(matrix_a: List[list], matrix_b: List[list]) -> List[list]: + """ + >>> subtract([[1,2],[3,4]],[[2,3],[4,5]]) + [[-1, -1], [-1, -1]] + >>> subtract([[1,2.5],[3,4]],[[2,3],[4,5.5]]) + [[-1, -0.5], [-1, -1.5]] + """ if _check_not_integer(matrix_a) and _check_not_integer(matrix_b): - rows, cols = _verify_matrix_sizes(matrix_a, matrix_b) - matrix_c = [] - for i in range(rows[0]): - list_1 = [] - for j in range(cols[0]): - val = matrix_a[i][j] - matrix_b[i][j] - list_1.append(val) - matrix_c.append(list_1) + _verify_matrix_sizes(matrix_a, matrix_b) + matrix_c = [[i - j for i, j in zip(m, n)] + for m, n in zip(matrix_a, matrix_b)] return matrix_c -def scalar_multiply(matrix, n): +def scalar_multiply(matrix: List[list], n: int) -> List[list]: + """ + >>> scalar_multiply([[1,2],[3,4]],5) + [[5, 10], [15, 20]] + >>> scalar_multiply([[1.4,2.3],[3,4]],5) + [[7.0, 11.5], [15, 20]] + """ return [[x * n for x in row] for row in matrix] -def multiply(matrix_a, matrix_b): +def multiply(matrix_a: List[list], matrix_b: List[list]) -> List[list]: + """ + >>> multiply([[1,2],[3,4]],[[5,5],[7,5]]) + [[19, 15], [43, 35]] + >>> multiply([[1,2.5],[3,4.5]],[[5,5],[7,5]]) + [[22.5, 17.5], [46.5, 37.5]] + """ if _check_not_integer(matrix_a) and _check_not_integer(matrix_b): matrix_c = [] rows, cols = _verify_matrix_sizes(matrix_a, matrix_b) @@ -48,41 +64,55 @@ def multiply(matrix_a, matrix_b): for j in range(cols[1]): val = 0 for k in range(cols[1]): - val = val + matrix_a[i][k] * matrix_b[k][j] + val += matrix_a[i][k] * matrix_b[k][j] list_1.append(val) matrix_c.append(list_1) return matrix_c -def identity(n): +def identity(n: int) -> List[list]: """ :param n: dimension for nxn matrix :type n: int :return: Identity matrix of shape [n, n] + >>> identity(3) + [[1, 0, 0], [0, 1, 0], [0, 0, 1]] """ n = int(n) return [[int(row == column) for column in range(n)] for row in range(n)] -def transpose(matrix, return_map=True): +def transpose(matrix: List[list], return_map: bool = True) -> List[list]: + """ + >>> transpose([[1,2],[3,4]]) # doctest: +ELLIPSIS + >> transpose([[1,2],[3,4]], return_map=False) + [[1, 3], [2, 4]] + """ if _check_not_integer(matrix): if return_map: return map(list, zip(*matrix)) else: - # mt = [] - # for i in range(len(matrix[0])): - # mt.append([row[i] for row in matrix]) - # return mt return [[row[i] for row in matrix] for i in range(len(matrix[0]))] -def minor(matrix, row, column): - minor = matrix[:row] + matrix[row + 1 :] - minor = [row[:column] + row[column + 1 :] for row in minor] +def minor(matrix: List[list], row: int, column: int) -> List[list]: + """ + >>> minor([[1, 2], [3, 4]], 1, 1) + [[1]] + """ + minor = matrix[:row] + matrix[row + 1:] + minor = [row[:column] + row[column + 1:] for row in minor] return minor -def determinant(matrix): +def determinant(matrix: List[list]) -> int: + """ + >>> determinant([[1, 2], [3, 4]]) + -2 + >>> determinant([[1.5, 2.5], [3, 4]]) + -1.5 + """ if len(matrix) == 1: return matrix[0][0] @@ -92,12 +122,18 @@ def determinant(matrix): return res -def inverse(matrix): +def inverse(matrix: List[list]) -> List[list]: + """ + >>> inverse([[1, 2], [3, 4]]) + [[-2.0, 1.0], [1.5, -0.5]] + >>> inverse([[1, 1], [1, 1]]) + """ + # https://stackoverflow.com/questions/20047519/python-doctests-test-for-none det = determinant(matrix) if det == 0: return None - matrix_minor = [[] for _ in range(len(matrix))] + matrix_minor = [[] for _ in matrix] for i in range(len(matrix)): for j in range(len(matrix)): matrix_minor[i].append(determinant(minor(matrix, i, j))) @@ -110,17 +146,18 @@ def inverse(matrix): return scalar_multiply(adjugate, 1 / det) -def _check_not_integer(matrix): +def _check_not_integer(matrix: List[list]) -> bool: if not isinstance(matrix, int) and not isinstance(matrix[0], int): return True raise TypeError("Expected a matrix, got int/list instead") -def _shape(matrix): +def _shape(matrix: List[list]) -> list: return list((len(matrix), len(matrix[0]))) -def _verify_matrix_sizes(matrix_a, matrix_b): +def _verify_matrix_sizes( + matrix_a: List[list], matrix_b: List[list]) -> Tuple[list]: shape = _shape(matrix_a) shape += _shape(matrix_b) if shape[0] != shape[2] or shape[1] != shape[3]: @@ -134,21 +171,24 @@ def _verify_matrix_sizes(matrix_a, matrix_b): def main(): matrix_a = [[12, 10], [3, 9]] matrix_b = [[3, 4], [7, 4]] - matrix_c = [[11, 12, 13, 14], [21, 22, 23, 24], [31, 32, 33, 34], [41, 42, 43, 44]] + matrix_c = [[11, 12, 13, 14], [21, 22, 23, 24], + [31, 32, 33, 34], [41, 42, 43, 44]] matrix_d = [[3, 0, 2], [2, 0, -2], [0, 1, 1]] print( - "Add Operation, %s + %s = %s \n" - % (matrix_a, matrix_b, (add(matrix_a, matrix_b))) - ) + f"Add Operation, {matrix_a} + {matrix_b}" + f" = {add(matrix_a, matrix_b)} \n") print( - "Multiply Operation, %s * %s = %s \n" - % (matrix_a, matrix_b, multiply(matrix_a, matrix_b)) + f"Multiply Operation, {matrix_a} * {matrix_b}", + f"= {multiply(matrix_a, matrix_b)} \n", ) - print("Identity: %s \n" % identity(5)) - print("Minor of {} = {} \n".format(matrix_c, minor(matrix_c, 1, 2))) - print("Determinant of {} = {} \n".format(matrix_b, determinant(matrix_b))) - print("Inverse of {} = {}\n".format(matrix_d, inverse(matrix_d))) + print(f"Identity: {identity(5)}\n") + print(f"Minor of {matrix_c} = {minor(matrix_c, 1, 2)} \n") + print(f"Determinant of {matrix_b} = {determinant(matrix_b)} \n") + print(f"Inverse of {matrix_d} = {inverse(matrix_d)}\n") if __name__ == "__main__": + import doctest + + doctest.testmod() main() diff --git a/matrix/searching_in_sorted_matrix.py b/matrix/searching_in_sorted_matrix.py index 3897ffb1d9c6..996805d4e231 100644 --- a/matrix/searching_in_sorted_matrix.py +++ b/matrix/searching_in_sorted_matrix.py @@ -1,14 +1,32 @@ -def search_in_a_sorted_matrix(mat, m, n, key): +from typing import List + + +def search_in_a_sorted_matrix( + mat: List[list], m: int, n: int, key: int or float) -> None: + ''' + >>> search_in_a_sorted_matrix(\ + [[2, 5, 7], [4, 8, 13], [9, 11, 15], [12, 17, 20]], 3, 3, 5) + Key 5 found at row- 1 column- 2 + >>> search_in_a_sorted_matrix(\ + [[2, 5, 7], [4, 8, 13], [9, 11, 15], [12, 17, 20]], 3, 3, 21) + Key 21 not found + >>> search_in_a_sorted_matrix(\ + [[2.1, 5, 7], [4, 8, 13], [9, 11, 15], [12, 17, 20]], 3, 3, 2.1) + Key 2.1 found at row- 1 column- 1 + >>> search_in_a_sorted_matrix(\ + [[2.1, 5, 7], [4, 8, 13], [9, 11, 15], [12, 17, 20]], 3, 3, 2.2) + Key 2.2 not found + ''' i, j = m - 1, 0 while i >= 0 and j < n: if key == mat[i][j]: - print("Key {} found at row- {} column- {}".format(key, i + 1, j + 1)) + print(f"Key {key} found at row- {i + 1} column- {j + 1}") return if key < mat[i][j]: i -= 1 else: j += 1 - print("Key %s not found" % (key)) + print(f"Key {key} not found") def main(): @@ -19,4 +37,6 @@ def main(): if __name__ == "__main__": + import doctest + doctest.testmod() main() From 70cf56538760c06c8957951ea443d596a99c7c68 Mon Sep 17 00:00:00 2001 From: vinayak Date: Sat, 4 Jul 2020 13:22:21 +0530 Subject: [PATCH 0658/1071] black (#2166) * black * Update matrix/matrix_operation.py Co-authored-by: Christian Clauss * Update matrix/matrix_operation.py Co-authored-by: Christian Clauss * Update matrix/matrix_operation.py Co-authored-by: Christian Clauss * Update matrix/matrix_operation.py Co-authored-by: Christian Clauss * flake8 error fix * Remove unreachable code * union Co-authored-by: Christian Clauss --- matrix/matrix_operation.py | 23 +++++++---------------- matrix/searching_in_sorted_matrix.py | 10 ++++++---- 2 files changed, 13 insertions(+), 20 deletions(-) diff --git a/matrix/matrix_operation.py b/matrix/matrix_operation.py index 2580ef85dcc6..002ce6b05f9f 100644 --- a/matrix/matrix_operation.py +++ b/matrix/matrix_operation.py @@ -14,9 +14,7 @@ def add(matrix_a: List[list], matrix_b: List[list]) -> List[list]: """ if _check_not_integer(matrix_a) and _check_not_integer(matrix_b): _verify_matrix_sizes(matrix_a, matrix_b) - matrix_c = [[i + j for i, j in zip(m, n)] - for m, n in zip(matrix_a, matrix_b)] - return matrix_c + return [[i + j for i, j in zip(m, n)] for m, n in zip(matrix_a, matrix_b)] def subtract(matrix_a: List[list], matrix_b: List[list]) -> List[list]: @@ -28,9 +26,7 @@ def subtract(matrix_a: List[list], matrix_b: List[list]) -> List[list]: """ if _check_not_integer(matrix_a) and _check_not_integer(matrix_b): _verify_matrix_sizes(matrix_a, matrix_b) - matrix_c = [[i - j for i, j in zip(m, n)] - for m, n in zip(matrix_a, matrix_b)] - return matrix_c + return [[i - j for i, j in zip(m, n)] for m, n in zip(matrix_a, matrix_b)] def scalar_multiply(matrix: List[list], n: int) -> List[list]: @@ -101,9 +97,8 @@ def minor(matrix: List[list], row: int, column: int) -> List[list]: >>> minor([[1, 2], [3, 4]], 1, 1) [[1]] """ - minor = matrix[:row] + matrix[row + 1:] - minor = [row[:column] + row[column + 1:] for row in minor] - return minor + minor = matrix[:row] + matrix[row + 1 :] + return [row[:column] + row[column + 1 :] for row in minor] def determinant(matrix: List[list]) -> int: @@ -156,8 +151,7 @@ def _shape(matrix: List[list]) -> list: return list((len(matrix), len(matrix[0]))) -def _verify_matrix_sizes( - matrix_a: List[list], matrix_b: List[list]) -> Tuple[list]: +def _verify_matrix_sizes(matrix_a: List[list], matrix_b: List[list]) -> Tuple[list]: shape = _shape(matrix_a) shape += _shape(matrix_b) if shape[0] != shape[2] or shape[1] != shape[3]: @@ -171,12 +165,9 @@ def _verify_matrix_sizes( def main(): matrix_a = [[12, 10], [3, 9]] matrix_b = [[3, 4], [7, 4]] - matrix_c = [[11, 12, 13, 14], [21, 22, 23, 24], - [31, 32, 33, 34], [41, 42, 43, 44]] + matrix_c = [[11, 12, 13, 14], [21, 22, 23, 24], [31, 32, 33, 34], [41, 42, 43, 44]] matrix_d = [[3, 0, 2], [2, 0, -2], [0, 1, 1]] - print( - f"Add Operation, {matrix_a} + {matrix_b}" - f" = {add(matrix_a, matrix_b)} \n") + print(f"Add Operation, {matrix_a} + {matrix_b} = {add(matrix_a, matrix_b)} \n") print( f"Multiply Operation, {matrix_a} * {matrix_b}", f"= {multiply(matrix_a, matrix_b)} \n", diff --git a/matrix/searching_in_sorted_matrix.py b/matrix/searching_in_sorted_matrix.py index 996805d4e231..470fc01dfb8f 100644 --- a/matrix/searching_in_sorted_matrix.py +++ b/matrix/searching_in_sorted_matrix.py @@ -1,9 +1,10 @@ -from typing import List +from typing import List, Union def search_in_a_sorted_matrix( - mat: List[list], m: int, n: int, key: int or float) -> None: - ''' + mat: List[list], m: int, n: int, key: Union[int, float] +) -> None: + """ >>> search_in_a_sorted_matrix(\ [[2, 5, 7], [4, 8, 13], [9, 11, 15], [12, 17, 20]], 3, 3, 5) Key 5 found at row- 1 column- 2 @@ -16,7 +17,7 @@ def search_in_a_sorted_matrix( >>> search_in_a_sorted_matrix(\ [[2.1, 5, 7], [4, 8, 13], [9, 11, 15], [12, 17, 20]], 3, 3, 2.2) Key 2.2 not found - ''' + """ i, j = m - 1, 0 while i >= 0 and j < n: if key == mat[i][j]: @@ -38,5 +39,6 @@ def main(): if __name__ == "__main__": import doctest + doctest.testmod() main() From 64bef606b606c3b2e78572b88a78ace41471abc6 Mon Sep 17 00:00:00 2001 From: Reinhold <63191266+reinhold-b@users.noreply.github.com> Date: Sat, 4 Jul 2020 11:23:23 +0200 Subject: [PATCH 0659/1071] double_linear_search algorithm (#2161) * linear search iterating from both array sides * Update double_linear_search.py * Update double_linear_search.py * added doctests * updated doctests * Update double_linear_search.py * Update double_linear_search.py * added blank after >>> * made all the requested changes * Update double_linear_search.py * Update double_linear_search.py Co-authored-by: Christian Clauss --- searches/double_linear_search.py | 37 ++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 searches/double_linear_search.py diff --git a/searches/double_linear_search.py b/searches/double_linear_search.py new file mode 100644 index 000000000000..6056f00fc2bb --- /dev/null +++ b/searches/double_linear_search.py @@ -0,0 +1,37 @@ +from typing import List + + +def double_linear_search(array: List[int], search_item: int) -> int: + """ + Iterate through the array from both sides to find the index of search_item. + + :param array: the array to be searched + :param search_item: the item to be searched + :return the index of search_item, if search_item is in array, else -1 + + Examples: + >>> double_linear_search([1, 5, 5, 10], 1) + 0 + >>> double_linear_search([1, 5, 5, 10], 5) + 1 + >>> double_linear_search([1, 5, 5, 10], 100) + -1 + >>> double_linear_search([1, 5, 5, 10], 10) + 3 + """ + # define the start and end index of the given array + start_ind, end_ind = 0, len(array) - 1 + while start_ind <= end_ind: + if array[start_ind] == search_item: + return start_ind + elif array[end_ind] == search_item: + return end_ind + else: + start_ind += 1 + end_ind -= 1 + # returns -1 if search_item is not found in array + return -1 + + +if __name__ == "__main__": + print(double_linear_search(list(range(100)), 40)) From 25d9d819a296301b90b84766167ceb476391db2e Mon Sep 17 00:00:00 2001 From: Akash Shroff <63399889+akashvshroff@users.noreply.github.com> Date: Sun, 5 Jul 2020 14:51:32 +0530 Subject: [PATCH 0660/1071] Gale Shapley Algorithm (#2100) * Gale Shapley Algorithm Implementation of a Nobel prize-winning algorithm that determines a stable matching in a bipartite graph. * Update graphs/gale_shapley_bigraph.py Co-authored-by: Christian Clauss * Fixed some flake8 issues. * Updated it to donors and recipients * description changes Co-authored-by: Christian Clauss * description changes Co-authored-by: Christian Clauss * description changes Co-authored-by: Christian Clauss * Edited the line lengths * Update gale_shapley_bigraph.py * Update gale_shapley_bigraph.py Co-authored-by: Christian Clauss --- graphs/gale_shapley_bigraph.py | 44 ++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 graphs/gale_shapley_bigraph.py diff --git a/graphs/gale_shapley_bigraph.py b/graphs/gale_shapley_bigraph.py new file mode 100644 index 000000000000..07a3922f86ec --- /dev/null +++ b/graphs/gale_shapley_bigraph.py @@ -0,0 +1,44 @@ +from typing import List + + +def stable_matching(donor_pref: List[int], recipient_pref: List[int]) -> List[int]: + """ + Finds the stable match in any bipartite graph, i.e a pairing where no 2 objects + prefer each other over their partner. The function accepts the preferences of + oegan donors and recipients (where both are assigned numbers from 0 to n-1) and + returns a list where the index position corresponds to the donor and value at the + index is the organ recipient. + + To better understand the algorithm, see also: + https://github.com/akashvshroff/Gale_Shapley_Stable_Matching (README). + https://www.youtube.com/watch?v=Qcv1IqHWAzg&t=13s (Numberphile YouTube). + + >>> donor_pref = [[0, 1, 3, 2], [0, 2, 3, 1], [1, 0, 2, 3], [0, 3, 1, 2]] + >>> recipient_pref = [[3, 1, 2, 0], [3, 1, 0, 2], [0, 3, 1, 2], [1, 0, 3, 2]] + >>> print(stable_matching(donor_pref, recipient_pref)) + [1, 2, 3, 0] + """ + assert len(donor_pref) == len(recipient_pref) + n = len(donor_pref) + unmatched_donors = list(range(n)) + donor_record = [-1] * n # who the donor has donated to + rec_record = [-1] * n # who the recipient has received from + num_donations = [0] * n + while unmatched_donors: + donor = unmatched_donors[0] + donor_preference = donor_pref[donor] + recipient = donor_preference[num_donations[donor]] + num_donations[donor] += 1 + rec_preference = recipient_pref[recipient] + prev_donor = rec_record[recipient] + if prev_donor != -1: + if rec_preference.index(prev_donor) > rec_preference.index(donor): + rec_record[recipient] = donor + donor_record[donor] = recipient + unmatched_donors.append(prev_donor) + unmatched_donors.remove(donor) + else: + rec_record[recipient] = donor + donor_record[donor] = recipient + unmatched_donors.remove(donor) + return donor_record From cd3e8f95a018b64367e52a20ded6bce7d28a1bfa Mon Sep 17 00:00:00 2001 From: Christian Clauss Date: Mon, 6 Jul 2020 05:18:18 +0200 Subject: [PATCH 0661/1071] isort --profile black --recursive . (#2170) * isort --profile black --recursive . * Update codespell.yml * typo: vertices * typo: Explanation * typo: Explanation * Fix typos --- .github/workflows/autoblack.yml | 3 ++- .github/workflows/codespell.yml | 4 ++-- dynamic_programming/max_non_adjacent_sum.py | 2 +- dynamic_programming/minimum_cost_path.py | 2 +- graphs/connected_components.py | 2 +- machine_learning/k_means_clust.py | 6 +++--- 6 files changed, 10 insertions(+), 9 deletions(-) diff --git a/.github/workflows/autoblack.yml b/.github/workflows/autoblack.yml index 44249f78725c..25a04f2767c8 100644 --- a/.github/workflows/autoblack.yml +++ b/.github/workflows/autoblack.yml @@ -11,12 +11,13 @@ jobs: steps: - uses: actions/checkout@v1 # Use v1, NOT v2 - uses: actions/setup-python@v2 - - run: pip install black + - run: pip install black isort - run: black --check . - name: If needed, commit black changes to a new pull request if: failure() run: | black . + isort --profile black --recursive . git config --global user.name github-actions git config --global user.email '${GITHUB_ACTOR}@users.noreply.github.com' git remote set-url origin https://x-access-token:${{ secrets.GITHUB_TOKEN }}@github.com/$GITHUB_REPOSITORY diff --git a/.github/workflows/codespell.yml b/.github/workflows/codespell.yml index 30f2f34b47ce..3479b0218a69 100644 --- a/.github/workflows/codespell.yml +++ b/.github/workflows/codespell.yml @@ -13,5 +13,5 @@ jobs: SKIP="./.*,./other/dictionary.txt,./other/words,./project_euler/problem_22/p022_names.txt" codespell -L ans,fo,hist,iff,secant,tim --skip=$SKIP --quiet-level=2 - name: Codespell comment - if: ${{ failure() }} - uses: plettich/python_codespell_action@master + if: ${{ failure() }} + uses: plettich/python_codespell_action@master diff --git a/dynamic_programming/max_non_adjacent_sum.py b/dynamic_programming/max_non_adjacent_sum.py index b9f99a226bd9..15dd8ce664ed 100644 --- a/dynamic_programming/max_non_adjacent_sum.py +++ b/dynamic_programming/max_non_adjacent_sum.py @@ -1,4 +1,4 @@ -# Video Explaination: https://www.youtube.com/watch?v=6w60Zi1NtL8&feature=emb_logo +# Video Explanation: https://www.youtube.com/watch?v=6w60Zi1NtL8&feature=emb_logo from typing import List diff --git a/dynamic_programming/minimum_cost_path.py b/dynamic_programming/minimum_cost_path.py index a8d424eb2790..09295a4fafbe 100644 --- a/dynamic_programming/minimum_cost_path.py +++ b/dynamic_programming/minimum_cost_path.py @@ -1,4 +1,4 @@ -# Youtube Explaination: https://www.youtube.com/watch?v=lBRtnuxg-gU +# Youtube Explanation: https://www.youtube.com/watch?v=lBRtnuxg-gU from typing import List diff --git a/graphs/connected_components.py b/graphs/connected_components.py index 6bcc160a9ab7..4af7803d74a7 100644 --- a/graphs/connected_components.py +++ b/graphs/connected_components.py @@ -12,7 +12,7 @@ def dfs(graph: dict, vert: int, visited: list) -> list: """ - Use depth first search to find all vertexes + Use depth first search to find all vertices being in the same component as initial vertex >>> dfs(test_graph_1, 0, 5 * [False]) [0, 1, 3, 2] diff --git a/machine_learning/k_means_clust.py b/machine_learning/k_means_clust.py index d5fa31135073..9f6c65f58fd6 100644 --- a/machine_learning/k_means_clust.py +++ b/machine_learning/k_means_clust.py @@ -250,7 +250,7 @@ def ReportGenerator( df["dummy"] = 1 numeric_cols = df.select_dtypes(np.number).columns report = ( - df.groupby(["Cluster"])[ # constract report dataframe + df.groupby(["Cluster"])[ # construct report dataframe numeric_cols ] # group by cluster number .agg( @@ -289,14 +289,14 @@ def ReportGenerator( clustersize = report[ (report["Features"] == "dummy") & (report["Type"] == "count") - ] # caclulating size of cluster(count of clientID's) + ] # calculate the size of cluster(count of clientID's) clustersize.Type = ( "ClusterSize" # rename created cluster df to match report column names ) clustersize.Features = "# of Customers" clusterproportion = pd.DataFrame( clustersize.iloc[:, 2:].values - / clustersize.iloc[:, 2:].values.sum() # caclulating proportion of cluster + / clustersize.iloc[:, 2:].values.sum() # calculating the proportion of cluster ) clusterproportion[ "Type" From 5f4da5d616926dbe77ece828986b8d19c7d65cb5 Mon Sep 17 00:00:00 2001 From: Christian Clauss Date: Mon, 6 Jul 2020 09:44:19 +0200 Subject: [PATCH 0662/1071] isort --profile black . (#2181) * updating DIRECTORY.md * isort --profile black . * Black after * updating DIRECTORY.md Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> --- .github/workflows/autoblack.yml | 2 +- DIRECTORY.md | 2 ++ arithmetic_analysis/in_static_equilibrium.py | 3 ++- arithmetic_analysis/newton_raphson.py | 2 +- ciphers/hill_cipher.py | 2 +- ciphers/playfair_cipher.py | 2 +- compression/burrows_wheeler.py | 2 +- computer_vision/harriscorner.py | 2 +- .../binary_tree/non_recursive_segment_tree.py | 2 +- .../binary_tree/segment_tree_other.py | 3 +-- data_structures/hashing/double_hash.py | 3 +-- .../hashing/hash_table_with_linked_list.py | 3 ++- .../convert_to_negative.py | 3 +-- digital_image_processing/dithering/burkes.py | 2 +- .../edge_detection/canny.py | 1 + .../filters/bilateral_filter.py | 4 ++-- digital_image_processing/filters/convolve.py | 4 ++-- .../filters/gaussian_filter.py | 5 ++-- .../filters/median_filter.py | 5 ++-- .../filters/sobel_filter.py | 3 ++- .../histogram_stretch.py | 5 ++-- digital_image_processing/resize/resize.py | 2 +- digital_image_processing/rotation/rotation.py | 4 ++-- digital_image_processing/sepia.py | 3 +-- .../test_digital_image_processing.py | 24 +++++++++---------- dynamic_programming/fractional_knapsack.py | 2 +- dynamic_programming/max_sub_array.py | 3 ++- .../optimal_binary_search_tree.py | 2 -- fuzzy_logic/fuzzy_operations.py | 3 +-- geodesy/lamberts_ellipsoidal_distance.py | 1 + graphics/bezier_curve.py | 4 ++-- graphs/basic_graphs.py | 1 - graphs/breadth_first_search_2.py | 3 +-- graphs/depth_first_search.py | 3 +-- ...irected_and_undirected_(weighted)_graph.py | 4 ++-- graphs/multi_heuristic_astar.py | 1 + greedy_method/test_knapsack.py | 1 + hashes/sha1.py | 3 +-- linear_algebra/src/test_linear_algebra.py | 11 +++++++-- machine_learning/gaussian_naive_bayes.py | 7 +++--- machine_learning/k_means_clust.py | 5 ++-- machine_learning/k_nearest_neighbours.py | 3 ++- machine_learning/knn_sklearn.py | 2 +- .../linear_discriminant_analysis.py | 4 +--- machine_learning/linear_regression.py | 2 +- machine_learning/logistic_regression.py | 6 ++--- machine_learning/lstm/lstm_prediction.py | 6 ++--- .../multilayer_perceptron_classifier.py | 1 - machine_learning/polymonial_regression.py | 2 +- machine_learning/random_forest_classifier.py | 5 ++-- machine_learning/random_forest_regressor.py | 6 ++--- .../sequential_minimum_optimization.py | 2 +- machine_learning/support_vector_machines.py | 2 +- maths/3n_plus_1.py | 2 +- maths/fibonacci.py | 4 ++-- maths/gamma.py | 3 ++- maths/gaussian.py | 2 +- maths/line_length.py | 2 +- maths/mobius_function.py | 2 +- maths/newton_raphson.py | 3 +-- maths/prime_numbers.py | 2 +- maths/relu.py | 2 +- maths/volume.py | 2 +- maths/zellers_congruence.py | 2 +- matrix/tests/test_matrix_operation.py | 4 +++- .../back_propagation_neural_network.py | 3 +-- neural_network/convolution_neural_network.py | 3 ++- other/least_recently_used.py | 2 +- other/sierpinski_triangle.py | 2 +- project_euler/problem_07/sol3.py | 2 +- project_euler/problem_42/solution42.py | 1 - scheduling/shortest_job_first.py | 2 +- scripts/validate_filenames.py | 2 +- searches/hill_climbing.py | 2 +- searches/simulated_annealing.py | 2 +- searches/tabu_search.py | 3 +-- sorts/external_sort.py | 2 +- sorts/random_normal_distribution_quicksort.py | 1 + web_programming/crawl_google_results.py | 3 +-- web_programming/get_imdbtop.py | 2 +- 80 files changed, 123 insertions(+), 127 deletions(-) diff --git a/.github/workflows/autoblack.yml b/.github/workflows/autoblack.yml index 25a04f2767c8..25d291c6ffbb 100644 --- a/.github/workflows/autoblack.yml +++ b/.github/workflows/autoblack.yml @@ -17,7 +17,7 @@ jobs: if: failure() run: | black . - isort --profile black --recursive . + isort --profile black . git config --global user.name github-actions git config --global user.email '${GITHUB_ACTOR}@users.noreply.github.com' git remote set-url origin https://x-access-token:${{ secrets.GITHUB_TOKEN }}@github.com/$GITHUB_REPOSITORY diff --git a/DIRECTORY.md b/DIRECTORY.md index 34ee376be1c3..17940c6a333b 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -253,6 +253,7 @@ * [Finding Bridges](https://github.com/TheAlgorithms/Python/blob/master/graphs/finding_bridges.py) * [Frequent Pattern Graph Miner](https://github.com/TheAlgorithms/Python/blob/master/graphs/frequent_pattern_graph_miner.py) * [G Topological Sort](https://github.com/TheAlgorithms/Python/blob/master/graphs/g_topological_sort.py) + * [Gale Shapley Bigraph](https://github.com/TheAlgorithms/Python/blob/master/graphs/gale_shapley_bigraph.py) * [Graph List](https://github.com/TheAlgorithms/Python/blob/master/graphs/graph_list.py) * [Graph Matrix](https://github.com/TheAlgorithms/Python/blob/master/graphs/graph_matrix.py) * [Graphs Floyd Warshall](https://github.com/TheAlgorithms/Python/blob/master/graphs/graphs_floyd_warshall.py) @@ -596,6 +597,7 @@ ## Searches * [Binary Search](https://github.com/TheAlgorithms/Python/blob/master/searches/binary_search.py) + * [Double Linear Search](https://github.com/TheAlgorithms/Python/blob/master/searches/double_linear_search.py) * [Fibonacci Search](https://github.com/TheAlgorithms/Python/blob/master/searches/fibonacci_search.py) * [Hill Climbing](https://github.com/TheAlgorithms/Python/blob/master/searches/hill_climbing.py) * [Interpolation Search](https://github.com/TheAlgorithms/Python/blob/master/searches/interpolation_search.py) diff --git a/arithmetic_analysis/in_static_equilibrium.py b/arithmetic_analysis/in_static_equilibrium.py index addaff888f7f..dd7fa706143e 100644 --- a/arithmetic_analysis/in_static_equilibrium.py +++ b/arithmetic_analysis/in_static_equilibrium.py @@ -6,9 +6,10 @@ mypy : passed """ -from numpy import array, cos, sin, radians, cross # type: ignore from typing import List +from numpy import array, cos, cross, radians, sin # type: ignore + def polar_force( magnitude: float, angle: float, radian_mode: bool = False diff --git a/arithmetic_analysis/newton_raphson.py b/arithmetic_analysis/newton_raphson.py index a3e8bbf0fc21..890cff060ec8 100644 --- a/arithmetic_analysis/newton_raphson.py +++ b/arithmetic_analysis/newton_raphson.py @@ -2,9 +2,9 @@ # Author: Syed Haseeb Shah (github.com/QuantumNovice) # The Newton-Raphson method (also known as Newton's method) is a way to # quickly find a good approximation for the root of a real-valued function - from decimal import Decimal from math import * # noqa: F401, F403 + from sympy import diff diff --git a/ciphers/hill_cipher.py b/ciphers/hill_cipher.py index 82382d873baf..0014c8693bc6 100644 --- a/ciphers/hill_cipher.py +++ b/ciphers/hill_cipher.py @@ -35,8 +35,8 @@ https://www.youtube.com/watch?v=4RhLNDqcjpA """ - import string + import numpy diff --git a/ciphers/playfair_cipher.py b/ciphers/playfair_cipher.py index 030fe8155a69..33b52906fb05 100644 --- a/ciphers/playfair_cipher.py +++ b/ciphers/playfair_cipher.py @@ -1,5 +1,5 @@ -import string import itertools +import string def chunker(seq, size): diff --git a/compression/burrows_wheeler.py b/compression/burrows_wheeler.py index 2a08c6092cda..03912f80e1a7 100644 --- a/compression/burrows_wheeler.py +++ b/compression/burrows_wheeler.py @@ -10,7 +10,7 @@ original character. The BWT is thus a "free" method of improving the efficiency of text compression algorithms, costing only some extra computation. """ -from typing import List, Dict +from typing import Dict, List def all_rotations(s: str) -> List[str]: diff --git a/computer_vision/harriscorner.py b/computer_vision/harriscorner.py index 35302f01411b..fb7f560f7873 100644 --- a/computer_vision/harriscorner.py +++ b/computer_vision/harriscorner.py @@ -1,5 +1,5 @@ -import numpy as np import cv2 +import numpy as np """ Harris Corner Detector diff --git a/data_structures/binary_tree/non_recursive_segment_tree.py b/data_structures/binary_tree/non_recursive_segment_tree.py index 97851af937d9..cdcf1fa8dd2d 100644 --- a/data_structures/binary_tree/non_recursive_segment_tree.py +++ b/data_structures/binary_tree/non_recursive_segment_tree.py @@ -35,7 +35,7 @@ >>> st.query(0, 2) [1, 2, 3] """ -from typing import List, Callable, TypeVar +from typing import Callable, List, TypeVar T = TypeVar("T") diff --git a/data_structures/binary_tree/segment_tree_other.py b/data_structures/binary_tree/segment_tree_other.py index c3ab493d5f4f..df98eeffb3c6 100644 --- a/data_structures/binary_tree/segment_tree_other.py +++ b/data_structures/binary_tree/segment_tree_other.py @@ -3,9 +3,8 @@ allowing queries to be done later in log(N) time function takes 2 values and returns a same type value """ - -from queue import Queue from collections.abc import Sequence +from queue import Queue class SegmentTreeNode(object): diff --git a/data_structures/hashing/double_hash.py b/data_structures/hashing/double_hash.py index ce4454db0bef..9c6c8fed6a00 100644 --- a/data_structures/hashing/double_hash.py +++ b/data_structures/hashing/double_hash.py @@ -1,7 +1,6 @@ #!/usr/bin/env python3 - from hash_table import HashTable -from number_theory.prime_numbers import next_prime, check_prime +from number_theory.prime_numbers import check_prime, next_prime class DoubleHash(HashTable): diff --git a/data_structures/hashing/hash_table_with_linked_list.py b/data_structures/hashing/hash_table_with_linked_list.py index 48d93bbc5cff..94934e37b65e 100644 --- a/data_structures/hashing/hash_table_with_linked_list.py +++ b/data_structures/hashing/hash_table_with_linked_list.py @@ -1,6 +1,7 @@ -from hash_table import HashTable from collections import deque +from hash_table import HashTable + class HashTableWithLinkedList(HashTable): def __init__(self, *args, **kwargs): diff --git a/digital_image_processing/convert_to_negative.py b/digital_image_processing/convert_to_negative.py index cba503938aec..7df44138973c 100644 --- a/digital_image_processing/convert_to_negative.py +++ b/digital_image_processing/convert_to_negative.py @@ -1,8 +1,7 @@ """ Implemented an algorithm using opencv to convert a colored image into its negative """ - -from cv2 import imread, imshow, waitKey, destroyAllWindows +from cv2 import destroyAllWindows, imread, imshow, waitKey def convert_to_negative(img): diff --git a/digital_image_processing/dithering/burkes.py b/digital_image_processing/dithering/burkes.py index 0de21f4c009b..2bf0bbe03225 100644 --- a/digital_image_processing/dithering/burkes.py +++ b/digital_image_processing/dithering/burkes.py @@ -1,8 +1,8 @@ """ Implementation Burke's algorithm (dithering) """ -from cv2 import destroyAllWindows, imread, imshow, waitKey import numpy as np +from cv2 import destroyAllWindows, imread, imshow, waitKey class Burkes: diff --git a/digital_image_processing/edge_detection/canny.py b/digital_image_processing/edge_detection/canny.py index 477ffcb9bbb1..295b4d825c12 100644 --- a/digital_image_processing/edge_detection/canny.py +++ b/digital_image_processing/edge_detection/canny.py @@ -1,5 +1,6 @@ import cv2 import numpy as np + from digital_image_processing.filters.convolve import img_convolve from digital_image_processing.filters.sobel_filter import sobel_filter diff --git a/digital_image_processing/filters/bilateral_filter.py b/digital_image_processing/filters/bilateral_filter.py index 753d6ddb7a3f..76ae4dd20345 100644 --- a/digital_image_processing/filters/bilateral_filter.py +++ b/digital_image_processing/filters/bilateral_filter.py @@ -9,11 +9,11 @@ Output: img:A 2d zero padded image with values in between 0 and 1 """ +import math +import sys import cv2 import numpy as np -import math -import sys def vec_gaussian(img: np.ndarray, variance: float) -> np.ndarray: diff --git a/digital_image_processing/filters/convolve.py b/digital_image_processing/filters/convolve.py index ec500d940366..299682010da6 100644 --- a/digital_image_processing/filters/convolve.py +++ b/digital_image_processing/filters/convolve.py @@ -1,8 +1,8 @@ # @Author : lightXu # @File : convolve.py # @Time : 2019/7/8 0008 下午 16:13 -from cv2 import imread, cvtColor, COLOR_BGR2GRAY, imshow, waitKey -from numpy import array, zeros, ravel, pad, dot, uint8 +from cv2 import COLOR_BGR2GRAY, cvtColor, imread, imshow, waitKey +from numpy import array, dot, pad, ravel, uint8, zeros def im2col(image, block_size): diff --git a/digital_image_processing/filters/gaussian_filter.py b/digital_image_processing/filters/gaussian_filter.py index 79026ddcc57a..87fa67fb65ea 100644 --- a/digital_image_processing/filters/gaussian_filter.py +++ b/digital_image_processing/filters/gaussian_filter.py @@ -1,10 +1,11 @@ """ Implementation of gaussian filter algorithm """ -from cv2 import imread, cvtColor, COLOR_BGR2GRAY, imshow, waitKey -from numpy import pi, mgrid, exp, square, zeros, ravel, dot, uint8 from itertools import product +from cv2 import COLOR_BGR2GRAY, cvtColor, imread, imshow, waitKey +from numpy import dot, exp, mgrid, pi, ravel, square, uint8, zeros + def gen_gaussian_kernel(k_size, sigma): center = k_size // 2 diff --git a/digital_image_processing/filters/median_filter.py b/digital_image_processing/filters/median_filter.py index 151ef8a55df1..174018569d62 100644 --- a/digital_image_processing/filters/median_filter.py +++ b/digital_image_processing/filters/median_filter.py @@ -1,9 +1,8 @@ """ Implementation of median filter algorithm """ - -from cv2 import imread, cvtColor, COLOR_BGR2GRAY, imshow, waitKey -from numpy import zeros_like, ravel, sort, multiply, divide, int8 +from cv2 import COLOR_BGR2GRAY, cvtColor, imread, imshow, waitKey +from numpy import divide, int8, multiply, ravel, sort, zeros_like def median_filter(gray_img, mask=3): diff --git a/digital_image_processing/filters/sobel_filter.py b/digital_image_processing/filters/sobel_filter.py index 822d49fe38a1..33284a32f424 100644 --- a/digital_image_processing/filters/sobel_filter.py +++ b/digital_image_processing/filters/sobel_filter.py @@ -2,7 +2,8 @@ # @File : sobel_filter.py # @Time : 2019/7/8 0008 下午 16:26 import numpy as np -from cv2 import imread, cvtColor, COLOR_BGR2GRAY, imshow, waitKey +from cv2 import COLOR_BGR2GRAY, cvtColor, imread, imshow, waitKey + from digital_image_processing.filters.convolve import img_convolve diff --git a/digital_image_processing/histogram_equalization/histogram_stretch.py b/digital_image_processing/histogram_equalization/histogram_stretch.py index d4a6c08418ee..0288a2c1fcf5 100644 --- a/digital_image_processing/histogram_equalization/histogram_stretch.py +++ b/digital_image_processing/histogram_equalization/histogram_stretch.py @@ -6,10 +6,9 @@ import copy import os -import numpy as np - import cv2 -import matplotlib.pyplot as plt +import numpy as np +from matplotlib import pyplot as plt class contrastStretch: diff --git a/digital_image_processing/resize/resize.py b/digital_image_processing/resize/resize.py index f33e80e580de..4836521f9f58 100644 --- a/digital_image_processing/resize/resize.py +++ b/digital_image_processing/resize/resize.py @@ -1,6 +1,6 @@ """ Multiple image resizing techniques """ import numpy as np -from cv2 import imread, imshow, waitKey, destroyAllWindows +from cv2 import destroyAllWindows, imread, imshow, waitKey class NearestNeighbour: diff --git a/digital_image_processing/rotation/rotation.py b/digital_image_processing/rotation/rotation.py index 37b45ca39897..2951f18fc0ec 100644 --- a/digital_image_processing/rotation/rotation.py +++ b/digital_image_processing/rotation/rotation.py @@ -1,6 +1,6 @@ -from matplotlib import pyplot as plt -import numpy as np import cv2 +import numpy as np +from matplotlib import pyplot as plt def get_rotation( diff --git a/digital_image_processing/sepia.py b/digital_image_processing/sepia.py index b97b4c0ae1bc..dfb5951676aa 100644 --- a/digital_image_processing/sepia.py +++ b/digital_image_processing/sepia.py @@ -1,8 +1,7 @@ """ Implemented an algorithm using opencv to tone an image with sepia technique """ - -from cv2 import imread, imshow, waitKey, destroyAllWindows +from cv2 import destroyAllWindows, imread, imshow, waitKey def make_sepia(img, factor: int): diff --git a/digital_image_processing/test_digital_image_processing.py b/digital_image_processing/test_digital_image_processing.py index fe8890de9a31..40f2f7b83b6d 100644 --- a/digital_image_processing/test_digital_image_processing.py +++ b/digital_image_processing/test_digital_image_processing.py @@ -1,21 +1,21 @@ """ PyTest's for Digital Image Processing """ - -import digital_image_processing.edge_detection.canny as canny -import digital_image_processing.filters.gaussian_filter as gg -import digital_image_processing.filters.median_filter as med -import digital_image_processing.filters.sobel_filter as sob -import digital_image_processing.filters.convolve as conv -import digital_image_processing.change_contrast as cc -import digital_image_processing.convert_to_negative as cn -import digital_image_processing.sepia as sp -import digital_image_processing.dithering.burkes as bs -import digital_image_processing.resize.resize as rs -from cv2 import imread, cvtColor, COLOR_BGR2GRAY +from cv2 import COLOR_BGR2GRAY, cvtColor, imread from numpy import array, uint8 from PIL import Image +from digital_image_processing import change_contrast as cc +from digital_image_processing import convert_to_negative as cn +from digital_image_processing import sepia as sp +from digital_image_processing.dithering import burkes as bs +from digital_image_processing.edge_detection import canny as canny +from digital_image_processing.filters import convolve as conv +from digital_image_processing.filters import gaussian_filter as gg +from digital_image_processing.filters import median_filter as med +from digital_image_processing.filters import sobel_filter as sob +from digital_image_processing.resize import resize as rs + img = imread(r"digital_image_processing/image_data/lena_small.jpg") gray = cvtColor(img, COLOR_BGR2GRAY) diff --git a/dynamic_programming/fractional_knapsack.py b/dynamic_programming/fractional_knapsack.py index 15210146bf66..c74af7ef8fc5 100644 --- a/dynamic_programming/fractional_knapsack.py +++ b/dynamic_programming/fractional_knapsack.py @@ -1,5 +1,5 @@ -from itertools import accumulate from bisect import bisect +from itertools import accumulate def fracKnapsack(vl, wt, W, n): diff --git a/dynamic_programming/max_sub_array.py b/dynamic_programming/max_sub_array.py index 284edb5841e4..1ca4f90bbaa2 100644 --- a/dynamic_programming/max_sub_array.py +++ b/dynamic_programming/max_sub_array.py @@ -73,9 +73,10 @@ def max_sub_array(nums: List[int]) -> int: A random simulation of this algorithm. """ import time - import matplotlib.pyplot as plt from random import randint + from matplotlib import pyplot as plt + inputs = [10, 100, 1000, 10000, 50000, 100000, 200000, 300000, 400000, 500000] tim = [] for i in inputs: diff --git a/dynamic_programming/optimal_binary_search_tree.py b/dynamic_programming/optimal_binary_search_tree.py index e6f93f85ef0f..0d94c1b61d39 100644 --- a/dynamic_programming/optimal_binary_search_tree.py +++ b/dynamic_programming/optimal_binary_search_tree.py @@ -16,9 +16,7 @@ # frequencies will be placed near the root of the tree while the nodes # with low frequencies will be placed near the leaves of the tree thus # reducing search time in the most frequent instances. - import sys - from random import randint diff --git a/fuzzy_logic/fuzzy_operations.py b/fuzzy_logic/fuzzy_operations.py index 795a8cde653f..0f573f158663 100644 --- a/fuzzy_logic/fuzzy_operations.py +++ b/fuzzy_logic/fuzzy_operations.py @@ -9,7 +9,6 @@ import numpy as np import skfuzzy as fuzz - if __name__ == "__main__": # Create universe of discourse in Python using linspace () X = np.linspace(start=0, stop=75, num=75, endpoint=True, retstep=False) @@ -45,7 +44,7 @@ # max-product composition # Plot each set A, set B and each operation result using plot() and subplot(). - import matplotlib.pyplot as plt + from matplotlib import pyplot as plt plt.figure() diff --git a/geodesy/lamberts_ellipsoidal_distance.py b/geodesy/lamberts_ellipsoidal_distance.py index 969e70befd0b..4a318265e5e6 100644 --- a/geodesy/lamberts_ellipsoidal_distance.py +++ b/geodesy/lamberts_ellipsoidal_distance.py @@ -1,4 +1,5 @@ from math import atan, cos, radians, sin, tan + from haversine_distance import haversine_distance diff --git a/graphics/bezier_curve.py b/graphics/bezier_curve.py index 512efadf86ee..48755647e02d 100644 --- a/graphics/bezier_curve.py +++ b/graphics/bezier_curve.py @@ -1,7 +1,7 @@ # https://en.wikipedia.org/wiki/B%C3%A9zier_curve # https://www.tutorialspoint.com/computer_graphics/computer_graphics_curves.htm - from typing import List, Tuple + from scipy.special import comb @@ -78,7 +78,7 @@ def plot_curve(self, step_size: float = 0.01): step_size: defines the step(s) at which to evaluate the Bezier curve. The smaller the step size, the finer the curve produced. """ - import matplotlib.pyplot as plt + from matplotlib import pyplot as plt to_plot_x: List[float] = [] # x coordinates of points to plot to_plot_y: List[float] = [] # y coordinates of points to plot diff --git a/graphs/basic_graphs.py b/graphs/basic_graphs.py index 1cbd82a2bd08..6e3c63251527 100644 --- a/graphs/basic_graphs.py +++ b/graphs/basic_graphs.py @@ -1,6 +1,5 @@ from collections import deque - if __name__ == "__main__": # Accept No. of Nodes and edges n, m = map(int, input().split(" ")) diff --git a/graphs/breadth_first_search_2.py b/graphs/breadth_first_search_2.py index 0c87b5d8bf3d..293a1012f61f 100644 --- a/graphs/breadth_first_search_2.py +++ b/graphs/breadth_first_search_2.py @@ -12,8 +12,7 @@ mark w as explored add w to Q (at the end) """ - -from typing import Set, Dict +from typing import Dict, Set G = { "A": ["B", "C"], diff --git a/graphs/depth_first_search.py b/graphs/depth_first_search.py index f3e0ed4ebaa6..1e2231907b78 100644 --- a/graphs/depth_first_search.py +++ b/graphs/depth_first_search.py @@ -11,8 +11,7 @@ if v unexplored: DFS(G, v) """ - -from typing import Set, Dict +from typing import Dict, Set def depth_first_search(graph: Dict, start: str) -> Set[int]: diff --git a/graphs/directed_and_undirected_(weighted)_graph.py b/graphs/directed_and_undirected_(weighted)_graph.py index 0312e982a9e0..267111a3a401 100644 --- a/graphs/directed_and_undirected_(weighted)_graph.py +++ b/graphs/directed_and_undirected_(weighted)_graph.py @@ -1,7 +1,7 @@ -from collections import deque -import random as rand import math as math +import random as rand import time +from collections import deque # the default weight is 1 if not assigned but all the implementation is weighted diff --git a/graphs/multi_heuristic_astar.py b/graphs/multi_heuristic_astar.py index 386aab695bb0..77ca5760d5f0 100644 --- a/graphs/multi_heuristic_astar.py +++ b/graphs/multi_heuristic_astar.py @@ -1,4 +1,5 @@ import heapq + import numpy as np diff --git a/greedy_method/test_knapsack.py b/greedy_method/test_knapsack.py index 71a259a1ec46..f3ae624ea7aa 100644 --- a/greedy_method/test_knapsack.py +++ b/greedy_method/test_knapsack.py @@ -1,4 +1,5 @@ import unittest + import greedy_knapsack as kp diff --git a/hashes/sha1.py b/hashes/sha1.py index 26069fb9b7fb..04ecdd788039 100644 --- a/hashes/sha1.py +++ b/hashes/sha1.py @@ -23,10 +23,9 @@ the final hash. Reference: https://deadhacker.com/2006/02/21/sha-1-illustrated/ """ - import argparse -import struct import hashlib # hashlib is only used inside the Test class +import struct import unittest diff --git a/linear_algebra/src/test_linear_algebra.py b/linear_algebra/src/test_linear_algebra.py index 21fed9529ac0..be6245747f87 100644 --- a/linear_algebra/src/test_linear_algebra.py +++ b/linear_algebra/src/test_linear_algebra.py @@ -6,9 +6,16 @@ This file contains the test-suite for the linear algebra library. """ - import unittest -from lib import Matrix, Vector, axpy, squareZeroMatrix, unitBasisVector, zeroVector + +from lib import ( + Matrix, + Vector, + axpy, + squareZeroMatrix, + unitBasisVector, + zeroVector, +) class Test(unittest.TestCase): diff --git a/machine_learning/gaussian_naive_bayes.py b/machine_learning/gaussian_naive_bayes.py index 24c884adb98a..c200aa5a4d2d 100644 --- a/machine_learning/gaussian_naive_bayes.py +++ b/machine_learning/gaussian_naive_bayes.py @@ -1,10 +1,9 @@ # Gaussian Naive Bayes Example - -from sklearn.naive_bayes import GaussianNB -from sklearn.metrics import plot_confusion_matrix +from matplotlib import pyplot as plt from sklearn.datasets import load_iris +from sklearn.metrics import plot_confusion_matrix from sklearn.model_selection import train_test_split -import matplotlib.pyplot as plt +from sklearn.naive_bayes import GaussianNB def main(): diff --git a/machine_learning/k_means_clust.py b/machine_learning/k_means_clust.py index 9f6c65f58fd6..071c58db256b 100644 --- a/machine_learning/k_means_clust.py +++ b/machine_learning/k_means_clust.py @@ -52,11 +52,12 @@ """ +import warnings + import numpy as np import pandas as pd from matplotlib import pyplot as plt from sklearn.metrics import pairwise_distances -import warnings warnings.filterwarnings("ignore") @@ -193,7 +194,7 @@ def kmeans( # Mock test below if False: # change to true to run this test case. - import sklearn.datasets as ds + from sklearn import datasets as ds dataset = ds.load_iris() k = 3 diff --git a/machine_learning/k_nearest_neighbours.py b/machine_learning/k_nearest_neighbours.py index 481a8e1dbcd0..e90ea09a58c1 100644 --- a/machine_learning/k_nearest_neighbours.py +++ b/machine_learning/k_nearest_neighbours.py @@ -1,5 +1,6 @@ -import numpy as np from collections import Counter + +import numpy as np from sklearn import datasets from sklearn.model_selection import train_test_split diff --git a/machine_learning/knn_sklearn.py b/machine_learning/knn_sklearn.py index c36a530736cd..9a9114102ff3 100644 --- a/machine_learning/knn_sklearn.py +++ b/machine_learning/knn_sklearn.py @@ -1,5 +1,5 @@ -from sklearn.model_selection import train_test_split from sklearn.datasets import load_iris +from sklearn.model_selection import train_test_split from sklearn.neighbors import KNeighborsClassifier # Load iris file diff --git a/machine_learning/linear_discriminant_analysis.py b/machine_learning/linear_discriminant_analysis.py index 01be288ea64a..b26da2628982 100644 --- a/machine_learning/linear_discriminant_analysis.py +++ b/machine_learning/linear_discriminant_analysis.py @@ -41,11 +41,9 @@ Author: @EverLookNeverSee """ - from math import log from os import name, system -from random import gauss -from random import seed +from random import gauss, seed # Make a training dataset drawn from a gaussian distribution diff --git a/machine_learning/linear_regression.py b/machine_learning/linear_regression.py index 29bb7c48589e..8c0dfebbca9d 100644 --- a/machine_learning/linear_regression.py +++ b/machine_learning/linear_regression.py @@ -7,8 +7,8 @@ fit our dataset. In this particular code, I had used a CSGO dataset (ADR vs Rating). We try to best fit a line through dataset and estimate the parameters. """ -import requests import numpy as np +import requests def collect_dataset(): diff --git a/machine_learning/logistic_regression.py b/machine_learning/logistic_regression.py index 1c1906e8e6b2..48d88ef61185 100644 --- a/machine_learning/logistic_regression.py +++ b/machine_learning/logistic_regression.py @@ -14,14 +14,12 @@ Coursera ML course https://medium.com/@martinpella/logistic-regression-from-scratch-in-python-124c5636b8ac """ - import numpy as np -import matplotlib.pyplot as plt +from matplotlib import pyplot as plt +from sklearn import datasets # get_ipython().run_line_magic('matplotlib', 'inline') -from sklearn import datasets - # In[67]: diff --git a/machine_learning/lstm/lstm_prediction.py b/machine_learning/lstm/lstm_prediction.py index fbf802f4d8ee..5452f0443f62 100644 --- a/machine_learning/lstm/lstm_prediction.py +++ b/machine_learning/lstm/lstm_prediction.py @@ -4,14 +4,12 @@ * http://colah.github.io/posts/2015-08-Understanding-LSTMs * https://en.wikipedia.org/wiki/Long_short-term_memory """ - -from keras.layers import Dense, LSTM -from keras.models import Sequential import numpy as np import pandas as pd +from keras.layers import LSTM, Dense +from keras.models import Sequential from sklearn.preprocessing import MinMaxScaler - if __name__ == "__main__": """ First part of building a model is to get the data and prepare diff --git a/machine_learning/multilayer_perceptron_classifier.py b/machine_learning/multilayer_perceptron_classifier.py index d78d2a9ed8eb..604185cef677 100644 --- a/machine_learning/multilayer_perceptron_classifier.py +++ b/machine_learning/multilayer_perceptron_classifier.py @@ -1,6 +1,5 @@ from sklearn.neural_network import MLPClassifier - X = [[0.0, 0.0], [1.0, 1.0], [1.0, 0.0], [0.0, 1.0]] y = [0, 1, 0, 0] diff --git a/machine_learning/polymonial_regression.py b/machine_learning/polymonial_regression.py index f781141f169c..374c35f7f905 100644 --- a/machine_learning/polymonial_regression.py +++ b/machine_learning/polymonial_regression.py @@ -1,5 +1,5 @@ -import matplotlib.pyplot as plt import pandas as pd +from matplotlib import pyplot as plt from sklearn.linear_model import LinearRegression # Splitting the dataset into the Training set and Test set diff --git a/machine_learning/random_forest_classifier.py b/machine_learning/random_forest_classifier.py index e7acd91346a1..6370254090f7 100644 --- a/machine_learning/random_forest_classifier.py +++ b/machine_learning/random_forest_classifier.py @@ -1,10 +1,9 @@ # Random Forest Classifier Example - +from matplotlib import pyplot as plt from sklearn.datasets import load_iris -from sklearn.model_selection import train_test_split from sklearn.ensemble import RandomForestClassifier from sklearn.metrics import plot_confusion_matrix -import matplotlib.pyplot as plt +from sklearn.model_selection import train_test_split def main(): diff --git a/machine_learning/random_forest_regressor.py b/machine_learning/random_forest_regressor.py index f78b6bbd0f42..0aade626b038 100644 --- a/machine_learning/random_forest_regressor.py +++ b/machine_learning/random_forest_regressor.py @@ -1,10 +1,8 @@ # Random Forest Regressor Example - from sklearn.datasets import load_boston -from sklearn.model_selection import train_test_split from sklearn.ensemble import RandomForestRegressor -from sklearn.metrics import mean_absolute_error -from sklearn.metrics import mean_squared_error +from sklearn.metrics import mean_absolute_error, mean_squared_error +from sklearn.model_selection import train_test_split def main(): diff --git a/machine_learning/sequential_minimum_optimization.py b/machine_learning/sequential_minimum_optimization.py index 3583b18ab2e6..98ce05c46cff 100644 --- a/machine_learning/sequential_minimum_optimization.py +++ b/machine_learning/sequential_minimum_optimization.py @@ -36,9 +36,9 @@ import sys import urllib.request -import matplotlib.pyplot as plt import numpy as np import pandas as pd +from matplotlib import pyplot as plt from sklearn.datasets import make_blobs, make_circles from sklearn.preprocessing import StandardScaler diff --git a/machine_learning/support_vector_machines.py b/machine_learning/support_vector_machines.py index 3bf54a69128d..c5e5085d8748 100644 --- a/machine_learning/support_vector_machines.py +++ b/machine_learning/support_vector_machines.py @@ -1,5 +1,5 @@ -from sklearn.datasets import load_iris from sklearn import svm +from sklearn.datasets import load_iris from sklearn.model_selection import train_test_split diff --git a/maths/3n_plus_1.py b/maths/3n_plus_1.py index d3684561827f..baaa74f89bf5 100644 --- a/maths/3n_plus_1.py +++ b/maths/3n_plus_1.py @@ -1,4 +1,4 @@ -from typing import Tuple, List +from typing import List, Tuple def n31(a: int) -> Tuple[List[int], int]: diff --git a/maths/fibonacci.py b/maths/fibonacci.py index d1e8cf7775fd..e6519035401e 100644 --- a/maths/fibonacci.py +++ b/maths/fibonacci.py @@ -7,10 +7,10 @@ reference-->Su, Francis E., et al. "Fibonacci Number Formula." Math Fun Facts. """ -import math import functools +import math import time -from decimal import getcontext, Decimal +from decimal import Decimal, getcontext getcontext().prec = 100 diff --git a/maths/gamma.py b/maths/gamma.py index 9193929baa28..69cd819ef186 100644 --- a/maths/gamma.py +++ b/maths/gamma.py @@ -1,6 +1,7 @@ import math -from scipy.integrate import quad + from numpy import inf +from scipy.integrate import quad def gamma(num: float) -> float: diff --git a/maths/gaussian.py b/maths/gaussian.py index edd52d1a4b2c..5d5800e00989 100644 --- a/maths/gaussian.py +++ b/maths/gaussian.py @@ -5,7 +5,7 @@ python : 3.7.3 """ -from numpy import pi, sqrt, exp +from numpy import exp, pi, sqrt def gaussian(x, mu: float = 0.0, sigma: float = 1.0) -> int: diff --git a/maths/line_length.py b/maths/line_length.py index 0b1ddb5b7866..6df0a916efe6 100644 --- a/maths/line_length.py +++ b/maths/line_length.py @@ -1,5 +1,5 @@ -from typing import Callable, Union import math as m +from typing import Callable, Union def line_length( diff --git a/maths/mobius_function.py b/maths/mobius_function.py index df0f66177501..4fcf35f21813 100644 --- a/maths/mobius_function.py +++ b/maths/mobius_function.py @@ -5,8 +5,8 @@ flake8 : True """ -from maths.prime_factors import prime_factors from maths.is_square_free import is_square_free +from maths.prime_factors import prime_factors def mobius(n: int) -> int: diff --git a/maths/newton_raphson.py b/maths/newton_raphson.py index 7b16d2dd9b2e..d8a98dfa6703 100644 --- a/maths/newton_raphson.py +++ b/maths/newton_raphson.py @@ -7,7 +7,6 @@ limit is reached or the gradient f'(x[n]) approaches zero. In both cases, exception is raised. If iteration limit is reached, try increasing maxiter. """ - import math as m @@ -42,7 +41,7 @@ def newton_raphson(f, x0=0, maxiter=100, step=0.0001, maxerror=1e-6, logsteps=Fa if __name__ == "__main__": - import matplotlib.pyplot as plt + from matplotlib import pyplot as plt f = lambda x: m.tanh(x) ** 2 - m.exp(3 * x) # noqa: E731 solution, error, steps = newton_raphson( diff --git a/maths/prime_numbers.py b/maths/prime_numbers.py index 1b2a29f1a203..38bebddeee41 100644 --- a/maths/prime_numbers.py +++ b/maths/prime_numbers.py @@ -1,5 +1,5 @@ -from typing import Generator import math +from typing import Generator def slow_primes(max: int) -> Generator[int, None, None]: diff --git a/maths/relu.py b/maths/relu.py index 38a937decb0c..89667ab3e73f 100644 --- a/maths/relu.py +++ b/maths/relu.py @@ -9,9 +9,9 @@ Script inspired from its corresponding Wikipedia article https://en.wikipedia.org/wiki/Rectifier_(neural_networks) """ +from typing import List import numpy as np -from typing import List def relu(vector: List[float]): diff --git a/maths/volume.py b/maths/volume.py index 04283743d71f..41d2331db3cb 100644 --- a/maths/volume.py +++ b/maths/volume.py @@ -3,8 +3,8 @@ Wikipedia reference: https://en.wikipedia.org/wiki/Volume """ -from typing import Union from math import pi, pow +from typing import Union def vol_cube(side_length: Union[int, float]) -> float: diff --git a/maths/zellers_congruence.py b/maths/zellers_congruence.py index 9c13c29210b1..8608b32f3ee3 100644 --- a/maths/zellers_congruence.py +++ b/maths/zellers_congruence.py @@ -1,5 +1,5 @@ -import datetime import argparse +import datetime def zeller(date_input: str) -> str: diff --git a/matrix/tests/test_matrix_operation.py b/matrix/tests/test_matrix_operation.py index bf049b32116e..3500dfeb0641 100644 --- a/matrix/tests/test_matrix_operation.py +++ b/matrix/tests/test_matrix_operation.py @@ -6,11 +6,13 @@ -vv -m mat_ops -p no:cacheprovider """ +import logging + # standard libraries import sys + import numpy as np import pytest -import logging # Custom/local libraries from matrix import matrix_operation as matop diff --git a/neural_network/back_propagation_neural_network.py b/neural_network/back_propagation_neural_network.py index c771dc46afc2..43e796e77be3 100644 --- a/neural_network/back_propagation_neural_network.py +++ b/neural_network/back_propagation_neural_network.py @@ -17,9 +17,8 @@ Date: 2017.11.23 """ - import numpy as np -import matplotlib.pyplot as plt +from matplotlib import pyplot as plt def sigmoid(x): diff --git a/neural_network/convolution_neural_network.py b/neural_network/convolution_neural_network.py index ac0eddeb5cb6..d821488025ef 100644 --- a/neural_network/convolution_neural_network.py +++ b/neural_network/convolution_neural_network.py @@ -14,8 +14,9 @@ - - - - - -- - - - - - - - - - - - - - - - - - - - - - - """ import pickle + import numpy as np -import matplotlib.pyplot as plt +from matplotlib import pyplot as plt class CNN: diff --git a/other/least_recently_used.py b/other/least_recently_used.py index e1b5ab5bd380..1b901d544b8d 100644 --- a/other/least_recently_used.py +++ b/other/least_recently_used.py @@ -1,5 +1,5 @@ -from abc import abstractmethod import sys +from abc import abstractmethod from collections import deque diff --git a/other/sierpinski_triangle.py b/other/sierpinski_triangle.py index e27db3a2e8c4..cf41ffa5f190 100644 --- a/other/sierpinski_triangle.py +++ b/other/sierpinski_triangle.py @@ -27,8 +27,8 @@ http://www.riannetrujillo.com/blog/python-fractal/ """ -import turtle import sys +import turtle PROGNAME = "Sierpinski Triangle" diff --git a/project_euler/problem_07/sol3.py b/project_euler/problem_07/sol3.py index 9b02ea87ec49..602eb13b623d 100644 --- a/project_euler/problem_07/sol3.py +++ b/project_euler/problem_07/sol3.py @@ -5,8 +5,8 @@ We can see that the 6th prime is 13. What is the Nth prime number? """ -import math import itertools +import math def primeCheck(number): diff --git a/project_euler/problem_42/solution42.py b/project_euler/problem_42/solution42.py index 2380472153c6..1e9bb49c7a06 100644 --- a/project_euler/problem_42/solution42.py +++ b/project_euler/problem_42/solution42.py @@ -15,7 +15,6 @@ """ import os - # Precomputes a list of the 100 first triangular numbers TRIANGULAR_NUMBERS = [int(0.5 * n * (n + 1)) for n in range(1, 101)] diff --git a/scheduling/shortest_job_first.py b/scheduling/shortest_job_first.py index 8aa642a8b25b..6a2fdeeecc5a 100644 --- a/scheduling/shortest_job_first.py +++ b/scheduling/shortest_job_first.py @@ -3,9 +3,9 @@ Please note arrival time and burst Please use spaces to separate times entered. """ +from typing import List import pandas as pd -from typing import List def calculate_waitingtime( diff --git a/scripts/validate_filenames.py b/scripts/validate_filenames.py index f22fda4149f3..01712e8f7707 100755 --- a/scripts/validate_filenames.py +++ b/scripts/validate_filenames.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 - import os + from build_directory_md import good_file_paths filepaths = list(good_file_paths()) diff --git a/searches/hill_climbing.py b/searches/hill_climbing.py index 324097ef5a24..70622ebefb4e 100644 --- a/searches/hill_climbing.py +++ b/searches/hill_climbing.py @@ -150,7 +150,7 @@ def hill_climbing( solution_found = True if visualization: - import matplotlib.pyplot as plt + from matplotlib import pyplot as plt plt.plot(range(iterations), scores) plt.xlabel("Iterations") diff --git a/searches/simulated_annealing.py b/searches/simulated_annealing.py index b12303274b14..8535e5419a4a 100644 --- a/searches/simulated_annealing.py +++ b/searches/simulated_annealing.py @@ -84,7 +84,7 @@ def simulated_annealing( current_state = next_state if visualization: - import matplotlib.pyplot as plt + from matplotlib import pyplot as plt plt.plot(range(iterations), scores) plt.xlabel("Iterations") diff --git a/searches/tabu_search.py b/searches/tabu_search.py index f0c3241dbe19..24d0dbf6f1c2 100644 --- a/searches/tabu_search.py +++ b/searches/tabu_search.py @@ -24,9 +24,8 @@ -s size_of_tabu_search e.g. python tabu_search.py -f tabudata2.txt -i 4 -s 3 """ - -import copy import argparse +import copy def generate_neighbours(path): diff --git a/sorts/external_sort.py b/sorts/external_sort.py index e5b2c045fa95..060e67adf827 100644 --- a/sorts/external_sort.py +++ b/sorts/external_sort.py @@ -3,8 +3,8 @@ # # Sort large text files in a minimum amount of memory # -import os import argparse +import os class FileSplitter: diff --git a/sorts/random_normal_distribution_quicksort.py b/sorts/random_normal_distribution_quicksort.py index 5da9337ee080..73eb70bea07f 100644 --- a/sorts/random_normal_distribution_quicksort.py +++ b/sorts/random_normal_distribution_quicksort.py @@ -1,5 +1,6 @@ from random import randint from tempfile import TemporaryFile + import numpy as np diff --git a/web_programming/crawl_google_results.py b/web_programming/crawl_google_results.py index 7d2be7c03c22..a33a3f3bbe5c 100644 --- a/web_programming/crawl_google_results.py +++ b/web_programming/crawl_google_results.py @@ -1,10 +1,9 @@ import sys import webbrowser +import requests from bs4 import BeautifulSoup from fake_useragent import UserAgent -import requests - if __name__ == "__main__": print("Googling.....") diff --git a/web_programming/get_imdbtop.py b/web_programming/get_imdbtop.py index 522e423b4eab..669e7f89824b 100644 --- a/web_programming/get_imdbtop.py +++ b/web_programming/get_imdbtop.py @@ -1,5 +1,5 @@ -from bs4 import BeautifulSoup import requests +from bs4 import BeautifulSoup def imdb_top(imdb_top_n): From 2c75a7b3dd4a54b52e8d39e2232f6bdceb902acb Mon Sep 17 00:00:00 2001 From: Christian Clauss Date: Mon, 6 Jul 2020 19:31:04 +0200 Subject: [PATCH 0663/1071] Numerous fixes to directed_and_undirected_(weighted)_graph.py (#2182) * Numerous fixes to directed_and_undirected_(weighted)_graph.py * dict.keys() is almost never need in modern Python --- ...irected_and_undirected_(weighted)_graph.py | 204 +++++++++--------- 1 file changed, 100 insertions(+), 104 deletions(-) diff --git a/graphs/directed_and_undirected_(weighted)_graph.py b/graphs/directed_and_undirected_(weighted)_graph.py index 267111a3a401..61e196adfaac 100644 --- a/graphs/directed_and_undirected_(weighted)_graph.py +++ b/graphs/directed_and_undirected_(weighted)_graph.py @@ -1,7 +1,7 @@ -import math as math -import random as rand -import time from collections import deque +from math import floor +from random import random +from time import time # the default weight is 1 if not assigned but all the implementation is weighted @@ -39,7 +39,7 @@ def dfs(self, s=-2, d=-1): stack = [] visited = [] if s == -2: - s = list(self.graph.keys())[0] + s = list(self.graph)[0] stack.append(s) visited.append(s) ss = s @@ -48,15 +48,15 @@ def dfs(self, s=-2, d=-1): # check if there is any non isolated nodes if len(self.graph[s]) != 0: ss = s - for __ in self.graph[s]: - if visited.count(__[1]) < 1: - if __[1] == d: + for node in self.graph[s]: + if visited.count(node[1]) < 1: + if node[1] == d: visited.append(d) return visited else: - stack.append(__[1]) - visited.append(__[1]) - ss = __[1] + stack.append(node[1]) + visited.append(node[1]) + ss = node[1] break # check if all the children are visited @@ -75,37 +75,35 @@ def dfs(self, s=-2, d=-1): # the count will be random from 10 to 10000 def fill_graph_randomly(self, c=-1): if c == -1: - c = (math.floor(rand.random() * 10000)) + 10 - for _ in range(c): + c = floor(random() * 10000) + 10 + for i in range(c): # every vertex has max 100 edges - e = math.floor(rand.random() * 102) + 1 - for __ in range(e): - n = math.floor(rand.random() * (c)) + 1 - if n == _: - continue - self.add_pair(_, n, 1) + for _ in range(floor(random() * 102) + 1): + n = floor(random() * c) + 1 + if n != i: + self.add_pair(i, n, 1) def bfs(self, s=-2): d = deque() visited = [] if s == -2: - s = list(self.graph.keys())[0] + s = list(self.graph)[0] d.append(s) visited.append(s) while d: s = d.popleft() if len(self.graph[s]) != 0: - for __ in self.graph[s]: - if visited.count(__[1]) < 1: - d.append(__[1]) - visited.append(__[1]) + for node in self.graph[s]: + if visited.count(node[1]) < 1: + d.append(node[1]) + visited.append(node[1]) return visited def in_degree(self, u): count = 0 - for _ in self.graph: - for __ in self.graph[_]: - if __[1] == u: + for x in self.graph: + for y in self.graph[x]: + if y[1] == u: count += 1 return count @@ -116,7 +114,7 @@ def topological_sort(self, s=-2): stack = [] visited = [] if s == -2: - s = list(self.graph.keys())[0] + s = list(self.graph)[0] stack.append(s) visited.append(s) ss = s @@ -126,11 +124,11 @@ def topological_sort(self, s=-2): # check if there is any non isolated nodes if len(self.graph[s]) != 0: ss = s - for __ in self.graph[s]: - if visited.count(__[1]) < 1: - stack.append(__[1]) - visited.append(__[1]) - ss = __[1] + for node in self.graph[s]: + if visited.count(node[1]) < 1: + stack.append(node[1]) + visited.append(node[1]) + ss = node[1] break # check if all the children are visited @@ -148,7 +146,7 @@ def topological_sort(self, s=-2): def cycle_nodes(self): stack = [] visited = [] - s = list(self.graph.keys())[0] + s = list(self.graph)[0] stack.append(s) visited.append(s) parent = -2 @@ -161,25 +159,25 @@ def cycle_nodes(self): # check if there is any non isolated nodes if len(self.graph[s]) != 0: ss = s - for __ in self.graph[s]: + for node in self.graph[s]: if ( - visited.count(__[1]) > 0 - and __[1] != parent - and indirect_parents.count(__[1]) > 0 + visited.count(node[1]) > 0 + and node[1] != parent + and indirect_parents.count(node[1]) > 0 and not on_the_way_back ): len_stack = len(stack) - 1 while True and len_stack >= 0: - if stack[len_stack] == __[1]: - anticipating_nodes.add(__[1]) + if stack[len_stack] == node[1]: + anticipating_nodes.add(node[1]) break else: anticipating_nodes.add(stack[len_stack]) len_stack -= 1 - if visited.count(__[1]) < 1: - stack.append(__[1]) - visited.append(__[1]) - ss = __[1] + if visited.count(node[1]) < 1: + stack.append(node[1]) + visited.append(node[1]) + ss = node[1] break # check if all the children are visited @@ -201,7 +199,7 @@ def cycle_nodes(self): def has_cycle(self): stack = [] visited = [] - s = list(self.graph.keys())[0] + s = list(self.graph)[0] stack.append(s) visited.append(s) parent = -2 @@ -214,26 +212,26 @@ def has_cycle(self): # check if there is any non isolated nodes if len(self.graph[s]) != 0: ss = s - for __ in self.graph[s]: + for node in self.graph[s]: if ( - visited.count(__[1]) > 0 - and __[1] != parent - and indirect_parents.count(__[1]) > 0 + visited.count(node[1]) > 0 + and node[1] != parent + and indirect_parents.count(node[1]) > 0 and not on_the_way_back ): len_stack_minus_one = len(stack) - 1 while True and len_stack_minus_one >= 0: - if stack[len_stack_minus_one] == __[1]: - anticipating_nodes.add(__[1]) + if stack[len_stack_minus_one] == node[1]: + anticipating_nodes.add(node[1]) break else: return True anticipating_nodes.add(stack[len_stack_minus_one]) len_stack_minus_one -= 1 - if visited.count(__[1]) < 1: - stack.append(__[1]) - visited.append(__[1]) - ss = __[1] + if visited.count(node[1]) < 1: + stack.append(node[1]) + visited.append(node[1]) + ss = node[1] break # check if all the children are visited @@ -253,15 +251,15 @@ def has_cycle(self): return False def dfs_time(self, s=-2, e=-1): - begin = time.time() + begin = time() self.dfs(s, e) - end = time.time() + end = time() return end - begin def bfs_time(self, s=-2): - begin = time.time() + begin = time() self.bfs(s) - end = time.time() + end = time() return end - begin @@ -309,7 +307,7 @@ def dfs(self, s=-2, d=-1): stack = [] visited = [] if s == -2: - s = list(self.graph.keys())[0] + s = list(self.graph)[0] stack.append(s) visited.append(s) ss = s @@ -318,15 +316,15 @@ def dfs(self, s=-2, d=-1): # check if there is any non isolated nodes if len(self.graph[s]) != 0: ss = s - for __ in self.graph[s]: - if visited.count(__[1]) < 1: - if __[1] == d: + for node in self.graph[s]: + if visited.count(node[1]) < 1: + if node[1] == d: visited.append(d) return visited else: - stack.append(__[1]) - visited.append(__[1]) - ss = __[1] + stack.append(node[1]) + visited.append(node[1]) + ss = node[1] break # check if all the children are visited @@ -345,30 +343,28 @@ def dfs(self, s=-2, d=-1): # the count will be random from 10 to 10000 def fill_graph_randomly(self, c=-1): if c == -1: - c = (math.floor(rand.random() * 10000)) + 10 - for _ in range(c): + c = floor(random() * 10000) + 10 + for i in range(c): # every vertex has max 100 edges - e = math.floor(rand.random() * 102) + 1 - for __ in range(e): - n = math.floor(rand.random() * (c)) + 1 - if n == _: - continue - self.add_pair(_, n, 1) + for _ in range(floor(random() * 102) + 1): + n = floor(random() * c) + 1 + if n != i: + self.add_pair(i, n, 1) def bfs(self, s=-2): d = deque() visited = [] if s == -2: - s = list(self.graph.keys())[0] + s = list(self.graph)[0] d.append(s) visited.append(s) while d: s = d.popleft() if len(self.graph[s]) != 0: - for __ in self.graph[s]: - if visited.count(__[1]) < 1: - d.append(__[1]) - visited.append(__[1]) + for node in self.graph[s]: + if visited.count(node[1]) < 1: + d.append(node[1]) + visited.append(node[1]) return visited def degree(self, u): @@ -377,7 +373,7 @@ def degree(self, u): def cycle_nodes(self): stack = [] visited = [] - s = list(self.graph.keys())[0] + s = list(self.graph)[0] stack.append(s) visited.append(s) parent = -2 @@ -390,25 +386,25 @@ def cycle_nodes(self): # check if there is any non isolated nodes if len(self.graph[s]) != 0: ss = s - for __ in self.graph[s]: + for node in self.graph[s]: if ( - visited.count(__[1]) > 0 - and __[1] != parent - and indirect_parents.count(__[1]) > 0 + visited.count(node[1]) > 0 + and node[1] != parent + and indirect_parents.count(node[1]) > 0 and not on_the_way_back ): len_stack = len(stack) - 1 while True and len_stack >= 0: - if stack[len_stack] == __[1]: - anticipating_nodes.add(__[1]) + if stack[len_stack] == node[1]: + anticipating_nodes.add(node[1]) break else: anticipating_nodes.add(stack[len_stack]) len_stack -= 1 - if visited.count(__[1]) < 1: - stack.append(__[1]) - visited.append(__[1]) - ss = __[1] + if visited.count(node[1]) < 1: + stack.append(node[1]) + visited.append(node[1]) + ss = node[1] break # check if all the children are visited @@ -430,7 +426,7 @@ def cycle_nodes(self): def has_cycle(self): stack = [] visited = [] - s = list(self.graph.keys())[0] + s = list(self.graph)[0] stack.append(s) visited.append(s) parent = -2 @@ -443,26 +439,26 @@ def has_cycle(self): # check if there is any non isolated nodes if len(self.graph[s]) != 0: ss = s - for __ in self.graph[s]: + for node in self.graph[s]: if ( - visited.count(__[1]) > 0 - and __[1] != parent - and indirect_parents.count(__[1]) > 0 + visited.count(node[1]) > 0 + and node[1] != parent + and indirect_parents.count(node[1]) > 0 and not on_the_way_back ): len_stack_minus_one = len(stack) - 1 while True and len_stack_minus_one >= 0: - if stack[len_stack_minus_one] == __[1]: - anticipating_nodes.add(__[1]) + if stack[len_stack_minus_one] == node[1]: + anticipating_nodes.add(node[1]) break else: return True anticipating_nodes.add(stack[len_stack_minus_one]) len_stack_minus_one -= 1 - if visited.count(__[1]) < 1: - stack.append(__[1]) - visited.append(__[1]) - ss = __[1] + if visited.count(node[1]) < 1: + stack.append(node[1]) + visited.append(node[1]) + ss = node[1] break # check if all the children are visited @@ -485,13 +481,13 @@ def all_nodes(self): return list(self.graph) def dfs_time(self, s=-2, e=-1): - begin = time.time() + begin = time() self.dfs(s, e) - end = time.time() + end = time() return end - begin def bfs_time(self, s=-2): - begin = time.time() + begin = time() self.bfs(s) - end = time.time() + end = time() return end - begin From 48df91d48b21fecfbdb87a0e8b011956594a74c2 Mon Sep 17 00:00:00 2001 From: lance-pyles <36748284+lance-pyles@users.noreply.github.com> Date: Mon, 6 Jul 2020 15:21:59 -0700 Subject: [PATCH 0664/1071] Add surface area class (#2183) * add surface area class * add new line to end of file * move surface area class into area class * add missing import * added pi import * fix typo * added blank line * fixed more import issues * comment fix * comment fixes --- maths/area.py | 33 +++++++++++++++++++++++++++++++-- 1 file changed, 31 insertions(+), 2 deletions(-) diff --git a/maths/area.py b/maths/area.py index 0621bf5dd809..a14fe130475f 100644 --- a/maths/area.py +++ b/maths/area.py @@ -1,8 +1,34 @@ """ Find the area of various geometric shapes """ +from math import pi +from typing import Union -import math + +def surface_area_cube(side_length: Union[int, float]) -> float: + """ + Calculate the Surface Area of a Cube. + + >>> surface_area_cube(1) + 6 + >>> surface_area_cube(3) + 54 + """ + return 6 * pow(side_length, 2) + + +def surface_area_sphere(radius: float) -> float: + """ + Calculate the Surface Area of a Sphere. + Wikipedia reference: https://en.wikipedia.org/wiki/Sphere + :return 4 * pi * r^2 + + >>> surface_area_sphere(5) + 314.1592653589793 + >>> surface_area_sphere(1) + 12.566370614359172 + """ + return 4 * pi * pow(radius, 2) def area_rectangle(base, height): @@ -62,7 +88,7 @@ def area_circle(radius): >> area_circle(20) 1256.6370614359173 """ - return math.pi * radius * radius + return pi * radius * radius def main(): @@ -73,6 +99,9 @@ def main(): print(f"Parallelogram: {area_parallelogram(10, 20)=}") print(f"Trapezium: {area_trapezium(10, 20, 30)=}") print(f"Circle: {area_circle(20)=}") + print("Surface Areas of various geometric shapes: \n") + print(f"Cube: {surface_area_cube(20)=}") + print(f"Sphere: {surface_area_sphere(20)=}") if __name__ == "__main__": From 367f8ceddd0f4c7ec1bdc506ed1d6b1c2808b2f2 Mon Sep 17 00:00:00 2001 From: wuyudi Date: Tue, 7 Jul 2020 06:22:44 +0800 Subject: [PATCH 0665/1071] enhance the ability of add (#2178) * enhance the ability of add * Update matrix_operation.py * Update matrix_operation.py * Update matrix_operation.py * Update matrix/matrix_operation.py Co-authored-by: Christian Clauss Co-authored-by: Christian Clauss --- matrix/matrix_operation.py | 28 ++++++++++++++++++---------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/matrix/matrix_operation.py b/matrix/matrix_operation.py index 002ce6b05f9f..da4e4be11c15 100644 --- a/matrix/matrix_operation.py +++ b/matrix/matrix_operation.py @@ -5,16 +5,20 @@ from typing import List, Tuple -def add(matrix_a: List[list], matrix_b: List[list]) -> List[list]: +def add(*matrix_s: List[list]) -> List[list]: """ >>> add([[1,2],[3,4]],[[2,3],[4,5]]) [[3, 5], [7, 9]] >>> add([[1.2,2.4],[3,4]],[[2,3],[4,5]]) [[3.2, 5.4], [7, 9]] + >>> add([[1, 2], [4, 5]], [[3, 7], [3, 4]], [[3, 5], [5, 7]]) + [[7, 14], [12, 16]] """ - if _check_not_integer(matrix_a) and _check_not_integer(matrix_b): - _verify_matrix_sizes(matrix_a, matrix_b) - return [[i + j for i, j in zip(m, n)] for m, n in zip(matrix_a, matrix_b)] + if all(_check_not_integer(m) for m in matrix_s): + a, *b = matrix_s + for matrix in b: + _verify_matrix_sizes(a, matrix) + return [[sum(t) for t in zip(*m)] for m in zip(*matrix_s)] def subtract(matrix_a: List[list], matrix_b: List[list]) -> List[list]: @@ -26,7 +30,7 @@ def subtract(matrix_a: List[list], matrix_b: List[list]) -> List[list]: """ if _check_not_integer(matrix_a) and _check_not_integer(matrix_b): _verify_matrix_sizes(matrix_a, matrix_b) - return [[i - j for i, j in zip(m, n)] for m, n in zip(matrix_a, matrix_b)] + return [[i - j for i, j in zip(*m)] for m in zip(matrix_a, matrix_b)] def scalar_multiply(matrix: List[list], n: int) -> List[list]: @@ -97,8 +101,8 @@ def minor(matrix: List[list], row: int, column: int) -> List[list]: >>> minor([[1, 2], [3, 4]], 1, 1) [[1]] """ - minor = matrix[:row] + matrix[row + 1 :] - return [row[:column] + row[column + 1 :] for row in minor] + minor = matrix[:row] + matrix[row + 1:] + return [row[:column] + row[column + 1:] for row in minor] def determinant(matrix: List[list]) -> int: @@ -151,7 +155,8 @@ def _shape(matrix: List[list]) -> list: return list((len(matrix), len(matrix[0]))) -def _verify_matrix_sizes(matrix_a: List[list], matrix_b: List[list]) -> Tuple[list]: +def _verify_matrix_sizes( + matrix_a: List[list], matrix_b: List[list]) -> Tuple[list]: shape = _shape(matrix_a) shape += _shape(matrix_b) if shape[0] != shape[2] or shape[1] != shape[3]: @@ -165,9 +170,12 @@ def _verify_matrix_sizes(matrix_a: List[list], matrix_b: List[list]) -> Tuple[li def main(): matrix_a = [[12, 10], [3, 9]] matrix_b = [[3, 4], [7, 4]] - matrix_c = [[11, 12, 13, 14], [21, 22, 23, 24], [31, 32, 33, 34], [41, 42, 43, 44]] + matrix_c = [[11, 12, 13, 14], [21, 22, 23, 24], + [31, 32, 33, 34], [41, 42, 43, 44]] matrix_d = [[3, 0, 2], [2, 0, -2], [0, 1, 1]] - print(f"Add Operation, {matrix_a} + {matrix_b} = {add(matrix_a, matrix_b)} \n") + print( + f"Add Operation, {matrix_a} + {matrix_b} =" + f"{add(matrix_a, matrix_b)} \n") print( f"Multiply Operation, {matrix_a} * {matrix_b}", f"= {multiply(matrix_a, matrix_b)} \n", From 728c0df3556ed62a2756898655b5d2144cc9d637 Mon Sep 17 00:00:00 2001 From: Marcos Cannabrava <54267712+marcoscannabrava@users.noreply.github.com> Date: Tue, 7 Jul 2020 00:00:07 -0300 Subject: [PATCH 0666/1071] Fix typo: Adjancent -> Adjacent (#2184) --- graphs/breadth_first_search_shortest_path.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/graphs/breadth_first_search_shortest_path.py b/graphs/breadth_first_search_shortest_path.py index e556d7966fa3..c25a5bb4f7dd 100644 --- a/graphs/breadth_first_search_shortest_path.py +++ b/graphs/breadth_first_search_shortest_path.py @@ -16,7 +16,7 @@ class Graph: def __init__(self, graph: Dict[str, str], source_vertex: str) -> None: - """Graph is implemented as dictionary of adjancency lists. Also, + """Graph is implemented as dictionary of adjacency lists. Also, Source vertex have to be defined upon initialization. """ self.graph = graph @@ -37,11 +37,11 @@ def breath_first_search(self) -> None: while queue: vertex = queue.pop(0) - for adjancent_vertex in self.graph[vertex]: - if adjancent_vertex not in visited: - visited.add(adjancent_vertex) - self.parent[adjancent_vertex] = vertex - queue.append(adjancent_vertex) + for adjacent_vertex in self.graph[vertex]: + if adjacent_vertex not in visited: + visited.add(adjacent_vertex) + self.parent[adjacent_vertex] = vertex + queue.append(adjacent_vertex) def shortest_path(self, target_vertex: str) -> str: """This shortest path function returns a string, describing the result: From aa01114c273819faa9d46d684f06698eff66eb83 Mon Sep 17 00:00:00 2001 From: D4rkia <49065066+D4rkia@users.noreply.github.com> Date: Tue, 7 Jul 2020 12:46:09 +0200 Subject: [PATCH 0667/1071] Add a missing "genetic algorithm" folder with a basic algorithm inside (#2179) * Add a basic genetic algorithm * Update basic_string.py * Improve comments and readability * Add url to wikipedia * Remove newline Co-authored-by: Christian Clauss * Sort import Co-authored-by: Christian Clauss * Apply suggestions from code review Co-authored-by: Christian Clauss * Improve Comments and readability * Update basic_string.py * Improve logic and efficiency * Add doctest * Update basic_string.py * Update basic_string.py * Update basic_string.py * Apply suggestions from code review Co-authored-by: Christian Clauss * Update basic_string.py * Update basic_string.py * Update basic_string.py Co-authored-by: Christian Clauss Co-authored-by: vinayak --- genetic_algorithm/basic_string.py | 174 ++++++++++++++++++++++++++++++ 1 file changed, 174 insertions(+) create mode 100644 genetic_algorithm/basic_string.py diff --git a/genetic_algorithm/basic_string.py b/genetic_algorithm/basic_string.py new file mode 100644 index 000000000000..482a6cb5e656 --- /dev/null +++ b/genetic_algorithm/basic_string.py @@ -0,0 +1,174 @@ +""" +Simple multithreaded algorithm to show how the 4 phases of a genetic algorithm works +(Evaluation, Selection, Crossover and Mutation) +https://en.wikipedia.org/wiki/Genetic_algorithm +Author: D4rkia +""" + +import random +from typing import List, Tuple + +# Maximum size of the population. bigger could be faster but is more memory expensive +N_POPULATION = 200 +# Number of elements selected in every generation for evolution the selection takes +# place from the best to the worst of that generation must be smaller than N_POPULATION +N_SELECTED = 50 +# Probability that an element of a generation can mutate changing one of its genes this +# guarantees that all genes will be used during evolution +MUTATION_PROBABILITY = 0.4 +# just a seed to improve randomness required by the algorithm +random.seed(random.randint(0, 1000)) + + +def basic(target: str, genes: List[str], debug: bool = True) -> Tuple[int, int, str]: + """ + Verify that the target contains no genes besides the ones inside genes variable. + + >>> from string import ascii_lowercase + >>> basic("doctest", ascii_lowercase, debug=False)[2] + 'doctest' + >>> genes = list(ascii_lowercase) + >>> genes.remove("e") + >>> basic("test", genes) + Traceback (most recent call last): + ... + ValueError: ['e'] is not in genes list, evolution cannot converge + >>> genes.remove("s") + >>> basic("test", genes) + Traceback (most recent call last): + ... + ValueError: ['e', 's'] is not in genes list, evolution cannot converge + >>> genes.remove("t") + >>> basic("test", genes) + Traceback (most recent call last): + ... + ValueError: ['e', 's', 't'] is not in genes list, evolution cannot converge + """ + + # Verify if N_POPULATION is bigger than N_SELECTED + if N_POPULATION < N_SELECTED: + raise ValueError(f"{N_POPULATION} must be bigger than {N_SELECTED}") + # Verify that the target contains no genes besides the ones inside genes variable. + not_in_genes_list = sorted({c for c in target if c not in genes}) + if not_in_genes_list: + raise ValueError( + f"{not_in_genes_list} is not in genes list, evolution cannot converge" + ) + + # Generate random starting population + population = [] + for _ in range(N_POPULATION): + population.append("".join([random.choice(genes) for i in range(len(target))])) + + # Just some logs to know what the algorithms is doing + generation, total_population = 0, 0 + + # This loop will end when we will find a perfect match for our target + while True: + generation += 1 + total_population += len(population) + + # Random population created now it's time to evaluate + def evaluate(item: str, main_target: str = target) -> Tuple[str, float]: + """ + Evaluate how similar the item is with the target by just + counting each char in the right position + >>> evaluate("Helxo Worlx", Hello World) + ["Helxo Worlx", 9] + """ + score = len( + [g for position, g in enumerate(item) if g == main_target[position]] + ) + return (item, float(score)) + + # Adding a bit of concurrency can make everything faster, + # + # import concurrent.futures + # population_score: List[Tuple[str, float]] = [] + # with concurrent.futures.ThreadPoolExecutor( + # max_workers=NUM_WORKERS) as executor: + # futures = {executor.submit(evaluate, item) for item in population} + # concurrent.futures.wait(futures) + # population_score = [item.result() for item in futures] + # + # but with a simple algorithm like this will probably be slower + # we just need to call evaluate for every item inside population + population_score = [evaluate(item) for item in population] + + # Check if there is a matching evolution + population_score = sorted(population_score, key=lambda x: x[1], reverse=True) + if population_score[0][0] == target: + return (generation, total_population, population_score[0][0]) + + # Print the Best result every 10 generation + # just to know that the algorithm is working + if debug and generation % 10 == 0: + print( + f"\nGeneration: {generation}" + f"\nTotal Population:{total_population}" + f"\nBest score: {population_score[0][1]}" + f"\nBest string: {population_score[0][0]}" + ) + + # Flush the old population keeping some of the best evolutions + # Keeping this avoid regression of evolution + population_best = population[: int(N_POPULATION / 3)] + population.clear() + population.extend(population_best) + # Normalize population score from 0 to 1 + population_score = [ + (item, score / len(target)) for item, score in population_score + ] + + # Select, Crossover and Mutate a new population + def select(parent_1: Tuple[str, float]) -> List[str]: + """Select the second parent and generate new population""" + pop = [] + # Generate more child proportionally to the fitness score + child_n = int(parent_1[1] * 100) + 1 + child_n = 10 if child_n >= 10 else child_n + for _ in range(child_n): + parent_2 = population_score[random.randint(0, N_SELECTED)][0] + child_1, child_2 = crossover(parent_1[0], parent_2) + # Append new string to the population list + pop.append(mutate(child_1)) + pop.append(mutate(child_2)) + return pop + + def crossover(parent_1: str, parent_2: str) -> Tuple[str, str]: + """Slice and combine two string in a random point""" + random_slice = random.randint(0, len(parent_1) - 1) + child_1 = parent_1[:random_slice] + parent_2[random_slice:] + child_2 = parent_2[:random_slice] + parent_1[random_slice:] + return (child_1, child_2) + + def mutate(child: str) -> str: + """Mutate a random gene of a child with another one from the list""" + child_list = list(child) + if random.uniform(0, 1) < MUTATION_PROBABILITY: + child_list[random.randint(0, len(child)) - 1] = random.choice(genes) + return "".join(child_list) + + # This is Selection + for i in range(N_SELECTED): + population.extend(select(population_score[int(i)])) + # Check if the population has already reached the maximum value and if so, + # break the cycle. if this check is disabled the algorithm will take + # forever to compute large strings but will also calculate small string in + # a lot fewer generations + if len(population) > N_POPULATION: + break + + +if __name__ == "__main__": + target_str = ( + "This is a genetic algorithm to evaluate, combine, evolve, and mutate a string!" + ) + genes_list = list( + " ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklm" + "nopqrstuvwxyz.,;!?+-*#@^'èéòà€ù=)(&%$£/\\" + ) + print( + "\nGeneration: %s\nTotal Population: %s\nTarget: %s" + % basic(target_str, genes_list) + ) From b6ca263983933c3ecc06ed0083dd11b6faf870c8 Mon Sep 17 00:00:00 2001 From: Hardik Aggarwal Date: Tue, 7 Jul 2020 16:56:10 +0530 Subject: [PATCH 0668/1071] Update decimal_to_binary.py (#2185) "an Integer" instead of "a Integer" --- conversions/decimal_to_binary.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/conversions/decimal_to_binary.py b/conversions/decimal_to_binary.py index e6821c09b4a2..8fcf226c346c 100644 --- a/conversions/decimal_to_binary.py +++ b/conversions/decimal_to_binary.py @@ -4,7 +4,7 @@ def decimal_to_binary(num: int) -> str: """ - Convert a Integer Decimal Number to a Binary Number as str. + Convert an Integer Decimal Number to a Binary Number as str. >>> decimal_to_binary(0) '0b0' >>> decimal_to_binary(2) From 05c14c6be8c8f49f6b4e1028a81a9364569f40ab Mon Sep 17 00:00:00 2001 From: David Aaron Banda Gutierrez <44423937+DavidBanda@users.noreply.github.com> Date: Fri, 10 Jul 2020 02:30:48 -0600 Subject: [PATCH 0669/1071] N queens math (#2175) * add new file for another solution to the n queens problem * Add the code for the algorithm, add comments and add at the top a general explanation * Update backtracking/n_queens_math.py Co-authored-by: Christian Clauss * Update backtracking/n_queens_math.py Co-authored-by: Christian Clauss * Update backtracking/n_queens_math.py Co-authored-by: Christian Clauss * Update backtracking/n_queens_math.py Co-authored-by: Christian Clauss * No newline at the end of the file * Type hints * whitespaces fixed * Fixed whitespaces * Add type hints * CodeSpell fixed * update * All changes made except changing the board variable to local * Add doctest * Update * Update * Update * Update n_queens_math.py Co-authored-by: Christian Clauss --- backtracking/n_queens_math.py | 165 ++++++++++++++++++++++++++++++++++ 1 file changed, 165 insertions(+) create mode 100644 backtracking/n_queens_math.py diff --git a/backtracking/n_queens_math.py b/backtracking/n_queens_math.py new file mode 100644 index 000000000000..fb2b74bd7c4a --- /dev/null +++ b/backtracking/n_queens_math.py @@ -0,0 +1,165 @@ +r""" +Problem: + +The n queens problem is of placing N queens on a N * N chess board such that no queen +can attack any other queens placed on that chess board. This means that one queen +cannot have any other queen on its horizontal, vertical and diagonal lines. + +Solution: + +To solve this problem we will use simple math. First we know the queen can move in all +the possible ways, we can simplify it in this: vertical, horizontal, diagonal left and + diagonal right. + +We can visualize it like this: + +left diagonal = \ +right diagonal = / + +On a chessboard vertical movement could be the rows and horizontal movement could be +the columns. + +In programming we can use an array, and in this array each index could be the rows and +each value in the array could be the column. For example: + + . Q . . We have this chessboard with one queen in each column and each queen + . . . Q can't attack to each other. + Q . . . The array for this example would look like this: [1, 3, 0, 2] + . . Q . + +So if we use an array and we verify that each value in the array is different to each +other we know that at least the queens can't attack each other in horizontal and +vertical. + +At this point we have that halfway completed and we will treat the chessboard as a +Cartesian plane. Hereinafter we are going to remember basic math, so in the school we +learned this formula: + + Slope of a line: + + y2 - y1 + m = ---------- + x2 - x1 + +This formula allow us to get the slope. For the angles 45º (right diagonal) and 135º +(left diagonal) this formula gives us m = 1, and m = -1 respectively. + +See:: +https://www.enotes.com/homework-help/write-equation-line-that-hits-origin-45-degree-1474860 + +Then we have this another formula: + +Slope intercept: + +y = mx + b + +b is where the line crosses the Y axis (to get more information see: +https://www.mathsisfun.com/y_intercept.html), if we change the formula to solve for b +we would have: + +y - mx = b + +And like we already have the m values for the angles 45º and 135º, this formula would +look like this: + +45º: y - (1)x = b +45º: y - x = b + +135º: y - (-1)x = b +135º: y + x = b + +y = row +x = column + +Applying this two formulas we can check if a queen in some position is being attacked +for another one or vice versa. + +""" +from typing import List + + +def depth_first_search( + possible_board: List[int], + diagonal_right_collisions: List[int], + diagonal_left_collisions: List[int], + boards: List[List[str]], + n: int, +) -> None: + """ + >>> boards = [] + >>> depth_first_search([], [], [], boards, 4) + >>> for board in boards: + ... print(board) + ['. Q . . ', '. . . Q ', 'Q . . . ', '. . Q . '] + ['. . Q . ', 'Q . . . ', '. . . Q ', '. Q . . '] + """ + + """ Get next row in the current board (possible_board) to fill it with a queen """ + row = len(possible_board) + + """ + If row is equal to the size of the board it means there are a queen in each row in + the current board (possible_board) + """ + if row == n: + """ + We convert the variable possible_board that looks like this: [1, 3, 0, 2] to + this: ['. Q . . ', '. . . Q ', 'Q . . . ', '. . Q . '] + """ + possible_board = [". " * i + "Q " + ". " * (n - 1 - i) for i in possible_board] + boards.append(possible_board) + return + + """ We iterate each column in the row to find all possible results in each row """ + for col in range(n): + + """ + We apply that we learned previously. First we check that in the current board + (possible_board) there are not other same value because if there is it means + that there are a collision in vertical. Then we apply the two formulas we + learned before: + + 45º: y - x = b or 45: row - col = b + 135º: y + x = b or row + col = b. + + And we verify if the results of this two formulas not exist in their variables + respectively. (diagonal_right_collisions, diagonal_left_collisions) + + If any or these are True it means there is a collision so we continue to the + next value in the for loop. + """ + if ( + col in possible_board + or row - col in diagonal_right_collisions + or row + col in diagonal_left_collisions + ): + continue + + """ If it is False we call dfs function again and we update the inputs """ + depth_first_search( + possible_board + [col], + diagonal_right_collisions + [row - col], + diagonal_left_collisions + [row + col], + boards, + n, + ) + + +def n_queens_solution(n: int) -> None: + boards = [] + depth_first_search([], [], [], boards, n) + + """ Print all the boards """ + for board in boards: + for column in board: + print(column) + print("") + + print(len(boards), "solutions were found.") + + +if __name__ == "__main__": + import doctest + + doctest.testmod() + n_queens_solution(4) From 6c2c08c07644a7221c716b81ff8e3d2d7fcbfe1f Mon Sep 17 00:00:00 2001 From: Advik Kulkarni <56193714+AxSmasher44@users.noreply.github.com> Date: Fri, 10 Jul 2020 18:26:43 +0530 Subject: [PATCH 0670/1071] sum_of_geometric_progression (#2168) * Add files via upload * Rename sum_of_Geometric_Progression.py to sum_of_geometric_progression.py * Update sum_of_geometric_progression.py * Update maths/sum_of_geometric_progression.py Co-authored-by: Christian Clauss * Update maths/sum_of_geometric_progression.py Co-authored-by: Christian Clauss * Update maths/sum_of_geometric_progression.py Co-authored-by: Christian Clauss * Update sum_of_geometric_progression.py * Update sum_of_geometric_progression.py * Type hints and test for zeros and negative numbers * Update sum_of_geometric_progression.py Co-authored-by: Christian Clauss --- maths/sum_of_geometric_progression.py | 28 +++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 maths/sum_of_geometric_progression.py diff --git a/maths/sum_of_geometric_progression.py b/maths/sum_of_geometric_progression.py new file mode 100644 index 000000000000..614d6646ec43 --- /dev/null +++ b/maths/sum_of_geometric_progression.py @@ -0,0 +1,28 @@ +def sum_of_geometric_progression( + first_term: int, common_ratio: int, num_of_terms: int +) -> float: + """" + Return the sum of n terms in a geometric progression. + >>> sum_of_geometric_progression(1, 2, 10) + 1023.0 + >>> sum_of_geometric_progression(1, 10, 5) + 11111.0 + >>> sum_of_geometric_progression(0, 2, 10) + 0.0 + >>> sum_of_geometric_progression(1, 0, 10) + 1.0 + >>> sum_of_geometric_progression(1, 2, 0) + -0.0 + >>> sum_of_geometric_progression(-1, 2, 10) + -1023.0 + >>> sum_of_geometric_progression(1, -2, 10) + -341.0 + >>> sum_of_geometric_progression(1, 2, -10) + -0.9990234375 + """ + if common_ratio == 1: + # Formula for sum if common ratio is 1 + return num_of_terms * first_term + + # Formula for finding sum of n terms of a GeometricProgression + return (first_term / (1 - common_ratio)) * (1 - common_ratio ** num_of_terms) From 1f1c3b0e4b5a55b315256675efd104d0b3e79cdb Mon Sep 17 00:00:00 2001 From: Dan Murphy Date: Fri, 10 Jul 2020 09:36:51 -0400 Subject: [PATCH 0671/1071] Added Normalization and Standardization Algorithms (#2192) * Added Standardization and Normalization algorithms with built-in stats * Implement ndigits for rounding Co-authored-by: Christian Clauss --- machine_learning/data_transformations.py | 62 ++++++++++++++++++++++++ 1 file changed, 62 insertions(+) create mode 100644 machine_learning/data_transformations.py diff --git a/machine_learning/data_transformations.py b/machine_learning/data_transformations.py new file mode 100644 index 000000000000..9e0d747e93fa --- /dev/null +++ b/machine_learning/data_transformations.py @@ -0,0 +1,62 @@ +""" +Normalization Wikipedia: https://en.wikipedia.org/wiki/Normalization +Normalization is the process of converting numerical data to a standard range of values. +This range is typically between [0, 1] or [-1, 1]. The equation for normalization is +x_norm = (x - x_min)/(x_max - x_min) where x_norm is the normalized value, x is the +value, x_min is the minimum value within the column or list of data, and x_max is the +maximum value within the column or list of data. Normalization is used to speed up the +training of data and put all of the data on a similar scale. This is useful because +variance in the range of values of a dataset can heavily impact optimization +(particularly Gradient Descent). + +Standardization Wikipedia: https://en.wikipedia.org/wiki/Standardization +Standardization is the process of converting numerical data to a normally distributed +range of values. This range will have a mean of 0 and standard deviation of 1. This is +also known as z-score normalization. The equation for standardization is +x_std = (x - mu)/(sigma) where mu is the mean of the column or list of values and sigma +is the standard deviation of the column or list of values. + +Choosing between Normalization & Standardization is more of an art of a science, but it +is often recommended to run experiments with both to see which performs better. +Additionally, a few rules of thumb are: + 1. gaussian (normal) distributions work better with standardization + 2. non-gaussian (non-normal) distributions work better with normalization + 3. If a column or list of values has extreme values / outliers, use standardization +""" +from statistics import mean, stdev + + +def normalization(data: list, ndigits: int = 3) -> list: + """ + Returns a normalized list of values + @params: data, a list of values to normalize + @returns: a list of normalized values (rounded to ndigits decimal places) + @examples: + >>> normalization([2, 7, 10, 20, 30, 50]) + [0.0, 0.104, 0.167, 0.375, 0.583, 1.0] + >>> normalization([5, 10, 15, 20, 25]) + [0.0, 0.25, 0.5, 0.75, 1.0] + """ + # variables for calculation + x_min = min(data) + x_max = max(data) + # normalize data + return [round((x - x_min) / (x_max - x_min), ndigits) for x in data] + + +def standardization(data: list, ndigits: int = 3) -> list: + """ + Returns a standardized list of values + @params: data, a list of values to standardize + @returns: a list of standardized values (rounded to ndigits decimal places) + @examples: + >>> standardization([2, 7, 10, 20, 30, 50]) + [-0.999, -0.719, -0.551, 0.009, 0.57, 1.69] + >>> standardization([5, 10, 15, 20, 25]) + [-1.265, -0.632, 0.0, 0.632, 1.265] + """ + # variables for calculation + mu = mean(data) + sigma = stdev(data) + # standardize data + return [round((x - mu) / (sigma), ndigits) for x in data] From 423dd2b0206b2c4748c70b741d84684e28bec8b3 Mon Sep 17 00:00:00 2001 From: Kim-R2O <67785939+Kim-R2O@users.noreply.github.com> Date: Fri, 10 Jul 2020 18:26:27 +0300 Subject: [PATCH 0672/1071] added daily horoscope scrapper script (#2167) * added daily horoscope scrapper * Update daily horoscope scrapper script code refactoring, script editing * Update web_programming/daily_horoscope.py Co-authored-by: Christian Clauss --- web_programming/daily_horoscope.py | 35 ++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 web_programming/daily_horoscope.py diff --git a/web_programming/daily_horoscope.py b/web_programming/daily_horoscope.py new file mode 100644 index 000000000000..ecb37ce106f4 --- /dev/null +++ b/web_programming/daily_horoscope.py @@ -0,0 +1,35 @@ +from bs4 import BeautifulSoup +import requests + + +def horoscope(zodiac_sign: int, day: str) -> str: + url = ( + "https://www.horoscope.com/us/horoscopes/general/" + f"horoscope-general-daily-{day}.aspx?sign={zodiac_sign}" + ) + soup = BeautifulSoup(requests.get(url).content, "html.parser") + return soup.find("div", class_="main-horoscope").p.text + + +if __name__ == "__main__": + print("Daily Horoscope. \n") + print( + "enter your Zodiac sign number:\n", + "1. Aries\n", + "2. Taurus\n", + "3. Gemini\n", + "4. Cancer\n", + "5. Leo\n", + "6. Virgo\n", + "7. Libra\n", + "8. Scorpio\n", + "9. Sagittarius\n", + "10. Capricorn\n", + "11. Aquarius\n", + "12. Pisces\n", + ) + zodiac_sign = int(input("number> ").strip()) + print("choose some day:\n", "yesterday\n", "today\n", "tomorrow\n") + day = input("enter the day> ") + horoscope_text = horoscope(zodiac_sign, day) + print(horoscope_text) From e292ddb5ec8fef4978a9154a034cf1f82ec4bc3b Mon Sep 17 00:00:00 2001 From: KARTHIKEYAN ANBARASU <53579498+karthikeyansa@users.noreply.github.com> Date: Mon, 13 Jul 2020 09:17:13 +0530 Subject: [PATCH 0673/1071] Update basic_graphs.py (#1990) * Update basic_graphs.py missing return statement line no:223. * Update basic_graphs.py Co-authored-by: vinayak --- graphs/basic_graphs.py | 1 + 1 file changed, 1 insertion(+) diff --git a/graphs/basic_graphs.py b/graphs/basic_graphs.py index 6e3c63251527..0f73d8d07b2a 100644 --- a/graphs/basic_graphs.py +++ b/graphs/basic_graphs.py @@ -220,6 +220,7 @@ def prim(G, s): if v[1] < dist.get(v[0], 100000): dist[v[0]] = v[1] path[v[0]] = u + return dist """ From 749ffd8c6fe7f0b3da6977dc12cdeaf46caea911 Mon Sep 17 00:00:00 2001 From: XxSamixX123 <60960667+XxSamixX123@users.noreply.github.com> Date: Mon, 13 Jul 2020 23:18:37 +0300 Subject: [PATCH 0674/1071] Added a binomial distribution formula calculator algorithm (#2197) * Add files via upload * Update binomial_distribution.py * Update maths/binomial_distribution.py Co-authored-by: Christian Clauss * Update binomial_distribution.py * Update maths/binomial_distribution.py Co-authored-by: Christian Clauss * Update binomial_distribution.py * Update binomial_distribution.py * Update binomial_distribution.py * Update binomial_distribution.py * Update binomial_distribution.py Co-authored-by: Christian Clauss --- maths/binomial_distribution.py | 40 ++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 maths/binomial_distribution.py diff --git a/maths/binomial_distribution.py b/maths/binomial_distribution.py new file mode 100644 index 000000000000..a74a5a7ed994 --- /dev/null +++ b/maths/binomial_distribution.py @@ -0,0 +1,40 @@ +"""For more information about the Binomial Distribution - + https://en.wikipedia.org/wiki/Binomial_distribution""" +from math import factorial + + +def binomial_distribution(successes: int, trials: int, prob: float) -> float: + """ + Return probability of k successes out of n tries, with p probability for one + success + + The function uses the factorial function in order to calculate the binomial + coefficient + + >>> binomial_distribution(3, 5, 0.7) + 0.30870000000000003 + >>> binomial_distribution (2, 4, 0.5) + 0.375 + """ + if successes > trials: + raise ValueError("""successes must be lower or equal to trials""") + if trials < 0 or successes < 0: + raise ValueError("the function is defined for non-negative integers") + if not isinstance(successes, int) or not isinstance(trials, int): + raise ValueError("the function is defined for non-negative integers") + if not 0 < prob < 1: + raise ValueError("prob has to be in range of 1 - 0") + probability = (prob ** successes) * ((1 - prob) ** (trials - successes)) + # Calculate the binomial coefficient: n! / k!(n-k)! + coefficient = float(factorial(trials)) + coefficient /= factorial(successes) * factorial(trials - successes) + return probability * coefficient + + +if __name__ == "__main__": + from doctest import testmod + + testmod() + print("Probability of 2 successes out of 4 trails") + print("with probability of 0.75 is:", end=" ") + print(binomial_distribution(2, 4, 0.75)) From 23cbe4c3528596c365bd0f8a4f5fd780d795ff98 Mon Sep 17 00:00:00 2001 From: Christian Clauss Date: Tue, 14 Jul 2020 10:23:14 +0200 Subject: [PATCH 0675/1071] black matrix_operation.py (#2199) * black matrix_operation.py * updating DIRECTORY.md * Update matrix_operation.py Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> Co-authored-by: vinayak --- DIRECTORY.md | 7 +++++++ matrix/matrix_operation.py | 14 +++++--------- 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/DIRECTORY.md b/DIRECTORY.md index 17940c6a333b..fb8312c635f8 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -19,6 +19,7 @@ * [Knight Tour](https://github.com/TheAlgorithms/Python/blob/master/backtracking/knight_tour.py) * [Minimax](https://github.com/TheAlgorithms/Python/blob/master/backtracking/minimax.py) * [N Queens](https://github.com/TheAlgorithms/Python/blob/master/backtracking/n_queens.py) + * [N Queens Math](https://github.com/TheAlgorithms/Python/blob/master/backtracking/n_queens_math.py) * [Rat In Maze](https://github.com/TheAlgorithms/Python/blob/master/backtracking/rat_in_maze.py) * [Sudoku](https://github.com/TheAlgorithms/Python/blob/master/backtracking/sudoku.py) * [Sum Of Subsets](https://github.com/TheAlgorithms/Python/blob/master/backtracking/sum_of_subsets.py) @@ -219,6 +220,9 @@ ## Fuzzy Logic * [Fuzzy Operations](https://github.com/TheAlgorithms/Python/blob/master/fuzzy_logic/fuzzy_operations.py) +## Genetic Algorithm + * [Basic String](https://github.com/TheAlgorithms/Python/blob/master/genetic_algorithm/basic_string.py) + ## Geodesy * [Haversine Distance](https://github.com/TheAlgorithms/Python/blob/master/geodesy/haversine_distance.py) * [Lamberts Ellipsoidal Distance](https://github.com/TheAlgorithms/Python/blob/master/geodesy/lamberts_ellipsoidal_distance.py) @@ -293,6 +297,7 @@ ## Machine Learning * [Astar](https://github.com/TheAlgorithms/Python/blob/master/machine_learning/astar.py) + * [Data Transformations](https://github.com/TheAlgorithms/Python/blob/master/machine_learning/data_transformations.py) * [Decision Tree](https://github.com/TheAlgorithms/Python/blob/master/machine_learning/decision_tree.py) * [Gaussian Naive Bayes](https://github.com/TheAlgorithms/Python/blob/master/machine_learning/gaussian_naive_bayes.py) * [Gradient Descent](https://github.com/TheAlgorithms/Python/blob/master/machine_learning/gradient_descent.py) @@ -402,6 +407,7 @@ * [Square Root](https://github.com/TheAlgorithms/Python/blob/master/maths/square_root.py) * [Sum Of Arithmetic Series](https://github.com/TheAlgorithms/Python/blob/master/maths/sum_of_arithmetic_series.py) * [Sum Of Digits](https://github.com/TheAlgorithms/Python/blob/master/maths/sum_of_digits.py) + * [Sum Of Geometric Progression](https://github.com/TheAlgorithms/Python/blob/master/maths/sum_of_geometric_progression.py) * [Test Prime Check](https://github.com/TheAlgorithms/Python/blob/master/maths/test_prime_check.py) * [Trapezoidal Rule](https://github.com/TheAlgorithms/Python/blob/master/maths/trapezoidal_rule.py) * [Volume](https://github.com/TheAlgorithms/Python/blob/master/maths/volume.py) @@ -680,6 +686,7 @@ * [Crawl Google Results](https://github.com/TheAlgorithms/Python/blob/master/web_programming/crawl_google_results.py) * [Current Stock Price](https://github.com/TheAlgorithms/Python/blob/master/web_programming/current_stock_price.py) * [Current Weather](https://github.com/TheAlgorithms/Python/blob/master/web_programming/current_weather.py) + * [Daily Horoscope](https://github.com/TheAlgorithms/Python/blob/master/web_programming/daily_horoscope.py) * [Emails From Url](https://github.com/TheAlgorithms/Python/blob/master/web_programming/emails_from_url.py) * [Fetch Bbc News](https://github.com/TheAlgorithms/Python/blob/master/web_programming/fetch_bbc_news.py) * [Fetch Github Info](https://github.com/TheAlgorithms/Python/blob/master/web_programming/fetch_github_info.py) diff --git a/matrix/matrix_operation.py b/matrix/matrix_operation.py index da4e4be11c15..ddc201a1aa15 100644 --- a/matrix/matrix_operation.py +++ b/matrix/matrix_operation.py @@ -101,8 +101,8 @@ def minor(matrix: List[list], row: int, column: int) -> List[list]: >>> minor([[1, 2], [3, 4]], 1, 1) [[1]] """ - minor = matrix[:row] + matrix[row + 1:] - return [row[:column] + row[column + 1:] for row in minor] + minor = matrix[:row] + matrix[row + 1 :] + return [row[:column] + row[column + 1 :] for row in minor] def determinant(matrix: List[list]) -> int: @@ -155,8 +155,7 @@ def _shape(matrix: List[list]) -> list: return list((len(matrix), len(matrix[0]))) -def _verify_matrix_sizes( - matrix_a: List[list], matrix_b: List[list]) -> Tuple[list]: +def _verify_matrix_sizes(matrix_a: List[list], matrix_b: List[list]) -> Tuple[list]: shape = _shape(matrix_a) shape += _shape(matrix_b) if shape[0] != shape[2] or shape[1] != shape[3]: @@ -170,12 +169,9 @@ def _verify_matrix_sizes( def main(): matrix_a = [[12, 10], [3, 9]] matrix_b = [[3, 4], [7, 4]] - matrix_c = [[11, 12, 13, 14], [21, 22, 23, 24], - [31, 32, 33, 34], [41, 42, 43, 44]] + matrix_c = [[11, 12, 13, 14], [21, 22, 23, 24], [31, 32, 33, 34], [41, 42, 43, 44]] matrix_d = [[3, 0, 2], [2, 0, -2], [0, 1, 1]] - print( - f"Add Operation, {matrix_a} + {matrix_b} =" - f"{add(matrix_a, matrix_b)} \n") + print(f"Add Operation, {matrix_a} + {matrix_b} =" f"{add(matrix_a, matrix_b)} \n") print( f"Multiply Operation, {matrix_a} * {matrix_b}", f"= {multiply(matrix_a, matrix_b)} \n", From 88e82db89a47155f3a3ad869b375c7359c80447d Mon Sep 17 00:00:00 2001 From: karimzakir02 <61005718+karimzakir02@users.noreply.github.com> Date: Wed, 15 Jul 2020 22:30:54 +0300 Subject: [PATCH 0676/1071] Celsius to Fahrenheit Conversions (#2188) * added conversions between celsius and fahrenheit * Renamed celsius_to_fahrenheit.py * Fixed spelling issues * modified file to fit the 88-character limit * added changes to pass the travis-ci test * further changed the files to pass the travis-ci test * further changed the files to pass the travis-ci test * Shortened conversions/fahrenheit_to_celsius.py Co-authored-by: Christian Clauss * Type hints added to conversions/fahrenheit_to_celsius.py Co-authored-by: Christian Clauss * changed the code to let the caller do the printing * addressed the changes made on github * Added Kelvin conversions and put temperature functions in a single file * Removed whitespace from a blank line * Update temperature_conversions.py Co-authored-by: Christian Clauss --- conversions/temperature_conversions.py | 88 ++++++++++++++++++++++++++ 1 file changed, 88 insertions(+) create mode 100644 conversions/temperature_conversions.py diff --git a/conversions/temperature_conversions.py b/conversions/temperature_conversions.py new file mode 100644 index 000000000000..6b99d7688e44 --- /dev/null +++ b/conversions/temperature_conversions.py @@ -0,0 +1,88 @@ +""" Convert between different units of temperature """ + + +def celsius_to_fahrenheit(celsius: float) -> float: + """ + Convert a given value from Celsius to Fahrenheit and round it to 2 decimal places. + + >>> celsius_to_fahrenheit(-40.0) + -40.0 + >>> celsius_to_fahrenheit(-20.0) + -4.0 + >>> celsius_to_fahrenheit(0) + 32.0 + >>> celsius_to_fahrenheit(20) + 68.0 + >>> celsius_to_fahrenheit("40") + 104.0 + >>> celsius_to_fahrenheit("celsius") + Traceback (most recent call last): + ... + ValueError: could not convert string to float: 'celsius' + """ + return round((float(celsius) * 9 / 5) + 32, 2) + + +def fahrenheit_to_celsius(fahrenheit: float) -> float: + """ + Convert a given value from Fahrenheit to Celsius and round it to 2 decimal places. + + >>> fahrenheit_to_celsius(0) + -17.78 + >>> fahrenheit_to_celsius(20.0) + -6.67 + >>> fahrenheit_to_celsius(40.0) + 4.44 + >>> fahrenheit_to_celsius(60) + 15.56 + >>> fahrenheit_to_celsius(80) + 26.67 + >>> fahrenheit_to_celsius("100") + 37.78 + >>> fahrenheit_to_celsius("fahrenheit") + Traceback (most recent call last): + ... + ValueError: could not convert string to float: 'fahrenheit' + """ + return round((float(fahrenheit) - 32) * 5 / 9, 2) + + +def celsius_to_kelvin(celsius: float) -> float: + """ + Convert a given value from Celsius to Kelvin and round it to 2 decimal places. + + >>> celsius_to_kelvin(0) + 273.15 + >>> celsius_to_kelvin(20.0) + 293.15 + >>> celsius_to_kelvin("40") + 313.15 + >>> celsius_to_kelvin("celsius") + Traceback (most recent call last): + ... + ValueError: could not convert string to float: 'celsius' + """ + return round(float(celsius) + 273.15, 2) + + +def kelvin_to_celsius(kelvin: float) -> float: + """ + Convert a given value from Kelvin to Celsius and round it to 2 decimal places. + + >>> kelvin_to_celsius(273.15) + 0.0 + >>> kelvin_to_celsius(300) + 26.85 + >>> kelvin_to_celsius("315.5") + 42.35 + >>> kelvin_to_celsius("kelvin") + Traceback (most recent call last): + ... + ValueError: could not convert string to float: 'kelvin' + """ + return round(float(kelvin) - 273.15, 2) + + +if __name__ == "__main__": + import doctest + doctest.testmod() From 9ec71cbdda4a52f024c9d24f0ece14600ca05301 Mon Sep 17 00:00:00 2001 From: ryuta69 Date: Tue, 21 Jul 2020 02:12:08 +0900 Subject: [PATCH 0677/1071] Add merge insertion sort (#2211) * Add merge insertion sort * Fix python naming conventions * Add wikipedia link * Add type hint * Fix python to python3 Co-authored-by: Christian Clauss * Refactor doubled process in if-condition into one outside of if-condition Co-authored-by: Christian Clauss * Refactor make python3 prior to python Co-authored-by: Christian Clauss * Fix name of is_surplus into has_last_odd_item * Add comment * Fix long comment to shorten Co-authored-by: Christian Clauss --- sorts/merge_insertion_sort.py | 179 ++++++++++++++++++++++++++++++++++ 1 file changed, 179 insertions(+) create mode 100644 sorts/merge_insertion_sort.py diff --git a/sorts/merge_insertion_sort.py b/sorts/merge_insertion_sort.py new file mode 100644 index 000000000000..339851699525 --- /dev/null +++ b/sorts/merge_insertion_sort.py @@ -0,0 +1,179 @@ +""" +This is a pure Python implementation of the merge-insertion sort algorithm +Source: https://en.wikipedia.org/wiki/Merge-insertion_sort + +For doctests run following command: +python3 -m doctest -v merge_insertion_sort.py +or +python -m doctest -v merge_insertion_sort.py + +For manual testing run: +python3 merge_insertion_sort.py +""" + +from typing import List + + +def merge_insertion_sort(collection: List[int]) -> List[int]: + """Pure implementation of merge-insertion sort algorithm in Python + + :param collection: some mutable ordered collection with heterogeneous + comparable items inside + :return: the same collection ordered by ascending + + Examples: + >>> merge_insertion_sort([0, 5, 3, 2, 2]) + [0, 2, 2, 3, 5] + + >>> merge_insertion_sort([99]) + [99] + + >>> merge_insertion_sort([-2, -5, -45]) + [-45, -5, -2] + """ + + def binary_search_insertion(sorted_list, item): + left = 0 + right = len(sorted_list) - 1 + while left <= right: + middle = (left + right) // 2 + if left == right: + if sorted_list[middle] < item: + left = middle + 1 + break + elif sorted_list[middle] < item: + left = middle + 1 + else: + right = middle - 1 + sorted_list.insert(left, item) + return sorted_list + + def sortlist_2d(list_2d): + def merge(left, right): + result = [] + while left and right: + if left[0][0] < right[0][0]: + result.append(left.pop(0)) + else: + result.append(right.pop(0)) + return result + left + right + + length = len(list_2d) + if length <= 1: + return list_2d + middle = length // 2 + return merge(sortlist_2d(list_2d[:middle]), sortlist_2d(list_2d[middle:])) + + if len(collection) <= 1: + return collection + + """ + Group the items into two pairs, and leave one element if there is a last odd item. + + Example: [999, 100, 75, 40, 10000] + -> [999, 100], [75, 40]. Leave 10000. + """ + two_paired_list = [] + has_last_odd_item = False + for i in range(0, len(collection), 2): + if i == len(collection) - 1: + has_last_odd_item = True + else: + """ + Sort two-pairs in each groups. + + Example: [999, 100], [75, 40] + -> [100, 999], [40, 75] + """ + if collection[i] < collection[i + 1]: + two_paired_list.append([collection[i], collection[i + 1]]) + else: + two_paired_list.append([collection[i + 1], collection[i]]) + + """ + Sort two_paired_list. + + Example: [100, 999], [40, 75] + -> [40, 75], [100, 999] + """ + sorted_list_2d = sortlist_2d(two_paired_list) + + """ + 40 < 100 is sure because it has already been sorted. + Generate the sorted_list of them so that you can avoid unnecessary comparison. + + Example: + group0 group1 + 40 100 + 75 999 + -> + group0 group1 + [40, 100] + 75 999 + """ + result = [i[0] for i in sorted_list_2d] + + """ + 100 < 999 is sure because it has already been sorted. + Put 999 in last of the sorted_list so that you can avoid unnecessary comparison. + + Example: + group0 group1 + [40, 100] + 75 999 + -> + group0 group1 + [40, 100, 999] + 75 + """ + result.append(sorted_list_2d[-1][1]) + + """ + Insert the last odd item left if there is. + + Example: + group0 group1 + [40, 100, 999] + 75 + -> + group0 group1 + [40, 100, 999, 10000] + 75 + """ + if has_last_odd_item: + pivot = collection[-1] + result = binary_search_insertion(result, pivot) + + """ + Insert the remaining items. + In this case, 40 < 75 is sure because it has already been sorted. + Therefore, you only need to insert 75 into [100, 999, 10000], + so that you can avoid unnecessary comparison. + + Example: + group0 group1 + [40, 100, 999, 10000] + ^ You don't need to compare with this as 40 < 75 is already sure. + 75 + -> + [40, 75, 100, 999, 10000] + """ + is_last_odd_item_inserted_before_this_index = False + for i in range(len(sorted_list_2d) - 1): + if result[i] == collection[-i]: + is_last_odd_item_inserted_before_this_index = True + pivot = sorted_list_2d[i][1] + # If last_odd_item is inserted before the item's index, + # you should forward index one more. + if is_last_odd_item_inserted_before_this_index: + result = result[: i + 2] + binary_search_insertion(result[i + 2 :], pivot) + else: + result = result[: i + 1] + binary_search_insertion(result[i + 1 :], pivot) + + return result + + +if __name__ == "__main__": + user_input = input("Enter numbers separated by a comma:\n").strip() + unsorted = [int(item) for item in user_input.split(",")] + print(merge_insertion_sort(unsorted)) From b3950035a61298704a5f19420d4746a0f12c71a0 Mon Sep 17 00:00:00 2001 From: lance-pyles <36748284+lance-pyles@users.noreply.github.com> Date: Wed, 22 Jul 2020 15:49:34 -0700 Subject: [PATCH 0678/1071] update variable names for consistency using standard formula terms; (#2223) * update variable names for consistency using standard formula terms; fix flake8 syntax errors; fix doctests; * tweak to variable name --- maths/area.py | 45 +++++++++++++++++++++++++-------------------- 1 file changed, 25 insertions(+), 20 deletions(-) diff --git a/maths/area.py b/maths/area.py index a14fe130475f..f317118ade06 100644 --- a/maths/area.py +++ b/maths/area.py @@ -31,14 +31,14 @@ def surface_area_sphere(radius: float) -> float: return 4 * pi * pow(radius, 2) -def area_rectangle(base, height): +def area_rectangle(length, width): """ Calculate the area of a rectangle - >> area_rectangle(10,20) + >>> area_rectangle(10,20) 200 """ - return base * height + return length * width def area_square(side_length): @@ -48,24 +48,24 @@ def area_square(side_length): >>> area_square(10) 100 """ - return side_length * side_length + return pow(side_length, 2) -def area_triangle(length, breadth): +def area_triangle(base, height): """ Calculate the area of a triangle >>> area_triangle(10,10) 50.0 """ - return 1 / 2 * length * breadth + return (base * height) / 2 def area_parallelogram(base, height): """ Calculate the area of a parallelogram - >> area_parallelogram(10,20) + >>> area_parallelogram(10,20) 200 """ return base * height @@ -75,8 +75,8 @@ def area_trapezium(base1, base2, height): """ Calculate the area of a trapezium - >> area_trapezium(10,20,30) - 450 + >>> area_trapezium(10,20,30) + 450.0 """ return 1 / 2 * (base1 + base2) * height @@ -85,24 +85,29 @@ def area_circle(radius): """ Calculate the area of a circle - >> area_circle(20) + >>> area_circle(20) 1256.6370614359173 """ - return pi * radius * radius + return pi * pow(radius, 2) def main(): print("Areas of various geometric shapes: \n") - print(f"Rectangle: {area_rectangle(10, 20)=}") - print(f"Square: {area_square(10)=}") - print(f"Triangle: {area_triangle(10, 10)=}") - print(f"Parallelogram: {area_parallelogram(10, 20)=}") - print(f"Trapezium: {area_trapezium(10, 20, 30)=}") - print(f"Circle: {area_circle(20)=}") - print("Surface Areas of various geometric shapes: \n") - print(f"Cube: {surface_area_cube(20)=}") - print(f"Sphere: {surface_area_sphere(20)=}") + print(f"Rectangle: {area_rectangle(10, 20)}") + print(f"Square: {area_square(10)}") + print(f"Triangle: {area_triangle(10, 10)}") + print(f"Parallelogram: {area_parallelogram(10, 20)}") + print(f"Trapezium: {area_trapezium(10, 20, 30)}") + print(f"Circle: {area_circle(20)}") + print("\nSurface Areas of various geometric shapes: \n") + print(f"Cube: {surface_area_cube(20)}") + print(f"Sphere: {surface_area_sphere(20)}") if __name__ == "__main__": + + import doctest + + doctest.testmod(verbose=True) # verbose so we can see methods missing tests + main() From a823a86a29689ff5550af7a337caf86c478854fe Mon Sep 17 00:00:00 2001 From: RobotGuy999 <68413067+RobotGuy999@users.noreply.github.com> Date: Thu, 23 Jul 2020 19:18:17 +0800 Subject: [PATCH 0679/1071] Added "Inverse of Matrix" Algorithm (#2209) * Added "Inverse of Matrix" Algorithm * Small quotation marks change * Update inverse_of_matrix.py * Updated doctests * Update inverse_of_matrix.py * Add type hints * swaped --> swapped Co-authored-by: Christian Clauss --- matrix/inverse_of_matrix.py | 39 +++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 matrix/inverse_of_matrix.py diff --git a/matrix/inverse_of_matrix.py b/matrix/inverse_of_matrix.py new file mode 100644 index 000000000000..abbeb79ddbdb --- /dev/null +++ b/matrix/inverse_of_matrix.py @@ -0,0 +1,39 @@ +from decimal import Decimal +from typing import List + + +def inverse_of_matrix(matrix: List[List[float]]) -> List[List[float]]: + """ + A matrix multiplied with its inverse gives the identity matrix. + This function finds the inverse of a 2x2 matrix. + If the determinant of a matrix is 0, its inverse does not exist. + + Sources for fixing inaccurate float arithmetic: + https://stackoverflow.com/questions/6563058/how-do-i-use-accurate-float-arithmetic-in-python + https://docs.python.org/3/library/decimal.html + + >>> inverse_of_matrix([[2, 5], [2, 0]]) + [[0.0, 0.5], [0.2, -0.2]] + >>> inverse_of_matrix([[2.5, 5], [1, 2]]) + Traceback (most recent call last): + ... + ValueError: This matrix has no inverse. + >>> inverse_of_matrix([[12, -16], [-9, 0]]) + [[0.0, -0.1111111111111111], [-0.0625, -0.08333333333333333]] + >>> inverse_of_matrix([[12, 3], [16, 8]]) + [[0.16666666666666666, -0.0625], [-0.3333333333333333, 0.25]] + >>> inverse_of_matrix([[10, 5], [3, 2.5]]) + [[0.25, -0.5], [-0.3, 1.0]] + """ + + D = Decimal # An abbreviation to be conciseness + # Calculate the determinant of the matrix + determinant = D(matrix[0][0]) * D(matrix[1][1]) - D(matrix[1][0]) * D(matrix[0][1]) + if determinant == 0: + raise ValueError("This matrix has no inverse.") + # Creates a copy of the matrix with swapped positions of the elements + swapped_matrix = [[0.0, 0.0], [0.0, 0.0]] + swapped_matrix[0][0], swapped_matrix[1][1] = matrix[1][1], matrix[0][0] + swapped_matrix[1][0], swapped_matrix[0][1] = -matrix[1][0], -matrix[0][1] + # Calculate the inverse of the matrix + return [[float(D(n) / determinant) or 0.0 for n in row] for row in swapped_matrix] From 99b40e2a267045a7e3d69acc338b85c525f51eda Mon Sep 17 00:00:00 2001 From: lance-pyles <36748284+lance-pyles@users.noreply.github.com> Date: Fri, 24 Jul 2020 13:55:18 -0700 Subject: [PATCH 0680/1071] add Rankine scale (#2232) * add Rankine scale black formatting * add Wikipedia links * add optional rounding, default to 2 digits * fix variable name * fixed variable name; helps to stage before commiting --- conversions/temperature_conversions.py | 216 +++++++++++++++++++++++-- 1 file changed, 199 insertions(+), 17 deletions(-) diff --git a/conversions/temperature_conversions.py b/conversions/temperature_conversions.py index 6b99d7688e44..0a8a5ddb6d2c 100644 --- a/conversions/temperature_conversions.py +++ b/conversions/temperature_conversions.py @@ -1,9 +1,11 @@ """ Convert between different units of temperature """ -def celsius_to_fahrenheit(celsius: float) -> float: +def celsius_to_fahrenheit(celsius: float, ndigits: int = 2) -> float: """ Convert a given value from Celsius to Fahrenheit and round it to 2 decimal places. + Wikipedia reference: https://en.wikipedia.org/wiki/Celsius + Wikipedia reference: https://en.wikipedia.org/wiki/Fahrenheit >>> celsius_to_fahrenheit(-40.0) -40.0 @@ -20,12 +22,54 @@ def celsius_to_fahrenheit(celsius: float) -> float: ... ValueError: could not convert string to float: 'celsius' """ - return round((float(celsius) * 9 / 5) + 32, 2) + return round((float(celsius) * 9 / 5) + 32, ndigits) -def fahrenheit_to_celsius(fahrenheit: float) -> float: +def celsius_to_kelvin(celsius: float, ndigits: int = 2) -> float: + """ + Convert a given value from Celsius to Kelvin and round it to 2 decimal places. + Wikipedia reference: https://en.wikipedia.org/wiki/Celsius + Wikipedia reference: https://en.wikipedia.org/wiki/Kelvin + + >>> celsius_to_kelvin(0) + 273.15 + >>> celsius_to_kelvin(20.0) + 293.15 + >>> celsius_to_kelvin("40") + 313.15 + >>> celsius_to_kelvin("celsius") + Traceback (most recent call last): + ... + ValueError: could not convert string to float: 'celsius' + """ + return round(float(celsius) + 273.15, ndigits) + + +def celsius_to_rankine(celsius: float, ndigits: int = 2) -> float: + """ + Convert a given value from Celsius to Rankine and round it to 2 decimal places. + Wikipedia reference: https://en.wikipedia.org/wiki/Celsius + Wikipedia reference: https://en.wikipedia.org/wiki/Rankine_scale + + >>> celsius_to_rankine(0) + 491.67 + >>> celsius_to_rankine(20.0) + 527.67 + >>> celsius_to_rankine("40") + 563.67 + >>> celsius_to_rankine("celsius") + Traceback (most recent call last): + ... + ValueError: could not convert string to float: 'celsius' + """ + return round((float(celsius) * 9 / 5) + 491.67, ndigits) + + +def fahrenheit_to_celsius(fahrenheit: float, ndigits: int = 2) -> float: """ Convert a given value from Fahrenheit to Celsius and round it to 2 decimal places. + Wikipedia reference: https://en.wikipedia.org/wiki/Fahrenheit + Wikipedia reference: https://en.wikipedia.org/wiki/Celsius >>> fahrenheit_to_celsius(0) -17.78 @@ -44,30 +88,66 @@ def fahrenheit_to_celsius(fahrenheit: float) -> float: ... ValueError: could not convert string to float: 'fahrenheit' """ - return round((float(fahrenheit) - 32) * 5 / 9, 2) + return round((float(fahrenheit) - 32) * 5 / 9, ndigits) -def celsius_to_kelvin(celsius: float) -> float: +def fahrenheit_to_kelvin(fahrenheit: float, ndigits: int = 2) -> float: """ - Convert a given value from Celsius to Kelvin and round it to 2 decimal places. + Convert a given value from Fahrenheit to Kelvin and round it to 2 decimal places. + Wikipedia reference: https://en.wikipedia.org/wiki/Fahrenheit + Wikipedia reference: https://en.wikipedia.org/wiki/Kelvin - >>> celsius_to_kelvin(0) - 273.15 - >>> celsius_to_kelvin(20.0) - 293.15 - >>> celsius_to_kelvin("40") - 313.15 - >>> celsius_to_kelvin("celsius") + >>> fahrenheit_to_kelvin(0) + 255.37 + >>> fahrenheit_to_kelvin(20.0) + 266.48 + >>> fahrenheit_to_kelvin(40.0) + 277.59 + >>> fahrenheit_to_kelvin(60) + 288.71 + >>> fahrenheit_to_kelvin(80) + 299.82 + >>> fahrenheit_to_kelvin("100") + 310.93 + >>> fahrenheit_to_kelvin("fahrenheit") Traceback (most recent call last): ... - ValueError: could not convert string to float: 'celsius' + ValueError: could not convert string to float: 'fahrenheit' + """ + return round(((float(fahrenheit) - 32) * 5 / 9) + 273.15, ndigits) + + +def fahrenheit_to_rankine(fahrenheit: float, ndigits: int = 2) -> float: + """ + Convert a given value from Fahrenheit to Rankine and round it to 2 decimal places. + Wikipedia reference: https://en.wikipedia.org/wiki/Fahrenheit + Wikipedia reference: https://en.wikipedia.org/wiki/Rankine_scale + + >>> fahrenheit_to_rankine(0) + 459.67 + >>> fahrenheit_to_rankine(20.0) + 479.67 + >>> fahrenheit_to_rankine(40.0) + 499.67 + >>> fahrenheit_to_rankine(60) + 519.67 + >>> fahrenheit_to_rankine(80) + 539.67 + >>> fahrenheit_to_rankine("100") + 559.67 + >>> fahrenheit_to_rankine("fahrenheit") + Traceback (most recent call last): + ... + ValueError: could not convert string to float: 'fahrenheit' """ - return round(float(celsius) + 273.15, 2) + return round(float(fahrenheit) + 459.67, ndigits) -def kelvin_to_celsius(kelvin: float) -> float: +def kelvin_to_celsius(kelvin: float, ndigits: int = 2) -> float: """ Convert a given value from Kelvin to Celsius and round it to 2 decimal places. + Wikipedia reference: https://en.wikipedia.org/wiki/Kelvin + Wikipedia reference: https://en.wikipedia.org/wiki/Celsius >>> kelvin_to_celsius(273.15) 0.0 @@ -80,9 +160,111 @@ def kelvin_to_celsius(kelvin: float) -> float: ... ValueError: could not convert string to float: 'kelvin' """ - return round(float(kelvin) - 273.15, 2) + return round(float(kelvin) - 273.15, ndigits) + + +def kelvin_to_fahrenheit(kelvin: float, ndigits: int = 2) -> float: + """ + Convert a given value from Kelvin to Fahrenheit and round it to 2 decimal places. + Wikipedia reference: https://en.wikipedia.org/wiki/Kelvin + Wikipedia reference: https://en.wikipedia.org/wiki/Fahrenheit + + >>> kelvin_to_fahrenheit(273.15) + 32.0 + >>> kelvin_to_fahrenheit(300) + 80.33 + >>> kelvin_to_fahrenheit("315.5") + 108.23 + >>> kelvin_to_fahrenheit("kelvin") + Traceback (most recent call last): + ... + ValueError: could not convert string to float: 'kelvin' + """ + return round(((float(kelvin) - 273.15) * 9 / 5) + 32, ndigits) + + +def kelvin_to_rankine(kelvin: float, ndigits: int = 2) -> float: + """ + Convert a given value from Kelvin to Rankine and round it to 2 decimal places. + Wikipedia reference: https://en.wikipedia.org/wiki/Kelvin + Wikipedia reference: https://en.wikipedia.org/wiki/Rankine_scale + + >>> kelvin_to_rankine(0) + 0.0 + >>> kelvin_to_rankine(20.0) + 36.0 + >>> kelvin_to_rankine("40") + 72.0 + >>> kelvin_to_rankine("kelvin") + Traceback (most recent call last): + ... + ValueError: could not convert string to float: 'kelvin' + """ + return round((float(kelvin) * 9 / 5), ndigits) + + +def rankine_to_celsius(rankine: float, ndigits: int = 2) -> float: + """ + Convert a given value from Rankine to Celsius and round it to 2 decimal places. + Wikipedia reference: https://en.wikipedia.org/wiki/Rankine_scale + Wikipedia reference: https://en.wikipedia.org/wiki/Celsius + + >>> rankine_to_celsius(273.15) + -121.4 + >>> rankine_to_celsius(300) + -106.48 + >>> rankine_to_celsius("315.5") + -97.87 + >>> rankine_to_celsius("rankine") + Traceback (most recent call last): + ... + ValueError: could not convert string to float: 'rankine' + """ + return round((float(rankine) - 491.67) * 5 / 9, ndigits) + + +def rankine_to_fahrenheit(rankine: float, ndigits: int = 2) -> float: + """ + Convert a given value from Rankine to Fahrenheit and round it to 2 decimal places. + Wikipedia reference: https://en.wikipedia.org/wiki/Rankine_scale + Wikipedia reference: https://en.wikipedia.org/wiki/Fahrenheit + + >>> rankine_to_fahrenheit(273.15) + -186.52 + >>> rankine_to_fahrenheit(300) + -159.67 + >>> rankine_to_fahrenheit("315.5") + -144.17 + >>> rankine_to_fahrenheit("rankine") + Traceback (most recent call last): + ... + ValueError: could not convert string to float: 'rankine' + """ + return round(float(rankine) - 459.67, ndigits) + + +def rankine_to_kelvin(rankine: float, ndigits: int = 2) -> float: + """ + Convert a given value from Rankine to Kelvin and round it to 2 decimal places. + Wikipedia reference: https://en.wikipedia.org/wiki/Rankine_scale + Wikipedia reference: https://en.wikipedia.org/wiki/Kelvin + + >>> rankine_to_kelvin(0) + 0.0 + >>> rankine_to_kelvin(20.0) + 11.11 + >>> rankine_to_kelvin("40") + 22.22 + >>> rankine_to_kelvin("rankine") + Traceback (most recent call last): + ... + ValueError: could not convert string to float: 'rankine' + """ + return round((float(rankine) * 5 / 9), ndigits) if __name__ == "__main__": + import doctest + doctest.testmod() From 44f9fd12c26a3f65cdeab286113c17f9f540bc6d Mon Sep 17 00:00:00 2001 From: lance-pyles <36748284+lance-pyles@users.noreply.github.com> Date: Sat, 25 Jul 2020 11:58:53 -0700 Subject: [PATCH 0681/1071] added tests (#2234) --- conversions/temperature_conversions.py | 40 ++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/conversions/temperature_conversions.py b/conversions/temperature_conversions.py index 0a8a5ddb6d2c..43d682a70a2e 100644 --- a/conversions/temperature_conversions.py +++ b/conversions/temperature_conversions.py @@ -7,6 +7,10 @@ def celsius_to_fahrenheit(celsius: float, ndigits: int = 2) -> float: Wikipedia reference: https://en.wikipedia.org/wiki/Celsius Wikipedia reference: https://en.wikipedia.org/wiki/Fahrenheit + >>> celsius_to_fahrenheit(273.354, 3) + 524.037 + >>> celsius_to_fahrenheit(273.354, 0) + 524.0 >>> celsius_to_fahrenheit(-40.0) -40.0 >>> celsius_to_fahrenheit(-20.0) @@ -31,6 +35,10 @@ def celsius_to_kelvin(celsius: float, ndigits: int = 2) -> float: Wikipedia reference: https://en.wikipedia.org/wiki/Celsius Wikipedia reference: https://en.wikipedia.org/wiki/Kelvin + >>> celsius_to_kelvin(273.354, 3) + 546.504 + >>> celsius_to_kelvin(273.354, 0) + 547.0 >>> celsius_to_kelvin(0) 273.15 >>> celsius_to_kelvin(20.0) @@ -51,6 +59,10 @@ def celsius_to_rankine(celsius: float, ndigits: int = 2) -> float: Wikipedia reference: https://en.wikipedia.org/wiki/Celsius Wikipedia reference: https://en.wikipedia.org/wiki/Rankine_scale + >>> celsius_to_rankine(273.354, 3) + 983.707 + >>> celsius_to_rankine(273.354, 0) + 984.0 >>> celsius_to_rankine(0) 491.67 >>> celsius_to_rankine(20.0) @@ -71,6 +83,10 @@ def fahrenheit_to_celsius(fahrenheit: float, ndigits: int = 2) -> float: Wikipedia reference: https://en.wikipedia.org/wiki/Fahrenheit Wikipedia reference: https://en.wikipedia.org/wiki/Celsius + >>> fahrenheit_to_celsius(273.354, 3) + 134.086 + >>> fahrenheit_to_celsius(273.354, 0) + 134.0 >>> fahrenheit_to_celsius(0) -17.78 >>> fahrenheit_to_celsius(20.0) @@ -97,6 +113,10 @@ def fahrenheit_to_kelvin(fahrenheit: float, ndigits: int = 2) -> float: Wikipedia reference: https://en.wikipedia.org/wiki/Fahrenheit Wikipedia reference: https://en.wikipedia.org/wiki/Kelvin + >>> fahrenheit_to_kelvin(273.354, 3) + 407.236 + >>> fahrenheit_to_kelvin(273.354, 0) + 407.0 >>> fahrenheit_to_kelvin(0) 255.37 >>> fahrenheit_to_kelvin(20.0) @@ -123,6 +143,10 @@ def fahrenheit_to_rankine(fahrenheit: float, ndigits: int = 2) -> float: Wikipedia reference: https://en.wikipedia.org/wiki/Fahrenheit Wikipedia reference: https://en.wikipedia.org/wiki/Rankine_scale + >>> fahrenheit_to_rankine(273.354, 3) + 733.024 + >>> fahrenheit_to_rankine(273.354, 0) + 733.0 >>> fahrenheit_to_rankine(0) 459.67 >>> fahrenheit_to_rankine(20.0) @@ -149,6 +173,10 @@ def kelvin_to_celsius(kelvin: float, ndigits: int = 2) -> float: Wikipedia reference: https://en.wikipedia.org/wiki/Kelvin Wikipedia reference: https://en.wikipedia.org/wiki/Celsius + >>> kelvin_to_celsius(273.354, 3) + 0.204 + >>> kelvin_to_celsius(273.354, 0) + 0.0 >>> kelvin_to_celsius(273.15) 0.0 >>> kelvin_to_celsius(300) @@ -169,6 +197,10 @@ def kelvin_to_fahrenheit(kelvin: float, ndigits: int = 2) -> float: Wikipedia reference: https://en.wikipedia.org/wiki/Kelvin Wikipedia reference: https://en.wikipedia.org/wiki/Fahrenheit + >>> kelvin_to_fahrenheit(273.354, 3) + 32.367 + >>> kelvin_to_fahrenheit(273.354, 0) + 32.0 >>> kelvin_to_fahrenheit(273.15) 32.0 >>> kelvin_to_fahrenheit(300) @@ -189,6 +221,10 @@ def kelvin_to_rankine(kelvin: float, ndigits: int = 2) -> float: Wikipedia reference: https://en.wikipedia.org/wiki/Kelvin Wikipedia reference: https://en.wikipedia.org/wiki/Rankine_scale + >>> kelvin_to_rankine(273.354, 3) + 492.037 + >>> kelvin_to_rankine(273.354, 0) + 492.0 >>> kelvin_to_rankine(0) 0.0 >>> kelvin_to_rankine(20.0) @@ -209,6 +245,10 @@ def rankine_to_celsius(rankine: float, ndigits: int = 2) -> float: Wikipedia reference: https://en.wikipedia.org/wiki/Rankine_scale Wikipedia reference: https://en.wikipedia.org/wiki/Celsius + >>> rankine_to_celsius(273.354, 3) + -121.287 + >>> rankine_to_celsius(273.354, 0) + -121.0 >>> rankine_to_celsius(273.15) -121.4 >>> rankine_to_celsius(300) From 977dfaa46c573129fe3cccf985e7b05366042e6b Mon Sep 17 00:00:00 2001 From: zakademic <67771932+zakademic@users.noreply.github.com> Date: Sun, 26 Jul 2020 01:51:10 -0700 Subject: [PATCH 0682/1071] Linear algebra/power iteration (#2190) * Initial commit of power iteration. * Added more documentation for power iteration and rayleigh quotient * Type hinting for rayleigh quotient * Changes after running black and flake8. * Added doctests, added unit tests. Removed Rayleigh quotient as it is not needed. * Update linear_algebra/src/power_iteration.py Changed convergence check line. Co-authored-by: Christian Clauss * Update linear_algebra/src/power_iteration.py Named tests more clearly. Co-authored-by: Christian Clauss * Changed naming in test function to be more clear. Changed naming in doctests to match function call. * Self running tests Co-authored-by: Zeyad Zaky Co-authored-by: Christian Clauss --- linear_algebra/src/power_iteration.py | 101 ++++++++++++++++++++++++++ 1 file changed, 101 insertions(+) create mode 100644 linear_algebra/src/power_iteration.py diff --git a/linear_algebra/src/power_iteration.py b/linear_algebra/src/power_iteration.py new file mode 100644 index 000000000000..476361e0d433 --- /dev/null +++ b/linear_algebra/src/power_iteration.py @@ -0,0 +1,101 @@ +import numpy as np + + +def power_iteration( + input_matrix: np.array, vector: np.array, error_tol=1e-12, max_iterations=100 +) -> [float, np.array]: + """ + Power Iteration. + Find the largest eignevalue and corresponding eigenvector + of matrix input_matrix given a random vector in the same space. + Will work so long as vector has component of largest eigenvector. + input_matrix must be symmetric. + + Input + input_matrix: input matrix whose largest eigenvalue we will find. + Numpy array. np.shape(input_matrix) == (N,N). + vector: random initial vector in same space as matrix. + Numpy array. np.shape(vector) == (N,) or (N,1) + + Output + largest_eigenvalue: largest eigenvalue of the matrix input_matrix. + Float. Scalar. + largest_eigenvector: eigenvector corresponding to largest_eigenvalue. + Numpy array. np.shape(largest_eigenvector) == (N,) or (N,1). + + >>> import numpy as np + >>> input_matrix = np.array([ + ... [41, 4, 20], + ... [ 4, 26, 30], + ... [20, 30, 50] + ... ]) + >>> vector = np.array([41,4,20]) + >>> power_iteration(input_matrix,vector) + (79.66086378788381, array([0.44472726, 0.46209842, 0.76725662])) + """ + + # Ensure matrix is square. + assert np.shape(input_matrix)[0] == np.shape(input_matrix)[1] + # Ensure proper dimensionality. + assert np.shape(input_matrix)[0] == np.shape(vector)[0] + + # Set convergence to False. Will define convergence when we exceed max_iterations + # or when we have small changes from one iteration to next. + + convergence = False + lamda_previous = 0 + iterations = 0 + error = 1e12 + + while not convergence: + # Multiple matrix by the vector. + w = np.dot(input_matrix, vector) + # Normalize the resulting output vector. + vector = w / np.linalg.norm(w) + # Find rayleigh quotient + # (faster than usual b/c we know vector is normalized already) + lamda = np.dot(vector.T, np.dot(input_matrix, vector)) + + # Check convergence. + error = np.abs(lamda - lamda_previous) / lamda + iterations += 1 + + if error <= error_tol or iterations >= max_iterations: + convergence = True + + lamda_previous = lamda + + return lamda, vector + + +def test_power_iteration() -> None: + """ + >>> test_power_iteration() # self running tests + """ + # Our implementation. + input_matrix = np.array([[41, 4, 20], [4, 26, 30], [20, 30, 50]]) + vector = np.array([41, 4, 20]) + eigen_value, eigen_vector = power_iteration(input_matrix, vector) + + # Numpy implementation. + + # Get eigen values and eigen vectors using built in numpy + # eigh (eigh used for symmetric or hermetian matrices). + eigen_values, eigen_vectors = np.linalg.eigh(input_matrix) + # Last eigen value is the maximum one. + eigen_value_max = eigen_values[-1] + # Last column in this matrix is eigen vector corresponding to largest eigen value. + eigen_vector_max = eigen_vectors[:, -1] + + # Check our implementation and numpy gives close answers. + assert np.abs(eigen_value - eigen_value_max) <= 1e-6 + # Take absolute values element wise of each eigenvector. + # as they are only unique to a minus sign. + assert np.linalg.norm(np.abs(eigen_vector) - np.abs(eigen_vector_max)) <= 1e-6 + + +if __name__ == "__main__": + import doctest + + doctest.testmod() + test_power_iteration() From e296f7b3ff26359ff411fa9e1ea4696aa3d975db Mon Sep 17 00:00:00 2001 From: Vasu Gamdha <40864108+vasugamdha@users.noreply.github.com> Date: Sun, 26 Jul 2020 16:12:18 +0530 Subject: [PATCH 0683/1071] Updated maths/number_of_digits.py (#2221) * Updated number_of_digits.py Added two more methods! * Update number_of_digits.py * Update number_of_digits.py * Added benchmarks! * Update number_of_digits.py * Update number_of_digits.py * Update number_of_digits.py * Update number_of_digits.py * Update number_of_digits.py * Update number_of_digits.py * Update number_of_digits.py --- maths/number_of_digits.py | 83 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 82 insertions(+), 1 deletion(-) diff --git a/maths/number_of_digits.py b/maths/number_of_digits.py index 30e82f60fadc..4bafa613c980 100644 --- a/maths/number_of_digits.py +++ b/maths/number_of_digits.py @@ -1,3 +1,7 @@ +import math +from timeit import timeit + + def num_digits(n: int) -> int: """ Find the number of digits in a number. @@ -14,5 +18,82 @@ def num_digits(n: int) -> int: return digits +def num_digits_fast(n: int) -> int: + """ + Find the number of digits in a number. + abs() is used as logarithm for negative numbers is not defined. + + >>> num_digits_fast(12345) + 5 + >>> num_digits_fast(123) + 3 + """ + return (math.floor(math.log(abs(n), 10) + 1)) + + +def num_digits_faster(n: int) -> int: + """ + Find the number of digits in a number. + abs() is used for negative numbers + + >>> num_digits_faster(12345) + 5 + >>> num_digits_faster(123) + 3 + """ + return (len(str(abs(n)))) + + +def benchmark() -> None: + """ + Benchmark code for comparing 3 functions, + with 3 different length int values. + """ + print('\nFor small_num = ', small_num, ':') + print("> num_digits()", + '\t\tans =', num_digits(small_num), + '\ttime =', timeit("z.num_digits(z.small_num)", + setup="import __main__ as z"), "seconds") + print("> num_digits_fast()", + '\tans =', num_digits_fast(small_num), + '\ttime =', timeit("z.num_digits_fast(z.small_num)", + setup="import __main__ as z"), "seconds") + print("> num_digits_faster()", + '\tans =', num_digits_faster(small_num), + '\ttime =', timeit("z.num_digits_faster(z.small_num)", + setup="import __main__ as z"), "seconds") + + print('\nFor medium_num = ', medium_num, ':') + print("> num_digits()", + '\t\tans =', num_digits(medium_num), + '\ttime =', timeit("z.num_digits(z.medium_num)", + setup="import __main__ as z"), "seconds") + print("> num_digits_fast()", + '\tans =', num_digits_fast(medium_num), + '\ttime =', timeit("z.num_digits_fast(z.medium_num)", + setup="import __main__ as z"), "seconds") + print("> num_digits_faster()", + '\tans =', num_digits_faster(medium_num), + '\ttime =', timeit("z.num_digits_faster(z.medium_num)", + setup="import __main__ as z"), "seconds") + + print('\nFor large_num = ', large_num, ':') + print("> num_digits()", + '\t\tans =', num_digits(large_num), + '\ttime =', timeit("z.num_digits(z.large_num)", + setup="import __main__ as z"), "seconds") + print("> num_digits_fast()", + '\tans =', num_digits_fast(large_num), + '\ttime =', timeit("z.num_digits_fast(z.large_num)", + setup="import __main__ as z"), "seconds") + print("> num_digits_faster()", + '\tans =', num_digits_faster(large_num), + '\ttime =', timeit("z.num_digits_faster(z.large_num)", + setup="import __main__ as z"), "seconds") + + if __name__ == "__main__": - print(num_digits(12345)) # ===> 5 + small_num = 262144 + medium_num = 1125899906842624 + large_num = 1267650600228229401496703205376 + benchmark() From dfb4ce407497e549564c5181cfda33ee4f64843f Mon Sep 17 00:00:00 2001 From: Utsav Akash Naskar Date: Mon, 27 Jul 2020 15:03:13 +0530 Subject: [PATCH 0684/1071] Added Finding Exponent Program (#2238) * Finding Exponent Program * Build Error Fix - 1 * Build Error Fix - 2 * Error Fix - 1 datatype * self-documenting naming convension added * Update and rename exponent_recursion.py to power_using_recursion.py * Fix typo * Fix typo Co-authored-by: Christian Clauss --- maths/power_using_recursion.py | 36 ++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) create mode 100644 maths/power_using_recursion.py diff --git a/maths/power_using_recursion.py b/maths/power_using_recursion.py new file mode 100644 index 000000000000..f82097f6d8ec --- /dev/null +++ b/maths/power_using_recursion.py @@ -0,0 +1,36 @@ +""" +== Raise base to the power of exponent using recursion == + Input --> + Enter the base: 3 + Enter the exponent: 4 + Output --> + 3 to the power of 4 is 81 + Input --> + Enter the base: 2 + Enter the exponent: 0 + Output --> + 2 to the power of 0 is 1 +""" + + +def power(base: int, exponent: int) -> float: + """ + power(3, 4) + 81 + >>> power(2, 0) + 1 + >>> all(power(base, exponent) == pow(base, exponent) + ... for base in range(-10, 10) for exponent in range(10)) + True + """ + return base * power(base, (exponent - 1)) if exponent else 1 + + +if __name__ == "__main__": + print("Raise base to the power of exponent using recursion...") + base = int(input("Enter the base: ").strip()) + exponent = int(input("Enter the exponent: ").strip()) + result = power(base, abs(exponent)) + if exponent < 0: # power() does not properly deal w/ negative exponents + result = 1 / result + print(f"{base} to the power of {exponent} is {result}") From bd74f20bf2c46989d56fa1f81b900675794e1267 Mon Sep 17 00:00:00 2001 From: spamegg <4255997+spamegg1@users.noreply.github.com> Date: Mon, 27 Jul 2020 16:23:55 +0300 Subject: [PATCH 0685/1071] added type hints and doctests to arithmetic_analysis/bisection.py (#2241) * added type hints and doctests to arithmetic_analysis/bisection.py continuing in line with #2128 * modified arithmetic_analysis/bisection.py Put back print statement at the end, replaced algorithm's print statement with an exception. * modified arithmetic_analysis/bisection.py Removed unnecessary type import "Optional" * modified arithmetic_analysis/bisection.py Replaced generic Exception with ValueError. * modified arithmetic_analysis/bisection.py fixed doctests --- arithmetic_analysis/bisection.py | 45 ++++++++++++++++++++++---------- 1 file changed, 31 insertions(+), 14 deletions(-) diff --git a/arithmetic_analysis/bisection.py b/arithmetic_analysis/bisection.py index 78582b025880..0ef691678702 100644 --- a/arithmetic_analysis/bisection.py +++ b/arithmetic_analysis/bisection.py @@ -1,12 +1,26 @@ -import math +from typing import Callable -def bisection( - function, a, b -): # finds where the function becomes 0 in [a,b] using bolzano - - start = a - end = b +def bisection(function: Callable[[float], float], a: float, b: float) -> float: + """ + finds where function becomes 0 in [a,b] using bolzano + >>> bisection(lambda x: x ** 3 - 1, -5, 5) + 1.0000000149011612 + >>> bisection(lambda x: x ** 3 - 1, 2, 1000) + Traceback (most recent call last): + ... + ValueError: could not find root in given interval. + >>> bisection(lambda x: x ** 2 - 4 * x + 3, 0, 2) + 1.0 + >>> bisection(lambda x: x ** 2 - 4 * x + 3, 2, 4) + 3.0 + >>> bisection(lambda x: x ** 2 - 4 * x + 3, 4, 1000) + Traceback (most recent call last): + ... + ValueError: could not find root in given interval. + """ + start: float = a + end: float = b if function(a) == 0: # one of the a or b is a root for the function return a elif function(b) == 0: @@ -14,12 +28,11 @@ def bisection( elif ( function(a) * function(b) > 0 ): # if none of these are root and they are both positive or negative, - # then his algorithm can't find the root - print("couldn't find root in [a,b]") - return + # then this algorithm can't find the root + raise ValueError("could not find root in given interval.") else: - mid = start + (end - start) / 2.0 - while abs(start - mid) > 10 ** -7: # until we achieve precise equals to 10^-7 + mid: float = start + (end - start) / 2.0 + while abs(start - mid) > 10 ** -7: # until precisely equals to 10^-7 if function(mid) == 0: return mid elif function(mid) * function(start) < 0: @@ -30,9 +43,13 @@ def bisection( return mid -def f(x): - return math.pow(x, 3) - 2 * x - 5 +def f(x: float) -> float: + return x ** 3 - 2 * x - 5 if __name__ == "__main__": print(bisection(f, 1, 1000)) + + import doctest + + doctest.testmod() From 093a56e3c28dee1d1abd67f735c7f686de459c9b Mon Sep 17 00:00:00 2001 From: lance-pyles <36748284+lance-pyles@users.noreply.github.com> Date: Mon, 27 Jul 2020 06:29:31 -0700 Subject: [PATCH 0686/1071] Remove function overhead in area (#2233) * remove function overhead add type hints * remove unused import --- maths/area.py | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/maths/area.py b/maths/area.py index f317118ade06..3a0fd97396e4 100644 --- a/maths/area.py +++ b/maths/area.py @@ -2,10 +2,9 @@ Find the area of various geometric shapes """ from math import pi -from typing import Union -def surface_area_cube(side_length: Union[int, float]) -> float: +def surface_area_cube(side_length: float) -> float: """ Calculate the Surface Area of a Cube. @@ -14,7 +13,7 @@ def surface_area_cube(side_length: Union[int, float]) -> float: >>> surface_area_cube(3) 54 """ - return 6 * pow(side_length, 2) + return 6 * side_length ** 2 def surface_area_sphere(radius: float) -> float: @@ -28,10 +27,10 @@ def surface_area_sphere(radius: float) -> float: >>> surface_area_sphere(1) 12.566370614359172 """ - return 4 * pi * pow(radius, 2) + return 4 * pi * radius ** 2 -def area_rectangle(length, width): +def area_rectangle(length: float, width: float) -> float: """ Calculate the area of a rectangle @@ -41,17 +40,17 @@ def area_rectangle(length, width): return length * width -def area_square(side_length): +def area_square(side_length: float) -> float: """ Calculate the area of a square >>> area_square(10) 100 """ - return pow(side_length, 2) + return side_length ** 2 -def area_triangle(base, height): +def area_triangle(base: float, height: float) -> float: """ Calculate the area of a triangle @@ -61,7 +60,7 @@ def area_triangle(base, height): return (base * height) / 2 -def area_parallelogram(base, height): +def area_parallelogram(base: float, height: float) -> float: """ Calculate the area of a parallelogram @@ -71,7 +70,7 @@ def area_parallelogram(base, height): return base * height -def area_trapezium(base1, base2, height): +def area_trapezium(base1: float, base2: float, height: float) -> float: """ Calculate the area of a trapezium @@ -81,14 +80,14 @@ def area_trapezium(base1, base2, height): return 1 / 2 * (base1 + base2) * height -def area_circle(radius): +def area_circle(radius: float) -> float: """ Calculate the area of a circle >>> area_circle(20) 1256.6370614359173 """ - return pi * pow(radius, 2) + return pi * radius ** 2 def main(): From ee282d3687bf77f289b6282e9e7782baf2513bd3 Mon Sep 17 00:00:00 2001 From: Palash Sharma <54630543+palashsharma891@users.noreply.github.com> Date: Tue, 28 Jul 2020 11:25:35 +0530 Subject: [PATCH 0687/1071] Update min_heap.py (#2245) Changed min head to min heap in the first line. --- data_structures/heap/min_heap.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data_structures/heap/min_heap.py b/data_structures/heap/min_heap.py index 5b96319197ec..9265c4839536 100644 --- a/data_structures/heap/min_heap.py +++ b/data_structures/heap/min_heap.py @@ -1,4 +1,4 @@ -# Min head data structure +# Min heap data structure # with decrease key functionality - in O(log(n)) time From 9ea144f3bdc8260c43715897103cf2177842e3db Mon Sep 17 00:00:00 2001 From: Utsav Akash Naskar Date: Tue, 28 Jul 2020 13:29:44 +0530 Subject: [PATCH 0688/1071] Added Python Program to Check Perfect Number (#2244) * Added Python Program to Check Perfet Number * CodeSpell Error Fix - 1 * Build Error Fix - 1 * Made suggested changes * Use generator expression Co-authored-by: Christian Clauss --- maths/perfect_number.py | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 maths/perfect_number.py diff --git a/maths/perfect_number.py b/maths/perfect_number.py new file mode 100644 index 000000000000..3de742399f62 --- /dev/null +++ b/maths/perfect_number.py @@ -0,0 +1,34 @@ +""" +== Perfect Number == +In number theory, a perfect number is a positive integer that is equal to the sum of +its positive divisors, excluding the number itself. +For example: 6 ==> divisors[1, 2, 3, 6] + Excluding 6, the sum(divisors) is 1 + 2 + 3 = 6 + So, 6 is a Perfect Number + +Other examples of Perfect Numbers: 28, 486, ... + +https://en.wikipedia.org/wiki/Perfect_number +""" + + +def perfect(number: int) -> bool: + """ + >>> perfect(27) + False + >>> perfect(28) + True + >>> perfect(29) + False + + Start from 1 because dividing by 0 will raise ZeroDivisionError. + A number at most can be divisible by the half of the number except the number + itself. For example, 6 is at most can be divisible by 3 except by 6 itself. + """ + return sum(i for i in range(1, ((number // 2) + 1)) if number % i == 0) == number + + +if __name__ == "__main__": + print("Program to check whether a number is a Perfect number or not.......") + number = int(input("Enter number: ").strip()) + print(f"{number} is {'' if perfect(number) else 'not '} a Perfect Number.") From f760ecc73c35e2c141adad1a5eb29c4cd8a2f57b Mon Sep 17 00:00:00 2001 From: lance-pyles <36748284+lance-pyles@users.noreply.github.com> Date: Tue, 28 Jul 2020 15:11:16 -0700 Subject: [PATCH 0689/1071] initial commit of prefix-conversions (#2243) * initial commit of prefix-conversions * minor tweaks; * Lose the blank lines Co-authored-by: Christian Clauss --- conversions/prefix_conversions.py | 100 ++++++++++++++++++++++++++++++ 1 file changed, 100 insertions(+) create mode 100644 conversions/prefix_conversions.py diff --git a/conversions/prefix_conversions.py b/conversions/prefix_conversions.py new file mode 100644 index 000000000000..c2440d1cf886 --- /dev/null +++ b/conversions/prefix_conversions.py @@ -0,0 +1,100 @@ +""" +Convert International System of Units (SI) and Binary prefixes +""" +from enum import Enum +from typing import Union + + +class SI_Unit(Enum): + yotta = 24 + zetta = 21 + exa = 18 + peta = 15 + tera = 12 + giga = 9 + mega = 6 + kilo = 3 + hecto = 2 + deca = 1 + deci = -1 + centi = -2 + milli = -3 + micro = -6 + nano = -9 + pico = -12 + femto = -15 + atto = -18 + zepto = -21 + yocto = -24 + + +class Binary_Unit(Enum): + yotta = 8 + zetta = 7 + exa = 6 + peta = 5 + tera = 4 + giga = 3 + mega = 2 + kilo = 1 + + +def convert_si_prefix( + known_amount: float, + known_prefix: Union[str, SI_Unit], + unknown_prefix: Union[str, SI_Unit], +) -> float: + """ + Wikipedia reference: https://en.wikipedia.org/wiki/Binary_prefix + Wikipedia reference: https://en.wikipedia.org/wiki/International_System_of_Units + >>> convert_si_prefix(1, SI_Unit.giga, SI_Unit.mega) + 1000 + >>> convert_si_prefix(1, SI_Unit.mega, SI_Unit.giga) + 0.001 + >>> convert_si_prefix(1, SI_Unit.kilo, SI_Unit.kilo) + 1 + >>> convert_si_prefix(1, 'giga', 'mega') + 1000 + >>> convert_si_prefix(1, 'gIGa', 'mEGa') + 1000 + """ + if isinstance(known_prefix, str): + known_prefix: SI_Unit = SI_Unit[known_prefix.lower()] + if isinstance(unknown_prefix, str): + unknown_prefix: SI_Unit = SI_Unit[unknown_prefix.lower()] + unknown_amount = known_amount * (10 ** (known_prefix.value - unknown_prefix.value)) + return unknown_amount + + +def convert_binary_prefix( + known_amount: float, + known_prefix: Union[str, Binary_Unit], + unknown_prefix: Union[str, Binary_Unit], +) -> float: + """ + Wikipedia reference: https://en.wikipedia.org/wiki/Metric_prefix + >>> convert_binary_prefix(1, Binary_Unit.giga, Binary_Unit.mega) + 1024 + >>> convert_binary_prefix(1, Binary_Unit.mega, Binary_Unit.giga) + 0.0009765625 + >>> convert_binary_prefix(1, Binary_Unit.kilo, Binary_Unit.kilo) + 1 + >>> convert_binary_prefix(1, 'giga', 'mega') + 1024 + >>> convert_binary_prefix(1, 'gIGa', 'mEGa') + 1024 + """ + if isinstance(known_prefix, str): + known_prefix: Binary_Unit = Binary_Unit[known_prefix.lower()] + if isinstance(unknown_prefix, str): + unknown_prefix: Binary_Unit = Binary_Unit[unknown_prefix.lower()] + unknown_amount = known_amount * ( + 2 ** ((known_prefix.value - unknown_prefix.value) * 10) + ) + return unknown_amount + + +if __name__ == "__main__": + import doctest + + doctest.testmod() From c7a5f1673e3b8782c432a3dc24119a9f51759c5d Mon Sep 17 00:00:00 2001 From: Utsav Akash Naskar Date: Wed, 29 Jul 2020 14:24:05 +0530 Subject: [PATCH 0690/1071] Python Program to Check Krishnamurthy Number (#2248) * Added Python Program to Check Perfet Number * CodeSpell Error Fix - 1 * Build Error Fix - 1 * Made suggested changes * Use generator expression * Added Python Program to Check Krishnamurthy Number or not * Added Python Program to Check Krishnamurthy Number * Added Python Program to Check Krishnamurthy Number or not * Build Error Fix - 1 * Build Error Fix - 2 * Fix Brackets positions * Fix Character Exceeding Error * Made Review Changes * Build Error Fix - 3 * Build Error Fix - 4 * Update krishnamurthy_number.py Co-authored-by: Christian Clauss --- maths/krishnamurthy_number.py | 49 +++++++++++++++++++++++++++++++++++ maths/perfect_number.py | 6 ++--- 2 files changed, 52 insertions(+), 3 deletions(-) create mode 100644 maths/krishnamurthy_number.py diff --git a/maths/krishnamurthy_number.py b/maths/krishnamurthy_number.py new file mode 100644 index 000000000000..c88f68a07f27 --- /dev/null +++ b/maths/krishnamurthy_number.py @@ -0,0 +1,49 @@ +""" + == Krishnamurthy Number == +It is also known as Peterson Number +A Krishnamurthy Number is a number whose sum of the +factorial of the digits equals to the original +number itself. + +For example: 145 = 1! + 4! + 5! + So, 145 is a Krishnamurthy Number +""" + + +def factorial(digit: int) -> int: + """ + >>> factorial(3) + 6 + >>> factorial(0) + 1 + >>> factorial(5) + 120 + """ + + return 1 if digit in (0, 1) else (digit * factorial(digit - 1)) + + +def krishnamurthy(number: int) -> bool: + """ + >>> krishnamurthy(145) + True + >>> krishnamurthy(240) + False + >>> krishnamurthy(1) + True + """ + + factSum = 0 + duplicate = number + while duplicate > 0: + duplicate, digit = divmod(duplicate, 10) + factSum += factorial(digit) + return factSum == number + + +if __name__ == "__main__": + print("Program to check whether a number is a Krisnamurthy Number or not.") + number = int(input("Enter number: ").strip()) + print( + f"{number} is {'' if krishnamurthy(number) else 'not '}a Krishnamurthy Number." + ) diff --git a/maths/perfect_number.py b/maths/perfect_number.py index 3de742399f62..148e988fb4c5 100644 --- a/maths/perfect_number.py +++ b/maths/perfect_number.py @@ -25,10 +25,10 @@ def perfect(number: int) -> bool: A number at most can be divisible by the half of the number except the number itself. For example, 6 is at most can be divisible by 3 except by 6 itself. """ - return sum(i for i in range(1, ((number // 2) + 1)) if number % i == 0) == number + return sum(i for i in range(1, number // 2 + 1) if number % i == 0) == number if __name__ == "__main__": - print("Program to check whether a number is a Perfect number or not.......") + print("Program to check whether a number is a Perfect number or not...") number = int(input("Enter number: ").strip()) - print(f"{number} is {'' if perfect(number) else 'not '} a Perfect Number.") + print(f"{number} is {'' if perfect(number) else 'not '}a Perfect Number.") From 373f193c6dbeef1a91ab0877dadc12a42efcf862 Mon Sep 17 00:00:00 2001 From: aryan26roy <50577809+aryan26roy@users.noreply.github.com> Date: Thu, 30 Jul 2020 01:02:36 +0530 Subject: [PATCH 0691/1071] Correcting the Gaussian Formula (#2249) * Correcting the Gaussian Formula I have added the parenthesis around the 2*sigma^2 term. * Update gaussian.py * Update gaussian.py * Update gaussian.py * Update gaussian.py * Update gaussian.py * Update gaussian.py Co-authored-by: Christian Clauss --- maths/gaussian.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/maths/gaussian.py b/maths/gaussian.py index 5d5800e00989..a5dba50a927d 100644 --- a/maths/gaussian.py +++ b/maths/gaussian.py @@ -1,9 +1,5 @@ """ Reference: https://en.wikipedia.org/wiki/Gaussian_function - -python/black : True -python : 3.7.3 - """ from numpy import exp, pi, sqrt @@ -16,6 +12,12 @@ def gaussian(x, mu: float = 0.0, sigma: float = 1.0) -> int: >>> gaussian(24) 3.342714441794458e-126 + >>> gaussian(1, 4, 2) + 0.06475879783294587 + + >>> gaussian(1, 5, 3) + 0.05467002489199788 + Supports NumPy Arrays Use numpy.meshgrid with this to generate gaussian blur on images. >>> import numpy as np @@ -49,8 +51,8 @@ def gaussian(x, mu: float = 0.0, sigma: float = 1.0) -> int: >>> gaussian(2523, mu=234234, sigma=3425) 0.0 - """ - return 1 / sqrt(2 * pi * sigma ** 2) * exp(-((x - mu) ** 2) / 2 * sigma ** 2) + """ + return 1 / sqrt(2 * pi * sigma ** 2) * exp(-((x - mu) ** 2) / (2 * sigma ** 2)) if __name__ == "__main__": From e2ee52d773aeaae4a7637d85c9dc421dc9b4d0b4 Mon Sep 17 00:00:00 2001 From: Sumuk Shashidhar Date: Sat, 1 Aug 2020 00:06:02 +0530 Subject: [PATCH 0692/1071] removed redundant data_structures folder (#2256) --- data_structures/{data_structures => }/heap/heap_generic.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename data_structures/{data_structures => }/heap/heap_generic.py (100%) diff --git a/data_structures/data_structures/heap/heap_generic.py b/data_structures/heap/heap_generic.py similarity index 100% rename from data_structures/data_structures/heap/heap_generic.py rename to data_structures/heap/heap_generic.py From 4535283554efea10db06919b73c77c825e8e9d2f Mon Sep 17 00:00:00 2001 From: Christian Clauss Date: Sat, 1 Aug 2020 05:53:23 +0200 Subject: [PATCH 0693/1071] Re-blacken (#2246) * Avoid double spaces * fixup! Format Python code with psf/black push Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> --- linear_algebra/src/test_linear_algebra.py | 9 +- maths/number_of_digits.py | 118 ++++++++++++++-------- web_programming/daily_horoscope.py | 2 +- 3 files changed, 79 insertions(+), 50 deletions(-) diff --git a/linear_algebra/src/test_linear_algebra.py b/linear_algebra/src/test_linear_algebra.py index be6245747f87..8db480ceb29d 100644 --- a/linear_algebra/src/test_linear_algebra.py +++ b/linear_algebra/src/test_linear_algebra.py @@ -8,14 +8,7 @@ """ import unittest -from lib import ( - Matrix, - Vector, - axpy, - squareZeroMatrix, - unitBasisVector, - zeroVector, -) +from lib import Matrix, Vector, axpy, squareZeroMatrix, unitBasisVector, zeroVector class Test(unittest.TestCase): diff --git a/maths/number_of_digits.py b/maths/number_of_digits.py index 4bafa613c980..ca4cce876617 100644 --- a/maths/number_of_digits.py +++ b/maths/number_of_digits.py @@ -28,7 +28,7 @@ def num_digits_fast(n: int) -> int: >>> num_digits_fast(123) 3 """ - return (math.floor(math.log(abs(n), 10) + 1)) + return math.floor(math.log(abs(n), 10) + 1) def num_digits_faster(n: int) -> int: @@ -41,7 +41,7 @@ def num_digits_faster(n: int) -> int: >>> num_digits_faster(123) 3 """ - return (len(str(abs(n)))) + return len(str(abs(n))) def benchmark() -> None: @@ -49,47 +49,83 @@ def benchmark() -> None: Benchmark code for comparing 3 functions, with 3 different length int values. """ - print('\nFor small_num = ', small_num, ':') - print("> num_digits()", - '\t\tans =', num_digits(small_num), - '\ttime =', timeit("z.num_digits(z.small_num)", - setup="import __main__ as z"), "seconds") - print("> num_digits_fast()", - '\tans =', num_digits_fast(small_num), - '\ttime =', timeit("z.num_digits_fast(z.small_num)", - setup="import __main__ as z"), "seconds") - print("> num_digits_faster()", - '\tans =', num_digits_faster(small_num), - '\ttime =', timeit("z.num_digits_faster(z.small_num)", - setup="import __main__ as z"), "seconds") + print("\nFor small_num = ", small_num, ":") + print( + "> num_digits()", + "\t\tans =", + num_digits(small_num), + "\ttime =", + timeit("z.num_digits(z.small_num)", setup="import __main__ as z"), + "seconds", + ) + print( + "> num_digits_fast()", + "\tans =", + num_digits_fast(small_num), + "\ttime =", + timeit("z.num_digits_fast(z.small_num)", setup="import __main__ as z"), + "seconds", + ) + print( + "> num_digits_faster()", + "\tans =", + num_digits_faster(small_num), + "\ttime =", + timeit("z.num_digits_faster(z.small_num)", setup="import __main__ as z"), + "seconds", + ) - print('\nFor medium_num = ', medium_num, ':') - print("> num_digits()", - '\t\tans =', num_digits(medium_num), - '\ttime =', timeit("z.num_digits(z.medium_num)", - setup="import __main__ as z"), "seconds") - print("> num_digits_fast()", - '\tans =', num_digits_fast(medium_num), - '\ttime =', timeit("z.num_digits_fast(z.medium_num)", - setup="import __main__ as z"), "seconds") - print("> num_digits_faster()", - '\tans =', num_digits_faster(medium_num), - '\ttime =', timeit("z.num_digits_faster(z.medium_num)", - setup="import __main__ as z"), "seconds") + print("\nFor medium_num = ", medium_num, ":") + print( + "> num_digits()", + "\t\tans =", + num_digits(medium_num), + "\ttime =", + timeit("z.num_digits(z.medium_num)", setup="import __main__ as z"), + "seconds", + ) + print( + "> num_digits_fast()", + "\tans =", + num_digits_fast(medium_num), + "\ttime =", + timeit("z.num_digits_fast(z.medium_num)", setup="import __main__ as z"), + "seconds", + ) + print( + "> num_digits_faster()", + "\tans =", + num_digits_faster(medium_num), + "\ttime =", + timeit("z.num_digits_faster(z.medium_num)", setup="import __main__ as z"), + "seconds", + ) - print('\nFor large_num = ', large_num, ':') - print("> num_digits()", - '\t\tans =', num_digits(large_num), - '\ttime =', timeit("z.num_digits(z.large_num)", - setup="import __main__ as z"), "seconds") - print("> num_digits_fast()", - '\tans =', num_digits_fast(large_num), - '\ttime =', timeit("z.num_digits_fast(z.large_num)", - setup="import __main__ as z"), "seconds") - print("> num_digits_faster()", - '\tans =', num_digits_faster(large_num), - '\ttime =', timeit("z.num_digits_faster(z.large_num)", - setup="import __main__ as z"), "seconds") + print("\nFor large_num = ", large_num, ":") + print( + "> num_digits()", + "\t\tans =", + num_digits(large_num), + "\ttime =", + timeit("z.num_digits(z.large_num)", setup="import __main__ as z"), + "seconds", + ) + print( + "> num_digits_fast()", + "\tans =", + num_digits_fast(large_num), + "\ttime =", + timeit("z.num_digits_fast(z.large_num)", setup="import __main__ as z"), + "seconds", + ) + print( + "> num_digits_faster()", + "\tans =", + num_digits_faster(large_num), + "\ttime =", + timeit("z.num_digits_faster(z.large_num)", setup="import __main__ as z"), + "seconds", + ) if __name__ == "__main__": diff --git a/web_programming/daily_horoscope.py b/web_programming/daily_horoscope.py index ecb37ce106f4..b0dd1cd65924 100644 --- a/web_programming/daily_horoscope.py +++ b/web_programming/daily_horoscope.py @@ -1,5 +1,5 @@ -from bs4 import BeautifulSoup import requests +from bs4 import BeautifulSoup def horoscope(zodiac_sign: int, day: str) -> str: From c6c9f4707bc7a2bb3d8a8c90eea5dba7ac12e55f Mon Sep 17 00:00:00 2001 From: Arin Khare <51566200+lol-cubes@users.noreply.github.com> Date: Sat, 1 Aug 2020 00:00:34 -0400 Subject: [PATCH 0694/1071] Karger's Algorithm (#2237) * Add implementation of Karger's Algorithm * Remove print statement from karger's algorithm function * Fix style issues in graphs/karger.py * Change for loops to set comprehensions where appropriate in graphs/karger.py --- graphs/karger.py | 83 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 83 insertions(+) create mode 100644 graphs/karger.py diff --git a/graphs/karger.py b/graphs/karger.py new file mode 100644 index 000000000000..d5a27c285fd4 --- /dev/null +++ b/graphs/karger.py @@ -0,0 +1,83 @@ +""" +An implementation of Karger's Algorithm for partitioning a graph. +""" + +import random +from typing import Dict, List, Set, Tuple + + +# Adjacency list representation of this graph: +# https://en.wikipedia.org/wiki/File:Single_run_of_Karger%E2%80%99s_Mincut_algorithm.svg +TEST_GRAPH = { + '1': ['2', '3', '4', '5'], + '2': ['1', '3', '4', '5'], + '3': ['1', '2', '4', '5', '10'], + '4': ['1', '2', '3', '5', '6'], + '5': ['1', '2', '3', '4', '7'], + '6': ['7', '8', '9', '10', '4'], + '7': ['6', '8', '9', '10', '5'], + '8': ['6', '7', '9', '10'], + '9': ['6', '7', '8', '10'], + '10': ['6', '7', '8', '9', '3'] +} + + +def partition_graph(graph: Dict[str, List[str]]) -> Set[Tuple[str, str]]: + """ + Partitions a graph using Karger's Algorithm. Implemented from + pseudocode found here: + https://en.wikipedia.org/wiki/Karger%27s_algorithm. + This function involves random choices, meaning it will not give + consistent outputs. + + Args: + graph: A dictionary containing adacency lists for the graph. + Nodes must be strings. + + Returns: + The cutset of the cut found by Karger's Algorithm. + + >>> graph = {'0':['1'], '1':['0']} + >>> partition_graph(graph) + {('0', '1')} + """ + # Dict that maps contracted nodes to a list of all the nodes it "contains." + contracted_nodes = {node: {node} for node in graph} + + graph_copy = {node: graph[node][:] for node in graph} + + while len(graph_copy) > 2: + + # Choose a random edge. + u = random.choice(list(graph_copy.keys())) + v = random.choice(graph_copy[u]) + + # Contract edge (u, v) to new node uv + uv = u + v + uv_neighbors = list(set(graph_copy[u] + graph_copy[v])) + uv_neighbors.remove(u) + uv_neighbors.remove(v) + graph_copy[uv] = uv_neighbors + for neighbor in uv_neighbors: + graph_copy[neighbor].append(uv) + + contracted_nodes[uv] = {contracted_node for contracted_node in + contracted_nodes[u].union(contracted_nodes[v])} + + # Remove nodes u and v. + del graph_copy[u] + del graph_copy[v] + for neighbor in uv_neighbors: + if u in graph_copy[neighbor]: + graph_copy[neighbor].remove(u) + if v in graph_copy[neighbor]: + graph_copy[neighbor].remove(v) + + # Find cutset. + groups = [contracted_nodes[node] for node in graph_copy] + return {(node, neighbor) for node in groups[0] + for neighbor in graph[node] if neighbor in groups[1]} + + +if __name__ == "__main__": + print(partition_graph(TEST_GRAPH)) From b57b6abb48c468c47b511de8613d2fdd20d5e07b Mon Sep 17 00:00:00 2001 From: Prashant Anand Date: Sat, 1 Aug 2020 14:26:04 +0900 Subject: [PATCH 0695/1071] fix doctests for recursive binary search (#2229) --- searches/binary_search.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/searches/binary_search.py b/searches/binary_search.py index 0d9e730258d7..d0f6296168fa 100644 --- a/searches/binary_search.py +++ b/searches/binary_search.py @@ -261,16 +261,16 @@ def binary_search_by_recursion(sorted_collection, item, left, right): :return: index of found item or None if item is not found Examples: - >>> binary_search_std_lib([0, 5, 7, 10, 15], 0) + >>> binary_search_by_recursion([0, 5, 7, 10, 15], 0, 0, 4) 0 - >>> binary_search_std_lib([0, 5, 7, 10, 15], 15) + >>> binary_search_by_recursion([0, 5, 7, 10, 15], 15, 0, 4) 4 - >>> binary_search_std_lib([0, 5, 7, 10, 15], 5) + >>> binary_search_by_recursion([0, 5, 7, 10, 15], 5, 0, 4) 1 - >>> binary_search_std_lib([0, 5, 7, 10, 15], 6) + >>> binary_search_by_recursion([0, 5, 7, 10, 15], 6, 0, 4) """ if right < left: From 9cda130c071d9454a78e217c9ae781044eaceb4d Mon Sep 17 00:00:00 2001 From: spamegg <4255997+spamegg1@users.noreply.github.com> Date: Sat, 1 Aug 2020 09:02:31 +0300 Subject: [PATCH 0696/1071] added type hints and doctests to arithmetic_analysis/intersection.py (#2242) continuing #2128 --- arithmetic_analysis/intersection.py | 33 ++++++++++++++++++++++++----- 1 file changed, 28 insertions(+), 5 deletions(-) diff --git a/arithmetic_analysis/intersection.py b/arithmetic_analysis/intersection.py index 8d14555b366f..204dd5d8a935 100644 --- a/arithmetic_analysis/intersection.py +++ b/arithmetic_analysis/intersection.py @@ -1,15 +1,38 @@ import math +from typing import Callable -def intersection(function, x0, x1): +def intersection(function: Callable[[float], float], x0: float, x1: float) -> float: """ function is the f we want to find its root x0 and x1 are two random starting points + >>> intersection(lambda x: x ** 3 - 1, -5, 5) + 0.9999999999954654 + >>> intersection(lambda x: x ** 3 - 1, 5, 5) + Traceback (most recent call last): + ... + ZeroDivisionError: float division by zero, could not find root + >>> intersection(lambda x: x ** 3 - 1, 100, 200) + 1.0000000000003888 + >>> intersection(lambda x: x ** 2 - 4 * x + 3, 0, 2) + 0.9999999998088019 + >>> intersection(lambda x: x ** 2 - 4 * x + 3, 2, 4) + 2.9999999998088023 + >>> intersection(lambda x: x ** 2 - 4 * x + 3, 4, 1000) + 3.0000000001786042 + >>> intersection(math.sin, -math.pi, math.pi) + 0.0 + >>> intersection(math.cos, -math.pi, math.pi) + Traceback (most recent call last): + ... + ZeroDivisionError: float division by zero, could not find root """ - x_n = x0 - x_n1 = x1 + x_n: float = x0 + x_n1: float = x1 while True: - x_n2 = x_n1 - ( + if x_n == x_n1 or function(x_n1) == function(x_n): + raise ZeroDivisionError("float division by zero, could not find root") + x_n2: float = x_n1 - ( function(x_n1) / ((function(x_n1) - function(x_n)) / (x_n1 - x_n)) ) if abs(x_n2 - x_n1) < 10 ** -5: @@ -18,7 +41,7 @@ def intersection(function, x0, x1): x_n1 = x_n2 -def f(x): +def f(x: float) -> float: return math.pow(x, 3) - (2 * x) - 5 From 473072bd4fb82bba172c6028b4b613d321d57713 Mon Sep 17 00:00:00 2001 From: spamegg <4255997+spamegg1@users.noreply.github.com> Date: Sat, 1 Aug 2020 16:17:46 +0300 Subject: [PATCH 0697/1071] added type hints and doctests to arithmetic_analysis/newton_method.py (#2259) * added type hints and doctests to arithmetic_analysis/newton_method.py Continuing #2128 Also changed some variable names, made them more descriptive. * Added type hints and doctests to arithmetic_analysis/newton_method.py added a type alias for Callable[[float], float] and cleaned up the exception handling * added type hints and doctests to arithmetic_analysis/newton_method.py improved exception handling * Update newton_method.py Co-authored-by: Christian Clauss --- arithmetic_analysis/newton_method.py | 47 +++++++++++++++++++++------- 1 file changed, 36 insertions(+), 11 deletions(-) diff --git a/arithmetic_analysis/newton_method.py b/arithmetic_analysis/newton_method.py index 1408a983041d..fd7ad45c2944 100644 --- a/arithmetic_analysis/newton_method.py +++ b/arithmetic_analysis/newton_method.py @@ -1,23 +1,48 @@ """Newton's Method.""" # Newton's Method - https://en.wikipedia.org/wiki/Newton%27s_method - - -# function is the f(x) and function1 is the f'(x) -def newton(function, function1, startingInt): - x_n = startingInt +from typing import Callable + +RealFunc = Callable[[float], float] # type alias for a real -> real function + + +# function is the f(x) and derivative is the f'(x) +def newton(function: RealFunc, derivative: RealFunc, starting_int: int,) -> float: + """ + >>> newton(lambda x: x ** 3 - 2 * x - 5, lambda x: 3 * x ** 2 - 2, 3) + 2.0945514815423474 + >>> newton(lambda x: x ** 3 - 1, lambda x: 3 * x ** 2, -2) + 1.0 + >>> newton(lambda x: x ** 3 - 1, lambda x: 3 * x ** 2, -4) + 1.0000000000000102 + >>> import math + >>> newton(math.sin, math.cos, 1) + 0.0 + >>> newton(math.sin, math.cos, 2) + 3.141592653589793 + >>> newton(math.cos, lambda x: -math.sin(x), 2) + 1.5707963267948966 + >>> newton(math.cos, lambda x: -math.sin(x), 0) + Traceback (most recent call last): + ... + ZeroDivisionError: Could not find root + """ + prev_guess float(starting_int) while True: - x_n1 = x_n - function(x_n) / function1(x_n) - if abs(x_n - x_n1) < 10 ** -5: - return x_n1 - x_n = x_n1 + try: + next_guess = prev_guess - function(prev_guess) / derivative(prev_guess) + except ZeroDivisionError: + raise ZeroDivisionError("Could not find root") + if abs(prev_guess - next_guess) < 10 ** -5: + return next_guess + prev_guess = next_guess -def f(x): +def f(x: float) -> float: return (x ** 3) - (2 * x) - 5 -def f1(x): +def f1(x: float) -> float: return 3 * (x ** 2) - 2 From 1495382367a2a25ffd362b71610251afe4fab668 Mon Sep 17 00:00:00 2001 From: Sven <69042800+Svn-Sp@users.noreply.github.com> Date: Sat, 1 Aug 2020 22:11:39 +0200 Subject: [PATCH 0698/1071] Fixed bug with incorrect LU decomposition (#2261) * Fixed Bug #2257 * = Co-authored-by: Svn-Sp Co-authored-by: Christian Clauss --- arithmetic_analysis/lu_decomposition.py | 8 ++++---- arithmetic_analysis/newton_method.py | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/arithmetic_analysis/lu_decomposition.py b/arithmetic_analysis/lu_decomposition.py index 4372621d74cb..763ba60f32b7 100644 --- a/arithmetic_analysis/lu_decomposition.py +++ b/arithmetic_analysis/lu_decomposition.py @@ -13,15 +13,15 @@ def LUDecompose(table): if rows != columns: return [] for i in range(columns): - for j in range(i - 1): + for j in range(i): sum = 0 - for k in range(j - 1): + for k in range(j): sum += L[i][k] * U[k][j] L[i][j] = (table[i][j] - sum) / U[j][j] L[i][i] = 1 - for j in range(i - 1, columns): + for j in range(i, columns): sum1 = 0 - for k in range(i - 1): + for k in range(i): sum1 += L[i][k] * U[k][j] U[i][j] = table[i][j] - sum1 return L, U diff --git a/arithmetic_analysis/newton_method.py b/arithmetic_analysis/newton_method.py index fd7ad45c2944..542f994aaf19 100644 --- a/arithmetic_analysis/newton_method.py +++ b/arithmetic_analysis/newton_method.py @@ -27,7 +27,7 @@ def newton(function: RealFunc, derivative: RealFunc, starting_int: int,) -> floa ... ZeroDivisionError: Could not find root """ - prev_guess float(starting_int) + prev_guess = float(starting_int) while True: try: next_guess = prev_guess - function(prev_guess) / derivative(prev_guess) From b875f66f00b5c0113176932262cc0d58ba78bd4c Mon Sep 17 00:00:00 2001 From: poloso Date: Sat, 1 Aug 2020 23:38:00 -0500 Subject: [PATCH 0699/1071] Deleted optimization empty directory (#2262) --- optimization/requirements.txt | 1 - 1 file changed, 1 deletion(-) delete mode 100644 optimization/requirements.txt diff --git a/optimization/requirements.txt b/optimization/requirements.txt deleted file mode 100644 index 4b43f7e68658..000000000000 --- a/optimization/requirements.txt +++ /dev/null @@ -1 +0,0 @@ -matplotlib \ No newline at end of file From b2e8672f028f3537cbce40cb56a28929f74d2482 Mon Sep 17 00:00:00 2001 From: TheSuperNoob Date: Sun, 2 Aug 2020 17:55:18 +0200 Subject: [PATCH 0700/1071] Fix doubly linked list algorithm (#2062) * Fix doubly linked list algorithm * Fix bug with insert_at_tail method Create __str__() method for Node class and LinkedList class * Simplify __str__() of LinkedList Returns empty string if there are no elements in the list * Fix description --- .../linked_list/doubly_linked_list.py | 148 +++++++++++------- 1 file changed, 92 insertions(+), 56 deletions(-) diff --git a/data_structures/linked_list/doubly_linked_list.py b/data_structures/linked_list/doubly_linked_list.py index e449ed6ec8ac..1b4005f59fae 100644 --- a/data_structures/linked_list/doubly_linked_list.py +++ b/data_structures/linked_list/doubly_linked_list.py @@ -5,80 +5,116 @@ - Each link references the next link and the previous one. - A Doubly Linked List (DLL) contains an extra pointer, typically called previous pointer, together with next pointer and data which are there in singly linked list. - - Advantages over SLL - IT can be traversed in both forward and backward direction., + - Advantages over SLL - It can be traversed in both forward and backward direction. Delete operation is more efficient""" -class LinkedList: # making main class named linked list +class LinkedList: + """ + >>> linked_list = LinkedList() + >>> linked_list.insert_at_head("a") + >>> linked_list.insert_at_tail("b") + >>> linked_list.delete_tail() + 'b' + >>> linked_list.is_empty + False + >>> linked_list.delete_head() + 'a' + >>> linked_list.is_empty + True + """ + def __init__(self): - self.head = None - self.tail = None + self.head = None # First node in list + self.tail = None # Last node in list + + def __str__(self): + current = self.head + nodes = [] + while current is not None: + nodes.append(current) + current = current.next + return " ".join(str(node) for node in nodes) - def insertHead(self, x): - newLink = Link(x) # Create a new link with a value attached to it - if self.isEmpty(): # Set the first element added to be the tail - self.tail = newLink + def insert_at_head(self, data): + new_node = Node(data) + if self.is_empty: + self.tail = new_node + self.head = new_node + else: + self.head.previous = new_node + new_node.next = self.head + self.head = new_node + + def delete_head(self) -> str: + if self.is_empty: + return "List is empty" + + head_data = self.head.data + if self.head.next: + self.head = self.head.next + self.head.previous = None + + else: # If there is no next previous node + self.head = None + self.tail = None + + return head_data + + def insert_at_tail(self, data): + new_node = Node(data) + if self.is_empty: + self.tail = new_node + self.head = new_node else: - self.head.previous = newLink # newLink <-- currenthead(head) - newLink.next = self.head # newLink <--> currenthead(head) - self.head = newLink # newLink(head) <--> oldhead - - def deleteHead(self): - temp = self.head - self.head = self.head.next # oldHead <--> 2ndElement(head) - # oldHead --> 2ndElement(head) nothing pointing at it so the old head will be - # removed - self.head.previous = None - if self.head is None: - self.tail = None # if empty linked list - return temp - - def insertTail(self, x): - newLink = Link(x) - newLink.next = None # currentTail(tail) newLink --> - self.tail.next = newLink # currentTail(tail) --> newLink --> - newLink.previous = self.tail # currentTail(tail) <--> newLink --> - self.tail = newLink # oldTail <--> newLink(tail) --> - - def deleteTail(self): - temp = self.tail - self.tail = self.tail.previous # 2ndLast(tail) <--> oldTail --> None - self.tail.next = None # 2ndlast(tail) --> None - return temp - - def delete(self, x): + self.tail.next = new_node + new_node.previous = self.tail + self.tail = new_node + + def delete_tail(self) -> str: + if self.is_empty: + return "List is empty" + + tail_data = self.tail.data + if self.tail.previous: + self.tail = self.tail.previous + self.tail.next = None + else: # if there is no previous node + self.head = None + self.tail = None + + return tail_data + + def delete(self, data) -> str: current = self.head - while current.value != x: # Find the position to delete - current = current.next + while current.data != data: # Find the position to delete + if current.next: + current = current.next + else: # We have reached the end an no value matches + return "No data matching given value" if current == self.head: - self.deleteHead() + self.delete_head() elif current == self.tail: - self.deleteTail() + self.delete_tail() else: # Before: 1 <--> 2(current) <--> 3 current.previous.next = current.next # 1 --> 3 current.next.previous = current.previous # 1 <--> 3 + return data - def isEmpty(self): # Will return True if the list is empty + @property + def is_empty(self): # return True if the list is empty return self.head is None - def display(self): # Prints contents of the list - current = self.head - while current is not None: - current.displayLink() - current = current.next - print() - - -class Link: - next = None # This points to the link in front of the new link - previous = None # This points to the link behind the new link - def __init__(self, x): - self.value = x +class Node: + def __init__(self, data): + self.data = data + self.previous = None + self.next = None - def displayLink(self): - print(f"{self.value}", end=" ") + def __str__(self): + return f"{self.data}" From 8e7aded87f5e1aaa88bf785c60432d3258b003f5 Mon Sep 17 00:00:00 2001 From: Rayvant Sahni <38404580+rayvantsahni@users.noreply.github.com> Date: Mon, 3 Aug 2020 12:15:53 +0530 Subject: [PATCH 0701/1071] World covid19 stats (#2271) * Josephus problem in Python Added the code for the josephus problem in python using circular linked lists. * Update josephus_problem.py * Added World covid19 stats in web programming * Delete josephus_problem.py * Type hints, algorithmic functions should not print Return a dict of world covid19 stats. Move all printing into the main functions. * Update world_covid19_stats.py * Update world_covid19_stats.py Co-authored-by: Christian Clauss --- web_programming/world_covid19_stats.py | 27 ++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 web_programming/world_covid19_stats.py diff --git a/web_programming/world_covid19_stats.py b/web_programming/world_covid19_stats.py new file mode 100644 index 000000000000..1907ed5f35f7 --- /dev/null +++ b/web_programming/world_covid19_stats.py @@ -0,0 +1,27 @@ +#!/usr/bin/env python3 + +''' +Provide the current worldwide COVID-19 statistics. +This data is being scrapped from 'https://www.worldometers.info/coronavirus/'. +''' + +import requests +from bs4 import BeautifulSoup + + +def world_covid19_stats(url: str = "https://www.worldometers.info/coronavirus") -> dict: + """ + Return a dict of current worldwide COVID-19 statistics + """ + soup = BeautifulSoup(requests.get(url).text, 'html.parser') + keys = soup.findAll('h1') + values = soup.findAll("div", {"class": "maincounter-number"}) + keys += soup.findAll("span", {"class": "panel-title"}) + values += soup.findAll("div", {"class": "number-table-main"}) + return {key.text.strip(): value.text.strip() for key, value in zip(keys, values)} + + +if __name__ == "__main__": + print("\033[1m" + "COVID-19 Status of the World" + "\033[0m\n") + for key, value in world_covid19_stats().items(): + print(f"{key}\n{value}\n") From a891f6802a7405a5587f5b693c8c54c5b05da233 Mon Sep 17 00:00:00 2001 From: Mark Date: Tue, 4 Aug 2020 23:11:07 +0300 Subject: [PATCH 0702/1071] Procentual proximity scoring algorithm implemented (#2280) * Procentual proximity scoring algorithm implemented - added requested changes - passed doctest - passed flake8 test * Apply suggestions from code review Co-authored-by: Christian Clauss * Function rename Co-authored-by: Christian Clauss --- other/scoring_algorithm.py | 89 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 89 insertions(+) create mode 100644 other/scoring_algorithm.py diff --git a/other/scoring_algorithm.py b/other/scoring_algorithm.py new file mode 100644 index 000000000000..a5d073d5e8d8 --- /dev/null +++ b/other/scoring_algorithm.py @@ -0,0 +1,89 @@ +''' +developed by: markmelnic +original repo: https://github.com/markmelnic/Scoring-Algorithm + +Analyse data using a range based percentual proximity algorithm +and calculate the linear maximum likelihood estimation. +The basic principle is that all values supplied will be broken +down to a range from 0 to 1 and each column's score will be added +up to get the total score. + +========== +Example for data of vehicles +price|mileage|registration_year +20k |60k |2012 +22k |50k |2011 +23k |90k |2015 +16k |210k |2010 + +We want the vehicle with the lowest price, +lowest mileage but newest registration year. +Thus the weights for each column are as follows: +[0, 0, 1] + +>>> procentual_proximity([[20, 60, 2012],[23, 90, 2015],[22, 50, 2011]], [0, 0, 1]) +[[20, 60, 2012, 2.0], [23, 90, 2015, 1.0], [22, 50, 2011, 1.3333333333333335]] +''' + + +def procentual_proximity(source_data : list, weights : list) -> list: + + ''' + weights - int list + possible values - 0 / 1 + 0 if lower values have higher weight in the data set + 1 if higher values have higher weight in the data set + ''' + + # getting data + data_lists = [] + for item in source_data: + for i in range(len(item)): + try: + data_lists[i].append(float(item[i])) + except IndexError: + # generate corresponding number of lists + data_lists.append([]) + data_lists[i].append(float(item[i])) + + score_lists = [] + # calculating each score + for dlist, weight in zip(data_lists, weights): + mind = min(dlist) + maxd = max(dlist) + + score = [] + # for weight 0 score is 1 - actual score + if weight == 0: + for item in dlist: + try: + score.append(1 - ((item - mind) / (maxd - mind))) + except ZeroDivisionError: + score.append(1) + + elif weight == 1: + for item in dlist: + try: + score.append((item - mind) / (maxd - mind)) + except ZeroDivisionError: + score.append(0) + + # weight not 0 or 1 + else: + raise ValueError("Invalid weight of %f provided" % (weight)) + + score_lists.append(score) + + # initialize final scores + final_scores = [0 for i in range(len(score_lists[0]))] + + # generate final scores + for i, slist in enumerate(score_lists): + for j, ele in enumerate(slist): + final_scores[j] = final_scores[j] + ele + + # append scores to source data + for i, ele in enumerate(final_scores): + source_data[i].append(ele) + + return source_data From 871f8f4e00eec285251f0f3eae4740af46a73666 Mon Sep 17 00:00:00 2001 From: Filip Hlasek Date: Wed, 5 Aug 2020 03:39:15 -0700 Subject: [PATCH 0703/1071] More efficient least common multiple. (#2281) * More efficient least common multiple. * lowest -> least * integer division * Update least_common_multiple.py Co-authored-by: Christian Clauss --- maths/least_common_multiple.py | 69 +++++++++++++++++++++++++++++----- 1 file changed, 60 insertions(+), 9 deletions(-) diff --git a/maths/least_common_multiple.py b/maths/least_common_multiple.py index 863744e182b6..0d087643e869 100644 --- a/maths/least_common_multiple.py +++ b/maths/least_common_multiple.py @@ -1,15 +1,17 @@ import unittest +from timeit import timeit -def find_lcm(first_num: int, second_num: int) -> int: - """Find the least common multiple of two numbers. +def least_common_multiple_slow(first_num: int, second_num: int) -> int: + """ + Find the least common multiple of two numbers. - Learn more: https://en.wikipedia.org/wiki/Least_common_multiple + Learn more: https://en.wikipedia.org/wiki/Least_common_multiple - >>> find_lcm(5,2) - 10 - >>> find_lcm(12,76) - 228 + >>> least_common_multiple_slow(5, 2) + 10 + >>> least_common_multiple_slow(12, 76) + 228 """ max_num = first_num if first_num >= second_num else second_num common_mult = max_num @@ -18,6 +20,52 @@ def find_lcm(first_num: int, second_num: int) -> int: return common_mult +def greatest_common_divisor(a: int, b: int) -> int: + """ + Calculate Greatest Common Divisor (GCD). + see greatest_common_divisor.py + >>> greatest_common_divisor(24, 40) + 8 + >>> greatest_common_divisor(1, 1) + 1 + >>> greatest_common_divisor(1, 800) + 1 + >>> greatest_common_divisor(11, 37) + 1 + >>> greatest_common_divisor(3, 5) + 1 + >>> greatest_common_divisor(16, 4) + 4 + """ + return b if a == 0 else greatest_common_divisor(b % a, a) + + +def least_common_multiple_fast(first_num: int, second_num: int) -> int: + """ + Find the least common multiple of two numbers. + https://en.wikipedia.org/wiki/Least_common_multiple#Using_the_greatest_common_divisor + >>> least_common_multiple_fast(5,2) + 10 + >>> least_common_multiple_fast(12,76) + 228 + """ + return first_num // greatest_common_divisor(first_num, second_num) * second_num + + +def benchmark(): + setup = ( + "from __main__ import least_common_multiple_slow, least_common_multiple_fast" + ) + print( + "least_common_multiple_slow():", + timeit("least_common_multiple_slow(1000, 999)", setup=setup), + ) + print( + "least_common_multiple_fast():", + timeit("least_common_multiple_fast(1000, 999)", setup=setup), + ) + + class TestLeastCommonMultiple(unittest.TestCase): test_inputs = [ @@ -35,10 +83,13 @@ class TestLeastCommonMultiple(unittest.TestCase): def test_lcm_function(self): for i, (first_num, second_num) in enumerate(self.test_inputs): - actual_result = find_lcm(first_num, second_num) + slow_result = least_common_multiple_slow(first_num, second_num) + fast_result = least_common_multiple_fast(first_num, second_num) with self.subTest(i=i): - self.assertEqual(actual_result, self.expected_results[i]) + self.assertEqual(slow_result, self.expected_results[i]) + self.assertEqual(fast_result, self.expected_results[i]) if __name__ == "__main__": + benchmark() unittest.main() From f0d7879a113ecdd6946006f70a6162c8789b8550 Mon Sep 17 00:00:00 2001 From: Sanders Lin <45224617+SandersLin@users.noreply.github.com> Date: Wed, 5 Aug 2020 19:18:41 +0800 Subject: [PATCH 0704/1071] fixed error in factorial.py (#1888) * Update factorial.py * updating DIRECTORY.md * Update dynamic_programming/factorial.py * Update factorial.py Co-authored-by: mateuszz0000 Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> Co-authored-by: Christian Clauss --- dynamic_programming/factorial.py | 33 +++++++++++--------------------- 1 file changed, 11 insertions(+), 22 deletions(-) diff --git a/dynamic_programming/factorial.py b/dynamic_programming/factorial.py index 546478441f31..1c9c927f5af3 100644 --- a/dynamic_programming/factorial.py +++ b/dynamic_programming/factorial.py @@ -1,36 +1,25 @@ # Factorial of a number using memoization -result = [-1] * 10 -result[0] = result[1] = 1 +from functools import lru_cache -def factorial(num): + +@lru_cache +def factorial(num: int) -> int: """ >>> factorial(7) 5040 >>> factorial(-1) - 'Number should not be negative.' - >>> [factorial(i) for i in range(5)] - [1, 1, 2, 6, 24] + Traceback (most recent call last): + ... + ValueError: Number should not be negative. + >>> [factorial(i) for i in range(10)] + [1, 1, 2, 6, 24, 120, 720, 5040, 40320, 362880] """ - if num < 0: - return "Number should not be negative." - if result[num] != -1: - return result[num] - else: - result[num] = num * factorial(num - 1) - # uncomment the following to see how recalculations are avoided - # print(result) - return result[num] + raise ValueError("Number should not be negative.") + return 1 if num in (0, 1) else num * factorial(num - 1) -# factorial of num -# uncomment the following to see how recalculations are avoided -# result=[-1]*10 -# result[0]=result[1]=1 -# print(factorial(5)) -# print(factorial(3)) -# print(factorial(7)) if __name__ == "__main__": import doctest From 1fb1fdd130506e1db137dbcc2087b391c1880849 Mon Sep 17 00:00:00 2001 From: Christian Clauss Date: Thu, 6 Aug 2020 17:50:23 +0200 Subject: [PATCH 0705/1071] requirements.txt: Unpin numpy (#2287) * requirements.txt: Unpin numpy * fixup! Format Python code with psf/black push * Less clutter * fixup! Format Python code with psf/black push Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> --- graphs/karger.py | 34 ++++++++++++++------------ other/scoring_algorithm.py | 10 ++++---- requirements.txt | 2 +- web_programming/world_covid19_stats.py | 8 +++--- 4 files changed, 29 insertions(+), 25 deletions(-) diff --git a/graphs/karger.py b/graphs/karger.py index d5a27c285fd4..baa0eebd90b4 100644 --- a/graphs/karger.py +++ b/graphs/karger.py @@ -5,20 +5,19 @@ import random from typing import Dict, List, Set, Tuple - # Adjacency list representation of this graph: # https://en.wikipedia.org/wiki/File:Single_run_of_Karger%E2%80%99s_Mincut_algorithm.svg TEST_GRAPH = { - '1': ['2', '3', '4', '5'], - '2': ['1', '3', '4', '5'], - '3': ['1', '2', '4', '5', '10'], - '4': ['1', '2', '3', '5', '6'], - '5': ['1', '2', '3', '4', '7'], - '6': ['7', '8', '9', '10', '4'], - '7': ['6', '8', '9', '10', '5'], - '8': ['6', '7', '9', '10'], - '9': ['6', '7', '8', '10'], - '10': ['6', '7', '8', '9', '3'] + "1": ["2", "3", "4", "5"], + "2": ["1", "3", "4", "5"], + "3": ["1", "2", "4", "5", "10"], + "4": ["1", "2", "3", "5", "6"], + "5": ["1", "2", "3", "4", "7"], + "6": ["7", "8", "9", "10", "4"], + "7": ["6", "8", "9", "10", "5"], + "8": ["6", "7", "9", "10"], + "9": ["6", "7", "8", "10"], + "10": ["6", "7", "8", "9", "3"], } @@ -61,8 +60,9 @@ def partition_graph(graph: Dict[str, List[str]]) -> Set[Tuple[str, str]]: for neighbor in uv_neighbors: graph_copy[neighbor].append(uv) - contracted_nodes[uv] = {contracted_node for contracted_node in - contracted_nodes[u].union(contracted_nodes[v])} + contracted_nodes[uv] = { + node for node in contracted_nodes[u].union(contracted_nodes[v]) + } # Remove nodes u and v. del graph_copy[u] @@ -75,8 +75,12 @@ def partition_graph(graph: Dict[str, List[str]]) -> Set[Tuple[str, str]]: # Find cutset. groups = [contracted_nodes[node] for node in graph_copy] - return {(node, neighbor) for node in groups[0] - for neighbor in graph[node] if neighbor in groups[1]} + return { + (node, neighbor) + for node in groups[0] + for neighbor in graph[node] + if neighbor in groups[1] + } if __name__ == "__main__": diff --git a/other/scoring_algorithm.py b/other/scoring_algorithm.py index a5d073d5e8d8..77e614e2622c 100644 --- a/other/scoring_algorithm.py +++ b/other/scoring_algorithm.py @@ -1,4 +1,4 @@ -''' +""" developed by: markmelnic original repo: https://github.com/markmelnic/Scoring-Algorithm @@ -23,17 +23,17 @@ >>> procentual_proximity([[20, 60, 2012],[23, 90, 2015],[22, 50, 2011]], [0, 0, 1]) [[20, 60, 2012, 2.0], [23, 90, 2015, 1.0], [22, 50, 2011, 1.3333333333333335]] -''' +""" -def procentual_proximity(source_data : list, weights : list) -> list: +def procentual_proximity(source_data: list, weights: list) -> list: - ''' + """ weights - int list possible values - 0 / 1 0 if lower values have higher weight in the data set 1 if higher values have higher weight in the data set - ''' + """ # getting data data_lists = [] diff --git a/requirements.txt b/requirements.txt index d21c13a54f1c..8362afc62509 100644 --- a/requirements.txt +++ b/requirements.txt @@ -5,7 +5,7 @@ flake8 keras matplotlib mypy -numpy>=1.17.4 +numpy opencv-python pandas pillow diff --git a/web_programming/world_covid19_stats.py b/web_programming/world_covid19_stats.py index 1907ed5f35f7..1dd1ff6d188e 100644 --- a/web_programming/world_covid19_stats.py +++ b/web_programming/world_covid19_stats.py @@ -1,9 +1,9 @@ #!/usr/bin/env python3 -''' +""" Provide the current worldwide COVID-19 statistics. This data is being scrapped from 'https://www.worldometers.info/coronavirus/'. -''' +""" import requests from bs4 import BeautifulSoup @@ -13,8 +13,8 @@ def world_covid19_stats(url: str = "https://www.worldometers.info/coronavirus") """ Return a dict of current worldwide COVID-19 statistics """ - soup = BeautifulSoup(requests.get(url).text, 'html.parser') - keys = soup.findAll('h1') + soup = BeautifulSoup(requests.get(url).text, "html.parser") + keys = soup.findAll("h1") values = soup.findAll("div", {"class": "maincounter-number"}) keys += soup.findAll("span", {"class": "panel-title"}) values += soup.findAll("div", {"class": "number-table-main"}) From e49ece95a4273ada6e20a8a103ac5ce42f5c4a45 Mon Sep 17 00:00:00 2001 From: Christian Clauss Date: Fri, 7 Aug 2020 21:20:56 +0200 Subject: [PATCH 0706/1071] PIL.Image.point() takes an int, not a float (#2284) * PIL.Image.point() takes an int, not a float @furkanatesli Trying to remove these warnings from our Travis CI build logs... https://travis-ci.com/github/TheAlgorithms/Python/builds/178602503#L809 * fixup! Format Python code with psf/black push * Revert changes to change_brightness.py * updating DIRECTORY.md Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> --- DIRECTORY.md | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/DIRECTORY.md b/DIRECTORY.md index fb8312c635f8..1dd3b3ade844 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -85,7 +85,9 @@ * [Decimal To Binary](https://github.com/TheAlgorithms/Python/blob/master/conversions/decimal_to_binary.py) * [Decimal To Hexadecimal](https://github.com/TheAlgorithms/Python/blob/master/conversions/decimal_to_hexadecimal.py) * [Decimal To Octal](https://github.com/TheAlgorithms/Python/blob/master/conversions/decimal_to_octal.py) + * [Prefix Conversions](https://github.com/TheAlgorithms/Python/blob/master/conversions/prefix_conversions.py) * [Roman To Integer](https://github.com/TheAlgorithms/Python/blob/master/conversions/roman_to_integer.py) + * [Temperature Conversions](https://github.com/TheAlgorithms/Python/blob/master/conversions/temperature_conversions.py) ## Data Structures * Binary Tree @@ -102,9 +104,6 @@ * [Segment Tree](https://github.com/TheAlgorithms/Python/blob/master/data_structures/binary_tree/segment_tree.py) * [Segment Tree Other](https://github.com/TheAlgorithms/Python/blob/master/data_structures/binary_tree/segment_tree_other.py) * [Treap](https://github.com/TheAlgorithms/Python/blob/master/data_structures/binary_tree/treap.py) - * Data Structures - * Heap - * [Heap Generic](https://github.com/TheAlgorithms/Python/blob/master/data_structures/data_structures/heap/heap_generic.py) * Disjoint Set * [Disjoint Set](https://github.com/TheAlgorithms/Python/blob/master/data_structures/disjoint_set/disjoint_set.py) * Hashing @@ -117,6 +116,7 @@ * Heap * [Binomial Heap](https://github.com/TheAlgorithms/Python/blob/master/data_structures/heap/binomial_heap.py) * [Heap](https://github.com/TheAlgorithms/Python/blob/master/data_structures/heap/heap.py) + * [Heap Generic](https://github.com/TheAlgorithms/Python/blob/master/data_structures/heap/heap_generic.py) * [Max Heap](https://github.com/TheAlgorithms/Python/blob/master/data_structures/heap/max_heap.py) * [Min Heap](https://github.com/TheAlgorithms/Python/blob/master/data_structures/heap/min_heap.py) * Linked List @@ -264,6 +264,7 @@ * [Greedy Best First](https://github.com/TheAlgorithms/Python/blob/master/graphs/greedy_best_first.py) * [Kahns Algorithm Long](https://github.com/TheAlgorithms/Python/blob/master/graphs/kahns_algorithm_long.py) * [Kahns Algorithm Topo](https://github.com/TheAlgorithms/Python/blob/master/graphs/kahns_algorithm_topo.py) + * [Karger](https://github.com/TheAlgorithms/Python/blob/master/graphs/karger.py) * [Minimum Spanning Tree Boruvka](https://github.com/TheAlgorithms/Python/blob/master/graphs/minimum_spanning_tree_boruvka.py) * [Minimum Spanning Tree Kruskal](https://github.com/TheAlgorithms/Python/blob/master/graphs/minimum_spanning_tree_kruskal.py) * [Minimum Spanning Tree Prims](https://github.com/TheAlgorithms/Python/blob/master/graphs/minimum_spanning_tree_prims.py) @@ -292,6 +293,7 @@ * Src * [Lib](https://github.com/TheAlgorithms/Python/blob/master/linear_algebra/src/lib.py) * [Polynom-For-Points](https://github.com/TheAlgorithms/Python/blob/master/linear_algebra/src/polynom-for-points.py) + * [Power Iteration](https://github.com/TheAlgorithms/Python/blob/master/linear_algebra/src/power_iteration.py) * [Rayleigh Quotient](https://github.com/TheAlgorithms/Python/blob/master/linear_algebra/src/rayleigh_quotient.py) * [Test Linear Algebra](https://github.com/TheAlgorithms/Python/blob/master/linear_algebra/src/test_linear_algebra.py) @@ -337,6 +339,7 @@ * [Binary Exp Mod](https://github.com/TheAlgorithms/Python/blob/master/maths/binary_exp_mod.py) * [Binary Exponentiation](https://github.com/TheAlgorithms/Python/blob/master/maths/binary_exponentiation.py) * [Binomial Coefficient](https://github.com/TheAlgorithms/Python/blob/master/maths/binomial_coefficient.py) + * [Binomial Distribution](https://github.com/TheAlgorithms/Python/blob/master/maths/binomial_distribution.py) * [Bisection](https://github.com/TheAlgorithms/Python/blob/master/maths/bisection.py) * [Ceil](https://github.com/TheAlgorithms/Python/blob/master/maths/ceil.py) * [Chudnovsky Algorithm](https://github.com/TheAlgorithms/Python/blob/master/maths/chudnovsky_algorithm.py) @@ -366,6 +369,7 @@ * [Jaccard Similarity](https://github.com/TheAlgorithms/Python/blob/master/maths/jaccard_similarity.py) * [Kadanes](https://github.com/TheAlgorithms/Python/blob/master/maths/kadanes.py) * [Karatsuba](https://github.com/TheAlgorithms/Python/blob/master/maths/karatsuba.py) + * [Krishnamurthy Number](https://github.com/TheAlgorithms/Python/blob/master/maths/krishnamurthy_number.py) * [Kth Lexicographic Permutation](https://github.com/TheAlgorithms/Python/blob/master/maths/kth_lexicographic_permutation.py) * [Largest Of Very Large Numbers](https://github.com/TheAlgorithms/Python/blob/master/maths/largest_of_very_large_numbers.py) * [Least Common Multiple](https://github.com/TheAlgorithms/Python/blob/master/maths/least_common_multiple.py) @@ -382,9 +386,11 @@ * [Number Of Digits](https://github.com/TheAlgorithms/Python/blob/master/maths/number_of_digits.py) * [Numerical Integration](https://github.com/TheAlgorithms/Python/blob/master/maths/numerical_integration.py) * [Perfect Cube](https://github.com/TheAlgorithms/Python/blob/master/maths/perfect_cube.py) + * [Perfect Number](https://github.com/TheAlgorithms/Python/blob/master/maths/perfect_number.py) * [Perfect Square](https://github.com/TheAlgorithms/Python/blob/master/maths/perfect_square.py) * [Pi Monte Carlo Estimation](https://github.com/TheAlgorithms/Python/blob/master/maths/pi_monte_carlo_estimation.py) * [Polynomial Evaluation](https://github.com/TheAlgorithms/Python/blob/master/maths/polynomial_evaluation.py) + * [Power Using Recursion](https://github.com/TheAlgorithms/Python/blob/master/maths/power_using_recursion.py) * [Prime Check](https://github.com/TheAlgorithms/Python/blob/master/maths/prime_check.py) * [Prime Factors](https://github.com/TheAlgorithms/Python/blob/master/maths/prime_factors.py) * [Prime Numbers](https://github.com/TheAlgorithms/Python/blob/master/maths/prime_numbers.py) @@ -415,6 +421,7 @@ ## Matrix * [Count Islands In Matrix](https://github.com/TheAlgorithms/Python/blob/master/matrix/count_islands_in_matrix.py) + * [Inverse Of Matrix](https://github.com/TheAlgorithms/Python/blob/master/matrix/inverse_of_matrix.py) * [Matrix Class](https://github.com/TheAlgorithms/Python/blob/master/matrix/matrix_class.py) * [Matrix Operation](https://github.com/TheAlgorithms/Python/blob/master/matrix/matrix_operation.py) * [Nth Fibonacci Using Matrix Exponentiation](https://github.com/TheAlgorithms/Python/blob/master/matrix/nth_fibonacci_using_matrix_exponentiation.py) @@ -460,6 +467,7 @@ * [Palindrome](https://github.com/TheAlgorithms/Python/blob/master/other/palindrome.py) * [Password Generator](https://github.com/TheAlgorithms/Python/blob/master/other/password_generator.py) * [Primelib](https://github.com/TheAlgorithms/Python/blob/master/other/primelib.py) + * [Scoring Algorithm](https://github.com/TheAlgorithms/Python/blob/master/other/scoring_algorithm.py) * [Sdes](https://github.com/TheAlgorithms/Python/blob/master/other/sdes.py) * [Sierpinski Triangle](https://github.com/TheAlgorithms/Python/blob/master/other/sierpinski_triangle.py) * [Tower Of Hanoi](https://github.com/TheAlgorithms/Python/blob/master/other/tower_of_hanoi.py) @@ -633,6 +641,7 @@ * [I Sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/i_sort.py) * [Insertion Sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/insertion_sort.py) * [Iterative Merge Sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/iterative_merge_sort.py) + * [Merge Insertion Sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/merge_insertion_sort.py) * [Merge Sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/merge_sort.py) * [Odd Even Transposition Parallel](https://github.com/TheAlgorithms/Python/blob/master/sorts/odd_even_transposition_parallel.py) * [Odd Even Transposition Single Threaded](https://github.com/TheAlgorithms/Python/blob/master/sorts/odd_even_transposition_single_threaded.py) @@ -693,3 +702,4 @@ * [Get Imdb Top 250 Movies Csv](https://github.com/TheAlgorithms/Python/blob/master/web_programming/get_imdb_top_250_movies_csv.py) * [Get Imdbtop](https://github.com/TheAlgorithms/Python/blob/master/web_programming/get_imdbtop.py) * [Slack Message](https://github.com/TheAlgorithms/Python/blob/master/web_programming/slack_message.py) + * [World Covid19 Stats](https://github.com/TheAlgorithms/Python/blob/master/web_programming/world_covid19_stats.py) From d25a926c0291092bc538af1004bba910f85b111e Mon Sep 17 00:00:00 2001 From: kanthuc Date: Tue, 11 Aug 2020 14:38:38 -0700 Subject: [PATCH 0707/1071] adding static type checking to basic_binary_tree.py (#2293) * adding static type checking to basic_binary_tree.py * Add static type checking to functions with None return type * Applying code review comments * Added missing import statement * fix spaciing * "cleaned up depth_of_tree" * Add doctests and then streamline display() and is_full_binary_tree() Co-authored-by: Christian Clauss --- .../binary_tree/basic_binary_tree.py | 104 +++++++++++------- 1 file changed, 65 insertions(+), 39 deletions(-) diff --git a/data_structures/binary_tree/basic_binary_tree.py b/data_structures/binary_tree/basic_binary_tree.py index 9b6e25d5ec56..5553056750ea 100644 --- a/data_structures/binary_tree/basic_binary_tree.py +++ b/data_structures/binary_tree/basic_binary_tree.py @@ -1,59 +1,85 @@ +from typing import Optional + + class Node: """ - This is the Class Node with a constructor that contains data variable to type data - and left, right pointers. + A Node has data variable and pointers to Nodes to its left and right. """ - - def __init__(self, data): + def __init__(self, data: int) -> None: self.data = data - self.left = None - self.right = None - + self.left: Optional[Node] = None + self.right: Optional[Node] = None -def display(tree): # In Order traversal of the tree - if tree is None: - return - - if tree.left is not None: +def display(tree: Optional[Node]) -> None: # In Order traversal of the tree + """ + >>> root = Node(1) + >>> root.left = Node(0) + >>> root.right = Node(2) + >>> display(root) + 0 + 1 + 2 + >>> display(root.right) + 2 + """ + if tree: display(tree.left) - - print(tree.data) - - if tree.right is not None: + print(tree.data) display(tree.right) - return - -def depth_of_tree( - tree, -): # This is the recursive function to find the depth of binary tree. - if tree is None: - return 0 - else: - depth_l_tree = depth_of_tree(tree.left) - depth_r_tree = depth_of_tree(tree.right) - if depth_l_tree > depth_r_tree: - return 1 + depth_l_tree - else: - return 1 + depth_r_tree +def depth_of_tree(tree: Optional[Node]) -> int: + """ + Recursive function that returns the depth of a binary tree. + + >>> root = Node(0) + >>> depth_of_tree(root) + 1 + >>> root.left = Node(0) + >>> depth_of_tree(root) + 2 + >>> root.right = Node(0) + >>> depth_of_tree(root) + 2 + >>> root.left.right = Node(0) + >>> depth_of_tree(root) + 3 + >>> depth_of_tree(root.left) + 2 + """ + return 1 + max(depth_of_tree(tree.left), depth_of_tree(tree.right)) if tree else 0 -def is_full_binary_tree( - tree, -): # This function returns that is it full binary tree or not? - if tree is None: - return True - if (tree.left is None) and (tree.right is None): +def is_full_binary_tree(tree: Node) -> bool: + """ + Returns True if this is a full binary tree + + >>> root = Node(0) + >>> is_full_binary_tree(root) + True + >>> root.left = Node(0) + >>> is_full_binary_tree(root) + False + >>> root.right = Node(0) + >>> is_full_binary_tree(root) + True + >>> root.left.left = Node(0) + >>> is_full_binary_tree(root) + False + >>> root.right.right = Node(0) + >>> is_full_binary_tree(root) + False + """ + if not tree: return True - if (tree.left is not None) and (tree.right is not None): + if tree.left and tree.right: return is_full_binary_tree(tree.left) and is_full_binary_tree(tree.right) else: - return False + return not tree.left and not tree.right -def main(): # Main function for testing. +def main() -> None: # Main function for testing. tree = Node(1) tree.left = Node(2) tree.right = Node(3) From aa46639cbc7a147527bbaafb4c9b2590cc29f40c Mon Sep 17 00:00:00 2001 From: Tapajyoti Bose <44058757+ruppysuppy@users.noreply.github.com> Date: Wed, 12 Aug 2020 15:46:17 +0530 Subject: [PATCH 0708/1071] Added Kruskal's Algorithm (more organized than the one present) (#2218) * Added Kruskal's Algorithm * Added Type Hints * fixup! Format Python code with psf/black push * Added Type Hints V2 * Implemented suggestions + uniform naming convention * removed redundant variable (self.nodes) * updating DIRECTORY.md Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> --- DIRECTORY.md | 1 + graphs/minimum_spanning_tree_kruskal2.py | 109 +++++++++++++++++++++++ 2 files changed, 110 insertions(+) create mode 100644 graphs/minimum_spanning_tree_kruskal2.py diff --git a/DIRECTORY.md b/DIRECTORY.md index 1dd3b3ade844..f97a8e55fff9 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -267,6 +267,7 @@ * [Karger](https://github.com/TheAlgorithms/Python/blob/master/graphs/karger.py) * [Minimum Spanning Tree Boruvka](https://github.com/TheAlgorithms/Python/blob/master/graphs/minimum_spanning_tree_boruvka.py) * [Minimum Spanning Tree Kruskal](https://github.com/TheAlgorithms/Python/blob/master/graphs/minimum_spanning_tree_kruskal.py) + * [Minimum Spanning Tree Kruskal2](https://github.com/TheAlgorithms/Python/blob/master/graphs/minimum_spanning_tree_kruskal2.py) * [Minimum Spanning Tree Prims](https://github.com/TheAlgorithms/Python/blob/master/graphs/minimum_spanning_tree_prims.py) * [Multi Heuristic Astar](https://github.com/TheAlgorithms/Python/blob/master/graphs/multi_heuristic_astar.py) * [Page Rank](https://github.com/TheAlgorithms/Python/blob/master/graphs/page_rank.py) diff --git a/graphs/minimum_spanning_tree_kruskal2.py b/graphs/minimum_spanning_tree_kruskal2.py new file mode 100644 index 000000000000..dfb87efeb89a --- /dev/null +++ b/graphs/minimum_spanning_tree_kruskal2.py @@ -0,0 +1,109 @@ +from __future__ import annotations + + +class DisjointSetTreeNode: + # Disjoint Set Node to store the parent and rank + def __init__(self, key: int) -> None: + self.key = key + self.parent = self + self.rank = 0 + + +class DisjointSetTree: + # Disjoint Set DataStructure + def __init__(self): + # map from node name to the node object + self.map = {} + + def make_set(self, x: int) -> None: + # create a new set with x as its member + self.map[x] = DisjointSetTreeNode(x) + + def find_set(self, x: int) -> DisjointSetTreeNode: + # find the set x belongs to (with path-compression) + elem_ref = self.map[x] + if elem_ref != elem_ref.parent: + elem_ref.parent = self.find_set(elem_ref.parent.key) + return elem_ref.parent + + def link(self, x: int, y: int) -> None: + # helper function for union operation + if x.rank > y.rank: + y.parent = x + else: + x.parent = y + if x.rank == y.rank: + y.rank += 1 + + def union(self, x: int, y: int) -> None: + # merge 2 disjoint sets + self.link(self.find_set(x), self.find_set(y)) + + +class GraphUndirectedWeighted: + def __init__(self): + # connections: map from the node to the neighbouring nodes (with weights) + self.connections = {} + + def add_node(self, node: int) -> None: + # add a node ONLY if its not present in the graph + if node not in self.connections: + self.connections[node] = {} + + def add_edge(self, node1: int, node2: int, weight: int) -> None: + # add an edge with the given weight + self.add_node(node1) + self.add_node(node2) + self.connections[node1][node2] = weight + self.connections[node2][node1] = weight + + def kruskal(self) -> GraphUndirectedWeighted: + # Kruskal's Algorithm to generate a Minimum Spanning Tree (MST) of a graph + """ + Details: https://en.wikipedia.org/wiki/Kruskal%27s_algorithm + + Example: + + >>> graph = GraphUndirectedWeighted() + >>> graph.add_edge(1, 2, 1) + >>> graph.add_edge(2, 3, 2) + >>> graph.add_edge(3, 4, 1) + >>> graph.add_edge(3, 5, 100) # Removed in MST + >>> graph.add_edge(4, 5, 5) + >>> assert 5 in graph.connections[3] + >>> mst = graph.kruskal() + >>> assert 5 not in mst.connections[3] + """ + + # getting the edges in ascending order of weights + edges = [] + seen = set() + for start in self.connections: + for end in self.connections[start]: + if (start, end) not in seen: + seen.add((end, start)) + edges.append((start, end, self.connections[start][end])) + edges.sort(key=lambda x: x[2]) + # creating the disjoint set + disjoint_set = DisjointSetTree() + [disjoint_set.make_set(node) for node in self.connections] + # MST generation + num_edges = 0 + index = 0 + graph = GraphUndirectedWeighted() + while num_edges < len(self.connections) - 1: + u, v, w = edges[index] + index += 1 + parentu = disjoint_set.find_set(u) + parentv = disjoint_set.find_set(v) + if parentu != parentv: + num_edges += 1 + graph.add_edge(u, v, w) + disjoint_set.union(u, v) + return graph + + +if __name__ == "__main__": + import doctest + + doctest.testmod() From d687030d9e582534c850493ebc8a22fb4cf1ec81 Mon Sep 17 00:00:00 2001 From: Du Yuanchao Date: Thu, 13 Aug 2020 00:32:35 +0800 Subject: [PATCH 0709/1071] fix number_of_digits bug (#2301) * fix bug * test larger negative * fixup! Format Python code with psf/black push Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> --- .../binary_tree/basic_binary_tree.py | 1 + maths/number_of_digits.py | 28 +++++++++++++++++-- 2 files changed, 27 insertions(+), 2 deletions(-) diff --git a/data_structures/binary_tree/basic_binary_tree.py b/data_structures/binary_tree/basic_binary_tree.py index 5553056750ea..575b157ee78a 100644 --- a/data_structures/binary_tree/basic_binary_tree.py +++ b/data_structures/binary_tree/basic_binary_tree.py @@ -5,6 +5,7 @@ class Node: """ A Node has data variable and pointers to Nodes to its left and right. """ + def __init__(self, data: int) -> None: self.data = data self.left: Optional[Node] = None diff --git a/maths/number_of_digits.py b/maths/number_of_digits.py index ca4cce876617..3c0eb7b3863f 100644 --- a/maths/number_of_digits.py +++ b/maths/number_of_digits.py @@ -10,11 +10,20 @@ def num_digits(n: int) -> int: 5 >>> num_digits(123) 3 + >>> num_digits(0) + 1 + >>> num_digits(-1) + 1 + >>> num_digits(-123456) + 6 """ digits = 0 - while n > 0: + n = abs(n) + while True: n = n // 10 digits += 1 + if n == 0: + break return digits @@ -27,8 +36,14 @@ def num_digits_fast(n: int) -> int: 5 >>> num_digits_fast(123) 3 + >>> num_digits_fast(0) + 1 + >>> num_digits_fast(-1) + 1 + >>> num_digits_fast(-123456) + 6 """ - return math.floor(math.log(abs(n), 10) + 1) + return 1 if n == 0 else math.floor(math.log(abs(n), 10) + 1) def num_digits_faster(n: int) -> int: @@ -40,6 +55,12 @@ def num_digits_faster(n: int) -> int: 5 >>> num_digits_faster(123) 3 + >>> num_digits_faster(0) + 1 + >>> num_digits_faster(-1) + 1 + >>> num_digits_faster(-123456) + 6 """ return len(str(abs(n))) @@ -133,3 +154,6 @@ def benchmark() -> None: medium_num = 1125899906842624 large_num = 1267650600228229401496703205376 benchmark() + import doctest + + doctest.testmod() From b3ae39249dc0f6f2c02b3a535aa4c95a32c361a7 Mon Sep 17 00:00:00 2001 From: Kushagra Bansal Date: Thu, 13 Aug 2020 20:22:47 +0530 Subject: [PATCH 0710/1071] Created problem_34 in project_euler (#2305) * Create __init__.py * Add files via upload * Rename solution.py.py to solution.py * Delete __init__.py * Update and rename solution.py to sol1.py * Update sol1.py * Create __init__.py * Update __init__.py * Delete __init__.py * Add files via upload * Update __init__.py * Add #\n * Update sol1.py Incorporates the proposed changes * Update sol1.py * Update sol1.py * Update project_euler/problem_34/sol1.py Co-authored-by: Christian Clauss * Update project_euler/problem_34/sol1.py Co-authored-by: Christian Clauss * Update project_euler/problem_34/sol1.py Co-authored-by: Christian Clauss * Update project_euler/problem_34/sol1.py Co-authored-by: Christian Clauss * Update sol1.py * Update sol1.py * Update sol1.py * Use int(n) instead of floor(n) Co-authored-by: Christian Clauss --- project_euler/problem_34/__init__.py | 1 + project_euler/problem_34/sol1.py | 67 ++++++++++++++++++++++++++++ 2 files changed, 68 insertions(+) create mode 100644 project_euler/problem_34/__init__.py create mode 100644 project_euler/problem_34/sol1.py diff --git a/project_euler/problem_34/__init__.py b/project_euler/problem_34/__init__.py new file mode 100644 index 000000000000..792d6005489e --- /dev/null +++ b/project_euler/problem_34/__init__.py @@ -0,0 +1 @@ +# diff --git a/project_euler/problem_34/sol1.py b/project_euler/problem_34/sol1.py new file mode 100644 index 000000000000..126aee9d2023 --- /dev/null +++ b/project_euler/problem_34/sol1.py @@ -0,0 +1,67 @@ +""" +145 is a curious number, as 1! + 4! + 5! = 1 + 24 + 120 = 145. +Find the sum of all numbers which are equal to the sum of the factorial of their digits. +Note: As 1! = 1 and 2! = 2 are not sums they are not included. +""" + + +def factorial(n: int) -> int: + """Return the factorial of n. + >>> factorial(5) + 120 + >>> factorial(1) + 1 + >>> factorial(0) + 1 + >>> factorial(-1) + Traceback (most recent call last): + ... + ValueError: n must be >= 0 + >>> factorial(1.1) + Traceback (most recent call last): + ... + ValueError: n must be exact integer + """ + + if not n >= 0: + raise ValueError("n must be >= 0") + if int(n) != n: + raise ValueError("n must be exact integer") + if n + 1 == n: # catch a value like 1e300 + raise OverflowError("n too large") + result = 1 + factor = 2 + while factor <= n: + result *= factor + factor += 1 + return result + + +def sum_of_digit_factorial(n: int) -> int: + """ + Returns the sum of the digits in n + >>> sum_of_digit_factorial(15) + 121 + >>> sum_of_digit_factorial(0) + 1 + """ + return sum(factorial(int(digit)) for digit in str(n)) + + +def compute() -> int: + """ + Returns the sum of all numbers whose + sum of the factorials of all digits + add up to the number itself. + >>> compute() + 40730 + """ + return sum( + num + for num in range(3, 7 * factorial(9) + 1) + if sum_of_digit_factorial(num) == num + ) + + +if __name__ == "__main__": + print(compute()) From fcc8a28c3180477b4747a556f10280ae83c9c291 Mon Sep 17 00:00:00 2001 From: Guillaume Rochedix <33205665+BriseBalloches@users.noreply.github.com> Date: Fri, 14 Aug 2020 22:00:08 +0200 Subject: [PATCH 0711/1071] Gnome sort : type hints, docstrings, doctests (#2307) * gnome_sort : type hints, docstring, doctests * !Gadeimnoprstu Co-authored-by: Christian Clauss --- sorts/gnome_sort.py | 51 ++++++++++++++++++++++++++++++++++++--------- 1 file changed, 41 insertions(+), 10 deletions(-) diff --git a/sorts/gnome_sort.py b/sorts/gnome_sort.py index 58a44c94da43..ea96e0a926a3 100644 --- a/sorts/gnome_sort.py +++ b/sorts/gnome_sort.py @@ -1,25 +1,56 @@ -"""Gnome Sort Algorithm.""" +""" +Gnome Sort Algorithm (A.K.A. Stupid Sort) +This algorithm iterates over a list comparing an element with the previous one. +If order is not respected, it swaps element backward until order is respected with +previous element. It resumes the initial iteration from element new position. -def gnome_sort(unsorted): - """Pure implementation of the gnome sort algorithm in Python.""" - if len(unsorted) <= 1: - return unsorted +For doctests run following command: +python3 -m doctest -v gnome_sort.py + +For manual testing run: +python3 gnome_sort.py +""" + + +def gnome_sort(lst: list) -> list: + """ + Pure implementation of the gnome sort algorithm in Python + + Take some mutable ordered collection with heterogeneous comparable items inside as + arguments, return the same collection ordered by ascending. + + Examples: + >>> gnome_sort([0, 5, 3, 2, 2]) + [0, 2, 2, 3, 5] + + >>> gnome_sort([]) + [] + + >>> gnome_sort([-2, -5, -45]) + [-45, -5, -2] + + >>> "".join(gnome_sort(list(set("Gnomes are stupid!")))) + ' !Gadeimnoprstu' + """ + if len(lst) <= 1: + return lst i = 1 - while i < len(unsorted): - if unsorted[i - 1] <= unsorted[i]: + while i < len(lst): + if lst[i - 1] <= lst[i]: i += 1 else: - unsorted[i - 1], unsorted[i] = unsorted[i], unsorted[i - 1] + lst[i - 1], lst[i] = lst[i], lst[i - 1] i -= 1 if i == 0: i = 1 + return lst + if __name__ == "__main__": user_input = input("Enter numbers separated by a comma:\n").strip() unsorted = [int(item) for item in user_input.split(",")] - gnome_sort(unsorted) - print(unsorted) + print(gnome_sort(unsorted)) From 14199e0590cf07f791c7422ee0b670d93ff0c5b0 Mon Sep 17 00:00:00 2001 From: Yukti Khosla <44090430+Yukti-09@users.noreply.github.com> Date: Sat, 15 Aug 2020 21:49:33 +0530 Subject: [PATCH 0712/1071] Create Transformations2D.py (#2310) * Create Transformations2D.py * Update Transformations2D.py * Drop numpy and add type hints and doctests * Rename Transformations2D.py to transformations_2d.py Co-authored-by: Christian Clauss --- linear_algebra/src/transformations_2d.py | 62 ++++++++++++++++++++++++ 1 file changed, 62 insertions(+) create mode 100644 linear_algebra/src/transformations_2d.py diff --git a/linear_algebra/src/transformations_2d.py b/linear_algebra/src/transformations_2d.py new file mode 100644 index 000000000000..9ee238fd7999 --- /dev/null +++ b/linear_algebra/src/transformations_2d.py @@ -0,0 +1,62 @@ +""" +2D Transformations are regularly used in Linear Algebra. + +I have added the codes for reflection, projection, scaling and rotation 2D matrices. + + scaling(5) = [[5.0, 0.0], [0.0, 5.0]] + rotation(45) = [[0.5253219888177297, -0.8509035245341184], + [0.8509035245341184, 0.5253219888177297]] +projection(45) = [[0.27596319193541496, 0.446998331800279], + [0.446998331800279, 0.7240368080645851]] +reflection(45) = [[0.05064397763545947, 0.893996663600558], + [0.893996663600558, 0.7018070490682369]] +""" +from math import cos, sin +from typing import List + + +def scaling(scaling_factor: float) -> List[List[float]]: + """ + >>> scaling(5) + [[5.0, 0.0], [0.0, 5.0]] + """ + scaling_factor = float(scaling_factor) + return [[scaling_factor * int(x == y) for x in range(2)] for y in range(2)] + + +def rotation(angle: float) -> List[List[float]]: + """ + >>> rotation(45) # doctest: +NORMALIZE_WHITESPACE + [[0.5253219888177297, -0.8509035245341184], + [0.8509035245341184, 0.5253219888177297]] + """ + c, s = cos(angle), sin(angle) + return [[c, -s], [s, c]] + + +def projection(angle: float) -> List[List[float]]: + """ + >>> projection(45) # doctest: +NORMALIZE_WHITESPACE + [[0.27596319193541496, 0.446998331800279], + [0.446998331800279, 0.7240368080645851]] + """ + c, s = cos(angle), sin(angle) + cs = c * s + return [[c * c, cs], [cs, s * s]] + + +def reflection(angle: float) -> List[List[float]]: + """ + >>> reflection(45) # doctest: +NORMALIZE_WHITESPACE + [[0.05064397763545947, 0.893996663600558], + [0.893996663600558, 0.7018070490682369]] + """ + c, s = cos(angle), sin(angle) + cs = c * s + return [[2 * c - 1, 2 * cs], [2 * cs, 2 * s - 1]] + + +print(f" {scaling(5) = }") +print(f" {rotation(45) = }") +print(f"{projection(45) = }") +print(f"{reflection(45) = }") From 7e4176ccaf9b03ca334a281ad0c99196827338eb Mon Sep 17 00:00:00 2001 From: Kushagra Bansal Date: Sun, 16 Aug 2020 11:22:48 +0530 Subject: [PATCH 0713/1071] Created problem_35 in project_euler (#2309) * Create __init__.py * Update __init__.py * Add files via upload * Update sol1.py * Update sol1.py to include type hints * Update CONTRIBUTING.md to fix typo * Update CONTRIBUTING.md * Update project_euler/problem_35/sol1.py Co-authored-by: Christian Clauss * Update project_euler/problem_35/sol1.py Co-authored-by: Christian Clauss * Update project_euler/problem_35/sol1.py Co-authored-by: Christian Clauss * Update project_euler/problem_35/sol1.py Co-authored-by: Christian Clauss * Update project_euler/problem_35/sol1.py Co-authored-by: Christian Clauss * Update project_euler/problem_35/sol1.py Co-authored-by: Christian Clauss * Update project_euler/problem_35/sol1.py Co-authored-by: Christian Clauss * Update sol1.py * Update sol1.py * Update sol1.py * Fix the print(f"string") Co-authored-by: Christian Clauss --- project_euler/problem_35/__init__.py | 1 + project_euler/problem_35/sol1.py | 69 ++++++++++++++++++++++++++++ 2 files changed, 70 insertions(+) create mode 100644 project_euler/problem_35/__init__.py create mode 100644 project_euler/problem_35/sol1.py diff --git a/project_euler/problem_35/__init__.py b/project_euler/problem_35/__init__.py new file mode 100644 index 000000000000..792d6005489e --- /dev/null +++ b/project_euler/problem_35/__init__.py @@ -0,0 +1 @@ +# diff --git a/project_euler/problem_35/sol1.py b/project_euler/problem_35/sol1.py new file mode 100644 index 000000000000..c47eb7d82f54 --- /dev/null +++ b/project_euler/problem_35/sol1.py @@ -0,0 +1,69 @@ +""" +The number 197 is called a circular prime because all rotations of the digits: +197, 971, and 719, are themselves prime. +There are thirteen such primes below 100: 2, 3, 5, 7, 11, 13, 17, 31, 37, 71, 73, +79, and 97. +How many circular primes are there below one million? + +To solve this problem in an efficient manner, we will first mark all the primes +below 1 million using the Seive of Eratosthenes. Then, out of all these primes, +we will rule out the numbers which contain an even digit. After this we will +generate each circular combination of the number and check if all are prime. +""" +from typing import List + +seive = [True] * 1000001 +i = 2 +while i * i <= 1000000: + if seive[i]: + for j in range(i * i, 1000001, i): + seive[j] = False + i += 1 + + +def is_prime(n: int) -> bool: + """ + For 2 <= n <= 1000000, return True if n is prime. + >>> is_prime(87) + False + >>> is_prime(23) + True + >>> is_prime(25363) + False + """ + return seive[n] + + +def contains_an_even_digit(n: int) -> bool: + """ + Return True if n contains an even digit. + >>> contains_an_even_digit(0) + True + >>> contains_an_even_digit(975317933) + False + >>> contains_an_even_digit(-245679) + True + """ + return any(digit in "02468" for digit in str(n)) + + +def find_circular_primes(limit: int = 1000000) -> List[int]: + """ + Return circular primes below limit. + >>> len(find_circular_primes(100)) + 13 + >>> len(find_circular_primes(1000000)) + 55 + """ + result = [2] # result already includes the number 2. + for num in range(3, limit + 1, 2): + if is_prime(num) and not contains_an_even_digit(num): + str_num = str(num) + list_nums = [int(str_num[j:] + str_num[:j]) for j in range(len(str_num))] + if all(is_prime(i) for i in list_nums): + result.append(num) + return result + + +if __name__ == "__main__": + print(f"{len(find_circular_primes()) = }") From 34294b564144d94530ff1be8b0f6e61465db3480 Mon Sep 17 00:00:00 2001 From: Du Yuanchao Date: Mon, 17 Aug 2020 02:31:06 +0800 Subject: [PATCH 0714/1071] Update sum_of_digits.py (#2319) * * support negative number * add different version * fixup! Format Python code with psf/black push * sum(int(c) for c in str(abs(n))) * updating DIRECTORY.md Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> Co-authored-by: Christian Clauss --- DIRECTORY.md | 5 ++ maths/sum_of_digits.py | 133 ++++++++++++++++++++++++++++++++++++++++- 2 files changed, 137 insertions(+), 1 deletion(-) diff --git a/DIRECTORY.md b/DIRECTORY.md index f97a8e55fff9..1530ed763591 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -297,6 +297,7 @@ * [Power Iteration](https://github.com/TheAlgorithms/Python/blob/master/linear_algebra/src/power_iteration.py) * [Rayleigh Quotient](https://github.com/TheAlgorithms/Python/blob/master/linear_algebra/src/rayleigh_quotient.py) * [Test Linear Algebra](https://github.com/TheAlgorithms/Python/blob/master/linear_algebra/src/test_linear_algebra.py) + * [Transformations 2D](https://github.com/TheAlgorithms/Python/blob/master/linear_algebra/src/transformations_2d.py) ## Machine Learning * [Astar](https://github.com/TheAlgorithms/Python/blob/master/machine_learning/astar.py) @@ -580,6 +581,10 @@ * [Sol32](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_32/sol32.py) * Problem 33 * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_33/sol1.py) + * Problem 34 + * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_34/sol1.py) + * Problem 35 + * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_35/sol1.py) * Problem 36 * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_36/sol1.py) * Problem 40 diff --git a/maths/sum_of_digits.py b/maths/sum_of_digits.py index 88baf2ca2ccc..64da00d4634c 100644 --- a/maths/sum_of_digits.py +++ b/maths/sum_of_digits.py @@ -1,3 +1,6 @@ +from timeit import timeit + + def sum_of_digits(n: int) -> int: """ Find the sum of digits of a number. @@ -6,7 +9,12 @@ def sum_of_digits(n: int) -> int: 15 >>> sum_of_digits(123) 6 + >>> sum_of_digits(-123) + 6 + >>> sum_of_digits(0) + 0 """ + n = -n if n < 0 else n res = 0 while n > 0: res += n % 10 @@ -14,5 +22,128 @@ def sum_of_digits(n: int) -> int: return res +def sum_of_digits_recursion(n: int) -> int: + """ + Find the sum of digits of a number using recursion + + >>> sum_of_digits_recursion(12345) + 15 + >>> sum_of_digits_recursion(123) + 6 + >>> sum_of_digits_recursion(-123) + 6 + >>> sum_of_digits_recursion(0) + 0 + """ + n = -n if n < 0 else n + return n if n < 10 else n % 10 + sum_of_digits(n // 10) + + +def sum_of_digits_compact(n: int) -> int: + """ + Find the sum of digits of a number + + >>> sum_of_digits_compact(12345) + 15 + >>> sum_of_digits_compact(123) + 6 + >>> sum_of_digits_compact(-123) + 6 + >>> sum_of_digits_compact(0) + 0 + """ + return sum(int(c) for c in str(abs(n))) + + +def benchmark() -> None: + """ + Benchmark code for comparing 3 functions, + with 3 different length int values. + """ + print("\nFor small_num = ", small_num, ":") + print( + "> sum_of_digits()", + "\t\tans =", + sum_of_digits(small_num), + "\ttime =", + timeit("z.sum_of_digits(z.small_num)", setup="import __main__ as z"), + "seconds", + ) + print( + "> sum_of_digits_recursion()", + "\tans =", + sum_of_digits_recursion(small_num), + "\ttime =", + timeit("z.sum_of_digits_recursion(z.small_num)", setup="import __main__ as z"), + "seconds", + ) + print( + "> sum_of_digits_compact()", + "\tans =", + sum_of_digits_compact(small_num), + "\ttime =", + timeit("z.sum_of_digits_compact(z.small_num)", setup="import __main__ as z"), + "seconds", + ) + + print("\nFor medium_num = ", medium_num, ":") + print( + "> sum_of_digits()", + "\t\tans =", + sum_of_digits(medium_num), + "\ttime =", + timeit("z.sum_of_digits(z.medium_num)", setup="import __main__ as z"), + "seconds", + ) + print( + "> sum_of_digits_recursion()", + "\tans =", + sum_of_digits_recursion(medium_num), + "\ttime =", + timeit("z.sum_of_digits_recursion(z.medium_num)", setup="import __main__ as z"), + "seconds", + ) + print( + "> sum_of_digits_compact()", + "\tans =", + sum_of_digits_compact(medium_num), + "\ttime =", + timeit("z.sum_of_digits_compact(z.medium_num)", setup="import __main__ as z"), + "seconds", + ) + + print("\nFor large_num = ", large_num, ":") + print( + "> sum_of_digits()", + "\t\tans =", + sum_of_digits(large_num), + "\ttime =", + timeit("z.sum_of_digits(z.large_num)", setup="import __main__ as z"), + "seconds", + ) + print( + "> sum_of_digits_recursion()", + "\tans =", + sum_of_digits_recursion(large_num), + "\ttime =", + timeit("z.sum_of_digits_recursion(z.large_num)", setup="import __main__ as z"), + "seconds", + ) + print( + "> sum_of_digits_compact()", + "\tans =", + sum_of_digits_compact(large_num), + "\ttime =", + timeit("z.sum_of_digits_compact(z.large_num)", setup="import __main__ as z"), + "seconds", + ) + + if __name__ == "__main__": - print(sum_of_digits(12345)) # ===> 15 + small_num = 262144 + medium_num = 1125899906842624 + large_num = 1267650600228229401496703205376 + benchmark() + import doctest + + doctest.testmod() From 88341d17279532e9b88cb54eeb45fa76d4f63ddc Mon Sep 17 00:00:00 2001 From: Kushagra Bansal Date: Mon, 17 Aug 2020 20:09:58 +0530 Subject: [PATCH 0715/1071] Created problem_37 in project_euler (#2323) * Create __init__.py * Add files via upload * Update sol1.py * Update sol1.py * Update sol1.py * Update sol1.py * Update sol1.py * Update project_euler/problem_37/sol1.py Co-authored-by: Christian Clauss Co-authored-by: Christian Clauss --- project_euler/problem_37/__init__.py | 1 + project_euler/problem_37/sol1.py | 92 ++++++++++++++++++++++++++++ 2 files changed, 93 insertions(+) create mode 100644 project_euler/problem_37/__init__.py create mode 100644 project_euler/problem_37/sol1.py diff --git a/project_euler/problem_37/__init__.py b/project_euler/problem_37/__init__.py new file mode 100644 index 000000000000..792d6005489e --- /dev/null +++ b/project_euler/problem_37/__init__.py @@ -0,0 +1 @@ +# diff --git a/project_euler/problem_37/sol1.py b/project_euler/problem_37/sol1.py new file mode 100644 index 000000000000..c01d64d83fbe --- /dev/null +++ b/project_euler/problem_37/sol1.py @@ -0,0 +1,92 @@ +""" +The number 3797 has an interesting property. Being prime itself, it is possible +to continuously remove digits from left to right, and remain prime at each stage: +3797, 797, 97, and 7. Similarly we can work from right to left: 3797, 379, 37, and 3. + +Find the sum of the only eleven primes that are both truncatable from left to right +and right to left. + +NOTE: 2, 3, 5, and 7 are not considered to be truncatable primes. +""" + + +from typing import List + +seive = [True] * 1000001 +seive[1] = False +i = 2 +while i * i <= 1000000: + if seive[i]: + for j in range(i * i, 1000001, i): + seive[j] = False + i += 1 + + +def is_prime(n: int) -> bool: + """ + Returns True if n is prime, + False otherwise, for 1 <= n <= 1000000 + >>> is_prime(87) + False + >>> is_prime(1) + False + >>> is_prime(25363) + False + """ + return seive[n] + + +def list_truncated_nums(n: int) -> List[int]: + """ + Returns a list of all left and right truncated numbers of n + >>> list_truncated_nums(927628) + [927628, 27628, 92762, 7628, 9276, 628, 927, 28, 92, 8, 9] + >>> list_truncated_nums(467) + [467, 67, 46, 7, 4] + >>> list_truncated_nums(58) + [58, 8, 5] + """ + str_num = str(n) + list_nums = [n] + for i in range(1, len(str_num)): + list_nums.append(int(str_num[i:])) + list_nums.append(int(str_num[:-i])) + return list_nums + + +def validate(n: int) -> bool: + """ + To optimize the approach, we will rule out the numbers above 1000, + whose first or last three digits are not prime + >>> validate(74679) + False + >>> validate(235693) + False + >>> validate(3797) + True + """ + if len(str(n)) > 3: + if not is_prime(int(str(n)[-3:])) or not is_prime(int(str(n)[:3])): + return False + return True + + +def compute_truncated_primes(count: int = 11) -> List[int]: + """ + Returns the list of truncated primes + >>> compute_truncated_primes(11) + [23, 37, 53, 73, 313, 317, 373, 797, 3137, 3797, 739397] + """ + list_truncated_primes = [] + num = 13 + while len(list_truncated_primes) != count: + if validate(num): + list_nums = list_truncated_nums(num) + if all(is_prime(i) for i in list_nums): + list_truncated_primes.append(num) + num += 2 + return list_truncated_primes + + +if __name__ == "__main__": + print(f"{sum(compute_truncated_primes(11)) = }") From 051be078e46c27d8b1a2d9db8ef2514f0a48ec48 Mon Sep 17 00:00:00 2001 From: Kushagra Bansal Date: Mon, 17 Aug 2020 21:13:46 +0530 Subject: [PATCH 0716/1071] Fix typo (#2325) --- CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 2761be61aa8b..63f60c51f8e6 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -59,7 +59,7 @@ We want your work to be readable by others; therefore, we encourage you to note - Expand acronyms because __gcd()__ is hard to understand but __greatest_common_divisor()__ is not. - Please follow the [Python Naming Conventions](https://pep8.org/#prescriptive-naming-conventions) so variable_names and function_names should be lower_case, CONSTANTS in UPPERCASE, ClassNames should be CamelCase, etc. -- We encourage the use of Python [f-strings](https://realpython.com/python-f-strings/#f-strings-a-new-and-improved-way-to-format-strings-in-python) where the make the code easier to read. +- We encourage the use of Python [f-strings](https://realpython.com/python-f-strings/#f-strings-a-new-and-improved-way-to-format-strings-in-python) where they make the code easier to read. - Please consider running [__psf/black__](https://github.com/python/black) on your Python file(s) before submitting your pull request. This is not yet a requirement but it does make your code more readable and automatically aligns it with much of [PEP 8](https://www.python.org/dev/peps/pep-0008/). There are other code formatters (autopep8, yapf) but the __black__ formatter is now hosted by the Python Software Foundation. To use it, From 9a32f0b46cbbfca72e72537c1ba96318819ddb8e Mon Sep 17 00:00:00 2001 From: Kushagra Bansal Date: Tue, 18 Aug 2020 16:19:02 +0530 Subject: [PATCH 0717/1071] Created problem_39 in project_euler (#2330) * Create __init__.py * Add files via upload * Update sol1.py * Update sol1.py * Update project_euler/problem_39/sol1.py Co-authored-by: Christian Clauss * Update sol1.py Co-authored-by: Christian Clauss --- project_euler/problem_39/__init__.py | 1 + project_euler/problem_39/sol1.py | 39 ++++++++++++++++++++++++++++ 2 files changed, 40 insertions(+) create mode 100644 project_euler/problem_39/__init__.py create mode 100644 project_euler/problem_39/sol1.py diff --git a/project_euler/problem_39/__init__.py b/project_euler/problem_39/__init__.py new file mode 100644 index 000000000000..792d6005489e --- /dev/null +++ b/project_euler/problem_39/__init__.py @@ -0,0 +1 @@ +# diff --git a/project_euler/problem_39/sol1.py b/project_euler/problem_39/sol1.py new file mode 100644 index 000000000000..5c21d4beca8c --- /dev/null +++ b/project_euler/problem_39/sol1.py @@ -0,0 +1,39 @@ +""" +If p is the perimeter of a right angle triangle with integral length sides, +{a,b,c}, there are exactly three solutions for p = 120. +{20,48,52}, {24,45,51}, {30,40,50} + +For which value of p ≤ 1000, is the number of solutions maximised? +""" + +from typing import Dict +from collections import Counter + + +def pythagorean_triple(max_perimeter: int) -> Dict: + """ + Returns a dictionary with keys as the perimeter of a right angled triangle + and value as the number of corresponding triplets. + >>> pythagorean_triple(15) + Counter({12: 1}) + >>> pythagorean_triple(40) + Counter({12: 1, 30: 1, 24: 1, 40: 1, 36: 1}) + >>> pythagorean_triple(50) + Counter({12: 1, 30: 1, 24: 1, 40: 1, 36: 1, 48: 1}) + """ + triplets = Counter() + for base in range(1, max_perimeter + 1): + for perpendicular in range(base, max_perimeter + 1): + hypotenuse = (base * base + perpendicular * perpendicular) ** 0.5 + if hypotenuse == int((hypotenuse)): + perimeter = int(base + perpendicular + hypotenuse) + if perimeter > max_perimeter: + continue + else: + triplets[perimeter] += 1 + return triplets + + +if __name__ == "__main__": + triplets = pythagorean_triple(1000) + print(f"{triplets.most_common()[0][0] = }") From 9351889fda986804421991d03b627d7db97e1c8b Mon Sep 17 00:00:00 2001 From: Kushagra Bansal Date: Wed, 19 Aug 2020 16:25:06 +0530 Subject: [PATCH 0718/1071] Created problem_41 in project_euler (#2334) * Create __init__.py * Add files via upload * Update sol1.py * Update sol1.py * Update sol1.py --- project_euler/problem_41/__init__.py | 1 + project_euler/problem_41/sol1.py | 53 ++++++++++++++++++++++++++++ 2 files changed, 54 insertions(+) create mode 100644 project_euler/problem_41/__init__.py create mode 100644 project_euler/problem_41/sol1.py diff --git a/project_euler/problem_41/__init__.py b/project_euler/problem_41/__init__.py new file mode 100644 index 000000000000..792d6005489e --- /dev/null +++ b/project_euler/problem_41/__init__.py @@ -0,0 +1 @@ +# diff --git a/project_euler/problem_41/sol1.py b/project_euler/problem_41/sol1.py new file mode 100644 index 000000000000..a7ce8697b166 --- /dev/null +++ b/project_euler/problem_41/sol1.py @@ -0,0 +1,53 @@ +from math import sqrt +from typing import List +from itertools import permutations + +""" +We shall say that an n-digit number is pandigital if it makes use of all the digits +1 to n exactly once. For example, 2143 is a 4-digit pandigital and is also prime. +What is the largest n-digit pandigital prime that exists? +""" + +""" +All pandigital numbers except for 1, 4 ,7 pandigital numbers are divisible by 3. +So we will check only 7 digit panddigital numbers to obtain the largest possible +pandigital prime. +""" + + +def is_prime(n: int) -> bool: + """ + Returns True if n is prime, + False otherwise. + >>> is_prime(67483) + False + >>> is_prime(563) + True + >>> is_prime(87) + False + """ + if n % 2 == 0: + return False + for i in range(3, int(sqrt(n) + 1), 2): + if n % i == 0: + return False + return True + + +def compute_pandigital_primes(n: int) -> List[int]: + """ + Returns a list of all n-digit pandigital primes. + >>> compute_pandigital_primes(2) + [] + >>> max(compute_pandigital_primes(4)) + 4231 + >>> max(compute_pandigital_primes(7)) + 7652413 + """ + pandigital_str = "".join(str(i) for i in range(1, n + 1)) + perm_list = [int("".join(i)) for i in permutations(pandigital_str, n)] + return [num for num in perm_list if is_prime(num)] + + +if __name__ == "__main__": + print(f"{max(compute_pandigital_primes(7)) = }") From 2eca71663bc01d82c7bdf27fe9248480902711f6 Mon Sep 17 00:00:00 2001 From: Kushagra Bansal Date: Wed, 19 Aug 2020 21:54:02 +0530 Subject: [PATCH 0719/1071] Created check_anagrams.py in strings (#2339) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add files via upload * Update check_anagrams.py * Update check_anagrams.py * Update check_anagrams.py * Update check_anagrams.py * “” or not Co-authored-by: Christian Clauss --- strings/check_anagrams.py | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 strings/check_anagrams.py diff --git a/strings/check_anagrams.py b/strings/check_anagrams.py new file mode 100644 index 000000000000..56c76af5f721 --- /dev/null +++ b/strings/check_anagrams.py @@ -0,0 +1,22 @@ +def check_anagrams(a: str, b: str) -> bool: + """ + Two strings are anagrams if they are made of the same letters + arranged differently (ignoring the case). + >>> check_anagrams('Silent', 'Listen') + True + >>> check_anagrams('This is a string', 'Is this a string') + True + >>> check_anagrams('There', 'Their') + False + """ + return sorted(a.lower()) == sorted(b.lower()) + + +if __name__ == "__main__": + input_A = input("Enter the first string ").strip() + input_B = input("Enter the second string ").strip() + + status = check_anagrams(input_A, input_B) + print( + f"{input_A} and {input_B} are {'' if status else 'not '}anagrams." + ) From 8817a3e66766f48237cd0ca78bc383894af45329 Mon Sep 17 00:00:00 2001 From: Christian Clauss Date: Wed, 19 Aug 2020 21:53:56 +0200 Subject: [PATCH 0720/1071] Delete natural_language_processing (#2317) * Delete natural_language_processing This file is useless. * updating DIRECTORY.md Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> --- natural_language_processing | 3 --- 1 file changed, 3 deletions(-) delete mode 100644 natural_language_processing diff --git a/natural_language_processing b/natural_language_processing deleted file mode 100644 index b864d9e08f70..000000000000 --- a/natural_language_processing +++ /dev/null @@ -1,3 +0,0 @@ -# Natural Language Processing - -https://en.wikipedia.org/wiki/Natural_language_processing From 456893cb5f34454e003378a1590b318df33ccfa2 Mon Sep 17 00:00:00 2001 From: Kushagra Bansal Date: Thu, 20 Aug 2020 20:32:14 +0530 Subject: [PATCH 0721/1071] Created problem_43 in project_euler (#2340) * Create __init__.py * Add files via upload * Update sol1.py * Lose a list() Co-authored-by: Christian Clauss --- project_euler/problem_43/__init__.py | 1 + project_euler/problem_43/sol1.py | 58 ++++++++++++++++++++++++++++ 2 files changed, 59 insertions(+) create mode 100644 project_euler/problem_43/__init__.py create mode 100644 project_euler/problem_43/sol1.py diff --git a/project_euler/problem_43/__init__.py b/project_euler/problem_43/__init__.py new file mode 100644 index 000000000000..792d6005489e --- /dev/null +++ b/project_euler/problem_43/__init__.py @@ -0,0 +1 @@ +# diff --git a/project_euler/problem_43/sol1.py b/project_euler/problem_43/sol1.py new file mode 100644 index 000000000000..2fc429f9f52b --- /dev/null +++ b/project_euler/problem_43/sol1.py @@ -0,0 +1,58 @@ +""" +The number, 1406357289, is a 0 to 9 pandigital number because it is made up of +each of the digits 0 to 9 in some order, but it also has a rather interesting +sub-string divisibility property. + +Let d1 be the 1st digit, d2 be the 2nd digit, and so on. In this way, we note +the following: + +d2d3d4=406 is divisible by 2 +d3d4d5=063 is divisible by 3 +d4d5d6=635 is divisible by 5 +d5d6d7=357 is divisible by 7 +d6d7d8=572 is divisible by 11 +d7d8d9=728 is divisible by 13 +d8d9d10=289 is divisible by 17 +Find the sum of all 0 to 9 pandigital numbers with this property. +""" + + +from itertools import permutations + + +def is_substring_divisible(num: tuple) -> bool: + """ + Returns True if the pandigital number passes + all the divisibility tests. + >>> is_substring_divisible((0, 1, 2, 4, 6, 5, 7, 3, 8, 9)) + False + >>> is_substring_divisible((5, 1, 2, 4, 6, 0, 7, 8, 3, 9)) + False + >>> is_substring_divisible((1, 4, 0, 6, 3, 5, 7, 2, 8, 9)) + True + """ + tests = [2, 3, 5, 7, 11, 13, 17] + for i, test in enumerate(tests): + if (num[i + 1] * 100 + num[i + 2] * 10 + num[i + 3]) % test != 0: + return False + return True + + +def compute_sum(n: int = 10) -> int: + """ + Returns the sum of all pandigital numbers which pass the + divisiility tests. + >>> compute_sum(10) + 16695334890 + """ + list_nums = [ + int("".join(map(str, num))) + for num in permutations(range(n)) + if is_substring_divisible(num) + ] + + return sum(list_nums) + + +if __name__ == "__main__": + print(f"{compute_sum(10) = }") From d3199da00016bce4666dbeead24a91b5275f9a30 Mon Sep 17 00:00:00 2001 From: Alex Joslin Date: Thu, 20 Aug 2020 08:49:43 -0700 Subject: [PATCH 0722/1071] Created Dijkstra's Two Stack Algorithm (#2321) * created dijkstra's two stack algorithm * Made changes to dijkstras two stack algorithm for documentation and testing purposes. * Made changes to dijkstras two stack algorithm for documentation and testing purposes. * Fixed Grammar Mistake * Added Explanation Reference * Imported stack instead of using my own Changed a few minor things. * Imported stack instead of using my own Changed a few minor things. * Update data_structures/stacks/dijkstras_two_stack_algorithm.py Co-authored-by: Christian Clauss * Update dijkstras_two_stack_algorithm.py Co-authored-by: Christian Clauss --- .../stacks/dijkstras_two_stack_algorithm.py | 83 +++++++++++++++++++ 1 file changed, 83 insertions(+) create mode 100644 data_structures/stacks/dijkstras_two_stack_algorithm.py diff --git a/data_structures/stacks/dijkstras_two_stack_algorithm.py b/data_structures/stacks/dijkstras_two_stack_algorithm.py new file mode 100644 index 000000000000..59d47085ada5 --- /dev/null +++ b/data_structures/stacks/dijkstras_two_stack_algorithm.py @@ -0,0 +1,83 @@ +""" +Author: Alexander Joslin +GitHub: github.com/echoaj + +Explanation: https://medium.com/@haleesammar/implemented-in-js-dijkstras-2-stack- + algorithm-for-evaluating-mathematical-expressions-fc0837dae1ea + +We can use Dijkstra's two stack algorithm to solve an equation +such as: (5 + ((4 * 2) * (2 + 3))) + +THESE ARE THE ALGORITHM'S RULES: +RULE 1: Scan the expression from left to right. When an operand is encountered, + push it onto the the operand stack. + +RULE 2: When an operator is encountered in the expression, + push it onto the operator stack. + +RULE 3: When a left parenthesis is encountered in the expression, ignore it. + +RULE 4: When a right parenthesis is encountered in the expression, + pop an operator off the operator stack. The two operands it must + operate on must be the last two operands pushed onto the operand stack. + We therefore pop the operand stack twice, perform the operation, + and push the result back onto the operand stack so it will be available + for use as an operand of the next operator popped off the operator stack. + +RULE 5: When the entire infix expression has been scanned, the value left on + the operand stack represents the value of the expression. + +NOTE: It only works with whole numbers. +""" +__author__ = "Alexander Joslin" + +from .stack import Stack + +import operator as op + + +def dijkstras_two_stack_algorithm(equation: str) -> int: + """ + DocTests + >>> dijkstras_two_stack_algorithm("(5 + 3)") + 8 + >>> dijkstras_two_stack_algorithm("((9 - (2 + 9)) + (8 - 1))") + 5 + >>> dijkstras_two_stack_algorithm("((((3 - 2) - (2 + 3)) + (2 - 4)) + 3)") + -3 + + :param equation: a string + :return: result: an integer + """ + operators = {"*": op.mul, "/": op.truediv, "+": op.add, "-": op.sub} + + operand_stack = Stack() + operator_stack = Stack() + + for i in equation: + if i.isdigit(): + # RULE 1 + operand_stack.push(int(i)) + elif i in operators: + # RULE 2 + operator_stack.push(i) + elif i == ")": + # RULE 4 + opr = operator_stack.peek() + operator_stack.pop() + num1 = operand_stack.peek() + operand_stack.pop() + num2 = operand_stack.peek() + operand_stack.pop() + + total = operators[opr](num2, num1) + operand_stack.push(total) + + # RULE 5 + return operand_stack.peek() + + +if __name__ == "__main__": + equation = "(5 + ((4 * 2) * (2 + 3)))" + # answer = 45 + print(f"{equation} = {dijkstras_two_stack_algorithm(equation)}") From 2eaacee7b4e43df6f45ac58125decea2754e37ab Mon Sep 17 00:00:00 2001 From: kanthuc Date: Thu, 20 Aug 2020 21:54:34 -0700 Subject: [PATCH 0723/1071] lowest_common_ancestor.py static type checking (#2329) * adding static type checking to basic_binary_tree.py * Add static type checking to functions with None return type * Applying code review comments * Added missing import statement * fix spaciing * "cleaned up depth_of_tree" * Add doctests and then streamline display() and is_full_binary_tree() * added static typing to lazy_segment_tree.py * added missing import statement * modified variable names for left and right elements * added static typing to lowest_common_ancestor.py * fixed formatting * modified files to meet style guidelines, edited docstrings and added some doctests * added and fixed doctests in lazy_segment_tree.py * fixed errors in doctests Co-authored-by: Christian Clauss --- .../binary_tree/lazy_segment_tree.py | 101 ++++++++++++------ .../binary_tree/lowest_common_ancestor.py | 56 +++++++--- 2 files changed, 107 insertions(+), 50 deletions(-) diff --git a/data_structures/binary_tree/lazy_segment_tree.py b/data_structures/binary_tree/lazy_segment_tree.py index 461996b87c26..66b995fa1733 100644 --- a/data_structures/binary_tree/lazy_segment_tree.py +++ b/data_structures/binary_tree/lazy_segment_tree.py @@ -1,84 +1,119 @@ import math +from typing import List class SegmentTree: - def __init__(self, N): + def __init__(self, N: int) -> None: self.N = N - self.st = [ - 0 for i in range(0, 4 * N) - ] # approximate the overall size of segment tree with array N - self.lazy = [0 for i in range(0, 4 * N)] # create array to store lazy update - self.flag = [0 for i in range(0, 4 * N)] # flag for lazy update + # approximate the overall size of segment tree with array N + self.st: List[int] = [0 for i in range(0, 4 * N)] + # create array to store lazy update + self.lazy: List[int] = [0 for i in range(0, 4 * N)] + self.flag: List[int] = [0 for i in range(0, 4 * N)] # flag for lazy update - def left(self, idx): + def left(self, idx: int) -> int: + """ + >>> segment_tree = SegmentTree(15) + >>> segment_tree.left(1) + 2 + >>> segment_tree.left(2) + 4 + >>> segment_tree.left(12) + 24 + """ return idx * 2 - def right(self, idx): + def right(self, idx: int) -> int: + """ + >>> segment_tree = SegmentTree(15) + >>> segment_tree.right(1) + 3 + >>> segment_tree.right(2) + 5 + >>> segment_tree.right(12) + 25 + """ return idx * 2 + 1 - def build(self, idx, l, r, A): # noqa: E741 - if l == r: # noqa: E741 - self.st[idx] = A[l - 1] + def build( + self, idx: int, left_element: int, right_element: int, A: List[int] + ) -> None: + if left_element == right_element: + self.st[idx] = A[left_element - 1] else: - mid = (l + r) // 2 - self.build(self.left(idx), l, mid, A) - self.build(self.right(idx), mid + 1, r, A) + mid = (left_element + right_element) // 2 + self.build(self.left(idx), left_element, mid, A) + self.build(self.right(idx), mid + 1, right_element, A) self.st[idx] = max(self.st[self.left(idx)], self.st[self.right(idx)]) - # update with O(lg N) (Normal segment tree without lazy update will take O(Nlg N) - # for each update) - def update(self, idx, l, r, a, b, val): # noqa: E741 + def update( + self, idx: int, left_element: int, right_element: int, a: int, b: int, val: int + ) -> bool: """ + update with O(lg N) (Normal segment tree without lazy update will take O(Nlg N) + for each update) + update(1, 1, N, a, b, v) for update val v to [a,b] """ if self.flag[idx] is True: self.st[idx] = self.lazy[idx] self.flag[idx] = False - if l != r: # noqa: E741 + if left_element != right_element: self.lazy[self.left(idx)] = self.lazy[idx] self.lazy[self.right(idx)] = self.lazy[idx] self.flag[self.left(idx)] = True self.flag[self.right(idx)] = True - if r < a or l > b: + if right_element < a or left_element > b: return True - if l >= a and r <= b: # noqa: E741 + if left_element >= a and right_element <= b: self.st[idx] = val - if l != r: # noqa: E741 + if left_element != right_element: self.lazy[self.left(idx)] = val self.lazy[self.right(idx)] = val self.flag[self.left(idx)] = True self.flag[self.right(idx)] = True return True - mid = (l + r) // 2 - self.update(self.left(idx), l, mid, a, b, val) - self.update(self.right(idx), mid + 1, r, a, b, val) + mid = (left_element + right_element) // 2 + self.update(self.left(idx), left_element, mid, a, b, val) + self.update(self.right(idx), mid + 1, right_element, a, b, val) self.st[idx] = max(self.st[self.left(idx)], self.st[self.right(idx)]) return True # query with O(lg N) - def query(self, idx, l, r, a, b): # noqa: E741 + def query( + self, idx: int, left_element: int, right_element: int, a: int, b: int + ) -> int: """ query(1, 1, N, a, b) for query max of [a,b] + >>> A = [1, 2, -4, 7, 3, -5, 6, 11, -20, 9, 14, 15, 5, 2, -8] + >>> segment_tree = SegmentTree(15) + >>> segment_tree.build(1, 1, 15, A) + >>> segment_tree.query(1, 1, 15, 4, 6) + 7 + >>> segment_tree.query(1, 1, 15, 7, 11) + 14 + >>> segment_tree.query(1, 1, 15, 7, 12) + 15 """ if self.flag[idx] is True: self.st[idx] = self.lazy[idx] self.flag[idx] = False - if l != r: # noqa: E741 + if left_element != right_element: self.lazy[self.left(idx)] = self.lazy[idx] self.lazy[self.right(idx)] = self.lazy[idx] self.flag[self.left(idx)] = True self.flag[self.right(idx)] = True - if r < a or l > b: + if right_element < a or left_element > b: return -math.inf - if l >= a and r <= b: # noqa: E741 + if left_element >= a and right_element <= b: return self.st[idx] - mid = (l + r) // 2 - q1 = self.query(self.left(idx), l, mid, a, b) - q2 = self.query(self.right(idx), mid + 1, r, a, b) + mid = (left_element + right_element) // 2 + q1 = self.query(self.left(idx), left_element, mid, a, b) + q2 = self.query(self.right(idx), mid + 1, right_element, a, b) return max(q1, q2) - def showData(self): + def show_data(self) -> None: showList = [] for i in range(1, N + 1): showList += [self.query(1, 1, self.N, i, i)] @@ -96,4 +131,4 @@ def showData(self): segt.update(1, 1, N, 1, 3, 111) print(segt.query(1, 1, N, 1, 15)) segt.update(1, 1, N, 7, 8, 235) - segt.showData() + segt.show_data() diff --git a/data_structures/binary_tree/lowest_common_ancestor.py b/data_structures/binary_tree/lowest_common_ancestor.py index f560eaa5ef29..c25536cdaef0 100644 --- a/data_structures/binary_tree/lowest_common_ancestor.py +++ b/data_structures/binary_tree/lowest_common_ancestor.py @@ -2,17 +2,29 @@ # https://en.wikipedia.org/wiki/Breadth-first_search import queue +from typing import Dict, List, Tuple -def swap(a, b): +def swap(a: int, b: int) -> Tuple[int, int]: + """ + Return a tuple (b, a) when given two integers a and b + >>> swap(2,3) + (3, 2) + >>> swap(3,4) + (4, 3) + >>> swap(67, 12) + (12, 67) + """ a ^= b b ^= a a ^= b return a, b -# creating sparse table which saves each nodes 2^i-th parent -def creatSparse(max_node, parent): +def create_sparse(max_node: int, parent: List[List[int]]) -> List[List[int]]: + """ + creating sparse table which saves each nodes 2^i-th parent + """ j = 1 while (1 << j) < max_node: for i in range(1, max_node + 1): @@ -22,7 +34,9 @@ def creatSparse(max_node, parent): # returns lca of node u,v -def LCA(u, v, level, parent): +def lowest_common_ancestor( + u: int, v: int, level: List[int], parent: List[List[int]] +) -> List[List[int]]: # u must be deeper in the tree than v if level[u] < level[v]: u, v = swap(u, v) @@ -42,10 +56,18 @@ def LCA(u, v, level, parent): # runs a breadth first search from root node of the tree -# sets every nodes direct parent -# parent of root node is set to 0 -# calculates depth of each node from root node -def bfs(level, parent, max_node, graph, root=1): +def breadth_first_search( + level: List[int], + parent: List[List[int]], + max_node: int, + graph: Dict[int, int], + root=1, +) -> Tuple[List[int], List[List[int]]]: + """ + sets every nodes direct parent + parent of root node is set to 0 + calculates depth of each node from root node + """ level[root] = 0 q = queue.Queue(maxsize=max_node) q.put(root) @@ -59,7 +81,7 @@ def bfs(level, parent, max_node, graph, root=1): return level, parent -def main(): +def main() -> None: max_node = 13 # initializing with 0 parent = [[0 for _ in range(max_node + 10)] for _ in range(20)] @@ -80,14 +102,14 @@ def main(): 12: [], 13: [], } - level, parent = bfs(level, parent, max_node, graph, 1) - parent = creatSparse(max_node, parent) - print("LCA of node 1 and 3 is: ", LCA(1, 3, level, parent)) - print("LCA of node 5 and 6 is: ", LCA(5, 6, level, parent)) - print("LCA of node 7 and 11 is: ", LCA(7, 11, level, parent)) - print("LCA of node 6 and 7 is: ", LCA(6, 7, level, parent)) - print("LCA of node 4 and 12 is: ", LCA(4, 12, level, parent)) - print("LCA of node 8 and 8 is: ", LCA(8, 8, level, parent)) + level, parent = breadth_first_search(level, parent, max_node, graph, 1) + parent = create_sparse(max_node, parent) + print("LCA of node 1 and 3 is: ", lowest_common_ancestor(1, 3, level, parent)) + print("LCA of node 5 and 6 is: ", lowest_common_ancestor(5, 6, level, parent)) + print("LCA of node 7 and 11 is: ", lowest_common_ancestor(7, 11, level, parent)) + print("LCA of node 6 and 7 is: ", lowest_common_ancestor(6, 7, level, parent)) + print("LCA of node 4 and 12 is: ", lowest_common_ancestor(4, 12, level, parent)) + print("LCA of node 8 and 8 is: ", lowest_common_ancestor(8, 8, level, parent)) if __name__ == "__main__": From 0591968947ed3af5b5474eb40af0d8c0995769d5 Mon Sep 17 00:00:00 2001 From: Du Yuanchao Date: Fri, 21 Aug 2020 14:39:03 +0800 Subject: [PATCH 0724/1071] Optimization and fix bug (#2342) * * optimization aliquot_sum * fix bug in average_median * fixup! Format Python code with psf/black push * Update maths/average_median.py * updating DIRECTORY.md Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> --- DIRECTORY.md | 10 ++++++++ .../stacks/dijkstras_two_stack_algorithm.py | 4 +-- maths/aliquot_sum.py | 4 ++- maths/average_median.py | 25 +++++++++---------- project_euler/problem_39/sol1.py | 2 +- project_euler/problem_41/sol1.py | 2 +- strings/check_anagrams.py | 4 +-- 7 files changed, 30 insertions(+), 21 deletions(-) diff --git a/DIRECTORY.md b/DIRECTORY.md index 1530ed763591..70f7726d99c0 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -138,6 +138,7 @@ * [Queue On Pseudo Stack](https://github.com/TheAlgorithms/Python/blob/master/data_structures/queue/queue_on_pseudo_stack.py) * Stacks * [Balanced Parentheses](https://github.com/TheAlgorithms/Python/blob/master/data_structures/stacks/balanced_parentheses.py) + * [Dijkstras Two Stack Algorithm](https://github.com/TheAlgorithms/Python/blob/master/data_structures/stacks/dijkstras_two_stack_algorithm.py) * [Infix To Postfix Conversion](https://github.com/TheAlgorithms/Python/blob/master/data_structures/stacks/infix_to_postfix_conversion.py) * [Infix To Prefix Conversion](https://github.com/TheAlgorithms/Python/blob/master/data_structures/stacks/infix_to_prefix_conversion.py) * [Linked Stack](https://github.com/TheAlgorithms/Python/blob/master/data_structures/stacks/linked_stack.py) @@ -587,10 +588,18 @@ * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_35/sol1.py) * Problem 36 * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_36/sol1.py) + * Problem 37 + * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_37/sol1.py) + * Problem 39 + * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_39/sol1.py) * Problem 40 * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_40/sol1.py) + * Problem 41 + * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_41/sol1.py) * Problem 42 * [Solution42](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_42/solution42.py) + * Problem 43 + * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_43/sol1.py) * Problem 47 * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_47/sol1.py) * Problem 48 @@ -676,6 +685,7 @@ ## Strings * [Aho-Corasick](https://github.com/TheAlgorithms/Python/blob/master/strings/aho-corasick.py) * [Boyer Moore Search](https://github.com/TheAlgorithms/Python/blob/master/strings/boyer_moore_search.py) + * [Check Anagrams](https://github.com/TheAlgorithms/Python/blob/master/strings/check_anagrams.py) * [Check Pangram](https://github.com/TheAlgorithms/Python/blob/master/strings/check_pangram.py) * [Is Palindrome](https://github.com/TheAlgorithms/Python/blob/master/strings/is_palindrome.py) * [Jaro Winkler](https://github.com/TheAlgorithms/Python/blob/master/strings/jaro_winkler.py) diff --git a/data_structures/stacks/dijkstras_two_stack_algorithm.py b/data_structures/stacks/dijkstras_two_stack_algorithm.py index 59d47085ada5..8b4668f9f839 100644 --- a/data_structures/stacks/dijkstras_two_stack_algorithm.py +++ b/data_structures/stacks/dijkstras_two_stack_algorithm.py @@ -31,10 +31,10 @@ """ __author__ = "Alexander Joslin" -from .stack import Stack - import operator as op +from .stack import Stack + def dijkstras_two_stack_algorithm(equation: str) -> int: """ diff --git a/maths/aliquot_sum.py b/maths/aliquot_sum.py index c8635bd61237..9c58aa61d19e 100644 --- a/maths/aliquot_sum.py +++ b/maths/aliquot_sum.py @@ -37,7 +37,9 @@ def aliquot_sum(input_num: int) -> int: raise ValueError("Input must be an integer") if input_num <= 0: raise ValueError("Input must be positive") - return sum(divisor for divisor in range(1, input_num) if input_num % divisor == 0) + return sum( + divisor for divisor in range(1, input_num // 2 + 1) if input_num % divisor == 0 + ) if __name__ == "__main__": diff --git a/maths/average_median.py b/maths/average_median.py index ccb250d7718c..0257e3f76f1a 100644 --- a/maths/average_median.py +++ b/maths/average_median.py @@ -6,6 +6,8 @@ def median(nums): 0 >>> median([4,1,3,2]) 2.5 + >>> median([2, 70, 6, 50, 20, 8, 4]) + 8 Args: nums: List of nums @@ -14,22 +16,19 @@ def median(nums): Median. """ sorted_list = sorted(nums) - med = None - if len(sorted_list) % 2 == 0: - mid_index_1 = len(sorted_list) // 2 - mid_index_2 = (len(sorted_list) // 2) - 1 - med = (sorted_list[mid_index_1] + sorted_list[mid_index_2]) / float(2) - else: - mid_index = (len(sorted_list) - 1) // 2 - med = sorted_list[mid_index] - return med + length = len(sorted_list) + mid_index = length >> 1 + return ( + (sorted_list[mid_index] + sorted_list[mid_index - 1]) / 2 + if length % 2 == 0 + else sorted_list[mid_index] + ) def main(): - print("Odd number of numbers:") - print(median([2, 4, 6, 8, 20, 50, 70])) - print("Even number of numbers:") - print(median([2, 4, 6, 8, 20, 50])) + import doctest + + doctest.testmod() if __name__ == "__main__": diff --git a/project_euler/problem_39/sol1.py b/project_euler/problem_39/sol1.py index 5c21d4beca8c..b0a5d5188fed 100644 --- a/project_euler/problem_39/sol1.py +++ b/project_euler/problem_39/sol1.py @@ -6,8 +6,8 @@ For which value of p ≤ 1000, is the number of solutions maximised? """ -from typing import Dict from collections import Counter +from typing import Dict def pythagorean_triple(max_perimeter: int) -> Dict: diff --git a/project_euler/problem_41/sol1.py b/project_euler/problem_41/sol1.py index a7ce8697b166..4ed09ccb8565 100644 --- a/project_euler/problem_41/sol1.py +++ b/project_euler/problem_41/sol1.py @@ -1,6 +1,6 @@ +from itertools import permutations from math import sqrt from typing import List -from itertools import permutations """ We shall say that an n-digit number is pandigital if it makes use of all the digits diff --git a/strings/check_anagrams.py b/strings/check_anagrams.py index 56c76af5f721..7cc1e2978db9 100644 --- a/strings/check_anagrams.py +++ b/strings/check_anagrams.py @@ -17,6 +17,4 @@ def check_anagrams(a: str, b: str) -> bool: input_B = input("Enter the second string ").strip() status = check_anagrams(input_A, input_B) - print( - f"{input_A} and {input_B} are {'' if status else 'not '}anagrams." - ) + print(f"{input_A} and {input_B} are {'' if status else 'not '}anagrams.") From 0bf1f22d37b8732a2f8ec74e7eace0068a0c231f Mon Sep 17 00:00:00 2001 From: SiddhantBobde <58205856+SiddhantBobde@users.noreply.github.com> Date: Fri, 21 Aug 2020 12:25:50 +0530 Subject: [PATCH 0725/1071] Added function for finding K-th smallest element in BST (#2318) * fixes: #2172 * fixes: #2172 * Added docstrings and type of parameters * fixed error * Added type hints * made changes * removed capital letters from function name * Added type hints * fixed bulid error * modified comments * fixed build error --- data_structures/binary_tree/binary_search_tree.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/data_structures/binary_tree/binary_search_tree.py b/data_structures/binary_tree/binary_search_tree.py index 3868130357fb..45c3933fe899 100644 --- a/data_structures/binary_tree/binary_search_tree.py +++ b/data_structures/binary_tree/binary_search_tree.py @@ -141,6 +141,20 @@ def traversal_tree(self, traversal_function=None): else: return traversal_function(self.root) + def inorder(self, arr: list, node: Node): + """Perform an inorder traversal and append values of the nodes to + a list named arr""" + if node: + self.inorder(arr, node.left) + arr.append(node.value) + self.inorder(arr, node.right) + + def find_kth_smallest(self, k: int, node: Node) -> int: + """Return the kth smallest element in a binary search tree """ + arr = [] + self.inorder(arr, node) # append all values to list using inorder traversal + return arr[k - 1] + def postorder(curr_node): """ From ae33419c121534c3e4738d774dcd72ddfd6b3b4b Mon Sep 17 00:00:00 2001 From: Kushagra Bansal Date: Fri, 21 Aug 2020 17:39:55 +0530 Subject: [PATCH 0726/1071] Created problem_46 in project_euler (#2343) * Create __init__.py * Add files via upload * Update sol1.py * Update sol1.py * Update project_euler/problem_46/sol1.py Co-authored-by: Christian Clauss * Update project_euler/problem_46/sol1.py Co-authored-by: Christian Clauss * Update sol1.py * exact Co-authored-by: Christian Clauss --- project_euler/problem_46/__init__.py | 1 + project_euler/problem_46/sol1.py | 88 ++++++++++++++++++++++++++++ 2 files changed, 89 insertions(+) create mode 100644 project_euler/problem_46/__init__.py create mode 100644 project_euler/problem_46/sol1.py diff --git a/project_euler/problem_46/__init__.py b/project_euler/problem_46/__init__.py new file mode 100644 index 000000000000..792d6005489e --- /dev/null +++ b/project_euler/problem_46/__init__.py @@ -0,0 +1 @@ +# diff --git a/project_euler/problem_46/sol1.py b/project_euler/problem_46/sol1.py new file mode 100644 index 000000000000..761e9b8cc7fb --- /dev/null +++ b/project_euler/problem_46/sol1.py @@ -0,0 +1,88 @@ +""" +It was proposed by Christian Goldbach that every odd composite number can be +written as the sum of a prime and twice a square. + +9 = 7 + 2 × 12 +15 = 7 + 2 × 22 +21 = 3 + 2 × 32 +25 = 7 + 2 × 32 +27 = 19 + 2 × 22 +33 = 31 + 2 × 12 + +It turns out that the conjecture was false. + +What is the smallest odd composite that cannot be written as the sum of a +prime and twice a square? +""" + +from typing import List + +seive = [True] * 100001 +i = 2 +while i * i <= 100000: + if seive[i]: + for j in range(i * i, 100001, i): + seive[j] = False + i += 1 + + +def is_prime(n: int) -> bool: + """ + Returns True if n is prime, + False otherwise, for 2 <= n <= 100000 + >>> is_prime(87) + False + >>> is_prime(23) + True + >>> is_prime(25363) + False + """ + return seive[n] + + +odd_composites = [num for num in range(3, len(seive), 2) if not is_prime(num)] + + +def compute_nums(n: int) -> List[int]: + """ + Returns a list of first n odd composite numbers which do + not follow the conjecture. + >>> compute_nums(1) + [5777] + >>> compute_nums(2) + [5777, 5993] + >>> compute_nums(0) + Traceback (most recent call last): + ... + ValueError: n must be >= 0 + >>> compute_nums("a") + Traceback (most recent call last): + ... + ValueError: n must be an integer + >>> compute_nums(1.1) + Traceback (most recent call last): + ... + ValueError: n must be an integer + + """ + if not isinstance(n, int): + raise ValueError("n must be an integer") + if n <= 0: + raise ValueError("n must be >= 0") + + list_nums = [] + for num in range(len(odd_composites)): + i = 0 + while 2 * i * i <= odd_composites[num]: + rem = odd_composites[num] - 2 * i * i + if is_prime(rem): + break + i += 1 + else: + list_nums.append(odd_composites[num]) + if len(list_nums) == n: + return list_nums + + +if __name__ == "__main__": + print(f"{compute_nums(1) = }") From 6822d1afeb2eaeb0454421d21276738ef3f52727 Mon Sep 17 00:00:00 2001 From: wuyudi Date: Sat, 22 Aug 2020 02:41:48 +0800 Subject: [PATCH 0727/1071] Update matrix_operation.py (#2344) * Update matrix_operation.py * Update matrix_operation.py * Update matrix_operation.py * Update matrix_operation.py * Update matrix_operation.py * Update matrix_operation.py --- matrix/matrix_operation.py | 71 +++++++++++++++++--------------------- 1 file changed, 31 insertions(+), 40 deletions(-) diff --git a/matrix/matrix_operation.py b/matrix/matrix_operation.py index ddc201a1aa15..b5a724ad688c 100644 --- a/matrix/matrix_operation.py +++ b/matrix/matrix_operation.py @@ -15,9 +15,8 @@ def add(*matrix_s: List[list]) -> List[list]: [[7, 14], [12, 16]] """ if all(_check_not_integer(m) for m in matrix_s): - a, *b = matrix_s - for matrix in b: - _verify_matrix_sizes(a, matrix) + for i in matrix_s[1:]: + _verify_matrix_sizes(matrix_s[0], i) return [[sum(t) for t in zip(*m)] for m in zip(*matrix_s)] @@ -28,8 +27,9 @@ def subtract(matrix_a: List[list], matrix_b: List[list]) -> List[list]: >>> subtract([[1,2.5],[3,4]],[[2,3],[4,5.5]]) [[-1, -0.5], [-1, -1.5]] """ - if _check_not_integer(matrix_a) and _check_not_integer(matrix_b): - _verify_matrix_sizes(matrix_a, matrix_b) + if _check_not_integer(matrix_a)\ + and _check_not_integer(matrix_b)\ + and _verify_matrix_sizes(matrix_a, matrix_b): return [[i - j for i, j in zip(*m)] for m in zip(matrix_a, matrix_b)] @@ -49,25 +49,19 @@ def multiply(matrix_a: List[list], matrix_b: List[list]) -> List[list]: [[19, 15], [43, 35]] >>> multiply([[1,2.5],[3,4.5]],[[5,5],[7,5]]) [[22.5, 17.5], [46.5, 37.5]] + >>> multiply([[1, 2, 3]], [[2], [3], [4]]) + [[20]] """ if _check_not_integer(matrix_a) and _check_not_integer(matrix_b): - matrix_c = [] rows, cols = _verify_matrix_sizes(matrix_a, matrix_b) - if cols[0] != rows[1]: - raise ValueError( - f"Cannot multiply matrix of dimensions ({rows[0]},{cols[0]}) " - f"and ({rows[1]},{cols[1]})" - ) - for i in range(rows[0]): - list_1 = [] - for j in range(cols[1]): - val = 0 - for k in range(cols[1]): - val += matrix_a[i][k] * matrix_b[k][j] - list_1.append(val) - matrix_c.append(list_1) - return matrix_c + if cols[0] != rows[1]: + raise ValueError( + f"Cannot multiply matrix of dimensions ({rows[0]},{cols[0]}) " + f"and ({rows[1]},{cols[1]})" + ) + return [[sum(m * n for m, n in zip(i, j)) for j in zip(*matrix_b)] + for i in matrix_a] def identity(n: int) -> List[list]: @@ -93,7 +87,7 @@ def transpose(matrix: List[list], return_map: bool = True) -> List[list]: if return_map: return map(list, zip(*matrix)) else: - return [[row[i] for row in matrix] for i in range(len(matrix[0]))] + return list(map(list, zip(*matrix))) def minor(matrix: List[list], row: int, column: int) -> List[list]: @@ -101,8 +95,8 @@ def minor(matrix: List[list], row: int, column: int) -> List[list]: >>> minor([[1, 2], [3, 4]], 1, 1) [[1]] """ - minor = matrix[:row] + matrix[row + 1 :] - return [row[:column] + row[column + 1 :] for row in minor] + minor = matrix[: row] + matrix[row + 1:] + return [row[:column] + row[column + 1:] for row in minor] def determinant(matrix: List[list]) -> int: @@ -115,10 +109,8 @@ def determinant(matrix: List[list]) -> int: if len(matrix) == 1: return matrix[0][0] - res = 0 - for x in range(len(matrix)): - res += matrix[0][x] * determinant(minor(matrix, 0, x)) * (-1) ** x - return res + return sum(x * determinant(minor(matrix, 0, i)) * (-1) ** i + for i, x in enumerate(matrix[0])) def inverse(matrix: List[list]) -> List[list]: @@ -132,10 +124,9 @@ def inverse(matrix: List[list]) -> List[list]: if det == 0: return None - matrix_minor = [[] for _ in matrix] - for i in range(len(matrix)): - for j in range(len(matrix)): - matrix_minor[i].append(determinant(minor(matrix, i, j))) + matrix_minor = [[determinant(minor(matrix, i, j)) + for j in range(len(matrix))] + for i in range(len(matrix))] cofactors = [ [x * (-1) ** (row + col) for col, x in enumerate(matrix_minor[row])] @@ -152,29 +143,29 @@ def _check_not_integer(matrix: List[list]) -> bool: def _shape(matrix: List[list]) -> list: - return list((len(matrix), len(matrix[0]))) + return len(matrix), len(matrix[0]) def _verify_matrix_sizes(matrix_a: List[list], matrix_b: List[list]) -> Tuple[list]: - shape = _shape(matrix_a) - shape += _shape(matrix_b) - if shape[0] != shape[2] or shape[1] != shape[3]: + shape = _shape(matrix_a) + _shape(matrix_b) + if shape[0] != shape[3] or shape[1] != shape[2]: raise ValueError( f"operands could not be broadcast together with shape " f"({shape[0], shape[1]}), ({shape[2], shape[3]})" ) - return [shape[0], shape[2]], [shape[1], shape[3]] + return (shape[0], shape[2]), (shape[1], shape[3]) def main(): matrix_a = [[12, 10], [3, 9]] matrix_b = [[3, 4], [7, 4]] - matrix_c = [[11, 12, 13, 14], [21, 22, 23, 24], [31, 32, 33, 34], [41, 42, 43, 44]] + matrix_c = [[11, 12, 13, 14], [21, 22, 23, 24], + [31, 32, 33, 34], [41, 42, 43, 44]] matrix_d = [[3, 0, 2], [2, 0, -2], [0, 1, 1]] - print(f"Add Operation, {matrix_a} + {matrix_b} =" f"{add(matrix_a, matrix_b)} \n") print( - f"Multiply Operation, {matrix_a} * {matrix_b}", - f"= {multiply(matrix_a, matrix_b)} \n", + f"Add Operation, {add(matrix_a, matrix_b) = } \n") + print( + f"Multiply Operation, {multiply(matrix_a, matrix_b) = } \n", ) print(f"Identity: {identity(5)}\n") print(f"Minor of {matrix_c} = {minor(matrix_c, 1, 2)} \n") From a46b5559e0ce8f953b60296c41d470544c907987 Mon Sep 17 00:00:00 2001 From: Kaif Kohari Date: Sat, 22 Aug 2020 03:28:26 +0530 Subject: [PATCH 0728/1071] Job fetching (#2219) * Adding job scarping algorithm to web programming * Delete fetch_jobs.py * Adding Jobs Scraping to web programming * Add Python type hints Co-authored-by: Christian Clauss --- web_programming/fetch_jobs.py | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 web_programming/fetch_jobs.py diff --git a/web_programming/fetch_jobs.py b/web_programming/fetch_jobs.py new file mode 100644 index 000000000000..888f41294974 --- /dev/null +++ b/web_programming/fetch_jobs.py @@ -0,0 +1,23 @@ +""" +Scraping jobs given job title and location from indeed website +""" +from typing import Generator, Tuple + +import requests +from bs4 import BeautifulSoup + +url = "https://www.indeed.co.in/jobs?q=mobile+app+development&l=" + + +def fetch_jobs(location: str = "mumbai") -> Generator[Tuple[str, str], None, None]: + soup = BeautifulSoup(requests.get(url + location).content, "html.parser") + # This attribute finds out all the specifics listed in a job + for job in soup.find_all("div", attrs={"data-tn-component": "organicJob"}): + job_title = job.find("a", attrs={"data-tn-element": "jobTitle"}).text.strip() + company_name = job.find("span", {"class": "company"}).text.strip() + yield job_title, company_name + + +if __name__ == "__main__": + for i, job in enumerate(fetch_jobs("Bangalore"), 1): + print(f"Job {i:>2} is {job[0]} at {job[1]}") From ee28deea4a22b04489f23cfc6fc287c69792c5a1 Mon Sep 17 00:00:00 2001 From: Guillaume Rochedix <33205665+BriseBalloches@users.noreply.github.com> Date: Sun, 23 Aug 2020 04:35:54 +0200 Subject: [PATCH 0729/1071] Insertion sort : type hint, docstring (#2327) * insertion sort : docstring, type hinting * Update insertion_sort.py Co-authored-by: Christian Clauss --- sorts/insertion_sort.py | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/sorts/insertion_sort.py b/sorts/insertion_sort.py index ca678381b431..28458ad1b86d 100644 --- a/sorts/insertion_sort.py +++ b/sorts/insertion_sort.py @@ -1,18 +1,21 @@ """ -This is a pure Python implementation of the insertion sort algorithm +A pure Python implementation of the insertion sort algorithm + +This algorithm sorts a collection by comparing adjacent elements. +When it finds that order is not respected, it moves the element compared +backward until the order is correct. It then goes back directly to the +element's initial position resuming forward comparison. For doctests run following command: -python -m doctest -v insertion_sort.py -or python3 -m doctest -v insertion_sort.py For manual testing run: -python insertion_sort.py +python3 insertion_sort.py """ -def insertion_sort(collection): - """Pure implementation of the insertion sort algorithm in Python +def insertion_sort(collection: list) -> list: + """A pure Python implementation of the insertion sort algorithm :param collection: some mutable ordered collection with heterogeneous comparable items inside @@ -47,4 +50,4 @@ def insertion_sort(collection): if __name__ == "__main__": user_input = input("Enter numbers separated by a comma:\n").strip() unsorted = [int(item) for item in user_input.split(",")] - print(insertion_sort(unsorted)) + print(f"{insertion_sort(unsorted) = }") From d402cd0b6eed0ec303c9f1256fdc933256b0e850 Mon Sep 17 00:00:00 2001 From: BAKEZQ Date: Sun, 23 Aug 2020 19:40:57 +0800 Subject: [PATCH 0730/1071] Fix SettingWithCopy warning by pandas (#2346) * Fix SettingWithCopy warning in pandas https://github.com/TheAlgorithms/Python/issues/2282 * Update k_means_clust.py * Update k_means_clust.py * Update k_means_clust.py * Update k_means_clust.py * Update k_means_clust.py * Update k_means_clust.py --- machine_learning/k_means_clust.py | 17 ++--------------- 1 file changed, 2 insertions(+), 15 deletions(-) diff --git a/machine_learning/k_means_clust.py b/machine_learning/k_means_clust.py index 071c58db256b..4da904ae3568 100644 --- a/machine_learning/k_means_clust.py +++ b/machine_learning/k_means_clust.py @@ -1,13 +1,10 @@ """README, Author - Anurag Kumar(mailto:anuragkumarak95@gmail.com) - Requirements: - sklearn - numpy - matplotlib - Python: - 3.5 - Inputs: - X , a 2D numpy array of features. - k , number of clusters to create. @@ -16,10 +13,8 @@ - maxiter , maximum number of iterations to process. - heterogeneity , empty list that will be filled with hetrogeneity values if passed to kmeans func. - Usage: 1. define 'k' value, 'X' features array and 'hetrogeneity' empty list - 2. create initial_centroids, initial_centroids = get_initial_centroids( X, @@ -27,9 +22,7 @@ seed=0 # seed value for initial centroid generation, # None for randomness(default=None) ) - 3. find centroids and clusters using kmeans function. - centroids, cluster_assignment = kmeans( X, k, @@ -38,19 +31,14 @@ record_heterogeneity=heterogeneity, verbose=True # whether to print logs in console or not.(default=False) ) - - 4. Plot the loss function, hetrogeneity values for every iteration saved in hetrogeneity list. plot_heterogeneity( heterogeneity, k ) - 5. Transfers Dataframe into excel format it must have feature called 'Clust' with k means clustering numbers in it. - - """ import warnings @@ -222,7 +210,6 @@ def ReportGenerator( in order to run the function following libraries must be imported: import pandas as pd import numpy as np - >>> data = pd.DataFrame() >>> data['numbers'] = [1, 2, 3] >>> data['col1'] = [0.5, 2.5, 4.5] @@ -287,10 +274,10 @@ def ReportGenerator( .T.reset_index() .rename(index=str, columns={"level_0": "Features", "level_1": "Type"}) ) # rename columns - + # calculate the size of cluster(count of clientID's) clustersize = report[ (report["Features"] == "dummy") & (report["Type"] == "count") - ] # calculate the size of cluster(count of clientID's) + ].copy() # avoid SettingWithCopyWarning clustersize.Type = ( "ClusterSize" # rename created cluster df to match report column names ) From f8c57130f2717545ff9c0ce595dd0c419b9f46e2 Mon Sep 17 00:00:00 2001 From: kanthuc Date: Mon, 24 Aug 2020 00:52:02 -0700 Subject: [PATCH 0731/1071] lazy_segment_tree.py-style-fixes (#2347) * fixed variable naming and unnecessary type hints * print(segt) Co-authored-by: Christian Clauss --- .../binary_tree/lazy_segment_tree.py | 65 ++++++++++--------- 1 file changed, 33 insertions(+), 32 deletions(-) diff --git a/data_structures/binary_tree/lazy_segment_tree.py b/data_structures/binary_tree/lazy_segment_tree.py index 66b995fa1733..38d93a32e767 100644 --- a/data_structures/binary_tree/lazy_segment_tree.py +++ b/data_structures/binary_tree/lazy_segment_tree.py @@ -3,13 +3,13 @@ class SegmentTree: - def __init__(self, N: int) -> None: - self.N = N - # approximate the overall size of segment tree with array N - self.st: List[int] = [0 for i in range(0, 4 * N)] + def __init__(self, size: int) -> None: + self.size = size + # approximate the overall size of segment tree with given value + self.segment_tree = [0 for i in range(0, 4 * size)] # create array to store lazy update - self.lazy: List[int] = [0 for i in range(0, 4 * N)] - self.flag: List[int] = [0 for i in range(0, 4 * N)] # flag for lazy update + self.lazy = [0 for i in range(0, 4 * size)] + self.flag = [0 for i in range(0, 4 * size)] # flag for lazy update def left(self, idx: int) -> int: """ @@ -39,24 +39,26 @@ def build( self, idx: int, left_element: int, right_element: int, A: List[int] ) -> None: if left_element == right_element: - self.st[idx] = A[left_element - 1] + self.segment_tree[idx] = A[left_element - 1] else: mid = (left_element + right_element) // 2 self.build(self.left(idx), left_element, mid, A) self.build(self.right(idx), mid + 1, right_element, A) - self.st[idx] = max(self.st[self.left(idx)], self.st[self.right(idx)]) + self.segment_tree[idx] = max( + self.segment_tree[self.left(idx)], self.segment_tree[self.right(idx)] + ) def update( self, idx: int, left_element: int, right_element: int, a: int, b: int, val: int ) -> bool: """ - update with O(lg N) (Normal segment tree without lazy update will take O(Nlg N) + update with O(lg n) (Normal segment tree without lazy update will take O(nlg n) for each update) - update(1, 1, N, a, b, v) for update val v to [a,b] + update(1, 1, size, a, b, v) for update val v to [a,b] """ if self.flag[idx] is True: - self.st[idx] = self.lazy[idx] + self.segment_tree[idx] = self.lazy[idx] self.flag[idx] = False if left_element != right_element: self.lazy[self.left(idx)] = self.lazy[idx] @@ -67,7 +69,7 @@ def update( if right_element < a or left_element > b: return True if left_element >= a and right_element <= b: - self.st[idx] = val + self.segment_tree[idx] = val if left_element != right_element: self.lazy[self.left(idx)] = val self.lazy[self.right(idx)] = val @@ -77,15 +79,17 @@ def update( mid = (left_element + right_element) // 2 self.update(self.left(idx), left_element, mid, a, b, val) self.update(self.right(idx), mid + 1, right_element, a, b, val) - self.st[idx] = max(self.st[self.left(idx)], self.st[self.right(idx)]) + self.segment_tree[idx] = max( + self.segment_tree[self.left(idx)], self.segment_tree[self.right(idx)] + ) return True - # query with O(lg N) + # query with O(lg n) def query( self, idx: int, left_element: int, right_element: int, a: int, b: int ) -> int: """ - query(1, 1, N, a, b) for query max of [a,b] + query(1, 1, size, a, b) for query max of [a,b] >>> A = [1, 2, -4, 7, 3, -5, 6, 11, -20, 9, 14, 15, 5, 2, -8] >>> segment_tree = SegmentTree(15) >>> segment_tree.build(1, 1, 15, A) @@ -97,7 +101,7 @@ def query( 15 """ if self.flag[idx] is True: - self.st[idx] = self.lazy[idx] + self.segment_tree[idx] = self.lazy[idx] self.flag[idx] = False if left_element != right_element: self.lazy[self.left(idx)] = self.lazy[idx] @@ -107,28 +111,25 @@ def query( if right_element < a or left_element > b: return -math.inf if left_element >= a and right_element <= b: - return self.st[idx] + return self.segment_tree[idx] mid = (left_element + right_element) // 2 q1 = self.query(self.left(idx), left_element, mid, a, b) q2 = self.query(self.right(idx), mid + 1, right_element, a, b) return max(q1, q2) - def show_data(self) -> None: - showList = [] - for i in range(1, N + 1): - showList += [self.query(1, 1, self.N, i, i)] - print(showList) + def __str__(self) -> None: + return [self.query(1, 1, self.size, i, i) for i in range(1, self.size + 1)] if __name__ == "__main__": A = [1, 2, -4, 7, 3, -5, 6, 11, -20, 9, 14, 15, 5, 2, -8] - N = 15 - segt = SegmentTree(N) - segt.build(1, 1, N, A) - print(segt.query(1, 1, N, 4, 6)) - print(segt.query(1, 1, N, 7, 11)) - print(segt.query(1, 1, N, 7, 12)) - segt.update(1, 1, N, 1, 3, 111) - print(segt.query(1, 1, N, 1, 15)) - segt.update(1, 1, N, 7, 8, 235) - segt.show_data() + size = 15 + segt = SegmentTree(size) + segt.build(1, 1, size, A) + print(segt.query(1, 1, size, 4, 6)) + print(segt.query(1, 1, size, 7, 11)) + print(segt.query(1, 1, size, 7, 12)) + segt.update(1, 1, size, 1, 3, 111) + print(segt.query(1, 1, size, 1, 15)) + segt.update(1, 1, size, 7, 8, 235) + print(segt) From 5cfc017ebb51b7107c2c930df94ab18ef5e3c88c Mon Sep 17 00:00:00 2001 From: Kushagra Bansal Date: Tue, 25 Aug 2020 13:16:13 +0530 Subject: [PATCH 0732/1071] Created problem_44 in project_euler (#2348) * Create __int__.py * Update and rename project_euler/__int__.py to project_euler/problem_44/__int__.py * Add files via upload * Update sol1.py * Update __int__.py * Delete __int__.py * Create __init__.py * Update project_euler/problem_44/sol1.py Co-authored-by: Christian Clauss * Update project_euler/problem_44/sol1.py Co-authored-by: Christian Clauss * Update project_euler/problem_44/sol1.py Co-authored-by: Christian Clauss Co-authored-by: Christian Clauss --- project_euler/problem_44/__init__.py | 1 + project_euler/problem_44/sol1.py | 45 ++++++++++++++++++++++++++++ 2 files changed, 46 insertions(+) create mode 100644 project_euler/problem_44/__init__.py create mode 100644 project_euler/problem_44/sol1.py diff --git a/project_euler/problem_44/__init__.py b/project_euler/problem_44/__init__.py new file mode 100644 index 000000000000..792d6005489e --- /dev/null +++ b/project_euler/problem_44/__init__.py @@ -0,0 +1 @@ +# diff --git a/project_euler/problem_44/sol1.py b/project_euler/problem_44/sol1.py new file mode 100644 index 000000000000..536720b39128 --- /dev/null +++ b/project_euler/problem_44/sol1.py @@ -0,0 +1,45 @@ +""" +Pentagonal numbers are generated by the formula, Pn=n(3n−1)/2. The first ten +pentagonal numbers are: +1, 5, 12, 22, 35, 51, 70, 92, 117, 145, ... +It can be seen that P4 + P7 = 22 + 70 = 92 = P8. However, their difference, +70 − 22 = 48, is not pentagonal. + +Find the pair of pentagonal numbers, Pj and Pk, for which their sum and difference +are pentagonal and D = |Pk − Pj| is minimised; what is the value of D? +""" + + +def is_pentagonal(n: int) -> bool: + """ + Returns True if n is pentagonal, False otherwise. + >>> is_pentagonal(330) + True + >>> is_pentagonal(7683) + False + >>> is_pentagonal(2380) + True + """ + root = (1 + 24 * n) ** 0.5 + return ((1 + root) / 6) % 1 == 0 + + +def compute_num(limit: int = 5000) -> int: + """ + Returns the minimum difference of two pentagonal numbers P1 and P2 such that + P1 + P2 is pentagonal and P2 - P1 is pentagonal. + >>> compute_num(5000) + 5482660 + """ + pentagonal_nums = [(i * (3 * i - 1)) // 2 for i in range(1, limit)] + for i, pentagonal_i in enumerate(pentagonal_nums): + for j in range(i, len(pentagonal_nums)): + pentagonal_j = pentagonal_nums[j] + a = pentagonal_i + pentagonal_j + b = pentagonal_j - pentagonal_i + if is_pentagonal(a) and is_pentagonal(b): + return b + + +if __name__ == "__main__": + print(f"{compute_num() = }") From 402ba7f49abc144dd1ce43b4133f43fd3a7e5b1c Mon Sep 17 00:00:00 2001 From: Kushagra Bansal Date: Tue, 25 Aug 2020 17:18:19 +0530 Subject: [PATCH 0733/1071] Created problem_45 in project_euler and Speed Boost for problem_34/sol1.py (#2349) * Create __init__.py * Add files via upload * Update sol1.py * Update sol1.py * Update project_euler/problem_45/sol1.py Co-authored-by: Christian Clauss * Update sol1.py * Update sol1.py * Update project_euler/problem_34/sol1.py Co-authored-by: Christian Clauss * Update project_euler/problem_34/sol1.py Co-authored-by: Christian Clauss * Update sol1.py * Update project_euler/problem_34/sol1.py Co-authored-by: Christian Clauss * Update sol1.py * Update project_euler/problem_34/sol1.py Co-authored-by: Christian Clauss Co-authored-by: Christian Clauss --- project_euler/problem_34/sol1.py | 43 +++------------------ project_euler/problem_45/__init__.py | 1 + project_euler/problem_45/sol1.py | 57 ++++++++++++++++++++++++++++ 3 files changed, 63 insertions(+), 38 deletions(-) create mode 100644 project_euler/problem_45/__init__.py create mode 100644 project_euler/problem_45/sol1.py diff --git a/project_euler/problem_34/sol1.py b/project_euler/problem_34/sol1.py index 126aee9d2023..c19fac5de897 100644 --- a/project_euler/problem_34/sol1.py +++ b/project_euler/problem_34/sol1.py @@ -4,37 +4,7 @@ Note: As 1! = 1 and 2! = 2 are not sums they are not included. """ - -def factorial(n: int) -> int: - """Return the factorial of n. - >>> factorial(5) - 120 - >>> factorial(1) - 1 - >>> factorial(0) - 1 - >>> factorial(-1) - Traceback (most recent call last): - ... - ValueError: n must be >= 0 - >>> factorial(1.1) - Traceback (most recent call last): - ... - ValueError: n must be exact integer - """ - - if not n >= 0: - raise ValueError("n must be >= 0") - if int(n) != n: - raise ValueError("n must be exact integer") - if n + 1 == n: # catch a value like 1e300 - raise OverflowError("n too large") - result = 1 - factor = 2 - while factor <= n: - result *= factor - factor += 1 - return result +from math import factorial def sum_of_digit_factorial(n: int) -> int: @@ -45,7 +15,7 @@ def sum_of_digit_factorial(n: int) -> int: >>> sum_of_digit_factorial(0) 1 """ - return sum(factorial(int(digit)) for digit in str(n)) + return sum(factorial(int(char)) for char in str(n)) def compute() -> int: @@ -56,12 +26,9 @@ def compute() -> int: >>> compute() 40730 """ - return sum( - num - for num in range(3, 7 * factorial(9) + 1) - if sum_of_digit_factorial(num) == num - ) + limit = 7 * factorial(9) + 1 + return sum(i for i in range(3, limit) if sum_of_digit_factorial(i) == i) if __name__ == "__main__": - print(compute()) + print(f"{compute()} = ") diff --git a/project_euler/problem_45/__init__.py b/project_euler/problem_45/__init__.py new file mode 100644 index 000000000000..792d6005489e --- /dev/null +++ b/project_euler/problem_45/__init__.py @@ -0,0 +1 @@ +# diff --git a/project_euler/problem_45/sol1.py b/project_euler/problem_45/sol1.py new file mode 100644 index 000000000000..ed66e6fab210 --- /dev/null +++ b/project_euler/problem_45/sol1.py @@ -0,0 +1,57 @@ +""" +Triangle, pentagonal, and hexagonal numbers are generated by the following formulae: +Triangle T(n) = (n * (n + 1)) / 2 1, 3, 6, 10, 15, ... +Pentagonal P(n) = (n * (3 * n − 1)) / 2 1, 5, 12, 22, 35, ... +Hexagonal H(n) = n * (2 * n − 1) 1, 6, 15, 28, 45, ... +It can be verified that T(285) = P(165) = H(143) = 40755. + +Find the next triangle number that is also pentagonal and hexagonal. +All trinagle numbers are hexagonal numbers. +T(2n-1) = n * (2 * n - 1) = H(n) +So we shall check only for hexagonal numbers which are also pentagonal. +""" + + +def hexagonal_num(n: int) -> int: + """ + Returns nth hexagonal number + >>> hexagonal_num(143) + 40755 + >>> hexagonal_num(21) + 861 + >>> hexagonal_num(10) + 190 + """ + return n * (2 * n - 1) + + +def is_pentagonal(n: int) -> bool: + """ + Returns True if n is pentagonal, False otherwise. + >>> is_pentagonal(330) + True + >>> is_pentagonal(7683) + False + >>> is_pentagonal(2380) + True + """ + root = (1 + 24 * n) ** 0.5 + return ((1 + root) / 6) % 1 == 0 + + +def compute_num(start: int = 144) -> int: + """ + Returns the next number which is traingular, pentagonal and hexagonal. + >>> compute_num(144) + 1533776805 + """ + n = start + num = hexagonal_num(n) + while not is_pentagonal(num): + n += 1 + num = hexagonal_num(n) + return num + + +if __name__ == "__main__": + print(f"{compute_num(144)} = ") From e77600638da54b4e4a5ab3bdbeef64d8f8085c6c Mon Sep 17 00:00:00 2001 From: Christian Clauss Date: Tue, 25 Aug 2020 15:47:06 +0200 Subject: [PATCH 0734/1071] Travis CI: Identify our ten slowest pytests (#2350) * Travis CI: Identify our ten slowest tests https://howchoo.com/g/mtblodnjzjc/how-to-measure-unit-test-execution-times-in-pytest helps us to find the individual tests that are slowing down our Travis CI checks. * fixup! Format Python code with psf/black push Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> --- .travis.yml | 2 +- matrix/matrix_operation.py | 40 ++++++++++++++++++++------------------ 2 files changed, 22 insertions(+), 20 deletions(-) diff --git a/.travis.yml b/.travis.yml index 21103c3570d3..a03f8161f13e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -15,6 +15,6 @@ before_script: - pip install -r requirements.txt # fast fail on black, flake8, validate_filenames script: - mypy --ignore-missing-imports . - - pytest --doctest-modules --cov-report=term-missing:skip-covered --cov=. . + - pytest --doctest-modules --durations=10 --cov-report=term-missing:skip-covered --cov=. . after_success: - scripts/build_directory_md.py 2>&1 | tee DIRECTORY.md diff --git a/matrix/matrix_operation.py b/matrix/matrix_operation.py index b5a724ad688c..42a94da12375 100644 --- a/matrix/matrix_operation.py +++ b/matrix/matrix_operation.py @@ -27,9 +27,11 @@ def subtract(matrix_a: List[list], matrix_b: List[list]) -> List[list]: >>> subtract([[1,2.5],[3,4]],[[2,3],[4,5.5]]) [[-1, -0.5], [-1, -1.5]] """ - if _check_not_integer(matrix_a)\ - and _check_not_integer(matrix_b)\ - and _verify_matrix_sizes(matrix_a, matrix_b): + if ( + _check_not_integer(matrix_a) + and _check_not_integer(matrix_b) + and _verify_matrix_sizes(matrix_a, matrix_b) + ): return [[i - j for i, j in zip(*m)] for m in zip(matrix_a, matrix_b)] @@ -60,8 +62,9 @@ def multiply(matrix_a: List[list], matrix_b: List[list]) -> List[list]: f"Cannot multiply matrix of dimensions ({rows[0]},{cols[0]}) " f"and ({rows[1]},{cols[1]})" ) - return [[sum(m * n for m, n in zip(i, j)) for j in zip(*matrix_b)] - for i in matrix_a] + return [ + [sum(m * n for m, n in zip(i, j)) for j in zip(*matrix_b)] for i in matrix_a + ] def identity(n: int) -> List[list]: @@ -95,8 +98,8 @@ def minor(matrix: List[list], row: int, column: int) -> List[list]: >>> minor([[1, 2], [3, 4]], 1, 1) [[1]] """ - minor = matrix[: row] + matrix[row + 1:] - return [row[:column] + row[column + 1:] for row in minor] + minor = matrix[:row] + matrix[row + 1 :] + return [row[:column] + row[column + 1 :] for row in minor] def determinant(matrix: List[list]) -> int: @@ -109,8 +112,10 @@ def determinant(matrix: List[list]) -> int: if len(matrix) == 1: return matrix[0][0] - return sum(x * determinant(minor(matrix, 0, i)) * (-1) ** i - for i, x in enumerate(matrix[0])) + return sum( + x * determinant(minor(matrix, 0, i)) * (-1) ** i + for i, x in enumerate(matrix[0]) + ) def inverse(matrix: List[list]) -> List[list]: @@ -124,9 +129,10 @@ def inverse(matrix: List[list]) -> List[list]: if det == 0: return None - matrix_minor = [[determinant(minor(matrix, i, j)) - for j in range(len(matrix))] - for i in range(len(matrix))] + matrix_minor = [ + [determinant(minor(matrix, i, j)) for j in range(len(matrix))] + for i in range(len(matrix)) + ] cofactors = [ [x * (-1) ** (row + col) for col, x in enumerate(matrix_minor[row])] @@ -159,14 +165,10 @@ def _verify_matrix_sizes(matrix_a: List[list], matrix_b: List[list]) -> Tuple[li def main(): matrix_a = [[12, 10], [3, 9]] matrix_b = [[3, 4], [7, 4]] - matrix_c = [[11, 12, 13, 14], [21, 22, 23, 24], - [31, 32, 33, 34], [41, 42, 43, 44]] + matrix_c = [[11, 12, 13, 14], [21, 22, 23, 24], [31, 32, 33, 34], [41, 42, 43, 44]] matrix_d = [[3, 0, 2], [2, 0, -2], [0, 1, 1]] - print( - f"Add Operation, {add(matrix_a, matrix_b) = } \n") - print( - f"Multiply Operation, {multiply(matrix_a, matrix_b) = } \n", - ) + print(f"Add Operation, {add(matrix_a, matrix_b) = } \n") + print(f"Multiply Operation, {multiply(matrix_a, matrix_b) = } \n",) print(f"Identity: {identity(5)}\n") print(f"Minor of {matrix_c} = {minor(matrix_c, 1, 2)} \n") print(f"Determinant of {matrix_b} = {determinant(matrix_b)} \n") From ee914c751ce2b9dc2ca573a3375e07c074e033cd Mon Sep 17 00:00:00 2001 From: Christian Clauss Date: Tue, 25 Aug 2020 15:48:04 +0200 Subject: [PATCH 0735/1071] Delete sleep_sort.py (#2352) * Delete sleep_sort.py A silly algorithm designed to waste time. #2350 demonstrates that it is a 20+ second denial of service attack on every Travis CI run that we do. * fixup! Format Python code with psf/black push Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> --- sorts/sleep_sort.py | 49 --------------------------------------------- 1 file changed, 49 deletions(-) delete mode 100644 sorts/sleep_sort.py diff --git a/sorts/sleep_sort.py b/sorts/sleep_sort.py deleted file mode 100644 index 0feda9c5e038..000000000000 --- a/sorts/sleep_sort.py +++ /dev/null @@ -1,49 +0,0 @@ -""" -Sleep sort is probably the wierdest of all sorting functions with time-complexity of -O(max(input)+n) which is quite different from almost all other sorting techniques. -If the number of inputs is small then the complexity can be approximated to be -O(max(input)) which is a constant - -If the number of inputs is large, the complexity is approximately O(n). - -This function uses multithreading a kind of higher order programming and calls n -functions, each with a sleep time equal to its number. Hence each of function wakes -in sorted time. - -This function is not stable for very large values. - -https://rosettacode.org/wiki/Sorting_algorithms/Sleep_sort -""" -from threading import Timer -from time import sleep -from typing import List - - -def sleep_sort(values: List[int]) -> List[int]: - """ - Sort the list using sleepsort. - >>> sleep_sort([3, 2, 4, 7, 3, 6, 9, 1]) - [1, 2, 3, 3, 4, 6, 7, 9] - >>> sleep_sort([3, 2, 1, 9, 8, 4, 2]) - [1, 2, 2, 3, 4, 8, 9] - """ - sleep_sort.result = [] - - def append_to_result(x): - sleep_sort.result.append(x) - - mx = values[0] - for value in values: - if mx < value: - mx = value - Timer(value, append_to_result, [value]).start() - sleep(mx + 1) - return sleep_sort.result - - -if __name__ == "__main__": - import doctest - - doctest.testmod() - - print(sleep_sort([3, 2, 4, 7, 3, 6, 9, 1])) From 2c0127d71a5716e9cfa512959683077b4aa926a4 Mon Sep 17 00:00:00 2001 From: Iheb Haboubi Date: Tue, 25 Aug 2020 20:26:11 +0100 Subject: [PATCH 0736/1071] Perfect square using binary search (#2351) * Add perfect_square_binary_search * Update tests * Add tests --- maths/perfect_square.py | 46 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/maths/perfect_square.py b/maths/perfect_square.py index 3e7a1c07a75f..4393dcfbc774 100644 --- a/maths/perfect_square.py +++ b/maths/perfect_square.py @@ -21,6 +21,52 @@ def perfect_square(num: int) -> bool: return math.sqrt(num) * math.sqrt(num) == num +def perfect_square_binary_search(n: int) -> bool: + """ + Check if a number is perfect square using binary search. + Time complexity : O(Log(n)) + Space complexity: O(1) + + >>> perfect_square_binary_search(9) + True + >>> perfect_square_binary_search(16) + True + >>> perfect_square_binary_search(1) + True + >>> perfect_square_binary_search(0) + True + >>> perfect_square_binary_search(10) + False + >>> perfect_square_binary_search(-1) + False + >>> perfect_square_binary_search(1.1) + False + >>> perfect_square_binary_search("a") + Traceback (most recent call last): + ... + TypeError: '<=' not supported between instances of 'int' and 'str' + >>> perfect_square_binary_search(None) + Traceback (most recent call last): + ... + TypeError: '<=' not supported between instances of 'int' and 'NoneType' + >>> perfect_square_binary_search([]) + Traceback (most recent call last): + ... + TypeError: '<=' not supported between instances of 'int' and 'list' + """ + left = 0 + right = n + while left <= right: + mid = (left + right) // 2 + if mid ** 2 == n: + return True + elif mid ** 2 > n: + right = mid - 1 + else: + left = mid + 1 + return False + + if __name__ == "__main__": import doctest From 9aa10ca358e93dd1355b589ab537f890445270d9 Mon Sep 17 00:00:00 2001 From: Kushagra Bansal Date: Wed, 26 Aug 2020 17:01:13 +0530 Subject: [PATCH 0737/1071] Created problem_55 in project_euler (#2354) * Create __init__.py * Add files via upload * Update sol1.py --- project_euler/problem_55/__init__.py | 1 + project_euler/problem_55/sol1.py | 76 ++++++++++++++++++++++++++++ 2 files changed, 77 insertions(+) create mode 100644 project_euler/problem_55/__init__.py create mode 100644 project_euler/problem_55/sol1.py diff --git a/project_euler/problem_55/__init__.py b/project_euler/problem_55/__init__.py new file mode 100644 index 000000000000..792d6005489e --- /dev/null +++ b/project_euler/problem_55/__init__.py @@ -0,0 +1 @@ +# diff --git a/project_euler/problem_55/sol1.py b/project_euler/problem_55/sol1.py new file mode 100644 index 000000000000..a2e27bbb9e93 --- /dev/null +++ b/project_euler/problem_55/sol1.py @@ -0,0 +1,76 @@ +""" +If we take 47, reverse and add, 47 + 74 = 121, which is palindromic. +Not all numbers produce palindromes so quickly. For example, +349 + 943 = 1292, +1292 + 2921 = 4213 +4213 + 3124 = 7337 +That is, 349 took three iterations to arrive at a palindrome. +Although no one has proved it yet, it is thought that some numbers, like 196, +never produce a palindrome. A number that never forms a palindrome through the +reverse and add process is called a Lychrel number. Due to the theoretical nature +of these numbers, and for the purpose of this problem, we shall assume that a number +is Lychrel until proven otherwise. In addition you are given that for every number +below ten-thousand, it will either (i) become a palindrome in less than fifty +iterations, or, (ii) no one, with all the computing power that exists, has managed +so far to map it to a palindrome. In fact, 10677 is the first number to be shown +to require over fifty iterations before producing a palindrome: +4668731596684224866951378664 (53 iterations, 28-digits). + +Surprisingly, there are palindromic numbers that are themselves Lychrel numbers; +the first example is 4994. +How many Lychrel numbers are there below ten-thousand? +""" + + +def is_palindrome(n: int) -> bool: + """ + Returns True if a number is palindrome. + >>> is_palindrome(12567321) + False + >>> is_palindrome(1221) + True + >>> is_palindrome(9876789) + True + """ + return str(n) == str(n)[::-1] + + +def sum_reverse(n: int) -> int: + """ + Returns the sum of n and reverse of n. + >>> sum_reverse(123) + 444 + >>> sum_reverse(3478) + 12221 + >>> sum_reverse(12) + 33 + """ + return int(n) + int(str(n)[::-1]) + + +def compute_lychrel_nums(limit: int) -> int: + """ + Returns the count of all lychrel numbers below limit. + >>> compute_lychrel_nums(10000) + 249 + >>> compute_lychrel_nums(5000) + 76 + >>> compute_lychrel_nums(1000) + 13 + """ + lychrel_nums = [] + for num in range(1, limit): + iterations = 0 + a = num + while iterations < 50: + num = sum_reverse(num) + iterations += 1 + if is_palindrome(num): + break + else: + lychrel_nums.append(a) + return len(lychrel_nums) + + +if __name__ == "__main__": + print(f"{compute_lychrel_nums(10000) = }") From 30126c26dd72300534a141e537881e7d4eccc1ca Mon Sep 17 00:00:00 2001 From: TrapinchO <67415128+TrapinchO@users.noreply.github.com> Date: Wed, 26 Aug 2020 21:52:17 +0200 Subject: [PATCH 0738/1071] Added enigma machine emulator (#2345) * Added Enigma machine file Added Enigma machine file to 'ciphers' section * Added doctest to validator * Fixed typo * Shortened some lines * Shortened some lines * Update enigma_machine.py * Shortened some lines * Update enigma_machine.py * Update enigma_machine.py * Update enigma_machine2.py * Update enigma_machine2.py * added f-strings * Update enigma_machine2.py * Update enigma_machine2.py * Updated some numbers * Plugboard improvement Added option to separate pair for plugboard by spaces * renamed variable * renamed some variables * improved plugboard exception * Update enigma_machine2.py * Update enigma_machine2.py --- ciphers/enigma_machine2.py | 256 +++++++++++++++++++++++++++++++++++++ 1 file changed, 256 insertions(+) create mode 100644 ciphers/enigma_machine2.py diff --git a/ciphers/enigma_machine2.py b/ciphers/enigma_machine2.py new file mode 100644 index 000000000000..4c79e1c2fab9 --- /dev/null +++ b/ciphers/enigma_machine2.py @@ -0,0 +1,256 @@ +""" +Wikipedia: https://en.wikipedia.org/wiki/Enigma_machine +Video explanation: https://youtu.be/QwQVMqfoB2E +Also check out Numberphile's and Computerphile's videos on this topic + +This module contains function 'enigma' which emulates +the famous Enigma machine from WWII. +Module includes: +- enigma function +- showcase of function usage +- 9 randnomly generated rotors +- reflector (aka static rotor) +- original alphabet + +Created by TrapinchO +""" + +# used alphabet -------------------------- +# from string.ascii_uppercase +abc = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' + +# -------------------------- default selection -------------------------- +# rotors -------------------------- +rotor1 = 'EGZWVONAHDCLFQMSIPJBYUKXTR' +rotor2 = 'FOBHMDKEXQNRAULPGSJVTYICZW' +rotor3 = 'ZJXESIUQLHAVRMDOYGTNFWPBKC' +# reflector -------------------------- +reflector = {'A': 'N', 'N': 'A', 'B': 'O', 'O': 'B', 'C': 'P', 'P': 'C', 'D': 'Q', + 'Q': 'D', 'E': 'R', 'R': 'E', 'F': 'S', 'S': 'F', 'G': 'T', 'T': 'G', + 'H': 'U', 'U': 'H', 'I': 'V', 'V': 'I', 'J': 'W', 'W': 'J', 'K': 'X', + 'X': 'K', 'L': 'Y', 'Y': 'L', 'M': 'Z', 'Z': 'M'} + +# -------------------------- extra rotors -------------------------- +rotor4 = 'RMDJXFUWGISLHVTCQNKYPBEZOA' +rotor5 = 'SGLCPQWZHKXAREONTFBVIYJUDM' +rotor6 = 'HVSICLTYKQUBXDWAJZOMFGPREN' +rotor7 = 'RZWQHFMVDBKICJLNTUXAGYPSOE' +rotor8 = 'LFKIJODBEGAMQPXVUHYSTCZRWN' +rotor9 = 'KOAEGVDHXPQZMLFTYWJNBRCIUS' + + +def _validator(rotpos: tuple, rotsel: tuple, pb: str) -> tuple: + """ + Checks if the values can be used for the 'enigma' function + + >>> _validator((1,1,1), (rotor1, rotor2, rotor3), 'POLAND') + ((1, 1, 1), ('EGZWVONAHDCLFQMSIPJBYUKXTR', 'FOBHMDKEXQNRAULPGSJVTYICZW', \ +'ZJXESIUQLHAVRMDOYGTNFWPBKC'), \ +{'P': 'O', 'O': 'P', 'L': 'A', 'A': 'L', 'N': 'D', 'D': 'N'}) + + :param rotpos: rotor_positon + :param rotsel: rotor_selection + :param pb: plugb -> validated and transformed + :return: (rotpos, rotsel, pb) + """ + # Checks if there are 3 unique rotors + + unique_rotsel = len(set(rotsel)) + if unique_rotsel < 3: + raise Exception(f'Please use 3 unique rotors (not {unique_rotsel})') + + # Checks if rotor positions are valid + rotorpos1, rotorpos2, rotorpos3 = rotpos + if not 0 < rotorpos1 <= len(abc): + raise ValueError(f'First rotor position is not within range of 1..26 (' + f'{rotorpos1}') + if not 0 < rotorpos2 <= len(abc): + raise ValueError(f'Second rotor position is not within range of 1..26 (' + f'{rotorpos2})') + if not 0 < rotorpos3 <= len(abc): + raise ValueError(f'Third rotor position is not within range of 1..26 (' + f'{rotorpos3})') + + # Validates string and returns dict + pb = _plugboard(pb) + + return rotpos, rotsel, pb + + +def _plugboard(pbstring: str) -> dict: + """ + https://en.wikipedia.org/wiki/Enigma_machine#Plugboard + + >>> _plugboard('PICTURES') + {'P': 'I', 'I': 'P', 'C': 'T', 'T': 'C', 'U': 'R', 'R': 'U', 'E': 'S', 'S': 'E'} + >>> _plugboard('POLAND') + {'P': 'O', 'O': 'P', 'L': 'A', 'A': 'L', 'N': 'D', 'D': 'N'} + + In the code, 'pb' stands for 'plugboard' + + Pairs can be separated by spaces + :param pbstring: string containing plugboard setting for the Enigma machine + :return: dictionary containing converted pairs + """ + + # tests the input string if it + # a) is type string + # b) has even length (so pairs can be made) + if not isinstance(pbstring, str): + raise TypeError(f'Plugboard setting isn\'t type string ({type(pbstring)})') + elif len(pbstring) % 2 != 0: + raise Exception(f'Odd number of symbols ({len(pbstring)})') + elif pbstring == '': + return {} + + pbstring.replace(' ', '') + + # Checks if all characters are unique + tmppbl = set() + for i in pbstring: + if i not in abc: + raise Exception(f'\'{i}\' not in list of symbols') + elif i in tmppbl: + raise Exception(f'Duplicate symbol ({i})') + else: + tmppbl.add(i) + del tmppbl + + # Created the dictionary + pb = {} + for i in range(0, len(pbstring) - 1, 2): + pb[pbstring[i]] = pbstring[i + 1] + pb[pbstring[i + 1]] = pbstring[i] + + return pb + + +def enigma(text: str, rotor_position: tuple, + rotor_selection: tuple = (rotor1, rotor2, rotor3), plugb: str = '') -> str: + """ + The only difference with real-world enigma is that I allowed string input. + All characters are converted to uppercase. (non-letter symbol are ignored) + How it works: + (for every letter in the message) + + - Input letter goes into the plugboard. + If it is connected to another one, switch it. + + - Letter goes through 3 rotors. + Each rotor can be represented as 2 sets of symbol, where one is shuffled. + Each symbol from the first set has corresponding symbol in + the second set and vice versa. + + example: + | ABCDEFGHIJKLMNOPQRSTUVWXYZ | e.g. F=D and D=F + | VKLEPDBGRNWTFCJOHQAMUZYIXS | + + - Symbol then goes through reflector (static rotor). + There it is switched with paired symbol + The reflector can be represented as2 sets, each with half of the alphanet. + There are usually 10 pairs of letters. + + Example: + | ABCDEFGHIJKLM | e.g. E is paired to X + | ZYXWVUTSRQPON | so when E goes in X goes out and vice versa + + - Letter then goes through the rotors again + + - If the letter is connected to plugboard, it is switched. + + - Return the letter + + >>> enigma('Hello World!', (1, 2, 1), plugb='pictures') + 'KORYH JUHHI!' + >>> enigma('KORYH, juhhi!', (1, 2, 1), plugb='pictures') + 'HELLO, WORLD!' + >>> enigma('hello world!', (1, 1, 1), plugb='pictures') + 'FPNCZ QWOBU!' + >>> enigma('FPNCZ QWOBU', (1, 1, 1), plugb='pictures') + 'HELLO WORLD' + + + :param text: input message + :param rotor_position: tuple with 3 values in range 1..26 + :param rotor_selection: tuple with 3 rotors () + :param plugb: string containing plugboard configuration (default '') + :return: en/decrypted string + """ + + text = text.upper() + rotor_position, rotor_selection, plugboard = _validator( + rotor_position, rotor_selection, plugb.upper()) + + rotorpos1, rotorpos2, rotorpos3 = rotor_position + rotor1, rotor2, rotor3 = rotor_selection + rotorpos1 -= 1 + rotorpos2 -= 1 + rotorpos3 -= 1 + plugboard = plugboard + + result = [] + + # encryption/decryption process -------------------------- + for symbol in text: + if symbol in abc: + + # 1st plugboard -------------------------- + if symbol in plugboard: + symbol = plugboard[symbol] + + # rotor ra -------------------------- + index = abc.index(symbol) + rotorpos1 + symbol = rotor1[index % len(abc)] + + # rotor rb -------------------------- + index = abc.index(symbol) + rotorpos2 + symbol = rotor2[index % len(abc)] + + # rotor rc -------------------------- + index = abc.index(symbol) + rotorpos3 + symbol = rotor3[index % len(abc)] + + # reflector -------------------------- + # this is the reason you don't need another machine to decipher + + symbol = reflector[symbol] + + # 2nd rotors + symbol = abc[rotor3.index(symbol) - rotorpos3] + symbol = abc[rotor2.index(symbol) - rotorpos2] + symbol = abc[rotor1.index(symbol) - rotorpos1] + + # 2nd plugboard + if symbol in plugboard: + symbol = plugboard[symbol] + + # moves/resets rotor positions + rotorpos1 += 1 + if rotorpos1 >= len(abc): + rotorpos1 = 0 + rotorpos2 += 1 + if rotorpos2 >= len(abc): + rotorpos2 = 0 + rotorpos3 += 1 + if rotorpos3 >= len(abc): + rotorpos3 = 0 + + # else: + # pass + # Error could be also raised + # raise ValueError( + # 'Invalid symbol('+repr(symbol)+')') + result.append(symbol) + + return "".join(result) + + +if __name__ == '__main__': + message = 'This is my Python script that emulates the Enigma machine from WWII.' + rotor_pos = (1, 1, 1) + pb = 'pictures' + rotor_sel = (rotor2, rotor4, rotor8) + en = enigma(message, rotor_pos, rotor_sel, pb) + + print('Encrypted message:', en) + print('Decrypted message:', enigma(en, rotor_pos, rotor_sel, pb)) From 61dde44434739d5f54ebc7e7021fee24ac592fc5 Mon Sep 17 00:00:00 2001 From: Firejay3 <68265194+Firejay3@users.noreply.github.com> Date: Thu, 27 Aug 2020 15:09:42 +0800 Subject: [PATCH 0739/1071] Added binery_or_operator.py to bit manipulation file (#2331) * added bitwise binary OR operator * Rename binary_OR_operator.py to binary_or_operator.py * Update binary_or_operator.py * Update binary_or_operator.py * Update bit_manipulation/binary_or_operator.py Co-authored-by: Christian Clauss * Update binary_or_operator.py * Update binary_or_operator.py * Nice!! Co-authored-by: Christian Clauss --- bit_manipulation/binary_or_operator.py | 48 ++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) create mode 100644 bit_manipulation/binary_or_operator.py diff --git a/bit_manipulation/binary_or_operator.py b/bit_manipulation/binary_or_operator.py new file mode 100644 index 000000000000..e83a86d6a8bc --- /dev/null +++ b/bit_manipulation/binary_or_operator.py @@ -0,0 +1,48 @@ +# https://www.tutorialspoint.com/python3/bitwise_operators_example.htm + + +def binary_or(a: int, b: int): + """ + Take in 2 integers, convert them to binary, and return a binary number that is the + result of a binary or operation on the integers provided. + + >>> binary_or(25, 32) + '0b111001' + >>> binary_or(37, 50) + '0b110111' + >>> binary_or(21, 30) + '0b11111' + >>> binary_or(58, 73) + '0b1111011' + >>> binary_or(0, 255) + '0b11111111' + >>> binary_or(0, 256) + '0b100000000' + >>> binary_or(0, -1) + Traceback (most recent call last): + ... + ValueError: the value of both input must be positive + >>> binary_or(0, 1.1) + Traceback (most recent call last): + ... + TypeError: 'float' object cannot be interpreted as an integer + >>> binary_or("0", "1") + Traceback (most recent call last): + ... + TypeError: '<' not supported between instances of 'str' and 'int' + """ + if a < 0 or b < 0: + raise ValueError("the value of both input must be positive") + a_binary = str(bin(a))[2:] # remove the leading "0b" + b_binary = str(bin(b))[2:] + max_len = max(len(a_binary), len(b_binary)) + return "0b" + "".join( + str(int("1" in (char_a, char_b))) + for char_a, char_b in zip(a_binary.zfill(max_len), b_binary.zfill(max_len)) + ) + + +if __name__ == "__main__": + import doctest + + doctest.testmod() From cf385ad7effb5d546ce44bf8e884a201fdb555ab Mon Sep 17 00:00:00 2001 From: wuyudi Date: Thu, 27 Aug 2020 15:45:03 +0800 Subject: [PATCH 0740/1071] Update merge_sort.py (#2356) * Update merge_sort.py * Update merge_sort.py --- sorts/merge_sort.py | 22 ++++++++++------------ 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/sorts/merge_sort.py b/sorts/merge_sort.py index e8031a1cb97c..572f38a57029 100644 --- a/sorts/merge_sort.py +++ b/sorts/merge_sort.py @@ -1,44 +1,40 @@ """ This is a pure Python implementation of the merge sort algorithm - For doctests run following command: python -m doctest -v merge_sort.py or python3 -m doctest -v merge_sort.py - For manual testing run: python merge_sort.py """ -def merge_sort(collection): +def merge_sort(collection: list) -> list: """Pure implementation of the merge sort algorithm in Python - :param collection: some mutable ordered collection with heterogeneous comparable items inside :return: the same collection ordered by ascending - Examples: >>> merge_sort([0, 5, 3, 2, 2]) [0, 2, 2, 3, 5] - >>> merge_sort([]) [] - >>> merge_sort([-2, -5, -45]) [-45, -5, -2] """ - def merge(left, right): + def merge(left: list, right: list) -> list: """merge left and right :param left: left collection :param right: right collection :return: merge result """ - result = [] - while left and right: - result.append((left if left[0] <= right[0] else right).pop(0)) - return result + left + right + def _merge(): + while left and right: + yield (left if left[0] <= right[0] else right).pop(0) + yield from left + yield from right + return list(_merge()) if len(collection) <= 1: return collection @@ -47,6 +43,8 @@ def merge(left, right): if __name__ == "__main__": + import doctest + doctest.testmod() user_input = input("Enter numbers separated by a comma:\n").strip() unsorted = [int(item) for item in user_input.split(",")] print(*merge_sort(unsorted), sep=",") From 194b56d3760023f5e66607f0b9a340d7c558aca8 Mon Sep 17 00:00:00 2001 From: Kushagra Bansal Date: Thu, 27 Aug 2020 17:10:03 +0530 Subject: [PATCH 0741/1071] Created problem_63 in project_euler (#2357) * Create __init__.py * Add files via upload * Update project_euler/problem_63/sol1.py Co-authored-by: Christian Clauss * Update sol1.py * Update sol1.py * Update sol1.py Co-authored-by: Christian Clauss --- project_euler/problem_63/__init__.py | 1 + project_euler/problem_63/sol1.py | 34 ++++++++++++++++++++++++++++ 2 files changed, 35 insertions(+) create mode 100644 project_euler/problem_63/__init__.py create mode 100644 project_euler/problem_63/sol1.py diff --git a/project_euler/problem_63/__init__.py b/project_euler/problem_63/__init__.py new file mode 100644 index 000000000000..792d6005489e --- /dev/null +++ b/project_euler/problem_63/__init__.py @@ -0,0 +1 @@ +# diff --git a/project_euler/problem_63/sol1.py b/project_euler/problem_63/sol1.py new file mode 100644 index 000000000000..e429db07bf8a --- /dev/null +++ b/project_euler/problem_63/sol1.py @@ -0,0 +1,34 @@ +""" +The 5-digit number, 16807=75, is also a fifth power. Similarly, the 9-digit number, +134217728=89, is a ninth power. +How many n-digit positive integers exist which are also an nth power? +""" + +""" +The maximum base can be 9 because all n-digit numbers < 10^n. +Now 9**23 has 22 digits so the maximum power can be 22. +Using these conclusions, we will calculate the result. +""" + + +def compute_nums(max_base: int = 10, max_power: int = 22) -> int: + """ + Returns the count of all n-digit numbers which are nth power + >>> compute_nums(10, 22) + 49 + >>> compute_nums(0, 0) + 0 + >>> compute_nums(1, 1) + 0 + >>> compute_nums(-1, -1) + 0 + """ + bases = range(1, max_base) + powers = range(1, max_power) + return sum( + 1 for power in powers for base in bases if len(str((base ** power))) == power + ) + + +if __name__ == "__main__": + print(f"{compute_nums(10, 22) = }") From 5ef784331e284a66a7c1845e423308b4a1f3e7d0 Mon Sep 17 00:00:00 2001 From: Kushagra Bansal Date: Fri, 28 Aug 2020 19:20:35 +0530 Subject: [PATCH 0742/1071] Created triplet_sum in Python/other (#2362) * Add files via upload * Update triplet_sum.py * Update triplet_sum.py * Update other/triplet_sum.py Co-authored-by: Christian Clauss * Update other/triplet_sum.py Co-authored-by: Christian Clauss * Update other/triplet_sum.py Co-authored-by: Christian Clauss * Update other/triplet_sum.py Co-authored-by: Christian Clauss * Update triplet_sum.py * Update other/triplet_sum.py Co-authored-by: Christian Clauss * Update other/triplet_sum.py Co-authored-by: Christian Clauss * Update other/triplet_sum.py Co-authored-by: Christian Clauss * Update other/triplet_sum.py Co-authored-by: Christian Clauss * Update other/triplet_sum.py Co-authored-by: Christian Clauss Co-authored-by: Christian Clauss --- other/triplet_sum.py | 89 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 89 insertions(+) create mode 100644 other/triplet_sum.py diff --git a/other/triplet_sum.py b/other/triplet_sum.py new file mode 100644 index 000000000000..a7d6e6331dbc --- /dev/null +++ b/other/triplet_sum.py @@ -0,0 +1,89 @@ +""" +Given an array of integers and another integer target, +we are required to find a triplet from the array such that it's sum is equal to +the target. +""" +from itertools import permutations +from random import randint +from timeit import repeat +from typing import List, Tuple + + +def make_dataset() -> Tuple[List[int], int]: + arr = [randint(-1000, 1000) for i in range(10)] + r = randint(-5000, 5000) + return (arr, r) + + +dataset = make_dataset() + + +def triplet_sum1(arr: List[int], target: int) -> Tuple[int, int, int]: + """ + Returns a triplet in in array with sum equal to target, + else (0, 0, 0). + >>> triplet_sum1([13, 29, 7, 23, 5], 35) + (5, 7, 23) + >>> triplet_sum1([37, 9, 19, 50, 44], 65) + (9, 19, 37) + >>> arr = [6, 47, 27, 1, 15] + >>> target = 11 + >>> triplet_sum1(arr, target) + (0, 0, 0) + """ + for triplet in permutations(arr, 3): + if sum(triplet) == target: + return tuple(sorted(triplet)) + return (0, 0, 0) + + +def triplet_sum2(arr: List[int], target: int) -> Tuple[int, int, int]: + """ + Returns a triplet in in array with sum equal to target, + else (0, 0, 0). + >>> triplet_sum2([13, 29, 7, 23, 5], 35) + (5, 7, 23) + >>> triplet_sum2([37, 9, 19, 50, 44], 65) + (9, 19, 37) + >>> arr = [6, 47, 27, 1, 15] + >>> target = 11 + >>> triplet_sum2(arr, target) + (0, 0, 0) + """ + arr.sort() + n = len(arr) + for i in range(n - 1): + left, right = i + 1, n - 1 + while left < right: + if arr[i] + arr[left] + arr[right] == target: + return (arr[i], arr[left], arr[right]) + elif arr[i] + arr[left] + arr[right] < target: + left += 1 + elif arr[i] + arr[left] + arr[right] > target: + right -= 1 + else: + return (0, 0, 0) + + +def solution_times() -> Tuple[float, float]: + setup_code = """ +from __main__ import dataset, triplet_sum1, triplet_sum2 +""" + test_code1 = """ +triplet_sum1(*dataset) +""" + test_code2 = """ +triplet_sum2(*dataset) +""" + times1 = repeat(setup=setup_code, stmt=test_code1, repeat=5, number=10000) + times2 = repeat(setup=setup_code, stmt=test_code2, repeat=5, number=10000) + return (min(times1), min(times2)) + + +if __name__ == "__main__": + from doctest import testmod + + testmod() + times = solution_times() + print(f"The time for naive implementation is {times[0]}.") + print(f"The time for optimized implementation is {times[1]}.") From 1f5134b36846cf0e5e936888a4fe51a2012e0d78 Mon Sep 17 00:00:00 2001 From: Aanuoluwapo Babajide <46856621+anubabajide@users.noreply.github.com> Date: Fri, 28 Aug 2020 17:25:02 +0100 Subject: [PATCH 0743/1071] Create alternate_disjoint_set.py (#2302) * Create alternate_disjoint_set.py This code implements a disjoint set using Lists with added heuristics for efficiency Union by Rank Heuristic and Path Compression * Update alternate_disjoint_set.py Added typehints, doctests and some suggested variable name change * Update alternate_disjoint_set.py * Formatted with Black * More formatting * Formatting on line 28 * Error in Doctest * Doctest Update in alternate disjoint set * Fixed build error * Fixed doctest --- .../disjoint_set/alternate_disjoint_set.py | 68 +++++++++++++++++++ 1 file changed, 68 insertions(+) create mode 100644 data_structures/disjoint_set/alternate_disjoint_set.py diff --git a/data_structures/disjoint_set/alternate_disjoint_set.py b/data_structures/disjoint_set/alternate_disjoint_set.py new file mode 100644 index 000000000000..5103335bc80a --- /dev/null +++ b/data_structures/disjoint_set/alternate_disjoint_set.py @@ -0,0 +1,68 @@ +""" +Implements a disjoint set using Lists and some added heuristics for efficiency +Union by Rank Heuristic and Path Compression +""" + + +class DisjointSet: + def __init__(self, set_counts: list) -> None: + """ + Initialize with a list of the number of items in each set + and with rank = 1 for each set + """ + self.set_counts = set_counts + self.max_set = max(set_counts) + num_sets = len(set_counts) + self.ranks = [1] * num_sets + self.parents = list(range(num_sets)) + + def merge(self, src: int, dst: int) -> bool: + """ + Merge two sets together using Union by rank heuristic + Return True if successful + Merge two disjoint sets + >>> A = DisjointSet([1, 1, 1]) + >>> A.merge(1, 2) + True + >>> A.merge(0, 2) + True + >>> A.merge(0, 1) + False + """ + src_parent = self.get_parent(src) + dst_parent = self.get_parent(dst) + + if src_parent == dst_parent: + return False + + if self.ranks[dst_parent] >= self.ranks[src_parent]: + self.set_counts[dst_parent] += self.set_counts[src_parent] + self.set_counts[src_parent] = 0 + self.parents[src_parent] = dst_parent + if self.ranks[dst_parent] == self.ranks[src_parent]: + self.ranks[dst_parent] += 1 + joined_set_size = self.set_counts[dst_parent] + else: + self.set_counts[src_parent] += self.set_counts[dst_parent] + self.set_counts[dst_parent] = 0 + self.parents[dst_parent] = src_parent + joined_set_size = self.set_counts[src_parent] + + self.max_set = max(self.max_set, joined_set_size) + return True + + def get_parent(self, disj_set: int) -> int: + """ + Find the Parent of a given set + >>> A = DisjointSet([1, 1, 1]) + >>> A.merge(1, 2) + True + >>> A.get_parent(0) + 0 + >>> A.get_parent(1) + 2 + """ + if self.parents[disj_set] == disj_set: + return disj_set + self.parents[disj_set] = self.get_parent(self.parents[disj_set]) + return self.parents[disj_set] From f2f0425357a0fda1d096493af0b184b98b4db8ae Mon Sep 17 00:00:00 2001 From: Kushagra Bansal Date: Sat, 29 Aug 2020 20:27:34 +0530 Subject: [PATCH 0744/1071] Created ugly_numbers.py in Python/maths (#2366) * Add files via upload * Update ugly_numbers.py * Update ugly_numbers.py * Update ugly_numbers.py --- maths/ugly_numbers.py | 54 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) create mode 100644 maths/ugly_numbers.py diff --git a/maths/ugly_numbers.py b/maths/ugly_numbers.py new file mode 100644 index 000000000000..4451a68cdaad --- /dev/null +++ b/maths/ugly_numbers.py @@ -0,0 +1,54 @@ +""" +Ugly numbers are numbers whose only prime factors are 2, 3 or 5. The sequence +1, 2, 3, 4, 5, 6, 8, 9, 10, 12, 15, … shows the first 11 ugly numbers. By convention, +1 is included. +Given an integer n, we have to find the nth ugly number. + +For more details, refer this article +https://www.geeksforgeeks.org/ugly-numbers/ +""" + + +def ugly_numbers(n: int) -> int: + """ + Returns the nth ugly number. + >>> ugly_numbers(100) + 1536 + >>> ugly_numbers(0) + 1 + >>> ugly_numbers(20) + 36 + >>> ugly_numbers(-5) + 1 + >>> ugly_numbers(-5.5) + Traceback (most recent call last): + ... + TypeError: 'float' object cannot be interpreted as an integer + """ + ugly_nums = [1] + + i2, i3, i5 = 0, 0, 0 + next_2 = ugly_nums[i2] * 2 + next_3 = ugly_nums[i3] * 3 + next_5 = ugly_nums[i5] * 5 + + for i in range(1, n): + next_num = min(next_2, next_3, next_5) + ugly_nums.append(next_num) + if next_num == next_2: + i2 += 1 + next_2 = ugly_nums[i2] * 2 + if next_num == next_3: + i3 += 1 + next_3 = ugly_nums[i3] * 3 + if next_num == next_5: + i5 += 1 + next_5 = ugly_nums[i5] * 5 + return ugly_nums[-1] + + +if __name__ == "__main__": + from doctest import testmod + + testmod(verbose=True) + print(f"{ugly_numbers(200) = }") From ab5a046581cb8f765dbe2e57e4429b1a22d649be Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Olsson=20Jarl?= Date: Sat, 29 Aug 2020 17:11:02 +0200 Subject: [PATCH 0745/1071] Added type hints and doctest for maths/prime_check. (#2367) * Added type hints and doctest for maths/prime_check. * Removed doctests. --- maths/prime_check.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/maths/prime_check.py b/maths/prime_check.py index e60281228fda..ed8fbbae809a 100644 --- a/maths/prime_check.py +++ b/maths/prime_check.py @@ -4,7 +4,7 @@ import unittest -def prime_check(number): +def prime_check(number: int) -> bool: """ Check to See if a Number is Prime. @@ -42,7 +42,7 @@ def test_primes(self): def test_not_primes(self): self.assertFalse(prime_check(-19), "Negative numbers are not prime.") self.assertFalse( - prime_check(0), "Zero doesn't have any divider, primes must have two" + prime_check(0), "Zero doesn't have any divider, primes must have two." ) self.assertFalse( prime_check(1), "One just have 1 divider, primes must have two." From 8c191f1fc98c44ca30343919d00126dd3eccb783 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Olsson=20Jarl?= Date: Sun, 30 Aug 2020 10:51:45 +0200 Subject: [PATCH 0746/1071] Added type hints for maths/fibonacci_sequence_recursion. (#2372) --- maths/fibonacci_sequence_recursion.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/maths/fibonacci_sequence_recursion.py b/maths/fibonacci_sequence_recursion.py index 91619600d5b4..794b9fc0bd3a 100644 --- a/maths/fibonacci_sequence_recursion.py +++ b/maths/fibonacci_sequence_recursion.py @@ -1,7 +1,7 @@ # Fibonacci Sequence Using Recursion -def recur_fibo(n): +def recur_fibo(n: int) -> int: """ >>> [recur_fibo(i) for i in range(12)] [0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89] @@ -9,7 +9,7 @@ def recur_fibo(n): return n if n <= 1 else recur_fibo(n - 1) + recur_fibo(n - 2) -def main(): +def main() -> None: limit = int(input("How many terms to include in fibonacci series: ")) if limit > 0: print(f"The first {limit} terms of the fibonacci series are as follows:") From 472f63eaa52dee6e5ef0bdeb972664db5e1a8687 Mon Sep 17 00:00:00 2001 From: kanthuc Date: Sun, 30 Aug 2020 12:22:36 -0700 Subject: [PATCH 0747/1071] Adding type hints to RedBlackTree (#2371) * redblacktree type hints * fixed type hints to pass flake8 --- data_structures/binary_tree/red_black_tree.py | 86 ++++++++++--------- 1 file changed, 47 insertions(+), 39 deletions(-) diff --git a/data_structures/binary_tree/red_black_tree.py b/data_structures/binary_tree/red_black_tree.py index f038b587616d..379cee61d888 100644 --- a/data_structures/binary_tree/red_black_tree.py +++ b/data_structures/binary_tree/red_black_tree.py @@ -2,6 +2,7 @@ python/black : true flake8 : passed """ +from typing import Iterator, Optional class RedBlackTree: @@ -18,7 +19,14 @@ class RedBlackTree: terms of the size of the tree. """ - def __init__(self, label=None, color=0, parent=None, left=None, right=None): + def __init__( + self, + label: Optional[int] = None, + color: int = 0, + parent: Optional["RedBlackTree"] = None, + left: Optional["RedBlackTree"] = None, + right: Optional["RedBlackTree"] = None, + ) -> None: """Initialize a new Red-Black Tree node with the given values: label: The value associated with this node color: 0 if black, 1 if red @@ -34,7 +42,7 @@ def __init__(self, label=None, color=0, parent=None, left=None, right=None): # Here are functions which are specific to red-black trees - def rotate_left(self): + def rotate_left(self) -> "RedBlackTree": """Rotate the subtree rooted at this node to the left and returns the new root to this subtree. Performing one rotation can be done in O(1). @@ -54,7 +62,7 @@ def rotate_left(self): right.parent = parent return right - def rotate_right(self): + def rotate_right(self) -> "RedBlackTree": """Rotate the subtree rooted at this node to the right and returns the new root to this subtree. Performing one rotation can be done in O(1). @@ -74,7 +82,7 @@ def rotate_right(self): left.parent = parent return left - def insert(self, label): + def insert(self, label: int) -> "RedBlackTree": """Inserts label into the subtree rooted at self, performs any rotations necessary to maintain balance, and then returns the new root to this subtree (likely self). @@ -100,7 +108,7 @@ def insert(self, label): self.right._insert_repair() return self.parent or self - def _insert_repair(self): + def _insert_repair(self) -> None: """Repair the coloring from inserting into a tree.""" if self.parent is None: # This node is the root, so it just needs to be black @@ -131,7 +139,7 @@ def _insert_repair(self): self.grandparent.color = 1 self.grandparent._insert_repair() - def remove(self, label): + def remove(self, label: int) -> "RedBlackTree": """Remove label from this tree.""" if self.label == label: if self.left and self.right: @@ -186,7 +194,7 @@ def remove(self, label): self.right.remove(label) return self.parent or self - def _remove_repair(self): + def _remove_repair(self) -> None: """Repair the coloring of the tree that may have been messed up.""" if color(self.sibling) == 1: self.sibling.color = 0 @@ -250,7 +258,7 @@ def _remove_repair(self): self.parent.color = 0 self.parent.sibling.color = 0 - def check_color_properties(self): + def check_color_properties(self) -> bool: """Check the coloring of the tree, and return True iff the tree is colored in a way which matches these five properties: (wording stolen from wikipedia article) @@ -287,7 +295,7 @@ def check_color_properties(self): # All properties were met return True - def check_coloring(self): + def check_coloring(self) -> None: """A helper function to recursively check Property 4 of a Red-Black Tree. See check_color_properties for more info. """ @@ -300,7 +308,7 @@ def check_coloring(self): return False return True - def black_height(self): + def black_height(self) -> int: """Returns the number of black nodes from this node to the leaves of the tree, or None if there isn't one such value (the tree is color incorrectly). @@ -322,14 +330,14 @@ def black_height(self): # Here are functions which are general to all binary search trees - def __contains__(self, label): + def __contains__(self, label) -> bool: """Search through the tree for label, returning True iff it is found somewhere in the tree. Guaranteed to run in O(log(n)) time. """ return self.search(label) is not None - def search(self, label): + def search(self, label: int) -> "RedBlackTree": """Search through the tree for label, returning its node if it's found, and None otherwise. This method is guaranteed to run in O(log(n)) time. @@ -347,7 +355,7 @@ def search(self, label): else: return self.left.search(label) - def floor(self, label): + def floor(self, label: int) -> int: """Returns the largest element in this tree which is at most label. This method is guaranteed to run in O(log(n)) time.""" if self.label == label: @@ -364,7 +372,7 @@ def floor(self, label): return attempt return self.label - def ceil(self, label): + def ceil(self, label: int) -> int: """Returns the smallest element in this tree which is at least label. This method is guaranteed to run in O(log(n)) time. """ @@ -382,7 +390,7 @@ def ceil(self, label): return attempt return self.label - def get_max(self): + def get_max(self) -> int: """Returns the largest element in this tree. This method is guaranteed to run in O(log(n)) time. """ @@ -392,7 +400,7 @@ def get_max(self): else: return self.label - def get_min(self): + def get_min(self) -> int: """Returns the smallest element in this tree. This method is guaranteed to run in O(log(n)) time. """ @@ -403,7 +411,7 @@ def get_min(self): return self.label @property - def grandparent(self): + def grandparent(self) -> "RedBlackTree": """Get the current node's grandparent, or None if it doesn't exist.""" if self.parent is None: return None @@ -411,7 +419,7 @@ def grandparent(self): return self.parent.parent @property - def sibling(self): + def sibling(self) -> "RedBlackTree": """Get the current node's sibling, or None if it doesn't exist.""" if self.parent is None: return None @@ -420,18 +428,18 @@ def sibling(self): else: return self.parent.left - def is_left(self): + def is_left(self) -> bool: """Returns true iff this node is the left child of its parent.""" return self.parent and self.parent.left is self - def is_right(self): + def is_right(self) -> bool: """Returns true iff this node is the right child of its parent.""" return self.parent and self.parent.right is self - def __bool__(self): + def __bool__(self) -> bool: return True - def __len__(self): + def __len__(self) -> int: """ Return the number of nodes in this tree. """ @@ -442,28 +450,28 @@ def __len__(self): ln += len(self.right) return ln - def preorder_traverse(self): + def preorder_traverse(self) -> Iterator[int]: yield self.label if self.left: yield from self.left.preorder_traverse() if self.right: yield from self.right.preorder_traverse() - def inorder_traverse(self): + def inorder_traverse(self) -> Iterator[int]: if self.left: yield from self.left.inorder_traverse() yield self.label if self.right: yield from self.right.inorder_traverse() - def postorder_traverse(self): + def postorder_traverse(self) -> Iterator[int]: if self.left: yield from self.left.postorder_traverse() if self.right: yield from self.right.postorder_traverse() yield self.label - def __repr__(self): + def __repr__(self) -> str: from pprint import pformat if self.left is None and self.right is None: @@ -476,7 +484,7 @@ def __repr__(self): indent=1, ) - def __eq__(self, other): + def __eq__(self, other) -> bool: """Test if two trees are equal.""" if self.label == other.label: return self.left == other.left and self.right == other.right @@ -484,7 +492,7 @@ def __eq__(self, other): return False -def color(node): +def color(node) -> int: """Returns the color of a node, allowing for None leaves.""" if node is None: return 0 @@ -498,7 +506,7 @@ def color(node): """ -def test_rotations(): +def test_rotations() -> bool: """Test that the rotate_left and rotate_right functions work.""" # Make a tree to test on tree = RedBlackTree(0) @@ -534,7 +542,7 @@ def test_rotations(): return True -def test_insertion_speed(): +def test_insertion_speed() -> bool: """Test that the tree balances inserts to O(log(n)) by doing a lot of them. """ @@ -544,7 +552,7 @@ def test_insertion_speed(): return True -def test_insert(): +def test_insert() -> bool: """Test the insert() method of the tree correctly balances, colors, and inserts. """ @@ -565,7 +573,7 @@ def test_insert(): return tree == ans -def test_insert_and_search(): +def test_insert_and_search() -> bool: """Tests searching through the tree for values.""" tree = RedBlackTree(0) tree.insert(8) @@ -583,7 +591,7 @@ def test_insert_and_search(): return True -def test_insert_delete(): +def test_insert_delete() -> bool: """Test the insert() and delete() method of the tree, verifying the insertion and removal of elements, and the balancing of the tree. """ @@ -607,7 +615,7 @@ def test_insert_delete(): return True -def test_floor_ceil(): +def test_floor_ceil() -> bool: """Tests the floor and ceiling functions in the tree.""" tree = RedBlackTree(0) tree.insert(-16) @@ -623,7 +631,7 @@ def test_floor_ceil(): return True -def test_min_max(): +def test_min_max() -> bool: """Tests the min and max functions in the tree.""" tree = RedBlackTree(0) tree.insert(-16) @@ -637,7 +645,7 @@ def test_min_max(): return True -def test_tree_traversal(): +def test_tree_traversal() -> bool: """Tests the three different tree traversal functions.""" tree = RedBlackTree(0) tree = tree.insert(-16) @@ -655,7 +663,7 @@ def test_tree_traversal(): return True -def test_tree_chaining(): +def test_tree_chaining() -> bool: """Tests the three different tree chaining functions.""" tree = RedBlackTree(0) tree = tree.insert(-16).insert(16).insert(8).insert(24).insert(20).insert(22) @@ -672,7 +680,7 @@ def print_results(msg: str, passes: bool) -> None: print(str(msg), "works!" if passes else "doesn't work :(") -def pytests(): +def pytests() -> None: assert test_rotations() assert test_insert() assert test_insert_and_search() @@ -682,7 +690,7 @@ def pytests(): assert test_tree_chaining() -def main(): +def main() -> None: """ >>> pytests() """ From 80daa5750a8e5ee6ffcbcad72b2b52540d0b502d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=B0=8F=E4=B9=88=E5=B0=8F=E5=84=BF=E9=83=8EEL?= Date: Tue, 1 Sep 2020 00:55:56 +0800 Subject: [PATCH 0748/1071] Fix bugs and add related tests (#2375) --- sorts/radix_sort.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/sorts/radix_sort.py b/sorts/radix_sort.py index c379c679787f..0ddf996cf1ee 100644 --- a/sorts/radix_sort.py +++ b/sorts/radix_sort.py @@ -7,11 +7,13 @@ def radix_sort(list_of_ints: List[int]) -> List[int]: True radix_sort(reversed(range(15))) == sorted(range(15)) True + radix_sort([1,100,10,1000]) == sorted([1,100,10,1000]) + True """ RADIX = 10 placement = 1 max_digit = max(list_of_ints) - while placement < max_digit: + while placement <= max_digit: # declare and initialize empty buckets buckets = [list() for _ in range(RADIX)] # split list_of_ints between the buckets From e92e433dbefacc61510466d6feccf27a5506e11a Mon Sep 17 00:00:00 2001 From: Muskan Kumar <31043527+muskanvk@users.noreply.github.com> Date: Tue, 1 Sep 2020 01:04:44 +0530 Subject: [PATCH 0749/1071] Update CONTRIBUTING.md (#2378) * Update CONTRIBUTING.md fixed dead link to the license * Update README.md Added License * Update README.md * Update README.md * Update README.md * Update CONTRIBUTING.md Co-authored-by: Christian Clauss --- CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 63f60c51f8e6..f8469d97ddb4 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -12,7 +12,7 @@ We are very happy that you consider implementing algorithms and data structure f - You did your work - no plagiarism allowed - Any plagiarized work will not be merged. -- Your work will be distributed under [MIT License](License) once your pull request is merged +- Your work will be distributed under [MIT License](LICENSE.md) once your pull request is merged - You submitted work fulfils or mostly fulfils our styles and standards **New implementation** is welcome! For example, new solutions for a problem, different representations for a graph data structure or algorithm designs with different complexity. From a1d1a44f515b5769136c90342bc1955e3bc8a26e Mon Sep 17 00:00:00 2001 From: Shubham Shaswat Date: Wed, 2 Sep 2020 23:03:12 +0530 Subject: [PATCH 0750/1071] added idf-smooth (#2174) * added idf-smooth * added idf-smooth * added idf-smooth --- machine_learning/word_frequency_functions.py | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/machine_learning/word_frequency_functions.py b/machine_learning/word_frequency_functions.py index e9e9e644b7d8..9cf7b694c6be 100644 --- a/machine_learning/word_frequency_functions.py +++ b/machine_learning/word_frequency_functions.py @@ -83,16 +83,17 @@ def document_frequency(term: str, corpus: str) -> int: return (len([doc for doc in docs if term in doc]), len(docs)) -def inverse_document_frequency(df: int, N: int) -> float: +def inverse_document_frequency(df: int, N: int, smoothing=False) -> float: """ Return an integer denoting the importance of a word. This measure of importance is calculated by log10(N/df), where N is the number of documents and df is the Document Frequency. - @params : df, the Document Frequency, and N, - the number of documents in the corpus. - @returns : log10(N/df) + @params : df, the Document Frequency, N, + the number of documents in the corpus and + smoothing, if True return the idf-smooth + @returns : log10(N/df) or 1+log10(N/1+df) @examples : >>> inverse_document_frequency(3, 0) Traceback (most recent call last): @@ -104,7 +105,14 @@ def inverse_document_frequency(df: int, N: int) -> float: Traceback (most recent call last): ... ZeroDivisionError: df must be > 0 + >>> inverse_document_frequency(0, 3,True) + 1.477 """ + if smoothing: + if N == 0: + raise ValueError("log10(0) is undefined.") + return round(1 + log10(N / (1 + df)), 3) + if df == 0: raise ZeroDivisionError("df must be > 0") elif N == 0: From c38dec091fdea3a2d5ab7b5992fa7c1607b8014d Mon Sep 17 00:00:00 2001 From: mohammadreza490 <47437328+mohammadreza490@users.noreply.github.com> Date: Thu, 3 Sep 2020 15:11:23 +0100 Subject: [PATCH 0751/1071] capitalize (#2389) * Create capitalize.py This function will capitalize the first character of a sentence or a word * Update capitalize.py * Update capitalize.py * Update capitalize.py * Update capitalize.py * Update capitalize.py * Update capitalize.py * Update capitalize.py * Update capitalize.py * Update capitalize.py * Update capitalize.py * Update capitalize.py * Update capitalize.py * Update capitalize.py * Update capitalize.py * Update capitalize.py * Update strings/capitalize.py Co-authored-by: Christian Clauss * Update capitalize.py * Update strings/capitalize.py Co-authored-by: Christian Clauss * Update capitalize.py * Update capitalize.py * Update capitalize.py * Update strings/capitalize.py Co-authored-by: Christian Clauss * Update capitalize.py * Update strings/capitalize.py Co-authored-by: Christian Clauss * Update capitalize.py * Update capitalize.py Co-authored-by: Christian Clauss --- strings/capitalize.py | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 strings/capitalize.py diff --git a/strings/capitalize.py b/strings/capitalize.py new file mode 100644 index 000000000000..2a84a325bca4 --- /dev/null +++ b/strings/capitalize.py @@ -0,0 +1,27 @@ +from string import ascii_lowercase, ascii_uppercase + + +def capitalize(sentence: str) -> str: + """ + This function will capitalize the first letter of a sentence or a word + >>> capitalize("hello world") + 'Hello world' + >>> capitalize("123 hello world") + '123 hello world' + >>> capitalize(" hello world") + ' hello world' + >>> capitalize("a") + 'A' + >>> capitalize("") + '' + """ + if not sentence: + return '' + lower_to_upper = {lc: uc for lc, uc in zip(ascii_lowercase, ascii_uppercase)} + return lower_to_upper.get(sentence[0], sentence[0]) + sentence[1:] + + +if __name__ == "__main__": + from doctest import testmod + + testmod() From 1385e47c36f9f5af75c1127bcafe61725a390895 Mon Sep 17 00:00:00 2001 From: mohammadreza490 <47437328+mohammadreza490@users.noreply.github.com> Date: Fri, 4 Sep 2020 14:48:44 +0100 Subject: [PATCH 0752/1071] Create hexadecimal_to_decimal (#2393) * Create hexadecimal_to_decimal * Update conversions/hexadecimal_to_decimal Co-authored-by: Tapajyoti Bose <44058757+ruppysuppy@users.noreply.github.com> * Update conversions/hexadecimal_to_decimal Co-authored-by: Tapajyoti Bose <44058757+ruppysuppy@users.noreply.github.com> * Update conversions/hexadecimal_to_decimal Co-authored-by: Christian Clauss * Update hexadecimal_to_decimal * Update hexadecimal_to_decimal * Update hexadecimal_to_decimal * Update hexadecimal_to_decimal * Update hexadecimal_to_decimal * Update conversions/hexadecimal_to_decimal Co-authored-by: Christian Clauss * Update hexadecimal_to_decimal Added negative hexadecimal conversion to decimal number * Update hexadecimal_to_decimal * Update conversions/hexadecimal_to_decimal Co-authored-by: Christian Clauss * Update conversions/hexadecimal_to_decimal Co-authored-by: Christian Clauss * Update hexadecimal_to_decimal * Update hexadecimal_to_decimal Co-authored-by: Tapajyoti Bose <44058757+ruppysuppy@users.noreply.github.com> Co-authored-by: Christian Clauss --- conversions/hexadecimal_to_decimal | 45 ++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100644 conversions/hexadecimal_to_decimal diff --git a/conversions/hexadecimal_to_decimal b/conversions/hexadecimal_to_decimal new file mode 100644 index 000000000000..e87caa0f4787 --- /dev/null +++ b/conversions/hexadecimal_to_decimal @@ -0,0 +1,45 @@ +hex_table = {hex(i)[2:]: i for i in range(16)} # Use [:2] to strip off the leading '0x' + + +def hex_to_decimal(hex_string: str) -> int: + """ + Convert a hexadecimal value to its decimal equivalent + #https://www.programiz.com/python-programming/methods/built-in/hex + + >>> hex_to_decimal("a") + 10 + >>> hex_to_decimal("12f") + 303 + >>> hex_to_decimal(" 12f ") + 303 + >>> hex_to_decimal("FfFf") + 65535 + >>> hex_to_decimal("-Ff") + -255 + >>> hex_to_decimal("F-f") + ValueError: Non-hexadecimal value was passed to the function + >>> hex_to_decimal("") + ValueError: Empty string value was passed to the function + >>> hex_to_decimal("12m") + ValueError: Non-hexadecimal value was passed to the function + """ + hex_string = hex_string.strip().lower() + if not hex_string: + raise ValueError("Empty string was passed to the function") + is_negative = hex_string[0] == "-" + if is_negative: + hex_string = hex_string[1:] + if not all(char in hex_table for char in hex_string): + raise ValueError("Non-hexadecimal value was passed to the function") + decimal_number = 0 + for char in hex_string: + decimal_number = 16 * decimal_number + hex_table[char] + if is_negative: + decimal_number = -decimal_number + return decimal_number + + +if __name__ == "__main__": + from doctest import testmod + + testmod() From 3b1c4f72cea93803fb8e155b0ae149395426c26f Mon Sep 17 00:00:00 2001 From: NEERAJ ADITYANANTH POLAMPALLI <65017645+NEERAJAP2001@users.noreply.github.com> Date: Sat, 5 Sep 2020 16:39:18 +0530 Subject: [PATCH 0753/1071] changed a typo (#2396) --- data_structures/linked_list/circular_linked_list.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data_structures/linked_list/circular_linked_list.py b/data_structures/linked_list/circular_linked_list.py index 290e30ebfad6..19d6ee6c6cb3 100644 --- a/data_structures/linked_list/circular_linked_list.py +++ b/data_structures/linked_list/circular_linked_list.py @@ -102,7 +102,7 @@ def append(self, data: Any) -> None: def prepend(self, data: Any) -> None: """ - Adds a ndoe with given data to the front of the CircularLinkedList + Adds a node with given data to the front of the CircularLinkedList >>> cll = CircularLinkedList() >>> cll.prepend(1) >>> cll.prepend(2) From c0dcc556b35093de62a8ed8e3d03f9514f7f3d48 Mon Sep 17 00:00:00 2001 From: NEERAJ ADITYANANTH POLAMPALLI <65017645+NEERAJAP2001@users.noreply.github.com> Date: Sun, 6 Sep 2020 14:10:46 +0530 Subject: [PATCH 0754/1071] Update triplet_sum.py (#2404) --- other/triplet_sum.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/other/triplet_sum.py b/other/triplet_sum.py index a7d6e6331dbc..247e3bb1618d 100644 --- a/other/triplet_sum.py +++ b/other/triplet_sum.py @@ -20,7 +20,7 @@ def make_dataset() -> Tuple[List[int], int]: def triplet_sum1(arr: List[int], target: int) -> Tuple[int, int, int]: """ - Returns a triplet in in array with sum equal to target, + Returns a triplet in the array with sum equal to target, else (0, 0, 0). >>> triplet_sum1([13, 29, 7, 23, 5], 35) (5, 7, 23) @@ -39,7 +39,7 @@ def triplet_sum1(arr: List[int], target: int) -> Tuple[int, int, int]: def triplet_sum2(arr: List[int], target: int) -> Tuple[int, int, int]: """ - Returns a triplet in in array with sum equal to target, + Returns a triplet in the array with sum equal to target, else (0, 0, 0). >>> triplet_sum2([13, 29, 7, 23, 5], 35) (5, 7, 23) From 25946e457080656f2fc23d130e674cccf6a0d0c7 Mon Sep 17 00:00:00 2001 From: Tanuj Dhiman <56601466+tanujdhiman@users.noreply.github.com> Date: Wed, 9 Sep 2020 22:34:46 +0530 Subject: [PATCH 0755/1071] Update scoring_functions.py (#2291) * Update scoring_functions.py We can find accuracy by manually if we are not going to use sklearn library. * Update scoring_functions.py * Update machine_learning/scoring_functions.py Co-authored-by: Christian Clauss --- machine_learning/scoring_functions.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/machine_learning/scoring_functions.py b/machine_learning/scoring_functions.py index a401df139748..08b969a95c3b 100755 --- a/machine_learning/scoring_functions.py +++ b/machine_learning/scoring_functions.py @@ -135,3 +135,7 @@ def mbd(predict, actual): score = float(numerator) / denumerator * 100 return score + + +def manual_accuracy(predict, actual): + return np.mean(np.array(actual) == np.array(predict)) From 4d0a8f235557e46a6b7fac6d4fa5c806788cf360 Mon Sep 17 00:00:00 2001 From: Du Yuanchao Date: Thu, 10 Sep 2020 16:31:26 +0800 Subject: [PATCH 0756/1071] Optimized recursive_bubble_sort (#2410) * optimized recursive_bubble_sort * Fixed doctest error due whitespace * reduce loop times for optimization * fixup! Format Python code with psf/black push Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> --- arithmetic_analysis/newton_method.py | 6 +- arithmetic_analysis/newton_raphson.py | 2 +- backtracking/all_permutations.py | 8 +- backtracking/all_subsequences.py | 8 +- backtracking/sudoku.py | 2 +- backtracking/sum_of_subsets.py | 12 +- blockchain/modular_division.py | 10 +- ciphers/enigma_machine2.py | 102 +++-- ciphers/xor_cipher.py | 70 +-- conversions/decimal_to_any.py | 98 ++-- conversions/decimal_to_binary.py | 44 +- conversions/decimal_to_hexadecimal.py | 66 +-- data_structures/binary_tree/avl_tree.py | 2 +- data_structures/binary_tree/red_black_tree.py | 10 +- data_structures/binary_tree/treap.py | 12 +- data_structures/hashing/double_hash.py | 2 +- data_structures/hashing/hash_table.py | 2 +- .../hashing/number_theory/prime_numbers.py | 4 +- data_structures/hashing/quadratic_probing.py | 2 +- data_structures/linked_list/deque_doubly.py | 12 +- .../stacks/infix_to_postfix_conversion.py | 4 +- data_structures/stacks/stack.py | 2 +- digital_image_processing/index_calculation.py | 430 +++++++++--------- divide_and_conquer/closest_pair_of_points.py | 2 +- divide_and_conquer/max_subarray_sum.py | 6 +- .../strassen_matrix_multiplication.py | 4 +- dynamic_programming/rod_cutting.py | 200 ++++---- graphs/bfs_shortest_path.py | 56 +-- graphs/depth_first_search.py | 24 +- graphs/prim.py | 24 +- hashes/md5.py | 4 +- linear_algebra/src/lib.py | 130 +++--- linear_algebra/src/test_linear_algebra.py | 26 +- machine_learning/decision_tree.py | 3 +- machine_learning/k_means_clust.py | 12 +- .../linear_discriminant_analysis.py | 2 +- machine_learning/linear_regression.py | 8 +- maths/kth_lexicographic_permutation.py | 26 +- maths/newton_raphson.py | 4 +- maths/prime_sieve_eratosthenes.py | 9 +- maths/relu.py | 20 +- maths/softmax.py | 32 +- maths/sum_of_geometric_progression.py | 2 +- maths/zellers_congruence.py | 3 +- matrix/matrix_operation.py | 4 +- other/activity_selection.py | 21 +- other/game_of_life.py | 2 +- other/least_recently_used.py | 10 +- other/magicdiamondpattern.py | 6 +- other/primelib.py | 92 ++-- project_euler/problem_09/sol2.py | 3 +- project_euler/problem_10/sol3.py | 2 +- project_euler/problem_15/sol1.py | 50 +- project_euler/problem_27/problem_27_sol1.py | 22 +- project_euler/problem_56/sol1.py | 22 +- sorts/merge_sort.py | 3 + sorts/recursive_bubble_sort.py | 41 +- strings/boyer_moore_search.py | 2 +- strings/capitalize.py | 2 +- traversals/binary_tree_traversals.py | 38 +- 60 files changed, 934 insertions(+), 893 deletions(-) diff --git a/arithmetic_analysis/newton_method.py b/arithmetic_analysis/newton_method.py index 542f994aaf19..97d5d3d3e470 100644 --- a/arithmetic_analysis/newton_method.py +++ b/arithmetic_analysis/newton_method.py @@ -7,7 +7,11 @@ # function is the f(x) and derivative is the f'(x) -def newton(function: RealFunc, derivative: RealFunc, starting_int: int,) -> float: +def newton( + function: RealFunc, + derivative: RealFunc, + starting_int: int, +) -> float: """ >>> newton(lambda x: x ** 3 - 2 * x - 5, lambda x: 3 * x ** 2 - 2, 3) 2.0945514815423474 diff --git a/arithmetic_analysis/newton_raphson.py b/arithmetic_analysis/newton_raphson.py index 890cff060ec8..948759a09a2a 100644 --- a/arithmetic_analysis/newton_raphson.py +++ b/arithmetic_analysis/newton_raphson.py @@ -9,7 +9,7 @@ def newton_raphson(func: str, a: int, precision: int = 10 ** -10) -> float: - """ Finds root from the point 'a' onwards by Newton-Raphson method + """Finds root from the point 'a' onwards by Newton-Raphson method >>> newton_raphson("sin(x)", 2) 3.1415926536808043 >>> newton_raphson("x**2 - 5*x +2", 0.4) diff --git a/backtracking/all_permutations.py b/backtracking/all_permutations.py index d144436033de..5244fef97f93 100644 --- a/backtracking/all_permutations.py +++ b/backtracking/all_permutations.py @@ -13,10 +13,10 @@ def generate_all_permutations(sequence): def create_state_space_tree(sequence, current_sequence, index, index_used): """ - Creates a state space tree to iterate through each branch using DFS. - We know that each state has exactly len(sequence) - index children. - It terminates when it reaches the end of the given sequence. - """ + Creates a state space tree to iterate through each branch using DFS. + We know that each state has exactly len(sequence) - index children. + It terminates when it reaches the end of the given sequence. + """ if index == len(sequence): print(current_sequence) diff --git a/backtracking/all_subsequences.py b/backtracking/all_subsequences.py index 8283386991d9..3851c4ab0118 100644 --- a/backtracking/all_subsequences.py +++ b/backtracking/all_subsequences.py @@ -13,10 +13,10 @@ def generate_all_subsequences(sequence): def create_state_space_tree(sequence, current_subsequence, index): """ - Creates a state space tree to iterate through each branch using DFS. - We know that each state has exactly two children. - It terminates when it reaches the end of the given sequence. - """ + Creates a state space tree to iterate through each branch using DFS. + We know that each state has exactly two children. + It terminates when it reaches the end of the given sequence. + """ if index == len(sequence): print(current_subsequence) diff --git a/backtracking/sudoku.py b/backtracking/sudoku.py index d864e2823a9b..b3d38b4cc7c7 100644 --- a/backtracking/sudoku.py +++ b/backtracking/sudoku.py @@ -105,7 +105,7 @@ def sudoku(grid): [7, 4, 5, 2, 8, 6, 3, 1, 9]] >>> sudoku(no_solution) False - """ + """ if is_completed(grid): return grid diff --git a/backtracking/sum_of_subsets.py b/backtracking/sum_of_subsets.py index c03df18ae743..425ddcff927e 100644 --- a/backtracking/sum_of_subsets.py +++ b/backtracking/sum_of_subsets.py @@ -19,13 +19,13 @@ def generate_sum_of_subsets_soln(nums, max_sum): def create_state_space_tree(nums, max_sum, num_index, path, result, remaining_nums_sum): """ - Creates a state space tree to iterate through each branch using DFS. - It terminates the branching of a node when any of the two conditions - given below satisfy. - This algorithm follows depth-fist-search and backtracks when the node is not - branchable. + Creates a state space tree to iterate through each branch using DFS. + It terminates the branching of a node when any of the two conditions + given below satisfy. + This algorithm follows depth-fist-search and backtracks when the node is not + branchable. - """ + """ if sum(path) > max_sum or (remaining_nums_sum + sum(path)) < max_sum: return if sum(path) == max_sum: diff --git a/blockchain/modular_division.py b/blockchain/modular_division.py index c09863a3c5f0..8fcf6e37cbed 100644 --- a/blockchain/modular_division.py +++ b/blockchain/modular_division.py @@ -74,13 +74,13 @@ def modular_division2(a, b, n): def extended_gcd(a, b): """ - >>> extended_gcd(10, 6) - (2, -1, 2) + >>> extended_gcd(10, 6) + (2, -1, 2) - >>> extended_gcd(7, 5) - (1, -2, 3) + >>> extended_gcd(7, 5) + (1, -2, 3) - ** extended_gcd function is used when d = gcd(a,b) is required in output + ** extended_gcd function is used when d = gcd(a,b) is required in output """ assert a >= 0 and b >= 0 diff --git a/ciphers/enigma_machine2.py b/ciphers/enigma_machine2.py index 4c79e1c2fab9..0fbe97284d38 100644 --- a/ciphers/enigma_machine2.py +++ b/ciphers/enigma_machine2.py @@ -17,26 +17,50 @@ # used alphabet -------------------------- # from string.ascii_uppercase -abc = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' +abc = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" # -------------------------- default selection -------------------------- # rotors -------------------------- -rotor1 = 'EGZWVONAHDCLFQMSIPJBYUKXTR' -rotor2 = 'FOBHMDKEXQNRAULPGSJVTYICZW' -rotor3 = 'ZJXESIUQLHAVRMDOYGTNFWPBKC' +rotor1 = "EGZWVONAHDCLFQMSIPJBYUKXTR" +rotor2 = "FOBHMDKEXQNRAULPGSJVTYICZW" +rotor3 = "ZJXESIUQLHAVRMDOYGTNFWPBKC" # reflector -------------------------- -reflector = {'A': 'N', 'N': 'A', 'B': 'O', 'O': 'B', 'C': 'P', 'P': 'C', 'D': 'Q', - 'Q': 'D', 'E': 'R', 'R': 'E', 'F': 'S', 'S': 'F', 'G': 'T', 'T': 'G', - 'H': 'U', 'U': 'H', 'I': 'V', 'V': 'I', 'J': 'W', 'W': 'J', 'K': 'X', - 'X': 'K', 'L': 'Y', 'Y': 'L', 'M': 'Z', 'Z': 'M'} +reflector = { + "A": "N", + "N": "A", + "B": "O", + "O": "B", + "C": "P", + "P": "C", + "D": "Q", + "Q": "D", + "E": "R", + "R": "E", + "F": "S", + "S": "F", + "G": "T", + "T": "G", + "H": "U", + "U": "H", + "I": "V", + "V": "I", + "J": "W", + "W": "J", + "K": "X", + "X": "K", + "L": "Y", + "Y": "L", + "M": "Z", + "Z": "M", +} # -------------------------- extra rotors -------------------------- -rotor4 = 'RMDJXFUWGISLHVTCQNKYPBEZOA' -rotor5 = 'SGLCPQWZHKXAREONTFBVIYJUDM' -rotor6 = 'HVSICLTYKQUBXDWAJZOMFGPREN' -rotor7 = 'RZWQHFMVDBKICJLNTUXAGYPSOE' -rotor8 = 'LFKIJODBEGAMQPXVUHYSTCZRWN' -rotor9 = 'KOAEGVDHXPQZMLFTYWJNBRCIUS' +rotor4 = "RMDJXFUWGISLHVTCQNKYPBEZOA" +rotor5 = "SGLCPQWZHKXAREONTFBVIYJUDM" +rotor6 = "HVSICLTYKQUBXDWAJZOMFGPREN" +rotor7 = "RZWQHFMVDBKICJLNTUXAGYPSOE" +rotor8 = "LFKIJODBEGAMQPXVUHYSTCZRWN" +rotor9 = "KOAEGVDHXPQZMLFTYWJNBRCIUS" def _validator(rotpos: tuple, rotsel: tuple, pb: str) -> tuple: @@ -57,19 +81,22 @@ def _validator(rotpos: tuple, rotsel: tuple, pb: str) -> tuple: unique_rotsel = len(set(rotsel)) if unique_rotsel < 3: - raise Exception(f'Please use 3 unique rotors (not {unique_rotsel})') + raise Exception(f"Please use 3 unique rotors (not {unique_rotsel})") # Checks if rotor positions are valid rotorpos1, rotorpos2, rotorpos3 = rotpos if not 0 < rotorpos1 <= len(abc): - raise ValueError(f'First rotor position is not within range of 1..26 (' - f'{rotorpos1}') + raise ValueError( + f"First rotor position is not within range of 1..26 (" f"{rotorpos1}" + ) if not 0 < rotorpos2 <= len(abc): - raise ValueError(f'Second rotor position is not within range of 1..26 (' - f'{rotorpos2})') + raise ValueError( + f"Second rotor position is not within range of 1..26 (" f"{rotorpos2})" + ) if not 0 < rotorpos3 <= len(abc): - raise ValueError(f'Third rotor position is not within range of 1..26 (' - f'{rotorpos3})') + raise ValueError( + f"Third rotor position is not within range of 1..26 (" f"{rotorpos3})" + ) # Validates string and returns dict pb = _plugboard(pb) @@ -97,21 +124,21 @@ def _plugboard(pbstring: str) -> dict: # a) is type string # b) has even length (so pairs can be made) if not isinstance(pbstring, str): - raise TypeError(f'Plugboard setting isn\'t type string ({type(pbstring)})') + raise TypeError(f"Plugboard setting isn't type string ({type(pbstring)})") elif len(pbstring) % 2 != 0: - raise Exception(f'Odd number of symbols ({len(pbstring)})') - elif pbstring == '': + raise Exception(f"Odd number of symbols ({len(pbstring)})") + elif pbstring == "": return {} - pbstring.replace(' ', '') + pbstring.replace(" ", "") # Checks if all characters are unique tmppbl = set() for i in pbstring: if i not in abc: - raise Exception(f'\'{i}\' not in list of symbols') + raise Exception(f"'{i}' not in list of symbols") elif i in tmppbl: - raise Exception(f'Duplicate symbol ({i})') + raise Exception(f"Duplicate symbol ({i})") else: tmppbl.add(i) del tmppbl @@ -125,8 +152,12 @@ def _plugboard(pbstring: str) -> dict: return pb -def enigma(text: str, rotor_position: tuple, - rotor_selection: tuple = (rotor1, rotor2, rotor3), plugb: str = '') -> str: +def enigma( + text: str, + rotor_position: tuple, + rotor_selection: tuple = (rotor1, rotor2, rotor3), + plugb: str = "", +) -> str: """ The only difference with real-world enigma is that I allowed string input. All characters are converted to uppercase. (non-letter symbol are ignored) @@ -179,7 +210,8 @@ def enigma(text: str, rotor_position: tuple, text = text.upper() rotor_position, rotor_selection, plugboard = _validator( - rotor_position, rotor_selection, plugb.upper()) + rotor_position, rotor_selection, plugb.upper() + ) rotorpos1, rotorpos2, rotorpos3 = rotor_position rotor1, rotor2, rotor3 = rotor_selection @@ -245,12 +277,12 @@ def enigma(text: str, rotor_position: tuple, return "".join(result) -if __name__ == '__main__': - message = 'This is my Python script that emulates the Enigma machine from WWII.' +if __name__ == "__main__": + message = "This is my Python script that emulates the Enigma machine from WWII." rotor_pos = (1, 1, 1) - pb = 'pictures' + pb = "pictures" rotor_sel = (rotor2, rotor4, rotor8) en = enigma(message, rotor_pos, rotor_sel, pb) - print('Encrypted message:', en) - print('Decrypted message:', enigma(en, rotor_pos, rotor_sel, pb)) + print("Encrypted message:", en) + print("Decrypted message:", enigma(en, rotor_pos, rotor_sel, pb)) diff --git a/ciphers/xor_cipher.py b/ciphers/xor_cipher.py index 0fcfbb0b9ae2..3b045fdac64a 100644 --- a/ciphers/xor_cipher.py +++ b/ciphers/xor_cipher.py @@ -21,20 +21,20 @@ class XORCipher: def __init__(self, key=0): """ - simple constructor that receives a key or uses - default key = 0 - """ + simple constructor that receives a key or uses + default key = 0 + """ # private field self.__key = key def encrypt(self, content, key): """ - input: 'content' of type string and 'key' of type int - output: encrypted string 'content' as a list of chars - if key not passed the method uses the key by the constructor. - otherwise key = 1 - """ + input: 'content' of type string and 'key' of type int + output: encrypted string 'content' as a list of chars + if key not passed the method uses the key by the constructor. + otherwise key = 1 + """ # precondition assert isinstance(key, int) and isinstance(content, str) @@ -55,11 +55,11 @@ def encrypt(self, content, key): def decrypt(self, content, key): """ - input: 'content' of type list and 'key' of type int - output: decrypted string 'content' as a list of chars - if key not passed the method uses the key by the constructor. - otherwise key = 1 - """ + input: 'content' of type list and 'key' of type int + output: decrypted string 'content' as a list of chars + if key not passed the method uses the key by the constructor. + otherwise key = 1 + """ # precondition assert isinstance(key, int) and isinstance(content, list) @@ -80,11 +80,11 @@ def decrypt(self, content, key): def encrypt_string(self, content, key=0): """ - input: 'content' of type string and 'key' of type int - output: encrypted string 'content' - if key not passed the method uses the key by the constructor. - otherwise key = 1 - """ + input: 'content' of type string and 'key' of type int + output: encrypted string 'content' + if key not passed the method uses the key by the constructor. + otherwise key = 1 + """ # precondition assert isinstance(key, int) and isinstance(content, str) @@ -105,11 +105,11 @@ def encrypt_string(self, content, key=0): def decrypt_string(self, content, key=0): """ - input: 'content' of type string and 'key' of type int - output: decrypted string 'content' - if key not passed the method uses the key by the constructor. - otherwise key = 1 - """ + input: 'content' of type string and 'key' of type int + output: decrypted string 'content' + if key not passed the method uses the key by the constructor. + otherwise key = 1 + """ # precondition assert isinstance(key, int) and isinstance(content, str) @@ -130,12 +130,12 @@ def decrypt_string(self, content, key=0): def encrypt_file(self, file, key=0): """ - input: filename (str) and a key (int) - output: returns true if encrypt process was - successful otherwise false - if key not passed the method uses the key by the constructor. - otherwise key = 1 - """ + input: filename (str) and a key (int) + output: returns true if encrypt process was + successful otherwise false + if key not passed the method uses the key by the constructor. + otherwise key = 1 + """ # precondition assert isinstance(file, str) and isinstance(key, int) @@ -155,12 +155,12 @@ def encrypt_file(self, file, key=0): def decrypt_file(self, file, key): """ - input: filename (str) and a key (int) - output: returns true if decrypt process was - successful otherwise false - if key not passed the method uses the key by the constructor. - otherwise key = 1 - """ + input: filename (str) and a key (int) + output: returns true if decrypt process was + successful otherwise false + if key not passed the method uses the key by the constructor. + otherwise key = 1 + """ # precondition assert isinstance(file, str) and isinstance(key, int) diff --git a/conversions/decimal_to_any.py b/conversions/decimal_to_any.py index cbed1ac12214..3c72a7732ac6 100644 --- a/conversions/decimal_to_any.py +++ b/conversions/decimal_to_any.py @@ -3,55 +3,55 @@ def decimal_to_any(num: int, base: int) -> str: """ - Convert a positive integer to another base as str. - >>> decimal_to_any(0, 2) - '0' - >>> decimal_to_any(5, 4) - '11' - >>> decimal_to_any(20, 3) - '202' - >>> decimal_to_any(58, 16) - '3A' - >>> decimal_to_any(243, 17) - 'E5' - >>> decimal_to_any(34923, 36) - 'QY3' - >>> decimal_to_any(10, 11) - 'A' - >>> decimal_to_any(16, 16) - '10' - >>> decimal_to_any(36, 36) - '10' - >>> # negatives will error - >>> decimal_to_any(-45, 8) # doctest: +ELLIPSIS - Traceback (most recent call last): - ... - ValueError: parameter must be positive int - >>> # floats will error - >>> decimal_to_any(34.4, 6) # doctest: +ELLIPSIS - Traceback (most recent call last): - ... - TypeError: int() can't convert non-string with explicit base - >>> # a float base will error - >>> decimal_to_any(5, 2.5) # doctest: +ELLIPSIS - Traceback (most recent call last): - ... - TypeError: 'float' object cannot be interpreted as an integer - >>> # a str base will error - >>> decimal_to_any(10, '16') # doctest: +ELLIPSIS - Traceback (most recent call last): - ... - TypeError: 'str' object cannot be interpreted as an integer - >>> # a base less than 2 will error - >>> decimal_to_any(7, 0) # doctest: +ELLIPSIS - Traceback (most recent call last): - ... - ValueError: base must be >= 2 - >>> # a base greater than 36 will error - >>> decimal_to_any(34, 37) # doctest: +ELLIPSIS - Traceback (most recent call last): - ... - ValueError: base must be <= 36 + Convert a positive integer to another base as str. + >>> decimal_to_any(0, 2) + '0' + >>> decimal_to_any(5, 4) + '11' + >>> decimal_to_any(20, 3) + '202' + >>> decimal_to_any(58, 16) + '3A' + >>> decimal_to_any(243, 17) + 'E5' + >>> decimal_to_any(34923, 36) + 'QY3' + >>> decimal_to_any(10, 11) + 'A' + >>> decimal_to_any(16, 16) + '10' + >>> decimal_to_any(36, 36) + '10' + >>> # negatives will error + >>> decimal_to_any(-45, 8) # doctest: +ELLIPSIS + Traceback (most recent call last): + ... + ValueError: parameter must be positive int + >>> # floats will error + >>> decimal_to_any(34.4, 6) # doctest: +ELLIPSIS + Traceback (most recent call last): + ... + TypeError: int() can't convert non-string with explicit base + >>> # a float base will error + >>> decimal_to_any(5, 2.5) # doctest: +ELLIPSIS + Traceback (most recent call last): + ... + TypeError: 'float' object cannot be interpreted as an integer + >>> # a str base will error + >>> decimal_to_any(10, '16') # doctest: +ELLIPSIS + Traceback (most recent call last): + ... + TypeError: 'str' object cannot be interpreted as an integer + >>> # a base less than 2 will error + >>> decimal_to_any(7, 0) # doctest: +ELLIPSIS + Traceback (most recent call last): + ... + ValueError: base must be >= 2 + >>> # a base greater than 36 will error + >>> decimal_to_any(34, 37) # doctest: +ELLIPSIS + Traceback (most recent call last): + ... + ValueError: base must be <= 36 """ if isinstance(num, float): raise TypeError("int() can't convert non-string with explicit base") diff --git a/conversions/decimal_to_binary.py b/conversions/decimal_to_binary.py index 8fcf226c346c..7e83aee4f7a5 100644 --- a/conversions/decimal_to_binary.py +++ b/conversions/decimal_to_binary.py @@ -4,28 +4,28 @@ def decimal_to_binary(num: int) -> str: """ - Convert an Integer Decimal Number to a Binary Number as str. - >>> decimal_to_binary(0) - '0b0' - >>> decimal_to_binary(2) - '0b10' - >>> decimal_to_binary(7) - '0b111' - >>> decimal_to_binary(35) - '0b100011' - >>> # negatives work too - >>> decimal_to_binary(-2) - '-0b10' - >>> # other floats will error - >>> decimal_to_binary(16.16) # doctest: +ELLIPSIS - Traceback (most recent call last): - ... - TypeError: 'float' object cannot be interpreted as an integer - >>> # strings will error as well - >>> decimal_to_binary('0xfffff') # doctest: +ELLIPSIS - Traceback (most recent call last): - ... - TypeError: 'str' object cannot be interpreted as an integer + Convert an Integer Decimal Number to a Binary Number as str. + >>> decimal_to_binary(0) + '0b0' + >>> decimal_to_binary(2) + '0b10' + >>> decimal_to_binary(7) + '0b111' + >>> decimal_to_binary(35) + '0b100011' + >>> # negatives work too + >>> decimal_to_binary(-2) + '-0b10' + >>> # other floats will error + >>> decimal_to_binary(16.16) # doctest: +ELLIPSIS + Traceback (most recent call last): + ... + TypeError: 'float' object cannot be interpreted as an integer + >>> # strings will error as well + >>> decimal_to_binary('0xfffff') # doctest: +ELLIPSIS + Traceback (most recent call last): + ... + TypeError: 'str' object cannot be interpreted as an integer """ if type(num) == float: diff --git a/conversions/decimal_to_hexadecimal.py b/conversions/decimal_to_hexadecimal.py index 6bd9533ab390..433f78dfecb7 100644 --- a/conversions/decimal_to_hexadecimal.py +++ b/conversions/decimal_to_hexadecimal.py @@ -23,39 +23,39 @@ def decimal_to_hexadecimal(decimal): """ - take integer decimal value, return hexadecimal representation as str beginning - with 0x - >>> decimal_to_hexadecimal(5) - '0x5' - >>> decimal_to_hexadecimal(15) - '0xf' - >>> decimal_to_hexadecimal(37) - '0x25' - >>> decimal_to_hexadecimal(255) - '0xff' - >>> decimal_to_hexadecimal(4096) - '0x1000' - >>> decimal_to_hexadecimal(999098) - '0xf3eba' - >>> # negatives work too - >>> decimal_to_hexadecimal(-256) - '-0x100' - >>> # floats are acceptable if equivalent to an int - >>> decimal_to_hexadecimal(17.0) - '0x11' - >>> # other floats will error - >>> decimal_to_hexadecimal(16.16) # doctest: +ELLIPSIS - Traceback (most recent call last): - ... - AssertionError - >>> # strings will error as well - >>> decimal_to_hexadecimal('0xfffff') # doctest: +ELLIPSIS - Traceback (most recent call last): - ... - AssertionError - >>> # results are the same when compared to Python's default hex function - >>> decimal_to_hexadecimal(-256) == hex(-256) - True + take integer decimal value, return hexadecimal representation as str beginning + with 0x + >>> decimal_to_hexadecimal(5) + '0x5' + >>> decimal_to_hexadecimal(15) + '0xf' + >>> decimal_to_hexadecimal(37) + '0x25' + >>> decimal_to_hexadecimal(255) + '0xff' + >>> decimal_to_hexadecimal(4096) + '0x1000' + >>> decimal_to_hexadecimal(999098) + '0xf3eba' + >>> # negatives work too + >>> decimal_to_hexadecimal(-256) + '-0x100' + >>> # floats are acceptable if equivalent to an int + >>> decimal_to_hexadecimal(17.0) + '0x11' + >>> # other floats will error + >>> decimal_to_hexadecimal(16.16) # doctest: +ELLIPSIS + Traceback (most recent call last): + ... + AssertionError + >>> # strings will error as well + >>> decimal_to_hexadecimal('0xfffff') # doctest: +ELLIPSIS + Traceback (most recent call last): + ... + AssertionError + >>> # results are the same when compared to Python's default hex function + >>> decimal_to_hexadecimal(-256) == hex(-256) + True """ assert type(decimal) in (int, float) and decimal == int(decimal) hexadecimal = "" diff --git a/data_structures/binary_tree/avl_tree.py b/data_structures/binary_tree/avl_tree.py index c6a45f1cbeb7..3362610b9303 100644 --- a/data_structures/binary_tree/avl_tree.py +++ b/data_structures/binary_tree/avl_tree.py @@ -109,7 +109,7 @@ def right_rotation(node): def left_rotation(node): """ - a mirror symmetry rotation of the left_rotation + a mirror symmetry rotation of the left_rotation """ print("right rotation node:", node.get_data()) ret = node.get_right() diff --git a/data_structures/binary_tree/red_black_tree.py b/data_structures/binary_tree/red_black_tree.py index 379cee61d888..5d721edfa45b 100644 --- a/data_structures/binary_tree/red_black_tree.py +++ b/data_structures/binary_tree/red_black_tree.py @@ -28,11 +28,11 @@ def __init__( right: Optional["RedBlackTree"] = None, ) -> None: """Initialize a new Red-Black Tree node with the given values: - label: The value associated with this node - color: 0 if black, 1 if red - parent: The parent to this node - left: This node's left child - right: This node's right child + label: The value associated with this node + color: 0 if black, 1 if red + parent: The parent to this node + left: This node's left child + right: This node's right child """ self.label = label self.parent = parent diff --git a/data_structures/binary_tree/treap.py b/data_structures/binary_tree/treap.py index 52b757d584c3..fbb57650280e 100644 --- a/data_structures/binary_tree/treap.py +++ b/data_structures/binary_tree/treap.py @@ -118,7 +118,7 @@ def inorder(root: Node): return else: inorder(root.left) - print(root.value, end=" ") + print(root.value, end=",") inorder(root.right) @@ -130,19 +130,19 @@ def interactTreap(root, args): >>> root = interactTreap(None, "+1") >>> inorder(root) - 1 + 1, >>> root = interactTreap(root, "+3 +5 +17 +19 +2 +16 +4 +0") >>> inorder(root) - 0 1 2 3 4 5 16 17 19 + 0,1,2,3,4,5,16,17,19, >>> root = interactTreap(root, "+4 +4 +4") >>> inorder(root) - 0 1 2 3 4 4 4 4 5 16 17 19 + 0,1,2,3,4,4,4,4,5,16,17,19, >>> root = interactTreap(root, "-0") >>> inorder(root) - 1 2 3 4 4 4 4 5 16 17 19 + 1,2,3,4,4,4,4,5,16,17,19, >>> root = interactTreap(root, "-4") >>> inorder(root) - 1 2 3 5 16 17 19 + 1,2,3,5,16,17,19, >>> root = interactTreap(root, "=0") Unknown command """ diff --git a/data_structures/hashing/double_hash.py b/data_structures/hashing/double_hash.py index 9c6c8fed6a00..1007849c5a53 100644 --- a/data_structures/hashing/double_hash.py +++ b/data_structures/hashing/double_hash.py @@ -5,7 +5,7 @@ class DoubleHash(HashTable): """ - Hash Table example with open addressing and Double Hash + Hash Table example with open addressing and Double Hash """ def __init__(self, *args, **kwargs): diff --git a/data_structures/hashing/hash_table.py b/data_structures/hashing/hash_table.py index 3b39742f9d09..6e03be95b737 100644 --- a/data_structures/hashing/hash_table.py +++ b/data_structures/hashing/hash_table.py @@ -4,7 +4,7 @@ class HashTable: """ - Basic Hash Table example with open addressing and linear probing + Basic Hash Table example with open addressing and linear probing """ def __init__(self, size_table, charge_factor=None, lim_charge=None): diff --git a/data_structures/hashing/number_theory/prime_numbers.py b/data_structures/hashing/number_theory/prime_numbers.py index 2a966e0da7f2..db4d40f475b2 100644 --- a/data_structures/hashing/number_theory/prime_numbers.py +++ b/data_structures/hashing/number_theory/prime_numbers.py @@ -6,8 +6,8 @@ def check_prime(number): """ - it's not the best solution - """ + it's not the best solution + """ special_non_primes = [0, 1, 2] if number in special_non_primes[:2]: return 2 diff --git a/data_structures/hashing/quadratic_probing.py b/data_structures/hashing/quadratic_probing.py index 668ddaa85048..06f3ced49d3c 100644 --- a/data_structures/hashing/quadratic_probing.py +++ b/data_structures/hashing/quadratic_probing.py @@ -5,7 +5,7 @@ class QuadraticProbing(HashTable): """ - Basic Hash Table example with open addressing using Quadratic Probing + Basic Hash Table example with open addressing using Quadratic Probing """ def __init__(self, *args, **kwargs): diff --git a/data_structures/linked_list/deque_doubly.py b/data_structures/linked_list/deque_doubly.py index 7025a7ea22f9..c6ee2ed27ba5 100644 --- a/data_structures/linked_list/deque_doubly.py +++ b/data_structures/linked_list/deque_doubly.py @@ -61,7 +61,7 @@ def _delete(self, node): class LinkedDeque(_DoublyLinkedBase): def first(self): - """ return first element + """return first element >>> d = LinkedDeque() >>> d.add_first('A').first() 'A' @@ -73,7 +73,7 @@ def first(self): return self._header._next._data def last(self): - """ return last element + """return last element >>> d = LinkedDeque() >>> d.add_last('A').last() 'A' @@ -87,14 +87,14 @@ def last(self): # DEque Insert Operations (At the front, At the end) def add_first(self, element): - """ insertion in the front + """insertion in the front >>> LinkedDeque().add_first('AV').first() 'AV' """ return self._insert(self._header, element, self._header._next) def add_last(self, element): - """ insertion in the end + """insertion in the end >>> LinkedDeque().add_last('B').last() 'B' """ @@ -103,7 +103,7 @@ def add_last(self, element): # DEqueu Remove Operations (At the front, At the end) def remove_first(self): - """ removal from the front + """removal from the front >>> d = LinkedDeque() >>> d.is_empty() True @@ -123,7 +123,7 @@ def remove_first(self): return self._delete(self._header._next) def remove_last(self): - """ removal in the end + """removal in the end >>> d = LinkedDeque() >>> d.is_empty() True diff --git a/data_structures/stacks/infix_to_postfix_conversion.py b/data_structures/stacks/infix_to_postfix_conversion.py index 61114402377a..4a1180c9d8e4 100644 --- a/data_structures/stacks/infix_to_postfix_conversion.py +++ b/data_structures/stacks/infix_to_postfix_conversion.py @@ -10,7 +10,7 @@ def is_operand(char): def precedence(char): - """ Return integer value representing an operator's precedence, or + """Return integer value representing an operator's precedence, or order of operation. https://en.wikipedia.org/wiki/Order_of_operations @@ -20,7 +20,7 @@ def precedence(char): def infix_to_postfix(expression): - """ Convert infix notation to postfix notation using the Shunting-yard + """Convert infix notation to postfix notation using the Shunting-yard algorithm. https://en.wikipedia.org/wiki/Shunting-yard_algorithm diff --git a/data_structures/stacks/stack.py b/data_structures/stacks/stack.py index baa0857eec0a..a4bcb5beabd4 100644 --- a/data_structures/stacks/stack.py +++ b/data_structures/stacks/stack.py @@ -2,7 +2,7 @@ class Stack: - """ A stack is an abstract data type that serves as a collection of + """A stack is an abstract data type that serves as a collection of elements with two principal operations: push() and pop(). push() adds an element to the top of the stack, and pop() removes an element from the top of a stack. The order in which elements come off of a stack are diff --git a/digital_image_processing/index_calculation.py b/digital_image_processing/index_calculation.py index d55815a6e15e..4350b8603390 100644 --- a/digital_image_processing/index_calculation.py +++ b/digital_image_processing/index_calculation.py @@ -10,98 +10,98 @@ # Class implemented to calculus the index class IndexCalculation: """ - # Class Summary - This algorithm consists in calculating vegetation indices, these - indices can be used for precision agriculture for example (or remote - sensing). There are functions to define the data and to calculate the - implemented indices. - - # Vegetation index - https://en.wikipedia.org/wiki/Vegetation_Index - A Vegetation Index (VI) is a spectral transformation of two or more bands - designed to enhance the contribution of vegetation properties and allow - reliable spatial and temporal inter-comparisons of terrestrial - photosynthetic activity and canopy structural variations - - # Information about channels (Wavelength range for each) - * nir - near-infrared - https://www.malvernpanalytical.com/br/products/technology/near-infrared-spectroscopy - Wavelength Range 700 nm to 2500 nm - * Red Edge - https://en.wikipedia.org/wiki/Red_edge - Wavelength Range 680 nm to 730 nm - * red - https://en.wikipedia.org/wiki/Color - Wavelength Range 635 nm to 700 nm - * blue - https://en.wikipedia.org/wiki/Color - Wavelength Range 450 nm to 490 nm - * green - https://en.wikipedia.org/wiki/Color - Wavelength Range 520 nm to 560 nm - - - # Implemented index list - #"abbreviationOfIndexName" -- list of channels used - - #"ARVI2" -- red, nir - #"CCCI" -- red, redEdge, nir - #"CVI" -- red, green, nir - #"GLI" -- red, green, blue - #"NDVI" -- red, nir - #"BNDVI" -- blue, nir - #"redEdgeNDVI" -- red, redEdge - #"GNDVI" -- green, nir - #"GBNDVI" -- green, blue, nir - #"GRNDVI" -- red, green, nir - #"RBNDVI" -- red, blue, nir - #"PNDVI" -- red, green, blue, nir - #"ATSAVI" -- red, nir - #"BWDRVI" -- blue, nir - #"CIgreen" -- green, nir - #"CIrededge" -- redEdge, nir - #"CI" -- red, blue - #"CTVI" -- red, nir - #"GDVI" -- green, nir - #"EVI" -- red, blue, nir - #"GEMI" -- red, nir - #"GOSAVI" -- green, nir - #"GSAVI" -- green, nir - #"Hue" -- red, green, blue - #"IVI" -- red, nir - #"IPVI" -- red, nir - #"I" -- red, green, blue - #"RVI" -- red, nir - #"MRVI" -- red, nir - #"MSAVI" -- red, nir - #"NormG" -- red, green, nir - #"NormNIR" -- red, green, nir - #"NormR" -- red, green, nir - #"NGRDI" -- red, green - #"RI" -- red, green - #"S" -- red, green, blue - #"IF" -- red, green, blue - #"DVI" -- red, nir - #"TVI" -- red, nir - #"NDRE" -- redEdge, nir - - #list of all index implemented - #allIndex = ["ARVI2", "CCCI", "CVI", "GLI", "NDVI", "BNDVI", "redEdgeNDVI", - "GNDVI", "GBNDVI", "GRNDVI", "RBNDVI", "PNDVI", "ATSAVI", - "BWDRVI", "CIgreen", "CIrededge", "CI", "CTVI", "GDVI", "EVI", - "GEMI", "GOSAVI", "GSAVI", "Hue", "IVI", "IPVI", "I", "RVI", - "MRVI", "MSAVI", "NormG", "NormNIR", "NormR", "NGRDI", "RI", - "S", "IF", "DVI", "TVI", "NDRE"] - - #list of index with not blue channel - #notBlueIndex = ["ARVI2", "CCCI", "CVI", "NDVI", "redEdgeNDVI", "GNDVI", - "GRNDVI", "ATSAVI", "CIgreen", "CIrededge", "CTVI", "GDVI", - "GEMI", "GOSAVI", "GSAVI", "IVI", "IPVI", "RVI", "MRVI", - "MSAVI", "NormG", "NormNIR", "NormR", "NGRDI", "RI", "DVI", - "TVI", "NDRE"] - - #list of index just with RGB channels - #RGBIndex = ["GLI", "CI", "Hue", "I", "NGRDI", "RI", "S", "IF"] + # Class Summary + This algorithm consists in calculating vegetation indices, these + indices can be used for precision agriculture for example (or remote + sensing). There are functions to define the data and to calculate the + implemented indices. + + # Vegetation index + https://en.wikipedia.org/wiki/Vegetation_Index + A Vegetation Index (VI) is a spectral transformation of two or more bands + designed to enhance the contribution of vegetation properties and allow + reliable spatial and temporal inter-comparisons of terrestrial + photosynthetic activity and canopy structural variations + + # Information about channels (Wavelength range for each) + * nir - near-infrared + https://www.malvernpanalytical.com/br/products/technology/near-infrared-spectroscopy + Wavelength Range 700 nm to 2500 nm + * Red Edge + https://en.wikipedia.org/wiki/Red_edge + Wavelength Range 680 nm to 730 nm + * red + https://en.wikipedia.org/wiki/Color + Wavelength Range 635 nm to 700 nm + * blue + https://en.wikipedia.org/wiki/Color + Wavelength Range 450 nm to 490 nm + * green + https://en.wikipedia.org/wiki/Color + Wavelength Range 520 nm to 560 nm + + + # Implemented index list + #"abbreviationOfIndexName" -- list of channels used + + #"ARVI2" -- red, nir + #"CCCI" -- red, redEdge, nir + #"CVI" -- red, green, nir + #"GLI" -- red, green, blue + #"NDVI" -- red, nir + #"BNDVI" -- blue, nir + #"redEdgeNDVI" -- red, redEdge + #"GNDVI" -- green, nir + #"GBNDVI" -- green, blue, nir + #"GRNDVI" -- red, green, nir + #"RBNDVI" -- red, blue, nir + #"PNDVI" -- red, green, blue, nir + #"ATSAVI" -- red, nir + #"BWDRVI" -- blue, nir + #"CIgreen" -- green, nir + #"CIrededge" -- redEdge, nir + #"CI" -- red, blue + #"CTVI" -- red, nir + #"GDVI" -- green, nir + #"EVI" -- red, blue, nir + #"GEMI" -- red, nir + #"GOSAVI" -- green, nir + #"GSAVI" -- green, nir + #"Hue" -- red, green, blue + #"IVI" -- red, nir + #"IPVI" -- red, nir + #"I" -- red, green, blue + #"RVI" -- red, nir + #"MRVI" -- red, nir + #"MSAVI" -- red, nir + #"NormG" -- red, green, nir + #"NormNIR" -- red, green, nir + #"NormR" -- red, green, nir + #"NGRDI" -- red, green + #"RI" -- red, green + #"S" -- red, green, blue + #"IF" -- red, green, blue + #"DVI" -- red, nir + #"TVI" -- red, nir + #"NDRE" -- redEdge, nir + + #list of all index implemented + #allIndex = ["ARVI2", "CCCI", "CVI", "GLI", "NDVI", "BNDVI", "redEdgeNDVI", + "GNDVI", "GBNDVI", "GRNDVI", "RBNDVI", "PNDVI", "ATSAVI", + "BWDRVI", "CIgreen", "CIrededge", "CI", "CTVI", "GDVI", "EVI", + "GEMI", "GOSAVI", "GSAVI", "Hue", "IVI", "IPVI", "I", "RVI", + "MRVI", "MSAVI", "NormG", "NormNIR", "NormR", "NGRDI", "RI", + "S", "IF", "DVI", "TVI", "NDRE"] + + #list of index with not blue channel + #notBlueIndex = ["ARVI2", "CCCI", "CVI", "NDVI", "redEdgeNDVI", "GNDVI", + "GRNDVI", "ATSAVI", "CIgreen", "CIrededge", "CTVI", "GDVI", + "GEMI", "GOSAVI", "GSAVI", "IVI", "IPVI", "RVI", "MRVI", + "MSAVI", "NormG", "NormNIR", "NormR", "NGRDI", "RI", "DVI", + "TVI", "NDRE"] + + #list of index just with RGB channels + #RGBIndex = ["GLI", "CI", "Hue", "I", "NGRDI", "RI", "S", "IF"] """ def __init__(self, red=None, green=None, blue=None, redEdge=None, nir=None): @@ -189,9 +189,9 @@ def ARVI2(self): def CCCI(self): """ - Canopy Chlorophyll Content Index - https://www.indexdatabase.de/db/i-single.php?id=224 - :return: index + Canopy Chlorophyll Content Index + https://www.indexdatabase.de/db/i-single.php?id=224 + :return: index """ return ((self.nir - self.redEdge) / (self.nir + self.redEdge)) / ( (self.nir - self.red) / (self.nir + self.red) @@ -199,17 +199,17 @@ def CCCI(self): def CVI(self): """ - Chlorophyll vegetation index - https://www.indexdatabase.de/db/i-single.php?id=391 - :return: index + Chlorophyll vegetation index + https://www.indexdatabase.de/db/i-single.php?id=391 + :return: index """ return self.nir * (self.red / (self.green ** 2)) def GLI(self): """ - self.green leaf index - https://www.indexdatabase.de/db/i-single.php?id=375 - :return: index + self.green leaf index + https://www.indexdatabase.de/db/i-single.php?id=375 + :return: index """ return (2 * self.green - self.red - self.blue) / ( 2 * self.green + self.red + self.blue @@ -217,43 +217,43 @@ def GLI(self): def NDVI(self): """ - Normalized Difference self.nir/self.red Normalized Difference Vegetation - Index, Calibrated NDVI - CDVI - https://www.indexdatabase.de/db/i-single.php?id=58 - :return: index + Normalized Difference self.nir/self.red Normalized Difference Vegetation + Index, Calibrated NDVI - CDVI + https://www.indexdatabase.de/db/i-single.php?id=58 + :return: index """ return (self.nir - self.red) / (self.nir + self.red) def BNDVI(self): """ - Normalized Difference self.nir/self.blue self.blue-normalized difference - vegetation index - https://www.indexdatabase.de/db/i-single.php?id=135 - :return: index + Normalized Difference self.nir/self.blue self.blue-normalized difference + vegetation index + https://www.indexdatabase.de/db/i-single.php?id=135 + :return: index """ return (self.nir - self.blue) / (self.nir + self.blue) def redEdgeNDVI(self): """ - Normalized Difference self.rededge/self.red - https://www.indexdatabase.de/db/i-single.php?id=235 - :return: index + Normalized Difference self.rededge/self.red + https://www.indexdatabase.de/db/i-single.php?id=235 + :return: index """ return (self.redEdge - self.red) / (self.redEdge + self.red) def GNDVI(self): """ - Normalized Difference self.nir/self.green self.green NDVI - https://www.indexdatabase.de/db/i-single.php?id=401 - :return: index + Normalized Difference self.nir/self.green self.green NDVI + https://www.indexdatabase.de/db/i-single.php?id=401 + :return: index """ return (self.nir - self.green) / (self.nir + self.green) def GBNDVI(self): """ - self.green-self.blue NDVI - https://www.indexdatabase.de/db/i-single.php?id=186 - :return: index + self.green-self.blue NDVI + https://www.indexdatabase.de/db/i-single.php?id=186 + :return: index """ return (self.nir - (self.green + self.blue)) / ( self.nir + (self.green + self.blue) @@ -261,9 +261,9 @@ def GBNDVI(self): def GRNDVI(self): """ - self.green-self.red NDVI - https://www.indexdatabase.de/db/i-single.php?id=185 - :return: index + self.green-self.red NDVI + https://www.indexdatabase.de/db/i-single.php?id=185 + :return: index """ return (self.nir - (self.green + self.red)) / ( self.nir + (self.green + self.red) @@ -271,17 +271,17 @@ def GRNDVI(self): def RBNDVI(self): """ - self.red-self.blue NDVI - https://www.indexdatabase.de/db/i-single.php?id=187 - :return: index + self.red-self.blue NDVI + https://www.indexdatabase.de/db/i-single.php?id=187 + :return: index """ return (self.nir - (self.blue + self.red)) / (self.nir + (self.blue + self.red)) def PNDVI(self): """ - Pan NDVI - https://www.indexdatabase.de/db/i-single.php?id=188 - :return: index + Pan NDVI + https://www.indexdatabase.de/db/i-single.php?id=188 + :return: index """ return (self.nir - (self.green + self.red + self.blue)) / ( self.nir + (self.green + self.red + self.blue) @@ -289,9 +289,9 @@ def PNDVI(self): def ATSAVI(self, X=0.08, a=1.22, b=0.03): """ - Adjusted transformed soil-adjusted VI - https://www.indexdatabase.de/db/i-single.php?id=209 - :return: index + Adjusted transformed soil-adjusted VI + https://www.indexdatabase.de/db/i-single.php?id=209 + :return: index """ return a * ( (self.nir - a * self.red - b) @@ -300,58 +300,58 @@ def ATSAVI(self, X=0.08, a=1.22, b=0.03): def BWDRVI(self): """ - self.blue-wide dynamic range vegetation index - https://www.indexdatabase.de/db/i-single.php?id=136 - :return: index + self.blue-wide dynamic range vegetation index + https://www.indexdatabase.de/db/i-single.php?id=136 + :return: index """ return (0.1 * self.nir - self.blue) / (0.1 * self.nir + self.blue) def CIgreen(self): """ - Chlorophyll Index self.green - https://www.indexdatabase.de/db/i-single.php?id=128 - :return: index + Chlorophyll Index self.green + https://www.indexdatabase.de/db/i-single.php?id=128 + :return: index """ return (self.nir / self.green) - 1 def CIrededge(self): """ - Chlorophyll Index self.redEdge - https://www.indexdatabase.de/db/i-single.php?id=131 - :return: index + Chlorophyll Index self.redEdge + https://www.indexdatabase.de/db/i-single.php?id=131 + :return: index """ return (self.nir / self.redEdge) - 1 def CI(self): """ - Coloration Index - https://www.indexdatabase.de/db/i-single.php?id=11 - :return: index + Coloration Index + https://www.indexdatabase.de/db/i-single.php?id=11 + :return: index """ return (self.red - self.blue) / self.red def CTVI(self): """ - Corrected Transformed Vegetation Index - https://www.indexdatabase.de/db/i-single.php?id=244 - :return: index + Corrected Transformed Vegetation Index + https://www.indexdatabase.de/db/i-single.php?id=244 + :return: index """ ndvi = self.NDVI() return ((ndvi + 0.5) / (abs(ndvi + 0.5))) * (abs(ndvi + 0.5) ** (1 / 2)) def GDVI(self): """ - Difference self.nir/self.green self.green Difference Vegetation Index - https://www.indexdatabase.de/db/i-single.php?id=27 - :return: index + Difference self.nir/self.green self.green Difference Vegetation Index + https://www.indexdatabase.de/db/i-single.php?id=27 + :return: index """ return self.nir - self.green def EVI(self): """ - Enhanced Vegetation Index - https://www.indexdatabase.de/db/i-single.php?id=16 - :return: index + Enhanced Vegetation Index + https://www.indexdatabase.de/db/i-single.php?id=16 + :return: index """ return 2.5 * ( (self.nir - self.red) / (self.nir + 6 * self.red - 7.5 * self.blue + 1) @@ -359,9 +359,9 @@ def EVI(self): def GEMI(self): """ - Global Environment Monitoring Index - https://www.indexdatabase.de/db/i-single.php?id=25 - :return: index + Global Environment Monitoring Index + https://www.indexdatabase.de/db/i-single.php?id=25 + :return: index """ n = (2 * (self.nir ** 2 - self.red ** 2) + 1.5 * self.nir + 0.5 * self.red) / ( self.nir + self.red + 0.5 @@ -370,27 +370,27 @@ def GEMI(self): def GOSAVI(self, Y=0.16): """ - self.green Optimized Soil Adjusted Vegetation Index - https://www.indexdatabase.de/db/i-single.php?id=29 - mit Y = 0,16 - :return: index + self.green Optimized Soil Adjusted Vegetation Index + https://www.indexdatabase.de/db/i-single.php?id=29 + mit Y = 0,16 + :return: index """ return (self.nir - self.green) / (self.nir + self.green + Y) def GSAVI(self, L=0.5): """ - self.green Soil Adjusted Vegetation Index - https://www.indexdatabase.de/db/i-single.php?id=31 - mit L = 0,5 - :return: index + self.green Soil Adjusted Vegetation Index + https://www.indexdatabase.de/db/i-single.php?id=31 + mit L = 0,5 + :return: index """ return ((self.nir - self.green) / (self.nir + self.green + L)) * (1 + L) def Hue(self): """ - Hue - https://www.indexdatabase.de/db/i-single.php?id=34 - :return: index + Hue + https://www.indexdatabase.de/db/i-single.php?id=34 + :return: index """ return np.arctan( ((2 * self.red - self.green - self.blue) / 30.5) * (self.green - self.blue) @@ -398,51 +398,51 @@ def Hue(self): def IVI(self, a=None, b=None): """ - Ideal vegetation index - https://www.indexdatabase.de/db/i-single.php?id=276 - b=intercept of vegetation line - a=soil line slope - :return: index + Ideal vegetation index + https://www.indexdatabase.de/db/i-single.php?id=276 + b=intercept of vegetation line + a=soil line slope + :return: index """ return (self.nir - b) / (a * self.red) def IPVI(self): """ - Infraself.red percentage vegetation index - https://www.indexdatabase.de/db/i-single.php?id=35 - :return: index + Infraself.red percentage vegetation index + https://www.indexdatabase.de/db/i-single.php?id=35 + :return: index """ return (self.nir / ((self.nir + self.red) / 2)) * (self.NDVI() + 1) def I(self): # noqa: E741,E743 """ - Intensity - https://www.indexdatabase.de/db/i-single.php?id=36 - :return: index + Intensity + https://www.indexdatabase.de/db/i-single.php?id=36 + :return: index """ return (self.red + self.green + self.blue) / 30.5 def RVI(self): """ - Ratio-Vegetation-Index - http://www.seos-project.eu/modules/remotesensing/remotesensing-c03-s01-p01.html - :return: index + Ratio-Vegetation-Index + http://www.seos-project.eu/modules/remotesensing/remotesensing-c03-s01-p01.html + :return: index """ return self.nir / self.red def MRVI(self): """ - Modified Normalized Difference Vegetation Index RVI - https://www.indexdatabase.de/db/i-single.php?id=275 - :return: index + Modified Normalized Difference Vegetation Index RVI + https://www.indexdatabase.de/db/i-single.php?id=275 + :return: index """ return (self.RVI() - 1) / (self.RVI() + 1) def MSAVI(self): """ - Modified Soil Adjusted Vegetation Index - https://www.indexdatabase.de/db/i-single.php?id=44 - :return: index + Modified Soil Adjusted Vegetation Index + https://www.indexdatabase.de/db/i-single.php?id=44 + :return: index """ return ( (2 * self.nir + 1) @@ -451,51 +451,51 @@ def MSAVI(self): def NormG(self): """ - Norm G - https://www.indexdatabase.de/db/i-single.php?id=50 - :return: index + Norm G + https://www.indexdatabase.de/db/i-single.php?id=50 + :return: index """ return self.green / (self.nir + self.red + self.green) def NormNIR(self): """ - Norm self.nir - https://www.indexdatabase.de/db/i-single.php?id=51 - :return: index + Norm self.nir + https://www.indexdatabase.de/db/i-single.php?id=51 + :return: index """ return self.nir / (self.nir + self.red + self.green) def NormR(self): """ - Norm R - https://www.indexdatabase.de/db/i-single.php?id=52 - :return: index + Norm R + https://www.indexdatabase.de/db/i-single.php?id=52 + :return: index """ return self.red / (self.nir + self.red + self.green) def NGRDI(self): """ - Normalized Difference self.green/self.red Normalized self.green self.red - difference index, Visible Atmospherically Resistant Indices self.green - (VIself.green) - https://www.indexdatabase.de/db/i-single.php?id=390 - :return: index + Normalized Difference self.green/self.red Normalized self.green self.red + difference index, Visible Atmospherically Resistant Indices self.green + (VIself.green) + https://www.indexdatabase.de/db/i-single.php?id=390 + :return: index """ return (self.green - self.red) / (self.green + self.red) def RI(self): """ - Normalized Difference self.red/self.green self.redness Index - https://www.indexdatabase.de/db/i-single.php?id=74 - :return: index + Normalized Difference self.red/self.green self.redness Index + https://www.indexdatabase.de/db/i-single.php?id=74 + :return: index """ return (self.red - self.green) / (self.red + self.green) def S(self): """ - Saturation - https://www.indexdatabase.de/db/i-single.php?id=77 - :return: index + Saturation + https://www.indexdatabase.de/db/i-single.php?id=77 + :return: index """ max = np.max([np.max(self.red), np.max(self.green), np.max(self.blue)]) min = np.min([np.min(self.red), np.min(self.green), np.min(self.blue)]) @@ -503,26 +503,26 @@ def S(self): def IF(self): """ - Shape Index - https://www.indexdatabase.de/db/i-single.php?id=79 - :return: index + Shape Index + https://www.indexdatabase.de/db/i-single.php?id=79 + :return: index """ return (2 * self.red - self.green - self.blue) / (self.green - self.blue) def DVI(self): """ - Simple Ratio self.nir/self.red Difference Vegetation Index, Vegetation Index - Number (VIN) - https://www.indexdatabase.de/db/i-single.php?id=12 - :return: index + Simple Ratio self.nir/self.red Difference Vegetation Index, Vegetation Index + Number (VIN) + https://www.indexdatabase.de/db/i-single.php?id=12 + :return: index """ return self.nir / self.red def TVI(self): """ - Transformed Vegetation Index - https://www.indexdatabase.de/db/i-single.php?id=98 - :return: index + Transformed Vegetation Index + https://www.indexdatabase.de/db/i-single.php?id=98 + :return: index """ return (self.NDVI() + 0.5) ** (1 / 2) diff --git a/divide_and_conquer/closest_pair_of_points.py b/divide_and_conquer/closest_pair_of_points.py index eecf53a7450e..cb7fa00d1c8f 100644 --- a/divide_and_conquer/closest_pair_of_points.py +++ b/divide_and_conquer/closest_pair_of_points.py @@ -82,7 +82,7 @@ def dis_between_closest_in_strip(points, points_counts, min_dis=float("inf")): def closest_pair_of_points_sqr(points_sorted_on_x, points_sorted_on_y, points_counts): - """ divide and conquer approach + """divide and conquer approach Parameters : points, points_count (list(tuple(int, int)), int) diff --git a/divide_and_conquer/max_subarray_sum.py b/divide_and_conquer/max_subarray_sum.py index 03bf9d25cb8a..43f58086e078 100644 --- a/divide_and_conquer/max_subarray_sum.py +++ b/divide_and_conquer/max_subarray_sum.py @@ -11,7 +11,7 @@ def max_sum_from_start(array): - """ This function finds the maximum contiguous sum of array from 0 index + """This function finds the maximum contiguous sum of array from 0 index Parameters : array (list[int]) : given array @@ -30,7 +30,7 @@ def max_sum_from_start(array): def max_cross_array_sum(array, left, mid, right): - """ This function finds the maximum contiguous sum of left and right arrays + """This function finds the maximum contiguous sum of left and right arrays Parameters : array, left, mid, right (list[int], int, int, int) @@ -46,7 +46,7 @@ def max_cross_array_sum(array, left, mid, right): def max_subarray_sum(array, left, right): - """ Maximum contiguous sub-array sum, using divide and conquer method + """Maximum contiguous sub-array sum, using divide and conquer method Parameters : array, left, right (list[int], int, int) : diff --git a/divide_and_conquer/strassen_matrix_multiplication.py b/divide_and_conquer/strassen_matrix_multiplication.py index ea54b0f52d29..486258e8bae0 100644 --- a/divide_and_conquer/strassen_matrix_multiplication.py +++ b/divide_and_conquer/strassen_matrix_multiplication.py @@ -29,7 +29,9 @@ def matrix_subtraction(matrix_a: List, matrix_b: List): ] -def split_matrix(a: List,) -> Tuple[List, List, List, List]: +def split_matrix( + a: List, +) -> Tuple[List, List, List, List]: """ Given an even length matrix, returns the top_left, top_right, bot_left, bot_right quadrant. diff --git a/dynamic_programming/rod_cutting.py b/dynamic_programming/rod_cutting.py index a4919742e739..442a39cb1616 100644 --- a/dynamic_programming/rod_cutting.py +++ b/dynamic_programming/rod_cutting.py @@ -13,30 +13,30 @@ def naive_cut_rod_recursive(n: int, prices: list): """ - Solves the rod-cutting problem via naively without using the benefit of dynamic - programming. The results is the same sub-problems are solved several times - leading to an exponential runtime - - Runtime: O(2^n) - - Arguments - ------- - n: int, the length of the rod - prices: list, the prices for each piece of rod. ``p[i-i]`` is the - price for a rod of length ``i`` - - Returns - ------- - The maximum revenue obtainable for a rod of length n given the list of prices - for each piece. - - Examples - -------- - >>> naive_cut_rod_recursive(4, [1, 5, 8, 9]) - 10 - >>> naive_cut_rod_recursive(10, [1, 5, 8, 9, 10, 17, 17, 20, 24, 30]) - 30 - """ + Solves the rod-cutting problem via naively without using the benefit of dynamic + programming. The results is the same sub-problems are solved several times + leading to an exponential runtime + + Runtime: O(2^n) + + Arguments + ------- + n: int, the length of the rod + prices: list, the prices for each piece of rod. ``p[i-i]`` is the + price for a rod of length ``i`` + + Returns + ------- + The maximum revenue obtainable for a rod of length n given the list of prices + for each piece. + + Examples + -------- + >>> naive_cut_rod_recursive(4, [1, 5, 8, 9]) + 10 + >>> naive_cut_rod_recursive(10, [1, 5, 8, 9, 10, 17, 17, 20, 24, 30]) + 30 + """ _enforce_args(n, prices) if n == 0: @@ -52,35 +52,35 @@ def naive_cut_rod_recursive(n: int, prices: list): def top_down_cut_rod(n: int, prices: list): """ - Constructs a top-down dynamic programming solution for the rod-cutting - problem via memoization. This function serves as a wrapper for - _top_down_cut_rod_recursive - - Runtime: O(n^2) - - Arguments - -------- - n: int, the length of the rod - prices: list, the prices for each piece of rod. ``p[i-i]`` is the - price for a rod of length ``i`` - - Note - ---- - For convenience and because Python's lists using 0-indexing, length(max_rev) = - n + 1, to accommodate for the revenue obtainable from a rod of length 0. - - Returns - ------- - The maximum revenue obtainable for a rod of length n given the list of prices - for each piece. - - Examples - ------- - >>> top_down_cut_rod(4, [1, 5, 8, 9]) - 10 - >>> top_down_cut_rod(10, [1, 5, 8, 9, 10, 17, 17, 20, 24, 30]) - 30 - """ + Constructs a top-down dynamic programming solution for the rod-cutting + problem via memoization. This function serves as a wrapper for + _top_down_cut_rod_recursive + + Runtime: O(n^2) + + Arguments + -------- + n: int, the length of the rod + prices: list, the prices for each piece of rod. ``p[i-i]`` is the + price for a rod of length ``i`` + + Note + ---- + For convenience and because Python's lists using 0-indexing, length(max_rev) = + n + 1, to accommodate for the revenue obtainable from a rod of length 0. + + Returns + ------- + The maximum revenue obtainable for a rod of length n given the list of prices + for each piece. + + Examples + ------- + >>> top_down_cut_rod(4, [1, 5, 8, 9]) + 10 + >>> top_down_cut_rod(10, [1, 5, 8, 9, 10, 17, 17, 20, 24, 30]) + 30 + """ _enforce_args(n, prices) max_rev = [float("-inf") for _ in range(n + 1)] return _top_down_cut_rod_recursive(n, prices, max_rev) @@ -88,24 +88,24 @@ def top_down_cut_rod(n: int, prices: list): def _top_down_cut_rod_recursive(n: int, prices: list, max_rev: list): """ - Constructs a top-down dynamic programming solution for the rod-cutting problem - via memoization. - - Runtime: O(n^2) - - Arguments - -------- - n: int, the length of the rod - prices: list, the prices for each piece of rod. ``p[i-i]`` is the - price for a rod of length ``i`` - max_rev: list, the computed maximum revenue for a piece of rod. - ``max_rev[i]`` is the maximum revenue obtainable for a rod of length ``i`` - - Returns - ------- - The maximum revenue obtainable for a rod of length n given the list of prices - for each piece. - """ + Constructs a top-down dynamic programming solution for the rod-cutting problem + via memoization. + + Runtime: O(n^2) + + Arguments + -------- + n: int, the length of the rod + prices: list, the prices for each piece of rod. ``p[i-i]`` is the + price for a rod of length ``i`` + max_rev: list, the computed maximum revenue for a piece of rod. + ``max_rev[i]`` is the maximum revenue obtainable for a rod of length ``i`` + + Returns + ------- + The maximum revenue obtainable for a rod of length n given the list of prices + for each piece. + """ if max_rev[n] >= 0: return max_rev[n] elif n == 0: @@ -125,28 +125,28 @@ def _top_down_cut_rod_recursive(n: int, prices: list, max_rev: list): def bottom_up_cut_rod(n: int, prices: list): """ - Constructs a bottom-up dynamic programming solution for the rod-cutting problem - - Runtime: O(n^2) - - Arguments - ---------- - n: int, the maximum length of the rod. - prices: list, the prices for each piece of rod. ``p[i-i]`` is the - price for a rod of length ``i`` - - Returns - ------- - The maximum revenue obtainable from cutting a rod of length n given - the prices for each piece of rod p. - - Examples - ------- - >>> bottom_up_cut_rod(4, [1, 5, 8, 9]) - 10 - >>> bottom_up_cut_rod(10, [1, 5, 8, 9, 10, 17, 17, 20, 24, 30]) - 30 - """ + Constructs a bottom-up dynamic programming solution for the rod-cutting problem + + Runtime: O(n^2) + + Arguments + ---------- + n: int, the maximum length of the rod. + prices: list, the prices for each piece of rod. ``p[i-i]`` is the + price for a rod of length ``i`` + + Returns + ------- + The maximum revenue obtainable from cutting a rod of length n given + the prices for each piece of rod p. + + Examples + ------- + >>> bottom_up_cut_rod(4, [1, 5, 8, 9]) + 10 + >>> bottom_up_cut_rod(10, [1, 5, 8, 9, 10, 17, 17, 20, 24, 30]) + 30 + """ _enforce_args(n, prices) # length(max_rev) = n + 1, to accommodate for the revenue obtainable from a rod of @@ -166,16 +166,16 @@ def bottom_up_cut_rod(n: int, prices: list): def _enforce_args(n: int, prices: list): """ - Basic checks on the arguments to the rod-cutting algorithms + Basic checks on the arguments to the rod-cutting algorithms - n: int, the length of the rod - prices: list, the price list for each piece of rod. + n: int, the length of the rod + prices: list, the price list for each piece of rod. - Throws ValueError: + Throws ValueError: - if n is negative or there are fewer items in the price list than the length of - the rod - """ + if n is negative or there are fewer items in the price list than the length of + the rod + """ if n < 0: raise ValueError(f"n must be greater than or equal to 0. Got n = {n}") diff --git a/graphs/bfs_shortest_path.py b/graphs/bfs_shortest_path.py index c3664796e677..1655ca64208d 100644 --- a/graphs/bfs_shortest_path.py +++ b/graphs/bfs_shortest_path.py @@ -20,18 +20,18 @@ def bfs_shortest_path(graph: dict, start, goal) -> str: """Find shortest path between `start` and `goal` nodes. - Args: - graph (dict): node/list of neighboring nodes key/value pairs. - start: start node. - goal: target node. - - Returns: - Shortest path between `start` and `goal` nodes as a string of nodes. - 'Not found' string if no path found. - - Example: - >>> bfs_shortest_path(graph, "G", "D") - ['G', 'C', 'A', 'B', 'D'] + Args: + graph (dict): node/list of neighboring nodes key/value pairs. + start: start node. + goal: target node. + + Returns: + Shortest path between `start` and `goal` nodes as a string of nodes. + 'Not found' string if no path found. + + Example: + >>> bfs_shortest_path(graph, "G", "D") + ['G', 'C', 'A', 'B', 'D'] """ # keep track of explored nodes explored = [] @@ -70,22 +70,22 @@ def bfs_shortest_path(graph: dict, start, goal) -> str: def bfs_shortest_path_distance(graph: dict, start, target) -> int: """Find shortest path distance between `start` and `target` nodes. - Args: - graph: node/list of neighboring nodes key/value pairs. - start: node to start search from. - target: node to search for. - - Returns: - Number of edges in shortest path between `start` and `target` nodes. - -1 if no path exists. - - Example: - >>> bfs_shortest_path_distance(graph, "G", "D") - 4 - >>> bfs_shortest_path_distance(graph, "A", "A") - 0 - >>> bfs_shortest_path_distance(graph, "A", "H") - -1 + Args: + graph: node/list of neighboring nodes key/value pairs. + start: node to start search from. + target: node to search for. + + Returns: + Number of edges in shortest path between `start` and `target` nodes. + -1 if no path exists. + + Example: + >>> bfs_shortest_path_distance(graph, "G", "D") + 4 + >>> bfs_shortest_path_distance(graph, "A", "A") + 0 + >>> bfs_shortest_path_distance(graph, "A", "H") + -1 """ if not graph or start not in graph or target not in graph: return -1 diff --git a/graphs/depth_first_search.py b/graphs/depth_first_search.py index 1e2231907b78..9ec7083c7fc1 100644 --- a/graphs/depth_first_search.py +++ b/graphs/depth_first_search.py @@ -17,18 +17,18 @@ def depth_first_search(graph: Dict, start: str) -> Set[int]: """Depth First Search on Graph - :param graph: directed graph in dictionary format - :param vertex: starting vectex as a string - :returns: the trace of the search - >>> G = { "A": ["B", "C", "D"], "B": ["A", "D", "E"], - ... "C": ["A", "F"], "D": ["B", "D"], "E": ["B", "F"], - ... "F": ["C", "E", "G"], "G": ["F"] } - >>> start = "A" - >>> output_G = list({'A', 'B', 'C', 'D', 'E', 'F', 'G'}) - >>> all(x in output_G for x in list(depth_first_search(G, "A"))) - True - >>> all(x in output_G for x in list(depth_first_search(G, "G"))) - True + :param graph: directed graph in dictionary format + :param vertex: starting vectex as a string + :returns: the trace of the search + >>> G = { "A": ["B", "C", "D"], "B": ["A", "D", "E"], + ... "C": ["A", "F"], "D": ["B", "D"], "E": ["B", "F"], + ... "F": ["C", "E", "G"], "G": ["F"] } + >>> start = "A" + >>> output_G = list({'A', 'B', 'C', 'D', 'E', 'F', 'G'}) + >>> all(x in output_G for x in list(depth_first_search(G, "A"))) + True + >>> all(x in output_G for x in list(depth_first_search(G, "G"))) + True """ explored, stack = set(start), [start] while stack: diff --git a/graphs/prim.py b/graphs/prim.py index a1d46a5a12a4..f7376cfb48ca 100644 --- a/graphs/prim.py +++ b/graphs/prim.py @@ -56,14 +56,14 @@ def connect(graph, a, b, edge): def prim(graph: list, root: Vertex) -> list: """Prim's Algorithm. - Runtime: - O(mn) with `m` edges and `n` vertices + Runtime: + O(mn) with `m` edges and `n` vertices - Return: - List with the edges of a Minimum Spanning Tree + Return: + List with the edges of a Minimum Spanning Tree - Usage: - prim(graph, graph[0]) + Usage: + prim(graph, graph[0]) """ a = [] for u in graph: @@ -86,14 +86,14 @@ def prim(graph: list, root: Vertex) -> list: def prim_heap(graph: list, root: Vertex) -> Iterator[tuple]: """Prim's Algorithm with min heap. - Runtime: - O((m + n)log n) with `m` edges and `n` vertices + Runtime: + O((m + n)log n) with `m` edges and `n` vertices - Yield: - Edges of a Minimum Spanning Tree + Yield: + Edges of a Minimum Spanning Tree - Usage: - prim(graph, graph[0]) + Usage: + prim(graph, graph[0]) """ for u in graph: u.key = math.inf diff --git a/hashes/md5.py b/hashes/md5.py index 85565533d175..b7888fb610ac 100644 --- a/hashes/md5.py +++ b/hashes/md5.py @@ -94,9 +94,7 @@ def not32(i): def sum32(a, b): - """ - - """ + """""" return (a + b) % 2 ** 32 diff --git a/linear_algebra/src/lib.py b/linear_algebra/src/lib.py index 10b9da65863f..353c8334093b 100644 --- a/linear_algebra/src/lib.py +++ b/linear_algebra/src/lib.py @@ -26,29 +26,29 @@ class Vector: """ - This class represents a vector of arbitrary size. - You need to give the vector components. - - Overview about the methods: - - constructor(components : list) : init the vector - set(components : list) : changes the vector components. - __str__() : toString method - component(i : int): gets the i-th component (start by 0) - __len__() : gets the size of the vector (number of components) - euclidLength() : returns the euclidean length of the vector. - operator + : vector addition - operator - : vector subtraction - operator * : scalar multiplication and dot product - copy() : copies this vector and returns it. - changeComponent(pos,value) : changes the specified component. - TODO: compare-operator + This class represents a vector of arbitrary size. + You need to give the vector components. + + Overview about the methods: + + constructor(components : list) : init the vector + set(components : list) : changes the vector components. + __str__() : toString method + component(i : int): gets the i-th component (start by 0) + __len__() : gets the size of the vector (number of components) + euclidLength() : returns the euclidean length of the vector. + operator + : vector addition + operator - : vector subtraction + operator * : scalar multiplication and dot product + copy() : copies this vector and returns it. + changeComponent(pos,value) : changes the specified component. + TODO: compare-operator """ def __init__(self, components=None): """ - input: components or nothing - simple constructor for init the vector + input: components or nothing + simple constructor for init the vector """ if components is None: components = [] @@ -56,9 +56,9 @@ def __init__(self, components=None): def set(self, components): """ - input: new components - changes the components of the vector. - replace the components with newer one. + input: new components + changes the components of the vector. + replace the components with newer one. """ if len(components) > 0: self.__components = list(components) @@ -67,14 +67,14 @@ def set(self, components): def __str__(self): """ - returns a string representation of the vector + returns a string representation of the vector """ return "(" + ",".join(map(str, self.__components)) + ")" def component(self, i): """ - input: index (start at 0) - output: the i-th component of the vector. + input: index (start at 0) + output: the i-th component of the vector. """ if type(i) is int and -len(self.__components) <= i < len(self.__components): return self.__components[i] @@ -83,13 +83,13 @@ def component(self, i): def __len__(self): """ - returns the size of the vector + returns the size of the vector """ return len(self.__components) def euclidLength(self): """ - returns the euclidean length of the vector + returns the euclidean length of the vector """ summe = 0 for c in self.__components: @@ -98,9 +98,9 @@ def euclidLength(self): def __add__(self, other): """ - input: other vector - assumes: other vector has the same size - returns a new vector that represents the sum. + input: other vector + assumes: other vector has the same size + returns a new vector that represents the sum. """ size = len(self) if size == len(other): @@ -111,9 +111,9 @@ def __add__(self, other): def __sub__(self, other): """ - input: other vector - assumes: other vector has the same size - returns a new vector that represents the difference. + input: other vector + assumes: other vector has the same size + returns a new vector that represents the difference. """ size = len(self) if size == len(other): @@ -124,8 +124,8 @@ def __sub__(self, other): def __mul__(self, other): """ - mul implements the scalar multiplication - and the dot-product + mul implements the scalar multiplication + and the dot-product """ if isinstance(other, float) or isinstance(other, int): ans = [c * other for c in self.__components] @@ -141,15 +141,15 @@ def __mul__(self, other): def copy(self): """ - copies this vector and returns it. + copies this vector and returns it. """ return Vector(self.__components) def changeComponent(self, pos, value): """ - input: an index (pos) and a value - changes the specified component (pos) with the - 'value' + input: an index (pos) and a value + changes the specified component (pos) with the + 'value' """ # precondition assert -len(self.__components) <= pos < len(self.__components) @@ -158,7 +158,7 @@ def changeComponent(self, pos, value): def zeroVector(dimension): """ - returns a zero-vector of size 'dimension' + returns a zero-vector of size 'dimension' """ # precondition assert isinstance(dimension, int) @@ -167,8 +167,8 @@ def zeroVector(dimension): def unitBasisVector(dimension, pos): """ - returns a unit basis vector with a One - at index 'pos' (indexing at 0) + returns a unit basis vector with a One + at index 'pos' (indexing at 0) """ # precondition assert isinstance(dimension, int) and (isinstance(pos, int)) @@ -179,9 +179,9 @@ def unitBasisVector(dimension, pos): def axpy(scalar, x, y): """ - input: a 'scalar' and two vectors 'x' and 'y' - output: a vector - computes the axpy operation + input: a 'scalar' and two vectors 'x' and 'y' + output: a vector + computes the axpy operation """ # precondition assert ( @@ -194,10 +194,10 @@ def axpy(scalar, x, y): def randomVector(N, a, b): """ - input: size (N) of the vector. - random range (a,b) - output: returns a random vector of size N, with - random integer components between 'a' and 'b'. + input: size (N) of the vector. + random range (a,b) + output: returns a random vector of size N, with + random integer components between 'a' and 'b'. """ random.seed(None) ans = [random.randint(a, b) for i in range(N)] @@ -224,8 +224,8 @@ class Matrix: def __init__(self, matrix, w, h): """ - simple constructor for initializing - the matrix with components. + simple constructor for initializing + the matrix with components. """ self.__matrix = matrix self.__width = w @@ -233,8 +233,8 @@ def __init__(self, matrix, w, h): def __str__(self): """ - returns a string representation of this - matrix. + returns a string representation of this + matrix. """ ans = "" for i in range(self.__height): @@ -248,7 +248,7 @@ def __str__(self): def changeComponent(self, x, y, value): """ - changes the x-y component of this matrix + changes the x-y component of this matrix """ if 0 <= x < self.__height and 0 <= y < self.__width: self.__matrix[x][y] = value @@ -257,7 +257,7 @@ def changeComponent(self, x, y, value): def component(self, x, y): """ - returns the specified (x,y) component + returns the specified (x,y) component """ if 0 <= x < self.__height and 0 <= y < self.__width: return self.__matrix[x][y] @@ -266,19 +266,19 @@ def component(self, x, y): def width(self): """ - getter for the width + getter for the width """ return self.__width def height(self): """ - getter for the height + getter for the height """ return self.__height def determinate(self) -> float: """ - returns the determinate of an nxn matrix using Laplace expansion + returns the determinate of an nxn matrix using Laplace expansion """ if self.__height == self.__width and self.__width >= 2: total = 0 @@ -305,8 +305,8 @@ def determinate(self) -> float: def __mul__(self, other): """ - implements the matrix-vector multiplication. - implements the matrix-scalar multiplication + implements the matrix-vector multiplication. + implements the matrix-scalar multiplication """ if isinstance(other, Vector): # vector-matrix if len(other) == self.__width: @@ -332,7 +332,7 @@ def __mul__(self, other): def __add__(self, other): """ - implements the matrix-addition. + implements the matrix-addition. """ if self.__width == other.width() and self.__height == other.height(): matrix = [] @@ -347,7 +347,7 @@ def __add__(self, other): def __sub__(self, other): """ - implements the matrix-subtraction. + implements the matrix-subtraction. """ if self.__width == other.width() and self.__height == other.height(): matrix = [] @@ -363,7 +363,7 @@ def __sub__(self, other): def squareZeroMatrix(N): """ - returns a square zero-matrix of dimension NxN + returns a square zero-matrix of dimension NxN """ ans = [[0] * N for i in range(N)] return Matrix(ans, N, N) @@ -371,8 +371,8 @@ def squareZeroMatrix(N): def randomMatrix(W, H, a, b): """ - returns a random matrix WxH with integer components - between 'a' and 'b' + returns a random matrix WxH with integer components + between 'a' and 'b' """ random.seed(None) matrix = [[random.randint(a, b) for j in range(W)] for i in range(H)] diff --git a/linear_algebra/src/test_linear_algebra.py b/linear_algebra/src/test_linear_algebra.py index 8db480ceb29d..668ffe858b99 100644 --- a/linear_algebra/src/test_linear_algebra.py +++ b/linear_algebra/src/test_linear_algebra.py @@ -14,7 +14,7 @@ class Test(unittest.TestCase): def test_component(self): """ - test for method component + test for method component """ x = Vector([1, 2, 3]) self.assertEqual(x.component(0), 1) @@ -23,28 +23,28 @@ def test_component(self): def test_str(self): """ - test for toString() method + test for toString() method """ x = Vector([0, 0, 0, 0, 0, 1]) self.assertEqual(str(x), "(0,0,0,0,0,1)") def test_size(self): """ - test for size()-method + test for size()-method """ x = Vector([1, 2, 3, 4]) self.assertEqual(len(x), 4) def test_euclidLength(self): """ - test for the eulidean length + test for the eulidean length """ x = Vector([1, 2]) self.assertAlmostEqual(x.euclidLength(), 2.236, 3) def test_add(self): """ - test for + operator + test for + operator """ x = Vector([1, 2, 3]) y = Vector([1, 1, 1]) @@ -54,7 +54,7 @@ def test_add(self): def test_sub(self): """ - test for - operator + test for - operator """ x = Vector([1, 2, 3]) y = Vector([1, 1, 1]) @@ -64,7 +64,7 @@ def test_sub(self): def test_mul(self): """ - test for * operator + test for * operator """ x = Vector([1, 2, 3]) a = Vector([2, -1, 4]) # for test of dot-product @@ -74,19 +74,19 @@ def test_mul(self): def test_zeroVector(self): """ - test for the global function zeroVector(...) + test for the global function zeroVector(...) """ self.assertTrue(str(zeroVector(10)).count("0") == 10) def test_unitBasisVector(self): """ - test for the global function unitBasisVector(...) + test for the global function unitBasisVector(...) """ self.assertEqual(str(unitBasisVector(3, 1)), "(0,1,0)") def test_axpy(self): """ - test for the global function axpy(...) (operation) + test for the global function axpy(...) (operation) """ x = Vector([1, 2, 3]) y = Vector([1, 0, 1]) @@ -94,7 +94,7 @@ def test_axpy(self): def test_copy(self): """ - test for the copy()-method + test for the copy()-method """ x = Vector([1, 0, 0, 0, 0, 0]) y = x.copy() @@ -102,7 +102,7 @@ def test_copy(self): def test_changeComponent(self): """ - test for the changeComponent(...)-method + test for the changeComponent(...)-method """ x = Vector([1, 0, 0]) x.changeComponent(0, 0) @@ -115,7 +115,7 @@ def test_str_matrix(self): def test_determinate(self): """ - test for determinate() + test for determinate() """ A = Matrix([[1, 1, 4, 5], [3, 3, 3, 2], [5, 1, 9, 0], [9, 7, 7, 9]], 4, 4) self.assertEqual(-376, A.determinate()) diff --git a/machine_learning/decision_tree.py b/machine_learning/decision_tree.py index fe1d54736563..ace6fb0fa883 100644 --- a/machine_learning/decision_tree.py +++ b/machine_learning/decision_tree.py @@ -135,8 +135,7 @@ def predict(self, x): class Test_Decision_Tree: - """Decision Tres test class - """ + """Decision Tres test class""" @staticmethod def helper_mean_squared_error_test(labels, prediction): diff --git a/machine_learning/k_means_clust.py b/machine_learning/k_means_clust.py index 4da904ae3568..130e7f1ad669 100644 --- a/machine_learning/k_means_clust.py +++ b/machine_learning/k_means_clust.py @@ -132,12 +132,12 @@ def kmeans( data, k, initial_centroids, maxiter=500, record_heterogeneity=None, verbose=False ): """This function runs k-means on given data and initial set of centroids. - maxiter: maximum number of iterations to run.(default=500) - record_heterogeneity: (optional) a list, to store the history of heterogeneity - as function of iterations - if None, do not store the history. - verbose: if True, print how many data points changed their cluster labels in - each iteration""" + maxiter: maximum number of iterations to run.(default=500) + record_heterogeneity: (optional) a list, to store the history of heterogeneity + as function of iterations + if None, do not store the history. + verbose: if True, print how many data points changed their cluster labels in + each iteration""" centroids = initial_centroids[:] prev_cluster_assignment = None diff --git a/machine_learning/linear_discriminant_analysis.py b/machine_learning/linear_discriminant_analysis.py index b26da2628982..22ee63a5a62b 100644 --- a/machine_learning/linear_discriminant_analysis.py +++ b/machine_learning/linear_discriminant_analysis.py @@ -153,7 +153,7 @@ def calculate_variance(items: list, means: list, total_count: int) -> float: def predict_y_values( x_items: list, means: list, variance: float, probabilities: list ) -> list: - """ This function predicts new indexes(groups for our data) + """This function predicts new indexes(groups for our data) :param x_items: a list containing all items(gaussian distribution of all classes) :param means: a list containing real mean values of each class :param variance: calculated value of variance by calculate_variance function diff --git a/machine_learning/linear_regression.py b/machine_learning/linear_regression.py index 8c0dfebbca9d..a726629efe00 100644 --- a/machine_learning/linear_regression.py +++ b/machine_learning/linear_regression.py @@ -12,7 +12,7 @@ def collect_dataset(): - """ Collect dataset of CSGO + """Collect dataset of CSGO The dataset contains ADR vs Rating of a Player :return : dataset obtained from the link, as matrix """ @@ -32,7 +32,7 @@ def collect_dataset(): def run_steep_gradient_descent(data_x, data_y, len_data, alpha, theta): - """ Run steep gradient descent and updates the Feature vector accordingly_ + """Run steep gradient descent and updates the Feature vector accordingly_ :param data_x : contains the dataset :param data_y : contains the output associated with each data-entry :param len_data : length of the data_ @@ -51,7 +51,7 @@ def run_steep_gradient_descent(data_x, data_y, len_data, alpha, theta): def sum_of_square_error(data_x, data_y, len_data, theta): - """ Return sum of square error for error calculation + """Return sum of square error for error calculation :param data_x : contains our dataset :param data_y : contains the output (result vector) :param len_data : len of the dataset @@ -66,7 +66,7 @@ def sum_of_square_error(data_x, data_y, len_data, theta): def run_linear_regression(data_x, data_y): - """ Implement Linear regression over the dataset + """Implement Linear regression over the dataset :param data_x : contains our dataset :param data_y : contains the output (result vector) :return : feature for line of best fit (Feature vector) diff --git a/maths/kth_lexicographic_permutation.py b/maths/kth_lexicographic_permutation.py index 491c1c84fa85..23eab626fbf8 100644 --- a/maths/kth_lexicographic_permutation.py +++ b/maths/kth_lexicographic_permutation.py @@ -1,18 +1,18 @@ def kthPermutation(k, n): """ - Finds k'th lexicographic permutation (in increasing order) of - 0,1,2,...n-1 in O(n^2) time. - - Examples: - First permutation is always 0,1,2,...n - >>> kthPermutation(0,5) - [0, 1, 2, 3, 4] - - The order of permutation of 0,1,2,3 is [0,1,2,3], [0,1,3,2], [0,2,1,3], - [0,2,3,1], [0,3,1,2], [0,3,2,1], [1,0,2,3], [1,0,3,2], [1,2,0,3], - [1,2,3,0], [1,3,0,2] - >>> kthPermutation(10,4) - [1, 3, 0, 2] + Finds k'th lexicographic permutation (in increasing order) of + 0,1,2,...n-1 in O(n^2) time. + + Examples: + First permutation is always 0,1,2,...n + >>> kthPermutation(0,5) + [0, 1, 2, 3, 4] + + The order of permutation of 0,1,2,3 is [0,1,2,3], [0,1,3,2], [0,2,1,3], + [0,2,3,1], [0,3,1,2], [0,3,2,1], [1,0,2,3], [1,0,3,2], [1,2,0,3], + [1,2,3,0], [1,3,0,2] + >>> kthPermutation(10,4) + [1, 3, 0, 2] """ # Factorails from 1! to (n-1)! factorials = [1] diff --git a/maths/newton_raphson.py b/maths/newton_raphson.py index d8a98dfa6703..f2b7cb9766d2 100644 --- a/maths/newton_raphson.py +++ b/maths/newton_raphson.py @@ -12,8 +12,8 @@ def calc_derivative(f, a, h=0.001): """ - Calculates derivative at point a for function f using finite difference - method + Calculates derivative at point a for function f using finite difference + method """ return (f(a + h) - f(a - h)) / (2 * h) diff --git a/maths/prime_sieve_eratosthenes.py b/maths/prime_sieve_eratosthenes.py index 0ebdfdb94e15..da3ae472ce23 100644 --- a/maths/prime_sieve_eratosthenes.py +++ b/maths/prime_sieve_eratosthenes.py @@ -19,9 +19,9 @@ def prime_sieve_eratosthenes(num): print the prime numbers up to n >>> prime_sieve_eratosthenes(10) - 2 3 5 7 + 2,3,5,7, >>> prime_sieve_eratosthenes(20) - 2 3 5 7 11 13 17 19 + 2,3,5,7,11,13,17,19, """ primes = [True for i in range(num + 1)] @@ -35,10 +35,13 @@ def prime_sieve_eratosthenes(num): for prime in range(2, num + 1): if primes[prime]: - print(prime, end=" ") + print(prime, end=",") if __name__ == "__main__": + import doctest + + doctest.testmod() num = int(input()) prime_sieve_eratosthenes(num) diff --git a/maths/relu.py b/maths/relu.py index 89667ab3e73f..826ada65fa16 100644 --- a/maths/relu.py +++ b/maths/relu.py @@ -16,20 +16,20 @@ def relu(vector: List[float]): """ - Implements the relu function + Implements the relu function - Parameters: - vector (np.array,list,tuple): A numpy array of shape (1,n) - consisting of real values or a similar list,tuple + Parameters: + vector (np.array,list,tuple): A numpy array of shape (1,n) + consisting of real values or a similar list,tuple - Returns: - relu_vec (np.array): The input numpy array, after applying - relu. + Returns: + relu_vec (np.array): The input numpy array, after applying + relu. - >>> vec = np.array([-1, 0, 5]) - >>> relu(vec) - array([0, 0, 5]) + >>> vec = np.array([-1, 0, 5]) + >>> relu(vec) + array([0, 0, 5]) """ # compare two arrays and then return element-wise maxima. diff --git a/maths/softmax.py b/maths/softmax.py index 92ff4ca27b88..e021a7f8a6fe 100644 --- a/maths/softmax.py +++ b/maths/softmax.py @@ -15,28 +15,28 @@ def softmax(vector): """ - Implements the softmax function + Implements the softmax function - Parameters: - vector (np.array,list,tuple): A numpy array of shape (1,n) - consisting of real values or a similar list,tuple + Parameters: + vector (np.array,list,tuple): A numpy array of shape (1,n) + consisting of real values or a similar list,tuple - Returns: - softmax_vec (np.array): The input numpy array after applying - softmax. + Returns: + softmax_vec (np.array): The input numpy array after applying + softmax. - The softmax vector adds up to one. We need to ceil to mitigate for - precision - >>> np.ceil(np.sum(softmax([1,2,3,4]))) - 1.0 + The softmax vector adds up to one. We need to ceil to mitigate for + precision + >>> np.ceil(np.sum(softmax([1,2,3,4]))) + 1.0 - >>> vec = np.array([5,5]) - >>> softmax(vec) - array([0.5, 0.5]) + >>> vec = np.array([5,5]) + >>> softmax(vec) + array([0.5, 0.5]) - >>> softmax([0]) - array([1.]) + >>> softmax([0]) + array([1.]) """ # Calculate e^x for each x in your vector where e is Euler's diff --git a/maths/sum_of_geometric_progression.py b/maths/sum_of_geometric_progression.py index 614d6646ec43..f29dd8005cff 100644 --- a/maths/sum_of_geometric_progression.py +++ b/maths/sum_of_geometric_progression.py @@ -1,7 +1,7 @@ def sum_of_geometric_progression( first_term: int, common_ratio: int, num_of_terms: int ) -> float: - """" + """ " Return the sum of n terms in a geometric progression. >>> sum_of_geometric_progression(1, 2, 10) 1023.0 diff --git a/maths/zellers_congruence.py b/maths/zellers_congruence.py index 8608b32f3ee3..2d4a22a0a5ba 100644 --- a/maths/zellers_congruence.py +++ b/maths/zellers_congruence.py @@ -63,8 +63,7 @@ def zeller(date_input: str) -> str: >>> zeller('01-31-19082939') Traceback (most recent call last): ... - ValueError: Must be 10 characters long -""" + ValueError: Must be 10 characters long""" # Days of the week for response days = { diff --git a/matrix/matrix_operation.py b/matrix/matrix_operation.py index 42a94da12375..3838dab6be09 100644 --- a/matrix/matrix_operation.py +++ b/matrix/matrix_operation.py @@ -168,7 +168,9 @@ def main(): matrix_c = [[11, 12, 13, 14], [21, 22, 23, 24], [31, 32, 33, 34], [41, 42, 43, 44]] matrix_d = [[3, 0, 2], [2, 0, -2], [0, 1, 1]] print(f"Add Operation, {add(matrix_a, matrix_b) = } \n") - print(f"Multiply Operation, {multiply(matrix_a, matrix_b) = } \n",) + print( + f"Multiply Operation, {multiply(matrix_a, matrix_b) = } \n", + ) print(f"Identity: {identity(5)}\n") print(f"Minor of {matrix_c} = {minor(matrix_c, 1, 2)} \n") print(f"Determinant of {matrix_b} = {determinant(matrix_b)} \n") diff --git a/other/activity_selection.py b/other/activity_selection.py index 8876eb2930fc..c03956cce5d2 100644 --- a/other/activity_selection.py +++ b/other/activity_selection.py @@ -16,14 +16,14 @@ def printMaxActivities(start, finish): >>> finish = [2, 4, 6, 7, 9, 9] >>> printMaxActivities(start, finish) The following activities are selected: - 0 1 3 4 + 0,1,3,4, """ n = len(finish) print("The following activities are selected:") # The first activity is always selected i = 0 - print(i, end=" ") + print(i, end=",") # Consider rest of the activities for j in range(n): @@ -32,16 +32,15 @@ def printMaxActivities(start, finish): # or equal to the finish time of previously # selected activity, then select it if start[j] >= finish[i]: - print(j, end=" ") + print(j, end=",") i = j -# Driver program to test above function -start = [1, 3, 0, 5, 8, 5] -finish = [2, 4, 6, 7, 9, 9] -printMaxActivities(start, finish) +if __name__ == "__main__": + import doctest -""" -The following activities are selected: -0 1 3 4 -""" + doctest.testmod() + + start = [1, 3, 0, 5, 8, 5] + finish = [2, 4, 6, 7, 9, 9] + printMaxActivities(start, finish) diff --git a/other/game_of_life.py b/other/game_of_life.py index 651467969fad..09863993dc3a 100644 --- a/other/game_of_life.py +++ b/other/game_of_life.py @@ -52,7 +52,7 @@ def seed(canvas): def run(canvas): - """ This function runs the rules of game through all points, and changes their + """This function runs the rules of game through all points, and changes their status accordingly.(in the same canvas) @Args: -- diff --git a/other/least_recently_used.py b/other/least_recently_used.py index 1b901d544b8d..213339636469 100644 --- a/other/least_recently_used.py +++ b/other/least_recently_used.py @@ -12,7 +12,7 @@ class LRUCache: @abstractmethod def __init__(self, n: int): - """ Creates an empty store and map for the keys. + """Creates an empty store and map for the keys. The LRUCache is set the size n. """ self.dq_store = deque() @@ -26,9 +26,9 @@ def __init__(self, n: int): def refer(self, x): """ - Looks for a page in the cache store and adds reference to the set. - Remove the least recently used key if the store is full. - Update store to reflect recent access. + Looks for a page in the cache store and adds reference to the set. + Remove the least recently used key if the store is full. + Update store to reflect recent access. """ if x not in self.key_reference_map: if len(self.dq_store) == LRUCache._MAX_CAPACITY: @@ -47,7 +47,7 @@ def refer(self, x): def display(self): """ - Prints all the elements in the store. + Prints all the elements in the store. """ for k in self.dq_store: print(k) diff --git a/other/magicdiamondpattern.py b/other/magicdiamondpattern.py index 37b5e4809f47..71bc50b51fc2 100644 --- a/other/magicdiamondpattern.py +++ b/other/magicdiamondpattern.py @@ -6,7 +6,7 @@ def floyd(n): """ Parameters: n : size of pattern - """ + """ for i in range(0, n): for j in range(0, n - i - 1): # printing spaces print(" ", end="") @@ -20,7 +20,7 @@ def reverse_floyd(n): """ Parameters: n : size of pattern - """ + """ for i in range(n, 0, -1): for j in range(i, 0, -1): # printing stars print("* ", end="") @@ -34,7 +34,7 @@ def pretty_print(n): """ Parameters: n : size of pattern - """ + """ if n <= 0: print(" ... .... nothing printing :(") return diff --git a/other/primelib.py b/other/primelib.py index a6d1d7dfb324..37883d9cf591 100644 --- a/other/primelib.py +++ b/other/primelib.py @@ -43,8 +43,8 @@ def isPrime(number): """ - input: positive integer 'number' - returns true if 'number' is prime otherwise false. + input: positive integer 'number' + returns true if 'number' is prime otherwise false. """ # precondition @@ -77,11 +77,11 @@ def isPrime(number): def sieveEr(N): """ - input: positive integer 'N' > 2 - returns a list of prime numbers from 2 up to N. + input: positive integer 'N' > 2 + returns a list of prime numbers from 2 up to N. - This function implements the algorithm called - sieve of erathostenes. + This function implements the algorithm called + sieve of erathostenes. """ @@ -115,9 +115,9 @@ def sieveEr(N): def getPrimeNumbers(N): """ - input: positive integer 'N' > 2 - returns a list of prime numbers from 2 up to N (inclusive) - This function is more efficient as function 'sieveEr(...)' + input: positive integer 'N' > 2 + returns a list of prime numbers from 2 up to N (inclusive) + This function is more efficient as function 'sieveEr(...)' """ # precondition @@ -144,8 +144,8 @@ def getPrimeNumbers(N): def primeFactorization(number): """ - input: positive integer 'number' - returns a list of the prime number factors of 'number' + input: positive integer 'number' + returns a list of the prime number factors of 'number' """ # precondition @@ -188,8 +188,8 @@ def primeFactorization(number): def greatestPrimeFactor(number): """ - input: positive integer 'number' >= 0 - returns the greatest prime number factor of 'number' + input: positive integer 'number' >= 0 + returns the greatest prime number factor of 'number' """ # precondition @@ -215,8 +215,8 @@ def greatestPrimeFactor(number): def smallestPrimeFactor(number): """ - input: integer 'number' >= 0 - returns the smallest prime number factor of 'number' + input: integer 'number' >= 0 + returns the smallest prime number factor of 'number' """ # precondition @@ -242,8 +242,8 @@ def smallestPrimeFactor(number): def isEven(number): """ - input: integer 'number' - returns true if 'number' is even, otherwise false. + input: integer 'number' + returns true if 'number' is even, otherwise false. """ # precondition @@ -258,8 +258,8 @@ def isEven(number): def isOdd(number): """ - input: integer 'number' - returns true if 'number' is odd, otherwise false. + input: integer 'number' + returns true if 'number' is odd, otherwise false. """ # precondition @@ -274,9 +274,9 @@ def isOdd(number): def goldbach(number): """ - Goldbach's assumption - input: a even positive integer 'number' > 2 - returns a list of two prime numbers whose sum is equal to 'number' + Goldbach's assumption + input: a even positive integer 'number' > 2 + returns a list of two prime numbers whose sum is equal to 'number' """ # precondition @@ -329,9 +329,9 @@ def goldbach(number): def gcd(number1, number2): """ - Greatest common divisor - input: two positive integer 'number1' and 'number2' - returns the greatest common divisor of 'number1' and 'number2' + Greatest common divisor + input: two positive integer 'number1' and 'number2' + returns the greatest common divisor of 'number1' and 'number2' """ # precondition @@ -363,9 +363,9 @@ def gcd(number1, number2): def kgV(number1, number2): """ - Least common multiple - input: two positive integer 'number1' and 'number2' - returns the least common multiple of 'number1' and 'number2' + Least common multiple + input: two positive integer 'number1' and 'number2' + returns the least common multiple of 'number1' and 'number2' """ # precondition @@ -443,9 +443,9 @@ def kgV(number1, number2): def getPrime(n): """ - Gets the n-th prime number. - input: positive integer 'n' >= 0 - returns the n-th prime number, beginning at index 0 + Gets the n-th prime number. + input: positive integer 'n' >= 0 + returns the n-th prime number, beginning at index 0 """ # precondition @@ -478,10 +478,10 @@ def getPrime(n): def getPrimesBetween(pNumber1, pNumber2): """ - input: prime numbers 'pNumber1' and 'pNumber2' - pNumber1 < pNumber2 - returns a list of all prime numbers between 'pNumber1' (exclusive) - and 'pNumber2' (exclusive) + input: prime numbers 'pNumber1' and 'pNumber2' + pNumber1 < pNumber2 + returns a list of all prime numbers between 'pNumber1' (exclusive) + and 'pNumber2' (exclusive) """ # precondition @@ -522,8 +522,8 @@ def getPrimesBetween(pNumber1, pNumber2): def getDivisors(n): """ - input: positive integer 'n' >= 1 - returns all divisors of n (inclusive 1 and 'n') + input: positive integer 'n' >= 1 + returns all divisors of n (inclusive 1 and 'n') """ # precondition @@ -547,8 +547,8 @@ def getDivisors(n): def isPerfectNumber(number): """ - input: positive integer 'number' > 1 - returns true if 'number' is a perfect number otherwise false. + input: positive integer 'number' > 1 + returns true if 'number' is a perfect number otherwise false. """ # precondition @@ -574,9 +574,9 @@ def isPerfectNumber(number): def simplifyFraction(numerator, denominator): """ - input: two integer 'numerator' and 'denominator' - assumes: 'denominator' != 0 - returns: a tuple with simplify numerator and denominator. + input: two integer 'numerator' and 'denominator' + assumes: 'denominator' != 0 + returns: a tuple with simplify numerator and denominator. """ # precondition @@ -604,8 +604,8 @@ def simplifyFraction(numerator, denominator): def factorial(n): """ - input: positive integer 'n' - returns the factorial of 'n' (n!) + input: positive integer 'n' + returns the factorial of 'n' (n!) """ # precondition @@ -624,8 +624,8 @@ def factorial(n): def fib(n): """ - input: positive integer 'n' - returns the n-th fibonacci term , indexing by 0 + input: positive integer 'n' + returns the n-th fibonacci term , indexing by 0 """ # precondition diff --git a/project_euler/problem_09/sol2.py b/project_euler/problem_09/sol2.py index de7b12d40c09..d16835ca09a2 100644 --- a/project_euler/problem_09/sol2.py +++ b/project_euler/problem_09/sol2.py @@ -23,8 +23,7 @@ def solution(n): product = -1 d = 0 for a in range(1, n // 3): - """Solving the two equations a**2+b**2=c**2 and a+b+c=N eliminating c - """ + """Solving the two equations a**2+b**2=c**2 and a+b+c=N eliminating c""" b = (n * n - 2 * a * n) // (2 * n - 2 * a) c = n - a - b if c * c == (a * a + b * b): diff --git a/project_euler/problem_10/sol3.py b/project_euler/problem_10/sol3.py index e5bc0731d8ab..739aaa9f16bb 100644 --- a/project_euler/problem_10/sol3.py +++ b/project_euler/problem_10/sol3.py @@ -12,7 +12,7 @@ def prime_sum(n: int) -> int: - """ Returns the sum of all the primes below n. + """Returns the sum of all the primes below n. >>> prime_sum(2_000_000) 142913828922 diff --git a/project_euler/problem_15/sol1.py b/project_euler/problem_15/sol1.py index 1be7d10ed674..feeb3ddab57a 100644 --- a/project_euler/problem_15/sol1.py +++ b/project_euler/problem_15/sol1.py @@ -8,31 +8,31 @@ def lattice_paths(n): """ - Returns the number of paths possible in a n x n grid starting at top left - corner going to bottom right corner and being able to move right and down - only. - -bruno@bruno-laptop:~/git/Python/project_euler/problem_15$ python3 sol1.py 50 -1.008913445455642e+29 -bruno@bruno-laptop:~/git/Python/project_euler/problem_15$ python3 sol1.py 25 -126410606437752.0 -bruno@bruno-laptop:~/git/Python/project_euler/problem_15$ python3 sol1.py 23 -8233430727600.0 -bruno@bruno-laptop:~/git/Python/project_euler/problem_15$ python3 sol1.py 15 -155117520.0 -bruno@bruno-laptop:~/git/Python/project_euler/problem_15$ python3 sol1.py 1 -2.0 - - >>> lattice_paths(25) - 126410606437752 - >>> lattice_paths(23) - 8233430727600 - >>> lattice_paths(20) - 137846528820 - >>> lattice_paths(15) - 155117520 - >>> lattice_paths(1) - 2 + Returns the number of paths possible in a n x n grid starting at top left + corner going to bottom right corner and being able to move right and down + only. + + bruno@bruno-laptop:~/git/Python/project_euler/problem_15$ python3 sol1.py 50 + 1.008913445455642e+29 + bruno@bruno-laptop:~/git/Python/project_euler/problem_15$ python3 sol1.py 25 + 126410606437752.0 + bruno@bruno-laptop:~/git/Python/project_euler/problem_15$ python3 sol1.py 23 + 8233430727600.0 + bruno@bruno-laptop:~/git/Python/project_euler/problem_15$ python3 sol1.py 15 + 155117520.0 + bruno@bruno-laptop:~/git/Python/project_euler/problem_15$ python3 sol1.py 1 + 2.0 + + >>> lattice_paths(25) + 126410606437752 + >>> lattice_paths(23) + 8233430727600 + >>> lattice_paths(20) + 137846528820 + >>> lattice_paths(15) + 155117520 + >>> lattice_paths(1) + 2 """ n = 2 * n # middle entry of odd rows starting at row 3 is the solution for n = 1, diff --git a/project_euler/problem_27/problem_27_sol1.py b/project_euler/problem_27/problem_27_sol1.py index 84b007a0bc88..e4833574c509 100644 --- a/project_euler/problem_27/problem_27_sol1.py +++ b/project_euler/problem_27/problem_27_sol1.py @@ -39,17 +39,17 @@ def is_prime(k: int) -> bool: def solution(a_limit: int, b_limit: int) -> int: """ - >>> solution(1000, 1000) - -59231 - >>> solution(200, 1000) - -59231 - >>> solution(200, 200) - -4925 - >>> solution(-1000, 1000) - 0 - >>> solution(-1000, -1000) - 0 - """ + >>> solution(1000, 1000) + -59231 + >>> solution(200, 1000) + -59231 + >>> solution(200, 200) + -4925 + >>> solution(-1000, 1000) + 0 + >>> solution(-1000, -1000) + 0 + """ longest = [0, 0, 0] # length, a, b for a in range((a_limit * -1) + 1, a_limit): for b in range(2, b_limit): diff --git a/project_euler/problem_56/sol1.py b/project_euler/problem_56/sol1.py index 2a00fa6a195d..98094ea8eb28 100644 --- a/project_euler/problem_56/sol1.py +++ b/project_euler/problem_56/sol1.py @@ -1,18 +1,18 @@ def maximum_digital_sum(a: int, b: int) -> int: """ - Considering natural numbers of the form, a**b, where a, b < 100, - what is the maximum digital sum? - :param a: - :param b: - :return: - >>> maximum_digital_sum(10,10) - 45 + Considering natural numbers of the form, a**b, where a, b < 100, + what is the maximum digital sum? + :param a: + :param b: + :return: + >>> maximum_digital_sum(10,10) + 45 - >>> maximum_digital_sum(100,100) - 972 + >>> maximum_digital_sum(100,100) + 972 - >>> maximum_digital_sum(100,200) - 1872 + >>> maximum_digital_sum(100,200) + 1872 """ # RETURN the MAXIMUM from the list of SUMs of the list of INT converted from STR of diff --git a/sorts/merge_sort.py b/sorts/merge_sort.py index 572f38a57029..4da29f32a36d 100644 --- a/sorts/merge_sort.py +++ b/sorts/merge_sort.py @@ -29,11 +29,13 @@ def merge(left: list, right: list) -> list: :param right: right collection :return: merge result """ + def _merge(): while left and right: yield (left if left[0] <= right[0] else right).pop(0) yield from left yield from right + return list(_merge()) if len(collection) <= 1: @@ -44,6 +46,7 @@ def _merge(): if __name__ == "__main__": import doctest + doctest.testmod() user_input = input("Enter numbers separated by a comma:\n").strip() unsorted = [int(item) for item in user_input.split(",")] diff --git a/sorts/recursive_bubble_sort.py b/sorts/recursive_bubble_sort.py index 79d706e6164d..82af89593e5b 100644 --- a/sorts/recursive_bubble_sort.py +++ b/sorts/recursive_bubble_sort.py @@ -1,41 +1,42 @@ -def bubble_sort(list1): +def bubble_sort(list_data: list, length: int = 0) -> list: """ It is similar is bubble sort but recursive. - :param list1: mutable ordered sequence of elements + :param list_data: mutable ordered sequence of elements + :param length: length of list data :return: the same list in ascending order - >>> bubble_sort([0, 5, 2, 3, 2]) + >>> bubble_sort([0, 5, 2, 3, 2], 5) [0, 2, 2, 3, 5] - >>> bubble_sort([]) + >>> bubble_sort([], 0) [] - >>> bubble_sort([-2, -45, -5]) + >>> bubble_sort([-2, -45, -5], 3) [-45, -5, -2] - >>> bubble_sort([-23, 0, 6, -4, 34]) + >>> bubble_sort([-23, 0, 6, -4, 34], 5) [-23, -4, 0, 6, 34] - >>> bubble_sort([-23, 0, 6, -4, 34]) == sorted([-23, 0, 6, -4, 34]) + >>> bubble_sort([-23, 0, 6, -4, 34], 5) == sorted([-23, 0, 6, -4, 34]) True - >>> bubble_sort(['z','a','y','b','x','c']) + >>> bubble_sort(['z','a','y','b','x','c'], 6) ['a', 'b', 'c', 'x', 'y', 'z'] + >>> bubble_sort([1.1, 3.3, 5.5, 7.7, 2.2, 4.4, 6.6]) + [1.1, 2.2, 3.3, 4.4, 5.5, 6.6, 7.7] """ + length = length or len(list_data) + swapped = False + for i in range(length - 1): + if list_data[i] > list_data[i + 1]: + list_data[i], list_data[i + 1] = list_data[i + 1], list_data[i] + swapped = True - for i, num in enumerate(list1): - try: - if list1[i + 1] < num: - list1[i] = list1[i + 1] - list1[i + 1] = num - bubble_sort(list1) - except IndexError: - pass - return list1 + return list_data if not swapped else bubble_sort(list_data, length - 1) if __name__ == "__main__": - list1 = [33, 99, 22, 11, 66] - bubble_sort(list1) - print(list1) + import doctest + + doctest.testmod() diff --git a/strings/boyer_moore_search.py b/strings/boyer_moore_search.py index f340855d7a05..9d32a6943906 100644 --- a/strings/boyer_moore_search.py +++ b/strings/boyer_moore_search.py @@ -25,7 +25,7 @@ def __init__(self, text, pattern): self.textLen, self.patLen = len(text), len(pattern) def match_in_pattern(self, char): - """ finds the index of char in pattern in reverse order + """finds the index of char in pattern in reverse order Parameters : char (chr): character to be searched diff --git a/strings/capitalize.py b/strings/capitalize.py index 2a84a325bca4..63603aa07e2d 100644 --- a/strings/capitalize.py +++ b/strings/capitalize.py @@ -16,7 +16,7 @@ def capitalize(sentence: str) -> str: '' """ if not sentence: - return '' + return "" lower_to_upper = {lc: uc for lc, uc in zip(ascii_lowercase, ascii_uppercase)} return lower_to_upper.get(sentence[0], sentence[0]) + sentence[1:] diff --git a/traversals/binary_tree_traversals.py b/traversals/binary_tree_traversals.py index 7f100e66f200..50cdd5af72d3 100644 --- a/traversals/binary_tree_traversals.py +++ b/traversals/binary_tree_traversals.py @@ -53,11 +53,11 @@ def pre_order(node: TreeNode) -> None: >>> tree_node2.left, tree_node2.right = tree_node4 , tree_node5 >>> tree_node3.left, tree_node3.right = tree_node6 , tree_node7 >>> pre_order(root) - 1 2 4 5 3 6 7 + 1,2,4,5,3,6,7, """ if not isinstance(node, TreeNode) or not node: return - print(node.data, end=" ") + print(node.data, end=",") pre_order(node.left) pre_order(node.right) @@ -75,12 +75,12 @@ def in_order(node: TreeNode) -> None: >>> tree_node2.left, tree_node2.right = tree_node4 , tree_node5 >>> tree_node3.left, tree_node3.right = tree_node6 , tree_node7 >>> in_order(root) - 4 2 5 1 6 3 7 + 4,2,5,1,6,3,7, """ if not isinstance(node, TreeNode) or not node: return in_order(node.left) - print(node.data, end=" ") + print(node.data, end=",") in_order(node.right) @@ -97,13 +97,13 @@ def post_order(node: TreeNode) -> None: >>> tree_node2.left, tree_node2.right = tree_node4 , tree_node5 >>> tree_node3.left, tree_node3.right = tree_node6 , tree_node7 >>> post_order(root) - 4 5 2 6 7 3 1 + 4,5,2,6,7,3,1, """ if not isinstance(node, TreeNode) or not node: return post_order(node.left) post_order(node.right) - print(node.data, end=" ") + print(node.data, end=",") def level_order(node: TreeNode) -> None: @@ -119,7 +119,7 @@ def level_order(node: TreeNode) -> None: >>> tree_node2.left, tree_node2.right = tree_node4 , tree_node5 >>> tree_node3.left, tree_node3.right = tree_node6 , tree_node7 >>> level_order(root) - 1 2 3 4 5 6 7 + 1,2,3,4,5,6,7, """ if not isinstance(node, TreeNode) or not node: return @@ -127,7 +127,7 @@ def level_order(node: TreeNode) -> None: q.put(node) while not q.empty(): node_dequeued = q.get() - print(node_dequeued.data, end=" ") + print(node_dequeued.data, end=",") if node_dequeued.left: q.put(node_dequeued.left) if node_dequeued.right: @@ -146,10 +146,10 @@ def level_order_actual(node: TreeNode) -> None: >>> root.left, root.right = tree_node2, tree_node3 >>> tree_node2.left, tree_node2.right = tree_node4 , tree_node5 >>> tree_node3.left, tree_node3.right = tree_node6 , tree_node7 - >>> level_order_actual(root) - 1 - 2 3 - 4 5 6 7 + >>> level_order_actual(root) + 1, + 2,3, + 4,5,6,7, """ if not isinstance(node, TreeNode) or not node: return @@ -159,7 +159,7 @@ def level_order_actual(node: TreeNode) -> None: list = [] while not q.empty(): node_dequeued = q.get() - print(node_dequeued.data, end=" ") + print(node_dequeued.data, end=",") if node_dequeued.left: list.append(node_dequeued.left) if node_dequeued.right: @@ -183,7 +183,7 @@ def pre_order_iter(node: TreeNode) -> None: >>> tree_node2.left, tree_node2.right = tree_node4 , tree_node5 >>> tree_node3.left, tree_node3.right = tree_node6 , tree_node7 >>> pre_order_iter(root) - 1 2 4 5 3 6 7 + 1,2,4,5,3,6,7, """ if not isinstance(node, TreeNode) or not node: return @@ -191,7 +191,7 @@ def pre_order_iter(node: TreeNode) -> None: n = node while n or stack: while n: # start from root node, find its left child - print(n.data, end=" ") + print(n.data, end=",") stack.append(n) n = n.left # end of while means current node doesn't have left child @@ -213,7 +213,7 @@ def in_order_iter(node: TreeNode) -> None: >>> tree_node2.left, tree_node2.right = tree_node4 , tree_node5 >>> tree_node3.left, tree_node3.right = tree_node6 , tree_node7 >>> in_order_iter(root) - 4 2 5 1 6 3 7 + 4,2,5,1,6,3,7, """ if not isinstance(node, TreeNode) or not node: return @@ -224,7 +224,7 @@ def in_order_iter(node: TreeNode) -> None: stack.append(n) n = n.left n = stack.pop() - print(n.data, end=" ") + print(n.data, end=",") n = n.right @@ -241,7 +241,7 @@ def post_order_iter(node: TreeNode) -> None: >>> tree_node2.left, tree_node2.right = tree_node4 , tree_node5 >>> tree_node3.left, tree_node3.right = tree_node6 , tree_node7 >>> post_order_iter(root) - 4 5 2 6 7 3 1 + 4,5,2,6,7,3,1, """ if not isinstance(node, TreeNode) or not node: return @@ -256,7 +256,7 @@ def post_order_iter(node: TreeNode) -> None: stack1.append(n.right) stack2.append(n) while stack2: # pop up from stack2 will be the post order - print(stack2.pop().data, end=" ") + print(stack2.pop().data, end=",") def prompt(s: str = "", width=50, char="*") -> str: From 696cd47e154e17c35520a734ff19d98b41f3f443 Mon Sep 17 00:00:00 2001 From: mohammadreza490 <47437328+mohammadreza490@users.noreply.github.com> Date: Thu, 10 Sep 2020 09:37:29 +0100 Subject: [PATCH 0757/1071] octal_to_decimal converter (#2399) * Create octal_to_decimal octal to decimal converter * Update octal_to_decimal * Update conversions/octal_to_decimal Co-authored-by: Christian Clauss Co-authored-by: Christian Clauss --- conversions/octal_to_decimal | 37 ++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 conversions/octal_to_decimal diff --git a/conversions/octal_to_decimal b/conversions/octal_to_decimal new file mode 100644 index 000000000000..a5b027e3ae8d --- /dev/null +++ b/conversions/octal_to_decimal @@ -0,0 +1,37 @@ +def oct_to_decimal(oct_string: str) -> int: + """ + Convert a octal value to its decimal equivalent + + >>> oct_to_decimal("12") + 10 + >>> oct_to_decimal(" 12 ") + 10 + >>> oct_to_decimal("-45") + -37 + >>> oct_to_decimal("2-0Fm") + ValueError: Non-octal value was passed to the function + >>> oct_to_decimal("") + ValueError: Empty string value was passed to the function + >>> oct_to_decimal("19") + ValueError: Non-octal value was passed to the function + """ + oct_string = str(oct_string).strip() + if not oct_string: + raise ValueError("Empty string was passed to the function") + is_negative = oct_string[0] == "-" + if is_negative: + oct_string = oct_string[1:] + if not all(0 <= int(char) <= 7 for char in oct_string): + raise ValueError("Non-octal value was passed to the function") + decimal_number = 0 + for char in oct_string: + decimal_number = 8 * decimal_number + int(char) + if is_negative: + decimal_number = -decimal_number + return decimal_number + + +if __name__ == "__main__": + from doctest import testmod + + testmod() From 1b3fec3f1f483f09e277a105e0708a41c56e5006 Mon Sep 17 00:00:00 2001 From: mohammadreza490 <47437328+mohammadreza490@users.noreply.github.com> Date: Fri, 11 Sep 2020 05:16:43 +0100 Subject: [PATCH 0758/1071] binary_to_decimal converter (#2400) * Create binary_to_decimal binary to decimal converter * Update conversions/binary_to_decimal Co-authored-by: Christian Clauss * Update binary_to_decimal * Update conversions/binary_to_decimal Co-authored-by: Christian Clauss * Update binary_to_decimal Co-authored-by: Christian Clauss --- conversions/binary_to_decimal | 39 +++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 conversions/binary_to_decimal diff --git a/conversions/binary_to_decimal b/conversions/binary_to_decimal new file mode 100644 index 000000000000..1f223daf825f --- /dev/null +++ b/conversions/binary_to_decimal @@ -0,0 +1,39 @@ +def bin_to_decimal(bin_string: str) -> int: + """ + Convert a binary value to its decimal equivalent + + >>> bin_to_decimal("101") + 5 + >>> bin_to_decimal(" 1010 ") + 10 + >>> bin_to_decimal("-11101") + -29 + >>> bin_to_decimal("0") + 0 + >>> bin_to_decimal("a") + ValueError: Non-binary value was passed to the function + >>> bin_to_decimal("") + ValueError: Empty string value was passed to the function + >>> bin_to_decimal("39") + ValueError: Non-binary value was passed to the function + """ + bin_string = str(bin_string).strip() + if not bin_string: + raise ValueError("Empty string was passed to the function") + is_negative = bin_string[0] == "-" + if is_negative: + bin_string = bin_string[1:] + if not all(char in "01" for char in bin_string): + raise ValueError("Non-binary value was passed to the function") + decimal_number = 0 + for char in bin_string: + decimal_number = 2 * decimal_number + int(char) + if is_negative: + decimal_number = -decimal_number + return decimal_number + + +if __name__ == "__main__": + from doctest import testmod + + testmod() From a191f89fe21a8c092b152799d40669dd52eec940 Mon Sep 17 00:00:00 2001 From: Marcos Cannabrava <54267712+marcoscannabrava@users.noreply.github.com> Date: Fri, 11 Sep 2020 11:23:26 -0300 Subject: [PATCH 0759/1071] Fix Non Recursive Depth First Search (#2207) * Fix Non Recursive Depth First Search * Unindent docstring * Reindent docstring by 1 space Co-authored-by: Christian Clauss Co-authored-by: Christian Clauss --- graphs/depth_first_search.py | 33 ++++++++++++--------------------- 1 file changed, 12 insertions(+), 21 deletions(-) diff --git a/graphs/depth_first_search.py b/graphs/depth_first_search.py index 9ec7083c7fc1..fee9ea07728d 100644 --- a/graphs/depth_first_search.py +++ b/graphs/depth_first_search.py @@ -1,24 +1,12 @@ -"""The DFS function simply calls itself recursively for every unvisited child of -its argument. We can emulate that behaviour precisely using a stack of iterators. -Instead of recursively calling with a node, we'll push an iterator to the node's -children onto the iterator stack. When the iterator at the top of the stack -terminates, we'll pop it off the stack. - -Pseudocode: - all nodes initially unexplored - mark s as explored - for every edge (s, v): - if v unexplored: - DFS(G, v) -""" -from typing import Dict, Set +"""Non recursive implementation of a DFS algorithm.""" + +from typing import Set, Dict def depth_first_search(graph: Dict, start: str) -> Set[int]: """Depth First Search on Graph - :param graph: directed graph in dictionary format - :param vertex: starting vectex as a string + :param vertex: starting vertex as a string :returns: the trace of the search >>> G = { "A": ["B", "C", "D"], "B": ["A", "D", "E"], ... "C": ["A", "F"], "D": ["B", "D"], "E": ["B", "F"], @@ -31,13 +19,16 @@ def depth_first_search(graph: Dict, start: str) -> Set[int]: True """ explored, stack = set(start), [start] + while stack: v = stack.pop() - # one difference from BFS is to pop last element here instead of first one - for w in graph[v]: - if w not in explored: - explored.add(w) - stack.append(w) + explored.add(v) + # Differences from BFS: + # 1) pop last element instead of first one + # 2) add adjacent elements to stack without exploring them + for adj in reversed(graph[v]): + if adj not in explored: + stack.append(adj) return explored From c6769560301ccff2b606d19ae440136b47482cf8 Mon Sep 17 00:00:00 2001 From: Santosh Mohan Rajkumar Date: Sat, 12 Sep 2020 01:55:05 +0530 Subject: [PATCH 0760/1071] lxmlCovidDataFetch (#2416) * lxmlCovidDataFetch * lxmlCovidDataFetch1 * Update worldometers_covid_with_lxml.py * Rename worldometers_covid_with_lxml.py to covid_stats_via_xpath.py Co-authored-by: Christian Clauss --- web_programming/covid_stats_via_xpath.py | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 web_programming/covid_stats_via_xpath.py diff --git a/web_programming/covid_stats_via_xpath.py b/web_programming/covid_stats_via_xpath.py new file mode 100644 index 000000000000..d22ed017878c --- /dev/null +++ b/web_programming/covid_stats_via_xpath.py @@ -0,0 +1,23 @@ +""" +This is to show simple COVID19 info fetching from worldometers site using lxml +* The main motivation to use lxml in place of bs4 is that it is faster and therefore +more convenient to use in Python web projects (e.g. Django or Flask-based) +""" + +from collections import namedtuple + +import requests +from lxml import html + +covid_data = namedtuple("covid_data", "cases deaths recovered") + + +def covid_stats(url: str = "https://www.worldometers.info/coronavirus/") -> covid_data: + xpath_str = '//div[@class = "maincounter-number"]/span/text()' + return covid_data(*html.fromstring(requests.get(url).content).xpath(xpath_str)) + + +fmt = """Total COVID-19 cases in the world: {} +Total deaths due to COVID-19 in the world: {} +Total COVID-19 patients recovered in the world: {}""" +print(fmt.format(*covid_stats())) From 2e790ce4caf8f40ec54bff06ec857c2bb777e7be Mon Sep 17 00:00:00 2001 From: Meysam Date: Sat, 12 Sep 2020 01:43:43 +0430 Subject: [PATCH 0761/1071] file-transfer: writing tests and ensuring that all is going well (#2413) * file-transfer: writing tests and ensuring that all is going well * def send_file(filename: str = "mytext.txt", testing: bool = False) -> None: * send_file(filename="mytext.txt", testing=True) * Update send_file.py * requirements.txt: lxml Co-authored-by: Christian Clauss --- file_transfer/send_file.py | 17 ++++++-------- file_transfer/tests/test_send_file.py | 32 +++++++++++++++++++++++++++ requirements.txt | 1 + 3 files changed, 40 insertions(+), 10 deletions(-) create mode 100644 file_transfer/tests/test_send_file.py diff --git a/file_transfer/send_file.py b/file_transfer/send_file.py index 6494114a9072..5b53471dfb50 100644 --- a/file_transfer/send_file.py +++ b/file_transfer/send_file.py @@ -1,11 +1,6 @@ -if __name__ == "__main__": - import socket # Import socket module - - ONE_CONNECTION_ONLY = ( - True # Set this to False if you wish to continuously accept connections - ) +def send_file(filename: str = "mytext.txt", testing: bool = False) -> None: + import socket - filename = "mytext.txt" port = 12312 # Reserve a port for your service. sock = socket.socket() # Create a socket object host = socket.gethostname() # Get local machine name @@ -29,10 +24,12 @@ print("Done sending") conn.close() - if ( - ONE_CONNECTION_ONLY - ): # This is to make sure that the program doesn't hang while testing + if testing: # Allow the test to complete break sock.shutdown(1) sock.close() + + +if __name__ == "__main__": + send_file() diff --git a/file_transfer/tests/test_send_file.py b/file_transfer/tests/test_send_file.py new file mode 100644 index 000000000000..170c2c0aed09 --- /dev/null +++ b/file_transfer/tests/test_send_file.py @@ -0,0 +1,32 @@ +from unittest.mock import patch, Mock + + +from file_transfer.send_file import send_file + + +@patch("socket.socket") +@patch("builtins.open") +def test_send_file_running_as_expected(file, sock): + # ===== initialization ===== + conn = Mock() + sock.return_value.accept.return_value = conn, Mock() + f = iter([1, None]) + file.return_value.__enter__.return_value.read.side_effect = lambda _: next(f) + + # ===== invoke ===== + send_file(filename="mytext.txt", testing=True) + + # ===== ensurance ===== + sock.assert_called_once() + sock.return_value.bind.assert_called_once() + sock.return_value.listen.assert_called_once() + sock.return_value.accept.assert_called_once() + conn.recv.assert_called_once() + + file.return_value.__enter__.assert_called_once() + file.return_value.__enter__.return_value.read.assert_called() + + conn.send.assert_called_once() + conn.close.assert_called_once() + sock.return_value.shutdown.assert_called_once() + sock.return_value.close.assert_called_once() diff --git a/requirements.txt b/requirements.txt index 8362afc62509..b070ffdf611d 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,6 +3,7 @@ black fake_useragent flake8 keras +lxml matplotlib mypy numpy From f754c0d31ffe9a6be990fecbc32c55fa97271d9c Mon Sep 17 00:00:00 2001 From: Guillaume Rochedix <33205665+BriseBalloches@users.noreply.github.com> Date: Sat, 12 Sep 2020 07:50:12 +0200 Subject: [PATCH 0762/1071] Jump search (#2415) * jump_search: doctest, docstring, type hint, inputs * jumpsearch.py: case number not found * trailing whitespace jump search --- searches/jump_search.py | 34 ++++++++++++++++++++++++++++++---- 1 file changed, 30 insertions(+), 4 deletions(-) diff --git a/searches/jump_search.py b/searches/jump_search.py index 5ba80e9d35be..31a9656c55fe 100644 --- a/searches/jump_search.py +++ b/searches/jump_search.py @@ -1,7 +1,28 @@ +""" +Pure Python implementation of the jump search algorithm. +This algorithm iterates through a sorted collection with a step of n^(1/2), +until the element compared is bigger than the one searched. +It will then perform a linear search until it matches the wanted number. +If not found, it returns -1. +""" + import math -def jump_search(arr, x): +def jump_search(arr: list, x: int) -> int: + """ + Pure Python implementation of the jump search algorithm. + Examples: + >>> jump_search([0, 1, 2, 3, 4, 5], 3) + 3 + >>> jump_search([-5, -2, -1], -1) + 2 + >>> jump_search([0, 5, 10, 20], 8) + -1 + >>> jump_search([0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610], 55) + 10 + """ + n = len(arr) step = int(math.floor(math.sqrt(n))) prev = 0 @@ -21,6 +42,11 @@ def jump_search(arr, x): if __name__ == "__main__": - arr = [0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610] - x = 55 - print(f"Number {x} is at index {jump_search(arr, x)}") + user_input = input("Enter numbers separated by a comma:\n").strip() + arr = [int(item) for item in user_input.split(",")] + x = int(input("Enter the number to be searched:\n")) + res = jump_search(arr, x) + if res == -1: + print("Number not found!") + else: + print(f"Number {x} is at index {res}") From 20e98fcded341d5aef6dfdfc5cdac431a55252bd Mon Sep 17 00:00:00 2001 From: Hasenn Date: Sun, 13 Sep 2020 10:11:27 +0200 Subject: [PATCH 0763/1071] Fix some warnings from LGTM (#2420) * fix assignment of a variable to itself * Fix unnecessary 'else' clause in loop * formatting and redundant reasignment fix * mark unreachable code with a TODO comment * fix variable defined multiple times * fix static method without static decorator * revert unintended autoformatting Co-authored-by: Christian Clauss * revert autoformatting issue * applied black autoformatting Co-authored-by: Christian Clauss --- ciphers/enigma_machine2.py | 1 - graphs/directed_and_undirected_(weighted)_graph.py | 3 +++ graphs/minimum_spanning_tree_boruvka.py | 1 + maths/chudnovsky_algorithm.py | 1 - other/triplet_sum.py | 3 +-- scheduling/shortest_job_first.py | 6 ++++-- 6 files changed, 9 insertions(+), 6 deletions(-) diff --git a/ciphers/enigma_machine2.py b/ciphers/enigma_machine2.py index 0fbe97284d38..4344db0056fd 100644 --- a/ciphers/enigma_machine2.py +++ b/ciphers/enigma_machine2.py @@ -218,7 +218,6 @@ def enigma( rotorpos1 -= 1 rotorpos2 -= 1 rotorpos3 -= 1 - plugboard = plugboard result = [] diff --git a/graphs/directed_and_undirected_(weighted)_graph.py b/graphs/directed_and_undirected_(weighted)_graph.py index 61e196adfaac..5cfa9e13edd9 100644 --- a/graphs/directed_and_undirected_(weighted)_graph.py +++ b/graphs/directed_and_undirected_(weighted)_graph.py @@ -226,6 +226,7 @@ def has_cycle(self): break else: return True + # TODO:The following code is unreachable. anticipating_nodes.add(stack[len_stack_minus_one]) len_stack_minus_one -= 1 if visited.count(node[1]) < 1: @@ -453,6 +454,8 @@ def has_cycle(self): break else: return True + # TODO: the following code is unreachable + # is this meant to be called in the else ? anticipating_nodes.add(stack[len_stack_minus_one]) len_stack_minus_one -= 1 if visited.count(node[1]) < 1: diff --git a/graphs/minimum_spanning_tree_boruvka.py b/graphs/minimum_spanning_tree_boruvka.py index f65aa7cef031..3b05f94b5140 100644 --- a/graphs/minimum_spanning_tree_boruvka.py +++ b/graphs/minimum_spanning_tree_boruvka.py @@ -146,6 +146,7 @@ def union(self, item1, item2): self.parent[root2] = root1 return root1 + @staticmethod def boruvka_mst(graph): """ Implementation of Boruvka's algorithm diff --git a/maths/chudnovsky_algorithm.py b/maths/chudnovsky_algorithm.py index fb188cd6a3d8..aaee7462822e 100644 --- a/maths/chudnovsky_algorithm.py +++ b/maths/chudnovsky_algorithm.py @@ -44,7 +44,6 @@ def pi(precision: int) -> str: getcontext().prec = precision num_iterations = ceil(precision / 14) constant_term = 426880 * Decimal(10005).sqrt() - multinomial_term = 1 exponential_term = 1 linear_term = 13591409 partial_sum = Decimal(linear_term) diff --git a/other/triplet_sum.py b/other/triplet_sum.py index 247e3bb1618d..25fed5d54579 100644 --- a/other/triplet_sum.py +++ b/other/triplet_sum.py @@ -61,8 +61,7 @@ def triplet_sum2(arr: List[int], target: int) -> Tuple[int, int, int]: left += 1 elif arr[i] + arr[left] + arr[right] > target: right -= 1 - else: - return (0, 0, 0) + return (0, 0, 0) def solution_times() -> Tuple[float, float]: diff --git a/scheduling/shortest_job_first.py b/scheduling/shortest_job_first.py index 6a2fdeeecc5a..ecb6e01fdfe6 100644 --- a/scheduling/shortest_job_first.py +++ b/scheduling/shortest_job_first.py @@ -11,7 +11,6 @@ def calculate_waitingtime( arrival_time: List[int], burst_time: List[int], no_of_processes: int ) -> List[int]: - """ Calculate the waiting time of each processes Return: list of waiting times. @@ -126,13 +125,16 @@ def calculate_average_times( for i in range(no_of_processes): print("Enter the arrival time and brust time for process:--" + str(i + 1)) arrival_time[i], burst_time[i] = map(int, input().split()) + waiting_time = calculate_waitingtime(arrival_time, burst_time, no_of_processes) + bt = burst_time n = no_of_processes wt = waiting_time turn_around_time = calculate_turnaroundtime(bt, n, wt) + calculate_average_times(waiting_time, turn_around_time, no_of_processes) - processes = list(range(1, no_of_processes + 1)) + fcfs = pd.DataFrame( list(zip(processes, burst_time, arrival_time, waiting_time, turn_around_time)), columns=[ From d6bff5c1331864ab41d387896582f422322ca80d Mon Sep 17 00:00:00 2001 From: Du Yuanchao Date: Sun, 13 Sep 2020 19:27:20 +0800 Subject: [PATCH 0764/1071] Renamed files and fixed Doctest (#2421) * * Renamed files * Fiexed doctest * fixup! Format Python code with psf/black push Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> --- .../{binary_to_decimal => binary_to_decimal.py} | 12 ++++++++---- ...ecimal_to_decimal => hexadecimal_to_decimal.py} | 14 +++++++++----- file_transfer/tests/test_send_file.py | 3 +-- graphs/depth_first_search.py | 2 +- 4 files changed, 19 insertions(+), 12 deletions(-) rename conversions/{binary_to_decimal => binary_to_decimal.py} (79%) rename conversions/{hexadecimal_to_decimal => hexadecimal_to_decimal.py} (81%) diff --git a/conversions/binary_to_decimal b/conversions/binary_to_decimal.py similarity index 79% rename from conversions/binary_to_decimal rename to conversions/binary_to_decimal.py index 1f223daf825f..a7625e475bdc 100644 --- a/conversions/binary_to_decimal +++ b/conversions/binary_to_decimal.py @@ -11,10 +11,16 @@ def bin_to_decimal(bin_string: str) -> int: >>> bin_to_decimal("0") 0 >>> bin_to_decimal("a") + Traceback (most recent call last): + ... ValueError: Non-binary value was passed to the function >>> bin_to_decimal("") - ValueError: Empty string value was passed to the function + Traceback (most recent call last): + ... + ValueError: Empty string was passed to the function >>> bin_to_decimal("39") + Traceback (most recent call last): + ... ValueError: Non-binary value was passed to the function """ bin_string = str(bin_string).strip() @@ -28,9 +34,7 @@ def bin_to_decimal(bin_string: str) -> int: decimal_number = 0 for char in bin_string: decimal_number = 2 * decimal_number + int(char) - if is_negative: - decimal_number = -decimal_number - return decimal_number + return -decimal_number if is_negative else decimal_number if __name__ == "__main__": diff --git a/conversions/hexadecimal_to_decimal b/conversions/hexadecimal_to_decimal.py similarity index 81% rename from conversions/hexadecimal_to_decimal rename to conversions/hexadecimal_to_decimal.py index e87caa0f4787..beb1c2c3ded6 100644 --- a/conversions/hexadecimal_to_decimal +++ b/conversions/hexadecimal_to_decimal.py @@ -17,14 +17,20 @@ def hex_to_decimal(hex_string: str) -> int: >>> hex_to_decimal("-Ff") -255 >>> hex_to_decimal("F-f") + Traceback (most recent call last): + ... ValueError: Non-hexadecimal value was passed to the function >>> hex_to_decimal("") - ValueError: Empty string value was passed to the function + Traceback (most recent call last): + ... + ValueError: Empty string was passed to the function >>> hex_to_decimal("12m") + Traceback (most recent call last): + ... ValueError: Non-hexadecimal value was passed to the function """ hex_string = hex_string.strip().lower() - if not hex_string: + if not hex_string: raise ValueError("Empty string was passed to the function") is_negative = hex_string[0] == "-" if is_negative: @@ -34,9 +40,7 @@ def hex_to_decimal(hex_string: str) -> int: decimal_number = 0 for char in hex_string: decimal_number = 16 * decimal_number + hex_table[char] - if is_negative: - decimal_number = -decimal_number - return decimal_number + return -decimal_number if is_negative else decimal_number if __name__ == "__main__": diff --git a/file_transfer/tests/test_send_file.py b/file_transfer/tests/test_send_file.py index 170c2c0aed09..2a6008448362 100644 --- a/file_transfer/tests/test_send_file.py +++ b/file_transfer/tests/test_send_file.py @@ -1,5 +1,4 @@ -from unittest.mock import patch, Mock - +from unittest.mock import Mock, patch from file_transfer.send_file import send_file diff --git a/graphs/depth_first_search.py b/graphs/depth_first_search.py index fee9ea07728d..43f2eaaea19a 100644 --- a/graphs/depth_first_search.py +++ b/graphs/depth_first_search.py @@ -1,6 +1,6 @@ """Non recursive implementation of a DFS algorithm.""" -from typing import Set, Dict +from typing import Dict, Set def depth_first_search(graph: Dict, start: str) -> Set[int]: From 44b8cb0c81b1c86dc8957d6d218f3643488e84a9 Mon Sep 17 00:00:00 2001 From: Du Yuanchao Date: Sun, 13 Sep 2020 19:56:03 +0800 Subject: [PATCH 0765/1071] Updated Stack (#2414) * * Added type hints * Added test * Formated code * updating DIRECTORY.md * Update stack.py * Test error conditions for pop, peek, and Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> Co-authored-by: Christian Clauss --- DIRECTORY.md | 20 +++++++- data_structures/stacks/stack.py | 87 ++++++++++++++++++++++----------- 2 files changed, 77 insertions(+), 30 deletions(-) diff --git a/DIRECTORY.md b/DIRECTORY.md index 70f7726d99c0..ac10afdc0ccd 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -24,6 +24,9 @@ * [Sudoku](https://github.com/TheAlgorithms/Python/blob/master/backtracking/sudoku.py) * [Sum Of Subsets](https://github.com/TheAlgorithms/Python/blob/master/backtracking/sum_of_subsets.py) +## Bit Manipulation + * [Binary Or Operator](https://github.com/TheAlgorithms/Python/blob/master/bit_manipulation/binary_or_operator.py) + ## Blockchain * [Chinese Remainder Theorem](https://github.com/TheAlgorithms/Python/blob/master/blockchain/chinese_remainder_theorem.py) * [Diophantine Equation](https://github.com/TheAlgorithms/Python/blob/master/blockchain/diophantine_equation.py) @@ -50,6 +53,7 @@ * [Deterministic Miller Rabin](https://github.com/TheAlgorithms/Python/blob/master/ciphers/deterministic_miller_rabin.py) * [Diffie](https://github.com/TheAlgorithms/Python/blob/master/ciphers/diffie.py) * [Elgamal Key Generator](https://github.com/TheAlgorithms/Python/blob/master/ciphers/elgamal_key_generator.py) + * [Enigma Machine2](https://github.com/TheAlgorithms/Python/blob/master/ciphers/enigma_machine2.py) * [Hill Cipher](https://github.com/TheAlgorithms/Python/blob/master/ciphers/hill_cipher.py) * [Mixed Keyword Cypher](https://github.com/TheAlgorithms/Python/blob/master/ciphers/mixed_keyword_cypher.py) * [Morse Code Implementation](https://github.com/TheAlgorithms/Python/blob/master/ciphers/morse_code_implementation.py) @@ -105,6 +109,7 @@ * [Segment Tree Other](https://github.com/TheAlgorithms/Python/blob/master/data_structures/binary_tree/segment_tree_other.py) * [Treap](https://github.com/TheAlgorithms/Python/blob/master/data_structures/binary_tree/treap.py) * Disjoint Set + * [Alternate Disjoint Set](https://github.com/TheAlgorithms/Python/blob/master/data_structures/disjoint_set/alternate_disjoint_set.py) * [Disjoint Set](https://github.com/TheAlgorithms/Python/blob/master/data_structures/disjoint_set/disjoint_set.py) * Hashing * [Double Hash](https://github.com/TheAlgorithms/Python/blob/master/data_structures/hashing/double_hash.py) @@ -419,6 +424,7 @@ * [Sum Of Geometric Progression](https://github.com/TheAlgorithms/Python/blob/master/maths/sum_of_geometric_progression.py) * [Test Prime Check](https://github.com/TheAlgorithms/Python/blob/master/maths/test_prime_check.py) * [Trapezoidal Rule](https://github.com/TheAlgorithms/Python/blob/master/maths/trapezoidal_rule.py) + * [Ugly Numbers](https://github.com/TheAlgorithms/Python/blob/master/maths/ugly_numbers.py) * [Volume](https://github.com/TheAlgorithms/Python/blob/master/maths/volume.py) * [Zellers Congruence](https://github.com/TheAlgorithms/Python/blob/master/maths/zellers_congruence.py) @@ -474,6 +480,7 @@ * [Sdes](https://github.com/TheAlgorithms/Python/blob/master/other/sdes.py) * [Sierpinski Triangle](https://github.com/TheAlgorithms/Python/blob/master/other/sierpinski_triangle.py) * [Tower Of Hanoi](https://github.com/TheAlgorithms/Python/blob/master/other/tower_of_hanoi.py) + * [Triplet Sum](https://github.com/TheAlgorithms/Python/blob/master/other/triplet_sum.py) * [Two Sum](https://github.com/TheAlgorithms/Python/blob/master/other/two_sum.py) * [Word Patterns](https://github.com/TheAlgorithms/Python/blob/master/other/word_patterns.py) @@ -600,6 +607,12 @@ * [Solution42](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_42/solution42.py) * Problem 43 * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_43/sol1.py) + * Problem 44 + * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_44/sol1.py) + * Problem 45 + * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_45/sol1.py) + * Problem 46 + * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_46/sol1.py) * Problem 47 * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_47/sol1.py) * Problem 48 @@ -608,10 +621,14 @@ * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_52/sol1.py) * Problem 53 * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_53/sol1.py) + * Problem 55 + * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_55/sol1.py) * Problem 551 * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_551/sol1.py) * Problem 56 * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_56/sol1.py) + * Problem 63 + * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_63/sol1.py) * Problem 67 * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_67/sol1.py) * Problem 76 @@ -673,7 +690,6 @@ * [Recursive Insertion Sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/recursive_insertion_sort.py) * [Selection Sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/selection_sort.py) * [Shell Sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/shell_sort.py) - * [Sleep Sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/sleep_sort.py) * [Stooge Sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/stooge_sort.py) * [Strand Sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/strand_sort.py) * [Tim Sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/tim_sort.py) @@ -685,6 +701,7 @@ ## Strings * [Aho-Corasick](https://github.com/TheAlgorithms/Python/blob/master/strings/aho-corasick.py) * [Boyer Moore Search](https://github.com/TheAlgorithms/Python/blob/master/strings/boyer_moore_search.py) + * [Capitalize](https://github.com/TheAlgorithms/Python/blob/master/strings/capitalize.py) * [Check Anagrams](https://github.com/TheAlgorithms/Python/blob/master/strings/check_anagrams.py) * [Check Pangram](https://github.com/TheAlgorithms/Python/blob/master/strings/check_pangram.py) * [Is Palindrome](https://github.com/TheAlgorithms/Python/blob/master/strings/is_palindrome.py) @@ -715,6 +732,7 @@ * [Emails From Url](https://github.com/TheAlgorithms/Python/blob/master/web_programming/emails_from_url.py) * [Fetch Bbc News](https://github.com/TheAlgorithms/Python/blob/master/web_programming/fetch_bbc_news.py) * [Fetch Github Info](https://github.com/TheAlgorithms/Python/blob/master/web_programming/fetch_github_info.py) + * [Fetch Jobs](https://github.com/TheAlgorithms/Python/blob/master/web_programming/fetch_jobs.py) * [Get Imdb Top 250 Movies Csv](https://github.com/TheAlgorithms/Python/blob/master/web_programming/get_imdb_top_250_movies_csv.py) * [Get Imdbtop](https://github.com/TheAlgorithms/Python/blob/master/web_programming/get_imdbtop.py) * [Slack Message](https://github.com/TheAlgorithms/Python/blob/master/web_programming/slack_message.py) diff --git a/data_structures/stacks/stack.py b/data_structures/stacks/stack.py index a4bcb5beabd4..840cde099d38 100644 --- a/data_structures/stacks/stack.py +++ b/data_structures/stacks/stack.py @@ -1,4 +1,5 @@ -__author__ = "Omkar Pathak" +class StackOverflowError(BaseException): + pass class Stack: @@ -7,18 +8,17 @@ class Stack: element to the top of the stack, and pop() removes an element from the top of a stack. The order in which elements come off of a stack are Last In, First Out (LIFO). - https://en.wikipedia.org/wiki/Stack_(abstract_data_type) """ - def __init__(self, limit=10): + def __init__(self, limit: int = 10): self.stack = [] self.limit = limit - def __bool__(self): + def __bool__(self) -> bool: return bool(self.stack) - def __str__(self): + def __str__(self) -> str: return str(self.stack) def push(self, data): @@ -29,21 +29,20 @@ def push(self, data): def pop(self): """ Pop an element off of the top of the stack.""" - if self.stack: - return self.stack.pop() - else: - raise IndexError("pop from an empty stack") + return self.stack.pop() def peek(self): """ Peek at the top-most element of the stack.""" - if self.stack: - return self.stack[-1] + return self.stack[-1] - def is_empty(self): + def is_empty(self) -> bool: """ Check if a stack is empty.""" return not bool(self.stack) - def size(self): + def is_full(self) -> bool: + return self.size() == self.limit + + def size(self) -> int: """ Return the size of the stack.""" return len(self.stack) @@ -52,24 +51,54 @@ def __contains__(self, item) -> bool: return item in self.stack -class StackOverflowError(BaseException): - pass - +def test_stack() -> None: + """ + >>> test_stack() + """ + stack = Stack(10) + assert bool(stack) is False + assert stack.is_empty() is True + assert stack.is_full() is False + assert str(stack) == "[]" + + try: + _ = stack.pop() + assert False # This should not happen + except IndexError: + assert True # This should happen + + try: + _ = stack.peek() + assert False # This should not happen + except IndexError: + assert True # This should happen -if __name__ == "__main__": - stack = Stack() for i in range(10): + assert stack.size() == i stack.push(i) - print("Stack demonstration:\n") - print("Initial stack: " + str(stack)) - print("pop(): " + str(stack.pop())) - print("After pop(), the stack is now: " + str(stack)) - print("peek(): " + str(stack.peek())) + assert bool(stack) is True + assert stack.is_empty() is False + assert stack.is_full() is True + assert str(stack) == str(list(range(10))) + assert stack.pop() == 9 + assert stack.peek() == 8 + stack.push(100) - print("After push(100), the stack is now: " + str(stack)) - print("is_empty(): " + str(stack.is_empty())) - print("size(): " + str(stack.size())) - num = 5 - if num in stack: - print(f"{num} is in stack") + assert str(stack) == str([0, 1, 2, 3, 4, 5, 6, 7, 8, 100]) + + try: + stack.push(200) + assert False # This should not happen + except StackOverflowError: + assert True # This should happen + + assert stack.is_empty() is False + assert stack.size() == 10 + + assert 5 in stack + assert 55 not in stack + + +if __name__ == "__main__": + test_stack() From 4e5b730e85ce02b05dc3fe1a862bc33bc29b6a73 Mon Sep 17 00:00:00 2001 From: Santosh Mohan Rajkumar Date: Mon, 14 Sep 2020 01:56:15 +0530 Subject: [PATCH 0766/1071] recaptchaVerification (#2417) * recaptchaVerification * recaptchaVerification * recaptchaVerification1 * recaptchaVerification2 * recaptchaVerification3 * recaptchaVerification4 * recaptchaVerificatio5 * recaptchaVerificatio5 * recaptchaVerificatio6 * drawOnVideoStreamOpenCV * matrixInverseMCAmethod * fixingImports * recaptchaVerificationfixes * recaptchaVerificationfixes * recaptchaVerificationfixes * recaptchaVerificationfixes * recaptchaVerificationfixes1 * recaptchaVerificationfixes1 * authenticate = login = render = redirect = print Co-authored-by: Christian Clauss --- web_programming/recaptcha_verification.py | 66 +++++++++++++++++++++++ 1 file changed, 66 insertions(+) create mode 100644 web_programming/recaptcha_verification.py diff --git a/web_programming/recaptcha_verification.py b/web_programming/recaptcha_verification.py new file mode 100644 index 000000000000..47c6c42f2ad0 --- /dev/null +++ b/web_programming/recaptcha_verification.py @@ -0,0 +1,66 @@ +""" +Recaptcha is a free captcha service offered by Google in order to secure websites and +forms. At https://www.google.com/recaptcha/admin/create you can create new recaptcha +keys and see the keys that your have already created. +* Keep in mind that recaptcha doesn't work with localhost +When you create a recaptcha key, your will get two separate keys: ClientKey & SecretKey. +ClientKey should be kept in your site's front end +SecretKey should be kept in your site's back end + +# An example HTML login form with recaptcha tag is shown below + +

+

Log in

+ {% csrf_token %} +
+ +
+
+ +
+
+ +
+ +
+ + + + + +Below a Django function for the views.py file contains a login form for demonstrating +recaptcha verification. +""" +import requests + +try: + from django.contrib.auth import authenticate, login + from django.shortcuts import redirect, render +except ImportError: + authenticate = login = render = redirect = print + + +def login_using_recaptcha(request): + # Enter your recaptcha secret key here + secret_key = "secretKey" + url = "https://www.google.com/recaptcha/api/siteverify" + + # when method is not POST, direct user to login page + if request.method != "POST": + return render(request, "login.html") + + # from the frontend, get username, password, and client_key + username = request.POST.get("username") + password = request.POST.get("password") + client_key = request.POST.get("g-recaptcha-response") + + # post recaptcha response to Google's recaptcha api + response = requests.post(url, data={"secret": secret_key, "response": client_key}) + # if the recaptcha api verified our keys + if response.json().get("success", False): + # authenticate the user + user_in_database = authenticate(request, username=username, password=password) + if user_in_database: + login(request, user_in_database) + return redirect("/your-webpage") + return render(request, "login.html") From 799fde4c07dd039807ffb5020824fc065728b17a Mon Sep 17 00:00:00 2001 From: Ashley Jeji George <40469421+Ashley-J-George@users.noreply.github.com> Date: Mon, 14 Sep 2020 16:14:46 +0530 Subject: [PATCH 0767/1071] Update linear_search.py (#2422) * Update linear_search.py Python implementation of recursive linear search algorithm * Update linear_search.py Added different doctests Added the parameter hints Handled the exception * Update linear_search.py added parameter hints to linear_search * Update linear_search.py Both the functions return the index if the target is found and -1 if it is not found The rec_linear_search raises an exception if there is an indexing problem Made changes in the doc comments * Update linear_search.py Co-authored-by: Christian Clauss --- searches/linear_search.py | 58 ++++++++++++++++++++++++++++----------- 1 file changed, 42 insertions(+), 16 deletions(-) diff --git a/searches/linear_search.py b/searches/linear_search.py index 2056bd7a4916..777080d14e36 100644 --- a/searches/linear_search.py +++ b/searches/linear_search.py @@ -2,17 +2,15 @@ This is pure Python implementation of linear search algorithm For doctests run following command: -python -m doctest -v linear_search.py -or python3 -m doctest -v linear_search.py For manual testing run: -python linear_search.py +python3 linear_search.py """ -def linear_search(sequence, target): - """Pure implementation of linear search algorithm in Python +def linear_search(sequence: list, target: int) -> int: + """A pure Python implementation of a linear search algorithm :param sequence: a collection with comparable items (as sorted items not required in Linear Search) @@ -22,30 +20,58 @@ def linear_search(sequence, target): Examples: >>> linear_search([0, 5, 7, 10, 15], 0) 0 - >>> linear_search([0, 5, 7, 10, 15], 15) 4 - >>> linear_search([0, 5, 7, 10, 15], 5) 1 - >>> linear_search([0, 5, 7, 10, 15], 6) - + -1 """ for index, item in enumerate(sequence): if item == target: return index - return None + return -1 + + +def rec_linear_search(sequence: list, low: int, high: int, target: int) -> int: + """ + A pure Python implementation of a recursive linear search algorithm + + :param sequence: a collection with comparable items (as sorted items not required + in Linear Search) + :param low: Lower bound of the array + :param high: Higher bound of the array + :param target: The element to be found + :return: Index of the key or -1 if key not found + + Examples: + >>> rec_linear_search([0, 30, 500, 100, 700], 0, 4, 0) + 0 + >>> rec_linear_search([0, 30, 500, 100, 700], 0, 4, 700) + 4 + >>> rec_linear_search([0, 30, 500, 100, 700], 0, 4, 30) + 1 + >>> rec_linear_search([0, 30, 500, 100, 700], 0, 4, -6) + -1 + """ + if not (0 <= high < len(sequence) and 0 <= low < len(sequence)): + raise Exception("Invalid upper or lower bound!") + if high < low: + return -1 + if sequence[low] == target: + return low + if sequence[high] == target: + return high + return rec_linear_search(sequence, low + 1, high - 1, target) if __name__ == "__main__": user_input = input("Enter numbers separated by comma:\n").strip() - sequence = [int(item) for item in user_input.split(",")] + sequence = [int(item.strip()) for item in user_input.split(",")] - target_input = input("Enter a single number to be found in the list:\n") - target = int(target_input) + target = int(input("Enter a single number to be found in the list:\n").strip()) result = linear_search(sequence, target) - if result is not None: - print(f"{target} found at position : {result}") + if result != -1: + print(f"linear_search({sequence}, {target}) = {result}") else: - print("Not found") + print(f"{target} was not found in {sequence}") From 10aa214fcb2a05fd479e23a0d9c1d1ea617a7769 Mon Sep 17 00:00:00 2001 From: Hasenn Date: Mon, 14 Sep 2020 14:40:27 +0200 Subject: [PATCH 0768/1071] Docstrings and formatting improvements (#2418) * Fix spelling in docstrings * Improve comments and formatting * Update print statement to reflect doctest change * improve phrasing and apply black * Update rat_in_maze.py This method is recursive starting from (i, j) and going in one of four directions: up, down, left, right. If a path is found to destination it returns True otherwise it returns False. Co-authored-by: Christian Clauss --- backtracking/hamiltonian_cycle.py | 6 +++--- backtracking/rat_in_maze.py | 23 +++++++++++------------ boolean_algebra/quine_mc_cluskey.py | 2 +- cellular_automata/one_dimensional.py | 5 +++-- 4 files changed, 18 insertions(+), 18 deletions(-) diff --git a/backtracking/hamiltonian_cycle.py b/backtracking/hamiltonian_cycle.py index 3bd61fc667d9..7be1ea350d7c 100644 --- a/backtracking/hamiltonian_cycle.py +++ b/backtracking/hamiltonian_cycle.py @@ -51,7 +51,7 @@ def util_hamilton_cycle(graph: List[List[int]], path: List[int], curr_ind: int) """ Pseudo-Code Base Case: - 1. Chceck if we visited all of vertices + 1. Check if we visited all of vertices 1.1 If last visited vertex has path to starting vertex return True either return False Recursive Step: @@ -59,8 +59,8 @@ def util_hamilton_cycle(graph: List[List[int]], path: List[int], curr_ind: int) Check if next vertex is valid for transiting from current vertex 2.1 Remember next vertex as next transition 2.2 Do recursive call and check if going to this vertex solves problem - 2.3 if next vertex leads to solution return True - 2.4 else backtrack, delete remembered vertex + 2.3 If next vertex leads to solution return True + 2.4 Else backtrack, delete remembered vertex Case 1: Use exact graph as in main function, with initialized values >>> graph = [[0, 1, 0, 1, 0], diff --git a/backtracking/rat_in_maze.py b/backtracking/rat_in_maze.py index ba96d6a52214..788aeac13c09 100644 --- a/backtracking/rat_in_maze.py +++ b/backtracking/rat_in_maze.py @@ -1,13 +1,13 @@ def solve_maze(maze: list) -> bool: """ - This method solves rat in maze algorithm. - In this problem we have n by n matrix and we have start point and end point - we want to go from source to distination. In this matrix 0 are block paths - 1 are open paths we can use. + This method solves the "rat in maze" problem. + In this problem we have some n by n matrix, a start point and an end point. + We want to go from the start to the end. In this matrix zeroes represent walls + and ones paths we can use. Parameters : maze(2D matrix) : maze Returns: - Return: True is maze has a solution or False if it does not. + Return: True if the maze has a solution or False if it does not. >>> maze = [[0, 1, 0, 1, 1], ... [0, 0, 0, 0, 0], ... [1, 0, 1, 0, 1], @@ -47,13 +47,13 @@ def solve_maze(maze: list) -> bool: ... [0, 1, 0], ... [1, 0, 0]] >>> solve_maze(maze) - Solution does not exists! + No solution exists! False >>> maze = [[0, 1], ... [1, 0]] >>> solve_maze(maze) - Solution does not exists! + No solution exists! False """ size = len(maze) @@ -63,16 +63,15 @@ def solve_maze(maze: list) -> bool: if solved: print("\n".join(str(row) for row in solutions)) else: - print("Solution does not exists!") + print("No solution exists!") return solved def run_maze(maze, i, j, solutions): """ - This method is recursive method which starts from i and j - and goes with 4 direction option up, down, left, right - if path found to destination it breaks and return True - otherwise False + This method is recursive starting from (i, j) and going in one of four directions: + up, down, left, right. + If a path is found to destination it returns True otherwise it returns False. Parameters: maze(2D matrix) : maze i, j : coordinates of matrix diff --git a/boolean_algebra/quine_mc_cluskey.py b/boolean_algebra/quine_mc_cluskey.py index 036cfbe63e79..a55b624483ca 100644 --- a/boolean_algebra/quine_mc_cluskey.py +++ b/boolean_algebra/quine_mc_cluskey.py @@ -146,7 +146,7 @@ def main(): minterms = [ int(x) for x in input( - "Enter the decimal representation of Minterms 'Spaces Seprated'\n" + "Enter the decimal representation of Minterms 'Spaces Separated'\n" ).split() ] binary = decimal_to_binary(no_of_variable, minterms) diff --git a/cellular_automata/one_dimensional.py b/cellular_automata/one_dimensional.py index 7819088c8cff..a6229dd9096f 100644 --- a/cellular_automata/one_dimensional.py +++ b/cellular_automata/one_dimensional.py @@ -32,8 +32,9 @@ def new_generation(cells: List[List[int]], rule: List[int], time: int) -> List[i next_generation = [] for i in range(population): # Get the neighbors of each cell - left_neighbor = 0 if i == 0 else cells[time][i - 1] # special: leftmost cell - right_neighbor = 0 if i == population - 1 else cells[time][i + 1] # rightmost + # Handle neighbours outside bounds by using 0 as their value + left_neighbor = 0 if i == 0 else cells[time][i - 1] + right_neighbor = 0 if i == population - 1 else cells[time][i + 1] # Define a new cell and add it to the new generation situation = 7 - int(f"{left_neighbor}{cells[time][i]}{right_neighbor}", 2) next_generation.append(rule[situation]) From cbbc43ba3ae24ad589d94beb65116c07917339ac Mon Sep 17 00:00:00 2001 From: Du Yuanchao Date: Tue, 15 Sep 2020 04:33:08 +0800 Subject: [PATCH 0769/1071] Updated problem_04 in project_euler (#2427) * Updated problem_04 in project_euler * fixup! Format Python code with psf/black push * That number is larger than our acceptable range. * updating DIRECTORY.md Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> Co-authored-by: Christian Clauss --- DIRECTORY.md | 6 ++++++ project_euler/problem_04/sol1.py | 19 ++++++++++++++----- 2 files changed, 20 insertions(+), 5 deletions(-) diff --git a/DIRECTORY.md b/DIRECTORY.md index ac10afdc0ccd..1dceb887940c 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -85,10 +85,12 @@ * [Harriscorner](https://github.com/TheAlgorithms/Python/blob/master/computer_vision/harriscorner.py) ## Conversions + * [Binary To Decimal](https://github.com/TheAlgorithms/Python/blob/master/conversions/binary_to_decimal.py) * [Decimal To Any](https://github.com/TheAlgorithms/Python/blob/master/conversions/decimal_to_any.py) * [Decimal To Binary](https://github.com/TheAlgorithms/Python/blob/master/conversions/decimal_to_binary.py) * [Decimal To Hexadecimal](https://github.com/TheAlgorithms/Python/blob/master/conversions/decimal_to_hexadecimal.py) * [Decimal To Octal](https://github.com/TheAlgorithms/Python/blob/master/conversions/decimal_to_octal.py) + * [Hexadecimal To Decimal](https://github.com/TheAlgorithms/Python/blob/master/conversions/hexadecimal_to_decimal.py) * [Prefix Conversions](https://github.com/TheAlgorithms/Python/blob/master/conversions/prefix_conversions.py) * [Roman To Integer](https://github.com/TheAlgorithms/Python/blob/master/conversions/roman_to_integer.py) * [Temperature Conversions](https://github.com/TheAlgorithms/Python/blob/master/conversions/temperature_conversions.py) @@ -222,6 +224,8 @@ ## File Transfer * [Receive File](https://github.com/TheAlgorithms/Python/blob/master/file_transfer/receive_file.py) * [Send File](https://github.com/TheAlgorithms/Python/blob/master/file_transfer/send_file.py) + * Tests + * [Test Send File](https://github.com/TheAlgorithms/Python/blob/master/file_transfer/tests/test_send_file.py) ## Fuzzy Logic * [Fuzzy Operations](https://github.com/TheAlgorithms/Python/blob/master/fuzzy_logic/fuzzy_operations.py) @@ -725,6 +729,7 @@ * [Binary Tree Traversals](https://github.com/TheAlgorithms/Python/blob/master/traversals/binary_tree_traversals.py) ## Web Programming + * [Covid Stats Via Xpath](https://github.com/TheAlgorithms/Python/blob/master/web_programming/covid_stats_via_xpath.py) * [Crawl Google Results](https://github.com/TheAlgorithms/Python/blob/master/web_programming/crawl_google_results.py) * [Current Stock Price](https://github.com/TheAlgorithms/Python/blob/master/web_programming/current_stock_price.py) * [Current Weather](https://github.com/TheAlgorithms/Python/blob/master/web_programming/current_weather.py) @@ -735,5 +740,6 @@ * [Fetch Jobs](https://github.com/TheAlgorithms/Python/blob/master/web_programming/fetch_jobs.py) * [Get Imdb Top 250 Movies Csv](https://github.com/TheAlgorithms/Python/blob/master/web_programming/get_imdb_top_250_movies_csv.py) * [Get Imdbtop](https://github.com/TheAlgorithms/Python/blob/master/web_programming/get_imdbtop.py) + * [Recaptcha Verification](https://github.com/TheAlgorithms/Python/blob/master/web_programming/recaptcha_verification.py) * [Slack Message](https://github.com/TheAlgorithms/Python/blob/master/web_programming/slack_message.py) * [World Covid19 Stats](https://github.com/TheAlgorithms/Python/blob/master/web_programming/world_covid19_stats.py) diff --git a/project_euler/problem_04/sol1.py b/project_euler/problem_04/sol1.py index 599345b5ab79..227b594d0df7 100644 --- a/project_euler/problem_04/sol1.py +++ b/project_euler/problem_04/sol1.py @@ -18,25 +18,34 @@ def solution(n): 29992 >>> solution(40000) 39893 + >>> solution(10000) + Traceback (most recent call last): + ... + ValueError: That number is larger than our acceptable range. """ # fetches the next number - for number in range(n - 1, 10000, -1): + for number in range(n - 1, 9999, -1): # converts number into string. - strNumber = str(number) + str_number = str(number) - # checks whether 'strNumber' is a palindrome. - if strNumber == strNumber[::-1]: + # checks whether 'str_number' is a palindrome. + if str_number == str_number[::-1]: divisor = 999 # if 'number' is a product of two 3-digit numbers # then number is the answer otherwise fetch next number. while divisor != 99: - if (number % divisor == 0) and (len(str(int(number / divisor))) == 3): + if (number % divisor == 0) and (len(str(number // divisor)) == 3.0): return number divisor -= 1 + raise ValueError("That number is larger than our acceptable range.") if __name__ == "__main__": + import doctest + + doctest.testmod() + print(solution(int(input().strip()))) From 1ac75f4683c8920382d9cbd676d7a3ae5fefc2a1 Mon Sep 17 00:00:00 2001 From: Ashley Jeji George <40469421+Ashley-J-George@users.noreply.github.com> Date: Wed, 16 Sep 2020 22:12:53 +0530 Subject: [PATCH 0770/1071] Create priority_queue_using_list.py (#2435) * Create priority_queue_using_list.py * Update priority_queue_using_list.py * Update priority_queue_using_list.py * Update priority_queue_using_list.py * Maximum queue size is 100 Co-authored-by: Christian Clauss --- .../queue/priority_queue_using_list.py | 232 ++++++++++++++++++ 1 file changed, 232 insertions(+) create mode 100644 data_structures/queue/priority_queue_using_list.py diff --git a/data_structures/queue/priority_queue_using_list.py b/data_structures/queue/priority_queue_using_list.py new file mode 100644 index 000000000000..0ba4e88cd876 --- /dev/null +++ b/data_structures/queue/priority_queue_using_list.py @@ -0,0 +1,232 @@ +""" +Pure Python implementations of a Fixed Priority Queue and an Element Priority Queue +using Python lists. +""" + + +class OverFlowError(Exception): + pass + + +class UnderFlowError(Exception): + pass + + +class FixedPriorityQueue: + """ + Tasks can be added to a Priority Queue at any time and in any order but when Tasks + are removed then the Task with the highest priority is removed in FIFO order. In + code we will use three levels of priority with priority zero Tasks being the most + urgent (high priority) and priority 2 tasks being the least urgent. + + Examples + >>> fpq = FixedPriorityQueue() + >>> fpq.enqueue(0, 10) + >>> fpq.enqueue(1, 70) + >>> fpq.enqueue(0, 100) + >>> fpq.enqueue(2, 1) + >>> fpq.enqueue(2, 5) + >>> fpq.enqueue(1, 7) + >>> fpq.enqueue(2, 4) + >>> fpq.enqueue(1, 64) + >>> fpq.enqueue(0, 128) + >>> print(fpq) + Priority 0: [10, 100, 128] + Priority 1: [70, 7, 64] + Priority 2: [1, 5, 4] + >>> fpq.dequeue() + 10 + >>> fpq.dequeue() + 100 + >>> fpq.dequeue() + 128 + >>> fpq.dequeue() + 70 + >>> fpq.dequeue() + 7 + >>> print(fpq) + Priority 0: [] + Priority 1: [64] + Priority 2: [1, 5, 4] + >>> fpq.dequeue() + 64 + >>> fpq.dequeue() + 1 + >>> fpq.dequeue() + 5 + >>> fpq.dequeue() + 4 + >>> fpq.dequeue() + Traceback (most recent call last): + ... + priority_queue_using_list.UnderFlowError: All queues are empty + >>> print(fpq) + Priority 0: [] + Priority 1: [] + Priority 2: [] + """ + + def __init__(self): + self.queues = [ + [], + [], + [], + ] + + def enqueue(self, priority: int, data: int) -> None: + """ + Add an element to a queue based on its priority. + If the priority is invalid ValueError is raised. + If the queue is full an OverFlowError is raised. + """ + try: + if len(self.queues[priority]) >= 100: + raise OverflowError("Maximum queue size is 100") + self.queues[priority].append(data) + except IndexError: + raise ValueError("Valid priorities are 0, 1, and 2") + + def dequeue(self) -> int: + """ + Return the highest priority element in FIFO order. + If the queue is empty then an under flow exception is raised. + """ + for queue in self.queues: + if queue: + return queue.pop(0) + raise UnderFlowError("All queues are empty") + + def __str__(self) -> str: + return "\n".join(f"Priority {i}: {q}" for i, q in enumerate(self.queues)) + + +class ElementPriorityQueue: + """ + Element Priority Queue is the same as Fixed Priority Queue except that the value of + the element itself is the priority. The rules for priorities are the same the as + Fixed Priority Queue. + + >>> epq = ElementPriorityQueue() + >>> epq.enqueue(10) + >>> epq.enqueue(70) + >>> epq.enqueue(4) + >>> epq.enqueue(1) + >>> epq.enqueue(5) + >>> epq.enqueue(7) + >>> epq.enqueue(4) + >>> epq.enqueue(64) + >>> epq.enqueue(128) + >>> print(epq) + [10, 70, 4, 1, 5, 7, 4, 64, 128] + >>> epq.dequeue() + 1 + >>> epq.dequeue() + 4 + >>> epq.dequeue() + 4 + >>> epq.dequeue() + 5 + >>> epq.dequeue() + 7 + >>> epq.dequeue() + 10 + >>> print(epq) + [70, 64, 128] + >>> epq.dequeue() + 64 + >>> epq.dequeue() + 70 + >>> epq.dequeue() + 128 + >>> epq.dequeue() + Traceback (most recent call last): + ... + priority_queue_using_list.UnderFlowError: The queue is empty + >>> print(epq) + [] + """ + + def __init__(self): + self.queue = [] + + def enqueue(self, data: int) -> None: + """ + This function enters the element into the queue + If the queue is full an Exception is raised saying Over Flow! + """ + if len(self.queue) == 100: + raise OverFlowError("Maximum queue size is 100") + self.queue.append(data) + + def dequeue(self) -> int: + """ + Return the highest priority element in FIFO order. + If the queue is empty then an under flow exception is raised. + """ + if not self.queue: + raise UnderFlowError("The queue is empty") + else: + data = min(self.queue) + self.queue.remove(data) + return data + + def __str__(self) -> str: + """ + Prints all the elements within the Element Priority Queue + """ + return str(self.queue) + + +def fixed_priority_queue(): + fpq = FixedPriorityQueue() + fpq.enqueue(0, 10) + fpq.enqueue(1, 70) + fpq.enqueue(0, 100) + fpq.enqueue(2, 1) + fpq.enqueue(2, 5) + fpq.enqueue(1, 7) + fpq.enqueue(2, 4) + fpq.enqueue(1, 64) + fpq.enqueue(0, 128) + print(fpq) + print(fpq.dequeue()) + print(fpq.dequeue()) + print(fpq.dequeue()) + print(fpq.dequeue()) + print(fpq.dequeue()) + print(fpq) + print(fpq.dequeue()) + print(fpq.dequeue()) + print(fpq.dequeue()) + print(fpq.dequeue()) + print(fpq.dequeue()) + + +def element_priority_queue(): + epq = ElementPriorityQueue() + epq.enqueue(10) + epq.enqueue(70) + epq.enqueue(100) + epq.enqueue(1) + epq.enqueue(5) + epq.enqueue(7) + epq.enqueue(4) + epq.enqueue(64) + epq.enqueue(128) + print(epq) + print(epq.dequeue()) + print(epq.dequeue()) + print(epq.dequeue()) + print(epq.dequeue()) + print(epq.dequeue()) + print(epq) + print(epq.dequeue()) + print(epq.dequeue()) + print(epq.dequeue()) + print(epq.dequeue()) + print(epq.dequeue()) + + +if __name__ == "__main__": + fixed_priority_queue() + element_priority_queue() From 86fb2991d5f722a94c77df44b3a2de4cb0cbd4d7 Mon Sep 17 00:00:00 2001 From: poloso Date: Thu, 17 Sep 2020 02:41:10 -0500 Subject: [PATCH 0771/1071] Corrected filename and include static types (#2440) * Corrected name and include static types - The name of the file is now compliant with python naming conventions - Add static type as stated in contributing guidelines * Apply suggestions from code review - Delete documentation line to run doctests - Delete type hints for variables that comes from functions Co-authored-by: Christian Clauss * Add edge cases tests. * print(f"{target} was {not_str}found in {sequence}") Co-authored-by: Christian Clauss --- searches/simple-binary-search.py | 26 ---------------- searches/simple_binary_search.py | 53 ++++++++++++++++++++++++++++++++ 2 files changed, 53 insertions(+), 26 deletions(-) delete mode 100644 searches/simple-binary-search.py create mode 100644 searches/simple_binary_search.py diff --git a/searches/simple-binary-search.py b/searches/simple-binary-search.py deleted file mode 100644 index 80e43ea346b2..000000000000 --- a/searches/simple-binary-search.py +++ /dev/null @@ -1,26 +0,0 @@ -# A binary search implementation to test if a number is in a list of elements - - -def binary_search(a_list, item): - """ - >>> test_list = [0, 1, 2, 8, 13, 17, 19, 32, 42] - >>> print(binary_search(test_list, 3)) - False - >>> print(binary_search(test_list, 13)) - True - """ - if len(a_list) == 0: - return False - midpoint = len(a_list) // 2 - if a_list[midpoint] == item: - return True - if item < a_list[midpoint]: - return binary_search(a_list[:midpoint], item) - else: - return binary_search(a_list[midpoint + 1 :], item) - - -if __name__ == "__main__": - import doctest - - doctest.testmod() diff --git a/searches/simple_binary_search.py b/searches/simple_binary_search.py new file mode 100644 index 000000000000..1d898e2d9ee0 --- /dev/null +++ b/searches/simple_binary_search.py @@ -0,0 +1,53 @@ +""" +Pure Python implementation of a binary search algorithm. + +For doctests run following command: +python3 -m doctest -v simple_binary_search.py + +For manual testing run: +python3 simple_binary_search.py +""" +from typing import List + + +def binary_search(a_list: List[int], item: int) -> bool: + """ + >>> test_list = [0, 1, 2, 8, 13, 17, 19, 32, 42] + >>> print(binary_search(test_list, 3)) + False + >>> print(binary_search(test_list, 13)) + True + >>> print(binary_search([4, 4, 5, 6, 7], 4)) + True + >>> print(binary_search([4, 4, 5, 6, 7], -10)) + False + >>> print(binary_search([-18, 2], -18)) + True + >>> print(binary_search([5], 5)) + True + >>> print(binary_search(['a', 'c', 'd'], 'c')) + True + >>> print(binary_search(['a', 'c', 'd'], 'f')) + False + >>> print(binary_search([], 1)) + False + >>> print(binary_search([.1, .4 , -.1], .1)) + True + """ + if len(a_list) == 0: + return False + midpoint = len(a_list) // 2 + if a_list[midpoint] == item: + return True + if item < a_list[midpoint]: + return binary_search(a_list[:midpoint], item) + else: + return binary_search(a_list[midpoint + 1:], item) + + +if __name__ == "__main__": + user_input = input("Enter numbers separated by comma:\n").strip() + sequence = [int(item.strip()) for item in user_input.split(",")] + target = int(input("Enter the number to be found in the list:\n").strip()) + not_str = "" if binary_search(sequence, target) else "not " + print(f"{target} was {not_str}found in {sequence}") From 2de2267319c2c44e416b840daa370ef463d655fe Mon Sep 17 00:00:00 2001 From: Du Yuanchao Date: Thu, 17 Sep 2020 17:37:53 +0800 Subject: [PATCH 0772/1071] Updated problem_06 in Project Euler (#2439) * * rename variable * fix type hint * fix doctest * added test function * fixed import error * fixup! Format Python code with psf/black push Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> --- project_euler/problem_06/sol1.py | 16 +++++++++------- project_euler/problem_06/sol2.py | 12 +++++++----- project_euler/problem_06/sol3.py | 5 ++++- project_euler/problem_06/sol4.py | 5 ++++- project_euler/problem_06/test_solutions.py | 18 ++++++++++++++++++ 5 files changed, 42 insertions(+), 14 deletions(-) create mode 100644 project_euler/problem_06/test_solutions.py diff --git a/project_euler/problem_06/sol1.py b/project_euler/problem_06/sol1.py index 513b354679a9..d6506ea2839c 100644 --- a/project_euler/problem_06/sol1.py +++ b/project_euler/problem_06/sol1.py @@ -15,7 +15,7 @@ """ -def solution(n): +def solution(n: int) -> int: """Returns the difference between the sum of the squares of the first n natural numbers and the square of the sum. @@ -28,14 +28,16 @@ def solution(n): >>> solution(50) 1582700 """ - suma = 0 - sumb = 0 + sum_of_squares = 0 + sum_of_ints = 0 for i in range(1, n + 1): - suma += i ** 2 - sumb += i - sum = sumb ** 2 - suma - return sum + sum_of_squares += i ** 2 + sum_of_ints += i + return sum_of_ints ** 2 - sum_of_squares if __name__ == "__main__": + import doctest + + doctest.testmod() print(solution(int(input().strip()))) diff --git a/project_euler/problem_06/sol2.py b/project_euler/problem_06/sol2.py index 18cdb51752ea..5032775f9f77 100644 --- a/project_euler/problem_06/sol2.py +++ b/project_euler/problem_06/sol2.py @@ -15,7 +15,7 @@ """ -def solution(n): +def solution(n: int) -> int: """Returns the difference between the sum of the squares of the first n natural numbers and the square of the sum. @@ -28,11 +28,13 @@ def solution(n): >>> solution(50) 1582700 """ - suma = n * (n + 1) / 2 - suma **= 2 - sumb = n * (n + 1) * (2 * n + 1) / 6 - return int(suma - sumb) + sum_cubes = (n * (n + 1) // 2) ** 2 + sum_squares = n * (n + 1) * (2 * n + 1) // 6 + return sum_cubes - sum_squares if __name__ == "__main__": + import doctest + + doctest.testmod() print(solution(int(input().strip()))) diff --git a/project_euler/problem_06/sol3.py b/project_euler/problem_06/sol3.py index ee739c9a1293..385ad05151fb 100644 --- a/project_euler/problem_06/sol3.py +++ b/project_euler/problem_06/sol3.py @@ -16,7 +16,7 @@ import math -def solution(n): +def solution(n: int) -> int: """Returns the difference between the sum of the squares of the first n natural numbers and the square of the sum. @@ -35,4 +35,7 @@ def solution(n): if __name__ == "__main__": + import doctest + + doctest.testmod() print(solution(int(input().strip()))) diff --git a/project_euler/problem_06/sol4.py b/project_euler/problem_06/sol4.py index 07eed57ba9b5..912a3e8f6096 100644 --- a/project_euler/problem_06/sol4.py +++ b/project_euler/problem_06/sol4.py @@ -15,7 +15,7 @@ """ -def solution(n): +def solution(n: int) -> int: """Returns the difference between the sum of the squares of the first n natural numbers and the square of the sum. @@ -36,4 +36,7 @@ def solution(n): if __name__ == "__main__": + import doctest + + doctest.testmod() print(solution(int(input("Enter a number: ").strip()))) diff --git a/project_euler/problem_06/test_solutions.py b/project_euler/problem_06/test_solutions.py new file mode 100644 index 000000000000..6d5337789655 --- /dev/null +++ b/project_euler/problem_06/test_solutions.py @@ -0,0 +1,18 @@ +from .sol1 import solution as sol1 +from .sol2 import solution as sol2 +from .sol3 import solution as sol3 +from .sol4 import solution as sol4 + + +def test_solutions() -> None: + """ + >>> test_solutions() + """ + assert sol1(10) == sol2(10) == sol3(10) == sol4(10) == 2640 + assert sol1(15) == sol2(15) == sol3(15) == sol4(15) == 13160 + assert sol1(20) == sol2(20) == sol3(20) == sol4(20) == 41230 + assert sol1(50) == sol2(50) == sol3(50) == sol4(50) == 1582700 + + +if __name__ == "__main__": + test_solutions() From 0dea049f4462444c4d44a7c66b092cef1245a646 Mon Sep 17 00:00:00 2001 From: avych <7556744+avych@users.noreply.github.com> Date: Fri, 18 Sep 2020 12:07:49 +0530 Subject: [PATCH 0773/1071] Added static type checking to polynom-for-points.py towards issue #2128 (#2335) * Added static type checking to linear_algebra/src/polynom-for-points.py * Fixed TravisCI errors * Update polynom-for-points.py Co-authored-by: Christian Clauss --- linear_algebra/src/polynom-for-points.py | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/linear_algebra/src/polynom-for-points.py b/linear_algebra/src/polynom-for-points.py index dc0c3d95102e..8db89fc2c1ea 100644 --- a/linear_algebra/src/polynom-for-points.py +++ b/linear_algebra/src/polynom-for-points.py @@ -1,4 +1,7 @@ -def points_to_polynomial(coordinates): +from typing import List + + +def points_to_polynomial(coordinates: List[List[int]]) -> str: """ coordinates is a two dimensional matrix: [[x, y], [x, y], ...] number of points you want to use @@ -57,7 +60,7 @@ def points_to_polynomial(coordinates): while count_of_line < x: count_in_line = 0 a = coordinates[count_of_line][0] - count_line = [] + count_line: List[int] = [] while count_in_line < x: count_line.append(a ** (x - (count_in_line + 1))) count_in_line += 1 @@ -66,7 +69,7 @@ def points_to_polynomial(coordinates): count_of_line = 0 # put the y values into a vector - vector = [] + vector: List[int] = [] while count_of_line < x: vector.append(coordinates[count_of_line][1]) count_of_line += 1 @@ -80,7 +83,7 @@ def points_to_polynomial(coordinates): zahlen += 1 if zahlen == x: break - bruch = (matrix[zahlen][count]) / (matrix[count][count]) + bruch = matrix[zahlen][count] / matrix[count][count] for counting_columns, item in enumerate(matrix[count]): # manipulating all the values in the matrix matrix[zahlen][counting_columns] -= item * bruch @@ -91,7 +94,7 @@ def points_to_polynomial(coordinates): count = 0 # make solutions - solution = [] + solution: List[str] = [] while count < x: solution.append(vector[count] / matrix[count][count]) count += 1 @@ -100,7 +103,7 @@ def points_to_polynomial(coordinates): solved = "f(x)=" while count < x: - remove_e = str(solution[count]).split("E") + remove_e: List[str] = str(solution[count]).split("E") if len(remove_e) > 1: solution[count] = remove_e[0] + "*10^" + remove_e[1] solved += "x^" + str(x - (count + 1)) + "*" + str(solution[count]) From dc415ec14a7918a8dce0af6ab34f6dc88c55852e Mon Sep 17 00:00:00 2001 From: Du Yuanchao Date: Fri, 18 Sep 2020 15:55:02 +0800 Subject: [PATCH 0774/1071] Added double linear search recursion (#2445) * double linear search recursion * fixup! Format Python code with psf/black push Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> --- searches/double_linear_search_recursion.py | 35 ++++++++++++++++++++++ searches/simple_binary_search.py | 2 +- 2 files changed, 36 insertions(+), 1 deletion(-) create mode 100644 searches/double_linear_search_recursion.py diff --git a/searches/double_linear_search_recursion.py b/searches/double_linear_search_recursion.py new file mode 100644 index 000000000000..1c483e974ca7 --- /dev/null +++ b/searches/double_linear_search_recursion.py @@ -0,0 +1,35 @@ +def search(list_data: list, key: int, left: int = 0, right: int = 0) -> int: + """ + Iterate through the array to find the index of key using recursion. + :param list_data: the list to be searched + :param key: the key to be searched + :param left: the index of first element + :param right: the index of last element + :return: the index of key value if found, -1 otherwise. + + >>> search(list(range(0, 11)), 5) + 5 + >>> search([1, 2, 4, 5, 3], 4) + 2 + >>> search([1, 2, 4, 5, 3], 6) + -1 + >>> search([5], 5) + 0 + >>> search([], 1) + -1 + """ + right = right or len(list_data) - 1 + if left > right: + return -1 + elif list_data[left] == key: + return left + elif list_data[right] == key: + return right + else: + return search(list_data, key, left + 1, right - 1) + + +if __name__ == "__main__": + import doctest + + doctest.testmod() diff --git a/searches/simple_binary_search.py b/searches/simple_binary_search.py index 1d898e2d9ee0..b6215312fb2d 100644 --- a/searches/simple_binary_search.py +++ b/searches/simple_binary_search.py @@ -42,7 +42,7 @@ def binary_search(a_list: List[int], item: int) -> bool: if item < a_list[midpoint]: return binary_search(a_list[:midpoint], item) else: - return binary_search(a_list[midpoint + 1:], item) + return binary_search(a_list[midpoint + 1 :], item) if __name__ == "__main__": From ecac7b097371b3f87bfd40b0d7d01d933f38b726 Mon Sep 17 00:00:00 2001 From: kanthuc Date: Fri, 18 Sep 2020 13:53:50 -0700 Subject: [PATCH 0775/1071] Contains loops.py add (#2442) * added an algorithm which checks a linked list for loops and returns true if one is found * added doctests and clarified meaning of loop * Define Node.__iter__() * Update and rename has_loop.py to has_duplicate_data.py * Update has_duplicate_data.py * Update has_duplicate_data.py * Update and rename has_duplicate_data.py to has_loop.py * Update has_loop.py Co-authored-by: Christian Clauss --- data_structures/linked_list/has_loop.py | 60 +++++++++++++++++++++++++ 1 file changed, 60 insertions(+) create mode 100644 data_structures/linked_list/has_loop.py diff --git a/data_structures/linked_list/has_loop.py b/data_structures/linked_list/has_loop.py new file mode 100644 index 000000000000..405ece7e27c8 --- /dev/null +++ b/data_structures/linked_list/has_loop.py @@ -0,0 +1,60 @@ +from typing import Any + + +class ContainsLoopError(Exception): + pass + + +class Node: + def __init__(self, data: Any) -> None: + self.data = data + self.next_node = None + + def __iter__(self): + node = self + visited = [] + while node: + if node in visited: + raise ContainsLoopError + visited.append(node) + yield node.data + node = node.next_node + + @property + def has_loop(self) -> bool: + """ + A loop is when the exact same Node appears more than once in a linked list. + >>> root_node = Node(1) + >>> root_node.next_node = Node(2) + >>> root_node.next_node.next_node = Node(3) + >>> root_node.next_node.next_node.next_node = Node(4) + >>> root_node.has_loop + False + >>> root_node.next_node.next_node.next_node = root_node.next_node + >>> root_node.has_loop + True + """ + try: + list(self) + return False + except ContainsLoopError: + return True + + +if __name__ == "__main__": + root_node = Node(1) + root_node.next_node = Node(2) + root_node.next_node.next_node = Node(3) + root_node.next_node.next_node.next_node = Node(4) + print(root_node.has_loop) # False + root_node.next_node.next_node.next_node = root_node.next_node + print(root_node.has_loop) # True + + root_node = Node(5) + root_node.next_node = Node(6) + root_node.next_node.next_node = Node(5) + root_node.next_node.next_node.next_node = Node(6) + print(root_node.has_loop) # False + + root_node = Node(1) + print(root_node.has_loop) # False From 363858ef3baf4e1a34822d8dd0945de3e3e384cf Mon Sep 17 00:00:00 2001 From: Christian Clauss Date: Sat, 19 Sep 2020 07:13:10 +0200 Subject: [PATCH 0776/1071] hyphen_files = [file for file in filepaths if "-" in file] (#2447) * hyphen_files = [file for file in filepaths if "-" in file] * updating DIRECTORY.md * Rename recursive-quick-sort.py to recursive_quick_sort.py * updating DIRECTORY.md * Rename aho-corasick.py to aho_corasick.py * updating DIRECTORY.md * Rename polynom-for-points.py to polynom_for_points.py * updating DIRECTORY.md Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> --- DIRECTORY.md | 12 ++++++++---- .../{polynom-for-points.py => polynom_for_points.py} | 0 scripts/validate_filenames.py | 7 ++++++- ...cursive-quick-sort.py => recursive_quick_sort.py} | 0 strings/{aho-corasick.py => aho_corasick.py} | 0 5 files changed, 14 insertions(+), 5 deletions(-) rename linear_algebra/src/{polynom-for-points.py => polynom_for_points.py} (100%) rename sorts/{recursive-quick-sort.py => recursive_quick_sort.py} (100%) rename strings/{aho-corasick.py => aho_corasick.py} (100%) diff --git a/DIRECTORY.md b/DIRECTORY.md index 1dceb887940c..3c066122a167 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -131,6 +131,7 @@ * [Deque Doubly](https://github.com/TheAlgorithms/Python/blob/master/data_structures/linked_list/deque_doubly.py) * [Doubly Linked List](https://github.com/TheAlgorithms/Python/blob/master/data_structures/linked_list/doubly_linked_list.py) * [From Sequence](https://github.com/TheAlgorithms/Python/blob/master/data_structures/linked_list/from_sequence.py) + * [Has Loop](https://github.com/TheAlgorithms/Python/blob/master/data_structures/linked_list/has_loop.py) * [Is Palindrome](https://github.com/TheAlgorithms/Python/blob/master/data_structures/linked_list/is_palindrome.py) * [Middle Element Of Linked List](https://github.com/TheAlgorithms/Python/blob/master/data_structures/linked_list/middle_element_of_linked_list.py) * [Print Reverse](https://github.com/TheAlgorithms/Python/blob/master/data_structures/linked_list/print_reverse.py) @@ -141,6 +142,7 @@ * [Circular Queue](https://github.com/TheAlgorithms/Python/blob/master/data_structures/queue/circular_queue.py) * [Double Ended Queue](https://github.com/TheAlgorithms/Python/blob/master/data_structures/queue/double_ended_queue.py) * [Linked Queue](https://github.com/TheAlgorithms/Python/blob/master/data_structures/queue/linked_queue.py) + * [Priority Queue Using List](https://github.com/TheAlgorithms/Python/blob/master/data_structures/queue/priority_queue_using_list.py) * [Queue On List](https://github.com/TheAlgorithms/Python/blob/master/data_structures/queue/queue_on_list.py) * [Queue On Pseudo Stack](https://github.com/TheAlgorithms/Python/blob/master/data_structures/queue/queue_on_pseudo_stack.py) * Stacks @@ -303,7 +305,7 @@ ## Linear Algebra * Src * [Lib](https://github.com/TheAlgorithms/Python/blob/master/linear_algebra/src/lib.py) - * [Polynom-For-Points](https://github.com/TheAlgorithms/Python/blob/master/linear_algebra/src/polynom-for-points.py) + * [Polynom For Points](https://github.com/TheAlgorithms/Python/blob/master/linear_algebra/src/polynom_for_points.py) * [Power Iteration](https://github.com/TheAlgorithms/Python/blob/master/linear_algebra/src/power_iteration.py) * [Rayleigh Quotient](https://github.com/TheAlgorithms/Python/blob/master/linear_algebra/src/rayleigh_quotient.py) * [Test Linear Algebra](https://github.com/TheAlgorithms/Python/blob/master/linear_algebra/src/test_linear_algebra.py) @@ -518,6 +520,7 @@ * [Sol2](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_06/sol2.py) * [Sol3](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_06/sol3.py) * [Sol4](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_06/sol4.py) + * [Test Solutions](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_06/test_solutions.py) * Problem 07 * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_07/sol1.py) * [Sol2](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_07/sol2.py) @@ -648,6 +651,7 @@ ## Searches * [Binary Search](https://github.com/TheAlgorithms/Python/blob/master/searches/binary_search.py) * [Double Linear Search](https://github.com/TheAlgorithms/Python/blob/master/searches/double_linear_search.py) + * [Double Linear Search Recursion](https://github.com/TheAlgorithms/Python/blob/master/searches/double_linear_search_recursion.py) * [Fibonacci Search](https://github.com/TheAlgorithms/Python/blob/master/searches/fibonacci_search.py) * [Hill Climbing](https://github.com/TheAlgorithms/Python/blob/master/searches/hill_climbing.py) * [Interpolation Search](https://github.com/TheAlgorithms/Python/blob/master/searches/interpolation_search.py) @@ -655,7 +659,7 @@ * [Linear Search](https://github.com/TheAlgorithms/Python/blob/master/searches/linear_search.py) * [Quick Select](https://github.com/TheAlgorithms/Python/blob/master/searches/quick_select.py) * [Sentinel Linear Search](https://github.com/TheAlgorithms/Python/blob/master/searches/sentinel_linear_search.py) - * [Simple-Binary-Search](https://github.com/TheAlgorithms/Python/blob/master/searches/simple-binary-search.py) + * [Simple Binary Search](https://github.com/TheAlgorithms/Python/blob/master/searches/simple_binary_search.py) * [Simulated Annealing](https://github.com/TheAlgorithms/Python/blob/master/searches/simulated_annealing.py) * [Tabu Search](https://github.com/TheAlgorithms/Python/blob/master/searches/tabu_search.py) * [Ternary Search](https://github.com/TheAlgorithms/Python/blob/master/searches/ternary_search.py) @@ -689,9 +693,9 @@ * [Radix Sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/radix_sort.py) * [Random Normal Distribution Quicksort](https://github.com/TheAlgorithms/Python/blob/master/sorts/random_normal_distribution_quicksort.py) * [Random Pivot Quick Sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/random_pivot_quick_sort.py) - * [Recursive-Quick-Sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/recursive-quick-sort.py) * [Recursive Bubble Sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/recursive_bubble_sort.py) * [Recursive Insertion Sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/recursive_insertion_sort.py) + * [Recursive Quick Sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/recursive_quick_sort.py) * [Selection Sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/selection_sort.py) * [Shell Sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/shell_sort.py) * [Stooge Sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/stooge_sort.py) @@ -703,7 +707,7 @@ * [Wiggle Sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/wiggle_sort.py) ## Strings - * [Aho-Corasick](https://github.com/TheAlgorithms/Python/blob/master/strings/aho-corasick.py) + * [Aho Corasick](https://github.com/TheAlgorithms/Python/blob/master/strings/aho_corasick.py) * [Boyer Moore Search](https://github.com/TheAlgorithms/Python/blob/master/strings/boyer_moore_search.py) * [Capitalize](https://github.com/TheAlgorithms/Python/blob/master/strings/capitalize.py) * [Check Anagrams](https://github.com/TheAlgorithms/Python/blob/master/strings/check_anagrams.py) diff --git a/linear_algebra/src/polynom-for-points.py b/linear_algebra/src/polynom_for_points.py similarity index 100% rename from linear_algebra/src/polynom-for-points.py rename to linear_algebra/src/polynom_for_points.py diff --git a/scripts/validate_filenames.py b/scripts/validate_filenames.py index 01712e8f7707..ca4e2c9fcd57 100755 --- a/scripts/validate_filenames.py +++ b/scripts/validate_filenames.py @@ -17,12 +17,17 @@ print(f"{len(space_files)} files contain space characters:") print("\n".join(space_files) + "\n") +hyphen_files = [file for file in filepaths if "-" in file] +if hyphen_files: + print(f"{len(hyphen_files)} files contain space characters:") + print("\n".join(hyphen_files) + "\n") + nodir_files = [file for file in filepaths if os.sep not in file] if nodir_files: print(f"{len(nodir_files)} files are not in a directory:") print("\n".join(nodir_files) + "\n") -bad_files = len(upper_files + space_files + nodir_files) +bad_files = len(upper_files + space_files + hyphen_files + nodir_files) if bad_files: import sys diff --git a/sorts/recursive-quick-sort.py b/sorts/recursive_quick_sort.py similarity index 100% rename from sorts/recursive-quick-sort.py rename to sorts/recursive_quick_sort.py diff --git a/strings/aho-corasick.py b/strings/aho_corasick.py similarity index 100% rename from strings/aho-corasick.py rename to strings/aho_corasick.py From 697495b017512268ff42d35c05fd4a69de57623e Mon Sep 17 00:00:00 2001 From: Christian Clauss Date: Sat, 19 Sep 2020 07:25:18 +0200 Subject: [PATCH 0777/1071] Fix copy / paste oversight (#2448) --- scripts/validate_filenames.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/validate_filenames.py b/scripts/validate_filenames.py index ca4e2c9fcd57..f09c1527cb0d 100755 --- a/scripts/validate_filenames.py +++ b/scripts/validate_filenames.py @@ -19,7 +19,7 @@ hyphen_files = [file for file in filepaths if "-" in file] if hyphen_files: - print(f"{len(hyphen_files)} files contain space characters:") + print(f"{len(hyphen_files)} files contain hyphen characters:") print("\n".join(hyphen_files) + "\n") nodir_files = [file for file in filepaths if os.sep not in file] From b22596cd965ef53b0faa4e2d32044a21c1458aab Mon Sep 17 00:00:00 2001 From: mohammadreza490 <47437328+mohammadreza490@users.noreply.github.com> Date: Sat, 19 Sep 2020 06:36:56 +0100 Subject: [PATCH 0778/1071] bin_to_octal (#2431) * bin_to_octal Converts binary values to the octal equivalent. * Update bin_to_octal * Update conversions/bin_to_octal Co-authored-by: Christian Clauss * Update conversions/bin_to_octal Co-authored-by: Du Yuanchao * Update conversions/bin_to_octal Co-authored-by: Du Yuanchao * Update conversions/bin_to_octal Co-authored-by: Du Yuanchao * Rename bin_to_octal to bin_to_octal.py Co-authored-by: Christian Clauss Co-authored-by: Du Yuanchao --- conversions/bin_to_octal.py | 45 +++++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100644 conversions/bin_to_octal.py diff --git a/conversions/bin_to_octal.py b/conversions/bin_to_octal.py new file mode 100644 index 000000000000..39aa4646e7a5 --- /dev/null +++ b/conversions/bin_to_octal.py @@ -0,0 +1,45 @@ +""" +The function below will convert any binary string to the octal equivalent. + +>>> bin_to_octal("1111") +'17' + +>>> bin_to_octal("101010101010011") +'52523' + +>>> bin_to_octal("") +Traceback (most recent call last): +... +ValueError: Empty string was passed to the function +>>> bin_to_octal("a-1") +Traceback (most recent call last): +... +ValueError: Non-binary value was passed to the function +""" + + +def bin_to_octal(bin_string: str) -> str: + if not all(char in "01" for char in bin_string): + raise ValueError("Non-binary value was passed to the function") + if not bin_string: + raise ValueError("Empty string was passed to the function") + oct_string = "" + while len(bin_string) % 3 != 0: + bin_string = "0" + bin_string + bin_string_in_3_list = [ + bin_string[index: index + 3] + for index, value in enumerate(bin_string) + if index % 3 == 0 + ] + for bin_group in bin_string_in_3_list: + oct_val = 0 + for index, val in enumerate(bin_group): + oct_val += int(2 ** (2 - index) * int(val)) + oct_string += str(oct_val) + return oct_string + + +if __name__ == "__main__": + from doctest import testmod + + testmod() From b05081a717546f021014454d03bcf2f6145148aa Mon Sep 17 00:00:00 2001 From: Christian Clauss Date: Sat, 19 Sep 2020 08:58:08 +0200 Subject: [PATCH 0779/1071] Update and rename bin_to_octal.py to binary_to_octal.py (#2449) * Update and rename bin_to_octal.py to binary_to_octal.py * fixup! Format Python code with psf/black push Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> --- conversions/{bin_to_octal.py => binary_to_octal.py} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename conversions/{bin_to_octal.py => binary_to_octal.py} (96%) diff --git a/conversions/bin_to_octal.py b/conversions/binary_to_octal.py similarity index 96% rename from conversions/bin_to_octal.py rename to conversions/binary_to_octal.py index 39aa4646e7a5..8b594887867e 100644 --- a/conversions/bin_to_octal.py +++ b/conversions/binary_to_octal.py @@ -27,7 +27,7 @@ def bin_to_octal(bin_string: str) -> str: while len(bin_string) % 3 != 0: bin_string = "0" + bin_string bin_string_in_3_list = [ - bin_string[index: index + 3] + bin_string[index : index + 3] for index, value in enumerate(bin_string) if index % 3 == 0 ] From 9b73884def8ea77e563764591a3eeb0fee1d7024 Mon Sep 17 00:00:00 2001 From: Susmith98 <33018940+susmith98@users.noreply.github.com> Date: Sun, 20 Sep 2020 01:19:37 +0530 Subject: [PATCH 0780/1071] Added a function that checks if given string can be rearranged to form a palindrome. (#2450) * Added check_if_string_can_be_rearranged_as_palindrome function. * Added counter implementation and benchmark function. * flake changes * Update and rename check_if_string_can_be_converted_to_palindrome.py to can_string_be_rearranged_as_palindrome.py * Update can_string_be_rearranged_as_palindrome.py * # Co-authored-by: svedire Co-authored-by: Christian Clauss --- .../can_string_be_rearranged_as_palindrome.py | 113 ++++++++++++++++++ 1 file changed, 113 insertions(+) create mode 100644 strings/can_string_be_rearranged_as_palindrome.py diff --git a/strings/can_string_be_rearranged_as_palindrome.py b/strings/can_string_be_rearranged_as_palindrome.py new file mode 100644 index 000000000000..92bc3b95b243 --- /dev/null +++ b/strings/can_string_be_rearranged_as_palindrome.py @@ -0,0 +1,113 @@ +# Created by susmith98 + +from collections import Counter +from timeit import timeit + +# Problem Description: +# Check if characters of the given string can be rearranged to form a palindrome. +# Counter is faster for long strings and non-Counter is faster for short strings. + + +def can_string_be_rearranged_as_palindrome_counter(input_str: str = "",) -> bool: + """ + A Palindrome is a String that reads the same forward as it does backwards. + Examples of Palindromes mom, dad, malayalam + >>> can_string_be_rearranged_as_palindrome_counter("Momo") + True + >>> can_string_be_rearranged_as_palindrome_counter("Mother") + False + >>> can_string_be_rearranged_as_palindrome_counter("Father") + False + >>> can_string_be_rearranged_as_palindrome_counter("A man a plan a canal Panama") + True + """ + return sum(c % 2 for c in Counter(input_str.replace(" ", "").lower()).values()) < 2 + + +def can_string_be_rearranged_as_palindrome(input_str: str = "") -> bool: + """ + A Palindrome is a String that reads the same forward as it does backwards. + Examples of Palindromes mom, dad, malayalam + >>> can_string_be_rearranged_as_palindrome("Momo") + True + >>> can_string_be_rearranged_as_palindrome("Mother") + False + >>> can_string_be_rearranged_as_palindrome("Father") + False + >>> can_string_be_rearranged_as_palindrome_counter("A man a plan a canal Panama") + True + """ + if len(input_str) == 0: + return True + lower_case_input_str = input_str.replace(" ", "").lower() + # character_freq_dict: Stores the frequency of every character in the input string + character_freq_dict = {} + + for character in lower_case_input_str: + character_freq_dict[character] = character_freq_dict.get(character, 0) + 1 + """ + Above line of code is equivalent to: + 1) Getting the frequency of current character till previous index + >>> character_freq = character_freq_dict.get(character, 0) + 2) Incrementing the frequency of current character by 1 + >>> character_freq = character_freq + 1 + 3) Updating the frequency of current character + >>> character_freq_dict[character] = character_freq + """ + """ + OBSERVATIONS: + Even length palindrome + -> Every character appears even no.of times. + Odd length palindrome + -> Every character appears even no.of times except for one character. + LOGIC: + Step 1: We'll count number of characters that appear odd number of times i.e oddChar + Step 2:If we find more than 1 character that appears odd number of times, + It is not possible to rearrange as a palindrome + """ + oddChar = 0 + + for character_count in character_freq_dict.values(): + if character_count % 2: + oddChar += 1 + if oddChar > 1: + return False + return True + + +def benchmark(input_str: str = "") -> None: + """ + Benchmark code for comparing above 2 functions + """ + print("\nFor string = ", input_str, ":") + print( + "> can_string_be_rearranged_as_palindrome_counter()", + "\tans =", + can_string_be_rearranged_as_palindrome_counter(input_str), + "\ttime =", + timeit( + "z.can_string_be_rearranged_as_palindrome_counter(z.check_str)", + setup="import __main__ as z", + ), + "seconds", + ) + print( + "> can_string_be_rearranged_as_palindrome()", + "\tans =", + can_string_be_rearranged_as_palindrome(input_str), + "\ttime =", + timeit( + "z.can_string_be_rearranged_as_palindrome(z.check_str)", + setup="import __main__ as z", + ), + "seconds", + ) + + +if __name__ == "__main__": + check_str = input( + "Enter string to determine if it can be rearranged as a palindrome or not: " + ).strip() + benchmark(check_str) + status = can_string_be_rearranged_as_palindrome_counter(check_str) + print(f"{check_str} can {'' if status else 'not '}be rearranged as a palindrome") From ea0759dbaa99721173bbe1a3c45cddf5f8ad4109 Mon Sep 17 00:00:00 2001 From: Dhruv Date: Sun, 20 Sep 2020 17:22:13 +0530 Subject: [PATCH 0781/1071] Create problem_54 in project Euler (#2451) * Add solution and test files for project euler 54 * Update sol1.py * updating DIRECTORY.md * Fix: use proper path to open files * Commit suggestions: - Use list comprehension instead of map - Sort imports using isort * Changes made as suggested (simplified a lot): - List and set comprehension instead of itemgetter - Using enumerate as it's easy to read - Divided into list of card values and set of card suit as set will remove all the duplicate values. So, no need for double indexing. - Add test for testing multiple calls to five_high_straight function * Add suggestions and simplified: - Split generate_random_hands function into two: - First will generate a random hand - Second, which will be called, will return a generator object Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> --- DIRECTORY.md | 4 + project_euler/problem_54/__init__.py | 0 project_euler/problem_54/poker_hands.txt | 1000 +++++++++++++++++++ project_euler/problem_54/sol1.py | 358 +++++++ project_euler/problem_54/test_poker_hand.py | 228 +++++ 5 files changed, 1590 insertions(+) create mode 100644 project_euler/problem_54/__init__.py create mode 100644 project_euler/problem_54/poker_hands.txt create mode 100644 project_euler/problem_54/sol1.py create mode 100644 project_euler/problem_54/test_poker_hand.py diff --git a/DIRECTORY.md b/DIRECTORY.md index 3c066122a167..d91d34803a1c 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -86,6 +86,7 @@ ## Conversions * [Binary To Decimal](https://github.com/TheAlgorithms/Python/blob/master/conversions/binary_to_decimal.py) + * [Binary To Octal](https://github.com/TheAlgorithms/Python/blob/master/conversions/binary_to_octal.py) * [Decimal To Any](https://github.com/TheAlgorithms/Python/blob/master/conversions/decimal_to_any.py) * [Decimal To Binary](https://github.com/TheAlgorithms/Python/blob/master/conversions/decimal_to_binary.py) * [Decimal To Hexadecimal](https://github.com/TheAlgorithms/Python/blob/master/conversions/decimal_to_hexadecimal.py) @@ -628,6 +629,9 @@ * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_52/sol1.py) * Problem 53 * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_53/sol1.py) + * Problem 54 + * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_54/sol1.py) + * [Test Poker Hand](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_54/test_poker_hand.py) * Problem 55 * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_55/sol1.py) * Problem 551 diff --git a/project_euler/problem_54/__init__.py b/project_euler/problem_54/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/project_euler/problem_54/poker_hands.txt b/project_euler/problem_54/poker_hands.txt new file mode 100644 index 000000000000..9ab00248c061 --- /dev/null +++ b/project_euler/problem_54/poker_hands.txt @@ -0,0 +1,1000 @@ +8C TS KC 9H 4S 7D 2S 5D 3S AC +5C AD 5D AC 9C 7C 5H 8D TD KS +3H 7H 6S KC JS QH TD JC 2D 8S +TH 8H 5C QS TC 9H 4D JC KS JS +7C 5H KC QH JD AS KH 4C AD 4S +5H KS 9C 7D 9H 8D 3S 5D 5C AH +6H 4H 5C 3H 2H 3S QH 5S 6S AS +TD 8C 4H 7C TC KC 4C 3H 7S KS +7C 9C 6D KD 3H 4C QS QC AC KH +JC 6S 5H 2H 2D KD 9D 7C AS JS +AD QH TH 9D 8H TS 6D 3S AS AC +2H 4S 5C 5S TC KC JD 6C TS 3C +QD AS 6H JS 2C 3D 9H KC 4H 8S +KD 8S 9S 7C 2S 3S 6D 6S 4H KC +3C 8C 2D 7D 4D 9S 4S QH 4H JD +8C KC 7S TC 2D TS 8H QD AC 5C +3D KH QD 6C 6S AD AS 8H 2H QS +6S 8D 4C 8S 6C QH TC 6D 7D 9D +2S 8D 8C 4C TS 9S 9D 9C AC 3D +3C QS 2S 4H JH 3D 2D TD 8S 9H +5H QS 8S 6D 3C 8C JD AS 7H 7D +6H TD 9D AS JH 6C QC 9S KD JC +AH 8S QS 4D TH AC TS 3C 3D 5C +5S 4D JS 3D 8H 6C TS 3S AD 8C +6D 7C 5D 5H 3S 5C JC 2H 5S 3D +5H 6H 2S KS 3D 5D JD 7H JS 8H +KH 4H AS JS QS QC TC 6D 7C KS +3D QS TS 2H JS 4D AS 9S JC KD +QD 5H 4D 5D KH 7H 3D JS KD 4H +2C 9H 6H 5C 9D 6C JC 2D TH 9S +7D 6D AS QD JH 4D JS 7C QS 5C +3H KH QD AD 8C 8H 3S TH 9D 5S +AH 9S 4D 9D 8S 4H JS 3C TC 8D +2C KS 5H QD 3S TS 9H AH AD 8S +5C 7H 5D KD 9H 4D 3D 2D KS AD +KS KC 9S 6D 2C QH 9D 9H TS TC +9C 6H 5D QH 4D AD 6D QC JS KH +9S 3H 9D JD 5C 4D 9H AS TC QH +2C 6D JC 9C 3C AD 9S KH 9D 7D +KC 9C 7C JC JS KD 3H AS 3C 7D +QD KH QS 2C 3S 8S 8H 9H 9C JC +QH 8D 3C KC 4C 4H 6D AD 9H 9D +3S KS QS 7H KH 7D 5H 5D JD AD +2H 2C 6H TH TC 7D 8D 4H 8C AS +4S 2H AC QC 3S 6D TH 4D 4C KH +4D TC KS AS 7C 3C 6D 2D 9H 6C +8C TD 5D QS 2C 7H 4C 9C 3H 9H +5H JH TS 7S TD 6H AD QD 8H 8S +5S AD 9C 8C 7C 8D 5H 9D 8S 2S +4H KH KS 9S 2S KC 5S AD 4S 7D +QS 9C QD 6H JS 5D AC 8D 2S AS +KH AC JC 3S 9D 9S 3C 9C 5S JS +AD 3C 3D KS 3S 5C 9C 8C TS 4S +JH 8D 5D 6H KD QS QD 3D 6C KC +8S JD 6C 3S 8C TC QC 3C QH JS +KC JC 8H 2S 9H 9C JH 8S 8C 9S +8S 2H QH 4D QC 9D KC AS TH 3C +8S 6H TH 7C 2H 6S 3C 3H AS 7S +QH 5S JS 4H 5H TS 8H AH AC JC +9D 8H 2S 4S TC JC 3C 7H 3H 5C +3D AD 3C 3S 4C QC AS 5D TH 8C +6S 9D 4C JS KH AH TS JD 8H AD +4C 6S 9D 7S AC 4D 3D 3S TC JD +AD 7H 6H 4H JH KC TD TS 7D 6S +8H JH TC 3S 8D 8C 9S 2C 5C 4D +2C 9D KC QH TH QS JC 9C 4H TS +QS 3C QD 8H KH 4H 8D TD 8S AC +7C 3C TH 5S 8H 8C 9C JD TC KD +QC TC JD TS 8C 3H 6H KD 7C TD +JH QS KS 9C 6D 6S AS 9H KH 6H +2H 4D AH 2D JH 6H TD 5D 4H JD +KD 8C 9S JH QD JS 2C QS 5C 7C +4S TC 7H 8D 2S 6H 7S 9C 7C KC +8C 5D 7H 4S TD QC 8S JS 4H KS +AD 8S JH 6D TD KD 7C 6C 2D 7D +JC 6H 6S JS 4H QH 9H AH 4C 3C +6H 5H AS 7C 7S 3D KH KC 5D 5C +JC 3D TD AS 4D 6D 6S QH JD KS +8C 7S 8S QH 2S JD 5C 7H AH QD +8S 3C 6H 6C 2C 8D TD 7D 4C 4D +5D QH KH 7C 2S 7H JS 6D QC QD +AD 6C 6S 7D TH 6H 2H 8H KH 4H +KS JS KD 5D 2D KH 7D 9C 8C 3D +9C 6D QD 3C KS 3S 7S AH JD 2D +AH QH AS JC 8S 8H 4C KC TH 7D +JC 5H TD 7C 5D KD 4C AD 8H JS +KC 2H AC AH 7D JH KH 5D 7S 6D +9S 5S 9C 6H 8S TD JD 9H 6C AC +7D 8S 6D TS KD 7H AC 5S 7C 5D +AH QC JC 4C TC 8C 2H TS 2C 7D +KD KC 6S 3D 7D 2S 8S 3H 5S 5C +8S 5D 8H 4C 6H KC 3H 7C 5S KD +JH 8C 3D 3C 6C KC TD 7H 7C 4C +JC KC 6H TS QS TD KS 8H 8C 9S +6C 5S 9C QH 7D AH KS KC 9S 2C +4D 4S 8H TD 9C 3S 7D 9D AS TH +6S 7D 3C 6H 5D KD 2C 5C 9D 9C +2H KC 3D AD 3H QD QS 8D JC 4S +8C 3H 9C 7C AD 5D JC 9D JS AS +5D 9H 5C 7H 6S 6C QC JC QD 9S +JC QS JH 2C 6S 9C QC 3D 4S TC +4H 5S 8D 3D 4D 2S KC 2H JS 2C +TD 3S TH KD 4D 7H JH JS KS AC +7S 8C 9S 2D 8S 7D 5C AD 9D AS +8C 7H 2S 6C TH 3H 4C 3S 8H AC +KD 5H JC 8H JD 2D 4H TD JH 5C +3D AS QH KS 7H JD 8S 5S 6D 5H +9S 6S TC QS JC 5C 5D 9C TH 8C +5H 3S JH 9H 2S 2C 6S 7S AS KS +8C QD JC QS TC QC 4H AC KH 6C +TC 5H 7D JH 4H 2H 8D JC KS 4D +5S 9C KH KD 9H 5C TS 3D 7D 2D +5H AS TC 4D 8C 2C TS 9D 3H 8D +6H 8D 2D 9H JD 6C 4S 5H 5S 6D +AD 9C JC 7D 6H 9S 6D JS 9H 3C +AD JH TC QS 4C 5D 9S 7C 9C AH +KD 6H 2H TH 8S QD KS 9D 9H AS +4H 8H 8D 5H 6C AH 5S AS AD 8S +QS 5D 4S 2H TD KS 5H AC 3H JC +9C 7D QD KD AC 6D 5H QH 6H 5S +KC AH QH 2H 7D QS 3H KS 7S JD +6C 8S 3H 6D KS QD 5D 5C 8H TC +9H 4D 4S 6S 9D KH QC 4H 6C JD +TD 2D QH 4S 6H JH KD 3C QD 8C +4S 6H 7C QD 9D AS AH 6S AD 3C +2C KC TH 6H 8D AH 5C 6D 8S 5D +TD TS 7C AD JC QD 9H 3C KC 7H +5D 4D 5S 8H 4H 7D 3H JD KD 2D +JH TD 6H QS 4S KD 5C 8S 7D 8H +AC 3D AS 8C TD 7H KH 5D 6C JD +9D KS 7C 6D QH TC JD KD AS KC +JH 8S 5S 7S 7D AS 2D 3D AD 2H +2H 5D AS 3C QD KC 6H 9H 9S 2C +9D 5D TH 4C JH 3H 8D TC 8H 9H +6H KD 2C TD 2H 6C 9D 2D JS 8C +KD 7S 3C 7C AS QH TS AD 8C 2S +QS 8H 6C JS 4C 9S QC AD TD TS +2H 7C TS TC 8C 3C 9H 2D 6D JC +TC 2H 8D JH KS 6D 3H TD TH 8H +9D TD 9H QC 5D 6C 8H 8C KC TS +2H 8C 3D AH 4D TH TC 7D 8H KC +TS 5C 2D 8C 6S KH AH 5H 6H KC +5S 5D AH TC 4C JD 8D 6H 8C 6C +KC QD 3D 8H 2D JC 9H 4H AD 2S +TD 6S 7D JS KD 4H QS 2S 3S 8C +4C 9H JH TS 3S 4H QC 5S 9S 9C +2C KD 9H JS 9S 3H JC TS 5D AC +AS 2H 5D AD 5H JC 7S TD JS 4C +2D 4S 8H 3D 7D 2C AD KD 9C TS +7H QD JH 5H JS AC 3D TH 4C 8H +6D KH KC QD 5C AD 7C 2D 4H AC +3D 9D TC 8S QD 2C JC 4H JD AH +6C TD 5S TC 8S AH 2C 5D AS AC +TH 7S 3D AS 6C 4C 7H 7D 4H AH +5C 2H KS 6H 7S 4H 5H 3D 3C 7H +3C 9S AC 7S QH 2H 3D 6S 3S 3H +2D 3H AS 2C 6H TC JS 6S 9C 6C +QH KD QD 6D AC 6H KH 2C TS 8C +8H 7D 3S 9H 5D 3H 4S QC 9S 5H +2D 9D 7H 6H 3C 8S 5H 4D 3S 4S +KD 9S 4S TC 7S QC 3S 8S 2H 7H +TC 3D 8C 3H 6C 2H 6H KS KD 4D +KC 3D 9S 3H JS 4S 8H 2D 6C 8S +6H QS 6C TC QD 9H 7D 7C 5H 4D +TD 9D 8D 6S 6C TC 5D TS JS 8H +4H KC JD 9H TC 2C 6S 5H 8H AS +JS 9C 5C 6S 9D JD 8H KC 4C 6D +4D 8D 8S 6C 7C 6H 7H 8H 5C KC +TC 3D JC 6D KS 9S 6H 7S 9C 2C +6C 3S KD 5H TS 7D 9H 9S 6H KH +3D QD 4C 6H TS AC 3S 5C 2H KD +4C AS JS 9S 7C TS 7H 9H JC KS +4H 8C JD 3H 6H AD 9S 4S 5S KS +4C 2C 7D 3D AS 9C 2S QS KC 6C +8S 5H 3D 2S AC 9D 6S 3S 4D TD +QD TH 7S TS 3D AC 7H 6C 5D QC +TC QD AD 9C QS 5C 8D KD 3D 3C +9D 8H AS 3S 7C 8S JD 2D 8D KC +4C TH AC QH JS 8D 7D 7S 9C KH +9D 8D 4C JH 2C 2S QD KD TS 4H +4D 6D 5D 2D JH 3S 8S 3H TC KH +AD 4D 2C QS 8C KD JH JD AH 5C +5C 6C 5H 2H JH 4H KS 7C TC 3H +3C 4C QC 5D JH 9C QD KH 8D TC +3H 9C JS 7H QH AS 7C 9H 5H JC +2D 5S QD 4S 3C KC 6S 6C 5C 4C +5D KH 2D TS 8S 9C AS 9S 7C 4C +7C AH 8C 8D 5S KD QH QS JH 2C +8C 9D AH 2H AC QC 5S 8H 7H 2C +QD 9H 5S QS QC 9C 5H JC TH 4H +6C 6S 3H 5H 3S 6H KS 8D AC 7S +AC QH 7H 8C 4S KC 6C 3D 3S TC +9D 3D JS TH AC 5H 3H 8S 3S TC +QD KH JS KS 9S QC 8D AH 3C AC +5H 6C KH 3S 9S JH 2D QD AS 8C +6C 4D 7S 7H 5S JC 6S 9H 4H JH +AH 5S 6H 9S AD 3S TH 2H 9D 8C +4C 8D 9H 7C QC AD 4S 9C KC 5S +9D 6H 4D TC 4C JH 2S 5D 3S AS +2H 6C 7C KH 5C AD QS TH JD 8S +3S 4S 7S AH AS KC JS 2S AD TH +JS KC 2S 7D 8C 5C 9C TS 5H 9D +7S 9S 4D TD JH JS KH 6H 5D 2C +JD JS JC TH 2D 3D QD 8C AC 5H +7S KH 5S 9D 5D TD 4S 6H 3C 2D +4S 5D AC 8D 4D 7C AD AS AH 9C +6S TH TS KS 2C QC AH AS 3C 4S +2H 8C 3S JC 5C 7C 3H 3C KH JH +7S 3H JC 5S 6H 4C 2S 4D KC 7H +4D 7C 4H 9S 8S 6S AD TC 6C JC +KH QS 3S TC 4C 8H 8S AC 3C TS +QD QS TH 3C TS 7H 7D AH TD JC +TD JD QC 4D 9S 7S TS AD 7D AC +AH 7H 4S 6D 7C 2H 9D KS JC TD +7C AH JD 4H 6D QS TS 2H 2C 5C +TC KC 8C 9S 4C JS 3C JC 6S AH +AS 7D QC 3D 5S JC JD 9D TD KH +TH 3C 2S 6H AH AC 5H 5C 7S 8H +QC 2D AC QD 2S 3S JD QS 6S 8H +KC 4H 3C 9D JS 6H 3S 8S AS 8C +7H KC 7D JD 2H JC QH 5S 3H QS +9H TD 3S 8H 7S AC 5C 6C AH 7C +8D 9H AH JD TD QS 7D 3S 9C 8S +AH QH 3C JD KC 4S 5S 5D TD KS +9H 7H 6S JH TH 4C 7C AD 5C 2D +7C KD 5S TC 9D 6S 6C 5D 2S TH +KC 9H 8D 5H 7H 4H QC 3D 7C AS +6S 8S QC TD 4S 5C TH QS QD 2S +8S 5H TH QC 9H 6S KC 7D 7C 5C +7H KD AH 4D KH 5C 4S 2D KC QH +6S 2C TD JC AS 4D 6C 8C 4H 5S +JC TC JD 5S 6S 8D AS 9D AD 3S +6D 6H 5D 5S TC 3D 7D QS 9D QD +4S 6C 8S 3S 7S AD KS 2D 7D 7C +KC QH JC AC QD 5D 8D QS 7H 7D +JS AH 8S 5H 3D TD 3H 4S 6C JH +4S QS 7D AS 9H JS KS 6D TC 5C +2D 5C 6H TC 4D QH 3D 9H 8S 6C +6D 7H TC TH 5S JD 5C 9C KS KD +8D TD QH 6S 4S 6C 8S KC 5C TC +5S 3D KS AC 4S 7D QD 4C TH 2S +TS 8H 9S 6S 7S QH 3C AH 7H 8C +4C 8C TS JS QC 3D 7D 5D 7S JH +8S 7S 9D QC AC 7C 6D 2H JH KC +JS KD 3C 6S 4S 7C AH QC KS 5H +KS 6S 4H JD QS TC 8H KC 6H AS +KH 7C TC 6S TD JC 5C 7D AH 3S +3H 4C 4H TC TH 6S 7H 6D 9C QH +7D 5H 4S 8C JS 4D 3D 8S QH KC +3H 6S AD 7H 3S QC 8S 4S 7S JS +3S JD KH TH 6H QS 9C 6C 2D QD +4S QH 4D 5H KC 7D 6D 8D TH 5S +TD AD 6S 7H KD KH 9H 5S KC JC +3H QC AS TS 4S QD KS 9C 7S KC +TS 6S QC 6C TH TC 9D 5C 5D KD +JS 3S 4H KD 4C QD 6D 9S JC 9D +8S JS 6D 4H JH 6H 6S 6C KS KH +AC 7D 5D TC 9S KH 6S QD 6H AS +AS 7H 6D QH 8D TH 2S KH 5C 5H +4C 7C 3D QC TC 4S KH 8C 2D JS +6H 5D 7S 5H 9C 9H JH 8S TH 7H +AS JS 2S QD KH 8H 4S AC 8D 8S +3H 4C TD KD 8C JC 5C QS 2D JD +TS 7D 5D 6C 2C QS 2H 3C AH KS +4S 7C 9C 7D JH 6C 5C 8H 9D QD +2S TD 7S 6D 9C 9S QS KH QH 5C +JC 6S 9C QH JH 8D 7S JS KH 2H +8D 5H TH KC 4D 4S 3S 6S 3D QS +2D JD 4C TD 7C 6D TH 7S JC AH +QS 7S 4C TH 9D TS AD 4D 3H 6H +2D 3H 7D JD 3D AS 2S 9C QC 8S +4H 9H 9C 2C 7S JH KD 5C 5D 6H +TC 9H 8H JC 3C 9S 8D KS AD KC +TS 5H JD QS QH QC 8D 5D KH AH +5D AS 8S 6S 4C AH QC QD TH 7H +3H 4H 7D 6S 4S 9H AS 8H JS 9D +JD 8C 2C 9D 7D 5H 5S 9S JC KD +KD 9C 4S QD AH 7C AD 9D AC TD +6S 4H 4S 9C 8D KS TC 9D JH 7C +5S JC 5H 4S QH AC 2C JS 2S 9S +8C 5H AS QD AD 5C 7D 8S QC TD +JC 4C 8D 5C KH QS 4D 6H 2H 2C +TH 4S 2D KC 3H QD AC 7H AD 9D +KH QD AS 8H TH KC 8D 7S QH 8C +JC 6C 7D 8C KH AD QS 2H 6S 2D +JC KH 2D 7D JS QC 5H 4C 5D AD +TS 3S AD 4S TD 2D TH 6S 9H JH +9H 2D QS 2C 4S 3D KH AS AC 9D +KH 6S 8H 4S KD 7D 9D TS QD QC +JH 5H AH KS AS AD JC QC 5S KH +5D 7D 6D KS KD 3D 7C 4D JD 3S +AC JS 8D 5H 9C 3H 4H 4D TS 2C +6H KS KH 9D 7C 2S 6S 8S 2H 3D +6H AC JS 7S 3S TD 8H 3H 4H TH +9H TC QC KC 5C KS 6H 4H AC 8S +TC 7D QH 4S JC TS 6D 6C AC KH +QH 7D 7C JH QS QD TH 3H 5D KS +3D 5S 8D JS 4C 2C KS 7H 9C 4H +5H 8S 4H TD 2C 3S QD QC 3H KC +QC JS KD 9C AD 5S 9D 7D 7H TS +8C JC KH 7C 7S 6C TS 2C QD TH +5S 9D TH 3C 7S QH 8S 9C 2H 5H +5D 9H 6H 2S JS KH 3H 7C 2H 5S +JD 5D 5S 2C TC 2S 6S 6C 3C 8S +4D KH 8H 4H 2D KS 3H 5C 2S 9H +3S 2D TD 7H 8S 6H JD KC 9C 8D +6S QD JH 7C 9H 5H 8S 8H TH TD +QS 7S TD 7D TS JC KD 7C 3C 2C +3C JD 8S 4H 2D 2S TD AS 4D AC +AH KS 6C 4C 4S 7D 8C 9H 6H AS +5S 3C 9S 2C QS KD 4D 4S AC 5D +2D TS 2C JS KH QH 5D 8C AS KC +KD 3H 6C TH 8S 7S KH 6H 9S AC +6H 7S 6C QS AH 2S 2H 4H 5D 5H +5H JC QD 2C 2S JD AS QC 6S 7D +6C TC AS KD 8H 9D 2C 7D JH 9S +2H 4C 6C AH 8S TD 3H TH 7C TS +KD 4S TS 6C QH 8D 9D 9C AH 7D +6D JS 5C QD QC 9C 5D 8C 2H KD +3C QH JH AD 6S AH KC 8S 6D 6H +3D 7C 4C 7S 5S 3S 6S 5H JC 3C +QH 7C 5H 3C 3S 8C TS 4C KD 9C +QD 3S 7S 5H 7H QH JC 7C 8C KD +3C KD KH 2S 4C TS AC 6S 2C 7C +2C KH 3C 4C 6H 4D 5H 5S 7S QD +4D 7C 8S QD TS 9D KS 6H KD 3C +QS 4D TS 7S 4C 3H QD 8D 9S TC +TS QH AC 6S 3C 9H 9D QS 8S 6H +3S 7S 5D 4S JS 2D 6C QH 6S TH +4C 4H AS JS 5D 3D TS 9C AC 8S +6S 9C 7C 3S 5C QS AD AS 6H 3C +9S 8C 7H 3H 6S 7C AS 9H JD KH +3D 3H 7S 4D 6C 7C AC 2H 9C TH +4H 5S 3H AC TC TH 9C 9H 9S 8D +8D 9H 5H 4D 6C 2H QD 6S 5D 3S +4C 5C JD QS 4D 3H TH AC QH 8C +QC 5S 3C 7H AD 4C KS 4H JD 6D +QS AH 3H KS 9H 2S JS JH 5H 2H +2H 5S TH 6S TS 3S KS 3C 5H JS +2D 9S 7H 3D KC JH 6D 7D JS TD +AC JS 8H 2C 8C JH JC 2D TH 7S +5D 9S 8H 2H 3D TC AH JC KD 9C +9D QD JC 2H 6D KH TS 9S QH TH +2C 8D 4S JD 5H 3H TH TC 9C KC +AS 3D 9H 7D 4D TH KH 2H 7S 3H +4H 7S KS 2S JS TS 8S 2H QD 8D +5S 6H JH KS 8H 2S QC AC 6S 3S +JC AS AD QS 8H 6C KH 4C 4D QD +2S 3D TS TD 9S KS 6S QS 5C 8D +3C 6D 4S QC KC JH QD TH KH AD +9H AH 4D KS 2S 8D JH JC 7C QS +2D 6C TH 3C 8H QD QH 2S 3S KS +6H 5D 9S 4C TS TD JS QD 9D JD +5H 8H KH 8S KS 7C TD AD 4S KD +2C 7C JC 5S AS 6C 7D 8S 5H 9C +6S QD 9S TS KH QS 5S QH 3C KC +7D 3H 3C KD 5C AS JH 7H 6H JD +9D 5C 9H KC 8H KS 4S AD 4D 2S +3S JD QD 8D 2S 7C 5S 6S 5H TS +6D 9S KC TD 3S 6H QD JD 5C 8D +5H 9D TS KD 8D 6H TD QC 4C 7D +6D 4S JD 9D AH 9S AS TD 9H QD +2D 5S 2H 9C 6H 9S TD QC 7D TC +3S 2H KS TS 2C 9C 8S JS 9D 7D +3C KC 6D 5D 6C 6H 8S AS 7S QS +JH 9S 2H 8D 4C 8H 9H AD TH KH +QC AS 2S JS 5C 6H KD 3H 7H 2C +QD 8H 2S 8D 3S 6D AH 2C TC 5C +JD JS TS 8S 3H 5D TD KC JC 6H +6S QS TC 3H 5D AH JC 7C 7D 4H +7C 5D 8H 9C 2H 9H JH KH 5S 2C +9C 7H 6S TH 3S QC QD 4C AC JD +2H 5D 9S 7D KC 3S QS 2D AS KH +2S 4S 2H 7D 5C TD TH QH 9S 4D +6D 3S TS 6H 4H KS 9D 8H 5S 2D +9H KS 4H 3S 5C 5D KH 6H 6S JS +KC AS 8C 4C JC KH QC TH QD AH +6S KH 9S 2C 5H TC 3C 7H JC 4D +JD 4S 6S 5S 8D 7H 7S 4D 4C 2H +7H 9H 5D KH 9C 7C TS TC 7S 5H +4C 8D QC TS 4S 9H 3D AD JS 7C +8C QS 5C 5D 3H JS AH KC 4S 9D +TS JD 8S QS TH JH KH 2D QD JS +JD QC 5D 6S 9H 3S 2C 8H 9S TS +2S 4C AD 7H JC 5C 2D 6D 4H 3D +7S JS 2C 4H 8C AD QD 9C 3S TD +JD TS 4C 6H 9H 7D QD 6D 3C AS +AS 7C 4C 6S 5D 5S 5C JS QC 4S +KD 6S 9S 7C 3C 5S 7D JH QD JS +4S 7S JH 2C 8S 5D 7H 3D QH AD +TD 6H 2H 8D 4H 2D 7C AD KH 5D +TS 3S 5H 2C QD AH 2S 5C KH TD +KC 4D 8C 5D AS 6C 2H 2S 9H 7C +KD JS QC TS QS KH JH 2C 5D AD +3S 5H KC 6C 9H 3H 2H AD 7D 7S +7S JS JH KD 8S 7D 2S 9H 7C 2H +9H 2D 8D QC 6S AD AS 8H 5H 6C +2S 7H 6C 6D 7D 8C 5D 9D JC 3C +7C 9C 7H JD 2H KD 3S KH AD 4S +QH AS 9H 4D JD KS KD TS KH 5H +4C 8H 5S 3S 3D 7D TD AD 7S KC +JS 8S 5S JC 8H TH 9C 4D 5D KC +7C 5S 9C QD 2C QH JS 5H 8D KH +TD 2S KS 3D AD KC 7S TC 3C 5D +4C 2S AD QS 6C 9S QD TH QH 5C +8C AD QS 2D 2S KC JD KS 6C JC +8D 4D JS 2H 5D QD 7S 7D QH TS +6S 7H 3S 8C 8S 9D QS 8H 6C 9S +4S TC 2S 5C QD 4D QS 6D TH 6S +3S 5C 9D 6H 8D 4C 7D TC 7C TD +AH 6S AS 7H 5S KD 3H 5H AC 4C +8D 8S AH KS QS 2C AD 6H 7D 5D +6H 9H 9S 2H QS 8S 9C 5D 2D KD +TS QC 5S JH 7D 7S TH 9S 9H AC +7H 3H 6S KC 4D 6D 5C 4S QD TS +TD 2S 7C QD 3H JH 9D 4H 7S 7H +KS 3D 4H 5H TC 2S AS 2D 6D 7D +8H 3C 7H TD 3H AD KC TH 9C KH +TC 4C 2C 9S 9D 9C 5C 2H JD 3C +3H AC TS 5D AD 8D 6H QC 6S 8C +2S TS 3S JD 7H 8S QH 4C 5S 8D +AC 4S 6C 3C KH 3D 7C 2D 8S 2H +4H 6C 8S TH 2H 4S 8H 9S 3H 7S +7C 4C 9C 2C 5C AS 5D KD 4D QH +9H 4H TS AS 7D 8D 5D 9S 8C 2H +QC KD AC AD 2H 7S AS 3S 2D 9S +2H QC 8H TC 6D QD QS 5D KH 3C +TH JD QS 4C 2S 5S AD 7H 3S AS +7H JS 3D 6C 3S 6D AS 9S AC QS +9C TS AS 8C TC 8S 6H 9D 8D 6C +4D JD 9C KC 7C 6D KS 3S 8C AS +3H 6S TC 8D TS 3S KC 9S 7C AS +8C QC 4H 4S 8S 6C 3S TC AH AC +4D 7D 5C AS 2H 6S TS QC AD TC +QD QC 8S 4S TH 3D AH TS JH 4H +5C 2D 9S 2C 3H 3C 9D QD QH 7D +KC 9H 6C KD 7S 3C 4D AS TC 2D +3D JS 4D 9D KS 7D TH QC 3H 3C +8D 5S 2H 9D 3H 8C 4C 4H 3C TH +JC TH 4S 6S JD 2D 4D 6C 3D 4C +TS 3S 2D 4H AC 2C 6S 2H JH 6H +TD 8S AD TC AH AC JH 9S 6S 7S +6C KC 4S JD 8D 9H 5S 7H QH AH +KD 8D TS JH 5C 5H 3H AD AS JS +2D 4H 3D 6C 8C 7S AD 5D 5C 8S +TD 5D 7S 9C 4S 5H 6C 8C 4C 8S +JS QH 9C AS 5C QS JC 3D QC 7C +JC 9C KH JH QS QC 2C TS 3D AD +5D JH AC 5C 9S TS 4C JD 8C KS +KC AS 2D KH 9H 2C 5S 4D 3D 6H +TH AH 2D 8S JC 3D 8C QH 7S 3S +8H QD 4H JC AS KH KS 3C 9S 6D +9S QH 7D 9C 4S AC 7H KH 4D KD +AH AD TH 6D 9C 9S KD KS QH 4H +QD 6H 9C 7C QS 6D 6S 9D 5S JH +AH 8D 5H QD 2H JC KS 4H KH 5S +5C 2S JS 8D 9C 8C 3D AS KC AH +JD 9S 2H QS 8H 5S 8C TH 5C 4C +QC QS 8C 2S 2C 3S 9C 4C KS KH +2D 5D 8S AH AD TD 2C JS KS 8C +TC 5S 5H 8H QC 9H 6H JD 4H 9S +3C JH 4H 9H AH 4S 2H 4C 8D AC +8S TH 4D 7D 6D QD QS 7S TC 7C +KH 6D 2D JD 5H JS QD JH 4H 4S +9C 7S JH 4S 3S TS QC 8C TC 4H +QH 9D 4D JH QS 3S 2C 7C 6C 2D +4H 9S JD 5C 5H AH 9D TS 2D 4C +KS JH TS 5D 2D AH JS 7H AS 8D +JS AH 8C AD KS 5S 8H 2C 6C TH +2H 5D AD AC KS 3D 8H TS 6H QC +6D 4H TS 9C 5H JS JH 6S JD 4C +JH QH 4H 2C 6D 3C 5D 4C QS KC +6H 4H 6C 7H 6S 2S 8S KH QC 8C +3H 3D 5D KS 4H TD AD 3S 4D TS +5S 7C 8S 7D 2C KS 7S 6C 8C JS +5D 2H 3S 7C 5C QD 5H 6D 9C 9H +JS 2S KD 9S 8D TD TS AC 8C 9D +5H QD 2S AC 8C 9H KS 7C 4S 3C +KH AS 3H 8S 9C JS QS 4S AD 4D +AS 2S TD AD 4D 9H JC 4C 5H QS +5D 7C 4H TC 2D 6C JS 4S KC 3S +4C 2C 5D AC 9H 3D JD 8S QS QH +2C 8S 6H 3C QH 6D TC KD AC AH +QC 6C 3S QS 4S AC 8D 5C AD KH +5S 4C AC KH AS QC 2C 5C 8D 9C +8H JD 3C KH 8D 5C 9C QD QH 9D +7H TS 2C 8C 4S TD JC 9C 5H QH +JS 4S 2C 7C TH 6C AS KS 7S JD +JH 7C 9H 7H TC 5H 3D 6D 5D 4D +2C QD JH 2H 9D 5S 3D TD AD KS +JD QH 3S 4D TH 7D 6S QS KS 4H +TC KS 5S 8D 8H AD 2S 2D 4C JH +5S JH TC 3S 2D QS 9D 4C KD 9S +AC KH 3H AS 9D KC 9H QD 6C 6S +9H 7S 3D 5C 7D KC TD 8H 4H 6S +3C 7H 8H TC QD 4D 7S 6S QH 6C +6D AD 4C QD 6C 5D 7D 9D KS TS +JH 2H JD 9S 7S TS KH 8D 5D 8H +2D 9S 4C 7D 9D 5H QD 6D AC 6S +7S 6D JC QD JH 4C 6S QS 2H 7D +8C TD JH KD 2H 5C QS 2C JS 7S +TC 5H 4H JH QD 3S 5S 5D 8S KH +KS KH 7C 2C 5D JH 6S 9C 6D JC +5H AH JD 9C JS KC 2H 6H 4D 5S +AS 3C TH QC 6H 9C 8S 8C TD 7C +KC 2C QD 9C KH 4D 7S 3C TS 9H +9C QC 2S TS 8C TD 9S QD 3S 3C +4D 9D TH JH AH 6S 2S JD QH JS +QD 9H 6C KD 7D 7H 5D 6S 8H AH +8H 3C 4S 2H 5H QS QH 7S 4H AC +QS 3C 7S 9S 4H 3S AH KS 9D 7C +AD 5S 6S 2H 2D 5H TC 4S 3C 8C +QH TS 6S 4D JS KS JH AS 8S 6D +2C 8S 2S TD 5H AS TC TS 6C KC +KC TS 8H 2H 3H 7C 4C 5S TH TD +KD AD KH 7H 7S 5D 5H 5S 2D 9C +AD 9S 3D 7S 8C QC 7C 9C KD KS +3C QC 9S 8C 4D 5C AS QD 6C 2C +2H KC 8S JD 7S AC 8D 5C 2S 4D +9D QH 3D 2S TC 3S KS 3C 9H TD +KD 6S AC 2C 7H 5H 3S 6C 6H 8C +QH TC 8S 6S KH TH 4H 5D TS 4D +8C JS 4H 6H 2C 2H 7D AC QD 3D +QS KC 6S 2D 5S 4H TD 3H JH 4C +7S 5H 7H 8H KH 6H QS TH KD 7D +5H AD KD 7C KH 5S TD 6D 3C 6C +8C 9C 5H JD 7C KC KH 7H 2H 3S +7S 4H AD 4D 8S QS TH 3D 7H 5S +8D TC KS KD 9S 6D AD JD 5C 2S +7H 8H 6C QD 2H 6H 9D TC 9S 7C +8D 6D 4C 7C 6C 3C TH KH JS JH +5S 3S 8S JS 9H AS AD 8H 7S KD +JH 7C 2C KC 5H AS AD 9C 9S JS +AD AC 2C 6S QD 7C 3H TH KS KD +9D JD 4H 8H 4C KH 7S TS 8C KC +3S 5S 2H 7S 6H 7D KS 5C 6D AD +5S 8C 9H QS 7H 7S 2H 6C 7D TD +QS 5S TD AC 9D KC 3D TC 2D 4D +TD 2H 7D JD QD 4C 7H 5D KC 3D +4C 3H 8S KD QH 5S QC 9H TC 5H +9C QD TH 5H TS 5C 9H AH QH 2C +4D 6S 3C AC 6C 3D 2C 2H TD TH +AC 9C 5D QC 4D AD 8D 6D 8C KC +AD 3C 4H AC 8D 8H 7S 9S TD JC +4H 9H QH JS 2D TH TD TC KD KS +5S 6S 9S 8D TH AS KH 5H 5C 8S +JD 2S 9S 6S 5S 8S 5D 7S 7H 9D +5D 8C 4C 9D AD TS 2C 7D KD TC +8S QS 4D KC 5C 8D 4S KH JD KD +AS 5C AD QH 7D 2H 9S 7H 7C TC +2S 8S JD KH 7S 6C 6D AD 5D QC +9H 6H 3S 8C 8H AH TC 4H JS TD +2C TS 4D 7H 2D QC 9C 5D TH 7C +6C 8H QC 5D TS JH 5C 5H 9H 4S +2D QC 7H AS JS 8S 2H 4C 4H 8D +JS 6S AC KD 3D 3C 4S 7H TH KC +QH KH 6S QS 5S 4H 3C QD 3S 3H +7H AS KH 8C 4H 9C 5S 3D 6S TS +9C 7C 3H 5S QD 2C 3D AD AC 5H +JH TD 2D 4C TS 3H KH AD 3S 7S +AS 4C 5H 4D 6S KD JC 3C 6H 2D +3H 6S 8C 2D TH 4S AH QH AD 5H +7C 2S 9H 7H KC 5C 6D 5S 3H JC +3C TC 9C 4H QD TD JH 6D 9H 5S +7C 6S 5C 5D 6C 4S 7H 9H 6H AH +AD 2H 7D KC 2C 4C 2S 9S 7H 3S +TH 4C 8S 6S 3S AD KS AS JH TD +5C TD 4S 4D AD 6S 5D TC 9C 7D +8H 3S 4D 4S 5S 6H 5C AC 3H 3D +9H 3C AC 4S QS 8S 9D QH 5H 4D +JC 6C 5H TS AC 9C JD 8C 7C QD +8S 8H 9C JD 2D QC QH 6H 3C 8D +KS JS 2H 6H 5H QH QS 3H 7C 6D +TC 3H 4S 7H QC 2H 3S 8C JS KH +AH 8H 5S 4C 9H JD 3H 7S JC AC +3C 2D 4C 5S 6C 4S QS 3S JD 3D +5H 2D TC AH KS 6D 7H AD 8C 6H +6C 7S 3C JD 7C 8H KS KH AH 6D +AH 7D 3H 8H 8S 7H QS 5H 9D 2D +JD AC 4H 7S 8S 9S KS AS 9D QH +7S 2C 8S 5S JH QS JC AH KD 4C +AH 2S 9H 4H 8D TS TD 6H QH JD +4H JC 3H QS 6D 7S 9C 8S 9D 8D +5H TD 4S 9S 4C 8C 8D 7H 3H 3D +QS KH 3S 2C 2S 3C 7S TD 4S QD +7C TD 4D 5S KH AC AS 7H 4C 6C +2S 5H 6D JD 9H QS 8S 2C 2H TD +2S TS 6H 9H 7S 4H JC 4C 5D 5S +2C 5H 7D 4H 3S QH JC JS 6D 8H +4C QH 7C QD 3S AD TH 8S 5S TS +9H TC 2S TD JC 7D 3S 3D TH QH +7D 4C 8S 5C JH 8H 6S 3S KC 3H +JC 3H KH TC QH TH 6H 2C AC 5H +QS 2H 9D 2C AS 6S 6C 2S 8C 8S +9H 7D QC TH 4H KD QS AC 7S 3C +4D JH 6S 5S 8H KS 9S QC 3S AS +JD 2D 6S 7S TC 9H KC 3H 7D KD +2H KH 7C 4D 4S 3H JS QD 7D KC +4C JC AS 9D 3C JS 6C 8H QD 4D +AH JS 3S 6C 4C 3D JH 6D 9C 9H +9H 2D 8C 7H 5S KS 6H 9C 2S TC +6C 8C AD 7H 6H 3D KH AS 5D TH +KS 8C 3S TS 8S 4D 5S 9S 6C 4H +9H 4S 4H 5C 7D KC 2D 2H 9D JH +5C JS TC 9D 9H 5H 7S KH JC 6S +7C 9H 8H 4D JC KH JD 2H TD TC +8H 6C 2H 2C KH 6H 9D QS QH 5H +AC 7D 2S 3D QD JC 2D 8D JD JH +2H JC 2D 7H 2C 3C 8D KD TD 4H +3S 4H 6D 8D TS 3H TD 3D 6H TH +JH JC 3S AC QH 9H 7H 8S QC 2C +7H TD QS 4S 8S 9C 2S 5D 4D 2H +3D TS 3H 2S QC 8H 6H KC JC KS +5D JD 7D TC 8C 6C 9S 3D 8D AC +8H 6H JH 6C 5D 8D 8S 4H AD 2C +9D 4H 2D 2C 3S TS AS TC 3C 5D +4D TH 5H KS QS 6C 4S 2H 3D AD +5C KC 6H 2C 5S 3C 4D 2D 9H 9S +JD 4C 3H TH QH 9H 5S AH 8S AC +7D 9S 6S 2H TD 9C 4H 8H QS 4C +3C 6H 5D 4H 8C 9C KC 6S QD QS +3S 9H KD TC 2D JS 8C 6S 4H 4S +2S 4C 8S QS 6H KH 3H TH 8C 5D +2C KH 5S 3S 7S 7H 6C 9D QD 8D +8H KS AC 2D KH TS 6C JS KC 7H +9C KS 5C TD QC AH 6C 5H 9S 7C +5D 4D 3H 4H 6S 7C 7S AH QD TD +2H 7D QC 6S TC TS AH 7S 9D 3H +TH 5H QD 9S KS 7S 7C 6H 8C TD +TH 2D 4D QC 5C 7D JD AH 9C 4H +4H 3H AH 8D 6H QC QH 9H 2H 2C +2D AD 4C TS 6H 7S TH 4H QS TD +3C KD 2H 3H QS JD TC QC 5D 8H +KS JC QD TH 9S KD 8D 8C 2D 9C +3C QD KD 6D 4D 8D AH AD QC 8S +8H 3S 9D 2S 3H KS 6H 4C 7C KC +TH 9S 5C 3D 7D 6H AC 7S 4D 2C +5C 3D JD 4D 2D 6D 5H 9H 4C KH +AS 7H TD 6C 2H 3D QD KS 4C 4S +JC 3C AC 7C JD JS 8H 9S QC 5D +JD 6S 5S 2H AS 8C 7D 5H JH 3D +8D TC 5S 9S 8S 3H JC 5H 7S AS +5C TD 3D 7D 4H 8D 7H 4D 5D JS +QS 9C KS TD 2S 8S 5C 2H 4H AS +TH 7S 4H 7D 3H JD KD 5D 2S KC +JD 7H 4S 8H 4C JS 6H QH 5S 4H +2C QS 8C 5S 3H QC 2S 6C QD AD +8C 3D JD TC 4H 2H AD 5S AC 2S +5D 2C JS 2D AD 9D 3D 4C 4S JH +8D 5H 5D 6H 7S 4D KS 9D TD JD +3D 6D 9C 2S AS 7D 5S 5C 8H JD +7C 8S 3S 6S 5H JD TC AD 7H 7S +2S 9D TS 4D AC 8D 6C QD JD 3H +9S KH 2C 3C AC 3D 5H 6H 8D 5D +KS 3D 2D 6S AS 4C 2S 7C 7H KH +AC 2H 3S JC 5C QH 4D 2D 5H 7S +TS AS JD 8C 6H JC 8S 5S 2C 5D +7S QH 7H 6C QC 8H 2D 7C JD 2S +2C QD 2S 2H JC 9C 5D 2D JD JH +7C 5C 9C 8S 7D 6D 8D 6C 9S JH +2C AD 6S 5H 3S KS 7S 9D KH 4C +7H 6C 2C 5C TH 9D 8D 3S QC AH +5S KC 6H TC 5H 8S TH 6D 3C AH +9C KD 4H AD TD 9S 4S 7D 6H 5D +7H 5C 5H 6D AS 4C KD KH 4H 9D +3C 2S 5C 6C JD QS 2H 9D 7D 3H +AC 2S 6S 7S JS QD 5C QS 6H AD +5H TH QC 7H TC 3S 7C 6D KC 3D +4H 3D QC 9S 8H 2C 3S JC KS 5C +4S 6S 2C 6H 8S 3S 3D 9H 3H JS +4S 8C 4D 2D 8H 9H 7D 9D AH TS +9S 2C 9H 4C 8D AS 7D 3D 6D 5S +6S 4C 7H 8C 3H 5H JC AH 9D 9C +2S 7C 5S JD 8C 3S 3D 4D 7D 6S +3C KC 4S 5D 7D 3D JD 7H 3H 4H +9C 9H 4H 4D TH 6D QD 8S 9S 7S +2H AC 8S 4S AD 8C 2C AH 7D TC +TS 9H 3C AD KS TC 3D 8C 8H JD +QC 8D 2C 3C 7D 7C JD 9H 9C 6C +AH 6S JS JH 5D AS QC 2C JD TD +9H KD 2H 5D 2D 3S 7D TC AH TS +TD 8H AS 5D AH QC AC 6S TC 5H +KS 4S 7H 4D 8D 9C TC 2H 6H 3H +3H KD 4S QD QH 3D 8H 8C TD 7S +8S JD TC AH JS QS 2D KH KS 4D +3C AD JC KD JS KH 4S TH 9H 2C +QC 5S JS 9S KS AS 7C QD 2S JD +KC 5S QS 3S 2D AC 5D 9H 8H KS +6H 9C TC AD 2C 6D 5S JD 6C 7C +QS KH TD QD 2C 3H 8S 2S QC AH +9D 9H JH TC QH 3C 2S JS 5C 7H +6C 3S 3D 2S 4S QD 2D TH 5D 2C +2D 6H 6D 2S JC QH AS 7H 4H KH +5H 6S KS AD TC TS 7C AC 4S 4H +AD 3C 4H QS 8C 9D KS 2H 2D 4D +4S 9D 6C 6D 9C AC 8D 3H 7H KD +JC AH 6C TS JD 6D AD 3S 5D QD +JC JH JD 3S 7S 8S JS QC 3H 4S +JD TH 5C 2C AD JS 7H 9S 2H 7S +8D 3S JH 4D QC AS JD 2C KC 6H +2C AC 5H KD 5S 7H QD JH AH 2D +JC QH 8D 8S TC 5H 5C AH 8C 6C +3H JS 8S QD JH 3C 4H 6D 5C 3S +6D 4S 4C AH 5H 5S 3H JD 7C 8D +8H AH 2H 3H JS 3C 7D QC 4H KD +6S 2H KD 5H 8H 2D 3C 8S 7S QD +2S 7S KC QC AH TC QS 6D 4C 8D +5S 9H 2C 3S QD 7S 6C 2H 7C 9D +3C 6C 5C 5S JD JC KS 3S 5D TS +7C KS 6S 5S 2S 2D TC 2H 5H QS +AS 7H 6S TS 5H 9S 9D 3C KD 2H +4S JS QS 3S 4H 7C 2S AC 6S 9D +8C JH 2H 5H 7C 5D QH QS KH QC +3S TD 3H 7C KC 8D 5H 8S KH 8C +4H KH JD TS 3C 7H AS QC JS 5S +AH 9D 2C 8D 4D 2D 6H 6C KC 6S +2S 6H 9D 3S 7H 4D KH 8H KD 3D +9C TC AC JH KH 4D JD 5H TD 3S +7S 4H 9D AS 4C 7D QS 9S 2S KH +3S 8D 8S KS 8C JC 5C KH 2H 5D +8S QH 2C 4D KC JS QC 9D AC 6H +8S 8C 7C JS JD 6S 4C 9C AC 4S +QH 5D 2C 7D JC 8S 2D JS JH 4C +JS 4C 7S TS JH KC KH 5H QD 4S +QD 8C 8D 2D 6S TD 9D AC QH 5S +QH QC JS 3D 3C 5C 4H KH 8S 7H +7C 2C 5S JC 8S 3H QC 5D 2H KC +5S 8D KD 6H 4H QD QH 6D AH 3D +7S KS 6C 2S 4D AC QS 5H TS JD +7C 2D TC 5D QS AC JS QC 6C KC +2C KS 4D 3H TS 8S AD 4H 7S 9S +QD 9H QH 5H 4H 4D KH 3S JC AD +4D AC KC 8D 6D 4C 2D KH 2C JD +2C 9H 2D AH 3H 6D 9C 7D TC KS +8C 3H KD 7C 5C 2S 4S 5H AS AH +TH JD 4H KD 3H TC 5C 3S AC KH +6D 7H AH 7S QC 6H 2D TD JD AS +JH 5D 7H TC 9S 7D JC AS 5S KH +2H 8C AD TH 6H QD KD 9H 6S 6C +QH KC 9D 4D 3S JS JH 4H 2C 9H +TC 7H KH 4H JC 7D 9S 3H QS 7S +AD 7D JH 6C 7H 4H 3S 3H 4D QH +JD 2H 5C AS 6C QC 4D 3C TC JH +AC JD 3H 6H 4C JC AD 7D 7H 9H +4H TC TS 2C 8C 6S KS 2H JD 9S +4C 3H QS QC 9S 9H 6D KC 9D 9C +5C AD 8C 2C QH TH QD JC 8D 8H +QC 2C 2S QD 9C 4D 3S 8D JH QS +9D 3S 2C 7S 7C JC TD 3C TC 9H +3C TS 8H 5C 4C 2C 6S 8D 7C 4H +KS 7H 2H TC 4H 2C 3S AS AH QS +8C 2D 2H 2C 4S 4C 6S 7D 5S 3S +TH QC 5D TD 3C QS KD KC KS AS +4D AH KD 9H KS 5C 4C 6H JC 7S +KC 4H 5C QS TC 2H JC 9S AH QH +4S 9H 3H 5H 3C QD 2H QC JH 8H +5D AS 7H 2C 3D JH 6H 4C 6S 7D +9C JD 9H AH JS 8S QH 3H KS 8H +3S AC QC TS 4D AD 3D AH 8S 9H +7H 3H QS 9C 9S 5H JH JS AH AC +8D 3C JD 2H AC 9C 7H 5S 4D 8H +7C JH 9H 6C JS 9S 7H 8C 9D 4H +2D AS 9S 6H 4D JS JH 9H AD QD +6H 7S JH KH AH 7H TD 5S 6S 2C +8H JH 6S 5H 5S 9D TC 4C QC 9S +7D 2C KD 3H 5H AS QD 7H JS 4D +TS QH 6C 8H TH 5H 3C 3H 9C 9D +AD KH JS 5D 3H AS AC 9S 5C KC +2C KH 8C JC QS 6D AH 2D KC TC +9D 3H 2S 7C 4D 6D KH KS 8D 7D +9H 2S TC JH AC QC 3H 5S 3S 8H +3S AS KD 8H 4C 3H 7C JH QH TS +7S 6D 7H 9D JH 4C 3D 3S 6C AS +4S 2H 2C 4C 8S 5H KC 8C QC QD +3H 3S 6C QS QC 2D 6S 5D 2C 9D +2H 8D JH 2S 3H 2D 6C 5C 7S AD +9H JS 5D QH 8S TS 2H 7S 6S AD +6D QC 9S 7H 5H 5C 7D KC JD 4H +QC 5S 9H 9C 4D 6S KS 2S 4C 7C +9H 7C 4H 8D 3S 6H 5C 8H JS 7S +2D 6H JS TD 4H 4D JC TH 5H KC +AC 7C 8D TH 3H 9S 2D 4C KC 4D +KD QS 9C 7S 3D KS AD TS 4C 4H +QH 9C 8H 2S 7D KS 7H 5D KD 4C +9C 2S 2H JC 6S 6C TC QC JH 5C +7S AC 8H KC 8S 6H QS JC 3D 6S +JS 2D JH 8C 4S 6H 8H 6D 5D AD +6H 7D 2S 4H 9H 7C AS AC 8H 5S +3C JS 4S 6D 5H 2S QH 6S 9C 2C +3D 5S 6S 9S 4C QS 8D QD 8S TC +9C 3D AH 9H 5S 2C 7D AD JC 3S +7H TC AS 3C 6S 6D 7S KH KC 9H +3S TC 8H 6S 5H JH 8C 7D AC 2S +QD 9D 9C 3S JC 8C KS 8H 5D 4D +JS AH JD 6D 9D 8C 9H 9S 8H 3H +2D 6S 4C 4D 8S AD 4S TC AH 9H +TS AC QC TH KC 6D 4H 7S 8C 2H +3C QD JS 9D 5S JC AH 2H TS 9H +3H 4D QH 5D 9C 5H 7D 4S JC 3S +8S TH 3H 7C 2H JD JS TS AC 8D +9C 2H TD KC JD 2S 8C 5S AD 2C +3D KD 7C 5H 4D QH QD TC 6H 7D +7H 2C KC 5S KD 6H AH QC 7S QH +6H 5C AC 5H 2C 9C 2D 7C TD 2S +4D 9D AH 3D 7C JD 4H 8C 4C KS +TH 3C JS QH 8H 4C AS 3D QS QC +4D 7S 5H JH 6D 7D 6H JS KH 3C +QD 8S 7D 2H 2C 7C JC 2S 5H 8C +QH 8S 9D TC 2H AD 7C 8D QD 6S +3S 7C AD 9H 2H 9S JD TS 4C 2D +3S AS 4H QC 2C 8H 8S 7S TD TC +JH TH TD 3S 4D 4H 5S 5D QS 2C +8C QD QH TC 6D 4S 9S 9D 4H QC +8C JS 9D 6H JD 3H AD 6S TD QC +KC 8S 3D 7C TD 7D 8D 9H 4S 3S +6C 4S 3D 9D KD TC KC KS AC 5S +7C 6S QH 3D JS KD 6H 6D 2D 8C +JD 2S 5S 4H 8S AC 2D 6S TS 5C +5H 8C 5S 3C 4S 3D 7C 8D AS 3H +AS TS 7C 3H AD 7D JC QS 6C 6H +3S 9S 4C AC QH 5H 5D 9H TS 4H +6C 5C 7H 7S TD AD JD 5S 2H 2S +7D 6C KC 3S JD 8D 8S TS QS KH +8S QS 8D 6C TH AC AH 2C 8H 9S +7H TD KH QH 8S 3D 4D AH JD AS +TS 3D 2H JC 2S JH KH 6C QC JS +KC TH 2D 6H 7S 2S TC 8C 9D QS +3C 9D 6S KH 8H 6D 5D TH 2C 2H +6H TC 7D AD 4D 8S TS 9H TD 7S +JS 6D JD JC 2H AC 6C 3D KH 8D +KH JD 9S 5D 4H 4C 3H 7S QS 5C +4H JD 5D 3S 3C 4D KH QH QS 7S +JD TS 8S QD AH 4C 6H 3S 5S 2C +QS 3D JD AS 8D TH 7C 6S QC KS +7S 2H 8C QC 7H AC 6D 2D TH KH +5S 6C 7H KH 7D AH 8C 5C 7S 3D +3C KD AD 7D 6C 4D KS 2D 8C 4S +7C 8D 5S 2D 2S AH AD 2C 9D TD +3C AD 4S KS JH 7C 5C 8C 9C TH +AS TD 4D 7C JD 8C QH 3C 5H 9S +3H 9C 8S 9S 6S QD KS AH 5H JH +QC 9C 5S 4H 2H TD 7D AS 8C 9D +8C 2C 9D KD TC 7S 3D KH QC 3C +4D AS 4C QS 5S 9D 6S JD QH KS +6D AH 6C 4C 5H TS 9H 7D 3D 5S +QS JD 7C 8D 9C AC 3S 6S 6C KH +8H JH 5D 9S 6D AS 6S 3S QC 7H +QD AD 5C JH 2H AH 4H AS KC 2C +JH 9C 2C 6H 2D JS 5D 9H KC 6D +7D 9D KD TH 3H AS 6S QC 6H AD +JD 4H 7D KC 3H JS 3C TH 3D QS +4C 3H 8C QD 5H 6H AS 8H AD JD +TH 8S KD 5D QC 7D JS 5S 5H TS +7D KC 9D QS 3H 3C 6D TS 7S AH +7C 4H 7H AH QC AC 4D 5D 6D TH +3C 4H 2S KD 8H 5H JH TC 6C JD +4S 8C 3D 4H JS TD 7S JH QS KD +7C QC KD 4D 7H 6S AD TD TC KH +5H 9H KC 3H 4D 3D AD 6S QD 6H +TH 7C 6H TS QH 5S 2C KC TD 6S +7C 4D 5S JD JH 7D AC KD KH 4H +7D 6C 8D 8H 5C JH 8S QD TH JD +8D 7D 6C 7C 9D KD AS 5C QH JH +9S 2C 8C 3C 4C KS JH 2D 8D 4H +7S 6C JH KH 8H 3H 9D 2D AH 6D +4D TC 9C 8D 7H TD KS TH KD 3C +JD 9H 8D QD AS KD 9D 2C 2S 9C +8D 3H 5C 7H KS 5H QH 2D 8C 9H +2D TH 6D QD 6C KC 3H 3S AD 4C +4H 3H JS 9D 3C TC 5H QH QC JC +3D 5C 6H 3S 3C JC 5S 7S 2S QH +AC 5C 8C 4D 5D 4H 2S QD 3C 3H +2C TD AH 9C KD JS 6S QD 4C QC +QS 8C 3S 4H TC JS 3H 7C JC AD +5H 4D 9C KS JC TD 9S TS 8S 9H +QD TS 7D AS AC 2C TD 6H 8H AH +6S AD 8C 4S 9H 8D 9D KH 8S 3C +QS 4D 2D 7S KH JS JC AD 4C 3C +QS 9S 7H KC TD TH 5H JS AC JH +6D AC 2S QS 7C AS KS 6S KH 5S +6D 8H KH 3C QS 2H 5C 9C 9D 6C +JS 2C 4C 6H 7D JC AC QD TD 3H +4H QC 8H JD 4C KD KS 5C KC 7S +6D 2D 3H 2S QD 5S 7H AS TH 6S +AS 6D 8D 2C 8S TD 8H QD JC AH +9C 9H 2D TD QH 2H 5C TC 3D 8H +KC 8S 3D KH 2S TS TC 6S 4D JH +9H 9D QS AC KC 6H 5D 4D 8D AH +9S 5C QS 4H 7C 7D 2H 8S AD JS +3D AC 9S AS 2C 2D 2H 3H JC KH +7H QH KH JD TC KS 5S 8H 4C 8D +2H 7H 3S 2S 5H QS 3C AS 9H KD +AD 3D JD 6H 5S 9C 6D AC 9S 3S +3D 5D 9C 2D AC 4S 2S AD 6C 6S +QC 4C 2D 3H 6S KC QH QD 2H JH +QC 3C 8S 4D 9S 2H 5C 8H QS QD +6D KD 6S 7H 3S KH 2H 5C JC 6C +3S 9S TC 6S 8H 2D AD 7S 8S TS +3C 6H 9C 3H 5C JC 8H QH TD QD +3C JS QD 5D TD 2C KH 9H TH AS +9S TC JD 3D 5C 5H AD QH 9H KC +TC 7H 4H 8H 3H TD 6S AC 7C 2S +QS 9D 5D 3C JC KS 4D 6C JH 2S +9S 6S 3C 7H TS 4C KD 6D 3D 9C +2D 9H AH AC 7H 2S JH 3S 7C QC +QD 9H 3C 2H AC AS 8S KD 8C KH +2D 7S TD TH 6D JD 8D 4D 2H 5S +8S QH KD JD QS JH 4D KC 5H 3S +3C KH QC 6D 8H 3S AH 7D TD 2D +5S 9H QH 4S 6S 6C 6D TS TH 7S +6C 4C 6D QS JS 9C TS 3H 8D 8S +JS 5C 7S AS 2C AH 2H AD 5S TC +KD 6C 9C 9D TS 2S JC 4H 2C QD +QS 9H TC 3H KC KS 4H 3C AD TH +KH 9C 2H KD 9D TC 7S KC JH 2D +7C 3S KC AS 8C 5D 9C 9S QH 3H +2D 8C TD 4C 2H QC 5D TC 2C 7D +KS 4D 6C QH TD KH 5D 7C AD 8D +2S 9S 8S 4C 8C 3D 6H QD 7C 7H +6C 8S QH 5H TS 5C 3C 4S 2S 2H +8S 6S 2H JC 3S 3H 9D 8C 2S 7H +QC 2C 8H 9C AC JD 4C 4H 6S 3S +3H 3S 7D 4C 9S 5H 8H JC 3D TC +QH 2S 2D 9S KD QD 9H AD 6D 9C +8D 2D KS 9S JC 4C JD KC 4S TH +KH TS 6D 4D 5C KD 5H AS 9H AD +QD JS 7C 6D 5D 5C TH 5H QH QS +9D QH KH 5H JH 4C 4D TC TH 6C +KH AS TS 9D KD 9C 7S 4D 8H 5S +KH AS 2S 7D 9D 4C TS TH AH 7C +KS 4D AC 8S 9S 8D TH QH 9D 5C +5D 5C 8C QS TC 4C 3D 3S 2C 8D +9D KS 2D 3C KC 4S 8C KH 6C JC +8H AH 6H 7D 7S QD 3C 4C 6C KC +3H 2C QH 8H AS 7D 4C 8C 4H KC +QD 5S 4H 2C TD AH JH QH 4C 8S +3H QS 5S JS 8H 2S 9H 9C 3S 2C +6H TS 7S JC QD AC TD KC 5S 3H +QH AS QS 7D JC KC 2C 4C 5C 5S +QH 3D AS JS 4H 8D 7H JC 2S 9C +5D 4D 2S 4S 9D 9C 2D QS 8H 7H +6D 7H 3H JS TS AC 2D JH 7C 8S +JH 5H KC 3C TC 5S 9H 4C 8H 9D +8S KC 5H 9H AD KS 9D KH 8D AH +JC 2H 9H KS 6S 3H QC 5H AH 9C +5C KH 5S AD 6C JC 9H QC 9C TD +5S 5D JC QH 2D KS 8H QS 2H TS +JH 5H 5S AH 7H 3C 8S AS TD KH +6H 3D JD 2C 4C KC 7S AH 6C JH +4C KS 9D AD 7S KC 7D 8H 3S 9C +7H 5C 5H 3C 8H QC 3D KH 6D JC +2D 4H 5D 7D QC AD AH 9H QH 8H +KD 8C JS 9D 3S 3C 2H 5D 6D 2S +8S 6S TS 3C 6H 8D 5S 3H TD 6C +KS 3D JH 9C 7C 9S QS 5S 4H 6H +7S 6S TH 4S KC KD 3S JC JH KS +7C 3C 2S 6D QH 2C 7S 5H 8H AH +KC 8D QD 6D KH 5C 7H 9D 3D 9C +6H 2D 8S JS 9S 2S 6D KC 7C TC +KD 9C JH 7H KC 8S 2S 7S 3D 6H +4H 9H 2D 4C 8H 7H 5S 8S 2H 8D +AD 7C 3C 7S 5S 4D 9H 3D JC KH +5D AS 7D 6D 9C JC 4C QH QS KH +KD JD 7D 3D QS QC 8S 6D JS QD +6S 8C 5S QH TH 9H AS AC 2C JD +QC KS QH 7S 3C 4C 5C KC 5D AH +6C 4H 9D AH 2C 3H KD 3D TS 5C +TD 8S QS AS JS 3H KD AC 4H KS +7D 5D TS 9H 4H 4C 9C 2H 8C QC +2C 7D 9H 4D KS 4C QH AD KD JS +QD AD AH KH 9D JS 9H JC KD JD +8S 3C 4S TS 7S 4D 5C 2S 6H 7C +JS 7S 5C KD 6D QH 8S TD 2H 6S +QH 6C TC 6H TD 4C 9D 2H QC 8H +3D TS 4D 2H 6H 6S 2C 7H 8S 6C +9H 9D JD JH 3S AH 2C 6S 3H 8S +2C QS 8C 5S 3H 2S 7D 3C AD 4S +5C QC QH AS TS 4S 6S 4C 5H JS +JH 5C TD 4C 6H JS KD KH QS 4H +TC KH JC 4D 9H 9D 8D KC 3C 8H +2H TC 8S AD 9S 4H TS 7H 2C 5C +4H 2S 6C 5S KS AH 9C 7C 8H KD +TS QH TD QS 3C JH AH 2C 8D 7D +5D KC 3H 5S AC 4S 7H QS 4C 2H +3D 7D QC KH JH 6D 6C TD TH KD +5S 8D TH 6C 9D 7D KH 8C 9S 6D +JD QS 7S QC 2S QH JC 4S KS 8D +7S 5S 9S JD KD 9C JC AD 2D 7C +4S 5H AH JH 9C 5D TD 7C 2D 6S +KC 6C 7H 6S 9C QD 5S 4H KS TD +6S 8D KS 2D TH TD 9H JD TS 3S +KH JS 4H 5D 9D TC TD QC JD TS +QS QD AC AD 4C 6S 2D AS 3H KC +4C 7C 3C TD QS 9C KC AS 8D AD +KC 7H QC 6D 8H 6S 5S AH 7S 8C +3S AD 9H JC 6D JD AS KH 6S JH +AD 3D TS KS 7H JH 2D JS QD AC +9C JD 7C 6D TC 6H 6C JC 3D 3S +QC KC 3S JC KD 2C 8D AH QS TS +AS KD 3D JD 8H 7C 8C 5C QD 6C diff --git a/project_euler/problem_54/sol1.py b/project_euler/problem_54/sol1.py new file mode 100644 index 000000000000..3275fe6cd483 --- /dev/null +++ b/project_euler/problem_54/sol1.py @@ -0,0 +1,358 @@ +""" +Problem: https://projecteuler.net/problem=54 + +In the card game poker, a hand consists of five cards and are ranked, +from lowest to highest, in the following way: + +High Card: Highest value card. +One Pair: Two cards of the same value. +Two Pairs: Two different pairs. +Three of a Kind: Three cards of the same value. +Straight: All cards are consecutive values. +Flush: All cards of the same suit. +Full House: Three of a kind and a pair. +Four of a Kind: Four cards of the same value. +Straight Flush: All cards are consecutive values of same suit. +Royal Flush: Ten, Jack, Queen, King, Ace, in same suit. + +The cards are valued in the order: +2, 3, 4, 5, 6, 7, 8, 9, 10, Jack, Queen, King, Ace. + +If two players have the same ranked hands then the rank made up of the highest +value wins; for example, a pair of eights beats a pair of fives. +But if two ranks tie, for example, both players have a pair of queens, then highest +cards in each hand are compared; if the highest cards tie then the next highest +cards are compared, and so on. + +The file, poker.txt, contains one-thousand random hands dealt to two players. +Each line of the file contains ten cards (separated by a single space): the +first five are Player 1's cards and the last five are Player 2's cards. +You can assume that all hands are valid (no invalid characters or repeated cards), +each player's hand is in no specific order, and in each hand there is a clear winner. + +How many hands does Player 1 win? + +Resources used: +https://en.wikipedia.org/wiki/Texas_hold_%27em +https://en.wikipedia.org/wiki/List_of_poker_hands + +Similar problem on codewars: +https://www.codewars.com/kata/ranking-poker-hands +https://www.codewars.com/kata/sortable-poker-hands +""" +from typing import List, Set, Tuple + + +class PokerHand(object): + """Create an object representing a Poker Hand based on an input of a + string which represents the best 5 card combination from the player's hand + and board cards. + + Attributes: (read-only) + hand: string representing the hand consisting of five cards + + Methods: + compare_with(opponent): takes in player's hand (self) and + opponent's hand (opponent) and compares both hands according to + the rules of Texas Hold'em. + Returns one of 3 strings (Win, Loss, Tie) based on whether + player's hand is better than opponent's hand. + + hand_name(): Returns a string made up of two parts: hand name + and high card. + + Supported operators: + Rich comparison operators: <, >, <=, >=, ==, != + + Supported builtin methods and functions: + list.sort(), sorted() + """ + + _HAND_NAME = [ + "High card", + "One pair", + "Two pairs", + "Three of a kind", + "Straight", + "Flush", + "Full house", + "Four of a kind", + "Straight flush", + "Royal flush", + ] + + _CARD_NAME = [ + "", # placeholder as lists are zero indexed + "One", + "Two", + "Three", + "Four", + "Five", + "Six", + "Seven", + "Eight", + "Nine", + "Ten", + "Jack", + "Queen", + "King", + "Ace", + ] + + def __init__(self, hand: str) -> None: + """ + Initialize hand. + Hand should of type str and should contain only five cards each + separated by a space. + + The cards should be of the following format: + [card value][card suit] + + The first character is the value of the card: + 2, 3, 4, 5, 6, 7, 8, 9, T(en), J(ack), Q(ueen), K(ing), A(ce) + + The second character represents the suit: + S(pades), H(earts), D(iamonds), C(lubs) + + For example: "6S 4C KC AS TH" + """ + if not isinstance(hand, str): + raise TypeError(f"Hand should be of type 'str': {hand!r}") + # split removes duplicate whitespaces so no need of strip + if len(hand.split(" ")) != 5: + raise ValueError(f"Hand should contain only 5 cards: {hand!r}") + self._hand = hand + self._first_pair = 0 + self._second_pair = 0 + self._card_values, self._card_suit = self._internal_state() + self._hand_type = self._get_hand_type() + self._high_card = self._card_values[0] + + @property + def hand(self): + """Returns the self hand""" + return self._hand + + def compare_with(self, other: "PokerHand") -> str: + """ + Determines the outcome of comparing self hand with other hand. + Returns the output as 'Win', 'Loss', 'Tie' according to the rules of + Texas Hold'em. + + Here are some examples: + >>> player = PokerHand("2H 3H 4H 5H 6H") # Stright flush + >>> opponent = PokerHand("KS AS TS QS JS") # Royal flush + >>> player.compare_with(opponent) + 'Loss' + + >>> player = PokerHand("2S AH 2H AS AC") # Full house + >>> opponent = PokerHand("2H 3H 5H 6H 7H") # Flush + >>> player.compare_with(opponent) + 'Win' + + >>> player = PokerHand("2S AH 4H 5S 6C") # High card + >>> opponent = PokerHand("AD 4C 5H 6H 2C") # High card + >>> player.compare_with(opponent) + 'Tie' + """ + # Breaking the tie works on the following order of precedence: + # 1. First pair (default 0) + # 2. Second pair (default 0) + # 3. Compare all cards in reverse order because they are sorted. + + # First pair and second pair will only be a non-zero value if the card + # type is either from the following: + # 21: Four of a kind + # 20: Full house + # 17: Three of a kind + # 16: Two pairs + # 15: One pair + if self._hand_type > other._hand_type: + return "Win" + elif self._hand_type < other._hand_type: + return "Loss" + elif self._first_pair == other._first_pair: + if self._second_pair == other._second_pair: + return self._compare_cards(other) + else: + return "Win" if self._second_pair > other._second_pair else "Loss" + return "Win" if self._first_pair > other._first_pair else "Loss" + + # This function is not part of the problem, I did it just for fun + def hand_name(self) -> str: + """ + Return the name of the hand in the following format: + 'hand name, high card' + + Here are some examples: + >>> PokerHand("KS AS TS QS JS").hand_name() + 'Royal flush' + + >>> PokerHand("2D 6D 3D 4D 5D").hand_name() + 'Straight flush, Six-high' + + >>> PokerHand("JC 6H JS JD JH").hand_name() + 'Four of a kind, Jacks' + + >>> PokerHand("3D 2H 3H 2C 2D").hand_name() + 'Full house, Twos over Threes' + + >>> PokerHand("2H 4D 3C AS 5S").hand_name() # Low ace + 'Straight, Five-high' + + Source: https://en.wikipedia.org/wiki/List_of_poker_hands + """ + name = PokerHand._HAND_NAME[self._hand_type - 14] + high = PokerHand._CARD_NAME[self._high_card] + pair1 = PokerHand._CARD_NAME[self._first_pair] + pair2 = PokerHand._CARD_NAME[self._second_pair] + if self._hand_type in [22, 19, 18]: + return name + f", {high}-high" + elif self._hand_type in [21, 17, 15]: + return name + f", {pair1}s" + elif self._hand_type in [20, 16]: + join = "over" if self._hand_type == 20 else "and" + return name + f", {pair1}s {join} {pair2}s" + elif self._hand_type == 23: + return name + else: + return name + f", {high}" + + def _compare_cards(self, other: "PokerHand") -> str: + # Enumerate gives us the index as well as the element of a list + for index, card_value in enumerate(self._card_values): + if card_value != other._card_values[index]: + return "Win" if card_value > other._card_values[index] else "Loss" + return "Tie" + + def _get_hand_type(self) -> int: + # Number representing the type of hand internally: + # 23: Royal flush + # 22: Straight flush + # 21: Four of a kind + # 20: Full house + # 19: Flush + # 18: Straight + # 17: Three of a kind + # 16: Two pairs + # 15: One pair + # 14: High card + if self._is_flush(): + if self._is_five_high_straight() or self._is_straight(): + return 23 if sum(self._card_values) == 60 else 22 + return 19 + elif self._is_five_high_straight() or self._is_straight(): + return 18 + return 14 + self._is_same_kind() + + def _is_flush(self) -> bool: + return len(self._card_suit) == 1 + + def _is_five_high_straight(self) -> bool: + # If a card is a five high straight (low ace) change the location of + # ace from the start of the list to the end. Check whether the first + # element is ace or not. (Don't want to change again) + # Five high straight (low ace): AH 2H 3S 4C 5D + # Why use sorted here? One call to this function will mutate the list to + # [5, 4, 3, 2, 14] and so for subsequent calls (which will be rare) we + # need to compare the sorted version. + # Refer test_multiple_calls_five_high_straight in test_poker_hand.py + if sorted(self._card_values) == [2, 3, 4, 5, 14]: + if self._card_values[0] == 14: + # Remember, our list is sorted in reverse order + ace_card = self._card_values.pop(0) + self._card_values.append(ace_card) + return True + return False + + def _is_straight(self) -> bool: + for i in range(4): + if self._card_values[i] - self._card_values[i + 1] != 1: + return False + return True + + def _is_same_kind(self) -> int: + # Kind Values for internal use: + # 7: Four of a kind + # 6: Full house + # 3: Three of a kind + # 2: Two pairs + # 1: One pair + # 0: False + kind = val1 = val2 = 0 + for i in range(4): + # Compare two cards at a time, if they are same increase 'kind', + # add the value of the card to val1, if it is repeating again we + # will add 2 to 'kind' as there are now 3 cards with same value. + # If we get card of different value than val1, we will do the same + # thing with val2 + if self._card_values[i] == self._card_values[i + 1]: + if not val1: + val1 = self._card_values[i] + kind += 1 + elif val1 == self._card_values[i]: + kind += 2 + elif not val2: + val2 = self._card_values[i] + kind += 1 + elif val2 == self._card_values[i]: + kind += 2 + # For consistency in hand type (look at note in _get_hand_type function) + kind = kind + 2 if kind in [4, 5] else kind + # first meaning first pair to compare in 'compare_with' + first = max(val1, val2) + second = min(val1, val2) + # If it's full house (three count pair + two count pair), make sure + # first pair is three count and if not then switch them both. + if kind == 6 and self._card_values.count(first) != 3: + first, second = second, first + self._first_pair = first + self._second_pair = second + return kind + + def _internal_state(self) -> Tuple[List[int], Set[str]]: + # Internal representation of hand as a list of card values and + # a set of card suit + trans: dict = {"T": "10", "J": "11", "Q": "12", "K": "13", "A": "14"} + new_hand = self._hand.translate(str.maketrans(trans)).split() + card_values = [int(card[:-1]) for card in new_hand] + card_suit = {card[-1] for card in new_hand} + return sorted(card_values, reverse=True), card_suit + + def __repr__(self): + return f'{self.__class__}("{self._hand}")' + + def __str__(self): + return self._hand + + # Rich comparison operators (used in list.sort() and sorted() builtin functions) + # Note that this is not part of the problem but another extra feature where + # if you have a list of PokerHand objects, you can sort them just through + # the builtin functions. + def __eq__(self, other): + if isinstance(other, PokerHand): + return self.compare_with(other) == "Tie" + return NotImplemented + + def __lt__(self, other): + if isinstance(other, PokerHand): + return self.compare_with(other) == "Loss" + return NotImplemented + + def __le__(self, other): + if isinstance(other, PokerHand): + return self < other or self == other + return NotImplemented + + def __gt__(self, other): + if isinstance(other, PokerHand): + return not self < other and self != other + return NotImplemented + + def __ge__(self, other): + if isinstance(other, PokerHand): + return not self < other + return NotImplemented + + def __hash__(self): + return object.__hash__(self) diff --git a/project_euler/problem_54/test_poker_hand.py b/project_euler/problem_54/test_poker_hand.py new file mode 100644 index 000000000000..f60c3aba6616 --- /dev/null +++ b/project_euler/problem_54/test_poker_hand.py @@ -0,0 +1,228 @@ +import os +from itertools import chain +from random import randrange, shuffle + +import pytest + +from .sol1 import PokerHand + +SORTED_HANDS = ( + "4S 3H 2C 7S 5H", + "9D 8H 2C 6S 7H", + "2D 6D 9D TH 7D", + "TC 8C 2S JH 6C", + "JH 8S TH AH QH", + "TS KS 5S 9S AC", + "KD 6S 9D TH AD", + "KS 8D 4D 9S 4S", # pair + "8C 4S KH JS 4D", # pair + "QH 8H KD JH 8S", # pair + "KC 4H KS 2H 8D", # pair + "KD 4S KC 3H 8S", # pair + "AH 8S AS KC JH", # pair + "3H 4C 4H 3S 2H", # 2 pairs + "5S 5D 2C KH KH", # 2 pairs + "3C KH 5D 5S KH", # 2 pairs + "AS 3C KH AD KH", # 2 pairs + "7C 7S 3S 7H 5S", # 3 of a kind + "7C 7S KH 2H 7H", # 3 of a kind + "AC KH QH AH AS", # 3 of a kind + "2H 4D 3C AS 5S", # straight (low ace) + "3C 5C 4C 2C 6H", # straight + "6S 8S 7S 5H 9H", # straight + "JS QS 9H TS KH", # straight + "QC KH TS JS AH", # straight (high ace) + "8C 9C 5C 3C TC", # flush + "3S 8S 9S 5S KS", # flush + "4C 5C 9C 8C KC", # flush + "JH 8H AH KH QH", # flush + "3D 2H 3H 2C 2D", # full house + "2H 2C 3S 3H 3D", # full house + "KH KC 3S 3H 3D", # full house + "JC 6H JS JD JH", # 4 of a kind + "JC 7H JS JD JH", # 4 of a kind + "JC KH JS JD JH", # 4 of a kind + "2S AS 4S 5S 3S", # straight flush (low ace) + "2D 6D 3D 4D 5D", # straight flush + "5C 6C 3C 7C 4C", # straight flush + "JH 9H TH KH QH", # straight flush + "JH AH TH KH QH", # royal flush (high ace straight flush) +) + +TEST_COMPARE = ( + ("2H 3H 4H 5H 6H", "KS AS TS QS JS", "Loss"), + ("2H 3H 4H 5H 6H", "AS AD AC AH JD", "Win"), + ("AS AH 2H AD AC", "JS JD JC JH 3D", "Win"), + ("2S AH 2H AS AC", "JS JD JC JH AD", "Loss"), + ("2S AH 2H AS AC", "2H 3H 5H 6H 7H", "Win"), + ("AS 3S 4S 8S 2S", "2H 3H 5H 6H 7H", "Win"), + ("2H 3H 5H 6H 7H", "2S 3H 4H 5S 6C", "Win"), + ("2S 3H 4H 5S 6C", "3D 4C 5H 6H 2S", "Tie"), + ("2S 3H 4H 5S 6C", "AH AC 5H 6H AS", "Win"), + ("2S 2H 4H 5S 4C", "AH AC 5H 6H AS", "Loss"), + ("2S 2H 4H 5S 4C", "AH AC 5H 6H 7S", "Win"), + ("6S AD 7H 4S AS", "AH AC 5H 6H 7S", "Loss"), + ("2S AH 4H 5S KC", "AH AC 5H 6H 7S", "Loss"), + ("2S 3H 6H 7S 9C", "7H 3C TH 6H 9S", "Loss"), + ("4S 5H 6H TS AC", "3S 5H 6H TS AC", "Win"), + ("2S AH 4H 5S 6C", "AD 4C 5H 6H 2C", "Tie"), + ("AS AH 3H AD AC", "AS AH 2H AD AC", "Win"), + ("AH AC 5H 5C QS", "AH AC 5H 5C KS", "Loss"), + ("AH AC 5H 5C QS", "KH KC 5H 5C QS", "Win"), + ("7C 7S KH 2H 7H", "3C 3S AH 2H 3H", "Win"), + ("3C 3S AH 2H 3H", "7C 7S KH 2H 7H", "Loss"), + ("6H 5H 4H 3H 2H", "5H 4H 3H 2H AH", "Win"), + ("5H 4H 3H 2H AH", "5H 4H 3H 2H AH", "Tie"), + ("5H 4H 3H 2H AH", "6H 5H 4H 3H 2H", "Loss"), + ("AH AD KS KC AC", "AH KD KH AC KC", "Win"), + ("2H 4D 3C AS 5S", "2H 4D 3C 6S 5S", "Loss"), + ("2H 3S 3C 3H 2S", "3S 3C 2S 2H 2D", "Win"), + ("4D 6D 5D 2D JH", "3S 8S 3H TC KH", "Loss"), + ("4S 6C 8S 3S 7S", "AD KS 2D 7D 7C", "Loss"), + ("6S 4C 7H 8C 3H", "5H JC AH 9D 9C", "Loss"), + ("9D 9H JH TC QH", "3C 2S JS 5C 7H", "Win"), + ("2H TC 8S AD 9S", "4H TS 7H 2C 5C", "Win"), + ("9D 3S 2C 7S 7C", "JC TD 3C TC 9H", "Loss"), +) + +TEST_FLUSH = ( + ("2H 3H 4H 5H 6H", True), + ("AS AH 2H AD AC", False), + ("2H 3H 5H 6H 7H", True), + ("KS AS TS QS JS", True), + ("8H 9H QS JS TH", False), + ("AS 3S 4S 8S 2S", True), +) + +TEST_STRAIGHT = ( + ("2H 3H 4H 5H 6H", True), + ("AS AH 2H AD AC", False), + ("2H 3H 5H 6H 7H", False), + ("KS AS TS QS JS", True), + ("8H 9H QS JS TH", True), +) + +TEST_FIVE_HIGH_STRAIGHT = ( + ("2H 4D 3C AS 5S", True, [5, 4, 3, 2, 14]), + ("2H 5D 3C AS 5S", False, [14, 5, 5, 3, 2]), + ("JH QD KC AS TS", False, [14, 13, 12, 11, 10]), + ("9D 3S 2C 7S 7C", False, [9, 7, 7, 3, 2]), +) + +TEST_KIND = ( + ("JH AH TH KH QH", 0), + ("JH 9H TH KH QH", 0), + ("JC KH JS JD JH", 7), + ("KH KC 3S 3H 3D", 6), + ("8C 9C 5C 3C TC", 0), + ("JS QS 9H TS KH", 0), + ("7C 7S KH 2H 7H", 3), + ("3C KH 5D 5S KH", 2), + ("QH 8H KD JH 8S", 1), + ("2D 6D 9D TH 7D", 0), +) + +TEST_TYPES = ( + ("JH AH TH KH QH", 23), + ("JH 9H TH KH QH", 22), + ("JC KH JS JD JH", 21), + ("KH KC 3S 3H 3D", 20), + ("8C 9C 5C 3C TC", 19), + ("JS QS 9H TS KH", 18), + ("7C 7S KH 2H 7H", 17), + ("3C KH 5D 5S KH", 16), + ("QH 8H KD JH 8S", 15), + ("2D 6D 9D TH 7D", 14), +) + + +def generate_random_hand(): + play, oppo = randrange(len(SORTED_HANDS)), randrange(len(SORTED_HANDS)) + expected = ["Loss", "Tie", "Win"][(play >= oppo) + (play > oppo)] + hand, other = SORTED_HANDS[play], SORTED_HANDS[oppo] + return hand, other, expected + + +def generate_random_hands(number_of_hands: int = 100): + return (generate_random_hand() for _ in range(number_of_hands)) + + +@pytest.mark.parametrize("hand, expected", TEST_FLUSH) +def test_hand_is_flush(hand, expected): + assert PokerHand(hand)._is_flush() == expected + + +@pytest.mark.parametrize("hand, expected", TEST_STRAIGHT) +def test_hand_is_straight(hand, expected): + assert PokerHand(hand)._is_straight() == expected + + +@pytest.mark.parametrize("hand, expected, card_values", TEST_FIVE_HIGH_STRAIGHT) +def test_hand_is_five_high_straight(hand, expected, card_values): + player = PokerHand(hand) + assert player._is_five_high_straight() == expected + assert player._card_values == card_values + + +@pytest.mark.parametrize("hand, expected", TEST_KIND) +def test_hand_is_same_kind(hand, expected): + assert PokerHand(hand)._is_same_kind() == expected + + +@pytest.mark.parametrize("hand, expected", TEST_TYPES) +def test_hand_values(hand, expected): + assert PokerHand(hand)._hand_type == expected + + +@pytest.mark.parametrize("hand, other, expected", TEST_COMPARE) +def test_compare_simple(hand, other, expected): + assert PokerHand(hand).compare_with(PokerHand(other)) == expected + + +@pytest.mark.parametrize("hand, other, expected", generate_random_hands()) +def test_compare_random(hand, other, expected): + assert PokerHand(hand).compare_with(PokerHand(other)) == expected + + +def test_hand_sorted(): + POKER_HANDS = [PokerHand(hand) for hand in SORTED_HANDS] + list_copy = POKER_HANDS.copy() + shuffle(list_copy) + user_sorted = chain(sorted(list_copy)) + for index, hand in enumerate(user_sorted): + assert hand == POKER_HANDS[index] + + +def test_custom_sort_five_high_straight(): + # Test that five high straights are compared correctly. + pokerhands = [PokerHand("2D AC 3H 4H 5S"), PokerHand("2S 3H 4H 5S 6C")] + pokerhands.sort(reverse=True) + assert pokerhands[0].__str__() == "2S 3H 4H 5S 6C" + + +def test_multiple_calls_five_high_straight(): + # Multiple calls to five_high_straight function should still return True + # and shouldn't mutate the list in every call other than the first. + pokerhand = PokerHand("2C 4S AS 3D 5C") + expected = True + expected_card_values = [5, 4, 3, 2, 14] + for _ in range(10): + assert pokerhand._is_five_high_straight() == expected + assert pokerhand._card_values == expected_card_values + + +def test_euler_project(): + # Problem number 54 from Project Euler + # Testing from poker_hands.txt file + answer = 0 + script_dir = os.path.abspath(os.path.dirname(__file__)) + poker_hands = os.path.join(script_dir, "poker_hands.txt") + with open(poker_hands, "r") as file_hand: + for line in file_hand: + player_hand = line[:14].strip() + opponent_hand = line[15:].strip() + player, opponent = PokerHand(player_hand), PokerHand(opponent_hand) + output = player.compare_with(opponent) + if output == "Win": + answer += 1 + assert answer == 376 From 718be54dbb430f647c439b39f9d5b4f09b62ccde Mon Sep 17 00:00:00 2001 From: Abhinav Anand Date: Mon, 21 Sep 2020 01:03:26 +0530 Subject: [PATCH 0782/1071] Update sol1.py (#2455) --- project_euler/problem_48/sol1.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/project_euler/problem_48/sol1.py b/project_euler/problem_48/sol1.py index 06ad1408dcef..01ff702d9cd5 100644 --- a/project_euler/problem_48/sol1.py +++ b/project_euler/problem_48/sol1.py @@ -2,14 +2,15 @@ Self Powers Problem 48 -The series, 11 + 22 + 33 + ... + 1010 = 10405071317. +The series, 1^1 + 2^2 + 3^3 + ... + 10^10 = 10405071317. -Find the last ten digits of the series, 11 + 22 + 33 + ... + 10001000. +Find the last ten digits of the series, 1^1 + 2^2 + 3^3 + ... + 1000^1000. """ def solution(): - """Returns the last 10 digits of the series, 11 + 22 + 33 + ... + 10001000. + """ + Returns the last 10 digits of the series, 1^1 + 2^2 + 3^3 + ... + 1000^1000. >>> solution() '9110846700' From a1ea76bcf3f4da8348cd483f8062690ddf279997 Mon Sep 17 00:00:00 2001 From: Du Yuanchao Date: Tue, 22 Sep 2020 21:15:11 +0800 Subject: [PATCH 0783/1071] Optimization problem_10 in project_euler (#2453) * optimization for problem09 in project_euler * added benchmark code * fixup! Format Python code with psf/black push * Update project_euler/problem_09/sol1.py Co-authored-by: Christian Clauss * updating DIRECTORY.md * Update project_euler/problem_09/sol1.py * fixup! Format Python code with psf/black push Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> Co-authored-by: Christian Clauss --- DIRECTORY.md | 1 + project_euler/problem_09/sol1.py | 39 +++++++++++++++++-- project_euler/problem_09/sol3.py | 5 +-- .../can_string_be_rearranged_as_palindrome.py | 4 +- 4 files changed, 42 insertions(+), 7 deletions(-) diff --git a/DIRECTORY.md b/DIRECTORY.md index d91d34803a1c..03044e01084b 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -713,6 +713,7 @@ ## Strings * [Aho Corasick](https://github.com/TheAlgorithms/Python/blob/master/strings/aho_corasick.py) * [Boyer Moore Search](https://github.com/TheAlgorithms/Python/blob/master/strings/boyer_moore_search.py) + * [Can String Be Rearranged As Palindrome](https://github.com/TheAlgorithms/Python/blob/master/strings/can_string_be_rearranged_as_palindrome.py) * [Capitalize](https://github.com/TheAlgorithms/Python/blob/master/strings/capitalize.py) * [Check Anagrams](https://github.com/TheAlgorithms/Python/blob/master/strings/check_anagrams.py) * [Check Pangram](https://github.com/TheAlgorithms/Python/blob/master/strings/check_pangram.py) diff --git a/project_euler/problem_09/sol1.py b/project_euler/problem_09/sol1.py index 3bb5c968115d..caba6b1b1530 100644 --- a/project_euler/problem_09/sol1.py +++ b/project_euler/problem_09/sol1.py @@ -16,7 +16,6 @@ def solution(): 1. a < b < c 2. a**2 + b**2 = c**2 3. a + b + c = 1000 - # The code below has been commented due to slow execution affecting Travis. # >>> solution() # 31875000 @@ -30,6 +29,40 @@ def solution(): return a * b * c +def solution_fast(): + """ + Returns the product of a,b,c which are Pythagorean Triplet that satisfies + the following: + 1. a < b < c + 2. a**2 + b**2 = c**2 + 3. a + b + c = 1000 + + # The code below has been commented due to slow execution affecting Travis. + # >>> solution_fast() + # 31875000 + """ + for a in range(300): + for b in range(400): + c = 1000 - a - b + if a < b < c and (a ** 2) + (b ** 2) == (c ** 2): + return a * b * c + + +def benchmark() -> None: + """ + Benchmark code comparing two different version function. + """ + import timeit + + print( + timeit.timeit("solution()", setup="from __main__ import solution", number=1000) + ) + print( + timeit.timeit( + "solution_fast()", setup="from __main__ import solution_fast", number=1000 + ) + ) + + if __name__ == "__main__": - print("Please Wait...") - print(solution()) + benchmark() diff --git a/project_euler/problem_09/sol3.py b/project_euler/problem_09/sol3.py index a6df46a3a66b..ed27f089bd40 100644 --- a/project_euler/problem_09/sol3.py +++ b/project_euler/problem_09/sol3.py @@ -25,11 +25,10 @@ def solution(): # 31875000 """ return [ - a * b * c + a * b * (1000 - a - b) for a in range(1, 999) for b in range(a, 999) - for c in range(b, 999) - if (a * a + b * b == c * c) and (a + b + c == 1000) + if (a * a + b * b == (1000 - a - b) ** 2) ][0] diff --git a/strings/can_string_be_rearranged_as_palindrome.py b/strings/can_string_be_rearranged_as_palindrome.py index 92bc3b95b243..7fedc5877e26 100644 --- a/strings/can_string_be_rearranged_as_palindrome.py +++ b/strings/can_string_be_rearranged_as_palindrome.py @@ -8,7 +8,9 @@ # Counter is faster for long strings and non-Counter is faster for short strings. -def can_string_be_rearranged_as_palindrome_counter(input_str: str = "",) -> bool: +def can_string_be_rearranged_as_palindrome_counter( + input_str: str = "", +) -> bool: """ A Palindrome is a String that reads the same forward as it does backwards. Examples of Palindromes mom, dad, malayalam From 6e6a49d19f918c09648199d5cab7d2c04fcb6a5e Mon Sep 17 00:00:00 2001 From: Dhruv Date: Wed, 23 Sep 2020 16:54:32 +0530 Subject: [PATCH 0784/1071] Config Travis CI for two jobs (#2463) * Testing Travis CI configuration for project Euler * Fix: Installing mypy and pytest-cov for testing * Remove unnecessary checks for project_euler job * Removing branches section * Update .travis.yml * Update .travis.yml * Update .travis.yml * Update .travis.yml Co-authored-by: Christian Clauss --- .travis.yml | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/.travis.yml b/.travis.yml index a03f8161f13e..cbbdc25e04d8 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,16 +5,23 @@ python: 3.8 cache: pip before_install: pip install --upgrade pip setuptools six install: pip install black flake8 +jobs: + include: + - name: Build + before_script: + - black --check . || true + - flake8 --ignore=E203,W503 --max-complexity=25 --max-line-length=88 --statistics --count . + - scripts/validate_filenames.py # no uppercase, no spaces, in a directory + - pip install -r requirements.txt # fast fail on black, flake8, validate_filenames + script: + - mypy --ignore-missing-imports . + - pytest --doctest-modules --ignore=project_euler/ --durations=10 --cov-report=term-missing:skip-covered --cov=. . + - name: Project Euler + before_script: pip install pytest-cov + script: + - pytest --doctest-modules --durations=10 --cov-report=term-missing:skip-covered --cov=project_euler/ project_euler/ notifications: webhooks: https://www.travisbuddy.com/ on_success: never -before_script: - - black --check . || true - - flake8 --ignore=E203,W503 --max-complexity=25 --max-line-length=88 --statistics --count . - - scripts/validate_filenames.py # no uppercase, no spaces, in a directory - - pip install -r requirements.txt # fast fail on black, flake8, validate_filenames -script: - - mypy --ignore-missing-imports . - - pytest --doctest-modules --durations=10 --cov-report=term-missing:skip-covered --cov=. . after_success: - scripts/build_directory_md.py 2>&1 | tee DIRECTORY.md From 9200a2e54362ecd6be6cfc7e257a37e7a4f16013 Mon Sep 17 00:00:00 2001 From: Christian Clauss Date: Wed, 23 Sep 2020 13:30:13 +0200 Subject: [PATCH 0785/1071] from __future__ import annotations (#2464) * from __future__ import annotations * fixup! from __future__ import annotations * fixup! from __future__ import annotations * fixup! Format Python code with psf/black push Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> --- .travis.yml | 6 ++--- arithmetic_analysis/in_static_equilibrium.py | 4 +-- backtracking/coloring.py | 8 +++--- backtracking/hamiltonian_cycle.py | 8 +++--- backtracking/knight_tour.py | 10 +++---- backtracking/n_queens_math.py | 10 +++---- cellular_automata/one_dimensional.py | 8 +++--- ciphers/rsa_factorization.py | 5 ++-- compression/burrows_wheeler.py | 6 ++--- .../binary_tree/lazy_segment_tree.py | 5 ++-- .../binary_tree/lowest_common_ancestor.py | 19 +++++++------- .../binary_tree/non_recursive_segment_tree.py | 6 +++-- data_structures/binary_tree/treap.py | 5 ++-- data_structures/linked_list/skip_list.py | 8 +++--- .../strassen_matrix_multiplication.py | 21 +++++++-------- dynamic_programming/fast_fibonacci.py | 5 ++-- dynamic_programming/fractional_knapsack_2.py | 6 ++--- .../iterating_through_submasks.py | 4 +-- .../longest_increasing_subsequence.py | 4 +-- ...longest_increasing_subsequence_o(nlogn).py | 4 +-- dynamic_programming/max_non_adjacent_sum.py | 4 +-- dynamic_programming/max_sub_array.py | 4 +-- dynamic_programming/minimum_cost_path.py | 4 +-- genetic_algorithm/basic_string.py | 13 +++++----- graphics/bezier_curve.py | 14 +++++----- graphs/bellman_ford.py | 4 +-- graphs/bidirectional_a_star.py | 13 +++++----- graphs/bidirectional_breadth_first_search.py | 13 +++++----- graphs/breadth_first_search_2.py | 4 +-- graphs/breadth_first_search_shortest_path.py | 4 +-- graphs/depth_first_search.py | 4 +-- graphs/gale_shapley_bigraph.py | 4 +-- graphs/greedy_best_first.py | 8 +++--- graphs/karger.py | 9 +++---- graphs/prim.py | 2 +- linear_algebra/src/polynom_for_points.py | 12 ++++----- linear_algebra/src/transformations_2d.py | 11 ++++---- maths/3n_plus_1.py | 4 +-- maths/abs_max.py | 4 +-- maths/allocation_number.py | 4 +-- maths/collatz_sequence.py | 4 +-- maths/entropy.py | 4 +-- maths/is_square_free.py | 4 +-- maths/line_length.py | 8 +++--- maths/monte_carlo_dice.py | 7 ++--- maths/prime_factors.py | 4 +-- maths/quadratic_equations_complex_numbers.py | 5 ++-- maths/relu.py | 4 +-- matrix/inverse_of_matrix.py | 5 ++-- matrix/matrix_operation.py | 26 +++++++++---------- matrix/searching_in_sorted_matrix.py | 22 +++++++++------- other/dijkstra_bankers_algorithm.py | 17 ++++++------ other/markov_chain.py | 9 ++++--- other/triplet_sum.py | 11 ++++---- project_euler/problem_35/sol1.py | 4 +-- project_euler/problem_37/sol1.py | 7 +++-- project_euler/problem_39/sol1.py | 5 ++-- project_euler/problem_41/sol1.py | 5 ++-- project_euler/problem_46/sol1.py | 4 +-- project_euler/problem_54/sol1.py | 4 +-- scheduling/first_come_first_served.py | 12 ++++----- scheduling/round_robin.py | 9 ++++--- scheduling/shortest_job_first.py | 12 ++++----- searches/double_linear_search.py | 4 +-- searches/simple_binary_search.py | 4 +-- sorts/iterative_merge_sort.py | 6 ++--- sorts/merge_insertion_sort.py | 4 +-- sorts/radix_sort.py | 4 +-- sorts/recursive_insertion_sort.py | 6 ++--- traversals/binary_tree_traversals.py | 3 ++- web_programming/fetch_jobs.py | 6 +++-- .../get_imdb_top_250_movies_csv.py | 5 ++-- 72 files changed, 275 insertions(+), 250 deletions(-) diff --git a/.travis.yml b/.travis.yml index cbbdc25e04d8..d2394b4097f3 100644 --- a/.travis.yml +++ b/.travis.yml @@ -14,14 +14,14 @@ jobs: - scripts/validate_filenames.py # no uppercase, no spaces, in a directory - pip install -r requirements.txt # fast fail on black, flake8, validate_filenames script: - - mypy --ignore-missing-imports . + - mypy --ignore-missing-imports . || true # https://github.com/python/mypy/issues/7907 - pytest --doctest-modules --ignore=project_euler/ --durations=10 --cov-report=term-missing:skip-covered --cov=. . - name: Project Euler before_script: pip install pytest-cov script: - pytest --doctest-modules --durations=10 --cov-report=term-missing:skip-covered --cov=project_euler/ project_euler/ +after_success: + - scripts/build_directory_md.py 2>&1 | tee DIRECTORY.md notifications: webhooks: https://www.travisbuddy.com/ on_success: never -after_success: - - scripts/build_directory_md.py 2>&1 | tee DIRECTORY.md diff --git a/arithmetic_analysis/in_static_equilibrium.py b/arithmetic_analysis/in_static_equilibrium.py index dd7fa706143e..f08b39c3505c 100644 --- a/arithmetic_analysis/in_static_equilibrium.py +++ b/arithmetic_analysis/in_static_equilibrium.py @@ -6,14 +6,14 @@ mypy : passed """ -from typing import List +from __future__ import annotations from numpy import array, cos, cross, radians, sin # type: ignore def polar_force( magnitude: float, angle: float, radian_mode: bool = False -) -> List[float]: +) -> list[float]: """ Resolves force along rectangular components. (force, angle) => (force_x, force_y) diff --git a/backtracking/coloring.py b/backtracking/coloring.py index 3956b21a9182..ceaffe3fae76 100644 --- a/backtracking/coloring.py +++ b/backtracking/coloring.py @@ -5,11 +5,11 @@ Wikipedia: https://en.wikipedia.org/wiki/Graph_coloring """ -from typing import List +from __future__ import annotations def valid_coloring( - neighbours: List[int], colored_vertices: List[int], color: int + neighbours: list[int], colored_vertices: list[int], color: int ) -> bool: """ For each neighbour check if coloring constraint is satisfied @@ -35,7 +35,7 @@ def valid_coloring( def util_color( - graph: List[List[int]], max_colors: int, colored_vertices: List[int], index: int + graph: list[list[int]], max_colors: int, colored_vertices: list[int], index: int ) -> bool: """ Pseudo-Code @@ -86,7 +86,7 @@ def util_color( return False -def color(graph: List[List[int]], max_colors: int) -> List[int]: +def color(graph: list[list[int]], max_colors: int) -> list[int]: """ Wrapper function to call subroutine called util_color which will either return True or False. diff --git a/backtracking/hamiltonian_cycle.py b/backtracking/hamiltonian_cycle.py index 7be1ea350d7c..bf15cce4aca4 100644 --- a/backtracking/hamiltonian_cycle.py +++ b/backtracking/hamiltonian_cycle.py @@ -6,11 +6,11 @@ Wikipedia: https://en.wikipedia.org/wiki/Hamiltonian_path """ -from typing import List +from __future__ import annotations def valid_connection( - graph: List[List[int]], next_ver: int, curr_ind: int, path: List[int] + graph: list[list[int]], next_ver: int, curr_ind: int, path: list[int] ) -> bool: """ Checks whether it is possible to add next into path by validating 2 statements @@ -47,7 +47,7 @@ def valid_connection( return not any(vertex == next_ver for vertex in path) -def util_hamilton_cycle(graph: List[List[int]], path: List[int], curr_ind: int) -> bool: +def util_hamilton_cycle(graph: list[list[int]], path: list[int], curr_ind: int) -> bool: """ Pseudo-Code Base Case: @@ -108,7 +108,7 @@ def util_hamilton_cycle(graph: List[List[int]], path: List[int], curr_ind: int) return False -def hamilton_cycle(graph: List[List[int]], start_index: int = 0) -> List[int]: +def hamilton_cycle(graph: list[list[int]], start_index: int = 0) -> list[int]: r""" Wrapper function to call subroutine called util_hamilton_cycle, which will either return array of vertices indicating hamiltonian cycle diff --git a/backtracking/knight_tour.py b/backtracking/knight_tour.py index e4a93fbc2105..2413ba468838 100644 --- a/backtracking/knight_tour.py +++ b/backtracking/knight_tour.py @@ -1,9 +1,9 @@ # Knight Tour Intro: https://www.youtube.com/watch?v=ab_dY3dZFHM -from typing import List, Tuple +from __future__ import annotations -def get_valid_pos(position: Tuple[int], n: int) -> List[Tuple[int]]: +def get_valid_pos(position: tuple[int], n: int) -> list[tuple[int]]: """ Find all the valid positions a knight can move to from the current position. @@ -32,7 +32,7 @@ def get_valid_pos(position: Tuple[int], n: int) -> List[Tuple[int]]: return permissible_positions -def is_complete(board: List[List[int]]) -> bool: +def is_complete(board: list[list[int]]) -> bool: """ Check if the board (matrix) has been completely filled with non-zero values. @@ -46,7 +46,7 @@ def is_complete(board: List[List[int]]) -> bool: return not any(elem == 0 for row in board for elem in row) -def open_knight_tour_helper(board: List[List[int]], pos: Tuple[int], curr: int) -> bool: +def open_knight_tour_helper(board: list[list[int]], pos: tuple[int], curr: int) -> bool: """ Helper function to solve knight tour problem. """ @@ -66,7 +66,7 @@ def open_knight_tour_helper(board: List[List[int]], pos: Tuple[int], curr: int) return False -def open_knight_tour(n: int) -> List[List[int]]: +def open_knight_tour(n: int) -> list[list[int]]: """ Find the solution for the knight tour problem for a board of size n. Raises ValueError if the tour cannot be performed for the given size. diff --git a/backtracking/n_queens_math.py b/backtracking/n_queens_math.py index fb2b74bd7c4a..811611971616 100644 --- a/backtracking/n_queens_math.py +++ b/backtracking/n_queens_math.py @@ -75,14 +75,14 @@ for another one or vice versa. """ -from typing import List +from __future__ import annotations def depth_first_search( - possible_board: List[int], - diagonal_right_collisions: List[int], - diagonal_left_collisions: List[int], - boards: List[List[str]], + possible_board: list[int], + diagonal_right_collisions: list[int], + diagonal_left_collisions: list[int], + boards: list[list[str]], n: int, ) -> None: """ diff --git a/cellular_automata/one_dimensional.py b/cellular_automata/one_dimensional.py index a6229dd9096f..da77e444502f 100644 --- a/cellular_automata/one_dimensional.py +++ b/cellular_automata/one_dimensional.py @@ -4,7 +4,7 @@ https://mathworld.wolfram.com/ElementaryCellularAutomaton.html """ -from typing import List +from __future__ import annotations from PIL import Image @@ -15,7 +15,7 @@ # fmt: on -def format_ruleset(ruleset: int) -> List[int]: +def format_ruleset(ruleset: int) -> list[int]: """ >>> format_ruleset(11100) [0, 0, 0, 1, 1, 1, 0, 0] @@ -27,7 +27,7 @@ def format_ruleset(ruleset: int) -> List[int]: return [int(c) for c in f"{ruleset:08}"[:8]] -def new_generation(cells: List[List[int]], rule: List[int], time: int) -> List[int]: +def new_generation(cells: list[list[int]], rule: list[int], time: int) -> list[int]: population = len(cells[0]) # 31 next_generation = [] for i in range(population): @@ -41,7 +41,7 @@ def new_generation(cells: List[List[int]], rule: List[int], time: int) -> List[i return next_generation -def generate_image(cells: List[List[int]]) -> Image.Image: +def generate_image(cells: list[list[int]]) -> Image.Image: """ Convert the cells into a greyscale PIL.Image.Image and return it to the caller. >>> from random import random diff --git a/ciphers/rsa_factorization.py b/ciphers/rsa_factorization.py index 9ec34e6c5a17..6df32b6cc887 100644 --- a/ciphers/rsa_factorization.py +++ b/ciphers/rsa_factorization.py @@ -7,12 +7,13 @@ More readable source: https://www.di-mgt.com.au/rsa_factorize_n.html large number can take minutes to factor, therefore are not included in doctest. """ +from __future__ import annotations + import math import random -from typing import List -def rsafactor(d: int, e: int, N: int) -> List[int]: +def rsafactor(d: int, e: int, N: int) -> list[int]: """ This function returns the factors of N, where p*q=N Return: [p, q] diff --git a/compression/burrows_wheeler.py b/compression/burrows_wheeler.py index 03912f80e1a7..1a6610915e65 100644 --- a/compression/burrows_wheeler.py +++ b/compression/burrows_wheeler.py @@ -10,10 +10,10 @@ original character. The BWT is thus a "free" method of improving the efficiency of text compression algorithms, costing only some extra computation. """ -from typing import Dict, List +from __future__ import annotations -def all_rotations(s: str) -> List[str]: +def all_rotations(s: str) -> list[str]: """ :param s: The string that will be rotated len(s) times. :return: A list with the rotations. @@ -43,7 +43,7 @@ def all_rotations(s: str) -> List[str]: return [s[i:] + s[:i] for i in range(len(s))] -def bwt_transform(s: str) -> Dict: +def bwt_transform(s: str) -> dict: """ :param s: The string that will be used at bwt algorithm :return: the string composed of the last char of each row of the ordered diff --git a/data_structures/binary_tree/lazy_segment_tree.py b/data_structures/binary_tree/lazy_segment_tree.py index 38d93a32e767..5bc79e74efcd 100644 --- a/data_structures/binary_tree/lazy_segment_tree.py +++ b/data_structures/binary_tree/lazy_segment_tree.py @@ -1,5 +1,6 @@ +from __future__ import annotations + import math -from typing import List class SegmentTree: @@ -36,7 +37,7 @@ def right(self, idx: int) -> int: return idx * 2 + 1 def build( - self, idx: int, left_element: int, right_element: int, A: List[int] + self, idx: int, left_element: int, right_element: int, A: list[int] ) -> None: if left_element == right_element: self.segment_tree[idx] = A[left_element - 1] diff --git a/data_structures/binary_tree/lowest_common_ancestor.py b/data_structures/binary_tree/lowest_common_ancestor.py index c25536cdaef0..2f1e893fcf99 100644 --- a/data_structures/binary_tree/lowest_common_ancestor.py +++ b/data_structures/binary_tree/lowest_common_ancestor.py @@ -1,11 +1,12 @@ # https://en.wikipedia.org/wiki/Lowest_common_ancestor # https://en.wikipedia.org/wiki/Breadth-first_search +from __future__ import annotations + import queue -from typing import Dict, List, Tuple -def swap(a: int, b: int) -> Tuple[int, int]: +def swap(a: int, b: int) -> tuple[int, int]: """ Return a tuple (b, a) when given two integers a and b >>> swap(2,3) @@ -21,7 +22,7 @@ def swap(a: int, b: int) -> Tuple[int, int]: return a, b -def create_sparse(max_node: int, parent: List[List[int]]) -> List[List[int]]: +def create_sparse(max_node: int, parent: list[list[int]]) -> list[list[int]]: """ creating sparse table which saves each nodes 2^i-th parent """ @@ -35,8 +36,8 @@ def create_sparse(max_node: int, parent: List[List[int]]) -> List[List[int]]: # returns lca of node u,v def lowest_common_ancestor( - u: int, v: int, level: List[int], parent: List[List[int]] -) -> List[List[int]]: + u: int, v: int, level: list[int], parent: list[list[int]] +) -> list[list[int]]: # u must be deeper in the tree than v if level[u] < level[v]: u, v = swap(u, v) @@ -57,12 +58,12 @@ def lowest_common_ancestor( # runs a breadth first search from root node of the tree def breadth_first_search( - level: List[int], - parent: List[List[int]], + level: list[int], + parent: list[list[int]], max_node: int, - graph: Dict[int, int], + graph: dict[int, int], root=1, -) -> Tuple[List[int], List[List[int]]]: +) -> tuple[list[int], list[list[int]]]: """ sets every nodes direct parent parent of root node is set to 0 diff --git a/data_structures/binary_tree/non_recursive_segment_tree.py b/data_structures/binary_tree/non_recursive_segment_tree.py index cdcf1fa8dd2d..064e5aded7b4 100644 --- a/data_structures/binary_tree/non_recursive_segment_tree.py +++ b/data_structures/binary_tree/non_recursive_segment_tree.py @@ -35,13 +35,15 @@ >>> st.query(0, 2) [1, 2, 3] """ -from typing import Callable, List, TypeVar +from __future__ import annotations + +from typing import Callable, TypeVar T = TypeVar("T") class SegmentTree: - def __init__(self, arr: List[T], fnc: Callable[[T, T], T]) -> None: + def __init__(self, arr: list[T], fnc: Callable[[T, T], T]) -> None: """ Segment Tree constructor, it works just with commutative combiner. :param arr: list of elements for the segment tree diff --git a/data_structures/binary_tree/treap.py b/data_structures/binary_tree/treap.py index fbb57650280e..26648f7aba61 100644 --- a/data_structures/binary_tree/treap.py +++ b/data_structures/binary_tree/treap.py @@ -1,7 +1,8 @@ # flake8: noqa +from __future__ import annotations + from random import random -from typing import Tuple class Node: @@ -33,7 +34,7 @@ def __str__(self): return value + left + right -def split(root: Node, value: int) -> Tuple[Node, Node]: +def split(root: Node, value: int) -> tuple[Node, Node]: """ We split current tree into 2 trees with value: diff --git a/data_structures/linked_list/skip_list.py b/data_structures/linked_list/skip_list.py index ee572cd3ed19..8f06e6193d52 100644 --- a/data_structures/linked_list/skip_list.py +++ b/data_structures/linked_list/skip_list.py @@ -3,8 +3,10 @@ https://epaperpress.com/sortsearch/download/skiplist.pdf """ +from __future__ import annotations + from random import random -from typing import Generic, List, Optional, Tuple, TypeVar +from typing import Generic, Optional, TypeVar KT = TypeVar("KT") VT = TypeVar("VT") @@ -14,7 +16,7 @@ class Node(Generic[KT, VT]): def __init__(self, key: KT, value: VT): self.key = key self.value = value - self.forward: List[Node[KT, VT]] = [] + self.forward: list[Node[KT, VT]] = [] def __repr__(self) -> str: """ @@ -122,7 +124,7 @@ def random_level(self) -> int: return level - def _locate_node(self, key) -> Tuple[Optional[Node[KT, VT]], List[Node[KT, VT]]]: + def _locate_node(self, key) -> tuple[Optional[Node[KT, VT]], list[Node[KT, VT]]]: """ :param key: Searched key, :return: Tuple with searched node (or None if given key is not present) diff --git a/divide_and_conquer/strassen_matrix_multiplication.py b/divide_and_conquer/strassen_matrix_multiplication.py index 486258e8bae0..29a174daebf9 100644 --- a/divide_and_conquer/strassen_matrix_multiplication.py +++ b/divide_and_conquer/strassen_matrix_multiplication.py @@ -1,8 +1,9 @@ +from __future__ import annotations + import math -from typing import List, Tuple -def default_matrix_multiplication(a: List, b: List) -> List: +def default_matrix_multiplication(a: list, b: list) -> list: """ Multiplication only for 2x2 matrices """ @@ -15,23 +16,21 @@ def default_matrix_multiplication(a: List, b: List) -> List: return new_matrix -def matrix_addition(matrix_a: List, matrix_b: List): +def matrix_addition(matrix_a: list, matrix_b: list): return [ [matrix_a[row][col] + matrix_b[row][col] for col in range(len(matrix_a[row]))] for row in range(len(matrix_a)) ] -def matrix_subtraction(matrix_a: List, matrix_b: List): +def matrix_subtraction(matrix_a: list, matrix_b: list): return [ [matrix_a[row][col] - matrix_b[row][col] for col in range(len(matrix_a[row]))] for row in range(len(matrix_a)) ] -def split_matrix( - a: List, -) -> Tuple[List, List, List, List]: +def split_matrix(a: list) -> tuple[list, list, list, list]: """ Given an even length matrix, returns the top_left, top_right, bot_left, bot_right quadrant. @@ -64,16 +63,16 @@ def split_matrix( return top_left, top_right, bot_left, bot_right -def matrix_dimensions(matrix: List) -> Tuple[int, int]: +def matrix_dimensions(matrix: list) -> tuple[int, int]: return len(matrix), len(matrix[0]) -def print_matrix(matrix: List) -> None: +def print_matrix(matrix: list) -> None: for i in range(len(matrix)): print(matrix[i]) -def actual_strassen(matrix_a: List, matrix_b: List) -> List: +def actual_strassen(matrix_a: list, matrix_b: list) -> list: """ Recursive function to calculate the product of two matrices, using the Strassen Algorithm. It only supports even length matrices. @@ -106,7 +105,7 @@ def actual_strassen(matrix_a: List, matrix_b: List) -> List: return new_matrix -def strassen(matrix1: List, matrix2: List) -> List: +def strassen(matrix1: list, matrix2: list) -> list: """ >>> strassen([[2,1,3],[3,4,6],[1,4,2],[7,6,7]], [[4,2,3,4],[2,1,1,1],[8,6,4,2]]) [[34, 23, 19, 15], [68, 46, 37, 28], [28, 18, 15, 12], [96, 62, 55, 48]] diff --git a/dynamic_programming/fast_fibonacci.py b/dynamic_programming/fast_fibonacci.py index 63481fe70a92..f48186a34c25 100644 --- a/dynamic_programming/fast_fibonacci.py +++ b/dynamic_programming/fast_fibonacci.py @@ -4,8 +4,9 @@ This program calculates the nth Fibonacci number in O(log(n)). It's possible to calculate F(1_000_000) in less than a second. """ +from __future__ import annotations + import sys -from typing import Tuple def fibonacci(n: int) -> int: @@ -20,7 +21,7 @@ def fibonacci(n: int) -> int: # returns (F(n), F(n-1)) -def _fib(n: int) -> Tuple[int, int]: +def _fib(n: int) -> tuple[int, int]: if n == 0: # (F(0), F(1)) return (0, 1) diff --git a/dynamic_programming/fractional_knapsack_2.py b/dynamic_programming/fractional_knapsack_2.py index eadb73c61a39..cae57738311b 100644 --- a/dynamic_programming/fractional_knapsack_2.py +++ b/dynamic_programming/fractional_knapsack_2.py @@ -2,12 +2,12 @@ # https://www.guru99.com/fractional-knapsack-problem-greedy.html # https://medium.com/walkinthecode/greedy-algorithm-fractional-knapsack-problem-9aba1daecc93 -from typing import List, Tuple +from __future__ import annotations def fractional_knapsack( - value: List[int], weight: List[int], capacity: int -) -> Tuple[int, List[int]]: + 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] diff --git a/dynamic_programming/iterating_through_submasks.py b/dynamic_programming/iterating_through_submasks.py index cb27a5b884bd..855af61d6707 100644 --- a/dynamic_programming/iterating_through_submasks.py +++ b/dynamic_programming/iterating_through_submasks.py @@ -5,10 +5,10 @@ its submasks. The mask s is submask of m if only bits that were included in bitmask are set """ -from typing import List +from __future__ import annotations -def list_of_submasks(mask: int) -> List[int]: +def list_of_submasks(mask: int) -> list[int]: """ Args: diff --git a/dynamic_programming/longest_increasing_subsequence.py b/dynamic_programming/longest_increasing_subsequence.py index 48d5e8e8fade..f5ca8a2b5cdc 100644 --- a/dynamic_programming/longest_increasing_subsequence.py +++ b/dynamic_programming/longest_increasing_subsequence.py @@ -10,10 +10,10 @@ Example: [10, 22, 9, 33, 21, 50, 41, 60, 80] as input will return [10, 22, 33, 41, 60, 80] as output """ -from typing import List +from __future__ import annotations -def longest_subsequence(array: List[int]) -> List[int]: # This function is recursive +def longest_subsequence(array: list[int]) -> list[int]: # This function is recursive """ Some examples >>> longest_subsequence([10, 22, 9, 33, 21, 50, 41, 60, 80]) diff --git a/dynamic_programming/longest_increasing_subsequence_o(nlogn).py b/dynamic_programming/longest_increasing_subsequence_o(nlogn).py index b33774057db3..af536f8bbd01 100644 --- a/dynamic_programming/longest_increasing_subsequence_o(nlogn).py +++ b/dynamic_programming/longest_increasing_subsequence_o(nlogn).py @@ -4,7 +4,7 @@ # comments: This programme outputs the Longest Strictly Increasing Subsequence in # O(NLogN) Where N is the Number of elements in the list ############################# -from typing import List +from __future__ import annotations def CeilIndex(v, l, r, key): # noqa: E741 @@ -17,7 +17,7 @@ def CeilIndex(v, l, r, key): # noqa: E741 return r -def LongestIncreasingSubsequenceLength(v: List[int]) -> int: +def LongestIncreasingSubsequenceLength(v: list[int]) -> int: """ >>> LongestIncreasingSubsequenceLength([2, 5, 3, 7, 11, 8, 10, 13, 6]) 6 diff --git a/dynamic_programming/max_non_adjacent_sum.py b/dynamic_programming/max_non_adjacent_sum.py index 15dd8ce664ed..5362b22ca9dc 100644 --- a/dynamic_programming/max_non_adjacent_sum.py +++ b/dynamic_programming/max_non_adjacent_sum.py @@ -1,9 +1,9 @@ # Video Explanation: https://www.youtube.com/watch?v=6w60Zi1NtL8&feature=emb_logo -from typing import List +from __future__ import annotations -def maximum_non_adjacent_sum(nums: List[int]) -> int: +def maximum_non_adjacent_sum(nums: list[int]) -> int: """ Find the maximum non-adjacent sum of the integers in the nums input list diff --git a/dynamic_programming/max_sub_array.py b/dynamic_programming/max_sub_array.py index 1ca4f90bbaa2..3060010ef7c6 100644 --- a/dynamic_programming/max_sub_array.py +++ b/dynamic_programming/max_sub_array.py @@ -1,7 +1,7 @@ """ author : Mayank Kumar Jha (mk9440) """ -from typing import List +from __future__ import annotations def find_max_sub_array(A, low, high): @@ -38,7 +38,7 @@ def find_max_cross_sum(A, low, mid, high): return max_left, max_right, (left_sum + right_sum) -def max_sub_array(nums: List[int]) -> int: +def max_sub_array(nums: list[int]) -> int: """ Finds the contiguous subarray which has the largest sum and return its sum. diff --git a/dynamic_programming/minimum_cost_path.py b/dynamic_programming/minimum_cost_path.py index 09295a4fafbe..3ad24b5528d1 100644 --- a/dynamic_programming/minimum_cost_path.py +++ b/dynamic_programming/minimum_cost_path.py @@ -1,9 +1,9 @@ # Youtube Explanation: https://www.youtube.com/watch?v=lBRtnuxg-gU -from typing import List +from __future__ import annotations -def minimum_cost_path(matrix: List[List[int]]) -> int: +def minimum_cost_path(matrix: list[list[int]]) -> int: """ Find the minimum cost traced by all possible paths from top left to bottom right in a given matrix diff --git a/genetic_algorithm/basic_string.py b/genetic_algorithm/basic_string.py index 482a6cb5e656..97dbe182bc82 100644 --- a/genetic_algorithm/basic_string.py +++ b/genetic_algorithm/basic_string.py @@ -5,8 +5,9 @@ Author: D4rkia """ +from __future__ import annotations + import random -from typing import List, Tuple # Maximum size of the population. bigger could be faster but is more memory expensive N_POPULATION = 200 @@ -20,7 +21,7 @@ random.seed(random.randint(0, 1000)) -def basic(target: str, genes: List[str], debug: bool = True) -> Tuple[int, int, str]: +def basic(target: str, genes: list[str], debug: bool = True) -> tuple[int, int, str]: """ Verify that the target contains no genes besides the ones inside genes variable. @@ -69,7 +70,7 @@ def basic(target: str, genes: List[str], debug: bool = True) -> Tuple[int, int, total_population += len(population) # Random population created now it's time to evaluate - def evaluate(item: str, main_target: str = target) -> Tuple[str, float]: + def evaluate(item: str, main_target: str = target) -> tuple[str, float]: """ Evaluate how similar the item is with the target by just counting each char in the right position @@ -84,7 +85,7 @@ def evaluate(item: str, main_target: str = target) -> Tuple[str, float]: # Adding a bit of concurrency can make everything faster, # # import concurrent.futures - # population_score: List[Tuple[str, float]] = [] + # population_score: list[tuple[str, float]] = [] # with concurrent.futures.ThreadPoolExecutor( # max_workers=NUM_WORKERS) as executor: # futures = {executor.submit(evaluate, item) for item in population} @@ -121,7 +122,7 @@ def evaluate(item: str, main_target: str = target) -> Tuple[str, float]: ] # Select, Crossover and Mutate a new population - def select(parent_1: Tuple[str, float]) -> List[str]: + def select(parent_1: tuple[str, float]) -> list[str]: """Select the second parent and generate new population""" pop = [] # Generate more child proportionally to the fitness score @@ -135,7 +136,7 @@ def select(parent_1: Tuple[str, float]) -> List[str]: pop.append(mutate(child_2)) return pop - def crossover(parent_1: str, parent_2: str) -> Tuple[str, str]: + def crossover(parent_1: str, parent_2: str) -> tuple[str, str]: """Slice and combine two string in a random point""" random_slice = random.randint(0, len(parent_1) - 1) child_1 = parent_1[:random_slice] + parent_2[random_slice:] diff --git a/graphics/bezier_curve.py b/graphics/bezier_curve.py index 48755647e02d..295ff47e8cdc 100644 --- a/graphics/bezier_curve.py +++ b/graphics/bezier_curve.py @@ -1,6 +1,6 @@ # https://en.wikipedia.org/wiki/B%C3%A9zier_curve # https://www.tutorialspoint.com/computer_graphics/computer_graphics_curves.htm -from typing import List, Tuple +from __future__ import annotations from scipy.special import comb @@ -12,7 +12,7 @@ class BezierCurve: This implementation works only for 2d coordinates in the xy plane. """ - def __init__(self, list_of_points: List[Tuple[float, float]]): + def __init__(self, list_of_points: list[tuple[float, float]]): """ list_of_points: Control points in the xy plane on which to interpolate. These points control the behavior (shape) of the Bezier curve. @@ -22,7 +22,7 @@ def __init__(self, list_of_points: List[Tuple[float, float]]): # Degree = 1 will produce a straight line. self.degree = len(list_of_points) - 1 - def basis_function(self, t: float) -> List[float]: + def basis_function(self, t: float) -> list[float]: """ The basis function determines the weight of each control point at time t. t: time value between 0 and 1 inclusive at which to evaluate the basis of @@ -36,7 +36,7 @@ def basis_function(self, t: float) -> List[float]: [0.0, 1.0] """ assert 0 <= t <= 1, "Time t must be between 0 and 1." - output_values: List[float] = [] + output_values: list[float] = [] for i in range(len(self.list_of_points)): # basis function for each i output_values.append( @@ -46,7 +46,7 @@ def basis_function(self, t: float) -> List[float]: assert round(sum(output_values), 5) == 1 return output_values - def bezier_curve_function(self, t: float) -> Tuple[float, float]: + def bezier_curve_function(self, t: float) -> tuple[float, float]: """ The function to produce the values of the Bezier curve at time t. t: the value of time t at which to evaluate the Bezier function @@ -80,8 +80,8 @@ def plot_curve(self, step_size: float = 0.01): """ from matplotlib import pyplot as plt - to_plot_x: List[float] = [] # x coordinates of points to plot - to_plot_y: List[float] = [] # y coordinates of points to plot + to_plot_x: list[float] = [] # x coordinates of points to plot + to_plot_y: list[float] = [] # y coordinates of points to plot t = 0.0 while t <= 1: diff --git a/graphs/bellman_ford.py b/graphs/bellman_ford.py index d4d37a365e03..ace7985647bb 100644 --- a/graphs/bellman_ford.py +++ b/graphs/bellman_ford.py @@ -1,4 +1,4 @@ -from typing import Dict, List +from __future__ import annotations def printDist(dist, V): @@ -7,7 +7,7 @@ def printDist(dist, V): print("\t".join(f"{i}\t{d}" for i, d in enumerate(distances))) -def BellmanFord(graph: List[Dict[str, int]], V: int, E: int, src: int) -> int: +def BellmanFord(graph: list[dict[str, int]], V: int, E: int, src: int) -> int: """ Returns shortest paths from a vertex src to all other vertices. diff --git a/graphs/bidirectional_a_star.py b/graphs/bidirectional_a_star.py index 76313af769be..72ff4fa65ff0 100644 --- a/graphs/bidirectional_a_star.py +++ b/graphs/bidirectional_a_star.py @@ -2,9 +2,10 @@ https://en.wikipedia.org/wiki/Bidirectional_search """ +from __future__ import annotations + import time from math import sqrt -from typing import List, Tuple # 1 for manhattan, 0 for euclidean HEURISTIC = 0 @@ -89,7 +90,7 @@ def __init__(self, start, goal): self.reached = False - def search(self) -> List[Tuple[int]]: + def search(self) -> list[tuple[int]]: while self.open_nodes: # Open Nodes are sorted using __lt__ self.open_nodes.sort() @@ -120,7 +121,7 @@ def search(self) -> List[Tuple[int]]: if not (self.reached): return [(self.start.pos)] - def get_successors(self, parent: Node) -> List[Node]: + def get_successors(self, parent: Node) -> list[Node]: """ Returns a list of successors (both in the grid and free spaces) """ @@ -146,7 +147,7 @@ def get_successors(self, parent: Node) -> List[Node]: ) return successors - def retrace_path(self, node: Node) -> List[Tuple[int]]: + def retrace_path(self, node: Node) -> list[tuple[int]]: """ Retrace the path from parents to parents until start node """ @@ -177,7 +178,7 @@ def __init__(self, start, goal): self.bwd_astar = AStar(goal, start) self.reached = False - def search(self) -> List[Tuple[int]]: + def search(self) -> list[tuple[int]]: while self.fwd_astar.open_nodes or self.bwd_astar.open_nodes: self.fwd_astar.open_nodes.sort() self.bwd_astar.open_nodes.sort() @@ -224,7 +225,7 @@ def search(self) -> List[Tuple[int]]: def retrace_bidirectional_path( self, fwd_node: Node, bwd_node: Node - ) -> List[Tuple[int]]: + ) -> list[tuple[int]]: fwd_path = self.fwd_astar.retrace_path(fwd_node) bwd_path = self.bwd_astar.retrace_path(bwd_node) bwd_path.pop() diff --git a/graphs/bidirectional_breadth_first_search.py b/graphs/bidirectional_breadth_first_search.py index d941c0db5893..39d8dc7d4187 100644 --- a/graphs/bidirectional_breadth_first_search.py +++ b/graphs/bidirectional_breadth_first_search.py @@ -2,8 +2,9 @@ https://en.wikipedia.org/wiki/Bidirectional_search """ +from __future__ import annotations + import time -from typing import List, Tuple grid = [ [0, 0, 0, 0, 0, 0, 0], @@ -51,7 +52,7 @@ def __init__(self, start, goal): self.node_queue = [self.start] self.reached = False - def search(self) -> List[Tuple[int]]: + def search(self) -> list[tuple[int]]: while self.node_queue: current_node = self.node_queue.pop(0) @@ -67,7 +68,7 @@ def search(self) -> List[Tuple[int]]: if not (self.reached): return [(self.start.pos)] - def get_successors(self, parent: Node) -> List[Node]: + def get_successors(self, parent: Node) -> list[Node]: """ Returns a list of successors (both in the grid and free spaces) """ @@ -86,7 +87,7 @@ def get_successors(self, parent: Node) -> List[Node]: ) return successors - def retrace_path(self, node: Node) -> List[Tuple[int]]: + def retrace_path(self, node: Node) -> list[tuple[int]]: """ Retrace the path from parents to parents until start node """ @@ -118,7 +119,7 @@ def __init__(self, start, goal): self.bwd_bfs = BreadthFirstSearch(goal, start) self.reached = False - def search(self) -> List[Tuple[int]]: + def search(self) -> list[tuple[int]]: while self.fwd_bfs.node_queue or self.bwd_bfs.node_queue: current_fwd_node = self.fwd_bfs.node_queue.pop(0) current_bwd_node = self.bwd_bfs.node_queue.pop(0) @@ -146,7 +147,7 @@ def search(self) -> List[Tuple[int]]: def retrace_bidirectional_path( self, fwd_node: Node, bwd_node: Node - ) -> List[Tuple[int]]: + ) -> list[tuple[int]]: fwd_path = self.fwd_bfs.retrace_path(fwd_node) bwd_path = self.bwd_bfs.retrace_path(bwd_node) bwd_path.pop() diff --git a/graphs/breadth_first_search_2.py b/graphs/breadth_first_search_2.py index 293a1012f61f..a90e963a4043 100644 --- a/graphs/breadth_first_search_2.py +++ b/graphs/breadth_first_search_2.py @@ -12,7 +12,7 @@ mark w as explored add w to Q (at the end) """ -from typing import Dict, Set +from __future__ import annotations G = { "A": ["B", "C"], @@ -24,7 +24,7 @@ } -def breadth_first_search(graph: Dict, start: str) -> Set[str]: +def breadth_first_search(graph: dict, start: str) -> set[str]: """ >>> ''.join(sorted(breadth_first_search(G, 'A'))) 'ABCDEF' diff --git a/graphs/breadth_first_search_shortest_path.py b/graphs/breadth_first_search_shortest_path.py index c25a5bb4f7dd..b43479d4659c 100644 --- a/graphs/breadth_first_search_shortest_path.py +++ b/graphs/breadth_first_search_shortest_path.py @@ -1,7 +1,7 @@ """Breath First Search (BFS) can be used when finding the shortest path from a given source node to a target node in an unweighted graph. """ -from typing import Dict +from __future__ import annotations graph = { "A": ["B", "C", "E"], @@ -15,7 +15,7 @@ class Graph: - def __init__(self, graph: Dict[str, str], source_vertex: str) -> None: + def __init__(self, graph: dict[str, str], source_vertex: str) -> None: """Graph is implemented as dictionary of adjacency lists. Also, Source vertex have to be defined upon initialization. """ diff --git a/graphs/depth_first_search.py b/graphs/depth_first_search.py index 43f2eaaea19a..907cc172f253 100644 --- a/graphs/depth_first_search.py +++ b/graphs/depth_first_search.py @@ -1,9 +1,9 @@ """Non recursive implementation of a DFS algorithm.""" -from typing import Dict, Set +from __future__ import annotations -def depth_first_search(graph: Dict, start: str) -> Set[int]: +def depth_first_search(graph: dict, start: str) -> set[int]: """Depth First Search on Graph :param graph: directed graph in dictionary format :param vertex: starting vertex as a string diff --git a/graphs/gale_shapley_bigraph.py b/graphs/gale_shapley_bigraph.py index 07a3922f86ec..59baf8296ea6 100644 --- a/graphs/gale_shapley_bigraph.py +++ b/graphs/gale_shapley_bigraph.py @@ -1,7 +1,7 @@ -from typing import List +from __future__ import annotations -def stable_matching(donor_pref: List[int], recipient_pref: List[int]) -> List[int]: +def stable_matching(donor_pref: list[int], recipient_pref: list[int]) -> list[int]: """ Finds the stable match in any bipartite graph, i.e a pairing where no 2 objects prefer each other over their partner. The function accepts the preferences of diff --git a/graphs/greedy_best_first.py b/graphs/greedy_best_first.py index 2e63a50ce30a..4b80a6853d3f 100644 --- a/graphs/greedy_best_first.py +++ b/graphs/greedy_best_first.py @@ -2,7 +2,7 @@ https://en.wikipedia.org/wiki/Best-first_search#Greedy_BFS """ -from typing import List, Tuple +from __future__ import annotations grid = [ [0, 0, 0, 0, 0, 0, 0], @@ -81,7 +81,7 @@ def __init__(self, start, goal): self.reached = False - def search(self) -> List[Tuple[int]]: + def search(self) -> list[tuple[int]]: """ Search for the path, if a path is not found, only the starting position is returned @@ -116,7 +116,7 @@ def search(self) -> List[Tuple[int]]: if not (self.reached): return [self.start.pos] - def get_successors(self, parent: Node) -> List[Node]: + def get_successors(self, parent: Node) -> list[Node]: """ Returns a list of successors (both in the grid and free spaces) """ @@ -143,7 +143,7 @@ def get_successors(self, parent: Node) -> List[Node]: ) return successors - def retrace_path(self, node: Node) -> List[Tuple[int]]: + def retrace_path(self, node: Node) -> list[tuple[int]]: """ Retrace the path from parents to parents until start node """ diff --git a/graphs/karger.py b/graphs/karger.py index baa0eebd90b4..f72128c8178a 100644 --- a/graphs/karger.py +++ b/graphs/karger.py @@ -2,8 +2,9 @@ An implementation of Karger's Algorithm for partitioning a graph. """ +from __future__ import annotations + import random -from typing import Dict, List, Set, Tuple # Adjacency list representation of this graph: # https://en.wikipedia.org/wiki/File:Single_run_of_Karger%E2%80%99s_Mincut_algorithm.svg @@ -21,7 +22,7 @@ } -def partition_graph(graph: Dict[str, List[str]]) -> Set[Tuple[str, str]]: +def partition_graph(graph: dict[str, list[str]]) -> set[tuple[str, str]]: """ Partitions a graph using Karger's Algorithm. Implemented from pseudocode found here: @@ -60,9 +61,7 @@ def partition_graph(graph: Dict[str, List[str]]) -> Set[Tuple[str, str]]: for neighbor in uv_neighbors: graph_copy[neighbor].append(uv) - contracted_nodes[uv] = { - node for node in contracted_nodes[u].union(contracted_nodes[v]) - } + contracted_nodes[uv] = set(contracted_nodes[u].union(contracted_nodes[v])) # Remove nodes u and v. del graph_copy[u] diff --git a/graphs/prim.py b/graphs/prim.py index f7376cfb48ca..70329da7e8e2 100644 --- a/graphs/prim.py +++ b/graphs/prim.py @@ -100,7 +100,7 @@ def prim_heap(graph: list, root: Vertex) -> Iterator[tuple]: u.pi = None root.key = 0 - h = [v for v in graph] + h = list(graph) hq.heapify(h) while h: diff --git a/linear_algebra/src/polynom_for_points.py b/linear_algebra/src/polynom_for_points.py index 8db89fc2c1ea..7a363723d9d2 100644 --- a/linear_algebra/src/polynom_for_points.py +++ b/linear_algebra/src/polynom_for_points.py @@ -1,7 +1,7 @@ -from typing import List +from __future__ import annotations -def points_to_polynomial(coordinates: List[List[int]]) -> str: +def points_to_polynomial(coordinates: list[list[int]]) -> str: """ coordinates is a two dimensional matrix: [[x, y], [x, y], ...] number of points you want to use @@ -60,7 +60,7 @@ def points_to_polynomial(coordinates: List[List[int]]) -> str: while count_of_line < x: count_in_line = 0 a = coordinates[count_of_line][0] - count_line: List[int] = [] + count_line: list[int] = [] while count_in_line < x: count_line.append(a ** (x - (count_in_line + 1))) count_in_line += 1 @@ -69,7 +69,7 @@ def points_to_polynomial(coordinates: List[List[int]]) -> str: count_of_line = 0 # put the y values into a vector - vector: List[int] = [] + vector: list[int] = [] while count_of_line < x: vector.append(coordinates[count_of_line][1]) count_of_line += 1 @@ -94,7 +94,7 @@ def points_to_polynomial(coordinates: List[List[int]]) -> str: count = 0 # make solutions - solution: List[str] = [] + solution: list[str] = [] while count < x: solution.append(vector[count] / matrix[count][count]) count += 1 @@ -103,7 +103,7 @@ def points_to_polynomial(coordinates: List[List[int]]) -> str: solved = "f(x)=" while count < x: - remove_e: List[str] = str(solution[count]).split("E") + remove_e: list[str] = str(solution[count]).split("E") if len(remove_e) > 1: solution[count] = remove_e[0] + "*10^" + remove_e[1] solved += "x^" + str(x - (count + 1)) + "*" + str(solution[count]) diff --git a/linear_algebra/src/transformations_2d.py b/linear_algebra/src/transformations_2d.py index 9ee238fd7999..6a15189c5676 100644 --- a/linear_algebra/src/transformations_2d.py +++ b/linear_algebra/src/transformations_2d.py @@ -11,11 +11,12 @@ reflection(45) = [[0.05064397763545947, 0.893996663600558], [0.893996663600558, 0.7018070490682369]] """ +from __future__ import annotations + from math import cos, sin -from typing import List -def scaling(scaling_factor: float) -> List[List[float]]: +def scaling(scaling_factor: float) -> list[list[float]]: """ >>> scaling(5) [[5.0, 0.0], [0.0, 5.0]] @@ -24,7 +25,7 @@ def scaling(scaling_factor: float) -> List[List[float]]: return [[scaling_factor * int(x == y) for x in range(2)] for y in range(2)] -def rotation(angle: float) -> List[List[float]]: +def rotation(angle: float) -> list[list[float]]: """ >>> rotation(45) # doctest: +NORMALIZE_WHITESPACE [[0.5253219888177297, -0.8509035245341184], @@ -34,7 +35,7 @@ def rotation(angle: float) -> List[List[float]]: return [[c, -s], [s, c]] -def projection(angle: float) -> List[List[float]]: +def projection(angle: float) -> list[list[float]]: """ >>> projection(45) # doctest: +NORMALIZE_WHITESPACE [[0.27596319193541496, 0.446998331800279], @@ -45,7 +46,7 @@ def projection(angle: float) -> List[List[float]]: return [[c * c, cs], [cs, s * s]] -def reflection(angle: float) -> List[List[float]]: +def reflection(angle: float) -> list[list[float]]: """ >>> reflection(45) # doctest: +NORMALIZE_WHITESPACE [[0.05064397763545947, 0.893996663600558], diff --git a/maths/3n_plus_1.py b/maths/3n_plus_1.py index baaa74f89bf5..28c9fd7b426f 100644 --- a/maths/3n_plus_1.py +++ b/maths/3n_plus_1.py @@ -1,7 +1,7 @@ -from typing import List, Tuple +from __future__ import annotations -def n31(a: int) -> Tuple[List[int], int]: +def n31(a: int) -> tuple[list[int], int]: """ Returns the Collatz sequence and its length of any positive integer. >>> n31(4) diff --git a/maths/abs_max.py b/maths/abs_max.py index 554e27f6ee66..e5a8219657ac 100644 --- a/maths/abs_max.py +++ b/maths/abs_max.py @@ -1,7 +1,7 @@ -from typing import List +from __future__ import annotations -def abs_max(x: List[int]) -> int: +def abs_max(x: list[int]) -> int: """ >>> abs_max([0,5,1,11]) 11 diff --git a/maths/allocation_number.py b/maths/allocation_number.py index c6f1e562f878..4e74bb2e6950 100644 --- a/maths/allocation_number.py +++ b/maths/allocation_number.py @@ -1,7 +1,7 @@ -from typing import List +from __future__ import annotations -def allocation_num(number_of_bytes: int, partitions: int) -> List[str]: +def allocation_num(number_of_bytes: int, partitions: int) -> list[str]: """ Divide a number of bytes into x partitions. diff --git a/maths/collatz_sequence.py b/maths/collatz_sequence.py index 6ace77312732..7b3636de69f4 100644 --- a/maths/collatz_sequence.py +++ b/maths/collatz_sequence.py @@ -1,7 +1,7 @@ -from typing import List +from __future__ import annotations -def collatz_sequence(n: int) -> List[int]: +def collatz_sequence(n: int) -> list[int]: """ Collatz conjecture: start with any positive integer n. The next term is obtained as follows: diff --git a/maths/entropy.py b/maths/entropy.py index c380afd3b5c9..74980ef9e0c6 100644 --- a/maths/entropy.py +++ b/maths/entropy.py @@ -4,11 +4,11 @@ Implementation of entropy of information https://en.wikipedia.org/wiki/Entropy_(information_theory) """ +from __future__ import annotations import math from collections import Counter from string import ascii_lowercase -from typing import Tuple def calculate_prob(text: str) -> None: @@ -89,7 +89,7 @@ def calculate_prob(text: str) -> None: print("{0:.1f}".format(round(((-1 * my_sec_sum) - (-1 * my_fir_sum))))) -def analyze_text(text: str) -> Tuple[dict, dict]: +def analyze_text(text: str) -> tuple[dict, dict]: """ Convert text input into two dicts of counts. The first dictionary stores the frequency of single character strings. diff --git a/maths/is_square_free.py b/maths/is_square_free.py index 6d27d0af3387..8d83d95ffb67 100644 --- a/maths/is_square_free.py +++ b/maths/is_square_free.py @@ -3,10 +3,10 @@ python/black : True flake8 : True """ -from typing import List +from __future__ import annotations -def is_square_free(factors: List[int]) -> bool: +def is_square_free(factors: list[int]) -> bool: """ # doctest: +NORMALIZE_WHITESPACE This functions takes a list of prime factors as input. diff --git a/maths/line_length.py b/maths/line_length.py index 6df0a916efe6..1d386b44b50d 100644 --- a/maths/line_length.py +++ b/maths/line_length.py @@ -1,4 +1,4 @@ -import math as m +import math from typing import Callable, Union @@ -29,7 +29,7 @@ def line_length( '10.000000' >>> def f(x): - ... return m.sin(5 * x) + m.cos(10 * x) + x * x/10 + ... return math.sin(5 * x) + math.cos(10 * x) + x * x/10 >>> f"{line_length(f, 0.0, 10.0, 10000):.6f}" '69.534930' """ @@ -43,7 +43,7 @@ def line_length( # Approximates curve as a sequence of linear lines and sums their length x2 = (x_end - x_start) / steps + x1 fx2 = fnc(x2) - length += m.hypot(x2 - x1, fx2 - fx1) + length += math.hypot(x2 - x1, fx2 - fx1) # Increment step x1 = x2 @@ -55,7 +55,7 @@ def line_length( if __name__ == "__main__": def f(x): - return m.sin(10 * x) + return math.sin(10 * x) print("f(x) = sin(10 * x)") print("The length of the curve from x = -10 to x = 10 is:") diff --git a/maths/monte_carlo_dice.py b/maths/monte_carlo_dice.py index c36c3e83e00b..e8e3abe83a99 100644 --- a/maths/monte_carlo_dice.py +++ b/maths/monte_carlo_dice.py @@ -1,5 +1,6 @@ +from __future__ import annotations + import random -from typing import List class Dice: @@ -16,7 +17,7 @@ def _str_(self): return "Fair Dice" -def throw_dice(num_throws: int, num_dice: int = 2) -> List[float]: +def throw_dice(num_throws: int, num_dice: int = 2) -> list[float]: """ Return probability list of all possible sums when throwing dice. @@ -35,7 +36,7 @@ def throw_dice(num_throws: int, num_dice: int = 2) -> List[float]: dices = [Dice() for i in range(num_dice)] count_of_sum = [0] * (len(dices) * Dice.NUM_SIDES + 1) for i in range(num_throws): - count_of_sum[sum([dice.roll() for dice in dices])] += 1 + count_of_sum[sum(dice.roll() for dice in dices)] += 1 probability = [round((count * 100) / num_throws, 2) for count in count_of_sum] return probability[num_dice:] # remove probability of sums that never appear diff --git a/maths/prime_factors.py b/maths/prime_factors.py index 34795dd98d1a..e520ae3a6d04 100644 --- a/maths/prime_factors.py +++ b/maths/prime_factors.py @@ -1,10 +1,10 @@ """ python/black : True """ -from typing import List +from __future__ import annotations -def prime_factors(n: int) -> List[int]: +def prime_factors(n: int) -> list[int]: """ Returns prime factors of n as a list. diff --git a/maths/quadratic_equations_complex_numbers.py b/maths/quadratic_equations_complex_numbers.py index 7c47bdef2297..01a411bc560d 100644 --- a/maths/quadratic_equations_complex_numbers.py +++ b/maths/quadratic_equations_complex_numbers.py @@ -1,8 +1,9 @@ +from __future__ import annotations + from cmath import sqrt -from typing import Tuple -def quadratic_roots(a: int, b: int, c: int) -> Tuple[complex, complex]: +def quadratic_roots(a: int, b: int, c: int) -> tuple[complex, complex]: """ Given the numerical coefficients a, b and c, calculates the roots for any quadratic equation of the form ax^2 + bx + c diff --git a/maths/relu.py b/maths/relu.py index 826ada65fa16..458c6bd5c391 100644 --- a/maths/relu.py +++ b/maths/relu.py @@ -9,12 +9,12 @@ Script inspired from its corresponding Wikipedia article https://en.wikipedia.org/wiki/Rectifier_(neural_networks) """ -from typing import List +from __future__ import annotations import numpy as np -def relu(vector: List[float]): +def relu(vector: list[float]): """ Implements the relu function diff --git a/matrix/inverse_of_matrix.py b/matrix/inverse_of_matrix.py index abbeb79ddbdb..9deca6c3c08e 100644 --- a/matrix/inverse_of_matrix.py +++ b/matrix/inverse_of_matrix.py @@ -1,8 +1,9 @@ +from __future__ import annotations + from decimal import Decimal -from typing import List -def inverse_of_matrix(matrix: List[List[float]]) -> List[List[float]]: +def inverse_of_matrix(matrix: list[list[float]]) -> list[list[float]]: """ A matrix multiplied with its inverse gives the identity matrix. This function finds the inverse of a 2x2 matrix. diff --git a/matrix/matrix_operation.py b/matrix/matrix_operation.py index 3838dab6be09..dca01f9c3183 100644 --- a/matrix/matrix_operation.py +++ b/matrix/matrix_operation.py @@ -2,10 +2,10 @@ Functions for 2D matrix operations """ -from typing import List, Tuple +from __future__ import annotations -def add(*matrix_s: List[list]) -> List[list]: +def add(*matrix_s: list[list]) -> list[list]: """ >>> add([[1,2],[3,4]],[[2,3],[4,5]]) [[3, 5], [7, 9]] @@ -20,7 +20,7 @@ def add(*matrix_s: List[list]) -> List[list]: return [[sum(t) for t in zip(*m)] for m in zip(*matrix_s)] -def subtract(matrix_a: List[list], matrix_b: List[list]) -> List[list]: +def subtract(matrix_a: list[list], matrix_b: list[list]) -> list[list]: """ >>> subtract([[1,2],[3,4]],[[2,3],[4,5]]) [[-1, -1], [-1, -1]] @@ -35,7 +35,7 @@ def subtract(matrix_a: List[list], matrix_b: List[list]) -> List[list]: return [[i - j for i, j in zip(*m)] for m in zip(matrix_a, matrix_b)] -def scalar_multiply(matrix: List[list], n: int) -> List[list]: +def scalar_multiply(matrix: list[list], n: int) -> list[list]: """ >>> scalar_multiply([[1,2],[3,4]],5) [[5, 10], [15, 20]] @@ -45,7 +45,7 @@ def scalar_multiply(matrix: List[list], n: int) -> List[list]: return [[x * n for x in row] for row in matrix] -def multiply(matrix_a: List[list], matrix_b: List[list]) -> List[list]: +def multiply(matrix_a: list[list], matrix_b: list[list]) -> list[list]: """ >>> multiply([[1,2],[3,4]],[[5,5],[7,5]]) [[19, 15], [43, 35]] @@ -67,7 +67,7 @@ def multiply(matrix_a: List[list], matrix_b: List[list]) -> List[list]: ] -def identity(n: int) -> List[list]: +def identity(n: int) -> list[list]: """ :param n: dimension for nxn matrix :type n: int @@ -79,7 +79,7 @@ def identity(n: int) -> List[list]: return [[int(row == column) for column in range(n)] for row in range(n)] -def transpose(matrix: List[list], return_map: bool = True) -> List[list]: +def transpose(matrix: list[list], return_map: bool = True) -> list[list]: """ >>> transpose([[1,2],[3,4]]) # doctest: +ELLIPSIS List[list]: return list(map(list, zip(*matrix))) -def minor(matrix: List[list], row: int, column: int) -> List[list]: +def minor(matrix: list[list], row: int, column: int) -> list[list]: """ >>> minor([[1, 2], [3, 4]], 1, 1) [[1]] @@ -102,7 +102,7 @@ def minor(matrix: List[list], row: int, column: int) -> List[list]: return [row[:column] + row[column + 1 :] for row in minor] -def determinant(matrix: List[list]) -> int: +def determinant(matrix: list[list]) -> int: """ >>> determinant([[1, 2], [3, 4]]) -2 @@ -118,7 +118,7 @@ def determinant(matrix: List[list]) -> int: ) -def inverse(matrix: List[list]) -> List[list]: +def inverse(matrix: list[list]) -> list[list]: """ >>> inverse([[1, 2], [3, 4]]) [[-2.0, 1.0], [1.5, -0.5]] @@ -142,17 +142,17 @@ def inverse(matrix: List[list]) -> List[list]: return scalar_multiply(adjugate, 1 / det) -def _check_not_integer(matrix: List[list]) -> bool: +def _check_not_integer(matrix: list[list]) -> bool: if not isinstance(matrix, int) and not isinstance(matrix[0], int): return True raise TypeError("Expected a matrix, got int/list instead") -def _shape(matrix: List[list]) -> list: +def _shape(matrix: list[list]) -> list: return len(matrix), len(matrix[0]) -def _verify_matrix_sizes(matrix_a: List[list], matrix_b: List[list]) -> Tuple[list]: +def _verify_matrix_sizes(matrix_a: list[list], matrix_b: list[list]) -> tuple[list]: shape = _shape(matrix_a) + _shape(matrix_b) if shape[0] != shape[3] or shape[1] != shape[2]: raise ValueError( diff --git a/matrix/searching_in_sorted_matrix.py b/matrix/searching_in_sorted_matrix.py index 470fc01dfb8f..ca6263a32f50 100644 --- a/matrix/searching_in_sorted_matrix.py +++ b/matrix/searching_in_sorted_matrix.py @@ -1,21 +1,23 @@ -from typing import List, Union +from __future__ import annotations + +from typing import Union def search_in_a_sorted_matrix( - mat: List[list], m: int, n: int, key: Union[int, float] + mat: list[list], m: int, n: int, key: Union[int, float] ) -> None: """ - >>> search_in_a_sorted_matrix(\ - [[2, 5, 7], [4, 8, 13], [9, 11, 15], [12, 17, 20]], 3, 3, 5) + >>> search_in_a_sorted_matrix( + ... [[2, 5, 7], [4, 8, 13], [9, 11, 15], [12, 17, 20]], 3, 3, 5) Key 5 found at row- 1 column- 2 - >>> search_in_a_sorted_matrix(\ - [[2, 5, 7], [4, 8, 13], [9, 11, 15], [12, 17, 20]], 3, 3, 21) + >>> search_in_a_sorted_matrix( + ... [[2, 5, 7], [4, 8, 13], [9, 11, 15], [12, 17, 20]], 3, 3, 21) Key 21 not found - >>> search_in_a_sorted_matrix(\ - [[2.1, 5, 7], [4, 8, 13], [9, 11, 15], [12, 17, 20]], 3, 3, 2.1) + >>> search_in_a_sorted_matrix( + ... [[2.1, 5, 7], [4, 8, 13], [9, 11, 15], [12, 17, 20]], 3, 3, 2.1) Key 2.1 found at row- 1 column- 1 - >>> search_in_a_sorted_matrix(\ - [[2.1, 5, 7], [4, 8, 13], [9, 11, 15], [12, 17, 20]], 3, 3, 2.2) + >>> search_in_a_sorted_matrix( + ... [[2.1, 5, 7], [4, 8, 13], [9, 11, 15], [12, 17, 20]], 3, 3, 2.2) Key 2.2 not found """ i, j = m - 1, 0 diff --git a/other/dijkstra_bankers_algorithm.py b/other/dijkstra_bankers_algorithm.py index 405c10b88495..be7bceba125d 100644 --- a/other/dijkstra_bankers_algorithm.py +++ b/other/dijkstra_bankers_algorithm.py @@ -15,8 +15,9 @@ (https://rosettacode.org/wiki/Banker%27s_algorithm) """ +from __future__ import annotations + import time -from typing import Dict, List import numpy as np @@ -40,9 +41,9 @@ class BankersAlgorithm: def __init__( self, - claim_vector: List[int], - allocated_resources_table: List[List[int]], - maximum_claim_table: List[List[int]], + claim_vector: list[int], + allocated_resources_table: list[list[int]], + maximum_claim_table: list[list[int]], ) -> None: """ :param claim_vector: A nxn/nxm list depicting the amount of each resources @@ -56,7 +57,7 @@ def __init__( self.__allocated_resources_table = allocated_resources_table self.__maximum_claim_table = maximum_claim_table - def __processes_resource_summation(self) -> List[int]: + def __processes_resource_summation(self) -> list[int]: """ Check for allocated resources in line with each resource in the claim vector """ @@ -65,7 +66,7 @@ def __processes_resource_summation(self) -> List[int]: for i in range(len(self.__allocated_resources_table[0])) ] - def __available_resources(self) -> List[int]: + def __available_resources(self) -> list[int]: """ Check for available resources in line with each resource in the claim vector """ @@ -73,7 +74,7 @@ def __available_resources(self) -> List[int]: self.__processes_resource_summation() ) - def __need(self) -> List[List[int]]: + def __need(self) -> list[list[int]]: """ Implement safety checker that calculates the needs by ensuring that max_claim[i][j] - alloc_table[i][j] <= avail[j] @@ -83,7 +84,7 @@ def __need(self) -> List[List[int]]: for i, allocated_resource in enumerate(self.__allocated_resources_table) ] - def __need_index_manager(self) -> Dict[int, List[int]]: + def __need_index_manager(self) -> dict[int, list[int]]: """ This function builds an index control dictionary to track original ids/indices of processes when altered during execution of method "main" diff --git a/other/markov_chain.py b/other/markov_chain.py index 9b13fa515709..b93c408cd288 100644 --- a/other/markov_chain.py +++ b/other/markov_chain.py @@ -1,6 +1,7 @@ +from __future__ import annotations + from collections import Counter from random import random -from typing import Dict, List, Tuple class MarkovChainGraphUndirectedUnweighted: @@ -23,7 +24,7 @@ def add_transition_probability( self.add_node(node2) self.connections[node1][node2] = probability - def get_nodes(self) -> List[str]: + def get_nodes(self) -> list[str]: return list(self.connections) def transition(self, node: str) -> str: @@ -37,8 +38,8 @@ def transition(self, node: str) -> str: def get_transitions( - start: str, transitions: List[Tuple[str, str, float]], steps: int -) -> Dict[str, int]: + start: str, transitions: list[tuple[str, str, float]], steps: int +) -> dict[str, int]: """ Running Markov Chain algorithm and calculating the number of times each node is visited diff --git a/other/triplet_sum.py b/other/triplet_sum.py index 25fed5d54579..0e78bb52bb72 100644 --- a/other/triplet_sum.py +++ b/other/triplet_sum.py @@ -3,13 +3,14 @@ we are required to find a triplet from the array such that it's sum is equal to the target. """ +from __future__ import annotations + from itertools import permutations from random import randint from timeit import repeat -from typing import List, Tuple -def make_dataset() -> Tuple[List[int], int]: +def make_dataset() -> tuple[list[int], int]: arr = [randint(-1000, 1000) for i in range(10)] r = randint(-5000, 5000) return (arr, r) @@ -18,7 +19,7 @@ def make_dataset() -> Tuple[List[int], int]: dataset = make_dataset() -def triplet_sum1(arr: List[int], target: int) -> Tuple[int, int, int]: +def triplet_sum1(arr: list[int], target: int) -> tuple[int, int, int]: """ Returns a triplet in the array with sum equal to target, else (0, 0, 0). @@ -37,7 +38,7 @@ def triplet_sum1(arr: List[int], target: int) -> Tuple[int, int, int]: return (0, 0, 0) -def triplet_sum2(arr: List[int], target: int) -> Tuple[int, int, int]: +def triplet_sum2(arr: list[int], target: int) -> tuple[int, int, int]: """ Returns a triplet in the array with sum equal to target, else (0, 0, 0). @@ -64,7 +65,7 @@ def triplet_sum2(arr: List[int], target: int) -> Tuple[int, int, int]: return (0, 0, 0) -def solution_times() -> Tuple[float, float]: +def solution_times() -> tuple[float, float]: setup_code = """ from __main__ import dataset, triplet_sum1, triplet_sum2 """ diff --git a/project_euler/problem_35/sol1.py b/project_euler/problem_35/sol1.py index c47eb7d82f54..5f023c56ae50 100644 --- a/project_euler/problem_35/sol1.py +++ b/project_euler/problem_35/sol1.py @@ -10,7 +10,7 @@ we will rule out the numbers which contain an even digit. After this we will generate each circular combination of the number and check if all are prime. """ -from typing import List +from __future__ import annotations seive = [True] * 1000001 i = 2 @@ -47,7 +47,7 @@ def contains_an_even_digit(n: int) -> bool: return any(digit in "02468" for digit in str(n)) -def find_circular_primes(limit: int = 1000000) -> List[int]: +def find_circular_primes(limit: int = 1000000) -> list[int]: """ Return circular primes below limit. >>> len(find_circular_primes(100)) diff --git a/project_euler/problem_37/sol1.py b/project_euler/problem_37/sol1.py index c01d64d83fbe..e3aec5a844fe 100644 --- a/project_euler/problem_37/sol1.py +++ b/project_euler/problem_37/sol1.py @@ -9,8 +9,7 @@ NOTE: 2, 3, 5, and 7 are not considered to be truncatable primes. """ - -from typing import List +from __future__ import annotations seive = [True] * 1000001 seive[1] = False @@ -36,7 +35,7 @@ def is_prime(n: int) -> bool: return seive[n] -def list_truncated_nums(n: int) -> List[int]: +def list_truncated_nums(n: int) -> list[int]: """ Returns a list of all left and right truncated numbers of n >>> list_truncated_nums(927628) @@ -71,7 +70,7 @@ def validate(n: int) -> bool: return True -def compute_truncated_primes(count: int = 11) -> List[int]: +def compute_truncated_primes(count: int = 11) -> list[int]: """ Returns the list of truncated primes >>> compute_truncated_primes(11) diff --git a/project_euler/problem_39/sol1.py b/project_euler/problem_39/sol1.py index b0a5d5188fed..79fa309f01c5 100644 --- a/project_euler/problem_39/sol1.py +++ b/project_euler/problem_39/sol1.py @@ -6,11 +6,12 @@ For which value of p ≤ 1000, is the number of solutions maximised? """ +from __future__ import annotations + from collections import Counter -from typing import Dict -def pythagorean_triple(max_perimeter: int) -> Dict: +def pythagorean_triple(max_perimeter: int) -> dict: """ Returns a dictionary with keys as the perimeter of a right angled triangle and value as the number of corresponding triplets. diff --git a/project_euler/problem_41/sol1.py b/project_euler/problem_41/sol1.py index 4ed09ccb8565..b4c0d842ae25 100644 --- a/project_euler/problem_41/sol1.py +++ b/project_euler/problem_41/sol1.py @@ -1,6 +1,7 @@ +from __future__ import annotations + from itertools import permutations from math import sqrt -from typing import List """ We shall say that an n-digit number is pandigital if it makes use of all the digits @@ -34,7 +35,7 @@ def is_prime(n: int) -> bool: return True -def compute_pandigital_primes(n: int) -> List[int]: +def compute_pandigital_primes(n: int) -> list[int]: """ Returns a list of all n-digit pandigital primes. >>> compute_pandigital_primes(2) diff --git a/project_euler/problem_46/sol1.py b/project_euler/problem_46/sol1.py index 761e9b8cc7fb..e94e9247d86b 100644 --- a/project_euler/problem_46/sol1.py +++ b/project_euler/problem_46/sol1.py @@ -15,7 +15,7 @@ prime and twice a square? """ -from typing import List +from __future__ import annotations seive = [True] * 100001 i = 2 @@ -43,7 +43,7 @@ def is_prime(n: int) -> bool: odd_composites = [num for num in range(3, len(seive), 2) if not is_prime(num)] -def compute_nums(n: int) -> List[int]: +def compute_nums(n: int) -> list[int]: """ Returns a list of first n odd composite numbers which do not follow the conjecture. diff --git a/project_euler/problem_54/sol1.py b/project_euler/problem_54/sol1.py index 3275fe6cd483..d36d3702d7c8 100644 --- a/project_euler/problem_54/sol1.py +++ b/project_euler/problem_54/sol1.py @@ -40,7 +40,7 @@ https://www.codewars.com/kata/ranking-poker-hands https://www.codewars.com/kata/sortable-poker-hands """ -from typing import List, Set, Tuple +from __future__ import annotations class PokerHand(object): @@ -310,7 +310,7 @@ def _is_same_kind(self) -> int: self._second_pair = second return kind - def _internal_state(self) -> Tuple[List[int], Set[str]]: + def _internal_state(self) -> tuple[list[int], set[str]]: # Internal representation of hand as a list of card values and # a set of card suit trans: dict = {"T": "10", "J": "11", "Q": "12", "K": "13", "A": "14"} diff --git a/scheduling/first_come_first_served.py b/scheduling/first_come_first_served.py index b51fc9fe0c04..c5f61720f97e 100644 --- a/scheduling/first_come_first_served.py +++ b/scheduling/first_come_first_served.py @@ -2,10 +2,10 @@ # In this Algorithm we just care about the order that the processes arrived # without carring about their duration time # https://en.wikipedia.org/wiki/Scheduling_(computing)#First_come,_first_served -from typing import List +from __future__ import annotations -def calculate_waiting_times(duration_times: List[int]) -> List[int]: +def calculate_waiting_times(duration_times: list[int]) -> list[int]: """ This function calculates the waiting time of some processes that have a specified duration time. @@ -24,8 +24,8 @@ def calculate_waiting_times(duration_times: List[int]) -> List[int]: def calculate_turnaround_times( - duration_times: List[int], waiting_times: List[int] -) -> List[int]: + duration_times: list[int], waiting_times: list[int] +) -> list[int]: """ This function calculates the turnaround time of some processes. Return: The time difference between the completion time and the @@ -44,7 +44,7 @@ def calculate_turnaround_times( ] -def calculate_average_turnaround_time(turnaround_times: List[int]) -> float: +def calculate_average_turnaround_time(turnaround_times: list[int]) -> float: """ This function calculates the average of the turnaround times Return: The average of the turnaround times. @@ -58,7 +58,7 @@ def calculate_average_turnaround_time(turnaround_times: List[int]) -> float: return sum(turnaround_times) / len(turnaround_times) -def calculate_average_waiting_time(waiting_times: List[int]) -> float: +def calculate_average_waiting_time(waiting_times: list[int]) -> float: """ This function calculates the average of the waiting times Return: The average of the waiting times. diff --git a/scheduling/round_robin.py b/scheduling/round_robin.py index 4a79301c1816..e8d54dd9a553 100755 --- a/scheduling/round_robin.py +++ b/scheduling/round_robin.py @@ -3,11 +3,12 @@ In Round Robin each process is assigned a fixed time slot in a cyclic way. https://en.wikipedia.org/wiki/Round-robin_scheduling """ +from __future__ import annotations + from statistics import mean -from typing import List -def calculate_waiting_times(burst_times: List[int]) -> List[int]: +def calculate_waiting_times(burst_times: list[int]) -> list[int]: """ Calculate the waiting times of a list of processes that have a specified duration. @@ -40,8 +41,8 @@ def calculate_waiting_times(burst_times: List[int]) -> List[int]: def calculate_turn_around_times( - burst_times: List[int], waiting_times: List[int] -) -> List[int]: + burst_times: list[int], waiting_times: list[int] +) -> list[int]: """ >>> calculate_turn_around_times([1, 2, 3, 4], [0, 1, 3]) [1, 3, 6] diff --git a/scheduling/shortest_job_first.py b/scheduling/shortest_job_first.py index ecb6e01fdfe6..f9e2ad975627 100644 --- a/scheduling/shortest_job_first.py +++ b/scheduling/shortest_job_first.py @@ -3,14 +3,14 @@ Please note arrival time and burst Please use spaces to separate times entered. """ -from typing import List +from __future__ import annotations import pandas as pd def calculate_waitingtime( - arrival_time: List[int], burst_time: List[int], no_of_processes: int -) -> List[int]: + arrival_time: list[int], burst_time: list[int], no_of_processes: int +) -> list[int]: """ Calculate the waiting time of each processes Return: list of waiting times. @@ -72,8 +72,8 @@ def calculate_waitingtime( def calculate_turnaroundtime( - burst_time: List[int], no_of_processes: int, waiting_time: List[int] -) -> List[int]: + burst_time: list[int], no_of_processes: int, waiting_time: list[int] +) -> list[int]: """ Calculate the turn around time of each Processes Return: list of turn around times. @@ -91,7 +91,7 @@ def calculate_turnaroundtime( def calculate_average_times( - waiting_time: List[int], turn_around_time: List[int], no_of_processes: int + waiting_time: list[int], turn_around_time: list[int], no_of_processes: int ): """ This function calculates the average of the waiting & turnaround times diff --git a/searches/double_linear_search.py b/searches/double_linear_search.py index 6056f00fc2bb..d9dad3c685b6 100644 --- a/searches/double_linear_search.py +++ b/searches/double_linear_search.py @@ -1,7 +1,7 @@ -from typing import List +from __future__ import annotations -def double_linear_search(array: List[int], search_item: int) -> int: +def double_linear_search(array: list[int], search_item: int) -> int: """ Iterate through the array from both sides to find the index of search_item. diff --git a/searches/simple_binary_search.py b/searches/simple_binary_search.py index b6215312fb2d..8495dda8d518 100644 --- a/searches/simple_binary_search.py +++ b/searches/simple_binary_search.py @@ -7,10 +7,10 @@ For manual testing run: python3 simple_binary_search.py """ -from typing import List +from __future__ import annotations -def binary_search(a_list: List[int], item: int) -> bool: +def binary_search(a_list: list[int], item: int) -> bool: """ >>> test_list = [0, 1, 2, 8, 13, 17, 19, 32, 42] >>> print(binary_search(test_list, 3)) diff --git a/sorts/iterative_merge_sort.py b/sorts/iterative_merge_sort.py index e6e1513393e0..5ee0badab9e6 100644 --- a/sorts/iterative_merge_sort.py +++ b/sorts/iterative_merge_sort.py @@ -9,10 +9,10 @@ python3 iterative_merge_sort.py """ -from typing import List +from __future__ import annotations -def merge(input_list: List, low: int, mid: int, high: int) -> List: +def merge(input_list: list, low: int, mid: int, high: int) -> list: """ sorting left-half and right-half individually then merging them into result @@ -26,7 +26,7 @@ def merge(input_list: List, low: int, mid: int, high: int) -> List: # iteration over the unsorted list -def iter_merge_sort(input_list: List) -> List: +def iter_merge_sort(input_list: list) -> list: """ Return a sorted copy of the input list diff --git a/sorts/merge_insertion_sort.py b/sorts/merge_insertion_sort.py index 339851699525..fb71d84a3c14 100644 --- a/sorts/merge_insertion_sort.py +++ b/sorts/merge_insertion_sort.py @@ -11,10 +11,10 @@ python3 merge_insertion_sort.py """ -from typing import List +from __future__ import annotations -def merge_insertion_sort(collection: List[int]) -> List[int]: +def merge_insertion_sort(collection: list[int]) -> list[int]: """Pure implementation of merge-insertion sort algorithm in Python :param collection: some mutable ordered collection with heterogeneous diff --git a/sorts/radix_sort.py b/sorts/radix_sort.py index 0ddf996cf1ee..7942462ea10d 100644 --- a/sorts/radix_sort.py +++ b/sorts/radix_sort.py @@ -1,7 +1,7 @@ -from typing import List +from __future__ import annotations -def radix_sort(list_of_ints: List[int]) -> List[int]: +def radix_sort(list_of_ints: list[int]) -> list[int]: """ radix_sort(range(15)) == sorted(range(15)) True diff --git a/sorts/recursive_insertion_sort.py b/sorts/recursive_insertion_sort.py index 5b14c2a6c139..66dd08157df1 100644 --- a/sorts/recursive_insertion_sort.py +++ b/sorts/recursive_insertion_sort.py @@ -2,10 +2,10 @@ A recursive implementation of the insertion sort algorithm """ -from typing import List +from __future__ import annotations -def rec_insertion_sort(collection: List, n: int): +def rec_insertion_sort(collection: list, n: int): """ Given a collection of numbers and its length, sorts the collections in ascending order @@ -36,7 +36,7 @@ def rec_insertion_sort(collection: List, n: int): rec_insertion_sort(collection, n - 1) -def insert_next(collection: List, index: int): +def insert_next(collection: list, index: int): """ Inserts the '(index-1)th' element into place diff --git a/traversals/binary_tree_traversals.py b/traversals/binary_tree_traversals.py index 50cdd5af72d3..cb471ba55bac 100644 --- a/traversals/binary_tree_traversals.py +++ b/traversals/binary_tree_traversals.py @@ -3,8 +3,9 @@ """ This is pure Python implementation of tree traversal algorithms """ +from __future__ import annotations + import queue -from typing import List class TreeNode: diff --git a/web_programming/fetch_jobs.py b/web_programming/fetch_jobs.py index 888f41294974..bb2171e1f0ee 100644 --- a/web_programming/fetch_jobs.py +++ b/web_programming/fetch_jobs.py @@ -1,7 +1,9 @@ """ Scraping jobs given job title and location from indeed website """ -from typing import Generator, Tuple +from __future__ import annotations + +from typing import Generator import requests from bs4 import BeautifulSoup @@ -9,7 +11,7 @@ url = "https://www.indeed.co.in/jobs?q=mobile+app+development&l=" -def fetch_jobs(location: str = "mumbai") -> Generator[Tuple[str, str], None, None]: +def fetch_jobs(location: str = "mumbai") -> Generator[tuple[str, str], None, None]: soup = BeautifulSoup(requests.get(url + location).content, "html.parser") # This attribute finds out all the specifics listed in a job for job in soup.find_all("div", attrs={"data-tn-component": "organicJob"}): diff --git a/web_programming/get_imdb_top_250_movies_csv.py b/web_programming/get_imdb_top_250_movies_csv.py index 811c21fb00e4..e54b076ebd94 100644 --- a/web_programming/get_imdb_top_250_movies_csv.py +++ b/web_programming/get_imdb_top_250_movies_csv.py @@ -1,11 +1,12 @@ +from __future__ import annotations + import csv -from typing import Dict import requests from bs4 import BeautifulSoup -def get_imdb_top_250_movies(url: str = "") -> Dict[str, float]: +def get_imdb_top_250_movies(url: str = "") -> dict[str, float]: url = url or "https://www.imdb.com/chart/top/?ref_=nv_mv_250" soup = BeautifulSoup(requests.get(url).text, "html.parser") titles = soup.find_all("td", attrs="titleColumn") From 5f9be0a6131d2e78b697be6304077e9a69932d1e Mon Sep 17 00:00:00 2001 From: spamegg <4255997+spamegg1@users.noreply.github.com> Date: Wed, 23 Sep 2020 22:55:51 +0300 Subject: [PATCH 0786/1071] Add Python type hints and doctests to other/two_sum.py (#2467) * Add Python type hints and doctests to other/two_sum.py #2465 * Update other/two_sum.py Co-authored-by: Christian Clauss * Update other/two_sum.py Co-authored-by: Christian Clauss * Update two_sum.py Co-authored-by: Christian Clauss --- other/two_sum.py | 36 ++++++++++++++++++++++++++---------- 1 file changed, 26 insertions(+), 10 deletions(-) diff --git a/other/two_sum.py b/other/two_sum.py index 8ac7a18ba9a4..5209acbc7e44 100644 --- a/other/two_sum.py +++ b/other/two_sum.py @@ -11,21 +11,37 @@ Because nums[0] + nums[1] = 2 + 7 = 9, return [0, 1]. """ +from __future__ import annotations -def twoSum(nums, target): +def two_sum(nums: list[int], target: int) -> list[int]: """ - :type nums: List[int] - :type target: int - :rtype: List[int] + >>> two_sum([2, 7, 11, 15], 9) + [0, 1] + >>> two_sum([15, 2, 11, 7], 13) + [1, 2] + >>> two_sum([2, 7, 11, 15], 17) + [0, 3] + >>> two_sum([7, 15, 11, 2], 18) + [0, 2] + >>> two_sum([2, 7, 11, 15], 26) + [2, 3] + >>> two_sum([2, 7, 11, 15], 8) + [] + >>> two_sum([3 * i for i in range(10)], 19) + [] """ chk_map = {} for index, val in enumerate(nums): compl = target - val if compl in chk_map: - indices = [chk_map[compl], index] - print(indices) - return [indices] - else: - chk_map[val] = index - return False + return [chk_map[compl], index] + chk_map[val] = index + return [] + + +if __name__ == "__main__": + import doctest + + doctest.testmod() + print(f"{two_sum([2, 7, 11, 15], 9) = }") From 4a3b8d682e2e296761d2fb866418e57fe5ff4b42 Mon Sep 17 00:00:00 2001 From: Vivek Date: Thu, 24 Sep 2020 13:00:22 +0530 Subject: [PATCH 0787/1071] Added binary_xor_operator.py and binary_and_operator.py (#2433) * Added binary_and_operator.py & binary_xor_operator.py * Updated binary_and_operator.py * Updated binary_xor_operator.py * Updated binary_xor_operator.py --- bit_manipulation/binary_and_operator.py | 51 +++++++++++++++++++++++++ bit_manipulation/binary_xor_operator.py | 51 +++++++++++++++++++++++++ 2 files changed, 102 insertions(+) create mode 100644 bit_manipulation/binary_and_operator.py create mode 100644 bit_manipulation/binary_xor_operator.py diff --git a/bit_manipulation/binary_and_operator.py b/bit_manipulation/binary_and_operator.py new file mode 100644 index 000000000000..e5dffe3e31d2 --- /dev/null +++ b/bit_manipulation/binary_and_operator.py @@ -0,0 +1,51 @@ +# https://www.tutorialspoint.com/python3/bitwise_operators_example.htm + +def binary_and(a: int, b: int): + """ + Take in 2 integers, convert them to binary, + return a binary number that is the + result of a binary and operation on the integers provided. + + >>> binary_and(25, 32) + '0b000000' + >>> binary_and(37, 50) + '0b100000' + >>> binary_and(21, 30) + '0b10100' + >>> binary_and(58, 73) + '0b0001000' + >>> binary_and(0, 255) + '0b00000000' + >>> binary_and(256, 256) + '0b100000000' + >>> binary_and(0, -1) + Traceback (most recent call last): + ... + ValueError: the value of both input must be positive + >>> binary_and(0, 1.1) + Traceback (most recent call last): + ... + TypeError: 'float' object cannot be interpreted as an integer + >>> binary_and("0", "1") + Traceback (most recent call last): + ... + TypeError: '<' not supported between instances of 'str' and 'int' + """ + if a < 0 or b < 0: + raise ValueError("the value of both input must be positive") + + a_binary = str(bin(a))[2:] # remove the leading "0b" + b_binary = str(bin(b))[2:] # remove the leading "0b" + + max_len = max(len(a_binary), len(b_binary)) + + return "0b" + "".join( + str(int(char_a == "1" and char_b == "1")) + for char_a, char_b in zip(a_binary.zfill(max_len), b_binary.zfill(max_len)) + ) + + +if __name__ == "__main__": + import doctest + + doctest.testmod() diff --git a/bit_manipulation/binary_xor_operator.py b/bit_manipulation/binary_xor_operator.py new file mode 100644 index 000000000000..32a8f272116e --- /dev/null +++ b/bit_manipulation/binary_xor_operator.py @@ -0,0 +1,51 @@ +# https://www.tutorialspoint.com/python3/bitwise_operators_example.htm + +def binary_xor(a: int, b: int): + """ + Take in 2 integers, convert them to binary, + return a binary number that is the + result of a binary xor operation on the integers provided. + + >>> binary_xor(25, 32) + '0b111001' + >>> binary_xor(37, 50) + '0b010111' + >>> binary_xor(21, 30) + '0b01011' + >>> binary_xor(58, 73) + '0b1110011' + >>> binary_xor(0, 255) + '0b11111111' + >>> binary_xor(256, 256) + '0b000000000' + >>> binary_xor(0, -1) + Traceback (most recent call last): + ... + ValueError: the value of both input must be positive + >>> binary_xor(0, 1.1) + Traceback (most recent call last): + ... + TypeError: 'float' object cannot be interpreted as an integer + >>> binary_xor("0", "1") + Traceback (most recent call last): + ... + TypeError: '<' not supported between instances of 'str' and 'int' + """ + if a < 0 or b < 0: + raise ValueError("the value of both input must be positive") + + a_binary = str(bin(a))[2:] # remove the leading "0b" + b_binary = str(bin(b))[2:] # remove the leading "0b" + + max_len = max(len(a_binary), len(b_binary)) + + return "0b" + "".join( + str(int(char_a != char_b)) + for char_a, char_b in zip(a_binary.zfill(max_len), b_binary.zfill(max_len)) + ) + + +if __name__ == "__main__": + import doctest + + doctest.testmod() From 902fe1c9070fa5b1e29d33e3742e3a871f07170a Mon Sep 17 00:00:00 2001 From: Du Yuanchao Date: Thu, 24 Sep 2020 19:12:52 +0800 Subject: [PATCH 0788/1071] Fixed reverse words algorithm (#2469) * updated reversed words * fixup! Format Python code with psf/black push Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> --- bit_manipulation/binary_and_operator.py | 1 + bit_manipulation/binary_xor_operator.py | 1 + strings/reverse_words.py | 17 +++++++---------- 3 files changed, 9 insertions(+), 10 deletions(-) diff --git a/bit_manipulation/binary_and_operator.py b/bit_manipulation/binary_and_operator.py index e5dffe3e31d2..f1b910f8cc9b 100644 --- a/bit_manipulation/binary_and_operator.py +++ b/bit_manipulation/binary_and_operator.py @@ -1,5 +1,6 @@ # https://www.tutorialspoint.com/python3/bitwise_operators_example.htm + def binary_and(a: int, b: int): """ Take in 2 integers, convert them to binary, diff --git a/bit_manipulation/binary_xor_operator.py b/bit_manipulation/binary_xor_operator.py index 32a8f272116e..0edf2ba6606d 100644 --- a/bit_manipulation/binary_xor_operator.py +++ b/bit_manipulation/binary_xor_operator.py @@ -1,5 +1,6 @@ # https://www.tutorialspoint.com/python3/bitwise_operators_example.htm + def binary_xor(a: int, b: int): """ Take in 2 integers, convert them to binary, diff --git a/strings/reverse_words.py b/strings/reverse_words.py index 8ab060fe1d24..504c1c2089dd 100644 --- a/strings/reverse_words.py +++ b/strings/reverse_words.py @@ -1,18 +1,15 @@ -# Created by sarathkaul on 18/11/19 -# Edited by farnswj1 on 4/4/20 - - def reverse_words(input_str: str) -> str: """ Reverses words in a given string - >>> sentence = "I love Python" - >>> reverse_words(sentence) == " ".join(sentence.split()[::-1]) - True - >>> reverse_words(sentence) + >>> reverse_words("I love Python") 'Python love I' + >>> reverse_words("I Love Python") + 'Python Love I' """ - return " ".join(reversed(input_str.split(" "))) + return " ".join(input_str.split()[::-1]) if __name__ == "__main__": - print(reverse_words("INPUT STRING")) + import doctest + + doctest.testmod() From 3a275caf0122474e42f9ac68e57618607bde3b96 Mon Sep 17 00:00:00 2001 From: Du Yuanchao Date: Thu, 24 Sep 2020 19:14:52 +0800 Subject: [PATCH 0789/1071] Fixed remove duplicate (#2470) * fixed remove duplicate * fixup! Format Python code with psf/black push Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> --- strings/remove_duplicate.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/strings/remove_duplicate.py b/strings/remove_duplicate.py index 6357050ac17d..5ab0e9962752 100644 --- a/strings/remove_duplicate.py +++ b/strings/remove_duplicate.py @@ -1,14 +1,15 @@ -""" Created by sarathkaul on 14/11/19 """ - - def remove_duplicates(sentence: str) -> str: """ - Reomove duplicates from sentence + Remove duplicates from sentence >>> remove_duplicates("Python is great and Java is also great") 'Java Python also and great is' + >>> remove_duplicates("Python is great and Java is also great") + 'Java Python also and great is' """ - return " ".join(sorted(set(sentence.split(" ")))) + return " ".join(sorted(set(sentence.split()))) if __name__ == "__main__": - print(remove_duplicates("INPUT_SENTENCE")) + import doctest + + doctest.testmod() From 08eb1efafe01c89f83914d8792ef0dc849f92360 Mon Sep 17 00:00:00 2001 From: Dhruv Date: Thu, 24 Sep 2020 18:46:55 +0530 Subject: [PATCH 0790/1071] Add solution() for problem 54 of Project Euler (#2472) * Add solution() for problem 54 of Project Euler * Add type hints for solution() function --- project_euler/problem_54/sol1.py | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/project_euler/problem_54/sol1.py b/project_euler/problem_54/sol1.py index d36d3702d7c8..4d75271784de 100644 --- a/project_euler/problem_54/sol1.py +++ b/project_euler/problem_54/sol1.py @@ -42,6 +42,8 @@ """ from __future__ import annotations +import os + class PokerHand(object): """Create an object representing a Poker Hand based on an input of a @@ -356,3 +358,24 @@ def __ge__(self, other): def __hash__(self): return object.__hash__(self) + + +def solution() -> int: + # Solution for problem number 54 from Project Euler + # Input from poker_hands.txt file + answer = 0 + script_dir = os.path.abspath(os.path.dirname(__file__)) + poker_hands = os.path.join(script_dir, "poker_hands.txt") + with open(poker_hands, "r") as file_hand: + for line in file_hand: + player_hand = line[:14].strip() + opponent_hand = line[15:].strip() + player, opponent = PokerHand(player_hand), PokerHand(opponent_hand) + output = player.compare_with(opponent) + if output == "Win": + answer += 1 + return answer + + +if __name__ == "__main__": + solution() From f564c9d7c6fd630d8a465390dab45cc33fa56f8d Mon Sep 17 00:00:00 2001 From: Guillaume Rochedix <33205665+BriseBalloches@users.noreply.github.com> Date: Fri, 25 Sep 2020 09:18:00 +0200 Subject: [PATCH 0791/1071] Wiggle sort (#2419) * wiggle sort : type hint + doctest * fixed function name in docstring * correction --- sorts/wiggle_sort.py | 28 ++++++++++++++++++++-------- 1 file changed, 20 insertions(+), 8 deletions(-) diff --git a/sorts/wiggle_sort.py b/sorts/wiggle_sort.py index 5e5220ffbf05..13bc3ce9606f 100644 --- a/sorts/wiggle_sort.py +++ b/sorts/wiggle_sort.py @@ -9,18 +9,30 @@ """ -def wiggle_sort(nums): - """Perform Wiggle Sort.""" - for i in range(len(nums)): +def wiggle_sort(nums: list) -> list: + """ + Python implementation of wiggle. + Example: + >>> wiggle_sort([0, 5, 3, 2, 2]) + [0, 5, 2, 3, 2] + >>> wiggle_sort([]) + [] + >>> wiggle_sort([-2, -5, -45]) + [-45, -2, -5] + >>> wiggle_sort([-2.1, -5.68, -45.11]) + [-45.11, -2.1, -5.68] + """ + for i, _ in enumerate(nums): if (i % 2 == 1) == (nums[i - 1] > nums[i]): nums[i - 1], nums[i] = nums[i], nums[i - 1] + return nums + if __name__ == "__main__": - print("Enter the array elements:\n") + print("Enter the array elements:") array = list(map(int, input().split())) - print("The unsorted array is:\n") - print(array) - wiggle_sort(array) - print("Array after Wiggle sort:\n") + print("The unsorted array is:") print(array) + print("Array after Wiggle sort:") + print(wiggle_sort(array)) From daa1b4d70f40d823dd86366c6eba15c930a52bf8 Mon Sep 17 00:00:00 2001 From: Kushagra Bansal Date: Fri, 25 Sep 2020 15:22:19 +0530 Subject: [PATCH 0792/1071] Created problem_97 in project euler (#2476) * Create __init__.py * Add files via upload * Update sol1.py * Update sol1.py * Update sol1.py * Update project_euler/problem_97/sol1.py Co-authored-by: Christian Clauss * Update sol1.py * Update project_euler/problem_97/sol1.py Co-authored-by: Christian Clauss Co-authored-by: Christian Clauss --- project_euler/problem_97/__init__.py | 1 + project_euler/problem_97/sol1.py | 46 ++++++++++++++++++++++++++++ 2 files changed, 47 insertions(+) create mode 100644 project_euler/problem_97/__init__.py create mode 100644 project_euler/problem_97/sol1.py diff --git a/project_euler/problem_97/__init__.py b/project_euler/problem_97/__init__.py new file mode 100644 index 000000000000..792d6005489e --- /dev/null +++ b/project_euler/problem_97/__init__.py @@ -0,0 +1 @@ +# diff --git a/project_euler/problem_97/sol1.py b/project_euler/problem_97/sol1.py new file mode 100644 index 000000000000..2e848c09a940 --- /dev/null +++ b/project_euler/problem_97/sol1.py @@ -0,0 +1,46 @@ +""" +The first known prime found to exceed one million digits was discovered in 1999, +and is a Mersenne prime of the form 2**6972593 − 1; it contains exactly 2,098,960 +digits. Subsequently other Mersenne primes, of the form 2**p − 1, have been found +which contain more digits. +However, in 2004 there was found a massive non-Mersenne prime which contains +2,357,207 digits: (28433 * (2 ** 7830457 + 1)). + +Find the last ten digits of this prime number. +""" + + +def solution(n: int = 10) -> str: + """ + Returns the last n digits of NUMBER. + >>> solution() + '8739992577' + >>> solution(8) + '39992577' + >>> solution(1) + '7' + >>> solution(-1) + Traceback (most recent call last): + ... + ValueError: Invalid input + >>> solution(8.3) + Traceback (most recent call last): + ... + ValueError: Invalid input + >>> solution("a") + Traceback (most recent call last): + ... + ValueError: Invalid input + """ + if not isinstance(n, int) or n < 0: + raise ValueError("Invalid input") + MODULUS = 10 ** n + NUMBER = 28433 * (pow(2, 7830457, MODULUS)) + 1 + return str(NUMBER % MODULUS) + + +if __name__ == "__main__": + from doctest import testmod + + testmod() + print(f"{solution(10) = }") From 53c2a24587aaf864ec40d2445d071a8274926c37 Mon Sep 17 00:00:00 2001 From: Abdujabbar Mirkhalikov Date: Fri, 25 Sep 2020 18:16:05 +0500 Subject: [PATCH 0793/1071] added type hints and doctests for minimax algorithm (#2478) * added type hints and doctests for minimax algorithm * Update backtracking/minimax.py Co-authored-by: Christian Clauss * last fix Co-authored-by: Christian Clauss --- backtracking/minimax.py | 59 +++++++++++++++++++++++++++++++++-------- 1 file changed, 48 insertions(+), 11 deletions(-) diff --git a/backtracking/minimax.py b/backtracking/minimax.py index 4cec0e403ddf..056447256395 100644 --- a/backtracking/minimax.py +++ b/backtracking/minimax.py @@ -1,3 +1,4 @@ +from __future__ import annotations import math """ Minimax helps to achieve maximum score in a game by checking all possible moves @@ -9,26 +10,62 @@ """ -def minimax(Depth, nodeIndex, isMax, scores, height): +def minimax(depth: int, node_index: int, is_max: bool, + scores: list[int], height: float) -> int: + """ + >>> import math + >>> scores = [90, 23, 6, 33, 21, 65, 123, 34423] + >>> height = math.log(len(scores), 2) + >>> minimax(0, 0, True, scores, height) + 65 + >>> minimax(-1, 0, True, scores, height) + Traceback (most recent call last): + ... + ValueError: Depth cannot be less than 0 + >>> minimax(0, 0, True, [], 2) + Traceback (most recent call last): + ... + ValueError: Scores cannot be empty + >>> scores = [3, 5, 2, 9, 12, 5, 23, 23] + >>> height = math.log(len(scores), 2) + >>> minimax(0, 0, True, scores, height) + 12 + >>> minimax('1', 2, True, [], 2 ) + Traceback (most recent call last): + ... + TypeError: '<' not supported between instances of 'str' and 'int' + """ - if Depth == height: - return scores[nodeIndex] + if depth < 0: + raise ValueError("Depth cannot be less than 0") - if isMax: + if len(scores) == 0: + raise ValueError("Scores cannot be empty") + + if depth == height: + return scores[node_index] + + if is_max: return max( - minimax(Depth + 1, nodeIndex * 2, False, scores, height), - minimax(Depth + 1, nodeIndex * 2 + 1, False, scores, height), + minimax(depth + 1, node_index * 2, False, scores, height), + minimax(depth + 1, node_index * 2 + 1, False, scores, height), ) + return min( - minimax(Depth + 1, nodeIndex * 2, True, scores, height), - minimax(Depth + 1, nodeIndex * 2 + 1, True, scores, height), + minimax(depth + 1, node_index * 2, True, scores, height), + minimax(depth + 1, node_index * 2 + 1, True, scores, height), ) -if __name__ == "__main__": - +def main(): scores = [90, 23, 6, 33, 21, 65, 123, 34423] height = math.log(len(scores), 2) - print("Optimal value : ", end="") print(minimax(0, 0, True, scores, height)) + + +if __name__ == "__main__": + import doctest + + doctest.testmod() + main() From a196a36514aca9fa4fd231970a4bf750b11a4983 Mon Sep 17 00:00:00 2001 From: Du Yuanchao Date: Fri, 25 Sep 2020 21:20:09 +0800 Subject: [PATCH 0794/1071] Fixed bugs (#2474) * fixed bug * fixup! Format Python code with psf/black push Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> --- strings/check_anagrams.py | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/strings/check_anagrams.py b/strings/check_anagrams.py index 7cc1e2978db9..3083000cbb5d 100644 --- a/strings/check_anagrams.py +++ b/strings/check_anagrams.py @@ -1,4 +1,9 @@ -def check_anagrams(a: str, b: str) -> bool: +""" +wiki: https://en.wikipedia.org/wiki/Anagram +""" + + +def check_anagrams(first_str: str, second_str: str) -> bool: """ Two strings are anagrams if they are made of the same letters arranged differently (ignoring the case). @@ -6,13 +11,21 @@ def check_anagrams(a: str, b: str) -> bool: True >>> check_anagrams('This is a string', 'Is this a string') True + >>> check_anagrams('This is a string', 'Is this a string') + True >>> check_anagrams('There', 'Their') False """ - return sorted(a.lower()) == sorted(b.lower()) + return ( + "".join(sorted(first_str.lower())).strip() + == "".join(sorted(second_str.lower())).strip() + ) if __name__ == "__main__": + from doctest import testmod + + testmod() input_A = input("Enter the first string ").strip() input_B = input("Enter the second string ").strip() From 18f1dcd48a22d3c84a8839d2198ed35e68d3641a Mon Sep 17 00:00:00 2001 From: Du Yuanchao Date: Fri, 25 Sep 2020 22:09:29 +0800 Subject: [PATCH 0795/1071] Updated singly_linked_list (#2477) * Updated singly_linked_list * fixup! Format Python code with psf/black push * undo __repr__ * updating DIRECTORY.md * UNTESTED CHANGES: Add an .__iter__() method. This will break tests, etc. * fixup! Format Python code with psf/black push * len(tuple(iter(self))) * fixed __repr__() * Update data_structures/linked_list/singly_linked_list.py Co-authored-by: Christian Clauss * Update data_structures/linked_list/singly_linked_list.py Co-authored-by: Christian Clauss * Update data_structures/linked_list/singly_linked_list.py Co-authored-by: Christian Clauss * Update data_structures/linked_list/singly_linked_list.py Co-authored-by: Christian Clauss * Update data_structures/linked_list/singly_linked_list.py Co-authored-by: Christian Clauss * Update data_structures/linked_list/singly_linked_list.py Co-authored-by: Christian Clauss * Update data_structures/linked_list/singly_linked_list.py Co-authored-by: Christian Clauss Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> Co-authored-by: Christian Clauss --- DIRECTORY.md | 2 + .../linked_list/singly_linked_list.py | 249 ++++++++++-------- 2 files changed, 143 insertions(+), 108 deletions(-) diff --git a/DIRECTORY.md b/DIRECTORY.md index 03044e01084b..f08c31794b1e 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -25,7 +25,9 @@ * [Sum Of Subsets](https://github.com/TheAlgorithms/Python/blob/master/backtracking/sum_of_subsets.py) ## Bit Manipulation + * [Binary And Operator](https://github.com/TheAlgorithms/Python/blob/master/bit_manipulation/binary_and_operator.py) * [Binary Or Operator](https://github.com/TheAlgorithms/Python/blob/master/bit_manipulation/binary_or_operator.py) + * [Binary Xor Operator](https://github.com/TheAlgorithms/Python/blob/master/bit_manipulation/binary_xor_operator.py) ## Blockchain * [Chinese Remainder Theorem](https://github.com/TheAlgorithms/Python/blob/master/blockchain/chinese_remainder_theorem.py) diff --git a/data_structures/linked_list/singly_linked_list.py b/data_structures/linked_list/singly_linked_list.py index 1f3e03a31b7e..39b14c520521 100644 --- a/data_structures/linked_list/singly_linked_list.py +++ b/data_structures/linked_list/singly_linked_list.py @@ -9,50 +9,110 @@ def __repr__(self): class LinkedList: def __init__(self): - self.head = None # initialize head to None + self.head = None + + def __iter__(self): + node = self.head + while node: + yield node.data + node = node.next + + def __len__(self) -> int: + """ + Return length of linked list i.e. number of nodes + >>> linked_list = LinkedList() + >>> len(linked_list) + 0 + >>> linked_list.insert_tail("head") + >>> len(linked_list) + 1 + >>> linked_list.insert_head("head") + >>> len(linked_list) + 2 + >>> _ = linked_list.delete_tail() + >>> len(linked_list) + 1 + >>> _ = linked_list.delete_head() + >>> len(linked_list) + 0 + """ + return len(tuple(iter(self))) + + def __repr__(self): + """ + String representation/visualization of a Linked Lists + """ + return "->".join([str(item) for item in self]) + + def __getitem__(self, index): + """ + Indexing Support. Used to get a node at particular position + """ + if index < 0: + raise ValueError("Negative indexes are not yet supported") + for i, node in enumerate(self): + if i == index: + return node.data + + # Used to change the data of a particular node + def __setitem__(self, index, data): + current = self.head + # If list is empty + if current is None: + raise IndexError("The Linked List is empty") + for i in range(index): + if current.next is None: + raise IndexError("list index out of range") + current = current.next + current.data = data def insert_tail(self, data) -> None: + self.insert_nth(len(self), data) + + def insert_head(self, data) -> None: + self.insert_nth(0, data) + + def insert_nth(self, index: int, data) -> None: + if not 0 <= index <= len(self): + raise IndexError("list index out of range") + new_node = Node(data) if self.head is None: - self.insert_head(data) # if this is first node, call insert_head + self.head = new_node + elif index == 0: + new_node.next = self.head # link new_node to head + self.head = new_node else: temp = self.head - while temp.next: # traverse to last node + for _ in range(index - 1): temp = temp.next - temp.next = Node(data) # create node & link to tail - - def insert_head(self, data) -> None: - new_node = Node(data) # create a new node - if self.head: - new_node.next = self.head # link new_node to head - self.head = new_node # make NewNode as head + new_node.next = temp.next + temp.next = new_node def print_list(self) -> None: # print every node data - temp = self.head - while temp: - print(temp.data) - temp = temp.next - - def delete_head(self): # delete from head - temp = self.head - if self.head: - self.head = self.head.next - temp.next = None - return temp + print(self) + + def delete_head(self): + return self.delete_nth(0) def delete_tail(self): # delete from tail - temp = self.head - if self.head: - if self.head.next is None: # if head is the only Node in the Linked List - self.head = None - else: - while temp.next.next: # find the 2nd last element - temp = temp.next - # (2nd last element).next = None and temp = last element - temp.next, temp = None, temp.next - return temp + return self.delete_nth(len(self) - 1) + + def delete_nth(self, index: int = 0): + if not 0 <= index <= len(self) - 1: # test if index is valid + raise IndexError("list index out of range") + delete_node = self.head # default first node + if index == 0: + self.head = self.head.next + else: + temp = self.head + for _ in range(index - 1): + temp = temp.next + delete_node = temp.next + temp.next = temp.next.next + return delete_node.data def is_empty(self) -> bool: - return self.head is None # return True if head is none + return self.head is None def reverse(self): prev = None @@ -70,101 +130,74 @@ def reverse(self): # Return prev in order to put the head at the end self.head = prev - def __repr__(self): # String representation/visualization of a Linked Lists - current = self.head - string_repr = "" - while current: - string_repr += f"{current} --> " - current = current.next - # END represents end of the LinkedList - return string_repr + "END" - # Indexing Support. Used to get a node at particular position - def __getitem__(self, index): - current = self.head +def test_singly_linked_list() -> None: + """ + >>> test_singly_linked_list() + """ + linked_list = LinkedList() + assert linked_list.is_empty() is True + assert str(linked_list) == "" - # If LinkedList is empty - if current is None: - raise IndexError("The Linked List is empty") + try: + linked_list.delete_head() + assert False # This should not happen. + except IndexError: + assert True # This should happen. - # Move Forward 'index' times - for _ in range(index): - # If the LinkedList ends before reaching specified node - if current.next is None: - raise IndexError("Index out of range.") - current = current.next - return current + try: + linked_list.delete_tail() + assert False # This should not happen. + except IndexError: + assert True # This should happen. - # Used to change the data of a particular node - def __setitem__(self, index, data): - current = self.head - # If list is empty - if current is None: - raise IndexError("The Linked List is empty") - for i in range(index): - if current.next is None: - raise IndexError("Index out of range.") - current = current.next - current.data = data + for i in range(10): + assert len(linked_list) == i + linked_list.insert_nth(i, i + 1) + assert str(linked_list) == "->".join(str(i) for i in range(1, 11)) - def __len__(self): - """ - Return length of linked list i.e. number of nodes - >>> linked_list = LinkedList() - >>> len(linked_list) - 0 - >>> linked_list.insert_tail("head") - >>> len(linked_list) - 1 - >>> linked_list.insert_head("head") - >>> len(linked_list) - 2 - >>> _ = linked_list.delete_tail() - >>> len(linked_list) - 1 - >>> _ = linked_list.delete_head() - >>> len(linked_list) - 0 - """ - if not self.head: - return 0 + linked_list.insert_head(0) + linked_list.insert_tail(11) + assert str(linked_list) == "->".join(str(i) for i in range(0, 12)) - count = 0 - cur_node = self.head - while cur_node.next: - count += 1 - cur_node = cur_node.next - return count + 1 + assert linked_list.delete_head() == 0 + assert linked_list.delete_nth(9) == 10 + assert linked_list.delete_tail() == 11 + assert str(linked_list) == "->".join(str(i) for i in range(1, 10)) def main(): - A = LinkedList() - A.insert_head(input("Inserting 1st at head ").strip()) - A.insert_head(input("Inserting 2nd at head ").strip()) + from doctest import testmod + + testmod() + + linked_list = LinkedList() + linked_list.insert_head(input("Inserting 1st at head ").strip()) + linked_list.insert_head(input("Inserting 2nd at head ").strip()) print("\nPrint list:") - A.print_list() - A.insert_tail(input("\nInserting 1st at tail ").strip()) - A.insert_tail(input("Inserting 2nd at tail ").strip()) + linked_list.print_list() + linked_list.insert_tail(input("\nInserting 1st at tail ").strip()) + linked_list.insert_tail(input("Inserting 2nd at tail ").strip()) print("\nPrint list:") - A.print_list() + linked_list.print_list() print("\nDelete head") - A.delete_head() + linked_list.delete_head() print("Delete tail") - A.delete_tail() + linked_list.delete_tail() print("\nPrint list:") - A.print_list() + linked_list.print_list() print("\nReverse linked list") - A.reverse() + linked_list.reverse() print("\nPrint list:") - A.print_list() + linked_list.print_list() print("\nString representation of linked list:") - print(A) + print(linked_list) print("\nReading/changing Node data using indexing:") - print(f"Element at Position 1: {A[1]}") - A[1] = input("Enter New Value: ").strip() + print(f"Element at Position 1: {linked_list[1]}") + linked_list[1] = input("Enter New Value: ").strip() print("New list:") - print(A) - print(f"length of A is : {len(A)}") + print(linked_list) + print(f"length of linked_list is : {len(linked_list)}") if __name__ == "__main__": From b81fcef66bc4dc6fae2594998404d0cac8504b1c Mon Sep 17 00:00:00 2001 From: Du Yuanchao Date: Sat, 26 Sep 2020 00:08:57 +0800 Subject: [PATCH 0796/1071] Fixed linked list bug (#2481) * * fixed __getitem__() function * add test * * updated doctests * updated __setitem__() func * fixup! Format Python code with psf/black push Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> --- backtracking/minimax.py | 6 ++- .../linked_list/singly_linked_list.py | 52 ++++++++++++++++--- 2 files changed, 48 insertions(+), 10 deletions(-) diff --git a/backtracking/minimax.py b/backtracking/minimax.py index 056447256395..91188090c899 100644 --- a/backtracking/minimax.py +++ b/backtracking/minimax.py @@ -1,4 +1,5 @@ from __future__ import annotations + import math """ Minimax helps to achieve maximum score in a game by checking all possible moves @@ -10,8 +11,9 @@ """ -def minimax(depth: int, node_index: int, is_max: bool, - scores: list[int], height: float) -> int: +def minimax( + depth: int, node_index: int, is_max: bool, scores: list[int], height: float +) -> int: """ >>> import math >>> scores = [90, 23, 6, 33, 21, 65, 123, 34423] diff --git a/data_structures/linked_list/singly_linked_list.py b/data_structures/linked_list/singly_linked_list.py index 39b14c520521..e45a210a1785 100644 --- a/data_structures/linked_list/singly_linked_list.py +++ b/data_structures/linked_list/singly_linked_list.py @@ -47,22 +47,51 @@ def __repr__(self): def __getitem__(self, index): """ Indexing Support. Used to get a node at particular position + >>> linked_list = LinkedList() + >>> for i in range(0, 10): + ... linked_list.insert_nth(i, i) + >>> all(str(linked_list[i]) == str(i) for i in range(0, 10)) + True + >>> linked_list[-10] + Traceback (most recent call last): + ... + ValueError: list index out of range. + >>> linked_list[len(linked_list)] + Traceback (most recent call last): + ... + ValueError: list index out of range. """ - if index < 0: - raise ValueError("Negative indexes are not yet supported") + if not 0 <= index < len(self): + raise ValueError("list index out of range.") for i, node in enumerate(self): if i == index: - return node.data + return node # Used to change the data of a particular node def __setitem__(self, index, data): + """ + >>> linked_list = LinkedList() + >>> for i in range(0, 10): + ... linked_list.insert_nth(i, i) + >>> linked_list[0] = 666 + >>> linked_list[0] + 666 + >>> linked_list[5] = -666 + >>> linked_list[5] + -666 + >>> linked_list[-10] = 666 + Traceback (most recent call last): + ... + ValueError: list index out of range. + >>> linked_list[len(linked_list)] = 666 + Traceback (most recent call last): + ... + ValueError: list index out of range. + """ + if not 0 <= index < len(self): + raise ValueError("list index out of range.") current = self.head - # If list is empty - if current is None: - raise IndexError("The Linked List is empty") for i in range(index): - if current.next is None: - raise IndexError("list index out of range") current = current.next current.data = data @@ -163,8 +192,15 @@ def test_singly_linked_list() -> None: assert linked_list.delete_head() == 0 assert linked_list.delete_nth(9) == 10 assert linked_list.delete_tail() == 11 + assert len(linked_list) == 9 assert str(linked_list) == "->".join(str(i) for i in range(1, 10)) + assert all(linked_list[i] == i + 1 for i in range(0, 9)) is True + + for i in range(0, 9): + linked_list[i] = -i + assert all(linked_list[i] == -i for i in range(0, 9)) is True + def main(): from doctest import testmod From e92cd9d5c5099cdcf8c80e7a7df2c1ff3190eacc Mon Sep 17 00:00:00 2001 From: Thomas Voss <57815710+Mango0x45@users.noreply.github.com> Date: Fri, 25 Sep 2020 19:03:15 +0200 Subject: [PATCH 0797/1071] Update morse_code_implementation.py (#2386) * Update morse_code_implementation.py Added more characters to MORSE_CODE_DICT for a more complete dictionary. Split words with "/" instead of a space as is standard. Fixed bug when encrypting a message with a comma. * Fixed comment typo --- ciphers/morse_code_implementation.py | 50 +++++++++++----------------- 1 file changed, 20 insertions(+), 30 deletions(-) diff --git a/ciphers/morse_code_implementation.py b/ciphers/morse_code_implementation.py index 6df4632af4cb..04af8fcf6fdf 100644 --- a/ciphers/morse_code_implementation.py +++ b/ciphers/morse_code_implementation.py @@ -1,6 +1,5 @@ # Python program to implement Morse Code Translator - # Dictionary representing the morse code chart MORSE_CODE_DICT = { "A": ".-", @@ -39,13 +38,22 @@ "8": "---..", "9": "----.", "0": "-----", - ", ": "--..--", + "&": ".-...", + "@": ".--.-.", + ":": "---...", + ",": "--..--", ".": ".-.-.-", + "'": ".----.", + '"': ".-..-.", "?": "..--..", "/": "-..-.", + "=": "-...-", + "+": ".-.-.", "-": "-....-", "(": "-.--.", ")": "-.--.-", + # Exclamation mark is not in ITU-R recommendation + "!": "-.-.--", } @@ -53,42 +61,24 @@ def encrypt(message): cipher = "" for letter in message: if letter != " ": - cipher += MORSE_CODE_DICT[letter] + " " else: + cipher += "/ " - cipher += " " - - return cipher + # Remove trailing space added on line 64 + return cipher[:-1] def decrypt(message): - - message += " " - decipher = "" - citext = "" - for letter in message: - - if letter != " ": - - i = 0 - - citext += letter - + letters = message.split(" ") + for letter in letters: + if letter != "/": + decipher += list(MORSE_CODE_DICT.keys())[ + list(MORSE_CODE_DICT.values()).index(letter) + ] else: - - i += 1 - - if i == 2: - - decipher += " " - else: - - decipher += list(MORSE_CODE_DICT.keys())[ - list(MORSE_CODE_DICT.values()).index(citext) - ] - citext = "" + decipher += " " return decipher From 72fe611462a517c6ebce5def75233027a0ffc4a8 Mon Sep 17 00:00:00 2001 From: Du Yuanchao Date: Sat, 26 Sep 2020 01:58:40 +0800 Subject: [PATCH 0798/1071] Updated lower and upper (#2468) * update lower and upper * fixup! Format Python code with psf/black push Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> --- strings/lower.py | 6 +----- strings/upper.py | 5 +---- 2 files changed, 2 insertions(+), 9 deletions(-) diff --git a/strings/lower.py b/strings/lower.py index a1bad44c7403..b7abe9fc957d 100644 --- a/strings/lower.py +++ b/strings/lower.py @@ -1,5 +1,4 @@ def lower(word: str) -> str: - """ Will convert the entire string to lowecase letters @@ -9,7 +8,6 @@ def lower(word: str) -> str: 'hellzo' >>> lower("WHAT") 'what' - >>> lower("wh[]32") 'wh[]32' >>> lower("whAT") @@ -19,9 +17,7 @@ def lower(word: str) -> str: # converting to ascii value int value and checking to see if char is a capital # letter if it is a capital letter it is getting shift by 32 which makes it a lower # case letter - return "".join( - chr(ord(char) + 32) if 65 <= ord(char) <= 90 else char for char in word - ) + return "".join(chr(ord(char) + 32) if "A" <= char <= "Z" else char for char in word) if __name__ == "__main__": diff --git a/strings/upper.py b/strings/upper.py index 2c264c641b12..411802a2a22f 100644 --- a/strings/upper.py +++ b/strings/upper.py @@ -8,7 +8,6 @@ def upper(word: str) -> str: 'HELLO' >>> upper("WHAT") 'WHAT' - >>> upper("wh[]32") 'WH[]32' """ @@ -16,9 +15,7 @@ def upper(word: str) -> str: # converting to ascii value int value and checking to see if char is a lower letter # if it is a capital letter it is getting shift by 32 which makes it a capital case # letter - return "".join( - chr(ord(char) - 32) if 97 <= ord(char) <= 122 else char for char in word - ) + return "".join(chr(ord(char) - 32) if "a" <= char <= "z" else char for char in word) if __name__ == "__main__": From db0db01d87868cb1d15628d772b13ed56df5adce Mon Sep 17 00:00:00 2001 From: Christian Clauss Date: Sat, 26 Sep 2020 16:54:41 +0200 Subject: [PATCH 0799/1071] Use self-documenting option instead of cryptic option (#2487) * Use self-documenting option instead of cryptic option @mateuszz0000 Your review, please. Pretend that you do not know what `-L` stands for. How many keystrokes and clicks does it take to confirm the purpose of this option? * updating DIRECTORY.md Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> --- .github/workflows/codespell.yml | 2 +- DIRECTORY.md | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/codespell.yml b/.github/workflows/codespell.yml index 3479b0218a69..02de2b6e89f2 100644 --- a/.github/workflows/codespell.yml +++ b/.github/workflows/codespell.yml @@ -11,7 +11,7 @@ jobs: - run: pip install codespell flake8 - run: | SKIP="./.*,./other/dictionary.txt,./other/words,./project_euler/problem_22/p022_names.txt" - codespell -L ans,fo,hist,iff,secant,tim --skip=$SKIP --quiet-level=2 + codespell --ignore-words-list=ans,fo,hist,iff,secant,som,tim --skip=$SKIP --quiet-level=2 - name: Codespell comment if: ${{ failure() }} uses: plettich/python_codespell_action@master diff --git a/DIRECTORY.md b/DIRECTORY.md index f08c31794b1e..3227711c7853 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -646,6 +646,8 @@ * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_67/sol1.py) * Problem 76 * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_76/sol1.py) + * Problem 97 + * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_97/sol1.py) * Problem 99 * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_99/sol1.py) From 7f48bb8c95641283e0d3dd8cdb633e8f67c772bf Mon Sep 17 00:00:00 2001 From: Du Yuanchao Date: Sat, 26 Sep 2020 22:57:09 +0800 Subject: [PATCH 0800/1071] Updated circular_linked_list (#2483) * Updated circular_linked_list * fixup! Format Python code with psf/black push * Update data_structures/linked_list/circular_linked_list.py Co-authored-by: Christian Clauss * updating DIRECTORY.md * delete print_list() * test is_empty() * test is_empty return False * fixup! Format Python code with psf/black push * fixed indentation * fixup! Format Python code with psf/black push Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> Co-authored-by: Christian Clauss --- .../linked_list/circular_linked_list.py | 290 +++++++----------- 1 file changed, 113 insertions(+), 177 deletions(-) diff --git a/data_structures/linked_list/circular_linked_list.py b/data_structures/linked_list/circular_linked_list.py index 19d6ee6c6cb3..f67c1e8f2cf7 100644 --- a/data_structures/linked_list/circular_linked_list.py +++ b/data_structures/linked_list/circular_linked_list.py @@ -2,201 +2,137 @@ class Node: - """ - Class to represent a single node. - - Each node has following attributes - * data - * next_ptr - """ - def __init__(self, data: Any): self.data = data - self.next_ptr = None + self.next = None class CircularLinkedList: - """ - Class to represent the CircularLinkedList. - - CircularLinkedList has following attributes. - * head - * length - """ - def __init__(self): self.head = None - self.length = 0 + self.tail = None - def __len__(self) -> int: - """ - Dunder method to return length of the CircularLinkedList - >>> cll = CircularLinkedList() - >>> len(cll) - 0 - >>> cll.append(1) - >>> len(cll) - 1 - >>> cll.prepend(0) - >>> len(cll) - 2 - >>> cll.delete_front() - >>> len(cll) - 1 - >>> cll.delete_rear() - >>> len(cll) - 0 - """ - return self.length - - def __str__(self) -> str: - """ - Dunder method to represent the string representation of the CircularLinkedList - >>> cll = CircularLinkedList() - >>> print(cll) - Empty linked list - >>> cll.append(1) - >>> cll.append(2) - >>> print(cll) - => - """ - current_node = self.head - if not current_node: - return "Empty linked list" - - results = [current_node.data] - current_node = current_node.next_ptr - - while current_node != self.head: - results.append(current_node.data) - current_node = current_node.next_ptr - - return " => ".join(f"" for result in results) - - def append(self, data: Any) -> None: - """ - Adds a node with given data to the end of the CircularLinkedList - >>> cll = CircularLinkedList() - >>> cll.append(1) - >>> print(f"{len(cll)}: {cll}") - 1: - >>> cll.append(2) - >>> print(f"{len(cll)}: {cll}") - 2: => - """ - current_node = self.head - - new_node = Node(data) - new_node.next_ptr = new_node + def __iter__(self): + node = self.head + while self.head: + yield node.data + node = node.next + if node == self.head: + break - if current_node: - while current_node.next_ptr != self.head: - current_node = current_node.next_ptr + def __len__(self) -> int: + return len(tuple(iter(self))) - current_node.next_ptr = new_node - new_node.next_ptr = self.head - else: - self.head = new_node + def __repr__(self): + return "->".join(str(item) for item in iter(self)) - self.length += 1 + def insert_tail(self, data: Any) -> None: + self.insert_nth(len(self), data) - def prepend(self, data: Any) -> None: - """ - Adds a node with given data to the front of the CircularLinkedList - >>> cll = CircularLinkedList() - >>> cll.prepend(1) - >>> cll.prepend(2) - >>> print(f"{len(cll)}: {cll}") - 2: => - """ - current_node = self.head + def insert_head(self, data: Any) -> None: + self.insert_nth(0, data) + def insert_nth(self, index: int, data: Any) -> None: + if index < 0 or index > len(self): + raise IndexError("list index out of range.") new_node = Node(data) - new_node.next_ptr = new_node - - if current_node: - while current_node.next_ptr != self.head: - current_node = current_node.next_ptr - - current_node.next_ptr = new_node - new_node.next_ptr = self.head - - self.head = new_node - self.length += 1 - - def delete_front(self) -> None: - """ - Removes the 1st node from the CircularLinkedList - >>> cll = CircularLinkedList() - >>> cll.delete_front() - Traceback (most recent call last): - ... - IndexError: Deleting from an empty list - >>> cll.append(1) - >>> cll.append(2) - >>> print(f"{len(cll)}: {cll}") - 2: => - >>> cll.delete_front() - >>> print(f"{len(cll)}: {cll}") - 1: - >>> cll.delete_front() - >>> print(f"{len(cll)}: {cll}") - 0: Empty linked list - """ - if not self.head: - raise IndexError("Deleting from an empty list") - - current_node = self.head - - if current_node.next_ptr == current_node: - self.head = None + if self.head is None: + new_node.next = new_node # first node points itself + self.tail = self.head = new_node + elif index == 0: # insert at head + new_node.next = self.head + self.head = self.tail.next = new_node else: - while current_node.next_ptr != self.head: - current_node = current_node.next_ptr - - current_node.next_ptr = self.head.next_ptr - self.head = self.head.next_ptr - - self.length -= 1 - if not self.head: - assert self.length == 0 - - def delete_rear(self) -> None: - """ - Removes the last node from the CircularLinkedList - >>> cll = CircularLinkedList() - >>> cll.delete_rear() - Traceback (most recent call last): - ... - IndexError: Deleting from an empty list - >>> cll.append(1) - >>> cll.append(2) - >>> print(f"{len(cll)}: {cll}") - 2: => - >>> cll.delete_rear() - >>> print(f"{len(cll)}: {cll}") - 1: - >>> cll.delete_rear() - >>> print(f"{len(cll)}: {cll}") - 0: Empty linked list - """ - if not self.head: - raise IndexError("Deleting from an empty list") - - temp_node, current_node = self.head, self.head - - if current_node.next_ptr == current_node: - self.head = None + temp = self.head + for _ in range(index - 1): + temp = temp.next + new_node.next = temp.next + temp.next = new_node + if index == len(self) - 1: # insert at tail + self.tail = new_node + + def delete_front(self): + return self.delete_nth(0) + + def delete_tail(self) -> None: + return self.delete_nth(len(self) - 1) + + def delete_nth(self, index: int = 0): + if not 0 <= index < len(self): + raise IndexError("list index out of range.") + delete_node = self.head + if self.head == self.tail: # just one node + self.head = self.tail = None + elif index == 0: # delete head node + self.tail.next = self.tail.next.next + self.head = self.head.next else: - while current_node.next_ptr != self.head: - temp_node = current_node - current_node = current_node.next_ptr + temp = self.head + for _ in range(index - 1): + temp = temp.next + delete_node = temp.next + temp.next = temp.next.next + if index == len(self) - 1: # delete at tail + self.tail = temp + return delete_node.data + + def is_empty(self): + return len(self) == 0 - temp_node.next_ptr = current_node.next_ptr - self.length -= 1 - if not self.head: - assert self.length == 0 +def test_circular_linked_list() -> None: + """ + >>> test_circular_linked_list() + """ + circular_linked_list = CircularLinkedList() + assert len(circular_linked_list) == 0 + assert circular_linked_list.is_empty() is True + assert str(circular_linked_list) == "" + + try: + circular_linked_list.delete_front() + assert False # This should not happen + except IndexError: + assert True # This should happen + + try: + circular_linked_list.delete_tail() + assert False # This should not happen + except IndexError: + assert True # This should happen + + try: + circular_linked_list.delete_nth(-1) + assert False + except IndexError: + assert True + + try: + circular_linked_list.delete_nth(0) + assert False + except IndexError: + assert True + + assert circular_linked_list.is_empty() is True + for i in range(5): + assert len(circular_linked_list) == i + circular_linked_list.insert_nth(i, i + 1) + assert str(circular_linked_list) == "->".join(str(i) for i in range(1, 6)) + + circular_linked_list.insert_tail(6) + assert str(circular_linked_list) == "->".join(str(i) for i in range(1, 7)) + circular_linked_list.insert_head(0) + assert str(circular_linked_list) == "->".join(str(i) for i in range(0, 7)) + + assert circular_linked_list.delete_front() == 0 + assert circular_linked_list.delete_tail() == 6 + assert str(circular_linked_list) == "->".join(str(i) for i in range(1, 6)) + assert circular_linked_list.delete_nth(2) == 3 + + circular_linked_list.insert_nth(2, 3) + assert str(circular_linked_list) == "->".join(str(i) for i in range(1, 6)) + + assert circular_linked_list.is_empty() is False if __name__ == "__main__": From 7446e695716eaf0f75f51a1c4805f8d8d698e653 Mon Sep 17 00:00:00 2001 From: Abdoulaye Balde <51192943+abdoulayegk@users.noreply.github.com> Date: Sat, 26 Sep 2020 14:58:29 +0000 Subject: [PATCH 0801/1071] Gradient Boosting Regressor (#2298) * Stock market prediction using greadient boosting * To reverse a string using stack * To reverse string using stack * Predict Stock Prices Python & Machine Learning * Gradient boosting regressor on boston dataset * Gradient boosting regressor implementation * Gradient boosting regressor * Gradient boosting regressor * Gradient boosting regressor * Removing files * GradientBoostingRegressor example * Demo Gradient Boosting * Demo Gradient boosting * demo of gradient boosting * gradient boosting demo * Fix spelling mistake * Fix formatting Co-authored-by: John Law --- .../gradient_boosting_regressor.py | 70 +++++++++++++++++++ 1 file changed, 70 insertions(+) create mode 100644 machine_learning/gradient_boosting_regressor.py diff --git a/machine_learning/gradient_boosting_regressor.py b/machine_learning/gradient_boosting_regressor.py new file mode 100644 index 000000000000..045aa056ec2f --- /dev/null +++ b/machine_learning/gradient_boosting_regressor.py @@ -0,0 +1,70 @@ +"""Implementation of GradientBoostingRegressor in sklearn using the + boston dataset which is very popular for regression problem to + predict house price. +""" + +import pandas as pd +import matplotlib.pyplot as plt +from sklearn.datasets import load_boston +from sklearn.metrics import mean_squared_error, r2_score +from sklearn.ensemble import GradientBoostingRegressor +from sklearn.model_selection import train_test_split + + +def main(): + + # loading the dataset from the sklearn + df = load_boston() + print(df.keys()) + # now let construct a data frame + df_boston = pd.DataFrame(df.data, columns=df.feature_names) + # let add the target to the dataframe + df_boston["Price"] = df.target + # print the first five rows using the head function + print(df_boston.head()) + # Summary statistics + print(df_boston.describe().T) + # Feature selection + + X = df_boston.iloc[:, :-1] + y = df_boston.iloc[:, -1] # target variable + # split the data with 75% train and 25% test sets. + X_train, X_test, y_train, y_test = train_test_split( + X, y, random_state=0, test_size=0.25 + ) + + model = GradientBoostingRegressor( + n_estimators=500, max_depth=5, min_samples_split=4, learning_rate=0.01 + ) + # training the model + model.fit(X_train, y_train) + # to see how good the model fit the data + training_score = model.score(X_train, y_train).round(3) + test_score = model.score(X_test, y_test).round(3) + print("Training score of GradientBoosting is :", training_score) + print( + "The test score of GradientBoosting is :", + test_score + ) + # Let us evaluation the model by finding the errors + y_pred = model.predict(X_test) + + # The mean squared error + print("Mean squared error: %.2f" % mean_squared_error(y_test, y_pred)) + # Explained variance score: 1 is perfect prediction + print("Test Variance score: %.2f" % r2_score(y_test, y_pred)) + + # So let's run the model against the test data + fig, ax = plt.subplots() + ax.scatter(y_test, y_pred, edgecolors=(0, 0, 0)) + ax.plot([y_test.min(), y_test.max()], + [y_test.min(), y_test.max()], "k--", lw=4) + ax.set_xlabel("Actual") + ax.set_ylabel("Predicted") + ax.set_title("Truth vs Predicted") + # this show function will display the plotting + plt.show() + + +if __name__ == "__main__": + main() From 8904af98a1e07a1f5b177c2d9f8f042c265e60b1 Mon Sep 17 00:00:00 2001 From: Du Yuanchao Date: Sat, 26 Sep 2020 22:58:59 +0800 Subject: [PATCH 0802/1071] Optimization for pangram string (#2473) * optimization for pangram string * fixup! Format Python code with psf/black push * Update strings/check_pangram.py Co-authored-by: Christian Clauss * updating DIRECTORY.md * Update strings/check_pangram.py Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> Co-authored-by: Christian Clauss --- strings/check_pangram.py | 52 ++++++++++++++++++++++++++++++++++++---- 1 file changed, 47 insertions(+), 5 deletions(-) diff --git a/strings/check_pangram.py b/strings/check_pangram.py index 410afd8cc609..e3695b918524 100644 --- a/strings/check_pangram.py +++ b/strings/check_pangram.py @@ -1,4 +1,6 @@ -# Created by sarathkaul on 12/11/19 +""" +wiki: https://en.wikipedia.org/wiki/Pangram +""" def check_pangram( @@ -8,10 +10,16 @@ def check_pangram( A Pangram String contains all the alphabets at least once. >>> check_pangram("The quick brown fox jumps over the lazy dog") True + >>> check_pangram("Waltz, bad nymph, for quick jigs vex.") + True + >>> check_pangram("Jived fox nymph grabs quick waltz.") + True >>> check_pangram("My name is Unknown") False >>> check_pangram("The quick brown fox jumps over the la_y dog") False + >>> check_pangram() + True """ frequency = set() input_str = input_str.replace( @@ -24,7 +32,41 @@ def check_pangram( return True if len(frequency) == 26 else False -if __name__ == "main": - check_str = "INPUT STRING" - status = check_pangram(check_str) - print(f"{check_str} is {'not ' if status else ''}a pangram string") +def check_pangram_faster( + input_str: str = "The quick brown fox jumps over the lazy dog", +) -> bool: + """ + >>> check_pangram_faster("The quick brown fox jumps over the lazy dog") + True + >>> check_pangram("Waltz, bad nymph, for quick jigs vex.") + True + >>> check_pangram("Jived fox nymph grabs quick waltz.") + True + >>> check_pangram_faster("The quick brown fox jumps over the la_y dog") + False + >>> check_pangram_faster() + True + """ + flag = [False] * 26 + for char in input_str: + if char.islower(): + flag[ord(char) - ord("a")] = True + return all(flag) + + +def benchmark() -> None: + """ + Benchmark code comparing different version. + """ + from timeit import timeit + + setup = "from __main__ import check_pangram, check_pangram_faster" + print(timeit("check_pangram()", setup=setup)) + print(timeit("check_pangram_faster()", setup=setup)) + + +if __name__ == "__main__": + import doctest + + doctest.testmod() + benchmark() From 187e8ccc95f280d3a303613c2fc80fcf50081a23 Mon Sep 17 00:00:00 2001 From: Dhruv Date: Sun, 27 Sep 2020 18:35:09 +0530 Subject: [PATCH 0803/1071] Small fix (#2498) --- arithmetic_analysis/newton_method.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arithmetic_analysis/newton_method.py b/arithmetic_analysis/newton_method.py index 97d5d3d3e470..a9a94372671e 100644 --- a/arithmetic_analysis/newton_method.py +++ b/arithmetic_analysis/newton_method.py @@ -36,7 +36,7 @@ def newton( try: next_guess = prev_guess - function(prev_guess) / derivative(prev_guess) except ZeroDivisionError: - raise ZeroDivisionError("Could not find root") + raise ZeroDivisionError("Could not find root") from None if abs(prev_guess - next_guess) < 10 ** -5: return next_guess prev_guess = next_guess From ceacfc6079a8b1358a3d148f9e6e9acf710ec569 Mon Sep 17 00:00:00 2001 From: Dhruv Date: Mon, 28 Sep 2020 11:48:19 +0530 Subject: [PATCH 0804/1071] Add algorithm for testing Project Euler solutions (#2471) * Add file for testing Project Euler solutions * Remove the importlib import * Update project_euler/solution_test.py Co-authored-by: Christian Clauss * Small tweaks to project_euler/solution_test.py * Test Project Euler solutions through Travis * Improved testing for Project Euler solutions: - Renamed file so that it isn't picked up by pytest - Fail fast on validating solutions through Travis CI * Update validate_solutions.py * Use namedtuple for input parameters and answer - Remove logging - Remove unnecessary checks for PROJECT_EULER_PATH as Travis CI picks up the same path * Fix flake8 errors: line too long * Small tweaks to validate_solutions.py * Add all answers & back to using dictionary * Using pytest for testing Project Euler solutions - As we want to fail fast on testing solutions, we need to test using this script first before we use tests written by the author. - As pytest stops testing as soon as it receives a traceback, we need to use pytest-subtests to tell pytest to test all the iterations for the function with given parameters. * Print error messages in oneline format * Separated answers into a separate file: - Add custom print function to print all the error messages at the end of all the tests - Let Travis skip if this failed Co-authored-by: Christian Clauss --- .travis.yml | 4 +- project_euler/project_euler_answers.json | 2902 ++++++++++++++++++++++ project_euler/validate_solutions.py | 67 + 3 files changed, 2972 insertions(+), 1 deletion(-) create mode 100644 project_euler/project_euler_answers.json create mode 100755 project_euler/validate_solutions.py diff --git a/.travis.yml b/.travis.yml index d2394b4097f3..f794cde82688 100644 --- a/.travis.yml +++ b/.travis.yml @@ -17,7 +17,9 @@ jobs: - mypy --ignore-missing-imports . || true # https://github.com/python/mypy/issues/7907 - pytest --doctest-modules --ignore=project_euler/ --durations=10 --cov-report=term-missing:skip-covered --cov=. . - name: Project Euler - before_script: pip install pytest-cov + before_script: + - pip install pytest-cov pytest-subtests + - pytest --tb=no --no-summary --capture=no project_euler/validate_solutions.py || true # fail fast on wrong solution script: - pytest --doctest-modules --durations=10 --cov-report=term-missing:skip-covered --cov=project_euler/ project_euler/ after_success: diff --git a/project_euler/project_euler_answers.json b/project_euler/project_euler_answers.json new file mode 100644 index 000000000000..3c4cc694e685 --- /dev/null +++ b/project_euler/project_euler_answers.json @@ -0,0 +1,2902 @@ +[ + [ + 1, + "233168" + ], + [ + 2, + "4613732" + ], + [ + 3, + "6857" + ], + [ + 4, + "906609" + ], + [ + 5, + "232792560" + ], + [ + 6, + "25164150" + ], + [ + 7, + "104743" + ], + [ + 8, + "23514624000" + ], + [ + 9, + "31875000" + ], + [ + 10, + "142913828922" + ], + [ + 11, + "70600674" + ], + [ + 12, + "76576500" + ], + [ + 13, + "5537376230" + ], + [ + 14, + "837799" + ], + [ + 15, + "137846528820" + ], + [ + 16, + "1366" + ], + [ + 17, + "21124" + ], + [ + 18, + "1074" + ], + [ + 19, + "171" + ], + [ + 20, + "648" + ], + [ + 21, + "31626" + ], + [ + 22, + "871198282" + ], + [ + 23, + "4179871" + ], + [ + 24, + "2783915460" + ], + [ + 25, + "4782" + ], + [ + 26, + "983" + ], + [ + 27, + "-59231" + ], + [ + 28, + "669171001" + ], + [ + 29, + "9183" + ], + [ + 30, + "443839" + ], + [ + 31, + "73682" + ], + [ + 32, + "45228" + ], + [ + 33, + "100" + ], + [ + 34, + "40730" + ], + [ + 35, + "55" + ], + [ + 36, + "872187" + ], + [ + 37, + "748317" + ], + [ + 38, + "932718654" + ], + [ + 39, + "840" + ], + [ + 40, + "210" + ], + [ + 41, + "7652413" + ], + [ + 42, + "162" + ], + [ + 43, + "16695334890" + ], + [ + 44, + "5482660" + ], + [ + 45, + "1533776805" + ], + [ + 46, + "5777" + ], + [ + 47, + "134043" + ], + [ + 48, + "9110846700" + ], + [ + 49, + "296962999629" + ], + [ + 50, + "997651" + ], + [ + 51, + "121313" + ], + [ + 52, + "142857" + ], + [ + 53, + "4075" + ], + [ + 54, + "376" + ], + [ + 55, + "249" + ], + [ + 56, + "972" + ], + [ + 57, + "153" + ], + [ + 58, + "26241" + ], + [ + 59, + "129448" + ], + [ + 60, + "26033" + ], + [ + 61, + "28684" + ], + [ + 62, + "127035954683" + ], + [ + 63, + "49" + ], + [ + 64, + "1322" + ], + [ + 65, + "272" + ], + [ + 66, + "661" + ], + [ + 67, + "7273" + ], + [ + 68, + "6531031914842725" + ], + [ + 69, + "510510" + ], + [ + 70, + "8319823" + ], + [ + 71, + "428570" + ], + [ + 72, + "303963552391" + ], + [ + 73, + "7295372" + ], + [ + 74, + "402" + ], + [ + 75, + "161667" + ], + [ + 76, + "190569291" + ], + [ + 77, + "71" + ], + [ + 78, + "55374" + ], + [ + 79, + "73162890" + ], + [ + 80, + "40886" + ], + [ + 81, + "427337" + ], + [ + 82, + "260324" + ], + [ + 83, + "425185" + ], + [ + 84, + "101524" + ], + [ + 85, + "2772" + ], + [ + 86, + "1818" + ], + [ + 87, + "1097343" + ], + [ + 88, + "7587457" + ], + [ + 89, + "743" + ], + [ + 90, + "1217" + ], + [ + 91, + "14234" + ], + [ + 92, + "8581146" + ], + [ + 93, + "1258" + ], + [ + 94, + "518408346" + ], + [ + 95, + "14316" + ], + [ + 96, + "24702" + ], + [ + 97, + "8739992577" + ], + [ + 98, + "18769" + ], + [ + 99, + "709" + ], + [ + 100, + "756872327473" + ], + [ + 101, + "37076114526" + ], + [ + 102, + "228" + ], + [ + 103, + "20313839404245" + ], + [ + 104, + "329468" + ], + [ + 105, + "73702" + ], + [ + 106, + "21384" + ], + [ + 107, + "259679" + ], + [ + 108, + "180180" + ], + [ + 109, + "38182" + ], + [ + 110, + "9350130049860600" + ], + [ + 111, + "612407567715" + ], + [ + 112, + "1587000" + ], + [ + 113, + "51161058134250" + ], + [ + 114, + "16475640049" + ], + [ + 115, + "168" + ], + [ + 116, + "20492570929" + ], + [ + 117, + "100808458960497" + ], + [ + 118, + "44680" + ], + [ + 119, + "248155780267521" + ], + [ + 120, + "333082500" + ], + [ + 121, + "2269" + ], + [ + 122, + "1582" + ], + [ + 123, + "21035" + ], + [ + 124, + "21417" + ], + [ + 125, + "2906969179" + ], + [ + 126, + "18522" + ], + [ + 127, + "18407904" + ], + [ + 128, + "14516824220" + ], + [ + 129, + "1000023" + ], + [ + 130, + "149253" + ], + [ + 131, + "173" + ], + [ + 132, + "843296" + ], + [ + 133, + "453647705" + ], + [ + 134, + "18613426663617118" + ], + [ + 135, + "4989" + ], + [ + 136, + "2544559" + ], + [ + 137, + "1120149658760" + ], + [ + 138, + "1118049290473932" + ], + [ + 139, + "10057761" + ], + [ + 140, + "5673835352990" + ], + [ + 141, + "878454337159" + ], + [ + 142, + "1006193" + ], + [ + 143, + "30758397" + ], + [ + 144, + "354" + ], + [ + 145, + "608720" + ], + [ + 146, + "676333270" + ], + [ + 147, + "846910284" + ], + [ + 148, + "2129970655314432" + ], + [ + 149, + "52852124" + ], + [ + 150, + "-271248680" + ], + [ + 151, + "0.464399" + ], + [ + 152, + "301" + ], + [ + 153, + "17971254122360635" + ], + [ + 154, + "479742450" + ], + [ + 155, + "3857447" + ], + [ + 156, + "21295121502550" + ], + [ + 157, + "53490" + ], + [ + 158, + "409511334375" + ], + [ + 159, + "14489159" + ], + [ + 160, + "16576" + ], + [ + 161, + "20574308184277971" + ], + [ + 162, + "3D58725572C62302" + ], + [ + 163, + "343047" + ], + [ + 164, + "378158756814587" + ], + [ + 165, + "2868868" + ], + [ + 166, + "7130034" + ], + [ + 167, + "3916160068885" + ], + [ + 168, + "59206" + ], + [ + 169, + "178653872807" + ], + [ + 170, + "9857164023" + ], + [ + 171, + "142989277" + ], + [ + 172, + "227485267000992000" + ], + [ + 173, + "1572729" + ], + [ + 174, + "209566" + ], + [ + 175, + "1,13717420,8" + ], + [ + 176, + "96818198400000" + ], + [ + 177, + "129325" + ], + [ + 178, + "126461847755" + ], + [ + 179, + "986262" + ], + [ + 180, + "285196020571078987" + ], + [ + 181, + "83735848679360680" + ], + [ + 182, + "399788195976" + ], + [ + 183, + "48861552" + ], + [ + 184, + "1725323624056" + ], + [ + 185, + "4640261571849533" + ], + [ + 186, + "2325629" + ], + [ + 187, + "17427258" + ], + [ + 188, + "95962097" + ], + [ + 189, + "10834893628237824" + ], + [ + 190, + "371048281" + ], + [ + 191, + "1918080160" + ], + [ + 192, + "57060635927998347" + ], + [ + 193, + "684465067343069" + ], + [ + 194, + "61190912" + ], + [ + 195, + "75085391" + ], + [ + 196, + "322303240771079935" + ], + [ + 197, + "1.710637717" + ], + [ + 198, + "52374425" + ], + [ + 199, + "0.00396087" + ], + [ + 200, + "229161792008" + ], + [ + 201, + "115039000" + ], + [ + 202, + "1209002624" + ], + [ + 203, + "34029210557338" + ], + [ + 204, + "2944730" + ], + [ + 205, + "0.5731441" + ], + [ + 206, + "1389019170" + ], + [ + 207, + "44043947822" + ], + [ + 208, + "331951449665644800" + ], + [ + 209, + "15964587728784" + ], + [ + 210, + "1598174770174689458" + ], + [ + 211, + "1922364685" + ], + [ + 212, + "328968937309" + ], + [ + 213, + "330.721154" + ], + [ + 214, + "1677366278943" + ], + [ + 215, + "806844323190414" + ], + [ + 216, + "5437849" + ], + [ + 217, + "6273134" + ], + [ + 218, + "0" + ], + [ + 219, + "64564225042" + ], + [ + 220, + "139776,963904" + ], + [ + 221, + "1884161251122450" + ], + [ + 222, + "1590933" + ], + [ + 223, + "61614848" + ], + [ + 224, + "4137330" + ], + [ + 225, + "2009" + ], + [ + 226, + "0.11316017" + ], + [ + 227, + "3780.618622" + ], + [ + 228, + "86226" + ], + [ + 229, + "11325263" + ], + [ + 230, + "850481152593119296" + ], + [ + 231, + "7526965179680" + ], + [ + 232, + "0.83648556" + ], + [ + 233, + "271204031455541309" + ], + [ + 234, + "1259187438574927161" + ], + [ + 235, + "1.002322108633" + ], + [ + 236, + "123/59" + ], + [ + 237, + "15836928" + ], + [ + 238, + "9922545104535661" + ], + [ + 239, + "0.001887854841" + ], + [ + 240, + "7448717393364181966" + ], + [ + 241, + "482316491800641154" + ], + [ + 242, + "997104142249036713" + ], + [ + 243, + "892371480" + ], + [ + 244, + "96356848" + ], + [ + 245, + "288084712410001" + ], + [ + 246, + "810834388" + ], + [ + 247, + "782252" + ], + [ + 248, + "23507044290" + ], + [ + 249, + "9275262564250418" + ], + [ + 250, + "1425480602091519" + ], + [ + 251, + "18946051" + ], + [ + 252, + "104924.0" + ], + [ + 253, + "11.492847" + ], + [ + 254, + "8184523820510" + ], + [ + 255, + "4.4474011180" + ], + [ + 256, + "85765680" + ], + [ + 257, + "139012411" + ], + [ + 258, + "12747994" + ], + [ + 259, + "20101196798" + ], + [ + 260, + "167542057" + ], + [ + 261, + "238890850232021" + ], + [ + 262, + "2531.205" + ], + [ + 263, + "2039506520" + ], + [ + 264, + "2816417.1055" + ], + [ + 265, + "209110240768" + ], + [ + 266, + "1096883702440585" + ], + [ + 267, + "0.999992836187" + ], + [ + 268, + "785478606870985" + ], + [ + 269, + "1311109198529286" + ], + [ + 270, + "82282080" + ], + [ + 271, + "4617456485273129588" + ], + [ + 272, + "8495585919506151122" + ], + [ + 273, + "2032447591196869022" + ], + [ + 274, + "1601912348822" + ], + [ + 275, + "15030564" + ], + [ + 276, + "5777137137739632912" + ], + [ + 277, + "1125977393124310" + ], + [ + 278, + "1228215747273908452" + ], + [ + 279, + "416577688" + ], + [ + 280, + "430.088247" + ], + [ + 281, + "1485776387445623" + ], + [ + 282, + "1098988351" + ], + [ + 283, + "28038042525570324" + ], + [ + 284, + "5a411d7b" + ], + [ + 285, + "157055.80999" + ], + [ + 286, + "52.6494571953" + ], + [ + 287, + "313135496" + ], + [ + 288, + "605857431263981935" + ], + [ + 289, + "6567944538" + ], + [ + 290, + "20444710234716473" + ], + [ + 291, + "4037526" + ], + [ + 292, + "3600060866" + ], + [ + 293, + "2209" + ], + [ + 294, + "789184709" + ], + [ + 295, + "4884650818" + ], + [ + 296, + "1137208419" + ], + [ + 297, + "2252639041804718029" + ], + [ + 298, + "1.76882294" + ], + [ + 299, + "549936643" + ], + [ + 300, + "8.0540771484375" + ], + [ + 301, + "2178309" + ], + [ + 302, + "1170060" + ], + [ + 303, + "1111981904675169" + ], + [ + 304, + "283988410192" + ], + [ + 305, + "18174995535140" + ], + [ + 306, + "852938" + ], + [ + 307, + "0.7311720251" + ], + [ + 308, + "1539669807660924" + ], + [ + 309, + "210139" + ], + [ + 310, + "2586528661783" + ], + [ + 311, + "2466018557" + ], + [ + 312, + "324681947" + ], + [ + 313, + "2057774861813004" + ], + [ + 314, + "132.52756426" + ], + [ + 315, + "13625242" + ], + [ + 316, + "542934735751917735" + ], + [ + 317, + "1856532.8455" + ], + [ + 318, + "709313889" + ], + [ + 319, + "268457129" + ], + [ + 320, + "278157919195482643" + ], + [ + 321, + "2470433131948040" + ], + [ + 322, + "999998760323313995" + ], + [ + 323, + "6.3551758451" + ], + [ + 324, + "96972774" + ], + [ + 325, + "54672965" + ], + [ + 326, + "1966666166408794329" + ], + [ + 327, + "34315549139516" + ], + [ + 328, + "260511850222" + ], + [ + 329, + "199740353/29386561536000" + ], + [ + 330, + "15955822" + ], + [ + 331, + "467178235146843549" + ], + [ + 332, + "2717.751525" + ], + [ + 333, + "3053105" + ], + [ + 334, + "150320021261690835" + ], + [ + 335, + "5032316" + ], + [ + 336, + "CAGBIHEFJDK" + ], + [ + 337, + "85068035" + ], + [ + 338, + "15614292" + ], + [ + 339, + "19823.542204" + ], + [ + 340, + "291504964" + ], + [ + 341, + "56098610614277014" + ], + [ + 342, + "5943040885644" + ], + [ + 343, + "269533451410884183" + ], + [ + 344, + "65579304332" + ], + [ + 345, + "13938" + ], + [ + 346, + "336108797689259276" + ], + [ + 347, + "11109800204052" + ], + [ + 348, + "1004195061" + ], + [ + 349, + "115384615384614952" + ], + [ + 350, + "84664213" + ], + [ + 351, + "11762187201804552" + ], + [ + 352, + "378563.260589" + ], + [ + 353, + "1.2759860331" + ], + [ + 354, + "58065134" + ], + [ + 355, + "1726545007" + ], + [ + 356, + "28010159" + ], + [ + 357, + "1739023853137" + ], + [ + 358, + "3284144505" + ], + [ + 359, + "40632119" + ], + [ + 360, + "878825614395267072" + ], + [ + 361, + "178476944" + ], + [ + 362, + "457895958010" + ], + [ + 363, + "0.0000372091" + ], + [ + 364, + "44855254" + ], + [ + 365, + "162619462356610313" + ], + [ + 366, + "88351299" + ], + [ + 367, + "48271207" + ], + [ + 368, + "253.6135092068" + ], + [ + 369, + "862400558448" + ], + [ + 370, + "41791929448408" + ], + [ + 371, + "40.66368097" + ], + [ + 372, + "301450082318807027" + ], + [ + 373, + "727227472448913" + ], + [ + 374, + "334420941" + ], + [ + 375, + "7435327983715286168" + ], + [ + 376, + "973059630185670" + ], + [ + 377, + "732385277" + ], + [ + 378, + "147534623725724718" + ], + [ + 379, + "132314136838185" + ], + [ + 380, + "6.3202e25093" + ], + [ + 381, + "139602943319822" + ], + [ + 382, + "697003956" + ], + [ + 383, + "22173624649806" + ], + [ + 384, + "3354706415856332783" + ], + [ + 385, + "3776957309612153700" + ], + [ + 386, + "528755790" + ], + [ + 387, + "696067597313468" + ], + [ + 388, + "831907372805129931" + ], + [ + 389, + "2406376.3623" + ], + [ + 390, + "2919133642971" + ], + [ + 391, + "61029882288" + ], + [ + 392, + "3.1486734435" + ], + [ + 393, + "112398351350823112" + ], + [ + 394, + "3.2370342194" + ], + [ + 395, + "28.2453753155" + ], + [ + 396, + "173214653" + ], + [ + 397, + "141630459461893728" + ], + [ + 398, + "2010.59096" + ], + [ + 399, + "1508395636674243,6.5e27330467" + ], + [ + 400, + "438505383468410633" + ], + [ + 401, + "281632621" + ], + [ + 402, + "356019862" + ], + [ + 403, + "18224771" + ], + [ + 404, + "1199215615081353" + ], + [ + 405, + "237696125" + ], + [ + 406, + "36813.12757207" + ], + [ + 407, + "39782849136421" + ], + [ + 408, + "299742733" + ], + [ + 409, + "253223948" + ], + [ + 410, + "799999783589946560" + ], + [ + 411, + "9936352" + ], + [ + 412, + "38788800" + ], + [ + 413, + "3079418648040719" + ], + [ + 414, + "552506775824935461" + ], + [ + 415, + "55859742" + ], + [ + 416, + "898082747" + ], + [ + 417, + "446572970925740" + ], + [ + 418, + "1177163565297340320" + ], + [ + 419, + "998567458,1046245404,43363922" + ], + [ + 420, + "145159332" + ], + [ + 421, + "2304215802083466198" + ], + [ + 422, + "92060460" + ], + [ + 423, + "653972374" + ], + [ + 424, + "1059760019628" + ], + [ + 425, + "46479497324" + ], + [ + 426, + "31591886008" + ], + [ + 427, + "97138867" + ], + [ + 428, + "747215561862" + ], + [ + 429, + "98792821" + ], + [ + 430, + "5000624921.38" + ], + [ + 431, + "23.386029052" + ], + [ + 432, + "754862080" + ], + [ + 433, + "326624372659664" + ], + [ + 434, + "863253606" + ], + [ + 435, + "252541322550" + ], + [ + 436, + "0.5276662759" + ], + [ + 437, + "74204709657207" + ], + [ + 438, + "2046409616809" + ], + [ + 439, + "968697378" + ], + [ + 440, + "970746056" + ], + [ + 441, + "5000088.8395" + ], + [ + 442, + "1295552661530920149" + ], + [ + 443, + "2744233049300770" + ], + [ + 444, + "1.200856722e263" + ], + [ + 445, + "659104042" + ], + [ + 446, + "907803852" + ], + [ + 447, + "530553372" + ], + [ + 448, + "106467648" + ], + [ + 449, + "103.37870096" + ], + [ + 450, + "583333163984220940" + ], + [ + 451, + "153651073760956" + ], + [ + 452, + "345558983" + ], + [ + 453, + "104354107" + ], + [ + 454, + "5435004633092" + ], + [ + 455, + "450186511399999" + ], + [ + 456, + "333333208685971546" + ], + [ + 457, + "2647787126797397063" + ], + [ + 458, + "423341841" + ], + [ + 459, + "3996390106631" + ], + [ + 460, + "18.420738199" + ], + [ + 461, + "159820276" + ], + [ + 462, + "5.5350769703e1512" + ], + [ + 463, + "808981553" + ], + [ + 464, + "198775297232878" + ], + [ + 465, + "585965659" + ], + [ + 466, + "258381958195474745" + ], + [ + 467, + "775181359" + ], + [ + 468, + "852950321" + ], + [ + 469, + "0.56766764161831" + ], + [ + 470, + "147668794" + ], + [ + 471, + "1.895093981e31" + ], + [ + 472, + "73811586" + ], + [ + 473, + "35856681704365" + ], + [ + 474, + "9690646731515010" + ], + [ + 475, + "75780067" + ], + [ + 476, + "110242.87794" + ], + [ + 477, + "25044905874565165" + ], + [ + 478, + "59510340" + ], + [ + 479, + "191541795" + ], + [ + 480, + "turnthestarson" + ], + [ + 481, + "729.12106947" + ], + [ + 482, + "1400824879147" + ], + [ + 483, + "4.993401567e22" + ], + [ + 484, + "8907904768686152599" + ], + [ + 485, + "51281274340" + ], + [ + 486, + "11408450515" + ], + [ + 487, + "106650212746" + ], + [ + 488, + "216737278" + ], + [ + 489, + "1791954757162" + ], + [ + 490, + "777577686" + ], + [ + 491, + "194505988824000" + ], + [ + 492, + "242586962923928" + ], + [ + 493, + "6.818741802" + ], + [ + 494, + "2880067194446832666" + ], + [ + 495, + "789107601" + ], + [ + 496, + "2042473533769142717" + ], + [ + 497, + "684901360" + ], + [ + 498, + "472294837" + ], + [ + 499, + "0.8660312" + ], + [ + 500, + "35407281" + ], + [ + 501, + "197912312715" + ], + [ + 502, + "749485217" + ], + [ + 503, + "3.8694550145" + ], + [ + 504, + "694687" + ], + [ + 505, + "714591308667615832" + ], + [ + 506, + "18934502" + ], + [ + 507, + "316558047002627270" + ], + [ + 508, + "891874596" + ], + [ + 509, + "151725678" + ], + [ + 510, + "315306518862563689" + ], + [ + 511, + "935247012" + ], + [ + 512, + "50660591862310323" + ], + [ + 513, + "2925619196" + ], + [ + 514, + "8986.86698" + ], + [ + 515, + "2422639000800" + ], + [ + 516, + "939087315" + ], + [ + 517, + "581468882" + ], + [ + 518, + "100315739184392" + ], + [ + 519, + "804739330" + ], + [ + 520, + "238413705" + ], + [ + 521, + "44389811" + ], + [ + 522, + "96772715" + ], + [ + 523, + "37125450.44" + ], + [ + 524, + "2432925835413407847" + ], + [ + 525, + "44.69921807" + ], + [ + 526, + "49601160286750947" + ], + [ + 527, + "11.92412011" + ], + [ + 528, + "779027989" + ], + [ + 529, + "23624465" + ], + [ + 530, + "207366437157977206" + ], + [ + 531, + "4515432351156203105" + ], + [ + 532, + "827306.56" + ], + [ + 533, + "789453601" + ], + [ + 534, + "11726115562784664" + ], + [ + 535, + "611778217" + ], + [ + 536, + "3557005261906288" + ], + [ + 537, + "779429131" + ], + [ + 538, + "22472871503401097" + ], + [ + 539, + "426334056" + ], + [ + 540, + "500000000002845" + ], + [ + 541, + "4580726482872451" + ], + [ + 542, + "697586734240314852" + ], + [ + 543, + "199007746081234640" + ], + [ + 544, + "640432376" + ], + [ + 545, + "921107572" + ], + [ + 546, + "215656873" + ], + [ + 547, + "11730879.0023" + ], + [ + 548, + "12144044603581281" + ], + [ + 549, + "476001479068717" + ], + [ + 550, + "328104836" + ], + [ + 551, + "73597483551591773" + ], + [ + 552, + "326227335" + ], + [ + 553, + "57717170" + ], + [ + 554, + "89539872" + ], + [ + 555, + "208517717451208352" + ], + [ + 556, + "52126939292957" + ], + [ + 557, + "2699929328" + ], + [ + 558, + "226754889" + ], + [ + 559, + "684724920" + ], + [ + 560, + "994345168" + ], + [ + 561, + "452480999988235494" + ], + [ + 562, + "51208732914368" + ], + [ + 563, + "27186308211734760" + ], + [ + 564, + "12363.698850" + ], + [ + 565, + "2992480851924313898" + ], + [ + 566, + "329569369413585" + ], + [ + 567, + "75.44817535" + ], + [ + 568, + "4228020" + ], + [ + 569, + "21025060" + ], + [ + 570, + "271197444" + ], + [ + 571, + "30510390701978" + ], + [ + 572, + "19737656" + ], + [ + 573, + "1252.9809" + ], + [ + 574, + "5780447552057000454" + ], + [ + 575, + "0.000989640561" + ], + [ + 576, + "344457.5871" + ], + [ + 577, + "265695031399260211" + ], + [ + 578, + "9219696799346" + ], + [ + 579, + "3805524" + ], + [ + 580, + "2327213148095366" + ], + [ + 581, + "2227616372734" + ], + [ + 582, + "19903" + ], + [ + 583, + "1174137929000" + ], + [ + 584, + "32.83822408" + ], + [ + 585, + "17714439395932" + ], + [ + 586, + "82490213" + ], + [ + 587, + "2240" + ], + [ + 588, + "11651930052" + ], + [ + 589, + "131776959.25" + ], + [ + 590, + "834171904" + ], + [ + 591, + "526007984625966" + ], + [ + 592, + "13415DF2BE9C" + ], + [ + 593, + "96632320042.0" + ], + [ + 594, + "47067598" + ], + [ + 595, + "54.17529329" + ], + [ + 596, + "734582049" + ], + [ + 597, + "0.5001817828" + ], + [ + 598, + "543194779059" + ], + [ + 599, + "12395526079546335" + ], + [ + 600, + "2668608479740672" + ], + [ + 601, + "1617243" + ], + [ + 602, + "269496760" + ], + [ + 603, + "879476477" + ], + [ + 604, + "1398582231101" + ], + [ + 605, + "59992576" + ], + [ + 606, + "158452775" + ], + [ + 607, + "13.1265108586" + ], + [ + 608, + "439689828" + ], + [ + 609, + "172023848" + ], + [ + 610, + "319.30207833" + ], + [ + 611, + "49283233900" + ], + [ + 612, + "819963842" + ], + [ + 613, + "0.3916721504" + ], + [ + 614, + "130694090" + ], + [ + 615, + "108424772" + ], + [ + 616, + "310884668312456458" + ], + [ + 617, + "1001133757" + ], + [ + 618, + "634212216" + ], + [ + 619, + "857810883" + ], + [ + 620, + "1470337306" + ], + [ + 621, + "11429712" + ], + [ + 622, + "3010983666182123972" + ], + [ + 623, + "3679796" + ], + [ + 624, + "984524441" + ], + [ + 625, + "551614306" + ], + [ + 626, + "695577663" + ], + [ + 627, + "220196142" + ], + [ + 628, + "210286684" + ], + [ + 629, + "626616617" + ], + [ + 630, + "9669182880384" + ], + [ + 631, + "869588692" + ], + [ + 632, + "728378714" + ], + [ + 633, + "1.0012e-10" + ], + [ + 634, + "4019680944" + ], + [ + 635, + "689294705" + ], + [ + 636, + "888316" + ], + [ + 637, + "49000634845039" + ], + [ + 638, + "18423394" + ], + [ + 639, + "797866893" + ], + [ + 640, + "50.317928" + ], + [ + 641, + "793525366" + ], + [ + 642, + "631499044" + ], + [ + 643, + "968274154" + ], + [ + 644, + "20.11208767" + ], + [ + 645, + "48894.2174" + ], + [ + 646, + "845218467" + ], + [ + 647, + "563132994232918611" + ], + [ + 648, + "301483197" + ], + [ + 649, + "924668016" + ], + [ + 650, + "538319652" + ], + [ + 651, + "448233151" + ], + [ + 652, + "983924497" + ], + [ + 653, + "1130658687" + ], + [ + 654, + "815868280" + ], + [ + 655, + "2000008332" + ], + [ + 656, + "888873503555187" + ], + [ + 657, + "219493139" + ], + [ + 658, + "958280177" + ], + [ + 659, + "238518915714422000" + ], + [ + 660, + "474766783" + ], + [ + 661, + "646231.2177" + ], + [ + 662, + "860873428" + ], + [ + 663, + "1884138010064752" + ], + [ + 664, + "35295862" + ], + [ + 665, + "11541685709674" + ], + [ + 666, + "0.48023168" + ], + [ + 667, + "1.5276527928" + ], + [ + 668, + "2811077773" + ], + [ + 669, + "56342087360542122" + ], + [ + 670, + "551055065" + ], + [ + 671, + "946106780" + ], + [ + 672, + "91627537" + ], + [ + 673, + "700325380" + ], + [ + 674, + "416678753" + ], + [ + 675, + "416146418" + ], + [ + 676, + "3562668074339584" + ], + [ + 677, + "984183023" + ], + [ + 678, + "1986065" + ], + [ + 679, + "644997092988678" + ], + [ + 680, + "563917241" + ], + [ + 681, + "2611227421428" + ], + [ + 682, + "290872710" + ], + [ + 683, + "2.38955315e11" + ], + [ + 684, + "922058210" + ], + [ + 685, + "662878999" + ], + [ + 686, + "193060223" + ], + [ + 687, + "0.3285320869" + ], + [ + 688, + "110941813" + ], + [ + 689, + "0.56565454" + ], + [ + 690, + "415157690" + ], + [ + 691, + "11570761" + ], + [ + 692, + "842043391019219959" + ], + [ + 693, + "699161" + ], + [ + 694, + "1339784153569958487" + ], + [ + 695, + "0.1017786859" + ], + [ + 696, + "436944244" + ], + [ + 697, + "4343871.06" + ], + [ + 698, + "57808202" + ], + [ + 699, + "37010438774467572" + ], + [ + 700, + "1517926517777556" + ], + [ + 701, + "13.51099836" + ], + [ + 702, + "622305608172525546" + ], + [ + 703, + "843437991" + ], + [ + 704, + "501985601490518144" + ], + [ + 705, + "480440153" + ], + [ + 706, + "884837055" + ], + [ + 707, + "652907799" + ], + [ + 708, + "28874142998632109" + ], + [ + 709, + "773479144" + ], + [ + 710, + "1275000" + ], + [ + 711, + "541510990" + ], + [ + 712, + "413876461" + ], + [ + 713, + "788626351539895" + ], + [ + 714, + "2.452767775565e20" + ], + [ + 715, + "883188017" + ], + [ + 716, + "238948623" + ], + [ + 717, + "1603036763131" + ], + [ + 718, + "228579116" + ], + [ + 719, + "128088830547982" + ], + [ + 720, + "688081048" + ], + [ + 721, + "700792959" + ], + [ + 722, + "3.376792776502e132" + ], + [ + 723, + "1395793419248" + ], + [ + 724, + "18128250110" + ], + [ + 725, + "4598797036650685" + ] +] \ No newline at end of file diff --git a/project_euler/validate_solutions.py b/project_euler/validate_solutions.py new file mode 100755 index 000000000000..01d70721ea8d --- /dev/null +++ b/project_euler/validate_solutions.py @@ -0,0 +1,67 @@ +#!/usr/bin/env python3 +import importlib.util +import json +import pathlib +from types import ModuleType +from typing import Generator + +import pytest + +PROJECT_EULER_DIR_PATH = pathlib.Path.cwd().joinpath("project_euler") +PROJECT_EULER_ANSWERS_PATH = PROJECT_EULER_DIR_PATH.joinpath( + "project_euler_answers.json" +) + +with open(PROJECT_EULER_ANSWERS_PATH) as file_handle: + PROBLEM_ANSWERS = json.load(file_handle) + +error_msgs = [] + + +def generate_solution_modules( + dir_path: pathlib.Path, +) -> Generator[ModuleType, None, None]: + # Iterating over every file or directory + for file_path in dir_path.iterdir(): + if file_path.suffix != ".py" or file_path.name.startswith(("_", "test")): + continue + # Importing the source file through the given path + # https://docs.python.org/3/library/importlib.html#importing-a-source-file-directly + spec = importlib.util.spec_from_file_location(file_path.name, str(file_path)) + module = importlib.util.module_from_spec(spec) + spec.loader.exec_module(module) + yield module + + +@pytest.mark.parametrize("problem_number, expected", PROBLEM_ANSWERS) +def test_project_euler(subtests, problem_number: int, expected: str): + problem_dir = PROJECT_EULER_DIR_PATH.joinpath(f"problem_{problem_number:02}") + # Check if the problem directory exist. If not, then skip. + if problem_dir.is_dir(): + for solution_module in generate_solution_modules(problem_dir): + # All the tests in a loop is considered as one test by pytest so, use + # subtests to make sure all the subtests are considered as different. + with subtests.test( + msg=f"Problem {problem_number} tests", solution_module=solution_module + ): + try: + answer = str(solution_module.solution()) + assert answer == expected, f"Expected {expected} but got {answer}" + except (AssertionError, AttributeError, TypeError) as err: + error_msgs.append( + f"problem_{problem_number}/{solution_module.__name__}: {err}" + ) + raise # We still want pytest to know that this test failed + else: + pytest.skip(f"Solution {problem_number} does not exist yet.") + + +# Run this function at the end of all the tests +# https://stackoverflow.com/a/52873379 +@pytest.fixture(scope="session", autouse=True) +def custom_print_message(request): + def print_error_messages(): + if error_msgs: + print("\n" + "\n".join(error_msgs)) + + request.addfinalizer(print_error_messages) From 1b637ba8ed0199e244979e9216e0a32fc439ecf7 Mon Sep 17 00:00:00 2001 From: xcodz-dot <71920621+xcodz-dot@users.noreply.github.com> Date: Mon, 28 Sep 2020 15:13:34 +0530 Subject: [PATCH 0805/1071] Create vector3_for_2d_rendering.py (#2496) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Create vector3_for_2d_rendering.py Edited for passing travis test * Delete vector3_for_2d_rendering.py * Create vector3_for_2d_rendering.py * Update vector3_for_2d_rendering.py Compressed the line 19 to 28 into 19 to 21 * Update vector3_for_2d_rendering.py * Update vector3_for_2d_rendering.py * Update vector3_for_2d_rendering.py completly corrected pep8 errors using Pycharm IDE * Update vector3_for_2d_rendering.py * Update graphics/vector3_for_2d_rendering.py Co-authored-by: Dhruv * Update graphics/vector3_for_2d_rendering.py Co-authored-by: Dhruv * Update graphics/vector3_for_2d_rendering.py Co-authored-by: Dhruv * Update vector3_for_2d_rendering.py * Update vector3_for_2d_rendering.py * Update graphics/vector3_for_2d_rendering.py Co-authored-by: Christian Clauss * Update graphics/vector3_for_2d_rendering.py Co-authored-by: Christian Clauss * Apply suggestions from code review Co-authored-by: Christian Clauss * Update vector3_for_2d_rendering.py Added A few extra names to __author__ 😄 * Update vector3_for_2d_rendering.py Used Pycharm to fix PEP8 errors, doctest errors * Update vector3_for_2d_rendering.py Added enough doctests * Update graphics/vector3_for_2d_rendering.py Co-authored-by: Christian Clauss * Remove second main() Co-authored-by: Dhruv Co-authored-by: Christian Clauss --- graphics/vector3_for_2d_rendering.py | 96 ++++++++++++++++++++++++++++ 1 file changed, 96 insertions(+) create mode 100644 graphics/vector3_for_2d_rendering.py diff --git a/graphics/vector3_for_2d_rendering.py b/graphics/vector3_for_2d_rendering.py new file mode 100644 index 000000000000..f55bfc7579b5 --- /dev/null +++ b/graphics/vector3_for_2d_rendering.py @@ -0,0 +1,96 @@ +""" +render 3d points for 2d surfaces. +""" + +from __future__ import annotations +import math + +__version__ = "2020.9.26" +__author__ = "xcodz-dot, cclaus, dhruvmanila" + + +def convert_to_2d(x: float, y: float, z: float, scale: float, + distance: float) -> tuple[float, float]: + """ + Converts 3d point to a 2d drawable point + + >>> convert_to_2d(1.0, 2.0, 3.0, 10.0, 10.0) + (7.6923076923076925, 15.384615384615385) + + >>> convert_to_2d(1, 2, 3, 10, 10) + (7.6923076923076925, 15.384615384615385) + + >>> convert_to_2d("1", 2, 3, 10, 10) # '1' is str + Traceback (most recent call last): + ... + TypeError: Input values must either be float or int: ['1', 2, 3, 10, 10] + """ + if not all(isinstance(val, (float, int)) for val in locals().values()): + raise TypeError("Input values must either be float or int: " + f"{list(locals().values())}") + projected_x = ((x * distance) / (z + distance)) * scale + projected_y = ((y * distance) / (z + distance)) * scale + return projected_x, projected_y + + +def rotate(x: float, y: float, z: float, axis: str, + angle: float) -> tuple[float, float, float]: + """ + rotate a point around a certain axis with a certain angle + angle can be any integer between 1, 360 and axis can be any one of + 'x', 'y', 'z' + + >>> rotate(1.0, 2.0, 3.0, 'y', 90.0) + (3.130524675073759, 2.0, 0.4470070007889556) + + >>> rotate(1, 2, 3, "z", 180) + (0.999736015495891, -2.0001319704760485, 3) + + >>> rotate('1', 2, 3, "z", 90.0) # '1' is str + Traceback (most recent call last): + ... + TypeError: Input values except axis must either be float or int: ['1', 2, 3, 90.0] + + >>> rotate(1, 2, 3, "n", 90) # 'n' is not a valid axis + Traceback (most recent call last): + ... + ValueError: not a valid axis, choose one of 'x', 'y', 'z' + + >>> rotate(1, 2, 3, "x", -90) + (1, -2.5049096187183877, -2.5933429780983657) + + >>> rotate(1, 2, 3, "x", 450) # 450 wrap around to 90 + (1, 3.5776792428178217, -0.44744970165427644) + """ + if not isinstance(axis, str): + raise TypeError("Axis must be a str") + input_variables = locals() + del input_variables["axis"] + if not all(isinstance(val, (float, int)) for val in input_variables.values()): + raise TypeError("Input values except axis must either be float or int: " + f"{list(input_variables.values())}") + angle = (angle % 360) / 450 * 180 / math.pi + if axis == 'z': + new_x = x * math.cos(angle) - y * math.sin(angle) + new_y = y * math.cos(angle) + x * math.sin(angle) + new_z = z + elif axis == 'x': + new_y = y * math.cos(angle) - z * math.sin(angle) + new_z = z * math.cos(angle) + y * math.sin(angle) + new_x = x + elif axis == 'y': + new_x = x * math.cos(angle) - z * math.sin(angle) + new_z = z * math.cos(angle) + x * math.sin(angle) + new_y = y + else: + raise ValueError("not a valid axis, choose one of 'x', 'y', 'z'") + + return new_x, new_y, new_z + + +if __name__ == "__main__": + import doctest + + doctest.testmod() + print(f"{convert_to_2d(1.0, 2.0, 3.0, 10.0, 10.0) = }") + print(f"{rotate(1.0, 2.0, 3.0, 'y', 90.0) = }") From 121dddc7f21178bf669ab362681817f2690a3815 Mon Sep 17 00:00:00 2001 From: Lewis Tian Date: Mon, 28 Sep 2020 22:40:47 +0800 Subject: [PATCH 0806/1071] Update maths/area.py (#2501) the parameters of geometric shapes should be non-negative values --- maths/area.py | 112 ++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 104 insertions(+), 8 deletions(-) diff --git a/maths/area.py b/maths/area.py index 3a0fd97396e4..0cbfd7957fa6 100644 --- a/maths/area.py +++ b/maths/area.py @@ -12,7 +12,13 @@ def surface_area_cube(side_length: float) -> float: 6 >>> surface_area_cube(3) 54 + >>> surface_area_cube(-1) + Traceback (most recent call last): + ... + ValueError: surface_area_cube() only accepts non-negative values """ + if side_length < 0: + raise ValueError("surface_area_cube() only accepts non-negative values") return 6 * side_length ** 2 @@ -26,7 +32,13 @@ def surface_area_sphere(radius: float) -> float: 314.1592653589793 >>> surface_area_sphere(1) 12.566370614359172 + >>> surface_area_sphere(-1) + Traceback (most recent call last): + ... + ValueError: surface_area_sphere() only accepts non-negative values """ + if radius < 0: + raise ValueError("surface_area_sphere() only accepts non-negative values") return 4 * pi * radius ** 2 @@ -34,9 +46,23 @@ def area_rectangle(length: float, width: float) -> float: """ Calculate the area of a rectangle - >>> area_rectangle(10,20) + >>> area_rectangle(10, 20) 200 - """ + >>> area_rectangle(-1, -2) + Traceback (most recent call last): + ... + ValueError: area_rectangle() only accepts non-negative values + >>> area_rectangle(1, -2) + Traceback (most recent call last): + ... + ValueError: area_rectangle() only accepts non-negative values + >>> area_rectangle(-1, 2) + Traceback (most recent call last): + ... + ValueError: area_rectangle() only accepts non-negative values + """ + if length < 0 or width < 0: + raise ValueError("area_rectangle() only accepts non-negative values") return length * width @@ -46,7 +72,13 @@ def area_square(side_length: float) -> float: >>> area_square(10) 100 + >>> area_square(-1) + Traceback (most recent call last): + ... + ValueError: area_square() only accepts non-negative values """ + if side_length < 0: + raise ValueError("area_square() only accepts non-negative values") return side_length ** 2 @@ -54,9 +86,23 @@ def area_triangle(base: float, height: float) -> float: """ Calculate the area of a triangle - >>> area_triangle(10,10) + >>> area_triangle(10, 10) 50.0 - """ + >>> area_triangle(-1, -2) + Traceback (most recent call last): + ... + ValueError: area_triangle() only accepts non-negative values + >>> area_triangle(1, -2) + Traceback (most recent call last): + ... + ValueError: area_triangle() only accepts non-negative values + >>> area_triangle(-1, 2) + Traceback (most recent call last): + ... + ValueError: area_triangle() only accepts non-negative values + """ + if base < 0 or height < 0: + raise ValueError("area_triangle() only accepts non-negative values") return (base * height) / 2 @@ -64,9 +110,23 @@ def area_parallelogram(base: float, height: float) -> float: """ Calculate the area of a parallelogram - >>> area_parallelogram(10,20) + >>> area_parallelogram(10, 20) 200 - """ + >>> area_parallelogram(-1, -2) + Traceback (most recent call last): + ... + ValueError: area_parallelogram() only accepts non-negative values + >>> area_parallelogram(1, -2) + Traceback (most recent call last): + ... + ValueError: area_parallelogram() only accepts non-negative values + >>> area_parallelogram(-1, 2) + Traceback (most recent call last): + ... + ValueError: area_parallelogram() only accepts non-negative values + """ + if base < 0 or height < 0: + raise ValueError("area_parallelogram() only accepts non-negative values") return base * height @@ -74,9 +134,39 @@ def area_trapezium(base1: float, base2: float, height: float) -> float: """ Calculate the area of a trapezium - >>> area_trapezium(10,20,30) + >>> area_trapezium(10, 20, 30) 450.0 - """ + >>> area_trapezium(-1, -2, -3) + Traceback (most recent call last): + ... + ValueError: area_trapezium() only accepts non-negative values + >>> area_trapezium(-1, 2, 3) + Traceback (most recent call last): + ... + ValueError: area_trapezium() only accepts non-negative values + >>> area_trapezium(1, -2, 3) + Traceback (most recent call last): + ... + ValueError: area_trapezium() only accepts non-negative values + >>> area_trapezium(1, 2, -3) + Traceback (most recent call last): + ... + ValueError: area_trapezium() only accepts non-negative values + >>> area_trapezium(-1, -2, 3) + Traceback (most recent call last): + ... + ValueError: area_trapezium() only accepts non-negative values + >>> area_trapezium(1, -2, -3) + Traceback (most recent call last): + ... + ValueError: area_trapezium() only accepts non-negative values + >>> area_trapezium(-1, 2, -3) + Traceback (most recent call last): + ... + ValueError: area_trapezium() only accepts non-negative values + """ + if base1 < 0 or base2 < 0 or height < 0: + raise ValueError("area_trapezium() only accepts non-negative values") return 1 / 2 * (base1 + base2) * height @@ -86,7 +176,13 @@ def area_circle(radius: float) -> float: >>> area_circle(20) 1256.6370614359173 + >>> area_circle(-1) + Traceback (most recent call last): + ... + ValueError: area_circle() only accepts non-negative values """ + if radius < 0: + raise ValueError("area_circle() only accepts non-negative values") return pi * radius ** 2 From 48357cea5b34f5a4d1cc2e9c457f5c4c6d5fe501 Mon Sep 17 00:00:00 2001 From: Dhruv Date: Mon, 28 Sep 2020 23:12:36 +0530 Subject: [PATCH 0807/1071] Add __init__.py files in all the directories (#2503) --- arithmetic_analysis/__init__.py | 0 arithmetic_analysis/image_data/__init__.py | 0 backtracking/__init__.py | 0 bit_manipulation/__init__.py | 0 blockchain/__init__.py | 0 boolean_algebra/__init__.py | 0 cellular_automata/__init__.py | 0 ciphers/__init__.py | 0 compression/__init__.py | 0 compression/image_data/__init__.py | 0 computer_vision/__init__.py | 0 conversions/__init__.py | 0 data_structures/__init__.py | 0 data_structures/binary_tree/__init__.py | 0 data_structures/disjoint_set/__init__.py | 0 data_structures/hashing/__init__.py | 0 data_structures/heap/__init__.py | 0 data_structures/queue/__init__.py | 0 data_structures/trie/__init__.py | 0 digital_image_processing/histogram_equalization/__init__.py | 0 .../histogram_equalization/image_data/__init__.py | 0 .../histogram_equalization/output_data/__init__.py | 0 digital_image_processing/image_data/__init__.py | 0 divide_and_conquer/__init__.py | 0 dynamic_programming/__init__.py | 0 file_transfer/__init__.py | 0 file_transfer/tests/__init__.py | 0 fuzzy_logic/__init__.py | 0 genetic_algorithm/__init__.py | 0 geodesy/__init__.py | 0 graphics/__init__.py | 0 graphs/__init__.py | 0 greedy_method/__init__.py | 0 hashes/__init__.py | 0 images/__init__.py | 0 linear_algebra/__init__.py | 0 linear_algebra/src/__init__.py | 0 machine_learning/__init__.py | 0 machine_learning/lstm/__init__.py | 0 maths/images/__init__.py | 0 maths/series/__init__.py | 0 matrix/__init__.py | 0 matrix/tests/__init__.py | 0 networking_flow/__init__.py | 0 neural_network/__init__.py | 0 other/__init__.py | 0 project_euler/__init__.py | 0 project_euler/problem_18/__init__.py | 0 project_euler/problem_23/__init__.py | 0 project_euler/problem_27/__init__.py | 0 project_euler/problem_30/__init__.py | 0 project_euler/problem_32/__init__.py | 0 project_euler/problem_42/__init__.py | 0 quantum/__init__.py | 0 scheduling/__init__.py | 0 scripts/__init__.py | 0 searches/__init__.py | 0 sorts/__init__.py | 0 strings/__init__.py | 0 traversals/__init__.py | 0 web_programming/__init__.py | 0 61 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 arithmetic_analysis/__init__.py create mode 100644 arithmetic_analysis/image_data/__init__.py create mode 100644 backtracking/__init__.py create mode 100644 bit_manipulation/__init__.py create mode 100644 blockchain/__init__.py create mode 100644 boolean_algebra/__init__.py create mode 100644 cellular_automata/__init__.py create mode 100644 ciphers/__init__.py create mode 100644 compression/__init__.py create mode 100644 compression/image_data/__init__.py create mode 100644 computer_vision/__init__.py create mode 100644 conversions/__init__.py create mode 100644 data_structures/__init__.py create mode 100644 data_structures/binary_tree/__init__.py create mode 100644 data_structures/disjoint_set/__init__.py create mode 100644 data_structures/hashing/__init__.py create mode 100644 data_structures/heap/__init__.py create mode 100644 data_structures/queue/__init__.py create mode 100644 data_structures/trie/__init__.py create mode 100644 digital_image_processing/histogram_equalization/__init__.py create mode 100644 digital_image_processing/histogram_equalization/image_data/__init__.py create mode 100644 digital_image_processing/histogram_equalization/output_data/__init__.py create mode 100644 digital_image_processing/image_data/__init__.py create mode 100644 divide_and_conquer/__init__.py create mode 100644 dynamic_programming/__init__.py create mode 100644 file_transfer/__init__.py create mode 100644 file_transfer/tests/__init__.py create mode 100644 fuzzy_logic/__init__.py create mode 100644 genetic_algorithm/__init__.py create mode 100644 geodesy/__init__.py create mode 100644 graphics/__init__.py create mode 100644 graphs/__init__.py create mode 100644 greedy_method/__init__.py create mode 100644 hashes/__init__.py create mode 100644 images/__init__.py create mode 100644 linear_algebra/__init__.py create mode 100644 linear_algebra/src/__init__.py create mode 100644 machine_learning/__init__.py create mode 100644 machine_learning/lstm/__init__.py create mode 100644 maths/images/__init__.py create mode 100644 maths/series/__init__.py create mode 100644 matrix/__init__.py create mode 100644 matrix/tests/__init__.py create mode 100644 networking_flow/__init__.py create mode 100644 neural_network/__init__.py create mode 100644 other/__init__.py create mode 100644 project_euler/__init__.py create mode 100644 project_euler/problem_18/__init__.py create mode 100644 project_euler/problem_23/__init__.py create mode 100644 project_euler/problem_27/__init__.py create mode 100644 project_euler/problem_30/__init__.py create mode 100644 project_euler/problem_32/__init__.py create mode 100644 project_euler/problem_42/__init__.py create mode 100644 quantum/__init__.py create mode 100644 scheduling/__init__.py create mode 100644 scripts/__init__.py create mode 100644 searches/__init__.py create mode 100644 sorts/__init__.py create mode 100644 strings/__init__.py create mode 100644 traversals/__init__.py create mode 100644 web_programming/__init__.py diff --git a/arithmetic_analysis/__init__.py b/arithmetic_analysis/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/arithmetic_analysis/image_data/__init__.py b/arithmetic_analysis/image_data/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/backtracking/__init__.py b/backtracking/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/bit_manipulation/__init__.py b/bit_manipulation/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/blockchain/__init__.py b/blockchain/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/boolean_algebra/__init__.py b/boolean_algebra/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/cellular_automata/__init__.py b/cellular_automata/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/ciphers/__init__.py b/ciphers/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/compression/__init__.py b/compression/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/compression/image_data/__init__.py b/compression/image_data/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/computer_vision/__init__.py b/computer_vision/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/conversions/__init__.py b/conversions/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/data_structures/__init__.py b/data_structures/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/data_structures/binary_tree/__init__.py b/data_structures/binary_tree/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/data_structures/disjoint_set/__init__.py b/data_structures/disjoint_set/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/data_structures/hashing/__init__.py b/data_structures/hashing/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/data_structures/heap/__init__.py b/data_structures/heap/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/data_structures/queue/__init__.py b/data_structures/queue/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/data_structures/trie/__init__.py b/data_structures/trie/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/digital_image_processing/histogram_equalization/__init__.py b/digital_image_processing/histogram_equalization/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/digital_image_processing/histogram_equalization/image_data/__init__.py b/digital_image_processing/histogram_equalization/image_data/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/digital_image_processing/histogram_equalization/output_data/__init__.py b/digital_image_processing/histogram_equalization/output_data/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/digital_image_processing/image_data/__init__.py b/digital_image_processing/image_data/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/divide_and_conquer/__init__.py b/divide_and_conquer/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/dynamic_programming/__init__.py b/dynamic_programming/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/file_transfer/__init__.py b/file_transfer/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/file_transfer/tests/__init__.py b/file_transfer/tests/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/fuzzy_logic/__init__.py b/fuzzy_logic/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/genetic_algorithm/__init__.py b/genetic_algorithm/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/geodesy/__init__.py b/geodesy/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/graphics/__init__.py b/graphics/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/graphs/__init__.py b/graphs/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/greedy_method/__init__.py b/greedy_method/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/hashes/__init__.py b/hashes/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/images/__init__.py b/images/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/linear_algebra/__init__.py b/linear_algebra/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/linear_algebra/src/__init__.py b/linear_algebra/src/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/machine_learning/__init__.py b/machine_learning/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/machine_learning/lstm/__init__.py b/machine_learning/lstm/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/maths/images/__init__.py b/maths/images/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/maths/series/__init__.py b/maths/series/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/matrix/__init__.py b/matrix/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/matrix/tests/__init__.py b/matrix/tests/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/networking_flow/__init__.py b/networking_flow/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/neural_network/__init__.py b/neural_network/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/other/__init__.py b/other/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/project_euler/__init__.py b/project_euler/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/project_euler/problem_18/__init__.py b/project_euler/problem_18/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/project_euler/problem_23/__init__.py b/project_euler/problem_23/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/project_euler/problem_27/__init__.py b/project_euler/problem_27/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/project_euler/problem_30/__init__.py b/project_euler/problem_30/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/project_euler/problem_32/__init__.py b/project_euler/problem_32/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/project_euler/problem_42/__init__.py b/project_euler/problem_42/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/quantum/__init__.py b/quantum/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/scheduling/__init__.py b/scheduling/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/scripts/__init__.py b/scripts/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/searches/__init__.py b/searches/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/sorts/__init__.py b/sorts/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/strings/__init__.py b/strings/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/traversals/__init__.py b/traversals/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/web_programming/__init__.py b/web_programming/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 From 9016fe192fdd3121b6cb20eafeed2dd9154848eb Mon Sep 17 00:00:00 2001 From: Dhruv Date: Tue, 29 Sep 2020 03:11:04 +0530 Subject: [PATCH 0808/1071] Fix imports for all namespace packages (#2506) * Fix imports as they're namespace packages * Fix import for scripts/validate_filenames.py * Fix path in doctest --- ciphers/affine_cipher.py | 2 +- ciphers/elgamal_key_generator.py | 4 ++-- ciphers/rsa_cipher.py | 2 +- ciphers/rsa_key_generator.py | 4 ++-- ciphers/transposition_cipher_encrypt_decrypt_file.py | 2 +- data_structures/hashing/double_hash.py | 4 ++-- data_structures/hashing/hash_table.py | 2 +- data_structures/hashing/hash_table_with_linked_list.py | 2 +- data_structures/hashing/quadratic_probing.py | 2 +- data_structures/linked_list/deque_doubly.py | 4 ++-- data_structures/queue/circular_queue.py | 6 +++--- data_structures/queue/priority_queue_using_list.py | 4 ++-- geodesy/lamberts_ellipsoidal_distance.py | 2 +- greedy_method/test_knapsack.py | 2 +- linear_algebra/src/test_linear_algebra.py | 2 +- scripts/validate_filenames.py | 5 ++++- searches/simulated_annealing.py | 2 +- 17 files changed, 27 insertions(+), 24 deletions(-) diff --git a/ciphers/affine_cipher.py b/ciphers/affine_cipher.py index 5d77cfef33f7..70e695de5013 100644 --- a/ciphers/affine_cipher.py +++ b/ciphers/affine_cipher.py @@ -1,7 +1,7 @@ import random import sys -import cryptomath_module as cryptomath +from . import cryptomath_module as cryptomath SYMBOLS = ( r""" !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`""" diff --git a/ciphers/elgamal_key_generator.py b/ciphers/elgamal_key_generator.py index 1b387751be27..5848e7e707e6 100644 --- a/ciphers/elgamal_key_generator.py +++ b/ciphers/elgamal_key_generator.py @@ -2,8 +2,8 @@ import random import sys -import cryptomath_module as cryptoMath -import rabin_miller as rabinMiller +from . import cryptomath_module as cryptoMath +from . import rabin_miller as rabinMiller min_primitive_root = 3 diff --git a/ciphers/rsa_cipher.py b/ciphers/rsa_cipher.py index 371966b8379b..fad0d6e60074 100644 --- a/ciphers/rsa_cipher.py +++ b/ciphers/rsa_cipher.py @@ -1,7 +1,7 @@ import os import sys -import rsa_key_generator as rkg +from . import rsa_key_generator as rkg DEFAULT_BLOCK_SIZE = 128 BYTE_SIZE = 256 diff --git a/ciphers/rsa_key_generator.py b/ciphers/rsa_key_generator.py index 5514c69917bf..315928d4b60c 100644 --- a/ciphers/rsa_key_generator.py +++ b/ciphers/rsa_key_generator.py @@ -2,8 +2,8 @@ import random import sys -import cryptomath_module as cryptoMath -import rabin_miller as rabinMiller +from . import cryptomath_module as cryptoMath +from . import rabin_miller as rabinMiller def main(): diff --git a/ciphers/transposition_cipher_encrypt_decrypt_file.py b/ciphers/transposition_cipher_encrypt_decrypt_file.py index 71e7c4608fdd..45aab056109a 100644 --- a/ciphers/transposition_cipher_encrypt_decrypt_file.py +++ b/ciphers/transposition_cipher_encrypt_decrypt_file.py @@ -2,7 +2,7 @@ import sys import time -import transposition_cipher as transCipher +from . import transposition_cipher as transCipher def main(): diff --git a/data_structures/hashing/double_hash.py b/data_structures/hashing/double_hash.py index 1007849c5a53..57b1ffff4770 100644 --- a/data_structures/hashing/double_hash.py +++ b/data_structures/hashing/double_hash.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 -from hash_table import HashTable -from number_theory.prime_numbers import check_prime, next_prime +from .hash_table import HashTable +from .number_theory.prime_numbers import check_prime, next_prime class DoubleHash(HashTable): diff --git a/data_structures/hashing/hash_table.py b/data_structures/hashing/hash_table.py index 6e03be95b737..fd9e6eec134c 100644 --- a/data_structures/hashing/hash_table.py +++ b/data_structures/hashing/hash_table.py @@ -1,5 +1,5 @@ #!/usr/bin/env python3 -from number_theory.prime_numbers import next_prime +from .number_theory.prime_numbers import next_prime class HashTable: diff --git a/data_structures/hashing/hash_table_with_linked_list.py b/data_structures/hashing/hash_table_with_linked_list.py index 94934e37b65e..fe838268fce8 100644 --- a/data_structures/hashing/hash_table_with_linked_list.py +++ b/data_structures/hashing/hash_table_with_linked_list.py @@ -1,6 +1,6 @@ from collections import deque -from hash_table import HashTable +from .hash_table import HashTable class HashTableWithLinkedList(HashTable): diff --git a/data_structures/hashing/quadratic_probing.py b/data_structures/hashing/quadratic_probing.py index 06f3ced49d3c..0930340a347f 100644 --- a/data_structures/hashing/quadratic_probing.py +++ b/data_structures/hashing/quadratic_probing.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 -from hash_table import HashTable +from .hash_table import HashTable class QuadraticProbing(HashTable): diff --git a/data_structures/linked_list/deque_doubly.py b/data_structures/linked_list/deque_doubly.py index c6ee2ed27ba5..b93fb8c4005e 100644 --- a/data_structures/linked_list/deque_doubly.py +++ b/data_structures/linked_list/deque_doubly.py @@ -112,7 +112,7 @@ def remove_first(self): ... IndexError: remove_first from empty list >>> d.add_first('A') # doctest: +ELLIPSIS - >> d.remove_first() 'A' >>> d.is_empty() @@ -132,7 +132,7 @@ def remove_last(self): ... IndexError: remove_first from empty list >>> d.add_first('A') # doctest: +ELLIPSIS - >> d.remove_last() 'A' >>> d.is_empty() diff --git a/data_structures/queue/circular_queue.py b/data_structures/queue/circular_queue.py index 229a67ebb8be..93a6ef805c7c 100644 --- a/data_structures/queue/circular_queue.py +++ b/data_structures/queue/circular_queue.py @@ -17,7 +17,7 @@ def __len__(self) -> int: >>> len(cq) 0 >>> cq.enqueue("A") # doctest: +ELLIPSIS - >> len(cq) 1 """ @@ -48,11 +48,11 @@ def enqueue(self, data): This function insert an element in the queue using self.rear value as an index >>> cq = CircularQueue(5) >>> cq.enqueue("A") # doctest: +ELLIPSIS - >> (cq.size, cq.first()) (1, 'A') >>> cq.enqueue("B") # doctest: +ELLIPSIS - >> (cq.size, cq.first()) (2, 'A') """ diff --git a/data_structures/queue/priority_queue_using_list.py b/data_structures/queue/priority_queue_using_list.py index 0ba4e88cd876..c5cf26433fff 100644 --- a/data_structures/queue/priority_queue_using_list.py +++ b/data_structures/queue/priority_queue_using_list.py @@ -59,7 +59,7 @@ class FixedPriorityQueue: >>> fpq.dequeue() Traceback (most recent call last): ... - priority_queue_using_list.UnderFlowError: All queues are empty + data_structures.queue.priority_queue_using_list.UnderFlowError: All queues are empty >>> print(fpq) Priority 0: [] Priority 1: [] @@ -141,7 +141,7 @@ class ElementPriorityQueue: >>> epq.dequeue() Traceback (most recent call last): ... - priority_queue_using_list.UnderFlowError: The queue is empty + data_structures.queue.priority_queue_using_list.UnderFlowError: The queue is empty >>> print(epq) [] """ diff --git a/geodesy/lamberts_ellipsoidal_distance.py b/geodesy/lamberts_ellipsoidal_distance.py index 4a318265e5e6..bf8f1b9a5080 100644 --- a/geodesy/lamberts_ellipsoidal_distance.py +++ b/geodesy/lamberts_ellipsoidal_distance.py @@ -1,6 +1,6 @@ from math import atan, cos, radians, sin, tan -from haversine_distance import haversine_distance +from .haversine_distance import haversine_distance def lamberts_ellipsoidal_distance( diff --git a/greedy_method/test_knapsack.py b/greedy_method/test_knapsack.py index f3ae624ea7aa..5e277a92114e 100644 --- a/greedy_method/test_knapsack.py +++ b/greedy_method/test_knapsack.py @@ -1,6 +1,6 @@ import unittest -import greedy_knapsack as kp +from . import greedy_knapsack as kp class TestClass(unittest.TestCase): diff --git a/linear_algebra/src/test_linear_algebra.py b/linear_algebra/src/test_linear_algebra.py index 668ffe858b99..6eba3a1638bd 100644 --- a/linear_algebra/src/test_linear_algebra.py +++ b/linear_algebra/src/test_linear_algebra.py @@ -8,7 +8,7 @@ """ import unittest -from lib import Matrix, Vector, axpy, squareZeroMatrix, unitBasisVector, zeroVector +from .lib import Matrix, Vector, axpy, squareZeroMatrix, unitBasisVector, zeroVector class Test(unittest.TestCase): diff --git a/scripts/validate_filenames.py b/scripts/validate_filenames.py index f09c1527cb0d..e75bf6c18b07 100755 --- a/scripts/validate_filenames.py +++ b/scripts/validate_filenames.py @@ -1,7 +1,10 @@ #!/usr/bin/env python3 import os -from build_directory_md import good_file_paths +try: + from .build_directory_md import good_file_paths +except ImportError: + from build_directory_md import good_file_paths filepaths = list(good_file_paths()) assert filepaths, "good_file_paths() failed!" diff --git a/searches/simulated_annealing.py b/searches/simulated_annealing.py index 8535e5419a4a..2aa980be7748 100644 --- a/searches/simulated_annealing.py +++ b/searches/simulated_annealing.py @@ -2,7 +2,7 @@ import math import random -from hill_climbing import SearchProblem +from .hill_climbing import SearchProblem def simulated_annealing( From 04322e67e5d3754f7be6525c0e078a955237067a Mon Sep 17 00:00:00 2001 From: Guillaume Rochedix <33205665+grochedix@users.noreply.github.com> Date: Tue, 29 Sep 2020 12:38:12 +0200 Subject: [PATCH 0809/1071] Heaps algorithm iterative (#2505) * heap's algorithm iterative * doctest * doctest * rebuild --- .../heaps_algorithm_iterative.py | 60 +++++++++++++++++++ 1 file changed, 60 insertions(+) create mode 100644 divide_and_conquer/heaps_algorithm_iterative.py diff --git a/divide_and_conquer/heaps_algorithm_iterative.py b/divide_and_conquer/heaps_algorithm_iterative.py new file mode 100644 index 000000000000..4dab41f539c0 --- /dev/null +++ b/divide_and_conquer/heaps_algorithm_iterative.py @@ -0,0 +1,60 @@ +""" +Heap's (iterative) algorithm returns the list of all permutations possible from a list. +It minimizes movement by generating each permutation from the previous one +by swapping only two elements. +More information: +https://en.wikipedia.org/wiki/Heap%27s_algorithm. +""" + + +def heaps(arr: list) -> list: + """ + Pure python implementation of the iterative Heap's algorithm, + returning all permutations of a list. + >>> heaps([]) + [()] + >>> heaps([0]) + [(0,)] + >>> heaps([-1, 1]) + [(-1, 1), (1, -1)] + >>> heaps([1, 2, 3]) + [(1, 2, 3), (2, 1, 3), (3, 1, 2), (1, 3, 2), (2, 3, 1), (3, 2, 1)] + >>> from itertools import permutations + >>> sorted(heaps([1,2,3])) == sorted(permutations([1,2,3])) + True + >>> all(sorted(heaps(x)) == sorted(permutations(x)) + ... for x in ([], [0], [-1, 1], [1, 2, 3])) + True + """ + + if len(arr) <= 1: + return [tuple(arr)] + + res = [] + + def generate(n: int, arr: list): + c = [0] * n + res.append(tuple(arr)) + + i = 0 + while i < n: + if c[i] < i: + if i % 2 == 0: + arr[0], arr[i] = arr[i], arr[0] + else: + arr[c[i]], arr[i] = arr[i], arr[c[i]] + res.append(tuple(arr)) + c[i] += 1 + i = 0 + else: + c[i] = 0 + i += 1 + + generate(len(arr), arr) + return res + + +if __name__ == "__main__": + user_input = input("Enter numbers separated by a comma:\n").strip() + arr = [int(item) for item in user_input.split(",")] + print(heaps(arr)) From d95d64335169d01061ad4347ca29905b3108599c Mon Sep 17 00:00:00 2001 From: Guillaume Rochedix <33205665+grochedix@users.noreply.github.com> Date: Tue, 29 Sep 2020 12:39:07 +0200 Subject: [PATCH 0810/1071] Heaps algorithm (#2475) * heaps_algorithm: new algo * typo * correction doctest * doctests: compare with itertools.permutations * doctest: sorted instead of set * doctest * doctest * rebuild --- divide_and_conquer/heaps_algorithm.py | 56 +++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) create mode 100644 divide_and_conquer/heaps_algorithm.py diff --git a/divide_and_conquer/heaps_algorithm.py b/divide_and_conquer/heaps_algorithm.py new file mode 100644 index 000000000000..af30ad664101 --- /dev/null +++ b/divide_and_conquer/heaps_algorithm.py @@ -0,0 +1,56 @@ +""" +Heap's algorithm returns the list of all permutations possible from a list. +It minimizes movement by generating each permutation from the previous one +by swapping only two elements. +More information: +https://en.wikipedia.org/wiki/Heap%27s_algorithm. +""" + + +def heaps(arr: list) -> list: + """ + Pure python implementation of the Heap's algorithm (recursive version), + returning all permutations of a list. + >>> heaps([]) + [()] + >>> heaps([0]) + [(0,)] + >>> heaps([-1, 1]) + [(-1, 1), (1, -1)] + >>> heaps([1, 2, 3]) + [(1, 2, 3), (2, 1, 3), (3, 1, 2), (1, 3, 2), (2, 3, 1), (3, 2, 1)] + >>> from itertools import permutations + >>> sorted(heaps([1,2,3])) == sorted(permutations([1,2,3])) + True + >>> all(sorted(heaps(x)) == sorted(permutations(x)) + ... for x in ([], [0], [-1, 1], [1, 2, 3])) + True + """ + + if len(arr) <= 1: + return [tuple(arr)] + + res = [] + + def generate(k: int, arr: list): + if k == 1: + res.append(tuple(arr[:])) + return + + generate(k - 1, arr) + + for i in range(k - 1): + if k % 2 == 0: # k is even + arr[i], arr[k - 1] = arr[k - 1], arr[i] + else: # k is odd + arr[0], arr[k - 1] = arr[k - 1], arr[0] + generate(k - 1, arr) + + generate(len(arr), arr) + return res + + +if __name__ == "__main__": + user_input = input("Enter numbers separated by a comma:\n").strip() + arr = [int(item) for item in user_input.split(",")] + print(heaps(arr)) From e16635050916f855aea856f7e3d0d9dbc4f082c1 Mon Sep 17 00:00:00 2001 From: Lewis Tian Date: Tue, 29 Sep 2020 19:55:48 +0800 Subject: [PATCH 0811/1071] Update sorts/quick_sort_3_partition.py (#2507) * Update sorts/quick_sort_3partition.py Another quick sort algorithm, returns a new sorted list * Update sorts/quick_sort_3_partition.py rename quick_sort_3partition to quick_sort_3part * Update sorts/quick_sort_3_partition.py rename quick_sort_3part to three_way_radix_quicksort Three-way radix quicksort: https://en.wikipedia.org/wiki/Quicksort#Three-way_radix_quicksort First divide the list into three parts. Then recursively sort the "less than" and "greater than" partitions. * Update sorts/quick_sort_3_partition.py Co-authored-by: Christian Clauss Co-authored-by: Christian Clauss --- sorts/quick_sort_3_partition.py | 31 ++++++++++++++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/sorts/quick_sort_3_partition.py b/sorts/quick_sort_3_partition.py index a25ac7def802..18c6e0f876d2 100644 --- a/sorts/quick_sort_3_partition.py +++ b/sorts/quick_sort_3_partition.py @@ -1,4 +1,4 @@ -def quick_sort_3partition(sorting, left, right): +def quick_sort_3partition(sorting: list, left: int, right: int) -> None: if right <= left: return a = i = left @@ -18,7 +18,36 @@ def quick_sort_3partition(sorting, left, right): quick_sort_3partition(sorting, b + 1, right) +def three_way_radix_quicksort(sorting: list) -> list: + """ + Three-way radix quicksort: + https://en.wikipedia.org/wiki/Quicksort#Three-way_radix_quicksort + First divide the list into three parts. + Then recursively sort the "less than" and "greater than" partitions. + + >>> three_way_radix_quicksort([]) + [] + >>> three_way_radix_quicksort([1]) + [1] + >>> three_way_radix_quicksort([-5, -2, 1, -2, 0, 1]) + [-5, -2, -2, 0, 1, 1] + >>> three_way_radix_quicksort([1, 2, 5, 1, 2, 0, 0, 5, 2, -1]) + [-1, 0, 0, 1, 1, 2, 2, 2, 5, 5] + """ + if len(sorting) <= 1: + return sorting + return ( + three_way_radix_quicksort([i for i in sorting if i < sorting[0]]) + + [i for i in sorting if i == sorting[0]] + + three_way_radix_quicksort([i for i in sorting if i > sorting[0]]) + ) + + if __name__ == "__main__": + import doctest + + doctest.testmod(verbose=True) + user_input = input("Enter numbers separated by a comma:\n").strip() unsorted = [int(item) for item in user_input.split(",")] quick_sort_3partition(unsorted, 0, len(unsorted) - 1) From e6e2dc69d5643c160510069377f58ed52839ea5d Mon Sep 17 00:00:00 2001 From: YOGESHWARAN R Date: Tue, 29 Sep 2020 21:34:55 +0530 Subject: [PATCH 0812/1071] Create instagram_crawler.py (#2509) * Create instagram_crawler.py * codespell --ignore-words-list=followings * Update web_programming/instagram_crawler.py Co-authored-by: Christian Clauss * Update web_programming/instagram_crawler.py Co-authored-by: Christian Clauss * Update web_programming/instagram_crawler.py Co-authored-by: Christian Clauss * Update web_programming/instagram_crawler.py Co-authored-by: Christian Clauss * Update web_programming/instagram_crawler.py Co-authored-by: Christian Clauss * Update web_programming/instagram_crawler.py Co-authored-by: Christian Clauss * Update web_programming/instagram_crawler.py Co-authored-by: Christian Clauss * Update instagram_crawler.py * Add doctests * fixup! except (json.decoder.JSONDecodeError, KeyError): * if getenv("CONTINUOUS_INTEGRATION"): return * Update instagram_crawler.py * Update web_programming/instagram_crawler.py Co-authored-by: Dhruv * added fake_useragent * Update instagram_crawler.py * Comment out doctests Co-authored-by: Christian Clauss Co-authored-by: Dhruv --- .github/workflows/codespell.yml | 2 +- web_programming/instagram_crawler.py | 140 +++++++++++++++++++++++++++ 2 files changed, 141 insertions(+), 1 deletion(-) create mode 100644 web_programming/instagram_crawler.py diff --git a/.github/workflows/codespell.yml b/.github/workflows/codespell.yml index 02de2b6e89f2..df419bbfab9e 100644 --- a/.github/workflows/codespell.yml +++ b/.github/workflows/codespell.yml @@ -11,7 +11,7 @@ jobs: - run: pip install codespell flake8 - run: | SKIP="./.*,./other/dictionary.txt,./other/words,./project_euler/problem_22/p022_names.txt" - codespell --ignore-words-list=ans,fo,hist,iff,secant,som,tim --skip=$SKIP --quiet-level=2 + codespell --ignore-words-list=ans,fo,followings,hist,iff,secant,som,tim --skip=$SKIP --quiet-level=2 - name: Codespell comment if: ${{ failure() }} uses: plettich/python_codespell_action@master diff --git a/web_programming/instagram_crawler.py b/web_programming/instagram_crawler.py new file mode 100644 index 000000000000..38e383648150 --- /dev/null +++ b/web_programming/instagram_crawler.py @@ -0,0 +1,140 @@ +#!/usr/bin/env python3 +from __future__ import annotations + +import json + +import requests +from bs4 import BeautifulSoup +from fake_useragent import UserAgent + +headers = {"UserAgent": UserAgent().random} + + +def extract_user_profile(script) -> dict: + """ + May raise json.decoder.JSONDecodeError + """ + data = script.contents[0] + info = json.loads(data[data.find('{"config"'): -1]) + return info["entry_data"]["ProfilePage"][0]["graphql"]["user"] + + +class InstagramUser: + """ + Class Instagram crawl instagram user information + + Usage: (doctest failing on Travis CI) + # >>> instagram_user = InstagramUser("github") + # >>> instagram_user.is_verified + True + # >>> instagram_user.biography + 'Built for developers.' + """ + + def __init__(self, username): + self.url = f"https://www.instagram.com/{username}/" + self.user_data = self.get_json() + + def get_json(self) -> dict: + """ + Return a dict of user information + """ + html = requests.get(self.url, headers=headers).text + scripts = BeautifulSoup(html, "html.parser").find_all("script") + try: + return extract_user_profile(scripts[4]) + except (json.decoder.JSONDecodeError, KeyError): + return extract_user_profile(scripts[3]) + + def __repr__(self) -> str: + return f"{self.__class__.__name__}('{self.username}')" + + def __str__(self) -> str: + return f"{self.fullname} ({self.username}) is {self.biography}" + + @property + def username(self) -> str: + return self.user_data["username"] + + @property + def fullname(self) -> str: + return self.user_data["full_name"] + + @property + def biography(self) -> str: + return self.user_data["biography"] + + @property + def email(self) -> str: + return self.user_data["business_email"] + + @property + def website(self) -> str: + return self.user_data["external_url"] + + @property + def number_of_followers(self) -> int: + return self.user_data["edge_followed_by"]["count"] + + @property + def number_of_followings(self) -> int: + return self.user_data["edge_follow"]["count"] + + @property + def number_of_posts(self) -> int: + return self.user_data["edge_owner_to_timeline_media"]["count"] + + @property + def profile_picture_url(self) -> str: + return self.user_data["profile_pic_url_hd"] + + @property + def is_verified(self) -> bool: + return self.user_data["is_verified"] + + @property + def is_private(self) -> bool: + return self.user_data["is_private"] + + +def test_instagram_user(username: str = "github") -> None: + """ + A self running doctest + >>> test_instagram_user() + """ + from os import getenv + + if getenv("CONTINUOUS_INTEGRATION"): + return # test failing on Travis CI + instagram_user = InstagramUser(username) + assert instagram_user.user_data + assert isinstance(instagram_user.user_data, dict) + assert instagram_user.username == username + if username != "github": + return + assert instagram_user.fullname == "GitHub" + assert instagram_user.biography == "Built for developers." + assert instagram_user.number_of_posts > 150 + assert instagram_user.number_of_followers > 120000 + assert instagram_user.number_of_followings > 15 + assert instagram_user.email == "support@github.com" + assert instagram_user.website == "https://github.com/readme" + assert instagram_user.profile_picture_url.startswith("https://instagram.") + assert instagram_user.is_verified is True + assert instagram_user.is_private is False + + +if __name__ == "__main__": + import doctest + + doctest.testmod() + instagram_user = InstagramUser("github") + print(instagram_user) + print(f"{instagram_user.number_of_posts = }") + print(f"{instagram_user.number_of_followers = }") + print(f"{instagram_user.number_of_followings = }") + print(f"{instagram_user.email = }") + print(f"{instagram_user.website = }") + print(f"{instagram_user.profile_picture_url = }") + print(f"{instagram_user.is_verified = }") + print(f"{instagram_user.is_private = }") From 0a42ae909542c2c75079353465077f77173f15bf Mon Sep 17 00:00:00 2001 From: Dhruv Date: Wed, 30 Sep 2020 14:08:00 +0530 Subject: [PATCH 0813/1071] Fix all errors mentioned in pre-commit run (#2512) * Fix all errors mentioned in pre-commit run: - Fix end of file - Remove trailing whitespace - Fix files with black - Fix imports with isort * Fix errors --- .github/stale.yml | 2 +- .github/workflows/autoblack.yml | 2 +- .travis.yml | 2 +- README.md | 2 +- ciphers/prehistoric_men.txt | 8 +- computer_vision/README.md | 4 +- graphics/vector3_for_2d_rendering.py | 28 +++--- linear_algebra/README.md | 92 +++++++++---------- .../gradient_boosting_regressor.py | 12 +-- maths/prime_sieve_eratosthenes.py | 4 +- project_euler/problem_11/grid.txt | 2 +- project_euler/problem_99/base_exp.txt | 2 +- project_euler/project_euler_answers.json | 2 +- sorts/normal_distribution_quick_sort.md | 13 ++- 14 files changed, 88 insertions(+), 87 deletions(-) diff --git a/.github/stale.yml b/.github/stale.yml index fe51e49e4707..22aae982abdc 100644 --- a/.github/stale.yml +++ b/.github/stale.yml @@ -16,5 +16,5 @@ markComment: > for your contributions. # Comment to post when closing a stale issue. Set to `false` to disable closeComment: > - Please reopen this issue once you commit the changes requested or + Please reopen this issue once you commit the changes requested or make improvements on the code. Thank you for your contributions. diff --git a/.github/workflows/autoblack.yml b/.github/workflows/autoblack.yml index 25d291c6ffbb..ce34170d44bb 100644 --- a/.github/workflows/autoblack.yml +++ b/.github/workflows/autoblack.yml @@ -19,7 +19,7 @@ jobs: black . isort --profile black . git config --global user.name github-actions - git config --global user.email '${GITHUB_ACTOR}@users.noreply.github.com' + git config --global user.email '${GITHUB_ACTOR}@users.noreply.github.com' git remote set-url origin https://x-access-token:${{ secrets.GITHUB_TOKEN }}@github.com/$GITHUB_REPOSITORY git commit -am "fixup! Format Python code with psf/black push" git push --force origin HEAD:$GITHUB_REF diff --git a/.travis.yml b/.travis.yml index f794cde82688..3f1343bfa713 100644 --- a/.travis.yml +++ b/.travis.yml @@ -9,7 +9,7 @@ jobs: include: - name: Build before_script: - - black --check . || true + - black --check . || true - flake8 --ignore=E203,W503 --max-complexity=25 --max-line-length=88 --statistics --count . - scripts/validate_filenames.py # no uppercase, no spaces, in a directory - pip install -r requirements.txt # fast fail on black, flake8, validate_filenames diff --git a/README.md b/README.md index 106f5907eebf..fef433dba63b 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ # The Algorithms - Python -[![Gitpod Ready-to-Code](https://img.shields.io/badge/Gitpod-Ready--to--Code-blue?logo=gitpod)](https://gitpod.io/#https://github.com/TheAlgorithms/Python) +[![Gitpod Ready-to-Code](https://img.shields.io/badge/Gitpod-Ready--to--Code-blue?logo=gitpod)](https://gitpod.io/#https://github.com/TheAlgorithms/Python) [![Gitter chat](https://img.shields.io/badge/Chat-Gitter-ff69b4.svg?label=Chat&logo=gitter&style=flat-square)](https://gitter.im/TheAlgorithms)  [![Build Status](https://img.shields.io/travis/TheAlgorithms/Python.svg?label=Travis%20CI&logo=travis&style=flat-square)](https://travis-ci.com/TheAlgorithms/Python)  [![LGTM](https://img.shields.io/lgtm/alerts/github/TheAlgorithms/Python.svg?label=LGTM&logo=LGTM&style=flat-square)](https://lgtm.com/projects/g/TheAlgorithms/Python/alerts)  diff --git a/ciphers/prehistoric_men.txt b/ciphers/prehistoric_men.txt index 86c4de821bfc..a58e533a8405 100644 --- a/ciphers/prehistoric_men.txt +++ b/ciphers/prehistoric_men.txt @@ -3,9 +3,9 @@ Braidwood, Illustrated by Susan T. Richert This eBook is for the use of anyone anywhere in the United States and most -other parts of the world at no cost and with almost no restrictions +other parts of the world at no cost and with almost no restrictions whatsoever. You may copy it, give it away or re-use it under the terms of -the Project Gutenberg License included with this eBook or online at +the Project Gutenberg License included with this eBook or online at www.gutenberg.org. If you are not located in the United States, you'll have to check the laws of the country where you are located before using this ebook. @@ -7109,9 +7109,9 @@ and permanent future for Project Gutenberg-tm and future generations. To learn more about the Project Gutenberg Literary Archive Foundation and how your efforts and donations can help, see Sections 3 and 4 and the Foundation information page at -www.gutenberg.org +www.gutenberg.org -Section 3. Information about the Project Gutenberg Literary +Section 3. Information about the Project Gutenberg Literary Archive Foundation The Project Gutenberg Literary Archive Foundation is a non profit diff --git a/computer_vision/README.md b/computer_vision/README.md index 3a561d2f1f24..8b1812de8e8b 100644 --- a/computer_vision/README.md +++ b/computer_vision/README.md @@ -2,7 +2,7 @@ Computer vision is a field of computer science that works on enabling computers to see, identify and process images in the same way that human vision does, and then provide appropriate output. -It is like imparting human intelligence and instincts to a computer. +It is like imparting human intelligence and instincts to a computer. Image processing and computer vision and little different from each other.Image processing means applying some algorithms for transforming image from one form to other like smoothing,contrasting, stretching etc -While in computer vision comes from modelling image processing using the techniques of machine learning.Computer vision applies machine learning to recognize patterns for interpretation of images. +While in computer vision comes from modelling image processing using the techniques of machine learning.Computer vision applies machine learning to recognize patterns for interpretation of images. Much like the process of visual reasoning of human vision diff --git a/graphics/vector3_for_2d_rendering.py b/graphics/vector3_for_2d_rendering.py index f55bfc7579b5..dfa22262a8d8 100644 --- a/graphics/vector3_for_2d_rendering.py +++ b/graphics/vector3_for_2d_rendering.py @@ -3,14 +3,16 @@ """ from __future__ import annotations + import math __version__ = "2020.9.26" __author__ = "xcodz-dot, cclaus, dhruvmanila" -def convert_to_2d(x: float, y: float, z: float, scale: float, - distance: float) -> tuple[float, float]: +def convert_to_2d( + x: float, y: float, z: float, scale: float, distance: float +) -> tuple[float, float]: """ Converts 3d point to a 2d drawable point @@ -26,15 +28,17 @@ def convert_to_2d(x: float, y: float, z: float, scale: float, TypeError: Input values must either be float or int: ['1', 2, 3, 10, 10] """ if not all(isinstance(val, (float, int)) for val in locals().values()): - raise TypeError("Input values must either be float or int: " - f"{list(locals().values())}") + raise TypeError( + "Input values must either be float or int: " f"{list(locals().values())}" + ) projected_x = ((x * distance) / (z + distance)) * scale projected_y = ((y * distance) / (z + distance)) * scale return projected_x, projected_y -def rotate(x: float, y: float, z: float, axis: str, - angle: float) -> tuple[float, float, float]: +def rotate( + x: float, y: float, z: float, axis: str, angle: float +) -> tuple[float, float, float]: """ rotate a point around a certain axis with a certain angle angle can be any integer between 1, 360 and axis can be any one of @@ -67,18 +71,20 @@ def rotate(x: float, y: float, z: float, axis: str, input_variables = locals() del input_variables["axis"] if not all(isinstance(val, (float, int)) for val in input_variables.values()): - raise TypeError("Input values except axis must either be float or int: " - f"{list(input_variables.values())}") + raise TypeError( + "Input values except axis must either be float or int: " + f"{list(input_variables.values())}" + ) angle = (angle % 360) / 450 * 180 / math.pi - if axis == 'z': + if axis == "z": new_x = x * math.cos(angle) - y * math.sin(angle) new_y = y * math.cos(angle) + x * math.sin(angle) new_z = z - elif axis == 'x': + elif axis == "x": new_y = y * math.cos(angle) - z * math.sin(angle) new_z = z * math.cos(angle) + y * math.sin(angle) new_x = x - elif axis == 'y': + elif axis == "y": new_x = x * math.cos(angle) - z * math.sin(angle) new_z = z * math.cos(angle) + x * math.sin(angle) new_y = y diff --git a/linear_algebra/README.md b/linear_algebra/README.md index 9f8d150a72fa..dc6085090d02 100644 --- a/linear_algebra/README.md +++ b/linear_algebra/README.md @@ -1,35 +1,35 @@ -# Linear algebra library for Python +# Linear algebra library for Python -This module contains classes and functions for doing linear algebra. +This module contains classes and functions for doing linear algebra. --- -## Overview +## Overview -### class Vector +### class Vector - - - This class represents a vector of arbitrary size and related operations. - - **Overview about the methods:** - - - constructor(components : list) : init the vector - - set(components : list) : changes the vector components. - - \_\_str\_\_() : toString method - - component(i : int): gets the i-th component (start by 0) - - \_\_len\_\_() : gets the size / length of the vector (number of components) - - euclidLength() : returns the eulidean length of the vector. - - operator + : vector addition - - operator - : vector subtraction - - operator * : scalar multiplication and dot product - - copy() : copies this vector and returns it. - - changeComponent(pos,value) : changes the specified component. - -- function zeroVector(dimension) - - returns a zero vector of 'dimension' -- function unitBasisVector(dimension,pos) - - returns a unit basis vector with a One at index 'pos' (indexing at 0) -- function axpy(scalar,vector1,vector2) - - computes the axpy operation + - This class represents a vector of arbitrary size and related operations. + + **Overview about the methods:** + + - constructor(components : list) : init the vector + - set(components : list) : changes the vector components. + - \_\_str\_\_() : toString method + - component(i : int): gets the i-th component (start by 0) + - \_\_len\_\_() : gets the size / length of the vector (number of components) + - euclidLength() : returns the eulidean length of the vector. + - operator + : vector addition + - operator - : vector subtraction + - operator * : scalar multiplication and dot product + - copy() : copies this vector and returns it. + - changeComponent(pos,value) : changes the specified component. + +- function zeroVector(dimension) + - returns a zero vector of 'dimension' +- function unitBasisVector(dimension,pos) + - returns a unit basis vector with a One at index 'pos' (indexing at 0) +- function axpy(scalar,vector1,vector2) + - computes the axpy operation - function randomVector(N,a,b) - returns a random vector of size N, with random integer components between 'a' and 'b'. @@ -37,39 +37,39 @@ This module contains classes and functions for doing linear algebra. - - This class represents a matrix of arbitrary size and operations on it. - **Overview about the methods:** - - - \_\_str\_\_() : returns a string representation - - operator * : implements the matrix vector multiplication - implements the matrix-scalar multiplication. - - changeComponent(x,y,value) : changes the specified component. - - component(x,y) : returns the specified component. - - width() : returns the width of the matrix + **Overview about the methods:** + + - \_\_str\_\_() : returns a string representation + - operator * : implements the matrix vector multiplication + implements the matrix-scalar multiplication. + - changeComponent(x,y,value) : changes the specified component. + - component(x,y) : returns the specified component. + - width() : returns the width of the matrix - height() : returns the height of the matrix - - determinate() : returns the determinate of the matrix if it is square - - operator + : implements the matrix-addition. - - operator - _ implements the matrix-subtraction - -- function squareZeroMatrix(N) - - returns a square zero-matrix of dimension NxN -- function randomMatrix(W,H,a,b) - - returns a random matrix WxH with integer components between 'a' and 'b' + - determinate() : returns the determinate of the matrix if it is square + - operator + : implements the matrix-addition. + - operator - _ implements the matrix-subtraction + +- function squareZeroMatrix(N) + - returns a square zero-matrix of dimension NxN +- function randomMatrix(W,H,a,b) + - returns a random matrix WxH with integer components between 'a' and 'b' --- -## Documentation +## Documentation This module uses docstrings to enable the use of Python's in-built `help(...)` function. For instance, try `help(Vector)`, `help(unitBasisVector)`, and `help(CLASSNAME.METHODNAME)`. --- -## Usage +## Usage Import the module `lib.py` from the **src** directory into your project. -Alternatively, you can directly use the Python bytecode file `lib.pyc`. +Alternatively, you can directly use the Python bytecode file `lib.pyc`. --- -## Tests +## Tests `src/tests.py` contains Python unit tests which can be run with `python3 -m unittest -v`. diff --git a/machine_learning/gradient_boosting_regressor.py b/machine_learning/gradient_boosting_regressor.py index 045aa056ec2f..0aa0e7a10ac5 100644 --- a/machine_learning/gradient_boosting_regressor.py +++ b/machine_learning/gradient_boosting_regressor.py @@ -3,11 +3,11 @@ predict house price. """ -import pandas as pd import matplotlib.pyplot as plt +import pandas as pd from sklearn.datasets import load_boston -from sklearn.metrics import mean_squared_error, r2_score from sklearn.ensemble import GradientBoostingRegressor +from sklearn.metrics import mean_squared_error, r2_score from sklearn.model_selection import train_test_split @@ -42,10 +42,7 @@ def main(): training_score = model.score(X_train, y_train).round(3) test_score = model.score(X_test, y_test).round(3) print("Training score of GradientBoosting is :", training_score) - print( - "The test score of GradientBoosting is :", - test_score - ) + print("The test score of GradientBoosting is :", test_score) # Let us evaluation the model by finding the errors y_pred = model.predict(X_test) @@ -57,8 +54,7 @@ def main(): # So let's run the model against the test data fig, ax = plt.subplots() ax.scatter(y_test, y_pred, edgecolors=(0, 0, 0)) - ax.plot([y_test.min(), y_test.max()], - [y_test.min(), y_test.max()], "k--", lw=4) + ax.plot([y_test.min(), y_test.max()], [y_test.min(), y_test.max()], "k--", lw=4) ax.set_xlabel("Actual") ax.set_ylabel("Predicted") ax.set_title("Truth vs Predicted") diff --git a/maths/prime_sieve_eratosthenes.py b/maths/prime_sieve_eratosthenes.py index da3ae472ce23..8d60e48c2140 100644 --- a/maths/prime_sieve_eratosthenes.py +++ b/maths/prime_sieve_eratosthenes.py @@ -4,10 +4,10 @@ Sieve of Eratosthenes Input : n =10 -Output: 2 3 5 7 +Output: 2 3 5 7 Input : n = 20 -Output: 2 3 5 7 11 13 17 19 +Output: 2 3 5 7 11 13 17 19 you can read in detail about this at https://en.wikipedia.org/wiki/Sieve_of_Eratosthenes diff --git a/project_euler/problem_11/grid.txt b/project_euler/problem_11/grid.txt index 1fc75c66a314..4ac24518973e 100644 --- a/project_euler/problem_11/grid.txt +++ b/project_euler/problem_11/grid.txt @@ -17,4 +17,4 @@ 04 42 16 73 38 25 39 11 24 94 72 18 08 46 29 32 40 62 76 36 20 69 36 41 72 30 23 88 34 62 99 69 82 67 59 85 74 04 36 16 20 73 35 29 78 31 90 01 74 31 49 71 48 86 81 16 23 57 05 54 -01 70 54 71 83 51 54 69 16 92 33 48 61 43 52 01 89 19 67 48 \ No newline at end of file +01 70 54 71 83 51 54 69 16 92 33 48 61 43 52 01 89 19 67 48 diff --git a/project_euler/problem_99/base_exp.txt b/project_euler/problem_99/base_exp.txt index abe95aa86036..e4b2d01522ca 100644 --- a/project_euler/problem_99/base_exp.txt +++ b/project_euler/problem_99/base_exp.txt @@ -997,4 +997,4 @@ 672276,515708 325361,545187 172115,573985 -13846,725685 \ No newline at end of file +13846,725685 diff --git a/project_euler/project_euler_answers.json b/project_euler/project_euler_answers.json index 3c4cc694e685..df5a60257f5a 100644 --- a/project_euler/project_euler_answers.json +++ b/project_euler/project_euler_answers.json @@ -2899,4 +2899,4 @@ 725, "4598797036650685" ] -] \ No newline at end of file +] diff --git a/sorts/normal_distribution_quick_sort.md b/sorts/normal_distribution_quick_sort.md index 635262bfdf7d..2a9f77b3ee95 100644 --- a/sorts/normal_distribution_quick_sort.md +++ b/sorts/normal_distribution_quick_sort.md @@ -13,9 +13,9 @@ The array elements are taken from a Standard Normal Distribution , having mean = ```python ->>> import numpy as np +>>> import numpy as np >>> from tempfile import TemporaryFile ->>> outfile = TemporaryFile() +>>> outfile = TemporaryFile() >>> p = 100 # 100 elements are to be sorted >>> mu, sigma = 0, 1 # mean and standard deviation >>> X = np.random.normal(mu, sigma, p) @@ -34,7 +34,7 @@ The array elements are taken from a Standard Normal Distribution , having mean = >>> s = np.random.normal(mu, sigma, p) >>> count, bins, ignored = plt.hist(s, 30, normed=True) >>> plt.plot(bins , 1/(sigma * np.sqrt(2 * np.pi)) *np.exp( - (bins - mu)**2 / (2 * sigma**2) ),linewidth=2, color='r') ->>> plt.show() +>>> plt.show() ``` @@ -52,15 +52,15 @@ The array elements are taken from a Standard Normal Distribution , having mean = -- -## Plotting the function for Checking 'The Number of Comparisons' taking place between Normal Distribution QuickSort and Ordinary QuickSort +## Plotting the function for Checking 'The Number of Comparisons' taking place between Normal Distribution QuickSort and Ordinary QuickSort ```python >>>import matplotlib.pyplot as plt - + # Normal Disrtibution QuickSort is red >>> plt.plot([1,2,4,16,32,64,128,256,512,1024,2048],[1,1,6,15,43,136,340,800,2156,6821,16325],linewidth=2, color='r') - + #Ordinary QuickSort is green >>> plt.plot([1,2,4,16,32,64,128,256,512,1024,2048],[1,1,4,16,67,122,362,949,2131,5086,12866],linewidth=2, color='g') @@ -73,4 +73,3 @@ The array elements are taken from a Standard Normal Distribution , having mean = ------------------ - From ae65f55de378f490bf7da143bfbbb2be06457f4d Mon Sep 17 00:00:00 2001 From: Dhruv Date: Wed, 30 Sep 2020 14:09:14 +0530 Subject: [PATCH 0814/1071] Add pre-commit hook for TheAlgorithms/Python (#2511) * Add pre-commit basic config file * Add pre-commit to requirements.txt * Small tweaks and use stable for black * Fix isort section in pre-commit-config file * Fix errors and EOF only for Python files --- .pre-commit-config.yaml | 60 +++++++++++++++++++++++++++++++++++++++++ requirements.txt | 1 + 2 files changed, 61 insertions(+) create mode 100644 .pre-commit-config.yaml diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 000000000000..2fbb9cb9bdb2 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,60 @@ +repos: + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v3.2.0 + hooks: + - id: check-executables-have-shebangs + - id: check-yaml + - id: end-of-file-fixer + types: [python] + - id: trailing-whitespace + exclude: | + (?x)^( + data_structures/heap/binomial_heap.py + )$ + - id: requirements-txt-fixer + - repo: https://github.com/psf/black + rev: stable + hooks: + - id: black + - repo: https://github.com/PyCQA/isort + rev: 5.5.3 + hooks: + - id: isort + args: + - --profile=black + - repo: https://gitlab.com/pycqa/flake8 + rev: 3.8.3 + hooks: + - id: flake8 + args: + - --ignore=E203,W503 + - --max-complexity=25 + - --max-line-length=88 +# FIXME: fix mypy errors and then uncomment this +# - repo: https://github.com/pre-commit/mirrors-mypy +# rev: v0.782 +# hooks: +# - id: mypy +# args: +# - --ignore-missing-imports + - repo: https://github.com/codespell-project/codespell + rev: v1.17.1 + hooks: + - id: codespell + args: + - --ignore-words-list=ans,fo,followings,hist,iff,secant,som,tim + - --skip="./.*,./other/dictionary.txt,./other/words,./project_euler/problem_22/p022_names.txt" + - --quiet-level=2 + exclude: | + (?x)^( + other/dictionary.txt | + other/words | + project_euler/problem_22/p022_names.txt + )$ + - repo: local + hooks: + - id: validate-filenames + name: Validate filenames + entry: ./scripts/validate_filenames.py + language: script + pass_filenames: false diff --git a/requirements.txt b/requirements.txt index b070ffdf611d..cb38123dc026 100644 --- a/requirements.txt +++ b/requirements.txt @@ -10,6 +10,7 @@ numpy opencv-python pandas pillow +pre-commit pytest pytest-cov requests From acaeb22bbd51f8218cad42df78d99d001f396ba1 Mon Sep 17 00:00:00 2001 From: Dhruv Date: Wed, 30 Sep 2020 18:53:34 +0530 Subject: [PATCH 0815/1071] Add GitHub action for pre-commit (#2515) * Add GitHub action file for pre-commit * Fix errors exposed by pre-commit hook: - Remove executable bit from files without shebang. I checked those file and it was not needed. - Fix with black * Apply suggestions from code review Co-authored-by: Christian Clauss Co-authored-by: Christian Clauss --- .github/workflows/pre-commit.yml | 15 +++++++++++++++ machine_learning/scoring_functions.py | 0 maths/sum_of_arithmetic_series.py | 0 scheduling/round_robin.py | 0 web_programming/instagram_crawler.py | 2 +- 5 files changed, 16 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/pre-commit.yml mode change 100755 => 100644 machine_learning/scoring_functions.py mode change 100755 => 100644 maths/sum_of_arithmetic_series.py mode change 100755 => 100644 scheduling/round_robin.py diff --git a/.github/workflows/pre-commit.yml b/.github/workflows/pre-commit.yml new file mode 100644 index 000000000000..7002d2d0a21e --- /dev/null +++ b/.github/workflows/pre-commit.yml @@ -0,0 +1,15 @@ +name: pre-commit + +on: [push, pull_request] + +jobs: + pre-commit: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: actions/setup-python@v2 + - name: Install pre-commit + run: | + python -m pip install --upgrade pip + python -m pip install --upgrade pre-commit + - run: pre-commit run --verbose --all-files --show-diff-on-failure diff --git a/machine_learning/scoring_functions.py b/machine_learning/scoring_functions.py old mode 100755 new mode 100644 diff --git a/maths/sum_of_arithmetic_series.py b/maths/sum_of_arithmetic_series.py old mode 100755 new mode 100644 diff --git a/scheduling/round_robin.py b/scheduling/round_robin.py old mode 100755 new mode 100644 diff --git a/web_programming/instagram_crawler.py b/web_programming/instagram_crawler.py index 38e383648150..c81635bd3593 100644 --- a/web_programming/instagram_crawler.py +++ b/web_programming/instagram_crawler.py @@ -15,7 +15,7 @@ def extract_user_profile(script) -> dict: May raise json.decoder.JSONDecodeError """ data = script.contents[0] - info = json.loads(data[data.find('{"config"'): -1]) + info = json.loads(data[data.find('{"config"') : -1]) return info["entry_data"]["ProfilePage"][0]["graphql"]["user"] From 7a502708e02e004495dcced75b847d7218c56e89 Mon Sep 17 00:00:00 2001 From: Dhruv Date: Wed, 30 Sep 2020 20:10:20 +0530 Subject: [PATCH 0816/1071] Add badges for code style and pre-commit in README.md (#2516) * Add badges for code style and pre-commit * Update badge style to flat-square * Update Gitpod logo style to flat-square --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index fef433dba63b..d52124e61d23 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,13 @@ # The Algorithms - Python -[![Gitpod Ready-to-Code](https://img.shields.io/badge/Gitpod-Ready--to--Code-blue?logo=gitpod)](https://gitpod.io/#https://github.com/TheAlgorithms/Python) +[![Gitpod Ready-to-Code](https://img.shields.io/badge/Gitpod-Ready--to--Code-blue?logo=gitpod&style=flat-square)](https://gitpod.io/#https://github.com/TheAlgorithms/Python) [![Gitter chat](https://img.shields.io/badge/Chat-Gitter-ff69b4.svg?label=Chat&logo=gitter&style=flat-square)](https://gitter.im/TheAlgorithms)  [![Build Status](https://img.shields.io/travis/TheAlgorithms/Python.svg?label=Travis%20CI&logo=travis&style=flat-square)](https://travis-ci.com/TheAlgorithms/Python)  [![LGTM](https://img.shields.io/lgtm/alerts/github/TheAlgorithms/Python.svg?label=LGTM&logo=LGTM&style=flat-square)](https://lgtm.com/projects/g/TheAlgorithms/Python/alerts)  [![contributions welcome](https://img.shields.io/static/v1.svg?label=Contributions&message=Welcome&color=0059b3&style=flat-square)](https://github.com/TheAlgorithms/Python/blob/master/CONTRIBUTING.md)  [![Donate](https://img.shields.io/badge/Donate-PayPal-green.svg?logo=paypal&style=flat-square)](https://www.paypal.me/TheAlgorithms/100)  ![](https://img.shields.io/github/repo-size/TheAlgorithms/Python.svg?label=Repo%20size&style=flat-square)  +[![pre-commit](https://img.shields.io/badge/pre--commit-enabled-brightgreen?logo=pre-commit&logoColor=white&style=flat-square)](https://github.com/pre-commit/pre-commit) +[![code style: black](https://img.shields.io/static/v1?label=code%20style&message=black&color=black&style=flat-square)](https://github.com/psf/black) ### All algorithms implemented in Python (for education) From ddfa9e4f2245c6c57b620c80932e3f78aebd25b9 Mon Sep 17 00:00:00 2001 From: Christian Clauss Date: Wed, 30 Sep 2020 19:26:38 +0200 Subject: [PATCH 0817/1071] Travis CI: Remove redundant tests (#2523) * Travis CI: Remove redundant tests * fixup! before_script * updating DIRECTORY.md Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> --- .travis.yml | 15 +++++---------- DIRECTORY.md | 6 ++++++ requirements.txt | 6 ------ 3 files changed, 11 insertions(+), 16 deletions(-) diff --git a/.travis.yml b/.travis.yml index 3f1343bfa713..bda0fc31ca5d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,26 +4,21 @@ language: python python: 3.8 cache: pip before_install: pip install --upgrade pip setuptools six -install: pip install black flake8 jobs: include: - name: Build - before_script: - - black --check . || true - - flake8 --ignore=E203,W503 --max-complexity=25 --max-line-length=88 --statistics --count . - - scripts/validate_filenames.py # no uppercase, no spaces, in a directory - - pip install -r requirements.txt # fast fail on black, flake8, validate_filenames + install: pip install pytest-cov -r requirements.txt script: - - mypy --ignore-missing-imports . || true # https://github.com/python/mypy/issues/7907 - pytest --doctest-modules --ignore=project_euler/ --durations=10 --cov-report=term-missing:skip-covered --cov=. . - name: Project Euler - before_script: + install: - pip install pytest-cov pytest-subtests + before_script: - pytest --tb=no --no-summary --capture=no project_euler/validate_solutions.py || true # fail fast on wrong solution script: - pytest --doctest-modules --durations=10 --cov-report=term-missing:skip-covered --cov=project_euler/ project_euler/ after_success: - scripts/build_directory_md.py 2>&1 | tee DIRECTORY.md notifications: - webhooks: https://www.travisbuddy.com/ - on_success: never + webhooks: https://www.travisbuddy.com/ + on_success: never diff --git a/DIRECTORY.md b/DIRECTORY.md index 3227711c7853..37eaadb89786 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -190,6 +190,8 @@ ## Divide And Conquer * [Closest Pair Of Points](https://github.com/TheAlgorithms/Python/blob/master/divide_and_conquer/closest_pair_of_points.py) * [Convex Hull](https://github.com/TheAlgorithms/Python/blob/master/divide_and_conquer/convex_hull.py) + * [Heaps Algorithm](https://github.com/TheAlgorithms/Python/blob/master/divide_and_conquer/heaps_algorithm.py) + * [Heaps Algorithm Iterative](https://github.com/TheAlgorithms/Python/blob/master/divide_and_conquer/heaps_algorithm_iterative.py) * [Inversions](https://github.com/TheAlgorithms/Python/blob/master/divide_and_conquer/inversions.py) * [Max Subarray Sum](https://github.com/TheAlgorithms/Python/blob/master/divide_and_conquer/max_subarray_sum.py) * [Mergesort](https://github.com/TheAlgorithms/Python/blob/master/divide_and_conquer/mergesort.py) @@ -244,6 +246,7 @@ ## Graphics * [Bezier Curve](https://github.com/TheAlgorithms/Python/blob/master/graphics/bezier_curve.py) + * [Vector3 For 2D Rendering](https://github.com/TheAlgorithms/Python/blob/master/graphics/vector3_for_2d_rendering.py) ## Graphs * [A Star](https://github.com/TheAlgorithms/Python/blob/master/graphs/a_star.py) @@ -319,6 +322,7 @@ * [Data Transformations](https://github.com/TheAlgorithms/Python/blob/master/machine_learning/data_transformations.py) * [Decision Tree](https://github.com/TheAlgorithms/Python/blob/master/machine_learning/decision_tree.py) * [Gaussian Naive Bayes](https://github.com/TheAlgorithms/Python/blob/master/machine_learning/gaussian_naive_bayes.py) + * [Gradient Boosting Regressor](https://github.com/TheAlgorithms/Python/blob/master/machine_learning/gradient_boosting_regressor.py) * [Gradient Descent](https://github.com/TheAlgorithms/Python/blob/master/machine_learning/gradient_descent.py) * [K Means Clust](https://github.com/TheAlgorithms/Python/blob/master/machine_learning/k_means_clust.py) * [K Nearest Neighbours](https://github.com/TheAlgorithms/Python/blob/master/machine_learning/k_nearest_neighbours.py) @@ -650,6 +654,7 @@ * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_97/sol1.py) * Problem 99 * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_99/sol1.py) + * [Validate Solutions](https://github.com/TheAlgorithms/Python/blob/master/project_euler/validate_solutions.py) ## Scheduling * [First Come First Served](https://github.com/TheAlgorithms/Python/blob/master/scheduling/first_come_first_served.py) @@ -753,6 +758,7 @@ * [Fetch Jobs](https://github.com/TheAlgorithms/Python/blob/master/web_programming/fetch_jobs.py) * [Get Imdb Top 250 Movies Csv](https://github.com/TheAlgorithms/Python/blob/master/web_programming/get_imdb_top_250_movies_csv.py) * [Get Imdbtop](https://github.com/TheAlgorithms/Python/blob/master/web_programming/get_imdbtop.py) + * [Instagram Crawler](https://github.com/TheAlgorithms/Python/blob/master/web_programming/instagram_crawler.py) * [Recaptcha Verification](https://github.com/TheAlgorithms/Python/blob/master/web_programming/recaptcha_verification.py) * [Slack Message](https://github.com/TheAlgorithms/Python/blob/master/web_programming/slack_message.py) * [World Covid19 Stats](https://github.com/TheAlgorithms/Python/blob/master/web_programming/world_covid19_stats.py) diff --git a/requirements.txt b/requirements.txt index cb38123dc026..31dc586c29db 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,18 +1,12 @@ beautifulsoup4 -black fake_useragent -flake8 keras lxml matplotlib -mypy numpy opencv-python pandas pillow -pre-commit -pytest -pytest-cov requests scikit-fuzzy sklearn From 2fa009aa530ee1c243090c31b69bbf7effc754e2 Mon Sep 17 00:00:00 2001 From: Du Yuanchao Date: Thu, 1 Oct 2020 08:53:42 +0800 Subject: [PATCH 0818/1071] Fix bucket sort (#2494) * fixed bucket sort * delete blank line --- sorts/bucket_sort.py | 34 ++++++++++++++++------------------ 1 file changed, 16 insertions(+), 18 deletions(-) diff --git a/sorts/bucket_sort.py b/sorts/bucket_sort.py index 178b4f664480..a0566be662e3 100644 --- a/sorts/bucket_sort.py +++ b/sorts/bucket_sort.py @@ -27,43 +27,41 @@ Source: https://en.wikipedia.org/wiki/Bucket_sort """ -DEFAULT_BUCKET_SIZE = 5 -def bucket_sort(my_list: list, bucket_size: int = DEFAULT_BUCKET_SIZE) -> list: +def bucket_sort(my_list: list) -> list: """ >>> data = [-1, 2, -5, 0] >>> bucket_sort(data) == sorted(data) True - >>> data = [9, 8, 7, 6, -12] >>> bucket_sort(data) == sorted(data) True - >>> data = [.4, 1.2, .1, .2, -.9] >>> bucket_sort(data) == sorted(data) True - - >>> bucket_sort([]) - Traceback (most recent call last): - ... - Exception: Please add some elements in the array. + >>> bucket_sort([]) == sorted([]) + True + >>> import random + >>> collection = random.sample(range(-50, 50), 50) + >>> bucket_sort(collection) == sorted(collection) + True """ if len(my_list) == 0: - raise Exception("Please add some elements in the array.") - - min_value, max_value = (min(my_list), max(my_list)) - bucket_count = (max_value - min_value) // bucket_size + 1 - buckets = [[] for _ in range(int(bucket_count))] + return [] + min_value, max_value = min(my_list), max(my_list) + bucket_count = int(max_value - min_value) + 1 + buckets = [[] for _ in range(bucket_count)] for i in range(len(my_list)): - buckets[int((my_list[i] - min_value) // bucket_size)].append(my_list[i]) + buckets[(int(my_list[i] - min_value) // bucket_count)].append(my_list[i]) - return sorted( - buckets[i][j] for i in range(len(buckets)) for j in range(len(buckets[i])) - ) + return [v for bucket in buckets for v in sorted(bucket)] if __name__ == "__main__": + from doctest import testmod + + testmod() assert bucket_sort([4, 5, 3, 2, 1]) == [1, 2, 3, 4, 5] assert bucket_sort([0, 1, -10, 15, 2, -2]) == [-10, -2, 0, 1, 2, 15] From 2388bf4e17935a9860319831473e6a2e0f801b71 Mon Sep 17 00:00:00 2001 From: Eugeniy Orlov Date: Thu, 1 Oct 2020 04:04:31 +0300 Subject: [PATCH 0819/1071] Add type hints to strings/min_cost_string_conversion.py (#2337) * done * add types for local variables * Revert "add types for local variables" This reverts commit 971c15673b2f0301f00b4464dfcd913a4dbb3b78. * rename variables * Update strings/min_cost_string_conversion.py Co-authored-by: Christian Clauss * rename strings * use flake8 * Update strings/min_cost_string_conversion.py Co-authored-by: Christian Clauss --- strings/min_cost_string_conversion.py | 86 +++++++++++++++------------ 1 file changed, 49 insertions(+), 37 deletions(-) diff --git a/strings/min_cost_string_conversion.py b/strings/min_cost_string_conversion.py index 34b42f3f0f64..e990aaa2679b 100644 --- a/strings/min_cost_string_conversion.py +++ b/strings/min_cost_string_conversion.py @@ -1,55 +1,67 @@ +from typing import List, Tuple + """ Algorithm for calculating the most cost-efficient sequence for converting one string into another. The only allowed operations are ----Copy character with cost cC ----Replace character with cost cR ----Delete character with cost cD ----Insert character with cost cI +--- Cost to copy a character is copy_cost +--- Cost to replace a character is replace_cost +--- Cost to delete a character is delete_cost +--- Cost to insert a character is insert_cost """ -def compute_transform_tables(X, Y, cC, cR, cD, cI): - X = list(X) - Y = list(Y) - m = len(X) - n = len(Y) - - costs = [[0 for _ in range(n + 1)] for _ in range(m + 1)] - ops = [[0 for _ in range(n + 1)] for _ in range(m + 1)] - - for i in range(1, m + 1): - costs[i][0] = i * cD - ops[i][0] = "D%c" % X[i - 1] - - for i in range(1, n + 1): - costs[0][i] = i * cI - ops[0][i] = "I%c" % Y[i - 1] - - for i in range(1, m + 1): - for j in range(1, n + 1): - if X[i - 1] == Y[j - 1]: - costs[i][j] = costs[i - 1][j - 1] + cC - ops[i][j] = "C%c" % X[i - 1] +def compute_transform_tables( + source_string: str, + destination_string: str, + copy_cost: int, + replace_cost: int, + delete_cost: int, + insert_cost: int, +) -> Tuple[List[int], List[str]]: + source_seq = list(source_string) + destination_seq = list(destination_string) + len_source_seq = len(source_seq) + len_destination_seq = len(destination_seq) + + costs = [ + [0 for _ in range(len_destination_seq + 1)] for _ in range(len_source_seq + 1) + ] + ops = [ + [0 for _ in range(len_destination_seq + 1)] for _ in range(len_source_seq + 1) + ] + + for i in range(1, len_source_seq + 1): + costs[i][0] = i * delete_cost + ops[i][0] = "D%c" % source_seq[i - 1] + + for i in range(1, len_destination_seq + 1): + costs[0][i] = i * insert_cost + ops[0][i] = "I%c" % destination_seq[i - 1] + + for i in range(1, len_source_seq + 1): + for j in range(1, len_destination_seq + 1): + if source_seq[i - 1] == destination_seq[j - 1]: + costs[i][j] = costs[i - 1][j - 1] + copy_cost + ops[i][j] = "C%c" % source_seq[i - 1] else: - costs[i][j] = costs[i - 1][j - 1] + cR - ops[i][j] = "R%c" % X[i - 1] + str(Y[j - 1]) + costs[i][j] = costs[i - 1][j - 1] + replace_cost + ops[i][j] = "R%c" % source_seq[i - 1] + str(destination_seq[j - 1]) - if costs[i - 1][j] + cD < costs[i][j]: - costs[i][j] = costs[i - 1][j] + cD - ops[i][j] = "D%c" % X[i - 1] + if costs[i - 1][j] + delete_cost < costs[i][j]: + costs[i][j] = costs[i - 1][j] + delete_cost + ops[i][j] = "D%c" % source_seq[i - 1] - if costs[i][j - 1] + cI < costs[i][j]: - costs[i][j] = costs[i][j - 1] + cI - ops[i][j] = "I%c" % Y[j - 1] + if costs[i][j - 1] + insert_cost < costs[i][j]: + costs[i][j] = costs[i][j - 1] + insert_cost + ops[i][j] = "I%c" % destination_seq[j - 1] return costs, ops -def assemble_transformation(ops, i, j): +def assemble_transformation(ops: List[str], i: int, j: int) -> List[str]: if i == 0 and j == 0: - seq = [] - return seq + return [] else: if ops[i][j][0] == "C" or ops[i][j][0] == "R": seq = assemble_transformation(ops, i - 1, j - 1) From 9b3f7c36d0e116fac5df72fa35aa2fe74bb1b927 Mon Sep 17 00:00:00 2001 From: Du Yuanchao Date: Fri, 2 Oct 2020 13:55:58 +0800 Subject: [PATCH 0820/1071] Test random input for bubble sort (#2492) --- sorts/bubble_sort.py | 29 +++++++++++++++++++---------- 1 file changed, 19 insertions(+), 10 deletions(-) diff --git a/sorts/bubble_sort.py b/sorts/bubble_sort.py index eb356bc7dcad..d4f0d25ca77c 100644 --- a/sorts/bubble_sort.py +++ b/sorts/bubble_sort.py @@ -8,18 +8,24 @@ def bubble_sort(collection): Examples: >>> bubble_sort([0, 5, 2, 3, 2]) [0, 2, 2, 3, 5] - - >>> bubble_sort([]) - [] - - >>> bubble_sort([-2, -45, -5]) - [-45, -5, -2] - - >>> bubble_sort([-23, 0, 6, -4, 34]) - [-23, -4, 0, 6, 34] - + >>> bubble_sort([0, 5, 2, 3, 2]) == sorted([0, 5, 2, 3, 2]) + True + >>> bubble_sort([]) == sorted([]) + True + >>> bubble_sort([-2, -45, -5]) == sorted([-2, -45, -5]) + True >>> bubble_sort([-23, 0, 6, -4, 34]) == sorted([-23, 0, 6, -4, 34]) True + >>> bubble_sort(['d', 'a', 'b', 'e', 'c']) == sorted(['d', 'a', 'b', 'e', 'c']) + True + >>> import random + >>> collection = random.sample(range(-50, 50), 100) + >>> bubble_sort(collection) == sorted(collection) + True + >>> import string + >>> collection = random.choices(string.ascii_letters + string.digits, k=100) + >>> bubble_sort(collection) == sorted(collection) + True """ length = len(collection) for i in range(length - 1): @@ -34,8 +40,11 @@ def bubble_sort(collection): if __name__ == "__main__": + import doctest import time + doctest.testmod() + user_input = input("Enter numbers separated by a comma:").strip() unsorted = [int(item) for item in user_input.split(",")] start = time.process_time() From 5903948cf3806b6faea2962874c5130780edd324 Mon Sep 17 00:00:00 2001 From: Dmytro Litvinov Date: Sat, 3 Oct 2020 10:22:22 +0300 Subject: [PATCH 0821/1071] Fixes: #2404. Fix PIL DeprecationWarnings in pytest output (#2678) --- digital_image_processing/change_contrast.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/digital_image_processing/change_contrast.py b/digital_image_processing/change_contrast.py index c7da52298ae2..6a150400249f 100644 --- a/digital_image_processing/change_contrast.py +++ b/digital_image_processing/change_contrast.py @@ -11,18 +11,18 @@ from PIL import Image -def change_contrast(img: Image, level: float) -> Image: +def change_contrast(img: Image, level: int) -> Image: """ Function to change contrast """ factor = (259 * (level + 255)) / (255 * (259 - level)) - def contrast(c: int) -> float: + def contrast(c: int) -> int: """ Fundamental Transformation/Operation that'll be performed on every bit. """ - return 128 + factor * (c - 128) + return int(128 + factor * (c - 128)) return img.point(contrast) From 6a395456ee806c3897a702474ba4ffadff3beb04 Mon Sep 17 00:00:00 2001 From: Kushagra Bansal Date: Sat, 3 Oct 2020 14:08:56 +0530 Subject: [PATCH 0822/1071] Created problem_112.py in project_euler (#2532) * Add files via upload * Create __init__.py * Update and rename project_euler/problem_112.py to project_euler/problem_112/sol1.py * Update project_euler/problem_112/sol1.py Co-authored-by: Dhruv * Update sol1.py * Update sol1.py * Update project_euler/problem_112/sol1.py Co-authored-by: Du Yuanchao * Update project_euler/problem_112/__init__.py Co-authored-by: Du Yuanchao * Update __init__.py * Update __init__.py * Update __init__.py * delete __init__.py content Co-authored-by: Dhruv Co-authored-by: Du Yuanchao --- project_euler/problem_112/__init__.py | 0 project_euler/problem_112/sol1.py | 89 +++++++++++++++++++++++++++ 2 files changed, 89 insertions(+) create mode 100644 project_euler/problem_112/__init__.py create mode 100644 project_euler/problem_112/sol1.py diff --git a/project_euler/problem_112/__init__.py b/project_euler/problem_112/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/project_euler/problem_112/sol1.py b/project_euler/problem_112/sol1.py new file mode 100644 index 000000000000..d8cb334c9508 --- /dev/null +++ b/project_euler/problem_112/sol1.py @@ -0,0 +1,89 @@ +""" +Problem 112: https://projecteuler.net/problem=112 + +Working from left-to-right if no digit is exceeded by the digit to its left it is +called an increasing number; for example, 134468. +Similarly if no digit is exceeded by the digit to its right it is called a decreasing +number; for example, 66420. +We shall call a positive integer that is neither increasing nor decreasing a "bouncy" +number, for example, 155349. +Clearly there cannot be any bouncy numbers below one-hundred, but just over half of +the numbers below one-thousand (525) are bouncy. In fact, the least number for which +the proportion of bouncy numbers first reaches 50% is 538. +Surprisingly, bouncy numbers become more and more common and by the time we reach +21780 the proportion of bouncy numbers is equal to 90%. + +Find the least number for which the proportion of bouncy numbers is exactly 99%. +""" + + +def check_bouncy(n: int) -> bool: + """ + Returns True if number is bouncy, False otherwise + >>> check_bouncy(6789) + False + >>> check_bouncy(-12345) + False + >>> check_bouncy(0) + False + >>> check_bouncy(6.74) + Traceback (most recent call last): + ... + ValueError: check_bouncy() accepts only integer arguments + >>> check_bouncy(132475) + True + >>> check_bouncy(34) + False + >>> check_bouncy(341) + True + >>> check_bouncy(47) + False + >>> check_bouncy(-12.54) + Traceback (most recent call last): + ... + ValueError: check_bouncy() accepts only integer arguments + >>> check_bouncy(-6548) + True + """ + if not isinstance(n, int): + raise ValueError("check_bouncy() accepts only integer arguments") + return "".join(sorted(str(n))) != str(n) and "".join(sorted(str(n)))[::-1] != str(n) + + +def solution(percent: float = 99) -> int: + """ + Returns the least number for which the proportion of bouncy numbers is + exactly 'percent' + >>> solution(50) + 538 + >>> solution(90) + 21780 + >>> solution(80) + 4770 + >>> solution(105) + Traceback (most recent call last): + ... + ValueError: solution() only accepts values from 0 to 100 + >>> solution(100.011) + Traceback (most recent call last): + ... + ValueError: solution() only accepts values from 0 to 100 + """ + if not 0 < percent < 100: + raise ValueError("solution() only accepts values from 0 to 100") + bouncy_num = 0 + num = 1 + + while True: + if check_bouncy(num): + bouncy_num += 1 + if (bouncy_num / num) * 100 >= percent: + return num + num += 1 + + +if __name__ == "__main__": + from doctest import testmod + + testmod() + print(f"{solution(99)}") From 43f92490fe2edd92d09887eb732eaf6ac5ef698e Mon Sep 17 00:00:00 2001 From: Du Yuanchao Date: Sat, 3 Oct 2020 23:19:08 +0800 Subject: [PATCH 0823/1071] Update insert sort (#2493) * delete duplicate file update insert sort * rename * fixed error * using enumerate() --- sorts/i_sort.py | 21 -------------------- sorts/insertion_sort.py | 43 ++++++++++++++++++++++++----------------- 2 files changed, 25 insertions(+), 39 deletions(-) delete mode 100644 sorts/i_sort.py diff --git a/sorts/i_sort.py b/sorts/i_sort.py deleted file mode 100644 index f6100a8d0819..000000000000 --- a/sorts/i_sort.py +++ /dev/null @@ -1,21 +0,0 @@ -def insertionSort(arr): - """ - >>> a = arr[:] - >>> insertionSort(a) - >>> a == sorted(a) - True - """ - for i in range(1, len(arr)): - key = arr[i] - j = i - 1 - while j >= 0 and key < arr[j]: - arr[j + 1] = arr[j] - j -= 1 - arr[j + 1] = key - - -arr = [12, 11, 13, 5, 6] -insertionSort(arr) -print("Sorted array is:") -for i in range(len(arr)): - print("%d" % arr[i]) diff --git a/sorts/insertion_sort.py b/sorts/insertion_sort.py index 28458ad1b86d..6d5bb2b46013 100644 --- a/sorts/insertion_sort.py +++ b/sorts/insertion_sort.py @@ -24,30 +24,37 @@ def insertion_sort(collection: list) -> list: Examples: >>> insertion_sort([0, 5, 3, 2, 2]) [0, 2, 2, 3, 5] - - >>> insertion_sort([]) - [] - - >>> insertion_sort([-2, -5, -45]) - [-45, -5, -2] + >>> insertion_sort([]) == sorted([]) + True + >>> insertion_sort([-2, -5, -45]) == sorted([-2, -5, -45]) + True + >>> insertion_sort(['d', 'a', 'b', 'e', 'c']) == sorted(['d', 'a', 'b', 'e', 'c']) + True + >>> import random + >>> collection = random.sample(range(-50, 50), 100) + >>> insertion_sort(collection) == sorted(collection) + True + >>> import string + >>> collection = random.choices(string.ascii_letters + string.digits, k=100) + >>> insertion_sort(collection) == sorted(collection) + True """ - for loop_index in range(1, len(collection)): - insertion_index = loop_index - while ( - insertion_index > 0 - and collection[insertion_index - 1] > collection[insertion_index] - ): - collection[insertion_index], collection[insertion_index - 1] = ( - collection[insertion_index - 1], - collection[insertion_index], - ) - insertion_index -= 1 - + for insert_index, insert_value in enumerate(collection[1:]): + temp_index = insert_index + while insert_index >= 0 and insert_value < collection[insert_index]: + collection[insert_index + 1] = collection[insert_index] + insert_index -= 1 + if insert_index != temp_index: + collection[insert_index + 1] = insert_value return collection if __name__ == "__main__": + from doctest import testmod + + testmod() + user_input = input("Enter numbers separated by a comma:\n").strip() unsorted = [int(item) for item in user_input.split(",")] print(f"{insertion_sort(unsorted) = }") From e9865ae611bd4d7398a8bad5055e9f87b9da22a4 Mon Sep 17 00:00:00 2001 From: Sabari Ganesh <64348740+SabariGanesh-K@users.noreply.github.com> Date: Sat, 3 Oct 2020 22:02:58 +0530 Subject: [PATCH 0824/1071] Update temperature_conversions.py (#2522) * Update temperature_conversions.py Added Reaumur scale unit conversions. * Update temperature_conversions.py * Update temperature_conversions.py --- conversions/temperature_conversions.py | 76 ++++++++++++++++++++++++++ 1 file changed, 76 insertions(+) diff --git a/conversions/temperature_conversions.py b/conversions/temperature_conversions.py index 43d682a70a2e..167c9dc64727 100644 --- a/conversions/temperature_conversions.py +++ b/conversions/temperature_conversions.py @@ -303,6 +303,82 @@ def rankine_to_kelvin(rankine: float, ndigits: int = 2) -> float: return round((float(rankine) * 5 / 9), ndigits) +def reaumur_to_kelvin(reaumur: float, ndigits: int = 2) -> float: + """ + Convert a given value from reaumur to Kelvin and round it to 2 decimal places. + Reference:- http://www.csgnetwork.com/temp2conv.html + + >>> reaumur_to_kelvin(0) + 273.15 + >>> reaumur_to_kelvin(20.0) + 298.15 + >>> reaumur_to_kelvin(40) + 323.15 + >>> reaumur_to_kelvin("reaumur") + Traceback (most recent call last): + ... + ValueError: could not convert string to float: 'reaumur' + """ + return round((float(reaumur) * 1.25 + 273.15), ndigits) + + +def reaumur_to_fahrenheit(reaumur: float, ndigits: int = 2) -> float: + """ + Convert a given value from reaumur to fahrenheit and round it to 2 decimal places. + Reference:- http://www.csgnetwork.com/temp2conv.html + + >>> reaumur_to_fahrenheit(0) + 32.0 + >>> reaumur_to_fahrenheit(20.0) + 77.0 + >>> reaumur_to_fahrenheit(40) + 122.0 + >>> reaumur_to_fahrenheit("reaumur") + Traceback (most recent call last): + ... + ValueError: could not convert string to float: 'reaumur' + """ + return round((float(reaumur) * 2.25 + 32), ndigits) + + +def reaumur_to_celsius(reaumur: float, ndigits: int = 2) -> float: + """ + Convert a given value from reaumur to celsius and round it to 2 decimal places. + Reference:- http://www.csgnetwork.com/temp2conv.html + + >>> reaumur_to_celsius(0) + 0.0 + >>> reaumur_to_celsius(20.0) + 25.0 + >>> reaumur_to_celsius(40) + 50.0 + >>> reaumur_to_celsius("reaumur") + Traceback (most recent call last): + ... + ValueError: could not convert string to float: 'reaumur' + """ + return round((float(reaumur) * 1.25), ndigits) + + +def reaumur_to_rankine(reaumur: float, ndigits: int = 2) -> float: + """ + Convert a given value from reaumur to rankine and round it to 2 decimal places. + Reference:- http://www.csgnetwork.com/temp2conv.html + + >>> reaumur_to_rankine(0) + 491.67 + >>> reaumur_to_rankine(20.0) + 536.67 + >>> reaumur_to_rankine(40) + 581.67 + >>> reaumur_to_rankine("reaumur") + Traceback (most recent call last): + ... + ValueError: could not convert string to float: 'reaumur' + """ + return round((float(reaumur) * 2.25 + 32 + 459.67), ndigits) + + if __name__ == "__main__": import doctest From 3b0169549ebe23ab7b681601a445ee8e2b87dd55 Mon Sep 17 00:00:00 2001 From: Richard Wheatley Date: Sun, 4 Oct 2020 15:20:36 +0100 Subject: [PATCH 0825/1071] Create auto_close_empty_issues.yml (#2756) * Create auto_close_empty_issues.yml * updating DIRECTORY.md Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> --- .github/workflows/auto_close_empty_issues.yml | 20 +++++++++++++++++++ DIRECTORY.md | 3 ++- 2 files changed, 22 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/auto_close_empty_issues.yml diff --git a/.github/workflows/auto_close_empty_issues.yml b/.github/workflows/auto_close_empty_issues.yml new file mode 100644 index 000000000000..bd2e72791478 --- /dev/null +++ b/.github/workflows/auto_close_empty_issues.yml @@ -0,0 +1,20 @@ +# GitHub Action that uses close-issue auto-close empty issues after they are opened. +# If the issue body text is empty the Action auto-closes it and sends a notification. +# Otherwise if the issue body is not empty, it does nothing and the issue remains open. +# https://github.com/marketplace/actions/close-issue + +name: auto_close_empty_issues +on: + issues: + types: [opened] +jobs: + check-issue-body-not-empty: + runs-on: ubuntu-latest + steps: + - if: github.event.issue.body == 0 + name: Close Issue + uses: peter-evans/close-issue@v1 + with: + comment: | + Issue body must contain content. + Auto-closing this issue. diff --git a/DIRECTORY.md b/DIRECTORY.md index 37eaadb89786..1dd1289ab36c 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -547,6 +547,8 @@ * Problem 11 * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_11/sol1.py) * [Sol2](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_11/sol2.py) + * Problem 112 + * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_112/sol1.py) * Problem 12 * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_12/sol1.py) * [Sol2](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_12/sol2.py) @@ -691,7 +693,6 @@ * [External Sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/external_sort.py) * [Gnome Sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/gnome_sort.py) * [Heap Sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/heap_sort.py) - * [I Sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/i_sort.py) * [Insertion Sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/insertion_sort.py) * [Iterative Merge Sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/iterative_merge_sort.py) * [Merge Insertion Sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/merge_insertion_sort.py) From fb9b9ecccf4fa5043af1a3168c5f28e9f2c3f455 Mon Sep 17 00:00:00 2001 From: Sabari Ganesh <64348740+SabariGanesh-K@users.noreply.github.com> Date: Sun, 4 Oct 2020 19:50:47 +0530 Subject: [PATCH 0826/1071] Update area.py (#2524) * Update area.py Added Area for Rhombhus * Update area.py Added rhombhus area. And fixed some gaps error. * Update area.py Added Rhombhus area. * Update area.py Fixed suggested changes --- maths/area.py | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/maths/area.py b/maths/area.py index 0cbfd7957fa6..393d45faa880 100644 --- a/maths/area.py +++ b/maths/area.py @@ -186,6 +186,30 @@ def area_circle(radius: float) -> float: return pi * radius ** 2 +def area_rhombus(diagonal_1: float, diagonal_2: float) -> float: + """ + Calculate the area of a rhombus + + >>> area_rhombus(10, 20) + 100.0 + >>> area_rhombus(-1, -2) + Traceback (most recent call last): + ... + ValueError: area_rhombus() only accepts non-negative values + >>> area_rhombus(1, -2) + Traceback (most recent call last): + ... + ValueError: area_rhombus() only accepts non-negative values + >>> area_rhombus(-1, 2) + Traceback (most recent call last): + ... + ValueError: area_rhombus() only accepts non-negative values + """ + if diagonal_1 < 0 or diagonal_2 < 0: + raise ValueError("area_rhombus() only accepts non-negative values") + return 1 / 2 * diagonal_1 * diagonal_2 + + def main(): print("Areas of various geometric shapes: \n") print(f"Rectangle: {area_rectangle(10, 20)}") @@ -197,6 +221,7 @@ def main(): print("\nSurface Areas of various geometric shapes: \n") print(f"Cube: {surface_area_cube(20)}") print(f"Sphere: {surface_area_sphere(20)}") + print(f"Rhombus: {area_rhombus(10, 20)}") if __name__ == "__main__": From b934da4516a18814e2645413a20eedb3ac03a170 Mon Sep 17 00:00:00 2001 From: Dhruv Date: Sun, 4 Oct 2020 21:16:13 +0530 Subject: [PATCH 0827/1071] Fix pre-commit error in GitHub action file (#2765) --- .github/workflows/auto_close_empty_issues.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/auto_close_empty_issues.yml b/.github/workflows/auto_close_empty_issues.yml index bd2e72791478..a6334d6ade32 100644 --- a/.github/workflows/auto_close_empty_issues.yml +++ b/.github/workflows/auto_close_empty_issues.yml @@ -1,10 +1,10 @@ # GitHub Action that uses close-issue auto-close empty issues after they are opened. -# If the issue body text is empty the Action auto-closes it and sends a notification. +# If the issue body text is empty the Action auto-closes it and sends a notification. # Otherwise if the issue body is not empty, it does nothing and the issue remains open. # https://github.com/marketplace/actions/close-issue name: auto_close_empty_issues -on: +on: issues: types: [opened] jobs: From e040ad2a01d2c7d7524682fb27da99aea663972b Mon Sep 17 00:00:00 2001 From: Iqrar Agalosi Nureyza Date: Mon, 5 Oct 2020 09:57:09 +0700 Subject: [PATCH 0828/1071] Add a solution for Project Euler 49 (#2702) * added doctests in modular_exponential.py * added doctests in modular_exponential.py * added URL link * updating DIRECTORY.md * Add problem 49 solution * updating DIRECTORY.md * Fix several mistakes These fixes are intended to follow the CONTRIBUTING.md * Move the import statements lower * Update project_euler/problem_49/sol1.py Co-authored-by: Dhruv Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> Co-authored-by: Dhruv --- DIRECTORY.md | 2 + project_euler/problem_49/__init__.py | 0 project_euler/problem_49/sol1.py | 139 +++++++++++++++++++++++++++ 3 files changed, 141 insertions(+) create mode 100644 project_euler/problem_49/__init__.py create mode 100644 project_euler/problem_49/sol1.py diff --git a/DIRECTORY.md b/DIRECTORY.md index 1dd1289ab36c..a8d00f6cb724 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -633,6 +633,8 @@ * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_47/sol1.py) * Problem 48 * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_48/sol1.py) + * Problem 49 + * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_49/sol1.py) * Problem 52 * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_52/sol1.py) * Problem 53 diff --git a/project_euler/problem_49/__init__.py b/project_euler/problem_49/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/project_euler/problem_49/sol1.py b/project_euler/problem_49/sol1.py new file mode 100644 index 000000000000..6c3d69ad0d11 --- /dev/null +++ b/project_euler/problem_49/sol1.py @@ -0,0 +1,139 @@ +""" +Prime permutations + +Problem 49 + +The arithmetic sequence, 1487, 4817, 8147, in which each of +the terms increases by 3330, is unusual in two ways: +(i) each of the three terms are prime, +(ii) each of the 4-digit numbers are permutations of one another. + +There are no arithmetic sequences made up of three 1-, 2-, or 3-digit primes, +exhibiting this property, but there is one other 4-digit increasing sequence. + +What 12-digit number do you form by concatenating the three terms in this sequence? + +Solution: + +First, we need to generate all 4 digits prime numbers. Then greedy +all of them and use permutation to form new numbers. Use binary search +to check if the permutated numbers is in our prime list and include +them in a candidate list. + +After that, bruteforce all passed candidates sequences using +3 nested loops since we know the answer will be 12 digits. +The bruteforce of this solution will be about 1 sec. +""" + +from itertools import permutations +from math import floor, sqrt + + +def is_prime(number: int) -> bool: + """ + function to check whether the number is prime or not. + >>> is_prime(2) + True + >>> is_prime(6) + False + >>> is_prime(1) + False + >>> is_prime(-800) + False + >>> is_prime(104729) + True + """ + + if number < 2: + return False + + for i in range(2, floor(sqrt(number)) + 1): + if number % i == 0: + return False + + return True + + +def search(target: int, prime_list: list) -> bool: + """ + function to search a number in a list using Binary Search. + >>> search(3, [1, 2, 3]) + True + >>> search(4, [1, 2, 3]) + False + >>> search(101, list(range(-100, 100))) + False + """ + + left, right = 0, len(prime_list) - 1 + while left <= right: + middle = (left + right) // 2 + if prime_list[middle] == target: + return True + elif prime_list[middle] < target: + left = middle + 1 + else: + right = middle - 1 + + return False + + +def solution(): + """ + Return the solution of the problem. + >>> solution() + 296962999629 + """ + prime_list = [n for n in range(1001, 10000, 2) if is_prime(n)] + candidates = [] + + for number in prime_list: + tmp_numbers = [] + + for prime_member in permutations(list(str(number))): + prime = int("".join(prime_member)) + + if prime % 2 == 0: + continue + + if search(prime, prime_list): + tmp_numbers.append(prime) + + tmp_numbers.sort() + if len(tmp_numbers) >= 3: + candidates.append(tmp_numbers) + + passed = [] + for candidate in candidates: + length = len(candidate) + found = False + + for i in range(length): + for j in range(i + 1, length): + for k in range(j + 1, length): + if ( + abs(candidate[i] - candidate[j]) + == abs(candidate[j] - candidate[k]) + and len(set([candidate[i], candidate[j], candidate[k]])) == 3 + ): + passed.append( + sorted([candidate[i], candidate[j], candidate[k]]) + ) + found = True + + if found: + break + if found: + break + if found: + break + + answer = set() + for seq in passed: + answer.add("".join([str(i) for i in seq])) + + return max([int(x) for x in answer]) + + +if __name__ == "__main__": + print(solution()) From 437c725e64bf18b103298b9ee0eb21373c397e0c Mon Sep 17 00:00:00 2001 From: Du Yuanchao Date: Mon, 5 Oct 2020 11:47:46 +0800 Subject: [PATCH 0829/1071] Fixed allocation_number (#2768) * fixed allocation_number * fixed pre-commit * fixed line too long * fixed bug --- maths/allocation_number.py | 55 ++++++++++++++++---------------------- 1 file changed, 23 insertions(+), 32 deletions(-) diff --git a/maths/allocation_number.py b/maths/allocation_number.py index 4e74bb2e6950..d419e74d01ff 100644 --- a/maths/allocation_number.py +++ b/maths/allocation_number.py @@ -1,37 +1,28 @@ +""" +In a multi-threaded download, this algorithm could be used to provide +each worker thread with a block of non-overlapping bytes to download. +For example: + for i in allocation_list: + requests.get(url,headers={'Range':f'bytes={i}'}) +""" from __future__ import annotations def allocation_num(number_of_bytes: int, partitions: int) -> list[str]: """ Divide a number of bytes into x partitions. + :param number_of_bytes: the total of bytes. + :param partitions: the number of partition need to be allocated. + :return: list of bytes to be assigned to each worker thread - In a multi-threaded download, this algorithm could be used to provide - each worker thread with a block of non-overlapping bytes to download. - For example: - for i in allocation_list: - requests.get(url,headers={'Range':f'bytes={i}'}) - - parameter - ------------ - : param number_of_bytes - : param partitions - - return - ------------ - : return: list of bytes to be assigned to each worker thread - - Examples: - ------------ >>> allocation_num(16647, 4) - ['0-4161', '4162-8322', '8323-12483', '12484-16647'] - >>> allocation_num(888, 888) - Traceback (most recent call last): - ... - ValueError: partitions can not >= number_of_bytes! + ['1-4161', '4162-8322', '8323-12483', '12484-16647'] + >>> allocation_num(50000, 5) + ['1-10000', '10001-20000', '20001-30000', '30001-40000', '40001-50000'] >>> allocation_num(888, 999) Traceback (most recent call last): ... - ValueError: partitions can not >= number_of_bytes! + ValueError: partitions can not > number_of_bytes! >>> allocation_num(888, -4) Traceback (most recent call last): ... @@ -39,16 +30,16 @@ def allocation_num(number_of_bytes: int, partitions: int) -> list[str]: """ if partitions <= 0: raise ValueError("partitions must be a positive number!") - if partitions >= number_of_bytes: - raise ValueError("partitions can not >= number_of_bytes!") + if partitions > number_of_bytes: + raise ValueError("partitions can not > number_of_bytes!") bytes_per_partition = number_of_bytes // partitions - allocation_list = [f"0-{bytes_per_partition}"] - for i in range(1, partitions - 1): - length = f"{bytes_per_partition * i + 1}-{bytes_per_partition * (i + 1)}" - allocation_list.append(length) - allocation_list.append( - f"{(bytes_per_partition * (partitions - 1)) + 1}-" f"{number_of_bytes}" - ) + allocation_list = [] + for i in range(partitions): + start_bytes = i * bytes_per_partition + 1 + end_bytes = ( + number_of_bytes if i == partitions - 1 else (i + 1) * bytes_per_partition + ) + allocation_list.append(f"{start_bytes}-{end_bytes}") return allocation_list From 477b2c24b83517652cef1e45e6c600e3328c0824 Mon Sep 17 00:00:00 2001 From: Sherman Hui <11592023+shermanhui@users.noreply.github.com> Date: Mon, 5 Oct 2020 04:08:57 -0700 Subject: [PATCH 0830/1071] Hacktoberfest: Update Linked List - `print_reverse` method (#2792) * chore: update print_reverse helper method Use a generator expression instead of slicing `elements_list` to improve the space and time complexity of `make_linked_list` to O(1) space and O(n) time by avoiding the creation a shallow copy of `elements_list`. * fix: add type checking and argument typing Add argument typing to all methods in `print_reverse` Add doctest to helper function `make_linked_list` and basic edge case tests to `print_reverse` * test: add `print_reverse` test Fix doctest syntax and remove edge case tests that are covered by typed arguments. Add `print_reverse` test that expects the correct values are printed out by adding a `test_print_reverse_output` helper function. * format code Co-authored-by: shellhub --- data_structures/linked_list/print_reverse.py | 73 ++++++++++++-------- 1 file changed, 44 insertions(+), 29 deletions(-) diff --git a/data_structures/linked_list/print_reverse.py b/data_structures/linked_list/print_reverse.py index c3a72b6b7a23..c46f228e7260 100644 --- a/data_structures/linked_list/print_reverse.py +++ b/data_structures/linked_list/print_reverse.py @@ -1,4 +1,4 @@ -# Program to print the elements of a linked list in reverse +from typing import List class Node: @@ -8,48 +8,63 @@ def __init__(self, data=None): def __repr__(self): """Returns a visual representation of the node and all its following nodes.""" - string_rep = "" + string_rep = [] temp = self while temp: - string_rep += f"<{temp.data}> ---> " + string_rep.append(f"{temp.data}") temp = temp.next - string_rep += "" - return string_rep + return "->".join(string_rep) -def make_linked_list(elements_list): +def make_linked_list(elements_list: List): """Creates a Linked List from the elements of the given sequence - (list/tuple) and returns the head of the Linked List.""" - - # if elements_list is empty + (list/tuple) and returns the head of the Linked List. + >>> make_linked_list([]) + Traceback (most recent call last): + ... + Exception: The Elements List is empty + >>> make_linked_list([7]) + 7 + >>> make_linked_list(['abc']) + abc + >>> make_linked_list([7, 25]) + 7->25 + """ if not elements_list: raise Exception("The Elements List is empty") - # Set first element as Head - head = Node(elements_list[0]) - current = head - # Loop through elements from position 1 - for data in elements_list[1:]: - current.next = Node(data) + current = head = Node(elements_list[0]) + for i in range(1, len(elements_list)): + current.next = Node(elements_list[i]) current = current.next return head -def print_reverse(head_node): - """Prints the elements of the given Linked List in reverse order""" - - # If reached end of the List - if head_node is None: - return None - else: - # Recurse +def print_reverse(head_node: Node) -> None: + """Prints the elements of the given Linked List in reverse order + >>> print_reverse([]) + >>> linked_list = make_linked_list([69, 88, 73]) + >>> print_reverse(linked_list) + 73 + 88 + 69 + """ + if head_node is not None and isinstance(head_node, Node): print_reverse(head_node.next) print(head_node.data) -list_data = [14, 52, 14, 12, 43] -linked_list = make_linked_list(list_data) -print("Linked List:") -print(linked_list) -print("Elements in Reverse:") -print_reverse(linked_list) +def main(): + from doctest import testmod + + testmod() + + linked_list = make_linked_list([14, 52, 14, 12, 43]) + print("Linked List:") + print(linked_list) + print("Elements in Reverse:") + print_reverse(linked_list) + + +if __name__ == "__main__": + main() From 7df91e681a7270d9ff10e504abe81df5bdd34776 Mon Sep 17 00:00:00 2001 From: Dmytro Litvinov Date: Mon, 5 Oct 2020 20:44:35 +0300 Subject: [PATCH 0831/1071] Add type hints for searches/ternary_search.py (#2874) --- searches/ternary_search.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/searches/ternary_search.py b/searches/ternary_search.py index 6fdee58cf5dc..b01db3eb845f 100644 --- a/searches/ternary_search.py +++ b/searches/ternary_search.py @@ -7,6 +7,7 @@ Space Complexity : O(1) """ import sys +from typing import List # This is the precision for this function which can be altered. # It is recommended for users to keep this number greater than or equal to 10. @@ -14,14 +15,14 @@ # This is the linear search that will occur after the search space has become smaller. -def lin_search(left, right, A, target): +def lin_search(left: int, right: int, A: List[int], target: int): for i in range(left, right + 1): if A[i] == target: return i # This is the iterative method of the ternary search algorithm. -def ite_ternary_search(A, target): +def ite_ternary_search(A: List[int], target: int): left = 0 right = len(A) - 1 while True: @@ -51,7 +52,7 @@ def ite_ternary_search(A, target): # This is the recursive method of the ternary search algorithm. -def rec_ternary_search(left, right, A, target): +def rec_ternary_search(left: int, right: int, A: List[int], target: int): if left < right: if right - left < precision: @@ -77,7 +78,7 @@ def rec_ternary_search(left, right, A, target): # This function is to check if the array is sorted. -def __assert_sorted(collection): +def __assert_sorted(collection: List[int]) -> bool: if collection != sorted(collection): raise ValueError("Collection must be sorted") return True From edf2cd2b0c81898aa2c2c735eff39924b2e4aa9e Mon Sep 17 00:00:00 2001 From: Edward Nuno Date: Mon, 5 Oct 2020 19:42:16 -0700 Subject: [PATCH 0832/1071] Fix typehints in project_euler/problem01 (#2891) Squashed commit of the following: commit 6801d073b31bf702814861cd3b07b634ca295bfa Author: Archaengel Date: Mon Oct 5 16:40:10 2020 -0700 Fix typehints in project_euler/problem01 commit 29afc3af114abd1b99dc3f7c8fc99128229db131 Author: Archaengel Date: Mon Oct 5 15:06:34 2020 -0700 Add typehints and default argument for project_euler/problem_01 --- project_euler/problem_01/sol1.py | 2 +- project_euler/problem_01/sol2.py | 2 +- project_euler/problem_01/sol3.py | 2 +- project_euler/problem_01/sol4.py | 2 +- project_euler/problem_01/sol5.py | 2 +- project_euler/problem_01/sol6.py | 2 +- project_euler/problem_01/sol7.py | 2 +- 7 files changed, 7 insertions(+), 7 deletions(-) diff --git a/project_euler/problem_01/sol1.py b/project_euler/problem_01/sol1.py index e81156edaee4..2dbb60cdb16f 100644 --- a/project_euler/problem_01/sol1.py +++ b/project_euler/problem_01/sol1.py @@ -6,7 +6,7 @@ """ -def solution(n): +def solution(n: int = 1000) -> int: """Returns the sum of all the multiples of 3 or 5 below n. >>> solution(3) diff --git a/project_euler/problem_01/sol2.py b/project_euler/problem_01/sol2.py index 8041c7ffa589..212fd40562d1 100644 --- a/project_euler/problem_01/sol2.py +++ b/project_euler/problem_01/sol2.py @@ -6,7 +6,7 @@ """ -def solution(n): +def solution(n: int = 1000) -> int: """Returns the sum of all the multiples of 3 or 5 below n. >>> solution(3) diff --git a/project_euler/problem_01/sol3.py b/project_euler/problem_01/sol3.py index c0bcbc06ec83..faa505615924 100644 --- a/project_euler/problem_01/sol3.py +++ b/project_euler/problem_01/sol3.py @@ -6,7 +6,7 @@ """ -def solution(n): +def solution(n: int = 1000) -> int: """ This solution is based on the pattern that the successive numbers in the series follow: 0+3,+2,+1,+3,+1,+2,+3. diff --git a/project_euler/problem_01/sol4.py b/project_euler/problem_01/sol4.py index e01dc977d8cf..d5e86320da5e 100644 --- a/project_euler/problem_01/sol4.py +++ b/project_euler/problem_01/sol4.py @@ -6,7 +6,7 @@ """ -def solution(n): +def solution(n: int = 1000) -> int: """Returns the sum of all the multiples of 3 or 5 below n. >>> solution(3) diff --git a/project_euler/problem_01/sol5.py b/project_euler/problem_01/sol5.py index bd96d965f92d..eae62ef5c75c 100644 --- a/project_euler/problem_01/sol5.py +++ b/project_euler/problem_01/sol5.py @@ -8,7 +8,7 @@ """A straightforward pythonic solution using list comprehension""" -def solution(n): +def solution(n: int = 1000) -> int: """Returns the sum of all the multiples of 3 or 5 below n. >>> solution(3) diff --git a/project_euler/problem_01/sol6.py b/project_euler/problem_01/sol6.py index c9f94b9f77c8..ca08a4a639ab 100644 --- a/project_euler/problem_01/sol6.py +++ b/project_euler/problem_01/sol6.py @@ -6,7 +6,7 @@ """ -def solution(n): +def solution(n: int = 1000) -> int: """Returns the sum of all the multiples of 3 or 5 below n. >>> solution(3) diff --git a/project_euler/problem_01/sol7.py b/project_euler/problem_01/sol7.py index a0510b54c409..8e53d2a81fb9 100644 --- a/project_euler/problem_01/sol7.py +++ b/project_euler/problem_01/sol7.py @@ -6,7 +6,7 @@ """ -def solution(n): +def solution(n: int = 1000) -> int: """Returns the sum of all the multiples of 3 or 5 below n. >>> solution(3) From a56e548264353676a87e76581a1901f2eaa61389 Mon Sep 17 00:00:00 2001 From: Noah H Date: Mon, 5 Oct 2020 22:51:39 -0400 Subject: [PATCH 0833/1071] Bring problem_30 solution in line with project style guidelines (#2896) --- project_euler/problem_30/{soln.py => sol1.py} | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) rename project_euler/problem_30/{soln.py => sol1.py} (89%) diff --git a/project_euler/problem_30/soln.py b/project_euler/problem_30/sol1.py similarity index 89% rename from project_euler/problem_30/soln.py rename to project_euler/problem_30/sol1.py index 3ade82208344..c9f2d71965e3 100644 --- a/project_euler/problem_30/soln.py +++ b/project_euler/problem_30/sol1.py @@ -31,6 +31,9 @@ def digitsum(s: str) -> int: return i if i == int(s) else 0 +def solution() -> int: + return sum(digitsum(str(i)) for i in range(1000, 1000000)) + + if __name__ == "__main__": - count = sum(digitsum(str(i)) for i in range(1000, 1000000)) - print(count) # --> 443839 + print(solution()) From a8ad2d10b426f28a1c04ee8d53f43a9f9f968851 Mon Sep 17 00:00:00 2001 From: Utkarsh Chaudhary Date: Tue, 6 Oct 2020 08:41:15 +0530 Subject: [PATCH 0834/1071] Add Project Euler 120 solution (#2887) --- project_euler/problem_120/__init__.py | 0 project_euler/problem_120/sol1.py | 32 +++++++++++++++++++++++++++ 2 files changed, 32 insertions(+) create mode 100644 project_euler/problem_120/__init__.py create mode 100644 project_euler/problem_120/sol1.py diff --git a/project_euler/problem_120/__init__.py b/project_euler/problem_120/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/project_euler/problem_120/sol1.py b/project_euler/problem_120/sol1.py new file mode 100644 index 000000000000..0e6821214560 --- /dev/null +++ b/project_euler/problem_120/sol1.py @@ -0,0 +1,32 @@ +""" +Problem 120 Square remainders: https://projecteuler.net/problem=120 + +Description: + +Let r be the remainder when (a−1)^n + (a+1)^n is divided by a^2. +For example, if a = 7 and n = 3, then r = 42: 6^3 + 8^3 = 728 ≡ 42 mod 49. +And as n varies, so too will r, but for a = 7 it turns out that r_max = 42. +For 3 ≤ a ≤ 1000, find ∑ r_max. + +Solution: + +On expanding the terms, we get 2 if n is even and 2an if n is odd. +For maximizing the value, 2an < a*a => n <= (a - 1)/2 (integer division) +""" + + +def solution(n: int = 1000) -> int: + """ + Returns ∑ r_max for 3 <= a <= n as explained above + >>> solution(10) + 300 + >>> solution(100) + 330750 + >>> solution(1000) + 333082500 + """ + return sum(2 * a * ((a - 1) // 2) for a in range(3, n + 1)) + + +if __name__ == "__main__": + print(solution()) From f36a2f621ed3b580c32bd863c8a5d2aaffbe76d1 Mon Sep 17 00:00:00 2001 From: Edward Nuno Date: Mon, 5 Oct 2020 21:34:16 -0700 Subject: [PATCH 0835/1071] Hacktoberfest 2020: Apply style guidelines for Project Euler problem_02 (#2898) * Fix typehints in project_euler/problem01 Squashed commit of the following: commit 6801d073b31bf702814861cd3b07b634ca295bfa Author: Archaengel Date: Mon Oct 5 16:40:10 2020 -0700 Fix typehints in project_euler/problem01 commit 29afc3af114abd1b99dc3f7c8fc99128229db131 Author: Archaengel Date: Mon Oct 5 15:06:34 2020 -0700 Add typehints and default argument for project_euler/problem_01 * Add default args, typehints, and expand variable names for PE prob 02 --- project_euler/problem_02/sol1.py | 8 ++++---- project_euler/problem_02/sol2.py | 18 +++++++++--------- project_euler/problem_02/sol3.py | 2 +- project_euler/problem_02/sol4.py | 6 +++--- project_euler/problem_02/sol5.py | 20 ++++++++++---------- 5 files changed, 27 insertions(+), 27 deletions(-) diff --git a/project_euler/problem_02/sol1.py b/project_euler/problem_02/sol1.py index ec89ddaeb2b5..2acc93b0affc 100644 --- a/project_euler/problem_02/sol1.py +++ b/project_euler/problem_02/sol1.py @@ -11,7 +11,7 @@ """ -def solution(n): +def solution(n: int = 4000000) -> int: """Returns the sum of all fibonacci sequence even elements that are lower or equals to n. @@ -28,13 +28,13 @@ def solution(n): """ i = 1 j = 2 - sum = 0 + total = 0 while j <= n: if j % 2 == 0: - sum += j + total += j i, j = j, i + j - return sum + return total if __name__ == "__main__": diff --git a/project_euler/problem_02/sol2.py b/project_euler/problem_02/sol2.py index bc5040cc6b3b..01fc552b9b21 100644 --- a/project_euler/problem_02/sol2.py +++ b/project_euler/problem_02/sol2.py @@ -11,28 +11,28 @@ """ -def solution(n): +def solution(n: int = 4000000) -> int: """Returns the sum of all fibonacci sequence even elements that are lower or equals to n. >>> solution(10) - [2, 8] + 10 >>> solution(15) - [2, 8] + 10 >>> solution(2) - [2] + 2 >>> solution(1) - [] + 0 >>> solution(34) - [2, 8, 34] + 44 """ - ls = [] + even_fibs = [] a, b = 0, 1 while b <= n: if b % 2 == 0: - ls.append(b) + even_fibs.append(b) a, b = b, a + b - return ls + return sum(even_fibs) if __name__ == "__main__": diff --git a/project_euler/problem_02/sol3.py b/project_euler/problem_02/sol3.py index f29f21c287e5..53d8ca6f1b68 100644 --- a/project_euler/problem_02/sol3.py +++ b/project_euler/problem_02/sol3.py @@ -11,7 +11,7 @@ """ -def solution(n): +def solution(n: int = 4000000) -> int: """Returns the sum of all fibonacci sequence even elements that are lower or equals to n. diff --git a/project_euler/problem_02/sol4.py b/project_euler/problem_02/sol4.py index be4328941aa3..a87410b7006d 100644 --- a/project_euler/problem_02/sol4.py +++ b/project_euler/problem_02/sol4.py @@ -13,7 +13,7 @@ from decimal import Decimal, getcontext -def solution(n): +def solution(n: int = 4000000) -> int: """Returns the sum of all fibonacci sequence even elements that are lower or equals to n. @@ -57,8 +57,8 @@ def solution(n): index = (math.floor(math.log(n * (phi + 2), phi) - 1) // 3) * 3 + 2 num = Decimal(round(phi ** Decimal(index + 1))) / (phi + 2) - sum = num // 2 - return int(sum) + total = num // 2 + return int(total) if __name__ == "__main__": diff --git a/project_euler/problem_02/sol5.py b/project_euler/problem_02/sol5.py index 180906cf8717..dcf6eae85891 100644 --- a/project_euler/problem_02/sol5.py +++ b/project_euler/problem_02/sol5.py @@ -11,7 +11,7 @@ """ -def solution(n): +def solution(n: int = 4000000) -> int: """Returns the sum of all fibonacci sequence even elements that are lower or equals to n. @@ -27,19 +27,19 @@ def solution(n): 44 """ - a = [0, 1] + fib = [0, 1] i = 0 - while a[i] <= n: - a.append(a[i] + a[i + 1]) - if a[i + 2] > n: + while fib[i] <= n: + fib.append(fib[i] + fib[i + 1]) + if fib[i + 2] > n: break i += 1 - sum = 0 - for j in range(len(a) - 1): - if a[j] % 2 == 0: - sum += a[j] + total = 0 + for j in range(len(fib) - 1): + if fib[j] % 2 == 0: + total += fib[j] - return sum + return total if __name__ == "__main__": From 000cedc07f1065282acd7e25add4d2847fe08391 Mon Sep 17 00:00:00 2001 From: Dmytro Litvinov Date: Tue, 6 Oct 2020 11:31:15 +0300 Subject: [PATCH 0836/1071] Add type hints for "strings" folder (#2882) * Add type hints for strings/ folder * Rerun other checks * updating DIRECTORY.md Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> --- DIRECTORY.md | 4 +++- strings/aho_corasick.py | 11 ++++++----- strings/boyer_moore_search.py | 9 +++++---- strings/knuth_morris_pratt.py | 7 +++++-- strings/levenshtein_distance.py | 2 +- strings/manacher.py | 2 +- strings/rabin_karp.py | 4 ++-- 7 files changed, 23 insertions(+), 16 deletions(-) diff --git a/DIRECTORY.md b/DIRECTORY.md index a8d00f6cb724..6a3d31709ed6 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -552,6 +552,8 @@ * Problem 12 * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_12/sol1.py) * [Sol2](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_12/sol2.py) + * Problem 120 + * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_120/sol1.py) * Problem 13 * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_13/sol1.py) * Problem 14 @@ -597,7 +599,7 @@ * Problem 29 * [Solution](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_29/solution.py) * Problem 30 - * [Soln](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_30/soln.py) + * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_30/sol1.py) * Problem 31 * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_31/sol1.py) * [Sol2](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_31/sol2.py) diff --git a/strings/aho_corasick.py b/strings/aho_corasick.py index bb6955bdd423..b959dbd58c32 100644 --- a/strings/aho_corasick.py +++ b/strings/aho_corasick.py @@ -1,8 +1,9 @@ from collections import deque +from typing import Dict, List, Union class Automaton: - def __init__(self, keywords): + def __init__(self, keywords: List[str]): self.adlist = list() self.adlist.append( {"value": "", "next_states": [], "fail_state": 0, "output": []} @@ -12,13 +13,13 @@ def __init__(self, keywords): self.add_keyword(keyword) self.set_fail_transitions() - def find_next_state(self, current_state, char): + def find_next_state(self, current_state: int, char: str) -> Union[int, None]: for state in self.adlist[current_state]["next_states"]: if char == self.adlist[state]["value"]: return state return None - def add_keyword(self, keyword): + def add_keyword(self, keyword: str) -> None: current_state = 0 for character in keyword: if self.find_next_state(current_state, character): @@ -36,7 +37,7 @@ def add_keyword(self, keyword): current_state = len(self.adlist) - 1 self.adlist[current_state]["output"].append(keyword) - def set_fail_transitions(self): + def set_fail_transitions(self) -> None: q = deque() for node in self.adlist[0]["next_states"]: q.append(node) @@ -61,7 +62,7 @@ def set_fail_transitions(self): + self.adlist[self.adlist[child]["fail_state"]]["output"] ) - def search_in(self, string): + def search_in(self, string: str) -> Dict[str, List[int]]: """ >>> A = Automaton(["what", "hat", "ver", "er"]) >>> A.search_in("whatever, err ... , wherever") diff --git a/strings/boyer_moore_search.py b/strings/boyer_moore_search.py index 9d32a6943906..a3e6cf614eab 100644 --- a/strings/boyer_moore_search.py +++ b/strings/boyer_moore_search.py @@ -17,14 +17,15 @@ n=length of main string m=length of pattern string """ +from typing import List class BoyerMooreSearch: - def __init__(self, text, pattern): + def __init__(self, text: str, pattern: str): self.text, self.pattern = text, pattern self.textLen, self.patLen = len(text), len(pattern) - def match_in_pattern(self, char): + def match_in_pattern(self, char: str) -> int: """finds the index of char in pattern in reverse order Parameters : @@ -40,7 +41,7 @@ def match_in_pattern(self, char): return i return -1 - def mismatch_in_text(self, currentPos): + def mismatch_in_text(self, currentPos: int) -> int: """ find the index of mis-matched character in text when compared with pattern from last @@ -58,7 +59,7 @@ def mismatch_in_text(self, currentPos): return currentPos + i return -1 - def bad_character_heuristic(self): + def bad_character_heuristic(self) -> List[int]: # searches pattern in text and returns index positions positions = [] for i in range(self.textLen - self.patLen + 1): diff --git a/strings/knuth_morris_pratt.py b/strings/knuth_morris_pratt.py index 2e5e0c7e73f1..a205ce37e3e5 100644 --- a/strings/knuth_morris_pratt.py +++ b/strings/knuth_morris_pratt.py @@ -1,4 +1,7 @@ -def kmp(pattern, text): +from typing import List + + +def kmp(pattern: str, text: str) -> bool: """ The Knuth-Morris-Pratt Algorithm for finding a pattern within a piece of text with complexity O(n + m) @@ -33,7 +36,7 @@ def kmp(pattern, text): return False -def get_failure_array(pattern): +def get_failure_array(pattern: str) -> List[int]: """ Calculates the new index we should go to if we fail a comparison :param pattern: diff --git a/strings/levenshtein_distance.py b/strings/levenshtein_distance.py index 9b8793544a99..54948a96670b 100644 --- a/strings/levenshtein_distance.py +++ b/strings/levenshtein_distance.py @@ -13,7 +13,7 @@ """ -def levenshtein_distance(first_word, second_word): +def levenshtein_distance(first_word: str, second_word: str) -> int: """Implementation of the levenshtein distance in Python. :param first_word: the first word to measure the difference. :param second_word: the second word to measure the difference. diff --git a/strings/manacher.py b/strings/manacher.py index 73b31a7bea9f..5476e06839b7 100644 --- a/strings/manacher.py +++ b/strings/manacher.py @@ -1,4 +1,4 @@ -def palindromic_string(input_string): +def palindromic_string(input_string: str) -> str: """ >>> palindromic_string('abbbaba') 'abbba' diff --git a/strings/rabin_karp.py b/strings/rabin_karp.py index d866b1397277..81ca611a76b3 100644 --- a/strings/rabin_karp.py +++ b/strings/rabin_karp.py @@ -4,7 +4,7 @@ modulus = 1000003 -def rabin_karp(pattern, text): +def rabin_karp(pattern: str, text: str) -> bool: """ The Rabin-Karp Algorithm for finding a pattern within a piece of text with complexity O(nm), most efficient when it is used with multiple patterns @@ -51,7 +51,7 @@ def rabin_karp(pattern, text): return False -def test_rabin_karp(): +def test_rabin_karp() -> None: """ >>> test_rabin_karp() Success. From e74adc4a6d00df8711243fe94f9192662b323b48 Mon Sep 17 00:00:00 2001 From: Vladimir Evgrafov Date: Tue, 6 Oct 2020 15:18:07 +0300 Subject: [PATCH 0837/1071] Project Euler Problem 10: style improvements (#2924) Rename the main solution functions to solution. Rename prime chec functions to is_prime. Add default args, typehints, expand variable names. --- project_euler/problem_10/sol1.py | 40 +++++++++++++++---------- project_euler/problem_10/sol2.py | 25 ++++++++++++---- project_euler/problem_10/sol3.py | 51 ++++++++++++++++---------------- 3 files changed, 68 insertions(+), 48 deletions(-) diff --git a/project_euler/problem_10/sol1.py b/project_euler/problem_10/sol1.py index c81085951ecf..4f3b3a4a42f5 100644 --- a/project_euler/problem_10/sol1.py +++ b/project_euler/problem_10/sol1.py @@ -1,4 +1,6 @@ """ +https://projecteuler.net/problem=10 + Problem Statement: The sum of the primes below 10 is 2 + 3 + 5 + 7 = 17. @@ -7,7 +9,17 @@ from math import sqrt -def is_prime(n): +def is_prime(n: int) -> bool: + """Returns boolean representing primality of given number num. + >>> is_prime(2) + True + >>> is_prime(3) + True + >>> is_prime(27) + False + >>> is_prime(2999) + True + """ for i in range(2, int(sqrt(n)) + 1): if n % i == 0: return False @@ -15,20 +27,7 @@ def is_prime(n): return True -def sum_of_primes(n): - if n > 2: - sumOfPrimes = 2 - else: - return 0 - - for i in range(3, n, 2): - if is_prime(i): - sumOfPrimes += i - - return sumOfPrimes - - -def solution(n): +def solution(n: int = 2000000) -> int: """Returns the sum of all the primes below n. # The code below has been commented due to slow execution affecting Travis. @@ -43,7 +42,16 @@ def solution(n): >>> solution(7) 10 """ - return sum_of_primes(n) + if n > 2: + sum_of_primes = 2 + else: + return 0 + + for i in range(3, n, 2): + if is_prime(i): + sum_of_primes += i + + return sum_of_primes if __name__ == "__main__": diff --git a/project_euler/problem_10/sol2.py b/project_euler/problem_10/sol2.py index b2e2b6e1adf3..39f5f5604053 100644 --- a/project_euler/problem_10/sol2.py +++ b/project_euler/problem_10/sol2.py @@ -1,4 +1,6 @@ """ +https://projecteuler.net/problem=10 + Problem Statement: The sum of the primes below 10 is 2 + 3 + 5 + 7 = 17. @@ -6,23 +8,34 @@ """ import math from itertools import takewhile - - -def primeCheck(number): +from typing import Iterator + + +def is_prime(number: int) -> bool: + """Returns boolean representing primality of given number num. + >>> is_prime(2) + True + >>> is_prime(3) + True + >>> is_prime(27) + False + >>> is_prime(2999) + True + """ if number % 2 == 0 and number > 2: return False return all(number % i for i in range(3, int(math.sqrt(number)) + 1, 2)) -def prime_generator(): +def prime_generator() -> Iterator[int]: num = 2 while True: - if primeCheck(num): + if is_prime(num): yield num num += 1 -def solution(n): +def solution(n: int = 2000000) -> int: """Returns the sum of all the primes below n. # The code below has been commented due to slow execution affecting Travis. diff --git a/project_euler/problem_10/sol3.py b/project_euler/problem_10/sol3.py index 739aaa9f16bb..ef895f546fa5 100644 --- a/project_euler/problem_10/sol3.py +++ b/project_euler/problem_10/sol3.py @@ -4,55 +4,54 @@ Problem Statement: The sum of the primes below 10 is 2 + 3 + 5 + 7 = 17. -Find the sum of all the primes below two million using Sieve_of_Eratosthenes: - -The sieve of Eratosthenes is one of the most efficient ways to find all primes -smaller than n when n is smaller than 10 million. Only for positive numbers. +Find the sum of all the primes below two million. """ -def prime_sum(n: int) -> int: - """Returns the sum of all the primes below n. +def solution(n: int = 2000000) -> int: + """Returns the sum of all the primes below n using Sieve of Eratosthenes: + + https://en.wikipedia.org/wiki/Sieve_of_Eratosthenes + The sieve of Eratosthenes is one of the most efficient ways to find all primes + smaller than n when n is smaller than 10 million. Only for positive numbers. - >>> prime_sum(2_000_000) + >>> solution(2_000_000) 142913828922 - >>> prime_sum(1_000) + >>> solution(1_000) 76127 - >>> prime_sum(5_000) + >>> solution(5_000) 1548136 - >>> prime_sum(10_000) + >>> solution(10_000) 5736396 - >>> prime_sum(7) + >>> solution(7) 10 - >>> prime_sum(7.1) # doctest: +ELLIPSIS + >>> solution(7.1) # doctest: +ELLIPSIS Traceback (most recent call last): ... TypeError: 'float' object cannot be interpreted as an integer - >>> prime_sum(-7) # doctest: +ELLIPSIS + >>> solution(-7) # doctest: +ELLIPSIS Traceback (most recent call last): ... IndexError: list assignment index out of range - >>> prime_sum("seven") # doctest: +ELLIPSIS + >>> solution("seven") # doctest: +ELLIPSIS Traceback (most recent call last): ... TypeError: can only concatenate str (not "int") to str """ - list_ = [0 for i in range(n + 1)] - list_[0] = 1 - list_[1] = 1 + primality_list = [0 for i in range(n + 1)] + primality_list[0] = 1 + primality_list[1] = 1 for i in range(2, int(n ** 0.5) + 1): - if list_[i] == 0: + if primality_list[i] == 0: for j in range(i * i, n + 1, i): - list_[j] = 1 - s = 0 + primality_list[j] = 1 + sum_of_primes = 0 for i in range(n): - if list_[i] == 0: - s += i - return s + if primality_list[i] == 0: + sum_of_primes += i + return sum_of_primes if __name__ == "__main__": - # import doctest - # doctest.testmod() - print(prime_sum(int(input().strip()))) + print(solution(int(input().strip()))) From 54401387a89e8ea83aece7687c8b65422c85ef49 Mon Sep 17 00:00:00 2001 From: Edward Nuno Date: Tue, 6 Oct 2020 07:54:39 -0700 Subject: [PATCH 0838/1071] Hacktoberfest 2020: Add style improvements for Project Euler Problem 03 (#2917) * Fix typehints in project_euler/problem01 Squashed commit of the following: commit 6801d073b31bf702814861cd3b07b634ca295bfa Author: Archaengel Date: Mon Oct 5 16:40:10 2020 -0700 Fix typehints in project_euler/problem01 commit 29afc3af114abd1b99dc3f7c8fc99128229db131 Author: Archaengel Date: Mon Oct 5 15:06:34 2020 -0700 Add typehints and default argument for project_euler/problem_01 * Add default args, typehints, and expand variable names for PE prob 02 * Add style improvements for first solution of PE Problem 02 * Add default arg and typehints for second solution of PE Problem 02 * Add default arg for third solution of PE Problem 02 * Add style improvements for 1st soln of PE problem 03 * Add default arg and typehints for 2nd soln of PE problem 03 * Add default arg for 3rd soln of PE problem 03 * Remove unnecessary newlines * Remove unnecessary newlines * Fix end of file for 2nd soln in PE problem 03 --- project_euler/problem_03/sol1.py | 64 ++++++++++++++++++++------------ project_euler/problem_03/sol2.py | 3 +- project_euler/problem_03/sol3.py | 9 +---- 3 files changed, 43 insertions(+), 33 deletions(-) diff --git a/project_euler/problem_03/sol1.py b/project_euler/problem_03/sol1.py index 347f8a53f5b4..22efeb2c4e90 100644 --- a/project_euler/problem_03/sol1.py +++ b/project_euler/problem_03/sol1.py @@ -5,24 +5,43 @@ e.g. for 10, largest prime factor = 5. For 17, largest prime factor = 17. """ + import math -def isprime(no): - if no == 2: +def isprime(num: int) -> bool: + """Returns boolean representing primality of given number num. + >>> isprime(2) + True + >>> isprime(3) + True + >>> isprime(27) + False + >>> isprime(2999) + True + >>> isprime(0) + Traceback (most recent call last): + ... + ValueError: Parameter num must be greater or equal to two. + >>> isprime(1) + Traceback (most recent call last): + ... + ValueError: Parameter num must be greater or equal to two. + """ + if num <= 1: + raise ValueError("Parameter num must be greater or equal to two.") + if num == 2: return True - elif no % 2 == 0: + elif num % 2 == 0: return False - sq = int(math.sqrt(no)) + 1 - for i in range(3, sq, 2): - if no % i == 0: + for i in range(3, int(math.sqrt(num)) + 1, 2): + if num % i == 0: return False return True -def solution(n): +def solution(n: int = 600851475143) -> int: """Returns the largest prime factor of a given number n. - >>> solution(13195) 29 >>> solution(10) @@ -54,24 +73,21 @@ def solution(n): raise TypeError("Parameter n must be int or passive of cast to int.") if n <= 0: raise ValueError("Parameter n must be greater or equal to one.") - maxNumber = 0 + max_number = 0 + if isprime(n): + return n + while n % 2 == 0: + n //= 2 if isprime(n): return n - else: - while n % 2 == 0: - n = n / 2 - if isprime(n): - return int(n) - else: - n1 = int(math.sqrt(n)) + 1 - for i in range(3, n1, 2): - if n % i == 0: - if isprime(n / i): - maxNumber = n / i - break - elif isprime(i): - maxNumber = i - return maxNumber + for i in range(3, int(math.sqrt(n)) + 1, 2): + if n % i == 0: + if isprime(n / i): + max_number = n / i + break + elif isprime(i): + max_number = i + return max_number if __name__ == "__main__": diff --git a/project_euler/problem_03/sol2.py b/project_euler/problem_03/sol2.py index daac041d4bd9..f28232109a84 100644 --- a/project_euler/problem_03/sol2.py +++ b/project_euler/problem_03/sol2.py @@ -7,9 +7,8 @@ """ -def solution(n): +def solution(n: int = 600851475143) -> int: """Returns the largest prime factor of a given number n. - >>> solution(13195) 29 >>> solution(10) diff --git a/project_euler/problem_03/sol3.py b/project_euler/problem_03/sol3.py index 5fe45df59984..676717cceca8 100644 --- a/project_euler/problem_03/sol3.py +++ b/project_euler/problem_03/sol3.py @@ -7,9 +7,8 @@ """ -def solution(n: int) -> int: +def solution(n: int = 600851475143) -> int: """Returns the largest prime factor of a given number n. - >>> solution(13195) 29 >>> solution(10) @@ -52,12 +51,8 @@ def solution(n: int) -> int: while n % i == 0: n = n / i i += 1 - return int(ans) if __name__ == "__main__": - # print(solution(int(input().strip()))) - import doctest - - doctest.testmod() + print(solution(int(input().strip()))) From 91ad30c2b0ed44860cb2b84826373844287b1def Mon Sep 17 00:00:00 2001 From: Noah H Date: Tue, 6 Oct 2020 23:03:03 -0400 Subject: [PATCH 0839/1071] Bring problem_29 solution in line with project style guidelines (#2949) --- project_euler/problem_29/{solution.py => sol1.py} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename project_euler/problem_29/{solution.py => sol1.py} (97%) diff --git a/project_euler/problem_29/solution.py b/project_euler/problem_29/sol1.py similarity index 97% rename from project_euler/problem_29/solution.py rename to project_euler/problem_29/sol1.py index 4313a7b06392..726bcaf6ebd8 100644 --- a/project_euler/problem_29/solution.py +++ b/project_euler/problem_29/sol1.py @@ -16,7 +16,7 @@ """ -def solution(n): +def solution(n: int = 100) -> int: """Returns the number of distinct terms in the sequence generated by a^b for 2 <= a <= 100 and 2 <= b <= 100. From 4d4ce400ecbfe3b207a1b1ec3c71bec65cba2584 Mon Sep 17 00:00:00 2001 From: Nelson Stoik Date: Tue, 6 Oct 2020 21:57:25 -0600 Subject: [PATCH 0840/1071] Add typehints and default input for project_euler/problem_25 (#2901) * add typehints and docstrings * add typehint and default value * add typehint and default value. Removed unused variable. * do not modifiy the given solution * add doctests * update sol1 after running black * add typehint, docstring, and doctest * update sol2 after running black --- project_euler/problem_25/sol1.py | 40 ++++++++++++++++++++++++++++---- project_euler/problem_25/sol2.py | 19 +++++++++++++-- project_euler/problem_25/sol3.py | 2 +- 3 files changed, 54 insertions(+), 7 deletions(-) diff --git a/project_euler/problem_25/sol1.py b/project_euler/problem_25/sol1.py index f0228915dc15..c30a74a43cb0 100644 --- a/project_euler/problem_25/sol1.py +++ b/project_euler/problem_25/sol1.py @@ -25,7 +25,24 @@ """ -def fibonacci(n): +def fibonacci(n: int) -> int: + """ + Computes the Fibonacci number for input n by iterating through n numbers + and creating an array of ints using the Fibonacci formula. + Returns the nth element of the array. + + >>> fibonacci(2) + 1 + >>> fibonacci(3) + 2 + >>> fibonacci(5) + 5 + >>> fibonacci(10) + 55 + >>> fibonacci(12) + 144 + + """ if n == 1 or type(n) is not int: return 0 elif n == 2: @@ -38,7 +55,21 @@ def fibonacci(n): return sequence[n] -def fibonacci_digits_index(n): +def fibonacci_digits_index(n: int) -> int: + """ + Computes incrementing Fibonacci numbers starting from 3 until the length + of the resulting Fibonacci result is the input value n. Returns the term + of the Fibonacci sequence where this occurs. + + >>> fibonacci_digits_index(1000) + 4782 + >>> fibonacci_digits_index(100) + 476 + >>> fibonacci_digits_index(50) + 237 + >>> fibonacci_digits_index(3) + 12 + """ digits = 0 index = 2 @@ -49,8 +80,9 @@ def fibonacci_digits_index(n): return index -def solution(n): - """Returns the index of the first term in the Fibonacci sequence to contain +def solution(n: int = 1000) -> int: + """ + Returns the index of the first term in the Fibonacci sequence to contain n digits. >>> solution(1000) diff --git a/project_euler/problem_25/sol2.py b/project_euler/problem_25/sol2.py index c98f09b1d316..ed3b54bb351f 100644 --- a/project_euler/problem_25/sol2.py +++ b/project_euler/problem_25/sol2.py @@ -25,14 +25,29 @@ """ -def fibonacci_generator(): +def fibonacci_generator() -> int: + """ + A generator that produces numbers in the Fibonacci sequence + + >>> generator = fibonacci_generator() + >>> next(generator) + 1 + >>> next(generator) + 2 + >>> next(generator) + 3 + >>> next(generator) + 5 + >>> next(generator) + 8 + """ a, b = 0, 1 while True: a, b = b, a + b yield b -def solution(n): +def solution(n: int = 1000) -> int: """Returns the index of the first term in the Fibonacci sequence to contain n digits. diff --git a/project_euler/problem_25/sol3.py b/project_euler/problem_25/sol3.py index 4a1d9da76bf7..c66411dc55fc 100644 --- a/project_euler/problem_25/sol3.py +++ b/project_euler/problem_25/sol3.py @@ -25,7 +25,7 @@ """ -def solution(n): +def solution(n: int = 1000) -> int: """Returns the index of the first term in the Fibonacci sequence to contain n digits. From ddf83ec886373e1cb35318690b9bc2c737fc9f9b Mon Sep 17 00:00:00 2001 From: Dhruv Date: Wed, 7 Oct 2020 10:17:43 +0530 Subject: [PATCH 0841/1071] Add default arguments for Project Euler problem 6 (#2957) - Add default arguments to solution function - Add link to Project Euler problem 6 - Add doctest for testing `solution()` - Removed test_solutions.py as it is redundant --- project_euler/problem_06/sol1.py | 6 ++++-- project_euler/problem_06/sol2.py | 6 ++++-- project_euler/problem_06/sol3.py | 6 ++++-- project_euler/problem_06/sol4.py | 6 +++--- project_euler/problem_06/test_solutions.py | 18 ------------------ 5 files changed, 15 insertions(+), 27 deletions(-) delete mode 100644 project_euler/problem_06/test_solutions.py diff --git a/project_euler/problem_06/sol1.py b/project_euler/problem_06/sol1.py index d6506ea2839c..38f995bbf822 100644 --- a/project_euler/problem_06/sol1.py +++ b/project_euler/problem_06/sol1.py @@ -1,5 +1,5 @@ """ -Problem: +Problem 6: https://projecteuler.net/problem=6 The sum of the squares of the first ten natural numbers is, 1^2 + 2^2 + ... + 10^2 = 385 @@ -15,7 +15,7 @@ """ -def solution(n: int) -> int: +def solution(n: int = 100) -> int: """Returns the difference between the sum of the squares of the first n natural numbers and the square of the sum. @@ -27,6 +27,8 @@ def solution(n: int) -> int: 41230 >>> solution(50) 1582700 + >>> solution() + 25164150 """ sum_of_squares = 0 sum_of_ints = 0 diff --git a/project_euler/problem_06/sol2.py b/project_euler/problem_06/sol2.py index 5032775f9f77..f4d74c993f0d 100644 --- a/project_euler/problem_06/sol2.py +++ b/project_euler/problem_06/sol2.py @@ -1,5 +1,5 @@ """ -Problem: +Problem 6: https://projecteuler.net/problem=6 The sum of the squares of the first ten natural numbers is, 1^2 + 2^2 + ... + 10^2 = 385 @@ -15,7 +15,7 @@ """ -def solution(n: int) -> int: +def solution(n: int = 100) -> int: """Returns the difference between the sum of the squares of the first n natural numbers and the square of the sum. @@ -27,6 +27,8 @@ def solution(n: int) -> int: 41230 >>> solution(50) 1582700 + >>> solution() + 25164150 """ sum_cubes = (n * (n + 1) // 2) ** 2 sum_squares = n * (n + 1) * (2 * n + 1) // 6 diff --git a/project_euler/problem_06/sol3.py b/project_euler/problem_06/sol3.py index 385ad05151fb..8b5c5d3ba4aa 100644 --- a/project_euler/problem_06/sol3.py +++ b/project_euler/problem_06/sol3.py @@ -1,5 +1,5 @@ """ -Problem: +Problem 6: https://projecteuler.net/problem=6 The sum of the squares of the first ten natural numbers is, 1^2 + 2^2 + ... + 10^2 = 385 @@ -16,7 +16,7 @@ import math -def solution(n: int) -> int: +def solution(n: int = 100) -> int: """Returns the difference between the sum of the squares of the first n natural numbers and the square of the sum. @@ -28,6 +28,8 @@ def solution(n: int) -> int: 41230 >>> solution(50) 1582700 + >>> solution() + 25164150 """ sum_of_squares = sum([i * i for i in range(1, n + 1)]) square_of_sum = int(math.pow(sum(range(1, n + 1)), 2)) diff --git a/project_euler/problem_06/sol4.py b/project_euler/problem_06/sol4.py index 912a3e8f6096..5fae84008448 100644 --- a/project_euler/problem_06/sol4.py +++ b/project_euler/problem_06/sol4.py @@ -1,5 +1,5 @@ """ -Problem: +Problem 6: https://projecteuler.net/problem=6 The sum of the squares of the first ten natural numbers is, 1^2 + 2^2 + ... + 10^2 = 385 @@ -15,7 +15,7 @@ """ -def solution(n: int) -> int: +def solution(n: int = 100) -> int: """Returns the difference between the sum of the squares of the first n natural numbers and the square of the sum. @@ -27,7 +27,7 @@ def solution(n: int) -> int: 41230 >>> solution(50) 1582700 - >>> solution(100) + >>> solution() 25164150 """ sum_of_squares = n * (n + 1) * (2 * n + 1) / 6 diff --git a/project_euler/problem_06/test_solutions.py b/project_euler/problem_06/test_solutions.py deleted file mode 100644 index 6d5337789655..000000000000 --- a/project_euler/problem_06/test_solutions.py +++ /dev/null @@ -1,18 +0,0 @@ -from .sol1 import solution as sol1 -from .sol2 import solution as sol2 -from .sol3 import solution as sol3 -from .sol4 import solution as sol4 - - -def test_solutions() -> None: - """ - >>> test_solutions() - """ - assert sol1(10) == sol2(10) == sol3(10) == sol4(10) == 2640 - assert sol1(15) == sol2(15) == sol3(15) == sol4(15) == 13160 - assert sol1(20) == sol2(20) == sol3(20) == sol4(20) == 41230 - assert sol1(50) == sol2(50) == sol3(50) == sol4(50) == 1582700 - - -if __name__ == "__main__": - test_solutions() From a5000d32edf1992615ecdb112e112b0490e5ddd3 Mon Sep 17 00:00:00 2001 From: Edward Nuno Date: Tue, 6 Oct 2020 22:03:34 -0700 Subject: [PATCH 0842/1071] Add style improvements to solutions for Project Euler Problem 04 (#2945) * Fix typehints in project_euler/problem01 Squashed commit of the following: commit 6801d073b31bf702814861cd3b07b634ca295bfa Author: Archaengel Date: Mon Oct 5 16:40:10 2020 -0700 Fix typehints in project_euler/problem01 commit 29afc3af114abd1b99dc3f7c8fc99128229db131 Author: Archaengel Date: Mon Oct 5 15:06:34 2020 -0700 Add typehints and default argument for project_euler/problem_01 * Add default args, typehints, and expand variable names for PE prob 02 * Add style improvements for first solution of PE Problem 02 * Add default arg and typehints for second solution of PE Problem 02 * Add default arg for third solution of PE Problem 02 * Add style improvements for 1st soln of PE problem 03 * Add default arg and typehints for 2nd soln of PE problem 03 * Add default arg for 3rd soln of PE problem 03 * Remove unnecessary newlines * Remove unnecessary newlines * Fix end of file for 2nd soln in PE problem 03 * Add style improvements to solutions for PE problem 04 * Restore original newlines in soln for PE problem 04 * Fix punctuation in docstring for PE problem 04 * Restore solution bodies for PE problem 04 * Expand variable names for 2nd soln of PE problem 04 --- project_euler/problem_04/sol1.py | 2 +- project_euler/problem_04/sol2.py | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/project_euler/problem_04/sol1.py b/project_euler/problem_04/sol1.py index 227b594d0df7..42f56f3ef389 100644 --- a/project_euler/problem_04/sol1.py +++ b/project_euler/problem_04/sol1.py @@ -8,7 +8,7 @@ """ -def solution(n): +def solution(n: int = 998001) -> int: """Returns the largest palindrome made from the product of two 3-digit numbers which is less than n. diff --git a/project_euler/problem_04/sol2.py b/project_euler/problem_04/sol2.py index 0f185f1216ab..8ee082ad2f6a 100644 --- a/project_euler/problem_04/sol2.py +++ b/project_euler/problem_04/sol2.py @@ -8,7 +8,7 @@ """ -def solution(n): +def solution(n: int = 998001) -> int: """Returns the largest palindrome made from the product of two 3-digit numbers which is less than n. @@ -22,8 +22,8 @@ def solution(n): answer = 0 for i in range(999, 99, -1): # 3 digit numbers range from 999 down to 100 for j in range(999, 99, -1): - t = str(i * j) - if t == t[::-1] and i * j < n: + product_string = str(i * j) + if product_string == product_string[::-1] and i * j < n: answer = max(answer, i * j) return answer From e41d04112fcaaceb286b0fd6d55a162af0893593 Mon Sep 17 00:00:00 2001 From: Joyce Date: Wed, 7 Oct 2020 17:53:14 +0800 Subject: [PATCH 0843/1071] Fixes: #2630 Add doctests and support for negative numbers (#2626) * add type hints to math/extended euclid * math/extended euclid - add doctest * math/extended euclid: remove manual doctest * change algorithm for negative numbers * improve naming of variables * Update extended_euclidean_algorithm.py Co-authored-by: Dhruv --- maths/extended_euclidean_algorithm.py | 95 +++++++++++++++------------ 1 file changed, 54 insertions(+), 41 deletions(-) diff --git a/maths/extended_euclidean_algorithm.py b/maths/extended_euclidean_algorithm.py index fe81bcfaf71d..e7087636ce09 100644 --- a/maths/extended_euclidean_algorithm.py +++ b/maths/extended_euclidean_algorithm.py @@ -3,59 +3,72 @@ Finds 2 numbers a and b such that it satisfies the equation am + bn = gcd(m, n) (a.k.a Bezout's Identity) + +https://en.wikipedia.org/wiki/Extended_Euclidean_algorithm """ # @Author: S. Sharma # @Date: 2019-02-25T12:08:53-06:00 # @Email: silentcat@protonmail.com -# @Last modified by: PatOnTheBack -# @Last modified time: 2019-07-05 +# @Last modified by: pikulet +# @Last modified time: 2020-10-02 import sys +from typing import Tuple -def extended_euclidean_algorithm(m, n): +def extended_euclidean_algorithm(a: int, b: int) -> Tuple[int, int]: """ Extended Euclidean Algorithm. Finds 2 numbers a and b such that it satisfies the equation am + bn = gcd(m, n) (a.k.a Bezout's Identity) + + >>> extended_euclidean_algorithm(1, 24) + (1, 0) + + >>> extended_euclidean_algorithm(8, 14) + (2, -1) + + >>> extended_euclidean_algorithm(240, 46) + (-9, 47) + + >>> extended_euclidean_algorithm(1, -4) + (1, 0) + + >>> extended_euclidean_algorithm(-2, -4) + (-1, 0) + + >>> extended_euclidean_algorithm(0, -4) + (0, -1) + + >>> extended_euclidean_algorithm(2, 0) + (1, 0) + """ - a = 0 - a_prime = 1 - b = 1 - b_prime = 0 - q = 0 - r = 0 - if m > n: - c = m - d = n - else: - c = n - d = m - - while True: - q = int(c / d) - r = c % d - if r == 0: - break - c = d - d = r - - t = a_prime - a_prime = a - a = t - q * a - - t = b_prime - b_prime = b - b = t - q * b - - pair = None - if m > n: - pair = (a, b) - else: - pair = (b, a) - return pair + # base cases + if abs(a) == 1: + return a, 0 + elif abs(b) == 1: + return 0, b + + old_remainder, remainder = a, b + old_coeff_a, coeff_a = 1, 0 + old_coeff_b, coeff_b = 0, 1 + + while remainder != 0: + quotient = old_remainder // remainder + old_remainder, remainder = remainder, old_remainder - quotient * remainder + old_coeff_a, coeff_a = coeff_a, old_coeff_a - quotient * coeff_a + old_coeff_b, coeff_b = coeff_b, old_coeff_b - quotient * coeff_b + + # sign correction for negative numbers + if a < 0: + old_coeff_a = -old_coeff_a + if b < 0: + old_coeff_b = -old_coeff_b + + return old_coeff_a, old_coeff_b def main(): @@ -63,9 +76,9 @@ def main(): if len(sys.argv) < 3: print("2 integer arguments required") exit(1) - m = int(sys.argv[1]) - n = int(sys.argv[2]) - print(extended_euclidean_algorithm(m, n)) + a = int(sys.argv[1]) + b = int(sys.argv[2]) + print(extended_euclidean_algorithm(a, b)) if __name__ == "__main__": From 11a5afd8a10b21dfebddcf3c6022402ac463b634 Mon Sep 17 00:00:00 2001 From: Dhruv Date: Wed, 7 Oct 2020 15:29:55 +0530 Subject: [PATCH 0844/1071] Add type hints and default args for problem 20 (#2962) - Improved variable names - Added type hints - Added default argument values for validate_solutions script --- project_euler/problem_20/sol1.py | 17 ++++++++++------- project_euler/problem_20/sol2.py | 8 +++++--- project_euler/problem_20/sol3.py | 8 +++++--- project_euler/problem_20/sol4.py | 8 +++++--- 4 files changed, 25 insertions(+), 16 deletions(-) diff --git a/project_euler/problem_20/sol1.py b/project_euler/problem_20/sol1.py index 13b3c987f046..b472024e54c0 100644 --- a/project_euler/problem_20/sol1.py +++ b/project_euler/problem_20/sol1.py @@ -1,4 +1,6 @@ """ +Problem 20: https://projecteuler.net/problem=20 + n! means n × (n − 1) × ... × 3 × 2 × 1 For example, 10! = 10 × 9 × ... × 3 × 2 × 1 = 3628800, @@ -8,14 +10,15 @@ """ -def factorial(n): +def factorial(num: int) -> int: + """Find the factorial of a given number n""" fact = 1 - for i in range(1, n + 1): + for i in range(1, num + 1): fact *= i return fact -def split_and_add(number): +def split_and_add(number: int) -> int: """Split number digits and add them.""" sum_of_digits = 0 while number > 0: @@ -25,8 +28,8 @@ def split_and_add(number): return sum_of_digits -def solution(n): - """Returns the sum of the digits in the number 100! +def solution(num: int = 100) -> int: + """Returns the sum of the digits in the factorial of num >>> solution(100) 648 >>> solution(50) @@ -42,8 +45,8 @@ def solution(n): >>> solution(1) 1 """ - f = factorial(n) - result = split_and_add(f) + nfact = factorial(num) + result = split_and_add(nfact) return result diff --git a/project_euler/problem_20/sol2.py b/project_euler/problem_20/sol2.py index 14e591795292..92e1e724a647 100644 --- a/project_euler/problem_20/sol2.py +++ b/project_euler/problem_20/sol2.py @@ -1,4 +1,6 @@ """ +Problem 20: https://projecteuler.net/problem=20 + n! means n × (n − 1) × ... × 3 × 2 × 1 For example, 10! = 10 × 9 × ... × 3 × 2 × 1 = 3628800, @@ -9,8 +11,8 @@ from math import factorial -def solution(n): - """Returns the sum of the digits in the number 100! +def solution(num: int = 100) -> int: + """Returns the sum of the digits in the factorial of num >>> solution(100) 648 >>> solution(50) @@ -26,7 +28,7 @@ def solution(n): >>> solution(1) 1 """ - return sum([int(x) for x in str(factorial(n))]) + return sum([int(x) for x in str(factorial(num))]) if __name__ == "__main__": diff --git a/project_euler/problem_20/sol3.py b/project_euler/problem_20/sol3.py index 13f9d7831c47..4f28ac5fcfde 100644 --- a/project_euler/problem_20/sol3.py +++ b/project_euler/problem_20/sol3.py @@ -1,4 +1,6 @@ """ +Problem 20: https://projecteuler.net/problem=20 + n! means n × (n − 1) × ... × 3 × 2 × 1 For example, 10! = 10 × 9 × ... × 3 × 2 × 1 = 3628800, @@ -9,8 +11,8 @@ from math import factorial -def solution(n): - """Returns the sum of the digits in the number 100! +def solution(num: int = 100) -> int: + """Returns the sum of the digits in the factorial of num >>> solution(1000) 10539 >>> solution(200) @@ -32,7 +34,7 @@ def solution(n): >>> solution(0) 1 """ - return sum(map(int, str(factorial(n)))) + return sum(map(int, str(factorial(num)))) if __name__ == "__main__": diff --git a/project_euler/problem_20/sol4.py b/project_euler/problem_20/sol4.py index 4c597220f09b..b32ce309dfa6 100644 --- a/project_euler/problem_20/sol4.py +++ b/project_euler/problem_20/sol4.py @@ -1,4 +1,6 @@ """ +Problem 20: https://projecteuler.net/problem=20 + n! means n × (n − 1) × ... × 3 × 2 × 1 For example, 10! = 10 × 9 × ... × 3 × 2 × 1 = 3628800, @@ -8,8 +10,8 @@ """ -def solution(n): - """Returns the sum of the digits in the number 100! +def solution(num: int = 100) -> int: + """Returns the sum of the digits in the factorial of num >>> solution(100) 648 >>> solution(50) @@ -27,7 +29,7 @@ def solution(n): """ fact = 1 result = 0 - for i in range(1, n + 1): + for i in range(1, num + 1): fact *= i for j in str(fact): From c510a7da7b1b814158b74e4c72c28a1400e733ca Mon Sep 17 00:00:00 2001 From: Ron U Date: Wed, 7 Oct 2020 15:22:56 +0300 Subject: [PATCH 0845/1071] Add doomsday algorithm (#2903) * Added doomsday algorithm * Adding unit-tests to doomsday algorithm * running black on doomsday * adding doctest and fixing a black issue [doomsday] * Update doomsday.py * fixing black issue [doomsday] * Update other/doomsday.py * Update doomsday.py * adding more doctests (following review comment) [doomsday] Co-authored-by: John Law --- other/doomsday.py | 59 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 59 insertions(+) create mode 100644 other/doomsday.py diff --git a/other/doomsday.py b/other/doomsday.py new file mode 100644 index 000000000000..d8fe261156a1 --- /dev/null +++ b/other/doomsday.py @@ -0,0 +1,59 @@ +#!/bin/python3 +# Doomsday algorithm info: https://en.wikipedia.org/wiki/Doomsday_rule + +DOOMSDAY_LEAP = [4, 1, 7, 4, 2, 6, 4, 1, 5, 3, 7, 5] +DOOMSDAY_NOT_LEAP = [3, 7, 7, 4, 2, 6, 4, 1, 5, 3, 7, 5] +WEEK_DAY_NAMES = { + 0: "Sunday", + 1: "Monday", + 2: "Tuesday", + 3: "Wednesday", + 4: "Thursday", + 5: "Friday", + 6: "Saturday", +} + + +def get_week_day(year: int, month: int, day: int) -> str: + """Returns the week-day name out of a given date. + + >>> get_week_day(2020, 10, 24) + 'Saturday' + >>> get_week_day(2017, 10, 24) + 'Tuesday' + >>> get_week_day(2019, 5, 3) + 'Friday' + >>> get_week_day(1970, 9, 16) + 'Wednesday' + >>> get_week_day(1870, 8, 13) + 'Saturday' + >>> get_week_day(2040, 3, 14) + 'Wednesday' + + """ + # minimal input check: + assert len(str(year)) > 2, "year should be in YYYY format" + assert 1 <= month <= 12, "month should be between 1 to 12" + assert 1 <= day <= 31, "day should be between 1 to 31" + + # Doomsday algorithm: + century = year // 100 + century_anchor = (5 * (century % 4) + 2) % 7 + centurian = year % 100 + centurian_m = centurian % 12 + dooms_day = ( + (centurian // 12) + centurian_m + (centurian_m // 4) + century_anchor + ) % 7 + day_anchor = ( + DOOMSDAY_NOT_LEAP[month - 1] + if (year % 4 != 0) or (centurian == 0 and (year % 400) == 0) + else DOOMSDAY_LEAP[month - 1] + ) + week_day = (dooms_day + day - day_anchor) % 7 + return WEEK_DAY_NAMES[week_day] + + +if __name__ == "__main__": + import doctest + + doctest.testmod() From 05616ca38eec9fba5a21544aa03dba96e6f6efc6 Mon Sep 17 00:00:00 2001 From: JasirZaeem <20666236+JasirZaeem@users.noreply.github.com> Date: Wed, 7 Oct 2020 18:35:06 +0530 Subject: [PATCH 0846/1071] Update code style for Project Euler Problem 28 (#2976) Add default arguments, typehints and rename solution function for Porject Euler Problem 28 --- project_euler/problem_28/sol1.py | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/project_euler/problem_28/sol1.py b/project_euler/problem_28/sol1.py index 11b48fea9adf..cbc7de6bea9a 100644 --- a/project_euler/problem_28/sol1.py +++ b/project_euler/problem_28/sol1.py @@ -1,4 +1,7 @@ """ +Problem 28 +Url: https://projecteuler.net/problem=28 +Statement: Starting with the number 1 and moving to the right in a clockwise direction a 5 by 5 spiral is formed as follows: @@ -17,19 +20,19 @@ from math import ceil -def diagonal_sum(n): +def solution(n: int = 1001) -> int: """Returns the sum of the numbers on the diagonals in a n by n spiral formed in the same way. - >>> diagonal_sum(1001) + >>> solution(1001) 669171001 - >>> diagonal_sum(500) + >>> solution(500) 82959497 - >>> diagonal_sum(100) + >>> solution(100) 651897 - >>> diagonal_sum(50) + >>> solution(50) 79697 - >>> diagonal_sum(10) + >>> solution(10) 537 """ total = 1 @@ -46,10 +49,10 @@ def diagonal_sum(n): import sys if len(sys.argv) == 1: - print(diagonal_sum(1001)) + print(solution()) else: try: n = int(sys.argv[1]) - print(diagonal_sum(n)) + print(solution(n)) except ValueError: print("Invalid entry - please enter a number") From c14cfa32dff90b2efaee17c8972fe89b61b2f6b7 Mon Sep 17 00:00:00 2001 From: Joan Date: Wed, 7 Oct 2020 15:38:02 +0200 Subject: [PATCH 0847/1071] Address #2786 - Fix code style in Project Euler Problem 76 (#2978) * fix code style in problem 76 Signed-off-by: joan.rosellr * Update sol1.py * Update sol1.py * Remove trailing whitespace Co-authored-by: Dhruv --- project_euler/problem_76/sol1.py | 27 ++++++++++++++------------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/project_euler/problem_76/sol1.py b/project_euler/problem_76/sol1.py index ed0ee6b507e9..60bb87089e7e 100644 --- a/project_euler/problem_76/sol1.py +++ b/project_euler/problem_76/sol1.py @@ -1,6 +1,6 @@ """ Counting Summations -Problem 76 +Problem 76: https://projecteuler.net/problem=76 It is possible to write five as a sum in exactly six different ways: @@ -16,25 +16,26 @@ """ -def partition(m): - """Returns the number of different ways one hundred can be written as a sum - of at least two positive integers. +def solution(m: int = 100) -> int: + """ + Returns the number of different ways the number m can be written as a + sum of at least two positive integers. - >>> partition(100) + >>> solution(100) 190569291 - >>> partition(50) + >>> solution(50) 204225 - >>> partition(30) + >>> solution(30) 5603 - >>> partition(10) + >>> solution(10) 41 - >>> partition(5) + >>> solution(5) 6 - >>> partition(3) + >>> solution(3) 2 - >>> partition(2) + >>> solution(2) 1 - >>> partition(1) + >>> solution(1) 0 """ memo = [[0 for _ in range(m)] for _ in range(m + 1)] @@ -51,4 +52,4 @@ def partition(m): if __name__ == "__main__": - print(partition(int(str(input()).strip()))) + print(solution(int(input("Enter a number: ").strip()))) From 40db8c205f2b233dde2fa6735e6a9a752862f98c Mon Sep 17 00:00:00 2001 From: poloso Date: Wed, 7 Oct 2020 09:27:19 -0500 Subject: [PATCH 0848/1071] Fix: Corrected test. List in test must be ordered. (#2632) * Fix: Corrected test. List in test must be ordered. * Add tests with big lists. --- searches/simple_binary_search.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/searches/simple_binary_search.py b/searches/simple_binary_search.py index 8495dda8d518..d1f7f7a51cbc 100644 --- a/searches/simple_binary_search.py +++ b/searches/simple_binary_search.py @@ -31,8 +31,14 @@ def binary_search(a_list: list[int], item: int) -> bool: False >>> print(binary_search([], 1)) False - >>> print(binary_search([.1, .4 , -.1], .1)) + >>> print(binary_search([-.1, .1 , .8], .1)) True + >>> binary_search(range(-5000, 5000, 10), 80) + True + >>> binary_search(range(-5000, 5000, 10), 1255) + False + >>> binary_search(range(0, 10000, 5), 2) + False """ if len(a_list) == 0: return False From a698fa9a3fd11f2b2fa2ec8209fc29fcc799de04 Mon Sep 17 00:00:00 2001 From: Joan Date: Wed, 7 Oct 2020 17:09:36 +0200 Subject: [PATCH 0849/1071] [Project Euler] Fix code style in Problem 55 (#2985) * fix code style and update problem description with link Signed-off-by: joan.rosellr * Update sol1.py Co-authored-by: Dhruv --- project_euler/problem_55/sol1.py | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/project_euler/problem_55/sol1.py b/project_euler/problem_55/sol1.py index a2e27bbb9e93..14e411541f3a 100644 --- a/project_euler/problem_55/sol1.py +++ b/project_euler/problem_55/sol1.py @@ -1,10 +1,15 @@ """ +Lychrel numbers +Problem 55: https://projecteuler.net/problem=55 + If we take 47, reverse and add, 47 + 74 = 121, which is palindromic. + Not all numbers produce palindromes so quickly. For example, 349 + 943 = 1292, 1292 + 2921 = 4213 4213 + 3124 = 7337 That is, 349 took three iterations to arrive at a palindrome. + Although no one has proved it yet, it is thought that some numbers, like 196, never produce a palindrome. A number that never forms a palindrome through the reverse and add process is called a Lychrel number. Due to the theoretical nature @@ -48,14 +53,14 @@ def sum_reverse(n: int) -> int: return int(n) + int(str(n)[::-1]) -def compute_lychrel_nums(limit: int) -> int: +def solution(limit: int = 10000) -> int: """ Returns the count of all lychrel numbers below limit. - >>> compute_lychrel_nums(10000) + >>> solution(10000) 249 - >>> compute_lychrel_nums(5000) + >>> solution(5000) 76 - >>> compute_lychrel_nums(1000) + >>> solution(1000) 13 """ lychrel_nums = [] @@ -73,4 +78,4 @@ def compute_lychrel_nums(limit: int) -> int: if __name__ == "__main__": - print(f"{compute_lychrel_nums(10000) = }") + print(f"{solution() = }") From ff9be86390ee916afa66d3eda3f8e7ecb09801eb Mon Sep 17 00:00:00 2001 From: Wen Hong <35587716+wenhongg@users.noreply.github.com> Date: Wed, 7 Oct 2020 23:16:11 +0800 Subject: [PATCH 0850/1071] Hacktoberfest 2020: Rename method for project_euler/problem_99 (#2981) * name method solution in project_euler/problem99 * rename function --- project_euler/problem_99/sol1.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/project_euler/problem_99/sol1.py b/project_euler/problem_99/sol1.py index 0148a80ef481..88912e1f0f9e 100644 --- a/project_euler/problem_99/sol1.py +++ b/project_euler/problem_99/sol1.py @@ -17,9 +17,9 @@ from math import log10 -def find_largest(data_file: str = "base_exp.txt") -> int: +def solution(data_file: str = "base_exp.txt") -> int: """ - >>> find_largest() + >>> solution() 709 """ largest = [0, 0] @@ -31,4 +31,4 @@ def find_largest(data_file: str = "base_exp.txt") -> int: if __name__ == "__main__": - print(find_largest()) + print(solution()) From 6a5a022082eead9cdb4d248181d07da38894d69f Mon Sep 17 00:00:00 2001 From: Suyash Gupta Date: Thu, 8 Oct 2020 08:50:11 +0530 Subject: [PATCH 0851/1071] Add type hints and default args for Project Euler problem 5 (#2982) * add type hints and default args for problem 5 * Update sol1.py * Update sol2.py Co-authored-by: Dhruv --- project_euler/problem_05/sol1.py | 2 +- project_euler/problem_05/sol2.py | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/project_euler/problem_05/sol1.py b/project_euler/problem_05/sol1.py index f8d83fc12b71..a347d6564fa7 100644 --- a/project_euler/problem_05/sol1.py +++ b/project_euler/problem_05/sol1.py @@ -8,7 +8,7 @@ """ -def solution(n): +def solution(n: int = 20) -> int: """Returns the smallest positive number that is evenly divisible(divisible with no remainder) by all of the numbers from 1 to n. diff --git a/project_euler/problem_05/sol2.py b/project_euler/problem_05/sol2.py index 5aa84d21c8e8..57b4cc823d82 100644 --- a/project_euler/problem_05/sol2.py +++ b/project_euler/problem_05/sol2.py @@ -9,18 +9,18 @@ """ Euclidean GCD Algorithm """ -def gcd(x, y): +def gcd(x: int, y: int) -> int: return x if y == 0 else gcd(y, x % y) """ Using the property lcm*gcd of two numbers = product of them """ -def lcm(x, y): +def lcm(x: int, y: int) -> int: return (x * y) // gcd(x, y) -def solution(n): +def solution(n: int = 20) -> int: """Returns the smallest positive number that is evenly divisible(divisible with no remainder) by all of the numbers from 1 to n. From ef53bbdf5a6648f6035f48c9bcbf0cf7499fba22 Mon Sep 17 00:00:00 2001 From: Nelson Stoik Date: Wed, 7 Oct 2020 21:22:24 -0600 Subject: [PATCH 0852/1071] Style Improvements for project_euler/problem_26 (#2958) * add typehints and docstrings * add typehint and default value * add typehint and default value. Removed unused variable. * do not modifiy the given solution * add doctests * update sol1 after running black * add typehint, docstring, and doctest * update sol2 after running black * add full problem statement and solution function with typehint and doctest * renamed original function instead of adding new one * don't alter original solution --- project_euler/problem_26/sol1.py | 26 ++++++++++++++++++++++---- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/project_euler/problem_26/sol1.py b/project_euler/problem_26/sol1.py index cab8e0eb580b..64e0bbfef472 100644 --- a/project_euler/problem_26/sol1.py +++ b/project_euler/problem_26/sol1.py @@ -1,20 +1,38 @@ """ Euler Problem 26 https://projecteuler.net/problem=26 + +Problem Statement: + +A unit fraction contains 1 in the numerator. The decimal representation of the +unit fractions with denominators 2 to 10 are given: + +1/2 = 0.5 +1/3 = 0.(3) +1/4 = 0.25 +1/5 = 0.2 +1/6 = 0.1(6) +1/7 = 0.(142857) +1/8 = 0.125 +1/9 = 0.(1) +1/10 = 0.1 +Where 0.1(6) means 0.166666..., and has a 1-digit recurring cycle. It can be +seen that 1/7 has a 6-digit recurring cycle. + Find the value of d < 1000 for which 1/d contains the longest recurring cycle in its decimal fraction part. """ -def find_digit(numerator: int, digit: int) -> int: +def solution(numerator: int = 1, digit: int = 1000) -> int: """ Considering any range can be provided, because as per the problem, the digit d < 1000 - >>> find_digit(1, 10) + >>> solution(1, 10) 7 - >>> find_digit(10, 100) + >>> solution(10, 100) 97 - >>> find_digit(10, 1000) + >>> solution(10, 1000) 983 """ the_digit = 1 From 21581eae3bd61e032ae0a293317421408806b210 Mon Sep 17 00:00:00 2001 From: poloso Date: Wed, 7 Oct 2020 22:36:19 -0500 Subject: [PATCH 0853/1071] Fix: Multiple errors in fibonacci search. (#2659) * Fix: Multiple errors in fibonacci search. - Test lists were not ordered, this is required for Fibonacci search - Place documentation of function inside function - Create multiple different tests including, float, char and negatives - Add type hints in line with #2128 * Fix: sort of modules and delete typehint. * Apply suggestions from code review Co-authored-by: Dhruv * Correct invocation of lru_cache. * Add check for input in fibonacci and doctest. * Correct typehints to comply to numpy style. * Correct ValueError to TypeError. Co-authored-by: Dhruv * Correct doctest for TypeError. * Rename single letter names as mentioned in CONTRIBUTING.md. * Fix: Bug in big lists. * Remove print(.) in doctests. * Refactor iterator to while loop. * Update searches/fibonacci_search.py Co-authored-by: Dhruv --- searches/fibonacci_search.py | 154 ++++++++++++++++++++++++++--------- 1 file changed, 116 insertions(+), 38 deletions(-) diff --git a/searches/fibonacci_search.py b/searches/fibonacci_search.py index 67f2df505d4e..ac8ecc99a187 100644 --- a/searches/fibonacci_search.py +++ b/searches/fibonacci_search.py @@ -1,51 +1,129 @@ -# run using python fibonacci_search.py -v - """ -@params -arr: input array -val: the value to be searched -output: the index of element in the array or -1 if not found -return 0 if input array is empty +This is pure Python implementation of fibonacci search. + +Resources used: +https://en.wikipedia.org/wiki/Fibonacci_search_technique + +For doctests run following command: +python3 -m doctest -v fibonacci_search.py + +For manual testing run: +python3 fibonacci_search.py """ +from functools import lru_cache -def fibonacci_search(arr, val): +@lru_cache() +def fibonacci(k: int) -> int: + """Finds fibonacci number in index k. + Parameters + ---------- + k : + Index of fibonacci. + + Returns + ------- + int + Fibonacci number in position k. + + >>> fibonacci(0) + 0 + >>> fibonacci(2) + 1 + >>> fibonacci(5) + 5 + >>> fibonacci(15) + 610 + >>> fibonacci('a') + Traceback (most recent call last): + TypeError: k must be an integer. + >>> fibonacci(-5) + Traceback (most recent call last): + ValueError: k integer must be greater or equal to zero. """ - >>> fibonacci_search([1,6,7,0,0,0], 6) + if not isinstance(k, int): + raise TypeError("k must be an integer.") + if k < 0: + raise ValueError("k integer must be greater or equal to zero.") + if k == 0: + return 0 + elif k == 1: + return 1 + else: + return fibonacci(k - 1) + fibonacci(k - 2) + + +def fibonacci_search(arr: list, val: int) -> int: + """A pure Python implementation of a fibonacci search algorithm. + + Parameters + ---------- + arr + List of sorted elements. + val + Element to search in list. + + Returns + ------- + int + The index of the element in the array. + -1 if the element is not found. + + >>> fibonacci_search([4, 5, 6, 7], 4) + 0 + >>> fibonacci_search([4, 5, 6, 7], -10) + -1 + >>> fibonacci_search([-18, 2], -18) + 0 + >>> fibonacci_search([5], 5) + 0 + >>> fibonacci_search(['a', 'c', 'd'], 'c') 1 - >>> fibonacci_search([1,-1, 5, 2, 9], 10) + >>> fibonacci_search(['a', 'c', 'd'], 'f') + -1 + >>> fibonacci_search([], 1) -1 + >>> fibonacci_search([.1, .4 , 7], .4) + 1 >>> fibonacci_search([], 9) - 0 + -1 + >>> fibonacci_search(list(range(100)), 63) + 63 + >>> fibonacci_search(list(range(100)), 99) + 99 + >>> fibonacci_search(list(range(-100, 100, 3)), -97) + 1 + >>> fibonacci_search(list(range(-100, 100, 3)), 0) + -1 + >>> fibonacci_search(list(range(-100, 100, 5)), 0) + 20 + >>> fibonacci_search(list(range(-100, 100, 5)), 95) + 39 """ - fib_N_2 = 0 - fib_N_1 = 1 - fibNext = fib_N_1 + fib_N_2 - length = len(arr) - if length == 0: - return 0 - while fibNext < len(arr): - fib_N_2 = fib_N_1 - fib_N_1 = fibNext - fibNext = fib_N_1 + fib_N_2 - index = -1 - while fibNext > 1: - i = min(index + fib_N_2, (length - 1)) - if arr[i] < val: - fibNext = fib_N_1 - fib_N_1 = fib_N_2 - fib_N_2 = fibNext - fib_N_1 - index = i - elif arr[i] > val: - fibNext = fib_N_2 - fib_N_1 = fib_N_1 - fib_N_2 - fib_N_2 = fibNext - fib_N_1 - else: - return i - if (fib_N_1 and index < length - 1) and (arr[index + 1] == val): - return index + 1 - return -1 + len_list = len(arr) + # Find m such that F_m >= n where F_i is the i_th fibonacci number. + i = 0 + while True: + if fibonacci(i) >= len_list: + fibb_k = i + break + i += 1 + offset = 0 + while fibb_k > 0: + index_k = min( + offset + fibonacci(fibb_k - 1), len_list - 1 + ) # Prevent out of range + item_k_1 = arr[index_k] + if item_k_1 == val: + return index_k + elif val < item_k_1: + fibb_k -= 1 + elif val > item_k_1: + offset += fibonacci(fibb_k - 1) + fibb_k -= 2 + else: + return -1 if __name__ == "__main__": From f3fe29cea1b97db60ee29a78a8f89d1d84ac8c2a Mon Sep 17 00:00:00 2001 From: Edward Nuno Date: Wed, 7 Oct 2020 20:51:17 -0700 Subject: [PATCH 0854/1071] Add style improvements to Project Euler problem 8 (#3001) --- project_euler/problem_08/sol1.py | 12 +++++++----- project_euler/problem_08/sol2.py | 4 +++- project_euler/problem_08/sol3.py | 27 ++++++++++++++++++--------- 3 files changed, 28 insertions(+), 15 deletions(-) diff --git a/project_euler/problem_08/sol1.py b/project_euler/problem_08/sol1.py index 1cccdb8c85d6..db15907b3fbd 100644 --- a/project_euler/problem_08/sol1.py +++ b/project_euler/problem_08/sol1.py @@ -1,4 +1,6 @@ """ +Problem 8: https://projecteuler.net/problem=8 + The four adjacent digits in the 1000-digit number that have the greatest product are 9 × 9 × 8 × 9 = 5832. @@ -50,21 +52,21 @@ 71636269561882670428252483600823257530420752963450""" -def solution(n): +def solution(n: str = N) -> int: """Find the thirteen adjacent digits in the 1000-digit number n that have the greatest product and returns it. >>> solution(N) 23514624000 """ - LargestProduct = -sys.maxsize - 1 + largest_product = -sys.maxsize - 1 for i in range(len(n) - 12): product = 1 for j in range(13): product *= int(n[i + j]) - if product > LargestProduct: - LargestProduct = product - return LargestProduct + if product > largest_product: + largest_product = product + return largest_product if __name__ == "__main__": diff --git a/project_euler/problem_08/sol2.py b/project_euler/problem_08/sol2.py index 60bd8254f2c3..1b338a9553d7 100644 --- a/project_euler/problem_08/sol2.py +++ b/project_euler/problem_08/sol2.py @@ -1,4 +1,6 @@ """ +Problem 8: https://projecteuler.net/problem=8 + The four adjacent digits in the 1000-digit number that have the greatest product are 9 × 9 × 8 × 9 = 5832. @@ -53,7 +55,7 @@ ) -def solution(n): +def solution(n: str = N) -> int: """Find the thirteen adjacent digits in the 1000-digit number n that have the greatest product and returns it. diff --git a/project_euler/problem_08/sol3.py b/project_euler/problem_08/sol3.py index f3e87c6d3436..17f68cba57d3 100644 --- a/project_euler/problem_08/sol3.py +++ b/project_euler/problem_08/sol3.py @@ -1,4 +1,6 @@ """ +Problem 8: https://projecteuler.net/problem=8 + The four adjacent digits in the 1000-digit number that have the greatest product are 9 × 9 × 8 × 9 = 5832. @@ -50,21 +52,28 @@ 71636269561882670428252483600823257530420752963450""" -def streval(s: str) -> int: - ret = 1 - for it in s: - ret *= int(it) - return ret +def str_eval(s: str) -> int: + """Returns product of digits in given string n + + >>> str_eval("987654321") + 362880 + >>> str_eval("22222222") + 256 + """ + product = 1 + for digit in s: + product *= int(digit) + return product -def solution(n: str) -> int: +def solution(n: str = N) -> int: """Find the thirteen adjacent digits in the 1000-digit number n that have the greatest product and returns it. >>> solution(N) 23514624000 """ - LargestProduct = -sys.maxsize - 1 + largest_product = -sys.maxsize - 1 substr = n[:13] cur_index = 13 while cur_index < len(n) - 13: @@ -72,10 +81,10 @@ def solution(n: str) -> int: substr = substr[1:] + n[cur_index] cur_index += 1 else: - LargestProduct = max(LargestProduct, streval(substr)) + largest_product = max(largest_product, str_eval(substr)) substr = n[cur_index : cur_index + 13] cur_index += 13 - return LargestProduct + return largest_product if __name__ == "__main__": From 6541236fdf807a185a7eaf5c159c45fa885d85fb Mon Sep 17 00:00:00 2001 From: Wen Hong <35587716+wenhongg@users.noreply.github.com> Date: Wed, 7 Oct 2020 20:57:14 -0700 Subject: [PATCH 0855/1071] Add solution method for project_euler/problem_37 (#2998) * add solution method * fix formatting --- project_euler/problem_37/sol1.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/project_euler/problem_37/sol1.py b/project_euler/problem_37/sol1.py index e3aec5a844fe..5423aac37c01 100644 --- a/project_euler/problem_37/sol1.py +++ b/project_euler/problem_37/sol1.py @@ -87,5 +87,12 @@ def compute_truncated_primes(count: int = 11) -> list[int]: return list_truncated_primes +def solution() -> int: + """ + Returns the sum of truncated primes + """ + return sum(compute_truncated_primes(11)) + + if __name__ == "__main__": print(f"{sum(compute_truncated_primes(11)) = }") From 719c5562d9d3788a008fe7ec5ef9fcf605564ff1 Mon Sep 17 00:00:00 2001 From: Dhruv Date: Thu, 8 Oct 2020 11:27:47 +0530 Subject: [PATCH 0856/1071] Add default args and type hints for problem 7 (#2973) - Add default argument values - Add type hints - Change one letter variable names to a more descriptive one - Add doctest for `solution()` --- project_euler/problem_07/sol1.py | 39 ++++++++++++++++++-------------- project_euler/problem_07/sol2.py | 29 +++++++++++++++--------- project_euler/problem_07/sol3.py | 13 +++++++---- 3 files changed, 49 insertions(+), 32 deletions(-) diff --git a/project_euler/problem_07/sol1.py b/project_euler/problem_07/sol1.py index 373915f886e1..727d7fb7fac6 100644 --- a/project_euler/problem_07/sol1.py +++ b/project_euler/problem_07/sol1.py @@ -1,4 +1,6 @@ """ +Problem 7: https://projecteuler.net/problem=7 + By listing the first six prime numbers: 2, 3, 5, 7, 11, and 13 @@ -8,20 +10,21 @@ from math import sqrt -def is_prime(n): - if n == 2: +def is_prime(num: int) -> bool: + """Determines whether the given number is prime or not""" + if num == 2: return True - elif n % 2 == 0: + elif num % 2 == 0: return False else: - sq = int(sqrt(n)) + 1 + sq = int(sqrt(num)) + 1 for i in range(3, sq, 2): - if n % i == 0: + if num % i == 0: return False return True -def solution(n): +def solution(nth: int = 10001) -> int: """Returns the n-th prime number. >>> solution(6) @@ -36,18 +39,20 @@ def solution(n): 229 >>> solution(100) 541 + >>> solution() + 104743 """ - i = 0 - j = 1 - while i != n and j < 3: - j += 1 - if is_prime(j): - i += 1 - while i != n: - j += 2 - if is_prime(j): - i += 1 - return j + count = 0 + number = 1 + while count != nth and number < 3: + number += 1 + if is_prime(number): + count += 1 + while count != nth: + number += 2 + if is_prime(number): + count += 1 + return number if __name__ == "__main__": diff --git a/project_euler/problem_07/sol2.py b/project_euler/problem_07/sol2.py index ec182b835c84..62806e1e2e5d 100644 --- a/project_euler/problem_07/sol2.py +++ b/project_euler/problem_07/sol2.py @@ -1,4 +1,6 @@ """ +Problem 7: https://projecteuler.net/problem=7 + By listing the first six prime numbers: 2, 3, 5, 7, 11, and 13 @@ -7,14 +9,15 @@ """ -def isprime(number): +def isprime(number: int) -> bool: + """Determines whether the given number is prime or not""" for i in range(2, int(number ** 0.5) + 1): if number % i == 0: return False return True -def solution(n): +def solution(nth: int = 10001) -> int: """Returns the n-th prime number. >>> solution(6) @@ -29,34 +32,38 @@ def solution(n): 229 >>> solution(100) 541 + >>> solution() + 104743 >>> solution(3.4) 5 >>> solution(0) Traceback (most recent call last): ... - ValueError: Parameter n must be greater or equal to one. + ValueError: Parameter nth must be greater or equal to one. >>> solution(-17) Traceback (most recent call last): ... - ValueError: Parameter n must be greater or equal to one. + ValueError: Parameter nth must be greater or equal to one. >>> solution([]) Traceback (most recent call last): ... - TypeError: Parameter n must be int or passive of cast to int. + TypeError: Parameter nth must be int or passive of cast to int. >>> solution("asd") Traceback (most recent call last): ... - TypeError: Parameter n must be int or passive of cast to int. + TypeError: Parameter nth must be int or passive of cast to int. """ try: - n = int(n) + nth = int(nth) except (TypeError, ValueError): - raise TypeError("Parameter n must be int or passive of cast to int.") - if n <= 0: - raise ValueError("Parameter n must be greater or equal to one.") + raise TypeError( + "Parameter nth must be int or passive of cast to int." + ) from None + if nth <= 0: + raise ValueError("Parameter nth must be greater or equal to one.") primes = [] num = 2 - while len(primes) < n: + while len(primes) < nth: if isprime(num): primes.append(num) num += 1 diff --git a/project_euler/problem_07/sol3.py b/project_euler/problem_07/sol3.py index 602eb13b623d..1182875c05c9 100644 --- a/project_euler/problem_07/sol3.py +++ b/project_euler/problem_07/sol3.py @@ -1,4 +1,6 @@ """ +Project 7: https://projecteuler.net/problem=7 + By listing the first six prime numbers: 2, 3, 5, 7, 11, and 13 @@ -9,7 +11,8 @@ import math -def primeCheck(number): +def prime_check(number: int) -> bool: + """Determines whether a given number is prime or not""" if number % 2 == 0 and number > 2: return False return all(number % i for i in range(3, int(math.sqrt(number)) + 1, 2)) @@ -18,12 +21,12 @@ def primeCheck(number): def prime_generator(): num = 2 while True: - if primeCheck(num): + if prime_check(num): yield num num += 1 -def solution(n): +def solution(nth: int = 10001) -> int: """Returns the n-th prime number. >>> solution(6) @@ -38,8 +41,10 @@ def solution(n): 229 >>> solution(100) 541 + >>> solution() + 104743 """ - return next(itertools.islice(prime_generator(), n - 1, n)) + return next(itertools.islice(prime_generator(), nth - 1, nth)) if __name__ == "__main__": From 7d54056497eace210314454712d63a088da55180 Mon Sep 17 00:00:00 2001 From: Joan Date: Thu, 8 Oct 2020 10:27:07 +0200 Subject: [PATCH 0857/1071] [Project Euler] Fix code style in Problem 41 (#2992) * add problem title and link, fix f-string Signed-off-by: joan.rosellr * fix code style and improve doctests Signed-off-by: joan.rosellr * undo changes to the main call Signed-off-by: joan.rosellr * remove assignment operator in f-string Signed-off-by: joan.rosellr * add newline after first import to attempt to fix pre-commit workflow Signed-off-by: joan.rosellr * undo doctest changes, rename compute_pandigital_primes to solution Signed-off-by: joan.rosellr * update solution to return the actual solution instead of a list Signed-off-by: joan.rosellr * Update sol1.py Co-authored-by: Dhruv --- project_euler/problem_41/sol1.py | 34 +++++++++++++++++--------------- 1 file changed, 18 insertions(+), 16 deletions(-) diff --git a/project_euler/problem_41/sol1.py b/project_euler/problem_41/sol1.py index b4c0d842ae25..80ef2125b82a 100644 --- a/project_euler/problem_41/sol1.py +++ b/project_euler/problem_41/sol1.py @@ -1,19 +1,19 @@ -from __future__ import annotations - -from itertools import permutations -from math import sqrt - """ +Pandigital prime +Problem 41: https://projecteuler.net/problem=41 + We shall say that an n-digit number is pandigital if it makes use of all the digits 1 to n exactly once. For example, 2143 is a 4-digit pandigital and is also prime. What is the largest n-digit pandigital prime that exists? -""" -""" All pandigital numbers except for 1, 4 ,7 pandigital numbers are divisible by 3. -So we will check only 7 digit panddigital numbers to obtain the largest possible +So we will check only 7 digit pandigital numbers to obtain the largest possible pandigital prime. """ +from __future__ import annotations + +from itertools import permutations +from math import sqrt def is_prime(n: int) -> bool: @@ -35,20 +35,22 @@ def is_prime(n: int) -> bool: return True -def compute_pandigital_primes(n: int) -> list[int]: +def solution(n: int = 7) -> int: """ - Returns a list of all n-digit pandigital primes. - >>> compute_pandigital_primes(2) - [] - >>> max(compute_pandigital_primes(4)) + Returns the maximum pandigital prime number of length n. + If there are none, then it will return 0. + >>> solution(2) + 0 + >>> solution(4) 4231 - >>> max(compute_pandigital_primes(7)) + >>> solution(7) 7652413 """ pandigital_str = "".join(str(i) for i in range(1, n + 1)) perm_list = [int("".join(i)) for i in permutations(pandigital_str, n)] - return [num for num in perm_list if is_prime(num)] + pandigitals = [num for num in perm_list if is_prime(num)] + return max(pandigitals) if pandigitals else 0 if __name__ == "__main__": - print(f"{max(compute_pandigital_primes(7)) = }") + print(f"{solution() = }") From 899870be4cc759941751477b75e744de5a180a30 Mon Sep 17 00:00:00 2001 From: Edward Nuno Date: Thu, 8 Oct 2020 04:21:32 -0700 Subject: [PATCH 0858/1071] Add style improvements to Project Euler problem 9 (#3046) --- project_euler/problem_09/sol1.py | 7 ++++--- project_euler/problem_09/sol2.py | 15 ++++++++------- project_euler/problem_09/sol3.py | 4 ++-- 3 files changed, 14 insertions(+), 12 deletions(-) diff --git a/project_euler/problem_09/sol1.py b/project_euler/problem_09/sol1.py index caba6b1b1530..1ab3376cae33 100644 --- a/project_euler/problem_09/sol1.py +++ b/project_euler/problem_09/sol1.py @@ -1,5 +1,6 @@ """ -Problem Statement: +Problem 9: https://projecteuler.net/problem=9 + A Pythagorean triplet is a set of three natural numbers, a < b < c, for which, a^2 + b^2 = c^2 For example, 3^2 + 4^2 = 9 + 16 = 25 = 5^2. @@ -9,7 +10,7 @@ """ -def solution(): +def solution() -> int: """ Returns the product of a,b,c which are Pythagorean Triplet that satisfies the following: @@ -29,7 +30,7 @@ def solution(): return a * b * c -def solution_fast(): +def solution_fast() -> int: """ Returns the product of a,b,c which are Pythagorean Triplet that satisfies the following: diff --git a/project_euler/problem_09/sol2.py b/project_euler/problem_09/sol2.py index d16835ca09a2..e22ed45e8644 100644 --- a/project_euler/problem_09/sol2.py +++ b/project_euler/problem_09/sol2.py @@ -1,5 +1,6 @@ """ -Problem Statement: +Problem 9: https://projecteuler.net/problem=9 + A Pythagorean triplet is a set of three natural numbers, a < b < c, for which, a^2 + b^2 = c^2 For example, 3^2 + 4^2 = 9 + 16 = 25 = 5^2. @@ -9,27 +10,27 @@ """ -def solution(n): +def solution(n: int = 1000) -> int: """ Return the product of a,b,c which are Pythagorean Triplet that satisfies the following: 1. a < b < c 2. a**2 + b**2 = c**2 - 3. a + b + c = 1000 + 3. a + b + c = n >>> solution(1000) 31875000 """ product = -1 - d = 0 + candidate = 0 for a in range(1, n // 3): """Solving the two equations a**2+b**2=c**2 and a+b+c=N eliminating c""" b = (n * n - 2 * a * n) // (2 * n - 2 * a) c = n - a - b if c * c == (a * a + b * b): - d = a * b * c - if d >= product: - product = d + candidate = a * b * c + if candidate >= product: + product = candidate return product diff --git a/project_euler/problem_09/sol3.py b/project_euler/problem_09/sol3.py index ed27f089bd40..0900a76e6c56 100644 --- a/project_euler/problem_09/sol3.py +++ b/project_euler/problem_09/sol3.py @@ -1,5 +1,5 @@ """ -Problem Statement: +Problem 9: https://projecteuler.net/problem=9 A Pythagorean triplet is a set of three natural numbers, a < b < c, for which, @@ -12,7 +12,7 @@ """ -def solution(): +def solution() -> int: """ Returns the product of a,b,c which are Pythagorean Triplet that satisfies the following: From e24248524a56680070da6248ed7ef95d5ec71c49 Mon Sep 17 00:00:00 2001 From: Nelson Stoik Date: Thu, 8 Oct 2020 05:23:00 -0600 Subject: [PATCH 0859/1071] Coding style with default argument for project_euler problem 27 (#3020) * add default arguments and problem url * Update and rename problem_27_sol1.py to sol1.py Co-authored-by: Dhruv --- project_euler/problem_27/{problem_27_sol1.py => sol1.py} | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) rename project_euler/problem_27/{problem_27_sol1.py => sol1.py} (92%) diff --git a/project_euler/problem_27/problem_27_sol1.py b/project_euler/problem_27/sol1.py similarity index 92% rename from project_euler/problem_27/problem_27_sol1.py rename to project_euler/problem_27/sol1.py index e4833574c509..6f28b925be08 100644 --- a/project_euler/problem_27/problem_27_sol1.py +++ b/project_euler/problem_27/sol1.py @@ -1,4 +1,9 @@ """ +Project Euler Problem 27 +https://projecteuler.net/problem=27 + +Problem Statement: + Euler discovered the remarkable quadratic formula: n2 + n + 41 It turns out that the formula will produce 40 primes for the consecutive values @@ -37,7 +42,7 @@ def is_prime(k: int) -> bool: return True -def solution(a_limit: int, b_limit: int) -> int: +def solution(a_limit: int = 1000, b_limit: int = 1000) -> int: """ >>> solution(1000, 1000) -59231 From 7d0d77334f104e8f6f68cfb102ecae3505341e3a Mon Sep 17 00:00:00 2001 From: kalpanajangra <72243101+kalpanajangra@users.noreply.github.com> Date: Thu, 8 Oct 2020 17:25:23 +0530 Subject: [PATCH 0860/1071] Add Project Euler Problem 71: Fixes #2695 (#2785) --- project_euler/problem_71/__init__.py | 0 project_euler/problem_71/sol1.py | 48 ++++++++++++++++++++++++++++ 2 files changed, 48 insertions(+) create mode 100644 project_euler/problem_71/__init__.py create mode 100644 project_euler/problem_71/sol1.py diff --git a/project_euler/problem_71/__init__.py b/project_euler/problem_71/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/project_euler/problem_71/sol1.py b/project_euler/problem_71/sol1.py new file mode 100644 index 000000000000..415b127e5374 --- /dev/null +++ b/project_euler/problem_71/sol1.py @@ -0,0 +1,48 @@ +""" +Ordered fractions +Problem 71 +https://projecteuler.net/problem=71 + +Consider the fraction n/d, where n and d are positive +integers. If n int: + """ + Returns the closest numerator of the fraction immediately to the + left of given fraction (numerator/denominator) from a list of reduced + proper fractions. + >>> solution() + 428570 + >>> solution(3, 7, 8) + 2 + >>> solution(6, 7, 60) + 47 + """ + max_numerator = 0 + max_denominator = 1 + + for current_denominator in range(1, limit + 1): + current_numerator = current_denominator * numerator // denominator + if current_denominator % denominator == 0: + current_numerator -= 1 + if current_numerator * max_denominator > current_denominator * max_numerator: + max_numerator = current_numerator + max_denominator = current_denominator + return max_numerator + + +if __name__ == "__main__": + print(solution(numerator=3, denominator=7, limit=1000000)) From c2a5033f9edc9d67d235395c1bc569eaad5de8f2 Mon Sep 17 00:00:00 2001 From: Meysam Date: Thu, 8 Oct 2020 15:51:48 +0330 Subject: [PATCH 0861/1071] graphs/kruskal: add a test case to verify the correctness, fix styles (#2443) * test/graphs/kruskal: adding a test case to verify the correctness of the algorithm Fixes #2128 * grahps/kruskal: running psf/black * graphs/kruskal: read edges in a friendlier fashion Co-authored-by: Christian Clauss * Update minimum_spanning_tree_kruskal.py * fixup! Format Python code with psf/black push * Update test_min_spanning_tree_kruskal.py * updating DIRECTORY.md Co-authored-by: Christian Clauss Co-authored-by: John Law Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> --- DIRECTORY.md | 2 ++ graphs/minimum_spanning_tree_kruskal.py | 33 ++++++++--------- .../tests/test_min_spanning_tree_kruskal.py | 36 +++++++++++++++++++ 3 files changed, 55 insertions(+), 16 deletions(-) create mode 100644 graphs/tests/test_min_spanning_tree_kruskal.py diff --git a/DIRECTORY.md b/DIRECTORY.md index 6a3d31709ed6..d3a378c3a2ee 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -293,6 +293,8 @@ * [Scc Kosaraju](https://github.com/TheAlgorithms/Python/blob/master/graphs/scc_kosaraju.py) * [Strongly Connected Components](https://github.com/TheAlgorithms/Python/blob/master/graphs/strongly_connected_components.py) * [Tarjans Scc](https://github.com/TheAlgorithms/Python/blob/master/graphs/tarjans_scc.py) + * Tests + * [Test Min Spanning Tree Kruskal](https://github.com/TheAlgorithms/Python/blob/master/graphs/tests/test_min_spanning_tree_kruskal.py) ## Greedy Method * [Greedy Knapsack](https://github.com/TheAlgorithms/Python/blob/master/greedy_method/greedy_knapsack.py) diff --git a/graphs/minimum_spanning_tree_kruskal.py b/graphs/minimum_spanning_tree_kruskal.py index 91b44f6508e7..610baf4b5fe6 100644 --- a/graphs/minimum_spanning_tree_kruskal.py +++ b/graphs/minimum_spanning_tree_kruskal.py @@ -1,13 +1,5 @@ -if __name__ == "__main__": - num_nodes, num_edges = list(map(int, input().strip().split())) - - edges = [] - - for i in range(num_edges): - node1, node2, cost = list(map(int, input().strip().split())) - edges.append((i, node1, node2, cost)) - - edges = sorted(edges, key=lambda edge: edge[3]) +def kruskal(num_nodes, num_edges, edges): + edges = sorted(edges, key=lambda edge: edge[2]) parent = list(range(num_nodes)) @@ -20,13 +12,22 @@ def find_parent(i): minimum_spanning_tree = [] for edge in edges: - parent_a = find_parent(edge[1]) - parent_b = find_parent(edge[2]) + parent_a = find_parent(edge[0]) + parent_b = find_parent(edge[1]) if parent_a != parent_b: - minimum_spanning_tree_cost += edge[3] + minimum_spanning_tree_cost += edge[2] minimum_spanning_tree.append(edge) parent[parent_a] = parent_b - print(minimum_spanning_tree_cost) - for edge in minimum_spanning_tree: - print(edge) + return minimum_spanning_tree + + +if __name__ == "__main__": # pragma: no cover + num_nodes, num_edges = list(map(int, input().strip().split())) + edges = [] + + for _ in range(num_edges): + node1, node2, cost = [int(x) for x in input().strip().split()] + edges.append((node1, node2, cost)) + + kruskal(num_nodes, num_edges, edges) diff --git a/graphs/tests/test_min_spanning_tree_kruskal.py b/graphs/tests/test_min_spanning_tree_kruskal.py new file mode 100644 index 000000000000..3a527aef384f --- /dev/null +++ b/graphs/tests/test_min_spanning_tree_kruskal.py @@ -0,0 +1,36 @@ +from graphs.minimum_spanning_tree_kruskal import kruskal + + +def test_kruskal_successful_result(): + num_nodes, num_edges = 9, 14 + edges = [ + [0, 1, 4], + [0, 7, 8], + [1, 2, 8], + [7, 8, 7], + [7, 6, 1], + [2, 8, 2], + [8, 6, 6], + [2, 3, 7], + [2, 5, 4], + [6, 5, 2], + [3, 5, 14], + [3, 4, 9], + [5, 4, 10], + [1, 7, 11], + ] + + result = kruskal(num_nodes, num_edges, edges) + + expected = [ + [7, 6, 1], + [2, 8, 2], + [6, 5, 2], + [0, 1, 4], + [2, 5, 4], + [2, 3, 7], + [0, 7, 8], + [3, 4, 9], + ] + + assert sorted(expected) == sorted(result) From c9500dc89ff668f028513b9921b600ee11747b10 Mon Sep 17 00:00:00 2001 From: Aman Saxena <48122342+Aman333Saxena@users.noreply.github.com> Date: Thu, 8 Oct 2020 19:07:09 +0530 Subject: [PATCH 0862/1071] [Project Euler] Fix code style for problem 56 (#3050) * rename method for project_euler/problem #56 * Update sol1.py * Removed whitespaces Co-authored-by: Dhruv --- project_euler/problem_56/sol1.py | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/project_euler/problem_56/sol1.py b/project_euler/problem_56/sol1.py index 98094ea8eb28..8eaa6e553342 100644 --- a/project_euler/problem_56/sol1.py +++ b/project_euler/problem_56/sol1.py @@ -1,17 +1,29 @@ -def maximum_digital_sum(a: int, b: int) -> int: +""" +Project Euler Problem 56: https://projecteuler.net/problem=56 + +A googol (10^100) is a massive number: one followed by one-hundred zeros; +100^100 is almost unimaginably large: one followed by two-hundred zeros. +Despite their size, the sum of the digits in each number is only 1. + +Considering natural numbers of the form, ab, where a, b < 100, +what is the maximum digital sum? +""" + + +def solution(a: int = 100, b: int = 100) -> int: """ Considering natural numbers of the form, a**b, where a, b < 100, what is the maximum digital sum? :param a: :param b: :return: - >>> maximum_digital_sum(10,10) + >>> solution(10,10) 45 - >>> maximum_digital_sum(100,100) + >>> solution(100,100) 972 - >>> maximum_digital_sum(100,200) + >>> solution(100,200) 1872 """ From 261be28120712f66a5bdb643585659eb4e2f932a Mon Sep 17 00:00:00 2001 From: fa1l Date: Fri, 9 Oct 2020 07:43:54 +0500 Subject: [PATCH 0863/1071] Fix coding style for Project Euler problem 39 (#3023) * improvements for project euler task 39 * add tests for solution() * fixed a typo * Update sol1.py Co-authored-by: Dhruv --- project_euler/problem_39/sol1.py | 29 ++++++++++++++++++++++------- 1 file changed, 22 insertions(+), 7 deletions(-) diff --git a/project_euler/problem_39/sol1.py b/project_euler/problem_39/sol1.py index 79fa309f01c5..c8ffa8934159 100644 --- a/project_euler/problem_39/sol1.py +++ b/project_euler/problem_39/sol1.py @@ -1,4 +1,6 @@ """ +Problem 39: https://projecteuler.net/problem=39 + If p is the perimeter of a right angle triangle with integral length sides, {a,b,c}, there are exactly three solutions for p = 120. {20,48,52}, {24,45,51}, {30,40,50} @@ -8,10 +10,11 @@ from __future__ import annotations +import typing from collections import Counter -def pythagorean_triple(max_perimeter: int) -> dict: +def pythagorean_triple(max_perimeter: int) -> typing.Counter[int]: """ Returns a dictionary with keys as the perimeter of a right angled triangle and value as the number of corresponding triplets. @@ -22,19 +25,31 @@ def pythagorean_triple(max_perimeter: int) -> dict: >>> pythagorean_triple(50) Counter({12: 1, 30: 1, 24: 1, 40: 1, 36: 1, 48: 1}) """ - triplets = Counter() + triplets: typing.Counter[int] = Counter() for base in range(1, max_perimeter + 1): for perpendicular in range(base, max_perimeter + 1): hypotenuse = (base * base + perpendicular * perpendicular) ** 0.5 - if hypotenuse == int((hypotenuse)): + if hypotenuse == int(hypotenuse): perimeter = int(base + perpendicular + hypotenuse) if perimeter > max_perimeter: continue - else: - triplets[perimeter] += 1 + triplets[perimeter] += 1 return triplets +def solution(n: int = 1000) -> int: + """ + Returns perimeter with maximum solutions. + >>> solution(100) + 90 + >>> solution(200) + 180 + >>> solution(1000) + 840 + """ + triplets = pythagorean_triple(n) + return triplets.most_common(1)[0][0] + + if __name__ == "__main__": - triplets = pythagorean_triple(1000) - print(f"{triplets.most_common()[0][0] = }") + print(f"Perimeter {solution()} has maximum solutions") From a3bbcd5f88985248cf44f5797b18e81f9115f85e Mon Sep 17 00:00:00 2001 From: Suyash Gupta Date: Fri, 9 Oct 2020 08:33:23 +0530 Subject: [PATCH 0864/1071] [Project Euler] Added type hints and refactored the code for Problem 14 (#3047) * added type hints and refactored the code a bit * made output statement more explicit * used f-strings and updated type hints * modified solution function to return an integer solution * updated docstring * Update sol1.py * Update sol2.py Co-authored-by: Dhruv --- project_euler/problem_14/sol1.py | 19 +++++++++---------- project_euler/problem_14/sol2.py | 23 +++++++++++------------ 2 files changed, 20 insertions(+), 22 deletions(-) diff --git a/project_euler/problem_14/sol1.py b/project_euler/problem_14/sol1.py index fda45bc94bb7..1745ec931e5a 100644 --- a/project_euler/problem_14/sol1.py +++ b/project_euler/problem_14/sol1.py @@ -1,4 +1,6 @@ """ +Problem 14: https://projecteuler.net/problem=14 + Problem Statement: The following iterative sequence is defined for the set of positive integers: @@ -17,7 +19,7 @@ """ -def solution(n): +def solution(n: int = 1000000) -> int: """Returns the number under n that generates the longest sequence using the formula: n → n/2 (n is even) @@ -25,13 +27,13 @@ def solution(n): # The code below has been commented due to slow execution affecting Travis. # >>> solution(1000000) - # {'counter': 525, 'largest_number': 837799} + # 837799 >>> solution(200) - {'counter': 125, 'largest_number': 171} + 171 >>> solution(5000) - {'counter': 238, 'largest_number': 3711} + 3711 >>> solution(15000) - {'counter': 276, 'largest_number': 13255} + 13255 """ largest_number = 0 pre_counter = 0 @@ -51,11 +53,8 @@ def solution(n): if counter > pre_counter: largest_number = input1 pre_counter = counter - return {"counter": pre_counter, "largest_number": largest_number} + return largest_number if __name__ == "__main__": - result = solution(int(input().strip())) - print( - ("Largest Number:", result["largest_number"], "->", result["counter"], "digits") - ) + print(solution(int(input().strip()))) diff --git a/project_euler/problem_14/sol2.py b/project_euler/problem_14/sol2.py index 375a34c72f57..20ad96327498 100644 --- a/project_euler/problem_14/sol2.py +++ b/project_euler/problem_14/sol2.py @@ -1,4 +1,6 @@ """ +Problem 14: https://projecteuler.net/problem=14 + Collatz conjecture: start with any positive integer n. Next term obtained from the previous term as follows: @@ -23,9 +25,10 @@ Which starting number, under one million, produces the longest chain? """ +from typing import List -def collatz_sequence(n): +def collatz_sequence(n: int) -> List[int]: """Returns the Collatz sequence for n.""" sequence = [n] while n != 1: @@ -37,27 +40,23 @@ def collatz_sequence(n): return sequence -def solution(n): +def solution(n: int = 1000000) -> int: """Returns the number under n that generates the longest Collatz sequence. # The code below has been commented due to slow execution affecting Travis. # >>> solution(1000000) - # {'counter': 525, 'largest_number': 837799} + # 837799 >>> solution(200) - {'counter': 125, 'largest_number': 171} + 171 >>> solution(5000) - {'counter': 238, 'largest_number': 3711} + 3711 >>> solution(15000) - {'counter': 276, 'largest_number': 13255} + 13255 """ result = max([(len(collatz_sequence(i)), i) for i in range(1, n)]) - return {"counter": result[0], "largest_number": result[1]} + return result[1] if __name__ == "__main__": - result = solution(int(input().strip())) - print( - "Longest Collatz sequence under one million is %d with length %d" - % (result["largest_number"], result["counter"]) - ) + print(solution(int(input().strip()))) From 1c0deb88ac4eb718733b3870dac6203885a9de2b Mon Sep 17 00:00:00 2001 From: Nelson Stoik Date: Thu, 8 Oct 2020 21:05:13 -0600 Subject: [PATCH 0865/1071] Coding style change for project_euler problem 36 and 35 (#3062) * add problem url. Add typehint, default value and doctest * run black * add project url. add solution function for problem 35 * add space between imports on problem 35 * Update sol1.py * Update sol1.py Co-authored-by: Dhruv --- project_euler/problem_35/sol1.py | 13 +++++++++++++ project_euler/problem_36/sol1.py | 30 +++++++++++++++++++++++------- 2 files changed, 36 insertions(+), 7 deletions(-) diff --git a/project_euler/problem_35/sol1.py b/project_euler/problem_35/sol1.py index 5f023c56ae50..17a4e9088ae2 100644 --- a/project_euler/problem_35/sol1.py +++ b/project_euler/problem_35/sol1.py @@ -1,4 +1,9 @@ """ +Project Euler Problem 35 +https://projecteuler.net/problem=35 + +Problem Statement: + The number 197 is called a circular prime because all rotations of the digits: 197, 971, and 719, are themselves prime. There are thirteen such primes below 100: 2, 3, 5, 7, 11, 13, 17, 31, 37, 71, 73, @@ -65,5 +70,13 @@ def find_circular_primes(limit: int = 1000000) -> list[int]: return result +def solution() -> int: + """ + >>> solution() + 55 + """ + return len(find_circular_primes()) + + if __name__ == "__main__": print(f"{len(find_circular_primes()) = }") diff --git a/project_euler/problem_36/sol1.py b/project_euler/problem_36/sol1.py index 39088cf25dd4..13a749862e5f 100644 --- a/project_euler/problem_36/sol1.py +++ b/project_euler/problem_36/sol1.py @@ -1,4 +1,9 @@ """ +Project Euler Problem 36 +https://projecteuler.net/problem=36 + +Problem Statement: + Double-base palindromes Problem 36 The decimal number, 585 = 10010010012 (binary), is palindromic in both bases. @@ -10,17 +15,28 @@ leading zeros.) """ +from typing import Union -def is_palindrome(n): - n = str(n) - if n == n[::-1]: - return True - else: - return False +def is_palindrome(n: Union[int, str]) -> bool: + """ + Return true if the input n is a palindrome. + Otherwise return false. n can be an integer or a string. + + >>> is_palindrome(909) + True + >>> is_palindrome(908) + False + >>> is_palindrome('10101') + True + >>> is_palindrome('10111') + False + """ + n = str(n) + return True if n == n[::-1] else False -def solution(n): +def solution(n: int = 1000000): """Return the sum of all numbers, less than n , which are palindromic in base 10 and base 2. From 216a194e9a0b2bbf6da8c64bc16322804d960296 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juan=20Jos=C3=A9=20Torres?= <43612178+JuanJTorres11@users.noreply.github.com> Date: Thu, 8 Oct 2020 22:16:55 -0500 Subject: [PATCH 0866/1071] [Project Euler] Fix code style for problems 15 and 34 (#3076) * Add type hints and default args to problem 15 * Changes function's name to solution in problem 34 * Update sol1.py * Update sol1.py Co-authored-by: Dhruv --- project_euler/problem_15/sol1.py | 47 ++++++++++++-------------------- project_euler/problem_34/sol1.py | 8 ++++-- 2 files changed, 23 insertions(+), 32 deletions(-) diff --git a/project_euler/problem_15/sol1.py b/project_euler/problem_15/sol1.py index feeb3ddab57a..da079d26120a 100644 --- a/project_euler/problem_15/sol1.py +++ b/project_euler/problem_15/sol1.py @@ -1,4 +1,6 @@ """ +Problem 15: https://projecteuler.net/problem=15 + Starting in the top left corner of a 2×2 grid, and only being able to move to the right and down, there are exactly 6 routes to the bottom right corner. How many such routes are there through a 20×20 grid? @@ -6,34 +8,21 @@ from math import factorial -def lattice_paths(n): +def solution(n: int = 20) -> int: """ - Returns the number of paths possible in a n x n grid starting at top left - corner going to bottom right corner and being able to move right and down - only. - - bruno@bruno-laptop:~/git/Python/project_euler/problem_15$ python3 sol1.py 50 - 1.008913445455642e+29 - bruno@bruno-laptop:~/git/Python/project_euler/problem_15$ python3 sol1.py 25 - 126410606437752.0 - bruno@bruno-laptop:~/git/Python/project_euler/problem_15$ python3 sol1.py 23 - 8233430727600.0 - bruno@bruno-laptop:~/git/Python/project_euler/problem_15$ python3 sol1.py 15 - 155117520.0 - bruno@bruno-laptop:~/git/Python/project_euler/problem_15$ python3 sol1.py 1 - 2.0 - - >>> lattice_paths(25) - 126410606437752 - >>> lattice_paths(23) - 8233430727600 - >>> lattice_paths(20) - 137846528820 - >>> lattice_paths(15) - 155117520 - >>> lattice_paths(1) - 2 - + Returns the number of paths possible in a n x n grid starting at top left + corner going to bottom right corner and being able to move right and down + only. + >>> solution(25) + 126410606437752 + >>> solution(23) + 8233430727600 + >>> solution(20) + 137846528820 + >>> solution(15) + 155117520 + >>> solution(1) + 2 """ n = 2 * n # middle entry of odd rows starting at row 3 is the solution for n = 1, # 2, 3,... @@ -46,10 +35,10 @@ def lattice_paths(n): import sys if len(sys.argv) == 1: - print(lattice_paths(20)) + print(solution(20)) else: try: n = int(sys.argv[1]) - print(lattice_paths(n)) + print(solution(n)) except ValueError: print("Invalid entry - please enter a number.") diff --git a/project_euler/problem_34/sol1.py b/project_euler/problem_34/sol1.py index c19fac5de897..78b318b76d06 100644 --- a/project_euler/problem_34/sol1.py +++ b/project_euler/problem_34/sol1.py @@ -1,4 +1,6 @@ """ +Problem 34: https://projecteuler.net/problem=34 + 145 is a curious number, as 1! + 4! + 5! = 1 + 24 + 120 = 145. Find the sum of all numbers which are equal to the sum of the factorial of their digits. Note: As 1! = 1 and 2! = 2 are not sums they are not included. @@ -18,12 +20,12 @@ def sum_of_digit_factorial(n: int) -> int: return sum(factorial(int(char)) for char in str(n)) -def compute() -> int: +def solution() -> int: """ Returns the sum of all numbers whose sum of the factorials of all digits add up to the number itself. - >>> compute() + >>> solution() 40730 """ limit = 7 * factorial(9) + 1 @@ -31,4 +33,4 @@ def compute() -> int: if __name__ == "__main__": - print(f"{compute()} = ") + print(f"{solution()} = ") From 10fe9d9be4ea1d00a9a40bffb58f2c5fdeebe9f4 Mon Sep 17 00:00:00 2001 From: fa1l Date: Fri, 9 Oct 2020 11:39:44 +0500 Subject: [PATCH 0867/1071] Coding style improvements for project_euler problem 45 & 16 (#3087) * improvements for project euler task 45 * fixed documentation * update pe_16/sol1.py * update pe_16/sol2.py * revert solution changes for sol1 * revert solution changes for sol2 * remove trailing spaces in sol1 * Update sol1.py Co-authored-by: Dhruv --- project_euler/problem_16/sol1.py | 4 +++- project_euler/problem_16/sol2.py | 4 +++- project_euler/problem_45/sol1.py | 8 +++++--- 3 files changed, 11 insertions(+), 5 deletions(-) diff --git a/project_euler/problem_16/sol1.py b/project_euler/problem_16/sol1.py index 67c50ac87876..f6620aa9482f 100644 --- a/project_euler/problem_16/sol1.py +++ b/project_euler/problem_16/sol1.py @@ -1,11 +1,13 @@ """ +Problem 16: https://projecteuler.net/problem=16 + 2^15 = 32768 and the sum of its digits is 3 + 2 + 7 + 6 + 8 = 26. What is the sum of the digits of the number 2^1000? """ -def solution(power): +def solution(power: int = 1000) -> int: """Returns the sum of the digits of the number 2^power. >>> solution(1000) 1366 diff --git a/project_euler/problem_16/sol2.py b/project_euler/problem_16/sol2.py index cd724d89a9e3..304d27d1e5d0 100644 --- a/project_euler/problem_16/sol2.py +++ b/project_euler/problem_16/sol2.py @@ -1,11 +1,13 @@ """ +Problem 16: https://projecteuler.net/problem=16 + 2^15 = 32768 and the sum of its digits is 3 + 2 + 7 + 6 + 8 = 26. What is the sum of the digits of the number 2^1000? """ -def solution(power): +def solution(power: int = 1000) -> int: """Returns the sum of the digits of the number 2^power. >>> solution(1000) diff --git a/project_euler/problem_45/sol1.py b/project_euler/problem_45/sol1.py index ed66e6fab210..cb30a4d97339 100644 --- a/project_euler/problem_45/sol1.py +++ b/project_euler/problem_45/sol1.py @@ -1,4 +1,6 @@ """ +Problem 45: https://projecteuler.net/problem=45 + Triangle, pentagonal, and hexagonal numbers are generated by the following formulae: Triangle T(n) = (n * (n + 1)) / 2 1, 3, 6, 10, 15, ... Pentagonal P(n) = (n * (3 * n − 1)) / 2 1, 5, 12, 22, 35, ... @@ -39,10 +41,10 @@ def is_pentagonal(n: int) -> bool: return ((1 + root) / 6) % 1 == 0 -def compute_num(start: int = 144) -> int: +def solution(start: int = 144) -> int: """ Returns the next number which is traingular, pentagonal and hexagonal. - >>> compute_num(144) + >>> solution(144) 1533776805 """ n = start @@ -54,4 +56,4 @@ def compute_num(start: int = 144) -> int: if __name__ == "__main__": - print(f"{compute_num(144)} = ") + print(f"{solution()} = ") From ec9f6b6467b799f57b1e4d0ed7efa418085bad48 Mon Sep 17 00:00:00 2001 From: Kushagra Bansal Date: Fri, 9 Oct 2020 17:51:04 +0530 Subject: [PATCH 0868/1071] Created max_sum_sliding_window in Python/other (#3065) * Add files via upload * Update max_sum_sliding_window.py * Update max_sum_sliding_window.py * Update max_sum_sliding_window.py * Added more tests * Update max_sum_sliding_window.py * Update max_sum_sliding_window.py --- other/max_sum_sliding_window.py | 45 +++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100644 other/max_sum_sliding_window.py diff --git a/other/max_sum_sliding_window.py b/other/max_sum_sliding_window.py new file mode 100644 index 000000000000..4be7d786f215 --- /dev/null +++ b/other/max_sum_sliding_window.py @@ -0,0 +1,45 @@ +""" +Given an array of integer elements and an integer 'k', we are required to find the +maximum sum of 'k' consecutive elements in the array. + +Instead of using a nested for loop, in a Brute force approach we will use a technique +called 'Window sliding technique' where the nested loops can be converted to a single +loop to reduce time complexity. +""" +from typing import List + + +def max_sum_in_array(array: List[int], k: int) -> int: + """ + Returns the maximum sum of k consecutive elements + >>> arr = [1, 4, 2, 10, 2, 3, 1, 0, 20] + >>> k = 4 + >>> max_sum_in_array(arr, k) + 24 + >>> k = 10 + >>> max_sum_in_array(arr,k) + Traceback (most recent call last): + ... + ValueError: Invalid Input + >>> arr = [1, 4, 2, 10, 2, 13, 1, 0, 2] + >>> k = 4 + >>> max_sum_in_array(arr, k) + 27 + """ + if len(array) < k or k < 0: + raise ValueError("Invalid Input") + max_sum = current_sum = sum(array[:k]) + for i in range(len(array) - k): + current_sum = current_sum - array[i] + array[i + k] + max_sum = max(max_sum, current_sum) + return max_sum + + +if __name__ == "__main__": + from doctest import testmod + from random import randint + + testmod() + array = [randint(-1000, 1000) for i in range(100)] + k = randint(0, 110) + print(f"The maximum sum of {k} consecutive elements is {max_sum_in_array(array,k)}") From c83e4b77c513506c0ae16c18a53d1a32b60182a4 Mon Sep 17 00:00:00 2001 From: fpringle Date: Fri, 9 Oct 2020 19:11:00 +0200 Subject: [PATCH 0869/1071] Added solution for Project Euler problem 125 (#3073) * Added solution for Project Euler problem 125 * Fixed typos --- project_euler/problem_125/__init__.py | 0 project_euler/problem_125/sol1.py | 56 +++++++++++++++++++++++++++ 2 files changed, 56 insertions(+) create mode 100644 project_euler/problem_125/__init__.py create mode 100644 project_euler/problem_125/sol1.py diff --git a/project_euler/problem_125/__init__.py b/project_euler/problem_125/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/project_euler/problem_125/sol1.py b/project_euler/problem_125/sol1.py new file mode 100644 index 000000000000..afc1f2890cef --- /dev/null +++ b/project_euler/problem_125/sol1.py @@ -0,0 +1,56 @@ +""" +Problem 125: https://projecteuler.net/problem=125 + +The palindromic number 595 is interesting because it can be written as the sum +of consecutive squares: 6^2 + 7^2 + 8^2 + 9^2 + 10^2 + 11^2 + 12^2. + +There are exactly eleven palindromes below one-thousand that can be written as +consecutive square sums, and the sum of these palindromes is 4164. Note that +1 = 0^2 + 1^2 has not been included as this problem is concerned with the +squares of positive integers. + +Find the sum of all the numbers less than 10^8 that are both palindromic and can +be written as the sum of consecutive squares. +""" + + +def is_palindrome(n: int) -> bool: + """ + Check if an integer is palindromic. + >>> is_palindrome(12521) + True + >>> is_palindrome(12522) + False + >>> is_palindrome(12210) + False + """ + if n % 10 == 0: + return False + s = str(n) + return s == s[::-1] + + +def solution() -> int: + """ + Returns the sum of all numbers less than 1e8 that are both palindromic and + can be written as the sum of consecutive squares. + """ + LIMIT = 10 ** 8 + answer = set() + first_square = 1 + sum_squares = 5 + while sum_squares < LIMIT: + last_square = first_square + 1 + while sum_squares < LIMIT: + if is_palindrome(sum_squares): + answer.add(sum_squares) + last_square += 1 + sum_squares += last_square ** 2 + first_square += 1 + sum_squares = first_square ** 2 + (first_square + 1) ** 2 + + return sum(answer) + + +if __name__ == "__main__": + print(solution()) From 927e14e7f2698ea4ac4525161ca5afbf351f22ab Mon Sep 17 00:00:00 2001 From: Carlos Meza Date: Fri, 9 Oct 2020 22:33:00 -0700 Subject: [PATCH 0870/1071] Cleanup Project Euler Problem 01 (#2900) * mv str statement into docstr * rename var to avoid redefining builtin * clean up module docstr --- project_euler/problem_01/sol1.py | 2 +- project_euler/problem_01/sol2.py | 12 ++++++------ project_euler/problem_01/sol3.py | 20 ++++++++++---------- project_euler/problem_01/sol4.py | 2 +- project_euler/problem_01/sol5.py | 5 ++--- project_euler/problem_01/sol6.py | 2 +- project_euler/problem_01/sol7.py | 2 +- 7 files changed, 22 insertions(+), 23 deletions(-) diff --git a/project_euler/problem_01/sol1.py b/project_euler/problem_01/sol1.py index 2dbb60cdb16f..385bbbbf43b3 100644 --- a/project_euler/problem_01/sol1.py +++ b/project_euler/problem_01/sol1.py @@ -1,7 +1,7 @@ """ Problem Statement: If we list all the natural numbers below 10 that are multiples of 3 or 5, -we get 3,5,6 and 9. The sum of these multiples is 23. +we get 3, 5, 6 and 9. The sum of these multiples is 23. Find the sum of all the multiples of 3 or 5 below N. """ diff --git a/project_euler/problem_01/sol2.py b/project_euler/problem_01/sol2.py index 212fd40562d1..f08f548cb752 100644 --- a/project_euler/problem_01/sol2.py +++ b/project_euler/problem_01/sol2.py @@ -1,7 +1,7 @@ """ Problem Statement: If we list all the natural numbers below 10 that are multiples of 3 or 5, -we get 3,5,6 and 9. The sum of these multiples is 23. +we get 3, 5, 6 and 9. The sum of these multiples is 23. Find the sum of all the multiples of 3 or 5 below N. """ @@ -19,14 +19,14 @@ def solution(n: int = 1000) -> int: 83700 """ - sum = 0 + total = 0 terms = (n - 1) // 3 - sum += ((terms) * (6 + (terms - 1) * 3)) // 2 # sum of an A.P. + total += ((terms) * (6 + (terms - 1) * 3)) // 2 # total of an A.P. terms = (n - 1) // 5 - sum += ((terms) * (10 + (terms - 1) * 5)) // 2 + total += ((terms) * (10 + (terms - 1) * 5)) // 2 terms = (n - 1) // 15 - sum -= ((terms) * (30 + (terms - 1) * 15)) // 2 - return sum + total -= ((terms) * (30 + (terms - 1) * 15)) // 2 + return total if __name__ == "__main__": diff --git a/project_euler/problem_01/sol3.py b/project_euler/problem_01/sol3.py index faa505615924..67cb83faf238 100644 --- a/project_euler/problem_01/sol3.py +++ b/project_euler/problem_01/sol3.py @@ -1,7 +1,7 @@ """ Problem Statement: If we list all the natural numbers below 10 that are multiples of 3 or 5, -we get 3,5,6 and 9. The sum of these multiples is 23. +we get 3, 5, 6 and 9. The sum of these multiples is 23. Find the sum of all the multiples of 3 or 5 below N. """ @@ -22,38 +22,38 @@ def solution(n: int = 1000) -> int: 83700 """ - sum = 0 + total = 0 num = 0 while 1: num += 3 if num >= n: break - sum += num + total += num num += 2 if num >= n: break - sum += num + total += num num += 1 if num >= n: break - sum += num + total += num num += 3 if num >= n: break - sum += num + total += num num += 1 if num >= n: break - sum += num + total += num num += 2 if num >= n: break - sum += num + total += num num += 3 if num >= n: break - sum += num - return sum + total += num + return total if __name__ == "__main__": diff --git a/project_euler/problem_01/sol4.py b/project_euler/problem_01/sol4.py index d5e86320da5e..77f323695898 100644 --- a/project_euler/problem_01/sol4.py +++ b/project_euler/problem_01/sol4.py @@ -1,7 +1,7 @@ """ Problem Statement: If we list all the natural numbers below 10 that are multiples of 3 or 5, -we get 3,5,6 and 9. The sum of these multiples is 23. +we get 3, 5, 6 and 9. The sum of these multiples is 23. Find the sum of all the multiples of 3 or 5 below N. """ diff --git a/project_euler/problem_01/sol5.py b/project_euler/problem_01/sol5.py index eae62ef5c75c..256516802ca0 100644 --- a/project_euler/problem_01/sol5.py +++ b/project_euler/problem_01/sol5.py @@ -1,15 +1,14 @@ """ Problem Statement: If we list all the natural numbers below 10 that are multiples of 3 or 5, -we get 3,5,6 and 9. The sum of these multiples is 23. +we get 3, 5, 6 and 9. The sum of these multiples is 23. Find the sum of all the multiples of 3 or 5 below N. """ -"""A straightforward pythonic solution using list comprehension""" - def solution(n: int = 1000) -> int: """Returns the sum of all the multiples of 3 or 5 below n. + A straightforward pythonic solution using list comprehension. >>> solution(3) 0 diff --git a/project_euler/problem_01/sol6.py b/project_euler/problem_01/sol6.py index ca08a4a639ab..5f60512a73fb 100644 --- a/project_euler/problem_01/sol6.py +++ b/project_euler/problem_01/sol6.py @@ -1,7 +1,7 @@ """ Problem Statement: If we list all the natural numbers below 10 that are multiples of 3 or 5, -we get 3,5,6 and 9. The sum of these multiples is 23. +we get 3, 5, 6 and 9. The sum of these multiples is 23. Find the sum of all the multiples of 3 or 5 below N. """ diff --git a/project_euler/problem_01/sol7.py b/project_euler/problem_01/sol7.py index 8e53d2a81fb9..5761c00f2996 100644 --- a/project_euler/problem_01/sol7.py +++ b/project_euler/problem_01/sol7.py @@ -1,7 +1,7 @@ """ Problem Statement: If we list all the natural numbers below 10 that are multiples of 3 or 5, -we get 3,5,6 and 9. The sum of these multiples is 23. +we get 3, 5, 6 and 9. The sum of these multiples is 23. Find the sum of all the multiples of 3 or 5 below N. """ From 8bd4ca67be5777db624063ed59a171e714d99487 Mon Sep 17 00:00:00 2001 From: Tapajyoti Bose <44058757+ruppysuppy@users.noreply.github.com> Date: Sat, 10 Oct 2020 11:49:36 +0530 Subject: [PATCH 0871/1071] Added all sub sequence type hints [Hacktober Fest] (#3123) * updating DIRECTORY.md * chore(all-subsequence): added type hints [HACKTOBER-FEST] * updating DIRECTORY.md Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> --- DIRECTORY.md | 9 ++++++--- backtracking/all_subsequences.py | 8 ++++++-- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/DIRECTORY.md b/DIRECTORY.md index d3a378c3a2ee..80859356cca9 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -473,6 +473,7 @@ * [Binary Exponentiation 2](https://github.com/TheAlgorithms/Python/blob/master/other/binary_exponentiation_2.py) * [Detecting English Programmatically](https://github.com/TheAlgorithms/Python/blob/master/other/detecting_english_programmatically.py) * [Dijkstra Bankers Algorithm](https://github.com/TheAlgorithms/Python/blob/master/other/dijkstra_bankers_algorithm.py) + * [Doomsday](https://github.com/TheAlgorithms/Python/blob/master/other/doomsday.py) * [Euclidean Gcd](https://github.com/TheAlgorithms/Python/blob/master/other/euclidean_gcd.py) * [Fischer Yates Shuffle](https://github.com/TheAlgorithms/Python/blob/master/other/fischer_yates_shuffle.py) * [Frequency Finder](https://github.com/TheAlgorithms/Python/blob/master/other/frequency_finder.py) @@ -487,6 +488,7 @@ * [Lru Cache](https://github.com/TheAlgorithms/Python/blob/master/other/lru_cache.py) * [Magicdiamondpattern](https://github.com/TheAlgorithms/Python/blob/master/other/magicdiamondpattern.py) * [Markov Chain](https://github.com/TheAlgorithms/Python/blob/master/other/markov_chain.py) + * [Max Sum Sliding Window](https://github.com/TheAlgorithms/Python/blob/master/other/max_sum_sliding_window.py) * [Nested Brackets](https://github.com/TheAlgorithms/Python/blob/master/other/nested_brackets.py) * [Palindrome](https://github.com/TheAlgorithms/Python/blob/master/other/palindrome.py) * [Password Generator](https://github.com/TheAlgorithms/Python/blob/master/other/password_generator.py) @@ -529,7 +531,6 @@ * [Sol2](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_06/sol2.py) * [Sol3](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_06/sol3.py) * [Sol4](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_06/sol4.py) - * [Test Solutions](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_06/test_solutions.py) * Problem 07 * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_07/sol1.py) * [Sol2](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_07/sol2.py) @@ -595,11 +596,11 @@ * Problem 26 * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_26/sol1.py) * Problem 27 - * [Problem 27 Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_27/problem_27_sol1.py) + * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_27/sol1.py) * Problem 28 * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_28/sol1.py) * Problem 29 - * [Solution](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_29/solution.py) + * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_29/sol1.py) * Problem 30 * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_30/sol1.py) * Problem 31 @@ -656,6 +657,8 @@ * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_63/sol1.py) * Problem 67 * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_67/sol1.py) + * Problem 71 + * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_71/sol1.py) * Problem 76 * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_76/sol1.py) * Problem 97 diff --git a/backtracking/all_subsequences.py b/backtracking/all_subsequences.py index 3851c4ab0118..9086e3a3d659 100644 --- a/backtracking/all_subsequences.py +++ b/backtracking/all_subsequences.py @@ -1,3 +1,5 @@ +from typing import Any, List + """ In this problem, we want to determine all possible subsequences of the given sequence. We use backtracking to solve this problem. @@ -7,11 +9,13 @@ """ -def generate_all_subsequences(sequence): +def generate_all_subsequences(sequence: List[Any]) -> None: create_state_space_tree(sequence, [], 0) -def create_state_space_tree(sequence, current_subsequence, index): +def create_state_space_tree( + sequence: List[Any], current_subsequence: List[Any], index: int +) -> None: """ Creates a state space tree to iterate through each branch using DFS. We know that each state has exactly two children. From 02d3ab81bd1e2014f51b94457bb4c5e373237e4a Mon Sep 17 00:00:00 2001 From: Tapajyoti Bose <44058757+ruppysuppy@users.noreply.github.com> Date: Sat, 10 Oct 2020 12:18:52 +0530 Subject: [PATCH 0872/1071] feat: added prim's algorithm v2 (#2742) * feat: added prim's algorithm v2 * updating DIRECTORY.md * chore: small tweaks * fixup! Format Python code with psf/black push * chore: added algorithm descriptor Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> --- DIRECTORY.md | 1 + graphs/minimum_spanning_tree_prims2.py | 271 +++++++++++++++++++++++++ 2 files changed, 272 insertions(+) create mode 100644 graphs/minimum_spanning_tree_prims2.py diff --git a/DIRECTORY.md b/DIRECTORY.md index 80859356cca9..b57cb2eb131b 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -287,6 +287,7 @@ * [Minimum Spanning Tree Kruskal](https://github.com/TheAlgorithms/Python/blob/master/graphs/minimum_spanning_tree_kruskal.py) * [Minimum Spanning Tree Kruskal2](https://github.com/TheAlgorithms/Python/blob/master/graphs/minimum_spanning_tree_kruskal2.py) * [Minimum Spanning Tree Prims](https://github.com/TheAlgorithms/Python/blob/master/graphs/minimum_spanning_tree_prims.py) + * [Minimum Spanning Tree Prims2](https://github.com/TheAlgorithms/Python/blob/master/graphs/minimum_spanning_tree_prims2.py) * [Multi Heuristic Astar](https://github.com/TheAlgorithms/Python/blob/master/graphs/multi_heuristic_astar.py) * [Page Rank](https://github.com/TheAlgorithms/Python/blob/master/graphs/page_rank.py) * [Prim](https://github.com/TheAlgorithms/Python/blob/master/graphs/prim.py) diff --git a/graphs/minimum_spanning_tree_prims2.py b/graphs/minimum_spanning_tree_prims2.py new file mode 100644 index 000000000000..10ed736c9d17 --- /dev/null +++ b/graphs/minimum_spanning_tree_prims2.py @@ -0,0 +1,271 @@ +""" +Prim's (also known as Jarník's) algorithm is a greedy algorithm that finds a minimum +spanning tree for a weighted undirected graph. This means it finds a subset of the +edges that forms a tree that includes every vertex, where the total weight of all the +edges in the tree is minimized. The algorithm operates by building this tree one vertex +at a time, from an arbitrary starting vertex, at each step adding the cheapest possible +connection from the tree to another vertex. +""" + +from sys import maxsize +from typing import Dict, Optional, Tuple, Union + + +def get_parent_position(position: int) -> int: + """ + heap helper function get the position of the parent of the current node + + >>> get_parent_position(1) + 0 + >>> get_parent_position(2) + 0 + """ + return (position - 1) // 2 + + +def get_child_left_position(position: int) -> int: + """ + heap helper function get the position of the left child of the current node + + >>> get_child_left_position(0) + 1 + """ + return (2 * position) + 1 + + +def get_child_right_position(position: int) -> int: + """ + heap helper function get the position of the right child of the current node + + >>> get_child_right_position(0) + 2 + """ + return (2 * position) + 2 + + +class MinPriorityQueue: + """ + Minimum Priority Queue Class + + Functions: + is_empty: function to check if the priority queue is empty + push: function to add an element with given priority to the queue + extract_min: function to remove and return the element with lowest weight (highest + priority) + update_key: function to update the weight of the given key + _bubble_up: helper function to place a node at the proper position (upward + movement) + _bubble_down: helper function to place a node at the proper position (downward + movement) + _swap_nodes: helper function to swap the nodes at the given positions + + >>> queue = MinPriorityQueue() + + >>> queue.push(1, 1000) + >>> queue.push(2, 100) + >>> queue.push(3, 4000) + >>> queue.push(4, 3000) + + >>> print(queue.extract_min()) + 2 + + >>> queue.update_key(4, 50) + + >>> print(queue.extract_min()) + 4 + >>> print(queue.extract_min()) + 1 + >>> print(queue.extract_min()) + 3 + """ + + def __init__(self) -> None: + self.heap = [] + self.position_map = {} + self.elements = 0 + + def __len__(self) -> int: + return self.elements + + def __repr__(self) -> str: + return str(self.heap) + + def is_empty(self) -> bool: + # Check if the priority queue is empty + return self.elements == 0 + + def push(self, elem: Union[int, str], weight: int) -> None: + # Add an element with given priority to the queue + self.heap.append((elem, weight)) + self.position_map[elem] = self.elements + self.elements += 1 + self._bubble_up(elem) + + def extract_min(self) -> Union[int, str]: + # Remove and return the element with lowest weight (highest priority) + if self.elements > 1: + self._swap_nodes(0, self.elements - 1) + elem, _ = self.heap.pop() + del self.position_map[elem] + self.elements -= 1 + if self.elements > 0: + bubble_down_elem, _ = self.heap[0] + self._bubble_down(bubble_down_elem) + return elem + + def update_key(self, elem: Union[int, str], weight: int) -> None: + # Update the weight of the given key + position = self.position_map[elem] + self.heap[position] = (elem, weight) + if position > 0: + parent_position = get_parent_position(position) + _, parent_weight = self.heap[parent_position] + if parent_weight > weight: + self._bubble_up(elem) + else: + self._bubble_down(elem) + else: + self._bubble_down(elem) + + def _bubble_up(self, elem: Union[int, str]) -> None: + # Place a node at the proper position (upward movement) [to be used internally + # only] + curr_pos = self.position_map[elem] + if curr_pos == 0: + return + parent_position = get_parent_position(curr_pos) + _, weight = self.heap[curr_pos] + _, parent_weight = self.heap[parent_position] + if parent_weight > weight: + self._swap_nodes(parent_position, curr_pos) + return self._bubble_up(elem) + return + + def _bubble_down(self, elem: Union[int, str]) -> None: + # Place a node at the proper position (downward movement) [to be used + # internally only] + curr_pos = self.position_map[elem] + _, weight = self.heap[curr_pos] + child_left_position = get_child_left_position(curr_pos) + child_right_position = get_child_right_position(curr_pos) + if child_left_position < self.elements and child_right_position < self.elements: + _, child_left_weight = self.heap[child_left_position] + _, child_right_weight = self.heap[child_right_position] + if child_right_weight < child_left_weight: + if child_right_weight < weight: + self._swap_nodes(child_right_position, curr_pos) + return self._bubble_down(elem) + if child_left_position < self.elements: + _, child_left_weight = self.heap[child_left_position] + if child_left_weight < weight: + self._swap_nodes(child_left_position, curr_pos) + return self._bubble_down(elem) + else: + return + if child_right_position < self.elements: + _, child_right_weight = self.heap[child_right_position] + if child_right_weight < weight: + self._swap_nodes(child_right_position, curr_pos) + return self._bubble_down(elem) + else: + return + + def _swap_nodes(self, node1_pos: int, node2_pos: int) -> None: + # Swap the nodes at the given positions + node1_elem = self.heap[node1_pos][0] + node2_elem = self.heap[node2_pos][0] + self.heap[node1_pos], self.heap[node2_pos] = ( + self.heap[node2_pos], + self.heap[node1_pos], + ) + self.position_map[node1_elem] = node2_pos + self.position_map[node2_elem] = node1_pos + + +class GraphUndirectedWeighted: + """ + Graph Undirected Weighted Class + + Functions: + add_node: function to add a node in the graph + add_edge: function to add an edge between 2 nodes in the graph + """ + + def __init__(self) -> None: + self.connections = {} + self.nodes = 0 + + def __repr__(self) -> str: + return str(self.connections) + + def __len__(self) -> int: + return self.nodes + + def add_node(self, node: Union[int, str]) -> None: + # Add a node in the graph if it is not in the graph + if node not in self.connections: + self.connections[node] = {} + self.nodes += 1 + + def add_edge( + self, node1: Union[int, str], node2: Union[int, str], weight: int + ) -> None: + # Add an edge between 2 nodes in the graph + self.add_node(node1) + self.add_node(node2) + self.connections[node1][node2] = weight + self.connections[node2][node1] = weight + + +def prims_algo( + graph: GraphUndirectedWeighted, +) -> Tuple[Dict[str, int], Dict[str, Optional[str]]]: + """ + >>> graph = GraphUndirectedWeighted() + + >>> graph.add_edge("a", "b", 3) + >>> graph.add_edge("b", "c", 10) + >>> graph.add_edge("c", "d", 5) + >>> graph.add_edge("a", "c", 15) + >>> graph.add_edge("b", "d", 100) + + >>> dist, parent = prims_algo(graph) + + >>> abs(dist["a"] - dist["b"]) + 3 + >>> abs(dist["d"] - dist["b"]) + 15 + >>> abs(dist["a"] - dist["c"]) + 13 + """ + # prim's algorithm for minimum spanning tree + dist = {node: maxsize for node in graph.connections} + parent = {node: None for node in graph.connections} + priority_queue = MinPriorityQueue() + [priority_queue.push(node, weight) for node, weight in dist.items()] + if priority_queue.is_empty(): + return dist, parent + + # initialization + node = priority_queue.extract_min() + dist[node] = 0 + for neighbour in graph.connections[node]: + if dist[neighbour] > dist[node] + graph.connections[node][neighbour]: + dist[neighbour] = dist[node] + graph.connections[node][neighbour] + priority_queue.update_key(neighbour, dist[neighbour]) + parent[neighbour] = node + # running prim's algorithm + while not priority_queue.is_empty(): + node = priority_queue.extract_min() + for neighbour in graph.connections[node]: + if dist[neighbour] > dist[node] + graph.connections[node][neighbour]: + dist[neighbour] = dist[node] + graph.connections[node][neighbour] + priority_queue.update_key(neighbour, dist[neighbour]) + parent[neighbour] = node + return dist, parent + + +if __name__ == "__main__": + from doctest import testmod + + testmod() From 3324bbb94b5b09d87fa3b90ad2e48e14e8a9b864 Mon Sep 17 00:00:00 2001 From: Tapajyoti Bose <44058757+ruppysuppy@users.noreply.github.com> Date: Sat, 10 Oct 2020 12:32:51 +0530 Subject: [PATCH 0873/1071] Added sudoku type hints [Hacktober Fest] (#3124) * chore(sudoku): added type hints [HACKTOBER-FEST] * updating DIRECTORY.md * chore: added matrix type alias Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> --- backtracking/sudoku.py | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/backtracking/sudoku.py b/backtracking/sudoku.py index b3d38b4cc7c7..614bdb8530ac 100644 --- a/backtracking/sudoku.py +++ b/backtracking/sudoku.py @@ -1,3 +1,7 @@ +from typing import List, Tuple, Union + +Matrix = List[List[int]] + """ Given a partially filled 9×9 2D array, the objective is to fill a 9×9 square grid with digits numbered 1 to 9, so that every row, column, and @@ -36,7 +40,7 @@ ] -def is_safe(grid, row, column, n): +def is_safe(grid: Matrix, row: int, column: int, n: int) -> bool: """ This function checks the grid to see if each row, column, and the 3x3 subgrids contain the digit 'n'. @@ -55,7 +59,7 @@ def is_safe(grid, row, column, n): return True -def is_completed(grid): +def is_completed(grid: Matrix) -> bool: """ This function checks if the puzzle is completed or not. it is completed when all the cells are assigned with a non-zero number. @@ -76,7 +80,7 @@ def is_completed(grid): return all(all(cell != 0 for cell in row) for row in grid) -def find_empty_location(grid): +def find_empty_location(grid: Matrix) -> Tuple[int, int]: """ This function finds an empty location so that we can assign a number for that particular row and column. @@ -87,7 +91,7 @@ def find_empty_location(grid): return i, j -def sudoku(grid): +def sudoku(grid: Matrix) -> Union[Matrix, bool]: """ Takes a partially filled-in grid and attempts to assign values to all unassigned locations in such a way to meet the requirements @@ -124,7 +128,7 @@ def sudoku(grid): return False -def print_solution(grid): +def print_solution(grid: Matrix) -> None: """ A function to print the solution in the form of a 9x9 grid From c07b82fa637f4594bdb7739bc9d7205497df10e3 Mon Sep 17 00:00:00 2001 From: Sandeep Gupta Date: Sat, 10 Oct 2020 17:06:25 +0530 Subject: [PATCH 0874/1071] Add Project Euler Problem 80 (#2885) * adding solution to problem 80 * updating DIRECTORY.md * fixing spell check * updating sol as per comments * Add reference link to the problem Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> Co-authored-by: Dhruv --- DIRECTORY.md | 2 ++ project_euler/problem_80/__init__.py | 0 project_euler/problem_80/sol1.py | 37 ++++++++++++++++++++++++++++ 3 files changed, 39 insertions(+) create mode 100644 project_euler/problem_80/__init__.py create mode 100644 project_euler/problem_80/sol1.py diff --git a/DIRECTORY.md b/DIRECTORY.md index b57cb2eb131b..064a4da23f17 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -662,6 +662,8 @@ * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_71/sol1.py) * Problem 76 * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_76/sol1.py) + * Problem 80 + * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_80/sol1.py) * Problem 97 * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_97/sol1.py) * Problem 99 diff --git a/project_euler/problem_80/__init__.py b/project_euler/problem_80/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/project_euler/problem_80/sol1.py b/project_euler/problem_80/sol1.py new file mode 100644 index 000000000000..db69d7e8451c --- /dev/null +++ b/project_euler/problem_80/sol1.py @@ -0,0 +1,37 @@ +""" +Project Euler Problem 80: https://projecteuler.net/problem=80 +Author: Sandeep Gupta +Problem statement: For the first one hundred natural numbers, find the total of +the digital sums of the first one hundred decimal digits for all the irrational +square roots. +Time: 5 October 2020, 18:30 +""" +import decimal + + +def solution() -> int: + """ + To evaluate the sum, Used decimal python module to calculate the decimal + places up to 100, the most important thing would be take calculate + a few extra places for decimal otherwise there will be rounding + error. + + >>> solution() + 40886 + """ + answer = 0 + decimal_context = decimal.Context(prec=105) + for i in range(2, 100): + number = decimal.Decimal(i) + sqrt_number = number.sqrt(decimal_context) + if len(str(sqrt_number)) > 1: + answer += int(str(sqrt_number)[0]) + sqrt_number = str(sqrt_number)[2:101] + answer += sum([int(x) for x in sqrt_number]) + return answer + + +if __name__ == "__main__": + import doctest + + doctest.testmod() From c961d5541fcc921c25fcc5bb04680e16aee2d4ae Mon Sep 17 00:00:00 2001 From: Dhruv Date: Sat, 10 Oct 2020 20:08:45 +0530 Subject: [PATCH 0875/1071] Add CODEOWNERS file to TheAlgorithms/Python (#3147) * Add CODEOWNERS file * Commented out the non-assigned directory --- .github/CODEOWNERS | 92 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 92 insertions(+) create mode 100644 .github/CODEOWNERS diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 000000000000..9e74de6dcee3 --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1,92 @@ +# This is a comment. +# Each line is a file pattern followed by one or more owners. + +# More details are here: https://help.github.com/articles/about-codeowners/ + +# The '*' pattern is global owners. + +# Order is important. The last matching pattern has the most precedence. + +/.travis.yml @cclauss @dhruvmanila + +/.pre-commit-config.yaml @cclauss @dhruvmanila + +/.github/ @cclauss + +# /arithmetic_analysis/ + +# /backtracking/ + +# /bit_manipulation/ + +# /blockchain/ + +# /boolean_algebra/ + +# /cellular_automata/ + +/ciphers/ @cclauss + +# /compression/ + +# /computer_vision/ + +/conversions/ @cclauss + +/data_structures/ @cclauss + +# /digital_image_processing/ + +# /divide_and_conquer/ + +# /dynamic_programming/ + +# /file_transfer/ + +# /fuzzy_logic/ + +# /genetic_algorithm/ + +# /geodesy/ + +# /graphics/ + +# /graphs/ + +# /greedy_method/ + +# /hashes/ + +# /images/ + +# /linear_algebra/ + +# /machine_learning/ + +# /maths/ + +# /matrix/ + +# /networking_flow/ + +# /neural_network/ + +/other/ @cclauss + +/project_euler/ @dhruvmanila + +# /quantum/ + +# /scheduling/ + +# /scripts/ + +# /searches/ + +# /sorts/ + +/strings/ @cclauss + +# /traversals/ + +/web_programming/ @cclass From 501a2ff430c403570f992f0e1017d81b0638b2ef Mon Sep 17 00:00:00 2001 From: Dhruv Date: Sat, 10 Oct 2020 21:23:17 +0530 Subject: [PATCH 0876/1071] [Project Euler] Fix code style for multiple problems (#3094) * Fix code style for Project Euler problems: - 13, 17, 21 - Default args - Type hints - File path * Fix code style for multiple problems * Made suggested changes --- project_euler/problem_13/sol1.py | 29 +++++++++----------- project_euler/problem_17/sol1.py | 4 +-- project_euler/problem_21/sol1.py | 7 +++-- project_euler/problem_31/sol1.py | 23 ++++++++-------- project_euler/problem_31/sol2.py | 7 +++-- project_euler/problem_33/sol1.py | 45 ++++++++++++++++++++----------- project_euler/problem_43/sol1.py | 8 +++--- project_euler/problem_44/sol1.py | 8 +++--- project_euler/problem_46/sol1.py | 9 ++++++- project_euler/problem_551/sol1.py | 4 +-- project_euler/problem_63/sol1.py | 12 ++++----- 11 files changed, 89 insertions(+), 67 deletions(-) diff --git a/project_euler/problem_13/sol1.py b/project_euler/problem_13/sol1.py index e36065ec8e11..19b427337c3d 100644 --- a/project_euler/problem_13/sol1.py +++ b/project_euler/problem_13/sol1.py @@ -1,30 +1,25 @@ """ +Problem 13: https://projecteuler.net/problem=13 + Problem Statement: Work out the first ten digits of the sum of the following one-hundred 50-digit numbers. """ +import os -def solution(array): - """Returns the first ten digits of the sum of the array elements. +def solution(): + """ + Returns the first ten digits of the sum of the array elements + from the file num.txt - >>> import os - >>> sum = 0 - >>> array = [] - >>> with open(os.path.dirname(__file__) + "/num.txt","r") as f: - ... for line in f: - ... array.append(int(line)) - ... - >>> solution(array) + >>> solution() '5537376230' """ - return str(sum(array))[:10] + file_path = os.path.join(os.path.dirname(__file__), "num.txt") + with open(file_path, "r") as file_hand: + return str(sum([int(line) for line in file_hand]))[:10] if __name__ == "__main__": - n = int(input().strip()) - - array = [] - for i in range(n): - array.append(int(input().strip())) - print(solution(array)) + print(solution()) diff --git a/project_euler/problem_17/sol1.py b/project_euler/problem_17/sol1.py index d585d81a0825..d4db1beb5a4e 100644 --- a/project_euler/problem_17/sol1.py +++ b/project_euler/problem_17/sol1.py @@ -1,6 +1,6 @@ """ Number letter counts -Problem 17 +Problem 17: https://projecteuler.net/problem=17 If the numbers 1 to 5 are written out in words: one, two, three, four, five, then there are 3 + 3 + 5 + 4 + 4 = 19 letters used in total. @@ -16,7 +16,7 @@ """ -def solution(n): +def solution(n: int = 1000) -> int: """Returns the number of letters used to write all numbers from 1 to n. where n is lower or equals to 1000. >>> solution(1000) diff --git a/project_euler/problem_21/sol1.py b/project_euler/problem_21/sol1.py index f01c9d0dad73..3fac79156e41 100644 --- a/project_euler/problem_21/sol1.py +++ b/project_euler/problem_21/sol1.py @@ -1,5 +1,3 @@ -from math import sqrt - """ Amicable Numbers Problem 21 @@ -15,9 +13,10 @@ Evaluate the sum of all the amicable numbers under 10000. """ +from math import sqrt -def sum_of_divisors(n): +def sum_of_divisors(n: int) -> int: total = 0 for i in range(1, int(sqrt(n) + 1)): if n % i == 0 and i != sqrt(n): @@ -27,7 +26,7 @@ def sum_of_divisors(n): return total - n -def solution(n): +def solution(n: int = 10000) -> int: """Returns the sum of all the amicable numbers under n. >>> solution(10000) diff --git a/project_euler/problem_31/sol1.py b/project_euler/problem_31/sol1.py index 09b60cdae89d..ba40cf383175 100644 --- a/project_euler/problem_31/sol1.py +++ b/project_euler/problem_31/sol1.py @@ -1,6 +1,7 @@ """ Coin sums -Problem 31 +Problem 31: https://projecteuler.net/problem=31 + In England the currency is made up of pound, £, and pence, p, and there are eight coins in general circulation: @@ -12,39 +13,39 @@ """ -def one_pence(): +def one_pence() -> int: return 1 -def two_pence(x): +def two_pence(x: int) -> int: return 0 if x < 0 else two_pence(x - 2) + one_pence() -def five_pence(x): +def five_pence(x: int) -> int: return 0 if x < 0 else five_pence(x - 5) + two_pence(x) -def ten_pence(x): +def ten_pence(x: int) -> int: return 0 if x < 0 else ten_pence(x - 10) + five_pence(x) -def twenty_pence(x): +def twenty_pence(x: int) -> int: return 0 if x < 0 else twenty_pence(x - 20) + ten_pence(x) -def fifty_pence(x): +def fifty_pence(x: int) -> int: return 0 if x < 0 else fifty_pence(x - 50) + twenty_pence(x) -def one_pound(x): +def one_pound(x: int) -> int: return 0 if x < 0 else one_pound(x - 100) + fifty_pence(x) -def two_pound(x): +def two_pound(x: int) -> int: return 0 if x < 0 else two_pound(x - 200) + one_pound(x) -def solution(n): +def solution(n: int = 200) -> int: """Returns the number of different ways can n pence be made using any number of coins? @@ -61,4 +62,4 @@ def solution(n): if __name__ == "__main__": - print(solution(int(str(input()).strip()))) + print(solution(int(input().strip()))) diff --git a/project_euler/problem_31/sol2.py b/project_euler/problem_31/sol2.py index b390b5b1efe5..f9e4dc384bff 100644 --- a/project_euler/problem_31/sol2.py +++ b/project_euler/problem_31/sol2.py @@ -1,4 +1,7 @@ -"""Coin sums +""" +Problem 31: https://projecteuler.net/problem=31 + +Coin sums In England the currency is made up of pound, £, and pence, p, and there are eight coins in general circulation: @@ -29,7 +32,7 @@ """ -def solution(pence: int) -> int: +def solution(pence: int = 200) -> int: """Returns the number of different ways to make X pence using any number of coins. The solution is based on dynamic programming paradigm in a bottom-up fashion. diff --git a/project_euler/problem_33/sol1.py b/project_euler/problem_33/sol1.py index 73a49023ae41..ba6e553d8689 100644 --- a/project_euler/problem_33/sol1.py +++ b/project_euler/problem_33/sol1.py @@ -1,5 +1,5 @@ """ -Problem: +Problem 33: https://projecteuler.net/problem=33 The fraction 49/98 is a curious fraction, as an inexperienced mathematician in attempting to simplify it may incorrectly believe @@ -14,27 +14,30 @@ If the product of these four fractions is given in its lowest common terms, find the value of the denominator. """ +from fractions import Fraction +from typing import List -def isDigitCancelling(num, den): +def is_digit_cancelling(num: int, den: int) -> bool: if num != den: if num % 10 == den // 10: if (num // 10) / (den % 10) == num / den: return True + return False -def solve(digit_len: int) -> str: +def fraction_list(digit_len: int) -> List[str]: """ - >>> solve(2) - '16/64 , 19/95 , 26/65 , 49/98' - >>> solve(3) - '16/64 , 19/95 , 26/65 , 49/98' - >>> solve(4) - '16/64 , 19/95 , 26/65 , 49/98' - >>> solve(0) - '' - >>> solve(5) - '16/64 , 19/95 , 26/65 , 49/98' + >>> fraction_list(2) + ['16/64', '19/95', '26/65', '49/98'] + >>> fraction_list(3) + ['16/64', '19/95', '26/65', '49/98'] + >>> fraction_list(4) + ['16/64', '19/95', '26/65', '49/98'] + >>> fraction_list(0) + [] + >>> fraction_list(5) + ['16/64', '19/95', '26/65', '49/98'] """ solutions = [] den = 11 @@ -42,14 +45,24 @@ def solve(digit_len: int) -> str: for num in range(den, last_digit): while den <= 99: if (num != den) and (num % 10 == den // 10) and (den % 10 != 0): - if isDigitCancelling(num, den): + if is_digit_cancelling(num, den): solutions.append(f"{num}/{den}") den += 1 num += 1 den = 10 - solutions = " , ".join(solutions) return solutions +def solution(n: int = 2) -> int: + """ + Return the solution to the problem + """ + result = 1.0 + for fraction in fraction_list(n): + frac = Fraction(fraction) + result *= frac.denominator / frac.numerator + return int(result) + + if __name__ == "__main__": - print(solve(2)) + print(solution()) diff --git a/project_euler/problem_43/sol1.py b/project_euler/problem_43/sol1.py index 2fc429f9f52b..1febe4a4d37f 100644 --- a/project_euler/problem_43/sol1.py +++ b/project_euler/problem_43/sol1.py @@ -1,4 +1,6 @@ """ +Problem 43: https://projecteuler.net/problem=43 + The number, 1406357289, is a 0 to 9 pandigital number because it is made up of each of the digits 0 to 9 in some order, but it also has a rather interesting sub-string divisibility property. @@ -38,11 +40,11 @@ def is_substring_divisible(num: tuple) -> bool: return True -def compute_sum(n: int = 10) -> int: +def solution(n: int = 10) -> int: """ Returns the sum of all pandigital numbers which pass the divisiility tests. - >>> compute_sum(10) + >>> solution(10) 16695334890 """ list_nums = [ @@ -55,4 +57,4 @@ def compute_sum(n: int = 10) -> int: if __name__ == "__main__": - print(f"{compute_sum(10) = }") + print(f"{solution() = }") diff --git a/project_euler/problem_44/sol1.py b/project_euler/problem_44/sol1.py index 536720b39128..d3ae6476d45f 100644 --- a/project_euler/problem_44/sol1.py +++ b/project_euler/problem_44/sol1.py @@ -1,4 +1,6 @@ """ +Problem 44: https://projecteuler.net/problem=44 + Pentagonal numbers are generated by the formula, Pn=n(3n−1)/2. The first ten pentagonal numbers are: 1, 5, 12, 22, 35, 51, 70, 92, 117, 145, ... @@ -24,11 +26,11 @@ def is_pentagonal(n: int) -> bool: return ((1 + root) / 6) % 1 == 0 -def compute_num(limit: int = 5000) -> int: +def solution(limit: int = 5000) -> int: """ Returns the minimum difference of two pentagonal numbers P1 and P2 such that P1 + P2 is pentagonal and P2 - P1 is pentagonal. - >>> compute_num(5000) + >>> solution(5000) 5482660 """ pentagonal_nums = [(i * (3 * i - 1)) // 2 for i in range(1, limit)] @@ -42,4 +44,4 @@ def compute_num(limit: int = 5000) -> int: if __name__ == "__main__": - print(f"{compute_num() = }") + print(f"{solution() = }") diff --git a/project_euler/problem_46/sol1.py b/project_euler/problem_46/sol1.py index e94e9247d86b..3fdf567551cc 100644 --- a/project_euler/problem_46/sol1.py +++ b/project_euler/problem_46/sol1.py @@ -1,4 +1,6 @@ """ +Problem 46: https://projecteuler.net/problem=46 + It was proposed by Christian Goldbach that every odd composite number can be written as the sum of a prime and twice a square. @@ -84,5 +86,10 @@ def compute_nums(n: int) -> list[int]: return list_nums +def solution() -> int: + """Return the solution to the problem""" + return compute_nums(1)[0] + + if __name__ == "__main__": - print(f"{compute_nums(1) = }") + print(f"{solution() = }") diff --git a/project_euler/problem_551/sol1.py b/project_euler/problem_551/sol1.py index 817474b3578b..71956691a56d 100644 --- a/project_euler/problem_551/sol1.py +++ b/project_euler/problem_551/sol1.py @@ -167,7 +167,7 @@ def add(digits, k, addend): digits.append(digit) -def solution(n): +def solution(n: int = 10 ** 15) -> int: """ returns n-th term of sequence @@ -197,4 +197,4 @@ def solution(n): if __name__ == "__main__": - print(solution(10 ** 15)) + print(f"{solution() = }") diff --git a/project_euler/problem_63/sol1.py b/project_euler/problem_63/sol1.py index e429db07bf8a..f6a8d3240ffd 100644 --- a/project_euler/problem_63/sol1.py +++ b/project_euler/problem_63/sol1.py @@ -11,16 +11,16 @@ """ -def compute_nums(max_base: int = 10, max_power: int = 22) -> int: +def solution(max_base: int = 10, max_power: int = 22) -> int: """ Returns the count of all n-digit numbers which are nth power - >>> compute_nums(10, 22) + >>> solution(10, 22) 49 - >>> compute_nums(0, 0) + >>> solution(0, 0) 0 - >>> compute_nums(1, 1) + >>> solution(1, 1) 0 - >>> compute_nums(-1, -1) + >>> solution(-1, -1) 0 """ bases = range(1, max_base) @@ -31,4 +31,4 @@ def compute_nums(max_base: int = 10, max_power: int = 22) -> int: if __name__ == "__main__": - print(f"{compute_nums(10, 22) = }") + print(f"{solution(10, 22) = }") From 2b5b2c6304f68112484a599af6ce6121088e14c2 Mon Sep 17 00:00:00 2001 From: Ravi Kandasamy Sundaram Date: Sat, 10 Oct 2020 19:59:02 +0200 Subject: [PATCH 0877/1071] Added solution for Project Euler problem 119 (#2931) Name: Digit power sum Problem Statement: The number 512 is interesting because it is equal to the sum of its digits raised to some power: 5 + 1 + 2 = 8, and 83 = 512. Another example of a number with this property is 614656 = 284. We shall define an to be the nth term of this sequence and insist that a number must contain at least two digits to have a sum. You are given that a2 = 512 and a10 = 614656. Find a30 Reference: https://projecteuler.net/problem=119 reference: #2695 Co-authored-by: Ravi Kandasamy Sundaram --- project_euler/problem_119/__init__.py | 0 project_euler/problem_119/sol1.py | 51 +++++++++++++++++++++++++++ 2 files changed, 51 insertions(+) create mode 100644 project_euler/problem_119/__init__.py create mode 100644 project_euler/problem_119/sol1.py diff --git a/project_euler/problem_119/__init__.py b/project_euler/problem_119/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/project_euler/problem_119/sol1.py b/project_euler/problem_119/sol1.py new file mode 100644 index 000000000000..7f343ac242e9 --- /dev/null +++ b/project_euler/problem_119/sol1.py @@ -0,0 +1,51 @@ +""" +Problem 119: https://projecteuler.net/problem=119 + +Name: Digit power sum + +The number 512 is interesting because it is equal to the sum of its digits +raised to some power: 5 + 1 + 2 = 8, and 8^3 = 512. Another example of a number +with this property is 614656 = 28^4. We shall define an to be the nth term of +this sequence and insist that a number must contain at least two digits to have a sum. +You are given that a2 = 512 and a10 = 614656. Find a30 +""" + +import math + + +def digit_sum(n: int) -> int: + """ + Returns the sum of the digits of the number. + >>> digit_sum(123) + 6 + >>> digit_sum(456) + 15 + >>> digit_sum(78910) + 25 + """ + return sum([int(digit) for digit in str(n)]) + + +def solution(n: int = 30) -> int: + """ + Returns the value of 30th digit power sum. + >>> solution(2) + 512 + >>> solution(5) + 5832 + >>> solution(10) + 614656 + """ + digit_to_powers = [] + for digit in range(2, 100): + for power in range(2, 100): + number = int(math.pow(digit, power)) + if digit == digit_sum(number): + digit_to_powers.append(number) + + digit_to_powers.sort() + return digit_to_powers[n - 1] + + +if __name__ == "__main__": + print(solution()) From 731190842ae1ec9a91dbf1f84ee0b50d501586c6 Mon Sep 17 00:00:00 2001 From: Christian Clauss Date: Sun, 11 Oct 2020 11:53:30 +0200 Subject: [PATCH 0878/1071] CODEOWNERS: Proper spelling of my userid (#3185) * CODEOWNERS: Proper spelling of my userid * updating DIRECTORY.md Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> --- .github/CODEOWNERS | 2 +- DIRECTORY.md | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 9e74de6dcee3..b10fed71db46 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -89,4 +89,4 @@ # /traversals/ -/web_programming/ @cclass +/web_programming/ @cclauss diff --git a/DIRECTORY.md b/DIRECTORY.md index 064a4da23f17..4e6ca62ce419 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -553,11 +553,15 @@ * [Sol2](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_11/sol2.py) * Problem 112 * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_112/sol1.py) + * Problem 119 + * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_119/sol1.py) * Problem 12 * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_12/sol1.py) * [Sol2](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_12/sol2.py) * Problem 120 * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_120/sol1.py) + * Problem 125 + * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_125/sol1.py) * Problem 13 * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_13/sol1.py) * Problem 14 From f029fcef7b841cd88421174a95e3f2b3a5b12bdb Mon Sep 17 00:00:00 2001 From: Michael D Date: Sun, 11 Oct 2020 14:29:27 +0200 Subject: [PATCH 0879/1071] Add solution for Project Euler problem 191 (#2875) * Project Euler problem 191 solution * Add type hints and reference links * Address requested changes - update documentation - split out helper function but mark it with an underscore - remove redundant comments or make them more explicit/helpful * Address requested changes --- project_euler/problem_191/__init__.py | 0 project_euler/problem_191/sol1.py | 105 ++++++++++++++++++++++++++ 2 files changed, 105 insertions(+) create mode 100644 project_euler/problem_191/__init__.py create mode 100644 project_euler/problem_191/sol1.py diff --git a/project_euler/problem_191/__init__.py b/project_euler/problem_191/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/project_euler/problem_191/sol1.py b/project_euler/problem_191/sol1.py new file mode 100644 index 000000000000..38325b363b89 --- /dev/null +++ b/project_euler/problem_191/sol1.py @@ -0,0 +1,105 @@ +""" +Prize Strings +Problem 191 + +A particular school offers cash rewards to children with good attendance and +punctuality. If they are absent for three consecutive days or late on more +than one occasion then they forfeit their prize. + +During an n-day period a trinary string is formed for each child consisting +of L's (late), O's (on time), and A's (absent). + +Although there are eighty-one trinary strings for a 4-day period that can be +formed, exactly forty-three strings would lead to a prize: + +OOOO OOOA OOOL OOAO OOAA OOAL OOLO OOLA OAOO OAOA +OAOL OAAO OAAL OALO OALA OLOO OLOA OLAO OLAA AOOO +AOOA AOOL AOAO AOAA AOAL AOLO AOLA AAOO AAOA AAOL +AALO AALA ALOO ALOA ALAO ALAA LOOO LOOA LOAO LOAA +LAOO LAOA LAAO + +How many "prize" strings exist over a 30-day period? + +References: + - The original Project Euler project page: + https://projecteuler.net/problem=191 +""" + + +cache = {} + + +def _calculate(days: int, absent: int, late: int) -> int: + """ + A small helper function for the recursion, mainly to have + a clean interface for the solution() function below. + + It should get called with the number of days (corresponding + to the desired length of the 'prize strings'), and the + initial values for the number of consecutive absent days and + number of total late days. + + >>> _calculate(days=4, absent=0, late=0) + 43 + >>> _calculate(days=30, absent=2, late=0) + 0 + >>> _calculate(days=30, absent=1, late=0) + 98950096 + """ + + # if we are absent twice, or late 3 consecutive days, + # no further prize strings are possible + if late == 3 or absent == 2: + return 0 + + # if we have no days left, and have not failed any other rules, + # we have a prize string + if days == 0: + return 1 + + # No easy solution, so now we need to do the recursive calculation + + # First, check if the combination is already in the cache, and + # if yes, return the stored value from there since we already + # know the number of possible prize strings from this point on + key = (days, absent, late) + if key in cache: + return cache[key] + + # now we calculate the three possible ways that can unfold from + # this point on, depending on our attendance today + + # 1) if we are late (but not absent), the "absent" counter stays as + # it is, but the "late" counter increases by one + state_late = _calculate(days - 1, absent, late + 1) + + # 2) if we are absent, the "absent" counter increases by 1, and the + # "late" counter resets to 0 + state_absent = _calculate(days - 1, absent + 1, 0) + + # 3) if we are on time, this resets the "late" counter and keeps the + # absent counter + state_ontime = _calculate(days - 1, absent, 0) + + prizestrings = state_late + state_absent + state_ontime + + cache[key] = prizestrings + return prizestrings + + +def solution(days: int = 30) -> int: + """ + Returns the number of possible prize strings for a particular number + of days, using a simple recursive function with caching to speed it up. + + >>> solution() + 1918080160 + >>> solution(4) + 43 + """ + + return _calculate(days, absent=0, late=0) + + +if __name__ == "__main__": + print(solution()) From 60f9895685613442e222d0deef0e5a10947a4176 Mon Sep 17 00:00:00 2001 From: Simon Landry Date: Sun, 11 Oct 2020 13:46:16 -0400 Subject: [PATCH 0880/1071] Fixes: #3163 - Add new solution for problem 234 (#3177) * Fixes: #3163 - Add new solution for problem 234 * Apply review suggestions --- project_euler/problem_234/sol1.py | 127 ++++++++++++++++++++++-------- 1 file changed, 95 insertions(+), 32 deletions(-) diff --git a/project_euler/problem_234/sol1.py b/project_euler/problem_234/sol1.py index b65a506d1def..7516b164db2d 100644 --- a/project_euler/problem_234/sol1.py +++ b/project_euler/problem_234/sol1.py @@ -17,40 +17,103 @@ What is the sum of all semidivisible numbers not exceeding 999966663333 ? """ +import math -def fib(a, b, n): - - if n == 1: - return a - elif n == 2: - return b - elif n == 3: - return str(a) + str(b) - - temp = 0 - for x in range(2, n): - c = str(a) + str(b) - temp = b - b = c - a = temp - return c - - -def solution(n): - """Returns the sum of all semidivisible numbers not exceeding n.""" - semidivisible = [] - for x in range(n): - l = [i for i in input().split()] # noqa: E741 - c2 = 1 - while 1: - if len(fib(l[0], l[1], c2)) < int(l[2]): - c2 += 1 - else: + +def prime_sieve(n: int) -> list: + """ + Sieve of Erotosthenes + Function to return all the prime numbers up to a certain number + https://en.wikipedia.org/wiki/Sieve_of_Eratosthenes + >>> prime_sieve(3) + [2] + >>> prime_sieve(50) + [2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47] + """ + is_prime = [True] * n + is_prime[0] = False + is_prime[1] = False + is_prime[2] = True + + for i in range(3, int(n ** 0.5 + 1), 2): + index = i * 2 + while index < n: + is_prime[index] = False + index = index + i + + primes = [2] + + for i in range(3, n, 2): + if is_prime[i]: + primes.append(i) + + return primes + + +def solution(limit: int = 999_966_663_333) -> int: + """ + Computes the solution to the problem up to the specified limit + >>> solution(1000) + 34825 + + >>> solution(10_000) + 1134942 + + >>> solution(100_000) + 36393008 + """ + primes_upper_bound = math.floor(math.sqrt(limit)) + 100 + primes = prime_sieve(primes_upper_bound) + + matches_sum = 0 + prime_index = 0 + last_prime = primes[prime_index] + + while (last_prime ** 2) <= limit: + next_prime = primes[prime_index + 1] + + lower_bound = last_prime ** 2 + upper_bound = next_prime ** 2 + + # Get numbers divisible by lps(current) + current = lower_bound + last_prime + while upper_bound > current <= limit: + matches_sum += current + current += last_prime + + # Reset the upper_bound + while (upper_bound - next_prime) > limit: + upper_bound -= next_prime + + # Add the numbers divisible by ups(current) + current = upper_bound - next_prime + while current > lower_bound: + matches_sum += current + current -= next_prime + + # Remove the numbers divisible by both ups and lps + current = 0 + while upper_bound > current <= limit: + if current <= lower_bound: + # Increment the current number + current += last_prime * next_prime + continue + + if current > limit: break - semidivisible.append(fib(l[0], l[1], c2 + 1)[int(l[2]) - 1]) - return semidivisible + + # Remove twice since it was added by both ups and lps + matches_sum -= current * 2 + + # Increment the current number + current += last_prime * next_prime + + # Setup for next pair + last_prime = next_prime + prime_index += 1 + + return matches_sum if __name__ == "__main__": - for i in solution(int(str(input()).strip())): - print(i) + print(solution()) From d02f6bbfbd8ff1c99e1e1e1a85572477412c6927 Mon Sep 17 00:00:00 2001 From: sarthaka1310 <56290744+sarthaka1310@users.noreply.github.com> Date: Sun, 11 Oct 2020 23:38:30 +0530 Subject: [PATCH 0881/1071] Added solution to Project Euler 69 (#2934) * Added solution to Project Euler 69 * Accept edits from code review Co-authored-by: Dhruv * Added doctests * Renaming and exception handling * Apply suggestions from code review Co-authored-by: Dhruv * Edited mistake. Co-authored-by: formal-acc Co-authored-by: Dhruv --- project_euler/problem_69/__init__.py | 0 project_euler/problem_69/sol1.py | 66 ++++++++++++++++++++++++++++ 2 files changed, 66 insertions(+) create mode 100644 project_euler/problem_69/__init__.py create mode 100644 project_euler/problem_69/sol1.py diff --git a/project_euler/problem_69/__init__.py b/project_euler/problem_69/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/project_euler/problem_69/sol1.py b/project_euler/problem_69/sol1.py new file mode 100644 index 000000000000..d148dd79a777 --- /dev/null +++ b/project_euler/problem_69/sol1.py @@ -0,0 +1,66 @@ +""" +Totient maximum +Problem 69: https://projecteuler.net/problem=69 + +Euler's Totient function, φ(n) [sometimes called the phi function], +is used to determine the number of numbers less than n which are relatively prime to n. +For example, as 1, 2, 4, 5, 7, and 8, +are all less than nine and relatively prime to nine, φ(9)=6. + +n Relatively Prime φ(n) n/φ(n) +2 1 1 2 +3 1,2 2 1.5 +4 1,3 2 2 +5 1,2,3,4 4 1.25 +6 1,5 2 3 +7 1,2,3,4,5,6 6 1.1666... +8 1,3,5,7 4 2 +9 1,2,4,5,7,8 6 1.5 +10 1,3,7,9 4 2.5 + +It can be seen that n=6 produces a maximum n/φ(n) for n ≤ 10. + +Find the value of n ≤ 1,000,000 for which n/φ(n) is a maximum. +""" + + +def solution(n: int = 10 ** 6) -> int: + """ + Returns solution to problem. + Algorithm: + 1. Precompute φ(k) for all natural k, k <= n using product formula (wikilink below) + https://en.wikipedia.org/wiki/Euler%27s_totient_function#Euler's_product_formula + + 2. Find k/φ(k) for all k ≤ n and return the k that attains maximum + + >>> solution(10) + 6 + + >>> solution(100) + 30 + + >>> solution(9973) + 2310 + + """ + + if n <= 0: + raise ValueError("Please enter an integer greater than 0") + + phi = list(range(n + 1)) + for number in range(2, n + 1): + if phi[number] == number: + phi[number] -= 1 + for multiple in range(number * 2, n + 1, number): + phi[multiple] = (phi[multiple] // number) * (number - 1) + + answer = 1 + for number in range(1, n + 1): + if (answer / phi[answer]) < (number / phi[number]): + answer = number + + return answer + + +if __name__ == "__main__": + print(solution()) From c425010c447f4bb3339829d55869781dcb390054 Mon Sep 17 00:00:00 2001 From: Jr Miranda Date: Sun, 11 Oct 2020 20:31:56 -0300 Subject: [PATCH 0882/1071] Fetch CO2 emission information (#3182) * Fetch CO2 emission information * fix blank lines * fix blank lines * Add type hints for function return values * Update co2_emission.py * Update co2_emission.py * Update co2_emission.py Co-authored-by: Christian Clauss --- web_programming/co2_emission.py | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 web_programming/co2_emission.py diff --git a/web_programming/co2_emission.py b/web_programming/co2_emission.py new file mode 100644 index 000000000000..97927e7ef541 --- /dev/null +++ b/web_programming/co2_emission.py @@ -0,0 +1,25 @@ +""" +Get CO2 emission data from the UK CarbonIntensity API +""" +from datetime import date + +import requests + +BASE_URL = "https://api.carbonintensity.org.uk/intensity" + + +# Emission in the last half hour +def fetch_last_half_hour() -> str: + last_half_hour = requests.get(BASE_URL).json()["data"][0] + return last_half_hour["intensity"]["actual"] + + +# Emissions in a specific date range +def fetch_from_to(start, end) -> list: + return requests.get(f"{BASE_URL}/{start}/{end}").json()["data"] + + +if __name__ == "__main__": + for entry in fetch_from_to(start=date(2020, 10, 1), end=date(2020, 10, 3)): + print("from {from} to {to}: {intensity[actual]}".format(**entry)) + print(f"{fetch_last_half_hour() = }") From 3aef85bceca826fb8151ecb553a2e4d272c77953 Mon Sep 17 00:00:00 2001 From: eba <56650568+EvanBlaine@users.noreply.github.com> Date: Sun, 11 Oct 2020 19:17:01 -0500 Subject: [PATCH 0883/1071] Fix English grammar in computer vision (#3210) --- computer_vision/README.md | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/computer_vision/README.md b/computer_vision/README.md index 8b1812de8e8b..94ee493086cc 100644 --- a/computer_vision/README.md +++ b/computer_vision/README.md @@ -3,6 +3,5 @@ Computer vision is a field of computer science that works on enabling computers to see, identify and process images in the same way that human vision does, and then provide appropriate output. It is like imparting human intelligence and instincts to a computer. -Image processing and computer vision and little different from each other.Image processing means applying some algorithms for transforming image from one form to other like smoothing,contrasting, stretching etc -While in computer vision comes from modelling image processing using the techniques of machine learning.Computer vision applies machine learning to recognize patterns for interpretation of images. -Much like the process of visual reasoning of human vision +Image processing and computer vision are a little different from each other. Image processing means applying some algorithms for transforming image from one form to the other like smoothing, contrasting, stretching, etc. +While computer vision comes from modelling image processing using the techniques of machine learning, computer vision applies machine learning to recognize patterns for interpretation of images (much like the process of visual reasoning of human vision). From f7e7ad2ef6ab39a5a298b24945496e8d9674f1dd Mon Sep 17 00:00:00 2001 From: Christian Clauss Date: Mon, 12 Oct 2020 02:17:26 +0200 Subject: [PATCH 0884/1071] CONTRIBUTING.md Jupyter files belong in the Jupyter repo. (#3211) * CONTRIBUTING.md Jupyter files belong in the Jupyter repo. * updating DIRECTORY.md * Update CONTRIBUTING.md Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> Co-authored-by: John Law --- CONTRIBUTING.md | 4 ++-- DIRECTORY.md | 4 ++++ 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index f8469d97ddb4..47f69c0a15a4 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -147,9 +147,9 @@ We want your work to be readable by others; therefore, we encourage you to note - Avoid importing external libraries for basic algorithms. Only use those libraries for complicated algorithms. - If you need a third party module that is not in the file __requirements.txt__, please add it to that file as part of your submission. -#### Other Standard While Submitting Your Work +#### Other Requirements for Submissions -- File extension for code should be `.py`. Jupyter notebook files are acceptable in machine learning algorithms. +- The file extension for code files should be `.py`. Jupyter Notebooks should be submitted to [TheAlgorithms/Jupyter](https://github.com/TheAlgorithms/Jupyter). - Strictly use snake_case (underscore_separated) in your file_name, as it will be easy to parse in future using scripts. - Please avoid creating new directories if at all possible. Try to fit your work into the existing directory structure. - If possible, follow the standard *within* the folder you are submitting to. diff --git a/DIRECTORY.md b/DIRECTORY.md index 4e6ca62ce419..e33d1c32bd86 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -578,6 +578,8 @@ * [Solution](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_18/solution.py) * Problem 19 * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_19/sol1.py) + * Problem 191 + * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_191/sol1.py) * Problem 20 * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_20/sol1.py) * [Sol2](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_20/sol2.py) @@ -662,6 +664,8 @@ * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_63/sol1.py) * Problem 67 * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_67/sol1.py) + * Problem 69 + * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_69/sol1.py) * Problem 71 * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_71/sol1.py) * Problem 76 From 3859c65995e6ca9d251b095b29797137bb92ba77 Mon Sep 17 00:00:00 2001 From: PotatoK123 <56174807+PotatoK123@users.noreply.github.com> Date: Mon, 12 Oct 2020 06:07:45 +0100 Subject: [PATCH 0885/1071] Added rail fence cipher (#3188) * Added rail fence cipher * Update rail_fence_cipher.py * Update rail_fence_cipher.py --- ciphers/rail_fence_cipher.py | 102 +++++++++++++++++++++++++++++++++++ 1 file changed, 102 insertions(+) create mode 100644 ciphers/rail_fence_cipher.py diff --git a/ciphers/rail_fence_cipher.py b/ciphers/rail_fence_cipher.py new file mode 100644 index 000000000000..2596415207ae --- /dev/null +++ b/ciphers/rail_fence_cipher.py @@ -0,0 +1,102 @@ +""" https://en.wikipedia.org/wiki/Rail_fence_cipher """ + + +def encrypt(input_string: str, key: int) -> str: + """ + Shuffles the character of a string by placing each of them + in a grid (the height is dependent on the key) in a zigzag + formation and reading it left to right. + + >>> encrypt("Hello World", 4) + 'HWe olordll' + + >>> encrypt("This is a message", 0) + Traceback (most recent call last): + ... + ValueError: Height of grid can't be 0 or negative + + >>> encrypt(b"This is a byte string", 5) + Traceback (most recent call last): + ... + TypeError: sequence item 0: expected str instance, int found + """ + grid = [[] for _ in range(key)] + lowest = key - 1 + + if key <= 0: + raise ValueError("Height of grid can't be 0 or negative") + if key == 1 or len(input_string) <= key: + return input_string + + for position, character in enumerate(input_string): + num = position % (lowest * 2) # puts it in bounds + num = min(num, lowest * 2 - num) # creates zigzag pattern + grid[num].append(character) + grid = ["".join(row) for row in grid] + output_string = "".join(grid) + + return output_string + + +def decrypt(input_string: str, key: int) -> str: + """ + Generates a template based on the key and fills it in with + the characters of the input string and then reading it in + a zigzag formation. + + >>> decrypt("HWe olordll", 4) + 'Hello World' + + >>> decrypt("This is a message", -10) + Traceback (most recent call last): + ... + ValueError: Height of grid can't be 0 or negative + + >>> decrypt("My key is very big", 100) + 'My key is very big' + """ + grid = [] + lowest = key - 1 + + if key <= 0: + raise ValueError("Height of grid can't be 0 or negative") + if key == 1: + return input_string + + temp_grid = [[] for _ in range(key)] # generates template + for position in range(len(input_string)): + num = position % (lowest * 2) # puts it in bounds + num = min(num, lowest * 2 - num) # creates zigzag pattern + temp_grid[num].append("*") + + counter = 0 + for row in temp_grid: # fills in the characters + splice = input_string[counter : counter + len(row)] + grid.append([character for character in splice]) + counter += len(row) + + output_string = "" # reads as zigzag + for position in range(len(input_string)): + num = position % (lowest * 2) # puts it in bounds + num = min(num, lowest * 2 - num) # creates zigzag pattern + output_string += grid[num][0] + grid[num].pop(0) + return output_string + + +def bruteforce(input_string: str) -> dict: + """Uses decrypt function by guessing every key + + >>> bruteforce("HWe olordll")[4] + 'Hello World' + """ + results = {} + for key_guess in range(1, len(input_string)): # tries every key + results[key_guess] = decrypt(input_string, key_guess) + return results + + +if __name__ == "__main__": + import doctest + + doctest.testmod() From 69f92838255656c91930a010f2feb0a9cd01b39f Mon Sep 17 00:00:00 2001 From: Dhruv Date: Mon, 12 Oct 2020 11:29:39 +0530 Subject: [PATCH 0886/1071] Start running validate_solutions script for Travis CI (#3215) * Removed print error_msgs at the end of test: This was done only to reduce the message clutter produced by 60 failing tests. As that is fixed, we can produce the traceback in short form and allow pytest to print the captured error message output at the end of test. * Start validate_solutions script for Travis CI I am separating out the solution testing and doctest as validating the solutions for the current number of solutions present is taking 2 minutes to run. --- .travis.yml | 9 ++++++--- project_euler/validate_solutions.py | 19 +++---------------- 2 files changed, 9 insertions(+), 19 deletions(-) diff --git a/.travis.yml b/.travis.yml index bda0fc31ca5d..ff59af2db34e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -12,11 +12,14 @@ jobs: - pytest --doctest-modules --ignore=project_euler/ --durations=10 --cov-report=term-missing:skip-covered --cov=. . - name: Project Euler install: - - pip install pytest-cov pytest-subtests - before_script: - - pytest --tb=no --no-summary --capture=no project_euler/validate_solutions.py || true # fail fast on wrong solution + - pip install pytest-cov script: - pytest --doctest-modules --durations=10 --cov-report=term-missing:skip-covered --cov=project_euler/ project_euler/ + - name: Project Euler Solution + install: + - pip install pytest-subtests + script: + - pytest --tb=short project_euler/validate_solutions.py after_success: - scripts/build_directory_md.py 2>&1 | tee DIRECTORY.md notifications: diff --git a/project_euler/validate_solutions.py b/project_euler/validate_solutions.py index 01d70721ea8d..f3ae9fbeffab 100755 --- a/project_euler/validate_solutions.py +++ b/project_euler/validate_solutions.py @@ -15,8 +15,6 @@ with open(PROJECT_EULER_ANSWERS_PATH) as file_handle: PROBLEM_ANSWERS = json.load(file_handle) -error_msgs = [] - def generate_solution_modules( dir_path: pathlib.Path, @@ -48,20 +46,9 @@ def test_project_euler(subtests, problem_number: int, expected: str): answer = str(solution_module.solution()) assert answer == expected, f"Expected {expected} but got {answer}" except (AssertionError, AttributeError, TypeError) as err: - error_msgs.append( - f"problem_{problem_number}/{solution_module.__name__}: {err}" + print( + f"problem_{problem_number:02}/{solution_module.__name__}: {err}" ) - raise # We still want pytest to know that this test failed + raise else: pytest.skip(f"Solution {problem_number} does not exist yet.") - - -# Run this function at the end of all the tests -# https://stackoverflow.com/a/52873379 -@pytest.fixture(scope="session", autouse=True) -def custom_print_message(request): - def print_error_messages(): - if error_msgs: - print("\n" + "\n".join(error_msgs)) - - request.addfinalizer(print_error_messages) From e551004551a142a9d9ac3e47075ac85d0d5d0819 Mon Sep 17 00:00:00 2001 From: forgithub0001 <72649492+forgithub0001@users.noreply.github.com> Date: Mon, 12 Oct 2020 14:08:50 +0530 Subject: [PATCH 0887/1071] Update CONTRIBUTING.md (#3223) --- CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 47f69c0a15a4..e248d09f11c3 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -53,7 +53,7 @@ Algorithms in this repo should not be how-to examples for existing Python packag We want your work to be readable by others; therefore, we encourage you to note the following: -- Please write in Python 3.7+. __print()__ is a function in Python 3 so __print "Hello"__ will _not_ work but __print("Hello")__ will. +- Please write in Python 3.7+. For instance: __print()__ is a function in Python 3 so __print "Hello"__ will _not_ work but __print("Hello")__ will. - Please focus hard on naming of functions, classes, and variables. Help your reader by using __descriptive names__ that can help you to remove redundant comments. - Single letter variable names are _old school_ so please avoid them unless their life only spans a few lines. - Expand acronyms because __gcd()__ is hard to understand but __greatest_common_divisor()__ is not. From 7a671483b6cf20bc9e94295a254ace9cbbbe7690 Mon Sep 17 00:00:00 2001 From: forgithub0001 <72649492+forgithub0001@users.noreply.github.com> Date: Mon, 12 Oct 2020 14:10:42 +0530 Subject: [PATCH 0888/1071] Update README.md (#3221) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index d52124e61d23..ac98b6371682 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ ### All algorithms implemented in Python (for education) -These implementations are for learning purposes. They may be less efficient than the implementations in the Python standard library. +These implementations are for learning purposes only. Therefore they may be less efficient than the implementations in the Python standard library. ## Contribution Guidelines From 7b60cea4907d05b4293cb94a53f2d795b59ee0a6 Mon Sep 17 00:00:00 2001 From: Kasper Primdal Lauritzen Date: Mon, 12 Oct 2020 13:28:46 +0200 Subject: [PATCH 0889/1071] Fix docstring for Euler problem 11, solution 2 (#3227) --- project_euler/problem_11/sol2.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/project_euler/problem_11/sol2.py b/project_euler/problem_11/sol2.py index 1482fc7d3b04..839ca6717571 100644 --- a/project_euler/problem_11/sol2.py +++ b/project_euler/problem_11/sol2.py @@ -28,7 +28,8 @@ def solution(): - """Returns the sum of all the multiples of 3 or 5 below n. + """Returns the greatest product of four adjacent numbers (horizontally, + vertically, or diagonally). >>> solution() 70600674 From b6b025f25a9efd991730ef27cd6837f00fd4bb80 Mon Sep 17 00:00:00 2001 From: Kasper Primdal Lauritzen Date: Mon, 12 Oct 2020 16:14:45 +0200 Subject: [PATCH 0890/1071] Fix docstring for Euler problem 11, solution 1 (#3228) Docstring must have been leftover from some FizzBuzz method. Now it describes the actual code. --- project_euler/problem_11/sol1.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/project_euler/problem_11/sol1.py b/project_euler/problem_11/sol1.py index 4e49013c8210..9dea73e8cef2 100644 --- a/project_euler/problem_11/sol1.py +++ b/project_euler/problem_11/sol1.py @@ -68,7 +68,8 @@ def largest_product(grid): def solution(): - """Returns the sum of all the multiples of 3 or 5 below n. + """Returns the greatest product of four adjacent numbers (horizontally, + vertically, or diagonally). >>> solution() 70600674 From 8f8d39d19125b296ed5210497e8f0a73c35e24e0 Mon Sep 17 00:00:00 2001 From: Utkarsh Chaudhary Date: Mon, 12 Oct 2020 19:46:15 +0530 Subject: [PATCH 0891/1071] Add a solution to Project Euler 72 (#2940) * Added Problem 72 * Removed args from solution() * Incorporated the suggested changes --- project_euler/problem_72/__init__.py | 0 project_euler/problem_72/sol1.py | 46 ++++++++++++++++++++++++++++ 2 files changed, 46 insertions(+) create mode 100644 project_euler/problem_72/__init__.py create mode 100644 project_euler/problem_72/sol1.py diff --git a/project_euler/problem_72/__init__.py b/project_euler/problem_72/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/project_euler/problem_72/sol1.py b/project_euler/problem_72/sol1.py new file mode 100644 index 000000000000..846396ab0f9c --- /dev/null +++ b/project_euler/problem_72/sol1.py @@ -0,0 +1,46 @@ +""" +Problem 72 Counting fractions: https://projecteuler.net/problem=72 + +Description: + +Consider the fraction, n/d, where n and d are positive integers. If n int: + """ + Returns an integer, the solution to the problem + >>> solution(10) + 31 + >>> solution(100) + 3043 + >>> solution(1_000) + 304191 + """ + + phi = [i - 1 for i in range(limit + 1)] + + for i in range(2, limit + 1): + for j in range(2 * i, limit + 1, i): + phi[j] -= phi[i] + + return sum(phi[2 : limit + 1]) + + +if __name__ == "__main__": + print(solution()) From 08c26e667b7fee67c36a510a49dc320c451ad00e Mon Sep 17 00:00:00 2001 From: Kushagra Bansal Date: Mon, 12 Oct 2020 19:53:21 +0530 Subject: [PATCH 0892/1071] Update CODEOWNERS to add my preferences (#3233) * Update CODEOWNERS * updating DIRECTORY.md Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> --- .github/CODEOWNERS | 6 +++--- DIRECTORY.md | 4 ++++ 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index b10fed71db46..d99417f6def7 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -39,7 +39,7 @@ # /divide_and_conquer/ -# /dynamic_programming/ +# /dynamic_programming/ @Kush1101 # /file_transfer/ @@ -63,7 +63,7 @@ # /machine_learning/ -# /maths/ +# /maths/ @Kush1101 # /matrix/ @@ -73,7 +73,7 @@ /other/ @cclauss -/project_euler/ @dhruvmanila +/project_euler/ @dhruvmanila @Kush1101 # /quantum/ diff --git a/DIRECTORY.md b/DIRECTORY.md index e33d1c32bd86..c1358b8d7686 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -63,6 +63,7 @@ * [Playfair Cipher](https://github.com/TheAlgorithms/Python/blob/master/ciphers/playfair_cipher.py) * [Porta Cipher](https://github.com/TheAlgorithms/Python/blob/master/ciphers/porta_cipher.py) * [Rabin Miller](https://github.com/TheAlgorithms/Python/blob/master/ciphers/rabin_miller.py) + * [Rail Fence Cipher](https://github.com/TheAlgorithms/Python/blob/master/ciphers/rail_fence_cipher.py) * [Rot13](https://github.com/TheAlgorithms/Python/blob/master/ciphers/rot13.py) * [Rsa Cipher](https://github.com/TheAlgorithms/Python/blob/master/ciphers/rsa_cipher.py) * [Rsa Factorization](https://github.com/TheAlgorithms/Python/blob/master/ciphers/rsa_factorization.py) @@ -668,6 +669,8 @@ * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_69/sol1.py) * Problem 71 * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_71/sol1.py) + * Problem 72 + * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_72/sol1.py) * Problem 76 * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_76/sol1.py) * Problem 80 @@ -768,6 +771,7 @@ * [Binary Tree Traversals](https://github.com/TheAlgorithms/Python/blob/master/traversals/binary_tree_traversals.py) ## Web Programming + * [Co2 Emission](https://github.com/TheAlgorithms/Python/blob/master/web_programming/co2_emission.py) * [Covid Stats Via Xpath](https://github.com/TheAlgorithms/Python/blob/master/web_programming/covid_stats_via_xpath.py) * [Crawl Google Results](https://github.com/TheAlgorithms/Python/blob/master/web_programming/crawl_google_results.py) * [Current Stock Price](https://github.com/TheAlgorithms/Python/blob/master/web_programming/current_stock_price.py) From 695217e964befe77b6921f3f778152071330d05d Mon Sep 17 00:00:00 2001 From: Christian Clauss Date: Mon, 12 Oct 2020 17:27:55 +0200 Subject: [PATCH 0893/1071] Update CODEOWNERS (#3235) --- .github/CODEOWNERS | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index d99417f6def7..029f94f7fb3a 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -39,7 +39,7 @@ # /divide_and_conquer/ -# /dynamic_programming/ @Kush1101 +/dynamic_programming/ @Kush1101 # /file_transfer/ @@ -63,7 +63,7 @@ # /machine_learning/ -# /maths/ @Kush1101 +/maths/ @Kush1101 # /matrix/ From 50d7ed84174e99997fc237b88260976098463688 Mon Sep 17 00:00:00 2001 From: Simon Landry Date: Mon, 12 Oct 2020 13:10:29 -0400 Subject: [PATCH 0894/1071] Add project euler problem 51 (#3018) * Add project euler problem 51 * Apply review suggestions --- project_euler/problem_51/__init__.py | 0 project_euler/problem_51/sol1.py | 111 +++++++++++++++++++++++++++ 2 files changed, 111 insertions(+) create mode 100644 project_euler/problem_51/__init__.py create mode 100644 project_euler/problem_51/sol1.py diff --git a/project_euler/problem_51/__init__.py b/project_euler/problem_51/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/project_euler/problem_51/sol1.py b/project_euler/problem_51/sol1.py new file mode 100644 index 000000000000..b160b5a2dbd4 --- /dev/null +++ b/project_euler/problem_51/sol1.py @@ -0,0 +1,111 @@ +""" +https://projecteuler.net/problem=51 +Prime digit replacements +Problem 51 + +By replacing the 1st digit of the 2-digit number *3, it turns out that six of +the nine possible values: 13, 23, 43, 53, 73, and 83, are all prime. + +By replacing the 3rd and 4th digits of 56**3 with the same digit, this 5-digit +number is the first example having seven primes among the ten generated numbers, +yielding the family: 56003, 56113, 56333, 56443, 56663, 56773, and 56993. +Consequently 56003, being the first member of this family, is the smallest prime +with this property. + +Find the smallest prime which, by replacing part of the number (not necessarily +adjacent digits) with the same digit, is part of an eight prime value family. +""" + +from collections import Counter +from typing import List + + +def prime_sieve(n: int) -> List[int]: + """ + Sieve of Erotosthenes + Function to return all the prime numbers up to a certain number + https://en.wikipedia.org/wiki/Sieve_of_Eratosthenes + + >>> prime_sieve(3) + [2] + + >>> prime_sieve(50) + [2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47] + """ + is_prime = [True] * n + is_prime[0] = False + is_prime[1] = False + is_prime[2] = True + + for i in range(3, int(n ** 0.5 + 1), 2): + index = i * 2 + while index < n: + is_prime[index] = False + index = index + i + + primes = [2] + + for i in range(3, n, 2): + if is_prime[i]: + primes.append(i) + + return primes + + +def digit_replacements(number: int) -> List[List[int]]: + """ + Returns all the possible families of digit replacements in a number which + contains at least one repeating digit + + >>> digit_replacements(544) + [[500, 511, 522, 533, 544, 555, 566, 577, 588, 599]] + + >>> digit_replacements(3112) + [[3002, 3112, 3222, 3332, 3442, 3552, 3662, 3772, 3882, 3992]] + """ + number = str(number) + replacements = [] + digits = ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9"] + + for duplicate in Counter(number) - Counter(set(number)): + family = [int(number.replace(duplicate, digit)) for digit in digits] + replacements.append(family) + + return replacements + + +def solution(family_length: int = 8) -> int: + """ + Returns the solution of the problem + + >>> solution(2) + 229399 + + >>> solution(3) + 221311 + """ + numbers_checked = set() + + # Filter primes with less than 3 replaceable digits + primes = { + x for x in set(prime_sieve(1_000_000)) if len(str(x)) - len(set(str(x))) >= 3 + } + + for prime in primes: + if prime in numbers_checked: + continue + + replacements = digit_replacements(prime) + + for family in replacements: + numbers_checked.update(family) + primes_in_family = primes.intersection(family) + + if len(primes_in_family) != family_length: + continue + + return min(primes_in_family) + + +if __name__ == "__main__": + print(solution()) From 6e01004535588b7ce77a5834dcdc4795884d393a Mon Sep 17 00:00:00 2001 From: Abhishek Jaisingh Date: Tue, 13 Oct 2020 01:11:05 +0530 Subject: [PATCH 0895/1071] Add First Quantum Qiskit Code Tutorial (#3173) * Add First Quantum Qiskit Code Tutorial * Add Qiskit Requirement * Address Review Comments * fixup! Format Python code with psf/black push * Update q1.py * updating DIRECTORY.md * Update and rename q1.py to single_qubit_measure.py * updating DIRECTORY.md Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> Co-authored-by: Christian Clauss --- DIRECTORY.md | 3 +++ quantum/single_qubit_measure.py | 34 +++++++++++++++++++++++++++++++++ requirements.txt | 1 + 3 files changed, 38 insertions(+) create mode 100755 quantum/single_qubit_measure.py diff --git a/DIRECTORY.md b/DIRECTORY.md index c1358b8d7686..eee4ae55ca10 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -681,6 +681,9 @@ * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_99/sol1.py) * [Validate Solutions](https://github.com/TheAlgorithms/Python/blob/master/project_euler/validate_solutions.py) +## Quantum + * [Single Qubit Measure](https://github.com/TheAlgorithms/Python/blob/master/quantum/single_qubit_measure.py) + ## Scheduling * [First Come First Served](https://github.com/TheAlgorithms/Python/blob/master/scheduling/first_come_first_served.py) * [Round Robin](https://github.com/TheAlgorithms/Python/blob/master/scheduling/round_robin.py) diff --git a/quantum/single_qubit_measure.py b/quantum/single_qubit_measure.py new file mode 100755 index 000000000000..99d807b034e4 --- /dev/null +++ b/quantum/single_qubit_measure.py @@ -0,0 +1,34 @@ +#!/usr/bin/env python3 +""" +Build a simple bare-minimum quantum circuit that starts with a single +qubit (by default, in state 0), runs the experiment 1000 times, and +finally prints the total count of the states finally observed. +Qiskit Docs: https://qiskit.org/documentation/getting_started.html +""" + +import qiskit as q + + +def single_qubit_measure(qubits: int, classical_bits: int) -> q.result.counts.Counts: + """ + >>> single_qubit_measure(1, 1) + {'0': 1000} + """ + # Use Aer's qasm_simulator + simulator = q.Aer.get_backend("qasm_simulator") + + # Create a Quantum Circuit acting on the q register + circuit = q.QuantumCircuit(qubits, classical_bits) + + # Map the quantum measurement to the classical bits + circuit.measure([0], [0]) + + # Execute the circuit on the qasm simulator + job = q.execute(circuit, simulator, shots=1000) + + # Return the histogram data of the results of the experiment. + return job.result().get_counts(circuit) + + +if __name__ == "__main__": + print(f"Total count for various states are: {single_qubit_measure(1, 1)}") diff --git a/requirements.txt b/requirements.txt index 31dc586c29db..67d9bbbd8448 100644 --- a/requirements.txt +++ b/requirements.txt @@ -7,6 +7,7 @@ numpy opencv-python pandas pillow +qiskit requests scikit-fuzzy sklearn From 29b32d355387b98d83bbb858723621dd5337a6ef Mon Sep 17 00:00:00 2001 From: Dhruv Date: Tue, 13 Oct 2020 15:41:12 +0530 Subject: [PATCH 0896/1071] Improve validate solutions script & fix pre-commit error (#3253) * Trying to time every solution * Proposal 2 for timing PE solutions: - Use pytest fixture along with --capture=no flag to print out the top DURATIONS slowest solution at the end of the test sessions. - Remove the print part and try ... except ... block from the test function. * Proposal 3 for timing PE solutions: Completely changed the way I was performing the tests. Instead of parametrizing the problem numbers and expected output, I will parametrize the solution file path. Steps: - Collect all the solution file paths - Convert the paths into a Python module - Call solution on the module - Assert the answer with the expected results For assertion, it was needed to convert the JSON list object to Python dictionary object which required changing the JSON file itself. * Add type hints for variables * Fix whitespace in single_qubit_measure --- .travis.yml | 4 +- project_euler/project_euler_answers.json | 3629 +++++----------------- project_euler/validate_solutions.py | 76 +- quantum/single_qubit_measure.py | 4 +- 4 files changed, 769 insertions(+), 2944 deletions(-) diff --git a/.travis.yml b/.travis.yml index ff59af2db34e..e43ff5c2f030 100644 --- a/.travis.yml +++ b/.travis.yml @@ -16,10 +16,8 @@ jobs: script: - pytest --doctest-modules --durations=10 --cov-report=term-missing:skip-covered --cov=project_euler/ project_euler/ - name: Project Euler Solution - install: - - pip install pytest-subtests script: - - pytest --tb=short project_euler/validate_solutions.py + - pytest --tb=short --durations=10 project_euler/validate_solutions.py after_success: - scripts/build_directory_md.py 2>&1 | tee DIRECTORY.md notifications: diff --git a/project_euler/project_euler_answers.json b/project_euler/project_euler_answers.json index df5a60257f5a..6889ad09703e 100644 --- a/project_euler/project_euler_answers.json +++ b/project_euler/project_euler_answers.json @@ -1,2902 +1,727 @@ -[ - [ - 1, - "233168" - ], - [ - 2, - "4613732" - ], - [ - 3, - "6857" - ], - [ - 4, - "906609" - ], - [ - 5, - "232792560" - ], - [ - 6, - "25164150" - ], - [ - 7, - "104743" - ], - [ - 8, - "23514624000" - ], - [ - 9, - "31875000" - ], - [ - 10, - "142913828922" - ], - [ - 11, - "70600674" - ], - [ - 12, - "76576500" - ], - [ - 13, - "5537376230" - ], - [ - 14, - "837799" - ], - [ - 15, - "137846528820" - ], - [ - 16, - "1366" - ], - [ - 17, - "21124" - ], - [ - 18, - "1074" - ], - [ - 19, - "171" - ], - [ - 20, - "648" - ], - [ - 21, - "31626" - ], - [ - 22, - "871198282" - ], - [ - 23, - "4179871" - ], - [ - 24, - "2783915460" - ], - [ - 25, - "4782" - ], - [ - 26, - "983" - ], - [ - 27, - "-59231" - ], - [ - 28, - "669171001" - ], - [ - 29, - "9183" - ], - [ - 30, - "443839" - ], - [ - 31, - "73682" - ], - [ - 32, - "45228" - ], - [ - 33, - "100" - ], - [ - 34, - "40730" - ], - [ - 35, - "55" - ], - [ - 36, - "872187" - ], - [ - 37, - "748317" - ], - [ - 38, - "932718654" - ], - [ - 39, - "840" - ], - [ - 40, - "210" - ], - [ - 41, - "7652413" - ], - [ - 42, - "162" - ], - [ - 43, - "16695334890" - ], - [ - 44, - "5482660" - ], - [ - 45, - "1533776805" - ], - [ - 46, - "5777" - ], - [ - 47, - "134043" - ], - [ - 48, - "9110846700" - ], - [ - 49, - "296962999629" - ], - [ - 50, - "997651" - ], - [ - 51, - "121313" - ], - [ - 52, - "142857" - ], - [ - 53, - "4075" - ], - [ - 54, - "376" - ], - [ - 55, - "249" - ], - [ - 56, - "972" - ], - [ - 57, - "153" - ], - [ - 58, - "26241" - ], - [ - 59, - "129448" - ], - [ - 60, - "26033" - ], - [ - 61, - "28684" - ], - [ - 62, - "127035954683" - ], - [ - 63, - "49" - ], - [ - 64, - "1322" - ], - [ - 65, - "272" - ], - [ - 66, - "661" - ], - [ - 67, - "7273" - ], - [ - 68, - "6531031914842725" - ], - [ - 69, - "510510" - ], - [ - 70, - "8319823" - ], - [ - 71, - "428570" - ], - [ - 72, - "303963552391" - ], - [ - 73, - "7295372" - ], - [ - 74, - "402" - ], - [ - 75, - "161667" - ], - [ - 76, - "190569291" - ], - [ - 77, - "71" - ], - [ - 78, - "55374" - ], - [ - 79, - "73162890" - ], - [ - 80, - "40886" - ], - [ - 81, - "427337" - ], - [ - 82, - "260324" - ], - [ - 83, - "425185" - ], - [ - 84, - "101524" - ], - [ - 85, - "2772" - ], - [ - 86, - "1818" - ], - [ - 87, - "1097343" - ], - [ - 88, - "7587457" - ], - [ - 89, - "743" - ], - [ - 90, - "1217" - ], - [ - 91, - "14234" - ], - [ - 92, - "8581146" - ], - [ - 93, - "1258" - ], - [ - 94, - "518408346" - ], - [ - 95, - "14316" - ], - [ - 96, - "24702" - ], - [ - 97, - "8739992577" - ], - [ - 98, - "18769" - ], - [ - 99, - "709" - ], - [ - 100, - "756872327473" - ], - [ - 101, - "37076114526" - ], - [ - 102, - "228" - ], - [ - 103, - "20313839404245" - ], - [ - 104, - "329468" - ], - [ - 105, - "73702" - ], - [ - 106, - "21384" - ], - [ - 107, - "259679" - ], - [ - 108, - "180180" - ], - [ - 109, - "38182" - ], - [ - 110, - "9350130049860600" - ], - [ - 111, - "612407567715" - ], - [ - 112, - "1587000" - ], - [ - 113, - "51161058134250" - ], - [ - 114, - "16475640049" - ], - [ - 115, - "168" - ], - [ - 116, - "20492570929" - ], - [ - 117, - "100808458960497" - ], - [ - 118, - "44680" - ], - [ - 119, - "248155780267521" - ], - [ - 120, - "333082500" - ], - [ - 121, - "2269" - ], - [ - 122, - "1582" - ], - [ - 123, - "21035" - ], - [ - 124, - "21417" - ], - [ - 125, - "2906969179" - ], - [ - 126, - "18522" - ], - [ - 127, - "18407904" - ], - [ - 128, - "14516824220" - ], - [ - 129, - "1000023" - ], - [ - 130, - "149253" - ], - [ - 131, - "173" - ], - [ - 132, - "843296" - ], - [ - 133, - "453647705" - ], - [ - 134, - "18613426663617118" - ], - [ - 135, - "4989" - ], - [ - 136, - "2544559" - ], - [ - 137, - "1120149658760" - ], - [ - 138, - "1118049290473932" - ], - [ - 139, - "10057761" - ], - [ - 140, - "5673835352990" - ], - [ - 141, - "878454337159" - ], - [ - 142, - "1006193" - ], - [ - 143, - "30758397" - ], - [ - 144, - "354" - ], - [ - 145, - "608720" - ], - [ - 146, - "676333270" - ], - [ - 147, - "846910284" - ], - [ - 148, - "2129970655314432" - ], - [ - 149, - "52852124" - ], - [ - 150, - "-271248680" - ], - [ - 151, - "0.464399" - ], - [ - 152, - "301" - ], - [ - 153, - "17971254122360635" - ], - [ - 154, - "479742450" - ], - [ - 155, - "3857447" - ], - [ - 156, - "21295121502550" - ], - [ - 157, - "53490" - ], - [ - 158, - "409511334375" - ], - [ - 159, - "14489159" - ], - [ - 160, - "16576" - ], - [ - 161, - "20574308184277971" - ], - [ - 162, - "3D58725572C62302" - ], - [ - 163, - "343047" - ], - [ - 164, - "378158756814587" - ], - [ - 165, - "2868868" - ], - [ - 166, - "7130034" - ], - [ - 167, - "3916160068885" - ], - [ - 168, - "59206" - ], - [ - 169, - "178653872807" - ], - [ - 170, - "9857164023" - ], - [ - 171, - "142989277" - ], - [ - 172, - "227485267000992000" - ], - [ - 173, - "1572729" - ], - [ - 174, - "209566" - ], - [ - 175, - "1,13717420,8" - ], - [ - 176, - "96818198400000" - ], - [ - 177, - "129325" - ], - [ - 178, - "126461847755" - ], - [ - 179, - "986262" - ], - [ - 180, - "285196020571078987" - ], - [ - 181, - "83735848679360680" - ], - [ - 182, - "399788195976" - ], - [ - 183, - "48861552" - ], - [ - 184, - "1725323624056" - ], - [ - 185, - "4640261571849533" - ], - [ - 186, - "2325629" - ], - [ - 187, - "17427258" - ], - [ - 188, - "95962097" - ], - [ - 189, - "10834893628237824" - ], - [ - 190, - "371048281" - ], - [ - 191, - "1918080160" - ], - [ - 192, - "57060635927998347" - ], - [ - 193, - "684465067343069" - ], - [ - 194, - "61190912" - ], - [ - 195, - "75085391" - ], - [ - 196, - "322303240771079935" - ], - [ - 197, - "1.710637717" - ], - [ - 198, - "52374425" - ], - [ - 199, - "0.00396087" - ], - [ - 200, - "229161792008" - ], - [ - 201, - "115039000" - ], - [ - 202, - "1209002624" - ], - [ - 203, - "34029210557338" - ], - [ - 204, - "2944730" - ], - [ - 205, - "0.5731441" - ], - [ - 206, - "1389019170" - ], - [ - 207, - "44043947822" - ], - [ - 208, - "331951449665644800" - ], - [ - 209, - "15964587728784" - ], - [ - 210, - "1598174770174689458" - ], - [ - 211, - "1922364685" - ], - [ - 212, - "328968937309" - ], - [ - 213, - "330.721154" - ], - [ - 214, - "1677366278943" - ], - [ - 215, - "806844323190414" - ], - [ - 216, - "5437849" - ], - [ - 217, - "6273134" - ], - [ - 218, - "0" - ], - [ - 219, - "64564225042" - ], - [ - 220, - "139776,963904" - ], - [ - 221, - "1884161251122450" - ], - [ - 222, - "1590933" - ], - [ - 223, - "61614848" - ], - [ - 224, - "4137330" - ], - [ - 225, - "2009" - ], - [ - 226, - "0.11316017" - ], - [ - 227, - "3780.618622" - ], - [ - 228, - "86226" - ], - [ - 229, - "11325263" - ], - [ - 230, - "850481152593119296" - ], - [ - 231, - "7526965179680" - ], - [ - 232, - "0.83648556" - ], - [ - 233, - "271204031455541309" - ], - [ - 234, - "1259187438574927161" - ], - [ - 235, - "1.002322108633" - ], - [ - 236, - "123/59" - ], - [ - 237, - "15836928" - ], - [ - 238, - "9922545104535661" - ], - [ - 239, - "0.001887854841" - ], - [ - 240, - "7448717393364181966" - ], - [ - 241, - "482316491800641154" - ], - [ - 242, - "997104142249036713" - ], - [ - 243, - "892371480" - ], - [ - 244, - "96356848" - ], - [ - 245, - "288084712410001" - ], - [ - 246, - "810834388" - ], - [ - 247, - "782252" - ], - [ - 248, - "23507044290" - ], - [ - 249, - "9275262564250418" - ], - [ - 250, - "1425480602091519" - ], - [ - 251, - "18946051" - ], - [ - 252, - "104924.0" - ], - [ - 253, - "11.492847" - ], - [ - 254, - "8184523820510" - ], - [ - 255, - "4.4474011180" - ], - [ - 256, - "85765680" - ], - [ - 257, - "139012411" - ], - [ - 258, - "12747994" - ], - [ - 259, - "20101196798" - ], - [ - 260, - "167542057" - ], - [ - 261, - "238890850232021" - ], - [ - 262, - "2531.205" - ], - [ - 263, - "2039506520" - ], - [ - 264, - "2816417.1055" - ], - [ - 265, - "209110240768" - ], - [ - 266, - "1096883702440585" - ], - [ - 267, - "0.999992836187" - ], - [ - 268, - "785478606870985" - ], - [ - 269, - "1311109198529286" - ], - [ - 270, - "82282080" - ], - [ - 271, - "4617456485273129588" - ], - [ - 272, - "8495585919506151122" - ], - [ - 273, - "2032447591196869022" - ], - [ - 274, - "1601912348822" - ], - [ - 275, - "15030564" - ], - [ - 276, - "5777137137739632912" - ], - [ - 277, - "1125977393124310" - ], - [ - 278, - "1228215747273908452" - ], - [ - 279, - "416577688" - ], - [ - 280, - "430.088247" - ], - [ - 281, - "1485776387445623" - ], - [ - 282, - "1098988351" - ], - [ - 283, - "28038042525570324" - ], - [ - 284, - "5a411d7b" - ], - [ - 285, - "157055.80999" - ], - [ - 286, - "52.6494571953" - ], - [ - 287, - "313135496" - ], - [ - 288, - "605857431263981935" - ], - [ - 289, - "6567944538" - ], - [ - 290, - "20444710234716473" - ], - [ - 291, - "4037526" - ], - [ - 292, - "3600060866" - ], - [ - 293, - "2209" - ], - [ - 294, - "789184709" - ], - [ - 295, - "4884650818" - ], - [ - 296, - "1137208419" - ], - [ - 297, - "2252639041804718029" - ], - [ - 298, - "1.76882294" - ], - [ - 299, - "549936643" - ], - [ - 300, - "8.0540771484375" - ], - [ - 301, - "2178309" - ], - [ - 302, - "1170060" - ], - [ - 303, - "1111981904675169" - ], - [ - 304, - "283988410192" - ], - [ - 305, - "18174995535140" - ], - [ - 306, - "852938" - ], - [ - 307, - "0.7311720251" - ], - [ - 308, - "1539669807660924" - ], - [ - 309, - "210139" - ], - [ - 310, - "2586528661783" - ], - [ - 311, - "2466018557" - ], - [ - 312, - "324681947" - ], - [ - 313, - "2057774861813004" - ], - [ - 314, - "132.52756426" - ], - [ - 315, - "13625242" - ], - [ - 316, - "542934735751917735" - ], - [ - 317, - "1856532.8455" - ], - [ - 318, - "709313889" - ], - [ - 319, - "268457129" - ], - [ - 320, - "278157919195482643" - ], - [ - 321, - "2470433131948040" - ], - [ - 322, - "999998760323313995" - ], - [ - 323, - "6.3551758451" - ], - [ - 324, - "96972774" - ], - [ - 325, - "54672965" - ], - [ - 326, - "1966666166408794329" - ], - [ - 327, - "34315549139516" - ], - [ - 328, - "260511850222" - ], - [ - 329, - "199740353/29386561536000" - ], - [ - 330, - "15955822" - ], - [ - 331, - "467178235146843549" - ], - [ - 332, - "2717.751525" - ], - [ - 333, - "3053105" - ], - [ - 334, - "150320021261690835" - ], - [ - 335, - "5032316" - ], - [ - 336, - "CAGBIHEFJDK" - ], - [ - 337, - "85068035" - ], - [ - 338, - "15614292" - ], - [ - 339, - "19823.542204" - ], - [ - 340, - "291504964" - ], - [ - 341, - "56098610614277014" - ], - [ - 342, - "5943040885644" - ], - [ - 343, - "269533451410884183" - ], - [ - 344, - "65579304332" - ], - [ - 345, - "13938" - ], - [ - 346, - "336108797689259276" - ], - [ - 347, - "11109800204052" - ], - [ - 348, - "1004195061" - ], - [ - 349, - "115384615384614952" - ], - [ - 350, - "84664213" - ], - [ - 351, - "11762187201804552" - ], - [ - 352, - "378563.260589" - ], - [ - 353, - "1.2759860331" - ], - [ - 354, - "58065134" - ], - [ - 355, - "1726545007" - ], - [ - 356, - "28010159" - ], - [ - 357, - "1739023853137" - ], - [ - 358, - "3284144505" - ], - [ - 359, - "40632119" - ], - [ - 360, - "878825614395267072" - ], - [ - 361, - "178476944" - ], - [ - 362, - "457895958010" - ], - [ - 363, - "0.0000372091" - ], - [ - 364, - "44855254" - ], - [ - 365, - "162619462356610313" - ], - [ - 366, - "88351299" - ], - [ - 367, - "48271207" - ], - [ - 368, - "253.6135092068" - ], - [ - 369, - "862400558448" - ], - [ - 370, - "41791929448408" - ], - [ - 371, - "40.66368097" - ], - [ - 372, - "301450082318807027" - ], - [ - 373, - "727227472448913" - ], - [ - 374, - "334420941" - ], - [ - 375, - "7435327983715286168" - ], - [ - 376, - "973059630185670" - ], - [ - 377, - "732385277" - ], - [ - 378, - "147534623725724718" - ], - [ - 379, - "132314136838185" - ], - [ - 380, - "6.3202e25093" - ], - [ - 381, - "139602943319822" - ], - [ - 382, - "697003956" - ], - [ - 383, - "22173624649806" - ], - [ - 384, - "3354706415856332783" - ], - [ - 385, - "3776957309612153700" - ], - [ - 386, - "528755790" - ], - [ - 387, - "696067597313468" - ], - [ - 388, - "831907372805129931" - ], - [ - 389, - "2406376.3623" - ], - [ - 390, - "2919133642971" - ], - [ - 391, - "61029882288" - ], - [ - 392, - "3.1486734435" - ], - [ - 393, - "112398351350823112" - ], - [ - 394, - "3.2370342194" - ], - [ - 395, - "28.2453753155" - ], - [ - 396, - "173214653" - ], - [ - 397, - "141630459461893728" - ], - [ - 398, - "2010.59096" - ], - [ - 399, - "1508395636674243,6.5e27330467" - ], - [ - 400, - "438505383468410633" - ], - [ - 401, - "281632621" - ], - [ - 402, - "356019862" - ], - [ - 403, - "18224771" - ], - [ - 404, - "1199215615081353" - ], - [ - 405, - "237696125" - ], - [ - 406, - "36813.12757207" - ], - [ - 407, - "39782849136421" - ], - [ - 408, - "299742733" - ], - [ - 409, - "253223948" - ], - [ - 410, - "799999783589946560" - ], - [ - 411, - "9936352" - ], - [ - 412, - "38788800" - ], - [ - 413, - "3079418648040719" - ], - [ - 414, - "552506775824935461" - ], - [ - 415, - "55859742" - ], - [ - 416, - "898082747" - ], - [ - 417, - "446572970925740" - ], - [ - 418, - "1177163565297340320" - ], - [ - 419, - "998567458,1046245404,43363922" - ], - [ - 420, - "145159332" - ], - [ - 421, - "2304215802083466198" - ], - [ - 422, - "92060460" - ], - [ - 423, - "653972374" - ], - [ - 424, - "1059760019628" - ], - [ - 425, - "46479497324" - ], - [ - 426, - "31591886008" - ], - [ - 427, - "97138867" - ], - [ - 428, - "747215561862" - ], - [ - 429, - "98792821" - ], - [ - 430, - "5000624921.38" - ], - [ - 431, - "23.386029052" - ], - [ - 432, - "754862080" - ], - [ - 433, - "326624372659664" - ], - [ - 434, - "863253606" - ], - [ - 435, - "252541322550" - ], - [ - 436, - "0.5276662759" - ], - [ - 437, - "74204709657207" - ], - [ - 438, - "2046409616809" - ], - [ - 439, - "968697378" - ], - [ - 440, - "970746056" - ], - [ - 441, - "5000088.8395" - ], - [ - 442, - "1295552661530920149" - ], - [ - 443, - "2744233049300770" - ], - [ - 444, - "1.200856722e263" - ], - [ - 445, - "659104042" - ], - [ - 446, - "907803852" - ], - [ - 447, - "530553372" - ], - [ - 448, - "106467648" - ], - [ - 449, - "103.37870096" - ], - [ - 450, - "583333163984220940" - ], - [ - 451, - "153651073760956" - ], - [ - 452, - "345558983" - ], - [ - 453, - "104354107" - ], - [ - 454, - "5435004633092" - ], - [ - 455, - "450186511399999" - ], - [ - 456, - "333333208685971546" - ], - [ - 457, - "2647787126797397063" - ], - [ - 458, - "423341841" - ], - [ - 459, - "3996390106631" - ], - [ - 460, - "18.420738199" - ], - [ - 461, - "159820276" - ], - [ - 462, - "5.5350769703e1512" - ], - [ - 463, - "808981553" - ], - [ - 464, - "198775297232878" - ], - [ - 465, - "585965659" - ], - [ - 466, - "258381958195474745" - ], - [ - 467, - "775181359" - ], - [ - 468, - "852950321" - ], - [ - 469, - "0.56766764161831" - ], - [ - 470, - "147668794" - ], - [ - 471, - "1.895093981e31" - ], - [ - 472, - "73811586" - ], - [ - 473, - "35856681704365" - ], - [ - 474, - "9690646731515010" - ], - [ - 475, - "75780067" - ], - [ - 476, - "110242.87794" - ], - [ - 477, - "25044905874565165" - ], - [ - 478, - "59510340" - ], - [ - 479, - "191541795" - ], - [ - 480, - "turnthestarson" - ], - [ - 481, - "729.12106947" - ], - [ - 482, - "1400824879147" - ], - [ - 483, - "4.993401567e22" - ], - [ - 484, - "8907904768686152599" - ], - [ - 485, - "51281274340" - ], - [ - 486, - "11408450515" - ], - [ - 487, - "106650212746" - ], - [ - 488, - "216737278" - ], - [ - 489, - "1791954757162" - ], - [ - 490, - "777577686" - ], - [ - 491, - "194505988824000" - ], - [ - 492, - "242586962923928" - ], - [ - 493, - "6.818741802" - ], - [ - 494, - "2880067194446832666" - ], - [ - 495, - "789107601" - ], - [ - 496, - "2042473533769142717" - ], - [ - 497, - "684901360" - ], - [ - 498, - "472294837" - ], - [ - 499, - "0.8660312" - ], - [ - 500, - "35407281" - ], - [ - 501, - "197912312715" - ], - [ - 502, - "749485217" - ], - [ - 503, - "3.8694550145" - ], - [ - 504, - "694687" - ], - [ - 505, - "714591308667615832" - ], - [ - 506, - "18934502" - ], - [ - 507, - "316558047002627270" - ], - [ - 508, - "891874596" - ], - [ - 509, - "151725678" - ], - [ - 510, - "315306518862563689" - ], - [ - 511, - "935247012" - ], - [ - 512, - "50660591862310323" - ], - [ - 513, - "2925619196" - ], - [ - 514, - "8986.86698" - ], - [ - 515, - "2422639000800" - ], - [ - 516, - "939087315" - ], - [ - 517, - "581468882" - ], - [ - 518, - "100315739184392" - ], - [ - 519, - "804739330" - ], - [ - 520, - "238413705" - ], - [ - 521, - "44389811" - ], - [ - 522, - "96772715" - ], - [ - 523, - "37125450.44" - ], - [ - 524, - "2432925835413407847" - ], - [ - 525, - "44.69921807" - ], - [ - 526, - "49601160286750947" - ], - [ - 527, - "11.92412011" - ], - [ - 528, - "779027989" - ], - [ - 529, - "23624465" - ], - [ - 530, - "207366437157977206" - ], - [ - 531, - "4515432351156203105" - ], - [ - 532, - "827306.56" - ], - [ - 533, - "789453601" - ], - [ - 534, - "11726115562784664" - ], - [ - 535, - "611778217" - ], - [ - 536, - "3557005261906288" - ], - [ - 537, - "779429131" - ], - [ - 538, - "22472871503401097" - ], - [ - 539, - "426334056" - ], - [ - 540, - "500000000002845" - ], - [ - 541, - "4580726482872451" - ], - [ - 542, - "697586734240314852" - ], - [ - 543, - "199007746081234640" - ], - [ - 544, - "640432376" - ], - [ - 545, - "921107572" - ], - [ - 546, - "215656873" - ], - [ - 547, - "11730879.0023" - ], - [ - 548, - "12144044603581281" - ], - [ - 549, - "476001479068717" - ], - [ - 550, - "328104836" - ], - [ - 551, - "73597483551591773" - ], - [ - 552, - "326227335" - ], - [ - 553, - "57717170" - ], - [ - 554, - "89539872" - ], - [ - 555, - "208517717451208352" - ], - [ - 556, - "52126939292957" - ], - [ - 557, - "2699929328" - ], - [ - 558, - "226754889" - ], - [ - 559, - "684724920" - ], - [ - 560, - "994345168" - ], - [ - 561, - "452480999988235494" - ], - [ - 562, - "51208732914368" - ], - [ - 563, - "27186308211734760" - ], - [ - 564, - "12363.698850" - ], - [ - 565, - "2992480851924313898" - ], - [ - 566, - "329569369413585" - ], - [ - 567, - "75.44817535" - ], - [ - 568, - "4228020" - ], - [ - 569, - "21025060" - ], - [ - 570, - "271197444" - ], - [ - 571, - "30510390701978" - ], - [ - 572, - "19737656" - ], - [ - 573, - "1252.9809" - ], - [ - 574, - "5780447552057000454" - ], - [ - 575, - "0.000989640561" - ], - [ - 576, - "344457.5871" - ], - [ - 577, - "265695031399260211" - ], - [ - 578, - "9219696799346" - ], - [ - 579, - "3805524" - ], - [ - 580, - "2327213148095366" - ], - [ - 581, - "2227616372734" - ], - [ - 582, - "19903" - ], - [ - 583, - "1174137929000" - ], - [ - 584, - "32.83822408" - ], - [ - 585, - "17714439395932" - ], - [ - 586, - "82490213" - ], - [ - 587, - "2240" - ], - [ - 588, - "11651930052" - ], - [ - 589, - "131776959.25" - ], - [ - 590, - "834171904" - ], - [ - 591, - "526007984625966" - ], - [ - 592, - "13415DF2BE9C" - ], - [ - 593, - "96632320042.0" - ], - [ - 594, - "47067598" - ], - [ - 595, - "54.17529329" - ], - [ - 596, - "734582049" - ], - [ - 597, - "0.5001817828" - ], - [ - 598, - "543194779059" - ], - [ - 599, - "12395526079546335" - ], - [ - 600, - "2668608479740672" - ], - [ - 601, - "1617243" - ], - [ - 602, - "269496760" - ], - [ - 603, - "879476477" - ], - [ - 604, - "1398582231101" - ], - [ - 605, - "59992576" - ], - [ - 606, - "158452775" - ], - [ - 607, - "13.1265108586" - ], - [ - 608, - "439689828" - ], - [ - 609, - "172023848" - ], - [ - 610, - "319.30207833" - ], - [ - 611, - "49283233900" - ], - [ - 612, - "819963842" - ], - [ - 613, - "0.3916721504" - ], - [ - 614, - "130694090" - ], - [ - 615, - "108424772" - ], - [ - 616, - "310884668312456458" - ], - [ - 617, - "1001133757" - ], - [ - 618, - "634212216" - ], - [ - 619, - "857810883" - ], - [ - 620, - "1470337306" - ], - [ - 621, - "11429712" - ], - [ - 622, - "3010983666182123972" - ], - [ - 623, - "3679796" - ], - [ - 624, - "984524441" - ], - [ - 625, - "551614306" - ], - [ - 626, - "695577663" - ], - [ - 627, - "220196142" - ], - [ - 628, - "210286684" - ], - [ - 629, - "626616617" - ], - [ - 630, - "9669182880384" - ], - [ - 631, - "869588692" - ], - [ - 632, - "728378714" - ], - [ - 633, - "1.0012e-10" - ], - [ - 634, - "4019680944" - ], - [ - 635, - "689294705" - ], - [ - 636, - "888316" - ], - [ - 637, - "49000634845039" - ], - [ - 638, - "18423394" - ], - [ - 639, - "797866893" - ], - [ - 640, - "50.317928" - ], - [ - 641, - "793525366" - ], - [ - 642, - "631499044" - ], - [ - 643, - "968274154" - ], - [ - 644, - "20.11208767" - ], - [ - 645, - "48894.2174" - ], - [ - 646, - "845218467" - ], - [ - 647, - "563132994232918611" - ], - [ - 648, - "301483197" - ], - [ - 649, - "924668016" - ], - [ - 650, - "538319652" - ], - [ - 651, - "448233151" - ], - [ - 652, - "983924497" - ], - [ - 653, - "1130658687" - ], - [ - 654, - "815868280" - ], - [ - 655, - "2000008332" - ], - [ - 656, - "888873503555187" - ], - [ - 657, - "219493139" - ], - [ - 658, - "958280177" - ], - [ - 659, - "238518915714422000" - ], - [ - 660, - "474766783" - ], - [ - 661, - "646231.2177" - ], - [ - 662, - "860873428" - ], - [ - 663, - "1884138010064752" - ], - [ - 664, - "35295862" - ], - [ - 665, - "11541685709674" - ], - [ - 666, - "0.48023168" - ], - [ - 667, - "1.5276527928" - ], - [ - 668, - "2811077773" - ], - [ - 669, - "56342087360542122" - ], - [ - 670, - "551055065" - ], - [ - 671, - "946106780" - ], - [ - 672, - "91627537" - ], - [ - 673, - "700325380" - ], - [ - 674, - "416678753" - ], - [ - 675, - "416146418" - ], - [ - 676, - "3562668074339584" - ], - [ - 677, - "984183023" - ], - [ - 678, - "1986065" - ], - [ - 679, - "644997092988678" - ], - [ - 680, - "563917241" - ], - [ - 681, - "2611227421428" - ], - [ - 682, - "290872710" - ], - [ - 683, - "2.38955315e11" - ], - [ - 684, - "922058210" - ], - [ - 685, - "662878999" - ], - [ - 686, - "193060223" - ], - [ - 687, - "0.3285320869" - ], - [ - 688, - "110941813" - ], - [ - 689, - "0.56565454" - ], - [ - 690, - "415157690" - ], - [ - 691, - "11570761" - ], - [ - 692, - "842043391019219959" - ], - [ - 693, - "699161" - ], - [ - 694, - "1339784153569958487" - ], - [ - 695, - "0.1017786859" - ], - [ - 696, - "436944244" - ], - [ - 697, - "4343871.06" - ], - [ - 698, - "57808202" - ], - [ - 699, - "37010438774467572" - ], - [ - 700, - "1517926517777556" - ], - [ - 701, - "13.51099836" - ], - [ - 702, - "622305608172525546" - ], - [ - 703, - "843437991" - ], - [ - 704, - "501985601490518144" - ], - [ - 705, - "480440153" - ], - [ - 706, - "884837055" - ], - [ - 707, - "652907799" - ], - [ - 708, - "28874142998632109" - ], - [ - 709, - "773479144" - ], - [ - 710, - "1275000" - ], - [ - 711, - "541510990" - ], - [ - 712, - "413876461" - ], - [ - 713, - "788626351539895" - ], - [ - 714, - "2.452767775565e20" - ], - [ - 715, - "883188017" - ], - [ - 716, - "238948623" - ], - [ - 717, - "1603036763131" - ], - [ - 718, - "228579116" - ], - [ - 719, - "128088830547982" - ], - [ - 720, - "688081048" - ], - [ - 721, - "700792959" - ], - [ - 722, - "3.376792776502e132" - ], - [ - 723, - "1395793419248" - ], - [ - 724, - "18128250110" - ], - [ - 725, - "4598797036650685" - ] -] +{ + "01": "233168", + "02": "4613732", + "03": "6857", + "04": "906609", + "05": "232792560", + "06": "25164150", + "07": "104743", + "08": "23514624000", + "09": "31875000", + "10": "142913828922", + "11": "70600674", + "12": "76576500", + "13": "5537376230", + "14": "837799", + "15": "137846528820", + "16": "1366", + "17": "21124", + "18": "1074", + "19": "171", + "20": "648", + "21": "31626", + "22": "871198282", + "23": "4179871", + "24": "2783915460", + "25": "4782", + "26": "983", + "27": "-59231", + "28": "669171001", + "29": "9183", + "30": "443839", + "31": "73682", + "32": "45228", + "33": "100", + "34": "40730", + "35": "55", + "36": "872187", + "37": "748317", + "38": "932718654", + "39": "840", + "40": "210", + "41": "7652413", + "42": "162", + "43": "16695334890", + "44": "5482660", + "45": "1533776805", + "46": "5777", + "47": "134043", + "48": "9110846700", + "49": "296962999629", + "50": "997651", + "51": "121313", + "52": "142857", + "53": "4075", + "54": "376", + "55": "249", + "56": "972", + "57": "153", + "58": "26241", + "59": "129448", + "60": "26033", + "61": "28684", + "62": "127035954683", + "63": "49", + "64": "1322", + "65": "272", + "66": "661", + "67": "7273", + "68": "6531031914842725", + "69": "510510", + "70": "8319823", + "71": "428570", + "72": "303963552391", + "73": "7295372", + "74": "402", + "75": "161667", + "76": "190569291", + "77": "71", + "78": "55374", + "79": "73162890", + "80": "40886", + "81": "427337", + "82": "260324", + "83": "425185", + "84": "101524", + "85": "2772", + "86": "1818", + "87": "1097343", + "88": "7587457", + "89": "743", + "90": "1217", + "91": "14234", + "92": "8581146", + "93": "1258", + "94": "518408346", + "95": "14316", + "96": "24702", + "97": "8739992577", + "98": "18769", + "99": "709", + "100": "756872327473", + "101": "37076114526", + "102": "228", + "103": "20313839404245", + "104": "329468", + "105": "73702", + "106": "21384", + "107": "259679", + "108": "180180", + "109": "38182", + "110": "9350130049860600", + "111": "612407567715", + "112": "1587000", + "113": "51161058134250", + "114": "16475640049", + "115": "168", + "116": "20492570929", + "117": "100808458960497", + "118": "44680", + "119": "248155780267521", + "120": "333082500", + "121": "2269", + "122": "1582", + "123": "21035", + "124": "21417", + "125": "2906969179", + "126": "18522", + "127": "18407904", + "128": "14516824220", + "129": "1000023", + "130": "149253", + "131": "173", + "132": "843296", + "133": "453647705", + "134": "18613426663617118", + "135": "4989", + "136": "2544559", + "137": "1120149658760", + "138": "1118049290473932", + "139": "10057761", + "140": "5673835352990", + "141": "878454337159", + "142": "1006193", + "143": "30758397", + "144": "354", + "145": "608720", + "146": "676333270", + "147": "846910284", + "148": "2129970655314432", + "149": "52852124", + "150": "-271248680", + "151": "0.464399", + "152": "301", + "153": "17971254122360635", + "154": "479742450", + "155": "3857447", + "156": "21295121502550", + "157": "53490", + "158": "409511334375", + "159": "14489159", + "160": "16576", + "161": "20574308184277971", + "162": "3D58725572C62302", + "163": "343047", + "164": "378158756814587", + "165": "2868868", + "166": "7130034", + "167": "3916160068885", + "168": "59206", + "169": "178653872807", + "170": "9857164023", + "171": "142989277", + "172": "227485267000992000", + "173": "1572729", + "174": "209566", + "175": "1,13717420,8", + "176": "96818198400000", + "177": "129325", + "178": "126461847755", + "179": "986262", + "180": "285196020571078987", + "181": "83735848679360680", + "182": "399788195976", + "183": "48861552", + "184": "1725323624056", + "185": "4640261571849533", + "186": "2325629", + "187": "17427258", + "188": "95962097", + "189": "10834893628237824", + "190": "371048281", + "191": "1918080160", + "192": "57060635927998347", + "193": "684465067343069", + "194": "61190912", + "195": "75085391", + "196": "322303240771079935", + "197": "1.710637717", + "198": "52374425", + "199": "0.00396087", + "200": "229161792008", + "201": "115039000", + "202": "1209002624", + "203": "34029210557338", + "204": "2944730", + "205": "0.5731441", + "206": "1389019170", + "207": "44043947822", + "208": "331951449665644800", + "209": "15964587728784", + "210": "1598174770174689458", + "211": "1922364685", + "212": "328968937309", + "213": "330.721154", + "214": "1677366278943", + "215": "806844323190414", + "216": "5437849", + "217": "6273134", + "218": "0", + "219": "64564225042", + "220": "139776,963904", + "221": "1884161251122450", + "222": "1590933", + "223": "61614848", + "224": "4137330", + "225": "2009", + "226": "0.11316017", + "227": "3780.618622", + "228": "86226", + "229": "11325263", + "230": "850481152593119296", + "231": "7526965179680", + "232": "0.83648556", + "233": "271204031455541309", + "234": "1259187438574927161", + "235": "1.002322108633", + "236": "123/59", + "237": "15836928", + "238": "9922545104535661", + "239": "0.001887854841", + "240": "7448717393364181966", + "241": "482316491800641154", + "242": "997104142249036713", + "243": "892371480", + "244": "96356848", + "245": "288084712410001", + "246": "810834388", + "247": "782252", + "248": "23507044290", + "249": "9275262564250418", + "250": "1425480602091519", + "251": "18946051", + "252": "104924.0", + "253": "11.492847", + "254": "8184523820510", + "255": "4.4474011180", + "256": "85765680", + "257": "139012411", + "258": "12747994", + "259": "20101196798", + "260": "167542057", + "261": "238890850232021", + "262": "2531.205", + "263": "2039506520", + "264": "2816417.1055", + "265": "209110240768", + "266": "1096883702440585", + "267": "0.999992836187", + "268": "785478606870985", + "269": "1311109198529286", + "270": "82282080", + "271": "4617456485273129588", + "272": "8495585919506151122", + "273": "2032447591196869022", + "274": "1601912348822", + "275": "15030564", + "276": "5777137137739632912", + "277": "1125977393124310", + "278": "1228215747273908452", + "279": "416577688", + "280": "430.088247", + "281": "1485776387445623", + "282": "1098988351", + "283": "28038042525570324", + "284": "5a411d7b", + "285": "157055.80999", + "286": "52.6494571953", + "287": "313135496", + "288": "605857431263981935", + "289": "6567944538", + "290": "20444710234716473", + "291": "4037526", + "292": "3600060866", + "293": "2209", + "294": "789184709", + "295": "4884650818", + "296": "1137208419", + "297": "2252639041804718029", + "298": "1.76882294", + "299": "549936643", + "300": "8.0540771484375", + "301": "2178309", + "302": "1170060", + "303": "1111981904675169", + "304": "283988410192", + "305": "18174995535140", + "306": "852938", + "307": "0.7311720251", + "308": "1539669807660924", + "309": "210139", + "310": "2586528661783", + "311": "2466018557", + "312": "324681947", + "313": "2057774861813004", + "314": "132.52756426", + "315": "13625242", + "316": "542934735751917735", + "317": "1856532.8455", + "318": "709313889", + "319": "268457129", + "320": "278157919195482643", + "321": "2470433131948040", + "322": "999998760323313995", + "323": "6.3551758451", + "324": "96972774", + "325": "54672965", + "326": "1966666166408794329", + "327": "34315549139516", + "328": "260511850222", + "329": "199740353/29386561536000", + "330": "15955822", + "331": "467178235146843549", + "332": "2717.751525", + "333": "3053105", + "334": "150320021261690835", + "335": "5032316", + "336": "CAGBIHEFJDK", + "337": "85068035", + "338": "15614292", + "339": "19823.542204", + "340": "291504964", + "341": "56098610614277014", + "342": "5943040885644", + "343": "269533451410884183", + "344": "65579304332", + "345": "13938", + "346": "336108797689259276", + "347": "11109800204052", + "348": "1004195061", + "349": "115384615384614952", + "350": "84664213", + "351": "11762187201804552", + "352": "378563.260589", + "353": "1.2759860331", + "354": "58065134", + "355": "1726545007", + "356": "28010159", + "357": "1739023853137", + "358": "3284144505", + "359": "40632119", + "360": "878825614395267072", + "361": "178476944", + "362": "457895958010", + "363": "0.0000372091", + "364": "44855254", + "365": "162619462356610313", + "366": "88351299", + "367": "48271207", + "368": "253.6135092068", + "369": "862400558448", + "370": "41791929448408", + "371": "40.66368097", + "372": "301450082318807027", + "373": "727227472448913", + "374": "334420941", + "375": "7435327983715286168", + "376": "973059630185670", + "377": "732385277", + "378": "147534623725724718", + "379": "132314136838185", + "380": "6.3202e25093", + "381": "139602943319822", + "382": "697003956", + "383": "22173624649806", + "384": "3354706415856332783", + "385": "3776957309612153700", + "386": "528755790", + "387": "696067597313468", + "388": "831907372805129931", + "389": "2406376.3623", + "390": "2919133642971", + "391": "61029882288", + "392": "3.1486734435", + "393": "112398351350823112", + "394": "3.2370342194", + "395": "28.2453753155", + "396": "173214653", + "397": "141630459461893728", + "398": "2010.59096", + "399": "1508395636674243,6.5e27330467", + "400": "438505383468410633", + "401": "281632621", + "402": "356019862", + "403": "18224771", + "404": "1199215615081353", + "405": "237696125", + "406": "36813.12757207", + "407": "39782849136421", + "408": "299742733", + "409": "253223948", + "410": "799999783589946560", + "411": "9936352", + "412": "38788800", + "413": "3079418648040719", + "414": "552506775824935461", + "415": "55859742", + "416": "898082747", + "417": "446572970925740", + "418": "1177163565297340320", + "419": "998567458,1046245404,43363922", + "420": "145159332", + "421": "2304215802083466198", + "422": "92060460", + "423": "653972374", + "424": "1059760019628", + "425": "46479497324", + "426": "31591886008", + "427": "97138867", + "428": "747215561862", + "429": "98792821", + "430": "5000624921.38", + "431": "23.386029052", + "432": "754862080", + "433": "326624372659664", + "434": "863253606", + "435": "252541322550", + "436": "0.5276662759", + "437": "74204709657207", + "438": "2046409616809", + "439": "968697378", + "440": "970746056", + "441": "5000088.8395", + "442": "1295552661530920149", + "443": "2744233049300770", + "444": "1.200856722e263", + "445": "659104042", + "446": "907803852", + "447": "530553372", + "448": "106467648", + "449": "103.37870096", + "450": "583333163984220940", + "451": "153651073760956", + "452": "345558983", + "453": "104354107", + "454": "5435004633092", + "455": "450186511399999", + "456": "333333208685971546", + "457": "2647787126797397063", + "458": "423341841", + "459": "3996390106631", + "460": "18.420738199", + "461": "159820276", + "462": "5.5350769703e1512", + "463": "808981553", + "464": "198775297232878", + "465": "585965659", + "466": "258381958195474745", + "467": "775181359", + "468": "852950321", + "469": "0.56766764161831", + "470": "147668794", + "471": "1.895093981e31", + "472": "73811586", + "473": "35856681704365", + "474": "9690646731515010", + "475": "75780067", + "476": "110242.87794", + "477": "25044905874565165", + "478": "59510340", + "479": "191541795", + "480": "turnthestarson", + "481": "729.12106947", + "482": "1400824879147", + "483": "4.993401567e22", + "484": "8907904768686152599", + "485": "51281274340", + "486": "11408450515", + "487": "106650212746", + "488": "216737278", + "489": "1791954757162", + "490": "777577686", + "491": "194505988824000", + "492": "242586962923928", + "493": "6.818741802", + "494": "2880067194446832666", + "495": "789107601", + "496": "2042473533769142717", + "497": "684901360", + "498": "472294837", + "499": "0.8660312", + "500": "35407281", + "501": "197912312715", + "502": "749485217", + "503": "3.8694550145", + "504": "694687", + "505": "714591308667615832", + "506": "18934502", + "507": "316558047002627270", + "508": "891874596", + "509": "151725678", + "510": "315306518862563689", + "511": "935247012", + "512": "50660591862310323", + "513": "2925619196", + "514": "8986.86698", + "515": "2422639000800", + "516": "939087315", + "517": "581468882", + "518": "100315739184392", + "519": "804739330", + "520": "238413705", + "521": "44389811", + "522": "96772715", + "523": "37125450.44", + "524": "2432925835413407847", + "525": "44.69921807", + "526": "49601160286750947", + "527": "11.92412011", + "528": "779027989", + "529": "23624465", + "530": "207366437157977206", + "531": "4515432351156203105", + "532": "827306.56", + "533": "789453601", + "534": "11726115562784664", + "535": "611778217", + "536": "3557005261906288", + "537": "779429131", + "538": "22472871503401097", + "539": "426334056", + "540": "500000000002845", + "541": "4580726482872451", + "542": "697586734240314852", + "543": "199007746081234640", + "544": "640432376", + "545": "921107572", + "546": "215656873", + "547": "11730879.0023", + "548": "12144044603581281", + "549": "476001479068717", + "550": "328104836", + "551": "73597483551591773", + "552": "326227335", + "553": "57717170", + "554": "89539872", + "555": "208517717451208352", + "556": "52126939292957", + "557": "2699929328", + "558": "226754889", + "559": "684724920", + "560": "994345168", + "561": "452480999988235494", + "562": "51208732914368", + "563": "27186308211734760", + "564": "12363.698850", + "565": "2992480851924313898", + "566": "329569369413585", + "567": "75.44817535", + "568": "4228020", + "569": "21025060", + "570": "271197444", + "571": "30510390701978", + "572": "19737656", + "573": "1252.9809", + "574": "5780447552057000454", + "575": "0.000989640561", + "576": "344457.5871", + "577": "265695031399260211", + "578": "9219696799346", + "579": "3805524", + "580": "2327213148095366", + "581": "2227616372734", + "582": "19903", + "583": "1174137929000", + "584": "32.83822408", + "585": "17714439395932", + "586": "82490213", + "587": "2240", + "588": "11651930052", + "589": "131776959.25", + "590": "834171904", + "591": "526007984625966", + "592": "13415DF2BE9C", + "593": "96632320042.0", + "594": "47067598", + "595": "54.17529329", + "596": "734582049", + "597": "0.5001817828", + "598": "543194779059", + "599": "12395526079546335", + "600": "2668608479740672", + "601": "1617243", + "602": "269496760", + "603": "879476477", + "604": "1398582231101", + "605": "59992576", + "606": "158452775", + "607": "13.1265108586", + "608": "439689828", + "609": "172023848", + "610": "319.30207833", + "611": "49283233900", + "612": "819963842", + "613": "0.3916721504", + "614": "130694090", + "615": "108424772", + "616": "310884668312456458", + "617": "1001133757", + "618": "634212216", + "619": "857810883", + "620": "1470337306", + "621": "11429712", + "622": "3010983666182123972", + "623": "3679796", + "624": "984524441", + "625": "551614306", + "626": "695577663", + "627": "220196142", + "628": "210286684", + "629": "626616617", + "630": "9669182880384", + "631": "869588692", + "632": "728378714", + "633": "1.0012e-10", + "634": "4019680944", + "635": "689294705", + "636": "888316", + "637": "49000634845039", + "638": "18423394", + "639": "797866893", + "640": "50.317928", + "641": "793525366", + "642": "631499044", + "643": "968274154", + "644": "20.11208767", + "645": "48894.2174", + "646": "845218467", + "647": "563132994232918611", + "648": "301483197", + "649": "924668016", + "650": "538319652", + "651": "448233151", + "652": "983924497", + "653": "1130658687", + "654": "815868280", + "655": "2000008332", + "656": "888873503555187", + "657": "219493139", + "658": "958280177", + "659": "238518915714422000", + "660": "474766783", + "661": "646231.2177", + "662": "860873428", + "663": "1884138010064752", + "664": "35295862", + "665": "11541685709674", + "666": "0.48023168", + "667": "1.5276527928", + "668": "2811077773", + "669": "56342087360542122", + "670": "551055065", + "671": "946106780", + "672": "91627537", + "673": "700325380", + "674": "416678753", + "675": "416146418", + "676": "3562668074339584", + "677": "984183023", + "678": "1986065", + "679": "644997092988678", + "680": "563917241", + "681": "2611227421428", + "682": "290872710", + "683": "2.38955315e11", + "684": "922058210", + "685": "662878999", + "686": "193060223", + "687": "0.3285320869", + "688": "110941813", + "689": "0.56565454", + "690": "415157690", + "691": "11570761", + "692": "842043391019219959", + "693": "699161", + "694": "1339784153569958487", + "695": "0.1017786859", + "696": "436944244", + "697": "4343871.06", + "698": "57808202", + "699": "37010438774467572", + "700": "1517926517777556", + "701": "13.51099836", + "702": "622305608172525546", + "703": "843437991", + "704": "501985601490518144", + "705": "480440153", + "706": "884837055", + "707": "652907799", + "708": "28874142998632109", + "709": "773479144", + "710": "1275000", + "711": "541510990", + "712": "413876461", + "713": "788626351539895", + "714": "2.452767775565e20", + "715": "883188017", + "716": "238948623", + "717": "1603036763131", + "718": "228579116", + "719": "128088830547982", + "720": "688081048", + "721": "700792959", + "722": "3.376792776502e132", + "723": "1395793419248", + "724": "18128250110", + "725": "4598797036650685" +} \ No newline at end of file diff --git a/project_euler/validate_solutions.py b/project_euler/validate_solutions.py index f3ae9fbeffab..b340fe945d76 100755 --- a/project_euler/validate_solutions.py +++ b/project_euler/validate_solutions.py @@ -3,7 +3,7 @@ import json import pathlib from types import ModuleType -from typing import Generator +from typing import Dict, List import pytest @@ -13,42 +13,44 @@ ) with open(PROJECT_EULER_ANSWERS_PATH) as file_handle: - PROBLEM_ANSWERS = json.load(file_handle) + PROBLEM_ANSWERS: Dict[str, str] = json.load(file_handle) -def generate_solution_modules( - dir_path: pathlib.Path, -) -> Generator[ModuleType, None, None]: - # Iterating over every file or directory - for file_path in dir_path.iterdir(): - if file_path.suffix != ".py" or file_path.name.startswith(("_", "test")): +def convert_path_to_module(file_path: pathlib.Path) -> ModuleType: + """Converts a file path to a Python module""" + spec = importlib.util.spec_from_file_location(file_path.name, str(file_path)) + module = importlib.util.module_from_spec(spec) + spec.loader.exec_module(module) + return module + + +def collect_solution_file_paths() -> List[pathlib.Path]: + """Collects all the solution file path in the Project Euler directory""" + solution_file_paths = [] + for problem_dir_path in PROJECT_EULER_DIR_PATH.iterdir(): + if problem_dir_path.is_file() or problem_dir_path.name.startswith("_"): continue - # Importing the source file through the given path - # https://docs.python.org/3/library/importlib.html#importing-a-source-file-directly - spec = importlib.util.spec_from_file_location(file_path.name, str(file_path)) - module = importlib.util.module_from_spec(spec) - spec.loader.exec_module(module) - yield module - - -@pytest.mark.parametrize("problem_number, expected", PROBLEM_ANSWERS) -def test_project_euler(subtests, problem_number: int, expected: str): - problem_dir = PROJECT_EULER_DIR_PATH.joinpath(f"problem_{problem_number:02}") - # Check if the problem directory exist. If not, then skip. - if problem_dir.is_dir(): - for solution_module in generate_solution_modules(problem_dir): - # All the tests in a loop is considered as one test by pytest so, use - # subtests to make sure all the subtests are considered as different. - with subtests.test( - msg=f"Problem {problem_number} tests", solution_module=solution_module - ): - try: - answer = str(solution_module.solution()) - assert answer == expected, f"Expected {expected} but got {answer}" - except (AssertionError, AttributeError, TypeError) as err: - print( - f"problem_{problem_number:02}/{solution_module.__name__}: {err}" - ) - raise - else: - pytest.skip(f"Solution {problem_number} does not exist yet.") + for file_path in problem_dir_path.iterdir(): + if file_path.suffix != ".py" or file_path.name.startswith(("_", "test")): + continue + solution_file_paths.append(file_path) + return solution_file_paths + + +def expand_parameters(param: pathlib.Path) -> str: + """Expand parameters in pytest parametrize""" + project_dirname = param.parent.name + solution_filename = param.name + return f"{project_dirname}/{solution_filename}" + + +@pytest.mark.parametrize( + "solution_path", collect_solution_file_paths(), ids=expand_parameters +) +def test_project_euler(solution_path: pathlib.Path): + """Testing for all Project Euler solutions""" + problem_number: str = solution_path.parent.name[8:] # problem_[extract his part] + expected: str = PROBLEM_ANSWERS[problem_number] + solution_module = convert_path_to_module(solution_path) + answer = str(solution_module.solution()) + assert answer == expected, f"Expected {expected} but got {answer}" diff --git a/quantum/single_qubit_measure.py b/quantum/single_qubit_measure.py index 99d807b034e4..7f058c2179a9 100755 --- a/quantum/single_qubit_measure.py +++ b/quantum/single_qubit_measure.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 """ -Build a simple bare-minimum quantum circuit that starts with a single -qubit (by default, in state 0), runs the experiment 1000 times, and +Build a simple bare-minimum quantum circuit that starts with a single +qubit (by default, in state 0), runs the experiment 1000 times, and finally prints the total count of the states finally observed. Qiskit Docs: https://qiskit.org/documentation/getting_started.html """ From ca2d269ed2efa5abb70b349a1ef1a0e925718b76 Mon Sep 17 00:00:00 2001 From: Abhishek Jaisingh Date: Tue, 13 Oct 2020 22:04:24 +0530 Subject: [PATCH 0897/1071] Add Qiskit Quantum NOT Gate Example Code (#3255) * Add Qiskit Quantum NOT Gate Example Code * Address Review Comments Signed-off-by: Abhishek Jaisingh --- quantum/not_gate.py | 39 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 quantum/not_gate.py diff --git a/quantum/not_gate.py b/quantum/not_gate.py new file mode 100644 index 000000000000..4f9fa1319ac9 --- /dev/null +++ b/quantum/not_gate.py @@ -0,0 +1,39 @@ +#!/usr/bin/env python3 +""" +Build a simple bare-minimum quantum circuit that starts with a single +qubit (by default, in state 0) and inverts it. Run the experiment 1000 +times and print the total count of the states finally observed. +Qiskit Docs: https://qiskit.org/documentation/getting_started.html +""" + +import qiskit as q + + +def single_qubit_measure(qubits: int, classical_bits: int) -> q.result.counts.Counts: + """ + >>> single_qubit_measure(1, 1) + {'11': 1000} + """ + # Use Aer's qasm_simulator + simulator = q.Aer.get_backend('qasm_simulator') + + # Create a Quantum Circuit acting on the q register + circuit = q.QuantumCircuit(qubits, classical_bits) + + # Apply X (NOT) Gate to Qubits 0 & 1 + circuit.x(0) + circuit.x(1) + + # Map the quantum measurement to the classical bits + circuit.measure([0, 1], [0, 1]) + + # Execute the circuit on the qasm simulator + job = q.execute(circuit, simulator, shots=1000) + + # Return the histogram data of the results of the experiment. + return job.result().get_counts(circuit) + + +if __name__ == '__main__': + counts = single_qubit_measure(2, 2) + print(f'Total count for various states are: {counts}') From 34d63d51553e624ed3f4a6560c0e6b241ba2587c Mon Sep 17 00:00:00 2001 From: Dhruv Date: Tue, 13 Oct 2020 22:43:40 +0530 Subject: [PATCH 0898/1071] Fix Travis CI slow build on Project Euler Solution job (#3262) * Fix Travis CI long run for validate_solutions * updating DIRECTORY.md Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> --- .travis.yml | 2 ++ DIRECTORY.md | 2 ++ 2 files changed, 4 insertions(+) diff --git a/.travis.yml b/.travis.yml index e43ff5c2f030..f31dae8467d6 100644 --- a/.travis.yml +++ b/.travis.yml @@ -16,6 +16,8 @@ jobs: script: - pytest --doctest-modules --durations=10 --cov-report=term-missing:skip-covered --cov=project_euler/ project_euler/ - name: Project Euler Solution + install: + - pip install pytest script: - pytest --tb=short --durations=10 project_euler/validate_solutions.py after_success: diff --git a/DIRECTORY.md b/DIRECTORY.md index eee4ae55ca10..8bc4c2af09d6 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -648,6 +648,8 @@ * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_48/sol1.py) * Problem 49 * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_49/sol1.py) + * Problem 51 + * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_51/sol1.py) * Problem 52 * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_52/sol1.py) * Problem 53 From 23ab159f30ece2e9171efae57119ada93d954645 Mon Sep 17 00:00:00 2001 From: Dhruv Date: Wed, 14 Oct 2020 11:28:52 +0530 Subject: [PATCH 0899/1071] Fix errors in Quantum algorithm (#3273) * Fix pre-commit errors in Quantum algorithm * updating DIRECTORY.md * Fix doctest * Update not_gate.py Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> --- DIRECTORY.md | 1 + quantum/not_gate.py | 14 ++++++++------ 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/DIRECTORY.md b/DIRECTORY.md index 8bc4c2af09d6..9a2ced122540 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -684,6 +684,7 @@ * [Validate Solutions](https://github.com/TheAlgorithms/Python/blob/master/project_euler/validate_solutions.py) ## Quantum + * [Not Gate](https://github.com/TheAlgorithms/Python/blob/master/quantum/not_gate.py) * [Single Qubit Measure](https://github.com/TheAlgorithms/Python/blob/master/quantum/single_qubit_measure.py) ## Scheduling diff --git a/quantum/not_gate.py b/quantum/not_gate.py index 4f9fa1319ac9..e68a780091c7 100644 --- a/quantum/not_gate.py +++ b/quantum/not_gate.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 """ -Build a simple bare-minimum quantum circuit that starts with a single -qubit (by default, in state 0) and inverts it. Run the experiment 1000 +Build a simple bare-minimum quantum circuit that starts with a single +qubit (by default, in state 0) and inverts it. Run the experiment 1000 times and print the total count of the states finally observed. Qiskit Docs: https://qiskit.org/documentation/getting_started.html """ @@ -11,11 +11,13 @@ def single_qubit_measure(qubits: int, classical_bits: int) -> q.result.counts.Counts: """ - >>> single_qubit_measure(1, 1) + >>> single_qubit_measure(2, 2) {'11': 1000} + >>> single_qubit_measure(4, 4) + {'0011': 1000} """ # Use Aer's qasm_simulator - simulator = q.Aer.get_backend('qasm_simulator') + simulator = q.Aer.get_backend("qasm_simulator") # Create a Quantum Circuit acting on the q register circuit = q.QuantumCircuit(qubits, classical_bits) @@ -34,6 +36,6 @@ def single_qubit_measure(qubits: int, classical_bits: int) -> q.result.counts.Co return job.result().get_counts(circuit) -if __name__ == '__main__': +if __name__ == "__main__": counts = single_qubit_measure(2, 2) - print(f'Total count for various states are: {counts}') + print(f"Total count for various states are: {counts}") From f164e11db41b14a9373a369456becf468b9b7240 Mon Sep 17 00:00:00 2001 From: mateuszz0000 Date: Wed, 14 Oct 2020 11:05:17 +0200 Subject: [PATCH 0900/1071] Update CODEOWNERS (#3280) * Update CODEOWNERS * Reduce @cclauss notifications during Hacktoberfest Co-authored-by: Christian Clauss --- .github/CODEOWNERS | 20 ++++++++------------ 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 029f94f7fb3a..260b9704eda7 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -7,11 +7,7 @@ # Order is important. The last matching pattern has the most precedence. -/.travis.yml @cclauss @dhruvmanila - -/.pre-commit-config.yaml @cclauss @dhruvmanila - -/.github/ @cclauss +/.* @cclauss @dhruvmanila # /arithmetic_analysis/ @@ -25,17 +21,17 @@ # /cellular_automata/ -/ciphers/ @cclauss +# /ciphers/ @cclauss # TODO: Uncomment this line after Hacktoberfest # /compression/ # /computer_vision/ -/conversions/ @cclauss +# /conversions/ @cclauss # TODO: Uncomment this line after Hacktoberfest -/data_structures/ @cclauss +# /data_structures/ @cclauss # TODO: Uncomment this line after Hacktoberfest -# /digital_image_processing/ +/digital_image_processing/ @mateuszz0000 # /divide_and_conquer/ @@ -71,7 +67,7 @@ # /neural_network/ -/other/ @cclauss +# /other/ @cclauss # TODO: Uncomment this line after Hacktoberfest /project_euler/ @dhruvmanila @Kush1101 @@ -83,9 +79,9 @@ # /searches/ -# /sorts/ +/sorts/ @mateuszz0000 -/strings/ @cclauss +# /strings/ @cclauss # TODO: Uncomment this line after Hacktoberfest # /traversals/ From 35eefac35917ad3b2b90a190929bf131ea13427b Mon Sep 17 00:00:00 2001 From: NAVEEN S R <56086391+nkpro2000sr@users.noreply.github.com> Date: Wed, 14 Oct 2020 15:23:37 +0530 Subject: [PATCH 0901/1071] fixed error (#3281) this will fix code from randomly throwing `SystemExit: The affine cipher becomes weak when key B is set to 0. Choose different key` exception. --- ciphers/affine_cipher.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ciphers/affine_cipher.py b/ciphers/affine_cipher.py index 70e695de5013..1b1943a3798d 100644 --- a/ciphers/affine_cipher.py +++ b/ciphers/affine_cipher.py @@ -94,7 +94,7 @@ def get_random_key(): while True: keyA = random.randint(2, len(SYMBOLS)) keyB = random.randint(2, len(SYMBOLS)) - if cryptomath.gcd(keyA, len(SYMBOLS)) == 1: + if cryptomath.gcd(keyA, len(SYMBOLS)) == 1 and keyB % len(SYMBOLS) != 0: return keyA * len(SYMBOLS) + keyB From f0aa63f0f947180955ab3272dbd1e4ebf646032c Mon Sep 17 00:00:00 2001 From: Phil Bazun Date: Wed, 14 Oct 2020 12:17:02 +0200 Subject: [PATCH 0902/1071] Add randomized heap. (#3241) --- data_structures/heap/randomized_heap.py | 188 ++++++++++++++++++++++++ 1 file changed, 188 insertions(+) create mode 100644 data_structures/heap/randomized_heap.py diff --git a/data_structures/heap/randomized_heap.py b/data_structures/heap/randomized_heap.py new file mode 100644 index 000000000000..0ddc2272efe8 --- /dev/null +++ b/data_structures/heap/randomized_heap.py @@ -0,0 +1,188 @@ +#!/usr/bin/env python3 + +from __future__ import annotations + +import random +from typing import Generic, Iterable, List, Optional, TypeVar + +T = TypeVar("T") + + +class RandomizedHeapNode(Generic[T]): + """ + One node of the randomized heap. Contains the value and references to + two children. + """ + + def __init__(self, value: T) -> None: + self._value: T = value + self.left: Optional[RandomizedHeapNode[T]] = None + self.right: Optional[RandomizedHeapNode[T]] = None + + @property + def value(self) -> T: + """Return the value of the node.""" + return self._value + + @staticmethod + def merge( + root1: Optional[RandomizedHeapNode[T]], root2: Optional[RandomizedHeapNode[T]] + ) -> Optional[RandomizedHeapNode[T]]: + """Merge 2 nodes together.""" + if not root1: + return root2 + + if not root2: + return root1 + + if root1.value > root2.value: + root1, root2 = root2, root1 + + if random.choice([True, False]): + root1.left, root1.right = root1.right, root1.left + + root1.left = RandomizedHeapNode.merge(root1.left, root2) + + return root1 + + +class RandomizedHeap(Generic[T]): + """ + A data structure that allows inserting a new value and to pop the smallest + values. Both operations take O(logN) time where N is the size of the + structure. + Wiki: https://en.wikipedia.org/wiki/Randomized_meldable_heap + + >>> RandomizedHeap([2, 3, 1, 5, 1, 7]).to_sorted_list() + [1, 1, 2, 3, 5, 7] + + >>> rh = RandomizedHeap() + >>> rh.pop() + Traceback (most recent call last): + ... + IndexError: Can't get top element for the empty heap. + + >>> rh.insert(1) + >>> rh.insert(-1) + >>> rh.insert(0) + >>> rh.to_sorted_list() + [-1, 0, 1] + """ + + def __init__(self, data: Optional[Iterable[T]] = ()) -> None: + """ + >>> rh = RandomizedHeap([3, 1, 3, 7]) + >>> rh.to_sorted_list() + [1, 3, 3, 7] + """ + self._root: Optional[RandomizedHeapNode[T]] = None + for item in data: + self.insert(item) + + def insert(self, value: T) -> None: + """ + Insert the value into the heap. + + >>> rh = RandomizedHeap() + >>> rh.insert(3) + >>> rh.insert(1) + >>> rh.insert(3) + >>> rh.insert(7) + >>> rh.to_sorted_list() + [1, 3, 3, 7] + """ + self._root = RandomizedHeapNode.merge(self._root, RandomizedHeapNode(value)) + + def pop(self) -> T: + """ + Pop the smallest value from the heap and return it. + + >>> rh = RandomizedHeap([3, 1, 3, 7]) + >>> rh.pop() + 1 + >>> rh.pop() + 3 + >>> rh.pop() + 3 + >>> rh.pop() + 7 + >>> rh.pop() + Traceback (most recent call last): + ... + IndexError: Can't get top element for the empty heap. + """ + result = self.top() + self._root = RandomizedHeapNode.merge(self._root.left, self._root.right) + + return result + + def top(self) -> T: + """ + Return the smallest value from the heap. + + >>> rh = RandomizedHeap() + >>> rh.insert(3) + >>> rh.top() + 3 + >>> rh.insert(1) + >>> rh.top() + 1 + >>> rh.insert(3) + >>> rh.top() + 1 + >>> rh.insert(7) + >>> rh.top() + 1 + """ + if not self._root: + raise IndexError("Can't get top element for the empty heap.") + return self._root.value + + def clear(self): + """ + Clear the heap. + + >>> rh = RandomizedHeap([3, 1, 3, 7]) + >>> rh.clear() + >>> rh.pop() + Traceback (most recent call last): + ... + IndexError: Can't get top element for the empty heap. + """ + self._root = None + + def to_sorted_list(self) -> List[T]: + """ + Returns sorted list containing all the values in the heap. + + >>> rh = RandomizedHeap([3, 1, 3, 7]) + >>> rh.to_sorted_list() + [1, 3, 3, 7] + """ + result = [] + while self: + result.append(self.pop()) + + return result + + def __bool__(self) -> bool: + """ + Check if the heap is not empty. + + >>> rh = RandomizedHeap() + >>> bool(rh) + False + >>> rh.insert(1) + >>> bool(rh) + True + >>> rh.clear() + >>> bool(rh) + False + """ + return self._root is not None + + +if __name__ == "__main__": + import doctest + + doctest.testmod() From dc069580b9e4c040251adbb8e090e24d3f0156aa Mon Sep 17 00:00:00 2001 From: Susmith98 <33018940+susmith98@users.noreply.github.com> Date: Wed, 14 Oct 2020 15:51:15 +0530 Subject: [PATCH 0903/1071] Added binary tree mirror algorithm (#3159) * Added binary tree mirror algorithm * Minor changes * Resolved comments * Minor Changes * resolved comments and updated doctests * updated doctests * updating DIRECTORY.md Co-authored-by: svedire Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> --- DIRECTORY.md | 1 + .../binary_tree/binary_tree_mirror.py | 44 +++++++++++++++++++ 2 files changed, 45 insertions(+) create mode 100644 data_structures/binary_tree/binary_tree_mirror.py diff --git a/DIRECTORY.md b/DIRECTORY.md index 9a2ced122540..e293ea935454 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -105,6 +105,7 @@ * [Basic Binary Tree](https://github.com/TheAlgorithms/Python/blob/master/data_structures/binary_tree/basic_binary_tree.py) * [Binary Search Tree](https://github.com/TheAlgorithms/Python/blob/master/data_structures/binary_tree/binary_search_tree.py) * [Binary Search Tree Recursive](https://github.com/TheAlgorithms/Python/blob/master/data_structures/binary_tree/binary_search_tree_recursive.py) + * [Binary Tree Mirror](https://github.com/TheAlgorithms/Python/blob/master/data_structures/binary_tree/binary_tree_mirror.py) * [Fenwick Tree](https://github.com/TheAlgorithms/Python/blob/master/data_structures/binary_tree/fenwick_tree.py) * [Lazy Segment Tree](https://github.com/TheAlgorithms/Python/blob/master/data_structures/binary_tree/lazy_segment_tree.py) * [Lowest Common Ancestor](https://github.com/TheAlgorithms/Python/blob/master/data_structures/binary_tree/lowest_common_ancestor.py) diff --git a/data_structures/binary_tree/binary_tree_mirror.py b/data_structures/binary_tree/binary_tree_mirror.py new file mode 100644 index 000000000000..dc7f657b37c7 --- /dev/null +++ b/data_structures/binary_tree/binary_tree_mirror.py @@ -0,0 +1,44 @@ +""" +Problem Description: +Given a binary tree, return it's mirror. +""" + + +def binary_tree_mirror_dict(binary_tree_mirror_dictionary: dict, root: int): + if not root or root not in binary_tree_mirror_dictionary: + return + left_child, right_child = binary_tree_mirror_dictionary[root][:2] + binary_tree_mirror_dictionary[root] = [right_child, left_child] + binary_tree_mirror_dict(binary_tree_mirror_dictionary, left_child) + binary_tree_mirror_dict(binary_tree_mirror_dictionary, right_child) + + +def binary_tree_mirror(binary_tree: dict, root: int = 1) -> dict: + """ + >>> binary_tree_mirror({ 1: [2,3], 2: [4,5], 3: [6,7], 7: [8,9]}, 1) + {1: [3, 2], 2: [5, 4], 3: [7, 6], 7: [9, 8]} + >>> binary_tree_mirror({ 1: [2,3], 2: [4,5], 3: [6,7], 4: [10,11]}, 1) + {1: [3, 2], 2: [5, 4], 3: [7, 6], 4: [11, 10]} + >>> binary_tree_mirror({ 1: [2,3], 2: [4,5], 3: [6,7], 4: [10,11]}, 5) + Traceback (most recent call last): + ... + ValueError: root 5 is not present in the binary_tree + >>> binary_tree_mirror({}, 5) + Traceback (most recent call last): + ... + ValueError: binary tree cannot be empty + """ + if not binary_tree: + raise ValueError("binary tree cannot be empty") + if root not in binary_tree: + raise ValueError(f"root {root} is not present in the binary_tree") + binary_tree_mirror_dictionary = dict(binary_tree) + binary_tree_mirror_dict(binary_tree_mirror_dictionary, root) + return binary_tree_mirror_dictionary + + +if __name__ == "__main__": + binary_tree = {1: [2, 3], 2: [4, 5], 3: [6, 7], 7: [8, 9]} + print(f"Binary tree: {binary_tree}") + binary_tree_mirror_dictionary = binary_tree_mirror(binary_tree, 5) + print(f"Binary tree mirror: {binary_tree_mirror_dictionary}") From 75a9b460ad2de8e0f76861051f8b05973deb0722 Mon Sep 17 00:00:00 2001 From: NAVEEN S R <56086391+nkpro2000sr@users.noreply.github.com> Date: Wed, 14 Oct 2020 15:55:20 +0530 Subject: [PATCH 0904/1071] added script to perform quantum entanglement (#3270) * added code to perform quantum entanglement * Update quantum_entanglement.py --- quantum/quantum_entanglement.py | 58 +++++++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) create mode 100644 quantum/quantum_entanglement.py diff --git a/quantum/quantum_entanglement.py b/quantum/quantum_entanglement.py new file mode 100644 index 000000000000..3d8e2771361c --- /dev/null +++ b/quantum/quantum_entanglement.py @@ -0,0 +1,58 @@ +#!/usr/bin/env python3 +""" +Build a quantum circuit with pair or group of qubits to perform +quantum entanglement. +Quantum entanglement is a phenomenon observed at the quantum scale +where entangled particles stay connected (in some sense) so that +the actions performed on one of the particles affects the other, +no matter the distance between two particles. +""" + +import qiskit + + +def quantum_entanglement(qubits: int = 2) -> qiskit.result.counts.Counts: + """ + # >>> quantum_entanglement(2) + # {'00': 500, '11': 500} + # ┌───┐ ┌─┐ + # q_0: ┤ H ├──■──┤M├─── + # └───┘┌─┴─┐└╥┘┌─┐ + # q_1: ─────┤ X ├─╫─┤M├ + # └───┘ ║ └╥┘ + # c: 2/═══════════╩══╩═ + # 0 1 + Args: + qubits (int): number of quibits to use. Defaults to 2 + Returns: + qiskit.result.counts.Counts: mapping of states to its counts + """ + classical_bits = qubits + + # Using Aer's qasm_simulator + simulator = qiskit.Aer.get_backend("qasm_simulator") + + # Creating a Quantum Circuit acting on the q register + circuit = qiskit.QuantumCircuit(qubits, classical_bits) + + # Adding a H gate on qubit 0 (now q0 in superposition) + circuit.h(0) + + for i in range(1, qubits): + # Adding CX (CNOT) gate + circuit.cx(i - 1, i) + + # Mapping the quantum measurement to the classical bits + circuit.measure(list(range(qubits)), list(range(classical_bits))) + + # Now measuring any one qubit would affect other qubits to collapse + # their super position and have same state as the measured one. + + # Executing the circuit on the qasm simulator + job = qiskit.execute(circuit, simulator, shots=1000) + + return job.result().get_counts(circuit) + + +if __name__ == "__main__": + print(f"Total count for various states are: {quantum_entanglement(3)}") From 5fcd250c99ec1b490c456dbc4be6b9ad63f78817 Mon Sep 17 00:00:00 2001 From: Mikail Farid Date: Wed, 14 Oct 2020 11:27:08 +0100 Subject: [PATCH 0905/1071] Added decimal_to_binary_recursion.py (#3266) * Added decimal_to_binary_recursion.py * Added decimal_to_binary_recursion.py * Made changes to docstring * Use divmod() * binary_recursive(div) + str(mod) * Be kind with user input if possible * Update decimal_to_binary_recursion.py * ValueError: invalid literal for int() with base 10: 'number' Co-authored-by: Christian Clauss --- conversions/decimal_to_binary_recursion.py | 53 ++++++++++++++++++++++ 1 file changed, 53 insertions(+) create mode 100644 conversions/decimal_to_binary_recursion.py diff --git a/conversions/decimal_to_binary_recursion.py b/conversions/decimal_to_binary_recursion.py new file mode 100644 index 000000000000..c149ea86592f --- /dev/null +++ b/conversions/decimal_to_binary_recursion.py @@ -0,0 +1,53 @@ +def binary_recursive(decimal: int) -> str: + """ + Take a positive integer value and return its binary equivalent. + >>> binary_recursive(1000) + '1111101000' + >>> binary_recursive("72") + '1001000' + >>> binary_recursive("number") + Traceback (most recent call last): + ... + ValueError: invalid literal for int() with base 10: 'number' + """ + decimal = int(decimal) + if decimal in (0, 1): # Exit cases for the recursion + return str(decimal) + div, mod = divmod(decimal, 2) + return binary_recursive(div) + str(mod) + + +def main(number: str) -> str: + """ + Take an integer value and raise ValueError for wrong inputs, + call the function above and return the output with prefix "0b" & "-0b" + for positive and negative integers respectively. + >>> main(0) + '0b0' + >>> main(40) + '0b101000' + >>> main(-40) + '-0b101000' + >>> main(40.8) + Traceback (most recent call last): + ... + ValueError: Input value is not an integer + >>> main("forty") + Traceback (most recent call last): + ... + ValueError: Input value is not an integer + """ + number = str(number).strip() + if not number: + raise ValueError("No input value was provided") + negative = "-" if number.startswith("-") else "" + number = number.lstrip("-") + if not number.isnumeric(): + raise ValueError("Input value is not an integer") + return f"{negative}0b{binary_recursive(int(number))}" + + +if __name__ == "__main__": + from doctest import testmod + + testmod() From e3c07f987f099531734c29a55f18ff0e1bb4fbbe Mon Sep 17 00:00:00 2001 From: Phil Bazun Date: Wed, 14 Oct 2020 12:35:53 +0200 Subject: [PATCH 0906/1071] Add skew heap data structure. (#3238) * Add skew heap data structure. * fixup! Add skew heap data structure. * fixup! Add skew heap data structure. * fixup! Add skew heap data structure. * Add tests. * Add __iter__ method. * fixup! Add __iter__ method. --- data_structures/heap/skew_heap.py | 192 ++++++++++++++++++++++++++++++ 1 file changed, 192 insertions(+) create mode 100644 data_structures/heap/skew_heap.py diff --git a/data_structures/heap/skew_heap.py b/data_structures/heap/skew_heap.py new file mode 100644 index 000000000000..417a383f733e --- /dev/null +++ b/data_structures/heap/skew_heap.py @@ -0,0 +1,192 @@ +#!/usr/bin/env python3 + +from __future__ import annotations + +from typing import Generic, Iterable, Iterator, Optional, TypeVar + +T = TypeVar("T") + + +class SkewNode(Generic[T]): + """ + One node of the skew heap. Contains the value and references to + two children. + """ + + def __init__(self, value: T) -> None: + self._value: T = value + self.left: Optional[SkewNode[T]] = None + self.right: Optional[SkewNode[T]] = None + + @property + def value(self) -> T: + """Return the value of the node.""" + return self._value + + @staticmethod + def merge( + root1: Optional[SkewNode[T]], root2: Optional[SkewNode[T]] + ) -> Optional[SkewNode[T]]: + """Merge 2 nodes together.""" + if not root1: + return root2 + + if not root2: + return root1 + + if root1.value > root2.value: + root1, root2 = root2, root1 + + result = root1 + temp = root1.right + result.right = root1.left + result.left = SkewNode.merge(temp, root2) + + return result + + +class SkewHeap(Generic[T]): + """ + A data structure that allows inserting a new value and to pop the smallest + values. Both operations take O(logN) time where N is the size of the + structure. + Wiki: https://en.wikipedia.org/wiki/Skew_heap + Visualisation: https://www.cs.usfca.edu/~galles/visualization/SkewHeap.html + + >>> list(SkewHeap([2, 3, 1, 5, 1, 7])) + [1, 1, 2, 3, 5, 7] + + >>> sh = SkewHeap() + >>> sh.pop() + Traceback (most recent call last): + ... + IndexError: Can't get top element for the empty heap. + + >>> sh.insert(1) + >>> sh.insert(-1) + >>> sh.insert(0) + >>> list(sh) + [-1, 0, 1] + """ + + def __init__(self, data: Optional[Iterable[T]] = ()) -> None: + """ + >>> sh = SkewHeap([3, 1, 3, 7]) + >>> list(sh) + [1, 3, 3, 7] + """ + self._root: Optional[SkewNode[T]] = None + for item in data: + self.insert(item) + + def __bool__(self) -> bool: + """ + Check if the heap is not empty. + + >>> sh = SkewHeap() + >>> bool(sh) + False + >>> sh.insert(1) + >>> bool(sh) + True + >>> sh.clear() + >>> bool(sh) + False + """ + return self._root is not None + + def __iter__(self) -> Iterator[T]: + """ + Returns sorted list containing all the values in the heap. + + >>> sh = SkewHeap([3, 1, 3, 7]) + >>> list(sh) + [1, 3, 3, 7] + """ + result = [] + while self: + result.append(self.pop()) + + # Pushing items back to the heap not to clear it. + for item in result: + self.insert(item) + + return iter(result) + + def insert(self, value: T) -> None: + """ + Insert the value into the heap. + + >>> sh = SkewHeap() + >>> sh.insert(3) + >>> sh.insert(1) + >>> sh.insert(3) + >>> sh.insert(7) + >>> list(sh) + [1, 3, 3, 7] + """ + self._root = SkewNode.merge(self._root, SkewNode(value)) + + def pop(self) -> T: + """ + Pop the smallest value from the heap and return it. + + >>> sh = SkewHeap([3, 1, 3, 7]) + >>> sh.pop() + 1 + >>> sh.pop() + 3 + >>> sh.pop() + 3 + >>> sh.pop() + 7 + >>> sh.pop() + Traceback (most recent call last): + ... + IndexError: Can't get top element for the empty heap. + """ + result = self.top() + self._root = SkewNode.merge(self._root.left, self._root.right) + + return result + + def top(self) -> T: + """ + Return the smallest value from the heap. + + >>> sh = SkewHeap() + >>> sh.insert(3) + >>> sh.top() + 3 + >>> sh.insert(1) + >>> sh.top() + 1 + >>> sh.insert(3) + >>> sh.top() + 1 + >>> sh.insert(7) + >>> sh.top() + 1 + """ + if not self._root: + raise IndexError("Can't get top element for the empty heap.") + return self._root.value + + def clear(self): + """ + Clear the heap. + + >>> sh = SkewHeap([3, 1, 3, 7]) + >>> sh.clear() + >>> sh.pop() + Traceback (most recent call last): + ... + IndexError: Can't get top element for the empty heap. + """ + self._root = None + + +if __name__ == "__main__": + import doctest + + doctest.testmod() From fda57d6924bda596a8ad43d015f266fa772d63d6 Mon Sep 17 00:00:00 2001 From: Christian Clauss Date: Wed, 14 Oct 2020 13:32:47 +0200 Subject: [PATCH 0907/1071] codespell.yml: Remove unused install of flake8 (#3283) * codespell.yml: Remove unused install of flake8 * updating DIRECTORY.md Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> --- .github/workflows/codespell.yml | 2 +- DIRECTORY.md | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/codespell.yml b/.github/workflows/codespell.yml index df419bbfab9e..500b737c70e3 100644 --- a/.github/workflows/codespell.yml +++ b/.github/workflows/codespell.yml @@ -8,7 +8,7 @@ jobs: steps: - uses: actions/checkout@v2 - uses: actions/setup-python@v2 - - run: pip install codespell flake8 + - run: pip install codespell - run: | SKIP="./.*,./other/dictionary.txt,./other/words,./project_euler/problem_22/p022_names.txt" codespell --ignore-words-list=ans,fo,followings,hist,iff,secant,som,tim --skip=$SKIP --quiet-level=2 diff --git a/DIRECTORY.md b/DIRECTORY.md index e293ea935454..8e7835e9ca91 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -92,6 +92,7 @@ * [Binary To Octal](https://github.com/TheAlgorithms/Python/blob/master/conversions/binary_to_octal.py) * [Decimal To Any](https://github.com/TheAlgorithms/Python/blob/master/conversions/decimal_to_any.py) * [Decimal To Binary](https://github.com/TheAlgorithms/Python/blob/master/conversions/decimal_to_binary.py) + * [Decimal To Binary Recursion](https://github.com/TheAlgorithms/Python/blob/master/conversions/decimal_to_binary_recursion.py) * [Decimal To Hexadecimal](https://github.com/TheAlgorithms/Python/blob/master/conversions/decimal_to_hexadecimal.py) * [Decimal To Octal](https://github.com/TheAlgorithms/Python/blob/master/conversions/decimal_to_octal.py) * [Hexadecimal To Decimal](https://github.com/TheAlgorithms/Python/blob/master/conversions/hexadecimal_to_decimal.py) @@ -131,6 +132,7 @@ * [Heap Generic](https://github.com/TheAlgorithms/Python/blob/master/data_structures/heap/heap_generic.py) * [Max Heap](https://github.com/TheAlgorithms/Python/blob/master/data_structures/heap/max_heap.py) * [Min Heap](https://github.com/TheAlgorithms/Python/blob/master/data_structures/heap/min_heap.py) + * [Randomized Heap](https://github.com/TheAlgorithms/Python/blob/master/data_structures/heap/randomized_heap.py) * Linked List * [Circular Linked List](https://github.com/TheAlgorithms/Python/blob/master/data_structures/linked_list/circular_linked_list.py) * [Deque Doubly](https://github.com/TheAlgorithms/Python/blob/master/data_structures/linked_list/deque_doubly.py) @@ -686,6 +688,7 @@ ## Quantum * [Not Gate](https://github.com/TheAlgorithms/Python/blob/master/quantum/not_gate.py) + * [Quantum Entanglement](https://github.com/TheAlgorithms/Python/blob/master/quantum/quantum_entanglement.py) * [Single Qubit Measure](https://github.com/TheAlgorithms/Python/blob/master/quantum/single_qubit_measure.py) ## Scheduling From ed30749943058cfaafeae47bad324200a0e139a0 Mon Sep 17 00:00:00 2001 From: Mayur Pardeshi <45143349+mayur200@users.noreply.github.com> Date: Thu, 15 Oct 2020 03:49:00 +0530 Subject: [PATCH 0908/1071] Added swap case program and removed unexpected expression part (#3212) * Removed an extra '=' which was creating an error while running a program. * Removed the unexpected expression part. * Added program for swap cases in string folder * removed if condition and exchange word with char * added '=' sign which I removed before because of unknowing error from pycharm * added space in test * removed costraint from problem statement * Update cocktail_shaker_sort.py * Update naive_string_search.py * Update swap_case.py * psf/black " not ' * added new line at the end of the file * Fix flake8 issues * added new line at the end of the file * added True and fixed comment * python file end with \n * Update swap_case.py * Update strings/swap_case.py * Update strings/swap_case.py * Apply suggestions from code review * Update strings/swap_case.py * Update swap_case.py * Update swap_case.py Co-authored-by: Christian Clauss --- strings/swap_case.py | 42 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 strings/swap_case.py diff --git a/strings/swap_case.py b/strings/swap_case.py new file mode 100644 index 000000000000..71e8aeb3a205 --- /dev/null +++ b/strings/swap_case.py @@ -0,0 +1,42 @@ +""" +This algorithm helps you to swap cases. + +User will give input and then program will perform swap cases. + +In other words, convert all lowercase letters to uppercase letters and vice versa. +For example: +1. Please input sentence: Algorithm.Python@89 + aLGORITHM.pYTHON@89 +2. Please input sentence: github.com/mayur200 + GITHUB.COM/MAYUR200 + +""" +import re + +# This re.compile() function saves the pattern from 'a' to 'z' and 'A' to 'Z' +# into 'regexp' variable +regexp = re.compile("[^a-zA-Z]+") + + +def swap_case(sentence): + """ + This function will convert all lowercase letters to uppercase letters + and vice versa. + + >>> swap_case('Algorithm.Python@89') + 'aLGORITHM.pYTHON@89' + """ + new_string = "" + for char in sentence: + if char.isupper(): + new_string += char.lower() + if char.islower(): + new_string += char.upper() + if regexp.search(char): + new_string += char + + return new_string + + +if __name__ == "__main__": + print(swap_case(input("Please input sentence:"))) From 671ab1d863b18edeb25d422926fe1521b6a71734 Mon Sep 17 00:00:00 2001 From: Peter Yao Date: Wed, 14 Oct 2020 20:43:39 -0700 Subject: [PATCH 0909/1071] Project Euler 62 Solution (#3029) * Add solution for Project Euler 62 * Add doctests and annotate function params and return values for get_digits() * Add extra newline between functions to fix flake8 errors * Add extra newlines between function names * Add missing return type for solution() * Remove parenthesis from if statement * Remove parentheses from while loop * Add to explanation and fix second Travis build * Compress get_digits(), add tests for solution(), add fstring and positional arg for solution() * Remove input param when calling solution() * Remove test case for the answer --- DIRECTORY.md | 2 + project_euler/problem_62/__init__.py | 0 project_euler/problem_62/sol1.py | 62 ++++++++++++++++++++++++++++ 3 files changed, 64 insertions(+) create mode 100644 project_euler/problem_62/__init__.py create mode 100644 project_euler/problem_62/sol1.py diff --git a/DIRECTORY.md b/DIRECTORY.md index 8e7835e9ca91..0678e10bb453 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -666,6 +666,8 @@ * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_551/sol1.py) * Problem 56 * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_56/sol1.py) + * Problem 62 + * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_62/sol1.py) * Problem 63 * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_63/sol1.py) * Problem 67 diff --git a/project_euler/problem_62/__init__.py b/project_euler/problem_62/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/project_euler/problem_62/sol1.py b/project_euler/problem_62/sol1.py new file mode 100644 index 000000000000..83286c801301 --- /dev/null +++ b/project_euler/problem_62/sol1.py @@ -0,0 +1,62 @@ +""" +Project Euler 62 +https://projecteuler.net/problem=62 + +The cube, 41063625 (345^3), can be permuted to produce two other cubes: +56623104 (384^3) and 66430125 (405^3). In fact, 41063625 is the smallest cube +which has exactly three permutations of its digits which are also cube. + +Find the smallest cube for which exactly five permutations of its digits are +cube. +""" + +from collections import defaultdict + + +def solution(max_base: int = 5) -> int: + """ + Iterate through every possible cube and sort the cube's digits in + ascending order. Sorting maintains an ordering of the digits that allows + you to compare permutations. Store each sorted sequence of digits in a + dictionary, whose key is the sequence of digits and value is a list of + numbers that are the base of the cube. + + Once you find 5 numbers that produce the same sequence of digits, return + the smallest one, which is at index 0 since we insert each base number in + ascending order. + + >>> solution(2) + 125 + >>> solution(3) + 41063625 + """ + freqs = defaultdict(list) + num = 0 + + while True: + digits = get_digits(num) + freqs[digits].append(num) + + if len(freqs[digits]) == max_base: + base = freqs[digits][0] ** 3 + return base + + num += 1 + + +def get_digits(num: int) -> str: + """ + Computes the sorted sequence of digits of the cube of num. + + >>> get_digits(3) + '27' + >>> get_digits(99) + '027999' + >>> get_digits(123) + '0166788' + """ + return "".join(sorted(list(str(num ** 3)))) + + +if __name__ == "__main__": + print(f"{solution() = }") From 2d7e08ef8324867f47f381cdc800ee00f6d77be0 Mon Sep 17 00:00:00 2001 From: Dhruv Date: Thu, 15 Oct 2020 09:47:19 +0530 Subject: [PATCH 0910/1071] Update README.md for Project Euler (#3256) * Update README.md for Project Euler * Add link to solution template * Add newlines for better separation * Add __name__ == __main__ block in template * Apply suggestions from code review Co-authored-by: John Law * Improve introduction part Co-authored-by: John Law --- project_euler/README.md | 115 ++++++++++++++++++++++++++++++++++++++-- 1 file changed, 111 insertions(+), 4 deletions(-) diff --git a/project_euler/README.md b/project_euler/README.md index f80d58ea0038..934e541cc067 100644 --- a/project_euler/README.md +++ b/project_euler/README.md @@ -1,11 +1,118 @@ -# ProjectEuler +# Project Euler Problems are taken from https://projecteuler.net/. Project Euler is a series of challenging mathematical/computer programming problems that will require more than just mathematical insights to solve. Project Euler is ideal for mathematicians who are learning to code. -Here the efficiency of your code is also checked. -I've tried to provide all the best possible solutions. +The solutions will be checked by our [automated testing on Travis CI](https://travis-ci.com/github/TheAlgorithms/Python/pull_requests) with the help of [this script](https://github.com/TheAlgorithms/Python/blob/master/project_euler/validate_solutions.py). The efficiency of your code is also checked. You can view the top 10 slowest solutions on Travis CI logs and open a pull request to improve those solutions. -For description of the problem statements, kindly visit https://projecteuler.net/show=all + +## Solution Guidelines + +Welcome to [TheAlgorithms/Python](https://github.com/TheAlgorithms/Python)! Before reading the solution guidelines, make sure you read the whole [Contributing Guidelines](https://github.com/TheAlgorithms/Python/blob/master/CONTRIBUTING.md) as it won't be repeated in here. If you have any doubt on the guidelines, please feel free to [state it clearly in an issue](https://github.com/TheAlgorithms/Python/issues/new) or ask the community in [Gitter](https://gitter.im/TheAlgorithms). You can use the [template](https://github.com/TheAlgorithms/Python/blob/master/project_euler/README.md#solution-template) we have provided below as your starting point but be sure to read the [Coding Style](https://github.com/TheAlgorithms/Python/blob/master/project_euler/README.md#coding-style) part first. + +### Coding Style + +* Please maintain consistency in project directory and solution file names. Keep the following points in mind: + * Create a new directory only for the problems which do not exist yet. + * If you create a new directory, please create an empty `__init__.py` file inside it as well. + * Please name the project directory as `problem_` where `problem_number` should be filled with 0s so as to occupy 3 digits. Example: `problem_001`, `problem_002`, `problem_067`, `problem_145`, and so on. + +* Please provide a link to the problem and other references, if used, in the module-level docstring. + +* All imports should come ***after*** the module-level docstring. + +* You can have as many helper functions as you want but there should be one main function called `solution` which should satisfy the conditions as stated below: + * It should contain positional argument(s) whose default value is the question input. Example: Please take a look at [problem 1](https://projecteuler.net/problem=1) where the question is to *Find the sum of all the multiples of 3 or 5 below 1000.* In this case the main solution function will be `solution(limit: int = 1000)`. + * When the `solution` function is called without any arguments like so: `solution()`, it should return the answer to the problem. + +* Every function, which includes all the helper functions, if any, and the main solution function, should have `doctest` in the function docstring along with a brief statement mentioning what the function is about. + * There should not be a `doctest` for testing the answer as that is done by our Travis CI build using this [script](https://github.com/TheAlgorithms/Python/blob/master/project_euler/validate_solutions.py). Keeping in mind the above example of [problem 1](https://projecteuler.net/problem=1): + + ```python + def solution(limit: int = 1000): + """ + A brief statement mentioning what the function is about. + + You can have a detailed explanation about the solution method in the + module-level docstring. + + >>> solution(1) + ... + >>> solution(16) + ... + >>> solution(100) + ... + """ + ``` + +### Solution Template + +You can use the below template as your starting point but please read the [Coding Style](https://github.com/TheAlgorithms/Python/blob/master/project_euler/README.md#coding-style) first to understand how the template works. + +Please change the name of the helper functions accordingly, change the parameter names with a descriptive one, replace the content within `[square brackets]` (including the brackets) with the appropriate content. + +```python +""" +Project Euler Problem [problem number]: [link to the original problem] + +... [Entire problem statement] ... + +... [Solution explanation - Optional] ... + +References [Optional]: +- [Wikipedia link to the topic] +- [Stackoverflow link] +... + +""" +import module1 +import module2 +... + +def helper1(arg1: [type hint], arg2: [type hint], ...) -> [Return type hint]: + """ + A brief statement explaining what the function is about. + + ... A more elaborate description ... [Optional] + + ... + [Doctest] + ... + + """ + ... + # calculations + ... + + return + + +# You can have multiple helper functions but the solution function should be +# after all the helper functions ... + + +def solution(arg1: [type hint], arg2: [type hint], ...) -> [Return type hint]: + """ + A brief statement mentioning what the function is about. + + You can have a detailed explanation about the solution in the + module-level docstring. + + ... + [Doctest as mentioned above] + ... + + """ + + ... + # calculations + ... + + return answer + + +if __name__ == "__main__": + print(f"{solution() = }") +``` From 44254cf112fc907b4204dbeb2e927d74ca52e69d Mon Sep 17 00:00:00 2001 From: Dhruv Date: Thu, 15 Oct 2020 12:43:28 +0530 Subject: [PATCH 0911/1071] Rename Project Euler directories and other dependent changes (#3300) * Rename all Project Euler directories: Reason: The change was done to maintain consistency throughout the directory and to keep all directories in sorted order. Due to the above change, some config files had to be modified: 'problem_22` -> `problem_022` * Update scripts to pad zeroes in PE directories --- .github/workflows/codespell.yml | 2 +- .pre-commit-config.yaml | 4 +- .../{problem_01 => problem_001}/__init__.py | 0 .../{problem_01 => problem_001}/sol1.py | 0 .../{problem_01 => problem_001}/sol2.py | 0 .../{problem_01 => problem_001}/sol3.py | 0 .../{problem_01 => problem_001}/sol4.py | 0 .../{problem_01 => problem_001}/sol5.py | 0 .../{problem_01 => problem_001}/sol6.py | 0 .../{problem_01 => problem_001}/sol7.py | 0 .../{problem_02 => problem_002}/__init__.py | 0 .../{problem_02 => problem_002}/sol1.py | 0 .../{problem_02 => problem_002}/sol2.py | 0 .../{problem_02 => problem_002}/sol3.py | 0 .../{problem_02 => problem_002}/sol4.py | 0 .../{problem_02 => problem_002}/sol5.py | 0 .../{problem_03 => problem_003}/__init__.py | 0 .../{problem_03 => problem_003}/sol1.py | 0 .../{problem_03 => problem_003}/sol2.py | 0 .../{problem_03 => problem_003}/sol3.py | 0 .../{problem_04 => problem_004}/__init__.py | 0 .../{problem_04 => problem_004}/sol1.py | 0 .../{problem_04 => problem_004}/sol2.py | 0 .../{problem_05 => problem_005}/__init__.py | 0 .../{problem_05 => problem_005}/sol1.py | 0 .../{problem_05 => problem_005}/sol2.py | 0 .../{problem_06 => problem_006}/__init__.py | 0 .../{problem_06 => problem_006}/sol1.py | 0 .../{problem_06 => problem_006}/sol2.py | 0 .../{problem_06 => problem_006}/sol3.py | 0 .../{problem_06 => problem_006}/sol4.py | 0 .../{problem_07 => problem_007}/__init__.py | 0 .../{problem_07 => problem_007}/sol1.py | 0 .../{problem_07 => problem_007}/sol2.py | 0 .../{problem_07 => problem_007}/sol3.py | 0 .../{problem_08 => problem_008}/__init__.py | 0 .../{problem_08 => problem_008}/sol1.py | 0 .../{problem_08 => problem_008}/sol2.py | 0 .../{problem_08 => problem_008}/sol3.py | 0 .../{problem_09 => problem_009}/__init__.py | 0 .../{problem_09 => problem_009}/sol1.py | 0 .../{problem_09 => problem_009}/sol2.py | 0 .../{problem_09 => problem_009}/sol3.py | 0 .../{problem_10 => problem_010}/__init__.py | 0 .../{problem_10 => problem_010}/sol1.py | 0 .../{problem_10 => problem_010}/sol2.py | 0 .../{problem_10 => problem_010}/sol3.py | 0 .../{problem_11 => problem_011}/__init__.py | 0 .../{problem_11 => problem_011}/grid.txt | 0 .../{problem_11 => problem_011}/sol1.py | 0 .../{problem_11 => problem_011}/sol2.py | 0 .../{problem_12 => problem_012}/__init__.py | 0 .../{problem_12 => problem_012}/sol1.py | 0 .../{problem_12 => problem_012}/sol2.py | 0 .../{problem_13 => problem_013}/__init__.py | 0 .../{problem_13 => problem_013}/num.txt | 0 .../{problem_13 => problem_013}/sol1.py | 0 .../{problem_14 => problem_014}/__init__.py | 0 .../{problem_14 => problem_014}/sol1.py | 0 .../{problem_14 => problem_014}/sol2.py | 0 .../{problem_15 => problem_015}/__init__.py | 0 .../{problem_15 => problem_015}/sol1.py | 0 .../{problem_16 => problem_016}/__init__.py | 0 .../{problem_16 => problem_016}/sol1.py | 0 .../{problem_16 => problem_016}/sol2.py | 0 .../{problem_17 => problem_017}/__init__.py | 0 .../{problem_17 => problem_017}/sol1.py | 0 .../{problem_18 => problem_018}/__init__.py | 0 .../{problem_18 => problem_018}/solution.py | 0 .../{problem_18 => problem_018}/triangle.txt | 0 .../{problem_19 => problem_019}/__init__.py | 0 .../{problem_19 => problem_019}/sol1.py | 0 .../{problem_20 => problem_020}/__init__.py | 0 .../{problem_20 => problem_020}/sol1.py | 0 .../{problem_20 => problem_020}/sol2.py | 0 .../{problem_20 => problem_020}/sol3.py | 0 .../{problem_20 => problem_020}/sol4.py | 0 .../{problem_21 => problem_021}/__init__.py | 0 .../{problem_21 => problem_021}/sol1.py | 0 .../{problem_22 => problem_022}/__init__.py | 0 .../p022_names.txt | 0 .../{problem_22 => problem_022}/sol1.py | 0 .../{problem_22 => problem_022}/sol2.py | 0 .../{problem_23 => problem_023}/__init__.py | 0 .../{problem_23 => problem_023}/sol1.py | 0 .../{problem_24 => problem_024}/__init__.py | 0 .../{problem_24 => problem_024}/sol1.py | 0 .../{problem_25 => problem_025}/__init__.py | 0 .../{problem_25 => problem_025}/sol1.py | 0 .../{problem_25 => problem_025}/sol2.py | 0 .../{problem_25 => problem_025}/sol3.py | 0 .../{problem_26 => problem_026}/__init__.py | 0 .../{problem_26 => problem_026}/sol1.py | 0 .../{problem_27 => problem_027}/__init__.py | 0 .../{problem_27 => problem_027}/sol1.py | 0 .../{problem_28 => problem_028}/__init__.py | 0 .../{problem_28 => problem_028}/sol1.py | 0 .../{problem_29 => problem_029}/__init__.py | 0 .../{problem_29 => problem_029}/sol1.py | 0 .../{problem_30 => problem_030}/__init__.py | 0 .../{problem_30 => problem_030}/sol1.py | 0 .../{problem_31 => problem_031}/__init__.py | 0 .../{problem_31 => problem_031}/sol1.py | 0 .../{problem_31 => problem_031}/sol2.py | 0 .../{problem_32 => problem_032}/__init__.py | 0 .../{problem_32 => problem_032}/sol32.py | 0 .../{problem_33 => problem_033}/__init__.py | 0 .../{problem_33 => problem_033}/sol1.py | 0 .../{problem_34 => problem_034}/__init__.py | 0 .../{problem_34 => problem_034}/sol1.py | 0 .../{problem_35 => problem_035}/__init__.py | 0 .../{problem_35 => problem_035}/sol1.py | 0 .../{problem_36 => problem_036}/__init__.py | 0 .../{problem_36 => problem_036}/sol1.py | 0 .../{problem_37 => problem_037}/__init__.py | 0 .../{problem_37 => problem_037}/sol1.py | 0 .../{problem_39 => problem_039}/__init__.py | 0 .../{problem_39 => problem_039}/sol1.py | 0 .../{problem_40 => problem_040}/__init__.py | 0 .../{problem_40 => problem_040}/sol1.py | 0 .../{problem_41 => problem_041}/__init__.py | 0 .../{problem_41 => problem_041}/sol1.py | 0 .../{problem_42 => problem_042}/__init__.py | 0 .../{problem_42 => problem_042}/solution42.py | 0 .../{problem_42 => problem_042}/words.txt | 0 .../{problem_43 => problem_043}/__init__.py | 0 .../{problem_43 => problem_043}/sol1.py | 0 .../{problem_44 => problem_044}/__init__.py | 0 .../{problem_44 => problem_044}/sol1.py | 0 .../{problem_45 => problem_045}/__init__.py | 0 .../{problem_45 => problem_045}/sol1.py | 0 .../{problem_46 => problem_046}/__init__.py | 0 .../{problem_46 => problem_046}/sol1.py | 0 .../{problem_47 => problem_047}/__init__.py | 0 .../{problem_47 => problem_047}/sol1.py | 0 .../{problem_48 => problem_048}/__init__.py | 0 .../{problem_48 => problem_048}/sol1.py | 0 .../{problem_49 => problem_049}/__init__.py | 0 .../{problem_49 => problem_049}/sol1.py | 0 .../{problem_51 => problem_051}/__init__.py | 0 .../{problem_51 => problem_051}/sol1.py | 0 .../{problem_52 => problem_052}/__init__.py | 0 .../{problem_52 => problem_052}/sol1.py | 0 .../{problem_53 => problem_053}/__init__.py | 0 .../{problem_53 => problem_053}/sol1.py | 0 .../{problem_54 => problem_054}/__init__.py | 0 .../poker_hands.txt | 0 .../{problem_54 => problem_054}/sol1.py | 0 .../test_poker_hand.py | 0 .../{problem_55 => problem_055}/__init__.py | 0 .../{problem_55 => problem_055}/sol1.py | 0 .../{problem_56 => problem_056}/__init__.py | 0 .../{problem_56 => problem_056}/sol1.py | 0 .../{problem_62 => problem_062}/__init__.py | 0 .../{problem_62 => problem_062}/sol1.py | 0 .../{problem_63 => problem_063}/__init__.py | 0 .../{problem_63 => problem_063}/sol1.py | 0 .../{problem_67 => problem_067}/__init__.py | 0 .../{problem_67 => problem_067}/sol1.py | 0 .../{problem_67 => problem_067}/triangle.txt | 0 .../{problem_69 => problem_069}/__init__.py | 0 .../{problem_69 => problem_069}/sol1.py | 0 .../{problem_71 => problem_071}/__init__.py | 0 .../{problem_71 => problem_071}/sol1.py | 0 .../{problem_72 => problem_072}/__init__.py | 0 .../{problem_72 => problem_072}/sol1.py | 0 .../{problem_76 => problem_076}/__init__.py | 0 .../{problem_76 => problem_076}/sol1.py | 0 .../{problem_80 => problem_080}/__init__.py | 0 .../{problem_80 => problem_080}/sol1.py | 0 .../{problem_97 => problem_097}/__init__.py | 0 .../{problem_97 => problem_097}/sol1.py | 0 .../{problem_99 => problem_099}/__init__.py | 0 .../{problem_99 => problem_099}/base_exp.txt | 0 .../{problem_99 => problem_099}/sol1.py | 0 project_euler/project_euler_answers.json | 200 +++++++++--------- project_euler/validate_solutions.py | 14 +- 177 files changed, 108 insertions(+), 112 deletions(-) rename project_euler/{problem_01 => problem_001}/__init__.py (100%) rename project_euler/{problem_01 => problem_001}/sol1.py (100%) rename project_euler/{problem_01 => problem_001}/sol2.py (100%) rename project_euler/{problem_01 => problem_001}/sol3.py (100%) rename project_euler/{problem_01 => problem_001}/sol4.py (100%) rename project_euler/{problem_01 => problem_001}/sol5.py (100%) rename project_euler/{problem_01 => problem_001}/sol6.py (100%) rename project_euler/{problem_01 => problem_001}/sol7.py (100%) rename project_euler/{problem_02 => problem_002}/__init__.py (100%) rename project_euler/{problem_02 => problem_002}/sol1.py (100%) rename project_euler/{problem_02 => problem_002}/sol2.py (100%) rename project_euler/{problem_02 => problem_002}/sol3.py (100%) rename project_euler/{problem_02 => problem_002}/sol4.py (100%) rename project_euler/{problem_02 => problem_002}/sol5.py (100%) rename project_euler/{problem_03 => problem_003}/__init__.py (100%) rename project_euler/{problem_03 => problem_003}/sol1.py (100%) rename project_euler/{problem_03 => problem_003}/sol2.py (100%) rename project_euler/{problem_03 => problem_003}/sol3.py (100%) rename project_euler/{problem_04 => problem_004}/__init__.py (100%) rename project_euler/{problem_04 => problem_004}/sol1.py (100%) rename project_euler/{problem_04 => problem_004}/sol2.py (100%) rename project_euler/{problem_05 => problem_005}/__init__.py (100%) rename project_euler/{problem_05 => problem_005}/sol1.py (100%) rename project_euler/{problem_05 => problem_005}/sol2.py (100%) rename project_euler/{problem_06 => problem_006}/__init__.py (100%) rename project_euler/{problem_06 => problem_006}/sol1.py (100%) rename project_euler/{problem_06 => problem_006}/sol2.py (100%) rename project_euler/{problem_06 => problem_006}/sol3.py (100%) rename project_euler/{problem_06 => problem_006}/sol4.py (100%) rename project_euler/{problem_07 => problem_007}/__init__.py (100%) rename project_euler/{problem_07 => problem_007}/sol1.py (100%) rename project_euler/{problem_07 => problem_007}/sol2.py (100%) rename project_euler/{problem_07 => problem_007}/sol3.py (100%) rename project_euler/{problem_08 => problem_008}/__init__.py (100%) rename project_euler/{problem_08 => problem_008}/sol1.py (100%) rename project_euler/{problem_08 => problem_008}/sol2.py (100%) rename project_euler/{problem_08 => problem_008}/sol3.py (100%) rename project_euler/{problem_09 => problem_009}/__init__.py (100%) rename project_euler/{problem_09 => problem_009}/sol1.py (100%) rename project_euler/{problem_09 => problem_009}/sol2.py (100%) rename project_euler/{problem_09 => problem_009}/sol3.py (100%) rename project_euler/{problem_10 => problem_010}/__init__.py (100%) rename project_euler/{problem_10 => problem_010}/sol1.py (100%) rename project_euler/{problem_10 => problem_010}/sol2.py (100%) rename project_euler/{problem_10 => problem_010}/sol3.py (100%) rename project_euler/{problem_11 => problem_011}/__init__.py (100%) rename project_euler/{problem_11 => problem_011}/grid.txt (100%) rename project_euler/{problem_11 => problem_011}/sol1.py (100%) rename project_euler/{problem_11 => problem_011}/sol2.py (100%) rename project_euler/{problem_12 => problem_012}/__init__.py (100%) rename project_euler/{problem_12 => problem_012}/sol1.py (100%) rename project_euler/{problem_12 => problem_012}/sol2.py (100%) rename project_euler/{problem_13 => problem_013}/__init__.py (100%) rename project_euler/{problem_13 => problem_013}/num.txt (100%) rename project_euler/{problem_13 => problem_013}/sol1.py (100%) rename project_euler/{problem_14 => problem_014}/__init__.py (100%) rename project_euler/{problem_14 => problem_014}/sol1.py (100%) rename project_euler/{problem_14 => problem_014}/sol2.py (100%) rename project_euler/{problem_15 => problem_015}/__init__.py (100%) rename project_euler/{problem_15 => problem_015}/sol1.py (100%) rename project_euler/{problem_16 => problem_016}/__init__.py (100%) rename project_euler/{problem_16 => problem_016}/sol1.py (100%) rename project_euler/{problem_16 => problem_016}/sol2.py (100%) rename project_euler/{problem_17 => problem_017}/__init__.py (100%) rename project_euler/{problem_17 => problem_017}/sol1.py (100%) rename project_euler/{problem_18 => problem_018}/__init__.py (100%) rename project_euler/{problem_18 => problem_018}/solution.py (100%) rename project_euler/{problem_18 => problem_018}/triangle.txt (100%) rename project_euler/{problem_19 => problem_019}/__init__.py (100%) rename project_euler/{problem_19 => problem_019}/sol1.py (100%) rename project_euler/{problem_20 => problem_020}/__init__.py (100%) rename project_euler/{problem_20 => problem_020}/sol1.py (100%) rename project_euler/{problem_20 => problem_020}/sol2.py (100%) rename project_euler/{problem_20 => problem_020}/sol3.py (100%) rename project_euler/{problem_20 => problem_020}/sol4.py (100%) rename project_euler/{problem_21 => problem_021}/__init__.py (100%) rename project_euler/{problem_21 => problem_021}/sol1.py (100%) rename project_euler/{problem_22 => problem_022}/__init__.py (100%) rename project_euler/{problem_22 => problem_022}/p022_names.txt (100%) rename project_euler/{problem_22 => problem_022}/sol1.py (100%) rename project_euler/{problem_22 => problem_022}/sol2.py (100%) rename project_euler/{problem_23 => problem_023}/__init__.py (100%) rename project_euler/{problem_23 => problem_023}/sol1.py (100%) rename project_euler/{problem_24 => problem_024}/__init__.py (100%) rename project_euler/{problem_24 => problem_024}/sol1.py (100%) rename project_euler/{problem_25 => problem_025}/__init__.py (100%) rename project_euler/{problem_25 => problem_025}/sol1.py (100%) rename project_euler/{problem_25 => problem_025}/sol2.py (100%) rename project_euler/{problem_25 => problem_025}/sol3.py (100%) rename project_euler/{problem_26 => problem_026}/__init__.py (100%) rename project_euler/{problem_26 => problem_026}/sol1.py (100%) rename project_euler/{problem_27 => problem_027}/__init__.py (100%) rename project_euler/{problem_27 => problem_027}/sol1.py (100%) rename project_euler/{problem_28 => problem_028}/__init__.py (100%) rename project_euler/{problem_28 => problem_028}/sol1.py (100%) rename project_euler/{problem_29 => problem_029}/__init__.py (100%) rename project_euler/{problem_29 => problem_029}/sol1.py (100%) rename project_euler/{problem_30 => problem_030}/__init__.py (100%) rename project_euler/{problem_30 => problem_030}/sol1.py (100%) rename project_euler/{problem_31 => problem_031}/__init__.py (100%) rename project_euler/{problem_31 => problem_031}/sol1.py (100%) rename project_euler/{problem_31 => problem_031}/sol2.py (100%) rename project_euler/{problem_32 => problem_032}/__init__.py (100%) rename project_euler/{problem_32 => problem_032}/sol32.py (100%) rename project_euler/{problem_33 => problem_033}/__init__.py (100%) rename project_euler/{problem_33 => problem_033}/sol1.py (100%) rename project_euler/{problem_34 => problem_034}/__init__.py (100%) rename project_euler/{problem_34 => problem_034}/sol1.py (100%) rename project_euler/{problem_35 => problem_035}/__init__.py (100%) rename project_euler/{problem_35 => problem_035}/sol1.py (100%) rename project_euler/{problem_36 => problem_036}/__init__.py (100%) rename project_euler/{problem_36 => problem_036}/sol1.py (100%) rename project_euler/{problem_37 => problem_037}/__init__.py (100%) rename project_euler/{problem_37 => problem_037}/sol1.py (100%) rename project_euler/{problem_39 => problem_039}/__init__.py (100%) rename project_euler/{problem_39 => problem_039}/sol1.py (100%) rename project_euler/{problem_40 => problem_040}/__init__.py (100%) rename project_euler/{problem_40 => problem_040}/sol1.py (100%) rename project_euler/{problem_41 => problem_041}/__init__.py (100%) rename project_euler/{problem_41 => problem_041}/sol1.py (100%) rename project_euler/{problem_42 => problem_042}/__init__.py (100%) rename project_euler/{problem_42 => problem_042}/solution42.py (100%) rename project_euler/{problem_42 => problem_042}/words.txt (100%) rename project_euler/{problem_43 => problem_043}/__init__.py (100%) rename project_euler/{problem_43 => problem_043}/sol1.py (100%) rename project_euler/{problem_44 => problem_044}/__init__.py (100%) rename project_euler/{problem_44 => problem_044}/sol1.py (100%) rename project_euler/{problem_45 => problem_045}/__init__.py (100%) rename project_euler/{problem_45 => problem_045}/sol1.py (100%) rename project_euler/{problem_46 => problem_046}/__init__.py (100%) rename project_euler/{problem_46 => problem_046}/sol1.py (100%) rename project_euler/{problem_47 => problem_047}/__init__.py (100%) rename project_euler/{problem_47 => problem_047}/sol1.py (100%) rename project_euler/{problem_48 => problem_048}/__init__.py (100%) rename project_euler/{problem_48 => problem_048}/sol1.py (100%) rename project_euler/{problem_49 => problem_049}/__init__.py (100%) rename project_euler/{problem_49 => problem_049}/sol1.py (100%) rename project_euler/{problem_51 => problem_051}/__init__.py (100%) rename project_euler/{problem_51 => problem_051}/sol1.py (100%) rename project_euler/{problem_52 => problem_052}/__init__.py (100%) rename project_euler/{problem_52 => problem_052}/sol1.py (100%) rename project_euler/{problem_53 => problem_053}/__init__.py (100%) rename project_euler/{problem_53 => problem_053}/sol1.py (100%) rename project_euler/{problem_54 => problem_054}/__init__.py (100%) rename project_euler/{problem_54 => problem_054}/poker_hands.txt (100%) rename project_euler/{problem_54 => problem_054}/sol1.py (100%) rename project_euler/{problem_54 => problem_054}/test_poker_hand.py (100%) rename project_euler/{problem_55 => problem_055}/__init__.py (100%) rename project_euler/{problem_55 => problem_055}/sol1.py (100%) rename project_euler/{problem_56 => problem_056}/__init__.py (100%) rename project_euler/{problem_56 => problem_056}/sol1.py (100%) rename project_euler/{problem_62 => problem_062}/__init__.py (100%) rename project_euler/{problem_62 => problem_062}/sol1.py (100%) rename project_euler/{problem_63 => problem_063}/__init__.py (100%) rename project_euler/{problem_63 => problem_063}/sol1.py (100%) rename project_euler/{problem_67 => problem_067}/__init__.py (100%) rename project_euler/{problem_67 => problem_067}/sol1.py (100%) rename project_euler/{problem_67 => problem_067}/triangle.txt (100%) rename project_euler/{problem_69 => problem_069}/__init__.py (100%) rename project_euler/{problem_69 => problem_069}/sol1.py (100%) rename project_euler/{problem_71 => problem_071}/__init__.py (100%) rename project_euler/{problem_71 => problem_071}/sol1.py (100%) rename project_euler/{problem_72 => problem_072}/__init__.py (100%) rename project_euler/{problem_72 => problem_072}/sol1.py (100%) rename project_euler/{problem_76 => problem_076}/__init__.py (100%) rename project_euler/{problem_76 => problem_076}/sol1.py (100%) rename project_euler/{problem_80 => problem_080}/__init__.py (100%) rename project_euler/{problem_80 => problem_080}/sol1.py (100%) rename project_euler/{problem_97 => problem_097}/__init__.py (100%) rename project_euler/{problem_97 => problem_097}/sol1.py (100%) rename project_euler/{problem_99 => problem_099}/__init__.py (100%) rename project_euler/{problem_99 => problem_099}/base_exp.txt (100%) rename project_euler/{problem_99 => problem_099}/sol1.py (100%) diff --git a/.github/workflows/codespell.yml b/.github/workflows/codespell.yml index 500b737c70e3..e336f697708c 100644 --- a/.github/workflows/codespell.yml +++ b/.github/workflows/codespell.yml @@ -10,7 +10,7 @@ jobs: - uses: actions/setup-python@v2 - run: pip install codespell - run: | - SKIP="./.*,./other/dictionary.txt,./other/words,./project_euler/problem_22/p022_names.txt" + SKIP="./.*,./other/dictionary.txt,./other/words,./project_euler/problem_022/p022_names.txt" codespell --ignore-words-list=ans,fo,followings,hist,iff,secant,som,tim --skip=$SKIP --quiet-level=2 - name: Codespell comment if: ${{ failure() }} diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 2fbb9cb9bdb2..01da6cad0335 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -43,13 +43,13 @@ repos: - id: codespell args: - --ignore-words-list=ans,fo,followings,hist,iff,secant,som,tim - - --skip="./.*,./other/dictionary.txt,./other/words,./project_euler/problem_22/p022_names.txt" + - --skip="./.*,./other/dictionary.txt,./other/words,./project_euler/problem_022/p022_names.txt" - --quiet-level=2 exclude: | (?x)^( other/dictionary.txt | other/words | - project_euler/problem_22/p022_names.txt + project_euler/problem_022/p022_names.txt )$ - repo: local hooks: diff --git a/project_euler/problem_01/__init__.py b/project_euler/problem_001/__init__.py similarity index 100% rename from project_euler/problem_01/__init__.py rename to project_euler/problem_001/__init__.py diff --git a/project_euler/problem_01/sol1.py b/project_euler/problem_001/sol1.py similarity index 100% rename from project_euler/problem_01/sol1.py rename to project_euler/problem_001/sol1.py diff --git a/project_euler/problem_01/sol2.py b/project_euler/problem_001/sol2.py similarity index 100% rename from project_euler/problem_01/sol2.py rename to project_euler/problem_001/sol2.py diff --git a/project_euler/problem_01/sol3.py b/project_euler/problem_001/sol3.py similarity index 100% rename from project_euler/problem_01/sol3.py rename to project_euler/problem_001/sol3.py diff --git a/project_euler/problem_01/sol4.py b/project_euler/problem_001/sol4.py similarity index 100% rename from project_euler/problem_01/sol4.py rename to project_euler/problem_001/sol4.py diff --git a/project_euler/problem_01/sol5.py b/project_euler/problem_001/sol5.py similarity index 100% rename from project_euler/problem_01/sol5.py rename to project_euler/problem_001/sol5.py diff --git a/project_euler/problem_01/sol6.py b/project_euler/problem_001/sol6.py similarity index 100% rename from project_euler/problem_01/sol6.py rename to project_euler/problem_001/sol6.py diff --git a/project_euler/problem_01/sol7.py b/project_euler/problem_001/sol7.py similarity index 100% rename from project_euler/problem_01/sol7.py rename to project_euler/problem_001/sol7.py diff --git a/project_euler/problem_02/__init__.py b/project_euler/problem_002/__init__.py similarity index 100% rename from project_euler/problem_02/__init__.py rename to project_euler/problem_002/__init__.py diff --git a/project_euler/problem_02/sol1.py b/project_euler/problem_002/sol1.py similarity index 100% rename from project_euler/problem_02/sol1.py rename to project_euler/problem_002/sol1.py diff --git a/project_euler/problem_02/sol2.py b/project_euler/problem_002/sol2.py similarity index 100% rename from project_euler/problem_02/sol2.py rename to project_euler/problem_002/sol2.py diff --git a/project_euler/problem_02/sol3.py b/project_euler/problem_002/sol3.py similarity index 100% rename from project_euler/problem_02/sol3.py rename to project_euler/problem_002/sol3.py diff --git a/project_euler/problem_02/sol4.py b/project_euler/problem_002/sol4.py similarity index 100% rename from project_euler/problem_02/sol4.py rename to project_euler/problem_002/sol4.py diff --git a/project_euler/problem_02/sol5.py b/project_euler/problem_002/sol5.py similarity index 100% rename from project_euler/problem_02/sol5.py rename to project_euler/problem_002/sol5.py diff --git a/project_euler/problem_03/__init__.py b/project_euler/problem_003/__init__.py similarity index 100% rename from project_euler/problem_03/__init__.py rename to project_euler/problem_003/__init__.py diff --git a/project_euler/problem_03/sol1.py b/project_euler/problem_003/sol1.py similarity index 100% rename from project_euler/problem_03/sol1.py rename to project_euler/problem_003/sol1.py diff --git a/project_euler/problem_03/sol2.py b/project_euler/problem_003/sol2.py similarity index 100% rename from project_euler/problem_03/sol2.py rename to project_euler/problem_003/sol2.py diff --git a/project_euler/problem_03/sol3.py b/project_euler/problem_003/sol3.py similarity index 100% rename from project_euler/problem_03/sol3.py rename to project_euler/problem_003/sol3.py diff --git a/project_euler/problem_04/__init__.py b/project_euler/problem_004/__init__.py similarity index 100% rename from project_euler/problem_04/__init__.py rename to project_euler/problem_004/__init__.py diff --git a/project_euler/problem_04/sol1.py b/project_euler/problem_004/sol1.py similarity index 100% rename from project_euler/problem_04/sol1.py rename to project_euler/problem_004/sol1.py diff --git a/project_euler/problem_04/sol2.py b/project_euler/problem_004/sol2.py similarity index 100% rename from project_euler/problem_04/sol2.py rename to project_euler/problem_004/sol2.py diff --git a/project_euler/problem_05/__init__.py b/project_euler/problem_005/__init__.py similarity index 100% rename from project_euler/problem_05/__init__.py rename to project_euler/problem_005/__init__.py diff --git a/project_euler/problem_05/sol1.py b/project_euler/problem_005/sol1.py similarity index 100% rename from project_euler/problem_05/sol1.py rename to project_euler/problem_005/sol1.py diff --git a/project_euler/problem_05/sol2.py b/project_euler/problem_005/sol2.py similarity index 100% rename from project_euler/problem_05/sol2.py rename to project_euler/problem_005/sol2.py diff --git a/project_euler/problem_06/__init__.py b/project_euler/problem_006/__init__.py similarity index 100% rename from project_euler/problem_06/__init__.py rename to project_euler/problem_006/__init__.py diff --git a/project_euler/problem_06/sol1.py b/project_euler/problem_006/sol1.py similarity index 100% rename from project_euler/problem_06/sol1.py rename to project_euler/problem_006/sol1.py diff --git a/project_euler/problem_06/sol2.py b/project_euler/problem_006/sol2.py similarity index 100% rename from project_euler/problem_06/sol2.py rename to project_euler/problem_006/sol2.py diff --git a/project_euler/problem_06/sol3.py b/project_euler/problem_006/sol3.py similarity index 100% rename from project_euler/problem_06/sol3.py rename to project_euler/problem_006/sol3.py diff --git a/project_euler/problem_06/sol4.py b/project_euler/problem_006/sol4.py similarity index 100% rename from project_euler/problem_06/sol4.py rename to project_euler/problem_006/sol4.py diff --git a/project_euler/problem_07/__init__.py b/project_euler/problem_007/__init__.py similarity index 100% rename from project_euler/problem_07/__init__.py rename to project_euler/problem_007/__init__.py diff --git a/project_euler/problem_07/sol1.py b/project_euler/problem_007/sol1.py similarity index 100% rename from project_euler/problem_07/sol1.py rename to project_euler/problem_007/sol1.py diff --git a/project_euler/problem_07/sol2.py b/project_euler/problem_007/sol2.py similarity index 100% rename from project_euler/problem_07/sol2.py rename to project_euler/problem_007/sol2.py diff --git a/project_euler/problem_07/sol3.py b/project_euler/problem_007/sol3.py similarity index 100% rename from project_euler/problem_07/sol3.py rename to project_euler/problem_007/sol3.py diff --git a/project_euler/problem_08/__init__.py b/project_euler/problem_008/__init__.py similarity index 100% rename from project_euler/problem_08/__init__.py rename to project_euler/problem_008/__init__.py diff --git a/project_euler/problem_08/sol1.py b/project_euler/problem_008/sol1.py similarity index 100% rename from project_euler/problem_08/sol1.py rename to project_euler/problem_008/sol1.py diff --git a/project_euler/problem_08/sol2.py b/project_euler/problem_008/sol2.py similarity index 100% rename from project_euler/problem_08/sol2.py rename to project_euler/problem_008/sol2.py diff --git a/project_euler/problem_08/sol3.py b/project_euler/problem_008/sol3.py similarity index 100% rename from project_euler/problem_08/sol3.py rename to project_euler/problem_008/sol3.py diff --git a/project_euler/problem_09/__init__.py b/project_euler/problem_009/__init__.py similarity index 100% rename from project_euler/problem_09/__init__.py rename to project_euler/problem_009/__init__.py diff --git a/project_euler/problem_09/sol1.py b/project_euler/problem_009/sol1.py similarity index 100% rename from project_euler/problem_09/sol1.py rename to project_euler/problem_009/sol1.py diff --git a/project_euler/problem_09/sol2.py b/project_euler/problem_009/sol2.py similarity index 100% rename from project_euler/problem_09/sol2.py rename to project_euler/problem_009/sol2.py diff --git a/project_euler/problem_09/sol3.py b/project_euler/problem_009/sol3.py similarity index 100% rename from project_euler/problem_09/sol3.py rename to project_euler/problem_009/sol3.py diff --git a/project_euler/problem_10/__init__.py b/project_euler/problem_010/__init__.py similarity index 100% rename from project_euler/problem_10/__init__.py rename to project_euler/problem_010/__init__.py diff --git a/project_euler/problem_10/sol1.py b/project_euler/problem_010/sol1.py similarity index 100% rename from project_euler/problem_10/sol1.py rename to project_euler/problem_010/sol1.py diff --git a/project_euler/problem_10/sol2.py b/project_euler/problem_010/sol2.py similarity index 100% rename from project_euler/problem_10/sol2.py rename to project_euler/problem_010/sol2.py diff --git a/project_euler/problem_10/sol3.py b/project_euler/problem_010/sol3.py similarity index 100% rename from project_euler/problem_10/sol3.py rename to project_euler/problem_010/sol3.py diff --git a/project_euler/problem_11/__init__.py b/project_euler/problem_011/__init__.py similarity index 100% rename from project_euler/problem_11/__init__.py rename to project_euler/problem_011/__init__.py diff --git a/project_euler/problem_11/grid.txt b/project_euler/problem_011/grid.txt similarity index 100% rename from project_euler/problem_11/grid.txt rename to project_euler/problem_011/grid.txt diff --git a/project_euler/problem_11/sol1.py b/project_euler/problem_011/sol1.py similarity index 100% rename from project_euler/problem_11/sol1.py rename to project_euler/problem_011/sol1.py diff --git a/project_euler/problem_11/sol2.py b/project_euler/problem_011/sol2.py similarity index 100% rename from project_euler/problem_11/sol2.py rename to project_euler/problem_011/sol2.py diff --git a/project_euler/problem_12/__init__.py b/project_euler/problem_012/__init__.py similarity index 100% rename from project_euler/problem_12/__init__.py rename to project_euler/problem_012/__init__.py diff --git a/project_euler/problem_12/sol1.py b/project_euler/problem_012/sol1.py similarity index 100% rename from project_euler/problem_12/sol1.py rename to project_euler/problem_012/sol1.py diff --git a/project_euler/problem_12/sol2.py b/project_euler/problem_012/sol2.py similarity index 100% rename from project_euler/problem_12/sol2.py rename to project_euler/problem_012/sol2.py diff --git a/project_euler/problem_13/__init__.py b/project_euler/problem_013/__init__.py similarity index 100% rename from project_euler/problem_13/__init__.py rename to project_euler/problem_013/__init__.py diff --git a/project_euler/problem_13/num.txt b/project_euler/problem_013/num.txt similarity index 100% rename from project_euler/problem_13/num.txt rename to project_euler/problem_013/num.txt diff --git a/project_euler/problem_13/sol1.py b/project_euler/problem_013/sol1.py similarity index 100% rename from project_euler/problem_13/sol1.py rename to project_euler/problem_013/sol1.py diff --git a/project_euler/problem_14/__init__.py b/project_euler/problem_014/__init__.py similarity index 100% rename from project_euler/problem_14/__init__.py rename to project_euler/problem_014/__init__.py diff --git a/project_euler/problem_14/sol1.py b/project_euler/problem_014/sol1.py similarity index 100% rename from project_euler/problem_14/sol1.py rename to project_euler/problem_014/sol1.py diff --git a/project_euler/problem_14/sol2.py b/project_euler/problem_014/sol2.py similarity index 100% rename from project_euler/problem_14/sol2.py rename to project_euler/problem_014/sol2.py diff --git a/project_euler/problem_15/__init__.py b/project_euler/problem_015/__init__.py similarity index 100% rename from project_euler/problem_15/__init__.py rename to project_euler/problem_015/__init__.py diff --git a/project_euler/problem_15/sol1.py b/project_euler/problem_015/sol1.py similarity index 100% rename from project_euler/problem_15/sol1.py rename to project_euler/problem_015/sol1.py diff --git a/project_euler/problem_16/__init__.py b/project_euler/problem_016/__init__.py similarity index 100% rename from project_euler/problem_16/__init__.py rename to project_euler/problem_016/__init__.py diff --git a/project_euler/problem_16/sol1.py b/project_euler/problem_016/sol1.py similarity index 100% rename from project_euler/problem_16/sol1.py rename to project_euler/problem_016/sol1.py diff --git a/project_euler/problem_16/sol2.py b/project_euler/problem_016/sol2.py similarity index 100% rename from project_euler/problem_16/sol2.py rename to project_euler/problem_016/sol2.py diff --git a/project_euler/problem_17/__init__.py b/project_euler/problem_017/__init__.py similarity index 100% rename from project_euler/problem_17/__init__.py rename to project_euler/problem_017/__init__.py diff --git a/project_euler/problem_17/sol1.py b/project_euler/problem_017/sol1.py similarity index 100% rename from project_euler/problem_17/sol1.py rename to project_euler/problem_017/sol1.py diff --git a/project_euler/problem_18/__init__.py b/project_euler/problem_018/__init__.py similarity index 100% rename from project_euler/problem_18/__init__.py rename to project_euler/problem_018/__init__.py diff --git a/project_euler/problem_18/solution.py b/project_euler/problem_018/solution.py similarity index 100% rename from project_euler/problem_18/solution.py rename to project_euler/problem_018/solution.py diff --git a/project_euler/problem_18/triangle.txt b/project_euler/problem_018/triangle.txt similarity index 100% rename from project_euler/problem_18/triangle.txt rename to project_euler/problem_018/triangle.txt diff --git a/project_euler/problem_19/__init__.py b/project_euler/problem_019/__init__.py similarity index 100% rename from project_euler/problem_19/__init__.py rename to project_euler/problem_019/__init__.py diff --git a/project_euler/problem_19/sol1.py b/project_euler/problem_019/sol1.py similarity index 100% rename from project_euler/problem_19/sol1.py rename to project_euler/problem_019/sol1.py diff --git a/project_euler/problem_20/__init__.py b/project_euler/problem_020/__init__.py similarity index 100% rename from project_euler/problem_20/__init__.py rename to project_euler/problem_020/__init__.py diff --git a/project_euler/problem_20/sol1.py b/project_euler/problem_020/sol1.py similarity index 100% rename from project_euler/problem_20/sol1.py rename to project_euler/problem_020/sol1.py diff --git a/project_euler/problem_20/sol2.py b/project_euler/problem_020/sol2.py similarity index 100% rename from project_euler/problem_20/sol2.py rename to project_euler/problem_020/sol2.py diff --git a/project_euler/problem_20/sol3.py b/project_euler/problem_020/sol3.py similarity index 100% rename from project_euler/problem_20/sol3.py rename to project_euler/problem_020/sol3.py diff --git a/project_euler/problem_20/sol4.py b/project_euler/problem_020/sol4.py similarity index 100% rename from project_euler/problem_20/sol4.py rename to project_euler/problem_020/sol4.py diff --git a/project_euler/problem_21/__init__.py b/project_euler/problem_021/__init__.py similarity index 100% rename from project_euler/problem_21/__init__.py rename to project_euler/problem_021/__init__.py diff --git a/project_euler/problem_21/sol1.py b/project_euler/problem_021/sol1.py similarity index 100% rename from project_euler/problem_21/sol1.py rename to project_euler/problem_021/sol1.py diff --git a/project_euler/problem_22/__init__.py b/project_euler/problem_022/__init__.py similarity index 100% rename from project_euler/problem_22/__init__.py rename to project_euler/problem_022/__init__.py diff --git a/project_euler/problem_22/p022_names.txt b/project_euler/problem_022/p022_names.txt similarity index 100% rename from project_euler/problem_22/p022_names.txt rename to project_euler/problem_022/p022_names.txt diff --git a/project_euler/problem_22/sol1.py b/project_euler/problem_022/sol1.py similarity index 100% rename from project_euler/problem_22/sol1.py rename to project_euler/problem_022/sol1.py diff --git a/project_euler/problem_22/sol2.py b/project_euler/problem_022/sol2.py similarity index 100% rename from project_euler/problem_22/sol2.py rename to project_euler/problem_022/sol2.py diff --git a/project_euler/problem_23/__init__.py b/project_euler/problem_023/__init__.py similarity index 100% rename from project_euler/problem_23/__init__.py rename to project_euler/problem_023/__init__.py diff --git a/project_euler/problem_23/sol1.py b/project_euler/problem_023/sol1.py similarity index 100% rename from project_euler/problem_23/sol1.py rename to project_euler/problem_023/sol1.py diff --git a/project_euler/problem_24/__init__.py b/project_euler/problem_024/__init__.py similarity index 100% rename from project_euler/problem_24/__init__.py rename to project_euler/problem_024/__init__.py diff --git a/project_euler/problem_24/sol1.py b/project_euler/problem_024/sol1.py similarity index 100% rename from project_euler/problem_24/sol1.py rename to project_euler/problem_024/sol1.py diff --git a/project_euler/problem_25/__init__.py b/project_euler/problem_025/__init__.py similarity index 100% rename from project_euler/problem_25/__init__.py rename to project_euler/problem_025/__init__.py diff --git a/project_euler/problem_25/sol1.py b/project_euler/problem_025/sol1.py similarity index 100% rename from project_euler/problem_25/sol1.py rename to project_euler/problem_025/sol1.py diff --git a/project_euler/problem_25/sol2.py b/project_euler/problem_025/sol2.py similarity index 100% rename from project_euler/problem_25/sol2.py rename to project_euler/problem_025/sol2.py diff --git a/project_euler/problem_25/sol3.py b/project_euler/problem_025/sol3.py similarity index 100% rename from project_euler/problem_25/sol3.py rename to project_euler/problem_025/sol3.py diff --git a/project_euler/problem_26/__init__.py b/project_euler/problem_026/__init__.py similarity index 100% rename from project_euler/problem_26/__init__.py rename to project_euler/problem_026/__init__.py diff --git a/project_euler/problem_26/sol1.py b/project_euler/problem_026/sol1.py similarity index 100% rename from project_euler/problem_26/sol1.py rename to project_euler/problem_026/sol1.py diff --git a/project_euler/problem_27/__init__.py b/project_euler/problem_027/__init__.py similarity index 100% rename from project_euler/problem_27/__init__.py rename to project_euler/problem_027/__init__.py diff --git a/project_euler/problem_27/sol1.py b/project_euler/problem_027/sol1.py similarity index 100% rename from project_euler/problem_27/sol1.py rename to project_euler/problem_027/sol1.py diff --git a/project_euler/problem_28/__init__.py b/project_euler/problem_028/__init__.py similarity index 100% rename from project_euler/problem_28/__init__.py rename to project_euler/problem_028/__init__.py diff --git a/project_euler/problem_28/sol1.py b/project_euler/problem_028/sol1.py similarity index 100% rename from project_euler/problem_28/sol1.py rename to project_euler/problem_028/sol1.py diff --git a/project_euler/problem_29/__init__.py b/project_euler/problem_029/__init__.py similarity index 100% rename from project_euler/problem_29/__init__.py rename to project_euler/problem_029/__init__.py diff --git a/project_euler/problem_29/sol1.py b/project_euler/problem_029/sol1.py similarity index 100% rename from project_euler/problem_29/sol1.py rename to project_euler/problem_029/sol1.py diff --git a/project_euler/problem_30/__init__.py b/project_euler/problem_030/__init__.py similarity index 100% rename from project_euler/problem_30/__init__.py rename to project_euler/problem_030/__init__.py diff --git a/project_euler/problem_30/sol1.py b/project_euler/problem_030/sol1.py similarity index 100% rename from project_euler/problem_30/sol1.py rename to project_euler/problem_030/sol1.py diff --git a/project_euler/problem_31/__init__.py b/project_euler/problem_031/__init__.py similarity index 100% rename from project_euler/problem_31/__init__.py rename to project_euler/problem_031/__init__.py diff --git a/project_euler/problem_31/sol1.py b/project_euler/problem_031/sol1.py similarity index 100% rename from project_euler/problem_31/sol1.py rename to project_euler/problem_031/sol1.py diff --git a/project_euler/problem_31/sol2.py b/project_euler/problem_031/sol2.py similarity index 100% rename from project_euler/problem_31/sol2.py rename to project_euler/problem_031/sol2.py diff --git a/project_euler/problem_32/__init__.py b/project_euler/problem_032/__init__.py similarity index 100% rename from project_euler/problem_32/__init__.py rename to project_euler/problem_032/__init__.py diff --git a/project_euler/problem_32/sol32.py b/project_euler/problem_032/sol32.py similarity index 100% rename from project_euler/problem_32/sol32.py rename to project_euler/problem_032/sol32.py diff --git a/project_euler/problem_33/__init__.py b/project_euler/problem_033/__init__.py similarity index 100% rename from project_euler/problem_33/__init__.py rename to project_euler/problem_033/__init__.py diff --git a/project_euler/problem_33/sol1.py b/project_euler/problem_033/sol1.py similarity index 100% rename from project_euler/problem_33/sol1.py rename to project_euler/problem_033/sol1.py diff --git a/project_euler/problem_34/__init__.py b/project_euler/problem_034/__init__.py similarity index 100% rename from project_euler/problem_34/__init__.py rename to project_euler/problem_034/__init__.py diff --git a/project_euler/problem_34/sol1.py b/project_euler/problem_034/sol1.py similarity index 100% rename from project_euler/problem_34/sol1.py rename to project_euler/problem_034/sol1.py diff --git a/project_euler/problem_35/__init__.py b/project_euler/problem_035/__init__.py similarity index 100% rename from project_euler/problem_35/__init__.py rename to project_euler/problem_035/__init__.py diff --git a/project_euler/problem_35/sol1.py b/project_euler/problem_035/sol1.py similarity index 100% rename from project_euler/problem_35/sol1.py rename to project_euler/problem_035/sol1.py diff --git a/project_euler/problem_36/__init__.py b/project_euler/problem_036/__init__.py similarity index 100% rename from project_euler/problem_36/__init__.py rename to project_euler/problem_036/__init__.py diff --git a/project_euler/problem_36/sol1.py b/project_euler/problem_036/sol1.py similarity index 100% rename from project_euler/problem_36/sol1.py rename to project_euler/problem_036/sol1.py diff --git a/project_euler/problem_37/__init__.py b/project_euler/problem_037/__init__.py similarity index 100% rename from project_euler/problem_37/__init__.py rename to project_euler/problem_037/__init__.py diff --git a/project_euler/problem_37/sol1.py b/project_euler/problem_037/sol1.py similarity index 100% rename from project_euler/problem_37/sol1.py rename to project_euler/problem_037/sol1.py diff --git a/project_euler/problem_39/__init__.py b/project_euler/problem_039/__init__.py similarity index 100% rename from project_euler/problem_39/__init__.py rename to project_euler/problem_039/__init__.py diff --git a/project_euler/problem_39/sol1.py b/project_euler/problem_039/sol1.py similarity index 100% rename from project_euler/problem_39/sol1.py rename to project_euler/problem_039/sol1.py diff --git a/project_euler/problem_40/__init__.py b/project_euler/problem_040/__init__.py similarity index 100% rename from project_euler/problem_40/__init__.py rename to project_euler/problem_040/__init__.py diff --git a/project_euler/problem_40/sol1.py b/project_euler/problem_040/sol1.py similarity index 100% rename from project_euler/problem_40/sol1.py rename to project_euler/problem_040/sol1.py diff --git a/project_euler/problem_41/__init__.py b/project_euler/problem_041/__init__.py similarity index 100% rename from project_euler/problem_41/__init__.py rename to project_euler/problem_041/__init__.py diff --git a/project_euler/problem_41/sol1.py b/project_euler/problem_041/sol1.py similarity index 100% rename from project_euler/problem_41/sol1.py rename to project_euler/problem_041/sol1.py diff --git a/project_euler/problem_42/__init__.py b/project_euler/problem_042/__init__.py similarity index 100% rename from project_euler/problem_42/__init__.py rename to project_euler/problem_042/__init__.py diff --git a/project_euler/problem_42/solution42.py b/project_euler/problem_042/solution42.py similarity index 100% rename from project_euler/problem_42/solution42.py rename to project_euler/problem_042/solution42.py diff --git a/project_euler/problem_42/words.txt b/project_euler/problem_042/words.txt similarity index 100% rename from project_euler/problem_42/words.txt rename to project_euler/problem_042/words.txt diff --git a/project_euler/problem_43/__init__.py b/project_euler/problem_043/__init__.py similarity index 100% rename from project_euler/problem_43/__init__.py rename to project_euler/problem_043/__init__.py diff --git a/project_euler/problem_43/sol1.py b/project_euler/problem_043/sol1.py similarity index 100% rename from project_euler/problem_43/sol1.py rename to project_euler/problem_043/sol1.py diff --git a/project_euler/problem_44/__init__.py b/project_euler/problem_044/__init__.py similarity index 100% rename from project_euler/problem_44/__init__.py rename to project_euler/problem_044/__init__.py diff --git a/project_euler/problem_44/sol1.py b/project_euler/problem_044/sol1.py similarity index 100% rename from project_euler/problem_44/sol1.py rename to project_euler/problem_044/sol1.py diff --git a/project_euler/problem_45/__init__.py b/project_euler/problem_045/__init__.py similarity index 100% rename from project_euler/problem_45/__init__.py rename to project_euler/problem_045/__init__.py diff --git a/project_euler/problem_45/sol1.py b/project_euler/problem_045/sol1.py similarity index 100% rename from project_euler/problem_45/sol1.py rename to project_euler/problem_045/sol1.py diff --git a/project_euler/problem_46/__init__.py b/project_euler/problem_046/__init__.py similarity index 100% rename from project_euler/problem_46/__init__.py rename to project_euler/problem_046/__init__.py diff --git a/project_euler/problem_46/sol1.py b/project_euler/problem_046/sol1.py similarity index 100% rename from project_euler/problem_46/sol1.py rename to project_euler/problem_046/sol1.py diff --git a/project_euler/problem_47/__init__.py b/project_euler/problem_047/__init__.py similarity index 100% rename from project_euler/problem_47/__init__.py rename to project_euler/problem_047/__init__.py diff --git a/project_euler/problem_47/sol1.py b/project_euler/problem_047/sol1.py similarity index 100% rename from project_euler/problem_47/sol1.py rename to project_euler/problem_047/sol1.py diff --git a/project_euler/problem_48/__init__.py b/project_euler/problem_048/__init__.py similarity index 100% rename from project_euler/problem_48/__init__.py rename to project_euler/problem_048/__init__.py diff --git a/project_euler/problem_48/sol1.py b/project_euler/problem_048/sol1.py similarity index 100% rename from project_euler/problem_48/sol1.py rename to project_euler/problem_048/sol1.py diff --git a/project_euler/problem_49/__init__.py b/project_euler/problem_049/__init__.py similarity index 100% rename from project_euler/problem_49/__init__.py rename to project_euler/problem_049/__init__.py diff --git a/project_euler/problem_49/sol1.py b/project_euler/problem_049/sol1.py similarity index 100% rename from project_euler/problem_49/sol1.py rename to project_euler/problem_049/sol1.py diff --git a/project_euler/problem_51/__init__.py b/project_euler/problem_051/__init__.py similarity index 100% rename from project_euler/problem_51/__init__.py rename to project_euler/problem_051/__init__.py diff --git a/project_euler/problem_51/sol1.py b/project_euler/problem_051/sol1.py similarity index 100% rename from project_euler/problem_51/sol1.py rename to project_euler/problem_051/sol1.py diff --git a/project_euler/problem_52/__init__.py b/project_euler/problem_052/__init__.py similarity index 100% rename from project_euler/problem_52/__init__.py rename to project_euler/problem_052/__init__.py diff --git a/project_euler/problem_52/sol1.py b/project_euler/problem_052/sol1.py similarity index 100% rename from project_euler/problem_52/sol1.py rename to project_euler/problem_052/sol1.py diff --git a/project_euler/problem_53/__init__.py b/project_euler/problem_053/__init__.py similarity index 100% rename from project_euler/problem_53/__init__.py rename to project_euler/problem_053/__init__.py diff --git a/project_euler/problem_53/sol1.py b/project_euler/problem_053/sol1.py similarity index 100% rename from project_euler/problem_53/sol1.py rename to project_euler/problem_053/sol1.py diff --git a/project_euler/problem_54/__init__.py b/project_euler/problem_054/__init__.py similarity index 100% rename from project_euler/problem_54/__init__.py rename to project_euler/problem_054/__init__.py diff --git a/project_euler/problem_54/poker_hands.txt b/project_euler/problem_054/poker_hands.txt similarity index 100% rename from project_euler/problem_54/poker_hands.txt rename to project_euler/problem_054/poker_hands.txt diff --git a/project_euler/problem_54/sol1.py b/project_euler/problem_054/sol1.py similarity index 100% rename from project_euler/problem_54/sol1.py rename to project_euler/problem_054/sol1.py diff --git a/project_euler/problem_54/test_poker_hand.py b/project_euler/problem_054/test_poker_hand.py similarity index 100% rename from project_euler/problem_54/test_poker_hand.py rename to project_euler/problem_054/test_poker_hand.py diff --git a/project_euler/problem_55/__init__.py b/project_euler/problem_055/__init__.py similarity index 100% rename from project_euler/problem_55/__init__.py rename to project_euler/problem_055/__init__.py diff --git a/project_euler/problem_55/sol1.py b/project_euler/problem_055/sol1.py similarity index 100% rename from project_euler/problem_55/sol1.py rename to project_euler/problem_055/sol1.py diff --git a/project_euler/problem_56/__init__.py b/project_euler/problem_056/__init__.py similarity index 100% rename from project_euler/problem_56/__init__.py rename to project_euler/problem_056/__init__.py diff --git a/project_euler/problem_56/sol1.py b/project_euler/problem_056/sol1.py similarity index 100% rename from project_euler/problem_56/sol1.py rename to project_euler/problem_056/sol1.py diff --git a/project_euler/problem_62/__init__.py b/project_euler/problem_062/__init__.py similarity index 100% rename from project_euler/problem_62/__init__.py rename to project_euler/problem_062/__init__.py diff --git a/project_euler/problem_62/sol1.py b/project_euler/problem_062/sol1.py similarity index 100% rename from project_euler/problem_62/sol1.py rename to project_euler/problem_062/sol1.py diff --git a/project_euler/problem_63/__init__.py b/project_euler/problem_063/__init__.py similarity index 100% rename from project_euler/problem_63/__init__.py rename to project_euler/problem_063/__init__.py diff --git a/project_euler/problem_63/sol1.py b/project_euler/problem_063/sol1.py similarity index 100% rename from project_euler/problem_63/sol1.py rename to project_euler/problem_063/sol1.py diff --git a/project_euler/problem_67/__init__.py b/project_euler/problem_067/__init__.py similarity index 100% rename from project_euler/problem_67/__init__.py rename to project_euler/problem_067/__init__.py diff --git a/project_euler/problem_67/sol1.py b/project_euler/problem_067/sol1.py similarity index 100% rename from project_euler/problem_67/sol1.py rename to project_euler/problem_067/sol1.py diff --git a/project_euler/problem_67/triangle.txt b/project_euler/problem_067/triangle.txt similarity index 100% rename from project_euler/problem_67/triangle.txt rename to project_euler/problem_067/triangle.txt diff --git a/project_euler/problem_69/__init__.py b/project_euler/problem_069/__init__.py similarity index 100% rename from project_euler/problem_69/__init__.py rename to project_euler/problem_069/__init__.py diff --git a/project_euler/problem_69/sol1.py b/project_euler/problem_069/sol1.py similarity index 100% rename from project_euler/problem_69/sol1.py rename to project_euler/problem_069/sol1.py diff --git a/project_euler/problem_71/__init__.py b/project_euler/problem_071/__init__.py similarity index 100% rename from project_euler/problem_71/__init__.py rename to project_euler/problem_071/__init__.py diff --git a/project_euler/problem_71/sol1.py b/project_euler/problem_071/sol1.py similarity index 100% rename from project_euler/problem_71/sol1.py rename to project_euler/problem_071/sol1.py diff --git a/project_euler/problem_72/__init__.py b/project_euler/problem_072/__init__.py similarity index 100% rename from project_euler/problem_72/__init__.py rename to project_euler/problem_072/__init__.py diff --git a/project_euler/problem_72/sol1.py b/project_euler/problem_072/sol1.py similarity index 100% rename from project_euler/problem_72/sol1.py rename to project_euler/problem_072/sol1.py diff --git a/project_euler/problem_76/__init__.py b/project_euler/problem_076/__init__.py similarity index 100% rename from project_euler/problem_76/__init__.py rename to project_euler/problem_076/__init__.py diff --git a/project_euler/problem_76/sol1.py b/project_euler/problem_076/sol1.py similarity index 100% rename from project_euler/problem_76/sol1.py rename to project_euler/problem_076/sol1.py diff --git a/project_euler/problem_80/__init__.py b/project_euler/problem_080/__init__.py similarity index 100% rename from project_euler/problem_80/__init__.py rename to project_euler/problem_080/__init__.py diff --git a/project_euler/problem_80/sol1.py b/project_euler/problem_080/sol1.py similarity index 100% rename from project_euler/problem_80/sol1.py rename to project_euler/problem_080/sol1.py diff --git a/project_euler/problem_97/__init__.py b/project_euler/problem_097/__init__.py similarity index 100% rename from project_euler/problem_97/__init__.py rename to project_euler/problem_097/__init__.py diff --git a/project_euler/problem_97/sol1.py b/project_euler/problem_097/sol1.py similarity index 100% rename from project_euler/problem_97/sol1.py rename to project_euler/problem_097/sol1.py diff --git a/project_euler/problem_99/__init__.py b/project_euler/problem_099/__init__.py similarity index 100% rename from project_euler/problem_99/__init__.py rename to project_euler/problem_099/__init__.py diff --git a/project_euler/problem_99/base_exp.txt b/project_euler/problem_099/base_exp.txt similarity index 100% rename from project_euler/problem_99/base_exp.txt rename to project_euler/problem_099/base_exp.txt diff --git a/project_euler/problem_99/sol1.py b/project_euler/problem_099/sol1.py similarity index 100% rename from project_euler/problem_99/sol1.py rename to project_euler/problem_099/sol1.py diff --git a/project_euler/project_euler_answers.json b/project_euler/project_euler_answers.json index 6889ad09703e..05c144d1e06a 100644 --- a/project_euler/project_euler_answers.json +++ b/project_euler/project_euler_answers.json @@ -1,103 +1,103 @@ { - "01": "233168", - "02": "4613732", - "03": "6857", - "04": "906609", - "05": "232792560", - "06": "25164150", - "07": "104743", - "08": "23514624000", - "09": "31875000", - "10": "142913828922", - "11": "70600674", - "12": "76576500", - "13": "5537376230", - "14": "837799", - "15": "137846528820", - "16": "1366", - "17": "21124", - "18": "1074", - "19": "171", - "20": "648", - "21": "31626", - "22": "871198282", - "23": "4179871", - "24": "2783915460", - "25": "4782", - "26": "983", - "27": "-59231", - "28": "669171001", - "29": "9183", - "30": "443839", - "31": "73682", - "32": "45228", - "33": "100", - "34": "40730", - "35": "55", - "36": "872187", - "37": "748317", - "38": "932718654", - "39": "840", - "40": "210", - "41": "7652413", - "42": "162", - "43": "16695334890", - "44": "5482660", - "45": "1533776805", - "46": "5777", - "47": "134043", - "48": "9110846700", - "49": "296962999629", - "50": "997651", - "51": "121313", - "52": "142857", - "53": "4075", - "54": "376", - "55": "249", - "56": "972", - "57": "153", - "58": "26241", - "59": "129448", - "60": "26033", - "61": "28684", - "62": "127035954683", - "63": "49", - "64": "1322", - "65": "272", - "66": "661", - "67": "7273", - "68": "6531031914842725", - "69": "510510", - "70": "8319823", - "71": "428570", - "72": "303963552391", - "73": "7295372", - "74": "402", - "75": "161667", - "76": "190569291", - "77": "71", - "78": "55374", - "79": "73162890", - "80": "40886", - "81": "427337", - "82": "260324", - "83": "425185", - "84": "101524", - "85": "2772", - "86": "1818", - "87": "1097343", - "88": "7587457", - "89": "743", - "90": "1217", - "91": "14234", - "92": "8581146", - "93": "1258", - "94": "518408346", - "95": "14316", - "96": "24702", - "97": "8739992577", - "98": "18769", - "99": "709", + "001": "233168", + "002": "4613732", + "003": "6857", + "004": "906609", + "005": "232792560", + "006": "25164150", + "007": "104743", + "008": "23514624000", + "009": "31875000", + "010": "142913828922", + "011": "70600674", + "012": "76576500", + "013": "5537376230", + "014": "837799", + "015": "137846528820", + "016": "1366", + "017": "21124", + "018": "1074", + "019": "171", + "020": "648", + "021": "31626", + "022": "871198282", + "023": "4179871", + "024": "2783915460", + "025": "4782", + "026": "983", + "027": "-59231", + "028": "669171001", + "029": "9183", + "030": "443839", + "031": "73682", + "032": "45228", + "033": "100", + "034": "40730", + "035": "55", + "036": "872187", + "037": "748317", + "038": "932718654", + "039": "840", + "040": "210", + "041": "7652413", + "042": "162", + "043": "16695334890", + "044": "5482660", + "045": "1533776805", + "046": "5777", + "047": "134043", + "048": "9110846700", + "049": "296962999629", + "050": "997651", + "051": "121313", + "052": "142857", + "053": "4075", + "054": "376", + "055": "249", + "056": "972", + "057": "153", + "058": "26241", + "059": "129448", + "060": "26033", + "061": "28684", + "062": "127035954683", + "063": "49", + "064": "1322", + "065": "272", + "066": "661", + "067": "7273", + "068": "6531031914842725", + "069": "510510", + "070": "8319823", + "071": "428570", + "072": "303963552391", + "073": "7295372", + "074": "402", + "075": "161667", + "076": "190569291", + "077": "71", + "078": "55374", + "079": "73162890", + "080": "40886", + "081": "427337", + "082": "260324", + "083": "425185", + "084": "101524", + "085": "2772", + "086": "1818", + "087": "1097343", + "088": "7587457", + "089": "743", + "090": "1217", + "091": "14234", + "092": "8581146", + "093": "1258", + "094": "518408346", + "095": "14316", + "096": "24702", + "097": "8739992577", + "098": "18769", + "099": "709", "100": "756872327473", "101": "37076114526", "102": "228", @@ -724,4 +724,4 @@ "723": "1395793419248", "724": "18128250110", "725": "4598797036650685" -} \ No newline at end of file +} diff --git a/project_euler/validate_solutions.py b/project_euler/validate_solutions.py index b340fe945d76..6cc1d6498e37 100755 --- a/project_euler/validate_solutions.py +++ b/project_euler/validate_solutions.py @@ -37,19 +37,15 @@ def collect_solution_file_paths() -> List[pathlib.Path]: return solution_file_paths -def expand_parameters(param: pathlib.Path) -> str: - """Expand parameters in pytest parametrize""" - project_dirname = param.parent.name - solution_filename = param.name - return f"{project_dirname}/{solution_filename}" - - @pytest.mark.parametrize( - "solution_path", collect_solution_file_paths(), ids=expand_parameters + "solution_path", + collect_solution_file_paths(), + ids=lambda path: f"{path.parent.name}/{path.name}", ) def test_project_euler(solution_path: pathlib.Path): """Testing for all Project Euler solutions""" - problem_number: str = solution_path.parent.name[8:] # problem_[extract his part] + # problem_[extract this part] and pad it with zeroes for width 3 + problem_number: str = solution_path.parent.name[8:].zfill(3) expected: str = PROBLEM_ANSWERS[problem_number] solution_module = convert_path_to_module(solution_path) answer = str(solution_module.solution()) From de2725f4ac7b79cfba46d1e0397bf959dddf31d4 Mon Sep 17 00:00:00 2001 From: Akash G Krishnan Date: Thu, 15 Oct 2020 14:00:12 +0530 Subject: [PATCH 0912/1071] Update mergesort.py (#2563) * Update mergesort.py 1) Updating the merge sort in python as the previous implementation was modifying the input array 2) divided the division part and conquer part of the merge sort algorithm as 2 functions namely mergeSort and merge. 3) function mergeSort divides the function into halves i.e the purpose of the function will be to divide the array 4) function merge will merge 2 halves into a sorted array 5)Added random test cases using shuffle as suggested by @dhruvmanila 6 The time and space complexity of the previous and my version remains the same. i.e (n log(n) time and n log(n) space 7) changed variables as per the python case as required and suggested by @dhruvmanila 8) Updated function names as suggested by @dhurvmanila * Update mergesort.py Added in few more test cases added type hints for the functions and parameters as suggested by @dhruvmanila formatted the code using Auto Pep8 * Update mergesort.py update and added new testcases * Update mergesort.py Added in doc test in merge function * Update mergesort.py fixing pre-commit fails * Update mergesort.py Co-authored-by: Dhruv --- divide_and_conquer/mergesort.py | 147 ++++++++++++++++++++++---------- 1 file changed, 104 insertions(+), 43 deletions(-) diff --git a/divide_and_conquer/mergesort.py b/divide_and_conquer/mergesort.py index f31a57251ce6..46a46941cab3 100644 --- a/divide_and_conquer/mergesort.py +++ b/divide_and_conquer/mergesort.py @@ -1,48 +1,109 @@ -def merge(arr, left, mid, right): - # overall array will divided into 2 array - # left_arr contains the left portion of array from left to mid - # right_arr contains the right portion of array from mid + 1 to right - left_arr = arr[left : mid + 1] - right_arr = arr[mid + 1 : right + 1] - k = left - i = 0 - j = 0 - while i < len(left_arr) and j < len(right_arr): - # change sign for Descending order - if left_arr[i] < right_arr[j]: - arr[k] = left_arr[i] - i += 1 - else: - arr[k] = right_arr[j] - j += 1 - k += 1 - while i < len(left_arr): - arr[k] = left_arr[i] - i += 1 - k += 1 - while j < len(right_arr): - arr[k] = right_arr[j] - j += 1 - k += 1 - return arr - - -def mergesort(arr, left, right): +from typing import List + + +def merge(left_half: List, right_half: List) -> List: + """Helper function for mergesort. + + >>> left_half = [-2] + >>> right_half = [-1] + >>> merge(left_half, right_half) + [-2, -1] + + >>> left_half = [1,2,3] + >>> right_half = [4,5,6] + >>> merge(left_half, right_half) + [1, 2, 3, 4, 5, 6] + + >>> left_half = [-2] + >>> right_half = [-1] + >>> merge(left_half, right_half) + [-2, -1] + + >>> left_half = [12, 15] + >>> right_half = [13, 14] + >>> merge(left_half, right_half) + [12, 13, 14, 15] + + >>> left_half = [] + >>> right_half = [] + >>> merge(left_half, right_half) + [] """ - >>> mergesort([3, 2, 1], 0, 2) - [1, 2, 3] - >>> mergesort([3, 2, 1, 0, 1, 2, 3, 5, 4], 0, 8) - [0, 1, 1, 2, 2, 3, 3, 4, 5] + sorted_array = [None] * (len(right_half) + len(left_half)) + + pointer1 = 0 # pointer to current index for left Half + pointer2 = 0 # pointer to current index for the right Half + index = 0 # pointer to current index for the sorted array Half + + while pointer1 < len(left_half) and pointer2 < len(right_half): + if left_half[pointer1] < right_half[pointer2]: + sorted_array[index] = left_half[pointer1] + pointer1 += 1 + index += 1 + else: + sorted_array[index] = right_half[pointer2] + pointer2 += 1 + index += 1 + while pointer1 < len(left_half): + sorted_array[index] = left_half[pointer1] + pointer1 += 1 + index += 1 + + while pointer2 < len(right_half): + sorted_array[index] = right_half[pointer2] + pointer2 += 1 + index += 1 + + return sorted_array + + +def merge_sort(array: List) -> List: + """Returns a list of sorted array elements using merge sort. + + >>> from random import shuffle + >>> array = [-2, 3, -10, 11, 99, 100000, 100, -200] + >>> shuffle(array) + >>> merge_sort(array) + [-200, -10, -2, 3, 11, 99, 100, 100000] + + >>> shuffle(array) + >>> merge_sort(array) + [-200, -10, -2, 3, 11, 99, 100, 100000] + + >>> array = [-200] + >>> merge_sort(array) + [-200] + + >>> array = [-2, 3, -10, 11, 99, 100000, 100, -200] + >>> shuffle(array) + >>> sorted(array) == merge_sort(array) + True + + >>> array = [-2] + >>> merge_sort(array) + [-2] + + >>> array = [] + >>> merge_sort(array) + [] + + >>> array = [10000000, 1, -1111111111, 101111111112, 9000002] + >>> sorted(array) == merge_sort(array) + True """ - if left < right: - mid = (left + right) // 2 - # print("ms1",a,b,m) - mergesort(arr, left, mid) - # print("ms2",a,m+1,e) - mergesort(arr, mid + 1, right) - # print("m",a,b,m,e) - merge(arr, left, mid, right) - return arr + if len(array) <= 1: + return array + # the actual formula to calculate the middle element = left + (right - left) // 2 + # this avoids integer overflow in case of large N + middle = 0 + (len(array) - 0) // 2 + + # Split the array into halves till the array length becomes equal to One + # merge the arrays of single length returned by mergeSort function and + # pass them into the merge arrays function which merges the array + left_half = array[:middle] + right_half = array[middle:] + + return merge(merge_sort(left_half), merge_sort(right_half)) if __name__ == "__main__": From aeb6edc79272069346d7f8de7dbe9fe93896f776 Mon Sep 17 00:00:00 2001 From: Christian Clauss Date: Thu, 15 Oct 2020 11:11:22 +0200 Subject: [PATCH 0913/1071] .pre-commit-config.yaml: Disable trailing-whitespace fixer (#3306) * .pre-commit-config.yaml: Disable trailing-whitespace fixer * updating DIRECTORY.md Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> --- .pre-commit-config.yaml | 12 +- DIRECTORY.md | 332 ++++++++++++++++++++-------------------- 2 files changed, 174 insertions(+), 170 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 01da6cad0335..3c08a082664a 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -6,11 +6,13 @@ repos: - id: check-yaml - id: end-of-file-fixer types: [python] - - id: trailing-whitespace - exclude: | - (?x)^( - data_structures/heap/binomial_heap.py - )$ + # Some doctests use print(x, sep=" ") and black will not fix inside triple quotes + # It is too confusing for new contributors to understand this and how to fix + #- id: trailing-whitespace + # exclude: | + # (?x)^( + # data_structures/heap/binomial_heap.py + # )$ - id: requirements-txt-fixer - repo: https://github.com/psf/black rev: stable diff --git a/DIRECTORY.md b/DIRECTORY.md index 0678e10bb453..fe6b604cbb7f 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -133,6 +133,7 @@ * [Max Heap](https://github.com/TheAlgorithms/Python/blob/master/data_structures/heap/max_heap.py) * [Min Heap](https://github.com/TheAlgorithms/Python/blob/master/data_structures/heap/min_heap.py) * [Randomized Heap](https://github.com/TheAlgorithms/Python/blob/master/data_structures/heap/randomized_heap.py) + * [Skew Heap](https://github.com/TheAlgorithms/Python/blob/master/data_structures/heap/skew_heap.py) * Linked List * [Circular Linked List](https://github.com/TheAlgorithms/Python/blob/master/data_structures/linked_list/circular_linked_list.py) * [Deque Doubly](https://github.com/TheAlgorithms/Python/blob/master/data_structures/linked_list/deque_doubly.py) @@ -507,185 +508,185 @@ * [Word Patterns](https://github.com/TheAlgorithms/Python/blob/master/other/word_patterns.py) ## Project Euler - * Problem 01 - * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_01/sol1.py) - * [Sol2](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_01/sol2.py) - * [Sol3](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_01/sol3.py) - * [Sol4](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_01/sol4.py) - * [Sol5](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_01/sol5.py) - * [Sol6](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_01/sol6.py) - * [Sol7](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_01/sol7.py) - * Problem 02 - * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_02/sol1.py) - * [Sol2](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_02/sol2.py) - * [Sol3](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_02/sol3.py) - * [Sol4](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_02/sol4.py) - * [Sol5](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_02/sol5.py) - * Problem 03 - * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_03/sol1.py) - * [Sol2](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_03/sol2.py) - * [Sol3](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_03/sol3.py) - * Problem 04 - * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_04/sol1.py) - * [Sol2](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_04/sol2.py) - * Problem 05 - * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_05/sol1.py) - * [Sol2](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_05/sol2.py) - * Problem 06 - * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_06/sol1.py) - * [Sol2](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_06/sol2.py) - * [Sol3](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_06/sol3.py) - * [Sol4](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_06/sol4.py) - * Problem 07 - * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_07/sol1.py) - * [Sol2](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_07/sol2.py) - * [Sol3](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_07/sol3.py) - * Problem 08 - * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_08/sol1.py) - * [Sol2](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_08/sol2.py) - * [Sol3](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_08/sol3.py) - * Problem 09 - * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_09/sol1.py) - * [Sol2](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_09/sol2.py) - * [Sol3](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_09/sol3.py) - * Problem 10 - * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_10/sol1.py) - * [Sol2](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_10/sol2.py) - * [Sol3](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_10/sol3.py) - * Problem 11 - * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_11/sol1.py) - * [Sol2](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_11/sol2.py) + * Problem 001 + * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_001/sol1.py) + * [Sol2](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_001/sol2.py) + * [Sol3](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_001/sol3.py) + * [Sol4](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_001/sol4.py) + * [Sol5](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_001/sol5.py) + * [Sol6](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_001/sol6.py) + * [Sol7](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_001/sol7.py) + * Problem 002 + * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_002/sol1.py) + * [Sol2](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_002/sol2.py) + * [Sol3](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_002/sol3.py) + * [Sol4](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_002/sol4.py) + * [Sol5](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_002/sol5.py) + * Problem 003 + * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_003/sol1.py) + * [Sol2](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_003/sol2.py) + * [Sol3](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_003/sol3.py) + * Problem 004 + * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_004/sol1.py) + * [Sol2](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_004/sol2.py) + * Problem 005 + * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_005/sol1.py) + * [Sol2](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_005/sol2.py) + * Problem 006 + * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_006/sol1.py) + * [Sol2](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_006/sol2.py) + * [Sol3](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_006/sol3.py) + * [Sol4](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_006/sol4.py) + * Problem 007 + * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_007/sol1.py) + * [Sol2](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_007/sol2.py) + * [Sol3](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_007/sol3.py) + * Problem 008 + * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_008/sol1.py) + * [Sol2](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_008/sol2.py) + * [Sol3](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_008/sol3.py) + * Problem 009 + * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_009/sol1.py) + * [Sol2](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_009/sol2.py) + * [Sol3](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_009/sol3.py) + * Problem 010 + * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_010/sol1.py) + * [Sol2](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_010/sol2.py) + * [Sol3](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_010/sol3.py) + * Problem 011 + * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_011/sol1.py) + * [Sol2](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_011/sol2.py) + * Problem 012 + * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_012/sol1.py) + * [Sol2](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_012/sol2.py) + * Problem 013 + * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_013/sol1.py) + * Problem 014 + * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_014/sol1.py) + * [Sol2](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_014/sol2.py) + * Problem 015 + * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_015/sol1.py) + * Problem 016 + * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_016/sol1.py) + * [Sol2](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_016/sol2.py) + * Problem 017 + * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_017/sol1.py) + * Problem 018 + * [Solution](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_018/solution.py) + * Problem 019 + * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_019/sol1.py) + * Problem 020 + * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_020/sol1.py) + * [Sol2](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_020/sol2.py) + * [Sol3](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_020/sol3.py) + * [Sol4](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_020/sol4.py) + * Problem 021 + * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_021/sol1.py) + * Problem 022 + * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_022/sol1.py) + * [Sol2](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_022/sol2.py) + * Problem 023 + * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_023/sol1.py) + * Problem 024 + * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_024/sol1.py) + * Problem 025 + * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_025/sol1.py) + * [Sol2](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_025/sol2.py) + * [Sol3](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_025/sol3.py) + * Problem 026 + * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_026/sol1.py) + * Problem 027 + * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_027/sol1.py) + * Problem 028 + * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_028/sol1.py) + * Problem 029 + * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_029/sol1.py) + * Problem 030 + * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_030/sol1.py) + * Problem 031 + * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_031/sol1.py) + * [Sol2](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_031/sol2.py) + * Problem 032 + * [Sol32](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_032/sol32.py) + * Problem 033 + * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_033/sol1.py) + * Problem 034 + * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_034/sol1.py) + * Problem 035 + * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_035/sol1.py) + * Problem 036 + * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_036/sol1.py) + * Problem 037 + * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_037/sol1.py) + * Problem 039 + * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_039/sol1.py) + * Problem 040 + * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_040/sol1.py) + * Problem 041 + * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_041/sol1.py) + * Problem 042 + * [Solution42](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_042/solution42.py) + * Problem 043 + * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_043/sol1.py) + * Problem 044 + * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_044/sol1.py) + * Problem 045 + * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_045/sol1.py) + * Problem 046 + * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_046/sol1.py) + * Problem 047 + * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_047/sol1.py) + * Problem 048 + * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_048/sol1.py) + * Problem 049 + * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_049/sol1.py) + * Problem 051 + * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_051/sol1.py) + * Problem 052 + * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_052/sol1.py) + * Problem 053 + * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_053/sol1.py) + * Problem 054 + * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_054/sol1.py) + * [Test Poker Hand](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_054/test_poker_hand.py) + * Problem 055 + * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_055/sol1.py) + * Problem 056 + * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_056/sol1.py) + * Problem 062 + * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_062/sol1.py) + * Problem 063 + * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_063/sol1.py) + * Problem 067 + * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_067/sol1.py) + * Problem 069 + * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_069/sol1.py) + * Problem 071 + * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_071/sol1.py) + * Problem 072 + * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_072/sol1.py) + * Problem 076 + * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_076/sol1.py) + * Problem 080 + * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_080/sol1.py) + * Problem 097 + * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_097/sol1.py) + * Problem 099 + * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_099/sol1.py) * Problem 112 * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_112/sol1.py) * Problem 119 * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_119/sol1.py) - * Problem 12 - * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_12/sol1.py) - * [Sol2](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_12/sol2.py) * Problem 120 * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_120/sol1.py) * Problem 125 * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_125/sol1.py) - * Problem 13 - * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_13/sol1.py) - * Problem 14 - * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_14/sol1.py) - * [Sol2](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_14/sol2.py) - * Problem 15 - * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_15/sol1.py) - * Problem 16 - * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_16/sol1.py) - * [Sol2](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_16/sol2.py) - * Problem 17 - * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_17/sol1.py) - * Problem 18 - * [Solution](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_18/solution.py) - * Problem 19 - * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_19/sol1.py) * Problem 191 * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_191/sol1.py) - * Problem 20 - * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_20/sol1.py) - * [Sol2](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_20/sol2.py) - * [Sol3](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_20/sol3.py) - * [Sol4](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_20/sol4.py) - * Problem 21 - * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_21/sol1.py) - * Problem 22 - * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_22/sol1.py) - * [Sol2](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_22/sol2.py) - * Problem 23 - * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_23/sol1.py) * Problem 234 * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_234/sol1.py) - * Problem 24 - * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_24/sol1.py) - * Problem 25 - * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_25/sol1.py) - * [Sol2](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_25/sol2.py) - * [Sol3](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_25/sol3.py) - * Problem 26 - * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_26/sol1.py) - * Problem 27 - * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_27/sol1.py) - * Problem 28 - * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_28/sol1.py) - * Problem 29 - * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_29/sol1.py) - * Problem 30 - * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_30/sol1.py) - * Problem 31 - * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_31/sol1.py) - * [Sol2](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_31/sol2.py) - * Problem 32 - * [Sol32](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_32/sol32.py) - * Problem 33 - * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_33/sol1.py) - * Problem 34 - * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_34/sol1.py) - * Problem 35 - * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_35/sol1.py) - * Problem 36 - * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_36/sol1.py) - * Problem 37 - * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_37/sol1.py) - * Problem 39 - * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_39/sol1.py) - * Problem 40 - * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_40/sol1.py) - * Problem 41 - * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_41/sol1.py) - * Problem 42 - * [Solution42](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_42/solution42.py) - * Problem 43 - * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_43/sol1.py) - * Problem 44 - * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_44/sol1.py) - * Problem 45 - * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_45/sol1.py) - * Problem 46 - * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_46/sol1.py) - * Problem 47 - * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_47/sol1.py) - * Problem 48 - * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_48/sol1.py) - * Problem 49 - * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_49/sol1.py) - * Problem 51 - * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_51/sol1.py) - * Problem 52 - * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_52/sol1.py) - * Problem 53 - * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_53/sol1.py) - * Problem 54 - * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_54/sol1.py) - * [Test Poker Hand](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_54/test_poker_hand.py) - * Problem 55 - * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_55/sol1.py) * Problem 551 * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_551/sol1.py) - * Problem 56 - * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_56/sol1.py) - * Problem 62 - * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_62/sol1.py) - * Problem 63 - * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_63/sol1.py) - * Problem 67 - * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_67/sol1.py) - * Problem 69 - * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_69/sol1.py) - * Problem 71 - * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_71/sol1.py) - * Problem 72 - * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_72/sol1.py) - * Problem 76 - * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_76/sol1.py) - * Problem 80 - * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_80/sol1.py) - * Problem 97 - * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_97/sol1.py) - * Problem 99 - * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_99/sol1.py) * [Validate Solutions](https://github.com/TheAlgorithms/Python/blob/master/project_euler/validate_solutions.py) ## Quantum @@ -775,6 +776,7 @@ * [Remove Duplicate](https://github.com/TheAlgorithms/Python/blob/master/strings/remove_duplicate.py) * [Reverse Words](https://github.com/TheAlgorithms/Python/blob/master/strings/reverse_words.py) * [Split](https://github.com/TheAlgorithms/Python/blob/master/strings/split.py) + * [Swap Case](https://github.com/TheAlgorithms/Python/blob/master/strings/swap_case.py) * [Upper](https://github.com/TheAlgorithms/Python/blob/master/strings/upper.py) * [Word Occurrence](https://github.com/TheAlgorithms/Python/blob/master/strings/word_occurrence.py) * [Z Function](https://github.com/TheAlgorithms/Python/blob/master/strings/z_function.py) From a11900513525d33e47ed8f3d55d05aff5eadea91 Mon Sep 17 00:00:00 2001 From: fpringle Date: Thu, 15 Oct 2020 11:59:53 +0200 Subject: [PATCH 0914/1071] Add solution for Project Euler problem 113 (#3109) * Added solution for Project Euler problem 113. #2695 * Updated formatting and doctests. Reference: #3256 --- project_euler/problem_113/__init__.py | 0 project_euler/problem_113/sol1.py | 75 +++++++++++++++++++++++++++ 2 files changed, 75 insertions(+) create mode 100644 project_euler/problem_113/__init__.py create mode 100644 project_euler/problem_113/sol1.py diff --git a/project_euler/problem_113/__init__.py b/project_euler/problem_113/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/project_euler/problem_113/sol1.py b/project_euler/problem_113/sol1.py new file mode 100644 index 000000000000..951d9b49c104 --- /dev/null +++ b/project_euler/problem_113/sol1.py @@ -0,0 +1,75 @@ +""" +Project Euler Problem 113: https://projecteuler.net/problem=113 + +Working from left-to-right if no digit is exceeded by the digit to its left it is +called an increasing number; for example, 134468. + +Similarly if no digit is exceeded by the digit to its right it is called a decreasing +number; for example, 66420. + +We shall call a positive integer that is neither increasing nor decreasing a +"bouncy" number; for example, 155349. + +As n increases, the proportion of bouncy numbers below n increases such that there +are only 12951 numbers below one-million that are not bouncy and only 277032 +non-bouncy numbers below 10^10. + +How many numbers below a googol (10^100) are not bouncy? +""" + + +def choose(n: int, r: int) -> int: + """ + Calculate the binomial coefficient c(n,r) using the multiplicative formula. + >>> choose(4,2) + 6 + >>> choose(5,3) + 10 + >>> choose(20,6) + 38760 + """ + ret = 1.0 + for i in range(1, r + 1): + ret *= (n + 1 - i) / i + return round(ret) + + +def non_bouncy_exact(n: int) -> int: + """ + Calculate the number of non-bouncy numbers with at most n digits. + >>> non_bouncy_exact(1) + 9 + >>> non_bouncy_exact(6) + 7998 + >>> non_bouncy_exact(10) + 136126 + """ + return choose(8 + n, n) + choose(9 + n, n) - 10 + + +def non_bouncy_upto(n: int) -> int: + """ + Calculate the number of non-bouncy numbers with at most n digits. + >>> non_bouncy_upto(1) + 9 + >>> non_bouncy_upto(6) + 12951 + >>> non_bouncy_upto(10) + 277032 + """ + return sum(non_bouncy_exact(i) for i in range(1, n + 1)) + + +def solution(num_digits: int = 100) -> int: + """ + Caclulate the number of non-bouncy numbers less than a googol. + >>> solution(6) + 12951 + >>> solution(10) + 277032 + """ + return non_bouncy_upto(num_digits) + + +if __name__ == "__main__": + print(f"{solution() = }") From 9482f6a5a964a2ed08189a139ce23404492439e7 Mon Sep 17 00:00:00 2001 From: fpringle Date: Thu, 15 Oct 2020 12:06:40 +0200 Subject: [PATCH 0915/1071] Add solution for Project Euler problem 173. (#3075) * Added solution for Project Euler problemm problem 173. #2695 * Added docstring * Update formatting, doctest and annotations. Reference: #3256 --- project_euler/problem_173/__init__.py | 0 project_euler/problem_173/sol1.py | 41 +++++++++++++++++++++++++++ 2 files changed, 41 insertions(+) create mode 100644 project_euler/problem_173/__init__.py create mode 100644 project_euler/problem_173/sol1.py diff --git a/project_euler/problem_173/__init__.py b/project_euler/problem_173/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/project_euler/problem_173/sol1.py b/project_euler/problem_173/sol1.py new file mode 100644 index 000000000000..d539b1437ef1 --- /dev/null +++ b/project_euler/problem_173/sol1.py @@ -0,0 +1,41 @@ +""" +Project Euler Problem 173: https://projecteuler.net/problem=173 + +We shall define a square lamina to be a square outline with a square "hole" so that +the shape possesses vertical and horizontal symmetry. For example, using exactly +thirty-two square tiles we can form two different square laminae: + +With one-hundred tiles, and not necessarily using all of the tiles at one time, it is +possible to form forty-one different square laminae. + +Using up to one million tiles how many different square laminae can be formed? +""" + + +from math import ceil, sqrt + + +def solution(limit: int = 1000000) -> int: + """ + Return the number of different square laminae that can be formed using up to + one million tiles. + >>> solution(100) + 41 + """ + answer = 0 + + for outer_width in range(3, (limit // 4) + 2): + if outer_width ** 2 > limit: + hole_width_lower_bound = max(ceil(sqrt(outer_width ** 2 - limit)), 1) + else: + hole_width_lower_bound = 1 + if (outer_width - hole_width_lower_bound) % 2: + hole_width_lower_bound += 1 + + answer += (outer_width - hole_width_lower_bound - 2) // 2 + 1 + + return answer + + +if __name__ == "__main__": + print(f"{solution() = }") From e035c6164f505f1b9d15c594c845e132097c8c53 Mon Sep 17 00:00:00 2001 From: Anshraj Shrivastava <42239140+rajansh87@users.noreply.github.com> Date: Thu, 15 Oct 2020 16:39:59 +0530 Subject: [PATCH 0916/1071] add binary_tree_traversals.py to data_structures (#3297) * add binary_tree_traversals.py to data_structures I have added some interesting binary tree traversing methods. * Fixed error * Update data_structures/binary_tree/binary_tree_traversals.py Co-authored-by: Christian Clauss * Update binary_tree_traversals.py * Update binary_tree_traversals.py * Update binary_tree_traversals.py * Update data_structures/binary_tree/binary_tree_traversals.py Co-authored-by: Christian Clauss * Update binary_tree_traversals.py * Update binary_tree_traversals.py * Update binary_tree_traversals.py * Update binary_tree_traversals.py * Update binary_tree_traversals.py * Update binary_tree_traversals.py * Update binary_tree_traversals.py * Update binary_tree_traversals.py * Update binary_tree_traversals.py * Update binary_tree_traversals.py * Update data_structures/binary_tree/binary_tree_traversals.py Co-authored-by: Christian Clauss * Update binary_tree_traversals.py * Update binary_tree_traversals.py * Update data_structures/binary_tree/binary_tree_traversals.py Co-authored-by: Christian Clauss * Update data_structures/binary_tree/binary_tree_traversals.py Co-authored-by: Christian Clauss * Update binary_tree_traversals.py * Update data_structures/binary_tree/binary_tree_traversals.py Co-authored-by: Christian Clauss * Update binary_tree_traversals.py * Doctests and type hints * Add spaces * Update binary_tree_traversals.py * black exclude data_structures/binary_tree/binary_tree_traversals.py * Add spaces again * Update binary_tree_traversals.py Co-authored-by: Christian Clauss --- .pre-commit-config.yaml | 4 + .../binary_tree/binary_tree_traversals.py | 161 ++++++++++++++++++ 2 files changed, 165 insertions(+) create mode 100644 data_structures/binary_tree/binary_tree_traversals.py diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 3c08a082664a..40d90741f30a 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -18,6 +18,10 @@ repos: rev: stable hooks: - id: black + exclude: | + (?x)^( + data_structures/binary_tree/binary_tree_traversals.py + )$ - repo: https://github.com/PyCQA/isort rev: 5.5.3 hooks: diff --git a/data_structures/binary_tree/binary_tree_traversals.py b/data_structures/binary_tree/binary_tree_traversals.py new file mode 100644 index 000000000000..7c0ee1dbbc2a --- /dev/null +++ b/data_structures/binary_tree/binary_tree_traversals.py @@ -0,0 +1,161 @@ +# https://en.wikipedia.org/wiki/Tree_traversal + + +class Node: + """ + A Node has data variable and pointers to its left and right nodes. + """ + + def __init__(self, data): + self.left = None + self.right = None + self.data = data + + +def make_tree() -> Node: + root = Node(1) + root.left = Node(2) + root.right = Node(3) + root.left.left = Node(4) + root.left.right = Node(5) + return root + + +def preorder(root: Node): + """ + Pre-order traversal visits root node, left subtree, right subtree. + >>> preorder(make_tree()) + [1, 2, 4, 5, 3] + """ + return [root.data] + preorder(root.left) + preorder(root.right) if root else [] + + +def postorder(root: Node): + """ + Post-order traversal visits left subtree, right subtree, root node. + >>> postorder(make_tree()) + [4, 5, 2, 3, 1] + """ + return postorder(root.left) + postorder(root.right) + [root.data] if root else [] + + +def inorder(root: Node): + """ + In-order traversal visits left subtree, root node, right subtree. + >>> inorder(make_tree()) + [4, 2, 5, 1, 3] + """ + return inorder(root.left) + [root.data] + inorder(root.right) if root else [] + + +def height(root: Node): + """ + Recursive function for calculating the height of the binary tree. + >>> height(None) + 0 + >>> height(make_tree()) + 3 + """ + return (max(height(root.left), height(root.right)) + 1) if root else 0 + + +def level_order_1(root: Node): + """ + Print whole binary tree in Level Order Traverse. + Level Order traverse: Visit nodes of the tree level-by-level. + """ + if not root: + return + temp = root + que = [temp] + while len(que) > 0: + print(que[0].data, end=" ") + temp = que.pop(0) + if temp.left: + que.append(temp.left) + if temp.right: + que.append(temp.right) + return que + + +def level_order_2(root: Node, level: int): + """ + Level-wise traversal: Print all nodes present at the given level of the binary tree + """ + if not root: + return root + if level == 1: + print(root.data, end=" ") + elif level > 1: + level_order_2(root.left, level - 1) + level_order_2(root.right, level - 1) + + +def print_left_to_right(root: Node, level: int): + """ + Print elements on particular level from left to right direction of the binary tree. + """ + if not root: + return + if level == 1: + print(root.data, end=" ") + elif level > 1: + print_left_to_right(root.left, level - 1) + print_left_to_right(root.right, level - 1) + + +def print_right_to_left(root: Node, level: int): + """ + Print elements on particular level from right to left direction of the binary tree. + """ + if not root: + return + if level == 1: + print(root.data, end=" ") + elif level > 1: + print_right_to_left(root.right, level - 1) + print_right_to_left(root.left, level - 1) + + +def zigzag(root: Node): + """ + ZigZag traverse: Print node left to right and right to left, alternatively. + """ + flag = 0 + height_tree = height(root) + for h in range(1, height_tree + 1): + if flag == 0: + print_left_to_right(root, h) + flag = 1 + else: + print_right_to_left(root, h) + flag = 0 + + +def main(): # Main function for testing. + """ + Create binary tree. + """ + root = make_tree() + """ + All Traversals of the binary are as follows: + """ + print(f" In-order Traversal is {inorder(root)}") + print(f" Pre-order Traversal is {preorder(root)}") + print(f"Post-order Traversal is {postorder(root)}") + print(f"Height of Tree is {height(root)}") + print("Complete Level Order Traversal is : ") + level_order_1(root) + print("\nLevel-wise order Traversal is : ") + for h in range(1, height(root) + 1): + level_order_2(root, h) + print("\nZigZag order Traversal is : ") + zigzag(root) + print() + + +if __name__ == "__main__": + import doctest + + doctest.testmod() + main() From 7c5cc28ba299b15acc258bf4c7aca0258eae1d2f Mon Sep 17 00:00:00 2001 From: Christian Clauss Date: Thu, 15 Oct 2020 13:20:43 +0200 Subject: [PATCH 0917/1071] Revert recent changes to .pre-commit-config.yaml (#3318) * Revert recent changes to .pre-commit-config.yaml We must continue to insist that algorithmic functions can not print() as discussed in CONTRIBUTING.md. * updating DIRECTORY.md Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> --- .pre-commit-config.yaml | 16 +++++----------- DIRECTORY.md | 5 +++++ 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 40d90741f30a..01da6cad0335 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -6,22 +6,16 @@ repos: - id: check-yaml - id: end-of-file-fixer types: [python] - # Some doctests use print(x, sep=" ") and black will not fix inside triple quotes - # It is too confusing for new contributors to understand this and how to fix - #- id: trailing-whitespace - # exclude: | - # (?x)^( - # data_structures/heap/binomial_heap.py - # )$ + - id: trailing-whitespace + exclude: | + (?x)^( + data_structures/heap/binomial_heap.py + )$ - id: requirements-txt-fixer - repo: https://github.com/psf/black rev: stable hooks: - id: black - exclude: | - (?x)^( - data_structures/binary_tree/binary_tree_traversals.py - )$ - repo: https://github.com/PyCQA/isort rev: 5.5.3 hooks: diff --git a/DIRECTORY.md b/DIRECTORY.md index fe6b604cbb7f..d2ce99e7160d 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -107,6 +107,7 @@ * [Binary Search Tree](https://github.com/TheAlgorithms/Python/blob/master/data_structures/binary_tree/binary_search_tree.py) * [Binary Search Tree Recursive](https://github.com/TheAlgorithms/Python/blob/master/data_structures/binary_tree/binary_search_tree_recursive.py) * [Binary Tree Mirror](https://github.com/TheAlgorithms/Python/blob/master/data_structures/binary_tree/binary_tree_mirror.py) + * [Binary Tree Traversals](https://github.com/TheAlgorithms/Python/blob/master/data_structures/binary_tree/binary_tree_traversals.py) * [Fenwick Tree](https://github.com/TheAlgorithms/Python/blob/master/data_structures/binary_tree/fenwick_tree.py) * [Lazy Segment Tree](https://github.com/TheAlgorithms/Python/blob/master/data_structures/binary_tree/lazy_segment_tree.py) * [Lowest Common Ancestor](https://github.com/TheAlgorithms/Python/blob/master/data_structures/binary_tree/lowest_common_ancestor.py) @@ -675,12 +676,16 @@ * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_099/sol1.py) * Problem 112 * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_112/sol1.py) + * Problem 113 + * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_113/sol1.py) * Problem 119 * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_119/sol1.py) * Problem 120 * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_120/sol1.py) * Problem 125 * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_125/sol1.py) + * Problem 173 + * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_173/sol1.py) * Problem 191 * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_191/sol1.py) * Problem 234 From f0033f87e0b4082e55dd3641282e65369e03c03e Mon Sep 17 00:00:00 2001 From: Mozartus <32893711+Mozartuss@users.noreply.github.com> Date: Thu, 15 Oct 2020 13:45:17 +0200 Subject: [PATCH 0918/1071] Create natural_sort.py (#3286) * add natural_sort.py * fix doctest * add 're' to requirements.txt and fix spelling errors * delete 're' from requirements.txt * fixing linting errors * Update sorts/natural_sort.py Co-authored-by: Christian Clauss * Update sorts/natural_sort.py Co-authored-by: Christian Clauss * Update natural_sort.py Co-authored-by: Christian Clauss --- sorts/natural_sort.py | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) create mode 100644 sorts/natural_sort.py diff --git a/sorts/natural_sort.py b/sorts/natural_sort.py new file mode 100644 index 000000000000..001ff2cf5b41 --- /dev/null +++ b/sorts/natural_sort.py @@ -0,0 +1,36 @@ +from __future__ import annotations + +import re + + +def natural_sort(input_list: list[str]) -> list[str]: + """ + Sort the given list of strings in the way that humans expect. + + The normal Python sort algorithm sorts lexicographically, + so you might not get the results that you expect... + + >>> example1 = ['2 ft 7 in', '1 ft 5 in', '10 ft 2 in', '2 ft 11 in', '7 ft 6 in'] + >>> sorted(example1) + ['1 ft 5 in', '10 ft 2 in', '2 ft 11 in', '2 ft 7 in', '7 ft 6 in'] + >>> # The natural sort algorithm sort based on meaning and not computer code point. + >>> natural_sort(example1) + ['1 ft 5 in', '2 ft 7 in', '2 ft 11 in', '7 ft 6 in', '10 ft 2 in'] + + >>> example2 = ['Elm11', 'Elm12', 'Elm2', 'elm0', 'elm1', 'elm10', 'elm13', 'elm9'] + >>> sorted(example2) + ['Elm11', 'Elm12', 'Elm2', 'elm0', 'elm1', 'elm10', 'elm13', 'elm9'] + >>> natural_sort(example2) + ['elm0', 'elm1', 'Elm2', 'elm9', 'elm10', 'Elm11', 'Elm12', 'elm13'] + """ + + def alphanum_key(key): + return [int(s) if s.isdigit() else s.lower() for s in re.split("([0-9]+)", key)] + + return sorted(input_list, key=alphanum_key) + + +if __name__ == "__main__": + import doctest + + doctest.testmod() From 2e90debab31f8fef88fafee5a7e2b2d1ab738d30 Mon Sep 17 00:00:00 2001 From: Christian Clauss Date: Thu, 15 Oct 2020 15:07:34 +0200 Subject: [PATCH 0919/1071] Tighten up quicksort() (#3319) * Tighten up quicksort() * updating DIRECTORY.md * str does not support .pop() Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> --- DIRECTORY.md | 1 + sorts/quick_sort.py | 40 ++++++++++++++-------------------------- 2 files changed, 15 insertions(+), 26 deletions(-) diff --git a/DIRECTORY.md b/DIRECTORY.md index d2ce99e7160d..2cf51f8c4beb 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -738,6 +738,7 @@ * [Iterative Merge Sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/iterative_merge_sort.py) * [Merge Insertion Sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/merge_insertion_sort.py) * [Merge Sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/merge_sort.py) + * [Natural Sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/natural_sort.py) * [Odd Even Transposition Parallel](https://github.com/TheAlgorithms/Python/blob/master/sorts/odd_even_transposition_parallel.py) * [Odd Even Transposition Single Threaded](https://github.com/TheAlgorithms/Python/blob/master/sorts/odd_even_transposition_single_threaded.py) * [Pancake Sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/pancake_sort.py) diff --git a/sorts/quick_sort.py b/sorts/quick_sort.py index f2a55c58b437..c6687a7fa8d5 100644 --- a/sorts/quick_sort.py +++ b/sorts/quick_sort.py @@ -1,48 +1,36 @@ """ -This is a pure Python implementation of the quick sort algorithm +A pure Python implementation of the quick sort algorithm For doctests run following command: -python -m doctest -v quick_sort.py -or python3 -m doctest -v quick_sort.py For manual testing run: -python quick_sort.py +python3 quick_sort.py """ -def quick_sort(collection): - """Pure implementation of quick sort algorithm in Python +def quick_sort(collection: list) -> list: + """A pure Python implementation of quick sort algorithm - :param collection: some mutable ordered collection with heterogeneous - comparable items inside + :param collection: a mutable collection of comparable items :return: the same collection ordered by ascending Examples: >>> quick_sort([0, 5, 3, 2, 2]) [0, 2, 2, 3, 5] - >>> quick_sort([]) [] - - >>> quick_sort([-2, -5, -45]) - [-45, -5, -2] + >>> quick_sort([-2, 5, 0, -45]) + [-45, -2, 0, 5] """ - length = len(collection) - if length <= 1: + if len(collection) < 2: return collection - else: - # Use the last element as the first pivot - pivot = collection.pop() - # Put elements greater than pivot in greater list - # Put elements lesser than pivot in lesser list - greater, lesser = [], [] - for element in collection: - if element > pivot: - greater.append(element) - else: - lesser.append(element) - return quick_sort(lesser) + [pivot] + quick_sort(greater) + pivot = collection.pop() # Use the last element as the first pivot + greater = [] # All elements greater than pivot + lesser = [] # All elements less than or equal to pivot + for element in collection: + (greater if element > pivot else lesser).append(element) + return quick_sort(lesser) + [pivot] + quick_sort(greater) if __name__ == "__main__": From 316fc41d6d6e2b5d9bd129d32e36b7fedf895a81 Mon Sep 17 00:00:00 2001 From: fpringle Date: Thu, 15 Oct 2020 20:58:15 +0200 Subject: [PATCH 0920/1071] Add solution for Project Euler problem 74. (#3125) * Added solution for Project Euler problem 74. Fixes: #2695 * Added doctest for solution() in project_euler/problem_74/sol1.py * Update docstrings and 0-padding of directory name. Reference: #3256 --- project_euler/problem_074/__init__.py | 0 project_euler/problem_074/sol1.py | 111 ++++++++++++++++++++++++++ 2 files changed, 111 insertions(+) create mode 100644 project_euler/problem_074/__init__.py create mode 100644 project_euler/problem_074/sol1.py diff --git a/project_euler/problem_074/__init__.py b/project_euler/problem_074/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/project_euler/problem_074/sol1.py b/project_euler/problem_074/sol1.py new file mode 100644 index 000000000000..5e6aff6f52f2 --- /dev/null +++ b/project_euler/problem_074/sol1.py @@ -0,0 +1,111 @@ +""" +Project Euler Problem 74: https://projecteuler.net/problem=74 + +The number 145 is well known for the property that the sum of the factorial of its +digits is equal to 145: + +1! + 4! + 5! = 1 + 24 + 120 = 145 + +Perhaps less well known is 169, in that it produces the longest chain of numbers that +link back to 169; it turns out that there are only three such loops that exist: + +169 → 363601 → 1454 → 169 +871 → 45361 → 871 +872 → 45362 → 872 + +It is not difficult to prove that EVERY starting number will eventually get stuck in +a loop. For example, + +69 → 363600 → 1454 → 169 → 363601 (→ 1454) +78 → 45360 → 871 → 45361 (→ 871) +540 → 145 (→ 145) + +Starting with 69 produces a chain of five non-repeating terms, but the longest +non-repeating chain with a starting number below one million is sixty terms. + +How many chains, with a starting number below one million, contain exactly sixty +non-repeating terms? +""" + + +DIGIT_FACTORIALS = { + "0": 1, + "1": 1, + "2": 2, + "3": 6, + "4": 24, + "5": 120, + "6": 720, + "7": 5040, + "8": 40320, + "9": 362880, +} + +CACHE_SUM_DIGIT_FACTORIALS = {145: 145} + +CHAIN_LENGTH_CACHE = { + 145: 0, + 169: 3, + 36301: 3, + 1454: 3, + 871: 2, + 45361: 2, + 872: 2, + 45361: 2, +} + + +def sum_digit_factorials(n: int) -> int: + """ + Return the sum of the factorial of the digits of n. + >>> sum_digit_factorials(145) + 145 + >>> sum_digit_factorials(45361) + 871 + >>> sum_digit_factorials(540) + 145 + """ + if n in CACHE_SUM_DIGIT_FACTORIALS: + return CACHE_SUM_DIGIT_FACTORIALS[n] + ret = sum([DIGIT_FACTORIALS[let] for let in str(n)]) + CACHE_SUM_DIGIT_FACTORIALS[n] = ret + return ret + + +def chain_length(n: int, previous: set = None) -> int: + """ + Calculate the length of the chain of non-repeating terms starting with n. + Previous is a set containing the previous member of the chain. + >>> chain_length(10101) + 11 + >>> chain_length(555) + 20 + >>> chain_length(178924) + 39 + """ + previous = previous or set() + if n in CHAIN_LENGTH_CACHE: + return CHAIN_LENGTH_CACHE[n] + next_number = sum_digit_factorials(n) + if next_number in previous: + CHAIN_LENGTH_CACHE[n] = 0 + return 0 + else: + previous.add(n) + ret = 1 + chain_length(next_number, previous) + CHAIN_LENGTH_CACHE[n] = ret + return ret + + +def solution(num_terms: int = 60, max_start: int = 1000000) -> int: + """ + Return the number of chains with a starting number below one million which + contain exactly n non-repeating terms. + >>> solution(10,1000) + 28 + """ + return sum(1 for i in range(1, max_start) if chain_length(i) == num_terms) + + +if __name__ == "__main__": + print(f"{solution() = }") From 83b825027e33c2885b6d9c9f2fd06f848f449f10 Mon Sep 17 00:00:00 2001 From: Meysam Date: Thu, 15 Oct 2020 22:38:52 +0330 Subject: [PATCH 0921/1071] Graphs/kruskal: adding doctest & type hints (#3101) * graphs/kruskal: add doctest & type hints this is a child of a previous PR #2443 its ancestor is #2128 * updating DIRECTORY.md * graphs/kruskal: fix max-line-length violation * fixup! Format Python code with psf/black push Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> --- graphs/minimum_spanning_tree_kruskal.py | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/graphs/minimum_spanning_tree_kruskal.py b/graphs/minimum_spanning_tree_kruskal.py index 610baf4b5fe6..a51f970341f7 100644 --- a/graphs/minimum_spanning_tree_kruskal.py +++ b/graphs/minimum_spanning_tree_kruskal.py @@ -1,4 +1,18 @@ -def kruskal(num_nodes, num_edges, edges): +from typing import List, Tuple + + +def kruskal(num_nodes: int, num_edges: int, edges: List[Tuple[int, int, int]]) -> int: + """ + >>> kruskal(4, 3, [(0, 1, 3), (1, 2, 5), (2, 3, 1)]) + [(2, 3, 1), (0, 1, 3), (1, 2, 5)] + + >>> kruskal(4, 5, [(0, 1, 3), (1, 2, 5), (2, 3, 1), (0, 2, 1), (0, 3, 2)]) + [(2, 3, 1), (0, 2, 1), (0, 1, 3)] + + >>> kruskal(4, 6, [(0, 1, 3), (1, 2, 5), (2, 3, 1), (0, 2, 1), (0, 3, 2), + ... (2, 1, 1)]) + [(2, 3, 1), (0, 2, 1), (2, 1, 1)] + """ edges = sorted(edges, key=lambda edge: edge[2]) parent = list(range(num_nodes)) From cc050dbf5b5591300839bdd168d095901377c151 Mon Sep 17 00:00:00 2001 From: Meysam Date: Thu, 15 Oct 2020 22:40:35 +0330 Subject: [PATCH 0922/1071] test/graphs/prim: writing a test case to verify the correctness of the algorithm (#2454) --- graphs/minimum_spanning_tree_prims.py | 2 +- graphs/tests/test_min_spanning_tree_prim.py | 47 +++++++++++++++++++++ 2 files changed, 48 insertions(+), 1 deletion(-) create mode 100644 graphs/tests/test_min_spanning_tree_prim.py diff --git a/graphs/minimum_spanning_tree_prims.py b/graphs/minimum_spanning_tree_prims.py index 527f3cf98c20..16b4286140ec 100644 --- a/graphs/minimum_spanning_tree_prims.py +++ b/graphs/minimum_spanning_tree_prims.py @@ -104,7 +104,7 @@ def deleteMinimum(heap, positions): return TreeEdges -if __name__ == "__main__": +if __name__ == "__main__": # pragma: no cover # < --------- Prims Algorithm --------- > n = int(input("Enter number of vertices: ").strip()) e = int(input("Enter number of edges: ").strip()) diff --git a/graphs/tests/test_min_spanning_tree_prim.py b/graphs/tests/test_min_spanning_tree_prim.py new file mode 100644 index 000000000000..9f3ec5c4712f --- /dev/null +++ b/graphs/tests/test_min_spanning_tree_prim.py @@ -0,0 +1,47 @@ +from collections import defaultdict + + +from graphs.minimum_spanning_tree_prims import PrimsAlgorithm as mst + + +def test_prim_successful_result(): + num_nodes, num_edges = 9, 14 # noqa: F841 + edges = [ + [0, 1, 4], + [0, 7, 8], + [1, 2, 8], + [7, 8, 7], + [7, 6, 1], + [2, 8, 2], + [8, 6, 6], + [2, 3, 7], + [2, 5, 4], + [6, 5, 2], + [3, 5, 14], + [3, 4, 9], + [5, 4, 10], + [1, 7, 11], + ] + + adjancency = defaultdict(list) + for node1, node2, cost in edges: + adjancency[node1].append([node2, cost]) + adjancency[node2].append([node1, cost]) + + result = mst(adjancency) + + expected = [ + [7, 6, 1], + [2, 8, 2], + [6, 5, 2], + [0, 1, 4], + [2, 5, 4], + [2, 3, 7], + [0, 7, 8], + [3, 4, 9], + ] + + for answer in expected: + edge = tuple(answer[:2]) + reverse = tuple(edge[::-1]) + assert edge in result or reverse in result From 5b024f4dd5a087c950c52301ce465dbeb4a6de43 Mon Sep 17 00:00:00 2001 From: Christian Clauss Date: Fri, 16 Oct 2020 00:33:25 +0200 Subject: [PATCH 0923/1071] BROKEN BUILD: Fix a failing precommit test (#3344) * Fix a failing precommit test * updating DIRECTORY.md Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> --- DIRECTORY.md | 3 +++ graphs/tests/test_min_spanning_tree_prim.py | 1 - 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/DIRECTORY.md b/DIRECTORY.md index 2cf51f8c4beb..4e67ad5156d9 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -302,6 +302,7 @@ * [Tarjans Scc](https://github.com/TheAlgorithms/Python/blob/master/graphs/tarjans_scc.py) * Tests * [Test Min Spanning Tree Kruskal](https://github.com/TheAlgorithms/Python/blob/master/graphs/tests/test_min_spanning_tree_kruskal.py) + * [Test Min Spanning Tree Prim](https://github.com/TheAlgorithms/Python/blob/master/graphs/tests/test_min_spanning_tree_prim.py) ## Greedy Method * [Greedy Knapsack](https://github.com/TheAlgorithms/Python/blob/master/greedy_method/greedy_knapsack.py) @@ -666,6 +667,8 @@ * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_071/sol1.py) * Problem 072 * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_072/sol1.py) + * Problem 074 + * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_074/sol1.py) * Problem 076 * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_076/sol1.py) * Problem 080 diff --git a/graphs/tests/test_min_spanning_tree_prim.py b/graphs/tests/test_min_spanning_tree_prim.py index 9f3ec5c4712f..048fbf595fa6 100644 --- a/graphs/tests/test_min_spanning_tree_prim.py +++ b/graphs/tests/test_min_spanning_tree_prim.py @@ -1,6 +1,5 @@ from collections import defaultdict - from graphs.minimum_spanning_tree_prims import PrimsAlgorithm as mst From 9d745b6156636f9e3b35f1560ae9abec29d48772 Mon Sep 17 00:00:00 2001 From: Jenia Dysin Date: Fri, 16 Oct 2020 09:11:52 +0300 Subject: [PATCH 0924/1071] Add typehints ciphers and bool alg (#3264) * updating DIRECTORY.md * updating DIRECTORY.md * Fixed accidental commit of file I have't touched * fixup! Format Python code with psf/black push * updating DIRECTORY.md * updating DIRECTORY.md * Fixed some suggested coding style issues * Update rsa_key_generator.py * Update rsa_key_generator.py Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> Co-authored-by: John Law --- boolean_algebra/quine_mc_cluskey.py | 12 +++++----- ciphers/affine_cipher.py | 4 ++-- ciphers/base64_cipher.py | 4 ++-- ciphers/brute_force_caesar_cipher.py | 2 +- ciphers/cryptomath_module.py | 4 ++-- ciphers/decrypt_caesar_with_chi_squared.py | 8 ++++--- ciphers/deterministic_miller_rabin.py | 4 ++-- ciphers/diffie.py | 2 +- ciphers/elgamal_key_generator.py | 6 ++--- ciphers/hill_cipher.py | 2 +- ciphers/mixed_keyword_cypher.py | 2 +- ciphers/morse_code_implementation.py | 4 ++-- ciphers/onepad_cipher.py | 4 ++-- ciphers/playfair_cipher.py | 8 +++---- ciphers/porta_cipher.py | 10 ++++---- ciphers/rabin_miller.py | 6 ++--- ciphers/rot13.py | 2 +- ciphers/rsa_cipher.py | 28 +++++++++++++++------- ciphers/rsa_factorization.py | 2 +- ciphers/rsa_key_generator.py | 5 ++-- ciphers/simple_substitution_cipher.py | 8 +++---- ciphers/trafid_cipher.py | 14 +++++++---- ciphers/transposition_cipher.py | 4 ++-- ciphers/vigenere_cipher.py | 6 ++--- ciphers/xor_cipher.py | 14 +++++------ 25 files changed, 92 insertions(+), 73 deletions(-) diff --git a/boolean_algebra/quine_mc_cluskey.py b/boolean_algebra/quine_mc_cluskey.py index a55b624483ca..19bac336f6c5 100644 --- a/boolean_algebra/quine_mc_cluskey.py +++ b/boolean_algebra/quine_mc_cluskey.py @@ -1,4 +1,4 @@ -def compare_string(string1, string2): +def compare_string(string1: str, string2: str) -> str: """ >>> compare_string('0010','0110') '0_10' @@ -19,7 +19,7 @@ def compare_string(string1, string2): return "".join(l1) -def check(binary): +def check(binary: [str]) -> [str]: """ >>> check(['0.00.01.5']) ['0.00.01.5'] @@ -43,7 +43,7 @@ def check(binary): binary = list(set(temp)) -def decimal_to_binary(no_of_variable, minterms): +def decimal_to_binary(no_of_variable: int, minterms: [float]) -> [str]: """ >>> decimal_to_binary(3,[1.5]) ['0.00.01.5'] @@ -59,7 +59,7 @@ def decimal_to_binary(no_of_variable, minterms): return temp -def is_for_table(string1, string2, count): +def is_for_table(string1: str, string2: str, count: int) -> bool: """ >>> is_for_table('__1','011',2) True @@ -79,7 +79,7 @@ def is_for_table(string1, string2, count): return False -def selection(chart, prime_implicants): +def selection(chart: [[int]], prime_implicants: [str]) -> [str]: """ >>> selection([[1]],['0.00.01.5']) ['0.00.01.5'] @@ -126,7 +126,7 @@ def selection(chart, prime_implicants): chart[j][i] = 0 -def prime_implicant_chart(prime_implicants, binary): +def prime_implicant_chart(prime_implicants: [str], binary: [str]) -> [[int]]: """ >>> prime_implicant_chart(['0.00.01.5'],['0.00.01.5']) [[1]] diff --git a/ciphers/affine_cipher.py b/ciphers/affine_cipher.py index 1b1943a3798d..cf8c0d5f4c1d 100644 --- a/ciphers/affine_cipher.py +++ b/ciphers/affine_cipher.py @@ -29,7 +29,7 @@ def main(): print(f"\n{mode.title()}ed text: \n{translated}") -def check_keys(keyA, keyB, mode): +def check_keys(keyA: int, keyB: int, mode: str) -> None: if mode == "encrypt": if keyA == 1: sys.exit( @@ -90,7 +90,7 @@ def decrypt_message(key: int, message: str) -> str: return plainText -def get_random_key(): +def get_random_key() -> int: while True: keyA = random.randint(2, len(SYMBOLS)) keyB = random.randint(2, len(SYMBOLS)) diff --git a/ciphers/base64_cipher.py b/ciphers/base64_cipher.py index 338476934f28..1dbe74a20fe7 100644 --- a/ciphers/base64_cipher.py +++ b/ciphers/base64_cipher.py @@ -1,4 +1,4 @@ -def encode_base64(text): +def encode_base64(text: str) -> str: r""" >>> encode_base64('WELCOME to base64 encoding 😁') 'V0VMQ09NRSB0byBiYXNlNjQgZW5jb2Rpbmcg8J+YgQ==' @@ -33,7 +33,7 @@ def encode_base64(text): return r[0 : len(r) - len(p)] + p -def decode_base64(text): +def decode_base64(text: str) -> str: r""" >>> decode_base64('V0VMQ09NRSB0byBiYXNlNjQgZW5jb2Rpbmcg8J+YgQ==') 'WELCOME to base64 encoding 😁' diff --git a/ciphers/brute_force_caesar_cipher.py b/ciphers/brute_force_caesar_cipher.py index 5f11cb848c41..13a165245403 100644 --- a/ciphers/brute_force_caesar_cipher.py +++ b/ciphers/brute_force_caesar_cipher.py @@ -1,4 +1,4 @@ -def decrypt(message): +def decrypt(message: str) -> None: """ >>> decrypt('TMDETUX PMDVU') Decryption using Key #0: TMDETUX PMDVU diff --git a/ciphers/cryptomath_module.py b/ciphers/cryptomath_module.py index fc38e4bd2a22..ffeac1617f64 100644 --- a/ciphers/cryptomath_module.py +++ b/ciphers/cryptomath_module.py @@ -1,10 +1,10 @@ -def gcd(a, b): +def gcd(a: int, b: int) -> int: while a != 0: a, b = b % a, a return b -def findModInverse(a, m): +def findModInverse(a: int, m: int) -> int: if gcd(a, m) != 1: return None u1, u2, u3 = 1, 0, a diff --git a/ciphers/decrypt_caesar_with_chi_squared.py b/ciphers/decrypt_caesar_with_chi_squared.py index 4036f9bdc43a..41b4a12ba453 100644 --- a/ciphers/decrypt_caesar_with_chi_squared.py +++ b/ciphers/decrypt_caesar_with_chi_squared.py @@ -1,12 +1,14 @@ #!/usr/bin/env python3 +from typing import Tuple + def decrypt_caesar_with_chi_squared( ciphertext: str, - cipher_alphabet=None, - frequencies_dict=None, + cipher_alphabet: str = None, + frequencies_dict: str = None, case_sensetive: bool = False, -) -> tuple: +) -> Tuple[int, float, str]: """ Basic Usage =========== diff --git a/ciphers/deterministic_miller_rabin.py b/ciphers/deterministic_miller_rabin.py index e604a7b84166..d7fcb67e936c 100644 --- a/ciphers/deterministic_miller_rabin.py +++ b/ciphers/deterministic_miller_rabin.py @@ -3,7 +3,7 @@ """ -def miller_rabin(n, allow_probable=False): +def miller_rabin(n: int, allow_probable: bool = False) -> bool: """Deterministic Miller-Rabin algorithm for primes ~< 3.32e24. Uses numerical analysis results to return whether or not the passed number @@ -87,7 +87,7 @@ def miller_rabin(n, allow_probable=False): return True -def test_miller_rabin(): +def test_miller_rabin() -> None: """Testing a nontrivial (ends in 1, 3, 7, 9) composite and a prime in each range. """ diff --git a/ciphers/diffie.py b/ciphers/diffie.py index c349aaa2f3b8..44b12bf9d103 100644 --- a/ciphers/diffie.py +++ b/ciphers/diffie.py @@ -1,4 +1,4 @@ -def find_primitive(n): +def find_primitive(n: int) -> int: for r in range(1, n): li = [] for x in range(n - 1): diff --git a/ciphers/elgamal_key_generator.py b/ciphers/elgamal_key_generator.py index 5848e7e707e6..52cf69074187 100644 --- a/ciphers/elgamal_key_generator.py +++ b/ciphers/elgamal_key_generator.py @@ -19,7 +19,7 @@ def main(): # so I used 4.80 Algorithm in # Handbook of Applied Cryptography(CRC Press, ISBN : 0-8493-8523-7, October 1996) # and it seems to run nicely! -def primitiveRoot(p_val): +def primitiveRoot(p_val: int) -> int: print("Generating primitive root of p") while True: g = random.randrange(3, p_val) @@ -30,7 +30,7 @@ def primitiveRoot(p_val): return g -def generateKey(keySize): +def generateKey(keySize: int) -> ((int, int, int, int), (int, int)): print("Generating prime p...") p = rabinMiller.generateLargePrime(keySize) # select large prime number. e_1 = primitiveRoot(p) # one primitive root on modulo p. @@ -43,7 +43,7 @@ def generateKey(keySize): return publicKey, privateKey -def makeKeyFiles(name, keySize): +def makeKeyFiles(name: str, keySize: int): if os.path.exists("%s_pubkey.txt" % name) or os.path.exists( "%s_privkey.txt" % name ): diff --git a/ciphers/hill_cipher.py b/ciphers/hill_cipher.py index 0014c8693bc6..3dabcd3fceab 100644 --- a/ciphers/hill_cipher.py +++ b/ciphers/hill_cipher.py @@ -64,7 +64,7 @@ class HillCipher: to_int = numpy.vectorize(lambda x: round(x)) - def __init__(self, encrypt_key): + def __init__(self, encrypt_key: int): """ encrypt_key is an NxN numpy array """ diff --git a/ciphers/mixed_keyword_cypher.py b/ciphers/mixed_keyword_cypher.py index 6c5d6dc1d210..59298d310ce0 100644 --- a/ciphers/mixed_keyword_cypher.py +++ b/ciphers/mixed_keyword_cypher.py @@ -1,4 +1,4 @@ -def mixed_keyword(key="college", pt="UNIVERSITY"): +def mixed_keyword(key: str = "college", pt: str = "UNIVERSITY") -> str: """ For key:hello diff --git a/ciphers/morse_code_implementation.py b/ciphers/morse_code_implementation.py index 04af8fcf6fdf..1cce2ef8b386 100644 --- a/ciphers/morse_code_implementation.py +++ b/ciphers/morse_code_implementation.py @@ -57,7 +57,7 @@ } -def encrypt(message): +def encrypt(message: str) -> str: cipher = "" for letter in message: if letter != " ": @@ -69,7 +69,7 @@ def encrypt(message): return cipher[:-1] -def decrypt(message): +def decrypt(message: str) -> str: decipher = "" letters = message.split(" ") for letter in letters: diff --git a/ciphers/onepad_cipher.py b/ciphers/onepad_cipher.py index fe07908afff5..a91f2b4d31c5 100644 --- a/ciphers/onepad_cipher.py +++ b/ciphers/onepad_cipher.py @@ -2,7 +2,7 @@ class Onepad: - def encrypt(self, text): + def encrypt(self, text: str) -> ([str], [int]): """Function to encrypt text using pseudo-random numbers""" plain = [ord(i) for i in text] key = [] @@ -14,7 +14,7 @@ def encrypt(self, text): key.append(k) return cipher, key - def decrypt(self, cipher, key): + def decrypt(self, cipher: [str], key: [int]) -> str: """Function to decrypt text using pseudo-random numbers.""" plain = [] for i in range(len(key)): diff --git a/ciphers/playfair_cipher.py b/ciphers/playfair_cipher.py index 33b52906fb05..219437448e53 100644 --- a/ciphers/playfair_cipher.py +++ b/ciphers/playfair_cipher.py @@ -11,7 +11,7 @@ def chunker(seq, size): yield chunk -def prepare_input(dirty): +def prepare_input(dirty: str) -> str: """ Prepare the plaintext by up-casing it and separating repeated letters with X's @@ -37,7 +37,7 @@ def prepare_input(dirty): return clean -def generate_table(key): +def generate_table(key: str) -> [str]: # I and J are used interchangeably to allow # us to use a 5x5 table (25 letters) @@ -59,7 +59,7 @@ def generate_table(key): return table -def encode(plaintext, key): +def encode(plaintext: str, key: str) -> str: table = generate_table(key) plaintext = prepare_input(plaintext) ciphertext = "" @@ -82,7 +82,7 @@ def encode(plaintext, key): return ciphertext -def decode(ciphertext, key): +def decode(ciphertext: str, key: str) -> str: table = generate_table(key) plaintext = "" diff --git a/ciphers/porta_cipher.py b/ciphers/porta_cipher.py index a8e79415958d..29043c4c9fac 100644 --- a/ciphers/porta_cipher.py +++ b/ciphers/porta_cipher.py @@ -28,7 +28,7 @@ } -def generate_table(key): +def generate_table(key: str) -> [(str, str)]: """ >>> generate_table('marvin') # doctest: +NORMALIZE_WHITESPACE [('ABCDEFGHIJKLM', 'UVWXYZNOPQRST'), ('ABCDEFGHIJKLM', 'NOPQRSTUVWXYZ'), @@ -38,7 +38,7 @@ def generate_table(key): return [alphabet[char] for char in key.upper()] -def encrypt(key, words): +def encrypt(key: str, words: str) -> str: """ >>> encrypt('marvin', 'jessica') 'QRACRWU' @@ -52,7 +52,7 @@ def encrypt(key, words): return cipher -def decrypt(key, words): +def decrypt(key: str, words: str) -> str: """ >>> decrypt('marvin', 'QRACRWU') 'JESSICA' @@ -60,7 +60,7 @@ def decrypt(key, words): return encrypt(key, words) -def get_position(table, char): +def get_position(table: [(str, str)], char: str) -> (int, int) or (None, None): """ >>> table = [ ... ('ABCDEFGHIJKLM', 'UVWXYZNOPQRST'), ('ABCDEFGHIJKLM', 'NOPQRSTUVWXYZ'), @@ -76,7 +76,7 @@ def get_position(table, char): return (None, None) if row == -1 else (row, table[row].index(char)) -def get_opponent(table, char): +def get_opponent(table: [(str, str)], char: str) -> str: """ >>> table = [ ... ('ABCDEFGHIJKLM', 'UVWXYZNOPQRST'), ('ABCDEFGHIJKLM', 'NOPQRSTUVWXYZ'), diff --git a/ciphers/rabin_miller.py b/ciphers/rabin_miller.py index c544abdf9acc..65c162984ece 100644 --- a/ciphers/rabin_miller.py +++ b/ciphers/rabin_miller.py @@ -3,7 +3,7 @@ import random -def rabinMiller(num): +def rabinMiller(num: int) -> bool: s = num - 1 t = 0 @@ -25,7 +25,7 @@ def rabinMiller(num): return True -def isPrime(num): +def isPrime(num: int) -> bool: if num < 2: return False @@ -210,7 +210,7 @@ def isPrime(num): return rabinMiller(num) -def generateLargePrime(keysize=1024): +def generateLargePrime(keysize: int = 1024) -> int: while True: num = random.randrange(2 ** (keysize - 1), 2 ** (keysize)) if isPrime(num): diff --git a/ciphers/rot13.py b/ciphers/rot13.py index 6bcb471d6e05..21dbda98eecc 100644 --- a/ciphers/rot13.py +++ b/ciphers/rot13.py @@ -1,4 +1,4 @@ -def dencrypt(s: str, n: int = 13): +def dencrypt(s: str, n: int = 13) -> str: """ https://en.wikipedia.org/wiki/ROT13 diff --git a/ciphers/rsa_cipher.py b/ciphers/rsa_cipher.py index fad0d6e60074..57c916a44d4b 100644 --- a/ciphers/rsa_cipher.py +++ b/ciphers/rsa_cipher.py @@ -40,7 +40,7 @@ def main(): print(decryptedText) -def getBlocksFromText(message, blockSize=DEFAULT_BLOCK_SIZE): +def getBlocksFromText(message: int, blockSize: int = DEFAULT_BLOCK_SIZE) -> [int]: messageBytes = message.encode("ascii") blockInts = [] @@ -52,7 +52,9 @@ def getBlocksFromText(message, blockSize=DEFAULT_BLOCK_SIZE): return blockInts -def getTextFromBlocks(blockInts, messageLength, blockSize=DEFAULT_BLOCK_SIZE): +def getTextFromBlocks( + blockInts: [int], messageLength: int, blockSize: int = DEFAULT_BLOCK_SIZE +) -> str: message = [] for blockInt in blockInts: blockMessage = [] @@ -65,7 +67,9 @@ def getTextFromBlocks(blockInts, messageLength, blockSize=DEFAULT_BLOCK_SIZE): return "".join(message) -def encryptMessage(message, key, blockSize=DEFAULT_BLOCK_SIZE): +def encryptMessage( + message: str, key: (int, int), blockSize: int = DEFAULT_BLOCK_SIZE +) -> [int]: encryptedBlocks = [] n, e = key @@ -74,7 +78,12 @@ def encryptMessage(message, key, blockSize=DEFAULT_BLOCK_SIZE): return encryptedBlocks -def decryptMessage(encryptedBlocks, messageLength, key, blockSize=DEFAULT_BLOCK_SIZE): +def decryptMessage( + encryptedBlocks: [int], + messageLength: int, + key: (int, int), + blockSize: int = DEFAULT_BLOCK_SIZE, +) -> str: decryptedBlocks = [] n, d = key for block in encryptedBlocks: @@ -82,7 +91,7 @@ def decryptMessage(encryptedBlocks, messageLength, key, blockSize=DEFAULT_BLOCK_ return getTextFromBlocks(decryptedBlocks, messageLength, blockSize) -def readKeyFile(keyFilename): +def readKeyFile(keyFilename: str) -> (int, int, int): with open(keyFilename) as fo: content = fo.read() keySize, n, EorD = content.split(",") @@ -90,8 +99,11 @@ def readKeyFile(keyFilename): def encryptAndWriteToFile( - messageFilename, keyFilename, message, blockSize=DEFAULT_BLOCK_SIZE -): + messageFilename: str, + keyFilename: str, + message: str, + blockSize: int = DEFAULT_BLOCK_SIZE, +) -> str: keySize, n, e = readKeyFile(keyFilename) if keySize < blockSize * 8: sys.exit( @@ -112,7 +124,7 @@ def encryptAndWriteToFile( return encryptedContent -def readFromFileAndDecrypt(messageFilename, keyFilename): +def readFromFileAndDecrypt(messageFilename: str, keyFilename: str) -> str: keySize, n, d = readKeyFile(keyFilename) with open(messageFilename) as fo: content = fo.read() diff --git a/ciphers/rsa_factorization.py b/ciphers/rsa_factorization.py index 6df32b6cc887..b18aab609e2d 100644 --- a/ciphers/rsa_factorization.py +++ b/ciphers/rsa_factorization.py @@ -13,7 +13,7 @@ import random -def rsafactor(d: int, e: int, N: int) -> list[int]: +def rsafactor(d: int, e: int, N: int) -> [int]: """ This function returns the factors of N, where p*q=N Return: [p, q] diff --git a/ciphers/rsa_key_generator.py b/ciphers/rsa_key_generator.py index 315928d4b60c..5693aa637ee9 100644 --- a/ciphers/rsa_key_generator.py +++ b/ciphers/rsa_key_generator.py @@ -1,6 +1,7 @@ import os import random import sys +from typing import Tuple from . import cryptomath_module as cryptoMath from . import rabin_miller as rabinMiller @@ -12,7 +13,7 @@ def main(): print("Key files generation successful.") -def generateKey(keySize): +def generateKey(keySize: int) -> Tuple[Tuple[int, int], Tuple[int, int]]: print("Generating prime p...") p = rabinMiller.generateLargePrime(keySize) print("Generating prime q...") @@ -33,7 +34,7 @@ def generateKey(keySize): return (publicKey, privateKey) -def makeKeyFiles(name, keySize): +def makeKeyFiles(name: int, keySize: int) -> None: if os.path.exists("%s_pubkey.txt" % (name)) or os.path.exists( "%s_privkey.txt" % (name) ): diff --git a/ciphers/simple_substitution_cipher.py b/ciphers/simple_substitution_cipher.py index 4c6d58ceca46..f5b711e616af 100644 --- a/ciphers/simple_substitution_cipher.py +++ b/ciphers/simple_substitution_cipher.py @@ -21,7 +21,7 @@ def main(): print("\n{}ion: \n{}".format(mode.title(), translated)) -def checkValidKey(key): +def checkValidKey(key: str) -> None: keyList = list(key) lettersList = list(LETTERS) keyList.sort() @@ -31,7 +31,7 @@ def checkValidKey(key): sys.exit("Error in the key or symbol set.") -def encryptMessage(key, message): +def encryptMessage(key: str, message: str) -> str: """ >>> encryptMessage('LFWOAYUISVKMNXPBDCRJTQEGHZ', 'Harshil Darji') 'Ilcrism Olcvs' @@ -39,7 +39,7 @@ def encryptMessage(key, message): return translateMessage(key, message, "encrypt") -def decryptMessage(key, message): +def decryptMessage(key: str, message: str) -> str: """ >>> decryptMessage('LFWOAYUISVKMNXPBDCRJTQEGHZ', 'Ilcrism Olcvs') 'Harshil Darji' @@ -47,7 +47,7 @@ def decryptMessage(key, message): return translateMessage(key, message, "decrypt") -def translateMessage(key, message, mode): +def translateMessage(key: str, message: str, mode: str) -> str: translated = "" charsA = LETTERS charsB = key diff --git a/ciphers/trafid_cipher.py b/ciphers/trafid_cipher.py index f1c954b5c34f..328814f97744 100644 --- a/ciphers/trafid_cipher.py +++ b/ciphers/trafid_cipher.py @@ -1,7 +1,7 @@ # https://en.wikipedia.org/wiki/Trifid_cipher -def __encryptPart(messagePart, character2Number): +def __encryptPart(messagePart: str, character2Number: dict) -> str: one, two, three = "", "", "" tmp = [] @@ -16,7 +16,7 @@ def __encryptPart(messagePart, character2Number): return one + two + three -def __decryptPart(messagePart, character2Number): +def __decryptPart(messagePart: str, character2Number: dict) -> (str, str, str): tmp, thisPart = "", "" result = [] @@ -32,7 +32,7 @@ def __decryptPart(messagePart, character2Number): return result[0], result[1], result[2] -def __prepare(message, alphabet): +def __prepare(message: str, alphabet: str) -> (str, str, dict, dict): # Validate message and alphabet, set to upper and remove spaces alphabet = alphabet.replace(" ", "").upper() message = message.replace(" ", "").upper() @@ -83,7 +83,9 @@ def __prepare(message, alphabet): return message, alphabet, character2Number, number2Character -def encryptMessage(message, alphabet="ABCDEFGHIJKLMNOPQRSTUVWXYZ.", period=5): +def encryptMessage( + message: str, alphabet: str = "ABCDEFGHIJKLMNOPQRSTUVWXYZ.", period: int = 5 +) -> str: message, alphabet, character2Number, number2Character = __prepare(message, alphabet) encrypted, encrypted_numeric = "", "" @@ -96,7 +98,9 @@ def encryptMessage(message, alphabet="ABCDEFGHIJKLMNOPQRSTUVWXYZ.", period=5): return encrypted -def decryptMessage(message, alphabet="ABCDEFGHIJKLMNOPQRSTUVWXYZ.", period=5): +def decryptMessage( + message: str, alphabet: str = "ABCDEFGHIJKLMNOPQRSTUVWXYZ.", period: int = 5 +) -> str: message, alphabet, character2Number, number2Character = __prepare(message, alphabet) decrypted_numeric = [] decrypted = "" diff --git a/ciphers/transposition_cipher.py b/ciphers/transposition_cipher.py index 4bba88955433..6a0a22d3e31d 100644 --- a/ciphers/transposition_cipher.py +++ b/ciphers/transposition_cipher.py @@ -22,7 +22,7 @@ def main(): print("Output:\n%s" % (text + "|")) -def encryptMessage(key, message): +def encryptMessage(key: int, message: str) -> str: """ >>> encryptMessage(6, 'Harshil Darji') 'Hlia rDsahrij' @@ -36,7 +36,7 @@ def encryptMessage(key, message): return "".join(cipherText) -def decryptMessage(key, message): +def decryptMessage(key: int, message: str) -> str: """ >>> decryptMessage(6, 'Hlia rDsahrij') 'Harshil Darji' diff --git a/ciphers/vigenere_cipher.py b/ciphers/vigenere_cipher.py index 6c10e7d773f2..eb523d078005 100644 --- a/ciphers/vigenere_cipher.py +++ b/ciphers/vigenere_cipher.py @@ -17,7 +17,7 @@ def main(): print(translated) -def encryptMessage(key, message): +def encryptMessage(key: str, message: str) -> str: """ >>> encryptMessage('HDarji', 'This is Harshil Darji from Dharmaj.') 'Akij ra Odrjqqs Gaisq muod Mphumrs.' @@ -25,7 +25,7 @@ def encryptMessage(key, message): return translateMessage(key, message, "encrypt") -def decryptMessage(key, message): +def decryptMessage(key: str, message: str) -> str: """ >>> decryptMessage('HDarji', 'Akij ra Odrjqqs Gaisq muod Mphumrs.') 'This is Harshil Darji from Dharmaj.' @@ -33,7 +33,7 @@ def decryptMessage(key, message): return translateMessage(key, message, "decrypt") -def translateMessage(key, message, mode): +def translateMessage(key: str, message: str, mode: str) -> str: translated = [] keyIndex = 0 key = key.upper() diff --git a/ciphers/xor_cipher.py b/ciphers/xor_cipher.py index 3b045fdac64a..818dec64131a 100644 --- a/ciphers/xor_cipher.py +++ b/ciphers/xor_cipher.py @@ -19,7 +19,7 @@ class XORCipher: - def __init__(self, key=0): + def __init__(self, key: int = 0): """ simple constructor that receives a key or uses default key = 0 @@ -28,7 +28,7 @@ def __init__(self, key=0): # private field self.__key = key - def encrypt(self, content, key): + def encrypt(self, content: str, key: int) -> [str]: """ input: 'content' of type string and 'key' of type int output: encrypted string 'content' as a list of chars @@ -53,7 +53,7 @@ def encrypt(self, content, key): return ans - def decrypt(self, content, key): + def decrypt(self, content: str, key: int) -> [str]: """ input: 'content' of type list and 'key' of type int output: decrypted string 'content' as a list of chars @@ -78,7 +78,7 @@ def decrypt(self, content, key): return ans - def encrypt_string(self, content, key=0): + def encrypt_string(self, content: str, key: int = 0) -> str: """ input: 'content' of type string and 'key' of type int output: encrypted string 'content' @@ -103,7 +103,7 @@ def encrypt_string(self, content, key=0): return ans - def decrypt_string(self, content, key=0): + def decrypt_string(self, content: str, key: int = 0) -> str: """ input: 'content' of type string and 'key' of type int output: decrypted string 'content' @@ -128,7 +128,7 @@ def decrypt_string(self, content, key=0): return ans - def encrypt_file(self, file, key=0): + def encrypt_file(self, file: str, key: int = 0) -> bool: """ input: filename (str) and a key (int) output: returns true if encrypt process was @@ -153,7 +153,7 @@ def encrypt_file(self, file, key=0): return True - def decrypt_file(self, file, key): + def decrypt_file(self, file: str, key: int) -> bool: """ input: filename (str) and a key (int) output: returns true if decrypt process was From d8f5b31fab57cc009e87a8d62c8d03075f66e9bd Mon Sep 17 00:00:00 2001 From: fpringle Date: Fri, 16 Oct 2020 11:42:51 +0200 Subject: [PATCH 0925/1071] Add solution for Project Euler problem 72 (#3122) * Added solution for Project Euler problem 72. * Update type annotations and 0-padding of the directory name. Reference: #3256 * Rename sol1.py to sol2.py * Added newline at the end of sol2.py * Revert sol1.py --- project_euler/problem_072/sol2.py | 45 +++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100644 project_euler/problem_072/sol2.py diff --git a/project_euler/problem_072/sol2.py b/project_euler/problem_072/sol2.py new file mode 100644 index 000000000000..08e92c18bb3a --- /dev/null +++ b/project_euler/problem_072/sol2.py @@ -0,0 +1,45 @@ +""" +Project Euler Problem 72: https://projecteuler.net/problem=72 + +Consider the fraction, n/d, where n and d are positive integers. If n int: + """ + Return the number of reduced proper fractions with denominator less than limit. + >>> solution(8) + 21 + >>> solution(1000) + 304191 + """ + primes = set(range(3, limit, 2)) + primes.add(2) + for p in range(3, limit, 2): + if p not in primes: + continue + primes.difference_update(set(range(p * p, limit, p))) + + phi = [float(n) for n in range(limit + 1)] + + for p in primes: + for n in range(p, limit + 1, p): + phi[n] *= 1 - 1 / p + + return int(sum(phi[2:])) + + +if __name__ == "__main__": + print(f"{solution() = }") From b96e6c7075e1f47f3a8a59d0b7a088f519ad61f0 Mon Sep 17 00:00:00 2001 From: fpringle Date: Fri, 16 Oct 2020 11:44:09 +0200 Subject: [PATCH 0926/1071] Add solution for Project Euler problem 174. (#3078) * Added solution for Project Euler problem 174. * Fixed import order and removed executable permission from sol1.py * Update docstrings, doctests, and annotations. Reference: #3256 * Update docstring * Update sol1.py Co-authored-by: Dhruv --- project_euler/problem_174/__init__.py | 0 project_euler/problem_174/sol1.py | 52 +++++++++++++++++++++++++++ 2 files changed, 52 insertions(+) create mode 100644 project_euler/problem_174/__init__.py create mode 100644 project_euler/problem_174/sol1.py diff --git a/project_euler/problem_174/__init__.py b/project_euler/problem_174/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/project_euler/problem_174/sol1.py b/project_euler/problem_174/sol1.py new file mode 100644 index 000000000000..cbc0df5a9d65 --- /dev/null +++ b/project_euler/problem_174/sol1.py @@ -0,0 +1,52 @@ +""" +Project Euler Problem 174: https://projecteuler.net/problem=174 + +We shall define a square lamina to be a square outline with a square "hole" so that +the shape possesses vertical and horizontal symmetry. + +Given eight tiles it is possible to form a lamina in only one way: 3x3 square with a +1x1 hole in the middle. However, using thirty-two tiles it is possible to form two +distinct laminae. + +If t represents the number of tiles used, we shall say that t = 8 is type L(1) and +t = 32 is type L(2). + +Let N(n) be the number of t ≤ 1000000 such that t is type L(n); for example, +N(15) = 832. + +What is ∑ N(n) for 1 ≤ n ≤ 10? +""" + +from collections import defaultdict +from math import ceil, sqrt + + +def solution(t_limit: int = 1000000, n_limit: int = 10) -> int: + """ + Return the sum of N(n) for 1 <= n <= n_limit. + + >>> solution(1000,5) + 249 + >>> solution(10000,10) + 2383 + """ + count: defaultdict = defaultdict(int) + + for outer_width in range(3, (t_limit // 4) + 2): + if outer_width * outer_width > t_limit: + hole_width_lower_bound = max( + ceil(sqrt(outer_width * outer_width - t_limit)), 1 + ) + else: + hole_width_lower_bound = 1 + + hole_width_lower_bound += (outer_width - hole_width_lower_bound) % 2 + + for hole_width in range(hole_width_lower_bound, outer_width - 1, 2): + count[outer_width * outer_width - hole_width * hole_width] += 1 + + return sum(1 for n in count.values() if 1 <= n <= 10) + + +if __name__ == "__main__": + print(f"{solution() = }") From b74f3a8b4885b99a48ddb3ab654bc2883f97721a Mon Sep 17 00:00:00 2001 From: fpringle Date: Fri, 16 Oct 2020 12:14:06 +0200 Subject: [PATCH 0927/1071] Add solution for Project Euler problem 91. (#3144) * Added solution for Project Euler problem 91. Reference: #2695 * Added doctest for solution() in project_euler/problem_91/sol1.py * Update docstring and 0-padding in directory name. Reference: #3256 * Update sol1.py Co-authored-by: Dhruv --- project_euler/problem_091/__init__.py | 0 project_euler/problem_091/sol1.py | 59 +++++++++++++++++++++++++++ 2 files changed, 59 insertions(+) create mode 100644 project_euler/problem_091/__init__.py create mode 100644 project_euler/problem_091/sol1.py diff --git a/project_euler/problem_091/__init__.py b/project_euler/problem_091/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/project_euler/problem_091/sol1.py b/project_euler/problem_091/sol1.py new file mode 100644 index 000000000000..6c9aa3fa6c70 --- /dev/null +++ b/project_euler/problem_091/sol1.py @@ -0,0 +1,59 @@ +""" +Project Euler Problem 91: https://projecteuler.net/problem=91 + +The points P (x1, y1) and Q (x2, y2) are plotted at integer coordinates and +are joined to the origin, O(0,0), to form ΔOPQ. + +There are exactly fourteen triangles containing a right angle that can be formed +when each coordinate lies between 0 and 2 inclusive; that is, +0 ≤ x1, y1, x2, y2 ≤ 2. + +Given that 0 ≤ x1, y1, x2, y2 ≤ 50, how many right triangles can be formed? +""" + + +from itertools import combinations, product + + +def is_right(x1: int, y1: int, x2: int, y2: int) -> bool: + """ + Check if the triangle described by P(x1,y1), Q(x2,y2) and O(0,0) is right-angled. + Note: this doesn't check if P and Q are equal, but that's handled by the use of + itertools.combinations in the solution function. + + >>> is_right(0, 1, 2, 0) + True + >>> is_right(1, 0, 2, 2) + False + """ + if x1 == y1 == 0 or x2 == y2 == 0: + return False + a_square = x1 * x1 + y1 * y1 + b_square = x2 * x2 + y2 * y2 + c_square = (x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2) + return ( + a_square + b_square == c_square + or a_square + c_square == b_square + or b_square + c_square == a_square + ) + + +def solution(limit: int = 50) -> int: + """ + Return the number of right triangles OPQ that can be formed by two points P, Q + which have both x- and y- coordinates between 0 and limit inclusive. + + >>> solution(2) + 14 + >>> solution(10) + 448 + """ + return sum( + 1 + for pt1, pt2 in combinations(product(range(limit + 1), repeat=2), 2) + if is_right(*pt1, *pt2) + ) + + +if __name__ == "__main__": + print(f"{solution() = }") From 9643d3060dd73ac4afa70283622cb5108fc95dab Mon Sep 17 00:00:00 2001 From: fpringle Date: Fri, 16 Oct 2020 12:30:45 +0200 Subject: [PATCH 0928/1071] Add solution for Project Euler problem 75. (#3129) * Added solution for Project Euler problem 75. * Added doctest for solution() in project_euler/problem_75/sol1.py * Update docstring and 0-padding of directory name. Reference: #3256 * More descriptive variable names * Moved solution explanation to module-level docstring --- project_euler/problem_075/__init__.py | 0 project_euler/problem_075/sol1.py | 60 +++++++++++++++++++++++++++ 2 files changed, 60 insertions(+) create mode 100644 project_euler/problem_075/__init__.py create mode 100644 project_euler/problem_075/sol1.py diff --git a/project_euler/problem_075/__init__.py b/project_euler/problem_075/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/project_euler/problem_075/sol1.py b/project_euler/problem_075/sol1.py new file mode 100644 index 000000000000..b57604d76a86 --- /dev/null +++ b/project_euler/problem_075/sol1.py @@ -0,0 +1,60 @@ +""" +Project Euler Problem 75: https://projecteuler.net/problem=75 + +It turns out that 12 cm is the smallest length of wire that can be bent to form an +integer sided right angle triangle in exactly one way, but there are many more examples. + +12 cm: (3,4,5) +24 cm: (6,8,10) +30 cm: (5,12,13) +36 cm: (9,12,15) +40 cm: (8,15,17) +48 cm: (12,16,20) + +In contrast, some lengths of wire, like 20 cm, cannot be bent to form an integer sided +right angle triangle, and other lengths allow more than one solution to be found; for +example, using 120 cm it is possible to form exactly three different integer sided +right angle triangles. + +120 cm: (30,40,50), (20,48,52), (24,45,51) + +Given that L is the length of the wire, for how many values of L ≤ 1,500,000 can +exactly one integer sided right angle triangle be formed? + +Solution: we generate all pythagorean triples using Euclid's formula and +keep track of the frequencies of the perimeters. + +Reference: https://en.wikipedia.org/wiki/Pythagorean_triple#Generating_a_triple +""" + +from collections import defaultdict +from math import gcd +from typing import DefaultDict + + +def solution(limit: int = 1500000) -> int: + """ + Return the number of values of L <= limit such that a wire of length L can be + formmed into an integer sided right angle triangle in exactly one way. + >>> solution(50) + 6 + >>> solution(1000) + 112 + >>> solution(50000) + 5502 + """ + frequencies: DefaultDict = defaultdict(int) + euclid_m = 2 + while 2 * euclid_m * (euclid_m + 1) <= limit: + for euclid_n in range((euclid_m % 2) + 1, euclid_m, 2): + if gcd(euclid_m, euclid_n) > 1: + continue + primitive_perimeter = 2 * euclid_m * (euclid_m + euclid_n) + for perimeter in range(primitive_perimeter, limit + 1, primitive_perimeter): + frequencies[perimeter] += 1 + euclid_m += 1 + return sum(1 for frequency in frequencies.values() if frequency == 1) + + +if __name__ == "__main__": + print(f"{solution() = }") From c33b683193334b1429ce2f120774815c94b5810b Mon Sep 17 00:00:00 2001 From: Akash G Krishnan Date: Fri, 16 Oct 2020 18:43:45 +0530 Subject: [PATCH 0929/1071] New doubly linkedlist PR: pull/2573 (#3380) https://github.com/TheAlgorithms/Python/pull/2573 the second implementation of the Doubly linked list --- .../linked_list/doubly_linked_list_two.py | 253 ++++++++++++++++++ 1 file changed, 253 insertions(+) create mode 100644 data_structures/linked_list/doubly_linked_list_two.py diff --git a/data_structures/linked_list/doubly_linked_list_two.py b/data_structures/linked_list/doubly_linked_list_two.py new file mode 100644 index 000000000000..184b6966b5a9 --- /dev/null +++ b/data_structures/linked_list/doubly_linked_list_two.py @@ -0,0 +1,253 @@ +""" +- A linked list is similar to an array, it holds values. However, links in a linked + list do not have indexes. +- This is an example of a double ended, doubly linked list. +- Each link references the next link and the previous one. +- A Doubly Linked List (DLL) contains an extra pointer, typically called previous + pointer, together with next pointer and data which are there in singly linked list. + - Advantages over SLL - It can be traversed in both forward and backward direction. + Delete operation is more efficient +""" + + +class Node: + def __init__(self, data: int, previous=None, next_node=None): + self.data = data + self.previous = previous + self.next = next_node + + def __str__(self) -> str: + return f"{self.data}" + + def get_data(self) -> int: + return self.data + + def get_next(self): + return self.next + + def get_previous(self): + return self.previous + + +class LinkedListIterator: + def __init__(self, head): + self.current = head + + def __iter__(self): + return self + + def __next__(self): + if not self.current: + raise StopIteration + else: + value = self.current.get_data() + self.current = self.current.get_next() + return value + + +class LinkedList: + def __init__(self): + self.head = None # First node in list + self.tail = None # Last node in list + + def __str__(self): + current = self.head + nodes = [] + while current is not None: + nodes.append(current.get_data()) + current = current.get_next() + return " ".join(str(node) for node in nodes) + + def __contains__(self, value: int): + current = self.head + while current: + if current.get_data() == value: + return True + current = current.get_next() + return False + + def __iter__(self): + return LinkedListIterator(self.head) + + def get_head_data(self): + if self.head: + return self.head.get_data() + return None + + def get_tail_data(self): + if self.tail: + return self.tail.get_data() + return None + + def set_head(self, node: Node) -> None: + + if self.head is None: + self.head = node + self.tail = node + else: + self.insert_before_node(self.head, node) + + def set_tail(self, node: Node) -> None: + if self.head is None: + self.set_head(node) + else: + self.insert_after_node(self.tail, node) + + def insert(self, value: int) -> None: + node = Node(value) + if self.head is None: + self.set_head(node) + else: + self.set_tail(node) + + def insert_before_node(self, node: Node, node_to_insert: Node) -> None: + node_to_insert.next = node + node_to_insert.previous = node.previous + + if node.get_previous() is None: + self.head = node_to_insert + else: + node.previous.next = node_to_insert + + node.previous = node_to_insert + + def insert_after_node(self, node: Node, node_to_insert: Node) -> None: + node_to_insert.previous = node + node_to_insert.next = node.next + + if node.get_next() is None: + self.tail = node_to_insert + else: + node.next.previous = node_to_insert + + node.next = node_to_insert + + def insert_at_position(self, position: int, value: int) -> None: + current_position = 1 + new_node = Node(value) + node = self.head + while node: + if current_position == position: + self.insert_before_node(node, new_node) + return None + current_position += 1 + node = node.next + self.insert_after_node(self.tail, new_node) + + def get_node(self, item: int) -> Node: + node = self.head + while node: + if node.get_data() == item: + return node + node = node.get_next() + raise Exception("Node not found") + + def delete_value(self, value): + node = self.get_node(value) + + if node is not None: + if node == self.head: + self.head = self.head.get_next() + + if node == self.tail: + self.tail = self.tail.get_previous() + + self.remove_node_pointers(node) + + @staticmethod + def remove_node_pointers(node: Node) -> None: + if node.get_next(): + node.next.previous = node.previous + + if node.get_previous(): + node.previous.next = node.next + + node.next = None + node.previous = None + + def is_empty(self): + return self.head is None + + +def create_linked_list() -> None: + """ + >>> new_linked_list = LinkedList() + >>> new_linked_list.get_head_data() is None + True + >>> new_linked_list.get_tail_data() is None + True + >>> new_linked_list.is_empty() + True + >>> new_linked_list.insert(10) + >>> new_linked_list.get_head_data() + 10 + >>> new_linked_list.get_tail_data() + 10 + >>> new_linked_list.insert_at_position(position=3, value=20) + >>> new_linked_list.get_head_data() + 10 + >>> new_linked_list.get_tail_data() + 20 + >>> new_linked_list.set_head(Node(1000)) + >>> new_linked_list.get_head_data() + 1000 + >>> new_linked_list.get_tail_data() + 20 + >>> new_linked_list.set_tail(Node(2000)) + >>> new_linked_list.get_head_data() + 1000 + >>> new_linked_list.get_tail_data() + 2000 + >>> for value in new_linked_list: + ... print(value) + 1000 + 10 + 20 + 2000 + >>> new_linked_list.is_empty() + False + >>> for value in new_linked_list: + ... print(value) + 1000 + 10 + 20 + 2000 + >>> 10 in new_linked_list + True + >>> new_linked_list.delete_value(value=10) + >>> 10 in new_linked_list + False + >>> new_linked_list.delete_value(value=2000) + >>> new_linked_list.get_tail_data() + 20 + >>> new_linked_list.delete_value(value=1000) + >>> new_linked_list.get_tail_data() + 20 + >>> new_linked_list.get_head_data() + 20 + >>> for value in new_linked_list: + ... print(value) + 20 + >>> new_linked_list.delete_value(value=20) + >>> for value in new_linked_list: + ... print(value) + >>> for value in range(1,10): + ... new_linked_list.insert(value=value) + >>> for value in new_linked_list: + ... print(value) + 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + """ + + +if __name__ == "__main__": + import doctest + + doctest.testmod() From fc98961814faa8ebb58f349cdba4fdc265eae184 Mon Sep 17 00:00:00 2001 From: Tanay Karve Date: Fri, 16 Oct 2020 18:45:20 +0530 Subject: [PATCH 0930/1071] Hacktoberfest 2020: Added computer vision algorithm (#2946) * Create meanthresholding.py * Rename meanthresholding.py to meanthreshold.py * Update meanthreshold.py * Update computer_vision/meanthreshold.py Verified this part works, thanks. Co-authored-by: Christian Clauss * Update computer_vision/meanthreshold.py Co-authored-by: Christian Clauss Co-authored-by: Christian Clauss --- computer_vision/meanthreshold.py | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 computer_vision/meanthreshold.py diff --git a/computer_vision/meanthreshold.py b/computer_vision/meanthreshold.py new file mode 100644 index 000000000000..76657933d6a9 --- /dev/null +++ b/computer_vision/meanthreshold.py @@ -0,0 +1,30 @@ +from PIL import Image + +""" +Mean thresholding algorithm for image processing +https://en.wikipedia.org/wiki/Thresholding_(image_processing) +""" + + +def mean_threshold(image: Image) -> Image: + """ + image: is a grayscale PIL image object + """ + height, width = image.size + mean = 0 + pixels = image.load() + for i in range(width): + for j in range(height): + pixel = pixels[j, i] + mean += pixel + mean //= width * height + + for j in range(width): + for i in range(height): + pixels[i, j] = 255 if pixels[i, j] > mean else 0 + return image + + +if __name__ == "__main__": + image = mean_threshold(Image.open("path_to_image").convert("L")) + image.save("output_image_path") From 58875674daab6511f41a7b4a3837998d08a862d8 Mon Sep 17 00:00:00 2001 From: Benjamin Smith Date: Fri, 16 Oct 2020 17:47:35 +0200 Subject: [PATCH 0931/1071] Project Euler 57 - Square root convergents (#3259) * include solution for problem 57 * fix line to long errors * update filenames and code to comply with new regulations * more descriptive local variables --- project_euler/problem_057/__init__.py | 0 project_euler/problem_057/sol1.py | 48 +++++++++++++++++++++++++++ 2 files changed, 48 insertions(+) create mode 100644 project_euler/problem_057/__init__.py create mode 100644 project_euler/problem_057/sol1.py diff --git a/project_euler/problem_057/__init__.py b/project_euler/problem_057/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/project_euler/problem_057/sol1.py b/project_euler/problem_057/sol1.py new file mode 100644 index 000000000000..04b6199f4717 --- /dev/null +++ b/project_euler/problem_057/sol1.py @@ -0,0 +1,48 @@ +""" +Project Euler Problem 57: https://projecteuler.net/problem=57 +It is possible to show that the square root of two can be expressed as an infinite +continued fraction. + +sqrt(2) = 1 + 1 / (2 + 1 / (2 + 1 / (2 + ...))) + +By expanding this for the first four iterations, we get: +1 + 1 / 2 = 3 / 2 = 1.5 +1 + 1 / (2 + 1 / 2} = 7 / 5 = 1.4 +1 + 1 / (2 + 1 / (2 + 1 / 2)) = 17 / 12 = 1.41666... +1 + 1 / (2 + 1 / (2 + 1 / (2 + 1 / 2))) = 41/ 29 = 1.41379... + +The next three expansions are 99/70, 239/169, and 577/408, but the eighth expansion, +1393/985, is the first example where the number of digits in the numerator exceeds +the number of digits in the denominator. + +In the first one-thousand expansions, how many fractions contain a numerator with +more digits than the denominator? +""" + + +def solution(n: int = 1000) -> int: + """ + returns number of fractions containing a numerator with more digits than + the denominator in the first n expansions. + >>> solution(14) + 2 + >>> solution(100) + 15 + >>> solution(10000) + 1508 + """ + prev_numerator, prev_denominator = 1, 1 + result = [] + for i in range(1, n + 1): + numerator = prev_numerator + 2 * prev_denominator + denominator = prev_numerator + prev_denominator + if len(str(numerator)) > len(str(denominator)): + result.append(i) + prev_numerator = numerator + prev_denominator = denominator + + return len(result) + + +if __name__ == "__main__": + print(f"{solution() = }") From 7d84f7fe61626396b2b64f3efa3fe2b740894289 Mon Sep 17 00:00:00 2001 From: Akash G Krishnan Date: Sat, 17 Oct 2020 00:15:26 +0530 Subject: [PATCH 0932/1071] Adding in the evaluate postfix notation using Stack (#2598) * Create evaluate_postfix_notations.py Adding in the evaluate postfix notation using Stacks one of the common use with simple stack question creating a new file for the data structure of stacks * Create evaluate_postfix_notations.py Adding in the evaluate postfix notation using Stacks one of the common use with simple stack question creating a new file for the data structure of stacks * Delete evaluate_postfix_notations.py * Evaluate postfix expression stack clean approach Sending in the PR again as the Previous request failed in pre commit * Update evaluate_postfix_notations.py * Update evaluate_postfix_notations.py Made changes as per the required for fixing the failing pre-commits. * Update evaluate_postfix_notations.py Made changes as suggested by @cclauss * Update evaluate_postfix_notations.py fixed pre-commit fails * Update evaluate_postfix_notations.py fixing pre-commit fails * Update evaluate_postfix_notations.py Deleted trailing white spaces causing pre-commits to fail * Update data_structures/stacks/evaluate_postfix_notations.py Co-authored-by: Christian Clauss * Update data_structures/stacks/evaluate_postfix_notations.py Co-authored-by: Christian Clauss Co-authored-by: Christian Clauss --- .../stacks/evaluate_postfix_notations.py | 49 +++++++++++++++++++ 1 file changed, 49 insertions(+) create mode 100644 data_structures/stacks/evaluate_postfix_notations.py diff --git a/data_structures/stacks/evaluate_postfix_notations.py b/data_structures/stacks/evaluate_postfix_notations.py new file mode 100644 index 000000000000..a03cb43bb020 --- /dev/null +++ b/data_structures/stacks/evaluate_postfix_notations.py @@ -0,0 +1,49 @@ +""" +The Reverse Polish Nation also known as Polish postfix notation +or simply postfix notation. +https://en.wikipedia.org/wiki/Reverse_Polish_notation +Classic examples of simple stack implementations +Valid operators are +, -, *, /. +Each operand may be an integer or another expression. +""" + + +def evaluate_postfix(postfix_notation: list) -> int: + """ + >>> evaluate_postfix(["2", "1", "+", "3", "*"]) + 9 + >>> evaluate_postfix(["4", "13", "5", "/", "+"]) + 6 + >>> evaluate_postfix([]) + 0 + """ + if not postfix_notation: + return 0 + + operations = {"+", "-", "*", "/"} + stack = [] + + for token in postfix_notation: + if token in operations: + b, a = stack.pop(), stack.pop() + if token == "+": + stack.append(a + b) + elif token == "-": + stack.append(a - b) + elif token == "*": + stack.append(a * b) + else: + if a * b < 0 and a % b != 0: + stack.append(a // b + 1) + else: + stack.append(a // b) + else: + stack.append(int(token)) + + return stack.pop() + + +if __name__ == "__main__": + import doctest + + doctest.testmod() From 5cb41e7820fb69a16ef47893fd2b16d76baef7e8 Mon Sep 17 00:00:00 2001 From: Dhruv Date: Sat, 17 Oct 2020 08:23:17 +0530 Subject: [PATCH 0933/1071] Create GitHub action only for Project Euler (#3378) * Add GitHub action for Project Euler only * Add second job for Project Euler * Remove Project Euler jobs from Travis CI * Fix typo for actions/setup-python * Rename the workflow file * Change name of file in workflow * Remove comments from Travis config file --- .github/workflows/project_euler.yml | 30 +++++++++++++++++++++++++++++ .travis.yml | 10 ---------- 2 files changed, 30 insertions(+), 10 deletions(-) create mode 100644 .github/workflows/project_euler.yml diff --git a/.github/workflows/project_euler.yml b/.github/workflows/project_euler.yml new file mode 100644 index 000000000000..852b0adbcb56 --- /dev/null +++ b/.github/workflows/project_euler.yml @@ -0,0 +1,30 @@ +on: + pull_request: + # only check if a file is changed within the project_euler directory + paths: + - 'project_euler/**' + - '.github/workflows/project_euler.yml' + +name: 'Project Euler' + +jobs: + project-euler: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: actions/setup-python@v2 + - name: Install pytest and pytest-cov + run: | + python -m pip install --upgrade pip + python -m pip install --upgrade pytest pytest-cov + - run: pytest --doctest-modules --durations=10 --cov-report=term-missing:skip-covered --cov=project_euler/ project_euler/ + validate-solutions: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: actions/setup-python@v2 + - name: Install pytest + run: | + python -m pip install --upgrade pip + python -m pip install --upgrade pytest + - run: pytest --durations=10 project_euler/validate_solutions.py diff --git a/.travis.yml b/.travis.yml index f31dae8467d6..2a4a6392d4e7 100644 --- a/.travis.yml +++ b/.travis.yml @@ -10,16 +10,6 @@ jobs: install: pip install pytest-cov -r requirements.txt script: - pytest --doctest-modules --ignore=project_euler/ --durations=10 --cov-report=term-missing:skip-covered --cov=. . - - name: Project Euler - install: - - pip install pytest-cov - script: - - pytest --doctest-modules --durations=10 --cov-report=term-missing:skip-covered --cov=project_euler/ project_euler/ - - name: Project Euler Solution - install: - - pip install pytest - script: - - pytest --tb=short --durations=10 project_euler/validate_solutions.py after_success: - scripts/build_directory_md.py 2>&1 | tee DIRECTORY.md notifications: From 35cade8389f6e44eeab82020f5067a9dbc834dcd Mon Sep 17 00:00:00 2001 From: acoder77 <73009264+acoder77@users.noreply.github.com> Date: Sat, 17 Oct 2020 10:55:25 +0530 Subject: [PATCH 0934/1071] Create .gitattributes for Cross OS compatibility (#3410) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit With this set, Windows users will have text files converted from Windows style line endings (\r\n) to Unix style line endings (\n) when they’re added to the repository. https://www.edwardthomson.com/blog/git_for_windows_line_endings.html --- .gitattributes | 1 + 1 file changed, 1 insertion(+) create mode 100644 .gitattributes diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 000000000000..176a458f94e0 --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +* text=auto From a88006d04a0b095523f8e19c21c77572324751ce Mon Sep 17 00:00:00 2001 From: Abhishek Jaisingh Date: Sat, 17 Oct 2020 11:11:24 +0530 Subject: [PATCH 0935/1071] Qiskit: Add Quantum Half Adder (#3405) * Qiskit: Add Quantum Half Adder * fixup! Format Python code with psf/black push Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> --- quantum/half_adder.py | 59 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 59 insertions(+) create mode 100755 quantum/half_adder.py diff --git a/quantum/half_adder.py b/quantum/half_adder.py new file mode 100755 index 000000000000..1310edbbfdf1 --- /dev/null +++ b/quantum/half_adder.py @@ -0,0 +1,59 @@ +#!/usr/bin/env python3 +""" +Build a half-adder quantum circuit that takes two bits as input, +encodes them into qubits, then runs the half-adder circuit calculating +the sum and carry qubits, observed over 1000 runs of the experiment +. + +References: +- https://en.wikipedia.org/wiki/Adder_(electronics) +- https://qiskit.org/textbook/ch-states/atoms-computation.html#4.2-Remembering-how-to-add- +""" + +import qiskit as q + + +def half_adder(bit0: int, bit1: int) -> q.result.counts.Counts: + """ + >>> half_adder(0, 0) + {'00': 1000} + >>> half_adder(0, 1) + {'01': 1000} + >>> half_adder(1, 0) + {'01': 1000} + >>> half_adder(1, 1) + {'10': 1000} + """ + # Use Aer's qasm_simulator + simulator = q.Aer.get_backend("qasm_simulator") + + qc_ha = q.QuantumCircuit(4, 2) + # encode inputs in qubits 0 and 1 + if bit0 == 1: + qc_ha.x(0) + if bit1 == 1: + qc_ha.x(1) + qc_ha.barrier() + + # use cnots to write XOR of the inputs on qubit2 + qc_ha.cx(0, 2) + qc_ha.cx(1, 2) + + # use ccx / toffoli gate to write AND of the inputs on qubit3 + qc_ha.ccx(0, 1, 3) + qc_ha.barrier() + + # extract outputs + qc_ha.measure(2, 0) # extract XOR value + qc_ha.measure(3, 1) # extract AND value + + # Execute the circuit on the qasm simulator + job = q.execute(qc_ha, simulator, shots=1000) + + # Return the histogram data of the results of the experiment. + return job.result().get_counts(qc_ha) + + +if __name__ == "__main__": + counts = half_adder(1, 1) + print(f"Half Adder Output Qubit Counts: {counts}") From 05f4089bf08776469adbef10d5a5cb0cb672e7c6 Mon Sep 17 00:00:00 2001 From: CapofWeird <40702379+CapofWeird@users.noreply.github.com> Date: Sat, 17 Oct 2020 03:56:11 -0400 Subject: [PATCH 0936/1071] Fixed typo in caesar_cipher.py (#2979) * Fixed typo in caesar_cipher.py * Typo fixes --- ciphers/caesar_cipher.py | 2 +- ciphers/xor_cipher.py | 2 +- hashes/sha1.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/ciphers/caesar_cipher.py b/ciphers/caesar_cipher.py index 7bda519767a1..4038919e5dde 100644 --- a/ciphers/caesar_cipher.py +++ b/ciphers/caesar_cipher.py @@ -220,7 +220,7 @@ def brute_force(input_string: str, alphabet=None) -> dict: def main(): while True: print(f'\n{"-" * 10}\n Menu\n{"-" * 10}') - print(*["1.Encrpyt", "2.Decrypt", "3.BruteForce", "4.Quit"], sep="\n") + print(*["1.Encrypt", "2.Decrypt", "3.BruteForce", "4.Quit"], sep="\n") # get user input choice = input("\nWhat would you like to do?: ").strip() or "4" diff --git a/ciphers/xor_cipher.py b/ciphers/xor_cipher.py index 818dec64131a..27e4262bc924 100644 --- a/ciphers/xor_cipher.py +++ b/ciphers/xor_cipher.py @@ -183,7 +183,7 @@ def decrypt_file(self, file: str, key: int) -> bool: # crypt = XORCipher() # key = 67 -# # test enrcypt +# # test encrypt # print(crypt.encrypt("hallo welt",key)) # # test decrypt # print(crypt.decrypt(crypt.encrypt("hallo welt",key), key)) diff --git a/hashes/sha1.py b/hashes/sha1.py index 04ecdd788039..cca38b7c3fdc 100644 --- a/hashes/sha1.py +++ b/hashes/sha1.py @@ -8,7 +8,7 @@ Also contains a Test class to verify that the generated Hash is same as that returned by the hashlib library -SHA1 hash or SHA1 sum of a string is a crytpographic function which means it is easy +SHA1 hash or SHA1 sum of a string is a cryptographic function which means it is easy to calculate forwards but extremely difficult to calculate backwards. What this means is, you can easily calculate the hash of a string, but it is extremely difficult to know the original string if you have its hash. This property is useful to communicate From f34434a214e4c937f3a5a5b0d1f1bd64a3bee7c5 Mon Sep 17 00:00:00 2001 From: Christian Clauss Date: Sat, 17 Oct 2020 13:42:29 +0200 Subject: [PATCH 0937/1071] Fix the build -- 88 chars per line max. (#3437) * Fix the build -- 88 chars per line max. * updating DIRECTORY.md Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> --- DIRECTORY.md | 13 +++++++++++++ quantum/half_adder.py | 4 ++-- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/DIRECTORY.md b/DIRECTORY.md index 4e67ad5156d9..72133a60b18a 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -86,6 +86,7 @@ ## Computer Vision * [Harriscorner](https://github.com/TheAlgorithms/Python/blob/master/computer_vision/harriscorner.py) + * [Meanthreshold](https://github.com/TheAlgorithms/Python/blob/master/computer_vision/meanthreshold.py) ## Conversions * [Binary To Decimal](https://github.com/TheAlgorithms/Python/blob/master/conversions/binary_to_decimal.py) @@ -139,6 +140,7 @@ * [Circular Linked List](https://github.com/TheAlgorithms/Python/blob/master/data_structures/linked_list/circular_linked_list.py) * [Deque Doubly](https://github.com/TheAlgorithms/Python/blob/master/data_structures/linked_list/deque_doubly.py) * [Doubly Linked List](https://github.com/TheAlgorithms/Python/blob/master/data_structures/linked_list/doubly_linked_list.py) + * [Doubly Linked List Two](https://github.com/TheAlgorithms/Python/blob/master/data_structures/linked_list/doubly_linked_list_two.py) * [From Sequence](https://github.com/TheAlgorithms/Python/blob/master/data_structures/linked_list/from_sequence.py) * [Has Loop](https://github.com/TheAlgorithms/Python/blob/master/data_structures/linked_list/has_loop.py) * [Is Palindrome](https://github.com/TheAlgorithms/Python/blob/master/data_structures/linked_list/is_palindrome.py) @@ -157,6 +159,7 @@ * Stacks * [Balanced Parentheses](https://github.com/TheAlgorithms/Python/blob/master/data_structures/stacks/balanced_parentheses.py) * [Dijkstras Two Stack Algorithm](https://github.com/TheAlgorithms/Python/blob/master/data_structures/stacks/dijkstras_two_stack_algorithm.py) + * [Evaluate Postfix Notations](https://github.com/TheAlgorithms/Python/blob/master/data_structures/stacks/evaluate_postfix_notations.py) * [Infix To Postfix Conversion](https://github.com/TheAlgorithms/Python/blob/master/data_structures/stacks/infix_to_postfix_conversion.py) * [Infix To Prefix Conversion](https://github.com/TheAlgorithms/Python/blob/master/data_structures/stacks/infix_to_prefix_conversion.py) * [Linked Stack](https://github.com/TheAlgorithms/Python/blob/master/data_structures/stacks/linked_stack.py) @@ -655,6 +658,8 @@ * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_055/sol1.py) * Problem 056 * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_056/sol1.py) + * Problem 057 + * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_057/sol1.py) * Problem 062 * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_062/sol1.py) * Problem 063 @@ -667,12 +672,17 @@ * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_071/sol1.py) * Problem 072 * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_072/sol1.py) + * [Sol2](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_072/sol2.py) * Problem 074 * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_074/sol1.py) + * Problem 075 + * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_075/sol1.py) * Problem 076 * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_076/sol1.py) * Problem 080 * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_080/sol1.py) + * Problem 091 + * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_091/sol1.py) * Problem 097 * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_097/sol1.py) * Problem 099 @@ -689,6 +699,8 @@ * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_125/sol1.py) * Problem 173 * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_173/sol1.py) + * Problem 174 + * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_174/sol1.py) * Problem 191 * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_191/sol1.py) * Problem 234 @@ -698,6 +710,7 @@ * [Validate Solutions](https://github.com/TheAlgorithms/Python/blob/master/project_euler/validate_solutions.py) ## Quantum + * [Half Adder](https://github.com/TheAlgorithms/Python/blob/master/quantum/half_adder.py) * [Not Gate](https://github.com/TheAlgorithms/Python/blob/master/quantum/not_gate.py) * [Quantum Entanglement](https://github.com/TheAlgorithms/Python/blob/master/quantum/quantum_entanglement.py) * [Single Qubit Measure](https://github.com/TheAlgorithms/Python/blob/master/quantum/single_qubit_measure.py) diff --git a/quantum/half_adder.py b/quantum/half_adder.py index 1310edbbfdf1..4af704e640be 100755 --- a/quantum/half_adder.py +++ b/quantum/half_adder.py @@ -6,8 +6,8 @@ . References: -- https://en.wikipedia.org/wiki/Adder_(electronics) -- https://qiskit.org/textbook/ch-states/atoms-computation.html#4.2-Remembering-how-to-add- +https://en.wikipedia.org/wiki/Adder_(electronics) +https://qiskit.org/textbook/ch-states/atoms-computation.html#4.2-Remembering-how-to-add- """ import qiskit as q From 3bbec1db49eea0598412aa839ed055d151541443 Mon Sep 17 00:00:00 2001 From: RadadiyaMohit <30775542+radadiyamohit81@users.noreply.github.com> Date: Sat, 17 Oct 2020 19:20:53 +0530 Subject: [PATCH 0938/1071] create beaufort cipher (#3206) * create beaufort cipher if you like my code, merge it and add the label as `hacktoberfest-accepted` * update the file * Update beaufort_cipher.py * Update beaufort_cipher.py * update as per black formatter * Update beaufort_cipher.py * update the file * update file * update file * update file * update file --- ciphers/beaufort_cipher.py | 82 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 82 insertions(+) create mode 100644 ciphers/beaufort_cipher.py diff --git a/ciphers/beaufort_cipher.py b/ciphers/beaufort_cipher.py new file mode 100644 index 000000000000..c885dec74001 --- /dev/null +++ b/ciphers/beaufort_cipher.py @@ -0,0 +1,82 @@ +""" +Author: Mohit Radadiya +""" + +from string import ascii_uppercase + +dict1 = {char: i for i, char in enumerate(ascii_uppercase)} +dict2 = {i: char for i, char in enumerate(ascii_uppercase)} + + +# This function generates the key in +# a cyclic manner until it's length isn't +# equal to the length of original text +def generate_key(message: str, key: str) -> str: + """ + >>> generate_key("THE GERMAN ATTACK","SECRET") + 'SECRETSECRETSECRE' + """ + x = len(message) + i = 0 + while True: + if x == i: + i = 0 + if len(key) == len(message): + break + key += key[i] + i += 1 + return key + + +# This function returns the encrypted text +# generated with the help of the key +def cipher_text(message: str, key_new: str) -> str: + """ + >>> cipher_text("THE GERMAN ATTACK","SECRETSECRETSECRE") + 'BDC PAYUWL JPAIYI' + """ + cipher_text = "" + i = 0 + for letter in message: + if letter == " ": + cipher_text += " " + else: + x = (dict1[letter] - dict1[key_new[i]]) % 26 + i += 1 + cipher_text += dict2[x] + return cipher_text + + +# This function decrypts the encrypted text +# and returns the original text +def original_text(cipher_text: str, key_new: str) -> str: + """ + >>> original_text("BDC PAYUWL JPAIYI","SECRETSECRETSECRE") + 'THE GERMAN ATTACK' + """ + or_txt = "" + i = 0 + for letter in cipher_text: + if letter == " ": + or_txt += " " + else: + x = (dict1[letter] + dict1[key_new[i]] + 26) % 26 + i += 1 + or_txt += dict2[x] + return or_txt + + +def main(): + message = "THE GERMAN ATTACK" + key = "SECRET" + key_new = generate_key(message, key) + s = cipher_text(message, key_new) + print(f"Encrypted Text = {s}") + print(f"Original Text = {original_text(s, key_new)}") + + +if __name__ == "__main__": + import doctest + + doctest.testmod() + main() From 2ec3750885b71a84969252ad978f046632bbeadf Mon Sep 17 00:00:00 2001 From: RadadiyaMohit <30775542+radadiyamohit81@users.noreply.github.com> Date: Sat, 17 Oct 2020 23:30:46 +0530 Subject: [PATCH 0939/1071] create monoalphabetic cipher (#3449) * create monoalphabetic cipher * update file * update file * update file * update file * update file * update after testing flake8 on this code * update file * update file * update file * update file * update file * update file --- ciphers/mono_alphabetic_ciphers.py | 59 ++++++++++++++++++++++++++++++ 1 file changed, 59 insertions(+) create mode 100644 ciphers/mono_alphabetic_ciphers.py diff --git a/ciphers/mono_alphabetic_ciphers.py b/ciphers/mono_alphabetic_ciphers.py new file mode 100644 index 000000000000..0a29d6442896 --- /dev/null +++ b/ciphers/mono_alphabetic_ciphers.py @@ -0,0 +1,59 @@ +LETTERS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + + +def translate_message(key, message, mode): + """ + >>> translate_message("QWERTYUIOPASDFGHJKLZXCVBNM","Hello World","encrypt") + 'Pcssi Bidsm' + """ + chars_a = LETTERS if mode == "decrypt" else key + chars_b = key if mode == "decrypt" else LETTERS + translated = "" + # loop through each symbol in the message + for symbol in message: + if symbol.upper() in chars_a: + # encrypt/decrypt the symbol + sym_index = chars_a.find(symbol.upper()) + if symbol.isupper(): + translated += chars_b[sym_index].upper() + else: + translated += chars_b[sym_index].lower() + else: + # symbol is not in LETTERS, just add it + translated += symbol + return translated + + +def encrypt_message(key: str, message: str) -> str: + """ + >>> encrypt_message("QWERTYUIOPASDFGHJKLZXCVBNM", "Hello World") + 'Pcssi Bidsm' + """ + return translate_message(key, message, "encrypt") + + +def decrypt_message(key: str, message: str) -> str: + """ + >>> decrypt_message("QWERTYUIOPASDFGHJKLZXCVBNM", "Hello World") + 'Itssg Vgksr' + """ + return translate_message(key, message, "decrypt") + + +def main(): + message = "Hello World" + key = "QWERTYUIOPASDFGHJKLZXCVBNM" + mode = "decrypt" # set to 'encrypt' or 'decrypt' + + if mode == "encrypt": + translated = encrypt_message(key, message) + elif mode == "decrypt": + translated = decrypt_message(key, message) + print(f"Using the key {key}, the {mode}ed message is: {translated}") + + +if __name__ == "__main__": + import doctest + + doctest.testmod() + main() From beb2c35dd864f093c49b43934064c5da7155859c Mon Sep 17 00:00:00 2001 From: Abhishek Jaisingh Date: Sun, 18 Oct 2020 20:24:46 +0530 Subject: [PATCH 0940/1071] Implement Deutsch-Jozsa Algorithm In Qiskit (#3447) * Implement Deutsch-Jozsa Algorithm In Qiskit Signed-off-by: Abhishek Jaisingh * Add Changes Requested In Review Signed-off-by: Abhishek Jaisingh * Address Further Review Comments * fixup! Format Python code with psf/black push Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> --- quantum/deutsch_jozsa.py | 122 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 122 insertions(+) create mode 100755 quantum/deutsch_jozsa.py diff --git a/quantum/deutsch_jozsa.py b/quantum/deutsch_jozsa.py new file mode 100755 index 000000000000..da1b6e4e9434 --- /dev/null +++ b/quantum/deutsch_jozsa.py @@ -0,0 +1,122 @@ +#!/usr/bin/env python3 +""" +Deutsch-Josza Algorithm is one of the first examples of a quantum +algorithm that is exponentially faster than any possible deterministic +classical algorithm + +Premise: +We are given a hidden Boolean function f, +which takes as input a string of bits, and returns either 0 or 1: + +f({x0,x1,x2,...}) -> 0 or 1, where xn is 0 or 1 + +The property of the given Boolean function is that it is guaranteed to +either be balanced or constant. A constant function returns all 0's +or all 1's for any input, while a balanced function returns 0's for +exactly half of all inputs and 1's for the other half. Our task is to +determine whether the given function is balanced or constant. + +References: +- https://en.wikipedia.org/wiki/Deutsch-Jozsa_algorithm +- https://qiskit.org/textbook/ch-algorithms/deutsch-jozsa.html +""" + +import numpy as np +import qiskit as q + + +def dj_oracle(case: str, num_qubits: int) -> q.QuantumCircuit: + """ + Returns a Quantum Circuit for the Oracle function. + The circuit returned can represent balanced or constant function, + according to the arguments passed + """ + # This circuit has num_qubits+1 qubits: the size of the input, + # plus one output qubit + oracle_qc = q.QuantumCircuit(num_qubits + 1) + + # First, let's deal with the case in which oracle is balanced + if case == "balanced": + # First generate a random number that tells us which CNOTs to + # wrap in X-gates: + b = np.random.randint(1, 2 ** num_qubits) + # Next, format 'b' as a binary string of length 'n', padded with zeros: + b_str = format(b, f"0{num_qubits}b") + # Next, we place the first X-gates. Each digit in our binary string + # correspopnds to a qubit, if the digit is 0, we do nothing, if it's 1 + # we apply an X-gate to that qubit: + for index, bit in enumerate(b_str): + if bit == "1": + oracle_qc.x(index) + # Do the controlled-NOT gates for each qubit, using the output qubit + # as the target: + for index in range(num_qubits): + oracle_qc.cx(index, num_qubits) + # Next, place the final X-gates + for index, bit in enumerate(b_str): + if bit == "1": + oracle_qc.x(index) + + # Case in which oracle is constant + if case == "constant": + # First decide what the fixed output of the oracle will be + # (either always 0 or always 1) + output = np.random.randint(2) + if output == 1: + oracle_qc.x(num_qubits) + + oracle_gate = oracle_qc.to_gate() + oracle_gate.name = "Oracle" # To show when we display the circuit + return oracle_gate + + +def dj_algorithm(oracle: q.QuantumCircuit, num_qubits: int) -> q.QuantumCircuit: + """ + Returns the complete Deustch-Jozsa Quantum Circuit, + adding Input & Output registers and Hadamard & Measurement Gates, + to the Oracle Circuit passed in arguments + """ + dj_circuit = q.QuantumCircuit(num_qubits + 1, num_qubits) + # Set up the output qubit: + dj_circuit.x(num_qubits) + dj_circuit.h(num_qubits) + # And set up the input register: + for qubit in range(num_qubits): + dj_circuit.h(qubit) + # Let's append the oracle gate to our circuit: + dj_circuit.append(oracle, range(num_qubits + 1)) + # Finally, perform the H-gates again and measure: + for qubit in range(num_qubits): + dj_circuit.h(qubit) + + for i in range(num_qubits): + dj_circuit.measure(i, i) + + return dj_circuit + + +def deutsch_jozsa(case: str, num_qubits: int) -> q.result.counts.Counts: + """ + Main function that builds the circuit using other helper functions, + runs the experiment 1000 times & returns the resultant qubit counts + >>> deutsch_jozsa("constant", 3) + {'000': 1000} + >>> deutsch_jozsa("balanced", 3) + {'111': 1000} + """ + # Use Aer's qasm_simulator + simulator = q.Aer.get_backend("qasm_simulator") + + oracle_gate = dj_oracle(case, num_qubits) + dj_circuit = dj_algorithm(oracle_gate, num_qubits) + + # Execute the circuit on the qasm simulator + job = q.execute(dj_circuit, simulator, shots=1000) + + # Return the histogram data of the results of the experiment. + return job.result().get_counts(dj_circuit) + + +if __name__ == "__main__": + print(f"Deutsch Jozsa - Constant Oracle: {deutsch_jozsa('constant', 3)}") + print(f"Deutsch Jozsa - Balanced Oracle: {deutsch_jozsa('balanced', 3)}") From a481bfa231ce1dc4eeaec2a1c1db740e267f663e Mon Sep 17 00:00:00 2001 From: Christian Clauss Date: Sun, 18 Oct 2020 18:07:27 +0200 Subject: [PATCH 0941/1071] Fix broken build: Remove trailing spaces (#3501) * Fix broken build: Remove trailing spaces * updating DIRECTORY.md * One more trailing space Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> --- DIRECTORY.md | 3 +++ quantum/deutsch_jozsa.py | 8 ++++---- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/DIRECTORY.md b/DIRECTORY.md index 72133a60b18a..0f4aeefe3b82 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -48,6 +48,7 @@ * [Base32](https://github.com/TheAlgorithms/Python/blob/master/ciphers/base32.py) * [Base64 Cipher](https://github.com/TheAlgorithms/Python/blob/master/ciphers/base64_cipher.py) * [Base85](https://github.com/TheAlgorithms/Python/blob/master/ciphers/base85.py) + * [Beaufort Cipher](https://github.com/TheAlgorithms/Python/blob/master/ciphers/beaufort_cipher.py) * [Brute Force Caesar Cipher](https://github.com/TheAlgorithms/Python/blob/master/ciphers/brute_force_caesar_cipher.py) * [Caesar Cipher](https://github.com/TheAlgorithms/Python/blob/master/ciphers/caesar_cipher.py) * [Cryptomath Module](https://github.com/TheAlgorithms/Python/blob/master/ciphers/cryptomath_module.py) @@ -58,6 +59,7 @@ * [Enigma Machine2](https://github.com/TheAlgorithms/Python/blob/master/ciphers/enigma_machine2.py) * [Hill Cipher](https://github.com/TheAlgorithms/Python/blob/master/ciphers/hill_cipher.py) * [Mixed Keyword Cypher](https://github.com/TheAlgorithms/Python/blob/master/ciphers/mixed_keyword_cypher.py) + * [Mono Alphabetic Ciphers](https://github.com/TheAlgorithms/Python/blob/master/ciphers/mono_alphabetic_ciphers.py) * [Morse Code Implementation](https://github.com/TheAlgorithms/Python/blob/master/ciphers/morse_code_implementation.py) * [Onepad Cipher](https://github.com/TheAlgorithms/Python/blob/master/ciphers/onepad_cipher.py) * [Playfair Cipher](https://github.com/TheAlgorithms/Python/blob/master/ciphers/playfair_cipher.py) @@ -710,6 +712,7 @@ * [Validate Solutions](https://github.com/TheAlgorithms/Python/blob/master/project_euler/validate_solutions.py) ## Quantum + * [Deutsch Jozsa](https://github.com/TheAlgorithms/Python/blob/master/quantum/deutsch_jozsa.py) * [Half Adder](https://github.com/TheAlgorithms/Python/blob/master/quantum/half_adder.py) * [Not Gate](https://github.com/TheAlgorithms/Python/blob/master/quantum/not_gate.py) * [Quantum Entanglement](https://github.com/TheAlgorithms/Python/blob/master/quantum/quantum_entanglement.py) diff --git a/quantum/deutsch_jozsa.py b/quantum/deutsch_jozsa.py index da1b6e4e9434..304eea196e03 100755 --- a/quantum/deutsch_jozsa.py +++ b/quantum/deutsch_jozsa.py @@ -5,14 +5,14 @@ classical algorithm Premise: -We are given a hidden Boolean function f, +We are given a hidden Boolean function f, which takes as input a string of bits, and returns either 0 or 1: f({x0,x1,x2,...}) -> 0 or 1, where xn is 0 or 1 - + The property of the given Boolean function is that it is guaranteed to -either be balanced or constant. A constant function returns all 0's -or all 1's for any input, while a balanced function returns 0's for +either be balanced or constant. A constant function returns all 0's +or all 1's for any input, while a balanced function returns 0's for exactly half of all inputs and 1's for the other half. Our task is to determine whether the given function is balanced or constant. From 4c92f8c0d06cbeeddcb7efc443f2043a5692b893 Mon Sep 17 00:00:00 2001 From: Anselm Hahn Date: Sun, 18 Oct 2020 21:54:43 +0200 Subject: [PATCH 0942/1071] Replace main with __main__ (#3518) --- web_programming/slack_message.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web_programming/slack_message.py b/web_programming/slack_message.py index 8ea9d5d0add2..f35aa3ca587e 100644 --- a/web_programming/slack_message.py +++ b/web_programming/slack_message.py @@ -13,7 +13,7 @@ def send_slack_message(message_body: str, slack_url: str) -> None: ) -if __name__ == "main": +if __name__ == "__main__": # Set the slack url to the one provided by Slack when you create the webhook at # https://my.slack.com/services/new/incoming-webhook/ send_slack_message("", "") From 79d57552aa4d8c4777dbcf5126dbb37142866469 Mon Sep 17 00:00:00 2001 From: JoaoVictorNascimento Date: Sun, 18 Oct 2020 18:44:19 -0300 Subject: [PATCH 0943/1071] Add Patience Sort (#3469) * Add Patience Sort * fix code for pre-commit * Fix params def * Adding new line at end of file * Remove Trailing Whitespace * Adding space between the methods of the Stack class * Removing Trailing Whitespace * Ordering Imports * Adding url patience sort Co-authored-by: jvnascimento --- DIRECTORY.md | 1 + sorts/patience_sort.py | 64 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 65 insertions(+) create mode 100644 sorts/patience_sort.py diff --git a/DIRECTORY.md b/DIRECTORY.md index 0f4aeefe3b82..d9196e130196 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -761,6 +761,7 @@ * [Odd Even Transposition Parallel](https://github.com/TheAlgorithms/Python/blob/master/sorts/odd_even_transposition_parallel.py) * [Odd Even Transposition Single Threaded](https://github.com/TheAlgorithms/Python/blob/master/sorts/odd_even_transposition_single_threaded.py) * [Pancake Sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/pancake_sort.py) + * [Patience Sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/patience_sort.py) * [Pigeon Sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/pigeon_sort.py) * [Pigeonhole Sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/pigeonhole_sort.py) * [Quick Sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/quick_sort.py) diff --git a/sorts/patience_sort.py b/sorts/patience_sort.py new file mode 100644 index 000000000000..f4e35d9a0ac6 --- /dev/null +++ b/sorts/patience_sort.py @@ -0,0 +1,64 @@ +from bisect import bisect_left +from functools import total_ordering +from heapq import merge + +""" +A pure Python implementation of the patience sort algorithm + +For more information: https://en.wikipedia.org/wiki/Patience_sorting + +This algorithm is based on the card game patience + +For doctests run following command: +python3 -m doctest -v patience_sort.py + +For manual testing run: +python3 patience_sort.py +""" + + +@total_ordering +class Stack(list): + def __lt__(self, other): + return self[-1] < other[-1] + + def __eq__(self, other): + return self[-1] == other[-1] + + +def patience_sort(collection: list) -> list: + """A pure implementation of quick sort algorithm in Python + + :param collection: some mutable ordered collection with heterogeneous + comparable items inside + :return: the same collection ordered by ascending + + Examples: + >>> patience_sort([1, 9, 5, 21, 17, 6]) + [1, 5, 6, 9, 17, 21] + + >>> patience_sort([]) + [] + + >>> patience_sort([-3, -17, -48]) + [-48, -17, -3] + """ + stacks = [] + # sort into stacks + for element in collection: + new_stacks = Stack([element]) + i = bisect_left(stacks, new_stacks) + if i != len(stacks): + stacks[i].append(element) + else: + stacks.append(new_stacks) + + # use a heap-based merge to merge stack efficiently + collection[:] = merge(*[reversed(stack) for stack in stacks]) + return collection + + +if __name__ == "__main__": + user_input = input("Enter numbers separated by a comma:\n").strip() + unsorted = [int(item) for item in user_input.split(",")] + print(patience_sort(unsorted)) From 802ac83c3d5df6cc4512b28eb90da75749300214 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20Schr=C3=B6der?= <5970416+tbsschroeder@users.noreply.github.com> Date: Mon, 19 Oct 2020 03:07:18 +0200 Subject: [PATCH 0944/1071] Add a naive recursive implementation of 0-1 Knapsack Problem (#2743) * Add naive recursive implementation of 0-1 Knapsack problem * Fix shadowing * Add doctest * Fix type hints * Add link to wiki * Blacked the file * Fix isort * Move knapsack / add readme and more tests * Add missed main in tests --- knapsack/README.md | 32 ++++++++++++++++++++++++ knapsack/__init__.py | 0 knapsack/knapsack.py | 47 +++++++++++++++++++++++++++++++++++ knapsack/test_knapsack.py | 52 +++++++++++++++++++++++++++++++++++++++ 4 files changed, 131 insertions(+) create mode 100644 knapsack/README.md create mode 100644 knapsack/__init__.py create mode 100644 knapsack/knapsack.py create mode 100644 knapsack/test_knapsack.py diff --git a/knapsack/README.md b/knapsack/README.md new file mode 100644 index 000000000000..6041c1e48eb8 --- /dev/null +++ b/knapsack/README.md @@ -0,0 +1,32 @@ +# A naive recursive implementation of 0-1 Knapsack Problem + +This overview is taken from: + + https://en.wikipedia.org/wiki/Knapsack_problem + +--- + +## Overview + +The knapsack problem is a problem in combinatorial optimization: Given a set of items, each with a weight and a value, determine the number of each item to include in a collection so that the total weight is less than or equal to a given limit and the total value is as large as possible. It derives its name from the problem faced by someone who is constrained by a fixed-size knapsack and must fill it with the most valuable items. The problem often arises in resource allocation where the decision makers have to choose from a set of non-divisible projects or tasks under a fixed budget or time constraint, respectively. + +The knapsack problem has been studied for more than a century, with early works dating as far back as 1897 The name "knapsack problem" dates back to the early works of mathematician Tobias Dantzig (1884–1956), and refers to the commonplace problem of packing the most valuable or useful items without overloading the luggage. + +--- + +## Documentation + +This module uses docstrings to enable the use of Python's in-built `help(...)` function. +For instance, try `help(Vector)`, `help(unitBasisVector)`, and `help(CLASSNAME.METHODNAME)`. + +--- + +## Usage + +Import the module `knapsack.py` from the **.** directory into your project. + +--- + +## Tests + +`.` contains Python unit tests which can be run with `python3 -m unittest -v`. diff --git a/knapsack/__init__.py b/knapsack/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/knapsack/knapsack.py b/knapsack/knapsack.py new file mode 100644 index 000000000000..756443ea6163 --- /dev/null +++ b/knapsack/knapsack.py @@ -0,0 +1,47 @@ +from typing import List + +""" A naive recursive implementation of 0-1 Knapsack Problem + https://en.wikipedia.org/wiki/Knapsack_problem +""" + + +def knapsack(capacity: int, weights: List[int], values: List[int], counter: int) -> int: + """ + Returns the maximum value that can be put in a knapsack of a capacity cap, + whereby each weight w has a specific value val. + + >>> cap = 50 + >>> val = [60, 100, 120] + >>> w = [10, 20, 30] + >>> c = len(val) + >>> knapsack(cap, w, val, c) + 220 + + The result is 220 cause the values of 100 and 120 got the weight of 50 + which is the limit of the capacity. + """ + + # Base Case + if counter == 0 or capacity == 0: + return 0 + + # If weight of the nth item is more than Knapsack of capacity, + # then this item cannot be included in the optimal solution, + # else return the maximum of two cases: + # (1) nth item included + # (2) not included + if weights[counter - 1] > capacity: + return knapsack(capacity, weights, values, counter - 1) + else: + left_capacity = capacity - weights[counter - 1] + new_value_included = values[counter - 1] + knapsack( + left_capacity, weights, values, counter - 1 + ) + without_new_value = knapsack(capacity, weights, values, counter - 1) + return max(new_value_included, without_new_value) + + +if __name__ == "__main__": + import doctest + + doctest.testmod() diff --git a/knapsack/test_knapsack.py b/knapsack/test_knapsack.py new file mode 100644 index 000000000000..248855fbce53 --- /dev/null +++ b/knapsack/test_knapsack.py @@ -0,0 +1,52 @@ +""" +Created on Fri Oct 16 09:31:07 2020 + +@author: Dr. Tobias Schröder +@license: MIT-license + +This file contains the test-suite for the knapsack problem. +""" +import unittest + +from knapsack import knapsack as k + + +class Test(unittest.TestCase): + def test_base_case(self): + """ + test for the base case + """ + cap = 0 + val = [0] + w = [0] + c = len(val) + self.assertEqual(k.knapsack(cap, w, val, c), 0) + + val = [60] + w = [10] + c = len(val) + self.assertEqual(k.knapsack(cap, w, val, c), 0) + + def test_easy_case(self): + """ + test for the base case + """ + cap = 3 + val = [1, 2, 3] + w = [3, 2, 1] + c = len(val) + self.assertEqual(k.knapsack(cap, w, val, c), 5) + + def test_knapsack(self): + """ + test for the knapsack + """ + cap = 50 + val = [60, 100, 120] + w = [10, 20, 30] + c = len(val) + self.assertEqual(k.knapsack(cap, w, val, c), 220) + + +if __name__ == "__main__": + unittest.main() From 74233022a0608b0ee25d3514a87a36e40bc821ac Mon Sep 17 00:00:00 2001 From: anneCoder1805 <66819522+anneCoder1805@users.noreply.github.com> Date: Tue, 20 Oct 2020 16:08:49 +0530 Subject: [PATCH 0945/1071] Median of Two Arrays (#3554) * Create medianOf TwoArrays.py This code finds the median of two arrays (which may or may not be sorted initially). Example: Enter elements of an array: 1 5 4 2 Enter elements of another array: 1 7 4 2 7 The median of two arrays is : 4 * Rename medianOf TwoArrays.py to median_of _two_arrays.py * Rename median_of _two_arrays.py to median_of_two_arrays.py * Update median_of_two_arrays.py * Update median_of_two_arrays.py * Update median_of_two_arrays.py * Update median_of_two_arrays.py * Update median_of_two_arrays.py * Update median_of_two_arrays.py * Update median_of_two_arrays.py * Update median_of_two_arrays.py * Update median_of_two_arrays.py * Update median_of_two_arrays.py * Update median_of_two_arrays.py * Update median_of_two_arrays.py * Update median_of_two_arrays.py * Update median_of_two_arrays.py * Update median_of_two_arrays.py * Update median_of_two_arrays.py * Update median_of_two_arrays.py --- other/median_of_two_arrays.py | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 other/median_of_two_arrays.py diff --git a/other/median_of_two_arrays.py b/other/median_of_two_arrays.py new file mode 100644 index 000000000000..cde12f5d7e3b --- /dev/null +++ b/other/median_of_two_arrays.py @@ -0,0 +1,33 @@ +from typing import List + + +def median_of_two_arrays(nums1: List[float], nums2: List[float]) -> float: + """ + >>> median_of_two_arrays([1, 2], [3]) + 2 + >>> median_of_two_arrays([0, -1.1], [2.5, 1]) + 0.5 + >>> median_of_two_arrays([], [2.5, 1]) + 1.75 + >>> median_of_two_arrays([], [0]) + 0 + >>> median_of_two_arrays([], []) + Traceback (most recent call last): + ... + IndexError: list index out of range + """ + all_numbers = sorted(nums1 + nums2) + div, mod = divmod(len(all_numbers), 2) + if mod == 1: + return all_numbers[div] + else: + return (all_numbers[div] + all_numbers[div - 1]) / 2 + + +if __name__ == "__main__": + import doctest + + doctest.testmod() + array_1 = [float(x) for x in input("Enter the elements of first array: ").split()] + array_2 = [float(x) for x in input("Enter the elements of second array: ").split()] + print(f"The median of two arrays is: {median_of_two_arrays(array_1, array_2)}") From 9b95e4f662462d167b18d744e36817e9c8849f0e Mon Sep 17 00:00:00 2001 From: Christian Clauss Date: Wed, 21 Oct 2020 12:46:14 +0200 Subject: [PATCH 0946/1071] Pyupgrade to python3.8 (#3616) * Upgrade to Python 3.8 syntax * updating DIRECTORY.md Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> --- DIRECTORY.md | 5 +++++ ciphers/simple_substitution_cipher.py | 2 +- ciphers/xor_cipher.py | 8 ++++---- compression/lempel_ziv.py | 6 +++--- compression/lempel_ziv_decompress.py | 6 +++--- data_structures/binary_tree/segment_tree_other.py | 6 +++--- data_structures/heap/heap.py | 2 +- data_structures/linked_list/deque_doubly.py | 2 +- graphs/minimum_spanning_tree_boruvka.py | 2 +- machine_learning/astar.py | 4 ++-- machine_learning/k_means_clust.py | 2 +- maths/entropy.py | 6 +++--- maths/numerical_integration.py | 2 +- project_euler/problem_013/sol1.py | 2 +- project_euler/problem_018/solution.py | 2 +- project_euler/problem_042/solution42.py | 2 +- project_euler/problem_049/sol1.py | 2 +- project_euler/problem_054/sol1.py | 4 ++-- project_euler/problem_054/test_poker_hand.py | 2 +- project_euler/problem_063/sol1.py | 2 +- project_euler/problem_067/sol1.py | 2 +- 21 files changed, 38 insertions(+), 33 deletions(-) diff --git a/DIRECTORY.md b/DIRECTORY.md index d9196e130196..866e0d658bf6 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -323,6 +323,10 @@ * [Sdbm](https://github.com/TheAlgorithms/Python/blob/master/hashes/sdbm.py) * [Sha1](https://github.com/TheAlgorithms/Python/blob/master/hashes/sha1.py) +## Knapsack + * [Knapsack](https://github.com/TheAlgorithms/Python/blob/master/knapsack/knapsack.py) + * [Test Knapsack](https://github.com/TheAlgorithms/Python/blob/master/knapsack/test_knapsack.py) + ## Linear Algebra * Src * [Lib](https://github.com/TheAlgorithms/Python/blob/master/linear_algebra/src/lib.py) @@ -502,6 +506,7 @@ * [Magicdiamondpattern](https://github.com/TheAlgorithms/Python/blob/master/other/magicdiamondpattern.py) * [Markov Chain](https://github.com/TheAlgorithms/Python/blob/master/other/markov_chain.py) * [Max Sum Sliding Window](https://github.com/TheAlgorithms/Python/blob/master/other/max_sum_sliding_window.py) + * [Median Of Two Arrays](https://github.com/TheAlgorithms/Python/blob/master/other/median_of_two_arrays.py) * [Nested Brackets](https://github.com/TheAlgorithms/Python/blob/master/other/nested_brackets.py) * [Palindrome](https://github.com/TheAlgorithms/Python/blob/master/other/palindrome.py) * [Password Generator](https://github.com/TheAlgorithms/Python/blob/master/other/password_generator.py) diff --git a/ciphers/simple_substitution_cipher.py b/ciphers/simple_substitution_cipher.py index f5b711e616af..646ea449fc06 100644 --- a/ciphers/simple_substitution_cipher.py +++ b/ciphers/simple_substitution_cipher.py @@ -18,7 +18,7 @@ def main(): mode = "decrypt" translated = decryptMessage(key, message) - print("\n{}ion: \n{}".format(mode.title(), translated)) + print(f"\n{mode.title()}ion: \n{translated}") def checkValidKey(key: str) -> None: diff --git a/ciphers/xor_cipher.py b/ciphers/xor_cipher.py index 27e4262bc924..32a350d4e61c 100644 --- a/ciphers/xor_cipher.py +++ b/ciphers/xor_cipher.py @@ -141,14 +141,14 @@ def encrypt_file(self, file: str, key: int = 0) -> bool: assert isinstance(file, str) and isinstance(key, int) try: - with open(file, "r") as fin: + with open(file) as fin: with open("encrypt.out", "w+") as fout: # actual encrypt-process for line in fin: fout.write(self.encrypt_string(line, key)) - except IOError: + except OSError: return False return True @@ -166,14 +166,14 @@ def decrypt_file(self, file: str, key: int) -> bool: assert isinstance(file, str) and isinstance(key, int) try: - with open(file, "r") as fin: + with open(file) as fin: with open("decrypt.out", "w+") as fout: # actual encrypt-process for line in fin: fout.write(self.decrypt_string(line, key)) - except IOError: + except OSError: return False return True diff --git a/compression/lempel_ziv.py b/compression/lempel_ziv.py index 3ac8573c43d8..2d0601b27b34 100644 --- a/compression/lempel_ziv.py +++ b/compression/lempel_ziv.py @@ -17,10 +17,10 @@ def read_file_binary(file_path: str) -> str: with open(file_path, "rb") as binary_file: data = binary_file.read() for dat in data: - curr_byte = "{0:08b}".format(dat) + curr_byte = f"{dat:08b}" result += curr_byte return result - except IOError: + except OSError: print("File not accessible") sys.exit() @@ -105,7 +105,7 @@ def write_file_binary(file_path: str, to_write: str) -> None: for elem in result_byte_array: opened_file.write(int(elem, 2).to_bytes(1, byteorder="big")) - except IOError: + except OSError: print("File not accessible") sys.exit() diff --git a/compression/lempel_ziv_decompress.py b/compression/lempel_ziv_decompress.py index 05c26740bf62..4d3c2c0d2cf3 100644 --- a/compression/lempel_ziv_decompress.py +++ b/compression/lempel_ziv_decompress.py @@ -16,10 +16,10 @@ def read_file_binary(file_path: str) -> str: with open(file_path, "rb") as binary_file: data = binary_file.read() for dat in data: - curr_byte = "{0:08b}".format(dat) + curr_byte = f"{dat:08b}" result += curr_byte return result - except IOError: + except OSError: print("File not accessible") sys.exit() @@ -76,7 +76,7 @@ def write_file_binary(file_path: str, to_write: str) -> None: for elem in result_byte_array[:-1]: opened_file.write(int(elem, 2).to_bytes(1, byteorder="big")) - except IOError: + except OSError: print("File not accessible") sys.exit() diff --git a/data_structures/binary_tree/segment_tree_other.py b/data_structures/binary_tree/segment_tree_other.py index df98eeffb3c6..90afd7ca8b71 100644 --- a/data_structures/binary_tree/segment_tree_other.py +++ b/data_structures/binary_tree/segment_tree_other.py @@ -7,7 +7,7 @@ from queue import Queue -class SegmentTreeNode(object): +class SegmentTreeNode: def __init__(self, start, end, val, left=None, right=None): self.start = start self.end = end @@ -17,10 +17,10 @@ def __init__(self, start, end, val, left=None, right=None): self.right = right def __str__(self): - return "val: %s, start: %s, end: %s" % (self.val, self.start, self.end) + return f"val: {self.val}, start: {self.start}, end: {self.end}" -class SegmentTree(object): +class SegmentTree: """ >>> import operator >>> num_arr = SegmentTree([2, 1, 5, 3, 4], operator.add) diff --git a/data_structures/heap/heap.py b/data_structures/heap/heap.py index b901c54a4284..2dc047436a77 100644 --- a/data_structures/heap/heap.py +++ b/data_structures/heap/heap.py @@ -1,7 +1,7 @@ #!/usr/bin/python3 -class Heap(object): +class Heap: """ >>> unsorted = [103, 9, 1, 7, 11, 15, 25, 201, 209, 107, 5] >>> h = Heap() diff --git a/data_structures/linked_list/deque_doubly.py b/data_structures/linked_list/deque_doubly.py index b93fb8c4005e..894f91d561cc 100644 --- a/data_structures/linked_list/deque_doubly.py +++ b/data_structures/linked_list/deque_doubly.py @@ -20,7 +20,7 @@ def __init__(self, link_p, element, link_n): self._next = link_n def has_next_and_prev(self): - return " Prev -> {0}, Next -> {1}".format( + return " Prev -> {}, Next -> {}".format( self._prev is not None, self._next is not None ) diff --git a/graphs/minimum_spanning_tree_boruvka.py b/graphs/minimum_spanning_tree_boruvka.py index 3b05f94b5140..32548b2ecb6c 100644 --- a/graphs/minimum_spanning_tree_boruvka.py +++ b/graphs/minimum_spanning_tree_boruvka.py @@ -99,7 +99,7 @@ def build(vertices=None, edges=None): g.add_edge(*edge) return g - class UnionFind(object): + class UnionFind: """ Disjoint set Union and Find for Boruvka's algorithm """ diff --git a/machine_learning/astar.py b/machine_learning/astar.py index 2f5c21a2bd5f..ee3fcff0b7bf 100644 --- a/machine_learning/astar.py +++ b/machine_learning/astar.py @@ -13,7 +13,7 @@ import numpy as np -class Cell(object): +class Cell: """ Class cell represents a cell in the world which have the property position : The position of the represented by tupleof x and y @@ -45,7 +45,7 @@ def showcell(self): print(self.position) -class Gridworld(object): +class Gridworld: """ Gridworld class represents the external world here a grid M*M matrix diff --git a/machine_learning/k_means_clust.py b/machine_learning/k_means_clust.py index 130e7f1ad669..f155d4845f41 100644 --- a/machine_learning/k_means_clust.py +++ b/machine_learning/k_means_clust.py @@ -251,7 +251,7 @@ def ReportGenerator( lambda x: np.mean( np.nan_to_num( sorted(x)[ - round((len(x) * 25 / 100)) : round(len(x) * 75 / 100) + round(len(x) * 25 / 100) : round(len(x) * 75 / 100) ] ) ), diff --git a/maths/entropy.py b/maths/entropy.py index 74980ef9e0c6..43bb3860fc12 100644 --- a/maths/entropy.py +++ b/maths/entropy.py @@ -68,7 +68,7 @@ def calculate_prob(text: str) -> None: my_fir_sum += prob * math.log2(prob) # entropy formula. # print entropy - print("{0:.1f}".format(round(-1 * my_fir_sum))) + print("{:.1f}".format(round(-1 * my_fir_sum))) # two len string all_sum = sum(two_char_strings.values()) @@ -83,10 +83,10 @@ def calculate_prob(text: str) -> None: my_sec_sum += prob * math.log2(prob) # print second entropy - print("{0:.1f}".format(round(-1 * my_sec_sum))) + print("{:.1f}".format(round(-1 * my_sec_sum))) # print the difference between them - print("{0:.1f}".format(round(((-1 * my_sec_sum) - (-1 * my_fir_sum))))) + print("{:.1f}".format(round((-1 * my_sec_sum) - (-1 * my_fir_sum)))) def analyze_text(text: str) -> tuple[dict, dict]: diff --git a/maths/numerical_integration.py b/maths/numerical_integration.py index 67fbc0ddbf30..87184a76b740 100644 --- a/maths/numerical_integration.py +++ b/maths/numerical_integration.py @@ -62,5 +62,5 @@ def f(x): i = 10 while i <= 100000: area = trapezoidal_area(f, -5, 5, i) - print("with {} steps: {}".format(i, area)) + print(f"with {i} steps: {area}") i *= 10 diff --git a/project_euler/problem_013/sol1.py b/project_euler/problem_013/sol1.py index 19b427337c3d..1ea08b12ee93 100644 --- a/project_euler/problem_013/sol1.py +++ b/project_euler/problem_013/sol1.py @@ -17,7 +17,7 @@ def solution(): '5537376230' """ file_path = os.path.join(os.path.dirname(__file__), "num.txt") - with open(file_path, "r") as file_hand: + with open(file_path) as file_hand: return str(sum([int(line) for line in file_hand]))[:10] diff --git a/project_euler/problem_018/solution.py b/project_euler/problem_018/solution.py index 38593813901e..82fc3ce3c9db 100644 --- a/project_euler/problem_018/solution.py +++ b/project_euler/problem_018/solution.py @@ -41,7 +41,7 @@ def solution(): script_dir = os.path.dirname(os.path.realpath(__file__)) triangle = os.path.join(script_dir, "triangle.txt") - with open(triangle, "r") as f: + with open(triangle) as f: triangle = f.readlines() a = [[int(y) for y in x.rstrip("\r\n").split(" ")] for x in triangle] diff --git a/project_euler/problem_042/solution42.py b/project_euler/problem_042/solution42.py index 1e9bb49c7a06..b3aecf4cf144 100644 --- a/project_euler/problem_042/solution42.py +++ b/project_euler/problem_042/solution42.py @@ -30,7 +30,7 @@ def solution(): wordsFilePath = os.path.join(script_dir, "words.txt") words = "" - with open(wordsFilePath, "r") as f: + with open(wordsFilePath) as f: words = f.readline() words = list(map(lambda word: word.strip('"'), words.strip("\r\n").split(","))) diff --git a/project_euler/problem_049/sol1.py b/project_euler/problem_049/sol1.py index 6c3d69ad0d11..c0d0715be91c 100644 --- a/project_euler/problem_049/sol1.py +++ b/project_euler/problem_049/sol1.py @@ -114,7 +114,7 @@ def solution(): if ( abs(candidate[i] - candidate[j]) == abs(candidate[j] - candidate[k]) - and len(set([candidate[i], candidate[j], candidate[k]])) == 3 + and len({candidate[i], candidate[j], candidate[k]}) == 3 ): passed.append( sorted([candidate[i], candidate[j], candidate[k]]) diff --git a/project_euler/problem_054/sol1.py b/project_euler/problem_054/sol1.py index 4d75271784de..d2fd810d1b69 100644 --- a/project_euler/problem_054/sol1.py +++ b/project_euler/problem_054/sol1.py @@ -45,7 +45,7 @@ import os -class PokerHand(object): +class PokerHand: """Create an object representing a Poker Hand based on an input of a string which represents the best 5 card combination from the player's hand and board cards. @@ -366,7 +366,7 @@ def solution() -> int: answer = 0 script_dir = os.path.abspath(os.path.dirname(__file__)) poker_hands = os.path.join(script_dir, "poker_hands.txt") - with open(poker_hands, "r") as file_hand: + with open(poker_hands) as file_hand: for line in file_hand: player_hand = line[:14].strip() opponent_hand = line[15:].strip() diff --git a/project_euler/problem_054/test_poker_hand.py b/project_euler/problem_054/test_poker_hand.py index f60c3aba6616..96317fc7df33 100644 --- a/project_euler/problem_054/test_poker_hand.py +++ b/project_euler/problem_054/test_poker_hand.py @@ -217,7 +217,7 @@ def test_euler_project(): answer = 0 script_dir = os.path.abspath(os.path.dirname(__file__)) poker_hands = os.path.join(script_dir, "poker_hands.txt") - with open(poker_hands, "r") as file_hand: + with open(poker_hands) as file_hand: for line in file_hand: player_hand = line[:14].strip() opponent_hand = line[15:].strip() diff --git a/project_euler/problem_063/sol1.py b/project_euler/problem_063/sol1.py index f6a8d3240ffd..29efddba4216 100644 --- a/project_euler/problem_063/sol1.py +++ b/project_euler/problem_063/sol1.py @@ -26,7 +26,7 @@ def solution(max_base: int = 10, max_power: int = 22) -> int: bases = range(1, max_base) powers = range(1, max_power) return sum( - 1 for power in powers for base in bases if len(str((base ** power))) == power + 1 for power in powers for base in bases if len(str(base ** power)) == power ) diff --git a/project_euler/problem_067/sol1.py b/project_euler/problem_067/sol1.py index 9494ff7bbabd..ebfa865a9479 100644 --- a/project_euler/problem_067/sol1.py +++ b/project_euler/problem_067/sol1.py @@ -25,7 +25,7 @@ def solution(): script_dir = os.path.dirname(os.path.realpath(__file__)) triangle = os.path.join(script_dir, "triangle.txt") - with open(triangle, "r") as f: + with open(triangle) as f: triangle = f.readlines() a = map(lambda x: x.rstrip("\r\n").split(" "), triangle) From 5e642607c8958aa1e4277f75399442b2048a4725 Mon Sep 17 00:00:00 2001 From: Du Yuanchao Date: Wed, 21 Oct 2020 22:31:09 +0800 Subject: [PATCH 0947/1071] Update doubly linked list (#3619) * update doubly linked list * reformat code add more test * add test to iter * updating DIRECTORY.md Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> --- .../linked_list/doubly_linked_list.py | 264 ++++++++++++------ 1 file changed, 185 insertions(+), 79 deletions(-) diff --git a/data_structures/linked_list/doubly_linked_list.py b/data_structures/linked_list/doubly_linked_list.py index 1b4005f59fae..0eb3cf101a3e 100644 --- a/data_structures/linked_list/doubly_linked_list.py +++ b/data_structures/linked_list/doubly_linked_list.py @@ -1,89 +1,156 @@ """ -- A linked list is similar to an array, it holds values. However, links in a linked - list do not have indexes. -- This is an example of a double ended, doubly linked list. -- Each link references the next link and the previous one. -- A Doubly Linked List (DLL) contains an extra pointer, typically called previous - pointer, together with next pointer and data which are there in singly linked list. - - Advantages over SLL - It can be traversed in both forward and backward direction. - Delete operation is more efficient""" - +https://en.wikipedia.org/wiki/Doubly_linked_list +""" -class LinkedList: - """ - >>> linked_list = LinkedList() - >>> linked_list.insert_at_head("a") - >>> linked_list.insert_at_tail("b") - >>> linked_list.delete_tail() - 'b' - >>> linked_list.is_empty - False - >>> linked_list.delete_head() - 'a' - >>> linked_list.is_empty - True - """ - def __init__(self): - self.head = None # First node in list - self.tail = None # Last node in list +class Node: + def __init__(self, data): + self.data = data + self.previous = None + self.next = None def __str__(self): - current = self.head - nodes = [] - while current is not None: - nodes.append(current) - current = current.next - return " ".join(str(node) for node in nodes) - - def insert_at_head(self, data): - new_node = Node(data) - if self.is_empty: - self.tail = new_node - self.head = new_node - else: - self.head.previous = new_node - new_node.next = self.head - self.head = new_node + return f"{self.data}" - def delete_head(self) -> str: - if self.is_empty: - return "List is empty" - head_data = self.head.data - if self.head.next: - self.head = self.head.next - self.head.previous = None +class DoublyLinkedList: + def __init__(self): + self.head = None + self.tail = None + + def __iter__(self): + """ + >>> linked_list = DoublyLinkedList() + >>> linked_list.insert_at_head('b') + >>> linked_list.insert_at_head('a') + >>> linked_list.insert_at_tail('c') + >>> tuple(linked_list) + ('a', 'b', 'c') + """ + node = self.head + while node: + yield node.data + node = node.next - else: # If there is no next previous node - self.head = None - self.tail = None + def __str__(self): + """ + >>> linked_list = DoublyLinkedList() + >>> linked_list.insert_at_tail('a') + >>> linked_list.insert_at_tail('b') + >>> linked_list.insert_at_tail('c') + >>> str(linked_list) + 'a->b->c' + """ + return "->".join([str(item) for item in self]) + + def __len__(self): + """ + >>> linked_list = DoublyLinkedList() + >>> for i in range(0, 5): + ... linked_list.insert_at_nth(i, i + 1) + >>> len(linked_list) == 5 + True + """ + return len(tuple(iter(self))) - return head_data + def insert_at_head(self, data): + self.insert_at_nth(0, data) def insert_at_tail(self, data): + self.insert_at_nth(len(self), data) + + def insert_at_nth(self, index: int, data): + """ + >>> linked_list = DoublyLinkedList() + >>> linked_list.insert_at_nth(-1, 666) + Traceback (most recent call last): + .... + IndexError: list index out of range + >>> linked_list.insert_at_nth(1, 666) + Traceback (most recent call last): + .... + IndexError: list index out of range + >>> linked_list.insert_at_nth(0, 2) + >>> linked_list.insert_at_nth(0, 1) + >>> linked_list.insert_at_nth(2, 4) + >>> linked_list.insert_at_nth(2, 3) + >>> str(linked_list) + '1->2->3->4' + >>> linked_list.insert_at_nth(5, 5) + Traceback (most recent call last): + .... + IndexError: list index out of range + """ + if not 0 <= index <= len(self): + raise IndexError("list index out of range") new_node = Node(data) - if self.is_empty: - self.tail = new_node + if self.head is None: + self.head = self.tail = new_node + elif index == 0: + self.head.previous = new_node + new_node.next = self.head self.head = new_node - else: + elif index == len(self): self.tail.next = new_node new_node.previous = self.tail self.tail = new_node - - def delete_tail(self) -> str: - if self.is_empty: - return "List is empty" - - tail_data = self.tail.data - if self.tail.previous: + else: + temp = self.head + for i in range(0, index): + temp = temp.next + temp.previous.next = new_node + new_node.previous = temp.previous + new_node.next = temp + temp.previous = new_node + + def delete_head(self): + return self.delete_at_nth(0) + + def delete_tail(self): + return self.delete_at_nth(len(self) - 1) + + def delete_at_nth(self, index: int): + """ + >>> linked_list = DoublyLinkedList() + >>> linked_list.delete_at_nth(0) + Traceback (most recent call last): + .... + IndexError: list index out of range + >>> for i in range(0, 5): + ... linked_list.insert_at_nth(i, i + 1) + >>> linked_list.delete_at_nth(0) == 1 + True + >>> linked_list.delete_at_nth(3) == 5 + True + >>> linked_list.delete_at_nth(1) == 3 + True + >>> str(linked_list) + '2->4' + >>> linked_list.delete_at_nth(2) + Traceback (most recent call last): + .... + IndexError: list index out of range + """ + if not 0 <= index <= len(self) - 1: + raise IndexError("list index out of range") + delete_node = self.head # default first node + if len(self) == 1: + self.head = self.tail = None + elif index == 0: + self.head = self.head.next + self.head.previous = None + elif index == len(self) - 1: + delete_node = self.tail self.tail = self.tail.previous self.tail.next = None - else: # if there is no previous node - self.head = None - self.tail = None - - return tail_data + else: + temp = self.head + for i in range(0, index): + temp = temp.next + delete_node = temp + temp.next.previous = temp.previous + temp.previous.next = temp.next + return delete_node.data def delete(self, data) -> str: current = self.head @@ -105,16 +172,55 @@ def delete(self, data) -> str: current.next.previous = current.previous # 1 <--> 3 return data - @property - def is_empty(self): # return True if the list is empty - return self.head is None + def is_empty(self): + """ + >>> linked_list = DoublyLinkedList() + >>> linked_list.is_empty() + True + >>> linked_list.insert_at_tail(1) + >>> linked_list.is_empty() + False + """ + return len(self) == 0 -class Node: - def __init__(self, data): - self.data = data - self.previous = None - self.next = None - - def __str__(self): - return f"{self.data}" +def test_doubly_linked_list() -> None: + """ + >>> test_doubly_linked_list() + """ + linked_list = DoublyLinkedList() + assert linked_list.is_empty() is True + assert str(linked_list) == "" + + try: + linked_list.delete_head() + assert False # This should not happen. + except IndexError: + assert True # This should happen. + + try: + linked_list.delete_tail() + assert False # This should not happen. + except IndexError: + assert True # This should happen. + + for i in range(10): + assert len(linked_list) == i + linked_list.insert_at_nth(i, i + 1) + assert str(linked_list) == "->".join(str(i) for i in range(1, 11)) + + linked_list.insert_at_head(0) + linked_list.insert_at_tail(11) + assert str(linked_list) == "->".join(str(i) for i in range(0, 12)) + + assert linked_list.delete_head() == 0 + assert linked_list.delete_at_nth(9) == 10 + assert linked_list.delete_tail() == 11 + assert len(linked_list) == 9 + assert str(linked_list) == "->".join(str(i) for i in range(1, 10)) + + +if __name__ == "__main__": + from doctest import testmod + + testmod() From c2b7acdf111337632612e26dfdc2b292ac31ddd2 Mon Sep 17 00:00:00 2001 From: Du Yuanchao Date: Sat, 24 Oct 2020 00:16:23 +0800 Subject: [PATCH 0948/1071] Update Linked Stack (#3625) * update linked_stack * remove properties * updating DIRECTORY.md Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> --- data_structures/stacks/linked_stack.py | 134 +++++++++++++++++++++---- 1 file changed, 112 insertions(+), 22 deletions(-) diff --git a/data_structures/stacks/linked_stack.py b/data_structures/stacks/linked_stack.py index 18ba87ddc221..1a2d07f20e7c 100644 --- a/data_structures/stacks/linked_stack.py +++ b/data_structures/stacks/linked_stack.py @@ -1,11 +1,14 @@ -""" A Stack using a Linked List like structure """ -from typing import Any, Optional +""" A Stack using a linked list like structure """ +from typing import Any class Node: - def __init__(self, data: Any, next: Optional["Node"] = None): - self.data: Any = data - self.next: Optional["Node"] = next + def __init__(self, data): + self.data = data + self.next = None + + def __str__(self): + return f"{self.data}" class LinkedStack: @@ -19,7 +22,7 @@ class LinkedStack: >>> stack.push(5) >>> stack.push(9) >>> stack.push('python') - >>> stack.is_empty(); + >>> stack.is_empty() False >>> stack.pop() 'python' @@ -39,29 +42,116 @@ class LinkedStack: """ def __init__(self) -> None: - self.top: Optional[Node] = None + self.top = None + + def __iter__(self): + node = self.top + while node: + yield node.data + node = node.next + + def __str__(self): + """ + >>> stack = LinkedStack() + >>> stack.push("c") + >>> stack.push("b") + >>> stack.push("a") + >>> str(stack) + 'a->b->c' + """ + return "->".join([str(item) for item in self]) + + def __len__(self): + """ + >>> stack = LinkedStack() + >>> len(stack) == 0 + True + >>> stack.push("c") + >>> stack.push("b") + >>> stack.push("a") + >>> len(stack) == 3 + True + """ + return len(tuple(iter(self))) def is_empty(self) -> bool: - """ returns boolean describing if stack is empty """ + """ + >>> stack = LinkedStack() + >>> stack.is_empty() + True + >>> stack.push(1) + >>> stack.is_empty() + False + """ return self.top is None def push(self, item: Any) -> None: - """ append item to top of stack """ - node: Node = Node(item) - if self.is_empty(): - self.top = node - else: - # each node points to the item "lower" in the stack + """ + >>> stack = LinkedStack() + >>> stack.push("Python") + >>> stack.push("Java") + >>> stack.push("C") + >>> str(stack) + 'C->Java->Python' + """ + node = Node(item) + if not self.is_empty(): node.next = self.top - self.top = node + self.top = node def pop(self) -> Any: - """ returns and removes item at top of stack """ + """ + >>> stack = LinkedStack() + >>> stack.pop() + Traceback (most recent call last): + ... + IndexError: pop from empty stack + >>> stack.push("c") + >>> stack.push("b") + >>> stack.push("a") + >>> stack.pop() == 'a' + True + >>> stack.pop() == 'b' + True + >>> stack.pop() == 'c' + True + """ if self.is_empty(): raise IndexError("pop from empty stack") - else: - # "remove" element by having top point to the next one - assert isinstance(self.top, Node) - node: Node = self.top - self.top = node.next - return node.data + assert isinstance(self.top, Node) + pop_node = self.top + self.top = self.top.next + return pop_node.data + + def peek(self) -> Any: + """ + >>> stack = LinkedStack() + >>> stack.push("Java") + >>> stack.push("C") + >>> stack.push("Python") + >>> stack.peek() + 'Python' + """ + if self.is_empty(): + raise IndexError("peek from empty stack") + return self.top.data + + def clear(self) -> None: + """ + >>> stack = LinkedStack() + >>> stack.push("Java") + >>> stack.push("C") + >>> stack.push("Python") + >>> str(stack) + 'Python->C->Java' + >>> stack.clear() + >>> len(stack) == 0 + True + """ + self.top = None + + +if __name__ == "__main__": + from doctest import testmod + + testmod() From 04fae4db9b7bccc219e1548e8b3bfcd135b38f64 Mon Sep 17 00:00:00 2001 From: Rolv Apneseth Date: Fri, 23 Oct 2020 17:17:29 +0100 Subject: [PATCH 0949/1071] Improved and shortened prime_check.py (#3454) * Made small improvements and shortened prime_check.py * improved descriptions on tests in prime_check.py * Ran black and isort --- maths/prime_check.py | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/maths/prime_check.py b/maths/prime_check.py index ed8fbbae809a..e2bcb7b8f151 100644 --- a/maths/prime_check.py +++ b/maths/prime_check.py @@ -5,25 +5,20 @@ def prime_check(number: int) -> bool: - """ - Check to See if a Number is Prime. + """Checks to see if a number is a prime. - A number is prime if it has exactly two dividers: 1 and itself. + A number is prime if it has exactly two factors: 1 and itself. """ - if number < 2: - # Negatives, 0 and 1 are not primes - return False - if number < 4: + + if 1 < number < 4: # 2 and 3 are primes return True - if number % 2 == 0: - # Even values are not primes + elif number < 2 or not number % 2: + # Negatives, 0, 1 and all even numbers are not primes return False - # Except 2, all primes are odd. If any odd value divide - # the number, then that number is not prime. - odd_numbers = range(3, int(math.sqrt(number)) + 1, 2) - return not any(number % i == 0 for i in odd_numbers) + odd_numbers = range(3, int(math.sqrt(number) + 1), 2) + return not any(not number % i for i in odd_numbers) class Test(unittest.TestCase): @@ -40,12 +35,17 @@ def test_primes(self): self.assertTrue(prime_check(29)) def test_not_primes(self): - self.assertFalse(prime_check(-19), "Negative numbers are not prime.") self.assertFalse( - prime_check(0), "Zero doesn't have any divider, primes must have two." + prime_check(-19), + "Negative numbers are excluded by definition of prime numbers.", + ) + self.assertFalse( + prime_check(0), + "Zero doesn't have any positive factors, primes must have exactly two.", ) self.assertFalse( - prime_check(1), "One just have 1 divider, primes must have two." + prime_check(1), + "One only has 1 positive factor, primes must have exactly two.", ) self.assertFalse(prime_check(2 * 2)) self.assertFalse(prime_check(2 * 3)) From 46af42d47a8dfbc06393929c973f55f2d1148261 Mon Sep 17 00:00:00 2001 From: Himadri Ganguly Date: Fri, 23 Oct 2020 22:25:13 +0530 Subject: [PATCH 0950/1071] Fix coin change (#2571) * Removed unused variable m. * Doctests are modified to match functions. * Added condition for negative values. * Fixed white-space around operator. * Fixed W293 blank line contains white-space error. * Update dynamic_programming/coin_change.py Co-authored-by: Tapajyoti Bose <44058757+ruppysuppy@users.noreply.github.com> * Fixed error in code. * Fixed whited spacing. * Fixed PEP8 error. * Added more test cases for coin change problem. * Removed extra test for negetive value. Co-authored-by: Tapajyoti Bose <44058757+ruppysuppy@users.noreply.github.com> --- dynamic_programming/coin_change.py | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/dynamic_programming/coin_change.py b/dynamic_programming/coin_change.py index 2d7106f0cc6f..2869b5857be1 100644 --- a/dynamic_programming/coin_change.py +++ b/dynamic_programming/coin_change.py @@ -7,20 +7,23 @@ """ -def dp_count(S, m, n): +def dp_count(S, n): """ - >>> dp_count([1, 2, 3], 3, 4) + >>> dp_count([1, 2, 3], 4) 4 - >>> dp_count([1, 2, 3], 3, 7) + >>> dp_count([1, 2, 3], 7) 8 - >>> dp_count([2, 5, 3, 6], 4, 10) + >>> dp_count([2, 5, 3, 6], 10) 5 - >>> dp_count([10], 1, 99) + >>> dp_count([10], 99) 0 - >>> dp_count([4, 5, 6], 3, 0) + >>> dp_count([4, 5, 6], 0) 1 + >>> dp_count([1, 2, 3], -5) + 0 """ - + if n < 0: + return 0 # table[i] represents the number of ways to get to amount i table = [0] * (n + 1) From 20260d2b9ca4f05233f5009a63e4d2826b2d312c Mon Sep 17 00:00:00 2001 From: Peter Yao Date: Fri, 23 Oct 2020 19:25:15 -0700 Subject: [PATCH 0951/1071] Add Project Euler 65 Solution (#3035) * Add solution for Project Euler 65, * Add URL to problem 65 and don't pass in parameter to solution() * Remove solution() tests * Add tests for solution(), add fstring and positional arg for solution * Rename directory and problem number to 065 * Remove directory * Move up explanation to module code block * Move solution() below helper function, rename variables --- DIRECTORY.md | 2 + project_euler/problem_065/__init__.py | 0 project_euler/problem_065/sol1.py | 99 +++++++++++++++++++++++++++ 3 files changed, 101 insertions(+) create mode 100644 project_euler/problem_065/__init__.py create mode 100644 project_euler/problem_065/sol1.py diff --git a/DIRECTORY.md b/DIRECTORY.md index 866e0d658bf6..f0f494e7a3b4 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -671,6 +671,8 @@ * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_062/sol1.py) * Problem 063 * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_063/sol1.py) + * Problem 065 + * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_065/sol1.py) * Problem 067 * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_067/sol1.py) * Problem 069 diff --git a/project_euler/problem_065/__init__.py b/project_euler/problem_065/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/project_euler/problem_065/sol1.py b/project_euler/problem_065/sol1.py new file mode 100644 index 000000000000..229769a77d07 --- /dev/null +++ b/project_euler/problem_065/sol1.py @@ -0,0 +1,99 @@ +""" +Project Euler Problem 65: https://projecteuler.net/problem=65 + +The square root of 2 can be written as an infinite continued fraction. + +sqrt(2) = 1 + 1 / (2 + 1 / (2 + 1 / (2 + 1 / (2 + ...)))) + +The infinite continued fraction can be written, sqrt(2) = [1;(2)], (2) +indicates that 2 repeats ad infinitum. In a similar way, sqrt(23) = +[4;(1,3,1,8)]. + +It turns out that the sequence of partial values of continued +fractions for square roots provide the best rational approximations. +Let us consider the convergents for sqrt(2). + +1 + 1 / 2 = 3/2 +1 + 1 / (2 + 1 / 2) = 7/5 +1 + 1 / (2 + 1 / (2 + 1 / 2)) = 17/12 +1 + 1 / (2 + 1 / (2 + 1 / (2 + 1 / 2))) = 41/29 + +Hence the sequence of the first ten convergents for sqrt(2) are: +1, 3/2, 7/5, 17/12, 41/29, 99/70, 239/169, 577/408, 1393/985, 3363/2378, ... + +What is most surprising is that the important mathematical constant, +e = [2;1,2,1,1,4,1,1,6,1,...,1,2k,1,...]. + +The first ten terms in the sequence of convergents for e are: +2, 3, 8/3, 11/4, 19/7, 87/32, 106/39, 193/71, 1264/465, 1457/536, ... + +The sum of digits in the numerator of the 10th convergent is +1 + 4 + 5 + 7 = 17. + +Find the sum of the digits in the numerator of the 100th convergent +of the continued fraction for e. + +----- + +The solution mostly comes down to finding an equation that will generate +the numerator of the continued fraction. For the i-th numerator, the +pattern is: + +n_i = m_i * n_(i-1) + n_(i-2) + +for m_i = the i-th index of the continued fraction representation of e, +n_0 = 1, and n_1 = 2 as the first 2 numbers of the representation. + +For example: +n_9 = 6 * 193 + 106 = 1264 +1 + 2 + 6 + 4 = 13 + +n_10 = 1 * 193 + 1264 = 1457 +1 + 4 + 5 + 7 = 17 +""" + + +def sum_digits(num: int) -> int: + """ + Returns the sum of every digit in num. + + >>> sum_digits(1) + 1 + >>> sum_digits(12345) + 15 + >>> sum_digits(999001) + 28 + """ + digit_sum = 0 + while num > 0: + digit_sum += num % 10 + num //= 10 + return digit_sum + + +def solution(max: int = 100) -> int: + """ + Returns the sum of the digits in the numerator of the max-th convergent of + the continued fraction for e. + + >>> solution(9) + 13 + >>> solution(10) + 17 + >>> solution(50) + 91 + """ + pre_numerator = 1 + cur_numerator = 2 + + for i in range(2, max + 1): + temp = pre_numerator + e_cont = 2 * i // 3 if i % 3 == 0 else 1 + pre_numerator = cur_numerator + cur_numerator = e_cont * pre_numerator + temp + + return sum_digits(cur_numerator) + + +if __name__ == "__main__": + print(f"{solution() = }") From 409438d250fcff2a1dff6ad5350673431ed2e870 Mon Sep 17 00:00:00 2001 From: fpringle Date: Sat, 24 Oct 2020 04:42:15 +0200 Subject: [PATCH 0952/1071] Add solution for Project Euler problem 38. (#3115) * Added solution for Project Euler problem 38. Fixes: #2695 * Update docstring and 0-padding in directory name. Reference: #3256 * Renamed is_9_palindromic to is_9_pandigital. * Changed just-in-case return value for solution() to None. * Moved exmplanation to module-level docstring and deleted unnecessary import --- project_euler/problem_038/__init__.py | 0 project_euler/problem_038/sol1.py | 77 +++++++++++++++++++++++++++ 2 files changed, 77 insertions(+) create mode 100644 project_euler/problem_038/__init__.py create mode 100644 project_euler/problem_038/sol1.py diff --git a/project_euler/problem_038/__init__.py b/project_euler/problem_038/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/project_euler/problem_038/sol1.py b/project_euler/problem_038/sol1.py new file mode 100644 index 000000000000..6d54f6df7ff8 --- /dev/null +++ b/project_euler/problem_038/sol1.py @@ -0,0 +1,77 @@ +""" +Project Euler Problem 38: https://projecteuler.net/problem=38 + +Take the number 192 and multiply it by each of 1, 2, and 3: + +192 × 1 = 192 +192 × 2 = 384 +192 × 3 = 576 + +By concatenating each product we get the 1 to 9 pandigital, 192384576. We will call +192384576 the concatenated product of 192 and (1,2,3) + +The same can be achieved by starting with 9 and multiplying by 1, 2, 3, 4, and 5, +giving the pandigital, 918273645, which is the concatenated product of 9 and +(1,2,3,4,5). + +What is the largest 1 to 9 pandigital 9-digit number that can be formed as the +concatenated product of an integer with (1,2, ... , n) where n > 1? + +Solution: +Since n>1, the largest candidate for the solution will be a concactenation of +a 4-digit number and its double, a 5-digit number. +Let a be the 4-digit number. +a has 4 digits => 1000 <= a < 10000 +2a has 5 digits => 10000 <= 2a < 100000 +=> 5000 <= a < 10000 + +The concatenation of a with 2a = a * 10^5 + 2a +so our candidate for a given a is 100002 * a. +We iterate through the search space 5000 <= a < 10000 in reverse order, +calculating the candidates for each a and checking if they are 1-9 pandigital. + +In case there are no 4-digit numbers that satisfy this property, we check +the 3-digit numbers with a similar formula (the example a=192 gives a lower +bound on the length of a): +a has 3 digits, etc... +=> 100 <= a < 334, candidate = a * 10^6 + 2a * 10^3 + 3a + = 1002003 * a +""" + +from typing import Union + + +def is_9_pandigital(n: int) -> bool: + """ + Checks whether n is a 9-digit 1 to 9 pandigital number. + >>> is_9_pandigital(12345) + False + >>> is_9_pandigital(156284973) + True + >>> is_9_pandigital(1562849733) + False + """ + s = str(n) + return len(s) == 9 and set(s) == set("123456789") + + +def solution() -> Union[int, None]: + """ + Return the largest 1 to 9 pandigital 9-digital number that can be formed as the + concatenated product of an integer with (1,2,...,n) where n > 1. + """ + for base_num in range(9999, 4999, -1): + candidate = 100002 * base_num + if is_9_pandigital(candidate): + return candidate + + for base_num in range(333, 99, -1): + candidate = 1002003 * base_num + if is_9_pandigital(candidate): + return candidate + + return None + + +if __name__ == "__main__": + print(f"{solution() = }") From 1cd8e685377a6b6f0f4ffacb5b78ca472ee874b8 Mon Sep 17 00:00:00 2001 From: Du Yuanchao Date: Sat, 24 Oct 2020 18:16:37 +0800 Subject: [PATCH 0953/1071] Update LinkedQueue (#3683) * update LinkedQueue * add type hint and rename --- data_structures/queue/linked_queue.py | 139 ++++++++++++++++++++------ 1 file changed, 108 insertions(+), 31 deletions(-) diff --git a/data_structures/queue/linked_queue.py b/data_structures/queue/linked_queue.py index 614c60cd1ae2..8526ad311ed0 100644 --- a/data_structures/queue/linked_queue.py +++ b/data_structures/queue/linked_queue.py @@ -1,18 +1,18 @@ -""" A Queue using a Linked List like structure """ -from typing import Any, Optional +""" A Queue using a linked list like structure """ +from typing import Any class Node: - def __init__(self, data: Any, next: Optional["Node"] = None): - self.data: Any = data - self.next: Optional["Node"] = next + def __init__(self, data: Any) -> None: + self.data = data + self.next = None + + def __str__(self) -> str: + return f"{self.data}" class LinkedQueue: """ - Linked List Queue implementing put (to end of queue), - get (from front of queue) and is_empty - >>> queue = LinkedQueue() >>> queue.is_empty() True @@ -35,40 +35,117 @@ class LinkedQueue: >>> queue.get() Traceback (most recent call last): ... - IndexError: get from empty queue + IndexError: dequeue from empty queue """ def __init__(self) -> None: - self.front: Optional[Node] = None - self.rear: Optional[Node] = None + self.front = self.rear = None + + def __iter__(self): + node = self.front + while node: + yield node.data + node = node.next + + def __len__(self) -> int: + """ + >>> queue = LinkedQueue() + >>> for i in range(1, 6): + ... queue.put(i) + >>> len(queue) + 5 + >>> for i in range(1, 6): + ... assert len(queue) == 6 - i + ... _ = queue.get() + >>> len(queue) + 0 + """ + return len(tuple(iter(self))) + + def __str__(self) -> str: + """ + >>> queue = LinkedQueue() + >>> for i in range(1, 4): + ... queue.put(i) + >>> queue.put("Python") + >>> queue.put(3.14) + >>> queue.put(True) + >>> str(queue) + '1 <- 2 <- 3 <- Python <- 3.14 <- True' + """ + return " <- ".join(str(item) for item in self) def is_empty(self) -> bool: - """ returns boolean describing if queue is empty """ - return self.front is None + """ + >>> queue = LinkedQueue() + >>> queue.is_empty() + True + >>> for i in range(1, 6): + ... queue.put(i) + >>> queue.is_empty() + False + """ + return len(self) == 0 - def put(self, item: Any) -> None: - """ append item to rear of queue """ - node: Node = Node(item) + def put(self, item) -> None: + """ + >>> queue = LinkedQueue() + >>> queue.get() + Traceback (most recent call last): + ... + IndexError: dequeue from empty queue + >>> for i in range(1, 6): + ... queue.put(i) + >>> str(queue) + '1 <- 2 <- 3 <- 4 <- 5' + """ + node = Node(item) if self.is_empty(): - # the queue contains just the single element - self.front = node - self.rear = node + self.front = self.rear = node else: - # not empty, so we add it to the rear of the queue assert isinstance(self.rear, Node) self.rear.next = node self.rear = node def get(self) -> Any: - """ returns and removes item at front of queue """ + """ + >>> queue = LinkedQueue() + >>> queue.get() + Traceback (most recent call last): + ... + IndexError: dequeue from empty queue + >>> queue = LinkedQueue() + >>> for i in range(1, 6): + ... queue.put(i) + >>> for i in range(1, 6): + ... assert queue.get() == i + >>> len(queue) + 0 + """ if self.is_empty(): - raise IndexError("get from empty queue") - else: - # "remove" element by having front point to the next one - assert isinstance(self.front, Node) - node: Node = self.front - self.front = node.next - if self.front is None: - self.rear = None - - return node.data + raise IndexError("dequeue from empty queue") + assert isinstance(self.front, Node) + node = self.front + self.front = self.front.next + if self.front is None: + self.rear = None + return node.data + + def clear(self) -> None: + """ + >>> queue = LinkedQueue() + >>> for i in range(1, 6): + ... queue.put(i) + >>> queue.clear() + >>> len(queue) + 0 + >>> str(queue) + '' + """ + self.front = self.rear = None + + +if __name__ == "__main__": + from doctest import testmod + + testmod() From b97529dd88b4b3668ad78ed1b8e35ea031face1c Mon Sep 17 00:00:00 2001 From: Dhruv Manilawala Date: Sat, 24 Oct 2020 19:07:33 +0530 Subject: [PATCH 0954/1071] Move validate_solutions and add durations flag to pytest.ini (#3704) * Move PE validate_solutions to scripts/ directory * Update pytest.ini file with durations settings * Remove codespell and autoblack workflow file * Dependent changes to test config files * Update pytest.ini --- .github/workflows/autoblack.yml | 25 ------------------- .github/workflows/codespell.yml | 17 ------------- .github/workflows/project_euler.yml | 7 +++--- .travis.yml | 2 +- pytest.ini | 1 + .../project_euler_answers.json | 0 .../validate_solutions.py | 4 +-- 7 files changed, 8 insertions(+), 48 deletions(-) delete mode 100644 .github/workflows/autoblack.yml delete mode 100644 .github/workflows/codespell.yml rename {project_euler => scripts}/project_euler_answers.json (100%) rename {project_euler => scripts}/validate_solutions.py (94%) diff --git a/.github/workflows/autoblack.yml b/.github/workflows/autoblack.yml deleted file mode 100644 index ce34170d44bb..000000000000 --- a/.github/workflows/autoblack.yml +++ /dev/null @@ -1,25 +0,0 @@ -# GitHub Action that uses Black to reformat Python code (if needed) when doing a git push. -# If all Python code in the repo is compliant with Black then this Action does nothing. -# Otherwise, Black is run and its changes are committed to the repo. -# https://github.com/cclauss/autoblack - -name: autoblack_push -on: [push] -jobs: - build: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v1 # Use v1, NOT v2 - - uses: actions/setup-python@v2 - - run: pip install black isort - - run: black --check . - - name: If needed, commit black changes to a new pull request - if: failure() - run: | - black . - isort --profile black . - git config --global user.name github-actions - git config --global user.email '${GITHUB_ACTOR}@users.noreply.github.com' - git remote set-url origin https://x-access-token:${{ secrets.GITHUB_TOKEN }}@github.com/$GITHUB_REPOSITORY - git commit -am "fixup! Format Python code with psf/black push" - git push --force origin HEAD:$GITHUB_REF diff --git a/.github/workflows/codespell.yml b/.github/workflows/codespell.yml deleted file mode 100644 index e336f697708c..000000000000 --- a/.github/workflows/codespell.yml +++ /dev/null @@ -1,17 +0,0 @@ -# GitHub Action to automate the identification of common misspellings in text files -# https://github.com/codespell-project/codespell -name: codespell -on: [push, pull_request] -jobs: - codespell: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - uses: actions/setup-python@v2 - - run: pip install codespell - - run: | - SKIP="./.*,./other/dictionary.txt,./other/words,./project_euler/problem_022/p022_names.txt" - codespell --ignore-words-list=ans,fo,followings,hist,iff,secant,som,tim --skip=$SKIP --quiet-level=2 - - name: Codespell comment - if: ${{ failure() }} - uses: plettich/python_codespell_action@master diff --git a/.github/workflows/project_euler.yml b/.github/workflows/project_euler.yml index 852b0adbcb56..e8b011af20a6 100644 --- a/.github/workflows/project_euler.yml +++ b/.github/workflows/project_euler.yml @@ -1,9 +1,10 @@ on: pull_request: - # only check if a file is changed within the project_euler directory + # only check if a file is changed within the project_euler directory and related files paths: - 'project_euler/**' - '.github/workflows/project_euler.yml' + - 'scripts/validate_solutions.py' name: 'Project Euler' @@ -17,7 +18,7 @@ jobs: run: | python -m pip install --upgrade pip python -m pip install --upgrade pytest pytest-cov - - run: pytest --doctest-modules --durations=10 --cov-report=term-missing:skip-covered --cov=project_euler/ project_euler/ + - run: pytest --doctest-modules --cov-report=term-missing:skip-covered --cov=project_euler/ project_euler/ validate-solutions: runs-on: ubuntu-latest steps: @@ -27,4 +28,4 @@ jobs: run: | python -m pip install --upgrade pip python -m pip install --upgrade pytest - - run: pytest --durations=10 project_euler/validate_solutions.py + - run: pytest scripts/validate_solutions.py diff --git a/.travis.yml b/.travis.yml index 2a4a6392d4e7..c74669ebcb51 100644 --- a/.travis.yml +++ b/.travis.yml @@ -9,7 +9,7 @@ jobs: - name: Build install: pip install pytest-cov -r requirements.txt script: - - pytest --doctest-modules --ignore=project_euler/ --durations=10 --cov-report=term-missing:skip-covered --cov=. . + - pytest --doctest-modules --ignore=project_euler/ --cov-report=term-missing:skip-covered --cov=. . after_success: - scripts/build_directory_md.py 2>&1 | tee DIRECTORY.md notifications: diff --git a/pytest.ini b/pytest.ini index a26de5e638dc..488379278230 100644 --- a/pytest.ini +++ b/pytest.ini @@ -2,3 +2,4 @@ [pytest] markers = mat_ops: mark a test as utilizing matrix operations. +addopts = --durations=10 diff --git a/project_euler/project_euler_answers.json b/scripts/project_euler_answers.json similarity index 100% rename from project_euler/project_euler_answers.json rename to scripts/project_euler_answers.json diff --git a/project_euler/validate_solutions.py b/scripts/validate_solutions.py similarity index 94% rename from project_euler/validate_solutions.py rename to scripts/validate_solutions.py index 6cc1d6498e37..e1f68ff843bb 100755 --- a/project_euler/validate_solutions.py +++ b/scripts/validate_solutions.py @@ -8,8 +8,8 @@ import pytest PROJECT_EULER_DIR_PATH = pathlib.Path.cwd().joinpath("project_euler") -PROJECT_EULER_ANSWERS_PATH = PROJECT_EULER_DIR_PATH.joinpath( - "project_euler_answers.json" +PROJECT_EULER_ANSWERS_PATH = pathlib.Path.cwd().joinpath( + "scripts", "project_euler_answers.json" ) with open(PROJECT_EULER_ANSWERS_PATH) as file_handle: From 12c69800bdb7e3eb25de2ece9837ed79dc58df2b Mon Sep 17 00:00:00 2001 From: Nandiya Date: Sat, 24 Oct 2020 21:07:27 +0700 Subject: [PATCH 0955/1071] Forecast (#3219) * add forecasting code * add statsmodel * sort import * sort import fix * fixing black * sort requirement * optimize code * try with limited data * sort again * sort fix * sort fix * delete warning and black * add code for forecasting * use black * add more hints to describe * add doctest * finding whitespace * fixing doctest * delete * revert back * revert back * revert back again * revert back again * revert back again * try trimming whitespace * try adding doctypeand etc * fixing reviews * deleting all the space * fixing the build * delete x * add description for safety checker * deleting subscription integer * fix docthint * make def to use function parameters and return values * make def to use function parameters and return values * type hints on data safety checker * optimize code * Update run.py Co-authored-by: FVFYK3GEHV22 Co-authored-by: Christian Clauss --- machine_learning/forecasting/__init__.py | 0 machine_learning/forecasting/ex_data.csv | 114 +++++++++++++++++ machine_learning/forecasting/run.py | 156 +++++++++++++++++++++++ requirements.txt | 1 + 4 files changed, 271 insertions(+) create mode 100644 machine_learning/forecasting/__init__.py create mode 100644 machine_learning/forecasting/ex_data.csv create mode 100644 machine_learning/forecasting/run.py diff --git a/machine_learning/forecasting/__init__.py b/machine_learning/forecasting/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/machine_learning/forecasting/ex_data.csv b/machine_learning/forecasting/ex_data.csv new file mode 100644 index 000000000000..1c429e649755 --- /dev/null +++ b/machine_learning/forecasting/ex_data.csv @@ -0,0 +1,114 @@ +total_user,total_events,days +18231,0.0,1 +22621,1.0,2 +15675,0.0,3 +23583,1.0,4 +68351,5.0,5 +34338,3.0,6 +19238,0.0,0 +24192,0.0,1 +70349,0.0,2 +103510,0.0,3 +128355,1.0,4 +148484,6.0,5 +153489,3.0,6 +162667,1.0,0 +311430,3.0,1 +435663,7.0,2 +273526,0.0,3 +628588,2.0,4 +454989,13.0,5 +539040,3.0,6 +52974,1.0,0 +103451,2.0,1 +810020,5.0,2 +580982,3.0,3 +216515,0.0,4 +134694,10.0,5 +93563,1.0,6 +55432,1.0,0 +169634,1.0,1 +254908,4.0,2 +315285,3.0,3 +191764,0.0,4 +514284,7.0,5 +181214,4.0,6 +78459,2.0,0 +161620,3.0,1 +245610,4.0,2 +326722,5.0,3 +214578,0.0,4 +312365,5.0,5 +232454,4.0,6 +178368,1.0,0 +97152,1.0,1 +222813,4.0,2 +285852,4.0,3 +192149,1.0,4 +142241,1.0,5 +173011,2.0,6 +56488,3.0,0 +89572,2.0,1 +356082,2.0,2 +172799,0.0,3 +142300,1.0,4 +78432,2.0,5 +539023,9.0,6 +62389,1.0,0 +70247,1.0,1 +89229,0.0,2 +94583,1.0,3 +102455,0.0,4 +129270,0.0,5 +311409,1.0,6 +1837026,0.0,0 +361824,0.0,1 +111379,2.0,2 +76337,2.0,3 +96747,0.0,4 +92058,0.0,5 +81929,2.0,6 +143423,0.0,0 +82939,0.0,1 +74403,1.0,2 +68234,0.0,3 +94556,1.0,4 +80311,0.0,5 +75283,3.0,6 +77724,0.0,0 +49229,2.0,1 +65708,2.0,2 +273864,1.0,3 +1711281,0.0,4 +1900253,5.0,5 +343071,1.0,6 +1551326,0.0,0 +56636,1.0,1 +272782,2.0,2 +1785678,0.0,3 +241866,0.0,4 +461904,0.0,5 +2191901,2.0,6 +102925,0.0,0 +242778,1.0,1 +298608,0.0,2 +322458,10.0,3 +216027,9.0,4 +916052,12.0,5 +193278,12.0,6 +263207,8.0,0 +672948,10.0,1 +281909,1.0,2 +384562,1.0,3 +1027375,2.0,4 +828905,9.0,5 +624188,22.0,6 +392218,8.0,0 +292581,10.0,1 +299869,12.0,2 +769455,20.0,3 +316443,8.0,4 +1212864,24.0,5 +1397338,28.0,6 +223249,8.0,0 +191264,14.0,1 diff --git a/machine_learning/forecasting/run.py b/machine_learning/forecasting/run.py new file mode 100644 index 000000000000..467371e8d2ff --- /dev/null +++ b/machine_learning/forecasting/run.py @@ -0,0 +1,156 @@ +""" +this is code for forecasting +but i modified it and used it for safety checker of data +for ex: you have a online shop and for some reason some data are +missing (the amount of data that u expected are not supposed to be) + then we can use it +*ps : 1. ofc we can use normal statistic method but in this case + the data is quite absurd and only a little^^ + 2. ofc u can use this and modified it for forecasting purpose + for the next 3 months sales or something, + u can just adjust it for ur own purpose +""" + +import numpy as np +import pandas as pd +from sklearn.preprocessing import Normalizer +from sklearn.svm import SVR +from statsmodels.tsa.statespace.sarimax import SARIMAX + + +def linear_regression_prediction( + train_dt: list, train_usr: list, train_mtch: list, test_dt: list, test_mtch: list +) -> float: + """ + First method: linear regression + input : training data (date, total_user, total_event) in list of float + output : list of total user prediction in float + >>> linear_regression_prediction([2,3,4,5], [5,3,4,6], [3,1,2,4], [2,1], [2,2]) + 5.000000000000003 + """ + x = [[1, item, train_mtch[i]] for i, item in enumerate(train_dt)] + x = np.array(x) + y = np.array(train_usr) + beta = np.dot(np.dot(np.linalg.inv(np.dot(x.transpose(), x)), x.transpose()), y) + return abs(beta[0] + test_dt[0] * beta[1] + test_mtch[0] + beta[2]) + + +def sarimax_predictor(train_user: list, train_match: list, test_match: list) -> float: + """ + second method: Sarimax + sarimax is a statistic method which using previous input + and learn its pattern to predict future data + input : training data (total_user, with exog data = total_event) in list of float + output : list of total user prediction in float + >>> sarimax_predictor([4,2,6,8], [3,1,2,4], [2]) + 6.6666671111109626 + """ + order = (1, 2, 1) + seasonal_order = (1, 1, 0, 7) + model = SARIMAX( + train_user, exog=train_match, order=order, seasonal_order=seasonal_order + ) + model_fit = model.fit(disp=False, maxiter=600, method="nm") + result = model_fit.predict(1, len(test_match), exog=[test_match]) + return result[0] + + +def support_vector_regressor(x_train: list, x_test: list, train_user: list) -> float: + """ + Third method: Support vector regressor + svr is quite the same with svm(support vector machine) + it uses the same principles as the SVM for classification, + with only a few minor differences and the only different is that + it suits better for regression purpose + input : training data (date, total_user, total_event) in list of float + where x = list of set (date and total event) + output : list of total user prediction in float + >>> support_vector_regressor([[5,2],[1,5],[6,2]], [[3,2]], [2,1,4]) + 1.634932078116079 + """ + regressor = SVR(kernel="rbf", C=1, gamma=0.1, epsilon=0.1) + regressor.fit(x_train, train_user) + y_pred = regressor.predict(x_test) + return y_pred[0] + + +def interquartile_range_checker(train_user: list) -> float: + """ + Optional method: interquatile range + input : list of total user in float + output : low limit of input in float + this method can be used to check whether some data is outlier or not + >>> interquartile_range_checker([1,2,3,4,5,6,7,8,9,10]) + 2.8 + """ + train_user.sort() + q1 = np.percentile(train_user, 25) + q3 = np.percentile(train_user, 75) + iqr = q3 - q1 + low_lim = q1 - (iqr * 0.1) + return low_lim + + +def data_safety_checker(list_vote: list, actual_result: float) -> None: + """ + Used to review all the votes (list result prediction) + and compare it to the actual result. + input : list of predictions + output : print whether it's safe or not + >>> data_safety_checker([2,3,4],5.0) + Today's data is not safe. + """ + safe = 0 + not_safe = 0 + for i in list_vote: + if i > actual_result: + safe = not_safe + 1 + else: + if abs(abs(i) - abs(actual_result)) <= 0.1: + safe = safe + 1 + else: + not_safe = not_safe + 1 + print(f"Today's data is {'not ' if safe <= not_safe else ''}safe.") + + +# data_input_df = pd.read_csv("ex_data.csv", header=None) +data_input = [[18231, 0.0, 1], [22621, 1.0, 2], [15675, 0.0, 3], [23583, 1.0, 4]] +data_input_df = pd.DataFrame(data_input, columns=["total_user", "total_even", "days"]) + +""" +data column = total user in a day, how much online event held in one day, +what day is that(sunday-saturday) +""" + +# start normalization +normalize_df = Normalizer().fit_transform(data_input_df.values) +# split data +total_date = normalize_df[:, 2].tolist() +total_user = normalize_df[:, 0].tolist() +total_match = normalize_df[:, 1].tolist() + +# for svr (input variable = total date and total match) +x = normalize_df[:, [1, 2]].tolist() +x_train = x[: len(x) - 1] +x_test = x[len(x) - 1 :] + +# for linear reression & sarimax +trn_date = total_date[: len(total_date) - 1] +trn_user = total_user[: len(total_user) - 1] +trn_match = total_match[: len(total_match) - 1] + +tst_date = total_date[len(total_date) - 1 :] +tst_user = total_user[len(total_user) - 1 :] +tst_match = total_match[len(total_match) - 1 :] + + +# voting system with forecasting +res_vote = [] +res_vote.append( + linear_regression_prediction(trn_date, trn_user, trn_match, tst_date, tst_match) +) +res_vote.append(sarimax_predictor(trn_user, trn_match, tst_match)) +res_vote.append(support_vector_regressor(x_train, x_test, trn_user)) + +# check the safety of todays'data^^ +data_safety_checker(res_vote, tst_user) diff --git a/requirements.txt b/requirements.txt index 67d9bbbd8448..8bbb8d524ed4 100644 --- a/requirements.txt +++ b/requirements.txt @@ -11,6 +11,7 @@ qiskit requests scikit-fuzzy sklearn +statsmodels sympy tensorflow xgboost From 89e8dbffba13ff5ae77e65d21260eac69ee0729a Mon Sep 17 00:00:00 2001 From: Sam Holst Date: Sat, 24 Oct 2020 10:19:59 -0700 Subject: [PATCH 0956/1071] removed extra line to match rest of file (#3528) --- scripts/validate_filenames.py | 1 - 1 file changed, 1 deletion(-) diff --git a/scripts/validate_filenames.py b/scripts/validate_filenames.py index e75bf6c18b07..419295fe679d 100755 --- a/scripts/validate_filenames.py +++ b/scripts/validate_filenames.py @@ -9,7 +9,6 @@ filepaths = list(good_file_paths()) assert filepaths, "good_file_paths() failed!" - upper_files = [file for file in filepaths if file != file.lower()] if upper_files: print(f"{len(upper_files)} files contain uppercase characters:") From 5be77f33f7c787f329d1810f685a68cc64265036 Mon Sep 17 00:00:00 2001 From: Phil Bazun Date: Sat, 24 Oct 2020 23:07:04 +0200 Subject: [PATCH 0957/1071] Add 0-1-bfs. (#3285) * Add 0-1-bfs. * fixup! Add 0-1-bfs. * fixup! Add 0-1-bfs. * Check edge weights. * Check edge vertecies. --- graphs/bfs_zero_one_shortest_path.py | 138 +++++++++++++++++++++++++++ 1 file changed, 138 insertions(+) create mode 100644 graphs/bfs_zero_one_shortest_path.py diff --git a/graphs/bfs_zero_one_shortest_path.py b/graphs/bfs_zero_one_shortest_path.py new file mode 100644 index 000000000000..a725fae7e48f --- /dev/null +++ b/graphs/bfs_zero_one_shortest_path.py @@ -0,0 +1,138 @@ +from collections import deque +from dataclasses import dataclass +from typing import Iterator, List + +""" +Finding the shortest path in 0-1-graph in O(E + V) which is faster than dijkstra. +0-1-graph is the weighted graph with the weights equal to 0 or 1. +Link: https://codeforces.com/blog/entry/22276 +""" + + +@dataclass +class Edge: + """Weighted directed graph edge.""" + + destination_vertex: int + weight: int + + +class AdjacencyList: + """Graph adjacency list.""" + + def __init__(self, size: int): + self._graph: List[List[Edge]] = [[] for _ in range(size)] + self._size = size + + def __getitem__(self, vertex: int) -> Iterator[Edge]: + """Get all the vertices adjacent to the given one.""" + return iter(self._graph[vertex]) + + @property + def size(self): + return self._size + + def add_edge(self, from_vertex: int, to_vertex: int, weight: int): + """ + >>> g = AdjacencyList(2) + >>> g.add_edge(0, 1, 0) + >>> g.add_edge(1, 0, 1) + >>> list(g[0]) + [Edge(destination_vertex=1, weight=0)] + >>> list(g[1]) + [Edge(destination_vertex=0, weight=1)] + >>> g.add_edge(0, 1, 2) + Traceback (most recent call last): + ... + ValueError: Edge weight must be either 0 or 1. + >>> g.add_edge(0, 2, 1) + Traceback (most recent call last): + ... + ValueError: Vertex indexes must be in [0; size). + """ + if weight not in (0, 1): + raise ValueError("Edge weight must be either 0 or 1.") + + if to_vertex < 0 or to_vertex >= self.size: + raise ValueError("Vertex indexes must be in [0; size).") + + self._graph[from_vertex].append(Edge(to_vertex, weight)) + + def get_shortest_path(self, start_vertex: int, finish_vertex: int) -> int: + """ + Return the shortest distance from start_vertex to finish_vertex in 0-1-graph. + 1 1 1 + 0--------->3 6--------7>------->8 + | ^ ^ ^ |1 + | | | |0 v + 0| |0 1| 9-------->10 + | | | ^ 1 + v | | |0 + 1--------->2<-------4------->5 + 0 1 1 + >>> g = AdjacencyList(11) + >>> g.add_edge(0, 1, 0) + >>> g.add_edge(0, 3, 1) + >>> g.add_edge(1, 2, 0) + >>> g.add_edge(2, 3, 0) + >>> g.add_edge(4, 2, 1) + >>> g.add_edge(4, 5, 1) + >>> g.add_edge(4, 6, 1) + >>> g.add_edge(5, 9, 0) + >>> g.add_edge(6, 7, 1) + >>> g.add_edge(7, 8, 1) + >>> g.add_edge(8, 10, 1) + >>> g.add_edge(9, 7, 0) + >>> g.add_edge(9, 10, 1) + >>> g.add_edge(1, 2, 2) + Traceback (most recent call last): + ... + ValueError: Edge weight must be either 0 or 1. + >>> g.get_shortest_path(0, 3) + 0 + >>> g.get_shortest_path(0, 4) + Traceback (most recent call last): + ... + ValueError: No path from start_vertex to finish_vertex. + >>> g.get_shortest_path(4, 10) + 2 + >>> g.get_shortest_path(4, 8) + 2 + >>> g.get_shortest_path(0, 1) + 0 + >>> g.get_shortest_path(1, 0) + Traceback (most recent call last): + ... + ValueError: No path from start_vertex to finish_vertex. + """ + queue = deque([start_vertex]) + distances = [None for i in range(self.size)] + distances[start_vertex] = 0 + + while queue: + current_vertex = queue.popleft() + current_distance = distances[current_vertex] + + for edge in self[current_vertex]: + new_distance = current_distance + edge.weight + if ( + distances[edge.destination_vertex] is not None + and new_distance >= distances[edge.destination_vertex] + ): + continue + distances[edge.destination_vertex] = new_distance + if edge.weight == 0: + queue.appendleft(edge.destination_vertex) + else: + queue.append(edge.destination_vertex) + + if distances[finish_vertex] is None: + raise ValueError("No path from start_vertex to finish_vertex.") + + return distances[finish_vertex] + + +if __name__ == "__main__": + import doctest + + doctest.testmod() From 98e9d6bdb6ce34dbdf47912803b979c11486d850 Mon Sep 17 00:00:00 2001 From: Michael D Date: Sun, 25 Oct 2020 04:23:16 +0100 Subject: [PATCH 0958/1071] Fix style of the first ten solutions for Project Euler (#3242) * Fix style of the first ten solutions for Project Euler - Unify the header docstring, and add reference URLs to wikipedia or similar - Fix docstrings to be properly multilined - Add newlines where appropriate - Add doctests where they were missing - Remove doctests that test for the correct solution - fix obvious spelling or grammar mistakes in comments and exception messages - Fix line endings to be UNIX. This makes two of the files seem to have changed completely - no functional changes in any of the solutions were done (except for the spelling fixes mentioned above) * Fix docstrings and main function as per Style Guide --- project_euler/problem_001/sol1.py | 13 ++-- project_euler/problem_001/sol2.py | 13 ++-- project_euler/problem_001/sol3.py | 10 +++- project_euler/problem_001/sol4.py | 99 ++++++++++++++++--------------- project_euler/problem_001/sol5.py | 15 +++-- project_euler/problem_001/sol6.py | 13 ++-- project_euler/problem_001/sol7.py | 13 ++-- project_euler/problem_002/sol1.py | 25 +++++--- project_euler/problem_002/sol2.py | 85 ++++++++++++++------------ project_euler/problem_002/sol3.py | 21 ++++--- project_euler/problem_002/sol4.py | 37 +++++++----- project_euler/problem_002/sol5.py | 24 +++++--- project_euler/problem_003/sol1.py | 44 ++++++++------ project_euler/problem_003/sol2.py | 32 ++++++---- project_euler/problem_003/sol3.py | 32 ++++++---- project_euler/problem_004/sol1.py | 26 ++++---- project_euler/problem_004/sol2.py | 21 ++++--- project_euler/problem_005/sol1.py | 36 ++++++----- project_euler/problem_005/sol2.py | 56 +++++++++++++---- project_euler/problem_006/sol1.py | 27 ++++----- project_euler/problem_006/sol2.py | 27 ++++----- project_euler/problem_006/sol3.py | 27 ++++----- project_euler/problem_006/sol4.py | 27 ++++----- project_euler/problem_007/sol1.py | 35 ++++++++--- project_euler/problem_007/sol2.py | 46 ++++++++------ project_euler/problem_007/sol3.py | 35 ++++++++--- project_euler/problem_008/sol1.py | 59 ++++++++++-------- project_euler/problem_008/sol2.py | 59 ++++++++++-------- project_euler/problem_008/sol3.py | 57 +++++++++--------- project_euler/problem_009/sol1.py | 36 +++++++---- project_euler/problem_009/sol2.py | 34 +++++++---- project_euler/problem_009/sol3.py | 21 ++++--- project_euler/problem_010/sol1.py | 23 ++++--- project_euler/problem_010/sol2.py | 26 +++++--- project_euler/problem_010/sol3.py | 30 ++++++---- 35 files changed, 716 insertions(+), 468 deletions(-) diff --git a/project_euler/problem_001/sol1.py b/project_euler/problem_001/sol1.py index 385bbbbf43b3..85ad32294c9b 100644 --- a/project_euler/problem_001/sol1.py +++ b/project_euler/problem_001/sol1.py @@ -1,13 +1,18 @@ """ -Problem Statement: +Project Euler Problem 1: https://projecteuler.net/problem=1 + +Multiples of 3 and 5 + If we list all the natural numbers below 10 that are multiples of 3 or 5, we get 3, 5, 6 and 9. The sum of these multiples is 23. -Find the sum of all the multiples of 3 or 5 below N. + +Find the sum of all the multiples of 3 or 5 below 1000. """ def solution(n: int = 1000) -> int: - """Returns the sum of all the multiples of 3 or 5 below n. + """ + Returns the sum of all the multiples of 3 or 5 below n. >>> solution(3) 0 @@ -25,4 +30,4 @@ def solution(n: int = 1000) -> int: if __name__ == "__main__": - print(solution(int(input().strip()))) + print(f"{solution() = }") diff --git a/project_euler/problem_001/sol2.py b/project_euler/problem_001/sol2.py index f08f548cb752..7093d3513378 100644 --- a/project_euler/problem_001/sol2.py +++ b/project_euler/problem_001/sol2.py @@ -1,13 +1,18 @@ """ -Problem Statement: +Project Euler Problem 1: https://projecteuler.net/problem=1 + +Multiples of 3 and 5 + If we list all the natural numbers below 10 that are multiples of 3 or 5, we get 3, 5, 6 and 9. The sum of these multiples is 23. -Find the sum of all the multiples of 3 or 5 below N. + +Find the sum of all the multiples of 3 or 5 below 1000. """ def solution(n: int = 1000) -> int: - """Returns the sum of all the multiples of 3 or 5 below n. + """ + Returns the sum of all the multiples of 3 or 5 below n. >>> solution(3) 0 @@ -30,4 +35,4 @@ def solution(n: int = 1000) -> int: if __name__ == "__main__": - print(solution(int(input().strip()))) + print(f"{solution() = }") diff --git a/project_euler/problem_001/sol3.py b/project_euler/problem_001/sol3.py index 67cb83faf238..8267fec84155 100644 --- a/project_euler/problem_001/sol3.py +++ b/project_euler/problem_001/sol3.py @@ -1,8 +1,12 @@ """ -Problem Statement: +Project Euler Problem 1: https://projecteuler.net/problem=1 + +Multiples of 3 and 5 + If we list all the natural numbers below 10 that are multiples of 3 or 5, we get 3, 5, 6 and 9. The sum of these multiples is 23. -Find the sum of all the multiples of 3 or 5 below N. + +Find the sum of all the multiples of 3 or 5 below 1000. """ @@ -57,4 +61,4 @@ def solution(n: int = 1000) -> int: if __name__ == "__main__": - print(solution(int(input().strip()))) + print(f"{solution() = }") diff --git a/project_euler/problem_001/sol4.py b/project_euler/problem_001/sol4.py index 77f323695898..a0643c05b34f 100644 --- a/project_euler/problem_001/sol4.py +++ b/project_euler/problem_001/sol4.py @@ -1,47 +1,52 @@ -""" -Problem Statement: -If we list all the natural numbers below 10 that are multiples of 3 or 5, -we get 3, 5, 6 and 9. The sum of these multiples is 23. -Find the sum of all the multiples of 3 or 5 below N. -""" - - -def solution(n: int = 1000) -> int: - """Returns the sum of all the multiples of 3 or 5 below n. - - >>> solution(3) - 0 - >>> solution(4) - 3 - >>> solution(10) - 23 - >>> solution(600) - 83700 - """ - - xmulti = [] - zmulti = [] - z = 3 - x = 5 - temp = 1 - while True: - result = z * temp - if result < n: - zmulti.append(result) - temp += 1 - else: - temp = 1 - break - while True: - result = x * temp - if result < n: - xmulti.append(result) - temp += 1 - else: - break - collection = list(set(xmulti + zmulti)) - return sum(collection) - - -if __name__ == "__main__": - print(solution(int(input().strip()))) +""" +Project Euler Problem 1: https://projecteuler.net/problem=1 + +Multiples of 3 and 5 + +If we list all the natural numbers below 10 that are multiples of 3 or 5, +we get 3, 5, 6 and 9. The sum of these multiples is 23. + +Find the sum of all the multiples of 3 or 5 below 1000. +""" + + +def solution(n: int = 1000) -> int: + """ + Returns the sum of all the multiples of 3 or 5 below n. + + >>> solution(3) + 0 + >>> solution(4) + 3 + >>> solution(10) + 23 + >>> solution(600) + 83700 + """ + + xmulti = [] + zmulti = [] + z = 3 + x = 5 + temp = 1 + while True: + result = z * temp + if result < n: + zmulti.append(result) + temp += 1 + else: + temp = 1 + break + while True: + result = x * temp + if result < n: + xmulti.append(result) + temp += 1 + else: + break + collection = list(set(xmulti + zmulti)) + return sum(collection) + + +if __name__ == "__main__": + print(f"{solution() = }") diff --git a/project_euler/problem_001/sol5.py b/project_euler/problem_001/sol5.py index 256516802ca0..7f0b0bd1bc7c 100644 --- a/project_euler/problem_001/sol5.py +++ b/project_euler/problem_001/sol5.py @@ -1,14 +1,19 @@ """ -Problem Statement: +Project Euler Problem 1: https://projecteuler.net/problem=1 + +Multiples of 3 and 5 + If we list all the natural numbers below 10 that are multiples of 3 or 5, we get 3, 5, 6 and 9. The sum of these multiples is 23. -Find the sum of all the multiples of 3 or 5 below N. + +Find the sum of all the multiples of 3 or 5 below 1000. """ def solution(n: int = 1000) -> int: - """Returns the sum of all the multiples of 3 or 5 below n. - A straightforward pythonic solution using list comprehension. + """ + Returns the sum of all the multiples of 3 or 5 below n. + A straightforward pythonic solution using list comprehension. >>> solution(3) 0 @@ -24,4 +29,4 @@ def solution(n: int = 1000) -> int: if __name__ == "__main__": - print(solution(int(input().strip()))) + print(f"{solution() = }") diff --git a/project_euler/problem_001/sol6.py b/project_euler/problem_001/sol6.py index 5f60512a73fb..8ddce18ced04 100644 --- a/project_euler/problem_001/sol6.py +++ b/project_euler/problem_001/sol6.py @@ -1,13 +1,18 @@ """ -Problem Statement: +Project Euler Problem 1: https://projecteuler.net/problem=1 + +Multiples of 3 and 5 + If we list all the natural numbers below 10 that are multiples of 3 or 5, we get 3, 5, 6 and 9. The sum of these multiples is 23. -Find the sum of all the multiples of 3 or 5 below N. + +Find the sum of all the multiples of 3 or 5 below 1000. """ def solution(n: int = 1000) -> int: - """Returns the sum of all the multiples of 3 or 5 below n. + """ + Returns the sum of all the multiples of 3 or 5 below n. >>> solution(3) 0 @@ -31,4 +36,4 @@ def solution(n: int = 1000) -> int: if __name__ == "__main__": - print(solution(int(input().strip()))) + print(f"{solution() = }") diff --git a/project_euler/problem_001/sol7.py b/project_euler/problem_001/sol7.py index 5761c00f2996..8f5d1977fdde 100644 --- a/project_euler/problem_001/sol7.py +++ b/project_euler/problem_001/sol7.py @@ -1,13 +1,18 @@ """ -Problem Statement: +Project Euler Problem 1: https://projecteuler.net/problem=1 + +Multiples of 3 and 5 + If we list all the natural numbers below 10 that are multiples of 3 or 5, we get 3, 5, 6 and 9. The sum of these multiples is 23. -Find the sum of all the multiples of 3 or 5 below N. + +Find the sum of all the multiples of 3 or 5 below 1000. """ def solution(n: int = 1000) -> int: - """Returns the sum of all the multiples of 3 or 5 below n. + """ + Returns the sum of all the multiples of 3 or 5 below n. >>> solution(3) 0 @@ -29,4 +34,4 @@ def solution(n: int = 1000) -> int: if __name__ == "__main__": - print(solution(int(input().strip()))) + print(f"{solution() = }") diff --git a/project_euler/problem_002/sol1.py b/project_euler/problem_002/sol1.py index 2acc93b0affc..539f68fb6bc1 100644 --- a/project_euler/problem_002/sol1.py +++ b/project_euler/problem_002/sol1.py @@ -1,19 +1,25 @@ """ -Problem: -Each new term in the Fibonacci sequence is generated by adding the previous two -terms. By starting with 1 and 2, the first 10 terms will be: +Project Euler Problem 2: https://projecteuler.net/problem=2 - 1,2,3,5,8,13,21,34,55,89,.. +Even Fibonacci Numbers + +Each new term in the Fibonacci sequence is generated by adding the previous +two terms. By starting with 1 and 2, the first 10 terms will be: + +1, 2, 3, 5, 8, 13, 21, 34, 55, 89, ... By considering the terms in the Fibonacci sequence whose values do not exceed -n, find the sum of the even-valued terms. e.g. for n=10, we have {2,8}, sum is -10. +four million, find the sum of the even-valued terms. + +References: + - https://en.wikipedia.org/wiki/Fibonacci_number """ def solution(n: int = 4000000) -> int: - """Returns the sum of all fibonacci sequence even elements that are lower - or equals to n. + """ + Returns the sum of all even fibonacci sequence elements that are lower + or equal to n. >>> solution(10) 10 @@ -26,6 +32,7 @@ def solution(n: int = 4000000) -> int: >>> solution(34) 44 """ + i = 1 j = 2 total = 0 @@ -38,4 +45,4 @@ def solution(n: int = 4000000) -> int: if __name__ == "__main__": - print(solution(int(input().strip()))) + print(f"{solution() = }") diff --git a/project_euler/problem_002/sol2.py b/project_euler/problem_002/sol2.py index 01fc552b9b21..9033d0a69bcf 100644 --- a/project_euler/problem_002/sol2.py +++ b/project_euler/problem_002/sol2.py @@ -1,39 +1,46 @@ -""" -Problem: -Each new term in the Fibonacci sequence is generated by adding the previous two -terms. By starting with 1 and 2, the first 10 terms will be: - - 1,2,3,5,8,13,21,34,55,89,.. - -By considering the terms in the Fibonacci sequence whose values do not exceed -n, find the sum of the even-valued terms. e.g. for n=10, we have {2,8}, sum is -10. -""" - - -def solution(n: int = 4000000) -> int: - """Returns the sum of all fibonacci sequence even elements that are lower - or equals to n. - - >>> solution(10) - 10 - >>> solution(15) - 10 - >>> solution(2) - 2 - >>> solution(1) - 0 - >>> solution(34) - 44 - """ - even_fibs = [] - a, b = 0, 1 - while b <= n: - if b % 2 == 0: - even_fibs.append(b) - a, b = b, a + b - return sum(even_fibs) - - -if __name__ == "__main__": - print(solution(int(input().strip()))) +""" +Project Euler Problem 2: https://projecteuler.net/problem=2 + +Even Fibonacci Numbers + +Each new term in the Fibonacci sequence is generated by adding the previous +two terms. By starting with 1 and 2, the first 10 terms will be: + +1, 2, 3, 5, 8, 13, 21, 34, 55, 89, ... + +By considering the terms in the Fibonacci sequence whose values do not exceed +four million, find the sum of the even-valued terms. + +References: + - https://en.wikipedia.org/wiki/Fibonacci_number +""" + + +def solution(n: int = 4000000) -> int: + """ + Returns the sum of all even fibonacci sequence elements that are lower + or equal to n. + + >>> solution(10) + 10 + >>> solution(15) + 10 + >>> solution(2) + 2 + >>> solution(1) + 0 + >>> solution(34) + 44 + """ + + even_fibs = [] + a, b = 0, 1 + while b <= n: + if b % 2 == 0: + even_fibs.append(b) + a, b = b, a + b + return sum(even_fibs) + + +if __name__ == "__main__": + print(f"{solution() = }") diff --git a/project_euler/problem_002/sol3.py b/project_euler/problem_002/sol3.py index 53d8ca6f1b68..3ae175a99815 100644 --- a/project_euler/problem_002/sol3.py +++ b/project_euler/problem_002/sol3.py @@ -1,19 +1,25 @@ """ -Problem: +Project Euler Problem 2: https://projecteuler.net/problem=2 + +Even Fibonacci Numbers + Each new term in the Fibonacci sequence is generated by adding the previous two terms. By starting with 1 and 2, the first 10 terms will be: - 1,2,3,5,8,13,21,34,55,89,.. +1, 2, 3, 5, 8, 13, 21, 34, 55, 89, ... By considering the terms in the Fibonacci sequence whose values do not exceed -n, find the sum of the even-valued terms. e.g. for n=10, we have {2,8}, sum is -10. +four million, find the sum of the even-valued terms. + +References: + - https://en.wikipedia.org/wiki/Fibonacci_number """ def solution(n: int = 4000000) -> int: - """Returns the sum of all fibonacci sequence even elements that are lower - or equals to n. + """ + Returns the sum of all even fibonacci sequence elements that are lower + or equal to n. >>> solution(10) 10 @@ -26,6 +32,7 @@ def solution(n: int = 4000000) -> int: >>> solution(34) 44 """ + if n <= 1: return 0 a = 0 @@ -38,4 +45,4 @@ def solution(n: int = 4000000) -> int: if __name__ == "__main__": - print(solution(int(input().strip()))) + print(f"{solution() = }") diff --git a/project_euler/problem_002/sol4.py b/project_euler/problem_002/sol4.py index a87410b7006d..70b7d6a80a1d 100644 --- a/project_euler/problem_002/sol4.py +++ b/project_euler/problem_002/sol4.py @@ -1,21 +1,27 @@ """ -Problem: -Each new term in the Fibonacci sequence is generated by adding the previous two -terms. By starting with 1 and 2, the first 10 terms will be: +Project Euler Problem 2: https://projecteuler.net/problem=2 - 1,2,3,5,8,13,21,34,55,89,.. +Even Fibonacci Numbers + +Each new term in the Fibonacci sequence is generated by adding the previous +two terms. By starting with 1 and 2, the first 10 terms will be: + +1, 2, 3, 5, 8, 13, 21, 34, 55, 89, ... By considering the terms in the Fibonacci sequence whose values do not exceed -n, find the sum of the even-valued terms. e.g. for n=10, we have {2,8}, sum is -10. +four million, find the sum of the even-valued terms. + +References: + - https://en.wikipedia.org/wiki/Fibonacci_number """ import math from decimal import Decimal, getcontext def solution(n: int = 4000000) -> int: - """Returns the sum of all fibonacci sequence even elements that are lower - or equals to n. + """ + Returns the sum of all even fibonacci sequence elements that are lower + or equal to n. >>> solution(10) 10 @@ -32,26 +38,27 @@ def solution(n: int = 4000000) -> int: >>> solution(0) Traceback (most recent call last): ... - ValueError: Parameter n must be greater or equal to one. + ValueError: Parameter n must be greater than or equal to one. >>> solution(-17) Traceback (most recent call last): ... - ValueError: Parameter n must be greater or equal to one. + ValueError: Parameter n must be greater than or equal to one. >>> solution([]) Traceback (most recent call last): ... - TypeError: Parameter n must be int or passive of cast to int. + TypeError: Parameter n must be int or castable to int. >>> solution("asd") Traceback (most recent call last): ... - TypeError: Parameter n must be int or passive of cast to int. + TypeError: Parameter n must be int or castable to int. """ + try: n = int(n) except (TypeError, ValueError): - raise TypeError("Parameter n must be int or passive of cast to int.") + raise TypeError("Parameter n must be int or castable to int.") if n <= 0: - raise ValueError("Parameter n must be greater or equal to one.") + raise ValueError("Parameter n must be greater than or equal to one.") getcontext().prec = 100 phi = (Decimal(5) ** Decimal(0.5) + 1) / Decimal(2) @@ -62,4 +69,4 @@ def solution(n: int = 4000000) -> int: if __name__ == "__main__": - print(solution(int(input().strip()))) + print(f"{solution() = }") diff --git a/project_euler/problem_002/sol5.py b/project_euler/problem_002/sol5.py index dcf6eae85891..390fd19ef638 100644 --- a/project_euler/problem_002/sol5.py +++ b/project_euler/problem_002/sol5.py @@ -1,19 +1,25 @@ """ -Problem: -Each new term in the Fibonacci sequence is generated by adding the previous two -terms. By starting with 1 and 2, the first 10 terms will be: +Project Euler Problem 2: https://projecteuler.net/problem=2 - 1,2,3,5,8,13,21,34,55,89,.. +Even Fibonacci Numbers + +Each new term in the Fibonacci sequence is generated by adding the previous +two terms. By starting with 1 and 2, the first 10 terms will be: + +1, 2, 3, 5, 8, 13, 21, 34, 55, 89, ... By considering the terms in the Fibonacci sequence whose values do not exceed -n, find the sum of the even-valued terms. e.g. for n=10, we have {2,8}, sum is -10. +four million, find the sum of the even-valued terms. + +References: + - https://en.wikipedia.org/wiki/Fibonacci_number """ def solution(n: int = 4000000) -> int: - """Returns the sum of all fibonacci sequence even elements that are lower - or equals to n. + """ + Returns the sum of all even fibonacci sequence elements that are lower + or equal to n. >>> solution(10) 10 @@ -43,4 +49,4 @@ def solution(n: int = 4000000) -> int: if __name__ == "__main__": - print(solution(int(input().strip()))) + print(f"{solution() = }") diff --git a/project_euler/problem_003/sol1.py b/project_euler/problem_003/sol1.py index 22efeb2c4e90..3441dbf9e0b3 100644 --- a/project_euler/problem_003/sol1.py +++ b/project_euler/problem_003/sol1.py @@ -1,16 +1,22 @@ """ -Problem: -The prime factors of 13195 are 5,7,13 and 29. What is the largest prime factor -of a given number N? +Project Euler Problem 3: https://projecteuler.net/problem=3 -e.g. for 10, largest prime factor = 5. For 17, largest prime factor = 17. -""" +Largest prime factor + +The prime factors of 13195 are 5, 7, 13 and 29. + +What is the largest prime factor of the number 600851475143? +References: + - https://en.wikipedia.org/wiki/Prime_number#Unique_factorization +""" import math def isprime(num: int) -> bool: - """Returns boolean representing primality of given number num. + """ + Returns boolean representing primality of given number num. + >>> isprime(2) True >>> isprime(3) @@ -22,14 +28,15 @@ def isprime(num: int) -> bool: >>> isprime(0) Traceback (most recent call last): ... - ValueError: Parameter num must be greater or equal to two. + ValueError: Parameter num must be greater than or equal to two. >>> isprime(1) Traceback (most recent call last): ... - ValueError: Parameter num must be greater or equal to two. + ValueError: Parameter num must be greater than or equal to two. """ + if num <= 1: - raise ValueError("Parameter num must be greater or equal to two.") + raise ValueError("Parameter num must be greater than or equal to two.") if num == 2: return True elif num % 2 == 0: @@ -41,7 +48,9 @@ def isprime(num: int) -> bool: def solution(n: int = 600851475143) -> int: - """Returns the largest prime factor of a given number n. + """ + Returns the largest prime factor of a given number n. + >>> solution(13195) 29 >>> solution(10) @@ -53,26 +62,27 @@ def solution(n: int = 600851475143) -> int: >>> solution(0) Traceback (most recent call last): ... - ValueError: Parameter n must be greater or equal to one. + ValueError: Parameter n must be greater than or equal to one. >>> solution(-17) Traceback (most recent call last): ... - ValueError: Parameter n must be greater or equal to one. + ValueError: Parameter n must be greater than or equal to one. >>> solution([]) Traceback (most recent call last): ... - TypeError: Parameter n must be int or passive of cast to int. + TypeError: Parameter n must be int or castable to int. >>> solution("asd") Traceback (most recent call last): ... - TypeError: Parameter n must be int or passive of cast to int. + TypeError: Parameter n must be int or castable to int. """ + try: n = int(n) except (TypeError, ValueError): - raise TypeError("Parameter n must be int or passive of cast to int.") + raise TypeError("Parameter n must be int or castable to int.") if n <= 0: - raise ValueError("Parameter n must be greater or equal to one.") + raise ValueError("Parameter n must be greater than or equal to one.") max_number = 0 if isprime(n): return n @@ -91,4 +101,4 @@ def solution(n: int = 600851475143) -> int: if __name__ == "__main__": - print(solution(int(input().strip()))) + print(f"{solution() = }") diff --git a/project_euler/problem_003/sol2.py b/project_euler/problem_003/sol2.py index f28232109a84..0af0daceed06 100644 --- a/project_euler/problem_003/sol2.py +++ b/project_euler/problem_003/sol2.py @@ -1,14 +1,21 @@ """ -Problem: -The prime factors of 13195 are 5,7,13 and 29. What is the largest prime factor -of a given number N? +Project Euler Problem 3: https://projecteuler.net/problem=3 -e.g. for 10, largest prime factor = 5. For 17, largest prime factor = 17. +Largest prime factor + +The prime factors of 13195 are 5, 7, 13 and 29. + +What is the largest prime factor of the number 600851475143? + +References: + - https://en.wikipedia.org/wiki/Prime_number#Unique_factorization """ def solution(n: int = 600851475143) -> int: - """Returns the largest prime factor of a given number n. + """ + Returns the largest prime factor of a given number n. + >>> solution(13195) 29 >>> solution(10) @@ -20,26 +27,27 @@ def solution(n: int = 600851475143) -> int: >>> solution(0) Traceback (most recent call last): ... - ValueError: Parameter n must be greater or equal to one. + ValueError: Parameter n must be greater than or equal to one. >>> solution(-17) Traceback (most recent call last): ... - ValueError: Parameter n must be greater or equal to one. + ValueError: Parameter n must be greater than or equal to one. >>> solution([]) Traceback (most recent call last): ... - TypeError: Parameter n must be int or passive of cast to int. + TypeError: Parameter n must be int or castable to int. >>> solution("asd") Traceback (most recent call last): ... - TypeError: Parameter n must be int or passive of cast to int. + TypeError: Parameter n must be int or castable to int. """ + try: n = int(n) except (TypeError, ValueError): - raise TypeError("Parameter n must be int or passive of cast to int.") + raise TypeError("Parameter n must be int or castable to int.") if n <= 0: - raise ValueError("Parameter n must be greater or equal to one.") + raise ValueError("Parameter n must be greater than or equal to one.") prime = 1 i = 2 while i * i <= n: @@ -53,4 +61,4 @@ def solution(n: int = 600851475143) -> int: if __name__ == "__main__": - print(solution(int(input().strip()))) + print(f"{solution() = }") diff --git a/project_euler/problem_003/sol3.py b/project_euler/problem_003/sol3.py index 676717cceca8..bc6f1d2f61ca 100644 --- a/project_euler/problem_003/sol3.py +++ b/project_euler/problem_003/sol3.py @@ -1,14 +1,21 @@ """ -Problem: -The prime factors of 13195 are 5,7,13 and 29. What is the largest prime factor -of a given number N? +Project Euler Problem 3: https://projecteuler.net/problem=3 -e.g. for 10, largest prime factor = 5. For 17, largest prime factor = 17. +Largest prime factor + +The prime factors of 13195 are 5, 7, 13 and 29. + +What is the largest prime factor of the number 600851475143? + +References: + - https://en.wikipedia.org/wiki/Prime_number#Unique_factorization """ def solution(n: int = 600851475143) -> int: - """Returns the largest prime factor of a given number n. + """ + Returns the largest prime factor of a given number n. + >>> solution(13195) 29 >>> solution(10) @@ -20,26 +27,27 @@ def solution(n: int = 600851475143) -> int: >>> solution(0) Traceback (most recent call last): ... - ValueError: Parameter n must be greater or equal to one. + ValueError: Parameter n must be greater than or equal to one. >>> solution(-17) Traceback (most recent call last): ... - ValueError: Parameter n must be greater or equal to one. + ValueError: Parameter n must be greater than or equal to one. >>> solution([]) Traceback (most recent call last): ... - TypeError: Parameter n must be int or passive of cast to int. + TypeError: Parameter n must be int or castable to int. >>> solution("asd") Traceback (most recent call last): ... - TypeError: Parameter n must be int or passive of cast to int. + TypeError: Parameter n must be int or castable to int. """ + try: n = int(n) except (TypeError, ValueError): - raise TypeError("Parameter n must be int or passive of cast to int.") + raise TypeError("Parameter n must be int or castable to int.") if n <= 0: - raise ValueError("Parameter n must be greater or equal to one.") + raise ValueError("Parameter n must be greater than or equal to one.") i = 2 ans = 0 if n == 2: @@ -55,4 +63,4 @@ def solution(n: int = 600851475143) -> int: if __name__ == "__main__": - print(solution(int(input().strip()))) + print(f"{solution() = }") diff --git a/project_euler/problem_004/sol1.py b/project_euler/problem_004/sol1.py index 42f56f3ef389..db6133a1a1d2 100644 --- a/project_euler/problem_004/sol1.py +++ b/project_euler/problem_004/sol1.py @@ -1,15 +1,21 @@ """ -Problem: -A palindromic number reads the same both ways. The largest palindrome made from -the product of two 2-digit numbers is 9009 = 91 x 99. +Project Euler Problem 4: https://projecteuler.net/problem=4 -Find the largest palindrome made from the product of two 3-digit numbers which -is less than N. +Largest palindrome product + +A palindromic number reads the same both ways. The largest palindrome made +from the product of two 2-digit numbers is 9009 = 91 × 99. + +Find the largest palindrome made from the product of two 3-digit numbers. + +References: + - https://en.wikipedia.org/wiki/Palindromic_number """ def solution(n: int = 998001) -> int: - """Returns the largest palindrome made from the product of two 3-digit + """ + Returns the largest palindrome made from the product of two 3-digit numbers which is less than n. >>> solution(20000) @@ -23,10 +29,10 @@ def solution(n: int = 998001) -> int: ... ValueError: That number is larger than our acceptable range. """ + # fetches the next number for number in range(n - 1, 9999, -1): - # converts number into string. str_number = str(number) # checks whether 'str_number' is a palindrome. @@ -44,8 +50,4 @@ def solution(n: int = 998001) -> int: if __name__ == "__main__": - import doctest - - doctest.testmod() - - print(solution(int(input().strip()))) + print(f"{solution() = }") diff --git a/project_euler/problem_004/sol2.py b/project_euler/problem_004/sol2.py index 8ee082ad2f6a..abc880966d58 100644 --- a/project_euler/problem_004/sol2.py +++ b/project_euler/problem_004/sol2.py @@ -1,15 +1,21 @@ """ -Problem: -A palindromic number reads the same both ways. The largest palindrome made from -the product of two 2-digit numbers is 9009 = 91 x 99. +Project Euler Problem 4: https://projecteuler.net/problem=4 -Find the largest palindrome made from the product of two 3-digit numbers which -is less than N. +Largest palindrome product + +A palindromic number reads the same both ways. The largest palindrome made +from the product of two 2-digit numbers is 9009 = 91 × 99. + +Find the largest palindrome made from the product of two 3-digit numbers. + +References: + - https://en.wikipedia.org/wiki/Palindromic_number """ def solution(n: int = 998001) -> int: - """Returns the largest palindrome made from the product of two 3-digit + """ + Returns the largest palindrome made from the product of two 3-digit numbers which is less than n. >>> solution(20000) @@ -19,6 +25,7 @@ def solution(n: int = 998001) -> int: >>> solution(40000) 39893 """ + answer = 0 for i in range(999, 99, -1): # 3 digit numbers range from 999 down to 100 for j in range(999, 99, -1): @@ -29,4 +36,4 @@ def solution(n: int = 998001) -> int: if __name__ == "__main__": - print(solution(int(input().strip()))) + print(f"{solution() = }") diff --git a/project_euler/problem_005/sol1.py b/project_euler/problem_005/sol1.py index a347d6564fa7..f272c102d2bb 100644 --- a/project_euler/problem_005/sol1.py +++ b/project_euler/problem_005/sol1.py @@ -1,23 +1,28 @@ """ -Problem: -2520 is the smallest number that can be divided by each of the numbers from 1 -to 10 without any remainder. +Project Euler Problem 5: https://projecteuler.net/problem=5 -What is the smallest positive number that is evenly divisible(divisible with no -remainder) by all of the numbers from 1 to N? +Smallest multiple + +2520 is the smallest number that can be divided by each of the numbers +from 1 to 10 without any remainder. + +What is the smallest positive number that is _evenly divisible_ by all +of the numbers from 1 to 20? + +References: + - https://en.wiktionary.org/wiki/evenly_divisible """ def solution(n: int = 20) -> int: - """Returns the smallest positive number that is evenly divisible(divisible + """ + Returns the smallest positive number that is evenly divisible (divisible with no remainder) by all of the numbers from 1 to n. >>> solution(10) 2520 >>> solution(15) 360360 - >>> solution(20) - 232792560 >>> solution(22) 232792560 >>> solution(3.4) @@ -25,26 +30,27 @@ def solution(n: int = 20) -> int: >>> solution(0) Traceback (most recent call last): ... - ValueError: Parameter n must be greater or equal to one. + ValueError: Parameter n must be greater than or equal to one. >>> solution(-17) Traceback (most recent call last): ... - ValueError: Parameter n must be greater or equal to one. + ValueError: Parameter n must be greater than or equal to one. >>> solution([]) Traceback (most recent call last): ... - TypeError: Parameter n must be int or passive of cast to int. + TypeError: Parameter n must be int or castable to int. >>> solution("asd") Traceback (most recent call last): ... - TypeError: Parameter n must be int or passive of cast to int. + TypeError: Parameter n must be int or castable to int. """ + try: n = int(n) except (TypeError, ValueError): - raise TypeError("Parameter n must be int or passive of cast to int.") + raise TypeError("Parameter n must be int or castable to int.") if n <= 0: - raise ValueError("Parameter n must be greater or equal to one.") + raise ValueError("Parameter n must be greater than or equal to one.") i = 0 while 1: i += n * (n - 1) @@ -60,4 +66,4 @@ def solution(n: int = 20) -> int: if __name__ == "__main__": - print(solution(int(input().strip()))) + print(f"{solution() = }") diff --git a/project_euler/problem_005/sol2.py b/project_euler/problem_005/sol2.py index 57b4cc823d82..c88044487d20 100644 --- a/project_euler/problem_005/sol2.py +++ b/project_euler/problem_005/sol2.py @@ -1,38 +1,70 @@ """ -Problem: -2520 is the smallest number that can be divided by each of the numbers from 1 -to 10 without any remainder. +Project Euler Problem 5: https://projecteuler.net/problem=5 -What is the smallest positive number that is evenly divisible(divisible with no -remainder) by all of the numbers from 1 to N? +Smallest multiple + +2520 is the smallest number that can be divided by each of the numbers +from 1 to 10 without any remainder. + +What is the smallest positive number that is _evenly divisible_ by all +of the numbers from 1 to 20? + +References: + - https://en.wiktionary.org/wiki/evenly_divisible + - https://en.wikipedia.org/wiki/Euclidean_algorithm + - https://en.wikipedia.org/wiki/Least_common_multiple """ -""" Euclidean GCD Algorithm """ def gcd(x: int, y: int) -> int: - return x if y == 0 else gcd(y, x % y) + """ + Euclidean GCD algorithm (Greatest Common Divisor) + >>> gcd(0, 0) + 0 + >>> gcd(23, 42) + 1 + >>> gcd(15, 33) + 3 + >>> gcd(12345, 67890) + 15 + """ -""" Using the property lcm*gcd of two numbers = product of them """ + return x if y == 0 else gcd(y, x % y) def lcm(x: int, y: int) -> int: + """ + Least Common Multiple. + + Using the property that lcm(a, b) * gcd(a, b) = a*b + + >>> lcm(3, 15) + 15 + >>> lcm(1, 27) + 27 + >>> lcm(13, 27) + 351 + >>> lcm(64, 48) + 192 + """ + return (x * y) // gcd(x, y) def solution(n: int = 20) -> int: - """Returns the smallest positive number that is evenly divisible(divisible + """ + Returns the smallest positive number that is evenly divisible (divisible with no remainder) by all of the numbers from 1 to n. >>> solution(10) 2520 >>> solution(15) 360360 - >>> solution(20) - 232792560 >>> solution(22) 232792560 """ + g = 1 for i in range(1, n + 1): g = lcm(g, i) @@ -40,4 +72,4 @@ def solution(n: int = 20) -> int: if __name__ == "__main__": - print(solution(int(input().strip()))) + print(f"{solution() = }") diff --git a/project_euler/problem_006/sol1.py b/project_euler/problem_006/sol1.py index 38f995bbf822..61dd7a321011 100644 --- a/project_euler/problem_006/sol1.py +++ b/project_euler/problem_006/sol1.py @@ -1,22 +1,25 @@ """ -Problem 6: https://projecteuler.net/problem=6 +Project Euler Problem 6: https://projecteuler.net/problem=6 + +Sum square difference The sum of the squares of the first ten natural numbers is, - 1^2 + 2^2 + ... + 10^2 = 385 + 1^2 + 2^2 + ... + 10^2 = 385 The square of the sum of the first ten natural numbers is, - (1 + 2 + ... + 10)^2 = 552 = 3025 + (1 + 2 + ... + 10)^2 = 55^2 = 3025 -Hence the difference between the sum of the squares of the first ten natural -numbers and the square of the sum is 3025 − 385 = 2640. +Hence the difference between the sum of the squares of the first ten +natural numbers and the square of the sum is 3025 - 385 = 2640. -Find the difference between the sum of the squares of the first N natural -numbers and the square of the sum. +Find the difference between the sum of the squares of the first one +hundred natural numbers and the square of the sum. """ def solution(n: int = 100) -> int: - """Returns the difference between the sum of the squares of the first n + """ + Returns the difference between the sum of the squares of the first n natural numbers and the square of the sum. >>> solution(10) @@ -27,9 +30,8 @@ def solution(n: int = 100) -> int: 41230 >>> solution(50) 1582700 - >>> solution() - 25164150 """ + sum_of_squares = 0 sum_of_ints = 0 for i in range(1, n + 1): @@ -39,7 +41,4 @@ def solution(n: int = 100) -> int: if __name__ == "__main__": - import doctest - - doctest.testmod() - print(solution(int(input().strip()))) + print(f"{solution() = }") diff --git a/project_euler/problem_006/sol2.py b/project_euler/problem_006/sol2.py index f4d74c993f0d..cd1bc5071e0e 100644 --- a/project_euler/problem_006/sol2.py +++ b/project_euler/problem_006/sol2.py @@ -1,22 +1,25 @@ """ -Problem 6: https://projecteuler.net/problem=6 +Project Euler Problem 6: https://projecteuler.net/problem=6 + +Sum square difference The sum of the squares of the first ten natural numbers is, - 1^2 + 2^2 + ... + 10^2 = 385 + 1^2 + 2^2 + ... + 10^2 = 385 The square of the sum of the first ten natural numbers is, - (1 + 2 + ... + 10)^2 = 552 = 3025 + (1 + 2 + ... + 10)^2 = 55^2 = 3025 -Hence the difference between the sum of the squares of the first ten natural -numbers and the square of the sum is 3025 − 385 = 2640. +Hence the difference between the sum of the squares of the first ten +natural numbers and the square of the sum is 3025 - 385 = 2640. -Find the difference between the sum of the squares of the first N natural -numbers and the square of the sum. +Find the difference between the sum of the squares of the first one +hundred natural numbers and the square of the sum. """ def solution(n: int = 100) -> int: - """Returns the difference between the sum of the squares of the first n + """ + Returns the difference between the sum of the squares of the first n natural numbers and the square of the sum. >>> solution(10) @@ -27,16 +30,12 @@ def solution(n: int = 100) -> int: 41230 >>> solution(50) 1582700 - >>> solution() - 25164150 """ + sum_cubes = (n * (n + 1) // 2) ** 2 sum_squares = n * (n + 1) * (2 * n + 1) // 6 return sum_cubes - sum_squares if __name__ == "__main__": - import doctest - - doctest.testmod() - print(solution(int(input().strip()))) + print(f"{solution() = }") diff --git a/project_euler/problem_006/sol3.py b/project_euler/problem_006/sol3.py index 8b5c5d3ba4aa..c87931309574 100644 --- a/project_euler/problem_006/sol3.py +++ b/project_euler/problem_006/sol3.py @@ -1,23 +1,26 @@ """ -Problem 6: https://projecteuler.net/problem=6 +Project Euler Problem 6: https://projecteuler.net/problem=6 + +Sum square difference The sum of the squares of the first ten natural numbers is, - 1^2 + 2^2 + ... + 10^2 = 385 + 1^2 + 2^2 + ... + 10^2 = 385 The square of the sum of the first ten natural numbers is, - (1 + 2 + ... + 10)^2 = 552 = 3025 + (1 + 2 + ... + 10)^2 = 55^2 = 3025 -Hence the difference between the sum of the squares of the first ten natural -numbers and the square of the sum is 3025 − 385 = 2640. +Hence the difference between the sum of the squares of the first ten +natural numbers and the square of the sum is 3025 - 385 = 2640. -Find the difference between the sum of the squares of the first N natural -numbers and the square of the sum. +Find the difference between the sum of the squares of the first one +hundred natural numbers and the square of the sum. """ import math def solution(n: int = 100) -> int: - """Returns the difference between the sum of the squares of the first n + """ + Returns the difference between the sum of the squares of the first n natural numbers and the square of the sum. >>> solution(10) @@ -28,16 +31,12 @@ def solution(n: int = 100) -> int: 41230 >>> solution(50) 1582700 - >>> solution() - 25164150 """ + sum_of_squares = sum([i * i for i in range(1, n + 1)]) square_of_sum = int(math.pow(sum(range(1, n + 1)), 2)) return square_of_sum - sum_of_squares if __name__ == "__main__": - import doctest - - doctest.testmod() - print(solution(int(input().strip()))) + print(f"{solution() = }") diff --git a/project_euler/problem_006/sol4.py b/project_euler/problem_006/sol4.py index 5fae84008448..748b141490a0 100644 --- a/project_euler/problem_006/sol4.py +++ b/project_euler/problem_006/sol4.py @@ -1,22 +1,25 @@ """ -Problem 6: https://projecteuler.net/problem=6 +Project Euler Problem 6: https://projecteuler.net/problem=6 + +Sum square difference The sum of the squares of the first ten natural numbers is, - 1^2 + 2^2 + ... + 10^2 = 385 + 1^2 + 2^2 + ... + 10^2 = 385 The square of the sum of the first ten natural numbers is, - (1 + 2 + ... + 10)^2 = 552 = 3025 + (1 + 2 + ... + 10)^2 = 55^2 = 3025 -Hence the difference between the sum of the squares of the first ten natural -numbers and the square of the sum is 3025 − 385 = 2640. +Hence the difference between the sum of the squares of the first ten +natural numbers and the square of the sum is 3025 - 385 = 2640. -Find the difference between the sum of the squares of the first N natural -numbers and the square of the sum. +Find the difference between the sum of the squares of the first one +hundred natural numbers and the square of the sum. """ def solution(n: int = 100) -> int: - """Returns the difference between the sum of the squares of the first n + """ + Returns the difference between the sum of the squares of the first n natural numbers and the square of the sum. >>> solution(10) @@ -27,16 +30,12 @@ def solution(n: int = 100) -> int: 41230 >>> solution(50) 1582700 - >>> solution() - 25164150 """ + sum_of_squares = n * (n + 1) * (2 * n + 1) / 6 square_of_sum = (n * (n + 1) / 2) ** 2 return int(square_of_sum - sum_of_squares) if __name__ == "__main__": - import doctest - - doctest.testmod() - print(solution(int(input("Enter a number: ").strip()))) + print(f"{solution() = }") diff --git a/project_euler/problem_007/sol1.py b/project_euler/problem_007/sol1.py index 727d7fb7fac6..78fbcb511611 100644 --- a/project_euler/problem_007/sol1.py +++ b/project_euler/problem_007/sol1.py @@ -1,17 +1,34 @@ """ -Problem 7: https://projecteuler.net/problem=7 +Project Euler Problem 7: https://projecteuler.net/problem=7 -By listing the first six prime numbers: +10001st prime - 2, 3, 5, 7, 11, and 13 +By listing the first six prime numbers: 2, 3, 5, 7, 11, and 13, we +can see that the 6th prime is 13. -We can see that the 6th prime is 13. What is the Nth prime number? +What is the 10001st prime number? + +References: + - https://en.wikipedia.org/wiki/Prime_number """ + from math import sqrt def is_prime(num: int) -> bool: - """Determines whether the given number is prime or not""" + """ + Determines whether the given number is prime or not + + >>> is_prime(2) + True + >>> is_prime(15) + False + >>> is_prime(29) + True + >>> is_prime(0) + False + """ + if num == 2: return True elif num % 2 == 0: @@ -25,7 +42,8 @@ def is_prime(num: int) -> bool: def solution(nth: int = 10001) -> int: - """Returns the n-th prime number. + """ + Returns the n-th prime number. >>> solution(6) 13 @@ -39,9 +57,8 @@ def solution(nth: int = 10001) -> int: 229 >>> solution(100) 541 - >>> solution() - 104743 """ + count = 0 number = 1 while count != nth and number < 3: @@ -56,4 +73,4 @@ def solution(nth: int = 10001) -> int: if __name__ == "__main__": - print(solution(int(input().strip()))) + print(f"{solution() = }") diff --git a/project_euler/problem_007/sol2.py b/project_euler/problem_007/sol2.py index 62806e1e2e5d..b395c631b766 100644 --- a/project_euler/problem_007/sol2.py +++ b/project_euler/problem_007/sol2.py @@ -1,16 +1,30 @@ """ -Problem 7: https://projecteuler.net/problem=7 +Project Euler Problem 7: https://projecteuler.net/problem=7 -By listing the first six prime numbers: +10001st prime - 2, 3, 5, 7, 11, and 13 +By listing the first six prime numbers: 2, 3, 5, 7, 11, and 13, we +can see that the 6th prime is 13. -We can see that the 6th prime is 13. What is the Nth prime number? +What is the 10001st prime number? + +References: + - https://en.wikipedia.org/wiki/Prime_number """ def isprime(number: int) -> bool: - """Determines whether the given number is prime or not""" + """ + Determines whether the given number is prime or not + + >>> isprime(2) + True + >>> isprime(15) + False + >>> isprime(29) + True + """ + for i in range(2, int(number ** 0.5) + 1): if number % i == 0: return False @@ -18,7 +32,8 @@ def isprime(number: int) -> bool: def solution(nth: int = 10001) -> int: - """Returns the n-th prime number. + """ + Returns the n-th prime number. >>> solution(6) 13 @@ -32,35 +47,32 @@ def solution(nth: int = 10001) -> int: 229 >>> solution(100) 541 - >>> solution() - 104743 >>> solution(3.4) 5 >>> solution(0) Traceback (most recent call last): ... - ValueError: Parameter nth must be greater or equal to one. + ValueError: Parameter nth must be greater than or equal to one. >>> solution(-17) Traceback (most recent call last): ... - ValueError: Parameter nth must be greater or equal to one. + ValueError: Parameter nth must be greater than or equal to one. >>> solution([]) Traceback (most recent call last): ... - TypeError: Parameter nth must be int or passive of cast to int. + TypeError: Parameter nth must be int or castable to int. >>> solution("asd") Traceback (most recent call last): ... - TypeError: Parameter nth must be int or passive of cast to int. + TypeError: Parameter nth must be int or castable to int. """ + try: nth = int(nth) except (TypeError, ValueError): - raise TypeError( - "Parameter nth must be int or passive of cast to int." - ) from None + raise TypeError("Parameter nth must be int or castable to int.") from None if nth <= 0: - raise ValueError("Parameter nth must be greater or equal to one.") + raise ValueError("Parameter nth must be greater than or equal to one.") primes = [] num = 2 while len(primes) < nth: @@ -73,4 +85,4 @@ def solution(nth: int = 10001) -> int: if __name__ == "__main__": - print(solution(int(input().strip()))) + print(f"{solution() = }") diff --git a/project_euler/problem_007/sol3.py b/project_euler/problem_007/sol3.py index 1182875c05c9..7911fa3e9d6f 100644 --- a/project_euler/problem_007/sol3.py +++ b/project_euler/problem_007/sol3.py @@ -1,24 +1,42 @@ """ -Project 7: https://projecteuler.net/problem=7 +Project Euler Problem 7: https://projecteuler.net/problem=7 -By listing the first six prime numbers: +10001st prime - 2, 3, 5, 7, 11, and 13 +By listing the first six prime numbers: 2, 3, 5, 7, 11, and 13, we +can see that the 6th prime is 13. -We can see that the 6th prime is 13. What is the Nth prime number? +What is the 10001st prime number? + +References: + - https://en.wikipedia.org/wiki/Prime_number """ import itertools import math def prime_check(number: int) -> bool: - """Determines whether a given number is prime or not""" + """ + Determines whether a given number is prime or not + + >>> prime_check(2) + True + >>> prime_check(15) + False + >>> prime_check(29) + True + """ + if number % 2 == 0 and number > 2: return False return all(number % i for i in range(3, int(math.sqrt(number)) + 1, 2)) def prime_generator(): + """ + Generate a sequence of prime numbers + """ + num = 2 while True: if prime_check(num): @@ -27,7 +45,8 @@ def prime_generator(): def solution(nth: int = 10001) -> int: - """Returns the n-th prime number. + """ + Returns the n-th prime number. >>> solution(6) 13 @@ -41,11 +60,9 @@ def solution(nth: int = 10001) -> int: 229 >>> solution(100) 541 - >>> solution() - 104743 """ return next(itertools.islice(prime_generator(), nth - 1, nth)) if __name__ == "__main__": - print(solution(int(input().strip()))) + print(f"{solution() = }") diff --git a/project_euler/problem_008/sol1.py b/project_euler/problem_008/sol1.py index db15907b3fbd..796080127778 100644 --- a/project_euler/problem_008/sol1.py +++ b/project_euler/problem_008/sol1.py @@ -1,33 +1,36 @@ """ -Problem 8: https://projecteuler.net/problem=8 +Project Euler Problem 8: https://projecteuler.net/problem=8 + +Largest product in a series The four adjacent digits in the 1000-digit number that have the greatest product are 9 × 9 × 8 × 9 = 5832. -73167176531330624919225119674426574742355349194934 -96983520312774506326239578318016984801869478851843 -85861560789112949495459501737958331952853208805511 -12540698747158523863050715693290963295227443043557 -66896648950445244523161731856403098711121722383113 -62229893423380308135336276614282806444486645238749 -30358907296290491560440772390713810515859307960866 -70172427121883998797908792274921901699720888093776 -65727333001053367881220235421809751254540594752243 -52584907711670556013604839586446706324415722155397 -53697817977846174064955149290862569321978468622482 -83972241375657056057490261407972968652414535100474 -82166370484403199890008895243450658541227588666881 -16427171479924442928230863465674813919123162824586 -17866458359124566529476545682848912883142607690042 -24219022671055626321111109370544217506941658960408 -07198403850962455444362981230987879927244284909188 -84580156166097919133875499200524063689912560717606 -05886116467109405077541002256983155200055935729725 -71636269561882670428252483600823257530420752963450 + 73167176531330624919225119674426574742355349194934 + 96983520312774506326239578318016984801869478851843 + 85861560789112949495459501737958331952853208805511 + 12540698747158523863050715693290963295227443043557 + 66896648950445244523161731856403098711121722383113 + 62229893423380308135336276614282806444486645238749 + 30358907296290491560440772390713810515859307960866 + 70172427121883998797908792274921901699720888093776 + 65727333001053367881220235421809751254540594752243 + 52584907711670556013604839586446706324415722155397 + 53697817977846174064955149290862569321978468622482 + 83972241375657056057490261407972968652414535100474 + 82166370484403199890008895243450658541227588666881 + 16427171479924442928230863465674813919123162824586 + 17866458359124566529476545682848912883142607690042 + 24219022671055626321111109370544217506941658960408 + 07198403850962455444362981230987879927244284909188 + 84580156166097919133875499200524063689912560717606 + 05886116467109405077541002256983155200055935729725 + 71636269561882670428252483600823257530420752963450 Find the thirteen adjacent digits in the 1000-digit number that have the greatest product. What is the value of this product? """ + import sys N = """73167176531330624919225119674426574742355349194934\ @@ -53,12 +56,18 @@ def solution(n: str = N) -> int: - """Find the thirteen adjacent digits in the 1000-digit number n that have + """ + Find the thirteen adjacent digits in the 1000-digit number n that have the greatest product and returns it. - >>> solution(N) - 23514624000 + >>> solution("13978431290823798458352374") + 609638400 + >>> solution("13978431295823798458352374") + 2612736000 + >>> solution("1397843129582379841238352374") + 209018880 """ + largest_product = -sys.maxsize - 1 for i in range(len(n) - 12): product = 1 @@ -70,4 +79,4 @@ def solution(n: str = N) -> int: if __name__ == "__main__": - print(solution(N)) + print(f"{solution() = }") diff --git a/project_euler/problem_008/sol2.py b/project_euler/problem_008/sol2.py index 1b338a9553d7..d2c1b4f7ca48 100644 --- a/project_euler/problem_008/sol2.py +++ b/project_euler/problem_008/sol2.py @@ -1,34 +1,35 @@ """ -Problem 8: https://projecteuler.net/problem=8 +Project Euler Problem 8: https://projecteuler.net/problem=8 + +Largest product in a series The four adjacent digits in the 1000-digit number that have the greatest product are 9 × 9 × 8 × 9 = 5832. -73167176531330624919225119674426574742355349194934 -96983520312774506326239578318016984801869478851843 -85861560789112949495459501737958331952853208805511 -12540698747158523863050715693290963295227443043557 -66896648950445244523161731856403098711121722383113 -62229893423380308135336276614282806444486645238749 -30358907296290491560440772390713810515859307960866 -70172427121883998797908792274921901699720888093776 -65727333001053367881220235421809751254540594752243 -52584907711670556013604839586446706324415722155397 -53697817977846174064955149290862569321978468622482 -83972241375657056057490261407972968652414535100474 -82166370484403199890008895243450658541227588666881 -16427171479924442928230863465674813919123162824586 -17866458359124566529476545682848912883142607690042 -24219022671055626321111109370544217506941658960408 -07198403850962455444362981230987879927244284909188 -84580156166097919133875499200524063689912560717606 -05886116467109405077541002256983155200055935729725 -71636269561882670428252483600823257530420752963450 + 73167176531330624919225119674426574742355349194934 + 96983520312774506326239578318016984801869478851843 + 85861560789112949495459501737958331952853208805511 + 12540698747158523863050715693290963295227443043557 + 66896648950445244523161731856403098711121722383113 + 62229893423380308135336276614282806444486645238749 + 30358907296290491560440772390713810515859307960866 + 70172427121883998797908792274921901699720888093776 + 65727333001053367881220235421809751254540594752243 + 52584907711670556013604839586446706324415722155397 + 53697817977846174064955149290862569321978468622482 + 83972241375657056057490261407972968652414535100474 + 82166370484403199890008895243450658541227588666881 + 16427171479924442928230863465674813919123162824586 + 17866458359124566529476545682848912883142607690042 + 24219022671055626321111109370544217506941658960408 + 07198403850962455444362981230987879927244284909188 + 84580156166097919133875499200524063689912560717606 + 05886116467109405077541002256983155200055935729725 + 71636269561882670428252483600823257530420752963450 Find the thirteen adjacent digits in the 1000-digit number that have the greatest product. What is the value of this product? """ - from functools import reduce N = ( @@ -56,12 +57,18 @@ def solution(n: str = N) -> int: - """Find the thirteen adjacent digits in the 1000-digit number n that have + """ + Find the thirteen adjacent digits in the 1000-digit number n that have the greatest product and returns it. - >>> solution(N) - 23514624000 + >>> solution("13978431290823798458352374") + 609638400 + >>> solution("13978431295823798458352374") + 2612736000 + >>> solution("1397843129582379841238352374") + 209018880 """ + return max( [ reduce(lambda x, y: int(x) * int(y), n[i : i + 13]) @@ -71,4 +78,4 @@ def solution(n: str = N) -> int: if __name__ == "__main__": - print(solution(str(N))) + print(f"{solution() = }") diff --git a/project_euler/problem_008/sol3.py b/project_euler/problem_008/sol3.py index 17f68cba57d3..4b99d0ea6e76 100644 --- a/project_euler/problem_008/sol3.py +++ b/project_euler/problem_008/sol3.py @@ -1,29 +1,31 @@ """ -Problem 8: https://projecteuler.net/problem=8 +Project Euler Problem 8: https://projecteuler.net/problem=8 + +Largest product in a series The four adjacent digits in the 1000-digit number that have the greatest product are 9 × 9 × 8 × 9 = 5832. -73167176531330624919225119674426574742355349194934 -96983520312774506326239578318016984801869478851843 -85861560789112949495459501737958331952853208805511 -12540698747158523863050715693290963295227443043557 -66896648950445244523161731856403098711121722383113 -62229893423380308135336276614282806444486645238749 -30358907296290491560440772390713810515859307960866 -70172427121883998797908792274921901699720888093776 -65727333001053367881220235421809751254540594752243 -52584907711670556013604839586446706324415722155397 -53697817977846174064955149290862569321978468622482 -83972241375657056057490261407972968652414535100474 -82166370484403199890008895243450658541227588666881 -16427171479924442928230863465674813919123162824586 -17866458359124566529476545682848912883142607690042 -24219022671055626321111109370544217506941658960408 -07198403850962455444362981230987879927244284909188 -84580156166097919133875499200524063689912560717606 -05886116467109405077541002256983155200055935729725 -71636269561882670428252483600823257530420752963450 + 73167176531330624919225119674426574742355349194934 + 96983520312774506326239578318016984801869478851843 + 85861560789112949495459501737958331952853208805511 + 12540698747158523863050715693290963295227443043557 + 66896648950445244523161731856403098711121722383113 + 62229893423380308135336276614282806444486645238749 + 30358907296290491560440772390713810515859307960866 + 70172427121883998797908792274921901699720888093776 + 65727333001053367881220235421809751254540594752243 + 52584907711670556013604839586446706324415722155397 + 53697817977846174064955149290862569321978468622482 + 83972241375657056057490261407972968652414535100474 + 82166370484403199890008895243450658541227588666881 + 16427171479924442928230863465674813919123162824586 + 17866458359124566529476545682848912883142607690042 + 24219022671055626321111109370544217506941658960408 + 07198403850962455444362981230987879927244284909188 + 84580156166097919133875499200524063689912560717606 + 05886116467109405077541002256983155200055935729725 + 71636269561882670428252483600823257530420752963450 Find the thirteen adjacent digits in the 1000-digit number that have the greatest product. What is the value of this product? @@ -53,13 +55,15 @@ def str_eval(s: str) -> int: - """Returns product of digits in given string n + """ + Returns product of digits in given string n >>> str_eval("987654321") 362880 >>> str_eval("22222222") 256 """ + product = 1 for digit in s: product *= int(digit) @@ -67,12 +71,11 @@ def str_eval(s: str) -> int: def solution(n: str = N) -> int: - """Find the thirteen adjacent digits in the 1000-digit number n that have + """ + Find the thirteen adjacent digits in the 1000-digit number n that have the greatest product and returns it. - - >>> solution(N) - 23514624000 """ + largest_product = -sys.maxsize - 1 substr = n[:13] cur_index = 13 @@ -88,4 +91,4 @@ def solution(n: str = N) -> int: if __name__ == "__main__": - print(solution(N)) + print(f"{solution() = }") diff --git a/project_euler/problem_009/sol1.py b/project_euler/problem_009/sol1.py index 1ab3376cae33..a58ea943e48b 100644 --- a/project_euler/problem_009/sol1.py +++ b/project_euler/problem_009/sol1.py @@ -1,26 +1,35 @@ """ -Problem 9: https://projecteuler.net/problem=9 +Project Euler Problem 9: https://projecteuler.net/problem=9 + +Special Pythagorean triplet A Pythagorean triplet is a set of three natural numbers, a < b < c, for which, + a^2 + b^2 = c^2 + For example, 3^2 + 4^2 = 9 + 16 = 25 = 5^2. There exists exactly one Pythagorean triplet for which a + b + c = 1000. -Find the product abc. +Find the product a*b*c. + +References: + - https://en.wikipedia.org/wiki/Pythagorean_triple """ def solution() -> int: """ - Returns the product of a,b,c which are Pythagorean Triplet that satisfies - the following: - 1. a < b < c - 2. a**2 + b**2 = c**2 - 3. a + b + c = 1000 + Returns the product of a,b,c which are Pythagorean Triplet that satisfies + the following: + 1. a < b < c + 2. a**2 + b**2 = c**2 + 3. a + b + c = 1000 + # The code below has been commented due to slow execution affecting Travis. # >>> solution() # 31875000 """ + for a in range(300): for b in range(400): for c in range(500): @@ -32,16 +41,17 @@ def solution() -> int: def solution_fast() -> int: """ - Returns the product of a,b,c which are Pythagorean Triplet that satisfies - the following: - 1. a < b < c - 2. a**2 + b**2 = c**2 - 3. a + b + c = 1000 + Returns the product of a,b,c which are Pythagorean Triplet that satisfies + the following: + 1. a < b < c + 2. a**2 + b**2 = c**2 + 3. a + b + c = 1000 # The code below has been commented due to slow execution affecting Travis. # >>> solution_fast() # 31875000 """ + for a in range(300): for b in range(400): c = 1000 - a - b @@ -66,4 +76,4 @@ def benchmark() -> None: if __name__ == "__main__": - benchmark() + print(f"{solution() = }") diff --git a/project_euler/problem_009/sol2.py b/project_euler/problem_009/sol2.py index e22ed45e8644..722ad522ee45 100644 --- a/project_euler/problem_009/sol2.py +++ b/project_euler/problem_009/sol2.py @@ -1,30 +1,40 @@ """ -Problem 9: https://projecteuler.net/problem=9 +Project Euler Problem 9: https://projecteuler.net/problem=9 + +Special Pythagorean triplet A Pythagorean triplet is a set of three natural numbers, a < b < c, for which, + a^2 + b^2 = c^2 + For example, 3^2 + 4^2 = 9 + 16 = 25 = 5^2. There exists exactly one Pythagorean triplet for which a + b + c = 1000. -Find the product abc. +Find the product a*b*c. + +References: + - https://en.wikipedia.org/wiki/Pythagorean_triple """ def solution(n: int = 1000) -> int: """ - Return the product of a,b,c which are Pythagorean Triplet that satisfies - the following: - 1. a < b < c - 2. a**2 + b**2 = c**2 - 3. a + b + c = n - - >>> solution(1000) - 31875000 + Return the product of a,b,c which are Pythagorean Triplet that satisfies + the following: + 1. a < b < c + 2. a**2 + b**2 = c**2 + 3. a + b + c = n + + >>> solution(36) + 1620 + >>> solution(126) + 66780 """ + product = -1 candidate = 0 for a in range(1, n // 3): - """Solving the two equations a**2+b**2=c**2 and a+b+c=N eliminating c""" + # Solving the two equations a**2+b**2=c**2 and a+b+c=N eliminating c b = (n * n - 2 * a * n) // (2 * n - 2 * a) c = n - a - b if c * c == (a * a + b * b): @@ -35,4 +45,4 @@ def solution(n: int = 1000) -> int: if __name__ == "__main__": - print(solution(int(input().strip()))) + print(f"{solution() = }") diff --git a/project_euler/problem_009/sol3.py b/project_euler/problem_009/sol3.py index 0900a76e6c56..03aed4b70761 100644 --- a/project_euler/problem_009/sol3.py +++ b/project_euler/problem_009/sol3.py @@ -1,5 +1,7 @@ """ -Problem 9: https://projecteuler.net/problem=9 +Project Euler Problem 9: https://projecteuler.net/problem=9 + +Special Pythagorean triplet A Pythagorean triplet is a set of three natural numbers, a < b < c, for which, @@ -8,22 +10,25 @@ For example, 3^2 + 4^2 = 9 + 16 = 25 = 5^2. There exists exactly one Pythagorean triplet for which a + b + c = 1000. -Find the product abc. +Find the product a*b*c. + +References: + - https://en.wikipedia.org/wiki/Pythagorean_triple """ def solution() -> int: """ - Returns the product of a,b,c which are Pythagorean Triplet that satisfies - the following: - - 1. a**2 + b**2 = c**2 - 2. a + b + c = 1000 + Returns the product of a,b,c which are Pythagorean Triplet that satisfies + the following: + 1. a**2 + b**2 = c**2 + 2. a + b + c = 1000 # The code below has been commented due to slow execution affecting Travis. # >>> solution() # 31875000 """ + return [ a * b * (1000 - a - b) for a in range(1, 999) @@ -33,4 +38,4 @@ def solution() -> int: if __name__ == "__main__": - print(solution()) + print(f"{solution() = }") diff --git a/project_euler/problem_010/sol1.py b/project_euler/problem_010/sol1.py index 4f3b3a4a42f5..bd49b3523c97 100644 --- a/project_euler/problem_010/sol1.py +++ b/project_euler/problem_010/sol1.py @@ -1,16 +1,23 @@ """ -https://projecteuler.net/problem=10 +Project Euler Problem 10: https://projecteuler.net/problem=10 + +Summation of primes -Problem Statement: The sum of the primes below 10 is 2 + 3 + 5 + 7 = 17. Find the sum of all the primes below two million. + +References: + - https://en.wikipedia.org/wiki/Prime_number """ + from math import sqrt def is_prime(n: int) -> bool: - """Returns boolean representing primality of given number num. + """ + Returns boolean representing primality of given number num. + >>> is_prime(2) True >>> is_prime(3) @@ -20,6 +27,7 @@ def is_prime(n: int) -> bool: >>> is_prime(2999) True """ + for i in range(2, int(sqrt(n)) + 1): if n % i == 0: return False @@ -28,11 +36,9 @@ def is_prime(n: int) -> bool: def solution(n: int = 2000000) -> int: - """Returns the sum of all the primes below n. + """ + Returns the sum of all the primes below n. - # The code below has been commented due to slow execution affecting Travis. - # >>> solution(2000000) - # 142913828922 >>> solution(1000) 76127 >>> solution(5000) @@ -42,6 +48,7 @@ def solution(n: int = 2000000) -> int: >>> solution(7) 10 """ + if n > 2: sum_of_primes = 2 else: @@ -55,4 +62,4 @@ def solution(n: int = 2000000) -> int: if __name__ == "__main__": - print(solution(int(input().strip()))) + print(f"{solution() = }") diff --git a/project_euler/problem_010/sol2.py b/project_euler/problem_010/sol2.py index 39f5f5604053..3a2f485dde50 100644 --- a/project_euler/problem_010/sol2.py +++ b/project_euler/problem_010/sol2.py @@ -1,10 +1,14 @@ """ -https://projecteuler.net/problem=10 +Project Euler Problem 10: https://projecteuler.net/problem=10 + +Summation of primes -Problem Statement: The sum of the primes below 10 is 2 + 3 + 5 + 7 = 17. Find the sum of all the primes below two million. + +References: + - https://en.wikipedia.org/wiki/Prime_number """ import math from itertools import takewhile @@ -12,7 +16,9 @@ def is_prime(number: int) -> bool: - """Returns boolean representing primality of given number num. + """ + Returns boolean representing primality of given number num. + >>> is_prime(2) True >>> is_prime(3) @@ -22,12 +28,17 @@ def is_prime(number: int) -> bool: >>> is_prime(2999) True """ + if number % 2 == 0 and number > 2: return False return all(number % i for i in range(3, int(math.sqrt(number)) + 1, 2)) def prime_generator() -> Iterator[int]: + """ + Generate a list sequence of prime numbers + """ + num = 2 while True: if is_prime(num): @@ -36,11 +47,9 @@ def prime_generator() -> Iterator[int]: def solution(n: int = 2000000) -> int: - """Returns the sum of all the primes below n. + """ + Returns the sum of all the primes below n. - # The code below has been commented due to slow execution affecting Travis. - # >>> solution(2000000) - # 142913828922 >>> solution(1000) 76127 >>> solution(5000) @@ -50,8 +59,9 @@ def solution(n: int = 2000000) -> int: >>> solution(7) 10 """ + return sum(takewhile(lambda x: x < n, prime_generator())) if __name__ == "__main__": - print(solution(int(input().strip()))) + print(f"{solution() = }") diff --git a/project_euler/problem_010/sol3.py b/project_euler/problem_010/sol3.py index ef895f546fa5..f49d9393c7af 100644 --- a/project_euler/problem_010/sol3.py +++ b/project_euler/problem_010/sol3.py @@ -1,43 +1,47 @@ """ -https://projecteuler.net/problem=10 +Project Euler Problem 10: https://projecteuler.net/problem=10 + +Summation of primes -Problem Statement: The sum of the primes below 10 is 2 + 3 + 5 + 7 = 17. Find the sum of all the primes below two million. + +References: + - https://en.wikipedia.org/wiki/Prime_number + - https://en.wikipedia.org/wiki/Sieve_of_Eratosthenes """ def solution(n: int = 2000000) -> int: - """Returns the sum of all the primes below n using Sieve of Eratosthenes: + """ + Returns the sum of all the primes below n using Sieve of Eratosthenes: - https://en.wikipedia.org/wiki/Sieve_of_Eratosthenes The sieve of Eratosthenes is one of the most efficient ways to find all primes smaller than n when n is smaller than 10 million. Only for positive numbers. - >>> solution(2_000_000) - 142913828922 - >>> solution(1_000) + >>> solution(1000) 76127 - >>> solution(5_000) + >>> solution(5000) 1548136 - >>> solution(10_000) + >>> solution(10000) 5736396 >>> solution(7) 10 - >>> solution(7.1) # doctest: +ELLIPSIS + >>> solution(7.1) # doctest: +ELLIPSIS Traceback (most recent call last): ... TypeError: 'float' object cannot be interpreted as an integer - >>> solution(-7) # doctest: +ELLIPSIS + >>> solution(-7) # doctest: +ELLIPSIS Traceback (most recent call last): ... IndexError: list assignment index out of range - >>> solution("seven") # doctest: +ELLIPSIS + >>> solution("seven") # doctest: +ELLIPSIS Traceback (most recent call last): ... TypeError: can only concatenate str (not "int") to str """ + primality_list = [0 for i in range(n + 1)] primality_list[0] = 1 primality_list[1] = 1 @@ -54,4 +58,4 @@ def solution(n: int = 2000000) -> int: if __name__ == "__main__": - print(solution(int(input().strip()))) + print(f"{solution() = }") From 3a191d9a7c22c2423040d5fe588b31806449d682 Mon Sep 17 00:00:00 2001 From: Ayoub Chegraoui Date: Sun, 25 Oct 2020 05:06:31 +0100 Subject: [PATCH 0959/1071] Add solution to Project Euler problem 81 (#3408) * Add solution to problem 81 - project euler * Update project_euler/problem_081/sol1.py Co-authored-by: Christian Clauss * Update project_euler/problem_081/sol1.py Co-authored-by: Christian Clauss Co-authored-by: Christian Clauss --- project_euler/problem_081/__init__.py | 0 project_euler/problem_081/matrix.txt | 80 +++++++++++++++++++++++++++ project_euler/problem_081/sol1.py | 47 ++++++++++++++++ 3 files changed, 127 insertions(+) create mode 100644 project_euler/problem_081/__init__.py create mode 100644 project_euler/problem_081/matrix.txt create mode 100644 project_euler/problem_081/sol1.py diff --git a/project_euler/problem_081/__init__.py b/project_euler/problem_081/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/project_euler/problem_081/matrix.txt b/project_euler/problem_081/matrix.txt new file mode 100644 index 000000000000..f65322a7e541 --- /dev/null +++ b/project_euler/problem_081/matrix.txt @@ -0,0 +1,80 @@ +4445,2697,5115,718,2209,2212,654,4348,3079,6821,7668,3276,8874,4190,3785,2752,9473,7817,9137,496,7338,3434,7152,4355,4552,7917,7827,2460,2350,691,3514,5880,3145,7633,7199,3783,5066,7487,3285,1084,8985,760,872,8609,8051,1134,9536,5750,9716,9371,7619,5617,275,9721,2997,2698,1887,8825,6372,3014,2113,7122,7050,6775,5948,2758,1219,3539,348,7989,2735,9862,1263,8089,6401,9462,3168,2758,3748,5870 +1096,20,1318,7586,5167,2642,1443,5741,7621,7030,5526,4244,2348,4641,9827,2448,6918,5883,3737,300,7116,6531,567,5997,3971,6623,820,6148,3287,1874,7981,8424,7672,7575,6797,6717,1078,5008,4051,8795,5820,346,1851,6463,2117,6058,3407,8211,117,4822,1317,4377,4434,5925,8341,4800,1175,4173,690,8978,7470,1295,3799,8724,3509,9849,618,3320,7068,9633,2384,7175,544,6583,1908,9983,481,4187,9353,9377 +9607,7385,521,6084,1364,8983,7623,1585,6935,8551,2574,8267,4781,3834,2764,2084,2669,4656,9343,7709,2203,9328,8004,6192,5856,3555,2260,5118,6504,1839,9227,1259,9451,1388,7909,5733,6968,8519,9973,1663,5315,7571,3035,4325,4283,2304,6438,3815,9213,9806,9536,196,5542,6907,2475,1159,5820,9075,9470,2179,9248,1828,4592,9167,3713,4640,47,3637,309,7344,6955,346,378,9044,8635,7466,5036,9515,6385,9230 +7206,3114,7760,1094,6150,5182,7358,7387,4497,955,101,1478,7777,6966,7010,8417,6453,4955,3496,107,449,8271,131,2948,6185,784,5937,8001,6104,8282,4165,3642,710,2390,575,715,3089,6964,4217,192,5949,7006,715,3328,1152,66,8044,4319,1735,146,4818,5456,6451,4113,1063,4781,6799,602,1504,6245,6550,1417,1343,2363,3785,5448,4545,9371,5420,5068,4613,4882,4241,5043,7873,8042,8434,3939,9256,2187 +3620,8024,577,9997,7377,7682,1314,1158,6282,6310,1896,2509,5436,1732,9480,706,496,101,6232,7375,2207,2306,110,6772,3433,2878,8140,5933,8688,1399,2210,7332,6172,6403,7333,4044,2291,1790,2446,7390,8698,5723,3678,7104,1825,2040,140,3982,4905,4160,2200,5041,2512,1488,2268,1175,7588,8321,8078,7312,977,5257,8465,5068,3453,3096,1651,7906,253,9250,6021,8791,8109,6651,3412,345,4778,5152,4883,7505 +1074,5438,9008,2679,5397,5429,2652,3403,770,9188,4248,2493,4361,8327,9587,707,9525,5913,93,1899,328,2876,3604,673,8576,6908,7659,2544,3359,3883,5273,6587,3065,1749,3223,604,9925,6941,2823,8767,7039,3290,3214,1787,7904,3421,7137,9560,8451,2669,9219,6332,1576,5477,6755,8348,4164,4307,2984,4012,6629,1044,2874,6541,4942,903,1404,9125,5160,8836,4345,2581,460,8438,1538,5507,668,3352,2678,6942 +4295,1176,5596,1521,3061,9868,7037,7129,8933,6659,5947,5063,3653,9447,9245,2679,767,714,116,8558,163,3927,8779,158,5093,2447,5782,3967,1716,931,7772,8164,1117,9244,5783,7776,3846,8862,6014,2330,6947,1777,3112,6008,3491,1906,5952,314,4602,8994,5919,9214,3995,5026,7688,6809,5003,3128,2509,7477,110,8971,3982,8539,2980,4689,6343,5411,2992,5270,5247,9260,2269,7474,1042,7162,5206,1232,4556,4757 +510,3556,5377,1406,5721,4946,2635,7847,4251,8293,8281,6351,4912,287,2870,3380,3948,5322,3840,4738,9563,1906,6298,3234,8959,1562,6297,8835,7861,239,6618,1322,2553,2213,5053,5446,4402,6500,5182,8585,6900,5756,9661,903,5186,7687,5998,7997,8081,8955,4835,6069,2621,1581,732,9564,1082,1853,5442,1342,520,1737,3703,5321,4793,2776,1508,1647,9101,2499,6891,4336,7012,3329,3212,1442,9993,3988,4930,7706 +9444,3401,5891,9716,1228,7107,109,3563,2700,6161,5039,4992,2242,8541,7372,2067,1294,3058,1306,320,8881,5756,9326,411,8650,8824,5495,8282,8397,2000,1228,7817,2099,6473,3571,5994,4447,1299,5991,543,7874,2297,1651,101,2093,3463,9189,6872,6118,872,1008,1779,2805,9084,4048,2123,5877,55,3075,1737,9459,4535,6453,3644,108,5982,4437,5213,1340,6967,9943,5815,669,8074,1838,6979,9132,9315,715,5048 +3327,4030,7177,6336,9933,5296,2621,4785,2755,4832,2512,2118,2244,4407,2170,499,7532,9742,5051,7687,970,6924,3527,4694,5145,1306,2165,5940,2425,8910,3513,1909,6983,346,6377,4304,9330,7203,6605,3709,3346,970,369,9737,5811,4427,9939,3693,8436,5566,1977,3728,2399,3985,8303,2492,5366,9802,9193,7296,1033,5060,9144,2766,1151,7629,5169,5995,58,7619,7565,4208,1713,6279,3209,4908,9224,7409,1325,8540 +6882,1265,1775,3648,4690,959,5837,4520,5394,1378,9485,1360,4018,578,9174,2932,9890,3696,116,1723,1178,9355,7063,1594,1918,8574,7594,7942,1547,6166,7888,354,6932,4651,1010,7759,6905,661,7689,6092,9292,3845,9605,8443,443,8275,5163,7720,7265,6356,7779,1798,1754,5225,6661,1180,8024,5666,88,9153,1840,3508,1193,4445,2648,3538,6243,6375,8107,5902,5423,2520,1122,5015,6113,8859,9370,966,8673,2442 +7338,3423,4723,6533,848,8041,7921,8277,4094,5368,7252,8852,9166,2250,2801,6125,8093,5738,4038,9808,7359,9494,601,9116,4946,2702,5573,2921,9862,1462,1269,2410,4171,2709,7508,6241,7522,615,2407,8200,4189,5492,5649,7353,2590,5203,4274,710,7329,9063,956,8371,3722,4253,4785,1194,4828,4717,4548,940,983,2575,4511,2938,1827,2027,2700,1236,841,5760,1680,6260,2373,3851,1841,4968,1172,5179,7175,3509 +4420,1327,3560,2376,6260,2988,9537,4064,4829,8872,9598,3228,1792,7118,9962,9336,4368,9189,6857,1829,9863,6287,7303,7769,2707,8257,2391,2009,3975,4993,3068,9835,3427,341,8412,2134,4034,8511,6421,3041,9012,2983,7289,100,1355,7904,9186,6920,5856,2008,6545,8331,3655,5011,839,8041,9255,6524,3862,8788,62,7455,3513,5003,8413,3918,2076,7960,6108,3638,6999,3436,1441,4858,4181,1866,8731,7745,3744,1000 +356,8296,8325,1058,1277,4743,3850,2388,6079,6462,2815,5620,8495,5378,75,4324,3441,9870,1113,165,1544,1179,2834,562,6176,2313,6836,8839,2986,9454,5199,6888,1927,5866,8760,320,1792,8296,7898,6121,7241,5886,5814,2815,8336,1576,4314,3109,2572,6011,2086,9061,9403,3947,5487,9731,7281,3159,1819,1334,3181,5844,5114,9898,4634,2531,4412,6430,4262,8482,4546,4555,6804,2607,9421,686,8649,8860,7794,6672 +9870,152,1558,4963,8750,4754,6521,6256,8818,5208,5691,9659,8377,9725,5050,5343,2539,6101,1844,9700,7750,8114,5357,3001,8830,4438,199,9545,8496,43,2078,327,9397,106,6090,8181,8646,6414,7499,5450,4850,6273,5014,4131,7639,3913,6571,8534,9703,4391,7618,445,1320,5,1894,6771,7383,9191,4708,9706,6939,7937,8726,9382,5216,3685,2247,9029,8154,1738,9984,2626,9438,4167,6351,5060,29,1218,1239,4785 +192,5213,8297,8974,4032,6966,5717,1179,6523,4679,9513,1481,3041,5355,9303,9154,1389,8702,6589,7818,6336,3539,5538,3094,6646,6702,6266,2759,4608,4452,617,9406,8064,6379,444,5602,4950,1810,8391,1536,316,8714,1178,5182,5863,5110,5372,4954,1978,2971,5680,4863,2255,4630,5723,2168,538,1692,1319,7540,440,6430,6266,7712,7385,5702,620,641,3136,7350,1478,3155,2820,9109,6261,1122,4470,14,8493,2095 +1046,4301,6082,474,4974,7822,2102,5161,5172,6946,8074,9716,6586,9962,9749,5015,2217,995,5388,4402,7652,6399,6539,1349,8101,3677,1328,9612,7922,2879,231,5887,2655,508,4357,4964,3554,5930,6236,7384,4614,280,3093,9600,2110,7863,2631,6626,6620,68,1311,7198,7561,1768,5139,1431,221,230,2940,968,5283,6517,2146,1646,869,9402,7068,8645,7058,1765,9690,4152,2926,9504,2939,7504,6074,2944,6470,7859 +4659,736,4951,9344,1927,6271,8837,8711,3241,6579,7660,5499,5616,3743,5801,4682,9748,8796,779,1833,4549,8138,4026,775,4170,2432,4174,3741,7540,8017,2833,4027,396,811,2871,1150,9809,2719,9199,8504,1224,540,2051,3519,7982,7367,2761,308,3358,6505,2050,4836,5090,7864,805,2566,2409,6876,3361,8622,5572,5895,3280,441,7893,8105,1634,2929,274,3926,7786,6123,8233,9921,2674,5340,1445,203,4585,3837 +5759,338,7444,7968,7742,3755,1591,4839,1705,650,7061,2461,9230,9391,9373,2413,1213,431,7801,4994,2380,2703,6161,6878,8331,2538,6093,1275,5065,5062,2839,582,1014,8109,3525,1544,1569,8622,7944,2905,6120,1564,1839,5570,7579,1318,2677,5257,4418,5601,7935,7656,5192,1864,5886,6083,5580,6202,8869,1636,7907,4759,9082,5854,3185,7631,6854,5872,5632,5280,1431,2077,9717,7431,4256,8261,9680,4487,4752,4286 +1571,1428,8599,1230,7772,4221,8523,9049,4042,8726,7567,6736,9033,2104,4879,4967,6334,6716,3994,1269,8995,6539,3610,7667,6560,6065,874,848,4597,1711,7161,4811,6734,5723,6356,6026,9183,2586,5636,1092,7779,7923,8747,6887,7505,9909,1792,3233,4526,3176,1508,8043,720,5212,6046,4988,709,5277,8256,3642,1391,5803,1468,2145,3970,6301,7767,2359,8487,9771,8785,7520,856,1605,8972,2402,2386,991,1383,5963 +1822,4824,5957,6511,9868,4113,301,9353,6228,2881,2966,6956,9124,9574,9233,1601,7340,973,9396,540,4747,8590,9535,3650,7333,7583,4806,3593,2738,8157,5215,8472,2284,9473,3906,6982,5505,6053,7936,6074,7179,6688,1564,1103,6860,5839,2022,8490,910,7551,7805,881,7024,1855,9448,4790,1274,3672,2810,774,7623,4223,4850,6071,9975,4935,1915,9771,6690,3846,517,463,7624,4511,614,6394,3661,7409,1395,8127 +8738,3850,9555,3695,4383,2378,87,6256,6740,7682,9546,4255,6105,2000,1851,4073,8957,9022,6547,5189,2487,303,9602,7833,1628,4163,6678,3144,8589,7096,8913,5823,4890,7679,1212,9294,5884,2972,3012,3359,7794,7428,1579,4350,7246,4301,7779,7790,3294,9547,4367,3549,1958,8237,6758,3497,3250,3456,6318,1663,708,7714,6143,6890,3428,6853,9334,7992,591,6449,9786,1412,8500,722,5468,1371,108,3939,4199,2535 +7047,4323,1934,5163,4166,461,3544,2767,6554,203,6098,2265,9078,2075,4644,6641,8412,9183,487,101,7566,5622,1975,5726,2920,5374,7779,5631,3753,3725,2672,3621,4280,1162,5812,345,8173,9785,1525,955,5603,2215,2580,5261,2765,2990,5979,389,3907,2484,1232,5933,5871,3304,1138,1616,5114,9199,5072,7442,7245,6472,4760,6359,9053,7876,2564,9404,3043,9026,2261,3374,4460,7306,2326,966,828,3274,1712,3446 +3975,4565,8131,5800,4570,2306,8838,4392,9147,11,3911,7118,9645,4994,2028,6062,5431,2279,8752,2658,7836,994,7316,5336,7185,3289,1898,9689,2331,5737,3403,1124,2679,3241,7748,16,2724,5441,6640,9368,9081,5618,858,4969,17,2103,6035,8043,7475,2181,939,415,1617,8500,8253,2155,7843,7974,7859,1746,6336,3193,2617,8736,4079,6324,6645,8891,9396,5522,6103,1857,8979,3835,2475,1310,7422,610,8345,7615 +9248,5397,5686,2988,3446,4359,6634,9141,497,9176,6773,7448,1907,8454,916,1596,2241,1626,1384,2741,3649,5362,8791,7170,2903,2475,5325,6451,924,3328,522,90,4813,9737,9557,691,2388,1383,4021,1609,9206,4707,5200,7107,8104,4333,9860,5013,1224,6959,8527,1877,4545,7772,6268,621,4915,9349,5970,706,9583,3071,4127,780,8231,3017,9114,3836,7503,2383,1977,4870,8035,2379,9704,1037,3992,3642,1016,4303 +5093,138,4639,6609,1146,5565,95,7521,9077,2272,974,4388,2465,2650,722,4998,3567,3047,921,2736,7855,173,2065,4238,1048,5,6847,9548,8632,9194,5942,4777,7910,8971,6279,7253,2516,1555,1833,3184,9453,9053,6897,7808,8629,4877,1871,8055,4881,7639,1537,7701,2508,7564,5845,5023,2304,5396,3193,2955,1088,3801,6203,1748,3737,1276,13,4120,7715,8552,3047,2921,106,7508,304,1280,7140,2567,9135,5266 +6237,4607,7527,9047,522,7371,4883,2540,5867,6366,5301,1570,421,276,3361,527,6637,4861,2401,7522,5808,9371,5298,2045,5096,5447,7755,5115,7060,8529,4078,1943,1697,1764,5453,7085,960,2405,739,2100,5800,728,9737,5704,5693,1431,8979,6428,673,7540,6,7773,5857,6823,150,5869,8486,684,5816,9626,7451,5579,8260,3397,5322,6920,1879,2127,2884,5478,4977,9016,6165,6292,3062,5671,5968,78,4619,4763 +9905,7127,9390,5185,6923,3721,9164,9705,4341,1031,1046,5127,7376,6528,3248,4941,1178,7889,3364,4486,5358,9402,9158,8600,1025,874,1839,1783,309,9030,1843,845,8398,1433,7118,70,8071,2877,3904,8866,6722,4299,10,1929,5897,4188,600,1889,3325,2485,6473,4474,7444,6992,4846,6166,4441,2283,2629,4352,7775,1101,2214,9985,215,8270,9750,2740,8361,7103,5930,8664,9690,8302,9267,344,2077,1372,1880,9550 +5825,8517,7769,2405,8204,1060,3603,7025,478,8334,1997,3692,7433,9101,7294,7498,9415,5452,3850,3508,6857,9213,6807,4412,7310,854,5384,686,4978,892,8651,3241,2743,3801,3813,8588,6701,4416,6990,6490,3197,6838,6503,114,8343,5844,8646,8694,65,791,5979,2687,2621,2019,8097,1423,3644,9764,4921,3266,3662,5561,2476,8271,8138,6147,1168,3340,1998,9874,6572,9873,6659,5609,2711,3931,9567,4143,7833,8887 +6223,2099,2700,589,4716,8333,1362,5007,2753,2848,4441,8397,7192,8191,4916,9955,6076,3370,6396,6971,3156,248,3911,2488,4930,2458,7183,5455,170,6809,6417,3390,1956,7188,577,7526,2203,968,8164,479,8699,7915,507,6393,4632,1597,7534,3604,618,3280,6061,9793,9238,8347,568,9645,2070,5198,6482,5000,9212,6655,5961,7513,1323,3872,6170,3812,4146,2736,67,3151,5548,2781,9679,7564,5043,8587,1893,4531 +5826,3690,6724,2121,9308,6986,8106,6659,2142,1642,7170,2877,5757,6494,8026,6571,8387,9961,6043,9758,9607,6450,8631,8334,7359,5256,8523,2225,7487,1977,9555,8048,5763,2414,4948,4265,2427,8978,8088,8841,9208,9601,5810,9398,8866,9138,4176,5875,7212,3272,6759,5678,7649,4922,5422,1343,8197,3154,3600,687,1028,4579,2084,9467,4492,7262,7296,6538,7657,7134,2077,1505,7332,6890,8964,4879,7603,7400,5973,739 +1861,1613,4879,1884,7334,966,2000,7489,2123,4287,1472,3263,4726,9203,1040,4103,6075,6049,330,9253,4062,4268,1635,9960,577,1320,3195,9628,1030,4092,4979,6474,6393,2799,6967,8687,7724,7392,9927,2085,3200,6466,8702,265,7646,8665,7986,7266,4574,6587,612,2724,704,3191,8323,9523,3002,704,5064,3960,8209,2027,2758,8393,4875,4641,9584,6401,7883,7014,768,443,5490,7506,1852,2005,8850,5776,4487,4269 +4052,6687,4705,7260,6645,6715,3706,5504,8672,2853,1136,8187,8203,4016,871,1809,1366,4952,9294,5339,6872,2645,6083,7874,3056,5218,7485,8796,7401,3348,2103,426,8572,4163,9171,3176,948,7654,9344,3217,1650,5580,7971,2622,76,2874,880,2034,9929,1546,2659,5811,3754,7096,7436,9694,9960,7415,2164,953,2360,4194,2397,1047,2196,6827,575,784,2675,8821,6802,7972,5996,6699,2134,7577,2887,1412,4349,4380 +4629,2234,6240,8132,7592,3181,6389,1214,266,1910,2451,8784,2790,1127,6932,1447,8986,2492,5476,397,889,3027,7641,5083,5776,4022,185,3364,5701,2442,2840,4160,9525,4828,6602,2614,7447,3711,4505,7745,8034,6514,4907,2605,7753,6958,7270,6936,3006,8968,439,2326,4652,3085,3425,9863,5049,5361,8688,297,7580,8777,7916,6687,8683,7141,306,9569,2384,1500,3346,4601,7329,9040,6097,2727,6314,4501,4974,2829 +8316,4072,2025,6884,3027,1808,5714,7624,7880,8528,4205,8686,7587,3230,1139,7273,6163,6986,3914,9309,1464,9359,4474,7095,2212,7302,2583,9462,7532,6567,1606,4436,8981,5612,6796,4385,5076,2007,6072,3678,8331,1338,3299,8845,4783,8613,4071,1232,6028,2176,3990,2148,3748,103,9453,538,6745,9110,926,3125,473,5970,8728,7072,9062,1404,1317,5139,9862,6496,6062,3338,464,1600,2532,1088,8232,7739,8274,3873 +2341,523,7096,8397,8301,6541,9844,244,4993,2280,7689,4025,4196,5522,7904,6048,2623,9258,2149,9461,6448,8087,7245,1917,8340,7127,8466,5725,6996,3421,5313,512,9164,9837,9794,8369,4185,1488,7210,1524,1016,4620,9435,2478,7765,8035,697,6677,3724,6988,5853,7662,3895,9593,1185,4727,6025,5734,7665,3070,138,8469,6748,6459,561,7935,8646,2378,462,7755,3115,9690,8877,3946,2728,8793,244,6323,8666,4271 +6430,2406,8994,56,1267,3826,9443,7079,7579,5232,6691,3435,6718,5698,4144,7028,592,2627,217,734,6194,8156,9118,58,2640,8069,4127,3285,694,3197,3377,4143,4802,3324,8134,6953,7625,3598,3584,4289,7065,3434,2106,7132,5802,7920,9060,7531,3321,1725,1067,3751,444,5503,6785,7937,6365,4803,198,6266,8177,1470,6390,1606,2904,7555,9834,8667,2033,1723,5167,1666,8546,8152,473,4475,6451,7947,3062,3281 +2810,3042,7759,1741,2275,2609,7676,8640,4117,1958,7500,8048,1757,3954,9270,1971,4796,2912,660,5511,3553,1012,5757,4525,6084,7198,8352,5775,7726,8591,7710,9589,3122,4392,6856,5016,749,2285,3356,7482,9956,7348,2599,8944,495,3462,3578,551,4543,7207,7169,7796,1247,4278,6916,8176,3742,8385,2310,1345,8692,2667,4568,1770,8319,3585,4920,3890,4928,7343,5385,9772,7947,8786,2056,9266,3454,2807,877,2660 +6206,8252,5928,5837,4177,4333,207,7934,5581,9526,8906,1498,8411,2984,5198,5134,2464,8435,8514,8674,3876,599,5327,826,2152,4084,2433,9327,9697,4800,2728,3608,3849,3861,3498,9943,1407,3991,7191,9110,5666,8434,4704,6545,5944,2357,1163,4995,9619,6754,4200,9682,6654,4862,4744,5953,6632,1054,293,9439,8286,2255,696,8709,1533,1844,6441,430,1999,6063,9431,7018,8057,2920,6266,6799,356,3597,4024,6665 +3847,6356,8541,7225,2325,2946,5199,469,5450,7508,2197,9915,8284,7983,6341,3276,3321,16,1321,7608,5015,3362,8491,6968,6818,797,156,2575,706,9516,5344,5457,9210,5051,8099,1617,9951,7663,8253,9683,2670,1261,4710,1068,8753,4799,1228,2621,3275,6188,4699,1791,9518,8701,5932,4275,6011,9877,2933,4182,6059,2930,6687,6682,9771,654,9437,3169,8596,1827,5471,8909,2352,123,4394,3208,8756,5513,6917,2056 +5458,8173,3138,3290,4570,4892,3317,4251,9699,7973,1163,1935,5477,6648,9614,5655,9592,975,9118,2194,7322,8248,8413,3462,8560,1907,7810,6650,7355,2939,4973,6894,3933,3784,3200,2419,9234,4747,2208,2207,1945,2899,1407,6145,8023,3484,5688,7686,2737,3828,3704,9004,5190,9740,8643,8650,5358,4426,1522,1707,3613,9887,6956,2447,2762,833,1449,9489,2573,1080,4167,3456,6809,2466,227,7125,2759,6250,6472,8089 +3266,7025,9756,3914,1265,9116,7723,9788,6805,5493,2092,8688,6592,9173,4431,4028,6007,7131,4446,4815,3648,6701,759,3312,8355,4485,4187,5188,8746,7759,3528,2177,5243,8379,3838,7233,4607,9187,7216,2190,6967,2920,6082,7910,5354,3609,8958,6949,7731,494,8753,8707,1523,4426,3543,7085,647,6771,9847,646,5049,824,8417,5260,2730,5702,2513,9275,4279,2767,8684,1165,9903,4518,55,9682,8963,6005,2102,6523 +1998,8731,936,1479,5259,7064,4085,91,7745,7136,3773,3810,730,8255,2705,2653,9790,6807,2342,355,9344,2668,3690,2028,9679,8102,574,4318,6481,9175,5423,8062,2867,9657,7553,3442,3920,7430,3945,7639,3714,3392,2525,4995,4850,2867,7951,9667,486,9506,9888,781,8866,1702,3795,90,356,1483,4200,2131,6969,5931,486,6880,4404,1084,5169,4910,6567,8335,4686,5043,2614,3352,2667,4513,6472,7471,5720,1616 +8878,1613,1716,868,1906,2681,564,665,5995,2474,7496,3432,9491,9087,8850,8287,669,823,347,6194,2264,2592,7871,7616,8508,4827,760,2676,4660,4881,7572,3811,9032,939,4384,929,7525,8419,5556,9063,662,8887,7026,8534,3111,1454,2082,7598,5726,6687,9647,7608,73,3014,5063,670,5461,5631,3367,9796,8475,7908,5073,1565,5008,5295,4457,1274,4788,1728,338,600,8415,8535,9351,7750,6887,5845,1741,125 +3637,6489,9634,9464,9055,2413,7824,9517,7532,3577,7050,6186,6980,9365,9782,191,870,2497,8498,2218,2757,5420,6468,586,3320,9230,1034,1393,9886,5072,9391,1178,8464,8042,6869,2075,8275,3601,7715,9470,8786,6475,8373,2159,9237,2066,3264,5000,679,355,3069,4073,494,2308,5512,4334,9438,8786,8637,9774,1169,1949,6594,6072,4270,9158,7916,5752,6794,9391,6301,5842,3285,2141,3898,8027,4310,8821,7079,1307 +8497,6681,4732,7151,7060,5204,9030,7157,833,5014,8723,3207,9796,9286,4913,119,5118,7650,9335,809,3675,2597,5144,3945,5090,8384,187,4102,1260,2445,2792,4422,8389,9290,50,1765,1521,6921,8586,4368,1565,5727,7855,2003,4834,9897,5911,8630,5070,1330,7692,7557,7980,6028,5805,9090,8265,3019,3802,698,9149,5748,1965,9658,4417,5994,5584,8226,2937,272,5743,1278,5698,8736,2595,6475,5342,6596,1149,6920 +8188,8009,9546,6310,8772,2500,9846,6592,6872,3857,1307,8125,7042,1544,6159,2330,643,4604,7899,6848,371,8067,2062,3200,7295,1857,9505,6936,384,2193,2190,301,8535,5503,1462,7380,5114,4824,8833,1763,4974,8711,9262,6698,3999,2645,6937,7747,1128,2933,3556,7943,2885,3122,9105,5447,418,2899,5148,3699,9021,9501,597,4084,175,1621,1,1079,6067,5812,4326,9914,6633,5394,4233,6728,9084,1864,5863,1225 +9935,8793,9117,1825,9542,8246,8437,3331,9128,9675,6086,7075,319,1334,7932,3583,7167,4178,1726,7720,695,8277,7887,6359,5912,1719,2780,8529,1359,2013,4498,8072,1129,9998,1147,8804,9405,6255,1619,2165,7491,1,8882,7378,3337,503,5758,4109,3577,985,3200,7615,8058,5032,1080,6410,6873,5496,1466,2412,9885,5904,4406,3605,8770,4361,6205,9193,1537,9959,214,7260,9566,1685,100,4920,7138,9819,5637,976 +3466,9854,985,1078,7222,8888,5466,5379,3578,4540,6853,8690,3728,6351,7147,3134,6921,9692,857,3307,4998,2172,5783,3931,9417,2541,6299,13,787,2099,9131,9494,896,8600,1643,8419,7248,2660,2609,8579,91,6663,5506,7675,1947,6165,4286,1972,9645,3805,1663,1456,8853,5705,9889,7489,1107,383,4044,2969,3343,152,7805,4980,9929,5033,1737,9953,7197,9158,4071,1324,473,9676,3984,9680,3606,8160,7384,5432 +1005,4512,5186,3953,2164,3372,4097,3247,8697,3022,9896,4101,3871,6791,3219,2742,4630,6967,7829,5991,6134,1197,1414,8923,8787,1394,8852,5019,7768,5147,8004,8825,5062,9625,7988,1110,3992,7984,9966,6516,6251,8270,421,3723,1432,4830,6935,8095,9059,2214,6483,6846,3120,1587,6201,6691,9096,9627,6671,4002,3495,9939,7708,7465,5879,6959,6634,3241,3401,2355,9061,2611,7830,3941,2177,2146,5089,7079,519,6351 +7280,8586,4261,2831,7217,3141,9994,9940,5462,2189,4005,6942,9848,5350,8060,6665,7519,4324,7684,657,9453,9296,2944,6843,7499,7847,1728,9681,3906,6353,5529,2822,3355,3897,7724,4257,7489,8672,4356,3983,1948,6892,7415,4153,5893,4190,621,1736,4045,9532,7701,3671,1211,1622,3176,4524,9317,7800,5638,6644,6943,5463,3531,2821,1347,5958,3436,1438,2999,994,850,4131,2616,1549,3465,5946,690,9273,6954,7991 +9517,399,3249,2596,7736,2142,1322,968,7350,1614,468,3346,3265,7222,6086,1661,5317,2582,7959,4685,2807,2917,1037,5698,1529,3972,8716,2634,3301,3412,8621,743,8001,4734,888,7744,8092,3671,8941,1487,5658,7099,2781,99,1932,4443,4756,4652,9328,1581,7855,4312,5976,7255,6480,3996,2748,1973,9731,4530,2790,9417,7186,5303,3557,351,7182,9428,1342,9020,7599,1392,8304,2070,9138,7215,2008,9937,1106,7110 +7444,769,9688,632,1571,6820,8743,4338,337,3366,3073,1946,8219,104,4210,6986,249,5061,8693,7960,6546,1004,8857,5997,9352,4338,6105,5008,2556,6518,6694,4345,3727,7956,20,3954,8652,4424,9387,2035,8358,5962,5304,5194,8650,8282,1256,1103,2138,6679,1985,3653,2770,2433,4278,615,2863,1715,242,3790,2636,6998,3088,1671,2239,957,5411,4595,6282,2881,9974,2401,875,7574,2987,4587,3147,6766,9885,2965 +3287,3016,3619,6818,9073,6120,5423,557,2900,2015,8111,3873,1314,4189,1846,4399,7041,7583,2427,2864,3525,5002,2069,748,1948,6015,2684,438,770,8367,1663,7887,7759,1885,157,7770,4520,4878,3857,1137,3525,3050,6276,5569,7649,904,4533,7843,2199,5648,7628,9075,9441,3600,7231,2388,5640,9096,958,3058,584,5899,8150,1181,9616,1098,8162,6819,8171,1519,1140,7665,8801,2632,1299,9192,707,9955,2710,7314 +1772,2963,7578,3541,3095,1488,7026,2634,6015,4633,4370,2762,1650,2174,909,8158,2922,8467,4198,4280,9092,8856,8835,5457,2790,8574,9742,5054,9547,4156,7940,8126,9824,7340,8840,6574,3547,1477,3014,6798,7134,435,9484,9859,3031,4,1502,4133,1738,1807,4825,463,6343,9701,8506,9822,9555,8688,8168,3467,3234,6318,1787,5591,419,6593,7974,8486,9861,6381,6758,194,3061,4315,2863,4665,3789,2201,1492,4416 +126,8927,6608,5682,8986,6867,1715,6076,3159,788,3140,4744,830,9253,5812,5021,7616,8534,1546,9590,1101,9012,9821,8132,7857,4086,1069,7491,2988,1579,2442,4321,2149,7642,6108,250,6086,3167,24,9528,7663,2685,1220,9196,1397,5776,1577,1730,5481,977,6115,199,6326,2183,3767,5928,5586,7561,663,8649,9688,949,5913,9160,1870,5764,9887,4477,6703,1413,4995,5494,7131,2192,8969,7138,3997,8697,646,1028 +8074,1731,8245,624,4601,8706,155,8891,309,2552,8208,8452,2954,3124,3469,4246,3352,1105,4509,8677,9901,4416,8191,9283,5625,7120,2952,8881,7693,830,4580,8228,9459,8611,4499,1179,4988,1394,550,2336,6089,6872,269,7213,1848,917,6672,4890,656,1478,6536,3165,4743,4990,1176,6211,7207,5284,9730,4738,1549,4986,4942,8645,3698,9429,1439,2175,6549,3058,6513,1574,6988,8333,3406,5245,5431,7140,7085,6407 +7845,4694,2530,8249,290,5948,5509,1588,5940,4495,5866,5021,4626,3979,3296,7589,4854,1998,5627,3926,8346,6512,9608,1918,7070,4747,4182,2858,2766,4606,6269,4107,8982,8568,9053,4244,5604,102,2756,727,5887,2566,7922,44,5986,621,1202,374,6988,4130,3627,6744,9443,4568,1398,8679,397,3928,9159,367,2917,6127,5788,3304,8129,911,2669,1463,9749,264,4478,8940,1109,7309,2462,117,4692,7724,225,2312 +4164,3637,2000,941,8903,39,3443,7172,1031,3687,4901,8082,4945,4515,7204,9310,9349,9535,9940,218,1788,9245,2237,1541,5670,6538,6047,5553,9807,8101,1925,8714,445,8332,7309,6830,5786,5736,7306,2710,3034,1838,7969,6318,7912,2584,2080,7437,6705,2254,7428,820,782,9861,7596,3842,3631,8063,5240,6666,394,4565,7865,4895,9890,6028,6117,4724,9156,4473,4552,602,470,6191,4927,5387,884,3146,1978,3000 +4258,6880,1696,3582,5793,4923,2119,1155,9056,9698,6603,3768,5514,9927,9609,6166,6566,4536,4985,4934,8076,9062,6741,6163,7399,4562,2337,5600,2919,9012,8459,1308,6072,1225,9306,8818,5886,7243,7365,8792,6007,9256,6699,7171,4230,7002,8720,7839,4533,1671,478,7774,1607,2317,5437,4705,7886,4760,6760,7271,3081,2997,3088,7675,6208,3101,6821,6840,122,9633,4900,2067,8546,4549,2091,7188,5605,8599,6758,5229 +7854,5243,9155,3556,8812,7047,2202,1541,5993,4600,4760,713,434,7911,7426,7414,8729,322,803,7960,7563,4908,6285,6291,736,3389,9339,4132,8701,7534,5287,3646,592,3065,7582,2592,8755,6068,8597,1982,5782,1894,2900,6236,4039,6569,3037,5837,7698,700,7815,2491,7272,5878,3083,6778,6639,3589,5010,8313,2581,6617,5869,8402,6808,2951,2321,5195,497,2190,6187,1342,1316,4453,7740,4154,2959,1781,1482,8256 +7178,2046,4419,744,8312,5356,6855,8839,319,2962,5662,47,6307,8662,68,4813,567,2712,9931,1678,3101,8227,6533,4933,6656,92,5846,4780,6256,6361,4323,9985,1231,2175,7178,3034,9744,6155,9165,7787,5836,9318,7860,9644,8941,6480,9443,8188,5928,161,6979,2352,5628,6991,1198,8067,5867,6620,3778,8426,2994,3122,3124,6335,3918,8897,2655,9670,634,1088,1576,8935,7255,474,8166,7417,9547,2886,5560,3842 +6957,3111,26,7530,7143,1295,1744,6057,3009,1854,8098,5405,2234,4874,9447,2620,9303,27,7410,969,40,2966,5648,7596,8637,4238,3143,3679,7187,690,9980,7085,7714,9373,5632,7526,6707,3951,9734,4216,2146,3602,5371,6029,3039,4433,4855,4151,1449,3376,8009,7240,7027,4602,2947,9081,4045,8424,9352,8742,923,2705,4266,3232,2264,6761,363,2651,3383,7770,6730,7856,7340,9679,2158,610,4471,4608,910,6241 +4417,6756,1013,8797,658,8809,5032,8703,7541,846,3357,2920,9817,1745,9980,7593,4667,3087,779,3218,6233,5568,4296,2289,2654,7898,5021,9461,5593,8214,9173,4203,2271,7980,2983,5952,9992,8399,3468,1776,3188,9314,1720,6523,2933,621,8685,5483,8986,6163,3444,9539,4320,155,3992,2828,2150,6071,524,2895,5468,8063,1210,3348,9071,4862,483,9017,4097,6186,9815,3610,5048,1644,1003,9865,9332,2145,1944,2213 +9284,3803,4920,1927,6706,4344,7383,4786,9890,2010,5228,1224,3158,6967,8580,8990,8883,5213,76,8306,2031,4980,5639,9519,7184,5645,7769,3259,8077,9130,1317,3096,9624,3818,1770,695,2454,947,6029,3474,9938,3527,5696,4760,7724,7738,2848,6442,5767,6845,8323,4131,2859,7595,2500,4815,3660,9130,8580,7016,8231,4391,8369,3444,4069,4021,556,6154,627,2778,1496,4206,6356,8434,8491,3816,8231,3190,5575,1015 +3787,7572,1788,6803,5641,6844,1961,4811,8535,9914,9999,1450,8857,738,4662,8569,6679,2225,7839,8618,286,2648,5342,2294,3205,4546,176,8705,3741,6134,8324,8021,7004,5205,7032,6637,9442,5539,5584,4819,5874,5807,8589,6871,9016,983,1758,3786,1519,6241,185,8398,495,3370,9133,3051,4549,9674,7311,9738,3316,9383,2658,2776,9481,7558,619,3943,3324,6491,4933,153,9738,4623,912,3595,7771,7939,1219,4405 +2650,3883,4154,5809,315,7756,4430,1788,4451,1631,6461,7230,6017,5751,138,588,5282,2442,9110,9035,6349,2515,1570,6122,4192,4174,3530,1933,4186,4420,4609,5739,4135,2963,6308,1161,8809,8619,2796,3819,6971,8228,4188,1492,909,8048,2328,6772,8467,7671,9068,2226,7579,6422,7056,8042,3296,2272,3006,2196,7320,3238,3490,3102,37,1293,3212,4767,5041,8773,5794,4456,6174,7279,7054,2835,7053,9088,790,6640 +3101,1057,7057,3826,6077,1025,2955,1224,1114,6729,5902,4698,6239,7203,9423,1804,4417,6686,1426,6941,8071,1029,4985,9010,6122,6597,1622,1574,3513,1684,7086,5505,3244,411,9638,4150,907,9135,829,981,1707,5359,8781,9751,5,9131,3973,7159,1340,6955,7514,7993,6964,8198,1933,2797,877,3993,4453,8020,9349,8646,2779,8679,2961,3547,3374,3510,1129,3568,2241,2625,9138,5974,8206,7669,7678,1833,8700,4480 +4865,9912,8038,8238,782,3095,8199,1127,4501,7280,2112,2487,3626,2790,9432,1475,6312,8277,4827,2218,5806,7132,8752,1468,7471,6386,739,8762,8323,8120,5169,9078,9058,3370,9560,7987,8585,8531,5347,9312,1058,4271,1159,5286,5404,6925,8606,9204,7361,2415,560,586,4002,2644,1927,2824,768,4409,2942,3345,1002,808,4941,6267,7979,5140,8643,7553,9438,7320,4938,2666,4609,2778,8158,6730,3748,3867,1866,7181 +171,3771,7134,8927,4778,2913,3326,2004,3089,7853,1378,1729,4777,2706,9578,1360,5693,3036,1851,7248,2403,2273,8536,6501,9216,613,9671,7131,7719,6425,773,717,8803,160,1114,7554,7197,753,4513,4322,8499,4533,2609,4226,8710,6627,644,9666,6260,4870,5744,7385,6542,6203,7703,6130,8944,5589,2262,6803,6381,7414,6888,5123,7320,9392,9061,6780,322,8975,7050,5089,1061,2260,3199,1150,1865,5386,9699,6501 +3744,8454,6885,8277,919,1923,4001,6864,7854,5519,2491,6057,8794,9645,1776,5714,9786,9281,7538,6916,3215,395,2501,9618,4835,8846,9708,2813,3303,1794,8309,7176,2206,1602,1838,236,4593,2245,8993,4017,10,8215,6921,5206,4023,5932,6997,7801,262,7640,3107,8275,4938,7822,2425,3223,3886,2105,8700,9526,2088,8662,8034,7004,5710,2124,7164,3574,6630,9980,4242,2901,9471,1491,2117,4562,1130,9086,4117,6698 +2810,2280,2331,1170,4554,4071,8387,1215,2274,9848,6738,1604,7281,8805,439,1298,8318,7834,9426,8603,6092,7944,1309,8828,303,3157,4638,4439,9175,1921,4695,7716,1494,1015,1772,5913,1127,1952,1950,8905,4064,9890,385,9357,7945,5035,7082,5369,4093,6546,5187,5637,2041,8946,1758,7111,6566,1027,1049,5148,7224,7248,296,6169,375,1656,7993,2816,3717,4279,4675,1609,3317,42,6201,3100,3144,163,9530,4531 +7096,6070,1009,4988,3538,5801,7149,3063,2324,2912,7911,7002,4338,7880,2481,7368,3516,2016,7556,2193,1388,3865,8125,4637,4096,8114,750,3144,1938,7002,9343,4095,1392,4220,3455,6969,9647,1321,9048,1996,1640,6626,1788,314,9578,6630,2813,6626,4981,9908,7024,4355,3201,3521,3864,3303,464,1923,595,9801,3391,8366,8084,9374,1041,8807,9085,1892,9431,8317,9016,9221,8574,9981,9240,5395,2009,6310,2854,9255 +8830,3145,2960,9615,8220,6061,3452,2918,6481,9278,2297,3385,6565,7066,7316,5682,107,7646,4466,68,1952,9603,8615,54,7191,791,6833,2560,693,9733,4168,570,9127,9537,1925,8287,5508,4297,8452,8795,6213,7994,2420,4208,524,5915,8602,8330,2651,8547,6156,1812,6271,7991,9407,9804,1553,6866,1128,2119,4691,9711,8315,5879,9935,6900,482,682,4126,1041,428,6247,3720,5882,7526,2582,4327,7725,3503,2631 +2738,9323,721,7434,1453,6294,2957,3786,5722,6019,8685,4386,3066,9057,6860,499,5315,3045,5194,7111,3137,9104,941,586,3066,755,4177,8819,7040,5309,3583,3897,4428,7788,4721,7249,6559,7324,825,7311,3760,6064,6070,9672,4882,584,1365,9739,9331,5783,2624,7889,1604,1303,1555,7125,8312,425,8936,3233,7724,1480,403,7440,1784,1754,4721,1569,652,3893,4574,5692,9730,4813,9844,8291,9199,7101,3391,8914 +6044,2928,9332,3328,8588,447,3830,1176,3523,2705,8365,6136,5442,9049,5526,8575,8869,9031,7280,706,2794,8814,5767,4241,7696,78,6570,556,5083,1426,4502,3336,9518,2292,1885,3740,3153,9348,9331,8051,2759,5407,9028,7840,9255,831,515,2612,9747,7435,8964,4971,2048,4900,5967,8271,1719,9670,2810,6777,1594,6367,6259,8316,3815,1689,6840,9437,4361,822,9619,3065,83,6344,7486,8657,8228,9635,6932,4864 +8478,4777,6334,4678,7476,4963,6735,3096,5860,1405,5127,7269,7793,4738,227,9168,2996,8928,765,733,1276,7677,6258,1528,9558,3329,302,8901,1422,8277,6340,645,9125,8869,5952,141,8141,1816,9635,4025,4184,3093,83,2344,2747,9352,7966,1206,1126,1826,218,7939,2957,2729,810,8752,5247,4174,4038,8884,7899,9567,301,5265,5752,7524,4381,1669,3106,8270,6228,6373,754,2547,4240,2313,5514,3022,1040,9738 +2265,8192,1763,1369,8469,8789,4836,52,1212,6690,5257,8918,6723,6319,378,4039,2421,8555,8184,9577,1432,7139,8078,5452,9628,7579,4161,7490,5159,8559,1011,81,478,5840,1964,1334,6875,8670,9900,739,1514,8692,522,9316,6955,1345,8132,2277,3193,9773,3923,4177,2183,1236,6747,6575,4874,6003,6409,8187,745,8776,9440,7543,9825,2582,7381,8147,7236,5185,7564,6125,218,7991,6394,391,7659,7456,5128,5294 +2132,8992,8160,5782,4420,3371,3798,5054,552,5631,7546,4716,1332,6486,7892,7441,4370,6231,4579,2121,8615,1145,9391,1524,1385,2400,9437,2454,7896,7467,2928,8400,3299,4025,7458,4703,7206,6358,792,6200,725,4275,4136,7390,5984,4502,7929,5085,8176,4600,119,3568,76,9363,6943,2248,9077,9731,6213,5817,6729,4190,3092,6910,759,2682,8380,1254,9604,3011,9291,5329,9453,9746,2739,6522,3765,5634,1113,5789 +5304,5499,564,2801,679,2653,1783,3608,7359,7797,3284,796,3222,437,7185,6135,8571,2778,7488,5746,678,6140,861,7750,803,9859,9918,2425,3734,2698,9005,4864,9818,6743,2475,132,9486,3825,5472,919,292,4411,7213,7699,6435,9019,6769,1388,802,2124,1345,8493,9487,8558,7061,8777,8833,2427,2238,5409,4957,8503,3171,7622,5779,6145,2417,5873,5563,5693,9574,9491,1937,7384,4563,6842,5432,2751,3406,7981 diff --git a/project_euler/problem_081/sol1.py b/project_euler/problem_081/sol1.py new file mode 100644 index 000000000000..afa143f23b33 --- /dev/null +++ b/project_euler/problem_081/sol1.py @@ -0,0 +1,47 @@ +""" +Problem 81: https://projecteuler.net/problem=81 +In the 5 by 5 matrix below, the minimal path sum from the top left to the bottom right, +by only moving to the right and down, is indicated in bold red and is equal to 2427. + + [131] 673 234 103 18 + [201] [96] [342] 965 150 + 630 803 [746] [422] 111 + 537 699 497 [121] 956 + 805 732 524 [37] [331] + +Find the minimal path sum from the top left to the bottom right by only moving right +and down in matrix.txt (https://projecteuler.net/project/resources/p081_matrix.txt), +a 31K text file containing an 80 by 80 matrix. +""" +import os + + +def solution(filename: str = "matrix.txt") -> int: + """ + Returns the minimal path sum from the top left to the bottom right of the matrix. + >>> solution() + 427337 + """ + with open(os.path.join(os.path.dirname(__file__), filename), "r") as in_file: + data = in_file.read() + + grid = [[int(cell) for cell in row.split(",")] for row in data.strip().splitlines()] + dp = [[0 for cell in row] for row in grid] + n = len(grid[0]) + + dp = [[0 for i in range(n)] for j in range(n)] + dp[0][0] = grid[0][0] + for i in range(1, n): + dp[0][i] = grid[0][i] + dp[0][i - 1] + for i in range(1, n): + dp[i][0] = grid[i][0] + dp[i - 1][0] + + for i in range(1, n): + for j in range(1, n): + dp[i][j] = grid[i][j] + min(dp[i - 1][j], dp[i][j - 1]) + + return dp[-1][-1] + + +if __name__ == "__main__": + print(f"{solution() = }") From 1b5c1b8344107cabcc7702194d37db8aabb6a525 Mon Sep 17 00:00:00 2001 From: Phil Bazun Date: Sun, 25 Oct 2020 10:24:35 +0100 Subject: [PATCH 0960/1071] Add single bit manipulation operations. (#3284) * Add single bit manipuation operations. * fixup! Add single bit manipuation operations. * Change wording. --- .../single_bit_manipulation_operations.py | 80 +++++++++++++++++++ 1 file changed, 80 insertions(+) create mode 100644 bit_manipulation/single_bit_manipulation_operations.py diff --git a/bit_manipulation/single_bit_manipulation_operations.py b/bit_manipulation/single_bit_manipulation_operations.py new file mode 100644 index 000000000000..114eafe3235b --- /dev/null +++ b/bit_manipulation/single_bit_manipulation_operations.py @@ -0,0 +1,80 @@ +#!/usr/bin/env python3 + +"""Provide the functionality to manipulate a single bit.""" + + +def set_bit(number: int, position: int): + """ + Set the bit at position to 1. + + Details: perform bitwise or for given number and X. + Where X is a number with all the bits – zeroes and bit on given + position – one. + + >>> set_bit(0b1101, 1) # 0b1111 + 15 + >>> set_bit(0b0, 5) # 0b100000 + 32 + >>> set_bit(0b1111, 1) # 0b1111 + 15 + """ + return number | (1 << position) + + +def clear_bit(number: int, position: int): + """ + Set the bit at position to 0. + + Details: perform bitwise and for given number and X. + Where X is a number with all the bits – ones and bit on given + position – zero. + + >>> clear_bit(0b10010, 1) # 0b10000 + 16 + >>> clear_bit(0b0, 5) # 0b0 + 0 + """ + return number & ~(1 << position) + + +def flip_bit(number: int, position: int): + """ + Flip the bit at position. + + Details: perform bitwise xor for given number and X. + Where X is a number with all the bits – zeroes and bit on given + position – one. + + >>> flip_bit(0b101, 1) # 0b111 + 7 + >>> flip_bit(0b101, 0) # 0b100 + 4 + """ + return number ^ (1 << position) + + +def is_bit_set(number: int, position: int) -> bool: + """ + Is the bit at position set? + + Details: Shift the bit at position to be the first (smallest) bit. + Then check if the first bit is set by anding the shifted number with 1. + + >>> is_bit_set(0b1010, 0) + False + >>> is_bit_set(0b1010, 1) + True + >>> is_bit_set(0b1010, 2) + False + >>> is_bit_set(0b1010, 3) + True + >>> is_bit_set(0b0, 17) + False + """ + return ((number >> position) & 1) == 1 + + +if __name__ == "__main__": + import doctest + + doctest.testmod() From 81b82bea47df8ae8368ce293a3d5af7fb8de23f3 Mon Sep 17 00:00:00 2001 From: Du Yuanchao Date: Mon, 26 Oct 2020 00:02:24 +0800 Subject: [PATCH 0961/1071] Update ceil and floor function (#3710) * Update ceil and floor function * add end line * updating DIRECTORY.md Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> --- DIRECTORY.md | 5 ++++- maths/ceil.py | 9 ++++++--- maths/floor.py | 11 ++++++----- 3 files changed, 16 insertions(+), 9 deletions(-) diff --git a/DIRECTORY.md b/DIRECTORY.md index f0f494e7a3b4..6f14f74fd0f7 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -340,6 +340,8 @@ * [Astar](https://github.com/TheAlgorithms/Python/blob/master/machine_learning/astar.py) * [Data Transformations](https://github.com/TheAlgorithms/Python/blob/master/machine_learning/data_transformations.py) * [Decision Tree](https://github.com/TheAlgorithms/Python/blob/master/machine_learning/decision_tree.py) + * Forecasting + * [Run](https://github.com/TheAlgorithms/Python/blob/master/machine_learning/forecasting/run.py) * [Gaussian Naive Bayes](https://github.com/TheAlgorithms/Python/blob/master/machine_learning/gaussian_naive_bayes.py) * [Gradient Boosting Regressor](https://github.com/TheAlgorithms/Python/blob/master/machine_learning/gradient_boosting_regressor.py) * [Gradient Descent](https://github.com/TheAlgorithms/Python/blob/master/machine_learning/gradient_descent.py) @@ -630,6 +632,8 @@ * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_036/sol1.py) * Problem 037 * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_037/sol1.py) + * Problem 038 + * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_038/sol1.py) * Problem 039 * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_039/sol1.py) * Problem 040 @@ -716,7 +720,6 @@ * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_234/sol1.py) * Problem 551 * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_551/sol1.py) - * [Validate Solutions](https://github.com/TheAlgorithms/Python/blob/master/project_euler/validate_solutions.py) ## Quantum * [Deutsch Jozsa](https://github.com/TheAlgorithms/Python/blob/master/quantum/deutsch_jozsa.py) diff --git a/maths/ceil.py b/maths/ceil.py index ac86798a357f..97578265c1a9 100644 --- a/maths/ceil.py +++ b/maths/ceil.py @@ -1,3 +1,8 @@ +""" +https://en.wikipedia.org/wiki/Floor_and_ceiling_functions +""" + + def ceil(x) -> int: """ Return the ceiling of x as an Integral. @@ -10,9 +15,7 @@ def ceil(x) -> int: ... in (1, -1, 0, -0, 1.1, -1.1, 1.0, -1.0, 1_000_000_000)) True """ - return ( - x if isinstance(x, int) or x - int(x) == 0 else int(x + 1) if x > 0 else int(x) - ) + return int(x) if x - int(x) <= 0 else int(x) + 1 if __name__ == "__main__": diff --git a/maths/floor.py b/maths/floor.py index 41bd5ecb3cd7..482250f5e59e 100644 --- a/maths/floor.py +++ b/maths/floor.py @@ -1,18 +1,19 @@ +""" +https://en.wikipedia.org/wiki/Floor_and_ceiling_functions +""" + + def floor(x) -> int: """ Return the floor of x as an Integral. - :param x: the number :return: the largest integer <= x. - >>> import math >>> all(floor(n) == math.floor(n) for n ... in (1, -1, 0, -0, 1.1, -1.1, 1.0, -1.0, 1_000_000_000)) True """ - return ( - x if isinstance(x, int) or x - int(x) == 0 else int(x) if x > 0 else int(x - 1) - ) + return int(x) if x - int(x) >= 0 else int(x) - 1 if __name__ == "__main__": From b93a9d8e8faa115d06a6bd656f5bf0e91254c1de Mon Sep 17 00:00:00 2001 From: Kushagra Bansal Date: Mon, 26 Oct 2020 09:37:11 +0530 Subject: [PATCH 0962/1071] Update lucas_series.py to include another method (#3620) * Update lucas_series.py Added another method to calculate lucas_numbers * Fix pre-commit error * Update lucas_series.py * Update lucas_series.py * Update lucas_series.py * Update lucas_series.py --- maths/lucas_series.py | 65 +++++++++++++++++++++++++++++++++++++------ 1 file changed, 56 insertions(+), 9 deletions(-) diff --git a/maths/lucas_series.py b/maths/lucas_series.py index 22ad893a6567..02eae8d8c658 100644 --- a/maths/lucas_series.py +++ b/maths/lucas_series.py @@ -1,22 +1,69 @@ -# Lucas Sequence Using Recursion +""" +https://en.wikipedia.org/wiki/Lucas_number +""" -def recur_luc(n): +def recursive_lucas_number(n): """ - >>> recur_luc(1) + Returns the nth lucas number + >>> recursive_lucas_number(1) 1 - >>> recur_luc(0) + >>> recursive_lucas_number(20) + 15127 + >>> recursive_lucas_number(0) 2 + >>> recursive_lucas_number(25) + 167761 + >>> recursive_lucas_number(-1.5) + Traceback (most recent call last): + ... + TypeError: recursive_lucas_number accepts only integer arguments. """ if n == 1: return n if n == 0: return 2 - return recur_luc(n - 1) + recur_luc(n - 2) + if not isinstance(n, int): + raise TypeError("recursive_lucas_number accepts only integer arguments.") + + return recursive_lucas_number(n - 1) + recursive_lucas_number(n - 2) + + +def dynamic_lucas_number(n: int) -> int: + """ + Returns the nth lucas number + >>> dynamic_lucas_number(1) + 1 + >>> dynamic_lucas_number(20) + 15127 + >>> dynamic_lucas_number(0) + 2 + >>> dynamic_lucas_number(25) + 167761 + >>> dynamic_lucas_number(-1.5) + Traceback (most recent call last): + ... + TypeError: dynamic_lucas_number accepts only integer arguments. + """ + if not isinstance(n, int): + raise TypeError("dynamic_lucas_number accepts only integer arguments.") + if n == 0: + return 2 + if n == 1: + return 1 + a, b = 2, 1 + for i in range(n): + a, b = b, a + b + return a if __name__ == "__main__": - limit = int(input("How many terms to include in Lucas series:")) - print("Lucas series:") - for i in range(limit): - print(recur_luc(i)) + from doctest import testmod + + testmod() + n = int(input("Enter the number of terms in lucas series:\n").strip()) + n = int(input("Enter the number of terms in lucas series:\n").strip()) + print("Using recursive function to calculate lucas series:") + print(" ".join(str(recursive_lucas_number(i)) for i in range(n))) + print("\nUsing dynamic function to calculate lucas series:") + print(" ".join(str(dynamic_lucas_number(i)) for i in range(n))) From 47199c123ccde8dc20ec19489063941170dd3c75 Mon Sep 17 00:00:00 2001 From: xcodz-dot <71920621+xcodz-dot@users.noreply.github.com> Date: Mon, 26 Oct 2020 12:05:49 +0530 Subject: [PATCH 0963/1071] Update CONTRIBUTING.md (#3698) * Update CONTRIBUTING.md Needed to tell people so we do not receive any duplicate solution. Do not count this as hactoberfest-accepted * Update CONTRIBUTING.md * Update CONTRIBUTING.md typo fix Co-authored-by: Du Yuanchao * Update CONTRIBUTING.md Co-authored-by: Du Yuanchao Co-authored-by: John Law --- CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index e248d09f11c3..bfad76a65b61 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -15,7 +15,7 @@ We are very happy that you consider implementing algorithms and data structure f - Your work will be distributed under [MIT License](LICENSE.md) once your pull request is merged - You submitted work fulfils or mostly fulfils our styles and standards -**New implementation** is welcome! For example, new solutions for a problem, different representations for a graph data structure or algorithm designs with different complexity. +**New implementation** is welcome! For example, new solutions for a problem, different representations for a graph data structure or algorithm designs with different complexity but **identical implementation** of an existing implementation is not allowed. Please check whether the solution is already implemented or not before submitting your pull request. **Improving comments** and **writing proper tests** are also highly welcome. From 8f81c460fef5294026015d2e39ef0e35f2e99e27 Mon Sep 17 00:00:00 2001 From: Rolv Apneseth Date: Mon, 26 Oct 2020 06:48:06 +0000 Subject: [PATCH 0964/1071] Made improvements to combinations.py (#3681) * Made improvements to combinations.py * Update maths/combinations.py Co-authored-by: Du Yuanchao * Function now raises an error when given invalid input * Update maths/combinations.py Co-authored-by: Du Yuanchao --- maths/combinations.py | 45 ++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 42 insertions(+), 3 deletions(-) diff --git a/maths/combinations.py b/maths/combinations.py index fd98992e6c16..40f4f7a9f850 100644 --- a/maths/combinations.py +++ b/maths/combinations.py @@ -1,19 +1,58 @@ +""" +https://en.wikipedia.org/wiki/Combination +""" from math import factorial -def combinations(n, k): +def combinations(n: int, k: int) -> int: """ + Returns the number of different combinations of k length which can + be made from n values, where n >= k. + + Examples: >>> combinations(10,5) 252 + >>> combinations(6,3) 20 + >>> combinations(20,5) 15504 + + >>> combinations(52, 5) + 2598960 + + >>> combinations(0, 0) + 1 + + >>> combinations(-4, -5) + ... + Traceback (most recent call last): + ValueError: Please enter positive integers for n and k where n >= k """ + + # If either of the conditions are true, the function is being asked + # to calculate a factorial of a negative number, which is not possible + if n < k or k < 0: + raise ValueError("Please enter positive integers for n and k where n >= k") return int(factorial(n) / ((factorial(k)) * (factorial(n - k)))) if __name__ == "__main__": - from doctest import testmod - testmod() + print( + "\nThe number of five-card hands possible from a standard", + f"fifty-two card deck is: {combinations(52, 5)}", + ) + + print( + "\nIf a class of 40 students must be arranged into groups of", + f"4 for group projects, there are {combinations(40, 4)} ways", + "to arrange them.\n", + ) + + print( + "If 10 teams are competing in a Formula One race, there", + f"are {combinations(10, 3)} ways that first, second and", + "third place can be awarded.\n", + ) From 9eefe681af81861f878c02f02f5d48c661cac255 Mon Sep 17 00:00:00 2001 From: Gaurav Chaudhari Date: Mon, 26 Oct 2020 12:39:33 +0530 Subject: [PATCH 0965/1071] fixes: #2969 (#3756) Signed-off-by: Gaurav Chaudhari --- .github/stale.yml | 20 -------------------- 1 file changed, 20 deletions(-) delete mode 100644 .github/stale.yml diff --git a/.github/stale.yml b/.github/stale.yml deleted file mode 100644 index 22aae982abdc..000000000000 --- a/.github/stale.yml +++ /dev/null @@ -1,20 +0,0 @@ -# Number of days of inactivity before an issue becomes stale -daysUntilStale: 30 -# Number of days of inactivity before a stale issue is closed -daysUntilClose: 7 -# Issues with these labels will never be considered stale -exemptLabels: - - bug - - help wanted - - OK to merge -# Label to use when marking an issue as stale -staleLabel: wontfix -# Comment to post when marking an issue as stale. Set to `false` to disable -markComment: > - This issue has been automatically marked as stale because it has not had - recent activity. It will be closed if no further activity occurs. Thank you - for your contributions. -# Comment to post when closing a stale issue. Set to `false` to disable -closeComment: > - Please reopen this issue once you commit the changes requested or - make improvements on the code. Thank you for your contributions. From 95db17ce0f436f4ced6949ef507dc2f07e696349 Mon Sep 17 00:00:00 2001 From: Shabab Karim Date: Mon, 26 Oct 2020 16:08:53 +0600 Subject: [PATCH 0966/1071] Added two pointer solution for two sum problem (#3468) --- other/two_pointer.py | 61 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 61 insertions(+) create mode 100644 other/two_pointer.py diff --git a/other/two_pointer.py b/other/two_pointer.py new file mode 100644 index 000000000000..ff234cddc9e4 --- /dev/null +++ b/other/two_pointer.py @@ -0,0 +1,61 @@ +""" +Given a sorted array of integers, return indices of the two numbers such +that they add up to a specific target using the two pointers technique. + +You may assume that each input would have exactly one solution, and you +may not use the same element twice. + +This is an alternative solution of the two-sum problem, which uses a +map to solve the problem. Hence can not solve the issue if there is a +constraint not use the same index twice. [1] + +Example: +Given nums = [2, 7, 11, 15], target = 9, + +Because nums[0] + nums[1] = 2 + 7 = 9, +return [0, 1]. + +[1]: https://github.com/TheAlgorithms/Python/blob/master/other/two_sum.py +""" +from __future__ import annotations + + +def two_pointer(nums: list[int], target: int) -> list[int]: + """ + >>> two_pointer([2, 7, 11, 15], 9) + [0, 1] + >>> two_pointer([2, 7, 11, 15], 17) + [0, 3] + >>> two_pointer([2, 7, 11, 15], 18) + [1, 2] + >>> two_pointer([2, 7, 11, 15], 26) + [2, 3] + >>> two_pointer([1, 3, 3], 6) + [1, 2] + >>> two_pointer([2, 7, 11, 15], 8) + [] + >>> two_pointer([3 * i for i in range(10)], 19) + [] + >>> two_pointer([1, 2, 3], 6) + [] + """ + i = 0 + j = len(nums) - 1 + + while i < j: + + if nums[i] + nums[j] == target: + return [i, j] + elif nums[i] + nums[j] < target: + i = i + 1 + else: + j = j - 1 + + return [] + + +if __name__ == "__main__": + import doctest + + doctest.testmod() + print(f"{two_pointer([2, 7, 11, 15], 9) = }") From a5aef147e90c640a6ee9c055c439e71384460479 Mon Sep 17 00:00:00 2001 From: John Law Date: Mon, 26 Oct 2020 23:48:57 +0800 Subject: [PATCH 0967/1071] Fix Project Euler Readme (#3754) * Fix Project Euler Readme * updating DIRECTORY.md * Update CONTRIBUTING.md * spacing Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> --- CONTRIBUTING.md | 2 +- DIRECTORY.md | 4 ++++ project_euler/README.md | 14 +++++++------- 3 files changed, 12 insertions(+), 8 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index bfad76a65b61..eedcb0250169 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -148,7 +148,7 @@ We want your work to be readable by others; therefore, we encourage you to note - If you need a third party module that is not in the file __requirements.txt__, please add it to that file as part of your submission. #### Other Requirements for Submissions - +- If you are submitting code in the `project_euler/` directory, please also read [the dedicated Guideline](https://github.com/TheAlgorithms/Python/blob/master/project_euler/README.md) before contributing to our Project Euler library. - The file extension for code files should be `.py`. Jupyter Notebooks should be submitted to [TheAlgorithms/Jupyter](https://github.com/TheAlgorithms/Jupyter). - Strictly use snake_case (underscore_separated) in your file_name, as it will be easy to parse in future using scripts. - Please avoid creating new directories if at all possible. Try to fit your work into the existing directory structure. diff --git a/DIRECTORY.md b/DIRECTORY.md index 6f14f74fd0f7..c3b32b1ab754 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -28,6 +28,7 @@ * [Binary And Operator](https://github.com/TheAlgorithms/Python/blob/master/bit_manipulation/binary_and_operator.py) * [Binary Or Operator](https://github.com/TheAlgorithms/Python/blob/master/bit_manipulation/binary_or_operator.py) * [Binary Xor Operator](https://github.com/TheAlgorithms/Python/blob/master/bit_manipulation/binary_xor_operator.py) + * [Single Bit Manipulation Operations](https://github.com/TheAlgorithms/Python/blob/master/bit_manipulation/single_bit_manipulation_operations.py) ## Blockchain * [Chinese Remainder Theorem](https://github.com/TheAlgorithms/Python/blob/master/blockchain/chinese_remainder_theorem.py) @@ -265,6 +266,7 @@ * [Basic Graphs](https://github.com/TheAlgorithms/Python/blob/master/graphs/basic_graphs.py) * [Bellman Ford](https://github.com/TheAlgorithms/Python/blob/master/graphs/bellman_ford.py) * [Bfs Shortest Path](https://github.com/TheAlgorithms/Python/blob/master/graphs/bfs_shortest_path.py) + * [Bfs Zero One Shortest Path](https://github.com/TheAlgorithms/Python/blob/master/graphs/bfs_zero_one_shortest_path.py) * [Bidirectional A Star](https://github.com/TheAlgorithms/Python/blob/master/graphs/bidirectional_a_star.py) * [Bidirectional Breadth First Search](https://github.com/TheAlgorithms/Python/blob/master/graphs/bidirectional_breadth_first_search.py) * [Breadth First Search](https://github.com/TheAlgorithms/Python/blob/master/graphs/breadth_first_search.py) @@ -694,6 +696,8 @@ * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_076/sol1.py) * Problem 080 * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_080/sol1.py) + * Problem 081 + * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_081/sol1.py) * Problem 091 * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_091/sol1.py) * Problem 097 diff --git a/project_euler/README.md b/project_euler/README.md index 934e541cc067..1cc6f8150e38 100644 --- a/project_euler/README.md +++ b/project_euler/README.md @@ -1,11 +1,11 @@ # Project Euler -Problems are taken from https://projecteuler.net/. +Problems are taken from https://projecteuler.net/, the Project Euler. [Problems are licensed under CC BY-NC-SA 4.0](https://projecteuler.net/copyright). -Project Euler is a series of challenging mathematical/computer programming problems that will require more than just mathematical +Project Euler is a series of challenging mathematical/computer programming problems that require more than just mathematical insights to solve. Project Euler is ideal for mathematicians who are learning to code. -The solutions will be checked by our [automated testing on Travis CI](https://travis-ci.com/github/TheAlgorithms/Python/pull_requests) with the help of [this script](https://github.com/TheAlgorithms/Python/blob/master/project_euler/validate_solutions.py). The efficiency of your code is also checked. You can view the top 10 slowest solutions on Travis CI logs and open a pull request to improve those solutions. +The solutions will be checked by our [automated testing on Travis CI](https://travis-ci.com/github/TheAlgorithms/Python/pull_requests) with the help of [this script](https://github.com/TheAlgorithms/Python/blob/master/scripts/validate_solutions.py). The efficiency of your code is also checked. You can view the top 10 slowest solutions on Travis CI logs (under `slowest 10 durations`) and open a pull request to improve those solutions. ## Solution Guidelines @@ -17,18 +17,18 @@ Welcome to [TheAlgorithms/Python](https://github.com/TheAlgorithms/Python)! Befo * Please maintain consistency in project directory and solution file names. Keep the following points in mind: * Create a new directory only for the problems which do not exist yet. * If you create a new directory, please create an empty `__init__.py` file inside it as well. - * Please name the project directory as `problem_` where `problem_number` should be filled with 0s so as to occupy 3 digits. Example: `problem_001`, `problem_002`, `problem_067`, `problem_145`, and so on. + * Please name the project **directory** as `problem_` where `problem_number` should be filled with 0s so as to occupy 3 digits. Example: `problem_001`, `problem_002`, `problem_067`, `problem_145`, and so on. -* Please provide a link to the problem and other references, if used, in the module-level docstring. +* Please provide a link to the problem and other references, if used, in the **module-level docstring**. * All imports should come ***after*** the module-level docstring. * You can have as many helper functions as you want but there should be one main function called `solution` which should satisfy the conditions as stated below: - * It should contain positional argument(s) whose default value is the question input. Example: Please take a look at [problem 1](https://projecteuler.net/problem=1) where the question is to *Find the sum of all the multiples of 3 or 5 below 1000.* In this case the main solution function will be `solution(limit: int = 1000)`. + * It should contain positional argument(s) whose default value is the question input. Example: Please take a look at [Problem 1](https://projecteuler.net/problem=1) where the question is to *Find the sum of all the multiples of 3 or 5 below 1000.* In this case the main solution function will be `solution(limit: int = 1000)`. * When the `solution` function is called without any arguments like so: `solution()`, it should return the answer to the problem. * Every function, which includes all the helper functions, if any, and the main solution function, should have `doctest` in the function docstring along with a brief statement mentioning what the function is about. - * There should not be a `doctest` for testing the answer as that is done by our Travis CI build using this [script](https://github.com/TheAlgorithms/Python/blob/master/project_euler/validate_solutions.py). Keeping in mind the above example of [problem 1](https://projecteuler.net/problem=1): + * There should not be a `doctest` for testing the answer as that is done by our Travis CI build using this [script](https://github.com/TheAlgorithms/Python/blob/master/project_euler/validate_solutions.py). Keeping in mind the above example of [Problem 1](https://projecteuler.net/problem=1): ```python def solution(limit: int = 1000): From aebf9bdaafbaa71ad4b5bf933ed2231ae7e4f731 Mon Sep 17 00:00:00 2001 From: Snimerjot Singh Date: Tue, 27 Oct 2020 09:35:37 +0530 Subject: [PATCH 0968/1071] Added reverse_letters.py (#3730) * Added reverse_letters.py * Update strings/reverse_letters.py Co-authored-by: Du Yuanchao Co-authored-by: Du Yuanchao --- strings/reverse_letters.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 strings/reverse_letters.py diff --git a/strings/reverse_letters.py b/strings/reverse_letters.py new file mode 100644 index 000000000000..10b8a6d72a0f --- /dev/null +++ b/strings/reverse_letters.py @@ -0,0 +1,19 @@ +def reverse_letters(input_str: str) -> str: + """ + Reverses letters in a given string without adjusting the position of the words + >>> reverse_letters('The cat in the hat') + 'ehT tac ni eht tah' + >>> reverse_letters('The quick brown fox jumped over the lazy dog.') + 'ehT kciuq nworb xof depmuj revo eht yzal .god' + >>> reverse_letters('Is this true?') + 'sI siht ?eurt' + >>> reverse_letters("I love Python") + 'I evol nohtyP' + """ + return " ".join([word[::-1] for word in input_str.split()]) + + +if __name__ == "__main__": + import doctest + + doctest.testmod() From 4fa8c9d4b458a29299e69cc9a2217fe31cc41ea2 Mon Sep 17 00:00:00 2001 From: Lewis Tian Date: Thu, 29 Oct 2020 08:35:31 +0800 Subject: [PATCH 0969/1071] Update graphs/depth_first_search_2.py (#3799) - update naming style to snake_case - add type hints --- graphs/depth_first_search_2.py | 56 +++++++++++++++++----------------- 1 file changed, 28 insertions(+), 28 deletions(-) diff --git a/graphs/depth_first_search_2.py b/graphs/depth_first_search_2.py index c932e76293ed..3072d527c1c7 100644 --- a/graphs/depth_first_search_2.py +++ b/graphs/depth_first_search_2.py @@ -8,58 +8,58 @@ def __init__(self): self.vertex = {} # for printing the Graph vertices - def printGraph(self): + def print_graph(self) -> None: print(self.vertex) - for i in self.vertex.keys(): + for i in self.vertex: print(i, " -> ", " -> ".join([str(j) for j in self.vertex[i]])) # for adding the edge between two vertices - def addEdge(self, fromVertex, toVertex): + def add_edge(self, from_vertex: int, to_vertex: int) -> None: # check if vertex is already present, - if fromVertex in self.vertex.keys(): - self.vertex[fromVertex].append(toVertex) + if from_vertex in self.vertex: + self.vertex[from_vertex].append(to_vertex) else: # else make a new vertex - self.vertex[fromVertex] = [toVertex] + self.vertex[from_vertex] = [to_vertex] - def DFS(self): + def dfs(self) -> None: # visited array for storing already visited nodes visited = [False] * len(self.vertex) # call the recursive helper function for i in range(len(self.vertex)): - if visited[i] is False: - self.DFSRec(i, visited) + if not visited[i]: + self.dfs_recursive(i, visited) - def DFSRec(self, startVertex, visited): + def dfs_recursive(self, start_vertex: int, visited: list) -> None: # mark start vertex as visited - visited[startVertex] = True + visited[start_vertex] = True - print(startVertex, end=" ") + print(start_vertex, end=" ") # Recur for all the vertices that are adjacent to this node - for i in self.vertex.keys(): - if visited[i] is False: - self.DFSRec(i, visited) + for i in self.vertex: + if not visited[i]: + self.dfs_recursive(i, visited) if __name__ == "__main__": g = Graph() - g.addEdge(0, 1) - g.addEdge(0, 2) - g.addEdge(1, 2) - g.addEdge(2, 0) - g.addEdge(2, 3) - g.addEdge(3, 3) + g.add_edge(0, 1) + g.add_edge(0, 2) + g.add_edge(1, 2) + g.add_edge(2, 0) + g.add_edge(2, 3) + g.add_edge(3, 3) - g.printGraph() + g.print_graph() print("DFS:") - g.DFS() + g.dfs() # OUTPUT: - # 0  ->  1 -> 2 - # 1  ->  2 - # 2  ->  0 -> 3 - # 3  ->  3 + # 0 -> 1 -> 2 + # 1 -> 2 + # 2 -> 0 -> 3 + # 3 -> 3 # DFS: - #  0 1 2 3 + # 0 1 2 3 From fd7da5ff8f7dabb73a3786a0951518de18388d71 Mon Sep 17 00:00:00 2001 From: Abhinand C <44578852+abhinand-c@users.noreply.github.com> Date: Thu, 29 Oct 2020 06:13:34 +0530 Subject: [PATCH 0970/1071] Add IBM Qiskit References (#2561) * Added IBM Qiskit References * space Co-authored-by: John Law --- quantum/README.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/quantum/README.md b/quantum/README.md index be5bd0843f4f..423d34fa3364 100644 --- a/quantum/README.md +++ b/quantum/README.md @@ -6,3 +6,10 @@ Started at https://github.com/TheAlgorithms/Python/issues/1831 * Google: https://research.google/teams/applied-science/quantum * IBM: https://qiskit.org and https://github.com/Qiskit * Rigetti: https://rigetti.com and https://github.com/rigetti + +## IBM Qiskit +- Start using by installing `pip install qiskit`, refer the [docs](https://qiskit.org/documentation/install.html) for more info. +- Tutorials & References + - https://github.com/Qiskit/qiskit-tutorials + - https://quantum-computing.ibm.com/docs/iql/first-circuit + - https://medium.com/qiskit/how-to-program-a-quantum-computer-982a9329ed02 From e20895a4ff84083c2097d17b7abb8f55c365e0c9 Mon Sep 17 00:00:00 2001 From: Simon Lammer Date: Thu, 29 Oct 2020 01:46:16 +0100 Subject: [PATCH 0971/1071] Implement the melkman anlgorithm for computing convex hulls (#2916) * Implement the melkman anlgorithm for computing convex hulls * Link melkman algorithm description * Format melkman algorithm code * Add type hints to functions * Fix build errors --- divide_and_conquer/convex_hull.py | 141 ++++++++++++++++++++++-------- 1 file changed, 105 insertions(+), 36 deletions(-) diff --git a/divide_and_conquer/convex_hull.py b/divide_and_conquer/convex_hull.py index cf2c7f835798..9c096f671385 100644 --- a/divide_and_conquer/convex_hull.py +++ b/divide_and_conquer/convex_hull.py @@ -13,6 +13,8 @@ """ +from typing import Iterable, List, Set, Union + class Point: """ @@ -81,7 +83,9 @@ def __hash__(self): return hash(self.x) -def _construct_points(list_of_tuples): +def _construct_points( + list_of_tuples: Union[List[Point], List[List[float]], Iterable[List[float]]] +) -> List[Point]: """ constructs a list of points from an array-like object of numbers @@ -110,20 +114,23 @@ def _construct_points(list_of_tuples): [] """ - points = [] + points: List[Point] = [] if list_of_tuples: for p in list_of_tuples: - try: - points.append(Point(p[0], p[1])) - except (IndexError, TypeError): - print( - f"Ignoring deformed point {p}. All points" - " must have at least 2 coordinates." - ) + if isinstance(p, Point): + points.append(p) + else: + try: + points.append(Point(p[0], p[1])) + except (IndexError, TypeError): + print( + f"Ignoring deformed point {p}. All points" + " must have at least 2 coordinates." + ) return points -def _validate_input(points): +def _validate_input(points: Union[List[Point], List[List[float]]]) -> List[Point]: """ validates an input instance before a convex-hull algorithms uses it @@ -165,33 +172,18 @@ def _validate_input(points): ValueError: Expecting an iterable object but got an non-iterable type 1 """ + if not hasattr(points, "__iter__"): + raise ValueError( + f"Expecting an iterable object but got an non-iterable type {points}" + ) + if not points: raise ValueError(f"Expecting a list of points but got {points}") - if isinstance(points, set): - points = list(points) - - try: - if hasattr(points, "__iter__") and not isinstance(points[0], Point): - if isinstance(points[0], (list, tuple)): - points = _construct_points(points) - else: - raise ValueError( - "Expecting an iterable of type Point, list or tuple. " - f"Found objects of type {type(points[0])} instead" - ) - elif not hasattr(points, "__iter__"): - raise ValueError( - f"Expecting an iterable object but got an non-iterable type {points}" - ) - except TypeError: - print("Expecting an iterable of type Point, list or tuple.") - raise - - return points + return _construct_points(points) -def _det(a, b, c): +def _det(a: Point, b: Point, c: Point) -> float: """ Computes the sign perpendicular distance of a 2d point c from a line segment ab. The sign indicates the direction of c relative to ab. @@ -226,7 +218,7 @@ def _det(a, b, c): return det -def convex_hull_bf(points): +def convex_hull_bf(points: List[Point]) -> List[Point]: """ Constructs the convex hull of a set of 2D points using a brute force algorithm. The algorithm basically considers all combinations of points (i, j) and uses the @@ -299,7 +291,7 @@ def convex_hull_bf(points): return sorted(convex_set) -def convex_hull_recursive(points): +def convex_hull_recursive(points: List[Point]) -> List[Point]: """ Constructs the convex hull of a set of 2D points using a divide-and-conquer strategy The algorithm exploits the geometric properties of the problem by repeatedly @@ -369,7 +361,9 @@ def convex_hull_recursive(points): return sorted(convex_set) -def _construct_hull(points, left, right, convex_set): +def _construct_hull( + points: List[Point], left: Point, right: Point, convex_set: Set[Point] +) -> None: """ Parameters @@ -411,6 +405,77 @@ def _construct_hull(points, left, right, convex_set): _construct_hull(candidate_points, extreme_point, right, convex_set) +def convex_hull_melkman(points: List[Point]) -> List[Point]: + """ + Constructs the convex hull of a set of 2D points using the melkman algorithm. + The algorithm works by iteratively inserting points of a simple polygonal chain + (meaning that no line segments between two consecutive points cross each other). + Sorting the points yields such a polygonal chain. + + For a detailed description, see http://cgm.cs.mcgill.ca/~athens/cs601/Melkman.html + + Runtime: O(n log n) - O(n) if points are already sorted in the input + + Parameters + --------- + points: array-like of object of Points, lists or tuples. + The set of 2d points for which the convex-hull is needed + + Returns + ------ + convex_set: list, the convex-hull of points sorted in non-decreasing order. + + See Also + -------- + + Examples + --------- + >>> convex_hull_melkman([[0, 0], [1, 0], [10, 1]]) + [(0.0, 0.0), (1.0, 0.0), (10.0, 1.0)] + >>> convex_hull_melkman([[0, 0], [1, 0], [10, 0]]) + [(0.0, 0.0), (10.0, 0.0)] + >>> convex_hull_melkman([[-1, 1],[-1, -1], [0, 0], [0.5, 0.5], [1, -1], [1, 1], + ... [-0.75, 1]]) + [(-1.0, -1.0), (-1.0, 1.0), (1.0, -1.0), (1.0, 1.0)] + >>> convex_hull_melkman([(0, 3), (2, 2), (1, 1), (2, 1), (3, 0), (0, 0), (3, 3), + ... (2, -1), (2, -4), (1, -3)]) + [(0.0, 0.0), (0.0, 3.0), (1.0, -3.0), (2.0, -4.0), (3.0, 0.0), (3.0, 3.0)] + """ + points = sorted(_validate_input(points)) + n = len(points) + + convex_hull = points[:2] + for i in range(2, n): + det = _det(convex_hull[1], convex_hull[0], points[i]) + if det > 0: + convex_hull.insert(0, points[i]) + break + elif det < 0: + convex_hull.append(points[i]) + break + else: + convex_hull[1] = points[i] + i += 1 + + for i in range(i, n): + if ( + _det(convex_hull[0], convex_hull[-1], points[i]) > 0 + and _det(convex_hull[-1], convex_hull[0], points[1]) < 0 + ): + # The point lies within the convex hull + continue + + convex_hull.insert(0, points[i]) + convex_hull.append(points[i]) + while _det(convex_hull[0], convex_hull[1], convex_hull[2]) >= 0: + del convex_hull[1] + while _det(convex_hull[-1], convex_hull[-2], convex_hull[-3]) <= 0: + del convex_hull[-2] + + # `convex_hull` is contains the convex hull in circular order + return sorted(convex_hull[1:] if len(convex_hull) > 3 else convex_hull) + + def main(): points = [ (0, 3), @@ -426,10 +491,14 @@ def main(): ] # the convex set of points is # [(0, 0), (0, 3), (1, -3), (2, -4), (3, 0), (3, 3)] - results_recursive = convex_hull_recursive(points) results_bf = convex_hull_bf(points) + + results_recursive = convex_hull_recursive(points) assert results_bf == results_recursive + results_melkman = convex_hull_melkman(points) + assert results_bf == results_melkman + print(results_bf) From e172a8b08b368b0872040f2a66cd7678d2259352 Mon Sep 17 00:00:00 2001 From: sharmapulkit04 <39304055+sharmapulkit04@users.noreply.github.com> Date: Thu, 29 Oct 2020 07:33:55 +0530 Subject: [PATCH 0972/1071] Hacktoberfest: Added first solution to Project Euler problem 58 (#3599) * Added solution to problem 58 * Update sol1.py Co-authored-by: John Law --- project_euler/problem_058/__init__.py | 1 + project_euler/problem_058/sol1.py | 86 +++++++++++++++++++++++++++ 2 files changed, 87 insertions(+) create mode 100644 project_euler/problem_058/__init__.py create mode 100644 project_euler/problem_058/sol1.py diff --git a/project_euler/problem_058/__init__.py b/project_euler/problem_058/__init__.py new file mode 100644 index 000000000000..792d6005489e --- /dev/null +++ b/project_euler/problem_058/__init__.py @@ -0,0 +1 @@ +# diff --git a/project_euler/problem_058/sol1.py b/project_euler/problem_058/sol1.py new file mode 100644 index 000000000000..d3b15157fbbd --- /dev/null +++ b/project_euler/problem_058/sol1.py @@ -0,0 +1,86 @@ +""" +Project Euler Problem 58:https://projecteuler.net/problem=58 + + +Starting with 1 and spiralling anticlockwise in the following way, +a square spiral with side length 7 is formed. + +37 36 35 34 33 32 31 +38 17 16 15 14 13 30 +39 18 5 4 3 12 29 +40 19 6 1 2 11 28 +41 20 7 8 9 10 27 +42 21 22 23 24 25 26 +43 44 45 46 47 48 49 + +It is interesting to note that the odd squares lie along the bottom right +diagonal ,but what is more interesting is that 8 out of the 13 numbers +lying along both diagonals are prime; that is, a ratio of 8/13 ≈ 62%. + +If one complete new layer is wrapped around the spiral above, +a square spiral with side length 9 will be formed. +If this process is continued, +what is the side length of the square spiral for which +the ratio of primes along both diagonals first falls below 10%? + +Solution: We have to find an odd length side for which square falls below +10%. With every layer we add 4 elements are being added to the diagonals +,lets say we have a square spiral of odd length with side length j, +then if we move from j to j+2, we are adding j*j+j+1,j*j+2*(j+1),j*j+3*(j+1) +j*j+4*(j+1). Out of these 4 only the first three can become prime +because last one reduces to (j+2)*(j+2). +So we check individually each one of these before incrementing our +count of current primes. + +""" + + +def isprime(d: int) -> int: + """ + returns whether the given digit is prime or not + >>> isprime(1) + 0 + >>> isprime(17) + 1 + >>> isprime(10000) + 0 + """ + if d == 1: + return 0 + + i = 2 + while i * i <= d: + if d % i == 0: + return 0 + i = i + 1 + return 1 + + +def solution(ratio: float = 0.1) -> int: + """ + returns the side length of the square spiral of odd length greater + than 1 for which the ratio of primes along both diagonals + first falls below the given ratio. + >>> solution(.5) + 11 + >>> solution(.2) + 309 + >>> solution(.111) + 11317 + """ + + j = 3 + primes = 3 + + while primes / (2 * j - 1) >= ratio: + for i in range(j * j + j + 1, (j + 2) * (j + 2), j + 1): + primes = primes + isprime(i) + + j = j + 2 + return j + + +if __name__ == "__main__": + import doctest + + doctest.testmod() From a1e9656ecadb2e6c2805c79b099c198b245fd43b Mon Sep 17 00:00:00 2001 From: Marcos Vinicius Date: Thu, 29 Oct 2020 00:09:39 -0300 Subject: [PATCH 0973/1071] Hacktoberfest: adding doctest to radix_sort.py file (#2779) * adding doctest to radix_sort.py file * fixup! Format Python code with psf/black push * Update radix_sort.py * Update radix_sort.py * fixup! Format Python code with psf/black push * Update radix_sort.py * line * fix tests Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> Co-authored-by: John Law --- sorts/radix_sort.py | 29 +++++++++++++++++++++++++---- 1 file changed, 25 insertions(+), 4 deletions(-) diff --git a/sorts/radix_sort.py b/sorts/radix_sort.py index 7942462ea10d..57dbbaa79076 100644 --- a/sorts/radix_sort.py +++ b/sorts/radix_sort.py @@ -1,13 +1,28 @@ +""" +This is a pure Python implementation of the quick sort algorithm +For doctests run following command: +python -m doctest -v radix_sort.py +or +python3 -m doctest -v radix_sort.py +For manual testing run: +python radix_sort.py +""" from __future__ import annotations +from typing import List -def radix_sort(list_of_ints: list[int]) -> list[int]: + +def radix_sort(list_of_ints: List[int]) -> List[int]: """ - radix_sort(range(15)) == sorted(range(15)) + Examples: + >>> radix_sort([0, 5, 3, 2, 2]) + [0, 2, 2, 3, 5] + + >>> radix_sort(list(range(15))) == sorted(range(15)) True - radix_sort(reversed(range(15))) == sorted(range(15)) + >>> radix_sort(list(range(14,-1,-1))) == sorted(range(15)) True - radix_sort([1,100,10,1000]) == sorted([1,100,10,1000]) + >>> radix_sort([1,100,10,1000]) == sorted([1,100,10,1000]) True """ RADIX = 10 @@ -29,3 +44,9 @@ def radix_sort(list_of_ints: list[int]) -> list[int]: # move to next placement *= RADIX return list_of_ints + + +if __name__ == "__main__": + import doctest + + doctest.testmod() From 9971f981a19072097d6d8761c068379de9bdb7ea Mon Sep 17 00:00:00 2001 From: fpringle Date: Thu, 29 Oct 2020 07:49:33 +0100 Subject: [PATCH 0974/1071] Added solution for Project Euler problem 87. (#3141) * Added solution for Project Euler problem 87. Fixes: #2695 * Update docstring and 0-padding in directory name. Reference: #3256 --- project_euler/problem_087/__init__.py | 0 project_euler/problem_087/sol1.py | 52 +++++++++++++++++++++++++++ 2 files changed, 52 insertions(+) create mode 100644 project_euler/problem_087/__init__.py create mode 100644 project_euler/problem_087/sol1.py diff --git a/project_euler/problem_087/__init__.py b/project_euler/problem_087/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/project_euler/problem_087/sol1.py b/project_euler/problem_087/sol1.py new file mode 100644 index 000000000000..f444481c17ac --- /dev/null +++ b/project_euler/problem_087/sol1.py @@ -0,0 +1,52 @@ +""" +Project Euler Problem 87: https://projecteuler.net/problem=87 + +The smallest number expressible as the sum of a prime square, prime cube, and prime +fourth power is 28. In fact, there are exactly four numbers below fifty that can be +expressed in such a way: + +28 = 22 + 23 + 24 +33 = 32 + 23 + 24 +49 = 52 + 23 + 24 +47 = 22 + 33 + 24 + +How many numbers below fifty million can be expressed as the sum of a prime square, +prime cube, and prime fourth power? +""" + + +def solution(limit: int = 50000000) -> int: + """ + Return the number of integers less than limit which can be expressed as the sum + of a prime square, prime cube, and prime fourth power. + >>> solution(50) + 4 + """ + ret = set() + prime_square_limit = int((limit - 24) ** (1 / 2)) + + primes = set(range(3, prime_square_limit + 1, 2)) + primes.add(2) + for p in range(3, prime_square_limit + 1, 2): + if p not in primes: + continue + primes.difference_update(set(range(p * p, prime_square_limit + 1, p))) + + for prime1 in primes: + square = prime1 * prime1 + for prime2 in primes: + cube = prime2 * prime2 * prime2 + if square + cube >= limit - 16: + break + for prime3 in primes: + tetr = prime3 * prime3 * prime3 * prime3 + total = square + cube + tetr + if total >= limit: + break + ret.add(total) + + return len(ret) + + +if __name__ == "__main__": + print(f"{solution() = }") From 99adac0eb135ab9e48e86db01da520eded2f985a Mon Sep 17 00:00:00 2001 From: PetitNigaud <44503597+PetitNigaud@users.noreply.github.com> Date: Thu, 29 Oct 2020 08:04:42 +0100 Subject: [PATCH 0975/1071] Add first solution for Project Euler Problem 207 (#3522) * add solution to Project Euler problem 206 * Add solution to Project Euler problem 205 * updating DIRECTORY.md * updating DIRECTORY.md * Revert "Add solution to Project Euler problem 205" This reverts commit 64e3d36cab2b68630b73a217c9ba455202d85cbb. * Revert "add solution to Project Euler problem 206" This reverts commit 53568cf4efd84f4b1c039eade335655842fa29e3. * add solution for project euler problem 207 * updating DIRECTORY.md * add type hint for output of helper function * Correct default parameter value in solution * use descriptive variable names and remove problem solution from doctest Fixes: #2695 Co-authored-by: nico Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> --- DIRECTORY.md | 2 + project_euler/problem_207/__init__.py | 0 project_euler/problem_207/sol1.py | 98 +++++++++++++++++++++++++++ 3 files changed, 100 insertions(+) create mode 100644 project_euler/problem_207/__init__.py create mode 100644 project_euler/problem_207/sol1.py diff --git a/DIRECTORY.md b/DIRECTORY.md index c3b32b1ab754..9e8981f5f61d 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -720,6 +720,8 @@ * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_174/sol1.py) * Problem 191 * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_191/sol1.py) + * Problem 207 + * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_207/sol1.py) * Problem 234 * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_234/sol1.py) * Problem 551 diff --git a/project_euler/problem_207/__init__.py b/project_euler/problem_207/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/project_euler/problem_207/sol1.py b/project_euler/problem_207/sol1.py new file mode 100644 index 000000000000..fb901fde1624 --- /dev/null +++ b/project_euler/problem_207/sol1.py @@ -0,0 +1,98 @@ +""" + +Project Euler Problem 207: https://projecteuler.net/problem=207 + +Problem Statement: +For some positive integers k, there exists an integer partition of the form +4**t = 2**t + k, where 4**t, 2**t, and k are all positive integers and t is a real +number. The first two such partitions are 4**1 = 2**1 + 2 and +4**1.5849625... = 2**1.5849625... + 6. +Partitions where t is also an integer are called perfect. +For any m ≥ 1 let P(m) be the proportion of such partitions that are perfect with +k ≤ m. +Thus P(6) = 1/2. +In the following table are listed some values of P(m) + + P(5) = 1/1 + P(10) = 1/2 + P(15) = 2/3 + P(20) = 1/2 + P(25) = 1/2 + P(30) = 2/5 + ... + P(180) = 1/4 + P(185) = 3/13 + +Find the smallest m for which P(m) < 1/12345 + +Solution: +Equation 4**t = 2**t + k solved for t gives: + t = log2(sqrt(4*k+1)/2 + 1/2) +For t to be real valued, sqrt(4*k+1) must be an integer which is implemented in +function check_t_real(k). For a perfect partition t must be an integer. +To speed up significantly the search for partitions, instead of incrementing k by one +per iteration, the next valid k is found by k = (i**2 - 1) / 4 with an integer i and +k has to be a positive integer. If this is the case a partition is found. The partition +is perfect if t os an integer. The integer i is increased with increment 1 until the +proportion perfect partitions / total partitions drops under the given value. + +""" + +import math + + +def check_partition_perfect(positive_integer: int) -> bool: + """ + + Check if t = f(positive_integer) = log2(sqrt(4*positive_integer+1)/2 + 1/2) is a + real number. + + >>> check_partition_perfect(2) + True + + >>> check_partition_perfect(6) + False + + """ + + exponent = math.log2(math.sqrt(4 * positive_integer + 1) / 2 + 1 / 2) + + return exponent == int(exponent) + + +def solution(max_proportion: float = 1 / 12345) -> int: + """ + Find m for which the proportion of perfect partitions to total partitions is lower + than max_proportion + + >>> solution(1) > 5 + True + + >>> solution(1/2) > 10 + True + + >>> solution(3 / 13) > 185 + True + + """ + + total_partitions = 0 + perfect_partitions = 0 + + integer = 3 + while True: + partition_candidate = (integer ** 2 - 1) / 4 + # if candidate is an integer, then there is a partition for k + if partition_candidate == int(partition_candidate): + partition_candidate = int(partition_candidate) + total_partitions += 1 + if check_partition_perfect(partition_candidate): + perfect_partitions += 1 + if perfect_partitions > 0: + if perfect_partitions / total_partitions < max_proportion: + return partition_candidate + integer += 1 + + +if __name__ == "__main__": + print(f"{solution() = }") From a6831c898a12e568c2e611d457dd71bd89488ce2 Mon Sep 17 00:00:00 2001 From: Joyce Date: Thu, 29 Oct 2020 00:17:26 -0700 Subject: [PATCH 0976/1071] math/greatest_common_divisor: add support for negative numbers (#2628) * add type hints to math/gcd * add doctest * math/gcd - run black formatter * math/gcd: remove manual doctest * add correction to gcd of negative numbers * add more doctest in iterative gcd --- maths/greatest_common_divisor.py | 39 ++++++++++++++++++++++---------- 1 file changed, 27 insertions(+), 12 deletions(-) diff --git a/maths/greatest_common_divisor.py b/maths/greatest_common_divisor.py index 0926ade5dec2..a2174a8eb74a 100644 --- a/maths/greatest_common_divisor.py +++ b/maths/greatest_common_divisor.py @@ -2,10 +2,12 @@ Greatest Common Divisor. Wikipedia reference: https://en.wikipedia.org/wiki/Greatest_common_divisor + +gcd(a, b) = gcd(a, -b) = gcd(-a, b) = gcd(-a, -b) by definition of divisibility """ -def greatest_common_divisor(a, b): +def greatest_common_divisor(a: int, b: int) -> int: """ Calculate Greatest Common Divisor (GCD). >>> greatest_common_divisor(24, 40) @@ -20,31 +22,44 @@ def greatest_common_divisor(a, b): 1 >>> greatest_common_divisor(16, 4) 4 + >>> greatest_common_divisor(-3, 9) + 3 + >>> greatest_common_divisor(9, -3) + 3 + >>> greatest_common_divisor(3, -9) + 3 + >>> greatest_common_divisor(-3, -9) + 3 """ - return b if a == 0 else greatest_common_divisor(b % a, a) - - -""" -Below method is more memory efficient because it does not use the stack (chunk of -memory). While above method is good, uses more memory for huge numbers because of the -recursive calls required to calculate the greatest common divisor. -""" + return abs(b) if a == 0 else greatest_common_divisor(b % a, a) -def gcd_by_iterative(x, y): +def gcd_by_iterative(x: int, y: int) -> int: """ + Below method is more memory efficient because it does not create additional + stack frames for recursive functions calls (as done in the above method). >>> gcd_by_iterative(24, 40) 8 >>> greatest_common_divisor(24, 40) == gcd_by_iterative(24, 40) True + >>> gcd_by_iterative(-3, -9) + 3 + >>> gcd_by_iterative(3, -9) + 3 + >>> gcd_by_iterative(1, -800) + 1 + >>> gcd_by_iterative(11, 37) + 1 """ while y: # --> when y=0 then loop will terminate and return x as final GCD. x, y = y, x % y - return x + return abs(x) def main(): - """Call Greatest Common Divisor function.""" + """ + Call Greatest Common Divisor function. + """ try: nums = input("Enter two integers separated by comma (,): ").split(",") num_1 = int(nums[0]) From a03b3f763fbe933737a3b0b4bcd3feec34f0fb64 Mon Sep 17 00:00:00 2001 From: Du Yuanchao Date: Thu, 29 Oct 2020 17:39:19 +0800 Subject: [PATCH 0977/1071] Balanced parentheses (#3768) * Fixed balanced_parentheses.py * fixed pre-commit * eliminate is_paired * remove unused line * updating DIRECTORY.md * Update data_structures/stacks/balanced_parentheses.py Co-authored-by: Christian Clauss * Add more test cases * Update data_structures/stacks/balanced_parentheses.py Co-authored-by: Christian Clauss Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> Co-authored-by: Christian Clauss --- DIRECTORY.md | 1 + .../stacks/balanced_parentheses.py | 38 +++++++++++++------ 2 files changed, 27 insertions(+), 12 deletions(-) diff --git a/DIRECTORY.md b/DIRECTORY.md index 9e8981f5f61d..ea5e01addeb0 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -520,6 +520,7 @@ * [Sierpinski Triangle](https://github.com/TheAlgorithms/Python/blob/master/other/sierpinski_triangle.py) * [Tower Of Hanoi](https://github.com/TheAlgorithms/Python/blob/master/other/tower_of_hanoi.py) * [Triplet Sum](https://github.com/TheAlgorithms/Python/blob/master/other/triplet_sum.py) + * [Two Pointer](https://github.com/TheAlgorithms/Python/blob/master/other/two_pointer.py) * [Two Sum](https://github.com/TheAlgorithms/Python/blob/master/other/two_sum.py) * [Word Patterns](https://github.com/TheAlgorithms/Python/blob/master/other/word_patterns.py) diff --git a/data_structures/stacks/balanced_parentheses.py b/data_structures/stacks/balanced_parentheses.py index 7aacd5969277..674f7ea436ed 100644 --- a/data_structures/stacks/balanced_parentheses.py +++ b/data_structures/stacks/balanced_parentheses.py @@ -1,23 +1,37 @@ from .stack import Stack -__author__ = "Omkar Pathak" - -def balanced_parentheses(parentheses): - """ Use a stack to check if a string of parentheses is balanced.""" - stack = Stack(len(parentheses)) - for parenthesis in parentheses: - if parenthesis == "(": - stack.push(parenthesis) - elif parenthesis == ")": - if stack.is_empty(): +def balanced_parentheses(parentheses: str) -> bool: + """Use a stack to check if a string of parentheses is balanced. + >>> balanced_parentheses("([]{})") + True + >>> balanced_parentheses("[()]{}{[()()]()}") + True + >>> balanced_parentheses("[(])") + False + >>> balanced_parentheses("1+2*3-4") + True + >>> balanced_parentheses("") + True + """ + stack = Stack() + bracket_pairs = {"(": ")", "[": "]", "{": "}"} + for bracket in parentheses: + if bracket in bracket_pairs: + stack.push(bracket) + elif bracket in (")", "]", "}"): + if stack.is_empty() or bracket_pairs[stack.pop()] != bracket: return False - stack.pop() return stack.is_empty() if __name__ == "__main__": + from doctest import testmod + + testmod() + examples = ["((()))", "((())", "(()))"] print("Balanced parentheses demonstration:\n") for example in examples: - print(example + ": " + str(balanced_parentheses(example))) + not_str = "" if balanced_parentheses(example) else "not " + print(f"{example} is {not_str}balanced") From c83ecacc31effb3cd263b6b80c2f6ea9ca9df10c Mon Sep 17 00:00:00 2001 From: ParamonPlay <56618202+ParamonPlay@users.noreply.github.com> Date: Fri, 30 Oct 2020 07:11:15 -0700 Subject: [PATCH 0978/1071] No issues so far (#3835) * Simplify GitHub Actions * Update stale.yml Co-authored-by: Christian Clauss --- .github/workflows/directory_writer.yml | 2 -- .github/workflows/stale.yml | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/.github/workflows/directory_writer.yml b/.github/workflows/directory_writer.yml index 6547d1c18c79..be8154a32696 100644 --- a/.github/workflows/directory_writer.yml +++ b/.github/workflows/directory_writer.yml @@ -8,8 +8,6 @@ jobs: steps: - uses: actions/checkout@v1 # v1, NOT v2 - uses: actions/setup-python@v2 - with: - python-version: 3.x - name: Write DIRECTORY.md run: | scripts/build_directory_md.py 2>&1 | tee DIRECTORY.md diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml index 4793f54f7af8..341153dbf455 100644 --- a/.github/workflows/stale.yml +++ b/.github/workflows/stale.yml @@ -6,7 +6,7 @@ jobs: stale: runs-on: ubuntu-latest steps: - - uses: actions/stale@v1 + - uses: actions/stale@v3 with: repo-token: ${{ secrets.GITHUB_TOKEN }} stale-issue-message: > From a5389899393d8962d4866c0f810c5c8bac8e5cbb Mon Sep 17 00:00:00 2001 From: Jake Gerber Date: Fri, 30 Oct 2020 15:10:44 -0700 Subject: [PATCH 0979/1071] Added decimal_isolate.py (#3700) * Add files via upload * Delete decimal_isolate.py * Added decimal_isolate file. * Update decimal_isolate.py * Update decimal_isolate.py * Update decimal_isolate.py * Update decimal_isolate.py * Update decimal_isolate.py * Delete decimal_isolate.py * Add files via upload * Update maths/decimal_isolate.py Co-authored-by: Christian Clauss * Update decimal_isolate.py * Update decimal_isolate.py * Update decimal_isolate.py * Update decimal_isolate.py Co-authored-by: Christian Clauss --- maths/decimal_isolate.py | 45 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100644 maths/decimal_isolate.py diff --git a/maths/decimal_isolate.py b/maths/decimal_isolate.py new file mode 100644 index 000000000000..0e3967a4671d --- /dev/null +++ b/maths/decimal_isolate.py @@ -0,0 +1,45 @@ +""" +Isolate the Decimal part of a Number +https://stackoverflow.com/questions/3886402/how-to-get-numbers-after-decimal-point +""" + + +def decimal_isolate(number, digitAmount): + + """ + Isolates the decimal part of a number. + If digitAmount > 0 round to that decimal place, else print the entire decimal. + >>> decimal_isolate(1.53, 0) + 0.53 + >>> decimal_isolate(35.345, 1) + 0.3 + >>> decimal_isolate(35.345, 2) + 0.34 + >>> decimal_isolate(35.345, 3) + 0.345 + >>> decimal_isolate(-14.789, 3) + -0.789 + >>> decimal_isolate(0, 2) + 0 + >>> decimal_isolate(-14.123, 1) + -0.1 + >>> decimal_isolate(-14.123, 2) + -0.12 + >>> decimal_isolate(-14.123, 3) + -0.123 + """ + if digitAmount > 0: + return round(number - int(number), digitAmount) + return number - int(number) + + +if __name__ == "__main__": + print(decimal_isolate(1.53, 0)) + print(decimal_isolate(35.345, 1)) + print(decimal_isolate(35.345, 2)) + print(decimal_isolate(35.345, 3)) + print(decimal_isolate(-14.789, 3)) + print(decimal_isolate(0, 2)) + print(decimal_isolate(-14.123, 1)) + print(decimal_isolate(-14.123, 2)) + print(decimal_isolate(-14.123, 3)) From 1f65007456bc48b90f6183b851c5b9f60fb52ae6 Mon Sep 17 00:00:00 2001 From: jbaenaxd Date: Sun, 1 Nov 2020 08:38:11 +0100 Subject: [PATCH 0980/1071] Shortened code (#3855) --- searches/quick_select.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/searches/quick_select.py b/searches/quick_select.py index 17dca395f73c..5ede8c4dd07f 100644 --- a/searches/quick_select.py +++ b/searches/quick_select.py @@ -44,8 +44,7 @@ def quick_select(items: list, index: int): if index >= len(items) or index < 0: return None - pivot = random.randint(0, len(items) - 1) - pivot = items[pivot] + pivot = items[random.randint(0, len(items) - 1)] count = 0 smaller, equal, larger = _partition(items, pivot) count = len(equal) From d3ead53882ecf9ecab9c210b6f08e7c85498b739 Mon Sep 17 00:00:00 2001 From: Akash Kumar Date: Sun, 1 Nov 2020 05:57:48 -0500 Subject: [PATCH 0981/1071] Added solution to Project Euler problem 301 (#3343) * Added solution to Project Euler problem 301 * Added newline to end of file * Fixed formatting and tests * Changed lossCount to loss_count * Fixed default parameter value for solution * Removed helper function and modified print stmt * Fixed code formatting * Optimized solution from O(n^2) to O(1) constant time * Update sol1.py --- project_euler/problem_301/__init__.py | 0 project_euler/problem_301/sol1.py | 58 +++++++++++++++++++++++++++ 2 files changed, 58 insertions(+) create mode 100644 project_euler/problem_301/__init__.py create mode 100644 project_euler/problem_301/sol1.py diff --git a/project_euler/problem_301/__init__.py b/project_euler/problem_301/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/project_euler/problem_301/sol1.py b/project_euler/problem_301/sol1.py new file mode 100644 index 000000000000..b1d434c189b7 --- /dev/null +++ b/project_euler/problem_301/sol1.py @@ -0,0 +1,58 @@ +""" +Project Euler Problem 301: https://projecteuler.net/problem=301 + +Problem Statement: +Nim is a game played with heaps of stones, where two players take +it in turn to remove any number of stones from any heap until no stones remain. + +We'll consider the three-heap normal-play version of +Nim, which works as follows: +- At the start of the game there are three heaps of stones. +- On each player's turn, the player may remove any positive + number of stones from any single heap. +- The first player unable to move (because no stones remain) loses. + +If (n1, n2, n3) indicates a Nim position consisting of heaps of size +n1, n2, and n3, then there is a simple function, which you may look up +or attempt to deduce for yourself, X(n1, n2, n3) that returns: +- zero if, with perfect strategy, the player about to + move will eventually lose; or +- non-zero if, with perfect strategy, the player about + to move will eventually win. + +For example X(1,2,3) = 0 because, no matter what the current player does, +the opponent can respond with a move that leaves two heaps of equal size, +at which point every move by the current player can be mirrored by the +opponent until no stones remain; so the current player loses. To illustrate: +- current player moves to (1,2,1) +- opponent moves to (1,0,1) +- current player moves to (0,0,1) +- opponent moves to (0,0,0), and so wins. + +For how many positive integers n <= 2^30 does X(n,2n,3n) = 0? +""" + + +def solution(exponent: int = 30) -> int: + """ + For any given exponent x >= 0, 1 <= n <= 2^x. + This function returns how many Nim games are lost given that + each Nim game has three heaps of the form (n, 2*n, 3*n). + >>> solution(0) + 1 + >>> solution(2) + 3 + >>> solution(10) + 144 + """ + # To find how many total games were lost for a given exponent x, + # we need to find the Fibonacci number F(x+2). + fibonacci_index = exponent + 2 + phi = (1 + 5 ** 0.5) / 2 + fibonacci = (phi ** fibonacci_index - (phi - 1) ** fibonacci_index) / 5 ** 0.5 + + return int(fibonacci) + + +if __name__ == "__main__": + print(f"{solution() = }") From d8f573c0fbc5363b268accca6d73c85b463da65f Mon Sep 17 00:00:00 2001 From: GGn0 <44038661+GGn0@users.noreply.github.com> Date: Sun, 1 Nov 2020 14:12:21 +0100 Subject: [PATCH 0982/1071] Add solution to problem 74 (#3110) * Add solution to problem 74 * Fix typo * Edit unnecessary comment * Rename folder, add default params in solution() * Rename file to solve conflicts * Fix doctests --- project_euler/problem_074/sol2.py | 116 ++++++++++++++++++++++++++++++ 1 file changed, 116 insertions(+) create mode 100644 project_euler/problem_074/sol2.py diff --git a/project_euler/problem_074/sol2.py b/project_euler/problem_074/sol2.py new file mode 100644 index 000000000000..0348ef1a6628 --- /dev/null +++ b/project_euler/problem_074/sol2.py @@ -0,0 +1,116 @@ +""" + Project Euler Problem 074: https://projecteuler.net/problem=74 + + Starting from any positive integer number + it is possible to attain another one summing the factorial of its digits. + + Repeating this step, we can build chains of numbers. + It is not difficult to prove that EVERY starting number + will eventually get stuck in a loop. + + The request is to find how many numbers less than one million + produce a chain with exactly 60 non repeating items. + + Solution approach: + This solution simply consists in a loop that generates + the chains of non repeating items. + The generation of the chain stops before a repeating item + or if the size of the chain is greater then the desired one. + After generating each chain, the length is checked and the counter increases. +""" + + +def factorial(a: int) -> int: + """Returns the factorial of the input a + >>> factorial(5) + 120 + + >>> factorial(6) + 720 + + >>> factorial(0) + 1 + """ + + # The factorial function is not defined for negative numbers + if a < 0: + raise ValueError("Invalid negative input!", a) + + # The case of 0! is handled separately + if a == 0: + return 1 + else: + # use a temporary support variable to store the computation + temporary_computation = 1 + + while a > 0: + temporary_computation *= a + a -= 1 + + return temporary_computation + + +def factorial_sum(a: int) -> int: + """Function to perform the sum of the factorial + of all the digits in a + + >>> factorial_sum(69) + 363600 + """ + + # Prepare a variable to hold the computation + fact_sum = 0 + + """ Convert a in string to iterate on its digits + convert the digit back into an int + and add its factorial to fact_sum. + """ + for i in str(a): + fact_sum += factorial(int(i)) + + return fact_sum + + +def solution(chain_length: int = 60, number_limit: int = 1000000) -> int: + """Returns the number of numbers that produce + chains with exactly 60 non repeating elements. + >>> solution(60,1000000) + 402 + >>> solution(15,1000000) + 17800 + """ + + # the counter for the chains with the exact desired length + chain_counter = 0 + + for i in range(1, number_limit + 1): + + # The temporary list will contain the elements of the chain + chain_list = [i] + + # The new element of the chain + new_chain_element = factorial_sum(chain_list[-1]) + + """ Stop computing the chain when you find a repeating item + or the length it greater then the desired one. + """ + while not (new_chain_element in chain_list) and ( + len(chain_list) <= chain_length + ): + chain_list += [new_chain_element] + + new_chain_element = factorial_sum(chain_list[-1]) + + """ If the while exited because the chain list contains the exact amount of elements + increase the counter + """ + chain_counter += len(chain_list) == chain_length + + return chain_counter + + +if __name__ == "__main__": + import doctest + + doctest.testmod() + print(f"{solution()}") From 786b32431cd1d0f57ec4b79a009959da8a2fe06c Mon Sep 17 00:00:00 2001 From: Du Yuanchao Date: Mon, 2 Nov 2020 00:35:31 +0800 Subject: [PATCH 0983/1071] Update infix to postfix (#3817) * add test to infix_to_postfix_conversion * fixed pre-commit error * fixed build error * updating DIRECTORY.md Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> --- DIRECTORY.md | 5 ++ data_structures/stacks/__init__.py | 22 ------ .../stacks/infix_to_postfix_conversion.py | 70 +++++++++++-------- 3 files changed, 45 insertions(+), 52 deletions(-) diff --git a/DIRECTORY.md b/DIRECTORY.md index ea5e01addeb0..7c695892112a 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -674,6 +674,8 @@ * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_056/sol1.py) * Problem 057 * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_057/sol1.py) + * Problem 058 + * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_058/sol1.py) * Problem 062 * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_062/sol1.py) * Problem 063 @@ -699,6 +701,8 @@ * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_080/sol1.py) * Problem 081 * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_081/sol1.py) + * Problem 087 + * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_087/sol1.py) * Problem 091 * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_091/sol1.py) * Problem 097 @@ -817,6 +821,7 @@ * [Prefix Function](https://github.com/TheAlgorithms/Python/blob/master/strings/prefix_function.py) * [Rabin Karp](https://github.com/TheAlgorithms/Python/blob/master/strings/rabin_karp.py) * [Remove Duplicate](https://github.com/TheAlgorithms/Python/blob/master/strings/remove_duplicate.py) + * [Reverse Letters](https://github.com/TheAlgorithms/Python/blob/master/strings/reverse_letters.py) * [Reverse Words](https://github.com/TheAlgorithms/Python/blob/master/strings/reverse_words.py) * [Split](https://github.com/TheAlgorithms/Python/blob/master/strings/split.py) * [Swap Case](https://github.com/TheAlgorithms/Python/blob/master/strings/swap_case.py) diff --git a/data_structures/stacks/__init__.py b/data_structures/stacks/__init__.py index f6995cf98977..e69de29bb2d1 100644 --- a/data_structures/stacks/__init__.py +++ b/data_structures/stacks/__init__.py @@ -1,22 +0,0 @@ -class Stack: - def __init__(self): - self.stack = [] - self.top = 0 - - def is_empty(self): - return self.top == 0 - - def push(self, item): - if self.top < len(self.stack): - self.stack[self.top] = item - else: - self.stack.append(item) - - self.top += 1 - - def pop(self): - if self.is_empty(): - return None - else: - self.top -= 1 - return self.stack[self.top] diff --git a/data_structures/stacks/infix_to_postfix_conversion.py b/data_structures/stacks/infix_to_postfix_conversion.py index 4a1180c9d8e4..dedba8479ac8 100644 --- a/data_structures/stacks/infix_to_postfix_conversion.py +++ b/data_structures/stacks/infix_to_postfix_conversion.py @@ -1,57 +1,67 @@ -import string +""" +https://en.wikipedia.org/wiki/Infix_notation +https://en.wikipedia.org/wiki/Reverse_Polish_notation +https://en.wikipedia.org/wiki/Shunting-yard_algorithm +""" +from .balanced_parentheses import balanced_parentheses from .stack import Stack -__author__ = "Omkar Pathak" - -def is_operand(char): - return char in string.ascii_letters or char in string.digits - - -def precedence(char): - """Return integer value representing an operator's precedence, or +def precedence(char: str) -> int: + """ + Return integer value representing an operator's precedence, or order of operation. - https://en.wikipedia.org/wiki/Order_of_operations """ - dictionary = {"+": 1, "-": 1, "*": 2, "/": 2, "^": 3} - return dictionary.get(char, -1) - + return {"+": 1, "-": 1, "*": 2, "/": 2, "^": 3}.get(char, -1) -def infix_to_postfix(expression): - """Convert infix notation to postfix notation using the Shunting-yard - algorithm. - https://en.wikipedia.org/wiki/Shunting-yard_algorithm - https://en.wikipedia.org/wiki/Infix_notation - https://en.wikipedia.org/wiki/Reverse_Polish_notation +def infix_to_postfix(expression_str: str) -> str: + """ + >>> infix_to_postfix("(1*(2+3)+4))") + Traceback (most recent call last): + ... + ValueError: Mismatched parentheses + >>> infix_to_postfix("") + '' + >>> infix_to_postfix("3+2") + '3 2 +' + >>> infix_to_postfix("(3+4)*5-6") + '3 4 + 5 * 6 -' + >>> infix_to_postfix("(1+2)*3/4-5") + '1 2 + 3 * 4 / 5 -' + >>> infix_to_postfix("a+b*c+(d*e+f)*g") + 'a b c * + d e * f + g * +' + >>> infix_to_postfix("x^y/(5*z)+2") + 'x y ^ 5 z * / 2 +' """ - stack = Stack(len(expression)) + if not balanced_parentheses(expression_str): + raise ValueError("Mismatched parentheses") + stack = Stack() postfix = [] - for char in expression: - if is_operand(char): + for char in expression_str: + if char.isalpha() or char.isdigit(): postfix.append(char) - elif char not in {"(", ")"}: - while not stack.is_empty() and precedence(char) <= precedence(stack.peek()): - postfix.append(stack.pop()) - stack.push(char) elif char == "(": stack.push(char) elif char == ")": while not stack.is_empty() and stack.peek() != "(": postfix.append(stack.pop()) - # Pop '(' from stack. If there is no '(', there is a mismatched - # parentheses. - if stack.peek() != "(": - raise ValueError("Mismatched parentheses") stack.pop() + else: + while not stack.is_empty() and precedence(char) <= precedence(stack.peek()): + postfix.append(stack.pop()) + stack.push(char) while not stack.is_empty(): postfix.append(stack.pop()) return " ".join(postfix) if __name__ == "__main__": + from doctest import testmod + + testmod() expression = "a+b*(c^d-e)^(f+g*h)-i" print("Infix to Postfix Notation demonstration:\n") From 61cb921d8799ce588bd4d03533252edfc6539076 Mon Sep 17 00:00:00 2001 From: Peter Yao Date: Mon, 2 Nov 2020 09:54:20 -0800 Subject: [PATCH 0984/1071] Project Euler 206 Solution (#3829) * Readd Project Euler 206 solution for issue #2695, dupe of pull request #3042 * Add PE 206 to directory * updating DIRECTORY.md Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> --- DIRECTORY.md | 2 + project_euler/problem_206/__init__.py | 0 project_euler/problem_206/sol1.py | 74 +++++++++++++++++++++++++++ 3 files changed, 76 insertions(+) create mode 100644 project_euler/problem_206/__init__.py create mode 100644 project_euler/problem_206/sol1.py diff --git a/DIRECTORY.md b/DIRECTORY.md index 7c695892112a..0cecae28d51d 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -725,6 +725,8 @@ * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_174/sol1.py) * Problem 191 * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_191/sol1.py) + * Problem 206 + * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_206/sol1.py) * Problem 207 * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_207/sol1.py) * Problem 234 diff --git a/project_euler/problem_206/__init__.py b/project_euler/problem_206/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/project_euler/problem_206/sol1.py b/project_euler/problem_206/sol1.py new file mode 100644 index 000000000000..ffac2b32aa77 --- /dev/null +++ b/project_euler/problem_206/sol1.py @@ -0,0 +1,74 @@ +""" +Project Euler Problem 206: https://projecteuler.net/problem=206 + +Find the unique positive integer whose square has the form 1_2_3_4_5_6_7_8_9_0, +where each “_” is a single digit. + +----- + +Instead of computing every single permutation of that number and going +through a 10^9 search space, we can narrow it down considerably. + +If the square ends in a 0, then the square root must also end in a 0. Thus, +the last missing digit must be 0 and the square root is a multiple of 10. +We can narrow the search space down to the first 8 digits and multiply the +result of that by 10 at the end. + +Now the last digit is a 9, which can only happen if the square root ends +in a 3 or 7. From this point, we can try one of two different methods to find +the answer: + +1. Start at the lowest possible base number whose square would be in the +format, and count up. The base we would start at is 101010103, whose square is +the closest number to 10203040506070809. Alternate counting up by 4 and 6 so +the last digit of the base is always a 3 or 7. + +2. Start at the highest possible base number whose square would be in the +format, and count down. That base would be 138902663, whose square is the +closest number to 1929394959697989. Alternate counting down by 6 and 4 so the +last digit of the base is always a 3 or 7. + +The solution does option 2 because the answer happens to be much closer to the +starting point. +""" + + +def is_square_form(num: int) -> bool: + """ + Determines if num is in the form 1_2_3_4_5_6_7_8_9 + + >>> is_square_form(1) + False + >>> is_square_form(112233445566778899) + True + >>> is_square_form(123456789012345678) + False + """ + digit = 9 + + while num > 0: + if num % 10 != digit: + return False + num //= 100 + digit -= 1 + + return True + + +def solution() -> int: + """ + Returns the first integer whose square is of the form 1_2_3_4_5_6_7_8_9_0 + """ + num = 138902663 + + while not is_square_form(num * num): + if num % 10 == 3: + num -= 6 # (3 - 6) % 10 = 7 + else: + num -= 4 # (7 - 4) % 10 = 3 + + return num * 10 + + +if __name__ == "__main__": + print(f"{solution() = }") From eaa7ef45c5cc4abb7756f239da56521babe01a91 Mon Sep 17 00:00:00 2001 From: Cho Yin Yong Date: Mon, 2 Nov 2020 20:31:33 -0500 Subject: [PATCH 0985/1071] kth order statistic divide and conquer algorithm (#3690) * kth order statistics divide and conquer algorithm * add explanation of algorithm. * fix PEP8 line too long error * update order to be compliant to isort * add doctest * make file black compliant --- divide_and_conquer/kth_order_statistic.py | 64 +++++++++++++++++++++++ 1 file changed, 64 insertions(+) create mode 100644 divide_and_conquer/kth_order_statistic.py diff --git a/divide_and_conquer/kth_order_statistic.py b/divide_and_conquer/kth_order_statistic.py new file mode 100644 index 000000000000..f6e81a306bff --- /dev/null +++ b/divide_and_conquer/kth_order_statistic.py @@ -0,0 +1,64 @@ +""" +Find the kth smallest element in linear time using divide and conquer. +Recall we can do this trivially in O(nlogn) time. Sort the list and +access kth element in constant time. + +This is a divide and conquer algorithm that can find a solution in O(n) time. + +For more information of this algorithm: +https://web.stanford.edu/class/archive/cs/cs161/cs161.1138/lectures/08/Small08.pdf +""" +from random import choice +from typing import List + + +def random_pivot(lst): + """ + Choose a random pivot for the list. + We can use a more sophisticated algorithm here, such as the median-of-medians + algorithm. + """ + return choice(lst) + + +def kth_number(lst: List[int], k: int) -> int: + """ + Return the kth smallest number in lst. + >>> kth_number([2, 1, 3, 4, 5], 3) + 3 + >>> kth_number([2, 1, 3, 4, 5], 1) + 1 + >>> kth_number([2, 1, 3, 4, 5], 5) + 5 + >>> kth_number([3, 2, 5, 6, 7, 8], 2) + 3 + >>> kth_number([25, 21, 98, 100, 76, 22, 43, 60, 89, 87], 4) + 43 + """ + # pick a pivot and separate into list based on pivot. + pivot = random_pivot(lst) + + # partition based on pivot + # linear time + small = [e for e in lst if e < pivot] + big = [e for e in lst if e > pivot] + + # if we get lucky, pivot might be the element we want. + # we can easily see this: + # small (elements smaller than k) + # + pivot (kth element) + # + big (elements larger than k) + if len(small) == k - 1: + return pivot + # pivot is in elements bigger than k + elif len(small) < k - 1: + return kth_number(big, k - len(small) - 1) + # pivot is in elements smaller than k + else: + return kth_number(small, k) + + +if __name__ == "__main__": + import doctest + + doctest.testmod() From ff00bfa0abbd0019d9e718c6beec2aa0e5d8b580 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fernando=20Benjam=C3=ADn=20P=C3=89REZ=20MAURERA?= Date: Mon, 2 Nov 2020 23:21:13 -0300 Subject: [PATCH 0986/1071] Added a solution for Project Euler Problem 203 "Squarefree Binomial Coefficients" (#3513) * Added a solution for Project Euler Problem 203 (https://projecteuler.net/problem=203) * Simplified loop that calculates the coefficients of the Pascal's Triangle. Changes based on review suggestion. * Moved get_squared_primes_to_use function outside the get_squarefree function and fixed a failing doctest with the former. --- project_euler/problem_203/__init__.py | 0 project_euler/problem_203/sol1.py | 188 ++++++++++++++++++++++++++ 2 files changed, 188 insertions(+) create mode 100644 project_euler/problem_203/__init__.py create mode 100644 project_euler/problem_203/sol1.py diff --git a/project_euler/problem_203/__init__.py b/project_euler/problem_203/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/project_euler/problem_203/sol1.py b/project_euler/problem_203/sol1.py new file mode 100644 index 000000000000..227b476da131 --- /dev/null +++ b/project_euler/problem_203/sol1.py @@ -0,0 +1,188 @@ +""" +Project Euler Problem 203: https://projecteuler.net/problem=203 + +The binomial coefficients (n k) can be arranged in triangular form, Pascal's +triangle, like this: + 1 + 1 1 + 1 2 1 + 1 3 3 1 + 1 4 6 4 1 + 1 5 10 10 5 1 + 1 6 15 20 15 6 1 +1 7 21 35 35 21 7 1 + ......... + +It can be seen that the first eight rows of Pascal's triangle contain twelve +distinct numbers: 1, 2, 3, 4, 5, 6, 7, 10, 15, 20, 21 and 35. + +A positive integer n is called squarefree if no square of a prime divides n. +Of the twelve distinct numbers in the first eight rows of Pascal's triangle, +all except 4 and 20 are squarefree. The sum of the distinct squarefree numbers +in the first eight rows is 105. + +Find the sum of the distinct squarefree numbers in the first 51 rows of +Pascal's triangle. + +References: +- https://en.wikipedia.org/wiki/Pascal%27s_triangle +""" + +import math +from typing import List, Set + + +def get_pascal_triangle_unique_coefficients(depth: int) -> Set[int]: + """ + Returns the unique coefficients of a Pascal's triangle of depth "depth". + + The coefficients of this triangle are symmetric. A further improvement to this + method could be to calculate the coefficients once per level. Nonetheless, + the current implementation is fast enough for the original problem. + + >>> get_pascal_triangle_unique_coefficients(1) + {1} + >>> get_pascal_triangle_unique_coefficients(2) + {1} + >>> get_pascal_triangle_unique_coefficients(3) + {1, 2} + >>> get_pascal_triangle_unique_coefficients(8) + {1, 2, 3, 4, 5, 6, 7, 35, 10, 15, 20, 21} + """ + coefficients = {1} + previous_coefficients = [1] + for step in range(2, depth + 1): + coefficients_begins_one = previous_coefficients + [0] + coefficients_ends_one = [0] + previous_coefficients + previous_coefficients = [] + for x, y in zip(coefficients_begins_one, coefficients_ends_one): + coefficients.add(x + y) + previous_coefficients.append(x + y) + return coefficients + + +def get_primes_squared(max_number: int) -> List[int]: + """ + Calculates all primes between 2 and round(sqrt(max_number)) and returns + them squared up. + + >>> get_primes_squared(2) + [] + >>> get_primes_squared(4) + [4] + >>> get_primes_squared(10) + [4, 9] + >>> get_primes_squared(100) + [4, 9, 25, 49] + """ + max_prime = round(math.sqrt(max_number)) + non_primes = set() + primes = [] + for num in range(2, max_prime + 1): + if num in non_primes: + continue + + counter = 2 + while num * counter <= max_prime: + non_primes.add(num * counter) + counter += 1 + + primes.append(num ** 2) + return primes + + +def get_squared_primes_to_use( + num_to_look: int, squared_primes: List[int], previous_index: int +) -> int: + """ + Returns an int indicating the last index on which squares of primes + in primes are lower than num_to_look. + + This method supposes that squared_primes is sorted in ascending order and that + each num_to_look is provided in ascending order as well. Under these + assumptions, it needs a previous_index parameter that tells what was + the index returned by the method for the previous num_to_look. + + If all the elements in squared_primes are greater than num_to_look, then the + method returns -1. + + >>> get_squared_primes_to_use(1, [4, 9, 16, 25], 0) + -1 + >>> get_squared_primes_to_use(4, [4, 9, 16, 25], 0) + 1 + >>> get_squared_primes_to_use(16, [4, 9, 16, 25], 1) + 3 + """ + idx = max(previous_index, 0) + + while idx < len(squared_primes) and squared_primes[idx] <= num_to_look: + idx += 1 + + if idx == 0 and squared_primes[idx] > num_to_look: + return -1 + + if idx == len(squared_primes) and squared_primes[-1] > num_to_look: + return -1 + + return idx + + +def get_squarefree( + unique_coefficients: Set[int], squared_primes: List[int] +) -> Set[int]: + """ + Calculates the squarefree numbers inside unique_coefficients given a + list of square of primes. + + Based on the definition of a non-squarefree number, then any non-squarefree + n can be decomposed as n = p*p*r, where p is positive prime number and r + is a positive integer. + + Under the previous formula, any coefficient that is lower than p*p is + squarefree as r cannot be negative. On the contrary, if any r exists such + that n = p*p*r, then the number is non-squarefree. + + >>> get_squarefree({1}, []) + set() + >>> get_squarefree({1, 2}, []) + set() + >>> get_squarefree({1, 2, 3, 4, 5, 6, 7, 35, 10, 15, 20, 21}, [4, 9, 25]) + {1, 2, 3, 5, 6, 7, 35, 10, 15, 21} + """ + + if len(squared_primes) == 0: + return set() + + non_squarefrees = set() + prime_squared_idx = 0 + for num in sorted(unique_coefficients): + prime_squared_idx = get_squared_primes_to_use( + num, squared_primes, prime_squared_idx + ) + if prime_squared_idx == -1: + continue + if any(num % prime == 0 for prime in squared_primes[:prime_squared_idx]): + non_squarefrees.add(num) + + return unique_coefficients.difference(non_squarefrees) + + +def solution(n: int = 51) -> int: + """ + Returns the sum of squarefrees for a given Pascal's Triangle of depth n. + + >>> solution(1) + 0 + >>> solution(8) + 105 + >>> solution(9) + 175 + """ + unique_coefficients = get_pascal_triangle_unique_coefficients(n) + primes = get_primes_squared(max(unique_coefficients)) + squarefrees = get_squarefree(unique_coefficients, primes) + return sum(squarefrees) + + +if __name__ == "__main__": + print(f"{solution() = }") From 29d0fbb0e029f847a03de71f8dd633beea3d4307 Mon Sep 17 00:00:00 2001 From: Shikhar Rai <34543293+kakashi215@users.noreply.github.com> Date: Mon, 2 Nov 2020 22:18:14 -0500 Subject: [PATCH 0987/1071] HACKTOBERFEST - Added solution to Euler 64. (#3706) * Added solution to Euler 64. Added Python solution to Project Euler Problem 64. Added a folder problem_064. Added __init__.py file. Added sol1.py file. * Update sol1.py Made formatting changes as mentioned by pre-commit * Update sol1.py Minor changes to variable naming and function calling as mentioned by @ruppysuppy * Update sol1.py Changes to function call as mentioned by @cclauss --- project_euler/problem_064/__init__.py | 0 project_euler/problem_064/sol1.py | 77 +++++++++++++++++++++++++++ 2 files changed, 77 insertions(+) create mode 100644 project_euler/problem_064/__init__.py create mode 100644 project_euler/problem_064/sol1.py diff --git a/project_euler/problem_064/__init__.py b/project_euler/problem_064/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/project_euler/problem_064/sol1.py b/project_euler/problem_064/sol1.py new file mode 100644 index 000000000000..69e3f6d97580 --- /dev/null +++ b/project_euler/problem_064/sol1.py @@ -0,0 +1,77 @@ +""" +Project Euler Problem 64: https://projecteuler.net/problem=64 + +All square roots are periodic when written as continued fractions. +For example, let us consider sqrt(23). +It can be seen that the sequence is repeating. +For conciseness, we use the notation sqrt(23)=[4;(1,3,1,8)], +to indicate that the block (1,3,1,8) repeats indefinitely. +Exactly four continued fractions, for N<=13, have an odd period. +How many continued fractions for N<=10000 have an odd period? + +References: +- https://en.wikipedia.org/wiki/Continued_fraction +""" + +from math import floor, sqrt + + +def continuous_fraction_period(n: int) -> int: + """ + Returns the continued fraction period of a number n. + + >>> continuous_fraction_period(2) + 1 + >>> continuous_fraction_period(5) + 1 + >>> continuous_fraction_period(7) + 4 + >>> continuous_fraction_period(11) + 2 + >>> continuous_fraction_period(13) + 5 + """ + numerator = 0.0 + denominator = 1.0 + ROOT = int(sqrt(n)) + integer_part = ROOT + period = 0 + while integer_part != 2 * ROOT: + numerator = denominator * integer_part - numerator + denominator = (n - numerator ** 2) / denominator + integer_part = int((ROOT + numerator) / denominator) + period += 1 + return period + + +def solution(n: int = 10000) -> int: + """ + Returns the count of numbers <= 10000 with odd periods. + This function calls continuous_fraction_period for numbers which are + not perfect squares. + This is checked in if sr - floor(sr) != 0 statement. + If an odd period is returned by continuous_fraction_period, + count_odd_periods is increased by 1. + + >>> solution(2) + 1 + >>> solution(5) + 2 + >>> solution(7) + 2 + >>> solution(11) + 3 + >>> solution(13) + 4 + """ + count_odd_periods = 0 + for i in range(2, n + 1): + sr = sqrt(i) + if sr - floor(sr) != 0: + if continuous_fraction_period(i) % 2 == 1: + count_odd_periods += 1 + return count_odd_periods + + +if __name__ == "__main__": + print(f"{solution(int(input().strip()))}") From fdba34f70464a54f3761e426d50a889c70671677 Mon Sep 17 00:00:00 2001 From: Dhruv Manilawala Date: Thu, 5 Nov 2020 16:36:59 +0530 Subject: [PATCH 0988/1071] Cache pre-commit workflow (#3863) --- .github/workflows/pre-commit.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.github/workflows/pre-commit.yml b/.github/workflows/pre-commit.yml index 7002d2d0a21e..96175cfecea5 100644 --- a/.github/workflows/pre-commit.yml +++ b/.github/workflows/pre-commit.yml @@ -7,6 +7,12 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 + - uses: actions/cache@v2 + with: + path: | + ~/.cache/pre-commit + ~/.cache/pip + key: ${{ runner.os }}-pre-commit-${{ hashFiles('.pre-commit-config.yaml') }} - uses: actions/setup-python@v2 - name: Install pre-commit run: | From c8db6a208b0595b6d2162177045d204b0719b57e Mon Sep 17 00:00:00 2001 From: Ravi Kandasamy Sundaram Date: Fri, 6 Nov 2020 17:55:02 +0100 Subject: [PATCH 0989/1071] Add solution for Project Euler problem 123 (#3072) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Name: Prime square remainders Let pn be the nth prime: 2, 3, 5, 7, 11, ..., and let r be the remainder when (pn−1)^n + (pn+1)^n is divided by pn^2. For example, when n = 3, p3 = 5, and 43 + 63 = 280 ≡ 5 mod 25. The least value of n for which the remainder first exceeds 10^9 is 7037. Find the least value of n for which the remainder first exceeds 10^10. Reference: https://projecteuler.net/problem=123 reference: #2695 Co-authored-by: Ravi Kandasamy Sundaram --- project_euler/problem_123/__init__.py | 0 project_euler/problem_123/sol1.py | 99 +++++++++++++++++++++++++++ 2 files changed, 99 insertions(+) create mode 100644 project_euler/problem_123/__init__.py create mode 100644 project_euler/problem_123/sol1.py diff --git a/project_euler/problem_123/__init__.py b/project_euler/problem_123/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/project_euler/problem_123/sol1.py b/project_euler/problem_123/sol1.py new file mode 100644 index 000000000000..85350c8bae49 --- /dev/null +++ b/project_euler/problem_123/sol1.py @@ -0,0 +1,99 @@ +""" +Problem 123: https://projecteuler.net/problem=123 + +Name: Prime square remainders + +Let pn be the nth prime: 2, 3, 5, 7, 11, ..., and +let r be the remainder when (pn−1)^n + (pn+1)^n is divided by pn^2. + +For example, when n = 3, p3 = 5, and 43 + 63 = 280 ≡ 5 mod 25. +The least value of n for which the remainder first exceeds 10^9 is 7037. + +Find the least value of n for which the remainder first exceeds 10^10. + + +Solution: + +n=1: (p-1) + (p+1) = 2p +n=2: (p-1)^2 + (p+1)^2 + = p^2 + 1 - 2p + p^2 + 1 + 2p (Using (p+b)^2 = (p^2 + b^2 + 2pb), + (p-b)^2 = (p^2 + b^2 - 2pb) and b = 1) + = 2p^2 + 2 +n=3: (p-1)^3 + (p+1)^3 (Similarly using (p+b)^3 & (p-b)^3 formula and so on) + = 2p^3 + 6p +n=4: 2p^4 + 12p^2 + 2 +n=5: 2p^5 + 20p^3 + 10p + +As you could see, when the expression is divided by p^2. +Except for the last term, the rest will result in the remainder 0. + +n=1: 2p +n=2: 2 +n=3: 6p +n=4: 2 +n=5: 10p + +So it could be simplified as, + r = 2pn when n is odd + r = 2 when n is even. +""" + +from typing import Dict, Generator + + +def sieve() -> Generator[int, None, None]: + """ + Returns a prime number generator using sieve method. + >>> type(sieve()) + + >>> primes = sieve() + >>> next(primes) + 2 + >>> next(primes) + 3 + >>> next(primes) + 5 + >>> next(primes) + 7 + >>> next(primes) + 11 + >>> next(primes) + 13 + """ + factor_map: Dict[int, int] = {} + prime = 2 + while True: + factor = factor_map.pop(prime, None) + if factor: + x = factor + prime + while x in factor_map: + x += factor + factor_map[x] = factor + else: + factor_map[prime * prime] = prime + yield prime + prime += 1 + + +def solution(limit: float = 1e10) -> int: + """ + Returns the least value of n for which the remainder first exceeds 10^10. + >>> solution(1e8) + 2371 + >>> solution(1e9) + 7037 + """ + primes = sieve() + + n = 1 + while True: + prime = next(primes) + if (2 * prime * n) > limit: + return n + # Ignore the next prime as the reminder will be 2. + next(primes) + n += 2 + + +if __name__ == "__main__": + print(solution()) From c0d88d7f71b0234740696cbe48d7ed88900261c1 Mon Sep 17 00:00:00 2001 From: Frank Schmitt Date: Fri, 6 Nov 2020 18:09:12 +0100 Subject: [PATCH 0990/1071] Fix handling of non ascii characters in swap case (fixes: #3847) (#3848) * #3847 fix handling of non-ASCII characters in swap_case * #3847 remove unused regex * Fix formatting (with black) Fixes: #3847 * Add type hints for `swap_case` function Co-authored-by: Frank Schmitt Co-authored-by: Dhruv Manilawala --- strings/swap_case.py | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/strings/swap_case.py b/strings/swap_case.py index 71e8aeb3a205..107fda4b52ec 100644 --- a/strings/swap_case.py +++ b/strings/swap_case.py @@ -11,14 +11,9 @@ GITHUB.COM/MAYUR200 """ -import re -# This re.compile() function saves the pattern from 'a' to 'z' and 'A' to 'Z' -# into 'regexp' variable -regexp = re.compile("[^a-zA-Z]+") - -def swap_case(sentence): +def swap_case(sentence: str) -> str: """ This function will convert all lowercase letters to uppercase letters and vice versa. @@ -30,13 +25,13 @@ def swap_case(sentence): for char in sentence: if char.isupper(): new_string += char.lower() - if char.islower(): + elif char.islower(): new_string += char.upper() - if regexp.search(char): + else: new_string += char return new_string if __name__ == "__main__": - print(swap_case(input("Please input sentence:"))) + print(swap_case(input("Please input sentence: "))) From 686d837d3e09f6808bd958a5d651a9c281ce526a Mon Sep 17 00:00:00 2001 From: Simon Landry Date: Sat, 7 Nov 2020 21:56:10 -0500 Subject: [PATCH 0991/1071] Add project euler problem 50 (#3016) * Add project euler problem 50 * Apply format changes * Descriptive function/parameter name and type hints Co-authored-by: Dhruv Manilawala --- project_euler/problem_050/__init__.py | 0 project_euler/problem_050/sol1.py | 85 +++++++++++++++++++++++++++ 2 files changed, 85 insertions(+) create mode 100644 project_euler/problem_050/__init__.py create mode 100644 project_euler/problem_050/sol1.py diff --git a/project_euler/problem_050/__init__.py b/project_euler/problem_050/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/project_euler/problem_050/sol1.py b/project_euler/problem_050/sol1.py new file mode 100644 index 000000000000..7d142e5ffc91 --- /dev/null +++ b/project_euler/problem_050/sol1.py @@ -0,0 +1,85 @@ +""" +Project Euler Problem 50: https://projecteuler.net/problem=50 + +Consecutive prime sum + +The prime 41, can be written as the sum of six consecutive primes: +41 = 2 + 3 + 5 + 7 + 11 + 13 + +This is the longest sum of consecutive primes that adds to a prime below +one-hundred. + +The longest sum of consecutive primes below one-thousand that adds to a prime, +contains 21 terms, and is equal to 953. + +Which prime, below one-million, can be written as the sum of the most +consecutive primes? +""" +from typing import List + + +def prime_sieve(limit: int) -> List[int]: + """ + Sieve of Erotosthenes + Function to return all the prime numbers up to a number 'limit' + https://en.wikipedia.org/wiki/Sieve_of_Eratosthenes + + >>> prime_sieve(3) + [2] + + >>> prime_sieve(50) + [2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47] + """ + is_prime = [True] * limit + is_prime[0] = False + is_prime[1] = False + is_prime[2] = True + + for i in range(3, int(limit ** 0.5 + 1), 2): + index = i * 2 + while index < limit: + is_prime[index] = False + index = index + i + + primes = [2] + + for i in range(3, limit, 2): + if is_prime[i]: + primes.append(i) + + return primes + + +def solution(ceiling: int = 1_000_000) -> int: + """ + Returns the biggest prime, below the celing, that can be written as the sum + of consecutive the most consecutive primes. + + >>> solution(500) + 499 + + >>> solution(1_000) + 953 + + >>> solution(10_000) + 9521 + """ + primes = prime_sieve(ceiling) + length = 0 + largest = 0 + + for i in range(len(primes)): + for j in range(i + length, len(primes)): + sol = sum(primes[i:j]) + if sol >= ceiling: + break + + if sol in primes: + length = j - i + largest = sol + + return largest + + +if __name__ == "__main__": + print(f"{solution() = }") From 83a24cb06ddc169c62128754474d306ec40b71f6 Mon Sep 17 00:00:00 2001 From: Lewis Tian Date: Sun, 8 Nov 2020 23:04:01 +0800 Subject: [PATCH 0992/1071] Update graphs/graph_list.py (#3813) - update naming style to snake_case - add type hints --- graphs/graph_list.py | 40 ++++++++++++++++++++-------------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/graphs/graph_list.py b/graphs/graph_list.py index a20940ab1598..a812fecd961e 100644 --- a/graphs/graph_list.py +++ b/graphs/graph_list.py @@ -7,34 +7,34 @@ class AdjacencyList: def __init__(self): - self.List = {} + self.adj_list = {} - def addEdge(self, fromVertex, toVertex): + def add_edge(self, from_vertex: int, to_vertex: int) -> None: # check if vertex is already present - if fromVertex in self.List.keys(): - self.List[fromVertex].append(toVertex) + if from_vertex in self.adj_list: + self.adj_list[from_vertex].append(to_vertex) else: - self.List[fromVertex] = [toVertex] + self.adj_list[from_vertex] = [to_vertex] - def printList(self): - for i in self.List: - print((i, "->", " -> ".join([str(j) for j in self.List[i]]))) + def print_list(self) -> None: + for i in self.adj_list: + print((i, "->", " -> ".join([str(j) for j in self.adj_list[i]]))) if __name__ == "__main__": al = AdjacencyList() - al.addEdge(0, 1) - al.addEdge(0, 4) - al.addEdge(4, 1) - al.addEdge(4, 3) - al.addEdge(1, 0) - al.addEdge(1, 4) - al.addEdge(1, 3) - al.addEdge(1, 2) - al.addEdge(2, 3) - al.addEdge(3, 4) - - al.printList() + al.add_edge(0, 1) + al.add_edge(0, 4) + al.add_edge(4, 1) + al.add_edge(4, 3) + al.add_edge(1, 0) + al.add_edge(1, 4) + al.add_edge(1, 3) + al.add_edge(1, 2) + al.add_edge(2, 3) + al.add_edge(3, 4) + + al.print_list() # OUTPUT: # 0 -> 1 -> 4 From 3d7704f0bfd7a009eab1b1b604b5da59e6ed0bf7 Mon Sep 17 00:00:00 2001 From: Dhruv Manilawala Date: Sun, 8 Nov 2020 21:31:14 +0530 Subject: [PATCH 0993/1071] Add config details for the stale action (#3870) --- .github/workflows/stale.yml | 42 ++++++++++++++++++++++--------------- 1 file changed, 25 insertions(+), 17 deletions(-) diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml index 341153dbf455..41ded1159891 100644 --- a/.github/workflows/stale.yml +++ b/.github/workflows/stale.yml @@ -1,23 +1,31 @@ -name: Mark stale issues and pull requests +name: Mark/Close stale issues and pull requests on: schedule: - - cron: "0 0 * * *" + - cron: "0 * * * *" # Run every hour jobs: stale: runs-on: ubuntu-latest steps: - - uses: actions/stale@v3 - with: - repo-token: ${{ secrets.GITHUB_TOKEN }} - stale-issue-message: > - Please reopen this issue once you add more information and updates here. - If this is not the case and you need some help, feel free to seek help - from our [Gitter](https://gitter.im/TheAlgorithms) or ping one of the - reviewers. Thank you for your contributions! - stale-pr-message: > - Please reopen this pull request once you commit the changes requested - or make improvements on the code. If this is not the case and you need - some help, feel free to seek help from our [Gitter](https://gitter.im/TheAlgorithms) - or ping one of the reviewers. Thank you for your contributions! - stale-issue-label: 'no-issue-activity' - stale-pr-label: 'no-pr-activity' + - uses: actions/stale@v3 + with: + repo-token: ${{ secrets.GITHUB_TOKEN }} + days-before-stale: 30 + days-before-close: 7 + stale-issue-message: > + This issue has been automatically marked as stale because it has not had + recent activity. It will be closed if no further activity occurs. Thank you + for your contributions. + close-issue-message: > + Please reopen this issue once you add more information and updates here. + If this is not the case and you need some help, feel free to seek help + from our [Gitter](https://gitter.im/TheAlgorithms) or ping one of the + reviewers. Thank you for your contributions! + stale-pr-message: > + This pull request has been automatically marked as stale because it has not had + recent activity. It will be closed if no further activity occurs. Thank you + for your contributions. + close-pr-message: > + Please reopen this pull request once you commit the changes requested + or make improvements on the code. If this is not the case and you need + some help, feel free to seek help from our [Gitter](https://gitter.im/TheAlgorithms) + or ping one of the reviewers. Thank you for your contributions! From eab6b70e0a1d5fd942e63c96e13be1134b3c5df3 Mon Sep 17 00:00:00 2001 From: Shivanirudh Date: Sun, 8 Nov 2020 22:56:22 +0530 Subject: [PATCH 0994/1071] DPLL algorithm (#3866) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * DPLL algorithm * Corrections complete * Formatting * Codespell hook * Corrections part 2 * Corrections v2 * Corrections v3 * Update and rename dpll.py to davis–putnam–logemann–loveland.py Co-authored-by: Christian Clauss --- ...42\200\223logemann\342\200\223loveland.py" | 357 ++++++++++++++++++ 1 file changed, 357 insertions(+) create mode 100644 "other/davis\342\200\223putnam\342\200\223logemann\342\200\223loveland.py" diff --git "a/other/davis\342\200\223putnam\342\200\223logemann\342\200\223loveland.py" "b/other/davis\342\200\223putnam\342\200\223logemann\342\200\223loveland.py" new file mode 100644 index 000000000000..d16de6dd988b --- /dev/null +++ "b/other/davis\342\200\223putnam\342\200\223logemann\342\200\223loveland.py" @@ -0,0 +1,357 @@ +#!/usr/bin/env python3 + +""" +Davis–Putnam–Logemann–Loveland (DPLL) algorithm is a complete, backtracking-based +search algorithm for deciding the satisfiability of propositional logic formulae in +conjunctive normal form, i.e, for solving the Conjunctive Normal Form SATisfiability +(CNF-SAT) problem. + +For more information about the algorithm: https://en.wikipedia.org/wiki/DPLL_algorithm +""" + +import random +from typing import Dict, List + + +class Clause: + """ + A clause represented in Conjunctive Normal Form. + A clause is a set of literals, either complemented or otherwise. + For example: + {A1, A2, A3'} is the clause (A1 v A2 v A3') + {A5', A2', A1} is the clause (A5' v A2' v A1) + + Create model + >>> clause = Clause(["A1", "A2'", "A3"]) + >>> clause.evaluate({"A1": True}) + True + """ + + def __init__(self, literals: List[int]) -> None: + """ + Represent the literals and an assignment in a clause." + """ + # Assign all literals to None initially + self.literals = {literal: None for literal in literals} + + def __str__(self) -> str: + """ + To print a clause as in Conjunctive Normal Form. + >>> str(Clause(["A1", "A2'", "A3"])) + "{A1 , A2' , A3}" + """ + return "{" + " , ".join(self.literals) + "}" + + def __len__(self) -> int: + """ + To print a clause as in Conjunctive Normal Form. + >>> len(Clause([])) + 0 + >>> len(Clause(["A1", "A2'", "A3"])) + 3 + """ + return len(self.literals) + + def assign(self, model: Dict[str, bool]) -> None: + """ + Assign values to literals of the clause as given by model. + """ + for literal in self.literals: + symbol = literal[:2] + if symbol in model: + value = model[symbol] + else: + continue + if value is not None: + # Complement assignment if literal is in complemented form + if literal.endswith("'"): + value = not value + self.literals[literal] = value + + def evaluate(self, model: Dict[str, bool]) -> bool: + """ + Evaluates the clause with the assignments in model. + This has the following steps: + 1. Return True if both a literal and its complement exist in the clause. + 2. Return True if a single literal has the assignment True. + 3. Return None(unable to complete evaluation) if a literal has no assignment. + 4. Compute disjunction of all values assigned in clause. + """ + for literal in self.literals: + symbol = literal.rstrip("'") if literal.endswith("'") else literal + "'" + if symbol in self.literals: + return True + + self.assign(model) + for value in self.literals.values(): + if value in (True, None): + return value + return any(self.literals.values()) + + +class Formula: + """ + A formula represented in Conjunctive Normal Form. + A formula is a set of clauses. + For example, + {{A1, A2, A3'}, {A5', A2', A1}} is ((A1 v A2 v A3') and (A5' v A2' v A1)) + """ + + def __init__(self, clauses: List[Clause]) -> None: + """ + Represent the number of clauses and the clauses themselves. + """ + self.clauses = list(clauses) + + def __str__(self) -> str: + """ + To print a formula as in Conjunctive Normal Form. + str(Formula([Clause(["A1", "A2'", "A3"]), Clause(["A5'", "A2'", "A1"])])) + "{{A1 , A2' , A3} , {A5' , A2' , A1}}" + """ + return "{" + " , ".join(str(clause) for clause in self.clauses) + "}" + + +def generate_clause() -> Clause: + """ + Randomly generate a clause. + All literals have the name Ax, where x is an integer from 1 to 5. + """ + literals = [] + no_of_literals = random.randint(1, 5) + base_var = "A" + i = 0 + while i < no_of_literals: + var_no = random.randint(1, 5) + var_name = base_var + str(var_no) + var_complement = random.randint(0, 1) + if var_complement == 1: + var_name += "'" + if var_name in literals: + i -= 1 + else: + literals.append(var_name) + i += 1 + return Clause(literals) + + +def generate_formula() -> Formula: + """ + Randomly generate a formula. + """ + clauses = set() + no_of_clauses = random.randint(1, 10) + while len(clauses) < no_of_clauses: + clauses.add(generate_clause()) + return Formula(set(clauses)) + + +def generate_parameters(formula: Formula) -> (List[Clause], List[str]): + """ + Return the clauses and symbols from a formula. + A symbol is the uncomplemented form of a literal. + For example, + Symbol of A3 is A3. + Symbol of A5' is A5. + + >>> formula = Formula([Clause(["A1", "A2'", "A3"]), Clause(["A5'", "A2'", "A1"])]) + >>> clauses, symbols = generate_parameters(formula) + >>> clauses_list = [str(i) for i in clauses] + >>> clauses_list + ["{A1 , A2' , A3}", "{A5' , A2' , A1}"] + >>> symbols + ['A1', 'A2', 'A3', 'A5'] + """ + clauses = formula.clauses + symbols_set = [] + for clause in formula.clauses: + for literal in clause.literals: + symbol = literal[:2] + if symbol not in symbols_set: + symbols_set.append(symbol) + return clauses, symbols_set + + +def find_pure_symbols( + clauses: List[Clause], symbols: List[str], model: Dict[str, bool] +) -> (List[str], Dict[str, bool]): + """ + Return pure symbols and their values to satisfy clause. + Pure symbols are symbols in a formula that exist only + in one form, either complemented or otherwise. + For example, + { { A4 , A3 , A5' , A1 , A3' } , { A4 } , { A3 } } has + pure symbols A4, A5' and A1. + This has the following steps: + 1. Ignore clauses that have already evaluated to be True. + 2. Find symbols that occur only in one form in the rest of the clauses. + 3. Assign value True or False depending on whether the symbols occurs + in normal or complemented form respectively. + + >>> formula = Formula([Clause(["A1", "A2'", "A3"]), Clause(["A5'", "A2'", "A1"])]) + >>> clauses, symbols = generate_parameters(formula) + + >>> pure_symbols, values = find_pure_symbols(clauses, symbols, {}) + >>> pure_symbols + ['A1', 'A2', 'A3', 'A5'] + >>> values + {'A1': True, 'A2': False, 'A3': True, 'A5': False} + """ + pure_symbols = [] + assignment = dict() + literals = [] + + for clause in clauses: + if clause.evaluate(model) is True: + continue + for literal in clause.literals: + literals.append(literal) + + for s in symbols: + sym = s + "'" + if (s in literals and sym not in literals) or ( + s not in literals and sym in literals + ): + pure_symbols.append(s) + for p in pure_symbols: + assignment[p] = None + for s in pure_symbols: + sym = s + "'" + if s in literals: + assignment[s] = True + elif sym in literals: + assignment[s] = False + return pure_symbols, assignment + + +def find_unit_clauses( + clauses: List[Clause], model: Dict[str, bool] +) -> (List[str], Dict[str, bool]): + """ + Returns the unit symbols and their values to satisfy clause. + Unit symbols are symbols in a formula that are: + - Either the only symbol in a clause + - Or all other literals in that clause have been assigned False + This has the following steps: + 1. Find symbols that are the only occurrences in a clause. + 2. Find symbols in a clause where all other literals are assigned False. + 3. Assign True or False depending on whether the symbols occurs in + normal or complemented form respectively. + + >>> clause1 = Clause(["A4", "A3", "A5'", "A1", "A3'"]) + >>> clause2 = Clause(["A4"]) + >>> clause3 = Clause(["A3"]) + >>> clauses, symbols = generate_parameters(Formula([clause1, clause2, clause3])) + + >>> unit_clauses, values = find_unit_clauses(clauses, {}) + >>> unit_clauses + ['A4', 'A3'] + >>> values + {'A4': True, 'A3': True} + """ + unit_symbols = [] + for clause in clauses: + if len(clause) == 1: + unit_symbols.append(list(clause.literals.keys())[0]) + else: + Fcount, Ncount = 0, 0 + for literal, value in clause.literals.items(): + if value is False: + Fcount += 1 + elif value is None: + sym = literal + Ncount += 1 + if Fcount == len(clause) - 1 and Ncount == 1: + unit_symbols.append(sym) + assignment = dict() + for i in unit_symbols: + symbol = i[:2] + assignment[symbol] = len(i) == 2 + unit_symbols = [i[:2] for i in unit_symbols] + + return unit_symbols, assignment + + +def dpll_algorithm( + clauses: List[Clause], symbols: List[str], model: Dict[str, bool] +) -> (bool, Dict[str, bool]): + """ + Returns the model if the formula is satisfiable, else None + This has the following steps: + 1. If every clause in clauses is True, return True. + 2. If some clause in clauses is False, return False. + 3. Find pure symbols. + 4. Find unit symbols. + + >>> formula = Formula([Clause(["A4", "A3", "A5'", "A1", "A3'"]), Clause(["A4"])]) + >>> clauses, symbols = generate_parameters(formula) + + >>> soln, model = dpll_algorithm(clauses, symbols, {}) + >>> soln + True + >>> model + {'A4': True} + """ + check_clause_all_true = True + for clause in clauses: + clause_check = clause.evaluate(model) + if clause_check is False: + return False, None + elif clause_check is None: + check_clause_all_true = False + continue + + if check_clause_all_true: + return True, model + + try: + pure_symbols, assignment = find_pure_symbols(clauses, symbols, model) + except RecursionError: + print("raises a RecursionError and is") + return None, {} + P = None + if len(pure_symbols) > 0: + P, value = pure_symbols[0], assignment[pure_symbols[0]] + + if P: + tmp_model = model + tmp_model[P] = value + tmp_symbols = [i for i in symbols] + if P in tmp_symbols: + tmp_symbols.remove(P) + return dpll_algorithm(clauses, tmp_symbols, tmp_model) + + unit_symbols, assignment = find_unit_clauses(clauses, model) + P = None + if len(unit_symbols) > 0: + P, value = unit_symbols[0], assignment[unit_symbols[0]] + if P: + tmp_model = model + tmp_model[P] = value + tmp_symbols = [i for i in symbols] + if P in tmp_symbols: + tmp_symbols.remove(P) + return dpll_algorithm(clauses, tmp_symbols, tmp_model) + P = symbols[0] + rest = symbols[1:] + tmp1, tmp2 = model, model + tmp1[P], tmp2[P] = True, False + + return dpll_algorithm(clauses, rest, tmp1) or dpll_algorithm(clauses, rest, tmp2) + + +if __name__ == "__main__": + import doctest + + doctest.testmod() + + formula = generate_formula() + print(f"The formula {formula} is", end=" ") + + clauses, symbols = generate_parameters(formula) + solution, model = dpll_algorithm(clauses, symbols, {}) + + if solution: + print(f"satisfiable with the assignment {model}.") + else: + print("not satisfiable.") From a6ad25c3518f2d84760aad06732e6e63cfb2b8da Mon Sep 17 00:00:00 2001 From: Prakhar Gurunani Date: Tue, 10 Nov 2020 12:50:27 +0530 Subject: [PATCH 0995/1071] Add molecular_chemistry.py (#2944) * Create molecular_chemistry.py * round up outputs * Remove floating point * Add Wikipedia references * fixup! Format Python code with psf/black push * Add Conversions/Molecular Chemistry * updating DIRECTORY.md * Update molecular_chemistry.py Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> Co-authored-by: Christian Clauss --- DIRECTORY.md | 1 + conversions/molecular_chemistry.py | 92 ++++++++++++++++++++++++++++++ 2 files changed, 93 insertions(+) create mode 100644 conversions/molecular_chemistry.py diff --git a/DIRECTORY.md b/DIRECTORY.md index 0cecae28d51d..fd45eacaad9b 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -100,6 +100,7 @@ * [Decimal To Hexadecimal](https://github.com/TheAlgorithms/Python/blob/master/conversions/decimal_to_hexadecimal.py) * [Decimal To Octal](https://github.com/TheAlgorithms/Python/blob/master/conversions/decimal_to_octal.py) * [Hexadecimal To Decimal](https://github.com/TheAlgorithms/Python/blob/master/conversions/hexadecimal_to_decimal.py) + * [Molecular Chemistry](https://github.com/TheAlgorithms/Python/blob/master/conversions/molecular_chemistry.py) * [Prefix Conversions](https://github.com/TheAlgorithms/Python/blob/master/conversions/prefix_conversions.py) * [Roman To Integer](https://github.com/TheAlgorithms/Python/blob/master/conversions/roman_to_integer.py) * [Temperature Conversions](https://github.com/TheAlgorithms/Python/blob/master/conversions/temperature_conversions.py) diff --git a/conversions/molecular_chemistry.py b/conversions/molecular_chemistry.py new file mode 100644 index 000000000000..8c68459965b0 --- /dev/null +++ b/conversions/molecular_chemistry.py @@ -0,0 +1,92 @@ +""" +Functions useful for doing molecular chemistry: +* molarity_to_normality +* moles_to_pressure +* moles_to_volume +* pressure_and_volume_to_temperature +""" + + +def molarity_to_normality(nfactor: int, moles: float, volume: float) -> float: + """ + Convert molarity to normality. + Volume is taken in litres. + + Wikipedia reference: https://en.wikipedia.org/wiki/Equivalent_concentration + Wikipedia reference: https://en.wikipedia.org/wiki/Molar_concentration + + >>> molarity_to_normality(2, 3.1, 0.31) + 20 + >>> molarity_to_normality(4, 11.4, 5.7) + 8 + """ + return round((float(moles / volume) * nfactor)) + + +def moles_to_pressure(volume: float, moles: float, temperature: float) -> float: + """ + Convert moles to pressure. + Ideal gas laws are used. + Temperature is taken in kelvin. + Volume is taken in litres. + Pressure has atm as SI unit. + + Wikipedia reference: https://en.wikipedia.org/wiki/Gas_laws + Wikipedia reference: https://en.wikipedia.org/wiki/Pressure + Wikipedia reference: https://en.wikipedia.org/wiki/Temperature + + >>> moles_to_pressure(0.82, 3, 300) + 90 + >>> moles_to_pressure(8.2, 5, 200) + 10 + """ + return round(float((moles * 0.0821 * temperature) / (volume))) + + +def moles_to_volume(pressure: float, moles: float, temperature: float) -> float: + """ + Convert moles to volume. + Ideal gas laws are used. + Temperature is taken in kelvin. + Volume is taken in litres. + Pressure has atm as SI unit. + + Wikipedia reference: https://en.wikipedia.org/wiki/Gas_laws + Wikipedia reference: https://en.wikipedia.org/wiki/Pressure + Wikipedia reference: https://en.wikipedia.org/wiki/Temperature + + >>> moles_to_volume(0.82, 3, 300) + 90 + >>> moles_to_volume(8.2, 5, 200) + 10 + """ + return round(float((moles * 0.0821 * temperature) / (pressure))) + + +def pressure_and_volume_to_temperature( + pressure: float, moles: float, volume: float +) -> float: + """ + Convert pressure and volume to temperature. + Ideal gas laws are used. + Temperature is taken in kelvin. + Volume is taken in litres. + Pressure has atm as SI unit. + + Wikipedia reference: https://en.wikipedia.org/wiki/Gas_laws + Wikipedia reference: https://en.wikipedia.org/wiki/Pressure + Wikipedia reference: https://en.wikipedia.org/wiki/Temperature + + >>> pressure_and_volume_to_temperature(0.82, 1, 2) + 20 + >>> pressure_and_volume_to_temperature(8.2, 5, 3) + 60 + """ + return round(float((pressure * volume) / (0.0821 * moles))) + + +if __name__ == "__main__": + + import doctest + + doctest.testmod() From 4851942ec02b18bbe4c8b58c46efed9cc2115e32 Mon Sep 17 00:00:00 2001 From: poloso Date: Tue, 10 Nov 2020 21:35:11 -0500 Subject: [PATCH 0996/1071] Reduce complexity linear_discriminant_analysis. (#2452) * Reduce complexity linear_discriminant_analysis. * Fix whitespace * Update machine_learning/linear_discriminant_analysis.py Co-authored-by: Dhruv Manilawala * fixup! Format Python code with psf/black push * Fix format to surpass pre-commit tests * updating DIRECTORY.md * Update machine_learning/linear_discriminant_analysis.py Co-authored-by: Dhruv Manilawala * fixup! Format Python code with psf/black push Co-authored-by: Dhruv Manilawala Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> --- .../linear_discriminant_analysis.py | 130 +++++++++--------- 1 file changed, 65 insertions(+), 65 deletions(-) diff --git a/machine_learning/linear_discriminant_analysis.py b/machine_learning/linear_discriminant_analysis.py index 22ee63a5a62b..0d19e970e973 100644 --- a/machine_learning/linear_discriminant_analysis.py +++ b/machine_learning/linear_discriminant_analysis.py @@ -2,6 +2,7 @@ Linear Discriminant Analysis + Assumptions About Data : 1. The input variables has a gaussian distribution. 2. The variance calculated for each input variables by class grouping is the @@ -44,6 +45,7 @@ from math import log from os import name, system from random import gauss, seed +from typing import Callable, TypeVar # Make a training dataset drawn from a gaussian distribution @@ -245,6 +247,40 @@ def accuracy(actual_y: list, predicted_y: list) -> float: return (correct / len(actual_y)) * 100 +num = TypeVar("num") + + +def valid_input( + input_type: Callable[[object], num], # Usually float or int + input_msg: str, + err_msg: str, + condition: Callable[[num], bool] = lambda x: True, + default: str = None, +) -> num: + """ + Ask for user value and validate that it fulfill a condition. + + :input_type: user input expected type of value + :input_msg: message to show user in the screen + :err_msg: message to show in the screen in case of error + :condition: function that represents the condition that user input is valid. + :default: Default value in case the user does not type anything + :return: user's input + """ + while True: + try: + user_input = input_type(input(input_msg).strip() or default) + if condition(user_input): + return user_input + else: + print(f"{user_input}: {err_msg}") + continue + except ValueError: + print( + f"{user_input}: Incorrect input type, expected {input_type.__name__!r}" + ) + + # Main Function def main(): """ This function starts execution phase """ @@ -254,48 +290,26 @@ def main(): print("First of all we should specify the number of classes that") print("we want to generate as training dataset") # Trying to get number of classes - n_classes = 0 - while True: - try: - user_input = int( - input("Enter the number of classes (Data Groupings): ").strip() - ) - if user_input > 0: - n_classes = user_input - break - else: - print( - f"Your entered value is {user_input} , Number of classes " - f"should be positive!" - ) - continue - except ValueError: - print("Your entered value is not numerical!") + n_classes = valid_input( + input_type=int, + condition=lambda x: x > 0, + input_msg="Enter the number of classes (Data Groupings): ", + err_msg="Number of classes should be positive!", + ) print("-" * 100) - std_dev = 1.0 # Default value for standard deviation of dataset # Trying to get the value of standard deviation - while True: - try: - user_sd = float( - input( - "Enter the value of standard deviation" - "(Default value is 1.0 for all classes): " - ).strip() - or "1.0" - ) - if user_sd >= 0.0: - std_dev = user_sd - break - else: - print( - f"Your entered value is {user_sd}, Standard deviation should " - f"not be negative!" - ) - continue - except ValueError: - print("Your entered value is not numerical!") + std_dev = valid_input( + input_type=float, + condition=lambda x: x >= 0, + input_msg=( + "Enter the value of standard deviation" + "(Default value is 1.0 for all classes): " + ), + err_msg="Standard deviation should not be negative!", + default="1.0", + ) print("-" * 100) @@ -303,38 +317,24 @@ def main(): # dataset counts = [] # An empty list to store instance counts of classes in dataset for i in range(n_classes): - while True: - try: - user_count = int( - input(f"Enter The number of instances for class_{i+1}: ") - ) - if user_count > 0: - counts.append(user_count) - break - else: - print( - f"Your entered value is {user_count}, Number of " - "instances should be positive!" - ) - continue - except ValueError: - print("Your entered value is not numerical!") + user_count = valid_input( + input_type=int, + condition=lambda x: x > 0, + input_msg=(f"Enter The number of instances for class_{i+1}: "), + err_msg="Number of instances should be positive!", + ) + counts.append(user_count) print("-" * 100) # An empty list to store values of user-entered means of classes user_means = [] for a in range(n_classes): - while True: - try: - user_mean = float( - input(f"Enter the value of mean for class_{a+1}: ") - ) - if isinstance(user_mean, float): - user_means.append(user_mean) - break - print(f"You entered an invalid value: {user_mean}") - except ValueError: - print("Your entered value is not numerical!") + user_mean = valid_input( + input_type=float, + input_msg=(f"Enter the value of mean for class_{a+1}: "), + err_msg="This is an invalid value.", + ) + user_means.append(user_mean) print("-" * 100) print("Standard deviation: ", std_dev) From eb5a9516032d06cbb6686e1053019fdad38c6d78 Mon Sep 17 00:00:00 2001 From: Cho Yin Yong Date: Wed, 11 Nov 2020 06:17:54 -0500 Subject: [PATCH 0997/1071] Peak of unimodal list DNC algorithm (#3691) * Peak of unimodal list DNC algorithm * fix black formatting issues * add doctest testing * make file black compliant --- divide_and_conquer/peak.py | 53 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 53 insertions(+) create mode 100644 divide_and_conquer/peak.py diff --git a/divide_and_conquer/peak.py b/divide_and_conquer/peak.py new file mode 100644 index 000000000000..f94f83ed3fcb --- /dev/null +++ b/divide_and_conquer/peak.py @@ -0,0 +1,53 @@ +""" +Finding the peak of a unimodal list using divide and conquer. +A unimodal array is defined as follows: array is increasing up to index p, +then decreasing afterwards. (for p >= 1) +An obvious solution can be performed in O(n), +to find the maximum of the array. +(From Kleinberg and Tardos. Algorithm Design. +Addison Wesley 2006: Chapter 5 Solved Exercise 1) +""" +from typing import List + + +def peak(lst: List[int]) -> int: + """ + Return the peak value of `lst`. + >>> peak([1, 2, 3, 4, 5, 4, 3, 2, 1]) + 5 + >>> peak([1, 10, 9, 8, 7, 6, 5, 4]) + 10 + >>> peak([1, 9, 8, 7]) + 9 + >>> peak([1, 2, 3, 4, 5, 6, 7, 0]) + 7 + >>> peak([1, 2, 3, 4, 3, 2, 1, 0, -1, -2]) + 4 + """ + # middle index + m = len(lst) // 2 + + # choose the middle 3 elements + three = lst[m - 1 : m + 2] + + # if middle element is peak + if three[1] > three[0] and three[1] > three[2]: + return three[1] + + # if increasing, recurse on right + elif three[0] < three[2]: + if len(lst[:m]) == 2: + m -= 1 + return peak(lst[m:]) + + # decreasing + else: + if len(lst[:m]) == 2: + m += 1 + return peak(lst[:m]) + + +if __name__ == "__main__": + import doctest + + doctest.testmod() From 9f6188cc407af77ee987ad1d4cb480cc7db6ad39 Mon Sep 17 00:00:00 2001 From: Rupansh Date: Wed, 11 Nov 2020 16:54:31 +0530 Subject: [PATCH 0998/1071] Add Quantum Full Adder circuit for classical integers (#2954) * requirements: add qiskit major library required for quantum computing * quantum: add quantum ripple adder implementation right now we are essentially performing the same task as a classic ripple adder Signed-off-by: rupansh-arch --- quantum/ripple_adder_classic.py | 108 ++++++++++++++++++++++++++++++++ 1 file changed, 108 insertions(+) create mode 100644 quantum/ripple_adder_classic.py diff --git a/quantum/ripple_adder_classic.py b/quantum/ripple_adder_classic.py new file mode 100644 index 000000000000..f5b0a980c8e2 --- /dev/null +++ b/quantum/ripple_adder_classic.py @@ -0,0 +1,108 @@ +# https://github.com/rupansh/QuantumComputing/blob/master/rippleadd.py +# https://en.wikipedia.org/wiki/Adder_(electronics)#Full_adder +# https://en.wikipedia.org/wiki/Controlled_NOT_gate + +from qiskit import Aer, QuantumCircuit, execute +from qiskit.providers import BaseBackend + + +def store_two_classics(val1: int, val2: int) -> (QuantumCircuit, str, str): + """ + Generates a Quantum Circuit which stores two classical integers + Returns the circuit and binary representation of the integers + """ + x, y = bin(val1)[2:], bin(val2)[2:] # Remove leading '0b' + + # Ensure that both strings are of the same length + if len(x) > len(y): + y = y.zfill(len(x)) + else: + x = x.zfill(len(y)) + + # We need (3 * number of bits in the larger number)+1 qBits + # The second parameter is the number of classical registers, to measure the result + circuit = QuantumCircuit((len(x) * 3) + 1, len(x) + 1) + + # We are essentially "not-ing" the bits that are 1 + # Reversed because its easier to perform ops on more significant bits + for i in range(len(x)): + if x[::-1][i] == "1": + circuit.x(i) + for j in range(len(y)): + if y[::-1][j] == "1": + circuit.x(len(x) + j) + + return circuit, x, y + + +def full_adder( + circuit: QuantumCircuit, + input1_loc: int, + input2_loc: int, + carry_in: int, + carry_out: int, +): + """ + Quantum Equivalent of a Full Adder Circuit + CX/CCX is like 2-way/3-way XOR + """ + circuit.ccx(input1_loc, input2_loc, carry_out) + circuit.cx(input1_loc, input2_loc) + circuit.ccx(input2_loc, carry_in, carry_out) + circuit.cx(input2_loc, carry_in) + circuit.cx(input1_loc, input2_loc) + + +def ripple_adder( + val1: int, val2: int, backend: BaseBackend = Aer.get_backend("qasm_simulator") +) -> int: + """ + Quantum Equivalent of a Ripple Adder Circuit + Uses qasm_simulator backend by default + + Currently only adds 'emulated' Classical Bits + but nothing prevents us from doing this with hadamard'd bits :) + + Only supports adding +ve Integers + + >>> ripple_adder(3, 4) + 7 + >>> ripple_adder(10, 4) + 14 + >>> ripple_adder(-1, 10) + Traceback (most recent call last): + ... + ValueError: Both Integers must be positive! + """ + + if val1 < 0 or val2 < 0: + raise ValueError("Both Integers must be positive!") + + # Store the Integers + circuit, x, y = store_two_classics(val1, val2) + + """ + We are essentially using each bit of x & y respectively as full_adder's input + the carry_input is used from the previous circuit (for circuit num > 1) + + the carry_out is just below carry_input because + it will be essentially the carry_input for the next full_adder + """ + for i in range(len(x)): + full_adder(circuit, i, len(x) + i, len(x) + len(y) + i, len(x) + len(y) + i + 1) + circuit.barrier() # Optional, just for aesthetics + + # Measure the resultant qBits + for i in range(len(x) + 1): + circuit.measure([(len(x) * 2) + i], [i]) + + res = execute(circuit, backend, shots=1).result() + + # The result is in binary. Convert it back to int + return int(list(res.get_counts().keys())[0], 2) + + +if __name__ == "__main__": + import doctest + + doctest.testmod() From 32def4b3c53a100c40b0cf082799574157775e40 Mon Sep 17 00:00:00 2001 From: boyuuuun <44187125+boyuuuun@users.noreply.github.com> Date: Fri, 13 Nov 2020 22:55:23 +0900 Subject: [PATCH 0999/1071] add crawl_google_scholar_citation.py (#3879) * add crawl_google_scholar_citation.py * pass flack8 * pass isort * pass isort * change comment in main * modify main code * delete file * change how to build url * add a key 'hl' in params dict * Update crawl_google_scholar_citation.py * Create crawl_google_results.py * codespell: Mater Co-authored-by: Christian Clauss --- .pre-commit-config.yaml | 2 +- .../crawl_google_scholar_citation.py | 32 +++++++++++++++++++ 2 files changed, 33 insertions(+), 1 deletion(-) create mode 100644 web_programming/crawl_google_scholar_citation.py diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 01da6cad0335..a3288e1c5eef 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -42,7 +42,7 @@ repos: hooks: - id: codespell args: - - --ignore-words-list=ans,fo,followings,hist,iff,secant,som,tim + - --ignore-words-list=ans,fo,followings,hist,iff,mater,secant,som,tim - --skip="./.*,./other/dictionary.txt,./other/words,./project_euler/problem_022/p022_names.txt" - --quiet-level=2 exclude: | diff --git a/web_programming/crawl_google_scholar_citation.py b/web_programming/crawl_google_scholar_citation.py new file mode 100644 index 000000000000..d023380c0818 --- /dev/null +++ b/web_programming/crawl_google_scholar_citation.py @@ -0,0 +1,32 @@ +""" +Get the citation from google scholar +using title and year of publication, and volume and pages of journal. +""" + +import requests +from bs4 import BeautifulSoup + + +def get_citation(base_url: str, params: dict) -> str: + """ + Return the citation number. + """ + soup = BeautifulSoup(requests.get(base_url, params=params).content, "html.parser") + div = soup.find("div", attrs={"class": "gs_ri"}) + anchors = div.find("div", attrs={"class": "gs_fl"}).find_all("a") + return anchors[2].get_text() + + +if __name__ == "__main__": + params = { + "title": ( + "Precisely geometry controlled microsupercapacitors for ultrahigh areal " + "capacitance, volumetric capacitance, and energy density" + ), + "journal": "Chem. Mater.", + "volume": 30, + "pages": "3979-3990", + "year": 2018, + "hl": "en", + } + print(get_citation("http://scholar.google.com/scholar_lookup", params=params)) From ae4d7d4d0433b865c1a3e35dbbb7d43f2dc8ab2c Mon Sep 17 00:00:00 2001 From: Steve Kim <54872857+SteveKimSR@users.noreply.github.com> Date: Fri, 13 Nov 2020 23:26:17 +0900 Subject: [PATCH 1000/1071] add similarity_search.py in machine_learning (#3864) * add similarity_search.py in machine_learning adding similarity_search algorithm in machine_learning * fix pre-commit test, apply feedback isort, codespell changed. applied feedback(np -> np.ndarray) * apply feedback add type hints to euclidean method * apply feedback - changed euclidean's type hints - changed few TypeError to ValueError - changed range(len()) to enumerate() - changed error's strings to f-string - implemented without type() - add euclidean's explanation * apply feedback - deleted try/catch in euclidean - added error tests - name change(value -> value_array) * # doctest: +NORMALIZE_WHITESPACE * Update machine_learning/similarity_search.py * placate flake8 Co-authored-by: Christian Clauss --- machine_learning/similarity_search.py | 137 ++++++++++++++++++++++++++ 1 file changed, 137 insertions(+) create mode 100644 machine_learning/similarity_search.py diff --git a/machine_learning/similarity_search.py b/machine_learning/similarity_search.py new file mode 100644 index 000000000000..6bfb12ed88cb --- /dev/null +++ b/machine_learning/similarity_search.py @@ -0,0 +1,137 @@ +""" +Similarity Search : https://en.wikipedia.org/wiki/Similarity_search +Similarity search is a search algorithm for finding the nearest vector from +vectors, used in natural language processing. +In this algorithm, it calculates distance with euclidean distance and +returns a list containing two data for each vector: + 1. the nearest vector + 2. distance between the vector and the nearest vector (float) +""" +import math + +import numpy as np + + +def euclidean(input_a: np.ndarray, input_b: np.ndarray) -> float: + """ + Calculates euclidean distance between two data. + :param input_a: ndarray of first vector. + :param input_b: ndarray of second vector. + :return: Euclidean distance of input_a and input_b. By using math.sqrt(), + result will be float. + + >>> euclidean(np.array([0]), np.array([1])) + 1.0 + >>> euclidean(np.array([0, 1]), np.array([1, 1])) + 1.0 + >>> euclidean(np.array([0, 0, 0]), np.array([0, 0, 1])) + 1.0 + """ + return math.sqrt(sum(pow(a - b, 2) for a, b in zip(input_a, input_b))) + + +def similarity_search(dataset: np.ndarray, value_array: np.ndarray) -> list: + """ + :param dataset: Set containing the vectors. Should be ndarray. + :param value_array: vector/vectors we want to know the nearest vector from dataset. + :return: Result will be a list containing + 1. the nearest vector + 2. distance from the vector + + >>> dataset = np.array([[0], [1], [2]]) + >>> value_array = np.array([[0]]) + >>> similarity_search(dataset, value_array) + [[[0], 0.0]] + + >>> dataset = np.array([[0, 0], [1, 1], [2, 2]]) + >>> value_array = np.array([[0, 1]]) + >>> similarity_search(dataset, value_array) + [[[0, 0], 1.0]] + + >>> dataset = np.array([[0, 0, 0], [1, 1, 1], [2, 2, 2]]) + >>> value_array = np.array([[0, 0, 1]]) + >>> similarity_search(dataset, value_array) + [[[0, 0, 0], 1.0]] + + >>> dataset = np.array([[0, 0, 0], [1, 1, 1], [2, 2, 2]]) + >>> value_array = np.array([[0, 0, 0], [0, 0, 1]]) + >>> similarity_search(dataset, value_array) + [[[0, 0, 0], 0.0], [[0, 0, 0], 1.0]] + + These are the errors that might occur: + + 1. If dimensions are different. + For example, dataset has 2d array and value_array has 1d array: + >>> dataset = np.array([[1]]) + >>> value_array = np.array([1]) + >>> similarity_search(dataset, value_array) + Traceback (most recent call last): + ... + ValueError: Wrong input data's dimensions... dataset : 2, value_array : 1 + + 2. If data's shapes are different. + For example, dataset has shape of (3, 2) and value_array has (2, 3). + We are expecting same shapes of two arrays, so it is wrong. + >>> dataset = np.array([[0, 0], [1, 1], [2, 2]]) + >>> value_array = np.array([[0, 0, 0], [0, 0, 1]]) + >>> similarity_search(dataset, value_array) + Traceback (most recent call last): + ... + ValueError: Wrong input data's shape... dataset : 2, value_array : 3 + + 3. If data types are different. + When trying to compare, we are expecting same types so they should be same. + If not, it'll come up with errors. + >>> dataset = np.array([[0, 0], [1, 1], [2, 2]], dtype=np.float32) + >>> value_array = np.array([[0, 0], [0, 1]], dtype=np.int32) + >>> similarity_search(dataset, value_array) # doctest: +NORMALIZE_WHITESPACE + Traceback (most recent call last): + ... + TypeError: Input data have different datatype... + dataset : float32, value_array : int32 + """ + + if dataset.ndim != value_array.ndim: + raise ValueError( + f"Wrong input data's dimensions... dataset : {dataset.ndim}, " + f"value_array : {value_array.ndim}" + ) + + try: + if dataset.shape[1] != value_array.shape[1]: + raise ValueError( + f"Wrong input data's shape... dataset : {dataset.shape[1]}, " + f"value_array : {value_array.shape[1]}" + ) + except IndexError: + if dataset.ndim != value_array.ndim: + raise TypeError("Wrong shape") + + if dataset.dtype != value_array.dtype: + raise TypeError( + f"Input data have different datatype... dataset : {dataset.dtype}, " + f"value_array : {value_array.dtype}" + ) + + answer = [] + + for value in value_array: + dist = euclidean(value, dataset[0]) + vector = dataset[0].tolist() + + for dataset_value in dataset[1:]: + temp_dist = euclidean(value, dataset_value) + + if dist > temp_dist: + dist = temp_dist + vector = dataset_value.tolist() + + answer.append([vector, dist]) + + return answer + + +if __name__ == "__main__": + import doctest + + doctest.testmod() From c6dd9753893533934c7804bb714bbce2de8dd1a7 Mon Sep 17 00:00:00 2001 From: poloso Date: Sat, 14 Nov 2020 12:04:29 -0500 Subject: [PATCH 1001/1071] Add type hints and tests. (#2461) * Add type hints, documentation and tests. * Update searches/ternary_search.py Sort collection and remove the assertion logic. Co-authored-by: Christian Clauss * Remove assert sorted logic. * Add assertion list is ordered. * updating DIRECTORY.md * updating DIRECTORY.md * Format with black. * Change names of variables to descriptive names * Remove print in doctests * Fix variables to snake_case notation. Co-authored-by: Christian Clauss Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> --- DIRECTORY.md | 9 ++ searches/ternary_search.py | 199 ++++++++++++++++++++++++------------- 2 files changed, 141 insertions(+), 67 deletions(-) diff --git a/DIRECTORY.md b/DIRECTORY.md index fd45eacaad9b..89bedfb61592 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -206,6 +206,7 @@ * [Heaps Algorithm](https://github.com/TheAlgorithms/Python/blob/master/divide_and_conquer/heaps_algorithm.py) * [Heaps Algorithm Iterative](https://github.com/TheAlgorithms/Python/blob/master/divide_and_conquer/heaps_algorithm_iterative.py) * [Inversions](https://github.com/TheAlgorithms/Python/blob/master/divide_and_conquer/inversions.py) + * [Kth Order Statistic](https://github.com/TheAlgorithms/Python/blob/master/divide_and_conquer/kth_order_statistic.py) * [Max Subarray Sum](https://github.com/TheAlgorithms/Python/blob/master/divide_and_conquer/max_subarray_sum.py) * [Mergesort](https://github.com/TheAlgorithms/Python/blob/master/divide_and_conquer/mergesort.py) * [Power](https://github.com/TheAlgorithms/Python/blob/master/divide_and_conquer/power.py) @@ -390,6 +391,7 @@ * [Chudnovsky Algorithm](https://github.com/TheAlgorithms/Python/blob/master/maths/chudnovsky_algorithm.py) * [Collatz Sequence](https://github.com/TheAlgorithms/Python/blob/master/maths/collatz_sequence.py) * [Combinations](https://github.com/TheAlgorithms/Python/blob/master/maths/combinations.py) + * [Decimal Isolate](https://github.com/TheAlgorithms/Python/blob/master/maths/decimal_isolate.py) * [Entropy](https://github.com/TheAlgorithms/Python/blob/master/maths/entropy.py) * [Eulers Totient](https://github.com/TheAlgorithms/Python/blob/master/maths/eulers_totient.py) * [Explicit Euler](https://github.com/TheAlgorithms/Python/blob/master/maths/explicit_euler.py) @@ -681,6 +683,8 @@ * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_062/sol1.py) * Problem 063 * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_063/sol1.py) + * Problem 064 + * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_064/sol1.py) * Problem 065 * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_065/sol1.py) * Problem 067 @@ -694,6 +698,7 @@ * [Sol2](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_072/sol2.py) * Problem 074 * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_074/sol1.py) + * [Sol2](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_074/sol2.py) * Problem 075 * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_075/sol1.py) * Problem 076 @@ -726,12 +731,16 @@ * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_174/sol1.py) * Problem 191 * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_191/sol1.py) + * Problem 203 + * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_203/sol1.py) * Problem 206 * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_206/sol1.py) * Problem 207 * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_207/sol1.py) * Problem 234 * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_234/sol1.py) + * Problem 301 + * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_301/sol1.py) * Problem 551 * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_551/sol1.py) diff --git a/searches/ternary_search.py b/searches/ternary_search.py index b01db3eb845f..9422a4ccb966 100644 --- a/searches/ternary_search.py +++ b/searches/ternary_search.py @@ -6,7 +6,6 @@ Time Complexity : O(log3 N) Space Complexity : O(1) """ -import sys from typing import List # This is the precision for this function which can be altered. @@ -15,90 +14,156 @@ # This is the linear search that will occur after the search space has become smaller. -def lin_search(left: int, right: int, A: List[int], target: int): - for i in range(left, right + 1): - if A[i] == target: - return i -# This is the iterative method of the ternary search algorithm. -def ite_ternary_search(A: List[int], target: int): - left = 0 - right = len(A) - 1 - while True: - if left < right: +def lin_search(left: int, right: int, array: List[int], target: int) -> int: + """Perform linear search in list. Returns -1 if element is not found. + + Parameters + ---------- + left : int + left index bound. + right : int + right index bound. + array : List[int] + List of elements to be searched on + target : int + Element that is searched + + Returns + ------- + int + index of element that is looked for. + + Examples + -------- + >>> lin_search(0, 4, [4, 5, 6, 7], 7) + 3 + >>> lin_search(0, 3, [4, 5, 6, 7], 7) + -1 + >>> lin_search(0, 2, [-18, 2], -18) + 0 + >>> lin_search(0, 1, [5], 5) + 0 + >>> lin_search(0, 3, ['a', 'c', 'd'], 'c') + 1 + >>> lin_search(0, 3, [.1, .4 , -.1], .1) + 0 + >>> lin_search(0, 3, [.1, .4 , -.1], -.1) + 2 + """ + for i in range(left, right): + if array[i] == target: + return i + return -1 + + +def ite_ternary_search(array: List[int], target: int) -> int: + """Iterative method of the ternary search algorithm. + >>> test_list = [0, 1, 2, 8, 13, 17, 19, 32, 42] + >>> ite_ternary_search(test_list, 3) + -1 + >>> ite_ternary_search(test_list, 13) + 4 + >>> ite_ternary_search([4, 5, 6, 7], 4) + 0 + >>> ite_ternary_search([4, 5, 6, 7], -10) + -1 + >>> ite_ternary_search([-18, 2], -18) + 0 + >>> ite_ternary_search([5], 5) + 0 + >>> ite_ternary_search(['a', 'c', 'd'], 'c') + 1 + >>> ite_ternary_search(['a', 'c', 'd'], 'f') + -1 + >>> ite_ternary_search([], 1) + -1 + >>> ite_ternary_search([.1, .4 , -.1], .1) + 0 + """ - if right - left < precision: - return lin_search(left, right, A, target) + left = 0 + right = len(array) + while left <= right: + if right - left < precision: + return lin_search(left, right, array, target) - oneThird = (left + right) / 3 + 1 - twoThird = 2 * (left + right) / 3 + 1 + one_third = (left + right) / 3 + 1 + two_third = 2 * (left + right) / 3 + 1 - if A[oneThird] == target: - return oneThird - elif A[twoThird] == target: - return twoThird + if array[one_third] == target: + return one_third + elif array[two_third] == target: + return two_third - elif target < A[oneThird]: - right = oneThird - 1 - elif A[twoThird] < target: - left = twoThird + 1 + elif target < array[one_third]: + right = one_third - 1 + elif array[two_third] < target: + left = two_third + 1 - else: - left = oneThird + 1 - right = twoThird - 1 else: - return None - -# This is the recursive method of the ternary search algorithm. -def rec_ternary_search(left: int, right: int, A: List[int], target: int): + left = one_third + 1 + right = two_third - 1 + else: + return -1 + + +def rec_ternary_search(left: int, right: int, array: List[int], target: int) -> int: + """Recursive method of the ternary search algorithm. + + >>> test_list = [0, 1, 2, 8, 13, 17, 19, 32, 42] + >>> rec_ternary_search(0, len(test_list), test_list, 3) + -1 + >>> rec_ternary_search(4, len(test_list), test_list, 42) + 8 + >>> rec_ternary_search(0, 2, [4, 5, 6, 7], 4) + 0 + >>> rec_ternary_search(0, 3, [4, 5, 6, 7], -10) + -1 + >>> rec_ternary_search(0, 1, [-18, 2], -18) + 0 + >>> rec_ternary_search(0, 1, [5], 5) + 0 + >>> rec_ternary_search(0, 2, ['a', 'c', 'd'], 'c') + 1 + >>> rec_ternary_search(0, 2, ['a', 'c', 'd'], 'f') + -1 + >>> rec_ternary_search(0, 0, [], 1) + -1 + >>> rec_ternary_search(0, 3, [.1, .4 , -.1], .1) + 0 + """ if left < right: - if right - left < precision: - return lin_search(left, right, A, target) - - oneThird = (left + right) / 3 + 1 - twoThird = 2 * (left + right) / 3 + 1 - - if A[oneThird] == target: - return oneThird - elif A[twoThird] == target: - return twoThird - - elif target < A[oneThird]: - return rec_ternary_search(left, oneThird - 1, A, target) - elif A[twoThird] < target: - return rec_ternary_search(twoThird + 1, right, A, target) - + return lin_search(left, right, array, target) + one_third = (left + right) / 3 + 1 + two_third = 2 * (left + right) / 3 + 1 + + if array[one_third] == target: + return one_third + elif array[two_third] == target: + return two_third + + elif target < array[one_third]: + return rec_ternary_search(left, one_third - 1, array, target) + elif array[two_third] < target: + return rec_ternary_search(two_third + 1, right, array, target) else: - return rec_ternary_search(oneThird + 1, twoThird - 1, A, target) + return rec_ternary_search(one_third + 1, two_third - 1, array, target) else: - return None - - -# This function is to check if the array is sorted. -def __assert_sorted(collection: List[int]) -> bool: - if collection != sorted(collection): - raise ValueError("Collection must be sorted") - return True + return -1 if __name__ == "__main__": - user_input = input("Enter numbers separated by coma:\n").strip() - collection = [int(item) for item in user_input.split(",")] - - try: - __assert_sorted(collection) - except ValueError: - sys.exit("Sequence must be sorted to apply the ternary search") - - target_input = input("Enter a single number to be found in the list:\n") - target = int(target_input) + user_input = input("Enter numbers separated by comma:\n").strip() + collection = [int(item.strip()) for item in user_input.split(",")] + assert collection == sorted(collection), f"List must be ordered.\n{collection}." + target = int(input("Enter the number to be found in the list:\n").strip()) result1 = ite_ternary_search(collection, target) result2 = rec_ternary_search(0, len(collection) - 1, collection, target) - - if result2 is not None: + if result2 != -1: print(f"Iterative search: {target} found at positions: {result1}") print(f"Recursive search: {target} found at positions: {result2}") else: From 78a0f61b1912206d7bfdc3f0604c1fba60bb605f Mon Sep 17 00:00:00 2001 From: sukyung99 <44187128+sukyung99@users.noreply.github.com> Date: Sun, 15 Nov 2020 12:44:40 +0900 Subject: [PATCH 1002/1071] Add Maths / Sigmoid Function (#3880) * Add Maths / Sigmoid Function * Update Sigmoid Function * Add doctest and type hints * Fix Trim Trailing Whitespace * Fix Error * Modified Black * Update sigmoid.py Co-authored-by: sukyung99 Co-authored-by: Christian Clauss --- maths/sigmoid.py | 39 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 maths/sigmoid.py diff --git a/maths/sigmoid.py b/maths/sigmoid.py new file mode 100644 index 000000000000..147588e8871f --- /dev/null +++ b/maths/sigmoid.py @@ -0,0 +1,39 @@ +""" +This script demonstrates the implementation of the Sigmoid function. + +The function takes a vector of K real numbers as input and then 1 / (1 + exp(-x)). +After through Sigmoid, the element of the vector mostly 0 between 1. or 1 between -1. + +Script inspired from its corresponding Wikipedia article +https://en.wikipedia.org/wiki/Sigmoid_function +""" + +import numpy as np + + +def sigmoid(vector: np.array) -> np.array: + """ + Implements the sigmoid function + + Parameters: + vector (np.array): A numpy array of shape (1,n) + consisting of real values + + Returns: + sigmoid_vec (np.array): The input numpy array, after applying + sigmoid. + + Examples: + >>> sigmoid(np.array([-1.0, 1.0, 2.0])) + array([0.26894142, 0.73105858, 0.88079708]) + + >>> sigmoid(np.array([0.0])) + array([0.5]) + """ + return 1 / (1 + np.exp(-vector)) + + +if __name__ == "__main__": + import doctest + + doctest.testmod() From b31ed8c49401efb1e941c3514384a90909572f0e Mon Sep 17 00:00:00 2001 From: Sethu Date: Wed, 18 Nov 2020 12:07:30 +0530 Subject: [PATCH 1003/1071] Modified comments on upper.py (#3884) * Modified comments on upper.py ,made it more clean * Update strings/upper.py Co-authored-by: xcodz-dot <71920621+xcodz-dot@users.noreply.github.com> * Update upper.py * Update upper.py Co-authored-by: xcodz-dot <71920621+xcodz-dot@users.noreply.github.com> Co-authored-by: Dhruv Manilawala --- strings/upper.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/strings/upper.py b/strings/upper.py index 411802a2a22f..5edd40b79808 100644 --- a/strings/upper.py +++ b/strings/upper.py @@ -12,9 +12,9 @@ def upper(word: str) -> str: 'WH[]32' """ - # converting to ascii value int value and checking to see if char is a lower letter - # if it is a capital letter it is getting shift by 32 which makes it a capital case - # letter + # Converting to ascii value int value and checking to see if char is a lower letter + # if it is a lowercase letter it is getting shift by 32 which makes it an uppercase + # case letter return "".join(chr(ord(char) - 32) if "a" <= char <= "z" else char for char in word) From 8a134e1f45d05e21194e8fbb5763d5645b36088f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=EC=88=98=EC=97=B0?= Date: Wed, 18 Nov 2020 19:35:51 +0900 Subject: [PATCH 1004/1071] update area.py (#3862) add method "area_ellipse" - Calculate the area of a ellipse Co-authored-by: 201502029 --- maths/area.py | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/maths/area.py b/maths/area.py index 393d45faa880..24216e223ebf 100644 --- a/maths/area.py +++ b/maths/area.py @@ -186,6 +186,32 @@ def area_circle(radius: float) -> float: return pi * radius ** 2 +def area_ellipse(radius_x: float, radius_y: float) -> float: + """ + Calculate the area of a ellipse + + >>> area_ellipse(10, 10) + 314.1592653589793 + >>> area_ellipse(10, 20) + 628.3185307179587 + >>> area_ellipse(-10, 20) + Traceback (most recent call last): + ... + ValueError: area_ellipse() only accepts non-negative values + >>> area_ellipse(10, -20) + Traceback (most recent call last): + ... + ValueError: area_ellipse() only accepts non-negative values + >>> area_ellipse(-10, -20) + Traceback (most recent call last): + ... + ValueError: area_ellipse() only accepts non-negative values + """ + if radius_x < 0 or radius_y < 0: + raise ValueError("area_ellipse() only accepts non-negative values") + return pi * radius_x * radius_y + + def area_rhombus(diagonal_1: float, diagonal_2: float) -> float: """ Calculate the area of a rhombus From b9b7fffcc2d3f80409269e2e2c1ba78c58ebcdbe Mon Sep 17 00:00:00 2001 From: Dhruv Manilawala Date: Thu, 19 Nov 2020 22:01:31 +0530 Subject: [PATCH 1005/1071] Move CI tests from Travis to GitHub (#3889) * Add initial support for moving tests to GitHub * Add setup Python step in the workflow * Remove Travis CI config file * Fix GitHub action file for build to trigger on PR * Use Python 3.8 as tensorflow is not yet supported * Fix ciphers.hill_cipher doctest error * Fix: instagram crawler tests failing on GitHub actions * Fix floating point errors in doctest * Small change to test cache * Apply suggestions from code review Co-authored-by: Christian Clauss * Update instagram_crawler.py Co-authored-by: Christian Clauss --- .github/workflows/build.yml | 25 +++++++++++++++++++++++++ .travis.yml | 17 ----------------- ciphers/hill_cipher.py | 4 ++-- machine_learning/forecasting/run.py | 5 +++-- web_programming/instagram_crawler.py | 8 ++++---- 5 files changed, 34 insertions(+), 25 deletions(-) create mode 100644 .github/workflows/build.yml delete mode 100644 .travis.yml diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 000000000000..015670558432 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,25 @@ +name: "build" + +on: + pull_request + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: actions/setup-python@v2 + with: + python-version: "3.8" + - uses: actions/cache@v2 + with: + path: ~/.cache/pip + key: ${{ runner.os }}-pip-${{ hashFiles('requirements.txt') }} + - name: Install dependencies + run: | + python -m pip install --upgrade pip setuptools six + python -m pip install pytest-cov -r requirements.txt + - name: Run tests + run: pytest --doctest-modules --ignore=project_euler/ --cov-report=term-missing:skip-covered --cov=. . + - if: ${{ success() }} + run: scripts/build_directory_md.py 2>&1 | tee DIRECTORY.md diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index c74669ebcb51..000000000000 --- a/.travis.yml +++ /dev/null @@ -1,17 +0,0 @@ -os: linux -dist: focal -language: python -python: 3.8 -cache: pip -before_install: pip install --upgrade pip setuptools six -jobs: - include: - - name: Build - install: pip install pytest-cov -r requirements.txt - script: - - pytest --doctest-modules --ignore=project_euler/ --cov-report=term-missing:skip-covered --cov=. . -after_success: - - scripts/build_directory_md.py 2>&1 | tee DIRECTORY.md -notifications: - webhooks: https://www.travisbuddy.com/ - on_success: never diff --git a/ciphers/hill_cipher.py b/ciphers/hill_cipher.py index 3dabcd3fceab..8237abf6aa5d 100644 --- a/ciphers/hill_cipher.py +++ b/ciphers/hill_cipher.py @@ -155,8 +155,8 @@ def make_decrypt_key(self): """ >>> hill_cipher = HillCipher(numpy.array([[2, 5], [1, 6]])) >>> hill_cipher.make_decrypt_key() - array([[ 6., 25.], - [ 5., 26.]]) + array([[ 6, 25], + [ 5, 26]]) """ det = round(numpy.linalg.det(self.encrypt_key)) diff --git a/machine_learning/forecasting/run.py b/machine_learning/forecasting/run.py index 467371e8d2ff..0e11f958825f 100644 --- a/machine_learning/forecasting/run.py +++ b/machine_learning/forecasting/run.py @@ -25,8 +25,9 @@ def linear_regression_prediction( First method: linear regression input : training data (date, total_user, total_event) in list of float output : list of total user prediction in float - >>> linear_regression_prediction([2,3,4,5], [5,3,4,6], [3,1,2,4], [2,1], [2,2]) - 5.000000000000003 + >>> n = linear_regression_prediction([2,3,4,5], [5,3,4,6], [3,1,2,4], [2,1], [2,2]) + >>> abs(n - 5.0) < 1e-6 # Checking precision because of floating point errors + True """ x = [[1, item, train_mtch[i]] for i, item in enumerate(train_dt)] x = np.array(x) diff --git a/web_programming/instagram_crawler.py b/web_programming/instagram_crawler.py index c81635bd3593..4536257a984e 100644 --- a/web_programming/instagram_crawler.py +++ b/web_programming/instagram_crawler.py @@ -23,7 +23,7 @@ class InstagramUser: """ Class Instagram crawl instagram user information - Usage: (doctest failing on Travis CI) + Usage: (doctest failing on GitHub Actions) # >>> instagram_user = InstagramUser("github") # >>> instagram_user.is_verified True @@ -102,10 +102,10 @@ def test_instagram_user(username: str = "github") -> None: A self running doctest >>> test_instagram_user() """ - from os import getenv + import os - if getenv("CONTINUOUS_INTEGRATION"): - return # test failing on Travis CI + if os.environ.get("CI"): + return None # test failing on GitHub Actions instagram_user = InstagramUser(username) assert instagram_user.user_data assert isinstance(instagram_user.user_data, dict) From bb693aa68196c1ed70794013b39e8b29bca13aaf Mon Sep 17 00:00:00 2001 From: Christian Clauss Date: Thu, 19 Nov 2020 17:48:47 +0100 Subject: [PATCH 1006/1071] Delete Travis_CI_tests_are_failing.md (#3902) * Delete Travis_CI_tests_are_failing.md * updating DIRECTORY.md Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> --- DIRECTORY.md | 10 ++++++++++ Travis_CI_tests_are_failing.md | 9 --------- 2 files changed, 10 insertions(+), 9 deletions(-) delete mode 100644 Travis_CI_tests_are_failing.md diff --git a/DIRECTORY.md b/DIRECTORY.md index 89bedfb61592..cd8f6fb8578c 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -209,6 +209,7 @@ * [Kth Order Statistic](https://github.com/TheAlgorithms/Python/blob/master/divide_and_conquer/kth_order_statistic.py) * [Max Subarray Sum](https://github.com/TheAlgorithms/Python/blob/master/divide_and_conquer/max_subarray_sum.py) * [Mergesort](https://github.com/TheAlgorithms/Python/blob/master/divide_and_conquer/mergesort.py) + * [Peak](https://github.com/TheAlgorithms/Python/blob/master/divide_and_conquer/peak.py) * [Power](https://github.com/TheAlgorithms/Python/blob/master/divide_and_conquer/power.py) * [Strassen Matrix Multiplication](https://github.com/TheAlgorithms/Python/blob/master/divide_and_conquer/strassen_matrix_multiplication.py) @@ -363,6 +364,7 @@ * [Random Forest Regressor](https://github.com/TheAlgorithms/Python/blob/master/machine_learning/random_forest_regressor.py) * [Scoring Functions](https://github.com/TheAlgorithms/Python/blob/master/machine_learning/scoring_functions.py) * [Sequential Minimum Optimization](https://github.com/TheAlgorithms/Python/blob/master/machine_learning/sequential_minimum_optimization.py) + * [Similarity Search](https://github.com/TheAlgorithms/Python/blob/master/machine_learning/similarity_search.py) * [Support Vector Machines](https://github.com/TheAlgorithms/Python/blob/master/machine_learning/support_vector_machines.py) * [Word Frequency Functions](https://github.com/TheAlgorithms/Python/blob/master/machine_learning/word_frequency_functions.py) @@ -455,6 +457,7 @@ * [Harmonic Series](https://github.com/TheAlgorithms/Python/blob/master/maths/series/harmonic_series.py) * [P Series](https://github.com/TheAlgorithms/Python/blob/master/maths/series/p_series.py) * [Sieve Of Eratosthenes](https://github.com/TheAlgorithms/Python/blob/master/maths/sieve_of_eratosthenes.py) + * [Sigmoid](https://github.com/TheAlgorithms/Python/blob/master/maths/sigmoid.py) * [Simpson Rule](https://github.com/TheAlgorithms/Python/blob/master/maths/simpson_rule.py) * [Softmax](https://github.com/TheAlgorithms/Python/blob/master/maths/softmax.py) * [Square Root](https://github.com/TheAlgorithms/Python/blob/master/maths/square_root.py) @@ -495,6 +498,7 @@ * [Autocomplete Using Trie](https://github.com/TheAlgorithms/Python/blob/master/other/autocomplete_using_trie.py) * [Binary Exponentiation](https://github.com/TheAlgorithms/Python/blob/master/other/binary_exponentiation.py) * [Binary Exponentiation 2](https://github.com/TheAlgorithms/Python/blob/master/other/binary_exponentiation_2.py) + * [Davis–Putnam–Logemann–Loveland](https://github.com/TheAlgorithms/Python/blob/master/other/davis–putnam–logemann–loveland.py) * [Detecting English Programmatically](https://github.com/TheAlgorithms/Python/blob/master/other/detecting_english_programmatically.py) * [Dijkstra Bankers Algorithm](https://github.com/TheAlgorithms/Python/blob/master/other/dijkstra_bankers_algorithm.py) * [Doomsday](https://github.com/TheAlgorithms/Python/blob/master/other/doomsday.py) @@ -662,6 +666,8 @@ * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_048/sol1.py) * Problem 049 * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_049/sol1.py) + * Problem 050 + * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_050/sol1.py) * Problem 051 * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_051/sol1.py) * Problem 052 @@ -723,6 +729,8 @@ * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_119/sol1.py) * Problem 120 * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_120/sol1.py) + * Problem 123 + * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_123/sol1.py) * Problem 125 * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_125/sol1.py) * Problem 173 @@ -749,6 +757,7 @@ * [Half Adder](https://github.com/TheAlgorithms/Python/blob/master/quantum/half_adder.py) * [Not Gate](https://github.com/TheAlgorithms/Python/blob/master/quantum/not_gate.py) * [Quantum Entanglement](https://github.com/TheAlgorithms/Python/blob/master/quantum/quantum_entanglement.py) + * [Ripple Adder Classic](https://github.com/TheAlgorithms/Python/blob/master/quantum/ripple_adder_classic.py) * [Single Qubit Measure](https://github.com/TheAlgorithms/Python/blob/master/quantum/single_qubit_measure.py) ## Scheduling @@ -848,6 +857,7 @@ * [Co2 Emission](https://github.com/TheAlgorithms/Python/blob/master/web_programming/co2_emission.py) * [Covid Stats Via Xpath](https://github.com/TheAlgorithms/Python/blob/master/web_programming/covid_stats_via_xpath.py) * [Crawl Google Results](https://github.com/TheAlgorithms/Python/blob/master/web_programming/crawl_google_results.py) + * [Crawl Google Scholar Citation](https://github.com/TheAlgorithms/Python/blob/master/web_programming/crawl_google_scholar_citation.py) * [Current Stock Price](https://github.com/TheAlgorithms/Python/blob/master/web_programming/current_stock_price.py) * [Current Weather](https://github.com/TheAlgorithms/Python/blob/master/web_programming/current_weather.py) * [Daily Horoscope](https://github.com/TheAlgorithms/Python/blob/master/web_programming/daily_horoscope.py) diff --git a/Travis_CI_tests_are_failing.md b/Travis_CI_tests_are_failing.md deleted file mode 100644 index 10bf5a6655d2..000000000000 --- a/Travis_CI_tests_are_failing.md +++ /dev/null @@ -1,9 +0,0 @@ -# Travis CI test are failing -### How do I find out what is wrong with my pull request? -1. In your PR look for the failing test and click the `Details` link: ![Travis_CI_fail_1.png](images/Travis_CI_fail_1.png) -2. On the next page, click `The build failed` link: ![Travis_CI_fail_2.png](images/Travis_CI_fail_2.png) -3. Now scroll down and look for `red` text describing the error(s) in the test log. - -Pull requests will __not__ be merged if the Travis CI tests are failing. - -If anything is unclear, please read through [CONTRIBUTING.md](CONTRIBUTING.md) and attempt to run the failing tests on your computer before asking for assistance. From fe6885926f4cdf74f230074318486df2e19d28ee Mon Sep 17 00:00:00 2001 From: Dhruv Manilawala Date: Thu, 19 Nov 2020 22:34:57 +0530 Subject: [PATCH 1007/1071] Update related to the change in CI testing (#3903) * Update README badge for GitHub CI * Run GitHub CI everyday as was done in Travis --- .github/workflows/build.yml | 4 +++- README.md | 10 +++++----- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 015670558432..01ac9aea7a7c 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -1,7 +1,9 @@ name: "build" on: - pull_request + pull_request: + schedule: + - cron: "0 0 * * *" # Run everyday jobs: build: diff --git a/README.md b/README.md index ac98b6371682..f81031b53ebb 100644 --- a/README.md +++ b/README.md @@ -1,14 +1,14 @@ # The Algorithms - Python -[![Gitpod Ready-to-Code](https://img.shields.io/badge/Gitpod-Ready--to--Code-blue?logo=gitpod&style=flat-square)](https://gitpod.io/#https://github.com/TheAlgorithms/Python) +[![Gitpod Ready-to-Code](https://img.shields.io/badge/Gitpod-Ready--to--Code-blue?logo=gitpod&style=flat-square)](https://gitpod.io/#https://github.com/TheAlgorithms/Python)  [![Gitter chat](https://img.shields.io/badge/Chat-Gitter-ff69b4.svg?label=Chat&logo=gitter&style=flat-square)](https://gitter.im/TheAlgorithms)  -[![Build Status](https://img.shields.io/travis/TheAlgorithms/Python.svg?label=Travis%20CI&logo=travis&style=flat-square)](https://travis-ci.com/TheAlgorithms/Python)  +[![GitHub Workflow Status](https://img.shields.io/github/workflow/status/TheAlgorithms/Python/build?label=CI&logo=github&style=flat-square)](https://github.com/TheAlgorithms/Python/actions)  [![LGTM](https://img.shields.io/lgtm/alerts/github/TheAlgorithms/Python.svg?label=LGTM&logo=LGTM&style=flat-square)](https://lgtm.com/projects/g/TheAlgorithms/Python/alerts)  [![contributions welcome](https://img.shields.io/static/v1.svg?label=Contributions&message=Welcome&color=0059b3&style=flat-square)](https://github.com/TheAlgorithms/Python/blob/master/CONTRIBUTING.md)  [![Donate](https://img.shields.io/badge/Donate-PayPal-green.svg?logo=paypal&style=flat-square)](https://www.paypal.me/TheAlgorithms/100)  ![](https://img.shields.io/github/repo-size/TheAlgorithms/Python.svg?label=Repo%20size&style=flat-square)  -[![pre-commit](https://img.shields.io/badge/pre--commit-enabled-brightgreen?logo=pre-commit&logoColor=white&style=flat-square)](https://github.com/pre-commit/pre-commit) -[![code style: black](https://img.shields.io/static/v1?label=code%20style&message=black&color=black&style=flat-square)](https://github.com/psf/black) - +[![pre-commit](https://img.shields.io/badge/pre--commit-enabled-brightgreen?logo=pre-commit&logoColor=white&style=flat-square)](https://github.com/pre-commit/pre-commit)  +[![code style: black](https://img.shields.io/static/v1?label=code%20style&message=black&color=black&style=flat-square)](https://github.com/psf/black)  + ### All algorithms implemented in Python (for education) From 2885a8cd9997303dcd19f1ffc50bd9beb8d99fd7 Mon Sep 17 00:00:00 2001 From: Dhruv Manilawala Date: Fri, 20 Nov 2020 00:39:31 +0530 Subject: [PATCH 1008/1071] Run latest stale action (#3904) --- .github/workflows/stale.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml index 41ded1159891..4b12a71d7aff 100644 --- a/.github/workflows/stale.yml +++ b/.github/workflows/stale.yml @@ -6,7 +6,7 @@ jobs: stale: runs-on: ubuntu-latest steps: - - uses: actions/stale@v3 + - uses: actions/stale@v3.0.13 with: repo-token: ${{ secrets.GITHUB_TOKEN }} days-before-stale: 30 From 83b8b3c141f2a11afd84a61e0ef62368bdcfab29 Mon Sep 17 00:00:00 2001 From: tacitvenom Date: Fri, 20 Nov 2020 04:14:08 +0000 Subject: [PATCH 1009/1071] Fixes: #3869: Optimize CI runtime of Project Euler's Problem 74's Solution 2 (#3893) * Fixes: #3869: Optimize Project Euler's Problem 74's Solution 2 * updating DIRECTORY.md Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> --- project_euler/problem_074/sol2.py | 68 ++++++++++++++++++------------- 1 file changed, 39 insertions(+), 29 deletions(-) diff --git a/project_euler/problem_074/sol2.py b/project_euler/problem_074/sol2.py index 0348ef1a6628..689593277a81 100644 --- a/project_euler/problem_074/sol2.py +++ b/project_euler/problem_074/sol2.py @@ -16,9 +16,13 @@ the chains of non repeating items. The generation of the chain stops before a repeating item or if the size of the chain is greater then the desired one. - After generating each chain, the length is checked and the counter increases. + After generating each chain, the length is checked and the + counter increases. """ +factorial_cache = {} +factorial_sum_cache = {} + def factorial(a: int) -> int: """Returns the factorial of the input a @@ -36,18 +40,23 @@ def factorial(a: int) -> int: if a < 0: raise ValueError("Invalid negative input!", a) + if a in factorial_cache: + return factorial_cache[a] + # The case of 0! is handled separately if a == 0: - return 1 + factorial_cache[a] = 1 else: # use a temporary support variable to store the computation + temporary_number = a temporary_computation = 1 - while a > 0: - temporary_computation *= a - a -= 1 + while temporary_number > 0: + temporary_computation *= temporary_number + temporary_number -= 1 - return temporary_computation + factorial_cache[a] = temporary_computation + return factorial_cache[a] def factorial_sum(a: int) -> int: @@ -57,7 +66,8 @@ def factorial_sum(a: int) -> int: >>> factorial_sum(69) 363600 """ - + if a in factorial_sum_cache: + return factorial_sum_cache[a] # Prepare a variable to hold the computation fact_sum = 0 @@ -67,17 +77,15 @@ def factorial_sum(a: int) -> int: """ for i in str(a): fact_sum += factorial(int(i)) - + factorial_sum_cache[a] = fact_sum return fact_sum def solution(chain_length: int = 60, number_limit: int = 1000000) -> int: """Returns the number of numbers that produce chains with exactly 60 non repeating elements. - >>> solution(60,1000000) - 402 - >>> solution(15,1000000) - 17800 + >>> solution(10, 1000) + 26 """ # the counter for the chains with the exact desired length @@ -86,25 +94,27 @@ def solution(chain_length: int = 60, number_limit: int = 1000000) -> int: for i in range(1, number_limit + 1): # The temporary list will contain the elements of the chain - chain_list = [i] + chain_set = {i} + len_chain_set = 1 + last_chain_element = i # The new element of the chain - new_chain_element = factorial_sum(chain_list[-1]) - - """ Stop computing the chain when you find a repeating item - or the length it greater then the desired one. - """ - while not (new_chain_element in chain_list) and ( - len(chain_list) <= chain_length - ): - chain_list += [new_chain_element] - - new_chain_element = factorial_sum(chain_list[-1]) - - """ If the while exited because the chain list contains the exact amount of elements - increase the counter - """ - chain_counter += len(chain_list) == chain_length + new_chain_element = factorial_sum(last_chain_element) + + # Stop computing the chain when you find a repeating item + # or the length it greater then the desired one. + + while new_chain_element not in chain_set and len_chain_set <= chain_length: + chain_set.add(new_chain_element) + + len_chain_set += 1 + last_chain_element = new_chain_element + new_chain_element = factorial_sum(last_chain_element) + + # If the while exited because the chain list contains the exact amount + # of elements increase the counter + if len_chain_set == chain_length: + chain_counter += 1 return chain_counter From fe0f2f32e70b81bb35480c9c6d118b90ce940c43 Mon Sep 17 00:00:00 2001 From: Dhruv Manilawala Date: Fri, 20 Nov 2020 11:41:22 +0530 Subject: [PATCH 1010/1071] Try the stale bot instead of stale action (#3906) * Try the stale bot instead * Add config file for the stale bot --- .github/stale.yml | 63 +++++++++++++++++++++++++++++++++++++ .github/workflows/stale.yml | 62 ++++++++++++++++++------------------ 2 files changed, 94 insertions(+), 31 deletions(-) create mode 100644 .github/stale.yml diff --git a/.github/stale.yml b/.github/stale.yml new file mode 100644 index 000000000000..ba6fd155d7a3 --- /dev/null +++ b/.github/stale.yml @@ -0,0 +1,63 @@ +# Configuration for probot-stale - https://github.com/probot/stale + +# Number of days of inactivity before an Issue or Pull Request becomes stale +daysUntilStale: 30 + +# Number of days of inactivity before an Issue or Pull Request with the stale label is closed. +# Set to false to disable. If disabled, issues still need to be closed manually, but will remain marked as stale. +daysUntilClose: 7 + +# Only issues or pull requests with all of these labels are check if stale. Defaults to `[]` (disabled) +onlyLabels: [] + +# Issues or Pull Requests with these labels will never be considered stale. Set to `[]` to disable +exemptLabels: + - "Status: on hold" + +# Set to true to ignore issues in a project (defaults to false) +exemptProjects: false + +# Set to true to ignore issues in a milestone (defaults to false) +exemptMilestones: false + +# Set to true to ignore issues with an assignee (defaults to false) +exemptAssignees: false + +# Label to use when marking as stale +staleLabel: stale + +# Limit the number of actions per hour, from 1-30. Default is 30 +limitPerRun: 30 + +# Comment to post when removing the stale label. +# unmarkComment: > +# Your comment here. + +# Optionally, specify configuration settings that are specific to just 'issues' or 'pulls': +pulls: + # Comment to post when marking as stale. Set to `false` to disable + markComment: > + This pull request has been automatically marked as stale because it has not had + recent activity. It will be closed if no further activity occurs. Thank you + for your contributions. + + # Comment to post when closing a stale Pull Request. + closeComment: > + Please reopen this pull request once you commit the changes requested + or make improvements on the code. If this is not the case and you need + some help, feel free to seek help from our [Gitter](https://gitter.im/TheAlgorithms) + or ping one of the reviewers. Thank you for your contributions! + +issues: + # Comment to post when marking as stale. Set to `false` to disable + markComment: > + This issue has been automatically marked as stale because it has not had + recent activity. It will be closed if no further activity occurs. Thank you + for your contributions. + + # Comment to post when closing a stale Issue. + closeComment: > + Please reopen this issue once you add more information and updates here. + If this is not the case and you need some help, feel free to seek help + from our [Gitter](https://gitter.im/TheAlgorithms) or ping one of the + reviewers. Thank you for your contributions! diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml index 4b12a71d7aff..42353d233a29 100644 --- a/.github/workflows/stale.yml +++ b/.github/workflows/stale.yml @@ -1,31 +1,31 @@ -name: Mark/Close stale issues and pull requests -on: - schedule: - - cron: "0 * * * *" # Run every hour -jobs: - stale: - runs-on: ubuntu-latest - steps: - - uses: actions/stale@v3.0.13 - with: - repo-token: ${{ secrets.GITHUB_TOKEN }} - days-before-stale: 30 - days-before-close: 7 - stale-issue-message: > - This issue has been automatically marked as stale because it has not had - recent activity. It will be closed if no further activity occurs. Thank you - for your contributions. - close-issue-message: > - Please reopen this issue once you add more information and updates here. - If this is not the case and you need some help, feel free to seek help - from our [Gitter](https://gitter.im/TheAlgorithms) or ping one of the - reviewers. Thank you for your contributions! - stale-pr-message: > - This pull request has been automatically marked as stale because it has not had - recent activity. It will be closed if no further activity occurs. Thank you - for your contributions. - close-pr-message: > - Please reopen this pull request once you commit the changes requested - or make improvements on the code. If this is not the case and you need - some help, feel free to seek help from our [Gitter](https://gitter.im/TheAlgorithms) - or ping one of the reviewers. Thank you for your contributions! +# name: Mark/Close stale issues and pull requests +# on: +# schedule: +# - cron: "0 * * * *" # Run every hour +# jobs: +# stale: +# runs-on: ubuntu-latest +# steps: +# - uses: actions/stale@v3.0.13 +# with: +# repo-token: ${{ secrets.GITHUB_TOKEN }} +# days-before-stale: 30 +# days-before-close: 7 +# stale-issue-message: > +# This issue has been automatically marked as stale because it has not had +# recent activity. It will be closed if no further activity occurs. Thank you +# for your contributions. +# close-issue-message: > +# Please reopen this issue once you add more information and updates here. +# If this is not the case and you need some help, feel free to seek help +# from our [Gitter](https://gitter.im/TheAlgorithms) or ping one of the +# reviewers. Thank you for your contributions! +# stale-pr-message: > +# This pull request has been automatically marked as stale because it has not had +# recent activity. It will be closed if no further activity occurs. Thank you +# for your contributions. +# close-pr-message: > +# Please reopen this pull request once you commit the changes requested +# or make improvements on the code. If this is not the case and you need +# some help, feel free to seek help from our [Gitter](https://gitter.im/TheAlgorithms) +# or ping one of the reviewers. Thank you for your contributions! From d0bc9e268d33b495cadeafc6613029989bc26aac Mon Sep 17 00:00:00 2001 From: Michael D Date: Sat, 21 Nov 2020 03:07:47 +0100 Subject: [PATCH 1011/1071] Add solution for Project Euler problem 188 (#2880) * Project Euler problem 188 solution * fix superscript notation * split out modexpt() function, and rename parameters * Add some more doctest, and add type hints * Add some reference links * Update docstrings and mark helper function private * Fix doctests and remove/improve redundant comments * fix as per style guide --- project_euler/problem_188/__init__.py | 0 project_euler/problem_188/sol1.py | 68 +++++++++++++++++++++++++++ 2 files changed, 68 insertions(+) create mode 100644 project_euler/problem_188/__init__.py create mode 100644 project_euler/problem_188/sol1.py diff --git a/project_euler/problem_188/__init__.py b/project_euler/problem_188/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/project_euler/problem_188/sol1.py b/project_euler/problem_188/sol1.py new file mode 100644 index 000000000000..6473c63620ed --- /dev/null +++ b/project_euler/problem_188/sol1.py @@ -0,0 +1,68 @@ +""" +Project Euler Problem 188: https://projecteuler.net/problem=188 + +The hyperexponentiation of a number + +The hyperexponentiation or tetration of a number a by a positive integer b, +denoted by a↑↑b or b^a, is recursively defined by: + +a↑↑1 = a, +a↑↑(k+1) = a(a↑↑k). + +Thus we have e.g. 3↑↑2 = 3^3 = 27, hence 3↑↑3 = 3^27 = 7625597484987 and +3↑↑4 is roughly 103.6383346400240996*10^12. + +Find the last 8 digits of 1777↑↑1855. + +References: + - https://en.wikipedia.org/wiki/Tetration +""" + + +# small helper function for modular exponentiation +def _modexpt(base: int, exponent: int, modulo_value: int) -> int: + """ + Returns the modular exponentiation, that is the value + of `base ** exponent % modulo_value`, without calculating + the actual number. + >>> _modexpt(2, 4, 10) + 6 + >>> _modexpt(2, 1024, 100) + 16 + >>> _modexpt(13, 65535, 7) + 6 + """ + + if exponent == 1: + return base + if exponent % 2 == 0: + x = _modexpt(base, exponent / 2, modulo_value) % modulo_value + return (x * x) % modulo_value + else: + return (base * _modexpt(base, exponent - 1, modulo_value)) % modulo_value + + +def solution(base: int = 1777, height: int = 1855, digits: int = 8) -> int: + """ + Returns the last 8 digits of the hyperexponentiation of base by + height, i.e. the number base↑↑height: + + >>> solution(base=3, height=2) + 27 + >>> solution(base=3, height=3) + 97484987 + >>> solution(base=123, height=456, digits=4) + 2547 + """ + + # calculate base↑↑height by right-assiciative repeated modular + # exponentiation + result = base + for i in range(1, height): + result = _modexpt(base, result, 10 ** digits) + + return result + + +if __name__ == "__main__": + print(f"{solution() = }") From 28d33f4b2d72b6de8730d4526de12975364fdca0 Mon Sep 17 00:00:00 2001 From: Peter Yao Date: Fri, 20 Nov 2020 18:42:07 -0800 Subject: [PATCH 1012/1071] Project Euler 70 Solution (#3041) * Add solution for Project Euler 70, Fixes: #2695 * Remove parameter from solution() * Add tests for all functions, add fstring and positional arg for solution() * Rename directory to 070 * Move up explanation to module code block * Move solution() below helper functions, rename variables * Remove whitespace from defining min_numerator * Add whitespace * Improve type hints with typing.List Co-authored-by: Dhruv Manilawala --- DIRECTORY.md | 2 + project_euler/problem_070/__init__.py | 0 project_euler/problem_070/sol1.py | 119 ++++++++++++++++++++++++++ 3 files changed, 121 insertions(+) create mode 100644 project_euler/problem_070/__init__.py create mode 100644 project_euler/problem_070/sol1.py diff --git a/DIRECTORY.md b/DIRECTORY.md index cd8f6fb8578c..71da6a402b31 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -697,6 +697,8 @@ * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_067/sol1.py) * Problem 069 * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_069/sol1.py) + * Problem 070 + * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_070/sol1.py) * Problem 071 * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_071/sol1.py) * Problem 072 diff --git a/project_euler/problem_070/__init__.py b/project_euler/problem_070/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/project_euler/problem_070/sol1.py b/project_euler/problem_070/sol1.py new file mode 100644 index 000000000000..9d27119ba95c --- /dev/null +++ b/project_euler/problem_070/sol1.py @@ -0,0 +1,119 @@ +""" +Project Euler Problem 70: https://projecteuler.net/problem=70 + +Euler's Totient function, φ(n) [sometimes called the phi function], is used to +determine the number of positive numbers less than or equal to n which are +relatively prime to n. For example, as 1, 2, 4, 5, 7, and 8, are all less than +nine and relatively prime to nine, φ(9)=6. + +The number 1 is considered to be relatively prime to every positive number, so +φ(1)=1. + +Interestingly, φ(87109)=79180, and it can be seen that 87109 is a permutation +of 79180. + +Find the value of n, 1 < n < 10^7, for which φ(n) is a permutation of n and +the ratio n/φ(n) produces a minimum. + +----- + +This is essentially brute force. Calculate all totients up to 10^7 and +find the minimum ratio of n/φ(n) that way. To minimize the ratio, we want +to minimize n and maximize φ(n) as much as possible, so we can store the +minimum fraction's numerator and denominator and calculate new fractions +with each totient to compare against. To avoid dividing by zero, I opt to +use cross multiplication. + +References: +Finding totients +https://en.wikipedia.org/wiki/Euler's_totient_function#Euler's_product_formula +""" +from typing import List + + +def get_totients(max_one: int) -> List[int]: + """ + Calculates a list of totients from 0 to max_one exclusive, using the + definition of Euler's product formula. + + >>> get_totients(5) + [0, 1, 1, 2, 2] + + >>> get_totients(10) + [0, 1, 1, 2, 2, 4, 2, 6, 4, 6] + """ + totients = [0] * max_one + + for i in range(0, max_one): + totients[i] = i + + for i in range(2, max_one): + if totients[i] == i: + for j in range(i, max_one, i): + totients[j] -= totients[j] // i + + return totients + + +def has_same_digits(num1: int, num2: int) -> bool: + """ + Return True if num1 and num2 have the same frequency of every digit, False + otherwise. + + digits[] is a frequency table where the index represents the digit from + 0-9, and the element stores the number of appearances. Increment the + respective index every time you see the digit in num1, and decrement if in + num2. At the end, if the numbers have the same digits, every index must + contain 0. + + >>> has_same_digits(123456789, 987654321) + True + + >>> has_same_digits(123, 12) + False + + >>> has_same_digits(1234566, 123456) + False + """ + digits = [0] * 10 + + while num1 > 0 and num2 > 0: + digits[num1 % 10] += 1 + digits[num2 % 10] -= 1 + num1 //= 10 + num2 //= 10 + + for digit in digits: + if digit != 0: + return False + + return True + + +def solution(max: int = 10000000) -> int: + """ + Finds the value of n from 1 to max such that n/φ(n) produces a minimum. + + >>> solution(100) + 21 + + >>> solution(10000) + 4435 + """ + + min_numerator = 1 # i + min_denominator = 0 # φ(i) + totients = get_totients(max + 1) + + for i in range(2, max + 1): + t = totients[i] + + if i * min_denominator < min_numerator * t and has_same_digits(i, t): + min_numerator = i + min_denominator = t + + return min_numerator + + +if __name__ == "__main__": + print(f"{solution() = }") From c938e7311ff0ab2c06399348aa261b8d2ea70d3e Mon Sep 17 00:00:00 2001 From: fpringle Date: Sat, 21 Nov 2020 03:52:26 +0100 Subject: [PATCH 1013/1071] Added solution for Project Euler problem 129. (#3113) * Added solution for Project Euler problem 129. * Added doctest for solution() in project_euler/problem_129/sol1.py * Update formatting. Reference: #3256 * More descriptive function and variable names, more doctests. --- project_euler/problem_129/__init__.py | 0 project_euler/problem_129/sol1.py | 57 +++++++++++++++++++++++++++ 2 files changed, 57 insertions(+) create mode 100644 project_euler/problem_129/__init__.py create mode 100644 project_euler/problem_129/sol1.py diff --git a/project_euler/problem_129/__init__.py b/project_euler/problem_129/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/project_euler/problem_129/sol1.py b/project_euler/problem_129/sol1.py new file mode 100644 index 000000000000..8afe82df162e --- /dev/null +++ b/project_euler/problem_129/sol1.py @@ -0,0 +1,57 @@ +""" +Project Euler Problem 129: https://projecteuler.net/problem=129 + +A number consisting entirely of ones is called a repunit. We shall define R(k) to be +a repunit of length k; for example, R(6) = 111111. + +Given that n is a positive integer and GCD(n, 10) = 1, it can be shown that there +always exists a value, k, for which R(k) is divisible by n, and let A(n) be the least +such value of k; for example, A(7) = 6 and A(41) = 5. + +The least value of n for which A(n) first exceeds ten is 17. + +Find the least value of n for which A(n) first exceeds one-million. +""" + + +def least_divisible_repunit(divisor: int) -> int: + """ + Return the least value k such that the Repunit of length k is divisible by divisor. + >>> least_divisible_repunit(7) + 6 + >>> least_divisible_repunit(41) + 5 + >>> least_divisible_repunit(1234567) + 34020 + """ + if divisor % 5 == 0 or divisor % 2 == 0: + return 0 + repunit = 1 + repunit_index = 1 + while repunit: + repunit = (10 * repunit + 1) % divisor + repunit_index += 1 + return repunit_index + + +def solution(limit: int = 1000000) -> int: + """ + Return the least value of n for which least_divisible_repunit(n) + first exceeds limit. + >>> solution(10) + 17 + >>> solution(100) + 109 + >>> solution(1000) + 1017 + """ + divisor = limit - 1 + if divisor % 2 == 0: + divisor += 1 + while least_divisible_repunit(divisor) <= limit: + divisor += 2 + return divisor + + +if __name__ == "__main__": + print(f"{solution() = }") From 06f01c0eeb4ddae0c829da610c2e9b5283893727 Mon Sep 17 00:00:00 2001 From: Dhruv Manilawala Date: Sat, 21 Nov 2020 09:04:49 +0530 Subject: [PATCH 1014/1071] Remove stale action workflow file (#3915) --- .github/workflows/stale.yml | 31 ------------------------------- 1 file changed, 31 deletions(-) delete mode 100644 .github/workflows/stale.yml diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml deleted file mode 100644 index 42353d233a29..000000000000 --- a/.github/workflows/stale.yml +++ /dev/null @@ -1,31 +0,0 @@ -# name: Mark/Close stale issues and pull requests -# on: -# schedule: -# - cron: "0 * * * *" # Run every hour -# jobs: -# stale: -# runs-on: ubuntu-latest -# steps: -# - uses: actions/stale@v3.0.13 -# with: -# repo-token: ${{ secrets.GITHUB_TOKEN }} -# days-before-stale: 30 -# days-before-close: 7 -# stale-issue-message: > -# This issue has been automatically marked as stale because it has not had -# recent activity. It will be closed if no further activity occurs. Thank you -# for your contributions. -# close-issue-message: > -# Please reopen this issue once you add more information and updates here. -# If this is not the case and you need some help, feel free to seek help -# from our [Gitter](https://gitter.im/TheAlgorithms) or ping one of the -# reviewers. Thank you for your contributions! -# stale-pr-message: > -# This pull request has been automatically marked as stale because it has not had -# recent activity. It will be closed if no further activity occurs. Thank you -# for your contributions. -# close-pr-message: > -# Please reopen this pull request once you commit the changes requested -# or make improvements on the code. If this is not the case and you need -# some help, feel free to seek help from our [Gitter](https://gitter.im/TheAlgorithms) -# or ping one of the reviewers. Thank you for your contributions! From b55e132b8024c71ff186b0c741fb11a070ee2265 Mon Sep 17 00:00:00 2001 From: Cory Metcalfe Date: Fri, 20 Nov 2020 23:29:29 -0600 Subject: [PATCH 1015/1071] Add solution for Project Euler: Problem 89 (#2948) * add solution for euler problem 89 * updates to accommodate euler solution guideline updates * use more descriptive vars * updating DIRECTORY.md Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> --- DIRECTORY.md | 6 + project_euler/problem_089/__init__.py | 1 + .../problem_089/numeralcleanup_test.txt | 5 + project_euler/problem_089/p089_roman.txt | 1000 +++++++++++++++++ project_euler/problem_089/sol1.py | 141 +++ 5 files changed, 1153 insertions(+) create mode 100644 project_euler/problem_089/__init__.py create mode 100644 project_euler/problem_089/numeralcleanup_test.txt create mode 100644 project_euler/problem_089/p089_roman.txt create mode 100644 project_euler/problem_089/sol1.py diff --git a/DIRECTORY.md b/DIRECTORY.md index 71da6a402b31..2b3f3073c3d4 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -717,6 +717,8 @@ * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_081/sol1.py) * Problem 087 * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_087/sol1.py) + * Problem 089 + * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_089/sol1.py) * Problem 091 * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_091/sol1.py) * Problem 097 @@ -735,10 +737,14 @@ * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_123/sol1.py) * Problem 125 * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_125/sol1.py) + * Problem 129 + * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_129/sol1.py) * Problem 173 * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_173/sol1.py) * Problem 174 * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_174/sol1.py) + * Problem 188 + * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_188/sol1.py) * Problem 191 * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_191/sol1.py) * Problem 203 diff --git a/project_euler/problem_089/__init__.py b/project_euler/problem_089/__init__.py new file mode 100644 index 000000000000..792d6005489e --- /dev/null +++ b/project_euler/problem_089/__init__.py @@ -0,0 +1 @@ +# diff --git a/project_euler/problem_089/numeralcleanup_test.txt b/project_euler/problem_089/numeralcleanup_test.txt new file mode 100644 index 000000000000..06142142cca9 --- /dev/null +++ b/project_euler/problem_089/numeralcleanup_test.txt @@ -0,0 +1,5 @@ +IIII +IV +IIIIIIIIII +X +VIIIII diff --git a/project_euler/problem_089/p089_roman.txt b/project_euler/problem_089/p089_roman.txt new file mode 100644 index 000000000000..50651c355a5b --- /dev/null +++ b/project_euler/problem_089/p089_roman.txt @@ -0,0 +1,1000 @@ +MMMMDCLXXII +MMDCCCLXXXIII +MMMDLXVIIII +MMMMDXCV +DCCCLXXII +MMCCCVI +MMMCDLXXXVII +MMMMCCXXI +MMMCCXX +MMMMDCCCLXXIII +MMMCCXXXVII +MMCCCLXXXXIX +MDCCCXXIIII +MMCXCVI +CCXCVIII +MMMCCCXXXII +MDCCXXX +MMMDCCCL +MMMMCCLXXXVI +MMDCCCXCVI +MMMDCII +MMMCCXII +MMMMDCCCCI +MMDCCCXCII +MDCXX +CMLXXXVII +MMMXXI +MMMMCCCXIV +MLXXII +MCCLXXVIIII +MMMMCCXXXXI +MMDCCCLXXII +MMMMXXXI +MMMDCCLXXX +MMDCCCLXXIX +MMMMLXXXV +MCXXI +MDCCCXXXVII +MMCCCLXVII +MCDXXXV +CCXXXIII +CMXX +MMMCLXIV +MCCCLXXXVI +DCCCXCVIII +MMMDCCCCXXXIV +CDXVIIII +MMCCXXXV +MDCCCXXXII +MMMMD +MMDCCLXIX +MMMMCCCLXXXXVI +MMDCCXLII +MMMDCCCVIIII +DCCLXXXIIII +MDCCCCXXXII +MMCXXVII +DCCCXXX +CCLXIX +MMMXI +MMMMCMLXXXXVIII +MMMMDLXXXVII +MMMMDCCCLX +MMCCLIV +CMIX +MMDCCCLXXXIIII +CLXXXII +MMCCCCXXXXV +MMMMDLXXXVIIII +MMMDCCCXXI +MMDCCCCLXXVI +MCCCCLXX +MMCDLVIIII +MMMDCCCLIX +MMMMCCCCXIX +MMMDCCCLXXV +XXXI +CDLXXXIII +MMMCXV +MMDCCLXIII +MMDXXX +MMMMCCCLVII +MMMDCI +MMMMCDLXXXIIII +MMMMCCCXVI +CCCLXXXVIII +MMMMCML +MMMMXXIV +MMMCCCCXXX +DCCX +MMMCCLX +MMDXXXIII +CCCLXIII +MMDCCXIII +MMMCCCXLIV +CLXXXXI +CXVI +MMMMCXXXIII +CLXX +DCCCXVIII +MLXVII +DLXXXX +MMDXXI +MMMMDLXXXXVIII +MXXII +LXI +DCCCCXLIII +MMMMDV +MMMMXXXIV +MDCCCLVIII +MMMCCLXXII +MMMMDCCXXXVI +MMMMLXXXIX +MDCCCLXXXI +MMMMDCCCXV +MMMMCCCCXI +MMMMCCCLIII +MDCCCLXXI +MMCCCCXI +MLXV +MMCDLXII +MMMMDXXXXII +MMMMDCCCXL +MMMMCMLVI +CCLXXXIV +MMMDCCLXXXVI +MMCLII +MMMCCCCXV +MMLXXXIII +MMMV +MMMV +DCCLXII +MMDCCCCXVI +MMDCXLVIII +CCLIIII +CCCXXV +MMDCCLXXXVIIII +MMMMDCLXXVIII +MMMMDCCCXCI +MMMMCCCXX +MMCCXLV +MMMDCCCLXIX +MMCCLXIIII +MMMDCCCXLIX +MMMMCCCLXIX +CMLXXXXI +MCMLXXXIX +MMCDLXI +MMDCLXXVIII +MMMMDCCLXI +MCDXXV +DL +CCCLXXII +MXVIIII +MCCCCLXVIII +CIII +MMMDCCLXXIIII +MMMDVIII +MMMMCCCLXXXXVII +MMDXXVII +MMDCCLXXXXV +MMMMCXLVI +MMMDCCLXXXII +MMMDXXXVI +MCXXII +CLI +DCLXXXIX +MMMCLI +MDCLXIII +MMMMDCCXCVII +MMCCCLXXXV +MMMDCXXVIII +MMMCDLX +MMMCMLII +MMMIV +MMMMDCCCLVIII +MMMDLXXXVIII +MCXXIV +MMMMLXXVI +CLXXIX +MMMCCCCXXVIIII +DCCLXXXV +MMMDCCCVI +LI +CLXXXVI +MMMMCCCLXXVI +MCCCLXVI +CCXXXIX +MMDXXXXI +MMDCCCXLI +DCCCLXXXVIII +MMMMDCCCIV +MDCCCCXV +MMCMVI +MMMMCMLXXXXV +MMDCCLVI +MMMMCCXLVIII +DCCCCIIII +MMCCCCIII +MMMDCCLXXXVIIII +MDCCCLXXXXV +DVII +MMMV +DCXXV +MMDCCCXCV +DCVIII +MMCDLXVI +MCXXVIII +MDCCXCVIII +MMDCLX +MMMDCCLXIV +MMCDLXXVII +MMDLXXXIIII +MMMMCCCXXII +MMMDCCCXLIIII +DCCCCLXVII +MMMCLXXXXIII +MCCXV +MMMMDCXI +MMMMDCLXXXXV +MMMCCCLII +MMCMIX +MMDCCXXV +MMDLXXXVI +MMMMDCXXVIIII +DCCCCXXXVIIII +MMCCXXXIIII +MMDCCLXXVIII +MDCCLXVIIII +MMCCLXXXV +MMMMDCCCLXXXVIII +MMCMXCI +MDXLII +MMMMDCCXIV +MMMMLI +DXXXXIII +MMDCCXI +MMMMCCLXXXIII +MMMDCCCLXXIII +MDCLVII +MMCD +MCCCXXVII +MMMMDCCIIII +MMMDCCXLVI +MMMCLXXXVII +MMMCCVIIII +MCCCCLXXIX +DL +DCCCLXXVI +MMDXCI +MMMMDCCCCXXXVI +MMCII +MMMDCCCXXXXV +MMMCDXLV +MMDCXXXXIV +MMD +MDCCCLXXXX +MMDCXLIII +MMCCXXXII +MMDCXXXXVIIII +DCCCLXXI +MDXCVIIII +MMMMCCLXXVIII +MDCLVIIII +MMMCCCLXXXIX +MDCLXXXV +MDLVIII +MMMMCCVII +MMMMDCXIV +MMMCCCLXIIII +MMIIII +MMMMCCCLXXIII +CCIII +MMMCCLV +MMMDXIII +MMMCCCXC +MMMDCCCXXI +MMMMCCCCXXXII +CCCLVI +MMMCCCLXXXVI +MXVIIII +MMMCCCCXIIII +CLXVII +MMMCCLXX +CCCCLXIV +MMXXXXII +MMMMCCLXXXX +MXL +CCXVI +CCCCLVIIII +MMCCCII +MCCCLVIII +MMMMCCCX +MCDLXXXXIV +MDCCCXIII +MMDCCCXL +MMMMCCCXXIII +DXXXIV +CVI +MMMMDCLXXX +DCCCVII +MMCMLXIIII +MMMDCCCXXXIII +DCCC +MDIII +MMCCCLXVI +MMMCCCCLXXI +MMDCCCCXVIII +CCXXXVII +CCCXXV +MDCCCXII +MMMCMV +MMMMCMXV +MMMMDCXCI +DXXI +MMCCXLVIIII +MMMMCMLII +MDLXXX +MMDCLXVI +CXXI +MMMDCCCLIIII +MMMCXXI +MCCIII +MMDCXXXXI +CCXCII +MMMMDXXXV +MMMCCCLXV +MMMMDLXV +MMMCCCCXXXII +MMMCCCVIII +DCCCCLXXXXII +MMCLXIV +MMMMCXI +MLXXXXVII +MMMCDXXXVIII +MDXXII +MLV +MMMMDLXVI +MMMCXII +XXXIII +MMMMDCCCXXVI +MMMLXVIIII +MMMLX +MMMCDLXVII +MDCCCLVII +MMCXXXVII +MDCCCCXXX +MMDCCCLXIII +MMMMDCXLIX +MMMMCMXLVIII +DCCCLXXVIIII +MDCCCLIII +MMMCMLXI +MMMMCCLXI +MMDCCCLIII +MMMDCCCVI +MMDXXXXIX +MMCLXXXXV +MMDXXX +MMMXIII +DCLXXIX +DCCLXII +MMMMDCCLXVIII +MDCCXXXXIII +CCXXXII +MMMMDCXXV +MMMCCCXXVIII +MDCVIII +MMMCLXXXXIIII +CLXXXI +MDCCCCXXXIII +MMMMDCXXX +MMMDCXXIV +MMMCCXXXVII +MCCCXXXXIIII +CXVIII +MMDCCCCIV +MMMMCDLXXV +MMMDLXIV +MDXCIII +MCCLXXXI +MMMDCCCXXIV +MCXLIII +MMMDCCCI +MCCLXXX +CCXV +MMDCCLXXI +MMDLXXXIII +MMMMDCXVII +MMMCMLXV +MCLXVIII +MMMMCCLXXVI +MMMDCCLXVIIII +MMMMDCCCIX +DLXXXXIX +DCCCXXII +MMMMIII +MMMMCCCLXXVI +DCCCXCIII +DXXXI +MXXXIIII +CCXII +MMMDCCLXXXIIII +MMMCXX +MMMCMXXVII +DCCCXXXX +MMCDXXXVIIII +MMMMDCCXVIII +LV +MMMDCCCCVI +MCCCII +MMCMLXVIIII +MDCCXI +MMMMDLXVII +MMCCCCLXI +MMDCCV +MMMCCCXXXIIII +MMMMDI +MMMDCCCXCV +MMDCCLXXXXI +MMMDXXVI +MMMDCCCLVI +MMDCXXX +MCCCVII +MMMMCCCLXII +MMMMXXV +MMCMXXV +MMLVI +MMDXXX +MMMMCVII +MDC +MCCIII +MMMMDCC +MMCCLXXV +MMDCCCXXXXVI +MMMMCCCLXV +CDXIIII +MLXIIII +CCV +MMMCMXXXI +CCCCLXVI +MDXXXII +MMMMCCCLVIII +MMV +MMMCLII +MCMLI +MMDCCXX +MMMMCCCCXXXVI +MCCLXXXI +MMMCMVI +DCCXXX +MMMMCCCLXV +DCCCXI +MMMMDCCCXIV +CCCXXI +MMDLXXV +CCCCLXXXX +MCCCLXXXXII +MMDCIX +DCCXLIIII +DXIV +MMMMCLII +CDLXI +MMMCXXVII +MMMMDCCCCLXIII +MMMDCLIIII +MCCCCXXXXII +MMCCCLX +CCCCLIII +MDCCLXXVI +MCMXXIII +MMMMDLXXVIII +MMDCCCCLX +MMMCCCLXXXX +MMMCDXXVI +MMMDLVIII +CCCLXI +MMMMDCXXII +MMDCCCXXI +MMDCCXIII +MMMMCLXXXVI +MDCCCCXXVI +MDV +MMDCCCCLXXVI +MMMMCCXXXVII +MMMDCCLXXVIIII +MMMCCCCLXVII +DCCXLI +MMCLXXXVIII +MCCXXXVI +MMDCXLVIII +MMMMCXXXII +MMMMDCCLXVI +MMMMCMLI +MMMMCLXV +MMMMDCCCXCIV +MCCLXXVII +LXXVIIII +DCCLII +MMMCCCXCVI +MMMCLV +MMDCCCXXXXVIII +DCCCXV +MXC +MMDCCLXXXXVII +MMMMCML +MMDCCCLXXVIII +DXXI +MCCCXLI +DCLXXXXI +MMCCCLXXXXVIII +MDCCCCLXXVIII +MMMMDXXV +MMMDCXXXVI +MMMCMXCVII +MMXVIIII +MMMDCCLXXIV +MMMCXXV +DXXXVIII +MMMMCLXVI +MDXII +MMCCCLXX +CCLXXI +DXIV +MMMCLIII +DLII +MMMCCCXLIX +MMCCCCXXVI +MMDCXLIII +MXXXXII +CCCLXXXV +MDCLXXVI +MDCXII +MMMCCCLXXXIII +MMDCCCCLXXXII +MMMMCCCLXXXV +MMDCXXI +DCCCXXX +MMMDCCCCLII +MMMDCCXXII +MMMMCDXCVIII +MMMCCLXVIIII +MMXXV +MMMMCDXIX +MMMMCCCX +MMMCCCCLXVI +MMMMDCLXXVIIII +MMMMDCXXXXIV +MMMCMXII +MMMMXXXIII +MMMMDLXXXII +DCCCLIV +MDXVIIII +MMMCLXXXXV +CCCCXX +MMDIX +MMCMLXXXVIII +DCCXLIII +DCCLX +D +MCCCVII +MMMMCCCLXXXIII +MDCCCLXXIIII +MMMDCCCCLXXXVII +MMMMCCCVII +MMMDCCLXXXXVI +CDXXXIV +MCCLXVIII +MMMMDLX +MMMMDXII +MMMMCCCCLIIII +MCMLXXXXIII +MMMMDCCCIII +MMDCLXXXIII +MDCCCXXXXIV +XXXXVII +MMMDCCCXXXII +MMMDCCCXLII +MCXXXV +MDCXXVIIII +MMMCXXXXIIII +MMMMCDXVII +MMMDXXIII +MMMMCCCCLXI +DCLXXXXVIIII +LXXXXI +CXXXIII +MCDX +MCCLVII +MDCXXXXII +MMMCXXIV +MMMMLXXXX +MMDCCCCXLV +MLXXX +MMDCCCCLX +MCDLIII +MMMCCCLXVII +MMMMCCCLXXIV +MMMDCVIII +DCCCCXXIII +MMXCI +MMDCCIV +MMMMDCCCXXXIV +CCCLXXI +MCCLXXXII +MCMIII +CCXXXI +DCCXXXVIII +MMMMDCCXLVIIII +MMMMCMXXXV +DCCCLXXV +DCCXCI +MMMMDVII +MMMMDCCCLXVIIII +CCCXCV +MMMMDCCXX +MCCCCII +MMMCCCXC +MMMCCCII +MMDCCLXXVII +MMDCLIIII +CCXLIII +MMMDCXVIII +MMMCCCIX +MCXV +MMCCXXV +MLXXIIII +MDCCXXVI +MMMCCCXX +MMDLXX +MMCCCCVI +MMDCCXX +MMMMDCCCCXCV +MDCCCXXXII +MMMMDCCCCXXXX +XCIV +MMCCCCLX +MMXVII +MLXXI +MMMDXXVIII +MDCCCCII +MMMCMLVII +MMCLXXXXVIII +MDCCCCLV +MCCCCLXXIIII +MCCCLII +MCDXLVI +MMMMDXVIII +DCCLXXXIX +MMMDCCLXIV +MDCCCCXLIII +CLXXXXV +MMMMCCXXXVI +MMMDCCCXXI +MMMMCDLXXVII +MCDLIII +MMCCXLVI +DCCCLV +MCDLXX +DCLXXVIII +MMDCXXXIX +MMMMDCLX +MMDCCLI +MMCXXXV +MMMCCXII +MMMMCMLXII +MMMMCCV +MCCCCLXIX +MMMMCCIII +CLXVII +MCCCLXXXXIIII +MMMMDCVIII +MMDCCCLXI +MMLXXIX +CMLXIX +MMDCCCXLVIIII +DCLXII +MMMCCCXLVII +MDCCCXXXV +MMMMDCCXCVI +DCXXX +XXVI +MMLXIX +MMCXI +DCXXXVII +MMMMCCCXXXXVIII +MMMMDCLXI +MMMMDCLXXIIII +MMMMVIII +MMMMDCCCLXII +MDCXCI +MMCCCXXIIII +CCCCXXXXV +MMDCCCXXI +MCVI +MMDCCLXVIII +MMMMCXL +MLXVIII +CMXXVII +CCCLV +MDCCLXXXIX +MMMCCCCLXV +MMDCCLXII +MDLXVI +MMMCCCXVIII +MMMMCCLXXXI +MMCXXVII +MMDCCCLXVIII +MMMCXCII +MMMMDCLVIII +MMMMDCCCXXXXII +MMDCCCCLXXXXVI +MDCCXL +MDCCLVII +MMMMDCCCLXXXVI +DCCXXXIII +MMMMDCCCCLXXXV +MMCCXXXXVIII +MMMCCLXXVIII +MMMDCLXXVIII +DCCCI +MMMMLXXXXVIIII +MMMCCCCLXXII +MMCLXXXVII +CCLXVI +MCDXLIII +MMCXXVIII +MDXIV +CCCXCVIII +CLXXVIII +MMCXXXXVIIII +MMMDCLXXXIV +CMLVIII +MCDLIX +MMMMDCCCXXXII +MMMMDCXXXIIII +MDCXXI +MMMDCXLV +MCLXXVIII +MCDXXII +IV +MCDLXXXXIII +MMMMDCCLXV +CCLI +MMMMDCCCXXXVIII +DCLXII +MCCCLXVII +MMMMDCCCXXXVI +MMDCCXLI +MLXI +MMMCDLXVIII +MCCCCXCIII +XXXIII +MMMDCLXIII +MMMMDCL +DCCCXXXXIIII +MMDLVII +DXXXVII +MCCCCXXIIII +MCVII +MMMMDCCXL +MMMMCXXXXIIII +MCCCCXXIV +MMCLXVIII +MMXCIII +MDCCLXXX +MCCCLIIII +MMDCLXXI +MXI +MCMLIV +MMMCCIIII +DCCLXXXVIIII +MDCLIV +MMMDCXIX +CMLXXXI +DCCLXXXVII +XXV +MMMXXXVI +MDVIIII +CLXIII +MMMCDLVIIII +MMCCCCVII +MMMLXX +MXXXXII +MMMMCCCLXVIII +MMDCCCXXVIII +MMMMDCXXXXI +MMMMDCCCXXXXV +MMMXV +MMMMCCXVIIII +MMDCCXIIII +MMMXXVII +MDCCLVIIII +MMCXXIIII +MCCCLXXIV +DCLVIII +MMMLVII +MMMCXLV +MMXCVII +MMMCCCLXXXVII +MMMMCCXXII +DXII +MMMDLV +MCCCLXXVIII +MMMCLIIII +MMMMCLXXXX +MMMCLXXXIIII +MDCXXIII +MMMMCCXVI +MMMMDLXXXIII +MMMDXXXXIII +MMMMCCCCLV +MMMDLXXXI +MMMCCLXXVI +MMMMXX +MMMMDLVI +MCCCCLXXX +MMMXXII +MMXXII +MMDCCCCXXXI +MMMDXXV +MMMDCLXXXVIIII +MMMDLXXXXVII +MDLXIIII +CMXC +MMMXXXVIII +MDLXXXVIII +MCCCLXXVI +MMCDLIX +MMDCCCXVIII +MDCCCXXXXVI +MMMMCMIV +MMMMDCIIII +MMCCXXXV +XXXXVI +MMMMCCXVII +MMCCXXIV +MCMLVIIII +MLXXXIX +MMMMLXXXIX +CLXXXXIX +MMMDCCCCLVIII +MMMMCCLXXIII +MCCCC +DCCCLIX +MMMCCCLXXXII +MMMCCLXVIIII +MCLXXXV +CDLXXXVII +DCVI +MMX +MMCCXIII +MMMMDCXX +MMMMXXVIII +DCCCLXII +MMMMCCCXLIII +MMMMCLXV +DXCI +MMMMCLXXX +MMMDCCXXXXI +MMMMXXXXVI +DCLX +MMMCCCXI +MCCLXXX +MMCDLXXII +DCCLXXI +MMMCCCXXXVI +MCCCCLXXXVIIII +CDLVIII +DCCLVI +MMMMDCXXXVIII +MMCCCLXXXIII +MMMMDCCLXXV +MMMXXXVI +CCCLXXXXIX +CV +CCCCXIII +CCCCXVI +MDCCCLXXXIIII +MMDCCLXXXII +MMMMCCCCLXXXI +MXXV +MMCCCLXXVIIII +MMMCCXII +MMMMCCXXXIII +MMCCCLXXXVI +MMMDCCCLVIIII +MCCXXXVII +MDCLXXV +XXXV +MMDLI +MMMCCXXX +MMMMCXXXXV +CCCCLIX +MMMMDCCCLXXIII +MMCCCXVII +DCCCXVI +MMMCCCXXXXV +MDCCCCXCV +CLXXXI +MMMMDCCLXX +MMMDCCCIII +MMCLXXVII +MMMDCCXXIX +MMDCCCXCIIII +MMMCDXXIIII +MMMMXXVIII +MMMMDCCCCLXVIII +MDCCCXX +MMMMCDXXI +MMMMDLXXXIX +CCXVI +MDVIII +MMCCLXXI +MMMDCCCLXXI +MMMCCCLXXVI +MMCCLXI +MMMMDCCCXXXIV +DLXXXVI +MMMMDXXXII +MMMXXIIII +MMMMCDIV +MMMMCCCXLVIII +MMMMCXXXVIII +MMMCCCLXVI +MDCCXVIII +MMCXX +CCCLIX +MMMMDCCLXXII +MDCCCLXXV +MMMMDCCCXXIV +DCCCXXXXVIII +MMMDCCCCXXXVIIII +MMMMCCXXXV +MDCLXXXIII +MMCCLXXXIV +MCLXXXXIIII +DXXXXIII +MCCCXXXXVIII +MMCLXXIX +MMMMCCLXIV +MXXII +MMMCXIX +MDCXXXVII +MMDCCVI +MCLXXXXVIII +MMMCXVI +MCCCLX +MMMCDX +CCLXVIIII +MMMCCLX +MCXXVIII +LXXXII +MCCCCLXXXI +MMMI +MMMCCCLXIV +MMMCCCXXVIIII +CXXXVIII +MMCCCXX +MMMCCXXVIIII +MCCLXVI +MMMCCCCXXXXVI +MMDCCXCIX +MCMLXXI +MMCCLXVIII +CDLXXXXIII +MMMMDCCXXII +MMMMDCCLXXXVII +MMMDCCLIV +MMCCLXIII +MDXXXVII +DCCXXXIIII +MCII +MMMDCCCLXXI +MMMLXXIII +MDCCCLIII +MMXXXVIII +MDCCXVIIII +MDCCCCXXXVII +MMCCCXVI +MCMXXII +MMMCCCLVIII +MMMMDCCCXX +MCXXIII +MMMDLXI +MMMMDXXII +MDCCCX +MMDXCVIIII +MMMDCCCCVIII +MMMMDCCCCXXXXVI +MMDCCCXXXV +MMCXCIV +MCMLXXXXIII +MMMCCCLXXVI +MMMMDCLXXXV +CMLXIX +DCXCII +MMXXVIII +MMMMCCCXXX +XXXXVIIII \ No newline at end of file diff --git a/project_euler/problem_089/sol1.py b/project_euler/problem_089/sol1.py new file mode 100644 index 000000000000..11582aa4ab1a --- /dev/null +++ b/project_euler/problem_089/sol1.py @@ -0,0 +1,141 @@ +""" +Project Euler Problem 89: https://projecteuler.net/problem=89 + +For a number written in Roman numerals to be considered valid there are basic rules +which must be followed. Even though the rules allow some numbers to be expressed in +more than one way there is always a "best" way of writing a particular number. + +For example, it would appear that there are at least six ways of writing the number +sixteen: + +IIIIIIIIIIIIIIII +VIIIIIIIIIII +VVIIIIII +XIIIIII +VVVI +XVI + +However, according to the rules only XIIIIII and XVI are valid, and the last example +is considered to be the most efficient, as it uses the least number of numerals. + +The 11K text file, roman.txt (right click and 'Save Link/Target As...'), contains one +thousand numbers written in valid, but not necessarily minimal, Roman numerals; see +About... Roman Numerals for the definitive rules for this problem. + +Find the number of characters saved by writing each of these in their minimal form. + +Note: You can assume that all the Roman numerals in the file contain no more than four +consecutive identical units. +""" + +import os + +SYMBOLS = {"I": 1, "V": 5, "X": 10, "L": 50, "C": 100, "D": 500, "M": 1000} + + +def parse_roman_numerals(numerals: str) -> int: + """ + Converts a string of roman numerals to an integer. + e.g. + >>> parse_roman_numerals("LXXXIX") + 89 + >>> parse_roman_numerals("IIII") + 4 + """ + + total_value = 0 + + index = 0 + while index < len(numerals) - 1: + current_value = SYMBOLS[numerals[index]] + next_value = SYMBOLS[numerals[index + 1]] + if current_value < next_value: + total_value -= current_value + else: + total_value += current_value + index += 1 + total_value += SYMBOLS[numerals[index]] + + return total_value + + +def generate_roman_numerals(num: int) -> str: + """ + Generates a string of roman numerals for a given integer. + e.g. + >>> generate_roman_numerals(89) + 'LXXXIX' + >>> generate_roman_numerals(4) + 'IV' + """ + + numerals = "" + + m_count = num // 1000 + numerals += m_count * "M" + num %= 1000 + + c_count = num // 100 + if c_count == 9: + numerals += "CM" + c_count -= 9 + elif c_count == 4: + numerals += "CD" + c_count -= 4 + if c_count >= 5: + numerals += "D" + c_count -= 5 + numerals += c_count * "C" + num %= 100 + + x_count = num // 10 + if x_count == 9: + numerals += "XC" + x_count -= 9 + elif x_count == 4: + numerals += "XL" + x_count -= 4 + if x_count >= 5: + numerals += "L" + x_count -= 5 + numerals += x_count * "X" + num %= 10 + + if num == 9: + numerals += "IX" + num -= 9 + elif num == 4: + numerals += "IV" + num -= 4 + if num >= 5: + numerals += "V" + num -= 5 + numerals += num * "I" + + return numerals + + +def solution(roman_numerals_filename: str = "/p089_roman.txt") -> int: + """ + Calculates and returns the answer to project euler problem 89. + + >>> solution("/numeralcleanup_test.txt") + 16 + """ + + savings = 0 + + file1 = open(os.path.dirname(__file__) + roman_numerals_filename, "r") + lines = file1.readlines() + for line in lines: + original = line.strip() + num = parse_roman_numerals(original) + shortened = generate_roman_numerals(num) + savings += len(original) - len(shortened) + + return savings + + +if __name__ == "__main__": + + print(f"{solution() = }") From fa364dfd274349ab3e674ea97780a7b9977cb7ef Mon Sep 17 00:00:00 2001 From: Akash G Krishnan Date: Sat, 21 Nov 2020 12:28:52 +0530 Subject: [PATCH 1016/1071] Changed how the Visited nodes are tracked (#3811) Updated the code to track visited Nodes with Set data structure instead of Lists to bring down the lookup time in visited from O(N) to O(1) as doing O(N) lookup each time in the visited List will become significantly slow when the graph grows --- graphs/bfs_shortest_path.py | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/graphs/bfs_shortest_path.py b/graphs/bfs_shortest_path.py index 1655ca64208d..754ba403537e 100644 --- a/graphs/bfs_shortest_path.py +++ b/graphs/bfs_shortest_path.py @@ -1,8 +1,6 @@ """Breadth-first search shortest path implementations. - doctest: python -m doctest -v bfs_shortest_path.py - Manual test: python bfs_shortest_path.py """ @@ -19,22 +17,19 @@ def bfs_shortest_path(graph: dict, start, goal) -> str: """Find shortest path between `start` and `goal` nodes. - Args: graph (dict): node/list of neighboring nodes key/value pairs. start: start node. goal: target node. - Returns: Shortest path between `start` and `goal` nodes as a string of nodes. 'Not found' string if no path found. - Example: >>> bfs_shortest_path(graph, "G", "D") ['G', 'C', 'A', 'B', 'D'] """ # keep track of explored nodes - explored = [] + explored = set() # keep track of all the paths to be checked queue = [[start]] @@ -61,7 +56,7 @@ def bfs_shortest_path(graph: dict, start, goal) -> str: return new_path # mark node as explored - explored.append(node) + explored.add(node) # in case there's no path between the 2 nodes return "So sorry, but a connecting path doesn't exist :(" @@ -69,16 +64,13 @@ def bfs_shortest_path(graph: dict, start, goal) -> str: def bfs_shortest_path_distance(graph: dict, start, target) -> int: """Find shortest path distance between `start` and `target` nodes. - Args: graph: node/list of neighboring nodes key/value pairs. start: node to start search from. target: node to search for. - Returns: Number of edges in shortest path between `start` and `target` nodes. -1 if no path exists. - Example: >>> bfs_shortest_path_distance(graph, "G", "D") 4 @@ -92,7 +84,7 @@ def bfs_shortest_path_distance(graph: dict, start, target) -> int: if start == target: return 0 queue = [start] - visited = [start] + visited = set(start) # Keep tab on distances from `start` node. dist = {start: 0, target: -1} while queue: @@ -103,7 +95,7 @@ def bfs_shortest_path_distance(graph: dict, start, target) -> int: ) for adjacent in graph[node]: if adjacent not in visited: - visited.append(adjacent) + visited.add(adjacent) queue.append(adjacent) dist[adjacent] = dist[node] + 1 return dist[target] From f036b9f3587fca094ce85f40b587e930e26ac726 Mon Sep 17 00:00:00 2001 From: Niranjan Hegde Date: Sat, 21 Nov 2020 13:34:08 +0530 Subject: [PATCH 1017/1071] Web programming contribution (#2436) * Currency Converter * currency converter * Currency Converter * currency converter * implemented changes * Implemented changes requested * TESTING = os.getenv("CONTINUOUS_INTEGRATION", False) * Update currency_converter.py * Update currency_converter.py Co-authored-by: Christian Clauss --- web_programming/currency_converter.py | 192 ++++++++++++++++++++++++++ 1 file changed, 192 insertions(+) create mode 100644 web_programming/currency_converter.py diff --git a/web_programming/currency_converter.py b/web_programming/currency_converter.py new file mode 100644 index 000000000000..6aed2a5578a5 --- /dev/null +++ b/web_programming/currency_converter.py @@ -0,0 +1,192 @@ +""" +This is used to convert the currency using the Amdoren Currency API +https://www.amdoren.com +""" + +import os + +import requests + +URL_BASE = "https://www.amdoren.com/api/currency.php" +TESTING = os.getenv("CI", False) +API_KEY = os.getenv("AMDOREN_API_KEY") +if not API_KEY and not TESTING: + raise KeyError("Please put your API key in an environment variable.") + + +# Currency and their description +list_of_currencies = """ +AED United Arab Emirates Dirham +AFN Afghan Afghani +ALL Albanian Lek +AMD Armenian Dram +ANG Netherlands Antillean Guilder +AOA Angolan Kwanza +ARS Argentine Peso +AUD Australian Dollar +AWG Aruban Florin +AZN Azerbaijani Manat +BAM Bosnia & Herzegovina Convertible Mark +BBD Barbadian Dollar +BDT Bangladeshi Taka +BGN Bulgarian Lev +BHD Bahraini Dinar +BIF Burundian Franc +BMD Bermudian Dollar +BND Brunei Dollar +BOB Bolivian Boliviano +BRL Brazilian Real +BSD Bahamian Dollar +BTN Bhutanese Ngultrum +BWP Botswana Pula +BYN Belarus Ruble +BZD Belize Dollar +CAD Canadian Dollar +CDF Congolese Franc +CHF Swiss Franc +CLP Chilean Peso +CNY Chinese Yuan +COP Colombian Peso +CRC Costa Rican Colon +CUC Cuban Convertible Peso +CVE Cape Verdean Escudo +CZK Czech Republic Koruna +DJF Djiboutian Franc +DKK Danish Krone +DOP Dominican Peso +DZD Algerian Dinar +EGP Egyptian Pound +ERN Eritrean Nakfa +ETB Ethiopian Birr +EUR Euro +FJD Fiji Dollar +GBP British Pound Sterling +GEL Georgian Lari +GHS Ghanaian Cedi +GIP Gibraltar Pound +GMD Gambian Dalasi +GNF Guinea Franc +GTQ Guatemalan Quetzal +GYD Guyanaese Dollar +HKD Hong Kong Dollar +HNL Honduran Lempira +HRK Croatian Kuna +HTG Haiti Gourde +HUF Hungarian Forint +IDR Indonesian Rupiah +ILS Israeli Shekel +INR Indian Rupee +IQD Iraqi Dinar +IRR Iranian Rial +ISK Icelandic Krona +JMD Jamaican Dollar +JOD Jordanian Dinar +JPY Japanese Yen +KES Kenyan Shilling +KGS Kyrgystani Som +KHR Cambodian Riel +KMF Comorian Franc +KPW North Korean Won +KRW South Korean Won +KWD Kuwaiti Dinar +KYD Cayman Islands Dollar +KZT Kazakhstan Tenge +LAK Laotian Kip +LBP Lebanese Pound +LKR Sri Lankan Rupee +LRD Liberian Dollar +LSL Lesotho Loti +LYD Libyan Dinar +MAD Moroccan Dirham +MDL Moldovan Leu +MGA Malagasy Ariary +MKD Macedonian Denar +MMK Myanma Kyat +MNT Mongolian Tugrik +MOP Macau Pataca +MRO Mauritanian Ouguiya +MUR Mauritian Rupee +MVR Maldivian Rufiyaa +MWK Malawi Kwacha +MXN Mexican Peso +MYR Malaysian Ringgit +MZN Mozambican Metical +NAD Namibian Dollar +NGN Nigerian Naira +NIO Nicaragua Cordoba +NOK Norwegian Krone +NPR Nepalese Rupee +NZD New Zealand Dollar +OMR Omani Rial +PAB Panamanian Balboa +PEN Peruvian Nuevo Sol +PGK Papua New Guinean Kina +PHP Philippine Peso +PKR Pakistani Rupee +PLN Polish Zloty +PYG Paraguayan Guarani +QAR Qatari Riyal +RON Romanian Leu +RSD Serbian Dinar +RUB Russian Ruble +RWF Rwanda Franc +SAR Saudi Riyal +SBD Solomon Islands Dollar +SCR Seychellois Rupee +SDG Sudanese Pound +SEK Swedish Krona +SGD Singapore Dollar +SHP Saint Helena Pound +SLL Sierra Leonean Leone +SOS Somali Shilling +SRD Surinamese Dollar +SSP South Sudanese Pound +STD Sao Tome and Principe Dobra +SYP Syrian Pound +SZL Swazi Lilangeni +THB Thai Baht +TJS Tajikistan Somoni +TMT Turkmenistani Manat +TND Tunisian Dinar +TOP Tonga Paanga +TRY Turkish Lira +TTD Trinidad and Tobago Dollar +TWD New Taiwan Dollar +TZS Tanzanian Shilling +UAH Ukrainian Hryvnia +UGX Ugandan Shilling +USD United States Dollar +UYU Uruguayan Peso +UZS Uzbekistan Som +VEF Venezuelan Bolivar +VND Vietnamese Dong +VUV Vanuatu Vatu +WST Samoan Tala +XAF Central African CFA franc +XCD East Caribbean Dollar +XOF West African CFA franc +XPF CFP Franc +YER Yemeni Rial +ZAR South African Rand +ZMW Zambian Kwacha +""" + + +def convert_currency( + from_: str = "USD", to: str = "INR", amount: float = 1.0, api_key: str = API_KEY +) -> str: + """https://www.amdoren.com/currency-api/""" + params = locals() + params["from"] = params.pop("from_") + res = requests.get(URL_BASE, params=params).json() + return str(res["amount"]) if res["error"] == 0 else res["error_message"] + + +if __name__ == "__main__": + print( + convert_currency( + input("Enter from currency: ").strip(), + input("Enter to currency: ").strip(), + float(input("Enter the amount: ").strip()), + ) + ) From f2c1f98a234677b7b0265751597c17692286e004 Mon Sep 17 00:00:00 2001 From: Dhruv Manilawala Date: Sat, 21 Nov 2020 16:12:00 +0530 Subject: [PATCH 1018/1071] Remove workflow file, task checked by the bot (#3917) --- .github/workflows/auto_close_empty_issues.yml | 20 ------------------- 1 file changed, 20 deletions(-) delete mode 100644 .github/workflows/auto_close_empty_issues.yml diff --git a/.github/workflows/auto_close_empty_issues.yml b/.github/workflows/auto_close_empty_issues.yml deleted file mode 100644 index a6334d6ade32..000000000000 --- a/.github/workflows/auto_close_empty_issues.yml +++ /dev/null @@ -1,20 +0,0 @@ -# GitHub Action that uses close-issue auto-close empty issues after they are opened. -# If the issue body text is empty the Action auto-closes it and sends a notification. -# Otherwise if the issue body is not empty, it does nothing and the issue remains open. -# https://github.com/marketplace/actions/close-issue - -name: auto_close_empty_issues -on: - issues: - types: [opened] -jobs: - check-issue-body-not-empty: - runs-on: ubuntu-latest - steps: - - if: github.event.issue.body == 0 - name: Close Issue - uses: peter-evans/close-issue@v1 - with: - comment: | - Issue body must contain content. - Auto-closing this issue. From 03e7f3732996f1a83ebdca71a2efc2665ed49f38 Mon Sep 17 00:00:00 2001 From: Joyce Date: Mon, 23 Nov 2020 13:37:42 +0800 Subject: [PATCH 1019/1071] [mypy] math/sieve_of_eratosthenes: Add type hints (#2627) * add type hints to math/sieve * add doctest * math/sieve: remove manual doctest * add check for negative * Update maths/sieve_of_eratosthenes.py * Update sieve_of_eratosthenes.py Co-authored-by: Dhruv Manilawala --- maths/sieve_of_eratosthenes.py | 38 +++++++++++++++++++--------------- 1 file changed, 21 insertions(+), 17 deletions(-) diff --git a/maths/sieve_of_eratosthenes.py b/maths/sieve_of_eratosthenes.py index faf6fc0f9a98..47a086546900 100644 --- a/maths/sieve_of_eratosthenes.py +++ b/maths/sieve_of_eratosthenes.py @@ -8,54 +8,58 @@ Reference: https://en.wikipedia.org/wiki/Sieve_of_Eratosthenes doctest provider: Bruno Simas Hadlich (https://github.com/brunohadlich) -Also thanks Dmitry (https://github.com/LizardWizzard) for finding the problem +Also thanks to Dmitry (https://github.com/LizardWizzard) for finding the problem """ import math +from typing import List -def sieve(n): +def prime_sieve(num: int) -> List[int]: """ Returns a list with all prime numbers up to n. - >>> sieve(50) + >>> prime_sieve(50) [2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47] - >>> sieve(25) + >>> prime_sieve(25) [2, 3, 5, 7, 11, 13, 17, 19, 23] - >>> sieve(10) + >>> prime_sieve(10) [2, 3, 5, 7] - >>> sieve(9) + >>> prime_sieve(9) [2, 3, 5, 7] - >>> sieve(2) + >>> prime_sieve(2) [2] - >>> sieve(1) + >>> prime_sieve(1) [] """ - l = [True] * (n + 1) # noqa: E741 + if num <= 0: + raise ValueError(f"{num}: Invalid input, please enter a positive integer.") + + sieve = [True] * (num + 1) prime = [] start = 2 - end = int(math.sqrt(n)) + end = int(math.sqrt(num)) while start <= end: # If start is a prime - if l[start] is True: + if sieve[start] is True: prime.append(start) # Set multiples of start be False - for i in range(start * start, n + 1, start): - if l[i] is True: - l[i] = False + for i in range(start * start, num + 1, start): + if sieve[i] is True: + sieve[i] = False start += 1 - for j in range(end + 1, n + 1): - if l[j] is True: + for j in range(end + 1, num + 1): + if sieve[j] is True: prime.append(j) return prime if __name__ == "__main__": - print(sieve(int(input("Enter n: ").strip()))) + print(prime_sieve(int(input("Enter a positive integer: ").strip()))) From 49d0c41905fd69338d6a84a73fe1f74c2be14adf Mon Sep 17 00:00:00 2001 From: Mikail Farid Date: Mon, 23 Nov 2020 07:11:28 +0100 Subject: [PATCH 1020/1071] Renamed octal_to_decimal to octal_to_decimal.py (#3420) * Renamed octal_to_decimal to octal_to_decimal.py * Updated octal_to_decimal.py * modified doctests * updated octal_to_decimal.py --- conversions/{octal_to_decimal => octal_to_decimal.py} | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) rename conversions/{octal_to_decimal => octal_to_decimal.py} (77%) diff --git a/conversions/octal_to_decimal b/conversions/octal_to_decimal.py similarity index 77% rename from conversions/octal_to_decimal rename to conversions/octal_to_decimal.py index a5b027e3ae8d..5a7373fef7e3 100644 --- a/conversions/octal_to_decimal +++ b/conversions/octal_to_decimal.py @@ -9,10 +9,16 @@ def oct_to_decimal(oct_string: str) -> int: >>> oct_to_decimal("-45") -37 >>> oct_to_decimal("2-0Fm") + Traceback (most recent call last): + ... ValueError: Non-octal value was passed to the function >>> oct_to_decimal("") - ValueError: Empty string value was passed to the function + Traceback (most recent call last): + ... + ValueError: Empty string was passed to the function >>> oct_to_decimal("19") + Traceback (most recent call last): + ... ValueError: Non-octal value was passed to the function """ oct_string = str(oct_string).strip() @@ -21,7 +27,7 @@ def oct_to_decimal(oct_string: str) -> int: is_negative = oct_string[0] == "-" if is_negative: oct_string = oct_string[1:] - if not all(0 <= int(char) <= 7 for char in oct_string): + if not oct_string.isdigit() or not all(0 <= int(char) <= 7 for char in oct_string): raise ValueError("Non-octal value was passed to the function") decimal_number = 0 for char in oct_string: From 9bf7b183e744a325a48a573165740d01bf60b2cf Mon Sep 17 00:00:00 2001 From: Tan Yong He Date: Mon, 23 Nov 2020 15:31:43 +0800 Subject: [PATCH 1021/1071] Improve Base16 Codebase (#3534) * Add doctest and remove input() usage * Apply suggestions from code review Co-authored-by: Dhruv Manilawala --- ciphers/base16.py | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/ciphers/base16.py b/ciphers/base16.py index 0210315d54e6..f27ea4628e54 100644 --- a/ciphers/base16.py +++ b/ciphers/base16.py @@ -1,13 +1,22 @@ import base64 -def main(): - inp = input("->") +def encode_to_b16(inp: str) -> bytes: + """ + Encodes a given utf-8 string into base-16. + >>> encode_to_b16('Hello World!') + b'48656C6C6F20576F726C6421' + >>> encode_to_b16('HELLO WORLD!') + b'48454C4C4F20574F524C4421' + >>> encode_to_b16('') + b'' + """ encoded = inp.encode("utf-8") # encoded the input (we need a bytes like object) b16encoded = base64.b16encode(encoded) # b16encoded the encoded string - print(b16encoded) - print(base64.b16decode(b16encoded).decode("utf-8")) # decoded it + return b16encoded if __name__ == "__main__": - main() + import doctest + + doctest.testmod() From 3fdbf9741dd910a78f3d614771d26c3dda3527fd Mon Sep 17 00:00:00 2001 From: Christian Clauss Date: Tue, 24 Nov 2020 12:41:10 +0100 Subject: [PATCH 1022/1071] Python 3.9 (#3926) * Upgrade to Python 3.9 * pip install wheel for faster builds * updating DIRECTORY.md * requirements.txt: tensorflow; python_version < '3.9' * keras requires tensorflow * Rename lstm_prediction.py to lstm_prediction.py_tf * Update requirements.txt * updating DIRECTORY.md * Update requirements.txt Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> Co-authored-by: Dhruv Manilawala --- .github/workflows/build.yml | 4 ++-- DIRECTORY.md | 3 +-- .../lstm/{lstm_prediction.py => lstm_prediction.py_tf} | 0 requirements.txt | 4 ++-- 4 files changed, 5 insertions(+), 6 deletions(-) rename machine_learning/lstm/{lstm_prediction.py => lstm_prediction.py_tf} (100%) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 01ac9aea7a7c..ae9b4e36b1ce 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -12,14 +12,14 @@ jobs: - uses: actions/checkout@v2 - uses: actions/setup-python@v2 with: - python-version: "3.8" + python-version: "3.9" - uses: actions/cache@v2 with: path: ~/.cache/pip key: ${{ runner.os }}-pip-${{ hashFiles('requirements.txt') }} - name: Install dependencies run: | - python -m pip install --upgrade pip setuptools six + python -m pip install --upgrade pip setuptools six wheel python -m pip install pytest-cov -r requirements.txt - name: Run tests run: pytest --doctest-modules --ignore=project_euler/ --cov-report=term-missing:skip-covered --cov=. . diff --git a/DIRECTORY.md b/DIRECTORY.md index 2b3f3073c3d4..e1e57307d593 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -356,8 +356,6 @@ * [Linear Discriminant Analysis](https://github.com/TheAlgorithms/Python/blob/master/machine_learning/linear_discriminant_analysis.py) * [Linear Regression](https://github.com/TheAlgorithms/Python/blob/master/machine_learning/linear_regression.py) * [Logistic Regression](https://github.com/TheAlgorithms/Python/blob/master/machine_learning/logistic_regression.py) - * Lstm - * [Lstm Prediction](https://github.com/TheAlgorithms/Python/blob/master/machine_learning/lstm/lstm_prediction.py) * [Multilayer Perceptron Classifier](https://github.com/TheAlgorithms/Python/blob/master/machine_learning/multilayer_perceptron_classifier.py) * [Polymonial Regression](https://github.com/TheAlgorithms/Python/blob/master/machine_learning/polymonial_regression.py) * [Random Forest Classifier](https://github.com/TheAlgorithms/Python/blob/master/machine_learning/random_forest_classifier.py) @@ -866,6 +864,7 @@ * [Covid Stats Via Xpath](https://github.com/TheAlgorithms/Python/blob/master/web_programming/covid_stats_via_xpath.py) * [Crawl Google Results](https://github.com/TheAlgorithms/Python/blob/master/web_programming/crawl_google_results.py) * [Crawl Google Scholar Citation](https://github.com/TheAlgorithms/Python/blob/master/web_programming/crawl_google_scholar_citation.py) + * [Currency Converter](https://github.com/TheAlgorithms/Python/blob/master/web_programming/currency_converter.py) * [Current Stock Price](https://github.com/TheAlgorithms/Python/blob/master/web_programming/current_stock_price.py) * [Current Weather](https://github.com/TheAlgorithms/Python/blob/master/web_programming/current_weather.py) * [Daily Horoscope](https://github.com/TheAlgorithms/Python/blob/master/web_programming/daily_horoscope.py) diff --git a/machine_learning/lstm/lstm_prediction.py b/machine_learning/lstm/lstm_prediction.py_tf similarity index 100% rename from machine_learning/lstm/lstm_prediction.py rename to machine_learning/lstm/lstm_prediction.py_tf diff --git a/requirements.txt b/requirements.txt index 8bbb8d524ed4..349d88944656 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,6 +1,6 @@ beautifulsoup4 fake_useragent -keras +keras; python_version < '3.9' lxml matplotlib numpy @@ -13,5 +13,5 @@ scikit-fuzzy sklearn statsmodels sympy -tensorflow +tensorflow; python_version < '3.9' xgboost From e031ad3db627a46d3c1cc0ae37739420f00c239d Mon Sep 17 00:00:00 2001 From: Sullivan <38718448+Epic-R-R@users.noreply.github.com> Date: Tue, 24 Nov 2020 19:48:00 +0330 Subject: [PATCH 1023/1071] Create instagram_pic (#3945) * Create instagram_pic * Update instagram_pic * Update instagram_pic * isort * Update instagram_pic.py Co-authored-by: Christian Clauss --- web_programming/instagram_pic.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 web_programming/instagram_pic.py diff --git a/web_programming/instagram_pic.py b/web_programming/instagram_pic.py new file mode 100644 index 000000000000..8521da674d7d --- /dev/null +++ b/web_programming/instagram_pic.py @@ -0,0 +1,16 @@ +from datetime import datetime + +import requests +from bs4 import BeautifulSoup + +if __name__ == "__main__": + url = input("Enter image url: ").strip() + print(f"Downloading image from {url} ...") + soup = BeautifulSoup(requests.get(url).content, "html.parser") + # The image URL is in the content field of the first meta tag with property og:image + image_url = soup.find("meta", {"property": "og:image"})["content"] + image_data = requests.get(image_url).content + file_name = f"{datetime.now():%Y-%m-%d_%H:%M:%S}.jpg" + with open(file_name, "wb") as fp: + fp.write(image_data) + print(f"Done. Image saved to disk as {file_name}.") From 287bf26bc87a0f68de51f817758b497e76d94271 Mon Sep 17 00:00:00 2001 From: Cho Yin Yong Date: Tue, 24 Nov 2020 19:30:15 -0500 Subject: [PATCH 1024/1071] Add a divide and conquer method in finding the maximum difference pair (#3692) * A divide and conquer method in finding the maximum difference pair * fix formatting issues * fix formatting issues * add doctest runner --- divide_and_conquer/max_difference_pair.py | 47 +++++++++++++++++++++++ 1 file changed, 47 insertions(+) create mode 100644 divide_and_conquer/max_difference_pair.py diff --git a/divide_and_conquer/max_difference_pair.py b/divide_and_conquer/max_difference_pair.py new file mode 100644 index 000000000000..b976aca43137 --- /dev/null +++ b/divide_and_conquer/max_difference_pair.py @@ -0,0 +1,47 @@ +from typing import List + + +def max_difference(a: List[int]) -> (int, int): + """ + We are given an array A[1..n] of integers, n >= 1. We want to + find a pair of indices (i, j) such that + 1 <= i <= j <= n and A[j] - A[i] is as large as possible. + + Explanation: + https://www.geeksforgeeks.org/maximum-difference-between-two-elements/ + + >>> max_difference([5, 11, 2, 1, 7, 9, 0, 7]) + (1, 9) + """ + # base case + if len(a) == 1: + return a[0], a[0] + else: + # split A into half. + first = a[: len(a) // 2] + second = a[len(a) // 2 :] + + # 2 sub problems, 1/2 of original size. + small1, big1 = max_difference(first) + small2, big2 = max_difference(second) + + # get min of first and max of second + # linear time + min_first = min(first) + max_second = max(second) + + # 3 cases, either (small1, big1), + # (min_first, max_second), (small2, big2) + # constant comparisons + if big2 - small2 > max_second - min_first and big2 - small2 > big1 - small1: + return small2, big2 + elif big1 - small1 > max_second - min_first: + return small1, big1 + else: + return min_first, max_second + + +if __name__ == "__main__": + import doctest + + doctest.testmod() From 098f02bc04550343767e3e1cfaef5f57af6b1046 Mon Sep 17 00:00:00 2001 From: lawric1 <67882089+lawric1@users.noreply.github.com> Date: Wed, 25 Nov 2020 02:13:14 -0300 Subject: [PATCH 1025/1071] Fixes: #3944 Authentication error; use tokens instead (#3949) * fixes #3944 authentication error * Fixes: #3944 authentication error * Fixed docstring failure in pre-commit, Fixed request.get params to GitHub REST API standards * run black formatter * Add USER_TOKEN constant and checks if empty, removes deprecated docstring * Add descriptive dict type hint, change headers format to f-string * Add Accept header * Fix pre-commit error * Fix pre-commit error * Add test for fetch_github_info * Remove test function from main file * Create test_fetch_github_info.py * Update test_fetch_github_info.py * Update test_fetch_github_info.py * No need to cover __name__ == __main__ block Co-authored-by: Dhruv Manilawala --- web_programming/fetch_github_info.py | 50 +++++++++++++++++------ web_programming/test_fetch_github_info.py | 27 ++++++++++++ 2 files changed, 64 insertions(+), 13 deletions(-) create mode 100644 web_programming/test_fetch_github_info.py diff --git a/web_programming/fetch_github_info.py b/web_programming/fetch_github_info.py index 227598bb20ab..c9198460f211 100644 --- a/web_programming/fetch_github_info.py +++ b/web_programming/fetch_github_info.py @@ -1,26 +1,50 @@ #!/usr/bin/env python3 - """ Created by sarathkaul on 14/11/19 +Updated by lawric1 on 24/11/20 -Basic authentication using an API password is deprecated and will soon no longer work. -Visit https://developer.github.com/changes/2020-02-14-deprecating-password-auth -for more information around suggested workarounds and removal dates. -""" +Authentication will be made via access token. +To generate your personal access token visit https://github.com/settings/tokens. + +NOTE: +Never hardcode any credential information in the code. Always use an environment +file to store the private information and use the `os` module to get the information +during runtime. +Create a ".env" file in the root directory and write these two lines in that file +with your token:: + +#!/usr/bin/env bash +export USER_TOKEN="" +""" +import os +from typing import Any, Dict import requests -_GITHUB_API = "https://api.github.com/user" +BASE_URL = "https://api.github.com" +# https://docs.github.com/en/free-pro-team@latest/rest/reference/users#get-the-authenticated-user +AUTHENTICATED_USER_ENDPOINT = BASE_URL + "/user" -def fetch_github_info(auth_user: str, auth_pass: str) -> dict: +# https://github.com/settings/tokens +USER_TOKEN = os.environ.get("USER_TOKEN", "") + + +def fetch_github_info(auth_token: str) -> Dict[Any, Any]: """ Fetch GitHub info of a user using the requests module """ - return requests.get(_GITHUB_API, auth=(auth_user, auth_pass)).json() - - -if __name__ == "__main__": - for key, value in fetch_github_info("", "").items(): - print(f"{key}: {value}") + headers = { + "Authorization": f"token {auth_token}", + "Accept": "application/vnd.github.v3+json", + } + return requests.get(AUTHENTICATED_USER_ENDPOINT, headers=headers).json() + + +if __name__ == "__main__": # pragma: no cover + if USER_TOKEN: + for key, value in fetch_github_info(USER_TOKEN).items(): + print(f"{key}: {value}") + else: + raise ValueError("'USER_TOKEN' field cannot be empty.") diff --git a/web_programming/test_fetch_github_info.py b/web_programming/test_fetch_github_info.py new file mode 100644 index 000000000000..2da97c782df7 --- /dev/null +++ b/web_programming/test_fetch_github_info.py @@ -0,0 +1,27 @@ +import json + +import requests + +from .fetch_github_info import AUTHENTICATED_USER_ENDPOINT, fetch_github_info + + +def test_fetch_github_info(monkeypatch): + class FakeResponse: + def __init__(self, content) -> None: + assert isinstance(content, (bytes, str)) + self.content = content + + def json(self): + return json.loads(self.content) + + def mock_response(*args, **kwargs): + assert args[0] == AUTHENTICATED_USER_ENDPOINT + assert "Authorization" in kwargs["headers"] + assert kwargs["headers"]["Authorization"].startswith("token ") + assert "Accept" in kwargs["headers"] + return FakeResponse(b'{"login":"test","id":1}') + + monkeypatch.setattr(requests, "get", mock_response) + result = fetch_github_info("token") + assert result["login"] == "test" + assert result["id"] == 1 From 5eb5483d655482b937abe0e59c34e41a455dfff8 Mon Sep 17 00:00:00 2001 From: Dhruv Manilawala Date: Wed, 25 Nov 2020 13:23:49 +0530 Subject: [PATCH 1026/1071] Update stalebot to take 5 actions per hour (#3922) * Update stalebot to take 5 actions per hour * updating DIRECTORY.md Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> --- .github/stale.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/stale.yml b/.github/stale.yml index ba6fd155d7a3..36ca56266b26 100644 --- a/.github/stale.yml +++ b/.github/stale.yml @@ -27,7 +27,7 @@ exemptAssignees: false staleLabel: stale # Limit the number of actions per hour, from 1-30. Default is 30 -limitPerRun: 30 +limitPerRun: 5 # Comment to post when removing the stale label. # unmarkComment: > From 2b50aaf2d3acdd54409316ce71985d4e161912f5 Mon Sep 17 00:00:00 2001 From: YeonJeongLee00 <67946956+YeonJeongLee00@users.noreply.github.com> Date: Wed, 25 Nov 2020 17:54:31 +0900 Subject: [PATCH 1027/1071] Create intro_sort.py (#3877) * Create intro_sort.py * modified intro_sort.py * add doctest * modified code black intro_sort.py * add more test * Update intro_sort.py added doctest, modified code * black intro_sort.py * add type hint * modified code --- sorts/intro_sort.py | 173 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 173 insertions(+) create mode 100644 sorts/intro_sort.py diff --git a/sorts/intro_sort.py b/sorts/intro_sort.py new file mode 100644 index 000000000000..f0e3645adbb7 --- /dev/null +++ b/sorts/intro_sort.py @@ -0,0 +1,173 @@ +""" +Introspective Sort is hybrid sort (Quick Sort + Heap Sort + Insertion Sort) +if the size of the list is under 16, use insertion sort +https://en.wikipedia.org/wiki/Introsort +""" +import math + + +def insertion_sort(array: list, start: int = 0, end: int = 0) -> list: + """ + >>> array = [4, 2, 6, 8, 1, 7, 8, 22, 14, 56, 27, 79, 23, 45, 14, 12] + + >>> insertion_sort(array, 0, len(array)) + [1, 2, 4, 6, 7, 8, 8, 12, 14, 14, 22, 23, 27, 45, 56, 79] + """ + end = end or len(array) + for i in range(start, end): + temp_index = i + temp_index_value = array[i] + while temp_index != start and temp_index_value < array[temp_index - 1]: + array[temp_index] = array[temp_index - 1] + temp_index -= 1 + array[temp_index] = temp_index_value + return array + + +def heapify(array: list, index: int, heap_size: int) -> None: # Max Heap + """ + >>> array = [4, 2, 6, 8, 1, 7, 8, 22, 14, 56, 27, 79, 23, 45, 14, 12] + + >>> heapify(array, len(array) // 2 ,len(array)) + """ + largest = index + left_index = 2 * index + 1 # Left Node + right_index = 2 * index + 2 # Right Node + + if left_index < heap_size and array[largest] < array[left_index]: + largest = left_index + + if right_index < heap_size and array[largest] < array[right_index]: + largest = right_index + + if largest != index: + array[index], array[largest] = array[largest], array[index] + heapify(array, largest, heap_size) + + +def heap_sort(array: list) -> list: + """ + >>> array = [4, 2, 6, 8, 1, 7, 8, 22, 14, 56, 27, 79, 23, 45, 14, 12] + + >>> heap_sort(array) + [1, 2, 4, 6, 7, 8, 8, 12, 14, 14, 22, 23, 27, 45, 56, 79] + """ + n = len(array) + + for i in range(n // 2, -1, -1): + heapify(array, i, n) + + for i in range(n - 1, 0, -1): + array[i], array[0] = array[0], array[i] + heapify(array, 0, i) + + return array + + +def median_of_3( + array: list, first_index: int, middle_index: int, last_index: int +) -> int: + """ + >>> array = [4, 2, 6, 8, 1, 7, 8, 22, 14, 56, 27, 79, 23, 45, 14, 12] + + >>> median_of_3(array, 0, 0 + ((len(array) - 0) // 2) + 1, len(array) - 1) + 12 + """ + if (array[first_index] > array[middle_index]) != ( + array[first_index] > array[last_index] + ): + return array[first_index] + elif (array[middle_index] > array[first_index]) != ( + array[middle_index] > array[last_index] + ): + return array[middle_index] + else: + return array[last_index] + + +def partition(array: list, low: int, high: int, pivot: int) -> int: + """ + >>> array = [4, 2, 6, 8, 1, 7, 8, 22, 14, 56, 27, 79, 23, 45, 14, 12] + + >>> partition(array, 0, len(array), 12) + 8 + """ + i = low + j = high + while True: + while array[i] < pivot: + i += 1 + j -= 1 + while pivot < array[j]: + j -= 1 + if i >= j: + return i + array[i], array[j] = array[j], array[i] + i += 1 + + +def sort(array: list) -> list: + """ + :param collection: some mutable ordered collection with heterogeneous + comparable items inside + :return: the same collection ordered by ascending + + Examples: + >>> sort([4, 2, 6, 8, 1, 7, 8, 22, 14, 56, 27, 79, 23, 45, 14, 12]) + [1, 2, 4, 6, 7, 8, 8, 12, 14, 14, 22, 23, 27, 45, 56, 79] + + >>> sort([-1, -5, -3, -13, -44]) + [-44, -13, -5, -3, -1] + + >>> sort([]) + [] + + >>> sort([5]) + [5] + + >>> sort([-3, 0, -7, 6, 23, -34]) + [-34, -7, -3, 0, 6, 23] + + >>> sort([1.7, 1.0, 3.3, 2.1, 0.3 ]) + [0.3, 1.0, 1.7, 2.1, 3.3] + + >>> sort(['d', 'a', 'b', 'e', 'c']) + ['a', 'b', 'c', 'd', 'e'] + """ + if len(array) == 0: + return array + max_depth = 2 * math.ceil(math.log2(len(array))) + size_threshold = 16 + return intro_sort(array, 0, len(array), size_threshold, max_depth) + + +def intro_sort( + array: list, start: int, end: int, size_threshold: int, max_depth: int +) -> list: + """ + >>> array = [4, 2, 6, 8, 1, 7, 8, 22, 14, 56, 27, 79, 23, 45, 14, 12] + + >>> max_depth = 2 * math.ceil(math.log2(len(array))) + + >>> intro_sort(array, 0, len(array), 16, max_depth) + [1, 2, 4, 6, 7, 8, 8, 12, 14, 14, 22, 23, 27, 45, 56, 79] + """ + while end - start > size_threshold: + if max_depth == 0: + return heap_sort(array) + max_depth -= 1 + pivot = median_of_3(array, start, start + ((end - start) // 2) + 1, end - 1) + p = partition(array, start, end, pivot) + intro_sort(array, p, end, size_threshold, max_depth) + end = p + return insertion_sort(array, start, end) + + +if __name__ == "__main__": + import doctest + + doctest.testmod() + + user_input = input("Enter numbers separated by a comma : ").strip() + unsorted = [float(item) for item in user_input.split(",")] + print(sort(unsorted)) From 4191b9594237a989555b29cfc287b7238050634f Mon Sep 17 00:00:00 2001 From: Erdum Date: Wed, 25 Nov 2020 16:01:49 +0500 Subject: [PATCH 1028/1071] Ohm's Law algorithm added (#3934) * New algorithm added * Errors resolvedc * New Algorithm * New algorithm added * Added new algorithm * work * New algorithm added * Hope this is final * Update electronics/ohms_law.py Co-authored-by: xcodz-dot <71920621+xcodz-dot@users.noreply.github.com> * update decimal value & negative value test * update as cclauss suggest * Update electronics/ohms_law.py Co-authored-by: Christian Clauss * updated as suggested by cclauss * update as suggested by cclauss * Update as suggested by cclauss * Update ohms_law.py Co-authored-by: xcodz-dot <71920621+xcodz-dot@users.noreply.github.com> Co-authored-by: Christian Clauss --- electronics/ohms_law.py | 39 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 electronics/ohms_law.py diff --git a/electronics/ohms_law.py b/electronics/ohms_law.py new file mode 100644 index 000000000000..a7b37b635397 --- /dev/null +++ b/electronics/ohms_law.py @@ -0,0 +1,39 @@ +# https://en.wikipedia.org/wiki/Ohm%27s_law + + +def ohms_law(voltage: float, current: float, resistance: float) -> float: + """ + Apply Ohm's Law, on any two given electrical values, which can be voltage, current, + and resistance, and then in a Python dict return name/value pair of the zero value. + + >>> ohms_law(voltage=10, resistance=5, current=0) + {'current': 2.0} + >>> ohms_law(voltage=0, current=0, resistance=10) + Traceback (most recent call last): + ... + ValueError: One and only one argument must be 0 + >>> ohms_law(voltage=0, current=1, resistance=-2) + Traceback (most recent call last): + ... + ValueError: Resistance cannot be negative + >>> ohms_law(resistance=0, voltage=-10, current=1) + {'resistance': -10.0} + >>> ohms_law(voltage=0, current=-1.5, resistance=2) + {'voltage': -3.0} + """ + if (voltage, current, resistance).count(0) != 1: + raise ValueError("One and only one argument must be 0") + if resistance < 0: + raise ValueError("Resistance cannot be negative") + if voltage == 0: + return {"voltage": float(current * resistance)} + elif current == 0: + return {"current": voltage / resistance} + elif resistance == 0: + return {"resistance": voltage / current} + + +if __name__ == "__main__": + import doctest + + doctest.testmod() From ce3ce3f8a8618e36624d997bc934b2ea997fa5d7 Mon Sep 17 00:00:00 2001 From: Hafidh <32499116+hfz1337@users.noreply.github.com> Date: Wed, 25 Nov 2020 13:38:02 +0100 Subject: [PATCH 1029/1071] Replace base64_cipher.py with an easy to understand version (#3925) * rename base64_cipher.py to base64_encoding.py * edit base64_encoding.py * import necessary modules inside doctests * make it behave like the official implementation * replace format with f-string where possible * replace format with f-string Co-authored-by: Christian Clauss * fix: syntax error due to closing parenthese * reformat code Co-authored-by: Christian Clauss --- ciphers/base64_cipher.py | 89 ----------------------- ciphers/base64_encoding.py | 142 +++++++++++++++++++++++++++++++++++++ 2 files changed, 142 insertions(+), 89 deletions(-) delete mode 100644 ciphers/base64_cipher.py create mode 100644 ciphers/base64_encoding.py diff --git a/ciphers/base64_cipher.py b/ciphers/base64_cipher.py deleted file mode 100644 index 1dbe74a20fe7..000000000000 --- a/ciphers/base64_cipher.py +++ /dev/null @@ -1,89 +0,0 @@ -def encode_base64(text: str) -> str: - r""" - >>> encode_base64('WELCOME to base64 encoding 😁') - 'V0VMQ09NRSB0byBiYXNlNjQgZW5jb2Rpbmcg8J+YgQ==' - >>> encode_base64('AÅᐃ𐀏🤓') - 'QcOF4ZCD8JCAj/CfpJM=' - >>> encode_base64('A'*60) - 'QUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFB\r\nQUFB' - """ - base64_chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/" - - byte_text = bytes(text, "utf-8") # put text in bytes for unicode support - r = "" # the result - c = -len(byte_text) % 3 # the length of padding - p = "=" * c # the padding - s = byte_text + b"\x00" * c # the text to encode - - i = 0 - while i < len(s): - if i > 0 and ((i / 3 * 4) % 76) == 0: - r = r + "\r\n" # for unix newline, put "\n" - - n = (s[i] << 16) + (s[i + 1] << 8) + s[i + 2] - - n1 = (n >> 18) & 63 - n2 = (n >> 12) & 63 - n3 = (n >> 6) & 63 - n4 = n & 63 - - r += base64_chars[n1] + base64_chars[n2] + base64_chars[n3] + base64_chars[n4] - i += 3 - - return r[0 : len(r) - len(p)] + p - - -def decode_base64(text: str) -> str: - r""" - >>> decode_base64('V0VMQ09NRSB0byBiYXNlNjQgZW5jb2Rpbmcg8J+YgQ==') - 'WELCOME to base64 encoding 😁' - >>> decode_base64('QcOF4ZCD8JCAj/CfpJM=') - 'AÅᐃ𐀏🤓' - >>> decode_base64("QUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUF" - ... "BQUFBQUFBQUFB\r\nQUFB") - 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' - """ - base64_chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/" - s = "" - - for i in text: - if i in base64_chars: - s += i - c = "" - else: - if i == "=": - c += "=" - - p = "" - if c == "=": - p = "A" - else: - if c == "==": - p = "AA" - - r = b"" - s = s + p - - i = 0 - while i < len(s): - n = ( - (base64_chars.index(s[i]) << 18) - + (base64_chars.index(s[i + 1]) << 12) - + (base64_chars.index(s[i + 2]) << 6) - + base64_chars.index(s[i + 3]) - ) - - r += bytes([(n >> 16) & 255]) + bytes([(n >> 8) & 255]) + bytes([n & 255]) - - i += 4 - - return str(r[0 : len(r) - len(p)], "utf-8") - - -def main(): - print(encode_base64("WELCOME to base64 encoding 😁")) - print(decode_base64(encode_base64("WELCOME to base64 encoding 😁"))) - - -if __name__ == "__main__": - main() diff --git a/ciphers/base64_encoding.py b/ciphers/base64_encoding.py new file mode 100644 index 000000000000..634afcb89873 --- /dev/null +++ b/ciphers/base64_encoding.py @@ -0,0 +1,142 @@ +B64_CHARSET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/" + + +def base64_encode(data: bytes) -> bytes: + """Encodes data according to RFC4648. + + The data is first transformed to binary and appended with binary digits so that its + length becomes a multiple of 6, then each 6 binary digits will match a character in + the B64_CHARSET string. The number of appended binary digits would later determine + how many "=" sign should be added, the padding. + For every 2 binary digits added, a "=" sign is added in the output. + We can add any binary digits to make it a multiple of 6, for instance, consider the + following example: + "AA" -> 0010100100101001 -> 001010 010010 1001 + As can be seen above, 2 more binary digits should be added, so there's 4 + possibilities here: 00, 01, 10 or 11. + That being said, Base64 encoding can be used in Steganography to hide data in these + appended digits. + + >>> from base64 import b64encode + >>> a = b"This pull request is part of Hacktoberfest20!" + >>> b = b"https://tools.ietf.org/html/rfc4648" + >>> c = b"A" + >>> base64_encode(a) == b64encode(a) + True + >>> base64_encode(b) == b64encode(b) + True + >>> base64_encode(c) == b64encode(c) + True + >>> base64_encode("abc") + Traceback (most recent call last): + ... + TypeError: a bytes-like object is required, not 'str' + """ + # Make sure the supplied data is a bytes-like object + if not isinstance(data, bytes): + raise TypeError( + f"a bytes-like object is required, not '{data.__class__.__name__}'" + ) + + binary_stream = "".join(bin(byte)[2:].zfill(8) for byte in data) + + padding_needed = len(binary_stream) % 6 != 0 + + if padding_needed: + # The padding that will be added later + padding = b"=" * ((6 - len(binary_stream) % 6) // 2) + + # Append binary_stream with arbitrary binary digits (0's by default) to make its + # length a multiple of 6. + binary_stream += "0" * (6 - len(binary_stream) % 6) + else: + padding = b"" + + # Encode every 6 binary digits to their corresponding Base64 character + return ( + "".join( + B64_CHARSET[int(binary_stream[index : index + 6], 2)] + for index in range(0, len(binary_stream), 6) + ).encode() + + padding + ) + + +def base64_decode(encoded_data: str) -> bytes: + """Decodes data according to RFC4648. + + This does the reverse operation of base64_encode. + We first transform the encoded data back to a binary stream, take off the + previously appended binary digits according to the padding, at this point we + would have a binary stream whose length is multiple of 8, the last step is + to convert every 8 bits to a byte. + + >>> from base64 import b64decode + >>> a = "VGhpcyBwdWxsIHJlcXVlc3QgaXMgcGFydCBvZiBIYWNrdG9iZXJmZXN0MjAh" + >>> b = "aHR0cHM6Ly90b29scy5pZXRmLm9yZy9odG1sL3JmYzQ2NDg=" + >>> c = "QQ==" + >>> base64_decode(a) == b64decode(a) + True + >>> base64_decode(b) == b64decode(b) + True + >>> base64_decode(c) == b64decode(c) + True + >>> base64_decode("abc") + Traceback (most recent call last): + ... + AssertionError: Incorrect padding + """ + # Make sure encoded_data is either a string or a bytes-like object + if not isinstance(encoded_data, bytes) and not isinstance(encoded_data, str): + raise TypeError( + "argument should be a bytes-like object or ASCII string, not " + f"'{encoded_data.__class__.__name__}'" + ) + + # In case encoded_data is a bytes-like object, make sure it contains only + # ASCII characters so we convert it to a string object + if isinstance(encoded_data, bytes): + try: + encoded_data = encoded_data.decode("utf-8") + except UnicodeDecodeError: + raise ValueError("base64 encoded data should only contain ASCII characters") + + padding = encoded_data.count("=") + + # Check if the encoded string contains non base64 characters + if padding: + assert all( + char in B64_CHARSET for char in encoded_data[:-padding] + ), "Invalid base64 character(s) found." + else: + assert all( + char in B64_CHARSET for char in encoded_data + ), "Invalid base64 character(s) found." + + # Check the padding + assert len(encoded_data) % 4 == 0 and padding < 3, "Incorrect padding" + + if padding: + # Remove padding if there is one + encoded_data = encoded_data[:-padding] + + binary_stream = "".join( + bin(B64_CHARSET.index(char))[2:].zfill(6) for char in encoded_data + )[: -padding * 2] + else: + binary_stream = "".join( + bin(B64_CHARSET.index(char))[2:].zfill(6) for char in encoded_data + ) + + data = [ + int(binary_stream[index : index + 8], 2) + for index in range(0, len(binary_stream), 8) + ] + + return bytes(data) + + +if __name__ == "__main__": + import doctest + + doctest.testmod() From ac6a160f1b390ac3162e3834066dcc787d32d29b Mon Sep 17 00:00:00 2001 From: Vivek Date: Thu, 26 Nov 2020 06:57:00 +0530 Subject: [PATCH 1030/1071] added binary_count_trailing_zeros.py (#2557) * added binary_count_trailing_zeros.py * updated binary_count_trailing_zeros.py file * changed file name to count_trailing_zeros.py * updated count_trailing_zeros.py * resolved flake8 error * renamed to binary_count_trailing_zeros.py * added required changes * resolved pre-commit error * added count_setbits.py * resolved errors * changed name to binary_count_setbits.py * updated file * reformated file --- bit_manipulation/binary_count_setbits.py | 41 +++++++++++++++++ .../binary_count_trailing_zeros.py | 44 +++++++++++++++++++ 2 files changed, 85 insertions(+) create mode 100644 bit_manipulation/binary_count_setbits.py create mode 100644 bit_manipulation/binary_count_trailing_zeros.py diff --git a/bit_manipulation/binary_count_setbits.py b/bit_manipulation/binary_count_setbits.py new file mode 100644 index 000000000000..3c92694533aa --- /dev/null +++ b/bit_manipulation/binary_count_setbits.py @@ -0,0 +1,41 @@ +def binary_count_setbits(a: int) -> int: + """ + Take in 1 integer, return a number that is + the number of 1's in binary representation of that number. + + >>> binary_count_setbits(25) + 3 + >>> binary_count_setbits(36) + 2 + >>> binary_count_setbits(16) + 1 + >>> binary_count_setbits(58) + 4 + >>> binary_count_setbits(4294967295) + 32 + >>> binary_count_setbits(0) + 0 + >>> binary_count_setbits(-10) + Traceback (most recent call last): + ... + ValueError: Input value must be a positive integer + >>> binary_count_setbits(0.8) + Traceback (most recent call last): + ... + TypeError: Input value must be a 'int' type + >>> binary_count_setbits("0") + Traceback (most recent call last): + ... + TypeError: '<' not supported between instances of 'str' and 'int' + """ + if a < 0: + raise ValueError("Input value must be a positive integer") + elif isinstance(a, float): + raise TypeError("Input value must be a 'int' type") + return bin(a).count("1") + + +if __name__ == "__main__": + import doctest + + doctest.testmod() diff --git a/bit_manipulation/binary_count_trailing_zeros.py b/bit_manipulation/binary_count_trailing_zeros.py new file mode 100644 index 000000000000..f401c4ab9266 --- /dev/null +++ b/bit_manipulation/binary_count_trailing_zeros.py @@ -0,0 +1,44 @@ +from math import log2 + + +def binary_count_trailing_zeros(a: int) -> int: + """ + Take in 1 integer, return a number that is + the number of trailing zeros in binary representation of that number. + + >>> binary_count_trailing_zeros(25) + 0 + >>> binary_count_trailing_zeros(36) + 2 + >>> binary_count_trailing_zeros(16) + 4 + >>> binary_count_trailing_zeros(58) + 1 + >>> binary_count_trailing_zeros(4294967296) + 32 + >>> binary_count_trailing_zeros(0) + 0 + >>> binary_count_trailing_zeros(-10) + Traceback (most recent call last): + ... + ValueError: Input value must be a positive integer + >>> binary_count_trailing_zeros(0.8) + Traceback (most recent call last): + ... + TypeError: Input value must be a 'int' type + >>> binary_count_trailing_zeros("0") + Traceback (most recent call last): + ... + TypeError: '<' not supported between instances of 'str' and 'int' + """ + if a < 0: + raise ValueError("Input value must be a positive integer") + elif isinstance(a, float): + raise TypeError("Input value must be a 'int' type") + return 0 if (a == 0) else int(log2(a & -a)) + + +if __name__ == "__main__": + import doctest + + doctest.testmod() From c5fb0a95043cb93ca522ca0633bf75a656d63b7f Mon Sep 17 00:00:00 2001 From: Dhruv Manilawala Date: Fri, 27 Nov 2020 15:27:12 +0530 Subject: [PATCH 1031/1071] Cleaned up knapsack and images directory (#3972) --- images/Travis_CI_fail_1.png | Bin 80257 -> 0 bytes images/Travis_CI_fail_2.png | Bin 45660 -> 0 bytes images/__init__.py | 0 {greedy_method => knapsack}/greedy_knapsack.py | 0 {greedy_method => knapsack/tests}/__init__.py | 0 .../tests/test_greedy_knapsack.py | 2 +- knapsack/{ => tests}/test_knapsack.py | 0 7 files changed, 1 insertion(+), 1 deletion(-) delete mode 100644 images/Travis_CI_fail_1.png delete mode 100644 images/Travis_CI_fail_2.png delete mode 100644 images/__init__.py rename {greedy_method => knapsack}/greedy_knapsack.py (100%) rename {greedy_method => knapsack/tests}/__init__.py (100%) rename greedy_method/test_knapsack.py => knapsack/tests/test_greedy_knapsack.py (98%) rename knapsack/{ => tests}/test_knapsack.py (100%) diff --git a/images/Travis_CI_fail_1.png b/images/Travis_CI_fail_1.png deleted file mode 100644 index 451e54e4844a0b9f7d501c3d7682869d9acd97fc..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 80257 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 diff --git a/images/Travis_CI_fail_2.png b/images/Travis_CI_fail_2.png deleted file mode 100644 index caa406099da12215efb783b13ae780c4c052fe7d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 45660 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 Date: Fri, 27 Nov 2020 09:48:20 -0500 Subject: [PATCH 1032/1071] Create euclidean_distance.py (#3350) * Create distance_formula.py * Remove whitespace * Update distance_formula.py * Update distance_formula.py * Update distance_formula.py * Generalize * Grammar mistake * Rename distance_formula.py to euclidean_distance.py * Update euclidean_distance.py * v1 - > v2 * Update euclidean_distance.py * Update euclidean_distance.py * Update euclidean_distance.py * Update euclidean_distance.py * Update euclidean_distance.py * Update euclidean_distance.py * Update maths/euclidean_distance.py Co-authored-by: Christian Clauss * Update euclidean_distance.py * Update euclidean_distance.py * Update euclidean_distance.py * Update euclidean_distance.py * Update euclidean_distance.py * Update euclidean_distance.py * Update euclidean_distance.py * Update euclidean_distance.py * Update euclidean_distance.py * Update euclidean_distance.py * Update euclidean_distance.py * Update euclidean_distance.py * Update euclidean_distance.py * Update euclidean_distance.py * Update euclidean_distance.py * Update euclidean_distance.py * Update euclidean_distance.py * Update euclidean_distance.py * Update euclidean_distance.py * Update maths/euclidean_distance.py Co-authored-by: Christian Clauss Co-authored-by: Christian Clauss --- maths/euclidean_distance.py | 62 +++++++++++++++++++++++++++++++++++++ 1 file changed, 62 insertions(+) create mode 100644 maths/euclidean_distance.py diff --git a/maths/euclidean_distance.py b/maths/euclidean_distance.py new file mode 100644 index 000000000000..6e0da6370219 --- /dev/null +++ b/maths/euclidean_distance.py @@ -0,0 +1,62 @@ +from typing import Iterable, Union + +import numpy as np + +Vector = Union[Iterable[float], Iterable[int], np.ndarray] +VectorOut = Union[np.float64, int, float] + + +def euclidean_distance(vector_1: Vector, vector_2: Vector) -> VectorOut: + """ + Calculate the distance between the two endpoints of two vectors. + A vector is defined as a list, tuple, or numpy 1D array. + >>> euclidean_distance((0, 0), (2, 2)) + 2.8284271247461903 + >>> euclidean_distance(np.array([0, 0, 0]), np.array([2, 2, 2])) + 3.4641016151377544 + >>> euclidean_distance(np.array([1, 2, 3, 4]), np.array([5, 6, 7, 8])) + 8.0 + >>> euclidean_distance([1, 2, 3, 4], [5, 6, 7, 8]) + 8.0 + """ + return np.sqrt(np.sum((np.asarray(vector_1) - np.asarray(vector_2)) ** 2)) + + +def euclidean_distance_no_np(vector_1: Vector, vector_2: Vector) -> VectorOut: + """ + Calculate the distance between the two endpoints of two vectors without numpy. + A vector is defined as a list, tuple, or numpy 1D array. + >>> euclidean_distance_no_np((0, 0), (2, 2)) + 2.8284271247461903 + >>> euclidean_distance_no_np([1, 2, 3, 4], [5, 6, 7, 8]) + 8.0 + """ + return sum((v1 - v2) ** 2 for v1, v2 in zip(vector_1, vector_2)) ** (1 / 2) + + +if __name__ == "__main__": + + def benchmark() -> None: + """ + Benchmarks + """ + from timeit import timeit + + print("Without Numpy") + print( + timeit( + "euclidean_distance_no_np([1, 2, 3], [4, 5, 6])", + number=10000, + globals=globals(), + ) + ) + print("With Numpy") + print( + timeit( + "euclidean_distance([1, 2, 3], [4, 5, 6])", + number=10000, + globals=globals(), + ) + ) + + benchmark() From 9333d2f0174d76f36ff27961f18d8110d67cbc67 Mon Sep 17 00:00:00 2001 From: Anubhav Solanki <55892162+AnubhavSolanki@users.noreply.github.com> Date: Fri, 27 Nov 2020 21:33:17 +0530 Subject: [PATCH 1033/1071] Create weight_conversion.py (#3964) * Create weight_conversion.py * Update weight_conversion.py Co-authored-by: Dhruv Manilawala --- conversions/weight_conversion.py | 287 +++++++++++++++++++++++++++++++ 1 file changed, 287 insertions(+) create mode 100644 conversions/weight_conversion.py diff --git a/conversions/weight_conversion.py b/conversions/weight_conversion.py new file mode 100644 index 000000000000..85515f2f6f88 --- /dev/null +++ b/conversions/weight_conversion.py @@ -0,0 +1,287 @@ +""" +Conversion of weight units. + +__author__ = "Anubhav Solanki" +__license__ = "MIT" +__version__ = "1.0.0" +__maintainer__ = "Anubhav Solanki" +__email__ = "anubhavsolanki0@gmail.com" + +USAGE : +-> Import this file into their respective project. +-> Use the function weight_conversion() for conversion of weight units. +-> Parameters : + -> from_type : From which type you want to convert + -> to_type : To which type you want to convert + -> value : the value which you want to convert + +REFERENCES : + +-> Wikipedia reference: https://en.wikipedia.org/wiki/Kilogram +-> Wikipedia reference: https://en.wikipedia.org/wiki/Gram +-> Wikipedia reference: https://en.wikipedia.org/wiki/Millimetre +-> Wikipedia reference: https://en.wikipedia.org/wiki/Tonne +-> Wikipedia reference: https://en.wikipedia.org/wiki/Long_ton +-> Wikipedia reference: https://en.wikipedia.org/wiki/Short_ton +-> Wikipedia reference: https://en.wikipedia.org/wiki/Pound +-> Wikipedia reference: https://en.wikipedia.org/wiki/Ounce +-> Wikipedia reference: https://en.wikipedia.org/wiki/Fineness#Karat +-> Wikipedia reference: https://en.wikipedia.org/wiki/Dalton_(unit) +""" + +KILOGRAM_CHART = { + "kilogram": 1, + "gram": pow(10, 3), + "milligram": pow(10, 6), + "metric-ton": pow(10, -3), + "long-ton": 0.0009842073, + "short-ton": 0.0011023122, + "pound": 2.2046244202, + "ounce": 35.273990723, + "carrat": 5000, + "atomic-mass-unit": 6.022136652e26, +} + +WEIGHT_TYPE_CHART = { + "kilogram": 1, + "gram": pow(10, -3), + "milligram": pow(10, -6), + "metric-ton": pow(10, 3), + "long-ton": 1016.04608, + "short-ton": 907.184, + "pound": 0.453592, + "ounce": 0.0283495, + "carrat": 0.0002, + "atomic-mass-unit": 1.660540199e-27, +} + + +def weight_conversion(from_type: str, to_type: str, value: float) -> float: + """ + Conversion of weight unit with the help of KILOGRAM_CHART + + "kilogram" : 1, + "gram" : pow(10, 3), + "milligram" : pow(10, 6), + "metric-ton" : pow(10, -3), + "long-ton" : 0.0009842073, + "short-ton" : 0.0011023122, + "pound" : 2.2046244202, + "ounce" : 35.273990723, + "carrat" : 5000, + "atomic-mass-unit" : 6.022136652E+26 + + >>> weight_conversion("kilogram","kilogram",4) + 4 + >>> weight_conversion("kilogram","gram",1) + 1000 + >>> weight_conversion("kilogram","milligram",4) + 4000000 + >>> weight_conversion("kilogram","metric-ton",4) + 0.004 + >>> weight_conversion("kilogram","long-ton",3) + 0.0029526219 + >>> weight_conversion("kilogram","short-ton",1) + 0.0011023122 + >>> weight_conversion("kilogram","pound",4) + 8.8184976808 + >>> weight_conversion("kilogram","ounce",4) + 141.095962892 + >>> weight_conversion("kilogram","carrat",3) + 15000 + >>> weight_conversion("kilogram","atomic-mass-unit",1) + 6.022136652e+26 + >>> weight_conversion("gram","kilogram",1) + 0.001 + >>> weight_conversion("gram","gram",3) + 3.0 + >>> weight_conversion("gram","milligram",2) + 2000.0 + >>> weight_conversion("gram","metric-ton",4) + 4e-06 + >>> weight_conversion("gram","long-ton",3) + 2.9526219e-06 + >>> weight_conversion("gram","short-ton",3) + 3.3069366000000003e-06 + >>> weight_conversion("gram","pound",3) + 0.0066138732606 + >>> weight_conversion("gram","ounce",1) + 0.035273990723 + >>> weight_conversion("gram","carrat",2) + 10.0 + >>> weight_conversion("gram","atomic-mass-unit",1) + 6.022136652e+23 + >>> weight_conversion("milligram","kilogram",1) + 1e-06 + >>> weight_conversion("milligram","gram",2) + 0.002 + >>> weight_conversion("milligram","milligram",3) + 3.0 + >>> weight_conversion("milligram","metric-ton",3) + 3e-09 + >>> weight_conversion("milligram","long-ton",3) + 2.9526219e-09 + >>> weight_conversion("milligram","short-ton",1) + 1.1023122e-09 + >>> weight_conversion("milligram","pound",3) + 6.6138732605999995e-06 + >>> weight_conversion("milligram","ounce",2) + 7.054798144599999e-05 + >>> weight_conversion("milligram","carrat",1) + 0.005 + >>> weight_conversion("milligram","atomic-mass-unit",1) + 6.022136652e+20 + >>> weight_conversion("metric-ton","kilogram",2) + 2000 + >>> weight_conversion("metric-ton","gram",2) + 2000000 + >>> weight_conversion("metric-ton","milligram",3) + 3000000000 + >>> weight_conversion("metric-ton","metric-ton",2) + 2.0 + >>> weight_conversion("metric-ton","long-ton",3) + 2.9526219 + >>> weight_conversion("metric-ton","short-ton",2) + 2.2046244 + >>> weight_conversion("metric-ton","pound",3) + 6613.8732606 + >>> weight_conversion("metric-ton","ounce",4) + 141095.96289199998 + >>> weight_conversion("metric-ton","carrat",4) + 20000000 + >>> weight_conversion("metric-ton","atomic-mass-unit",1) + 6.022136652e+29 + >>> weight_conversion("long-ton","kilogram",4) + 4064.18432 + >>> weight_conversion("long-ton","gram",4) + 4064184.32 + >>> weight_conversion("long-ton","milligram",3) + 3048138240.0 + >>> weight_conversion("long-ton","metric-ton",4) + 4.06418432 + >>> weight_conversion("long-ton","long-ton",3) + 2.999999907217152 + >>> weight_conversion("long-ton","short-ton",1) + 1.119999989746176 + >>> weight_conversion("long-ton","pound",3) + 6720.000000049448 + >>> weight_conversion("long-ton","ounce",1) + 35840.000000060514 + >>> weight_conversion("long-ton","carrat",4) + 20320921.599999998 + >>> weight_conversion("long-ton","atomic-mass-unit",4) + 2.4475073353955697e+30 + >>> weight_conversion("short-ton","kilogram",3) + 2721.5519999999997 + >>> weight_conversion("short-ton","gram",3) + 2721552.0 + >>> weight_conversion("short-ton","milligram",1) + 907184000.0 + >>> weight_conversion("short-ton","metric-ton",4) + 3.628736 + >>> weight_conversion("short-ton","long-ton",3) + 2.6785713457296 + >>> weight_conversion("short-ton","short-ton",3) + 2.9999999725344 + >>> weight_conversion("short-ton","pound",2) + 4000.0000000294335 + >>> weight_conversion("short-ton","ounce",4) + 128000.00000021611 + >>> weight_conversion("short-ton","carrat",4) + 18143680.0 + >>> weight_conversion("short-ton","atomic-mass-unit",1) + 5.463186016507968e+29 + >>> weight_conversion("pound","kilogram",4) + 1.814368 + >>> weight_conversion("pound","gram",2) + 907.184 + >>> weight_conversion("pound","milligram",3) + 1360776.0 + >>> weight_conversion("pound","metric-ton",3) + 0.001360776 + >>> weight_conversion("pound","long-ton",2) + 0.0008928571152432 + >>> weight_conversion("pound","short-ton",1) + 0.0004999999954224 + >>> weight_conversion("pound","pound",3) + 3.0000000000220752 + >>> weight_conversion("pound","ounce",1) + 16.000000000027015 + >>> weight_conversion("pound","carrat",1) + 2267.96 + >>> weight_conversion("pound","atomic-mass-unit",4) + 1.0926372033015936e+27 + >>> weight_conversion("ounce","kilogram",3) + 0.0850485 + >>> weight_conversion("ounce","gram",3) + 85.0485 + >>> weight_conversion("ounce","milligram",4) + 113398.0 + >>> weight_conversion("ounce","metric-ton",4) + 0.000113398 + >>> weight_conversion("ounce","long-ton",4) + 0.0001116071394054 + >>> weight_conversion("ounce","short-ton",4) + 0.0001249999988556 + >>> weight_conversion("ounce","pound",1) + 0.0625000000004599 + >>> weight_conversion("ounce","ounce",2) + 2.000000000003377 + >>> weight_conversion("ounce","carrat",1) + 141.7475 + >>> weight_conversion("ounce","atomic-mass-unit",1) + 1.70724563015874e+25 + >>> weight_conversion("carrat","kilogram",1) + 0.0002 + >>> weight_conversion("carrat","gram",4) + 0.8 + >>> weight_conversion("carrat","milligram",2) + 400.0 + >>> weight_conversion("carrat","metric-ton",2) + 4.0000000000000003e-07 + >>> weight_conversion("carrat","long-ton",3) + 5.9052438e-07 + >>> weight_conversion("carrat","short-ton",4) + 8.818497600000002e-07 + >>> weight_conversion("carrat","pound",1) + 0.00044092488404000004 + >>> weight_conversion("carrat","ounce",2) + 0.0141095962892 + >>> weight_conversion("carrat","carrat",4) + 4.0 + >>> weight_conversion("carrat","atomic-mass-unit",4) + 4.8177093216e+23 + >>> weight_conversion("atomic-mass-unit","kilogram",4) + 6.642160796e-27 + >>> weight_conversion("atomic-mass-unit","gram",2) + 3.321080398e-24 + >>> weight_conversion("atomic-mass-unit","milligram",2) + 3.3210803980000002e-21 + >>> weight_conversion("atomic-mass-unit","metric-ton",3) + 4.9816205970000004e-30 + >>> weight_conversion("atomic-mass-unit","long-ton",3) + 4.9029473573977584e-30 + >>> weight_conversion("atomic-mass-unit","short-ton",1) + 1.830433719948128e-30 + >>> weight_conversion("atomic-mass-unit","pound",3) + 1.0982602420317504e-26 + >>> weight_conversion("atomic-mass-unit","ounce",2) + 1.1714775914938915e-25 + >>> weight_conversion("atomic-mass-unit","carrat",2) + 1.660540199e-23 + >>> weight_conversion("atomic-mass-unit","atomic-mass-unit",2) + 1.999999998903455 + """ + if to_type not in KILOGRAM_CHART or from_type not in WEIGHT_TYPE_CHART: + raise ValueError( + f"Invalid 'from_type' or 'to_type' value: {from_type!r}, {to_type!r}\n" + f"Supported values are: {', '.join(WEIGHT_TYPE_CHART)}" + ) + return value * KILOGRAM_CHART[to_type] * WEIGHT_TYPE_CHART[from_type] + + +if __name__ == "__main__": + + import doctest + + doctest.testmod() From 1e1708b8a1644d994ad004cc6fe20893a328519c Mon Sep 17 00:00:00 2001 From: fpringle Date: Fri, 27 Nov 2020 17:08:14 +0100 Subject: [PATCH 1034/1071] Added solution for Project Euler problem 77 (#3132) * Added solution for Project Euler problem 77. * Update docstrings, doctest, type annotations and 0-padding in directory name. Reference: #3256 * Implemented lru_cache, better type hints, more doctests for problem 77 * updating DIRECTORY.md * updating DIRECTORY.md * Added solution for Project Euler problem 77. Fixes: 2695 * Update docstrings, doctest, type annotations and 0-padding in directory name. Reference: #3256 * Implemented lru_cache, better type hints, more doctests for problem 77 * better variable names Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> --- DIRECTORY.md | 2 + project_euler/problem_077/__init__.py | 0 project_euler/problem_077/sol1.py | 81 +++++++++++++++++++++++++++ 3 files changed, 83 insertions(+) create mode 100644 project_euler/problem_077/__init__.py create mode 100644 project_euler/problem_077/sol1.py diff --git a/DIRECTORY.md b/DIRECTORY.md index e1e57307d593..7fe7c63a2571 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -709,6 +709,8 @@ * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_075/sol1.py) * Problem 076 * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_076/sol1.py) + * Problem 077 + * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_077/sol1.py) * Problem 080 * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_080/sol1.py) * Problem 081 diff --git a/project_euler/problem_077/__init__.py b/project_euler/problem_077/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/project_euler/problem_077/sol1.py b/project_euler/problem_077/sol1.py new file mode 100644 index 000000000000..e92992a90ab3 --- /dev/null +++ b/project_euler/problem_077/sol1.py @@ -0,0 +1,81 @@ +""" +Project Euler Problem 77: https://projecteuler.net/problem=77 + +It is possible to write ten as the sum of primes in exactly five different ways: + +7 + 3 +5 + 5 +5 + 3 + 2 +3 + 3 + 2 + 2 +2 + 2 + 2 + 2 + 2 + +What is the first value which can be written as the sum of primes in over +five thousand different ways? +""" + +from functools import lru_cache +from math import ceil +from typing import Optional, Set + +NUM_PRIMES = 100 + +primes = set(range(3, NUM_PRIMES, 2)) +primes.add(2) +prime: int + +for prime in range(3, ceil(NUM_PRIMES ** 0.5), 2): + if prime not in primes: + continue + primes.difference_update(set(range(prime * prime, NUM_PRIMES, prime))) + + +@lru_cache(maxsize=100) +def partition(number_to_partition: int) -> Set[int]: + """ + Return a set of integers corresponding to unique prime partitions of n. + The unique prime partitions can be represented as unique prime decompositions, + e.g. (7+3) <-> 7*3 = 12, (3+3+2+2) = 3*3*2*2 = 36 + >>> partition(10) + {32, 36, 21, 25, 30} + >>> partition(15) + {192, 160, 105, 44, 112, 243, 180, 150, 216, 26, 125, 126} + >>> len(partition(20)) + 26 + """ + if number_to_partition < 0: + return set() + elif number_to_partition == 0: + return {1} + + ret: Set[int] = set() + prime: int + sub: int + + for prime in primes: + if prime > number_to_partition: + continue + for sub in partition(number_to_partition - prime): + ret.add(sub * prime) + + return ret + + +def solution(number_unique_partitions: int = 5000) -> Optional[int]: + """ + Return the smallest integer that can be written as the sum of primes in over + m unique ways. + >>> solution(4) + 10 + >>> solution(500) + 45 + >>> solution(1000) + 53 + """ + for number_to_partition in range(1, NUM_PRIMES): + if len(partition(number_to_partition)) > number_unique_partitions: + return number_to_partition + return None + + +if __name__ == "__main__": + print(f"{solution() = }") From 9c6080a6fc31ae7737535ba305de20a5cc0805b5 Mon Sep 17 00:00:00 2001 From: arif599 <59244462+arif599@users.noreply.github.com> Date: Sat, 28 Nov 2020 05:50:18 +0000 Subject: [PATCH 1035/1071] data_structures/linked_list: Add __str__() function (#3961) * Adding __str__() function * Removing white space * Update data_structures/linked_list/__init__.py Co-authored-by: xcodz-dot <71920621+xcodz-dot@users.noreply.github.com> * Adding type hints * Update __init__.py * Update __init__.py * Adding the changes requested * Updating to fix pre-commit * Updating __init__.py * Updating __init__.py Co-authored-by: xcodz-dot <71920621+xcodz-dot@users.noreply.github.com> --- data_structures/linked_list/__init__.py | 46 +++++++++++++++++++++---- 1 file changed, 40 insertions(+), 6 deletions(-) diff --git a/data_structures/linked_list/__init__.py b/data_structures/linked_list/__init__.py index 3ddfea5c5abf..a5f5537b1d96 100644 --- a/data_structures/linked_list/__init__.py +++ b/data_structures/linked_list/__init__.py @@ -1,19 +1,30 @@ +""" +Linked Lists consists of Nodes. +Nodes contain data and also may link to other nodes: + - Head Node: First node, the address of the + head node gives us access of the complete list + - Last node: points to null +""" + +from typing import Any + + class Node: - def __init__(self, item, next): + def __init__(self, item: Any, next: Any) -> None: self.item = item self.next = next class LinkedList: - def __init__(self): + def __init__(self) -> None: self.head = None self.size = 0 - def add(self, item): + def add(self, item: Any) -> None: self.head = Node(item, self.head) self.size += 1 - def remove(self): + def remove(self) -> Any: if self.is_empty(): return None else: @@ -22,10 +33,33 @@ def remove(self): self.size -= 1 return item - def is_empty(self): + def is_empty(self) -> bool: return self.head is None - def __len__(self): + def __str__(self) -> str: + """ + >>> linked_list = LinkedList() + >>> linked_list.add(23) + >>> linked_list.add(14) + >>> linked_list.add(9) + >>> print(linked_list) + 9 --> 14 --> 23 + """ + if not self.is_empty: + return "" + else: + iterate = self.head + item_str = "" + item_list = [] + while iterate: + item_list.append(str(iterate.item)) + iterate = iterate.next + + item_str = " --> ".join(item_list) + + return item_str + + def __len__(self) -> int: """ >>> linked_list = LinkedList() >>> len(linked_list) From 8cafadd759a519ce5b3c60f3ba4a0819e42d060f Mon Sep 17 00:00:00 2001 From: xcodz-dot <71920621+xcodz-dot@users.noreply.github.com> Date: Sat, 28 Nov 2020 19:09:27 +0530 Subject: [PATCH 1036/1071] Update CONTRIBUTING.md with pre-commit plugin instructions (#3979) * Update CONTRIBUTING.md * Update CONTRIBUTING.md * Update CONTRIBUTING.md * Update CONTRIBUTING.md Co-authored-by: Christian Clauss * Update CONTRIBUTING.md Co-authored-by: Christian Clauss * Update CONTRIBUTING.md Co-authored-by: Dhruv Manilawala Co-authored-by: Christian Clauss --- CONTRIBUTING.md | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index eedcb0250169..e4c81a5ecd98 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -49,6 +49,19 @@ Algorithms should: Algorithms in this repo should not be how-to examples for existing Python packages. Instead, they should perform internal calculations or manipulations to convert input values into different output values. Those calculations or manipulations can use data types, classes, or functions of existing Python packages but each algorithm in this repo should add unique value. +#### Pre-commit plugin +Use [pre-commit](https://pre-commit.com/#installation) to automatically format your code to match our coding style: + +```bash +python3 -m pip install pre-commit # required only once +pre-commit install +``` +That's it! The plugin will run every time you commit any changes. If there are any errors found during the run, fix them and commit those changes. You can even run the plugin manually on all files: + +```bash +pre-commit run --all-files --show-diff-on-failure +``` + #### Coding Style We want your work to be readable by others; therefore, we encourage you to note the following: @@ -64,14 +77,14 @@ We want your work to be readable by others; therefore, we encourage you to note - Please consider running [__psf/black__](https://github.com/python/black) on your Python file(s) before submitting your pull request. This is not yet a requirement but it does make your code more readable and automatically aligns it with much of [PEP 8](https://www.python.org/dev/peps/pep-0008/). There are other code formatters (autopep8, yapf) but the __black__ formatter is now hosted by the Python Software Foundation. To use it, ```bash - pip3 install black # only required the first time + python3 -m pip install black # only required the first time black . ``` - All submissions will need to pass the test __flake8 . --ignore=E203,W503 --max-line-length=88__ before they will be accepted so if possible, try this test locally on your Python file(s) before submitting your pull request. ```bash - pip3 install flake8 # only required the first time + python3 -m pip install flake8 # only required the first time flake8 . --ignore=E203,W503 --max-line-length=88 --show-source ``` From bfb0c3533d88f82c18f7ae792b2d2e154b9b51c7 Mon Sep 17 00:00:00 2001 From: Du Yuanchao Date: Sat, 28 Nov 2020 22:42:30 +0800 Subject: [PATCH 1037/1071] Fixed LGTM and typehint (#3970) * fixed LGTM fixed typehint * updating DIRECTORY.md * Update lucas_series.py * Update lucas_series.py Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> Co-authored-by: John Law --- DIRECTORY.md | 12 +++++++++++- maths/lucas_series.py | 27 ++++++++++++--------------- 2 files changed, 23 insertions(+), 16 deletions(-) diff --git a/DIRECTORY.md b/DIRECTORY.md index 7fe7c63a2571..079dae1884bc 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -26,6 +26,8 @@ ## Bit Manipulation * [Binary And Operator](https://github.com/TheAlgorithms/Python/blob/master/bit_manipulation/binary_and_operator.py) + * [Binary Count Setbits](https://github.com/TheAlgorithms/Python/blob/master/bit_manipulation/binary_count_setbits.py) + * [Binary Count Trailing Zeros](https://github.com/TheAlgorithms/Python/blob/master/bit_manipulation/binary_count_trailing_zeros.py) * [Binary Or Operator](https://github.com/TheAlgorithms/Python/blob/master/bit_manipulation/binary_or_operator.py) * [Binary Xor Operator](https://github.com/TheAlgorithms/Python/blob/master/bit_manipulation/binary_xor_operator.py) * [Single Bit Manipulation Operations](https://github.com/TheAlgorithms/Python/blob/master/bit_manipulation/single_bit_manipulation_operations.py) @@ -47,7 +49,7 @@ * [Atbash](https://github.com/TheAlgorithms/Python/blob/master/ciphers/atbash.py) * [Base16](https://github.com/TheAlgorithms/Python/blob/master/ciphers/base16.py) * [Base32](https://github.com/TheAlgorithms/Python/blob/master/ciphers/base32.py) - * [Base64 Cipher](https://github.com/TheAlgorithms/Python/blob/master/ciphers/base64_cipher.py) + * [Base64 Encoding](https://github.com/TheAlgorithms/Python/blob/master/ciphers/base64_encoding.py) * [Base85](https://github.com/TheAlgorithms/Python/blob/master/ciphers/base85.py) * [Beaufort Cipher](https://github.com/TheAlgorithms/Python/blob/master/ciphers/beaufort_cipher.py) * [Brute Force Caesar Cipher](https://github.com/TheAlgorithms/Python/blob/master/ciphers/brute_force_caesar_cipher.py) @@ -101,6 +103,7 @@ * [Decimal To Octal](https://github.com/TheAlgorithms/Python/blob/master/conversions/decimal_to_octal.py) * [Hexadecimal To Decimal](https://github.com/TheAlgorithms/Python/blob/master/conversions/hexadecimal_to_decimal.py) * [Molecular Chemistry](https://github.com/TheAlgorithms/Python/blob/master/conversions/molecular_chemistry.py) + * [Octal To Decimal](https://github.com/TheAlgorithms/Python/blob/master/conversions/octal_to_decimal.py) * [Prefix Conversions](https://github.com/TheAlgorithms/Python/blob/master/conversions/prefix_conversions.py) * [Roman To Integer](https://github.com/TheAlgorithms/Python/blob/master/conversions/roman_to_integer.py) * [Temperature Conversions](https://github.com/TheAlgorithms/Python/blob/master/conversions/temperature_conversions.py) @@ -207,6 +210,7 @@ * [Heaps Algorithm Iterative](https://github.com/TheAlgorithms/Python/blob/master/divide_and_conquer/heaps_algorithm_iterative.py) * [Inversions](https://github.com/TheAlgorithms/Python/blob/master/divide_and_conquer/inversions.py) * [Kth Order Statistic](https://github.com/TheAlgorithms/Python/blob/master/divide_and_conquer/kth_order_statistic.py) + * [Max Difference Pair](https://github.com/TheAlgorithms/Python/blob/master/divide_and_conquer/max_difference_pair.py) * [Max Subarray Sum](https://github.com/TheAlgorithms/Python/blob/master/divide_and_conquer/max_subarray_sum.py) * [Mergesort](https://github.com/TheAlgorithms/Python/blob/master/divide_and_conquer/mergesort.py) * [Peak](https://github.com/TheAlgorithms/Python/blob/master/divide_and_conquer/peak.py) @@ -243,6 +247,9 @@ * [Subset Generation](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/subset_generation.py) * [Sum Of Subset](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/sum_of_subset.py) +## Electronics + * [Ohms Law](https://github.com/TheAlgorithms/Python/blob/master/electronics/ohms_law.py) + ## File Transfer * [Receive File](https://github.com/TheAlgorithms/Python/blob/master/file_transfer/receive_file.py) * [Send File](https://github.com/TheAlgorithms/Python/blob/master/file_transfer/send_file.py) @@ -804,6 +811,7 @@ * [Gnome Sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/gnome_sort.py) * [Heap Sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/heap_sort.py) * [Insertion Sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/insertion_sort.py) + * [Intro Sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/intro_sort.py) * [Iterative Merge Sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/iterative_merge_sort.py) * [Merge Insertion Sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/merge_insertion_sort.py) * [Merge Sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/merge_sort.py) @@ -877,6 +885,8 @@ * [Get Imdb Top 250 Movies Csv](https://github.com/TheAlgorithms/Python/blob/master/web_programming/get_imdb_top_250_movies_csv.py) * [Get Imdbtop](https://github.com/TheAlgorithms/Python/blob/master/web_programming/get_imdbtop.py) * [Instagram Crawler](https://github.com/TheAlgorithms/Python/blob/master/web_programming/instagram_crawler.py) + * [Instagram Pic](https://github.com/TheAlgorithms/Python/blob/master/web_programming/instagram_pic.py) * [Recaptcha Verification](https://github.com/TheAlgorithms/Python/blob/master/web_programming/recaptcha_verification.py) * [Slack Message](https://github.com/TheAlgorithms/Python/blob/master/web_programming/slack_message.py) + * [Test Fetch Github Info](https://github.com/TheAlgorithms/Python/blob/master/web_programming/test_fetch_github_info.py) * [World Covid19 Stats](https://github.com/TheAlgorithms/Python/blob/master/web_programming/world_covid19_stats.py) diff --git a/maths/lucas_series.py b/maths/lucas_series.py index 02eae8d8c658..6b32c2022e13 100644 --- a/maths/lucas_series.py +++ b/maths/lucas_series.py @@ -3,7 +3,7 @@ """ -def recursive_lucas_number(n): +def recursive_lucas_number(n_th_number: int) -> int: """ Returns the nth lucas number >>> recursive_lucas_number(1) @@ -19,17 +19,19 @@ def recursive_lucas_number(n): ... TypeError: recursive_lucas_number accepts only integer arguments. """ - if n == 1: - return n - if n == 0: - return 2 - if not isinstance(n, int): + if not isinstance(n_th_number, int): raise TypeError("recursive_lucas_number accepts only integer arguments.") + if n_th_number == 0: + return 2 + if n_th_number == 1: + return 1 - return recursive_lucas_number(n - 1) + recursive_lucas_number(n - 2) + return recursive_lucas_number(n_th_number - 1) + recursive_lucas_number( + n_th_number - 2 + ) -def dynamic_lucas_number(n: int) -> int: +def dynamic_lucas_number(n_th_number: int) -> int: """ Returns the nth lucas number >>> dynamic_lucas_number(1) @@ -45,14 +47,10 @@ def dynamic_lucas_number(n: int) -> int: ... TypeError: dynamic_lucas_number accepts only integer arguments. """ - if not isinstance(n, int): + if not isinstance(n_th_number, int): raise TypeError("dynamic_lucas_number accepts only integer arguments.") - if n == 0: - return 2 - if n == 1: - return 1 a, b = 2, 1 - for i in range(n): + for i in range(n_th_number): a, b = b, a + b return a @@ -62,7 +60,6 @@ def dynamic_lucas_number(n: int) -> int: testmod() n = int(input("Enter the number of terms in lucas series:\n").strip()) - n = int(input("Enter the number of terms in lucas series:\n").strip()) print("Using recursive function to calculate lucas series:") print(" ".join(str(recursive_lucas_number(i)) for i in range(n))) print("\nUsing dynamic function to calculate lucas series:") From 52a6213ddc804f571e1c54fdd8e9b0068acaf06e Mon Sep 17 00:00:00 2001 From: Sullivan <38718448+Epic-R-R@users.noreply.github.com> Date: Sun, 29 Nov 2020 15:44:18 +0330 Subject: [PATCH 1038/1071] Instagram Video and IGTV downloader (#3981) * Instagram Video and IGTV downloader Download Video and IGTV from Instagram * Update * Update * Some Change * Update * Update instagram_video.py * Update instagram_video.py * Update instagram_video.py * Update instagram_video.py Co-authored-by: Christian Clauss Co-authored-by: Dhruv Manilawala --- web_programming/instagram_video.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 web_programming/instagram_video.py diff --git a/web_programming/instagram_video.py b/web_programming/instagram_video.py new file mode 100644 index 000000000000..243cece1a50e --- /dev/null +++ b/web_programming/instagram_video.py @@ -0,0 +1,17 @@ +from datetime import datetime + +import requests + + +def download_video(url: str) -> bytes: + base_url = "https://downloadgram.net/wp-json/wppress/video-downloader/video?url=" + video_url = requests.get(base_url + url).json()[0]["urls"][0]["src"] + return requests.get(video_url).content + + +if __name__ == "__main__": + url = input("Enter Video/IGTV url: ").strip() + file_name = f"{datetime.now():%Y-%m-%d_%H:%M:%S}.mp4" + with open(file_name, "wb") as fp: + fp.write(download_video(url)) + print(f"Done. Video saved to disk as {file_name}.") From 5de90aafc7491f81b0a2b606639a518167bc15cf Mon Sep 17 00:00:00 2001 From: jenra Date: Sun, 29 Nov 2020 11:09:33 -0500 Subject: [PATCH 1039/1071] Hacktoberfest 2020: Conway's Game of Life (#3070) * Created conways_game_of_life.py * Added new_generation(list[int[int]]) -> list[list[int]] * Added glider example * Added comments and shortened glider example * Fixed index out of bounds error * Added test * Added blinker example * Added ability to generate images * Moved image generating code into a separate function * Added comments * Comment * Reformatted file * Formatting * Removed glider test * Update cellular_automata/conways_game_of_life.py Co-authored-by: John Law * Update conways_game_of_life.py * Update conways_game_of_life.py Co-authored-by: John Law --- cellular_automata/conways_game_of_life.py | 100 ++++++++++++++++++++++ 1 file changed, 100 insertions(+) create mode 100644 cellular_automata/conways_game_of_life.py diff --git a/cellular_automata/conways_game_of_life.py b/cellular_automata/conways_game_of_life.py new file mode 100644 index 000000000000..321baa3a3794 --- /dev/null +++ b/cellular_automata/conways_game_of_life.py @@ -0,0 +1,100 @@ +""" +Conway's Game of Life implemented in Python. +https://en.wikipedia.org/wiki/Conway%27s_Game_of_Life +""" + +from __future__ import annotations + +from typing import List + +from PIL import Image + +# Define glider example +GLIDER = [ + [0, 1, 0, 0, 0, 0, 0, 0], + [0, 0, 1, 0, 0, 0, 0, 0], + [1, 1, 1, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0], +] + +# Define blinker example +BLINKER = [[0, 1, 0], [0, 1, 0], [0, 1, 0]] + + +def new_generation(cells: List[List[int]]) -> List[List[int]]: + """ + Generates the next generation for a given state of Conway's Game of Life. + >>> new_generation(BLINKER) + [[0, 0, 0], [1, 1, 1], [0, 0, 0]] + """ + next_generation = [] + for i in range(len(cells)): + next_generation_row = [] + for j in range(len(cells[i])): + # Get the number of live neighbours + neighbour_count = 0 + if i > 0 and j > 0: + neighbour_count += cells[i - 1][j - 1] + if i > 0: + neighbour_count += cells[i - 1][j] + if i > 0 and j < len(cells[i]) - 1: + neighbour_count += cells[i - 1][j + 1] + if j > 0: + neighbour_count += cells[i][j - 1] + if j < len(cells[i]) - 1: + neighbour_count += cells[i][j + 1] + if i < len(cells) - 1 and j > 0: + neighbour_count += cells[i + 1][j - 1] + if i < len(cells) - 1: + neighbour_count += cells[i + 1][j] + if i < len(cells) - 1 and j < len(cells[i]) - 1: + neighbour_count += cells[i + 1][j + 1] + + # Rules of the game of life (excerpt from Wikipedia): + # 1. Any live cell with two or three live neighbours survives. + # 2. Any dead cell with three live neighbours becomes a live cell. + # 3. All other live cells die in the next generation. + # Similarly, all other dead cells stay dead. + alive = cells[i][j] == 1 + if ( + (alive and 2 <= neighbour_count <= 3) + or not alive + and neighbour_count == 3 + ): + next_generation_row.append(1) + else: + next_generation_row.append(0) + + next_generation.append(next_generation_row) + return next_generation + + +def generate_images(cells: list[list[int]], frames) -> list[Image.Image]: + """ + Generates a list of images of subsequent Game of Life states. + """ + images = [] + for _ in range(frames): + # Create output image + img = Image.new("RGB", (len(cells[0]), len(cells))) + pixels = img.load() + + # Save cells to image + for x in range(len(cells)): + for y in range(len(cells[0])): + colour = 255 - cells[y][x] * 255 + pixels[x, y] = (colour, colour, colour) + + # Save image + images.append(img) + cells = new_generation(cells) + return images + + +if __name__ == "__main__": + images = generate_images(GLIDER, 16) + images[0].save("out.gif", save_all=True, append_images=images[1:]) From e07766230d5aea4ddf090d9c5347a90e8c2137b3 Mon Sep 17 00:00:00 2001 From: Jenia Dysin Date: Sun, 29 Nov 2020 18:19:50 +0200 Subject: [PATCH 1040/1071] Add static typing to backtracking algorithms (#2684) * Added static typing to backtracking algorithms * Ran psf/black to fix some minor issues. * updating DIRECTORY.md * updating DIRECTORY.md Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> Co-authored-by: John Law --- DIRECTORY.md | 13 ++++++++----- backtracking/all_combinations.py | 10 ++++++++-- backtracking/all_permutations.py | 6 ++++-- backtracking/n_queens.py | 6 +++--- backtracking/rat_in_maze.py | 4 ++-- backtracking/sum_of_subsets.py | 11 +++++++++-- 6 files changed, 34 insertions(+), 16 deletions(-) diff --git a/DIRECTORY.md b/DIRECTORY.md index 079dae1884bc..00da7922d54d 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -41,6 +41,7 @@ * [Quine Mc Cluskey](https://github.com/TheAlgorithms/Python/blob/master/boolean_algebra/quine_mc_cluskey.py) ## Cellular Automata + * [Conways Game Of Life](https://github.com/TheAlgorithms/Python/blob/master/cellular_automata/conways_game_of_life.py) * [One Dimensional](https://github.com/TheAlgorithms/Python/blob/master/cellular_automata/one_dimensional.py) ## Ciphers @@ -107,6 +108,7 @@ * [Prefix Conversions](https://github.com/TheAlgorithms/Python/blob/master/conversions/prefix_conversions.py) * [Roman To Integer](https://github.com/TheAlgorithms/Python/blob/master/conversions/roman_to_integer.py) * [Temperature Conversions](https://github.com/TheAlgorithms/Python/blob/master/conversions/temperature_conversions.py) + * [Weight Conversion](https://github.com/TheAlgorithms/Python/blob/master/conversions/weight_conversion.py) ## Data Structures * Binary Tree @@ -321,10 +323,6 @@ * [Test Min Spanning Tree Kruskal](https://github.com/TheAlgorithms/Python/blob/master/graphs/tests/test_min_spanning_tree_kruskal.py) * [Test Min Spanning Tree Prim](https://github.com/TheAlgorithms/Python/blob/master/graphs/tests/test_min_spanning_tree_prim.py) -## Greedy Method - * [Greedy Knapsack](https://github.com/TheAlgorithms/Python/blob/master/greedy_method/greedy_knapsack.py) - * [Test Knapsack](https://github.com/TheAlgorithms/Python/blob/master/greedy_method/test_knapsack.py) - ## Hashes * [Adler32](https://github.com/TheAlgorithms/Python/blob/master/hashes/adler32.py) * [Chaos Machine](https://github.com/TheAlgorithms/Python/blob/master/hashes/chaos_machine.py) @@ -336,8 +334,11 @@ * [Sha1](https://github.com/TheAlgorithms/Python/blob/master/hashes/sha1.py) ## Knapsack + * [Greedy Knapsack](https://github.com/TheAlgorithms/Python/blob/master/knapsack/greedy_knapsack.py) * [Knapsack](https://github.com/TheAlgorithms/Python/blob/master/knapsack/knapsack.py) - * [Test Knapsack](https://github.com/TheAlgorithms/Python/blob/master/knapsack/test_knapsack.py) + * Tests + * [Test Greedy Knapsack](https://github.com/TheAlgorithms/Python/blob/master/knapsack/tests/test_greedy_knapsack.py) + * [Test Knapsack](https://github.com/TheAlgorithms/Python/blob/master/knapsack/tests/test_knapsack.py) ## Linear Algebra * Src @@ -400,6 +401,7 @@ * [Combinations](https://github.com/TheAlgorithms/Python/blob/master/maths/combinations.py) * [Decimal Isolate](https://github.com/TheAlgorithms/Python/blob/master/maths/decimal_isolate.py) * [Entropy](https://github.com/TheAlgorithms/Python/blob/master/maths/entropy.py) + * [Euclidean Distance](https://github.com/TheAlgorithms/Python/blob/master/maths/euclidean_distance.py) * [Eulers Totient](https://github.com/TheAlgorithms/Python/blob/master/maths/eulers_totient.py) * [Explicit Euler](https://github.com/TheAlgorithms/Python/blob/master/maths/explicit_euler.py) * [Extended Euclidean Algorithm](https://github.com/TheAlgorithms/Python/blob/master/maths/extended_euclidean_algorithm.py) @@ -886,6 +888,7 @@ * [Get Imdbtop](https://github.com/TheAlgorithms/Python/blob/master/web_programming/get_imdbtop.py) * [Instagram Crawler](https://github.com/TheAlgorithms/Python/blob/master/web_programming/instagram_crawler.py) * [Instagram Pic](https://github.com/TheAlgorithms/Python/blob/master/web_programming/instagram_pic.py) + * [Instagram Video](https://github.com/TheAlgorithms/Python/blob/master/web_programming/instagram_video.py) * [Recaptcha Verification](https://github.com/TheAlgorithms/Python/blob/master/web_programming/recaptcha_verification.py) * [Slack Message](https://github.com/TheAlgorithms/Python/blob/master/web_programming/slack_message.py) * [Test Fetch Github Info](https://github.com/TheAlgorithms/Python/blob/master/web_programming/test_fetch_github_info.py) diff --git a/backtracking/all_combinations.py b/backtracking/all_combinations.py index 854dc5198422..0444ed093449 100644 --- a/backtracking/all_combinations.py +++ b/backtracking/all_combinations.py @@ -16,7 +16,13 @@ def generate_all_combinations(n: int, k: int) -> [[int]]: return result -def create_all_state(increment, total_number, level, current_list, total_list): +def create_all_state( + increment: int, + total_number: int, + level: int, + current_list: [int], + total_list: [int], +) -> None: if level == 0: total_list.append(current_list[:]) return @@ -27,7 +33,7 @@ def create_all_state(increment, total_number, level, current_list, total_list): current_list.pop() -def print_all_state(total_list): +def print_all_state(total_list: [int]) -> None: for i in total_list: print(*i) diff --git a/backtracking/all_permutations.py b/backtracking/all_permutations.py index 5244fef97f93..59c7b7bbf41e 100644 --- a/backtracking/all_permutations.py +++ b/backtracking/all_permutations.py @@ -7,11 +7,13 @@ """ -def generate_all_permutations(sequence): +def generate_all_permutations(sequence: [int]) -> None: create_state_space_tree(sequence, [], 0, [0 for i in range(len(sequence))]) -def create_state_space_tree(sequence, current_sequence, index, index_used): +def create_state_space_tree( + sequence: [int], current_sequence: [int], index: int, index_used: int +) -> None: """ Creates a state space tree to iterate through each branch using DFS. We know that each state has exactly len(sequence) - index children. diff --git a/backtracking/n_queens.py b/backtracking/n_queens.py index ca7beb830bba..31696b4a84d3 100644 --- a/backtracking/n_queens.py +++ b/backtracking/n_queens.py @@ -10,7 +10,7 @@ solution = [] -def isSafe(board, row, column): +def isSafe(board: [[int]], row: int, column: int) -> bool: """ This function returns a boolean value True if it is safe to place a queen there considering the current state of the board. @@ -38,7 +38,7 @@ def isSafe(board, row, column): return True -def solve(board, row): +def solve(board: [[int]], row: int) -> bool: """ It creates a state space tree and calls the safe function until it receives a False Boolean and terminates that branch and backtracks to the next @@ -68,7 +68,7 @@ def solve(board, row): return False -def printboard(board): +def printboard(board: [[int]]) -> None: """ Prints the boards that have a successful combination. """ diff --git a/backtracking/rat_in_maze.py b/backtracking/rat_in_maze.py index 788aeac13c09..8dc484c3f92d 100644 --- a/backtracking/rat_in_maze.py +++ b/backtracking/rat_in_maze.py @@ -1,4 +1,4 @@ -def solve_maze(maze: list) -> bool: +def solve_maze(maze: [[int]]) -> bool: """ This method solves the "rat in maze" problem. In this problem we have some n by n matrix, a start point and an end point. @@ -67,7 +67,7 @@ def solve_maze(maze: list) -> bool: return solved -def run_maze(maze, i, j, solutions): +def run_maze(maze: [[int]], i: int, j: int, solutions: [[int]]) -> bool: """ This method is recursive starting from (i, j) and going in one of four directions: up, down, left, right. diff --git a/backtracking/sum_of_subsets.py b/backtracking/sum_of_subsets.py index 425ddcff927e..b71edc2eefb5 100644 --- a/backtracking/sum_of_subsets.py +++ b/backtracking/sum_of_subsets.py @@ -8,7 +8,7 @@ """ -def generate_sum_of_subsets_soln(nums, max_sum): +def generate_sum_of_subsets_soln(nums: [int], max_sum: [int]) -> [int]: result = [] path = [] num_index = 0 @@ -17,7 +17,14 @@ def generate_sum_of_subsets_soln(nums, max_sum): return result -def create_state_space_tree(nums, max_sum, num_index, path, result, remaining_nums_sum): +def create_state_space_tree( + nums: [int], + max_sum: int, + num_index: int, + path: [int], + result: [int], + remaining_nums_sum: int, +) -> None: """ Creates a state space tree to iterate through each branch using DFS. It terminates the branching of a node when any of the two conditions From 0febbd397ec2a41cff7f9fa2c182c14516f5bb36 Mon Sep 17 00:00:00 2001 From: Jenia Dysin Date: Sun, 29 Nov 2020 18:20:54 +0200 Subject: [PATCH 1041/1071] Add typehints to blockchain (#3149) * updating DIRECTORY.md * updating DIRECTORY.md * Added type hints to blockchain algorithms * updating DIRECTORY.md Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> --- blockchain/chinese_remainder_theorem.py | 8 ++++---- blockchain/diophantine_equation.py | 8 ++++---- blockchain/modular_division.py | 12 ++++++------ 3 files changed, 14 insertions(+), 14 deletions(-) diff --git a/blockchain/chinese_remainder_theorem.py b/blockchain/chinese_remainder_theorem.py index b6a486f0b1ed..3e4b2b7b4f10 100644 --- a/blockchain/chinese_remainder_theorem.py +++ b/blockchain/chinese_remainder_theorem.py @@ -12,7 +12,7 @@ # Extended Euclid -def extended_euclid(a, b): +def extended_euclid(a: int, b: int) -> (int, int): """ >>> extended_euclid(10, 6) (-1, 2) @@ -29,7 +29,7 @@ def extended_euclid(a, b): # Uses ExtendedEuclid to find inverses -def chinese_remainder_theorem(n1, r1, n2, r2): +def chinese_remainder_theorem(n1: int, r1: int, n2: int, r2: int) -> int: """ >>> chinese_remainder_theorem(5,1,7,3) 31 @@ -51,7 +51,7 @@ def chinese_remainder_theorem(n1, r1, n2, r2): # ----------SAME SOLUTION USING InvertModulo instead ExtendedEuclid---------------- # This function find the inverses of a i.e., a^(-1) -def invert_modulo(a, n): +def invert_modulo(a: int, n: int) -> int: """ >>> invert_modulo(2, 5) 3 @@ -67,7 +67,7 @@ def invert_modulo(a, n): # Same a above using InvertingModulo -def chinese_remainder_theorem2(n1, r1, n2, r2): +def chinese_remainder_theorem2(n1: int, r1: int, n2: int, r2: int) -> int: """ >>> chinese_remainder_theorem2(5,1,7,3) 31 diff --git a/blockchain/diophantine_equation.py b/blockchain/diophantine_equation.py index 751b0efb7227..a92c2a13cfd5 100644 --- a/blockchain/diophantine_equation.py +++ b/blockchain/diophantine_equation.py @@ -5,7 +5,7 @@ # GCD ( Greatest Common Divisor ) or HCF ( Highest Common Factor ) -def diophantine(a, b, c): +def diophantine(a: int, b: int, c: int) -> (int, int): """ >>> diophantine(10,6,14) (-7.0, 14.0) @@ -37,7 +37,7 @@ def diophantine(a, b, c): # n is the number of solution you want, n = 2 by default -def diophantine_all_soln(a, b, c, n=2): +def diophantine_all_soln(a: int, b: int, c: int, n: int = 2) -> None: """ >>> diophantine_all_soln(10, 6, 14) -7.0 14.0 @@ -72,7 +72,7 @@ def diophantine_all_soln(a, b, c, n=2): # Euclid's Algorithm -def greatest_common_divisor(a, b): +def greatest_common_divisor(a: int, b: int) -> int: """ >>> greatest_common_divisor(7,5) 1 @@ -98,7 +98,7 @@ def greatest_common_divisor(a, b): # x and y, then d = gcd(a,b) -def extended_gcd(a, b): +def extended_gcd(a: int, b: int) -> (int, int, int): """ >>> extended_gcd(10, 6) (2, -1, 2) diff --git a/blockchain/modular_division.py b/blockchain/modular_division.py index 8fcf6e37cbed..e012db28fab8 100644 --- a/blockchain/modular_division.py +++ b/blockchain/modular_division.py @@ -14,7 +14,7 @@ # Uses ExtendedEuclid to find the inverse of a -def modular_division(a, b, n): +def modular_division(a: int, b: int, n: int) -> int: """ >>> modular_division(4,8,5) 2 @@ -33,7 +33,7 @@ def modular_division(a, b, n): # This function find the inverses of a i.e., a^(-1) -def invert_modulo(a, n): +def invert_modulo(a: int, n: int) -> int: """ >>> invert_modulo(2, 5) 3 @@ -51,7 +51,7 @@ def invert_modulo(a, n): # ------------------ Finding Modular division using invert_modulo ------------------- # This function used the above inversion of a to find x = (b*a^(-1))mod n -def modular_division2(a, b, n): +def modular_division2(a: int, b: int, n: int) -> int: """ >>> modular_division2(4,8,5) 2 @@ -72,7 +72,7 @@ def modular_division2(a, b, n): # and y, then d = gcd(a,b) -def extended_gcd(a, b): +def extended_gcd(a: int, b: int) -> (int, int, int): """ >>> extended_gcd(10, 6) (2, -1, 2) @@ -99,7 +99,7 @@ def extended_gcd(a, b): # Extended Euclid -def extended_euclid(a, b): +def extended_euclid(a: int, b: int) -> (int, int): """ >>> extended_euclid(10, 6) (-1, 2) @@ -119,7 +119,7 @@ def extended_euclid(a, b): # Euclid's Algorithm -def greatest_common_divisor(a, b): +def greatest_common_divisor(a: int, b: int) -> int: """ >>> greatest_common_divisor(7,5) 1 From 25164bb6380ae760bed5fe3efc5f2fc3ec5c38a1 Mon Sep 17 00:00:00 2001 From: John Law Date: Mon, 30 Nov 2020 01:30:31 +0800 Subject: [PATCH 1042/1071] Fix mypy in #2684 (#3987) * Fix mypy in #2684 * fix pre-commit --- backtracking/all_combinations.py | 11 ++++++----- backtracking/all_permutations.py | 14 +++++++++----- backtracking/n_queens.py | 10 ++++++---- backtracking/rat_in_maze.py | 8 ++++++-- backtracking/sum_of_subsets.py | 13 +++++++------ 5 files changed, 34 insertions(+), 22 deletions(-) diff --git a/backtracking/all_combinations.py b/backtracking/all_combinations.py index 0444ed093449..76462837ce35 100644 --- a/backtracking/all_combinations.py +++ b/backtracking/all_combinations.py @@ -3,15 +3,16 @@ numbers out of 1 ... n. We use backtracking to solve this problem. Time complexity: O(C(n,k)) which is O(n choose k) = O((n!/(k! * (n - k)!))) """ +from typing import List -def generate_all_combinations(n: int, k: int) -> [[int]]: +def generate_all_combinations(n: int, k: int) -> List[List[int]]: """ >>> generate_all_combinations(n=4, k=2) [[1, 2], [1, 3], [1, 4], [2, 3], [2, 4], [3, 4]] """ - result = [] + result: List[List[int]] = [] create_all_state(1, n, k, [], result) return result @@ -20,8 +21,8 @@ def create_all_state( increment: int, total_number: int, level: int, - current_list: [int], - total_list: [int], + current_list: List[int], + total_list: List[List[int]], ) -> None: if level == 0: total_list.append(current_list[:]) @@ -33,7 +34,7 @@ def create_all_state( current_list.pop() -def print_all_state(total_list: [int]) -> None: +def print_all_state(total_list: List[List[int]]) -> None: for i in total_list: print(*i) diff --git a/backtracking/all_permutations.py b/backtracking/all_permutations.py index 59c7b7bbf41e..a0032c5ca814 100644 --- a/backtracking/all_permutations.py +++ b/backtracking/all_permutations.py @@ -5,14 +5,18 @@ Time complexity: O(n! * n), where n denotes the length of the given sequence. """ +from typing import List, Union -def generate_all_permutations(sequence: [int]) -> None: +def generate_all_permutations(sequence: List[Union[int, str]]) -> None: create_state_space_tree(sequence, [], 0, [0 for i in range(len(sequence))]) def create_state_space_tree( - sequence: [int], current_sequence: [int], index: int, index_used: int + sequence: List[Union[int, str]], + current_sequence: List[Union[int, str]], + index: int, + index_used: List[int], ) -> None: """ Creates a state space tree to iterate through each branch using DFS. @@ -40,8 +44,8 @@ def create_state_space_tree( sequence = list(map(int, input().split())) """ -sequence = [3, 1, 2, 4] +sequence: List[Union[int, str]] = [3, 1, 2, 4] generate_all_permutations(sequence) -sequence = ["A", "B", "C"] -generate_all_permutations(sequence) +sequence_2: List[Union[int, str]] = ["A", "B", "C"] +generate_all_permutations(sequence_2) diff --git a/backtracking/n_queens.py b/backtracking/n_queens.py index 31696b4a84d3..29b8d819acf3 100644 --- a/backtracking/n_queens.py +++ b/backtracking/n_queens.py @@ -7,10 +7,12 @@ diagonal lines. """ +from typing import List + solution = [] -def isSafe(board: [[int]], row: int, column: int) -> bool: +def isSafe(board: List[List[int]], row: int, column: int) -> bool: """ This function returns a boolean value True if it is safe to place a queen there considering the current state of the board. @@ -38,7 +40,7 @@ def isSafe(board: [[int]], row: int, column: int) -> bool: return True -def solve(board: [[int]], row: int) -> bool: +def solve(board: List[List[int]], row: int) -> bool: """ It creates a state space tree and calls the safe function until it receives a False Boolean and terminates that branch and backtracks to the next @@ -53,7 +55,7 @@ def solve(board: [[int]], row: int) -> bool: solution.append(board) printboard(board) print() - return + return True for i in range(len(board)): """ For every row it iterates through each column to check if it is feasible to @@ -68,7 +70,7 @@ def solve(board: [[int]], row: int) -> bool: return False -def printboard(board: [[int]]) -> None: +def printboard(board: List[List[int]]) -> None: """ Prints the boards that have a successful combination. """ diff --git a/backtracking/rat_in_maze.py b/backtracking/rat_in_maze.py index 8dc484c3f92d..cd2a8f41daa8 100644 --- a/backtracking/rat_in_maze.py +++ b/backtracking/rat_in_maze.py @@ -1,4 +1,7 @@ -def solve_maze(maze: [[int]]) -> bool: +from typing import List + + +def solve_maze(maze: List[List[int]]) -> bool: """ This method solves the "rat in maze" problem. In this problem we have some n by n matrix, a start point and an end point. @@ -67,7 +70,7 @@ def solve_maze(maze: [[int]]) -> bool: return solved -def run_maze(maze: [[int]], i: int, j: int, solutions: [[int]]) -> bool: +def run_maze(maze: List[List[int]], i: int, j: int, solutions: List[List[int]]) -> bool: """ This method is recursive starting from (i, j) and going in one of four directions: up, down, left, right. @@ -106,6 +109,7 @@ def run_maze(maze: [[int]], i: int, j: int, solutions: [[int]]) -> bool: solutions[i][j] = 0 return False + return False if __name__ == "__main__": diff --git a/backtracking/sum_of_subsets.py b/backtracking/sum_of_subsets.py index b71edc2eefb5..f695b8f7a80e 100644 --- a/backtracking/sum_of_subsets.py +++ b/backtracking/sum_of_subsets.py @@ -6,11 +6,12 @@ Summation of the chosen numbers must be equal to given number M and one number can be used only once. """ +from typing import List -def generate_sum_of_subsets_soln(nums: [int], max_sum: [int]) -> [int]: - result = [] - path = [] +def generate_sum_of_subsets_soln(nums: List[int], max_sum: int) -> List[List[int]]: + result: List[List[int]] = [] + path: List[int] = [] num_index = 0 remaining_nums_sum = sum(nums) create_state_space_tree(nums, max_sum, num_index, path, result, remaining_nums_sum) @@ -18,11 +19,11 @@ def generate_sum_of_subsets_soln(nums: [int], max_sum: [int]) -> [int]: def create_state_space_tree( - nums: [int], + nums: List[int], max_sum: int, num_index: int, - path: [int], - result: [int], + path: List[int], + result: List[List[int]], remaining_nums_sum: int, ) -> None: """ From ba6310b6470346fdac85ea4ef697e5939c30b180 Mon Sep 17 00:00:00 2001 From: Dhruv Manilawala Date: Sun, 29 Nov 2020 23:11:09 +0530 Subject: [PATCH 1043/1071] Validate only submitted Project Euler solution (#3977) * Update validate solution script to fetch only submitted solution * Update workflow file with the updated PE script * Fix: do not fetch `validate_solutions.py` script * Update script to use the requests package for API calls * Fix: install requests module * Pytest ignore scripts/ directory --- .github/workflows/build.yml | 2 +- .github/workflows/project_euler.yml | 18 +++++++----- scripts/validate_solutions.py | 45 +++++++++++++++++++++++++++-- 3 files changed, 55 insertions(+), 10 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index ae9b4e36b1ce..9e15d18ade8e 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -22,6 +22,6 @@ jobs: python -m pip install --upgrade pip setuptools six wheel python -m pip install pytest-cov -r requirements.txt - name: Run tests - run: pytest --doctest-modules --ignore=project_euler/ --cov-report=term-missing:skip-covered --cov=. . + run: pytest --doctest-modules --ignore=project_euler/ --ignore=scripts/ --cov-report=term-missing:skip-covered --cov=. . - if: ${{ success() }} run: scripts/build_directory_md.py 2>&1 | tee DIRECTORY.md diff --git a/.github/workflows/project_euler.yml b/.github/workflows/project_euler.yml index e8b011af20a6..995295fcaa9a 100644 --- a/.github/workflows/project_euler.yml +++ b/.github/workflows/project_euler.yml @@ -1,12 +1,14 @@ on: pull_request: - # only check if a file is changed within the project_euler directory and related files + # Run only if a file is changed within the project_euler directory and related files paths: - - 'project_euler/**' - - '.github/workflows/project_euler.yml' - - 'scripts/validate_solutions.py' + - "project_euler/**" + - ".github/workflows/project_euler.yml" + - "scripts/validate_solutions.py" + schedule: + - cron: "0 0 * * *" # Run everyday -name: 'Project Euler' +name: "Project Euler" jobs: project-euler: @@ -24,8 +26,10 @@ jobs: steps: - uses: actions/checkout@v2 - uses: actions/setup-python@v2 - - name: Install pytest + - name: Install pytest and requests run: | python -m pip install --upgrade pip - python -m pip install --upgrade pytest + python -m pip install --upgrade pytest requests - run: pytest scripts/validate_solutions.py + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/scripts/validate_solutions.py b/scripts/validate_solutions.py index e1f68ff843bb..fd804ea5aa31 100755 --- a/scripts/validate_solutions.py +++ b/scripts/validate_solutions.py @@ -1,11 +1,13 @@ #!/usr/bin/env python3 import importlib.util import json +import os import pathlib from types import ModuleType from typing import Dict, List import pytest +import requests PROJECT_EULER_DIR_PATH = pathlib.Path.cwd().joinpath("project_euler") PROJECT_EULER_ANSWERS_PATH = pathlib.Path.cwd().joinpath( @@ -24,7 +26,7 @@ def convert_path_to_module(file_path: pathlib.Path) -> ModuleType: return module -def collect_solution_file_paths() -> List[pathlib.Path]: +def all_solution_file_paths() -> List[pathlib.Path]: """Collects all the solution file path in the Project Euler directory""" solution_file_paths = [] for problem_dir_path in PROJECT_EULER_DIR_PATH.iterdir(): @@ -37,12 +39,51 @@ def collect_solution_file_paths() -> List[pathlib.Path]: return solution_file_paths +def get_files_url() -> str: + """Return the pull request number which triggered this action.""" + with open(os.environ["GITHUB_EVENT_PATH"]) as file: + event = json.load(file) + return event["pull_request"]["url"] + "/files" + + +def added_solution_file_path() -> List[pathlib.Path]: + """Collects only the solution file path which got added in the current + pull request. + + This will only be triggered if the script is ran from GitHub Actions. + """ + solution_file_paths = [] + headers = { + "Accept": "application/vnd.github.v3+json", + "Authorization": "token " + os.environ["GITHUB_TOKEN"], + } + files = requests.get(get_files_url(), headers=headers).json() + for file in files: + filepath = pathlib.Path.cwd().joinpath(file["filename"]) + if ( + filepath.suffix != ".py" + or filepath.name.startswith(("_", "test")) + or not filepath.name.startswith("sol") + ): + continue + solution_file_paths.append(filepath) + return solution_file_paths + + +def collect_solution_file_paths() -> List[pathlib.Path]: + if os.environ.get("CI") and os.environ.get("GITHUB_EVENT_NAME") == "pull_request": + # Return only if there are any, otherwise default to all solutions + if filepaths := added_solution_file_path(): + return filepaths + return all_solution_file_paths() + + @pytest.mark.parametrize( "solution_path", collect_solution_file_paths(), ids=lambda path: f"{path.parent.name}/{path.name}", ) -def test_project_euler(solution_path: pathlib.Path): +def test_project_euler(solution_path: pathlib.Path) -> None: """Testing for all Project Euler solutions""" # problem_[extract this part] and pad it with zeroes for width 3 problem_number: str = solution_path.parent.name[8:].zfill(3) From 06dad4f9d8624d9b9a4be56fef47a657f6ce6b82 Mon Sep 17 00:00:00 2001 From: John Law Date: Mon, 30 Nov 2020 01:46:26 +0800 Subject: [PATCH 1044/1071] Fix mypy in #3149 (#3988) * Fix mypy in #3149 * Fix pre-commit --- blockchain/chinese_remainder_theorem.py | 21 +++++---- blockchain/diophantine_equation.py | 50 ++++++++++----------- blockchain/modular_division.py | 58 +++++++++++++------------ 3 files changed, 67 insertions(+), 62 deletions(-) diff --git a/blockchain/chinese_remainder_theorem.py b/blockchain/chinese_remainder_theorem.py index 3e4b2b7b4f10..b50147ac1215 100644 --- a/blockchain/chinese_remainder_theorem.py +++ b/blockchain/chinese_remainder_theorem.py @@ -1,18 +1,21 @@ -# Chinese Remainder Theorem: -# GCD ( Greatest Common Divisor ) or HCF ( Highest Common Factor ) +""" +Chinese Remainder Theorem: +GCD ( Greatest Common Divisor ) or HCF ( Highest Common Factor ) -# If GCD(a,b) = 1, then for any remainder ra modulo a and any remainder rb modulo b -# there exists integer n, such that n = ra (mod a) and n = ra(mod b). If n1 and n2 are -# two such integers, then n1=n2(mod ab) +If GCD(a,b) = 1, then for any remainder ra modulo a and any remainder rb modulo b +there exists integer n, such that n = ra (mod a) and n = ra(mod b). If n1 and n2 are +two such integers, then n1=n2(mod ab) -# Algorithm : +Algorithm : -# 1. Use extended euclid algorithm to find x,y such that a*x + b*y = 1 -# 2. Take n = ra*by + rb*ax +1. Use extended euclid algorithm to find x,y such that a*x + b*y = 1 +2. Take n = ra*by + rb*ax +""" +from typing import Tuple # Extended Euclid -def extended_euclid(a: int, b: int) -> (int, int): +def extended_euclid(a: int, b: int) -> Tuple[int, int]: """ >>> extended_euclid(10, 6) (-1, 2) diff --git a/blockchain/diophantine_equation.py b/blockchain/diophantine_equation.py index a92c2a13cfd5..7df674cb1438 100644 --- a/blockchain/diophantine_equation.py +++ b/blockchain/diophantine_equation.py @@ -1,12 +1,14 @@ -# Diophantine Equation : Given integers a,b,c ( at least one of a and b != 0), the -# diophantine equation a*x + b*y = c has a solution (where x and y are integers) -# iff gcd(a,b) divides c. +from typing import Tuple -# GCD ( Greatest Common Divisor ) or HCF ( Highest Common Factor ) - -def diophantine(a: int, b: int, c: int) -> (int, int): +def diophantine(a: int, b: int, c: int) -> Tuple[float, float]: """ + Diophantine Equation : Given integers a,b,c ( at least one of a and b != 0), the + diophantine equation a*x + b*y = c has a solution (where x and y are integers) + iff gcd(a,b) divides c. + + GCD ( Greatest Common Divisor ) or HCF ( Highest Common Factor ) + >>> diophantine(10,6,14) (-7.0, 14.0) @@ -26,19 +28,19 @@ def diophantine(a: int, b: int, c: int) -> (int, int): return (r * x, r * y) -# Lemma : if n|ab and gcd(a,n) = 1, then n|b. - -# Finding All solutions of Diophantine Equations: +def diophantine_all_soln(a: int, b: int, c: int, n: int = 2) -> None: + """ + Lemma : if n|ab and gcd(a,n) = 1, then n|b. -# Theorem : Let gcd(a,b) = d, a = d*p, b = d*q. If (x0,y0) is a solution of Diophantine -# Equation a*x + b*y = c. a*x0 + b*y0 = c, then all the solutions have the form -# a(x0 + t*q) + b(y0 - t*p) = c, where t is an arbitrary integer. + Finding All solutions of Diophantine Equations: -# n is the number of solution you want, n = 2 by default + Theorem : Let gcd(a,b) = d, a = d*p, b = d*q. If (x0,y0) is a solution of + Diophantine Equation a*x + b*y = c. a*x0 + b*y0 = c, then all the + solutions have the form a(x0 + t*q) + b(y0 - t*p) = c, + where t is an arbitrary integer. + n is the number of solution you want, n = 2 by default -def diophantine_all_soln(a: int, b: int, c: int, n: int = 2) -> None: - """ >>> diophantine_all_soln(10, 6, 14) -7.0 14.0 -4.0 9.0 @@ -67,13 +69,12 @@ def diophantine_all_soln(a: int, b: int, c: int, n: int = 2) -> None: print(x, y) -# Euclid's Lemma : d divides a and b, if and only if d divides a-b and b - -# Euclid's Algorithm - - def greatest_common_divisor(a: int, b: int) -> int: """ + Euclid's Lemma : d divides a and b, if and only if d divides a-b and b + + Euclid's Algorithm + >>> greatest_common_divisor(7,5) 1 @@ -94,12 +95,11 @@ def greatest_common_divisor(a: int, b: int) -> int: return b -# Extended Euclid's Algorithm : If d divides a and b and d = a*x + b*y for integers -# x and y, then d = gcd(a,b) - - -def extended_gcd(a: int, b: int) -> (int, int, int): +def extended_gcd(a: int, b: int) -> Tuple[int, int, int]: """ + Extended Euclid's Algorithm : If d divides a and b and d = a*x + b*y for integers + x and y, then d = gcd(a,b) + >>> extended_gcd(10, 6) (2, -1, 2) diff --git a/blockchain/modular_division.py b/blockchain/modular_division.py index e012db28fab8..4f7f50a92ad0 100644 --- a/blockchain/modular_division.py +++ b/blockchain/modular_division.py @@ -1,21 +1,23 @@ -# Modular Division : -# An efficient algorithm for dividing b by a modulo n. +from typing import Tuple -# GCD ( Greatest Common Divisor ) or HCF ( Highest Common Factor ) -# Given three integers a, b, and n, such that gcd(a,n)=1 and n>1, the algorithm should -# return an integer x such that 0≤x≤n−1, and b/a=x(modn) (that is, b=ax(modn)). +def modular_division(a: int, b: int, n: int) -> int: + """ + Modular Division : + An efficient algorithm for dividing b by a modulo n. -# Theorem: -# a has a multiplicative inverse modulo n iff gcd(a,n) = 1 + GCD ( Greatest Common Divisor ) or HCF ( Highest Common Factor ) + Given three integers a, b, and n, such that gcd(a,n)=1 and n>1, the algorithm should + return an integer x such that 0≤x≤n−1, and b/a=x(modn) (that is, b=ax(modn)). -# This find x = b*a^(-1) mod n -# Uses ExtendedEuclid to find the inverse of a + Theorem: + a has a multiplicative inverse modulo n iff gcd(a,n) = 1 -def modular_division(a: int, b: int, n: int) -> int: - """ + This find x = b*a^(-1) mod n + Uses ExtendedEuclid to find the inverse of a + >>> modular_division(4,8,5) 2 @@ -32,9 +34,10 @@ def modular_division(a: int, b: int, n: int) -> int: return x -# This function find the inverses of a i.e., a^(-1) def invert_modulo(a: int, n: int) -> int: """ + This function find the inverses of a i.e., a^(-1) + >>> invert_modulo(2, 5) 3 @@ -50,9 +53,11 @@ def invert_modulo(a: int, n: int) -> int: # ------------------ Finding Modular division using invert_modulo ------------------- -# This function used the above inversion of a to find x = (b*a^(-1))mod n + def modular_division2(a: int, b: int, n: int) -> int: """ + This function used the above inversion of a to find x = (b*a^(-1))mod n + >>> modular_division2(4,8,5) 2 @@ -68,17 +73,15 @@ def modular_division2(a: int, b: int, n: int) -> int: return x -# Extended Euclid's Algorithm : If d divides a and b and d = a*x + b*y for integers x -# and y, then d = gcd(a,b) - - -def extended_gcd(a: int, b: int) -> (int, int, int): +def extended_gcd(a: int, b: int) -> Tuple[int, int, int]: """ - >>> extended_gcd(10, 6) - (2, -1, 2) + Extended Euclid's Algorithm : If d divides a and b and d = a*x + b*y for integers x + and y, then d = gcd(a,b) + >>> extended_gcd(10, 6) + (2, -1, 2) - >>> extended_gcd(7, 5) - (1, -2, 3) + >>> extended_gcd(7, 5) + (1, -2, 3) ** extended_gcd function is used when d = gcd(a,b) is required in output @@ -98,9 +101,9 @@ def extended_gcd(a: int, b: int) -> (int, int, int): return (d, x, y) -# Extended Euclid -def extended_euclid(a: int, b: int) -> (int, int): +def extended_euclid(a: int, b: int) -> Tuple[int, int]: """ + Extended Euclid >>> extended_euclid(10, 6) (-1, 2) @@ -115,12 +118,11 @@ def extended_euclid(a: int, b: int) -> (int, int): return (y, x - k * y) -# Euclid's Lemma : d divides a and b, if and only if d divides a-b and b -# Euclid's Algorithm - - def greatest_common_divisor(a: int, b: int) -> int: """ + Euclid's Lemma : d divides a and b, if and only if d divides a-b and b + Euclid's Algorithm + >>> greatest_common_divisor(7,5) 1 From daceb87a9685d5e12f43c2e4135ee4b06c0669f1 Mon Sep 17 00:00:00 2001 From: Erdum Date: Mon, 30 Nov 2020 03:07:10 +0500 Subject: [PATCH 1045/1071] Electric power (#3976) * Electric power * updated as suggested by cclauss * updated as suggested by cclauss * decimal value error * All done --- electronics/electric_power.py | 49 +++++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) create mode 100644 electronics/electric_power.py diff --git a/electronics/electric_power.py b/electronics/electric_power.py new file mode 100644 index 000000000000..768c3d5c7232 --- /dev/null +++ b/electronics/electric_power.py @@ -0,0 +1,49 @@ +# https://en.m.wikipedia.org/wiki/Electric_power +from collections import namedtuple + + +def electric_power(voltage: float, current: float, power: float) -> float: + """ + This function can calculate any one of the three (voltage, current, power), + fundamental value of electrical system. + examples are below: + >>> electric_power(voltage=0, current=2, power=5) + result(name='voltage', value=2.5) + >>> electric_power(voltage=2, current=2, power=0) + result(name='power', value=4.0) + >>> electric_power(voltage=-2, current=3, power=0) + result(name='power', value=6.0) + >>> electric_power(voltage=2, current=4, power=2) + Traceback (most recent call last): + File "", line 15, in + ValueError: Only one argument must be 0 + >>> electric_power(voltage=0, current=0, power=2) + Traceback (most recent call last): + File "", line 19, in + ValueError: Only one argument must be 0 + >>> electric_power(voltage=0, current=2, power=-4) + Traceback (most recent call last): + File "", line 23, in >> electric_power(voltage=2.2, current=2.2, power=0) + result(name='power', value=4.84) + """ + result = namedtuple("result", "name value") + if (voltage, current, power).count(0) != 1: + raise ValueError("Only one argument must be 0") + elif power < 0: + raise ValueError( + "Power cannot be negative in any electrical/electronics system" + ) + elif voltage == 0: + return result("voltage", power / current) + elif current == 0: + return result("current", power / voltage) + elif power == 0: + return result("power", float(round(abs(voltage * current), 2))) + + +if __name__ == "__main__": + import doctest + + doctest.testmod() From 07a4ce9fb89031b4f1f3991fcf769b86a10a4aa8 Mon Sep 17 00:00:00 2001 From: wuyudi Date: Mon, 30 Nov 2020 22:59:23 +0800 Subject: [PATCH 1046/1071] Update pigeon_sort.py (#2359) * Update pigeon_sort.py * Update pigeon_sort.py * Update pigeon_sort.py --- sorts/pigeon_sort.py | 28 +++++++++------------------- 1 file changed, 9 insertions(+), 19 deletions(-) diff --git a/sorts/pigeon_sort.py b/sorts/pigeon_sort.py index cc6205f804dc..3126e47c719e 100644 --- a/sorts/pigeon_sort.py +++ b/sorts/pigeon_sort.py @@ -26,29 +26,17 @@ def pigeon_sort(array): if len(array) == 0: return array - # Manually finds the minimum and maximum of the array. - min = array[0] - max = array[0] - - for i in range(len(array)): - if array[i] < min: - min = array[i] - elif array[i] > max: - max = array[i] + _min, _max = min(array), max(array) # Compute the variables - holes_range = max - min + 1 - holes = [0 for _ in range(holes_range)] - holes_repeat = [0 for _ in range(holes_range)] + holes_range = _max - _min + 1 + holes, holes_repeat = [0] * holes_range, [0] * holes_range # Make the sorting. - for i in range(len(array)): - index = array[i] - min - if holes[index] != array[i]: - holes[index] = array[i] - holes_repeat[index] += 1 - else: - holes_repeat[index] += 1 + for i in array: + index = i - _min + holes[index] = i + holes_repeat[index] += 1 # Makes the array back by replacing the numbers. index = 0 @@ -63,6 +51,8 @@ def pigeon_sort(array): if __name__ == "__main__": + import doctest + doctest.testmod() user_input = input("Enter numbers separated by comma:\n") unsorted = [int(x) for x in user_input.split(",")] print(pigeon_sort(unsorted)) From f8b2c43fda28efdcb7bb50c4beb443330b9b64e9 Mon Sep 17 00:00:00 2001 From: Dhruv Manilawala Date: Mon, 30 Nov 2020 21:03:29 +0530 Subject: [PATCH 1047/1071] Fix pre-commit error on master (#3992) * Update pigeon_sort.py * updating DIRECTORY.md * Add type hints and return annotation Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> --- DIRECTORY.md | 1 + sorts/pigeon_sort.py | 4 +++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/DIRECTORY.md b/DIRECTORY.md index 00da7922d54d..2307685f1330 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -250,6 +250,7 @@ * [Sum Of Subset](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/sum_of_subset.py) ## Electronics + * [Electric Power](https://github.com/TheAlgorithms/Python/blob/master/electronics/electric_power.py) * [Ohms Law](https://github.com/TheAlgorithms/Python/blob/master/electronics/ohms_law.py) ## File Transfer diff --git a/sorts/pigeon_sort.py b/sorts/pigeon_sort.py index 3126e47c719e..3d81f0643865 100644 --- a/sorts/pigeon_sort.py +++ b/sorts/pigeon_sort.py @@ -9,9 +9,10 @@ For manual testing run: python pigeon_sort.py """ +from typing import List -def pigeon_sort(array): +def pigeon_sort(array: List[int]) -> List[int]: """ Implementation of pigeon hole sort algorithm :param array: Collection of comparable items @@ -52,6 +53,7 @@ def pigeon_sort(array): if __name__ == "__main__": import doctest + doctest.testmod() user_input = input("Enter numbers separated by comma:\n") unsorted = [int(x) for x in user_input.split(",")] From 860d4f547bcfbe96b5c1e1b507124b13c0dc7399 Mon Sep 17 00:00:00 2001 From: Maliha Date: Thu, 3 Dec 2020 07:02:48 -0800 Subject: [PATCH 1048/1071] Create merge_two_lists.py that implements merging of two sorted linked lists (#3874) * Create merge_two_lists.py that implements merging of two sorted linked lists * Update merge_two_lists.py Fixed formatting errors * Fixed trailing whitespace * Change name of function to def __str__() * updating DIRECTORY.md * Imported classes from singly_linked_list.py * Update merge_two_lists.py * Update merge_two_lists.py Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> Co-authored-by: Christian Clauss --- DIRECTORY.md | 1 + .../linked_list/merge_two_lists.py | 83 +++++++++++++++++++ 2 files changed, 84 insertions(+) create mode 100644 data_structures/linked_list/merge_two_lists.py diff --git a/DIRECTORY.md b/DIRECTORY.md index 2307685f1330..c9c3a09eb599 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -153,6 +153,7 @@ * [From Sequence](https://github.com/TheAlgorithms/Python/blob/master/data_structures/linked_list/from_sequence.py) * [Has Loop](https://github.com/TheAlgorithms/Python/blob/master/data_structures/linked_list/has_loop.py) * [Is Palindrome](https://github.com/TheAlgorithms/Python/blob/master/data_structures/linked_list/is_palindrome.py) + * [Merge Two Lists](https://github.com/TheAlgorithms/Python/blob/master/data_structures/linked_list/merge_two_lists.py) * [Middle Element Of Linked List](https://github.com/TheAlgorithms/Python/blob/master/data_structures/linked_list/middle_element_of_linked_list.py) * [Print Reverse](https://github.com/TheAlgorithms/Python/blob/master/data_structures/linked_list/print_reverse.py) * [Singly Linked List](https://github.com/TheAlgorithms/Python/blob/master/data_structures/linked_list/singly_linked_list.py) diff --git a/data_structures/linked_list/merge_two_lists.py b/data_structures/linked_list/merge_two_lists.py new file mode 100644 index 000000000000..96ec6b8abc85 --- /dev/null +++ b/data_structures/linked_list/merge_two_lists.py @@ -0,0 +1,83 @@ +""" +Algorithm that merges two sorted linked lists into one sorted linked list. +""" +from __future__ import annotations + +from collections.abc import Iterable, Iterator +from dataclasses import dataclass +from typing import Optional + +test_data_odd = (3, 9, -11, 0, 7, 5, 1, -1) +test_data_even = (4, 6, 2, 0, 8, 10, 3, -2) + + +@dataclass +class Node: + data: int + next: Optional[Node] + + +class SortedLinkedList: + def __init__(self, ints: Iterable[int]) -> None: + self.head: Optional[Node] = None + for i in reversed(sorted(ints)): + self.head = Node(i, self.head) + + def __iter__(self) -> Iterator[int]: + """ + >>> tuple(SortedLinkedList(test_data_odd)) == tuple(sorted(test_data_odd)) + True + >>> tuple(SortedLinkedList(test_data_even)) == tuple(sorted(test_data_even)) + True + """ + node = self.head + while node: + yield node.data + node = node.next + + def __len__(self) -> int: + """ + >>> for i in range(3): + ... len(SortedLinkedList(range(i))) == i + True + True + True + >>> len(SortedLinkedList(test_data_odd)) + 8 + """ + return len(tuple(iter(self))) + + def __str__(self) -> str: + """ + >>> str(SortedLinkedList([])) + '' + >>> str(SortedLinkedList(test_data_odd)) + '-11 -> -1 -> 0 -> 1 -> 3 -> 5 -> 7 -> 9' + >>> str(SortedLinkedList(test_data_even)) + '-2 -> 0 -> 2 -> 3 -> 4 -> 6 -> 8 -> 10' + """ + return " -> ".join([str(node) for node in self]) + + +def merge_lists( + sll_one: SortedLinkedList, sll_two: SortedLinkedList +) -> SortedLinkedList: + """ + >>> SSL = SortedLinkedList + >>> merged = merge_lists(SSL(test_data_odd), SSL(test_data_even)) + >>> len(merged) + 16 + >>> str(merged) + '-11 -> -2 -> -1 -> 0 -> 0 -> 1 -> 2 -> 3 -> 3 -> 4 -> 5 -> 6 -> 7 -> 8 -> 9 -> 10' + >>> list(merged) == list(sorted(test_data_odd + test_data_even)) + True + """ + return SortedLinkedList(list(sll_one) + list(sll_two)) + + +if __name__ == "__main__": + import doctest + + doctest.testmod() + SSL = SortedLinkedList + print(merge_lists(SSL(test_data_odd), SSL(test_data_even))) From c359768e257a7bcbfe93e137a0fc1e81b92d6573 Mon Sep 17 00:00:00 2001 From: Jogendra Singh <58473917+Joe-Sin7h@users.noreply.github.com> Date: Wed, 9 Dec 2020 11:38:49 +0530 Subject: [PATCH 1049/1071] Update bitonic_sort with type hints, doctest, snake_case names (#4016) * Updated input * Fix pre-commit error * Add type hints, doctests, black, snake_case Co-authored-by: Dhruv Manilawala --- sorts/bitonic_sort.py | 144 ++++++++++++++++++++++++++---------------- 1 file changed, 91 insertions(+), 53 deletions(-) diff --git a/sorts/bitonic_sort.py b/sorts/bitonic_sort.py index be3499de13cd..c718973e5ecb 100644 --- a/sorts/bitonic_sort.py +++ b/sorts/bitonic_sort.py @@ -1,58 +1,96 @@ -# Python program for Bitonic Sort. Note that this program -# works only when size of input is a power of 2. - - -# The parameter dir indicates the sorting direction, ASCENDING -# or DESCENDING; if (a[i] > a[j]) agrees with the direction, -# then a[i] and a[j] are interchanged. -def compAndSwap(a, i, j, dire): - if (dire == 1 and a[i] > a[j]) or (dire == 0 and a[i] < a[j]): - a[i], a[j] = a[j], a[i] - - # It recursively sorts a bitonic sequence in ascending order, - - -# if dir = 1, and in descending order otherwise (means dir=0). -# The sequence to be sorted starts at index position low, -# the parameter cnt is the number of elements to be sorted. -def bitonic_merge(a, low, cnt, dire): - if cnt > 1: - k = int(cnt / 2) - for i in range(low, low + k): - compAndSwap(a, i, i + k, dire) - bitonic_merge(a, low, k, dire) - bitonic_merge(a, low + k, k, dire) - - # This function first produces a bitonic sequence by recursively - - -# sorting its two halves in opposite sorting orders, and then -# calls bitonic_merge to make them in the same order -def bitonic_sort(a, low, cnt, dire): - if cnt > 1: - k = int(cnt / 2) - bitonic_sort(a, low, k, 1) - bitonic_sort(a, low + k, k, 0) - bitonic_merge(a, low, cnt, dire) - - # Caller of bitonic_sort for sorting the entire array of length N - - -# in ASCENDING order -def sort(a, N, up): - bitonic_sort(a, 0, N, up) +""" +Python program for Bitonic Sort. + +Note that this program works only when size of input is a power of 2. +""" +from typing import List + + +def comp_and_swap(array: List[int], index1: int, index2: int, direction: int) -> None: + """Compare the value at given index1 and index2 of the array and swap them as per + the given direction. + + The parameter direction indicates the sorting direction, ASCENDING(1) or + DESCENDING(0); if (a[i] > a[j]) agrees with the direction, then a[i] and a[j] are + interchanged. + + >>> arr = [12, 42, -21, 1] + >>> comp_and_swap(arr, 1, 2, 1) + >>> print(arr) + [12, -21, 42, 1] + + >>> comp_and_swap(arr, 1, 2, 0) + >>> print(arr) + [12, 42, -21, 1] + + >>> comp_and_swap(arr, 0, 3, 1) + >>> print(arr) + [1, 42, -21, 12] + + >>> comp_and_swap(arr, 0, 3, 0) + >>> print(arr) + [12, 42, -21, 1] + """ + if (direction == 1 and array[index1] > array[index2]) or ( + direction == 0 and array[index1] < array[index2] + ): + array[index1], array[index2] = array[index2], array[index1] + + +def bitonic_merge(array: List[int], low: int, length: int, direction: int) -> None: + """ + It recursively sorts a bitonic sequence in ascending order, if direction = 1, and in + descending if direction = 0. + The sequence to be sorted starts at index position low, the parameter length is the + number of elements to be sorted. + + >>> arr = [12, 42, -21, 1] + >>> bitonic_merge(arr, 0, 4, 1) + >>> print(arr) + [-21, 1, 12, 42] + + >>> bitonic_merge(arr, 0, 4, 0) + >>> print(arr) + [42, 12, 1, -21] + """ + if length > 1: + middle = int(length / 2) + for i in range(low, low + middle): + comp_and_swap(array, i, i + middle, direction) + bitonic_merge(array, low, middle, direction) + bitonic_merge(array, low + middle, middle, direction) + + +def bitonic_sort(array: List[int], low: int, length: int, direction: int) -> None: + """ + This function first produces a bitonic sequence by recursively sorting its two + halves in opposite sorting orders, and then calls bitonic_merge to make them in the + same order. + + >>> arr = [12, 34, 92, -23, 0, -121, -167, 145] + >>> bitonic_sort(arr, 0, 8, 1) + >>> arr + [-167, -121, -23, 0, 12, 34, 92, 145] + + >>> bitonic_sort(arr, 0, 8, 0) + >>> arr + [145, 92, 34, 12, 0, -23, -121, -167] + """ + if length > 1: + middle = int(length / 2) + bitonic_sort(array, low, middle, 1) + bitonic_sort(array, low + middle, middle, 0) + bitonic_merge(array, low, length, direction) if __name__ == "__main__": + user_input = input("Enter numbers separated by a comma:\n").strip() + unsorted = [int(item.strip()) for item in user_input.split(",")] - a = [] - - n = int(input().strip()) - for i in range(n): - a.append(int(input().strip())) - up = 1 + bitonic_sort(unsorted, 0, len(unsorted), 1) + print("\nSorted array in ascending order is: ", end="") + print(*unsorted, sep=", ") - sort(a, n, up) - print("\n\nSorted array is") - for i in range(n): - print("%d" % a[i]) + bitonic_merge(unsorted, 0, len(unsorted), 0) + print("Sorted array in descending order is: ", end="") + print(*unsorted, sep=", ") From c39be1d8b84fbc788160051c06e6b8f2bd66ed4f Mon Sep 17 00:00:00 2001 From: Lewis Tian Date: Wed, 9 Dec 2020 17:21:46 +0800 Subject: [PATCH 1050/1071] update graphs/breadth_first_search.py (#3908) * update graphs/breadth_first_search.py - update naming style to snake_case - add type hints * add doctests --- graphs/breadth_first_search.py | 74 ++++++++++++++++++++++++---------- 1 file changed, 53 insertions(+), 21 deletions(-) diff --git a/graphs/breadth_first_search.py b/graphs/breadth_first_search.py index e40ec9d1d06d..ee9855bd0c2d 100644 --- a/graphs/breadth_first_search.py +++ b/graphs/breadth_first_search.py @@ -2,24 +2,52 @@ """ Author: OMKAR PATHAK """ +from typing import Set + class Graph: - def __init__(self): + def __init__(self) -> None: self.vertices = {} - def printGraph(self): - """prints adjacency list representation of graaph""" - for i in self.vertices.keys(): + def print_graph(self) -> None: + """ + prints adjacency list representation of graaph + >>> g = Graph() + >>> g.print_graph() + >>> g.add_edge(0, 1) + >>> g.print_graph() + 0 : 1 + """ + for i in self.vertices: print(i, " : ", " -> ".join([str(j) for j in self.vertices[i]])) - def addEdge(self, fromVertex, toVertex): - """adding the edge between two vertices""" - if fromVertex in self.vertices.keys(): - self.vertices[fromVertex].append(toVertex) + def add_edge(self, from_vertex: int, to_vertex: int) -> None: + """ + adding the edge between two vertices + >>> g = Graph() + >>> g.print_graph() + >>> g.add_edge(0, 1) + >>> g.print_graph() + 0 : 1 + """ + if from_vertex in self.vertices: + self.vertices[from_vertex].append(to_vertex) else: - self.vertices[fromVertex] = [toVertex] + self.vertices[from_vertex] = [to_vertex] - def BFS(self, startVertex): + def bfs(self, start_vertex: int) -> Set[int]: + """ + >>> g = Graph() + >>> g.add_edge(0, 1) + >>> g.add_edge(0, 1) + >>> g.add_edge(0, 2) + >>> g.add_edge(1, 2) + >>> g.add_edge(2, 0) + >>> g.add_edge(2, 3) + >>> g.add_edge(3, 3) + >>> sorted(g.bfs(2)) + [0, 1, 2, 3] + """ # initialize set for storing already visited vertices visited = set() @@ -27,8 +55,8 @@ def BFS(self, startVertex): queue = [] # mark the source node as visited and enqueue it - visited.add(startVertex) - queue.append(startVertex) + visited.add(start_vertex) + queue.append(start_vertex) while queue: vertex = queue.pop(0) @@ -42,18 +70,22 @@ def BFS(self, startVertex): if __name__ == "__main__": + from doctest import testmod + + testmod(verbose=True) + g = Graph() - g.addEdge(0, 1) - g.addEdge(0, 2) - g.addEdge(1, 2) - g.addEdge(2, 0) - g.addEdge(2, 3) - g.addEdge(3, 3) - - g.printGraph() + g.add_edge(0, 1) + g.add_edge(0, 2) + g.add_edge(1, 2) + g.add_edge(2, 0) + g.add_edge(2, 3) + g.add_edge(3, 3) + + g.print_graph() # 0 : 1 -> 2 # 1 : 2 # 2 : 0 -> 3 # 3 : 3 - assert sorted(g.BFS(2)) == [0, 1, 2, 3] + assert sorted(g.bfs(2)) == [0, 1, 2, 3] From e7ab06f5dedd2a3f216bb90b794edd760d9f8f4d Mon Sep 17 00:00:00 2001 From: Alex Joslin Date: Wed, 9 Dec 2020 01:22:07 -0800 Subject: [PATCH 1051/1071] Implemented minimum steps to one using tabulation. (#3911) * Implemented minimum steps to one using tabulation. * Update minimum_steps_to_one.py Made the parameter "n" more descriptive. Changed it to number * `n` to `number` Co-authored-by: John Law --- dynamic_programming/minimum_steps_to_one.py | 65 +++++++++++++++++++++ 1 file changed, 65 insertions(+) create mode 100644 dynamic_programming/minimum_steps_to_one.py diff --git a/dynamic_programming/minimum_steps_to_one.py b/dynamic_programming/minimum_steps_to_one.py new file mode 100644 index 000000000000..f4eb7033dd20 --- /dev/null +++ b/dynamic_programming/minimum_steps_to_one.py @@ -0,0 +1,65 @@ +""" +YouTube Explanation: https://www.youtube.com/watch?v=f2xi3c1S95M + +Given an integer n, return the minimum steps to 1 + +AVAILABLE STEPS: + * Decrement by 1 + * if n is divisible by 2, divide by 2 + * if n is divisible by 3, divide by 3 + + +Example 1: n = 10 +10 -> 9 -> 3 -> 1 +Result: 3 steps + +Example 2: n = 15 +15 -> 5 -> 4 -> 2 -> 1 +Result: 4 steps + +Example 3: n = 6 +6 -> 2 -> 1 +Result: 2 step +""" + +from __future__ import annotations + +__author__ = "Alexander Joslin" + + +def min_steps_to_one(number: int) -> int: + """ + Minimum steps to 1 implemented using tabulation. + >>> min_steps_to_one(10) + 3 + >>> min_steps_to_one(15) + 4 + >>> min_steps_to_one(6) + 2 + + :param number: + :return int: + """ + + if number <= 0: + raise ValueError(f"n must be greater than 0. Got n = {number}") + + table = [number + 1] * (number + 1) + + # starting position + table[1] = 0 + for i in range(1, number): + table[i + 1] = min(table[i + 1], table[i] + 1) + # check if out of bounds + if i * 2 <= number: + table[i * 2] = min(table[i * 2], table[i] + 1) + # check if out of bounds + if i * 3 <= number: + table[i * 3] = min(table[i * 3], table[i] + 1) + return table[number] + + +if __name__ == "__main__": + import doctest + + doctest.testmod() From b1801398ec339a4e3813b38054f0ec34dfa43bfe Mon Sep 17 00:00:00 2001 From: fpringle Date: Wed, 9 Dec 2020 12:14:51 +0100 Subject: [PATCH 1052/1071] Add Project Euler Problem 180 (#4017) * Added solution for Project Euler problem 180 * Fixed minor details in Project Euler problem 180 * updating DIRECTORY.md Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> --- DIRECTORY.md | 3 + project_euler/problem_180/__init__.py | 0 project_euler/problem_180/sol1.py | 174 ++++++++++++++++++++++++++ 3 files changed, 177 insertions(+) create mode 100644 project_euler/problem_180/__init__.py create mode 100644 project_euler/problem_180/sol1.py diff --git a/DIRECTORY.md b/DIRECTORY.md index c9c3a09eb599..10523a85c48e 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -245,6 +245,7 @@ * [Max Sum Contiguous Subsequence](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/max_sum_contiguous_subsequence.py) * [Minimum Cost Path](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/minimum_cost_path.py) * [Minimum Partition](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/minimum_partition.py) + * [Minimum Steps To One](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/minimum_steps_to_one.py) * [Optimal Binary Search Tree](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/optimal_binary_search_tree.py) * [Rod Cutting](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/rod_cutting.py) * [Subset Generation](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/subset_generation.py) @@ -754,6 +755,8 @@ * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_173/sol1.py) * Problem 174 * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_174/sol1.py) + * Problem 180 + * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_180/sol1.py) * Problem 188 * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_188/sol1.py) * Problem 191 diff --git a/project_euler/problem_180/__init__.py b/project_euler/problem_180/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/project_euler/problem_180/sol1.py b/project_euler/problem_180/sol1.py new file mode 100644 index 000000000000..6112db2ea370 --- /dev/null +++ b/project_euler/problem_180/sol1.py @@ -0,0 +1,174 @@ +""" +Project Euler Problem 234: https://projecteuler.net/problem=234 + +For any integer n, consider the three functions + +f1,n(x,y,z) = x^(n+1) + y^(n+1) - z^(n+1) +f2,n(x,y,z) = (xy + yz + zx)*(x^(n-1) + y^(n-1) - z^(n-1)) +f3,n(x,y,z) = xyz*(xn-2 + yn-2 - zn-2) + +and their combination + +fn(x,y,z) = f1,n(x,y,z) + f2,n(x,y,z) - f3,n(x,y,z) + +We call (x,y,z) a golden triple of order k if x, y, and z are all rational numbers +of the form a / b with 0 < a < b ≤ k and there is (at least) one integer n, +so that fn(x,y,z) = 0. + +Let s(x,y,z) = x + y + z. +Let t = u / v be the sum of all distinct s(x,y,z) for all golden triples +(x,y,z) of order 35. +All the s(x,y,z) and t must be in reduced form. + +Find u + v. + + +Solution: + +By expanding the brackets it is easy to show that +fn(x, y, z) = (x + y + z) * (x^n + y^n - z^n). + +Since x,y,z are positive, the requirement fn(x, y, z) = 0 is fulfilled if and +only if x^n + y^n = z^n. + +By Fermat's Last Theorem, this means that the absolute value of n can not +exceed 2, i.e. n is in {-2, -1, 0, 1, 2}. We can eliminate n = 0 since then the +equation would reduce to 1 + 1 = 1, for which there are no solutions. + +So all we have to do is iterate through the possible numerators and denominators +of x and y, calculate the corresponding z, and check if the corresponding numerator and +denominator are integer and satisfy 0 < z_num < z_den <= 0. We use a set "uniquq_s" +to make sure there are no duplicates, and the fractions.Fraction class to make sure +we get the right numerator and denominator. + +Reference: +https://en.wikipedia.org/wiki/Fermat%27s_Last_Theorem +""" + + +from fractions import Fraction +from math import gcd, sqrt +from typing import Tuple + + +def is_sq(number: int) -> bool: + """ + Check if number is a perfect square. + + >>> is_sq(1) + True + >>> is_sq(1000001) + False + >>> is_sq(1000000) + True + """ + sq: int = int(number ** 0.5) + return number == sq * sq + + +def add_three( + x_num: int, x_den: int, y_num: int, y_den: int, z_num: int, z_den: int +) -> Tuple[int, int]: + """ + Given the numerators and denominators of three fractions, return the + numerator and denominator of their sum in lowest form. + >>> add_three(1, 3, 1, 3, 1, 3) + (1, 1) + >>> add_three(2, 5, 4, 11, 12, 3) + (262, 55) + """ + top: int = x_num * y_den * z_den + y_num * x_den * z_den + z_num * x_den * y_den + bottom: int = x_den * y_den * z_den + hcf: int = gcd(top, bottom) + top //= hcf + bottom //= hcf + return top, bottom + + +def solution(order: int = 35) -> int: + """ + Find the sum of the numerator and denominator of the sum of all s(x,y,z) for + golden triples (x,y,z) of the given order. + + >>> solution(5) + 296 + >>> solution(10) + 12519 + >>> solution(20) + 19408891927 + """ + unique_s: set = set() + hcf: int + total: Fraction = Fraction(0) + fraction_sum: Tuple[int, int] + + for x_num in range(1, order + 1): + for x_den in range(x_num + 1, order + 1): + for y_num in range(1, order + 1): + for y_den in range(y_num + 1, order + 1): + # n=1 + z_num = x_num * y_den + x_den * y_num + z_den = x_den * y_den + hcf = gcd(z_num, z_den) + z_num //= hcf + z_den //= hcf + if 0 < z_num < z_den <= order: + fraction_sum = add_three( + x_num, x_den, y_num, y_den, z_num, z_den + ) + unique_s.add(fraction_sum) + + # n=2 + z_num = ( + x_num * x_num * y_den * y_den + x_den * x_den * y_num * y_num + ) + z_den = x_den * x_den * y_den * y_den + if is_sq(z_num) and is_sq(z_den): + z_num = int(sqrt(z_num)) + z_den = int(sqrt(z_den)) + hcf = gcd(z_num, z_den) + z_num //= hcf + z_den //= hcf + if 0 < z_num < z_den <= order: + fraction_sum = add_three( + x_num, x_den, y_num, y_den, z_num, z_den + ) + unique_s.add(fraction_sum) + + # n=-1 + z_num = x_num * y_num + z_den = x_den * y_num + x_num * y_den + hcf = gcd(z_num, z_den) + z_num //= hcf + z_den //= hcf + if 0 < z_num < z_den <= order: + fraction_sum = add_three( + x_num, x_den, y_num, y_den, z_num, z_den + ) + unique_s.add(fraction_sum) + + # n=2 + z_num = x_num * x_num * y_num * y_num + z_den = ( + x_den * x_den * y_num * y_num + x_num * x_num * y_den * y_den + ) + if is_sq(z_num) and is_sq(z_den): + z_num = int(sqrt(z_num)) + z_den = int(sqrt(z_den)) + hcf = gcd(z_num, z_den) + z_num //= hcf + z_den //= hcf + if 0 < z_num < z_den <= order: + fraction_sum = add_three( + x_num, x_den, y_num, y_den, z_num, z_den + ) + unique_s.add(fraction_sum) + + for num, den in unique_s: + total += Fraction(num, den) + + return total.denominator + total.numerator + + +if __name__ == "__main__": + print(f"{solution() = }") From bd4b83fcc7fba84e2d71d560f65299fd56c15640 Mon Sep 17 00:00:00 2001 From: Umair Kamran Date: Wed, 9 Dec 2020 19:01:58 +0500 Subject: [PATCH 1053/1071] Chore: Added type hints to searches/binary_search.py (#2682) * Chore: Added type hints to searches/binary_search.py * Use -1 as the sentinal value * Wrap long lines * Update binary_search.py * Update binary_search.py Co-authored-by: Christian Clauss --- searches/binary_search.py | 74 +++++++++++++++------------------------ 1 file changed, 28 insertions(+), 46 deletions(-) diff --git a/searches/binary_search.py b/searches/binary_search.py index d0f6296168fa..35e0dd0596d2 100644 --- a/searches/binary_search.py +++ b/searches/binary_search.py @@ -1,18 +1,21 @@ +#!/usr/bin/env python3 + """ This is pure Python implementation of binary search algorithms For doctests run following command: -python -m doctest -v binary_search.py -or python3 -m doctest -v binary_search.py For manual testing run: -python binary_search.py +python3 binary_search.py """ import bisect +from typing import List, Optional -def bisect_left(sorted_collection, item, lo=0, hi=None): +def bisect_left( + sorted_collection: List[int], item: int, lo: int = 0, hi: int = -1 +) -> int: """ Locates the first element in a sorted array that is larger or equal to a given value. @@ -43,7 +46,7 @@ def bisect_left(sorted_collection, item, lo=0, hi=None): >>> bisect_left([0, 5, 7, 10, 15], 6, 2) 2 """ - if hi is None: + if hi < 0: hi = len(sorted_collection) while lo < hi: @@ -56,7 +59,9 @@ def bisect_left(sorted_collection, item, lo=0, hi=None): return lo -def bisect_right(sorted_collection, item, lo=0, hi=None): +def bisect_right( + sorted_collection: List[int], item: int, lo: int = 0, hi: int = -1 +) -> int: """ Locates the first element in a sorted array that is larger than a given value. @@ -86,7 +91,7 @@ def bisect_right(sorted_collection, item, lo=0, hi=None): >>> bisect_right([0, 5, 7, 10, 15], 6, 2) 2 """ - if hi is None: + if hi < 0: hi = len(sorted_collection) while lo < hi: @@ -99,7 +104,9 @@ def bisect_right(sorted_collection, item, lo=0, hi=None): return lo -def insort_left(sorted_collection, item, lo=0, hi=None): +def insort_left( + sorted_collection: List[int], item: int, lo: int = 0, hi: int = -1 +) -> None: """ Inserts a given value into a sorted array before other values with the same value. @@ -140,7 +147,9 @@ def insort_left(sorted_collection, item, lo=0, hi=None): sorted_collection.insert(bisect_left(sorted_collection, item, lo, hi), item) -def insort_right(sorted_collection, item, lo=0, hi=None): +def insort_right( + sorted_collection: List[int], item: int, lo: int = 0, hi: int = -1 +) -> None: """ Inserts a given value into a sorted array after other values with the same value. @@ -181,7 +190,7 @@ def insort_right(sorted_collection, item, lo=0, hi=None): sorted_collection.insert(bisect_right(sorted_collection, item, lo, hi), item) -def binary_search(sorted_collection, item): +def binary_search(sorted_collection: List[int], item: int) -> Optional[int]: """Pure implementation of binary search algorithm in Python Be careful collection must be ascending sorted, otherwise result will be @@ -219,7 +228,7 @@ def binary_search(sorted_collection, item): return None -def binary_search_std_lib(sorted_collection, item): +def binary_search_std_lib(sorted_collection: List[int], item: int) -> Optional[int]: """Pure implementation of binary search algorithm in Python using stdlib Be careful collection must be ascending sorted, otherwise result will be @@ -248,7 +257,9 @@ def binary_search_std_lib(sorted_collection, item): return None -def binary_search_by_recursion(sorted_collection, item, left, right): +def binary_search_by_recursion( + sorted_collection: List[int], item: int, left: int, right: int +) -> Optional[int]: """Pure implementation of binary search algorithm in Python by recursion @@ -286,41 +297,12 @@ def binary_search_by_recursion(sorted_collection, item, left, right): return binary_search_by_recursion(sorted_collection, item, midpoint + 1, right) -def __assert_sorted(collection): - """Check if collection is ascending sorted, if not - raises :py:class:`ValueError` - - :param collection: collection - :return: True if collection is ascending sorted - :raise: :py:class:`ValueError` if collection is not ascending sorted - - Examples: - >>> __assert_sorted([0, 1, 2, 4]) - True - - >>> __assert_sorted([10, -1, 5]) - Traceback (most recent call last): - ... - ValueError: Collection must be ascending sorted - """ - if collection != sorted(collection): - raise ValueError("Collection must be ascending sorted") - return True - - if __name__ == "__main__": - import sys - user_input = input("Enter numbers separated by comma:\n").strip() - collection = [int(item) for item in user_input.split(",")] - try: - __assert_sorted(collection) - except ValueError: - sys.exit("Sequence must be ascending sorted to apply binary search") - - target_input = input("Enter a single number to be found in the list:\n") - target = int(target_input) + collection = sorted(int(item) for item in user_input.split(",")) + target = int(input("Enter a single number to be found in the list:\n")) result = binary_search(collection, target) - if result is not None: - print(f"{target} found at positions: {result}") + if result is None: + print(f"{target} was not found in {collection}.") else: - print("Not found") + print(f"{target} was found at position {result} in {collection}.") From 75759fae22e44aa101d8d81705f0a995d038612c Mon Sep 17 00:00:00 2001 From: fpringle Date: Thu, 10 Dec 2020 14:18:17 +0100 Subject: [PATCH 1054/1071] Add solution for Project Euler problem 085 (#4024) * Added solution for Project Euler problem 085. * updating DIRECTORY.md * Minor tweaks to Project Euler problem 85 * Variable comments for project euler problem 85 Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> --- DIRECTORY.md | 2 + project_euler/problem_085/__init__.py | 0 project_euler/problem_085/sol1.py | 108 ++++++++++++++++++++++++++ 3 files changed, 110 insertions(+) create mode 100644 project_euler/problem_085/__init__.py create mode 100644 project_euler/problem_085/sol1.py diff --git a/DIRECTORY.md b/DIRECTORY.md index 10523a85c48e..cb582e793ade 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -727,6 +727,8 @@ * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_080/sol1.py) * Problem 081 * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_081/sol1.py) + * Problem 085 + * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_085/sol1.py) * Problem 087 * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_087/sol1.py) * Problem 089 diff --git a/project_euler/problem_085/__init__.py b/project_euler/problem_085/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/project_euler/problem_085/sol1.py b/project_euler/problem_085/sol1.py new file mode 100644 index 000000000000..74e36b1301a4 --- /dev/null +++ b/project_euler/problem_085/sol1.py @@ -0,0 +1,108 @@ +""" +Project Euler Problem 85: https://projecteuler.net/problem=85 + +By counting carefully it can be seen that a rectangular grid measuring 3 by 2 +contains eighteen rectangles. + +Although there exists no rectangular grid that contains exactly two million +rectangles, find the area of the grid with the nearest solution. + +Solution: + + For a grid with side-lengths a and b, the number of rectangles contained in the grid + is [a*(a+1)/2] * [b*(b+1)/2)], which happens to be the product of the a-th and b-th + triangle numbers. So to find the solution grid (a,b), we need to find the two + triangle numbers whose product is closest to two million. + + Denote these two triangle numbers Ta and Tb. We want their product Ta*Tb to be + as close as possible to 2m. Assuming that the best solution is fairly close to 2m, + We can assume that both Ta and Tb are roughly bounded by 2m. Since Ta = a(a+1)/2, + we can assume that a (and similarly b) are roughly bounded by sqrt(2 * 2m) = 2000. + Since this is a rough bound, to be on the safe side we add 10%. Therefore we start + by generating all the triangle numbers Ta for 1 <= a <= 2200. This can be done + iteratively since the ith triangle number is the sum of 1,2, ... ,i, and so + T(i) = T(i-1) + i. + + We then search this list of triangle numbers for the two that give a product + closest to our target of two million. Rather than testing every combination of 2 + elements of the list, which would find the result in quadratic time, we can find + the best pair in linear time. + + We iterate through the list of triangle numbers using enumerate() so we have a + and Ta. Since we want Ta * Tb to be as close as possible to 2m, we know that Tb + needs to be roughly 2m / Ta. Using the formula Tb = b*(b+1)/2 as well as the + quadratic formula, we can solve for b: + b is roughly (-1 + sqrt(1 + 8 * 2m / Ta)) / 2. + + Since the closest integers to this estimate will give product closest to 2m, + we only need to consider the integers above and below. It's then a simple matter + to get the triangle numbers corresponding to those integers, calculate the product + Ta * Tb, compare that product to our target 2m, and keep track of the (a,b) pair + that comes the closest. + + +Reference: https://en.wikipedia.org/wiki/Triangular_number + https://en.wikipedia.org/wiki/Quadratic_formula +""" + + +from math import ceil, floor, sqrt +from typing import List + + +def solution(target: int = 2000000) -> int: + """ + Find the area of the grid which contains as close to two million rectangles + as possible. + >>> solution(20) + 6 + >>> solution(2000) + 72 + >>> solution(2000000000) + 86595 + """ + triangle_numbers: List[int] = [0] + idx: int + + for idx in range(1, ceil(sqrt(target * 2) * 1.1)): + triangle_numbers.append(triangle_numbers[-1] + idx) + + # we want this to be as close as possible to target + best_product: int = 0 + # the area corresponding to the grid that gives the product closest to target + area: int = 0 + # an estimate of b, using the quadratic formula + b_estimate: float + # the largest integer less than b_estimate + b_floor: int + # the largest integer less than b_estimate + b_ceil: int + # the triangle number corresponding to b_floor + triangle_b_first_guess: int + # the triangle number corresponding to b_ceil + triangle_b_second_guess: int + + for idx_a, triangle_a in enumerate(triangle_numbers[1:], 1): + b_estimate = (-1 + sqrt(1 + 8 * target / triangle_a)) / 2 + b_floor = floor(b_estimate) + b_ceil = ceil(b_estimate) + triangle_b_first_guess = triangle_numbers[b_floor] + triangle_b_second_guess = triangle_numbers[b_ceil] + + if abs(target - triangle_b_first_guess * triangle_a) < abs( + target - best_product + ): + best_product = triangle_b_first_guess * triangle_a + area = idx_a * b_floor + + if abs(target - triangle_b_second_guess * triangle_a) < abs( + target - best_product + ): + best_product = triangle_b_second_guess * triangle_a + area = idx_a * b_ceil + + return area + + +if __name__ == "__main__": + print(f"{solution() = }") From 110a740d5d026c4675489ea2acfefda773c4e032 Mon Sep 17 00:00:00 2001 From: Abdeldjaouad Nusayr Medakene <31663979+MrGeek1337@users.noreply.github.com> Date: Thu, 10 Dec 2020 18:25:57 +0100 Subject: [PATCH 1055/1071] Update ciphers/caesar_cipher.py with type hints (#3860) * Update caesar_cipher.py improved for conciseness and readability * Add type hints Co-authored-by: Dhruv Manilawala --- ciphers/caesar_cipher.py | 32 ++++++++------------------------ 1 file changed, 8 insertions(+), 24 deletions(-) diff --git a/ciphers/caesar_cipher.py b/ciphers/caesar_cipher.py index 4038919e5dde..4b2f76c7d873 100644 --- a/ciphers/caesar_cipher.py +++ b/ciphers/caesar_cipher.py @@ -1,7 +1,8 @@ from string import ascii_letters +from typing import Dict, Optional -def encrypt(input_string: str, key: int, alphabet=None) -> str: +def encrypt(input_string: str, key: int, alphabet: Optional[str] = None) -> str: """ encrypt ======= @@ -79,7 +80,7 @@ def encrypt(input_string: str, key: int, alphabet=None) -> str: return result -def decrypt(input_string: str, key: int, alphabet=None) -> str: +def decrypt(input_string: str, key: int, alphabet: Optional[str] = None) -> str: """ decrypt ======= @@ -144,7 +145,7 @@ def decrypt(input_string: str, key: int, alphabet=None) -> str: return encrypt(input_string, key, alphabet) -def brute_force(input_string: str, alphabet=None) -> dict: +def brute_force(input_string: str, alphabet: Optional[str] = None) -> Dict[int, str]: """ brute_force =========== @@ -193,31 +194,18 @@ def brute_force(input_string: str, alphabet=None) -> dict: # Set default alphabet to lower and upper case english chars alpha = alphabet or ascii_letters - # The key during testing (will increase) - key = 1 - - # The encoded result - result = "" - # To store data on all the combinations brute_force_data = {} # Cycle through each combination - while key <= len(alpha): - # Decrypt the message - result = decrypt(input_string, key, alpha) - - # Update the data - brute_force_data[key] = result - - # Reset result and increase the key - result = "" - key += 1 + for key in range(1, len(alpha) + 1): + # Decrypt the message and store the result in the data + brute_force_data[key] = decrypt(input_string, key, alpha) return brute_force_data -def main(): +if __name__ == "__main__": while True: print(f'\n{"-" * 10}\n Menu\n{"-" * 10}') print(*["1.Encrypt", "2.Decrypt", "3.BruteForce", "4.Quit"], sep="\n") @@ -248,7 +236,3 @@ def main(): elif choice == "4": print("Goodbye.") break - - -if __name__ == "__main__": - main() From 533e36d32ba415be382e9c8c0803d5261b489afd Mon Sep 17 00:00:00 2001 From: zakademic <67771932+zakademic@users.noreply.github.com> Date: Fri, 11 Dec 2020 20:40:23 -0800 Subject: [PATCH 1056/1071] Add conjugate gradient method algorithm (#2486) * Initial commit of the conjugate gradient method * Update linear_algebra/src/conjugate_gradient.py * Added documentation links, changed variable names to lower case and more descriptive naming, added check for symmetry in _is_matrix_spd * Made changes to some variable naming to be more clear * Update conjugate_gradient.py Co-authored-by: Zeyad Zaky Co-authored-by: Christian Clauss Co-authored-by: Dhruv Manilawala --- linear_algebra/src/conjugate_gradient.py | 173 +++++++++++++++++++++++ 1 file changed, 173 insertions(+) create mode 100644 linear_algebra/src/conjugate_gradient.py diff --git a/linear_algebra/src/conjugate_gradient.py b/linear_algebra/src/conjugate_gradient.py new file mode 100644 index 000000000000..1a65b8ccf019 --- /dev/null +++ b/linear_algebra/src/conjugate_gradient.py @@ -0,0 +1,173 @@ +""" +Resources: +- https://en.wikipedia.org/wiki/Conjugate_gradient_method +- https://en.wikipedia.org/wiki/Definite_symmetric_matrix +""" +import numpy as np + + +def _is_matrix_spd(matrix: np.array) -> bool: + """ + Returns True if input matrix is symmetric positive definite. + Returns False otherwise. + + For a matrix to be SPD, all eigenvalues must be positive. + + >>> import numpy as np + >>> matrix = np.array([ + ... [4.12401784, -5.01453636, -0.63865857], + ... [-5.01453636, 12.33347422, -3.40493586], + ... [-0.63865857, -3.40493586, 5.78591885]]) + >>> _is_matrix_spd(matrix) + True + >>> matrix = np.array([ + ... [0.34634879, 1.96165514, 2.18277744], + ... [0.74074469, -1.19648894, -1.34223498], + ... [-0.7687067 , 0.06018373, -1.16315631]]) + >>> _is_matrix_spd(matrix) + False + """ + # Ensure matrix is square. + assert np.shape(matrix)[0] == np.shape(matrix)[1] + + # If matrix not symmetric, exit right away. + if np.allclose(matrix, matrix.T) is False: + return False + + # Get eigenvalues and eignevectors for a symmetric matrix. + eigen_values, _ = np.linalg.eigh(matrix) + + # Check sign of all eigenvalues. + return np.all(eigen_values > 0) + + +def _create_spd_matrix(dimension: np.int64) -> np.array: + """ + Returns a symmetric positive definite matrix given a dimension. + + Input: + dimension gives the square matrix dimension. + + Output: + spd_matrix is an diminesion x dimensions symmetric positive definite (SPD) matrix. + + >>> import numpy as np + >>> dimension = 3 + >>> spd_matrix = _create_spd_matrix(dimension) + >>> _is_matrix_spd(spd_matrix) + True + """ + random_matrix = np.random.randn(dimension, dimension) + spd_matrix = np.dot(random_matrix, random_matrix.T) + assert _is_matrix_spd(spd_matrix) + return spd_matrix + + +def conjugate_gradient( + spd_matrix: np.array, + load_vector: np.array, + max_iterations: int = 1000, + tol: float = 1e-8, +) -> np.array: + """ + Returns solution to the linear system np.dot(spd_matrix, x) = b. + + Input: + spd_matrix is an NxN Symmetric Positive Definite (SPD) matrix. + load_vector is an Nx1 vector. + + Output: + x is an Nx1 vector that is the solution vector. + + >>> import numpy as np + >>> spd_matrix = np.array([ + ... [8.73256573, -5.02034289, -2.68709226], + ... [-5.02034289, 3.78188322, 0.91980451], + ... [-2.68709226, 0.91980451, 1.94746467]]) + >>> b = np.array([ + ... [-5.80872761], + ... [ 3.23807431], + ... [ 1.95381422]]) + >>> conjugate_gradient(spd_matrix, b) + array([[-0.63114139], + [-0.01561498], + [ 0.13979294]]) + """ + # Ensure proper dimensionality. + assert np.shape(spd_matrix)[0] == np.shape(spd_matrix)[1] + assert np.shape(load_vector)[0] == np.shape(spd_matrix)[0] + assert _is_matrix_spd(spd_matrix) + + # Initialize solution guess, residual, search direction. + x0 = np.zeros((np.shape(load_vector)[0], 1)) + r0 = np.copy(load_vector) + p0 = np.copy(r0) + + # Set initial errors in solution guess and residual. + error_residual = 1e9 + error_x_solution = 1e9 + error = 1e9 + + # Set iteration counter to threshold number of iterations. + iterations = 0 + + while error > tol: + + # Save this value so we only calculate the matrix-vector product once. + w = np.dot(spd_matrix, p0) + + # The main algorithm. + + # Update search direction magnitude. + alpha = np.dot(r0.T, r0) / np.dot(p0.T, w) + # Update solution guess. + x = x0 + alpha * p0 + # Calculate new residual. + r = r0 - alpha * w + # Calculate new Krylov subspace scale. + beta = np.dot(r.T, r) / np.dot(r0.T, r0) + # Calculate new A conjuage search direction. + p = r + beta * p0 + + # Calculate errors. + error_residual = np.linalg.norm(r - r0) + error_x_solution = np.linalg.norm(x - x0) + error = np.maximum(error_residual, error_x_solution) + + # Update variables. + x0 = np.copy(x) + r0 = np.copy(r) + p0 = np.copy(p) + + # Update number of iterations. + iterations += 1 + + return x + + +def test_conjugate_gradient() -> None: + """ + >>> test_conjugate_gradient() # self running tests + """ + # Create linear system with SPD matrix and known solution x_true. + dimension = 3 + spd_matrix = _create_spd_matrix(dimension) + x_true = np.random.randn(dimension, 1) + b = np.dot(spd_matrix, x_true) + + # Numpy solution. + x_numpy = np.linalg.solve(spd_matrix, b) + + # Our implementation. + x_conjugate_gradient = conjugate_gradient(spd_matrix, b) + + # Ensure both solutions are close to x_true (and therefore one another). + assert np.linalg.norm(x_numpy - x_true) <= 1e-6 + assert np.linalg.norm(x_conjugate_gradient - x_true) <= 1e-6 + + +if __name__ == "__main__": + import doctest + + doctest.testmod() + test_conjugate_gradient() From a6f6eb264995c41db646467a0078aa24e4ebec48 Mon Sep 17 00:00:00 2001 From: fpringle Date: Sat, 12 Dec 2020 06:19:35 +0100 Subject: [PATCH 1057/1071] Add solution for Project Euler problem 86 (#4025) * Added solution for Project Euler problem 86 * updating DIRECTORY.md Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> --- DIRECTORY.md | 2 + project_euler/problem_086/__init__.py | 0 project_euler/problem_086/sol1.py | 105 ++++++++++++++++++++++++++ 3 files changed, 107 insertions(+) create mode 100644 project_euler/problem_086/__init__.py create mode 100644 project_euler/problem_086/sol1.py diff --git a/DIRECTORY.md b/DIRECTORY.md index cb582e793ade..7eec7e0811dd 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -729,6 +729,8 @@ * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_081/sol1.py) * Problem 085 * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_085/sol1.py) + * Problem 086 + * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_086/sol1.py) * Problem 087 * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_087/sol1.py) * Problem 089 diff --git a/project_euler/problem_086/__init__.py b/project_euler/problem_086/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/project_euler/problem_086/sol1.py b/project_euler/problem_086/sol1.py new file mode 100644 index 000000000000..0bf66e6b5a31 --- /dev/null +++ b/project_euler/problem_086/sol1.py @@ -0,0 +1,105 @@ +""" +Project Euler Problem 86: https://projecteuler.net/problem=86 + +A spider, S, sits in one corner of a cuboid room, measuring 6 by 5 by 3, and a fly, F, +sits in the opposite corner. By travelling on the surfaces of the room the shortest +"straight line" distance from S to F is 10 and the path is shown on the diagram. + +However, there are up to three "shortest" path candidates for any given cuboid and the +shortest route doesn't always have integer length. + +It can be shown that there are exactly 2060 distinct cuboids, ignoring rotations, with +integer dimensions, up to a maximum size of M by M by M, for which the shortest route +has integer length when M = 100. This is the least value of M for which the number of +solutions first exceeds two thousand; the number of solutions when M = 99 is 1975. + +Find the least value of M such that the number of solutions first exceeds one million. + +Solution: + Label the 3 side-lengths of the cuboid a,b,c such that 1 <= a <= b <= c <= M. + By conceptually "opening up" the cuboid and laying out its faces on a plane, + it can be seen that the shortest distance between 2 opposite corners is + sqrt((a+b)^2 + c^2). This distance is an integer if and only if (a+b),c make up + the first 2 sides of a pythagorean triplet. + + The second useful insight is rather than calculate the number of cuboids + with integral shortest distance for each maximum cuboid side-length M, + we can calculate this number iteratively each time we increase M, as follows. + The set of cuboids satisfying this property with maximum side-length M-1 is a + subset of the cuboids satisfying the property with maximum side-length M + (since any cuboids with side lengths <= M-1 are also <= M). To calculate the + number of cuboids in the larger set (corresponding to M) we need only consider + the cuboids which have at least one side of length M. Since we have ordered the + side lengths a <= b <= c, we can assume that c = M. Then we just need to count + the number of pairs a,b satisfying the conditions: + sqrt((a+b)^2 + M^2) is integer + 1 <= a <= b <= M + + To count the number of pairs (a,b) satisfying these conditions, write d = a+b. + Now we have: + 1 <= a <= b <= M => 2 <= d <= 2*M + we can actually make the second equality strict, + since d = 2*M => d^2 + M^2 = 5M^2 + => shortest distance = M * sqrt(5) + => not integral. + a + b = d => b = d - a + and a <= b + => a <= d/2 + also a <= M + => a <= min(M, d//2) + + a + b = d => a = d - b + and b <= M + => a >= d - M + also a >= 1 + => a >= max(1, d - M) + + So a is in range(max(1, d - M), min(M, d // 2) + 1) + + For a given d, the number of cuboids satisfying the required property with c = M + and a + b = d is the length of this range, which is + min(M, d // 2) + 1 - max(1, d - M). + + In the code below, d is sum_shortest_sides + and M is max_cuboid_size. + + +""" + + +from math import sqrt + + +def solution(limit: int = 1000000) -> int: + """ + Return the least value of M such that there are more than one million cuboids + of side lengths 1 <= a,b,c <= M such that the shortest distance between two + opposite vertices of the cuboid is integral. + >>> solution(100) + 24 + >>> solution(1000) + 72 + >>> solution(2000) + 100 + >>> solution(20000) + 288 + """ + num_cuboids: int = 0 + max_cuboid_size: int = 0 + sum_shortest_sides: int + + while num_cuboids <= limit: + max_cuboid_size += 1 + for sum_shortest_sides in range(2, 2 * max_cuboid_size + 1): + if sqrt(sum_shortest_sides ** 2 + max_cuboid_size ** 2).is_integer(): + num_cuboids += ( + min(max_cuboid_size, sum_shortest_sides // 2) + - max(1, sum_shortest_sides - max_cuboid_size) + + 1 + ) + + return max_cuboid_size + + +if __name__ == "__main__": + print(f"{solution() = }") From ae8a5f86754ea1cc466314fa40a664c7322d4be9 Mon Sep 17 00:00:00 2001 From: fpringle Date: Sun, 13 Dec 2020 12:09:52 +0100 Subject: [PATCH 1058/1071] Add solution for Project Euler problem 59 (#4031) * Added solution for Project Euler problem 59 * updating DIRECTORY.md * Formatting, type hints, no more evil map functions * Doctests * Added doctests for Project Euler problem 59 Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> --- DIRECTORY.md | 3 + project_euler/problem_059/__init__.py | 0 project_euler/problem_059/p059_cipher.txt | 1 + project_euler/problem_059/sol1.py | 128 ++++++++++++++++++++++ project_euler/problem_059/test_cipher.txt | 1 + 5 files changed, 133 insertions(+) create mode 100644 project_euler/problem_059/__init__.py create mode 100644 project_euler/problem_059/p059_cipher.txt create mode 100644 project_euler/problem_059/sol1.py create mode 100644 project_euler/problem_059/test_cipher.txt diff --git a/DIRECTORY.md b/DIRECTORY.md index 7eec7e0811dd..929a986b0f3b 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -345,6 +345,7 @@ ## Linear Algebra * Src + * [Conjugate Gradient](https://github.com/TheAlgorithms/Python/blob/master/linear_algebra/src/conjugate_gradient.py) * [Lib](https://github.com/TheAlgorithms/Python/blob/master/linear_algebra/src/lib.py) * [Polynom For Points](https://github.com/TheAlgorithms/Python/blob/master/linear_algebra/src/polynom_for_points.py) * [Power Iteration](https://github.com/TheAlgorithms/Python/blob/master/linear_algebra/src/power_iteration.py) @@ -695,6 +696,8 @@ * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_057/sol1.py) * Problem 058 * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_058/sol1.py) + * Problem 059 + * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_059/sol1.py) * Problem 062 * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_062/sol1.py) * Problem 063 diff --git a/project_euler/problem_059/__init__.py b/project_euler/problem_059/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/project_euler/problem_059/p059_cipher.txt b/project_euler/problem_059/p059_cipher.txt new file mode 100644 index 000000000000..b3b3247298d1 --- /dev/null +++ b/project_euler/problem_059/p059_cipher.txt @@ -0,0 +1 @@ +36,22,80,0,0,4,23,25,19,17,88,4,4,19,21,11,88,22,23,23,29,69,12,24,0,88,25,11,12,2,10,28,5,6,12,25,10,22,80,10,30,80,10,22,21,69,23,22,69,61,5,9,29,2,66,11,80,8,23,3,17,88,19,0,20,21,7,10,17,17,29,20,69,8,17,21,29,2,22,84,80,71,60,21,69,11,5,8,21,25,22,88,3,0,10,25,0,10,5,8,88,2,0,27,25,21,10,31,6,25,2,16,21,82,69,35,63,11,88,4,13,29,80,22,13,29,22,88,31,3,88,3,0,10,25,0,11,80,10,30,80,23,29,19,12,8,2,10,27,17,9,11,45,95,88,57,69,16,17,19,29,80,23,29,19,0,22,4,9,1,80,3,23,5,11,28,92,69,9,5,12,12,21,69,13,30,0,0,0,0,27,4,0,28,28,28,84,80,4,22,80,0,20,21,2,25,30,17,88,21,29,8,2,0,11,3,12,23,30,69,30,31,23,88,4,13,29,80,0,22,4,12,10,21,69,11,5,8,88,31,3,88,4,13,17,3,69,11,21,23,17,21,22,88,65,69,83,80,84,87,68,69,83,80,84,87,73,69,83,80,84,87,65,83,88,91,69,29,4,6,86,92,69,15,24,12,27,24,69,28,21,21,29,30,1,11,80,10,22,80,17,16,21,69,9,5,4,28,2,4,12,5,23,29,80,10,30,80,17,16,21,69,27,25,23,27,28,0,84,80,22,23,80,17,16,17,17,88,25,3,88,4,13,29,80,17,10,5,0,88,3,16,21,80,10,30,80,17,16,25,22,88,3,0,10,25,0,11,80,12,11,80,10,26,4,4,17,30,0,28,92,69,30,2,10,21,80,12,12,80,4,12,80,10,22,19,0,88,4,13,29,80,20,13,17,1,10,17,17,13,2,0,88,31,3,88,4,13,29,80,6,17,2,6,20,21,69,30,31,9,20,31,18,11,94,69,54,17,8,29,28,28,84,80,44,88,24,4,14,21,69,30,31,16,22,20,69,12,24,4,12,80,17,16,21,69,11,5,8,88,31,3,88,4,13,17,3,69,11,21,23,17,21,22,88,25,22,88,17,69,11,25,29,12,24,69,8,17,23,12,80,10,30,80,17,16,21,69,11,1,16,25,2,0,88,31,3,88,4,13,29,80,21,29,2,12,21,21,17,29,2,69,23,22,69,12,24,0,88,19,12,10,19,9,29,80,18,16,31,22,29,80,1,17,17,8,29,4,0,10,80,12,11,80,84,67,80,10,10,80,7,1,80,21,13,4,17,17,30,2,88,4,13,29,80,22,13,29,69,23,22,69,12,24,12,11,80,22,29,2,12,29,3,69,29,1,16,25,28,69,12,31,69,11,92,69,17,4,69,16,17,22,88,4,13,29,80,23,25,4,12,23,80,22,9,2,17,80,70,76,88,29,16,20,4,12,8,28,12,29,20,69,26,9,69,11,80,17,23,80,84,88,31,3,88,4,13,29,80,21,29,2,12,21,21,17,29,2,69,12,31,69,12,24,0,88,20,12,25,29,0,12,21,23,86,80,44,88,7,12,20,28,69,11,31,10,22,80,22,16,31,18,88,4,13,25,4,69,12,24,0,88,3,16,21,80,10,30,80,17,16,25,22,88,3,0,10,25,0,11,80,17,23,80,7,29,80,4,8,0,23,23,8,12,21,17,17,29,28,28,88,65,75,78,68,81,65,67,81,72,70,83,64,68,87,74,70,81,75,70,81,67,80,4,22,20,69,30,2,10,21,80,8,13,28,17,17,0,9,1,25,11,31,80,17,16,25,22,88,30,16,21,18,0,10,80,7,1,80,22,17,8,73,88,17,11,28,80,17,16,21,11,88,4,4,19,25,11,31,80,17,16,21,69,11,1,16,25,2,0,88,2,10,23,4,73,88,4,13,29,80,11,13,29,7,29,2,69,75,94,84,76,65,80,65,66,83,77,67,80,64,73,82,65,67,87,75,72,69,17,3,69,17,30,1,29,21,1,88,0,23,23,20,16,27,21,1,84,80,18,16,25,6,16,80,0,0,0,23,29,3,22,29,3,69,12,24,0,88,0,0,10,25,8,29,4,0,10,80,10,30,80,4,88,19,12,10,19,9,29,80,18,16,31,22,29,80,1,17,17,8,29,4,0,10,80,12,11,80,84,86,80,35,23,28,9,23,7,12,22,23,69,25,23,4,17,30,69,12,24,0,88,3,4,21,21,69,11,4,0,8,3,69,26,9,69,15,24,12,27,24,69,49,80,13,25,20,69,25,2,23,17,6,0,28,80,4,12,80,17,16,25,22,88,3,16,21,92,69,49,80,13,25,6,0,88,20,12,11,19,10,14,21,23,29,20,69,12,24,4,12,80,17,16,21,69,11,5,8,88,31,3,88,4,13,29,80,22,29,2,12,29,3,69,73,80,78,88,65,74,73,70,69,83,80,84,87,72,84,88,91,69,73,95,87,77,70,69,83,80,84,87,70,87,77,80,78,88,21,17,27,94,69,25,28,22,23,80,1,29,0,0,22,20,22,88,31,11,88,4,13,29,80,20,13,17,1,10,17,17,13,2,0,88,31,3,88,4,13,29,80,6,17,2,6,20,21,75,88,62,4,21,21,9,1,92,69,12,24,0,88,3,16,21,80,10,30,80,17,16,25,22,88,29,16,20,4,12,8,28,12,29,20,69,26,9,69,65,64,69,31,25,19,29,3,69,12,24,0,88,18,12,9,5,4,28,2,4,12,21,69,80,22,10,13,2,17,16,80,21,23,7,0,10,89,69,23,22,69,12,24,0,88,19,12,10,19,16,21,22,0,10,21,11,27,21,69,23,22,69,12,24,0,88,0,0,10,25,8,29,4,0,10,80,10,30,80,4,88,19,12,10,19,9,29,80,18,16,31,22,29,80,1,17,17,8,29,4,0,10,80,12,11,80,84,86,80,36,22,20,69,26,9,69,11,25,8,17,28,4,10,80,23,29,17,22,23,30,12,22,23,69,49,80,13,25,6,0,88,28,12,19,21,18,17,3,0,88,18,0,29,30,69,25,18,9,29,80,17,23,80,1,29,4,0,10,29,12,22,21,69,12,24,0,88,3,16,21,3,69,23,22,69,12,24,0,88,3,16,26,3,0,9,5,0,22,4,69,11,21,23,17,21,22,88,25,11,88,7,13,17,19,13,88,4,13,29,80,0,0,0,10,22,21,11,12,3,69,25,2,0,88,21,19,29,30,69,22,5,8,26,21,23,11,94 \ No newline at end of file diff --git a/project_euler/problem_059/sol1.py b/project_euler/problem_059/sol1.py new file mode 100644 index 000000000000..1f55029b2613 --- /dev/null +++ b/project_euler/problem_059/sol1.py @@ -0,0 +1,128 @@ +""" +Each character on a computer is assigned a unique code and the preferred standard is +ASCII (American Standard Code for Information Interchange). +For example, uppercase A = 65, asterisk (*) = 42, and lowercase k = 107. + +A modern encryption method is to take a text file, convert the bytes to ASCII, then +XOR each byte with a given value, taken from a secret key. The advantage with the +XOR function is that using the same encryption key on the cipher text, restores +the plain text; for example, 65 XOR 42 = 107, then 107 XOR 42 = 65. + +For unbreakable encryption, the key is the same length as the plain text message, and +the key is made up of random bytes. The user would keep the encrypted message and the +encryption key in different locations, and without both "halves", it is impossible to +decrypt the message. + +Unfortunately, this method is impractical for most users, so the modified method is +to use a password as a key. If the password is shorter than the message, which is +likely, the key is repeated cyclically throughout the message. The balance for this +method is using a sufficiently long password key for security, but short enough to +be memorable. + +Your task has been made easy, as the encryption key consists of three lower case +characters. Using p059_cipher.txt (right click and 'Save Link/Target As...'), a +file containing the encrypted ASCII codes, and the knowledge that the plain text +must contain common English words, decrypt the message and find the sum of the ASCII +values in the original text. +""" + + +import string +from itertools import cycle, product +from pathlib import Path +from typing import List, Optional, Set, Tuple + +VALID_CHARS: str = ( + string.ascii_letters + string.digits + string.punctuation + string.whitespace +) +LOWERCASE_INTS: List[int] = [ord(letter) for letter in string.ascii_lowercase] +VALID_INTS: Set[int] = {ord(char) for char in VALID_CHARS} + +COMMON_WORDS: List[str] = ["the", "be", "to", "of", "and", "in", "that", "have"] + + +def try_key(ciphertext: List[int], key: Tuple[int, ...]) -> Optional[str]: + """ + Given an encrypted message and a possible 3-character key, decrypt the message. + If the decrypted message contains a invalid character, i.e. not an ASCII letter, + a digit, punctuation or whitespace, then we know the key is incorrect, so return + None. + >>> try_key([0, 17, 20, 4, 27], (104, 116, 120)) + 'hello' + >>> try_key([68, 10, 300, 4, 27], (104, 116, 120)) is None + True + """ + decoded: str = "" + keychar: int + cipherchar: int + decodedchar: int + + for keychar, cipherchar in zip(cycle(key), ciphertext): + decodedchar = cipherchar ^ keychar + if decodedchar not in VALID_INTS: + return None + decoded += chr(decodedchar) + + return decoded + + +def filter_valid_chars(ciphertext: List[int]) -> List[str]: + """ + Given an encrypted message, test all 3-character strings to try and find the + key. Return a list of the possible decrypted messages. + >>> from itertools import cycle + >>> text = "The enemy's gate is down" + >>> key = "end" + >>> encoded = [ord(k) ^ ord(c) for k,c in zip(cycle(key), text)] + >>> text in filter_valid_chars(encoded) + True + """ + possibles: List[str] = [] + for key in product(LOWERCASE_INTS, repeat=3): + encoded = try_key(ciphertext, key) + if encoded is not None: + possibles.append(encoded) + return possibles + + +def filter_common_word(possibles: List[str], common_word: str) -> List[str]: + """ + Given a list of possible decoded messages, narrow down the possibilities + for checking for the presence of a specified common word. Only decoded messages + containing common_word will be returned. + >>> filter_common_word(['asfla adf', 'I am here', ' !?! #a'], 'am') + ['I am here'] + >>> filter_common_word(['athla amf', 'I am here', ' !?! #a'], 'am') + ['athla amf', 'I am here'] + """ + return [possible for possible in possibles if common_word in possible.lower()] + + +def solution(filename: str = "p059_cipher.txt") -> int: + """ + Test the ciphertext against all possible 3-character keys, then narrow down the + possibilities by filtering using common words until there's only one possible + decoded message. + >>> solution("test_cipher.txt") + 3000 + """ + ciphertext: List[int] + possibles: List[str] + common_word: str + decoded_text: str + data: str = Path(__file__).parent.joinpath(filename).read_text(encoding="utf-8") + + ciphertext = [int(number) for number in data.strip().split(",")] + + possibles = filter_valid_chars(ciphertext) + for common_word in COMMON_WORDS: + possibles = filter_common_word(possibles, common_word) + if len(possibles) == 1: + break + + decoded_text = possibles[0] + return sum([ord(char) for char in decoded_text]) + + +if __name__ == "__main__": + print(f"{solution() = }") diff --git a/project_euler/problem_059/test_cipher.txt b/project_euler/problem_059/test_cipher.txt new file mode 100644 index 000000000000..27c53740cc1a --- /dev/null +++ b/project_euler/problem_059/test_cipher.txt @@ -0,0 +1 @@ +63,13,28,75,0,23,14,8,0,76,22,89,12,4,13,14,69,16,24,69,29,4,18,23,69,69,59,14,69,11,14,4,29,18 From 53371b2381c2233c057c0ad75d377d8c03ff83c8 Mon Sep 17 00:00:00 2001 From: Du Yuanchao Date: Fri, 18 Dec 2020 17:39:51 +0800 Subject: [PATCH 1059/1071] Optimization for shell sort (#4038) * fixed shell sort * udpate code style * Update sorts/shell_sort.py Co-authored-by: John Law Co-authored-by: John Law --- sorts/shell_sort.py | 23 +++++++++-------------- 1 file changed, 9 insertions(+), 14 deletions(-) diff --git a/sorts/shell_sort.py b/sorts/shell_sort.py index bf3c2c7f9cc6..2e749e43d056 100644 --- a/sorts/shell_sort.py +++ b/sorts/shell_sort.py @@ -1,13 +1,5 @@ """ -This is a pure Python implementation of the shell sort algorithm - -For doctests run following command: -python -m doctest -v shell_sort.py -or -python3 -m doctest -v shell_sort.py - -For manual testing run: -python shell_sort.py +https://en.wikipedia.org/wiki/Shellsort#Pseudocode """ @@ -19,26 +11,29 @@ def shell_sort(collection): >>> shell_sort([0, 5, 3, 2, 2]) [0, 2, 2, 3, 5] - >>> shell_sort([]) [] - >>> shell_sort([-2, -5, -45]) [-45, -5, -2] """ # Marcin Ciura's gap sequence - gaps = [701, 301, 132, 57, 23, 10, 4, 1] + gaps = [701, 301, 132, 57, 23, 10, 4, 1] for gap in gaps: for i in range(gap, len(collection)): + insert_value = collection[i] j = i - while j >= gap and collection[j] < collection[j - gap]: - collection[j], collection[j - gap] = collection[j - gap], collection[j] + while j >= gap and collection[j - gap] > insert_value: + collection[j] = collection[j - gap] j -= gap + collection[j] = insert_value return collection if __name__ == "__main__": + from doctest import testmod + + testmod() user_input = input("Enter numbers separated by a comma:\n").strip() unsorted = [int(item) for item in user_input.split(",")] print(shell_sort(unsorted)) From 00f22a9970c6638ff59891d8cb271db52bab1bc4 Mon Sep 17 00:00:00 2001 From: sharmapulkit04 <39304055+sharmapulkit04@users.noreply.github.com> Date: Sat, 19 Dec 2020 11:46:15 +0530 Subject: [PATCH 1060/1071] Add solution for Project Euler problem 135 (#4035) --- project_euler/problem_135/__init__.py | 0 project_euler/problem_135/sol1.py | 61 +++++++++++++++++++++++++++ 2 files changed, 61 insertions(+) create mode 100644 project_euler/problem_135/__init__.py create mode 100644 project_euler/problem_135/sol1.py diff --git a/project_euler/problem_135/__init__.py b/project_euler/problem_135/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/project_euler/problem_135/sol1.py b/project_euler/problem_135/sol1.py new file mode 100644 index 000000000000..d71a0439c7e9 --- /dev/null +++ b/project_euler/problem_135/sol1.py @@ -0,0 +1,61 @@ +""" +Project Euler Problem 135: https://projecteuler.net/problem=135 + +Given the positive integers, x, y, and z, +are consecutive terms of an arithmetic progression, +the least value of the positive integer, n, +for which the equation, +x2 − y2 − z2 = n, has exactly two solutions is n = 27: + +342 − 272 − 202 = 122 − 92 − 62 = 27 + +It turns out that n = 1155 is the least value +which has exactly ten solutions. + +How many values of n less than one million +have exactly ten distinct solutions? + + +Taking x,y,z of the form a+d,a,a-d respectively, +the given equation reduces to a*(4d-a)=n. +Calculating no of solutions for every n till 1 million by fixing a +,and n must be multiple of a. +Total no of steps=n*(1/1+1/2+1/3+1/4..+1/n) +,so roughly O(nlogn) time complexity. + +""" + + +def solution(limit: int = 1000000) -> int: + """ + returns the values of n less than or equal to the limit + have exactly ten distinct solutions. + >>> solution(100) + 0 + >>> solution(10000) + 45 + >>> solution(50050) + 292 + """ + limit = limit + 1 + frequency = [0] * limit + for first_term in range(1, limit): + for n in range(first_term, limit, first_term): + common_difference = first_term + n / first_term + if common_difference % 4: # d must be divisble by 4 + continue + else: + common_difference /= 4 + if ( + first_term > common_difference + and first_term < 4 * common_difference + ): # since x,y,z are positive integers + frequency[n] += 1 # so z>0 and a>d ,also 4d Date: Mon, 21 Dec 2020 13:55:59 -0800 Subject: [PATCH 1061/1071] add integer to roman function (#4050) * add integer to roman function simply added fastest method i found. * Rename roman_to_integer.py to roman_numerals.py * Update roman_numerals.py * Update roman_numerals.py Co-authored-by: Christian Clauss --- ...{roman_to_integer.py => roman_numerals.py} | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) rename conversions/{roman_to_integer.py => roman_numerals.py} (50%) diff --git a/conversions/roman_to_integer.py b/conversions/roman_numerals.py similarity index 50% rename from conversions/roman_to_integer.py rename to conversions/roman_numerals.py index ce52b6fb7cbb..9933e6a78a4d 100644 --- a/conversions/roman_to_integer.py +++ b/conversions/roman_numerals.py @@ -21,6 +21,38 @@ def roman_to_int(roman: str) -> int: return total +def int_to_roman(number: int) -> str: + """ + Given a integer, convert it to an roman numeral. + https://en.wikipedia.org/wiki/Roman_numerals + >>> tests = {"III": 3, "CLIV": 154, "MIX": 1009, "MMD": 2500, "MMMCMXCIX": 3999} + >>> all(int_to_roman(value) == key for key, value in tests.items()) + True + """ + ROMAN = [ + (1000, "M"), + (900, "CM"), + (500, "D"), + (400, "CD"), + (100, "C"), + (90, "XC"), + (50, "L"), + (40, "XL"), + (10, "X"), + (9, "IX"), + (5, "V"), + (4, "IV"), + (1, "I"), + ] + result = [] + for (arabic, roman) in ROMAN: + (factor, number) = divmod(number, arabic) + result.append(roman * factor) + if number == 0: + break + return "".join(result) + + if __name__ == "__main__": import doctest From 2ff2ccbeecf24d3171841e362600944b547a4e51 Mon Sep 17 00:00:00 2001 From: fpringle Date: Tue, 22 Dec 2020 13:02:31 +0100 Subject: [PATCH 1062/1071] Add solution for Project Euler problem 101 (#4033) * Added solution for Project Euler problem 101 * Got rid of map functions * updating DIRECTORY.md * Better function/variable names * Better variable names * Type hints * Doctest for nested function Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> --- DIRECTORY.md | 2 + project_euler/problem_101/__init__.py | 0 project_euler/problem_101/sol1.py | 219 ++++++++++++++++++++++++++ 3 files changed, 221 insertions(+) create mode 100644 project_euler/problem_101/__init__.py create mode 100644 project_euler/problem_101/sol1.py diff --git a/DIRECTORY.md b/DIRECTORY.md index 929a986b0f3b..1f1bb9907e52 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -744,6 +744,8 @@ * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_097/sol1.py) * Problem 099 * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_099/sol1.py) + * Problem 101 + * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_101/sol1.py) * Problem 112 * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_112/sol1.py) * Problem 113 diff --git a/project_euler/problem_101/__init__.py b/project_euler/problem_101/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/project_euler/problem_101/sol1.py b/project_euler/problem_101/sol1.py new file mode 100644 index 000000000000..e66316090fb2 --- /dev/null +++ b/project_euler/problem_101/sol1.py @@ -0,0 +1,219 @@ +""" +If we are presented with the first k terms of a sequence it is impossible to say with +certainty the value of the next term, as there are infinitely many polynomial functions +that can model the sequence. + +As an example, let us consider the sequence of cube +numbers. This is defined by the generating function, +u(n) = n3: 1, 8, 27, 64, 125, 216, ... + +Suppose we were only given the first two terms of this sequence. Working on the +principle that "simple is best" we should assume a linear relationship and predict the +next term to be 15 (common difference 7). Even if we were presented with the first three +terms, by the same principle of simplicity, a quadratic relationship should be +assumed. + +We shall define OP(k, n) to be the nth term of the optimum polynomial +generating function for the first k terms of a sequence. It should be clear that +OP(k, n) will accurately generate the terms of the sequence for n ≤ k, and potentially +the first incorrect term (FIT) will be OP(k, k+1); in which case we shall call it a +bad OP (BOP). + +As a basis, if we were only given the first term of sequence, it would be most +sensible to assume constancy; that is, for n ≥ 2, OP(1, n) = u(1). + +Hence we obtain the +following OPs for the cubic sequence: + +OP(1, n) = 1 1, 1, 1, 1, ... +OP(2, n) = 7n-6 1, 8, 15, ... +OP(3, n) = 6n^2-11n+6 1, 8, 27, 58, ... +OP(4, n) = n^3 1, 8, 27, 64, 125, ... + +Clearly no BOPs exist for k ≥ 4. + +By considering the sum of FITs generated by the BOPs (indicated in red above), we +obtain 1 + 15 + 58 = 74. + +Consider the following tenth degree polynomial generating function: + +1 - n + n^2 - n^3 + n^4 - n^5 + n^6 - n^7 + n^8 - n^9 + n^10 + +Find the sum of FITs for the BOPs. +""" + + +from typing import Callable, List, Union + +Matrix = List[List[Union[float, int]]] + + +def solve(matrix: Matrix, vector: Matrix) -> Matrix: + """ + Solve the linear system of equations Ax = b (A = "matrix", b = "vector") + for x using Gaussian elimination and back substitution. We assume that A + is an invertible square matrix and that b is a column vector of the + same height. + >>> solve([[1, 0], [0, 1]], [[1],[2]]) + [[1.0], [2.0]] + >>> solve([[2, 1, -1],[-3, -1, 2],[-2, 1, 2]],[[8], [-11],[-3]]) + [[2.0], [3.0], [-1.0]] + """ + size: int = len(matrix) + augmented: Matrix = [[0 for _ in range(size + 1)] for _ in range(size)] + row: int + row2: int + col: int + col2: int + pivot_row: int + ratio: float + + for row in range(size): + for col in range(size): + augmented[row][col] = matrix[row][col] + + augmented[row][size] = vector[row][0] + + row = 0 + col = 0 + while row < size and col < size: + # pivoting + pivot_row = max( + [(abs(augmented[row2][col]), row2) for row2 in range(col, size)] + )[1] + if augmented[pivot_row][col] == 0: + col += 1 + continue + else: + augmented[row], augmented[pivot_row] = augmented[pivot_row], augmented[row] + + for row2 in range(row + 1, size): + ratio = augmented[row2][col] / augmented[row][col] + augmented[row2][col] = 0 + for col2 in range(col + 1, size + 1): + augmented[row2][col2] -= augmented[row][col2] * ratio + + row += 1 + col += 1 + + # back substitution + for col in range(1, size): + for row in range(col): + ratio = augmented[row][col] / augmented[col][col] + for col2 in range(col, size + 1): + augmented[row][col2] -= augmented[col][col2] * ratio + + # round to get rid of numbers like 2.000000000000004 + return [ + [round(augmented[row][size] / augmented[row][row], 10)] for row in range(size) + ] + + +def interpolate(y_list: List[int]) -> Callable[[int], int]: + """ + Given a list of data points (1,y0),(2,y1), ..., return a function that + interpolates the data points. We find the coefficients of the interpolating + polynomial by solving a system of linear equations corresponding to + x = 1, 2, 3... + + >>> interpolate([1])(3) + 1 + >>> interpolate([1, 8])(3) + 15 + >>> interpolate([1, 8, 27])(4) + 58 + >>> interpolate([1, 8, 27, 64])(6) + 216 + """ + + size: int = len(y_list) + matrix: Matrix = [[0 for _ in range(size)] for _ in range(size)] + vector: Matrix = [[0] for _ in range(size)] + coeffs: Matrix + x_val: int + y_val: int + col: int + + for x_val, y_val in enumerate(y_list): + for col in range(size): + matrix[x_val][col] = (x_val + 1) ** (size - col - 1) + vector[x_val][0] = y_val + + coeffs = solve(matrix, vector) + + def interpolated_func(var: int) -> int: + """ + >>> interpolate([1])(3) + 1 + >>> interpolate([1, 8])(3) + 15 + >>> interpolate([1, 8, 27])(4) + 58 + >>> interpolate([1, 8, 27, 64])(6) + 216 + """ + return sum( + round(coeffs[x_val][0]) * (var ** (size - x_val - 1)) + for x_val in range(size) + ) + + return interpolated_func + + +def question_function(variable: int) -> int: + """ + The generating function u as specified in the question. + >>> question_function(0) + 1 + >>> question_function(1) + 1 + >>> question_function(5) + 8138021 + >>> question_function(10) + 9090909091 + """ + return ( + 1 + - variable + + variable ** 2 + - variable ** 3 + + variable ** 4 + - variable ** 5 + + variable ** 6 + - variable ** 7 + + variable ** 8 + - variable ** 9 + + variable ** 10 + ) + + +def solution(func: Callable[[int], int] = question_function, order: int = 10) -> int: + """ + Find the sum of the FITs of the BOPS. For each interpolating polynomial of order + 1, 2, ... , 10, find the first x such that the value of the polynomial at x does + not equal u(x). + >>> solution(lambda n: n ** 3, 3) + 74 + """ + data_points: List[int] = [func(x_val) for x_val in range(1, order + 1)] + + polynomials: List[Callable[[int], int]] = [ + interpolate(data_points[:max_coeff]) for max_coeff in range(1, order + 1) + ] + + ret: int = 0 + poly: int + x_val: int + + for poly in polynomials: + x_val = 1 + while func(x_val) == poly(x_val): + x_val += 1 + + ret += poly(x_val) + + return ret + + +if __name__ == "__main__": + print(f"{solution() = }") From ad5108d6a49155bc0a5aca498426265004b0265f Mon Sep 17 00:00:00 2001 From: Dhruv Manilawala Date: Wed, 23 Dec 2020 15:22:43 +0530 Subject: [PATCH 1063/1071] Fix mypy errors for arithmetic analysis algorithms (#4053) --- arithmetic_analysis/in_static_equilibrium.py | 11 +-- arithmetic_analysis/lu_decomposition.py | 70 +++++++++++++------ .../newton_forward_interpolation.py | 7 +- arithmetic_analysis/newton_raphson.py | 5 +- arithmetic_analysis/secant_method.py | 57 +++++++-------- 5 files changed, 90 insertions(+), 60 deletions(-) diff --git a/arithmetic_analysis/in_static_equilibrium.py b/arithmetic_analysis/in_static_equilibrium.py index f08b39c3505c..9b2892151850 100644 --- a/arithmetic_analysis/in_static_equilibrium.py +++ b/arithmetic_analysis/in_static_equilibrium.py @@ -1,19 +1,14 @@ """ Checks if a system of forces is in static equilibrium. - -python/black : true -flake8 : passed -mypy : passed """ +from typing import List -from __future__ import annotations - -from numpy import array, cos, cross, radians, sin # type: ignore +from numpy import array, cos, cross, radians, sin def polar_force( magnitude: float, angle: float, radian_mode: bool = False -) -> list[float]: +) -> List[float]: """ Resolves force along rectangular components. (force, angle) => (force_x, force_y) diff --git a/arithmetic_analysis/lu_decomposition.py b/arithmetic_analysis/lu_decomposition.py index 763ba60f32b7..ef37d1b7b4ef 100644 --- a/arithmetic_analysis/lu_decomposition.py +++ b/arithmetic_analysis/lu_decomposition.py @@ -1,34 +1,64 @@ -"""Lower-Upper (LU) Decomposition.""" +"""Lower-Upper (LU) Decomposition. -# lower–upper (LU) decomposition - https://en.wikipedia.org/wiki/LU_decomposition -import numpy +Reference: +- https://en.wikipedia.org/wiki/LU_decomposition +""" +from typing import Tuple +import numpy as np +from numpy import ndarray -def LUDecompose(table): + +def lower_upper_decomposition(table: ndarray) -> Tuple[ndarray, ndarray]: + """Lower-Upper (LU) Decomposition + + Example: + + >>> matrix = np.array([[2, -2, 1], [0, 1, 2], [5, 3, 1]]) + >>> outcome = lower_upper_decomposition(matrix) + >>> outcome[0] + array([[1. , 0. , 0. ], + [0. , 1. , 0. ], + [2.5, 8. , 1. ]]) + >>> outcome[1] + array([[ 2. , -2. , 1. ], + [ 0. , 1. , 2. ], + [ 0. , 0. , -17.5]]) + + >>> matrix = np.array([[2, -2, 1], [0, 1, 2]]) + >>> lower_upper_decomposition(matrix) + Traceback (most recent call last): + ... + ValueError: 'table' has to be of square shaped array but got a 2x3 array: + [[ 2 -2 1] + [ 0 1 2]] + """ # Table that contains our data # Table has to be a square array so we need to check first - rows, columns = numpy.shape(table) - L = numpy.zeros((rows, columns)) - U = numpy.zeros((rows, columns)) + rows, columns = np.shape(table) if rows != columns: - return [] + raise ValueError( + f"'table' has to be of square shaped array but got a {rows}x{columns} " + + f"array:\n{table}" + ) + lower = np.zeros((rows, columns)) + upper = np.zeros((rows, columns)) for i in range(columns): for j in range(i): - sum = 0 + total = 0 for k in range(j): - sum += L[i][k] * U[k][j] - L[i][j] = (table[i][j] - sum) / U[j][j] - L[i][i] = 1 + total += lower[i][k] * upper[k][j] + lower[i][j] = (table[i][j] - total) / upper[j][j] + lower[i][i] = 1 for j in range(i, columns): - sum1 = 0 + total = 0 for k in range(i): - sum1 += L[i][k] * U[k][j] - U[i][j] = table[i][j] - sum1 - return L, U + total += lower[i][k] * upper[k][j] + upper[i][j] = table[i][j] - total + return lower, upper if __name__ == "__main__": - matrix = numpy.array([[2, -2, 1], [0, 1, 2], [5, 3, 1]]) - L, U = LUDecompose(matrix) - print(L) - print(U) + import doctest + + doctest.testmod() diff --git a/arithmetic_analysis/newton_forward_interpolation.py b/arithmetic_analysis/newton_forward_interpolation.py index d32e3efbd1f2..66cde4b73c4f 100644 --- a/arithmetic_analysis/newton_forward_interpolation.py +++ b/arithmetic_analysis/newton_forward_interpolation.py @@ -1,10 +1,11 @@ # https://www.geeksforgeeks.org/newton-forward-backward-interpolation/ import math +from typing import List # for calculating u value -def ucal(u, p): +def ucal(u: float, p: int) -> float: """ >>> ucal(1, 2) 0 @@ -19,9 +20,9 @@ def ucal(u, p): return temp -def main(): +def main() -> None: n = int(input("enter the numbers of values: ")) - y = [] + y: List[List[float]] = [] for i in range(n): y.append([]) for i in range(n): diff --git a/arithmetic_analysis/newton_raphson.py b/arithmetic_analysis/newton_raphson.py index 948759a09a2a..146bb0aa5adf 100644 --- a/arithmetic_analysis/newton_raphson.py +++ b/arithmetic_analysis/newton_raphson.py @@ -4,11 +4,14 @@ # quickly find a good approximation for the root of a real-valued function from decimal import Decimal from math import * # noqa: F401, F403 +from typing import Union from sympy import diff -def newton_raphson(func: str, a: int, precision: int = 10 ** -10) -> float: +def newton_raphson( + func: str, a: Union[float, Decimal], precision: float = 10 ** -10 +) -> float: """Finds root from the point 'a' onwards by Newton-Raphson method >>> newton_raphson("sin(x)", 2) 3.1415926536808043 diff --git a/arithmetic_analysis/secant_method.py b/arithmetic_analysis/secant_method.py index b05d44c627d8..7eb1dd8f5c6b 100644 --- a/arithmetic_analysis/secant_method.py +++ b/arithmetic_analysis/secant_method.py @@ -1,28 +1,29 @@ -# Implementing Secant method in Python -# Author: dimgrichr - - -from math import exp - - -def f(x): - """ - >>> f(5) - 39.98652410600183 - """ - return 8 * x - 2 * exp(-x) - - -def SecantMethod(lower_bound, upper_bound, repeats): - """ - >>> SecantMethod(1, 3, 2) - 0.2139409276214589 - """ - x0 = lower_bound - x1 = upper_bound - for i in range(0, repeats): - x0, x1 = x1, x1 - (f(x1) * (x1 - x0)) / (f(x1) - f(x0)) - return x1 - - -print(f"The solution is: {SecantMethod(1, 3, 2)}") +""" +Implementing Secant method in Python +Author: dimgrichr +""" +from math import exp + + +def f(x: float) -> float: + """ + >>> f(5) + 39.98652410600183 + """ + return 8 * x - 2 * exp(-x) + + +def secant_method(lower_bound: float, upper_bound: float, repeats: int) -> float: + """ + >>> secant_method(1, 3, 2) + 0.2139409276214589 + """ + x0 = lower_bound + x1 = upper_bound + for i in range(0, repeats): + x0, x1 = x1, x1 - (f(x1) * (x1 - x0)) / (f(x1) - f(x0)) + return x1 + + +if __name__ == "__main__": + print(f"Example: {secant_method(1, 3, 2) = }") From 0ccb213c1140d094e4c86bc04c767290b4ebaf15 Mon Sep 17 00:00:00 2001 From: fpringle Date: Wed, 23 Dec 2020 18:48:19 +0100 Subject: [PATCH 1064/1071] Add solution for Project Euler problem 102 (#4051) * Added solution for Project Euler problem 102 * Got rid of map functions * Snake case variable names * Type hints * updating DIRECTORY.md Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> --- DIRECTORY.md | 6 +- project_euler/problem_102/__init__.py | 0 project_euler/problem_102/p102_triangles.txt | 1000 ++++++++++++++++++ project_euler/problem_102/sol1.py | 81 ++ project_euler/problem_102/test_triangles.txt | 2 + 5 files changed, 1088 insertions(+), 1 deletion(-) create mode 100644 project_euler/problem_102/__init__.py create mode 100644 project_euler/problem_102/p102_triangles.txt create mode 100644 project_euler/problem_102/sol1.py create mode 100644 project_euler/problem_102/test_triangles.txt diff --git a/DIRECTORY.md b/DIRECTORY.md index 1f1bb9907e52..d73ae11eb7c2 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -106,7 +106,7 @@ * [Molecular Chemistry](https://github.com/TheAlgorithms/Python/blob/master/conversions/molecular_chemistry.py) * [Octal To Decimal](https://github.com/TheAlgorithms/Python/blob/master/conversions/octal_to_decimal.py) * [Prefix Conversions](https://github.com/TheAlgorithms/Python/blob/master/conversions/prefix_conversions.py) - * [Roman To Integer](https://github.com/TheAlgorithms/Python/blob/master/conversions/roman_to_integer.py) + * [Roman Numerals](https://github.com/TheAlgorithms/Python/blob/master/conversions/roman_numerals.py) * [Temperature Conversions](https://github.com/TheAlgorithms/Python/blob/master/conversions/temperature_conversions.py) * [Weight Conversion](https://github.com/TheAlgorithms/Python/blob/master/conversions/weight_conversion.py) @@ -746,6 +746,8 @@ * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_099/sol1.py) * Problem 101 * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_101/sol1.py) + * Problem 102 + * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_102/sol1.py) * Problem 112 * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_112/sol1.py) * Problem 113 @@ -760,6 +762,8 @@ * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_125/sol1.py) * Problem 129 * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_129/sol1.py) + * Problem 135 + * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_135/sol1.py) * Problem 173 * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_173/sol1.py) * Problem 174 diff --git a/project_euler/problem_102/__init__.py b/project_euler/problem_102/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/project_euler/problem_102/p102_triangles.txt b/project_euler/problem_102/p102_triangles.txt new file mode 100644 index 000000000000..3f01a1ac1f41 --- /dev/null +++ b/project_euler/problem_102/p102_triangles.txt @@ -0,0 +1,1000 @@ +-340,495,-153,-910,835,-947 +-175,41,-421,-714,574,-645 +-547,712,-352,579,951,-786 +419,-864,-83,650,-399,171 +-429,-89,-357,-930,296,-29 +-734,-702,823,-745,-684,-62 +-971,762,925,-776,-663,-157 +162,570,628,485,-807,-896 +641,91,-65,700,887,759 +215,-496,46,-931,422,-30 +-119,359,668,-609,-358,-494 +440,929,968,214,760,-857 +-700,785,838,29,-216,411 +-770,-458,-325,-53,-505,633 +-752,-805,349,776,-799,687 +323,5,561,-36,919,-560 +-907,358,264,320,204,274 +-728,-466,350,969,292,-345 +940,836,272,-533,748,185 +411,998,813,520,316,-949 +-152,326,658,-762,148,-651 +330,507,-9,-628,101,174 +551,-496,772,-541,-702,-45 +-164,-489,-90,322,631,-59 +673,366,-4,-143,-606,-704 +428,-609,801,-449,740,-269 +453,-924,-785,-346,-853,111 +-738,555,-181,467,-426,-20 +958,-692,784,-343,505,-569 +620,27,263,54,-439,-726 +804,87,998,859,871,-78 +-119,-453,-709,-292,-115,-56 +-626,138,-940,-476,-177,-274 +-11,160,142,588,446,158 +538,727,550,787,330,810 +420,-689,854,-546,337,516 +872,-998,-607,748,473,-192 +653,440,-516,-985,808,-857 +374,-158,331,-940,-338,-641 +137,-925,-179,771,734,-715 +-314,198,-115,29,-641,-39 +759,-574,-385,355,590,-603 +-189,-63,-168,204,289,305 +-182,-524,-715,-621,911,-255 +331,-816,-833,471,168,126 +-514,581,-855,-220,-731,-507 +129,169,576,651,-87,-458 +783,-444,-881,658,-266,298 +603,-430,-598,585,368,899 +43,-724,962,-376,851,409 +-610,-646,-883,-261,-482,-881 +-117,-237,978,641,101,-747 +579,125,-715,-712,208,534 +672,-214,-762,372,874,533 +-564,965,38,715,367,242 +500,951,-700,-981,-61,-178 +-382,-224,-959,903,-282,-60 +-355,295,426,-331,-591,655 +892,128,958,-271,-993,274 +-454,-619,302,138,-790,-874 +-642,601,-574,159,-290,-318 +266,-109,257,-686,54,975 +162,628,-478,840,264,-266 +466,-280,982,1,904,-810 +721,839,730,-807,777,981 +-129,-430,748,263,943,96 +434,-94,410,-990,249,-704 +237,42,122,-732,44,-51 +909,-116,-229,545,292,717 +824,-768,-807,-370,-262,30 +675,58,332,-890,-651,791 +363,825,-717,254,684,240 +405,-715,900,166,-589,422 +-476,686,-830,-319,634,-807 +633,837,-971,917,-764,207 +-116,-44,-193,-70,908,809 +-26,-252,998,408,70,-713 +-601,645,-462,842,-644,-591 +-160,653,274,113,-138,687 +369,-273,-181,925,-167,-693 +-338,135,480,-967,-13,-840 +-90,-270,-564,695,161,907 +607,-430,869,-713,461,-469 +919,-165,-776,522,606,-708 +-203,465,288,207,-339,-458 +-453,-534,-715,975,838,-677 +-973,310,-350,934,546,-805 +-835,385,708,-337,-594,-772 +-14,914,900,-495,-627,594 +833,-713,-213,578,-296,699 +-27,-748,484,455,915,291 +270,889,739,-57,442,-516 +119,811,-679,905,184,130 +-678,-469,925,553,612,482 +101,-571,-732,-842,644,588 +-71,-737,566,616,957,-663 +-634,-356,90,-207,936,622 +598,443,964,-895,-58,529 +847,-467,929,-742,91,10 +-633,829,-780,-408,222,-30 +-818,57,275,-38,-746,198 +-722,-825,-549,597,-391,99 +-570,908,430,873,-103,-360 +342,-681,512,434,542,-528 +297,850,479,609,543,-357 +9,784,212,548,56,859 +-152,560,-240,-969,-18,713 +140,-133,34,-635,250,-163 +-272,-22,-169,-662,989,-604 +471,-765,355,633,-742,-118 +-118,146,942,663,547,-376 +583,16,162,264,715,-33 +-230,-446,997,-838,561,555 +372,397,-729,-318,-276,649 +92,982,-970,-390,-922,922 +-981,713,-951,-337,-669,670 +-999,846,-831,-504,7,-128 +455,-954,-370,682,-510,45 +822,-960,-892,-385,-662,314 +-668,-686,-367,-246,530,-341 +-723,-720,-926,-836,-142,757 +-509,-134,384,-221,-873,-639 +-803,-52,-706,-669,373,-339 +933,578,631,-616,770,555 +741,-564,-33,-605,-576,275 +-715,445,-233,-730,734,-704 +120,-10,-266,-685,-490,-17 +-232,-326,-457,-946,-457,-116 +811,52,639,826,-200,147 +-329,279,293,612,943,955 +-721,-894,-393,-969,-642,453 +-688,-826,-352,-75,371,79 +-809,-979,407,497,858,-248 +-485,-232,-242,-582,-81,849 +141,-106,123,-152,806,-596 +-428,57,-992,811,-192,478 +864,393,122,858,255,-876 +-284,-780,240,457,354,-107 +956,605,-477,44,26,-678 +86,710,-533,-815,439,327 +-906,-626,-834,763,426,-48 +201,-150,-904,652,475,412 +-247,149,81,-199,-531,-148 +923,-76,-353,175,-121,-223 +427,-674,453,472,-410,585 +931,776,-33,85,-962,-865 +-655,-908,-902,208,869,792 +-316,-102,-45,-436,-222,885 +-309,768,-574,653,745,-975 +896,27,-226,993,332,198 +323,655,-89,260,240,-902 +501,-763,-424,793,813,616 +993,375,-938,-621,672,-70 +-880,-466,-283,770,-824,143 +63,-283,886,-142,879,-116 +-964,-50,-521,-42,-306,-161 +724,-22,866,-871,933,-383 +-344,135,282,966,-80,917 +-281,-189,420,810,362,-582 +-515,455,-588,814,162,332 +555,-436,-123,-210,869,-943 +589,577,232,286,-554,876 +-773,127,-58,-171,-452,125 +-428,575,906,-232,-10,-224 +437,276,-335,-348,605,878 +-964,511,-386,-407,168,-220 +307,513,912,-463,-423,-416 +-445,539,273,886,-18,760 +-396,-585,-670,414,47,364 +143,-506,754,906,-971,-203 +-544,472,-180,-541,869,-465 +-779,-15,-396,890,972,-220 +-430,-564,503,182,-119,456 +89,-10,-739,399,506,499 +954,162,-810,-973,127,870 +890,952,-225,158,828,237 +-868,952,349,465,574,750 +-915,369,-975,-596,-395,-134 +-135,-601,575,582,-667,640 +413,890,-560,-276,-555,-562 +-633,-269,561,-820,-624,499 +371,-92,-784,-593,864,-717 +-971,655,-439,367,754,-951 +172,-347,36,279,-247,-402 +633,-301,364,-349,-683,-387 +-780,-211,-713,-948,-648,543 +72,58,762,-465,-66,462 +78,502,781,-832,713,836 +-431,-64,-484,-392,208,-343 +-64,101,-29,-860,-329,844 +398,391,828,-858,700,395 +578,-896,-326,-604,314,180 +97,-321,-695,185,-357,852 +854,839,283,-375,951,-209 +194,96,-564,-847,162,524 +-354,532,494,621,580,560 +419,-678,-450,926,-5,-924 +-661,905,519,621,-143,394 +-573,268,296,-562,-291,-319 +-211,266,-196,158,564,-183 +18,-585,-398,777,-581,864 +790,-894,-745,-604,-418,70 +848,-339,150,773,11,851 +-954,-809,-53,-20,-648,-304 +658,-336,-658,-905,853,407 +-365,-844,350,-625,852,-358 +986,-315,-230,-159,21,180 +-15,599,45,-286,-941,847 +-613,-68,184,639,-987,550 +334,675,-56,-861,923,340 +-848,-596,960,231,-28,-34 +707,-811,-994,-356,-167,-171 +-470,-764,72,576,-600,-204 +379,189,-542,-576,585,800 +440,540,-445,-563,379,-334 +-155,64,514,-288,853,106 +-304,751,481,-520,-708,-694 +-709,132,594,126,-844,63 +723,471,421,-138,-962,892 +-440,-263,39,513,-672,-954 +775,809,-581,330,752,-107 +-376,-158,335,-708,-514,578 +-343,-769,456,-187,25,413 +548,-877,-172,300,-500,928 +938,-102,423,-488,-378,-969 +-36,564,-55,131,958,-800 +-322,511,-413,503,700,-847 +-966,547,-88,-17,-359,-67 +637,-341,-437,-181,527,-153 +-74,449,-28,3,485,189 +-997,658,-224,-948,702,-807 +-224,736,-896,127,-945,-850 +-395,-106,439,-553,-128,124 +-841,-445,-758,-572,-489,212 +633,-327,13,-512,952,771 +-940,-171,-6,-46,-923,-425 +-142,-442,-817,-998,843,-695 +340,847,-137,-920,-988,-658 +-653,217,-679,-257,651,-719 +-294,365,-41,342,74,-892 +690,-236,-541,494,408,-516 +180,-807,225,790,494,59 +707,605,-246,656,284,271 +65,294,152,824,442,-442 +-321,781,-540,341,316,415 +420,371,-2,545,995,248 +56,-191,-604,971,615,449 +-981,-31,510,592,-390,-362 +-317,-968,913,365,97,508 +832,63,-864,-510,86,202 +-483,456,-636,340,-310,676 +981,-847,751,-508,-962,-31 +-157,99,73,797,63,-172 +220,858,872,924,866,-381 +996,-169,805,321,-164,971 +896,11,-625,-973,-782,76 +578,-280,730,-729,307,-905 +-580,-749,719,-698,967,603 +-821,874,-103,-623,662,-491 +-763,117,661,-644,672,-607 +592,787,-798,-169,-298,690 +296,644,-526,-762,-447,665 +534,-818,852,-120,57,-379 +-986,-549,-329,294,954,258 +-133,352,-660,-77,904,-356 +748,343,215,500,317,-277 +311,7,910,-896,-809,795 +763,-602,-753,313,-352,917 +668,619,-474,-597,-650,650 +-297,563,-701,-987,486,-902 +-461,-740,-657,233,-482,-328 +-446,-250,-986,-458,-629,520 +542,-49,-327,-469,257,-947 +121,-575,-634,-143,-184,521 +30,504,455,-645,-229,-945 +-12,-295,377,764,771,125 +-686,-133,225,-25,-376,-143 +-6,-46,338,270,-405,-872 +-623,-37,582,467,963,898 +-804,869,-477,420,-475,-303 +94,41,-842,-193,-768,720 +-656,-918,415,645,-357,460 +-47,-486,-911,468,-608,-686 +-158,251,419,-394,-655,-895 +272,-695,979,508,-358,959 +-776,650,-918,-467,-690,-534 +-85,-309,-626,167,-366,-429 +-880,-732,-186,-924,970,-875 +517,645,-274,962,-804,544 +721,402,104,640,478,-499 +198,684,-134,-723,-452,-905 +-245,745,239,238,-826,441 +-217,206,-32,462,-981,-895 +-51,989,526,-173,560,-676 +-480,-659,-976,-580,-727,466 +-996,-90,-995,158,-239,642 +302,288,-194,-294,17,924 +-943,969,-326,114,-500,103 +-619,163,339,-880,230,421 +-344,-601,-795,557,565,-779 +590,345,-129,-202,-125,-58 +-777,-195,159,674,775,411 +-939,312,-665,810,121,855 +-971,254,712,815,452,581 +442,-9,327,-750,61,757 +-342,869,869,-160,390,-772 +620,601,565,-169,-69,-183 +-25,924,-817,964,321,-970 +-64,-6,-133,978,825,-379 +601,436,-24,98,-115,940 +-97,502,614,-574,922,513 +-125,262,-946,695,99,-220 +429,-721,719,-694,197,-558 +326,689,-70,-908,-673,338 +-468,-856,-902,-254,-358,305 +-358,530,542,355,-253,-47 +-438,-74,-362,963,988,788 +137,717,467,622,319,-380 +-86,310,-336,851,918,-288 +721,395,646,-53,255,-425 +255,175,912,84,-209,878 +-632,-485,-400,-357,991,-608 +235,-559,992,-297,857,-591 +87,-71,148,130,647,578 +-290,-584,-639,-788,-21,592 +386,984,625,-731,-993,-336 +-538,634,-209,-828,-150,-774 +-754,-387,607,-781,976,-199 +412,-798,-664,295,709,-537 +-412,932,-880,-232,561,852 +-656,-358,-198,-964,-433,-848 +-762,-668,-632,186,-673,-11 +-876,237,-282,-312,-83,682 +403,73,-57,-436,-622,781 +-587,873,798,976,-39,329 +-369,-622,553,-341,817,794 +-108,-616,920,-849,-679,96 +290,-974,234,239,-284,-321 +-22,394,-417,-419,264,58 +-473,-551,69,923,591,-228 +-956,662,-113,851,-581,-794 +-258,-681,413,-471,-637,-817 +-866,926,992,-653,-7,794 +556,-350,602,917,831,-610 +188,245,-906,361,492,174 +-720,384,-818,329,638,-666 +-246,846,890,-325,-59,-850 +-118,-509,620,-762,-256,15 +-787,-536,-452,-338,-399,813 +458,560,525,-311,-608,-419 +494,-811,-825,-127,-812,894 +-801,890,-629,-860,574,925 +-709,-193,-213,138,-410,-403 +861,91,708,-187,5,-222 +789,646,777,154,90,-49 +-267,-830,-114,531,591,-698 +-126,-82,881,-418,82,652 +-894,130,-726,-935,393,-815 +-142,563,654,638,-712,-597 +-759,60,-23,977,100,-765 +-305,595,-570,-809,482,762 +-161,-267,53,963,998,-529 +-300,-57,798,353,703,486 +-990,696,-764,699,-565,719 +-232,-205,566,571,977,369 +740,865,151,-817,-204,-293 +94,445,-768,229,537,-406 +861,620,37,-424,-36,656 +390,-369,952,733,-464,569 +-482,-604,959,554,-705,-626 +-396,-615,-991,108,272,-723 +143,780,535,142,-917,-147 +138,-629,-217,-908,905,115 +915,103,-852,64,-468,-642 +570,734,-785,-268,-326,-759 +738,531,-332,586,-779,24 +870,440,-217,473,-383,415 +-296,-333,-330,-142,-924,950 +118,120,-35,-245,-211,-652 +61,634,153,-243,838,789 +726,-582,210,105,983,537 +-313,-323,758,234,29,848 +-847,-172,-593,733,-56,617 +54,255,-512,156,-575,675 +-873,-956,-148,623,95,200 +700,-370,926,649,-978,157 +-639,-202,719,130,747,222 +194,-33,955,943,505,114 +-226,-790,28,-930,827,783 +-392,-74,-28,714,218,-612 +209,626,-888,-683,-912,495 +487,751,614,933,631,445 +-348,-34,-411,-106,835,321 +-689,872,-29,-800,312,-542 +-52,566,827,570,-862,-77 +471,992,309,-402,389,912 +24,520,-83,-51,555,503 +-265,-317,283,-970,-472,690 +606,526,137,71,-651,150 +217,-518,663,66,-605,-331 +-562,232,-76,-503,205,-323 +842,-521,546,285,625,-186 +997,-927,344,909,-546,974 +-677,419,81,121,-705,771 +719,-379,-944,-797,784,-155 +-378,286,-317,-797,-111,964 +-288,-573,784,80,-532,-646 +-77,407,-248,-797,769,-816 +-24,-637,287,-858,-927,-333 +-902,37,894,-823,141,684 +125,467,-177,-516,686,399 +-321,-542,641,-590,527,-224 +-400,-712,-876,-208,632,-543 +-676,-429,664,-242,-269,922 +-608,-273,-141,930,687,380 +786,-12,498,494,310,326 +-739,-617,606,-960,804,188 +384,-368,-243,-350,-459,31 +-550,397,320,-868,328,-279 +969,-179,853,864,-110,514 +910,793,302,-822,-285,488 +-605,-128,218,-283,-17,-227 +16,324,667,708,750,3 +485,-813,19,585,71,930 +-218,816,-687,-97,-732,-360 +-497,-151,376,-23,3,315 +-412,-989,-610,-813,372,964 +-878,-280,87,381,-311,69 +-609,-90,-731,-679,150,585 +889,27,-162,605,75,-770 +448,617,-988,0,-103,-504 +-800,-537,-69,627,608,-668 +534,686,-664,942,830,920 +-238,775,495,932,-793,497 +-343,958,-914,-514,-691,651 +568,-136,208,359,728,28 +286,912,-794,683,556,-102 +-638,-629,-484,445,-64,-497 +58,505,-801,-110,872,632 +-390,777,353,267,976,369 +-993,515,105,-133,358,-572 +964,996,355,-212,-667,38 +-725,-614,-35,365,132,-196 +237,-536,-416,-302,312,477 +-664,574,-210,224,48,-925 +869,-261,-256,-240,-3,-698 +712,385,32,-34,916,-315 +895,-409,-100,-346,728,-624 +-806,327,-450,889,-781,-939 +-586,-403,698,318,-939,899 +557,-57,-920,659,333,-51 +-441,232,-918,-205,246,1 +783,167,-797,-595,245,-736 +-36,-531,-486,-426,-813,-160 +777,-843,817,313,-228,-572 +735,866,-309,-564,-81,190 +-413,645,101,719,-719,218 +-83,164,767,796,-430,-459 +122,779,-15,-295,-96,-892 +462,379,70,548,834,-312 +-630,-534,124,187,-737,114 +-299,-604,318,-591,936,826 +-879,218,-642,-483,-318,-866 +-691,62,-658,761,-895,-854 +-822,493,687,569,910,-202 +-223,784,304,-5,541,925 +-914,541,737,-662,-662,-195 +-622,615,414,358,881,-878 +339,745,-268,-968,-280,-227 +-364,855,148,-709,-827,472 +-890,-532,-41,664,-612,577 +-702,-859,971,-722,-660,-920 +-539,-605,737,149,973,-802 +800,42,-448,-811,152,511 +-933,377,-110,-105,-374,-937 +-766,152,482,120,-308,390 +-568,775,-292,899,732,890 +-177,-317,-502,-259,328,-511 +612,-696,-574,-660,132,31 +-119,563,-805,-864,179,-672 +425,-627,183,-331,839,318 +-711,-976,-749,152,-916,261 +181,-63,497,211,262,406 +-537,700,-859,-765,-928,77 +892,832,231,-749,-82,613 +816,216,-642,-216,-669,-912 +-6,624,-937,-370,-344,268 +737,-710,-869,983,-324,-274 +565,952,-547,-158,374,-444 +51,-683,645,-845,515,636 +-953,-631,114,-377,-764,-144 +-8,470,-242,-399,-675,-730 +-540,689,-20,47,-607,590 +-329,-710,-779,942,-388,979 +123,829,674,122,203,563 +46,782,396,-33,386,610 +872,-846,-523,-122,-55,-190 +388,-994,-525,974,127,596 +781,-680,796,-34,-959,-62 +-749,173,200,-384,-745,-446 +379,618,136,-250,-224,970 +-58,240,-921,-760,-901,-626 +366,-185,565,-100,515,688 +489,999,-893,-263,-637,816 +838,-496,-316,-513,419,479 +107,676,-15,882,98,-397 +-999,941,-903,-424,670,-325 +171,-979,835,178,169,-984 +-609,-607,378,-681,184,402 +-316,903,-575,-800,224,983 +591,-18,-460,551,-167,918 +-756,405,-117,441,163,-320 +456,24,6,881,-836,-539 +-489,-585,915,651,-892,-382 +-177,-122,73,-711,-386,591 +181,724,530,686,-131,241 +737,288,886,216,233,33 +-548,-386,-749,-153,-85,-982 +-835,227,904,160,-99,25 +-9,-42,-162,728,840,-963 +217,-763,870,771,47,-846 +-595,808,-491,556,337,-900 +-134,281,-724,441,-134,708 +-789,-508,651,-962,661,315 +-839,-923,339,402,41,-487 +300,-790,48,703,-398,-811 +955,-51,462,-685,960,-717 +910,-880,592,-255,-51,-776 +-885,169,-793,368,-565,458 +-905,940,-492,-630,-535,-988 +245,797,763,869,-82,550 +-310,38,-933,-367,-650,824 +-95,32,-83,337,226,990 +-218,-975,-191,-208,-785,-293 +-672,-953,517,-901,-247,465 +681,-148,261,-857,544,-923 +640,341,446,-618,195,769 +384,398,-846,365,671,815 +578,576,-911,907,762,-859 +548,-428,144,-630,-759,-146 +710,-73,-700,983,-97,-889 +-46,898,-973,-362,-817,-717 +151,-81,-125,-900,-478,-154 +483,615,-537,-932,181,-68 +786,-223,518,25,-306,-12 +-422,268,-809,-683,635,468 +983,-734,-694,-608,-110,4 +-786,-196,749,-354,137,-8 +-181,36,668,-200,691,-973 +-629,-838,692,-736,437,-871 +-208,-536,-159,-596,8,197 +-3,370,-686,170,913,-376 +44,-998,-149,-993,-200,512 +-519,136,859,497,536,434 +77,-985,972,-340,-705,-837 +-381,947,250,360,344,322 +-26,131,699,750,707,384 +-914,655,299,193,406,955 +-883,-921,220,595,-546,794 +-599,577,-569,-404,-704,489 +-594,-963,-624,-460,880,-760 +-603,88,-99,681,55,-328 +976,472,139,-453,-531,-860 +192,-290,513,-89,666,432 +417,487,575,293,567,-668 +655,711,-162,449,-980,972 +-505,664,-685,-239,603,-592 +-625,-802,-67,996,384,-636 +365,-593,522,-666,-200,-431 +-868,708,560,-860,-630,-355 +-702,785,-637,-611,-597,960 +-137,-696,-93,-803,408,406 +891,-123,-26,-609,-610,518 +133,-832,-198,555,708,-110 +791,617,-69,487,696,315 +-900,694,-565,517,-269,-416 +914,135,-781,600,-71,-600 +991,-915,-422,-351,-837,313 +-840,-398,-302,21,590,146 +62,-558,-702,-384,-625,831 +-363,-426,-924,-496,792,-908 +73,361,-817,-466,400,922 +-626,-164,-626,860,-524,286 +255,26,-944,809,-606,986 +-457,-256,-103,50,-867,-871 +-223,803,196,480,612,136 +-820,-928,700,780,-977,721 +717,332,53,-933,-128,793 +-602,-648,562,593,890,702 +-469,-875,-527,911,-475,-222 +110,-281,-552,-536,-816,596 +-981,654,413,-981,-75,-95 +-754,-742,-515,894,-220,-344 +795,-52,156,408,-603,76 +474,-157,423,-499,-807,-791 +260,688,40,-52,702,-122 +-584,-517,-390,-881,302,-504 +61,797,665,708,14,668 +366,166,458,-614,564,-983 +72,539,-378,796,381,-824 +-485,201,-588,842,736,379 +-149,-894,-298,705,-303,-406 +660,-935,-580,521,93,633 +-382,-282,-375,-841,-828,171 +-567,743,-100,43,144,122 +-281,-786,-749,-551,296,304 +11,-426,-792,212,857,-175 +594,143,-699,289,315,137 +341,596,-390,107,-631,-804 +-751,-636,-424,-854,193,651 +-145,384,749,675,-786,517 +224,-865,-323,96,-916,258 +-309,403,-388,826,35,-270 +-942,709,222,158,-699,-103 +-589,842,-997,29,-195,-210 +264,426,566,145,-217,623 +217,965,507,-601,-453,507 +-206,307,-982,4,64,-292 +676,-49,-38,-701,550,883 +5,-850,-438,659,745,-773 +933,238,-574,-570,91,-33 +-866,121,-928,358,459,-843 +-568,-631,-352,-580,-349,189 +-737,849,-963,-486,-662,970 +135,334,-967,-71,-365,-792 +789,21,-227,51,990,-275 +240,412,-886,230,591,256 +-609,472,-853,-754,959,661 +401,521,521,314,929,982 +-499,784,-208,71,-302,296 +-557,-948,-553,-526,-864,793 +270,-626,828,44,37,14 +-412,224,617,-593,502,699 +41,-908,81,562,-849,163 +165,917,761,-197,331,-341 +-687,314,799,755,-969,648 +-164,25,578,439,-334,-576 +213,535,874,-177,-551,24 +-689,291,-795,-225,-496,-125 +465,461,558,-118,-568,-909 +567,660,-810,46,-485,878 +-147,606,685,-690,-774,984 +568,-886,-43,854,-738,616 +-800,386,-614,585,764,-226 +-518,23,-225,-732,-79,440 +-173,-291,-689,636,642,-447 +-598,-16,227,410,496,211 +-474,-930,-656,-321,-420,36 +-435,165,-819,555,540,144 +-969,149,828,568,394,648 +65,-848,257,720,-625,-851 +981,899,275,635,465,-877 +80,290,792,760,-191,-321 +-605,-858,594,33,706,593 +585,-472,318,-35,354,-927 +-365,664,803,581,-965,-814 +-427,-238,-480,146,-55,-606 +879,-193,250,-890,336,117 +-226,-322,-286,-765,-836,-218 +-913,564,-667,-698,937,283 +872,-901,810,-623,-52,-709 +473,171,717,38,-429,-644 +225,824,-219,-475,-180,234 +-530,-797,-948,238,851,-623 +85,975,-363,529,598,28 +-799,166,-804,210,-769,851 +-687,-158,885,736,-381,-461 +447,592,928,-514,-515,-661 +-399,-777,-493,80,-544,-78 +-884,631,171,-825,-333,551 +191,268,-577,676,137,-33 +212,-853,709,798,583,-56 +-908,-172,-540,-84,-135,-56 +303,311,406,-360,-240,811 +798,-708,824,59,234,-57 +491,693,-74,585,-85,877 +509,-65,-936,329,-51,722 +-122,858,-52,467,-77,-609 +850,760,547,-495,-953,-952 +-460,-541,890,910,286,724 +-914,843,-579,-983,-387,-460 +989,-171,-877,-326,-899,458 +846,175,-915,540,-1000,-982 +-852,-920,-306,496,530,-18 +338,-991,160,85,-455,-661 +-186,-311,-460,-563,-231,-414 +-932,-302,959,597,793,748 +-366,-402,-788,-279,514,53 +-940,-956,447,-956,211,-285 +564,806,-911,-914,934,754 +575,-858,-277,15,409,-714 +848,462,100,-381,135,242 +330,718,-24,-190,860,-78 +479,458,941,108,-866,-653 +212,980,962,-962,115,841 +-827,-474,-206,881,323,765 +506,-45,-30,-293,524,-133 +832,-173,547,-852,-561,-842 +-397,-661,-708,819,-545,-228 +521,51,-489,852,36,-258 +227,-164,189,465,-987,-882 +-73,-997,641,-995,449,-615 +151,-995,-638,415,257,-400 +-663,-297,-748,537,-734,198 +-585,-401,-81,-782,-80,-105 +99,-21,238,-365,-704,-368 +45,416,849,-211,-371,-1 +-404,-443,795,-406,36,-933 +272,-363,981,-491,-380,77 +713,-342,-366,-849,643,911 +-748,671,-537,813,961,-200 +-194,-909,703,-662,-601,188 +281,500,724,286,267,197 +-832,847,-595,820,-316,637 +520,521,-54,261,923,-10 +4,-808,-682,-258,441,-695 +-793,-107,-969,905,798,446 +-108,-739,-590,69,-855,-365 +380,-623,-930,817,468,713 +759,-849,-236,433,-723,-931 +95,-320,-686,124,-69,-329 +-655,518,-210,-523,284,-866 +144,303,639,70,-171,269 +173,-333,947,-304,55,40 +274,878,-482,-888,-835,375 +-982,-854,-36,-218,-114,-230 +905,-979,488,-485,-479,114 +877,-157,553,-530,-47,-321 +350,664,-881,442,-220,-284 +434,-423,-365,878,-726,584 +535,909,-517,-447,-660,-141 +-966,191,50,353,182,-642 +-785,-634,123,-907,-162,511 +146,-850,-214,814,-704,25 +692,1,521,492,-637,274 +-662,-372,-313,597,983,-647 +-962,-526,68,-549,-819,231 +740,-890,-318,797,-666,948 +-190,-12,-468,-455,948,284 +16,478,-506,-888,628,-154 +272,630,-976,308,433,3 +-169,-391,-132,189,302,-388 +109,-784,474,-167,-265,-31 +-177,-532,283,464,421,-73 +650,635,592,-138,1,-387 +-932,703,-827,-492,-355,686 +586,-311,340,-618,645,-434 +-951,736,647,-127,-303,590 +188,444,903,718,-931,500 +-872,-642,-296,-571,337,241 +23,65,152,125,880,470 +512,823,-42,217,823,-263 +180,-831,-380,886,607,762 +722,443,-149,-216,-115,759 +-19,660,-36,901,923,231 +562,-322,-626,-968,194,-825 +204,-920,938,784,362,150 +-410,-266,-715,559,-672,124 +-198,446,-140,454,-461,-447 +83,-346,830,-493,-759,-382 +-881,601,581,234,-134,-925 +-494,914,-42,899,235,629 +-390,50,956,437,774,-700 +-514,514,44,-512,-576,-313 +63,-688,808,-534,-570,-399 +-726,572,-896,102,-294,-28 +-688,757,401,406,955,-511 +-283,423,-485,480,-767,908 +-541,952,-594,116,-854,451 +-273,-796,236,625,-626,257 +-407,-493,373,826,-309,297 +-750,955,-476,641,-809,713 +8,415,695,226,-111,2 +733,209,152,-920,401,995 +921,-103,-919,66,871,-947 +-907,89,-869,-214,851,-559 +-307,748,524,-755,314,-711 +188,897,-72,-763,482,103 +545,-821,-232,-596,-334,-754 +-217,-788,-820,388,-200,-662 +779,160,-723,-975,-142,-998 +-978,-519,-78,-981,842,904 +-504,-736,-295,21,-472,-482 +391,115,-705,574,652,-446 +813,-988,865,830,-263,487 +194,80,774,-493,-761,-872 +-415,-284,-803,7,-810,670 +-484,-4,881,-872,55,-852 +-379,822,-266,324,-48,748 +-304,-278,406,-60,959,-89 +404,756,577,-643,-332,658 +291,460,125,491,-312,83 +311,-734,-141,582,282,-557 +-450,-661,-981,710,-177,794 +328,264,-787,971,-743,-407 +-622,518,993,-241,-738,229 +273,-826,-254,-917,-710,-111 +809,770,96,368,-818,725 +-488,773,502,-342,534,745 +-28,-414,236,-315,-484,363 +179,-466,-566,713,-683,56 +560,-240,-597,619,916,-940 +893,473,872,-868,-642,-461 +799,489,383,-321,-776,-833 +980,490,-508,764,-512,-426 +917,961,-16,-675,440,559 +-812,212,784,-987,-132,554 +-886,454,747,806,190,231 +910,341,21,-66,708,725 +29,929,-831,-494,-303,389 +-103,492,-271,-174,-515,529 +-292,119,419,788,247,-951 +483,543,-347,-673,664,-549 +-926,-871,-437,337,162,-877 +299,472,-771,5,-88,-643 +-103,525,-725,-998,264,22 +-505,708,550,-545,823,347 +-738,931,59,147,-156,-259 +456,968,-162,889,132,-911 +535,120,968,-517,-864,-541 +24,-395,-593,-766,-565,-332 +834,611,825,-576,280,629 +211,-548,140,-278,-592,929 +-999,-240,-63,-78,793,573 +-573,160,450,987,529,322 +63,353,315,-187,-461,577 +189,-950,-247,656,289,241 +209,-297,397,664,-805,484 +-655,452,435,-556,917,874 +253,-756,262,-888,-778,-214 +793,-451,323,-251,-401,-458 +-396,619,-651,-287,-668,-781 +698,720,-349,742,-807,546 +738,280,680,279,-540,858 +-789,387,530,-36,-551,-491 +162,579,-427,-272,228,710 +689,356,917,-580,729,217 +-115,-638,866,424,-82,-194 +411,-338,-917,172,227,-29 +-612,63,630,-976,-64,-204 +-200,911,583,-571,682,-579 +91,298,396,-183,788,-955 +141,-873,-277,149,-396,916 +321,958,-136,573,541,-777 +797,-909,-469,-877,988,-653 +784,-198,129,883,-203,399 +-68,-810,223,-423,-467,-512 +531,-445,-603,-997,-841,641 +-274,-242,174,261,-636,-158 +-574,494,-796,-798,-798,99 +95,-82,-613,-954,-753,986 +-883,-448,-864,-401,938,-392 +913,930,-542,-988,310,410 +506,-99,43,512,790,-222 +724,31,49,-950,260,-134 +-287,-947,-234,-700,56,588 +-33,782,-144,948,105,-791 +548,-546,-652,-293,881,-520 +691,-91,76,991,-631,742 +-520,-429,-244,-296,724,-48 +778,646,377,50,-188,56 +-895,-507,-898,-165,-674,652 +654,584,-634,177,-349,-620 +114,-980,355,62,182,975 +516,9,-442,-298,274,-579 +-238,262,-431,-896,506,-850 +47,748,846,821,-537,-293 +839,726,593,285,-297,840 +634,-486,468,-304,-887,-567 +-864,914,296,-124,335,233 +88,-253,-523,-956,-554,803 +-587,417,281,-62,-409,-363 +-136,-39,-292,-768,-264,876 +-127,506,-891,-331,-744,-430 +778,584,-750,-129,-479,-94 +-876,-771,-987,-757,180,-641 +-777,-694,411,-87,329,190 +-347,-999,-882,158,-754,232 +-105,918,188,237,-110,-591 +-209,703,-838,77,838,909 +-995,-339,-762,750,860,472 +185,271,-289,173,811,-300 +2,65,-656,-22,36,-139 +765,-210,883,974,961,-905 +-212,295,-615,-840,77,474 +211,-910,-440,703,-11,859 +-559,-4,-196,841,-277,969 +-73,-159,-887,126,978,-371 +-569,633,-423,-33,512,-393 +503,143,-383,-109,-649,-998 +-663,339,-317,-523,-2,596 +690,-380,570,378,-652,132 +72,-744,-930,399,-525,935 +865,-983,115,37,995,826 +594,-621,-872,443,188,-241 +-1000,291,754,234,-435,-869 +-868,901,654,-907,59,181 +-868,-793,-431,596,-446,-564 +900,-944,-680,-796,902,-366 +331,430,943,853,-851,-942 +315,-538,-354,-909,139,721 +170,-884,-225,-818,-808,-657 +-279,-34,-533,-871,-972,552 +691,-986,-800,-950,654,-747 +603,988,899,841,-630,591 +876,-949,809,562,602,-536 +-693,363,-189,495,738,-1000 +-383,431,-633,297,665,959 +-740,686,-207,-803,188,-520 +-820,226,31,-339,10,121 +-312,-844,624,-516,483,621 +-822,-529,69,-278,800,328 +834,-82,-759,420,811,-264 +-960,-240,-921,561,173,46 +-324,909,-790,-814,-2,-785 +976,334,-290,-891,704,-581 +150,-798,689,-823,237,-639 +-551,-320,876,-502,-622,-628 +-136,845,904,595,-702,-261 +-857,-377,-522,-101,-943,-805 +-682,-787,-888,-459,-752,-985 +-571,-81,623,-133,447,643 +-375,-158,72,-387,-324,-696 +-660,-650,340,188,569,526 +727,-218,16,-7,-595,-988 +-966,-684,802,-783,-272,-194 +115,-566,-888,47,712,180 +-237,-69,45,-272,981,-812 +48,897,439,417,50,325 +348,616,180,254,104,-784 +-730,811,-548,612,-736,790 +138,-810,123,930,65,865 +-768,-299,-49,-895,-692,-418 +487,-531,802,-159,-12,634 +808,-179,552,-73,470,717 +720,-644,886,-141,625,144 +-485,-505,-347,-244,-916,66 +600,-565,995,-5,324,227 +-771,-35,904,-482,753,-303 +-701,65,426,-763,-504,-479 +409,733,-823,475,64,718 +865,975,368,893,-413,-433 +812,-597,-970,819,813,624 +193,-642,-381,-560,545,398 +711,28,-316,771,717,-865 +-509,462,809,-136,786,635 +618,-49,484,169,635,547 +-747,685,-882,-496,-332,82 +-501,-851,870,563,290,570 +-279,-829,-509,397,457,816 +-508,80,850,-188,483,-326 +860,-100,360,119,-205,787 +-870,21,-39,-827,-185,932 +826,284,-136,-866,-330,-97 +-944,-82,745,899,-97,365 +929,262,564,632,-115,632 +244,-276,713,330,-897,-214 +-890,-109,664,876,-974,-907 +716,249,816,489,723,141 +-96,-560,-272,45,-70,645 +762,-503,414,-828,-254,-646 +909,-13,903,-422,-344,-10 +658,-486,743,545,50,674 +-241,507,-367,18,-48,-241 +886,-268,884,-762,120,-486 +-412,-528,879,-647,223,-393 +851,810,234,937,-726,797 +-999,942,839,-134,-996,-189 +100,979,-527,-521,378,800 +544,-844,-832,-530,-77,-641 +43,889,31,442,-934,-503 +-330,-370,-309,-439,173,547 +169,945,62,-753,-542,-597 +208,751,-372,-647,-520,70 +765,-840,907,-257,379,918 +334,-135,-689,730,-427,618 +137,-508,66,-695,78,169 +-962,-123,400,-417,151,969 +328,689,666,427,-555,-642 +-907,343,605,-341,-647,582 +-667,-363,-571,818,-265,-399 +525,-938,904,898,725,692 +-176,-802,-858,-9,780,275 +580,170,-740,287,691,-97 +365,557,-375,361,-288,859 +193,737,842,-808,520,282 +-871,65,-799,836,179,-720 +958,-144,744,-789,797,-48 +122,582,662,912,68,757 +595,241,-801,513,388,186 +-103,-677,-259,-731,-281,-857 +921,319,-696,683,-88,-997 +775,200,78,858,648,768 +316,821,-763,68,-290,-741 +564,664,691,504,760,787 +694,-119,973,-385,309,-760 +777,-947,-57,990,74,19 +971,626,-496,-781,-602,-239 +-651,433,11,-339,939,294 +-965,-728,560,569,-708,-247 diff --git a/project_euler/problem_102/sol1.py b/project_euler/problem_102/sol1.py new file mode 100644 index 000000000000..00af726656ce --- /dev/null +++ b/project_euler/problem_102/sol1.py @@ -0,0 +1,81 @@ +""" +Three distinct points are plotted at random on a Cartesian plane, +for which -1000 ≤ x, y ≤ 1000, such that a triangle is formed. + +Consider the following two triangles: + +A(-340,495), B(-153,-910), C(835,-947) + +X(-175,41), Y(-421,-714), Z(574,-645) + +It can be verified that triangle ABC contains the origin, whereas +triangle XYZ does not. + +Using triangles.txt (right click and 'Save Link/Target As...'), a 27K text +file containing the coordinates of one thousand "random" triangles, find +the number of triangles for which the interior contains the origin. + +NOTE: The first two examples in the file represent the triangles in the +example given above. +""" + +from pathlib import Path +from typing import List, Tuple + + +def vector_product(point1: Tuple[int, int], point2: Tuple[int, int]) -> int: + """ + Return the 2-d vector product of two vectors. + >>> vector_product((1, 2), (-5, 0)) + 10 + >>> vector_product((3, 1), (6, 10)) + 24 + """ + return point1[0] * point2[1] - point1[1] * point2[0] + + +def contains_origin(x1: int, y1: int, x2: int, y2: int, x3: int, y3: int) -> bool: + """ + Check if the triangle given by the points A(x1, y1), B(x2, y2), C(x3, y3) + contains the origin. + >>> contains_origin(-340, 495, -153, -910, 835, -947) + True + >>> contains_origin(-175, 41, -421, -714, 574, -645) + False + """ + point_a: Tuple[int, int] = (x1, y1) + point_a_to_b: Tuple[int, int] = (x2 - x1, y2 - y1) + point_a_to_c: Tuple[int, int] = (x3 - x1, y3 - y1) + a: float = -vector_product(point_a, point_a_to_b) / vector_product( + point_a_to_c, point_a_to_b + ) + b: float = +vector_product(point_a, point_a_to_c) / vector_product( + point_a_to_c, point_a_to_b + ) + + return a > 0 and b > 0 and a + b < 1 + + +def solution(filename: str = "p102_triangles.txt") -> int: + """ + Find the number of triangles whose interior contains the origin. + >>> solution("test_triangles.txt") + 1 + """ + data: str = Path(__file__).parent.joinpath(filename).read_text(encoding="utf-8") + + triangles: List[List[int]] = [] + for line in data.strip().split("\n"): + triangles.append([int(number) for number in line.split(",")]) + + ret: int = 0 + triangle: List[int] + + for triangle in triangles: + ret += contains_origin(*triangle) + + return ret + + +if __name__ == "__main__": + print(f"{solution() = }") diff --git a/project_euler/problem_102/test_triangles.txt b/project_euler/problem_102/test_triangles.txt new file mode 100644 index 000000000000..5c10cd651e9b --- /dev/null +++ b/project_euler/problem_102/test_triangles.txt @@ -0,0 +1,2 @@ +-340,495,-153,-910,835,-947 +-175,41,-421,-714,574,-645 From f3ba9b6c508a24cd0e10fb08d0235c1f838fb73a Mon Sep 17 00:00:00 2001 From: Dhruv Manilawala Date: Thu, 24 Dec 2020 18:16:21 +0530 Subject: [PATCH 1065/1071] [mypy] Add/fix type annotations for backtracking algorithms (#4055) * Fix mypy errors for backtracking algorithms * Fix CI failure --- backtracking/all_subsequences.py | 28 ++++++-------- backtracking/coloring.py | 8 ++-- backtracking/hamiltonian_cycle.py | 8 ++-- backtracking/knight_tour.py | 12 +++--- backtracking/minimax.py | 26 ++++++------- backtracking/n_queens_math.py | 63 ++++++++++++++----------------- backtracking/sudoku.py | 59 ++++++++++++++++------------- 7 files changed, 98 insertions(+), 106 deletions(-) diff --git a/backtracking/all_subsequences.py b/backtracking/all_subsequences.py index 9086e3a3d659..99db4ea46589 100644 --- a/backtracking/all_subsequences.py +++ b/backtracking/all_subsequences.py @@ -1,12 +1,11 @@ -from typing import Any, List - """ - In this problem, we want to determine all possible subsequences - of the given sequence. We use backtracking to solve this problem. +In this problem, we want to determine all possible subsequences +of the given sequence. We use backtracking to solve this problem. - Time complexity: O(2^n), - where n denotes the length of the given sequence. +Time complexity: O(2^n), +where n denotes the length of the given sequence. """ +from typing import Any, List def generate_all_subsequences(sequence: List[Any]) -> None: @@ -32,15 +31,10 @@ def create_state_space_tree( current_subsequence.pop() -""" -remove the comment to take an input from the user - -print("Enter the elements") -sequence = list(map(int, input().split())) -""" - -sequence = [3, 1, 2, 4] -generate_all_subsequences(sequence) +if __name__ == "__main__": + seq: List[Any] = [3, 1, 2, 4] + generate_all_subsequences(seq) -sequence = ["A", "B", "C"] -generate_all_subsequences(sequence) + seq.clear() + seq.extend(["A", "B", "C"]) + generate_all_subsequences(seq) diff --git a/backtracking/coloring.py b/backtracking/coloring.py index ceaffe3fae76..3956b21a9182 100644 --- a/backtracking/coloring.py +++ b/backtracking/coloring.py @@ -5,11 +5,11 @@ Wikipedia: https://en.wikipedia.org/wiki/Graph_coloring """ -from __future__ import annotations +from typing import List def valid_coloring( - neighbours: list[int], colored_vertices: list[int], color: int + neighbours: List[int], colored_vertices: List[int], color: int ) -> bool: """ For each neighbour check if coloring constraint is satisfied @@ -35,7 +35,7 @@ def valid_coloring( def util_color( - graph: list[list[int]], max_colors: int, colored_vertices: list[int], index: int + graph: List[List[int]], max_colors: int, colored_vertices: List[int], index: int ) -> bool: """ Pseudo-Code @@ -86,7 +86,7 @@ def util_color( return False -def color(graph: list[list[int]], max_colors: int) -> list[int]: +def color(graph: List[List[int]], max_colors: int) -> List[int]: """ Wrapper function to call subroutine called util_color which will either return True or False. diff --git a/backtracking/hamiltonian_cycle.py b/backtracking/hamiltonian_cycle.py index bf15cce4aca4..7be1ea350d7c 100644 --- a/backtracking/hamiltonian_cycle.py +++ b/backtracking/hamiltonian_cycle.py @@ -6,11 +6,11 @@ Wikipedia: https://en.wikipedia.org/wiki/Hamiltonian_path """ -from __future__ import annotations +from typing import List def valid_connection( - graph: list[list[int]], next_ver: int, curr_ind: int, path: list[int] + graph: List[List[int]], next_ver: int, curr_ind: int, path: List[int] ) -> bool: """ Checks whether it is possible to add next into path by validating 2 statements @@ -47,7 +47,7 @@ def valid_connection( return not any(vertex == next_ver for vertex in path) -def util_hamilton_cycle(graph: list[list[int]], path: list[int], curr_ind: int) -> bool: +def util_hamilton_cycle(graph: List[List[int]], path: List[int], curr_ind: int) -> bool: """ Pseudo-Code Base Case: @@ -108,7 +108,7 @@ def util_hamilton_cycle(graph: list[list[int]], path: list[int], curr_ind: int) return False -def hamilton_cycle(graph: list[list[int]], start_index: int = 0) -> list[int]: +def hamilton_cycle(graph: List[List[int]], start_index: int = 0) -> List[int]: r""" Wrapper function to call subroutine called util_hamilton_cycle, which will either return array of vertices indicating hamiltonian cycle diff --git a/backtracking/knight_tour.py b/backtracking/knight_tour.py index 2413ba468838..8e6613e07d8b 100644 --- a/backtracking/knight_tour.py +++ b/backtracking/knight_tour.py @@ -1,9 +1,9 @@ # Knight Tour Intro: https://www.youtube.com/watch?v=ab_dY3dZFHM -from __future__ import annotations +from typing import List, Tuple -def get_valid_pos(position: tuple[int], n: int) -> list[tuple[int]]: +def get_valid_pos(position: Tuple[int, int], n: int) -> List[Tuple[int, int]]: """ Find all the valid positions a knight can move to from the current position. @@ -32,7 +32,7 @@ def get_valid_pos(position: tuple[int], n: int) -> list[tuple[int]]: return permissible_positions -def is_complete(board: list[list[int]]) -> bool: +def is_complete(board: List[List[int]]) -> bool: """ Check if the board (matrix) has been completely filled with non-zero values. @@ -46,7 +46,9 @@ def is_complete(board: list[list[int]]) -> bool: return not any(elem == 0 for row in board for elem in row) -def open_knight_tour_helper(board: list[list[int]], pos: tuple[int], curr: int) -> bool: +def open_knight_tour_helper( + board: List[List[int]], pos: Tuple[int, int], curr: int +) -> bool: """ Helper function to solve knight tour problem. """ @@ -66,7 +68,7 @@ def open_knight_tour_helper(board: list[list[int]], pos: tuple[int], curr: int) return False -def open_knight_tour(n: int) -> list[list[int]]: +def open_knight_tour(n: int) -> List[List[int]]: """ Find the solution for the knight tour problem for a board of size n. Raises ValueError if the tour cannot be performed for the given size. diff --git a/backtracking/minimax.py b/backtracking/minimax.py index 91188090c899..dda29b47d6cc 100644 --- a/backtracking/minimax.py +++ b/backtracking/minimax.py @@ -1,18 +1,18 @@ -from __future__ import annotations - -import math +""" +Minimax helps to achieve maximum score in a game by checking all possible moves +depth is current depth in game tree. -""" Minimax helps to achieve maximum score in a game by checking all possible moves - depth is current depth in game tree. - nodeIndex is index of current node in scores[]. - if move is of maximizer return true else false - leaves of game tree is stored in scores[] - height is maximum height of Game tree +nodeIndex is index of current node in scores[]. +if move is of maximizer return true else false +leaves of game tree is stored in scores[] +height is maximum height of Game tree """ +import math +from typing import List def minimax( - depth: int, node_index: int, is_max: bool, scores: list[int], height: float + depth: int, node_index: int, is_max: bool, scores: List[int], height: float ) -> int: """ >>> import math @@ -32,10 +32,6 @@ def minimax( >>> height = math.log(len(scores), 2) >>> minimax(0, 0, True, scores, height) 12 - >>> minimax('1', 2, True, [], 2 ) - Traceback (most recent call last): - ... - TypeError: '<' not supported between instances of 'str' and 'int' """ if depth < 0: @@ -59,7 +55,7 @@ def minimax( ) -def main(): +def main() -> None: scores = [90, 23, 6, 33, 21, 65, 123, 34423] height = math.log(len(scores), 2) print("Optimal value : ", end="") diff --git a/backtracking/n_queens_math.py b/backtracking/n_queens_math.py index 811611971616..a8651c5c362e 100644 --- a/backtracking/n_queens_math.py +++ b/backtracking/n_queens_math.py @@ -75,14 +75,14 @@ for another one or vice versa. """ -from __future__ import annotations +from typing import List def depth_first_search( - possible_board: list[int], - diagonal_right_collisions: list[int], - diagonal_left_collisions: list[int], - boards: list[list[str]], + possible_board: List[int], + diagonal_right_collisions: List[int], + diagonal_left_collisions: List[int], + boards: List[List[str]], n: int, ) -> None: """ @@ -94,40 +94,33 @@ def depth_first_search( ['. . Q . ', 'Q . . . ', '. . . Q ', '. Q . . '] """ - """ Get next row in the current board (possible_board) to fill it with a queen """ + # Get next row in the current board (possible_board) to fill it with a queen row = len(possible_board) - """ - If row is equal to the size of the board it means there are a queen in each row in - the current board (possible_board) - """ + # If row is equal to the size of the board it means there are a queen in each row in + # the current board (possible_board) if row == n: - """ - We convert the variable possible_board that looks like this: [1, 3, 0, 2] to - this: ['. Q . . ', '. . . Q ', 'Q . . . ', '. . Q . '] - """ - possible_board = [". " * i + "Q " + ". " * (n - 1 - i) for i in possible_board] - boards.append(possible_board) + # We convert the variable possible_board that looks like this: [1, 3, 0, 2] to + # this: ['. Q . . ', '. . . Q ', 'Q . . . ', '. . Q . '] + boards.append([". " * i + "Q " + ". " * (n - 1 - i) for i in possible_board]) return - """ We iterate each column in the row to find all possible results in each row """ + # We iterate each column in the row to find all possible results in each row for col in range(n): - """ - We apply that we learned previously. First we check that in the current board - (possible_board) there are not other same value because if there is it means - that there are a collision in vertical. Then we apply the two formulas we - learned before: - - 45º: y - x = b or 45: row - col = b - 135º: y + x = b or row + col = b. - - And we verify if the results of this two formulas not exist in their variables - respectively. (diagonal_right_collisions, diagonal_left_collisions) - - If any or these are True it means there is a collision so we continue to the - next value in the for loop. - """ + # We apply that we learned previously. First we check that in the current board + # (possible_board) there are not other same value because if there is it means + # that there are a collision in vertical. Then we apply the two formulas we + # learned before: + # + # 45º: y - x = b or 45: row - col = b + # 135º: y + x = b or row + col = b. + # + # And we verify if the results of this two formulas not exist in their variables + # respectively. (diagonal_right_collisions, diagonal_left_collisions) + # + # If any or these are True it means there is a collision so we continue to the + # next value in the for loop. if ( col in possible_board or row - col in diagonal_right_collisions @@ -135,7 +128,7 @@ def depth_first_search( ): continue - """ If it is False we call dfs function again and we update the inputs """ + # If it is False we call dfs function again and we update the inputs depth_first_search( possible_board + [col], diagonal_right_collisions + [row - col], @@ -146,10 +139,10 @@ def depth_first_search( def n_queens_solution(n: int) -> None: - boards = [] + boards: List[List[str]] = [] depth_first_search([], [], [], boards, n) - """ Print all the boards """ + # Print all the boards for board in boards: for column in board: print(column) diff --git a/backtracking/sudoku.py b/backtracking/sudoku.py index 614bdb8530ac..3bfaddd6e56f 100644 --- a/backtracking/sudoku.py +++ b/backtracking/sudoku.py @@ -1,20 +1,20 @@ -from typing import List, Tuple, Union +""" +Given a partially filled 9×9 2D array, the objective is to fill a 9×9 +square grid with digits numbered 1 to 9, so that every row, column, and +and each of the nine 3×3 sub-grids contains all of the digits. + +This can be solved using Backtracking and is similar to n-queens. +We check to see if a cell is safe or not and recursively call the +function on the next column to see if it returns True. if yes, we +have solved the puzzle. else, we backtrack and place another number +in that cell and repeat this process. +""" +from typing import List, Optional, Tuple Matrix = List[List[int]] -""" - Given a partially filled 9×9 2D array, the objective is to fill a 9×9 - square grid with digits numbered 1 to 9, so that every row, column, and - and each of the nine 3×3 sub-grids contains all of the digits. - - This can be solved using Backtracking and is similar to n-queens. - We check to see if a cell is safe or not and recursively call the - function on the next column to see if it returns True. if yes, we - have solved the puzzle. else, we backtrack and place another number - in that cell and repeat this process. -""" # assigning initial values to the grid -initial_grid = [ +initial_grid: Matrix = [ [3, 0, 6, 5, 0, 8, 4, 0, 0], [5, 2, 0, 0, 0, 0, 0, 0, 0], [0, 8, 7, 0, 0, 0, 0, 3, 1], @@ -27,7 +27,7 @@ ] # a grid with no solution -no_solution = [ +no_solution: Matrix = [ [5, 0, 6, 5, 0, 8, 4, 0, 3], [5, 2, 0, 0, 0, 0, 0, 0, 2], [1, 8, 7, 0, 0, 0, 0, 3, 1], @@ -80,7 +80,7 @@ def is_completed(grid: Matrix) -> bool: return all(all(cell != 0 for cell in row) for row in grid) -def find_empty_location(grid: Matrix) -> Tuple[int, int]: +def find_empty_location(grid: Matrix) -> Optional[Tuple[int, int]]: """ This function finds an empty location so that we can assign a number for that particular row and column. @@ -89,9 +89,10 @@ def find_empty_location(grid: Matrix) -> Tuple[int, int]: for j in range(9): if grid[i][j] == 0: return i, j + return None -def sudoku(grid: Matrix) -> Union[Matrix, bool]: +def sudoku(grid: Matrix) -> Optional[Matrix]: """ Takes a partially filled-in grid and attempts to assign values to all unassigned locations in such a way to meet the requirements @@ -107,25 +108,30 @@ def sudoku(grid: Matrix) -> Union[Matrix, bool]: [1, 3, 8, 9, 4, 7, 2, 5, 6], [6, 9, 2, 3, 5, 1, 8, 7, 4], [7, 4, 5, 2, 8, 6, 3, 1, 9]] - >>> sudoku(no_solution) - False + >>> sudoku(no_solution) is None + True """ if is_completed(grid): return grid - row, column = find_empty_location(grid) + location = find_empty_location(grid) + if location is not None: + row, column = location + else: + # If the location is ``None``, then the grid is solved. + return grid for digit in range(1, 10): if is_safe(grid, row, column, digit): grid[row][column] = digit - if sudoku(grid): + if sudoku(grid) is not None: return grid grid[row][column] = 0 - return False + return None def print_solution(grid: Matrix) -> None: @@ -141,11 +147,12 @@ def print_solution(grid: Matrix) -> None: if __name__ == "__main__": # make a copy of grid so that you can compare with the unmodified grid - for grid in (initial_grid, no_solution): - grid = list(map(list, grid)) - solution = sudoku(grid) - if solution: - print("grid after solving:") + for example_grid in (initial_grid, no_solution): + print("\nExample grid:\n" + "=" * 20) + print_solution(example_grid) + print("\nExample grid solution:") + solution = sudoku(example_grid) + if solution is not None: print_solution(solution) else: print("Cannot find a solution.") From 8f47d9f807fae641bffe97ee28ea2e213c2818d8 Mon Sep 17 00:00:00 2001 From: Dhruv Manilawala Date: Thu, 24 Dec 2020 18:16:44 +0530 Subject: [PATCH 1066/1071] Fix type annotations for bit manipulation algorithms (#4056) --- bit_manipulation/binary_and_operator.py | 2 +- bit_manipulation/binary_or_operator.py | 2 +- bit_manipulation/binary_xor_operator.py | 2 +- bit_manipulation/single_bit_manipulation_operations.py | 6 +++--- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/bit_manipulation/binary_and_operator.py b/bit_manipulation/binary_and_operator.py index f1b910f8cc9b..191ff8eb44a4 100644 --- a/bit_manipulation/binary_and_operator.py +++ b/bit_manipulation/binary_and_operator.py @@ -1,7 +1,7 @@ # https://www.tutorialspoint.com/python3/bitwise_operators_example.htm -def binary_and(a: int, b: int): +def binary_and(a: int, b: int) -> str: """ Take in 2 integers, convert them to binary, return a binary number that is the diff --git a/bit_manipulation/binary_or_operator.py b/bit_manipulation/binary_or_operator.py index e83a86d6a8bc..dabf5bcb09fd 100644 --- a/bit_manipulation/binary_or_operator.py +++ b/bit_manipulation/binary_or_operator.py @@ -1,7 +1,7 @@ # https://www.tutorialspoint.com/python3/bitwise_operators_example.htm -def binary_or(a: int, b: int): +def binary_or(a: int, b: int) -> str: """ Take in 2 integers, convert them to binary, and return a binary number that is the result of a binary or operation on the integers provided. diff --git a/bit_manipulation/binary_xor_operator.py b/bit_manipulation/binary_xor_operator.py index 0edf2ba6606d..6f8962192ad8 100644 --- a/bit_manipulation/binary_xor_operator.py +++ b/bit_manipulation/binary_xor_operator.py @@ -1,7 +1,7 @@ # https://www.tutorialspoint.com/python3/bitwise_operators_example.htm -def binary_xor(a: int, b: int): +def binary_xor(a: int, b: int) -> str: """ Take in 2 integers, convert them to binary, return a binary number that is the diff --git a/bit_manipulation/single_bit_manipulation_operations.py b/bit_manipulation/single_bit_manipulation_operations.py index 114eafe3235b..e4a54028d9ee 100644 --- a/bit_manipulation/single_bit_manipulation_operations.py +++ b/bit_manipulation/single_bit_manipulation_operations.py @@ -3,7 +3,7 @@ """Provide the functionality to manipulate a single bit.""" -def set_bit(number: int, position: int): +def set_bit(number: int, position: int) -> int: """ Set the bit at position to 1. @@ -21,7 +21,7 @@ def set_bit(number: int, position: int): return number | (1 << position) -def clear_bit(number: int, position: int): +def clear_bit(number: int, position: int) -> int: """ Set the bit at position to 0. @@ -37,7 +37,7 @@ def clear_bit(number: int, position: int): return number & ~(1 << position) -def flip_bit(number: int, position: int): +def flip_bit(number: int, position: int) -> int: """ Flip the bit at position. From 207ac957ef02a5885aeb75728ed257a0d76f9974 Mon Sep 17 00:00:00 2001 From: Mark Huang Date: Sat, 26 Dec 2020 11:12:37 +0800 Subject: [PATCH 1067/1071] [mypy] Add type hints and docstrings to heap.py (#3013) * Add type hints and docstrings to heap.py - Add type hints - Add docstrings - Add explanatory comments - Improve code readability - Change to use f-string * Fix import sorting * fixup! Format Python code with psf/black push * Fix static type error * Fix failing test * Fix type hints * Add return annotation Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> Co-authored-by: Dhruv Manilawala --- data_structures/heap/heap.py | 186 ++++++++++++++++++++--------------- 1 file changed, 107 insertions(+), 79 deletions(-) diff --git a/data_structures/heap/heap.py b/data_structures/heap/heap.py index 2dc047436a77..8592362c23b9 100644 --- a/data_structures/heap/heap.py +++ b/data_structures/heap/heap.py @@ -1,101 +1,138 @@ -#!/usr/bin/python3 +from typing import Iterable, List, Optional class Heap: - """ + """A Max Heap Implementation + >>> unsorted = [103, 9, 1, 7, 11, 15, 25, 201, 209, 107, 5] >>> h = Heap() - >>> h.build_heap(unsorted) - >>> h.display() + >>> h.build_max_heap(unsorted) + >>> print(h) [209, 201, 25, 103, 107, 15, 1, 9, 7, 11, 5] >>> - >>> h.get_max() + >>> h.extract_max() 209 - >>> h.display() + >>> print(h) [201, 107, 25, 103, 11, 15, 1, 9, 7, 5] >>> >>> h.insert(100) - >>> h.display() + >>> print(h) [201, 107, 25, 103, 100, 15, 1, 9, 7, 5, 11] >>> >>> h.heap_sort() - >>> h.display() + >>> print(h) [1, 5, 7, 9, 11, 15, 25, 100, 103, 107, 201] - >>> """ - def __init__(self): - self.h = [] - self.curr_size = 0 + def __init__(self) -> None: + self.h: List[float] = [] + self.heap_size: int = 0 + + def __repr__(self) -> str: + return str(self.h) - def get_left_child_index(self, i): - left_child_index = 2 * i + 1 - if left_child_index < self.curr_size: + def parent_index(self, child_idx: int) -> Optional[int]: + """ return the parent index of given child """ + if child_idx > 0: + return (child_idx - 1) // 2 + return None + + def left_child_idx(self, parent_idx: int) -> Optional[int]: + """ + return the left child index if the left child exists. + if not, return None. + """ + left_child_index = 2 * parent_idx + 1 + if left_child_index < self.heap_size: return left_child_index return None - def get_right_child(self, i): - right_child_index = 2 * i + 2 - if right_child_index < self.curr_size: + def right_child_idx(self, parent_idx: int) -> Optional[int]: + """ + return the right child index if the right child exists. + if not, return None. + """ + right_child_index = 2 * parent_idx + 2 + if right_child_index < self.heap_size: return right_child_index return None - def max_heapify(self, index): - if index < self.curr_size: - largest = index - lc = self.get_left_child_index(index) - rc = self.get_right_child(index) - if lc is not None and self.h[lc] > self.h[largest]: - largest = lc - if rc is not None and self.h[rc] > self.h[largest]: - largest = rc - if largest != index: - self.h[largest], self.h[index] = self.h[index], self.h[largest] - self.max_heapify(largest) - - def build_heap(self, collection): - self.curr_size = len(collection) + def max_heapify(self, index: int) -> None: + """ + correct a single violation of the heap property in a subtree's root. + """ + if index < self.heap_size: + violation: int = index + left_child = self.left_child_idx(index) + right_child = self.right_child_idx(index) + # check which child is larger than its parent + if left_child is not None and self.h[left_child] > self.h[violation]: + violation = left_child + if right_child is not None and self.h[right_child] > self.h[violation]: + violation = right_child + # if violation indeed exists + if violation != index: + # swap to fix the violation + self.h[violation], self.h[index] = self.h[index], self.h[violation] + # fix the subsequent violation recursively if any + self.max_heapify(violation) + + def build_max_heap(self, collection: Iterable[float]) -> None: + """ build max heap from an unsorted array""" self.h = list(collection) - if self.curr_size <= 1: - return - for i in range(self.curr_size // 2 - 1, -1, -1): - self.max_heapify(i) - - def get_max(self): - if self.curr_size >= 2: + self.heap_size = len(self.h) + if self.heap_size > 1: + # max_heapify from right to left but exclude leaves (last level) + for i in range(self.heap_size // 2 - 1, -1, -1): + self.max_heapify(i) + + def max(self) -> float: + """ return the max in the heap """ + if self.heap_size >= 1: + return self.h[0] + else: + raise Exception("Empty heap") + + def extract_max(self) -> float: + """ get and remove max from heap """ + if self.heap_size >= 2: me = self.h[0] self.h[0] = self.h.pop(-1) - self.curr_size -= 1 + self.heap_size -= 1 self.max_heapify(0) return me - elif self.curr_size == 1: - self.curr_size -= 1 + elif self.heap_size == 1: + self.heap_size -= 1 return self.h.pop(-1) - return None - - def heap_sort(self): - size = self.curr_size + else: + raise Exception("Empty heap") + + def insert(self, value: float) -> None: + """ insert a new value into the max heap """ + self.h.append(value) + idx = (self.heap_size - 1) // 2 + self.heap_size += 1 + while idx >= 0: + self.max_heapify(idx) + idx = (idx - 1) // 2 + + def heap_sort(self) -> None: + size = self.heap_size for j in range(size - 1, 0, -1): self.h[0], self.h[j] = self.h[j], self.h[0] - self.curr_size -= 1 + self.heap_size -= 1 self.max_heapify(0) - self.curr_size = size + self.heap_size = size - def insert(self, data): - self.h.append(data) - curr = (self.curr_size - 1) // 2 - self.curr_size += 1 - while curr >= 0: - self.max_heapify(curr) - curr = (curr - 1) // 2 - def display(self): - print(self.h) +if __name__ == "__main__": + import doctest + # run doc test + doctest.testmod() -def main(): + # demo for unsorted in [ - [], [0], [2], [3, 5], @@ -110,26 +147,17 @@ def main(): [103, 9, 1, 7, 11, 15, 25, 201, 209, 107, 5], [-45, -2, -5], ]: - print("source unsorted list: %s" % unsorted) + print(f"unsorted array: {unsorted}") - h = Heap() - h.build_heap(unsorted) - print("after build heap: ", end=" ") - h.display() + heap = Heap() + heap.build_max_heap(unsorted) + print(f"after build heap: {heap}") - print("max value: %s" % h.get_max()) - print("delete max value: ", end=" ") - h.display() + print(f"max value: {heap.extract_max()}") + print(f"after max value removed: {heap}") - h.insert(100) - print("after insert new value 100: ", end=" ") - h.display() + heap.insert(100) + print(f"after new value 100 inserted: {heap}") - h.heap_sort() - print("heap sort: ", end=" ") - h.display() - print() - - -if __name__ == "__main__": - main() + heap.heap_sort() + print(f"heap-sorted array: {heap}\n") From 64d85041700157055b02b19011886de3ff745ca0 Mon Sep 17 00:00:00 2001 From: Ramandeep Singh Date: Sat, 26 Dec 2020 21:43:20 +0530 Subject: [PATCH 1068/1071] Add 2-hidden layer neural network with back propagation using sigmoid activation function (#4037) * added neural network with 2 hidden layers * Revert "added neural network with 2 hidden layers" This reverts commit fa4e2ac86eceaae018cb18c720420665b485f3b7. * added neural network with 2 hidden layers * passing pre-commit requirements * doctest completed * added return hints * added example * example added * completed doctest's * changes made as per the review * changes made * changes after review * changes * spacing * changed return type * changed dtype --- .../2_hidden_layers_neural_network.py | 295 ++++++++++++++++++ 1 file changed, 295 insertions(+) create mode 100644 neural_network/2_hidden_layers_neural_network.py diff --git a/neural_network/2_hidden_layers_neural_network.py b/neural_network/2_hidden_layers_neural_network.py new file mode 100644 index 000000000000..baa4316200d9 --- /dev/null +++ b/neural_network/2_hidden_layers_neural_network.py @@ -0,0 +1,295 @@ +""" +References: + - http://neuralnetworksanddeeplearning.com/chap2.html (Backpropagation) + - https://en.wikipedia.org/wiki/Sigmoid_function (Sigmoid activation function) + - https://en.wikipedia.org/wiki/Feedforward_neural_network (Feedforward) +""" + +import numpy + + +class TwoHiddenLayerNeuralNetwork: + def __init__(self, input_array: numpy.ndarray, output_array: numpy.ndarray) -> None: + """ + This function initializes the TwoHiddenLayerNeuralNetwork class with random + weights for every layer and initializes predicted output with zeroes. + + input_array : input values for training the neural network (i.e training data) . + output_array : expected output values of the given inputs. + """ + + # Input values provided for training the model. + self.input_array = input_array + + # Random initial weights are assigned where first argument is the + # number of nodes in previous layer and second argument is the + # number of nodes in the next layer. + + # Random initial weights are assigned. + # self.input_array.shape[1] is used to represent number of nodes in input layer. + # First hidden layer consists of 4 nodes. + self.input_layer_and_first_hidden_layer_weights = numpy.random.rand( + self.input_array.shape[1], 4 + ) + + # Random initial values for the first hidden layer. + # First hidden layer has 4 nodes. + # Second hidden layer has 3 nodes. + self.first_hidden_layer_and_second_hidden_layer_weights = numpy.random.rand( + 4, 3 + ) + + # Random initial values for the second hidden layer. + # Second hidden layer has 3 nodes. + # Output layer has 1 node. + self.second_hidden_layer_and_output_layer_weights = numpy.random.rand(3, 1) + + # Real output values provided. + self.output_array = output_array + + # Predicted output values by the neural network. + # Predicted_output array initially consists of zeroes. + self.predicted_output = numpy.zeros(output_array.shape) + + def feedforward(self) -> numpy.ndarray: + """ + The information moves in only one direction i.e. forward from the input nodes, + through the two hidden nodes and to the output nodes. + There are no cycles or loops in the network. + + Return layer_between_second_hidden_layer_and_output + (i.e the last layer of the neural network). + + >>> input_val = numpy.array(([0, 0, 0], [0, 0, 0], [0, 0, 0]), dtype=float) + >>> output_val = numpy.array(([0], [0], [0]), dtype=float) + >>> nn = TwoHiddenLayerNeuralNetwork(input_val, output_val) + >>> res = nn.feedforward() + >>> array_sum = numpy.sum(res) + >>> numpy.isnan(array_sum) + False + """ + # Layer_between_input_and_first_hidden_layer is the layer connecting the + # input nodes with the first hidden layer nodes. + self.layer_between_input_and_first_hidden_layer = sigmoid( + numpy.dot(self.input_array, self.input_layer_and_first_hidden_layer_weights) + ) + + # layer_between_first_hidden_layer_and_second_hidden_layer is the layer + # connecting the first hidden set of nodes with the second hidden set of nodes. + self.layer_between_first_hidden_layer_and_second_hidden_layer = sigmoid( + numpy.dot( + self.layer_between_input_and_first_hidden_layer, + self.first_hidden_layer_and_second_hidden_layer_weights, + ) + ) + + # layer_between_second_hidden_layer_and_output is the layer connecting + # second hidden layer with the output node. + self.layer_between_second_hidden_layer_and_output = sigmoid( + numpy.dot( + self.layer_between_first_hidden_layer_and_second_hidden_layer, + self.second_hidden_layer_and_output_layer_weights, + ) + ) + + return self.layer_between_second_hidden_layer_and_output + + def back_propagation(self) -> None: + """ + Function for fine-tuning the weights of the neural net based on the + error rate obtained in the previous epoch (i.e., iteration). + Updation is done using derivative of sogmoid activation function. + + >>> input_val = numpy.array(([0, 0, 0], [0, 0, 0], [0, 0, 0]), dtype=float) + >>> output_val = numpy.array(([0], [0], [0]), dtype=float) + >>> nn = TwoHiddenLayerNeuralNetwork(input_val, output_val) + >>> res = nn.feedforward() + >>> nn.back_propagation() + >>> updated_weights = nn.second_hidden_layer_and_output_layer_weights + >>> (res == updated_weights).all() + False + """ + + updated_second_hidden_layer_and_output_layer_weights = numpy.dot( + self.layer_between_first_hidden_layer_and_second_hidden_layer.T, + 2 + * (self.output_array - self.predicted_output) + * sigmoid_derivative(self.predicted_output), + ) + updated_first_hidden_layer_and_second_hidden_layer_weights = numpy.dot( + self.layer_between_input_and_first_hidden_layer.T, + numpy.dot( + 2 + * (self.output_array - self.predicted_output) + * sigmoid_derivative(self.predicted_output), + self.second_hidden_layer_and_output_layer_weights.T, + ) + * sigmoid_derivative( + self.layer_between_first_hidden_layer_and_second_hidden_layer + ), + ) + updated_input_layer_and_first_hidden_layer_weights = numpy.dot( + self.input_array.T, + numpy.dot( + numpy.dot( + 2 + * (self.output_array - self.predicted_output) + * sigmoid_derivative(self.predicted_output), + self.second_hidden_layer_and_output_layer_weights.T, + ) + * sigmoid_derivative( + self.layer_between_first_hidden_layer_and_second_hidden_layer + ), + self.first_hidden_layer_and_second_hidden_layer_weights.T, + ) + * sigmoid_derivative(self.layer_between_input_and_first_hidden_layer), + ) + + self.input_layer_and_first_hidden_layer_weights += ( + updated_input_layer_and_first_hidden_layer_weights + ) + self.first_hidden_layer_and_second_hidden_layer_weights += ( + updated_first_hidden_layer_and_second_hidden_layer_weights + ) + self.second_hidden_layer_and_output_layer_weights += ( + updated_second_hidden_layer_and_output_layer_weights + ) + + def train(self, output: numpy.ndarray, iterations: int, give_loss: bool) -> None: + """ + Performs the feedforwarding and back propagation process for the + given number of iterations. + Every iteration will update the weights of neural network. + + output : real output values,required for calculating loss. + iterations : number of times the weights are to be updated. + give_loss : boolean value, If True then prints loss for each iteration, + If False then nothing is printed + + >>> input_val = numpy.array(([0, 0, 0], [0, 1, 0], [0, 0, 1]), dtype=float) + >>> output_val = numpy.array(([0], [1], [1]), dtype=float) + >>> nn = TwoHiddenLayerNeuralNetwork(input_val, output_val) + >>> first_iteration_weights = nn.feedforward() + >>> nn.back_propagation() + >>> updated_weights = nn.second_hidden_layer_and_output_layer_weights + >>> (first_iteration_weights == updated_weights).all() + False + """ + for iteration in range(1, iterations + 1): + self.output = self.feedforward() + self.back_propagation() + if give_loss: + loss = numpy.mean(numpy.square(output - self.feedforward())) + print(f"Iteration {iteration} Loss: {loss}") + + def predict(self, input: numpy.ndarray) -> int: + """ + Predict's the output for the given input values using + the trained neural network. + + The output value given by the model ranges in-between 0 and 1. + The predict function returns 1 if the model value is greater + than the threshold value else returns 0, + as the real output values are in binary. + + >>> input_val = numpy.array(([0, 0, 0], [0, 1, 0], [0, 0, 1]), dtype=float) + >>> output_val = numpy.array(([0], [1], [1]), dtype=float) + >>> nn = TwoHiddenLayerNeuralNetwork(input_val, output_val) + >>> nn.train(output_val, 1000, False) + >>> nn.predict([0,1,0]) + 1 + """ + + # Input values for which the predictions are to be made. + self.array = input + + self.layer_between_input_and_first_hidden_layer = sigmoid( + numpy.dot(self.array, self.input_layer_and_first_hidden_layer_weights) + ) + + self.layer_between_first_hidden_layer_and_second_hidden_layer = sigmoid( + numpy.dot( + self.layer_between_input_and_first_hidden_layer, + self.first_hidden_layer_and_second_hidden_layer_weights, + ) + ) + + self.layer_between_second_hidden_layer_and_output = sigmoid( + numpy.dot( + self.layer_between_first_hidden_layer_and_second_hidden_layer, + self.second_hidden_layer_and_output_layer_weights, + ) + ) + + return int(self.layer_between_second_hidden_layer_and_output > 0.6) + + +def sigmoid(value: numpy.ndarray) -> numpy.ndarray: + """ + Applies sigmoid activation function. + + return normalized values + + >>> sigmoid(numpy.array(([1, 0, 2], [1, 0, 0]), dtype=numpy.float64)) + array([[0.73105858, 0.5 , 0.88079708], + [0.73105858, 0.5 , 0.5 ]]) + """ + return 1 / (1 + numpy.exp(-value)) + + +def sigmoid_derivative(value: numpy.ndarray) -> numpy.ndarray: + """ + Provides the derivative value of the sigmoid function. + + returns derivative of the sigmoid value + + >>> sigmoid_derivative(numpy.array(([1, 0, 2], [1, 0, 0]), dtype=numpy.float64)) + array([[ 0., 0., -2.], + [ 0., 0., 0.]]) + """ + return (value) * (1 - (value)) + + +def example() -> int: + """ + Example for "how to use the neural network class and use the + respected methods for the desired output". + Calls the TwoHiddenLayerNeuralNetwork class and + provides the fixed input output values to the model. + Model is trained for a fixed amount of iterations then the predict method is called. + In this example the output is divided into 2 classes i.e. binary classification, + the two classes are represented by '0' and '1'. + + >>> example() + 1 + """ + # Input values. + input = numpy.array( + ( + [0, 0, 0], + [0, 0, 1], + [0, 1, 0], + [0, 1, 1], + [1, 0, 0], + [1, 0, 1], + [1, 1, 0], + [1, 1, 1], + ), + dtype=numpy.float64, + ) + + # True output values for the given input values. + output = numpy.array(([0], [1], [1], [0], [1], [0], [0], [1]), dtype=numpy.float64) + + # Calling neural network class. + neural_network = TwoHiddenLayerNeuralNetwork(input_array=input, output_array=output) + + # Calling training function. + # Set give_loss to True if you want to see loss in every iteration. + neural_network.train(output=output, iterations=10, give_loss=False) + + return neural_network.predict(numpy.array(([1, 1, 1]), dtype=numpy.float64)) + + +if __name__ == "__main__": + example() From 00e279ea44d34f8daf363dbdf1d5bee72a8da4c3 Mon Sep 17 00:00:00 2001 From: shan7030 <42472191+shan7030@users.noreply.github.com> Date: Mon, 28 Dec 2020 09:34:40 +0530 Subject: [PATCH 1069/1071] [mypy] Add/fix type annotations for scheduling algorithms (#4074) * Fix mypy errors for scheduling/first_come_first_served * Fix mypy errors for scheduling/round_robin.py * Fix mypy errors for scheduling/shortest_job_first.py * Fix isort errors --- scheduling/first_come_first_served.py | 12 ++++++------ scheduling/round_robin.py | 9 ++++----- scheduling/shortest_job_first.py | 16 ++++++++-------- 3 files changed, 18 insertions(+), 19 deletions(-) diff --git a/scheduling/first_come_first_served.py b/scheduling/first_come_first_served.py index c5f61720f97e..b51fc9fe0c04 100644 --- a/scheduling/first_come_first_served.py +++ b/scheduling/first_come_first_served.py @@ -2,10 +2,10 @@ # In this Algorithm we just care about the order that the processes arrived # without carring about their duration time # https://en.wikipedia.org/wiki/Scheduling_(computing)#First_come,_first_served -from __future__ import annotations +from typing import List -def calculate_waiting_times(duration_times: list[int]) -> list[int]: +def calculate_waiting_times(duration_times: List[int]) -> List[int]: """ This function calculates the waiting time of some processes that have a specified duration time. @@ -24,8 +24,8 @@ def calculate_waiting_times(duration_times: list[int]) -> list[int]: def calculate_turnaround_times( - duration_times: list[int], waiting_times: list[int] -) -> list[int]: + duration_times: List[int], waiting_times: List[int] +) -> List[int]: """ This function calculates the turnaround time of some processes. Return: The time difference between the completion time and the @@ -44,7 +44,7 @@ def calculate_turnaround_times( ] -def calculate_average_turnaround_time(turnaround_times: list[int]) -> float: +def calculate_average_turnaround_time(turnaround_times: List[int]) -> float: """ This function calculates the average of the turnaround times Return: The average of the turnaround times. @@ -58,7 +58,7 @@ def calculate_average_turnaround_time(turnaround_times: list[int]) -> float: return sum(turnaround_times) / len(turnaround_times) -def calculate_average_waiting_time(waiting_times: list[int]) -> float: +def calculate_average_waiting_time(waiting_times: List[int]) -> float: """ This function calculates the average of the waiting times Return: The average of the waiting times. diff --git a/scheduling/round_robin.py b/scheduling/round_robin.py index e8d54dd9a553..4a79301c1816 100644 --- a/scheduling/round_robin.py +++ b/scheduling/round_robin.py @@ -3,12 +3,11 @@ In Round Robin each process is assigned a fixed time slot in a cyclic way. https://en.wikipedia.org/wiki/Round-robin_scheduling """ -from __future__ import annotations - from statistics import mean +from typing import List -def calculate_waiting_times(burst_times: list[int]) -> list[int]: +def calculate_waiting_times(burst_times: List[int]) -> List[int]: """ Calculate the waiting times of a list of processes that have a specified duration. @@ -41,8 +40,8 @@ def calculate_waiting_times(burst_times: list[int]) -> list[int]: def calculate_turn_around_times( - burst_times: list[int], waiting_times: list[int] -) -> list[int]: + burst_times: List[int], waiting_times: List[int] +) -> List[int]: """ >>> calculate_turn_around_times([1, 2, 3, 4], [0, 1, 3]) [1, 3, 6] diff --git a/scheduling/shortest_job_first.py b/scheduling/shortest_job_first.py index f9e2ad975627..a49d037d6a23 100644 --- a/scheduling/shortest_job_first.py +++ b/scheduling/shortest_job_first.py @@ -3,17 +3,17 @@ Please note arrival time and burst Please use spaces to separate times entered. """ -from __future__ import annotations +from typing import List import pandas as pd def calculate_waitingtime( - arrival_time: list[int], burst_time: list[int], no_of_processes: int -) -> list[int]: + arrival_time: List[int], burst_time: List[int], no_of_processes: int +) -> List[int]: """ Calculate the waiting time of each processes - Return: list of waiting times. + Return: List of waiting times. >>> calculate_waitingtime([1,2,3,4],[3,3,5,1],4) [0, 3, 5, 0] >>> calculate_waitingtime([1,2,3],[2,5,1],3) @@ -72,8 +72,8 @@ def calculate_waitingtime( def calculate_turnaroundtime( - burst_time: list[int], no_of_processes: int, waiting_time: list[int] -) -> list[int]: + burst_time: List[int], no_of_processes: int, waiting_time: List[int] +) -> List[int]: """ Calculate the turn around time of each Processes Return: list of turn around times. @@ -91,8 +91,8 @@ def calculate_turnaroundtime( def calculate_average_times( - waiting_time: list[int], turn_around_time: list[int], no_of_processes: int -): + waiting_time: List[int], turn_around_time: List[int], no_of_processes: int +) -> None: """ This function calculates the average of the waiting & turnaround times Prints: Average Waiting time & Average Turn Around Time From 80f5213df5726c9268b6e3771ae6aaf1b6e3bc82 Mon Sep 17 00:00:00 2001 From: fpringle Date: Mon, 28 Dec 2020 08:51:02 +0100 Subject: [PATCH 1070/1071] Add solution for Project Euler problem 107 (#4066) * Added solution for Project Euler problem 107 * Doctests and better variable names * Type hints * Small edits * Forward reference for typing hint * updating DIRECTORY.md Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> --- DIRECTORY.md | 3 + project_euler/problem_107/__init__.py | 0 project_euler/problem_107/p107_network.txt | 40 +++++++ project_euler/problem_107/sol1.py | 128 +++++++++++++++++++++ project_euler/problem_107/test_network.txt | 7 ++ 5 files changed, 178 insertions(+) create mode 100644 project_euler/problem_107/__init__.py create mode 100644 project_euler/problem_107/p107_network.txt create mode 100644 project_euler/problem_107/sol1.py create mode 100644 project_euler/problem_107/test_network.txt diff --git a/DIRECTORY.md b/DIRECTORY.md index d73ae11eb7c2..4f17cf9c03ed 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -499,6 +499,7 @@ * [Minimum Cut](https://github.com/TheAlgorithms/Python/blob/master/networking_flow/minimum_cut.py) ## Neural Network + * [2 Hidden Layers Neural Network](https://github.com/TheAlgorithms/Python/blob/master/neural_network/2_hidden_layers_neural_network.py) * [Back Propagation Neural Network](https://github.com/TheAlgorithms/Python/blob/master/neural_network/back_propagation_neural_network.py) * [Convolution Neural Network](https://github.com/TheAlgorithms/Python/blob/master/neural_network/convolution_neural_network.py) * [Perceptron](https://github.com/TheAlgorithms/Python/blob/master/neural_network/perceptron.py) @@ -748,6 +749,8 @@ * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_101/sol1.py) * Problem 102 * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_102/sol1.py) + * Problem 107 + * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_107/sol1.py) * Problem 112 * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_112/sol1.py) * Problem 113 diff --git a/project_euler/problem_107/__init__.py b/project_euler/problem_107/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/project_euler/problem_107/p107_network.txt b/project_euler/problem_107/p107_network.txt new file mode 100644 index 000000000000..fcc3c4192b96 --- /dev/null +++ b/project_euler/problem_107/p107_network.txt @@ -0,0 +1,40 @@ +-,-,-,427,668,495,377,678,-,177,-,-,870,-,869,624,300,609,131,-,251,-,-,-,856,221,514,-,591,762,182,56,-,884,412,273,636,-,-,774 +-,-,262,-,-,508,472,799,-,956,578,363,940,143,-,162,122,910,-,729,802,941,922,573,531,539,667,607,-,920,-,-,315,649,937,-,185,102,636,289 +-,262,-,-,926,-,958,158,647,47,621,264,81,-,402,813,649,386,252,391,264,637,349,-,-,-,108,-,727,225,578,699,-,898,294,-,575,168,432,833 +427,-,-,-,366,-,-,635,-,32,962,468,893,854,718,427,448,916,258,-,760,909,529,311,404,-,-,588,680,875,-,615,-,409,758,221,-,-,76,257 +668,-,926,366,-,-,-,250,268,-,503,944,-,677,-,727,793,457,981,191,-,-,-,351,969,925,987,328,282,589,-,873,477,-,-,19,450,-,-,- +495,508,-,-,-,-,-,765,711,819,305,302,926,-,-,582,-,861,-,683,293,-,-,66,-,27,-,-,290,-,786,-,554,817,33,-,54,506,386,381 +377,472,958,-,-,-,-,-,-,120,42,-,134,219,457,639,538,374,-,-,-,966,-,-,-,-,-,449,120,797,358,232,550,-,305,997,662,744,686,239 +678,799,158,635,250,765,-,-,-,35,-,106,385,652,160,-,890,812,605,953,-,-,-,79,-,712,613,312,452,-,978,900,-,901,-,-,225,533,770,722 +-,-,647,-,268,711,-,-,-,283,-,172,-,663,236,36,403,286,986,-,-,810,761,574,53,793,-,-,777,330,936,883,286,-,174,-,-,-,828,711 +177,956,47,32,-,819,120,35,283,-,50,-,565,36,767,684,344,489,565,-,-,103,810,463,733,665,494,644,863,25,385,-,342,470,-,-,-,730,582,468 +-,578,621,962,503,305,42,-,-,50,-,155,519,-,-,256,990,801,154,53,474,650,402,-,-,-,966,-,-,406,989,772,932,7,-,823,391,-,-,933 +-,363,264,468,944,302,-,106,172,-,155,-,-,-,380,438,-,41,266,-,-,104,867,609,-,270,861,-,-,165,-,675,250,686,995,366,191,-,433,- +870,940,81,893,-,926,134,385,-,565,519,-,-,313,851,-,-,-,248,220,-,826,359,829,-,234,198,145,409,68,359,-,814,218,186,-,-,929,203,- +-,143,-,854,677,-,219,652,663,36,-,-,313,-,132,-,433,598,-,-,168,870,-,-,-,128,437,-,383,364,966,227,-,-,807,993,-,-,526,17 +869,-,402,718,-,-,457,160,236,767,-,380,851,132,-,-,596,903,613,730,-,261,-,142,379,885,89,-,848,258,112,-,900,-,-,818,639,268,600,- +624,162,813,427,727,582,639,-,36,684,256,438,-,-,-,-,539,379,664,561,542,-,999,585,-,-,321,398,-,-,950,68,193,-,697,-,390,588,848,- +300,122,649,448,793,-,538,890,403,344,990,-,-,433,596,539,-,-,73,-,318,-,-,500,-,968,-,291,-,-,765,196,504,757,-,542,-,395,227,148 +609,910,386,916,457,861,374,812,286,489,801,41,-,598,903,379,-,-,-,946,136,399,-,941,707,156,757,258,251,-,807,-,-,-,461,501,-,-,616,- +131,-,252,258,981,-,-,605,986,565,154,266,248,-,613,664,73,-,-,686,-,-,575,627,817,282,-,698,398,222,-,649,-,-,-,-,-,654,-,- +-,729,391,-,191,683,-,953,-,-,53,-,220,-,730,561,-,946,686,-,-,389,729,553,304,703,455,857,260,-,991,182,351,477,867,-,-,889,217,853 +251,802,264,760,-,293,-,-,-,-,474,-,-,168,-,542,318,136,-,-,-,-,392,-,-,-,267,407,27,651,80,927,-,974,977,-,-,457,117,- +-,941,637,909,-,-,966,-,810,103,650,104,826,870,261,-,-,399,-,389,-,-,-,202,-,-,-,-,867,140,403,962,785,-,511,-,1,-,707,- +-,922,349,529,-,-,-,-,761,810,402,867,359,-,-,999,-,-,575,729,392,-,-,388,939,-,959,-,83,463,361,-,-,512,931,-,224,690,369,- +-,573,-,311,351,66,-,79,574,463,-,609,829,-,142,585,500,941,627,553,-,202,388,-,164,829,-,620,523,639,936,-,-,490,-,695,-,505,109,- +856,531,-,404,969,-,-,-,53,733,-,-,-,-,379,-,-,707,817,304,-,-,939,164,-,-,616,716,728,-,889,349,-,963,150,447,-,292,586,264 +221,539,-,-,925,27,-,712,793,665,-,270,234,128,885,-,968,156,282,703,-,-,-,829,-,-,-,822,-,-,-,736,576,-,697,946,443,-,205,194 +514,667,108,-,987,-,-,613,-,494,966,861,198,437,89,321,-,757,-,455,267,-,959,-,616,-,-,-,349,156,339,-,102,790,359,-,439,938,809,260 +-,607,-,588,328,-,449,312,-,644,-,-,145,-,-,398,291,258,698,857,407,-,-,620,716,822,-,-,293,486,943,-,779,-,6,880,116,775,-,947 +591,-,727,680,282,290,120,452,777,863,-,-,409,383,848,-,-,251,398,260,27,867,83,523,728,-,349,293,-,212,684,505,341,384,9,992,507,48,-,- +762,920,225,875,589,-,797,-,330,25,406,165,68,364,258,-,-,-,222,-,651,140,463,639,-,-,156,486,212,-,-,349,723,-,-,186,-,36,240,752 +182,-,578,-,-,786,358,978,936,385,989,-,359,966,112,950,765,807,-,991,80,403,361,936,889,-,339,943,684,-,-,965,302,676,725,-,327,134,-,147 +56,-,699,615,873,-,232,900,883,-,772,675,-,227,-,68,196,-,649,182,927,962,-,-,349,736,-,-,505,349,965,-,474,178,833,-,-,555,853,- +-,315,-,-,477,554,550,-,286,342,932,250,814,-,900,193,504,-,-,351,-,785,-,-,-,576,102,779,341,723,302,474,-,689,-,-,-,451,-,- +884,649,898,409,-,817,-,901,-,470,7,686,218,-,-,-,757,-,-,477,974,-,512,490,963,-,790,-,384,-,676,178,689,-,245,596,445,-,-,343 +412,937,294,758,-,33,305,-,174,-,-,995,186,807,-,697,-,461,-,867,977,511,931,-,150,697,359,6,9,-,725,833,-,245,-,949,-,270,-,112 +273,-,-,221,19,-,997,-,-,-,823,366,-,993,818,-,542,501,-,-,-,-,-,695,447,946,-,880,992,186,-,-,-,596,949,-,91,-,768,273 +636,185,575,-,450,54,662,225,-,-,391,191,-,-,639,390,-,-,-,-,-,1,224,-,-,443,439,116,507,-,327,-,-,445,-,91,-,248,-,344 +-,102,168,-,-,506,744,533,-,730,-,-,929,-,268,588,395,-,654,889,457,-,690,505,292,-,938,775,48,36,134,555,451,-,270,-,248,-,371,680 +-,636,432,76,-,386,686,770,828,582,-,433,203,526,600,848,227,616,-,217,117,707,369,109,586,205,809,-,-,240,-,853,-,-,-,768,-,371,-,540 +774,289,833,257,-,381,239,722,711,468,933,-,-,17,-,-,148,-,-,853,-,-,-,-,264,194,260,947,-,752,147,-,-,343,112,273,344,680,540,- diff --git a/project_euler/problem_107/sol1.py b/project_euler/problem_107/sol1.py new file mode 100644 index 000000000000..80a10e499f76 --- /dev/null +++ b/project_euler/problem_107/sol1.py @@ -0,0 +1,128 @@ +""" +The following undirected network consists of seven vertices and twelve edges +with a total weight of 243. + +The same network can be represented by the matrix below. + + A B C D E F G +A - 16 12 21 - - - +B 16 - - 17 20 - - +C 12 - - 28 - 31 - +D 21 17 28 - 18 19 23 +E - 20 - 18 - - 11 +F - - 31 19 - - 27 +G - - - 23 11 27 - + +However, it is possible to optimise the network by removing some edges and still +ensure that all points on the network remain connected. The network which achieves +the maximum saving is shown below. It has a weight of 93, representing a saving of +243 - 93 = 150 from the original network. + +Using network.txt (right click and 'Save Link/Target As...'), a 6K text file +containing a network with forty vertices, and given in matrix form, find the maximum +saving which can be achieved by removing redundant edges whilst ensuring that the +network remains connected. + +Solution: + We use Prim's algorithm to find a Minimum Spanning Tree. + Reference: https://en.wikipedia.org/wiki/Prim%27s_algorithm +""" + +import os +from typing import Dict, List, Mapping, Set, Tuple + +EdgeT = Tuple[int, int] + + +class Graph: + """ + A class representing an undirected weighted graph. + """ + + def __init__(self, vertices: Set[int], edges: Mapping[EdgeT, int]) -> None: + self.vertices: Set[int] = vertices + self.edges: Dict[EdgeT, int] = { + (min(edge), max(edge)): weight for edge, weight in edges.items() + } + + def add_edge(self, edge: EdgeT, weight: int) -> None: + """ + Add a new edge to the graph. + >>> graph = Graph({1, 2}, {(2, 1): 4}) + >>> graph.add_edge((3, 1), 5) + >>> sorted(graph.vertices) + [1, 2, 3] + >>> sorted([(v,k) for k,v in graph.edges.items()]) + [(4, (1, 2)), (5, (1, 3))] + """ + self.vertices.add(edge[0]) + self.vertices.add(edge[1]) + self.edges[(min(edge), max(edge))] = weight + + def prims_algorithm(self) -> "Graph": + """ + Run Prim's algorithm to find the minimum spanning tree. + Reference: https://en.wikipedia.org/wiki/Prim%27s_algorithm + >>> graph = Graph({1,2,3,4},{(1,2):5, (1,3):10, (1,4):20, (2,4):30, (3,4):1}) + >>> mst = graph.prims_algorithm() + >>> sorted(mst.vertices) + [1, 2, 3, 4] + >>> sorted(mst.edges) + [(1, 2), (1, 3), (3, 4)] + """ + subgraph: Graph = Graph({min(self.vertices)}, {}) + min_edge: EdgeT + min_weight: int + edge: EdgeT + weight: int + + while len(subgraph.vertices) < len(self.vertices): + min_weight = max(self.edges.values()) + 1 + for edge, weight in self.edges.items(): + if (edge[0] in subgraph.vertices) ^ (edge[1] in subgraph.vertices): + if weight < min_weight: + min_edge = edge + min_weight = weight + + subgraph.add_edge(min_edge, min_weight) + + return subgraph + + +def solution(filename: str = "p107_network.txt") -> int: + """ + Find the maximum saving which can be achieved by removing redundant edges + whilst ensuring that the network remains connected. + >>> solution("test_network.txt") + 150 + """ + script_dir: str = os.path.abspath(os.path.dirname(__file__)) + network_file: str = os.path.join(script_dir, filename) + adjacency_matrix: List[List[str]] + edges: Dict[EdgeT, int] = dict() + data: List[str] + edge1: int + edge2: int + + with open(network_file, "r") as f: + data = f.read().strip().split("\n") + + adjaceny_matrix = [line.split(",") for line in data] + + for edge1 in range(1, len(adjaceny_matrix)): + for edge2 in range(edge1): + if adjaceny_matrix[edge1][edge2] != "-": + edges[(edge2, edge1)] = int(adjaceny_matrix[edge1][edge2]) + + graph: Graph = Graph(set(range(len(adjaceny_matrix))), edges) + + subgraph: Graph = graph.prims_algorithm() + + initial_total: int = sum(graph.edges.values()) + optimal_total: int = sum(subgraph.edges.values()) + + return initial_total - optimal_total + + +if __name__ == "__main__": + print(f"{solution() = }") diff --git a/project_euler/problem_107/test_network.txt b/project_euler/problem_107/test_network.txt new file mode 100644 index 000000000000..f5f2accb5720 --- /dev/null +++ b/project_euler/problem_107/test_network.txt @@ -0,0 +1,7 @@ +-,16,12,21,-,-,- +16,-,-,17,20,-,- +12,-,-,28,-,31,- +21,17,28,-,18,19,23 +-,20,-,18,-,-,11 +-,-,31,19,-,-,27 +-,-,-,23,11,27,- From dd4b2656806e64d1c28301ded3b5c4d863de76db Mon Sep 17 00:00:00 2001 From: SiddhantJain15 Date: Mon, 28 Dec 2020 13:36:57 +0530 Subject: [PATCH 1071/1071] Add function to calculate area of triangle using Heron's formula (#4065) * Update area.py Modified area of triangle function. Added a new algorithm to calculate area when 3 sides are known * Add files via upload * Update area.py * Update area.py * Update area.py * Update area.py * Remove unnecessary whitespace Co-authored-by: Dhruv Manilawala --- maths/area.py | 81 ++++++++++++++++++++++++++++++++++----------------- 1 file changed, 55 insertions(+), 26 deletions(-) diff --git a/maths/area.py b/maths/area.py index 24216e223ebf..8689f323cc9a 100644 --- a/maths/area.py +++ b/maths/area.py @@ -1,7 +1,7 @@ """ Find the area of various geometric shapes """ -from math import pi +from math import pi, sqrt def surface_area_cube(side_length: float) -> float: @@ -26,7 +26,7 @@ def surface_area_sphere(radius: float) -> float: """ Calculate the Surface Area of a Sphere. Wikipedia reference: https://en.wikipedia.org/wiki/Sphere - :return 4 * pi * r^2 + Formula: 4 * pi * r^2 >>> surface_area_sphere(5) 314.1592653589793 @@ -44,7 +44,7 @@ def surface_area_sphere(radius: float) -> float: def area_rectangle(length: float, width: float) -> float: """ - Calculate the area of a rectangle + Calculate the area of a rectangle. >>> area_rectangle(10, 20) 200 @@ -68,7 +68,7 @@ def area_rectangle(length: float, width: float) -> float: def area_square(side_length: float) -> float: """ - Calculate the area of a square + Calculate the area of a square. >>> area_square(10) 100 @@ -84,7 +84,7 @@ def area_square(side_length: float) -> float: def area_triangle(base: float, height: float) -> float: """ - Calculate the area of a triangle + Calculate the area of a triangle given the base and height. >>> area_triangle(10, 10) 50.0 @@ -106,9 +106,42 @@ def area_triangle(base: float, height: float) -> float: return (base * height) / 2 +def area_triangle_three_sides(side1: float, side2: float, side3: float) -> float: + """ + Calculate area of triangle when the length of 3 sides are known. + + This function uses Heron's formula: https://en.wikipedia.org/wiki/Heron%27s_formula + + >>> area_triangle_three_sides(5, 12, 13) + 30.0 + >>> area_triangle_three_sides(10, 11, 12) + 51.521233486786784 + >>> area_triangle_three_sides(-1, -2, -1) + Traceback (most recent call last): + ... + ValueError: area_triangle_three_sides() only accepts non-negative values + >>> area_triangle_three_sides(1, -2, 1) + Traceback (most recent call last): + ... + ValueError: area_triangle_three_sides() only accepts non-negative values + """ + if side1 < 0 or side2 < 0 or side3 < 0: + raise ValueError("area_triangle_three_sides() only accepts non-negative values") + elif side1 + side2 < side3 or side1 + side3 < side2 or side2 + side3 < side1: + raise ValueError("Given three sides do not form a triangle") + semi_perimeter = (side1 + side2 + side3) / 2 + area = sqrt( + semi_perimeter + * (semi_perimeter - side1) + * (semi_perimeter - side2) + * (semi_perimeter - side3) + ) + return area + + def area_parallelogram(base: float, height: float) -> float: """ - Calculate the area of a parallelogram + Calculate the area of a parallelogram. >>> area_parallelogram(10, 20) 200 @@ -132,7 +165,7 @@ def area_parallelogram(base: float, height: float) -> float: def area_trapezium(base1: float, base2: float, height: float) -> float: """ - Calculate the area of a trapezium + Calculate the area of a trapezium. >>> area_trapezium(10, 20, 30) 450.0 @@ -172,7 +205,7 @@ def area_trapezium(base1: float, base2: float, height: float) -> float: def area_circle(radius: float) -> float: """ - Calculate the area of a circle + Calculate the area of a circle. >>> area_circle(20) 1256.6370614359173 @@ -188,7 +221,7 @@ def area_circle(radius: float) -> float: def area_ellipse(radius_x: float, radius_y: float) -> float: """ - Calculate the area of a ellipse + Calculate the area of a ellipse. >>> area_ellipse(10, 10) 314.1592653589793 @@ -214,7 +247,7 @@ def area_ellipse(radius_x: float, radius_y: float) -> float: def area_rhombus(diagonal_1: float, diagonal_2: float) -> float: """ - Calculate the area of a rhombus + Calculate the area of a rhombus. >>> area_rhombus(10, 20) 100.0 @@ -236,24 +269,20 @@ def area_rhombus(diagonal_1: float, diagonal_2: float) -> float: return 1 / 2 * diagonal_1 * diagonal_2 -def main(): - print("Areas of various geometric shapes: \n") - print(f"Rectangle: {area_rectangle(10, 20)}") - print(f"Square: {area_square(10)}") - print(f"Triangle: {area_triangle(10, 10)}") - print(f"Parallelogram: {area_parallelogram(10, 20)}") - print(f"Trapezium: {area_trapezium(10, 20, 30)}") - print(f"Circle: {area_circle(20)}") - print("\nSurface Areas of various geometric shapes: \n") - print(f"Cube: {surface_area_cube(20)}") - print(f"Sphere: {surface_area_sphere(20)}") - print(f"Rhombus: {area_rhombus(10, 20)}") - - if __name__ == "__main__": - import doctest doctest.testmod(verbose=True) # verbose so we can see methods missing tests - main() + print("[DEMO] Areas of various geometric shapes: \n") + print(f"Rectangle: {area_rectangle(10, 20) = }") + print(f"Square: {area_square(10) = }") + print(f"Triangle: {area_triangle(10, 10) = }") + print(f"Triangle: {area_triangle_three_sides(5, 12, 13) = }") + print(f"Parallelogram: {area_parallelogram(10, 20) = }") + print(f"Trapezium: {area_trapezium(10, 20, 30) = }") + print(f"Circle: {area_circle(20) = }") + print("\nSurface Areas of various geometric shapes: \n") + print(f"Cube: {surface_area_cube(20) = }") + print(f"Sphere: {surface_area_sphere(20) = }") + print(f"Rhombus: {area_rhombus(10, 20) = }")